|
|
@ -1,6 +1,6 @@ |
|
|
|
/* |
|
|
|
* fmail - Mail formatter |
|
|
|
* Copyright (C) 2011,2018 Damien Goutte-Gattat |
|
|
|
* Copyright (C) 2011,2018,2019 Damien Goutte-Gattat |
|
|
|
* |
|
|
|
* This program is free software: you can redistribute it and/or modify |
|
|
|
* it under the terms of the GNU General Public License as published by |
|
|
@ -80,6 +80,7 @@ for submission to a mail submission agent.\n"); |
|
|
|
puts("\ |
|
|
|
Cryptography options:\n\ |
|
|
|
-s, --sign Sign the message.\n\ |
|
|
|
-E, --encrypt RECP Encrypt for the specified recipient.\n\ |
|
|
|
"); |
|
|
|
|
|
|
|
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT); |
|
|
@ -92,7 +93,7 @@ info(void) |
|
|
|
{ |
|
|
|
printf("\ |
|
|
|
fmail %s\n\ |
|
|
|
Copyright (C) 2018 Damien Goutte-Gattat\n\ |
|
|
|
Copyright (C) 2019 Damien Goutte-Gattat\n\ |
|
|
|
\n\ |
|
|
|
This program is released under the GNU General Public License.\n\ |
|
|
|
See the COPYING file or <http://www.gnu.org/licenses/gpl.html>.\n\ |
|
|
@ -113,9 +114,11 @@ typedef struct fmail_ctx |
|
|
|
{ |
|
|
|
const char **attachments; |
|
|
|
size_t att_count; |
|
|
|
const char **recipients; |
|
|
|
size_t rcp_count; |
|
|
|
const char *footer; |
|
|
|
magic_t magic_ctx; |
|
|
|
gpgme_ctx_t signing_ctx; |
|
|
|
gpgme_ctx_t crypto_ctx; |
|
|
|
FILE *input; |
|
|
|
} fmail_ctx_t; |
|
|
|
|
|
|
@ -299,7 +302,7 @@ process_text_body(fmail_ctx_t *ctx, FILE *out) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Signing stuff. */ |
|
|
|
/* Crypto stuff. */ |
|
|
|
|
|
|
|
gpgme_ctx_t |
|
|
|
initialize_gpgme(void) |
|
|
@ -329,7 +332,7 @@ hash_algo_to_string(gpgme_hash_algo_t algo) |
|
|
|
case GPGME_MD_RMD160: return "pgp-ripemd160"; |
|
|
|
case GPGME_MD_MD2: return "pgp-md2"; |
|
|
|
case GPGME_MD_TIGER: return "pgp-tiger192"; |
|
|
|
case GPGME_MD_HAVAL: return "php-haval-5-160"; |
|
|
|
case GPGME_MD_HAVAL: return "pgp-haval-5-160"; |
|
|
|
case GPGME_MD_SHA256: return "pgp-sha256"; |
|
|
|
case GPGME_MD_SHA384: return "pgp-sha384"; |
|
|
|
case GPGME_MD_SHA512: return "pgp-sha512"; |
|
|
@ -401,6 +404,107 @@ sign_stream(gpgme_ctx_t ctx, FILE *in, FILE *out) |
|
|
|
gpgme_data_release(gout); |
|
|
|
} |
|
|
|
|
|
|
|
static gpgme_key_t * |
|
|
|
get_recipient_keys(gpgme_ctx_t ctx, const char **recipients, size_t nr) |
|
|
|
{ |
|
|
|
gpgme_key_t *keys, key; |
|
|
|
gpgme_error_t gerr; |
|
|
|
unsigned i, j, k; |
|
|
|
|
|
|
|
keys = NULL; |
|
|
|
|
|
|
|
for ( i = j = gerr = 0; i < nr && ! gerr ; i++ ) { |
|
|
|
k = j; |
|
|
|
gerr = gpgme_op_keylist_start (ctx, recipients[i], 0); |
|
|
|
while ( ! gerr ) { |
|
|
|
gerr = gpgme_op_keylist_next (ctx, &key); |
|
|
|
if ( gerr ) |
|
|
|
break; |
|
|
|
|
|
|
|
if ( j % 10 == 0 ) |
|
|
|
keys = xrealloc(keys, j + 10); |
|
|
|
keys[j++] = key; |
|
|
|
} |
|
|
|
if ( gpgme_err_code(gerr) == GPG_ERR_EOF ) |
|
|
|
gerr = 0; |
|
|
|
|
|
|
|
if ( k == j ) |
|
|
|
errx(EXIT_FAILURE, "no key found for recipient %s", recipients[i]); |
|
|
|
} |
|
|
|
if ( gerr != 0 && gpgme_err_code(gerr) != GPG_ERR_EOF) |
|
|
|
errx(EXIT_FAILURE, "cannot get recipient keys: %s", gpgme_strerror(gerr)); |
|
|
|
|
|
|
|
/* GpgME expects a NULL-terminated array of keys. */ |
|
|
|
if ( j % 10 == 0 ) |
|
|
|
keys = xrealloc(keys, j + 1); |
|
|
|
keys[j++] = NULL; |
|
|
|
|
|
|
|
return keys; |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
encrypt_stream(gpgme_ctx_t ctx, |
|
|
|
FILE *in, |
|
|
|
FILE *out, |
|
|
|
const char **recipients, |
|
|
|
size_t nr, |
|
|
|
int sign) |
|
|
|
{ |
|
|
|
gpgme_data_t gin, gout; |
|
|
|
gpgme_key_t *keys, *key; |
|
|
|
gpgme_error_t gerr; |
|
|
|
char boundary[32], buffer[512]; |
|
|
|
int n; |
|
|
|
|
|
|
|
keys = get_recipient_keys(ctx, recipients, nr); |
|
|
|
gpgme_data_new_from_stream(&gin, in); |
|
|
|
gpgme_data_new(&gout); |
|
|
|
|
|
|
|
if ( sign ) |
|
|
|
gerr = gpgme_op_encrypt_sign(ctx, keys, 0, gin, gout); |
|
|
|
else |
|
|
|
gerr = gpgme_op_encrypt(ctx, keys, 0, gin, gout); |
|
|
|
if ( gerr != GPG_ERR_NO_ERROR ) |
|
|
|
errx(EXIT_FAILURE, "encrypting failed: %s", gpgme_strerror(gerr)); |
|
|
|
|
|
|
|
generate_boundary(boundary, sizeof(boundary)); |
|
|
|
fprintf(out, "Content-Type: multipart/encrypted;\r\n" |
|
|
|
" boundary=\"%s\";\r\n" |
|
|
|
" protocol=\"application/pgp-encrypted\r\n" |
|
|
|
"\r\n" |
|
|
|
"--%s\r\n" |
|
|
|
"Content-Type: application/pgp-encrypted\r\n" |
|
|
|
"\r\n" |
|
|
|
"Version: 1\r\n" |
|
|
|
"\r\n" |
|
|
|
"--%s\r\n" |
|
|
|
"Content-Type: application/octet-stream\r\n" |
|
|
|
"\r\n", |
|
|
|
boundary, boundary, boundary); |
|
|
|
|
|
|
|
gpgme_data_seek(gout, 0, SEEK_SET); |
|
|
|
while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) { |
|
|
|
int i = 0; |
|
|
|
|
|
|
|
while ( i < n ) { |
|
|
|
if ( buffer[i] == '\n' ) |
|
|
|
fputc('\r', out); |
|
|
|
fputc(buffer[i], out); |
|
|
|
|
|
|
|
i += 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fprintf(out, "\r\n--%s--\r\n", boundary); |
|
|
|
|
|
|
|
gpgme_data_release(gin); |
|
|
|
gpgme_data_release(gout); |
|
|
|
|
|
|
|
for ( key = keys; *key != NULL; key++ ) |
|
|
|
gpgme_key_release(*key); |
|
|
|
free(keys); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Attachments stuff. */ |
|
|
@ -419,6 +523,13 @@ initialize_magic(void) |
|
|
|
return ctx; |
|
|
|
} |
|
|
|
|
|
|
|
static const char * |
|
|
|
get_basename(const char *filename) |
|
|
|
{ |
|
|
|
char *last_slash = strrchr(filename, '/'); |
|
|
|
return last_slash ? last_slash + 1 : filename; |
|
|
|
} |
|
|
|
|
|
|
|
static void |
|
|
|
process_attachment(const char *filename, magic_t ctx, FILE *out) |
|
|
|
{ |
|
|
@ -439,7 +550,7 @@ process_attachment(const char *filename, magic_t ctx, FILE *out) |
|
|
|
|
|
|
|
fprintf(out, "Content-Type: %s\r\n", mime); |
|
|
|
fprintf(out, "Content-Transfer-Encoding: %s\r\n", binary ? "base64" : "quoted-printable"); |
|
|
|
fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", filename); |
|
|
|
fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", get_basename(filename)); |
|
|
|
|
|
|
|
f = fopen(filename, "r"); |
|
|
|
if ( binary ) |
|
|
@ -525,13 +636,15 @@ int |
|
|
|
main(int argc, char *argv[]) |
|
|
|
{ |
|
|
|
char c, with_useragent, with_date; |
|
|
|
string_buffer_t *headers; |
|
|
|
string_buffer_t headers; |
|
|
|
fmail_ctx_t ctx; |
|
|
|
int do_sign; |
|
|
|
|
|
|
|
struct option options[] = { |
|
|
|
{ "help", 0, NULL, 'h' }, |
|
|
|
{ "version", 0, NULL, 'v' }, |
|
|
|
{ "sign", 0, NULL, 's' }, |
|
|
|
{ "encrypt", 1, NULL, 'E' }, |
|
|
|
{ "attach", 1, NULL, 'a' }, |
|
|
|
{ "edit", 0, NULL, 'e' }, |
|
|
|
{ "footer", 1, NULL, 'f' }, |
|
|
@ -549,17 +662,19 @@ main(int argc, char *argv[]) |
|
|
|
setlocale(LC_ALL, ""); |
|
|
|
srand(time(NULL)); |
|
|
|
|
|
|
|
with_useragent = with_date = 0; |
|
|
|
with_useragent = with_date = do_sign = 0; |
|
|
|
|
|
|
|
headers = sb_new(0); |
|
|
|
ctx.signing_ctx = NULL; |
|
|
|
sb_init(&headers, 0); |
|
|
|
ctx.crypto_ctx = NULL; |
|
|
|
ctx.magic_ctx = initialize_magic(); |
|
|
|
ctx.attachments = NULL; |
|
|
|
ctx.att_count = 0; |
|
|
|
ctx.recipients = NULL; |
|
|
|
ctx.rcp_count = 0; |
|
|
|
ctx.footer = NULL; |
|
|
|
ctx.input = stdin; |
|
|
|
|
|
|
|
while ( (c = getopt_long(argc, argv, "hvsa:ef:H:F:T:C:S:UD", |
|
|
|
while ( (c = getopt_long(argc, argv, "hvsE:a:ef:H:F:T:C:S:UD", |
|
|
|
options, NULL)) != -1 ) { |
|
|
|
switch ( c ) { |
|
|
|
case 'h': |
|
|
@ -575,8 +690,13 @@ main(int argc, char *argv[]) |
|
|
|
break; |
|
|
|
|
|
|
|
case 's': |
|
|
|
if ( ! ctx.signing_ctx ) |
|
|
|
ctx.signing_ctx = initialize_gpgme(); |
|
|
|
do_sign = 1; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'E': |
|
|
|
if ( ctx.rcp_count % 10 == 0 ) |
|
|
|
ctx.recipients = xrealloc(ctx.recipients, ctx.rcp_count + 10); |
|
|
|
ctx.recipients[ctx.rcp_count++] = optarg; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'a': |
|
|
@ -594,23 +714,23 @@ main(int argc, char *argv[]) |
|
|
|
break; |
|
|
|
|
|
|
|
case 'H': |
|
|
|
sb_addf(headers, "%s\r\n", optarg); |
|
|
|
sb_addf(&headers, "%s\r\n", optarg); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'F': |
|
|
|
sb_addf(headers, "From: %s\r\n", optarg); |
|
|
|
sb_addf(&headers, "From: %s\r\n", optarg); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'T': |
|
|
|
sb_addf(headers, "To: %s\r\n", optarg); |
|
|
|
sb_addf(&headers, "To: %s\r\n", optarg); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'C': |
|
|
|
sb_addf(headers, "Cc: %s\r\n", optarg); |
|
|
|
sb_addf(&headers, "Cc: %s\r\n", optarg); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'S': |
|
|
|
sb_addf(headers, "Subject: %s\r\n", optarg); |
|
|
|
sb_addf(&headers, "Subject: %s\r\n", optarg); |
|
|
|
break; |
|
|
|
|
|
|
|
case 'U': |
|
|
@ -623,31 +743,38 @@ main(int argc, char *argv[]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if ( do_sign || ctx.rcp_count ) |
|
|
|
ctx.crypto_ctx = initialize_gpgme(); |
|
|
|
|
|
|
|
/* Generate automatic headers */ |
|
|
|
if ( with_date ) |
|
|
|
sb_addf(headers, "Date: %s\r\n", rfc2822_date()); |
|
|
|
sb_addf(&headers, "Date: %s\r\n", rfc2822_date()); |
|
|
|
if ( with_useragent ) |
|
|
|
sb_addf(headers, "User-Agent: fmail %s\r\n", VERSION); |
|
|
|
sb_addf(&headers, "User-Agent: fmail %s\r\n", VERSION); |
|
|
|
|
|
|
|
/* Fire editor instead of reading from stdin? */ |
|
|
|
if ( ! ctx.input ) |
|
|
|
ctx.input = read_input_from_editor(); |
|
|
|
|
|
|
|
/* Read user-provided headers. */ |
|
|
|
read_headers(ctx.input, headers); |
|
|
|
read_headers(ctx.input, &headers); |
|
|
|
|
|
|
|
/* Write all headers. */ |
|
|
|
fprintf(stdout, "%s", sb_get(headers)); |
|
|
|
fprintf(stdout, "%s", headers.buffer); |
|
|
|
free(headers.buffer); |
|
|
|
|
|
|
|
if ( ctx.signing_ctx ) { |
|
|
|
if ( do_sign || ctx.rcp_count ) { |
|
|
|
FILE *tmp = tmpfile(); |
|
|
|
|
|
|
|
process_text_body(&ctx, tmp); |
|
|
|
fseek(tmp, 0, SEEK_SET); |
|
|
|
sign_stream(ctx.signing_ctx, tmp, stdout); |
|
|
|
if ( ctx.rcp_count == 0 ) |
|
|
|
sign_stream(ctx.crypto_ctx, tmp, stdout); |
|
|
|
else |
|
|
|
encrypt_stream(ctx.crypto_ctx, tmp, stdout, ctx.recipients, ctx.rcp_count, do_sign); |
|
|
|
|
|
|
|
fclose(tmp); |
|
|
|
gpgme_release(ctx.signing_ctx); |
|
|
|
gpgme_release(ctx.crypto_ctx); |
|
|
|
} |
|
|
|
else |
|
|
|
process_text_body(&ctx, stdout); |
|
|
@ -657,6 +784,9 @@ main(int argc, char *argv[]) |
|
|
|
if ( ctx.attachments ) |
|
|
|
free(ctx.attachments); |
|
|
|
|
|
|
|
if ( ctx.recipients ) |
|
|
|
free(ctx.recipients); |
|
|
|
|
|
|
|
magic_close(ctx.magic_ctx); |
|
|
|
|
|
|
|
return EXIT_SUCCESS; |
|
|
|