A mail formatter.
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

852 linhas
20 KiB

  1. /*
  2. * fmail - Mail formatter
  3. * Copyright (C) 2011,2018,2019 Damien Goutte-Gattat
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #ifdef HAVE_CONFIG_H
  19. #include <config.h>
  20. #endif
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <getopt.h>
  24. #include <locale.h>
  25. #include <string.h>
  26. #include <ctype.h>
  27. #include <time.h>
  28. #include <unistd.h>
  29. #include <sys/stat.h>
  30. #include <err.h>
  31. #include <gpgme.h>
  32. #include <magic.h>
  33. #include <sbuffer.h>
  34. #include <xmem.h>
  35. /* Help and informations about the program. */
  36. static void
  37. usage(int status)
  38. {
  39. puts("\
  40. Usage: fmail [options]\n\
  41. Read a message from standard input and format it\n\
  42. for submission to a mail submission agent.\n");
  43. puts("Options:\n\
  44. -h, --help Display this help message.\n\
  45. -v, --version Display the version message.\n\
  46. ");
  47. puts("\
  48. -e, --edit Fire an editor to type mail body\n\
  49. instead of reading it from\n\
  50. standard input.\n\
  51. -f, --footer FILE Include FILE as the mail footer.\n\
  52. ");
  53. puts("\
  54. Headers options:\n\
  55. -H, --header \"NAME: TEXT\"\n\
  56. Add an arbitrary header.\n\
  57. -F, --from TEXT Set the From: header.\n\
  58. -T, --to TEXT Add a To: header.\n\
  59. -C, --cc TEXT Add a Cc: header.\n\
  60. -S, --subject TEXT Set the Subject: header.\n\
  61. -U, --user-agent Add a User-Agent: header.\n\
  62. -D, --date Add a Date: header with the current date.\n\
  63. ");
  64. puts("\
  65. Attachments options:\n\
  66. -a, --attach FILE Attach the specified file.\n\
  67. ");
  68. puts("\
  69. Cryptography options:\n\
  70. -s, --sign Sign the message.\n\
  71. -E, --encrypt RECP Encrypt for the specified recipient.\n\
  72. ");
  73. printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
  74. exit(status);
  75. }
  76. static void
  77. info(void)
  78. {
  79. printf("\
  80. fmail %s\n\
  81. Copyright (C) 2019 Damien Goutte-Gattat\n\
  82. \n\
  83. This program is released under the GNU General Public License.\n\
  84. See the COPYING file or <http://www.gnu.org/licenses/gpl.html>.\n\
  85. ", VERSION);
  86. exit(EXIT_SUCCESS);
  87. }
  88. /* Helper functions. */
  89. const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  90. "abcdefghijklmnopqrstuvwxyz"
  91. "0123456789+/";
  92. typedef struct fmail_ctx
  93. {
  94. const char **attachments;
  95. size_t att_count;
  96. const char **recipients;
  97. size_t rcp_count;
  98. const char *footer;
  99. magic_t magic_ctx;
  100. gpgme_ctx_t crypto_ctx;
  101. FILE *input;
  102. } fmail_ctx_t;
  103. static char *
  104. generate_boundary(char *buffer, size_t len)
  105. {
  106. unsigned i;
  107. for ( i = 0; i < len - 1; i++ )
  108. buffer[i] = base64_chars[rand() % (sizeof(base64_chars) - 1)];
  109. buffer[i] = '\0';
  110. return buffer;
  111. }
  112. static int
  113. check_7bit_encoding(const char *filename)
  114. {
  115. FILE *f;
  116. int c, ok = 1;
  117. unsigned n;
  118. if ( ! (f = fopen(filename, "r")) )
  119. return 0; /* We'll error out later. */
  120. while ( ! feof(f) && ! ok ) {
  121. c = fgetc(f);
  122. if ( c == 0 || c == '\r' || c > 127 )
  123. ok = 0;
  124. else if ( c == '\n' )
  125. n = 0;
  126. else
  127. n += 1;
  128. if ( n > 998 )
  129. ok = 0;
  130. }
  131. fclose(f);
  132. return ok;
  133. }
  134. static void
  135. mime7bit_encode_stream(FILE *in, FILE *out)
  136. {
  137. int c;
  138. while ( (c = fgetc(in)) != EOF ) {
  139. if ( c == '\n' )
  140. fputc('\r', out);
  141. fputc(c, out);
  142. }
  143. }
  144. static void
  145. qp_encode_stream(FILE *in, FILE *out)
  146. {
  147. int c;
  148. unsigned n;
  149. n = 0;
  150. while ( ! feof(in) ) {
  151. c = fgetc(in);
  152. if ( n >= 72 ) {
  153. fprintf(out, "=\r\n");
  154. n = 0;
  155. }
  156. if ( c == '\t' || c == ' ' ) {
  157. int next = fgetc(in);
  158. if ( next == '\n' ) {
  159. fprintf(out, "=%02X", c);
  160. n += 3;
  161. }
  162. else {
  163. fputc(c, out);
  164. n += 1;
  165. }
  166. ungetc(next, in);
  167. }
  168. else if ( c == '=' ) {
  169. fprintf(out, "=%02X", c);
  170. n += 3;
  171. }
  172. else if ( isprint(c) ) {
  173. fputc(c, out);
  174. n += 1;
  175. }
  176. else if ( c == '\n' ) {
  177. fprintf(out, "\r\n");
  178. n = 0;
  179. }
  180. else if ( c != EOF ) {
  181. fprintf(out, "=%02X", c);
  182. n += 3;
  183. }
  184. }
  185. }
  186. static void
  187. base64_encode_stream(FILE *in, FILE *out)
  188. {
  189. unsigned char buffer[3];
  190. int n, j;
  191. j = 0;
  192. while ( (n = fread(buffer, 1, 3, in)) > 0 ) {
  193. char quartet[4];
  194. if ( n < 3 )
  195. buffer[2] = 0;
  196. if ( n < 2 )
  197. buffer[1] = 0;
  198. quartet[0] = base64_chars[buffer[0] >> 2];
  199. quartet[1] = base64_chars[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xf0) >> 4)];
  200. quartet[2] = (unsigned char) (n > 1 ? base64_chars[((buffer[1] & 0x0f) << 2) | ((buffer[2] & 0x0c) >> 6)] : '=' );
  201. quartet[3] = (unsigned char) (n > 2 ? base64_chars[buffer[2] & 0x3f] : '=');
  202. j += 1;
  203. fwrite(quartet, 1, 4, out);
  204. if ( j == 16 ) {
  205. fprintf(out, "\r\n");
  206. j = 0;
  207. }
  208. }
  209. }
  210. static char *
  211. rfc2822_date(void)
  212. {
  213. time_t timestamp;
  214. struct tm *timestruct;
  215. const char *rfc2822_format = "%a, %d %b %Y %H:%M:%S %z";
  216. static char buffer[32];
  217. timestamp = time(NULL);
  218. timestruct = localtime(&timestamp);
  219. strftime(buffer, sizeof(buffer), rfc2822_format, timestruct);
  220. return buffer;
  221. }
  222. static void
  223. read_headers(FILE *in, string_buffer_t *headers)
  224. {
  225. int c, empty_line;
  226. size_t n;
  227. n = empty_line = 0;
  228. while ( ! empty_line ) {
  229. c = fgetc(in);
  230. if ( c == '\n' ) {
  231. if ( n == 0 )
  232. empty_line = 1;
  233. else {
  234. sb_add(headers, "\r\n");
  235. n = 0;
  236. }
  237. }
  238. else if ( c == EOF ) {
  239. /* EOF before reaching the end of headers, abort. */
  240. errx(EXIT_FAILURE, "cannot read mail headers");
  241. }
  242. else {
  243. sb_addc(headers, c);
  244. n += 1;
  245. }
  246. }
  247. }
  248. static void process_attachment(const char *, magic_t, FILE *);
  249. static void
  250. process_text_body(fmail_ctx_t *ctx, FILE *out)
  251. {
  252. char boundary[32];
  253. unsigned i;
  254. if ( ctx->att_count > 0 ) {
  255. generate_boundary(boundary, sizeof(boundary));
  256. fprintf(out, "Content-Type: multipart/mixed;\r\n"
  257. " boundary=\"%s\"\r\n"
  258. "\r\n"
  259. "--%s\r\n",
  260. boundary, boundary);
  261. }
  262. fprintf(out, "Content-Type: text/plain; charset=\"utf-8\"\r\n"
  263. "Content-Transfer-Encoding: quoted-printable\r\n"
  264. "Content-Disposition: inline\r\n\r\n");
  265. qp_encode_stream(ctx->input, out);
  266. fprintf(out, "\r\n");
  267. if ( ctx->footer ) {
  268. FILE *f;
  269. if ( ! (f = fopen(ctx->footer, "r")) )
  270. err(EXIT_FAILURE, "cannot open footer file '%s'", ctx->footer);
  271. fprintf(out, "-- \r\n");
  272. qp_encode_stream(f, out);
  273. fprintf(out, "\r\n");
  274. fclose(f);
  275. }
  276. for ( i = 0; i < ctx->att_count; i++ ) {
  277. fprintf(out, "--%s\r\n", boundary);
  278. process_attachment(ctx->attachments[i], ctx->magic_ctx, out);
  279. if ( i == ctx->att_count - 1 )
  280. fprintf(out, "--%s--\r\n", boundary);
  281. }
  282. }
  283. /* Crypto stuff. */
  284. gpgme_ctx_t
  285. initialize_gpgme(void)
  286. {
  287. gpgme_ctx_t ctx;
  288. gpg_error_t gerr;
  289. gpgme_check_version(NULL);
  290. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  291. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  292. if ( (gerr = gpgme_new(&ctx)) != GPG_ERR_NO_ERROR )
  293. errx(EXIT_FAILURE, "cannot initialize GPGME: %s",
  294. gpgme_strerror(gerr));
  295. gpgme_set_armor(ctx, 1);
  296. return ctx;
  297. }
  298. static const char *
  299. hash_algo_to_string(gpgme_hash_algo_t algo)
  300. {
  301. switch ( algo ) {
  302. case GPGME_MD_MD5: return "pgp-md5";
  303. case GPGME_MD_SHA1: return "pgp-sha1";
  304. case GPGME_MD_RMD160: return "pgp-ripemd160";
  305. case GPGME_MD_MD2: return "pgp-md2";
  306. case GPGME_MD_TIGER: return "pgp-tiger192";
  307. case GPGME_MD_HAVAL: return "pgp-haval-5-160";
  308. case GPGME_MD_SHA256: return "pgp-sha256";
  309. case GPGME_MD_SHA384: return "pgp-sha384";
  310. case GPGME_MD_SHA512: return "pgp-sha512";
  311. case GPGME_MD_SHA224: return "pgp-sha224";
  312. case GPGME_MD_MD4: return "pgp-md4";
  313. case GPGME_MD_CRC32: return "pgp-crc32";
  314. case GPGME_MD_CRC32_RFC1510: return "pgp-crc32-rfc1510";
  315. case GPGME_MD_CRC24_RFC2440: return "pgp-crc24-rfc2440";
  316. case GPGME_MD_NONE: return "";
  317. }
  318. return ""; /* FIXME: What to do here? */
  319. }
  320. static void
  321. sign_stream(gpgme_ctx_t ctx, FILE *in, FILE *out)
  322. {
  323. gpgme_data_t gin, gout;
  324. gpgme_sign_result_t result;
  325. gpgme_error_t gerr;
  326. char boundary[32], buffer[512];
  327. int n;
  328. gpgme_data_new_from_stream(&gin, in);
  329. gpgme_data_new(&gout);
  330. gerr = gpgme_op_sign(ctx, gin, gout, GPGME_SIG_MODE_DETACH);
  331. if ( gerr != GPG_ERR_NO_ERROR )
  332. errx(EXIT_FAILURE, "signing failed: %s", gpgme_strerror(gerr));
  333. result = gpgme_op_sign_result(ctx);
  334. generate_boundary(boundary, sizeof(boundary));
  335. fprintf(out, "Content-Type: multipart/signed;\r\n"
  336. " boundary=\"%s\";\r\n"
  337. " protocol=\"application/pgp-signature\";\r\n"
  338. " micalg=%s\r\n"
  339. "\r\n"
  340. "--%s\r\n",
  341. boundary,
  342. hash_algo_to_string(result->signatures->hash_algo),
  343. boundary);
  344. fseek(in, 0, SEEK_SET);
  345. while ( (n = fread(buffer, 1, sizeof(buffer), in)) > 0 )
  346. fwrite(buffer, 1, n, out);
  347. fprintf(out, "\r\n"
  348. "--%s\r\n"
  349. "Content-Type: application/pgp-signature; name=signature.asc\r\n"
  350. "\r\n",
  351. boundary);
  352. gpgme_data_seek(gout, 0, SEEK_SET);
  353. while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
  354. int i = 0;
  355. while ( i < n ) {
  356. if ( buffer[i] == '\n' )
  357. fputc('\r', out);
  358. fputc(buffer[i], out);
  359. i += 1;
  360. }
  361. }
  362. fprintf(out, "\r\n--%s--\r\n", boundary);
  363. gpgme_data_release(gin);
  364. gpgme_data_release(gout);
  365. }
  366. static gpgme_key_t *
  367. get_recipient_keys(gpgme_ctx_t ctx, const char **recipients, size_t nr)
  368. {
  369. gpgme_key_t *keys, key;
  370. gpgme_error_t gerr;
  371. unsigned i, j, k;
  372. keys = NULL;
  373. for ( i = j = gerr = 0; i < nr && ! gerr ; i++ ) {
  374. k = j;
  375. gerr = gpgme_op_keylist_start (ctx, recipients[i], 0);
  376. while ( ! gerr ) {
  377. gerr = gpgme_op_keylist_next (ctx, &key);
  378. if ( gerr )
  379. break;
  380. if ( j % 10 == 0 )
  381. keys = xrealloc(keys, j + 10);
  382. keys[j++] = key;
  383. }
  384. if ( gpgme_err_code(gerr) == GPG_ERR_EOF )
  385. gerr = 0;
  386. if ( k == j )
  387. errx(EXIT_FAILURE, "no key found for recipient %s", recipients[i]);
  388. }
  389. if ( gerr != 0 && gpgme_err_code(gerr) != GPG_ERR_EOF)
  390. errx(EXIT_FAILURE, "cannot get recipient keys: %s", gpgme_strerror(gerr));
  391. /* GpgME expects a NULL-terminated array of keys. */
  392. if ( j % 10 == 0 )
  393. keys = xrealloc(keys, j + 1);
  394. keys[j++] = NULL;
  395. return keys;
  396. }
  397. static void
  398. encrypt_stream(gpgme_ctx_t ctx,
  399. FILE *in,
  400. FILE *out,
  401. const char **recipients,
  402. size_t nr,
  403. int sign)
  404. {
  405. gpgme_data_t gin, gout;
  406. gpgme_key_t *keys, *key;
  407. gpgme_error_t gerr;
  408. char boundary[32], buffer[512];
  409. int n;
  410. keys = get_recipient_keys(ctx, recipients, nr);
  411. gpgme_data_new_from_stream(&gin, in);
  412. gpgme_data_new(&gout);
  413. if ( sign )
  414. gerr = gpgme_op_encrypt_sign(ctx, keys, 0, gin, gout);
  415. else
  416. gerr = gpgme_op_encrypt(ctx, keys, 0, gin, gout);
  417. if ( gerr != GPG_ERR_NO_ERROR )
  418. errx(EXIT_FAILURE, "encrypting failed: %s", gpgme_strerror(gerr));
  419. generate_boundary(boundary, sizeof(boundary));
  420. fprintf(out, "Content-Type: multipart/encrypted;\r\n"
  421. " boundary=\"%s\";\r\n"
  422. " protocol=\"application/pgp-encrypted\r\n"
  423. "\r\n"
  424. "--%s\r\n"
  425. "Content-Type: application/pgp-encrypted\r\n"
  426. "\r\n"
  427. "Version: 1\r\n"
  428. "\r\n"
  429. "--%s\r\n"
  430. "Content-Type: application/octet-stream\r\n"
  431. "\r\n",
  432. boundary, boundary, boundary);
  433. gpgme_data_seek(gout, 0, SEEK_SET);
  434. while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
  435. int i = 0;
  436. while ( i < n ) {
  437. if ( buffer[i] == '\n' )
  438. fputc('\r', out);
  439. fputc(buffer[i], out);
  440. i += 1;
  441. }
  442. }
  443. fprintf(out, "\r\n--%s--\r\n", boundary);
  444. gpgme_data_release(gin);
  445. gpgme_data_release(gout);
  446. for ( key = keys; *key != NULL; key++ )
  447. gpgme_key_release(*key);
  448. free(keys);
  449. }
  450. /* Attachments stuff. */
  451. static magic_t
  452. initialize_magic(void)
  453. {
  454. magic_t ctx;
  455. if ( ! (ctx = magic_open(MAGIC_SYMLINK | MAGIC_MIME)) )
  456. err(EXIT_FAILURE, "cannot obtain libmagic cookie");
  457. if ( magic_load(ctx, NULL) == -1 )
  458. err(EXIT_FAILURE, "cannot load default magic database");
  459. return ctx;
  460. }
  461. static const char *
  462. get_basename(const char *filename)
  463. {
  464. char *last_slash = strrchr(filename, '/');
  465. return last_slash ? last_slash + 1 : filename;
  466. }
  467. typedef enum {
  468. ENCODING_7BIT = 0,
  469. ENCODING_QP,
  470. ENCODING_BASE64
  471. } encoding_t;
  472. const char *encoding_names[] = { "7bit", "quoted-printable", "base64" };
  473. void (*encoding_funcs[])(FILE *, FILE *) = {
  474. mime7bit_encode_stream,
  475. qp_encode_stream,
  476. base64_encode_stream
  477. };
  478. static void
  479. process_attachment(const char *filename, magic_t ctx, FILE *out)
  480. {
  481. const char *mime;
  482. encoding_t encoding;
  483. FILE *f;
  484. if ( (mime = magic_file(ctx, filename)) ) {
  485. const char *last_eq;
  486. last_eq = strrchr(mime, '=');
  487. if ( strcmp(last_eq, "=binary") == 0 )
  488. encoding = ENCODING_BASE64;
  489. else if ( strcmp(last_eq, "=us-ascii") == 0 && check_7bit_encoding(filename) )
  490. encoding = ENCODING_7BIT;
  491. else
  492. encoding = ENCODING_QP;
  493. }
  494. else {
  495. mime = "application/octet-stream";
  496. encoding = ENCODING_BASE64;
  497. }
  498. fprintf(out, "Content-Type: %s\r\n", mime);
  499. fprintf(out, "Content-Transfer-Encoding: %s\r\n", encoding_names[encoding]);
  500. fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", get_basename(filename));
  501. if ( ! (f = fopen(filename, "r")) )
  502. err(EXIT_FAILURE, "cannot open attachment '%s'", filename);
  503. encoding_funcs[encoding](f, out);
  504. fclose(f);
  505. fprintf(out, "\r\n");
  506. }
  507. /* Read mail from editor. */
  508. static const char *
  509. get_editor(void)
  510. {
  511. const char *editor;
  512. if ( ! (editor = getenv("EDITOR")) )
  513. if ( ! (editor = getenv("VISUAL")) )
  514. editor = "vi";
  515. return editor;
  516. }
  517. static int
  518. is_usable_as_tmp_dir(const char *dirname)
  519. {
  520. struct stat st_buf;
  521. if ( stat(dirname, &st_buf) != -1 )
  522. if ( S_ISDIR(st_buf.st_mode) )
  523. if ( access(dirname, R_OK | W_OK) != -1 )
  524. return 1;
  525. return 0;
  526. }
  527. static const char *
  528. get_tmp_dir(void)
  529. {
  530. const char *tmp;
  531. if ( ! ((tmp = getenv("TMPDIR")) && is_usable_as_tmp_dir(tmp)) )
  532. if ( ! ((tmp = getenv("TMP")) && is_usable_as_tmp_dir(tmp)) )
  533. tmp = is_usable_as_tmp_dir("/tmp") ? "/tmp" : ".";
  534. return tmp;
  535. }
  536. static FILE *
  537. read_input_from_editor(void)
  538. {
  539. char *command, *filename;
  540. int fd;
  541. FILE *f;
  542. xasprintf(&filename, "%s/fmailXXXXXX", get_tmp_dir());
  543. if ( (fd = mkstemp(filename)) == -1 )
  544. err(EXIT_FAILURE, "cannot create temporary file");
  545. xasprintf(&command, "%s %s", get_editor(), filename);
  546. if ( system(command) == -1 )
  547. err(EXIT_FAILURE, "cannot execute editor");
  548. if ( (f = fdopen(fd, "r")) == NULL )
  549. err(EXIT_FAILURE, "cannot open temporary file");
  550. unlink(filename);
  551. free(filename);
  552. free(command);
  553. return f;
  554. }
  555. /* Main function. */
  556. int
  557. main(int argc, char *argv[])
  558. {
  559. char c, with_useragent, with_date;
  560. string_buffer_t headers;
  561. fmail_ctx_t ctx;
  562. int do_sign;
  563. struct option options[] = {
  564. { "help", 0, NULL, 'h' },
  565. { "version", 0, NULL, 'v' },
  566. { "sign", 0, NULL, 's' },
  567. { "encrypt", 1, NULL, 'E' },
  568. { "attach", 1, NULL, 'a' },
  569. { "edit", 0, NULL, 'e' },
  570. { "footer", 1, NULL, 'f' },
  571. { "header", 1, NULL, 'H' },
  572. { "from", 1, NULL, 'F' },
  573. { "to", 1, NULL, 'T' },
  574. { "cc", 1, NULL, 'C' },
  575. { "subject", 1, NULL, 'S' },
  576. { "user-agent", 0, NULL, 'U' },
  577. { "date", 0, NULL, 'D' },
  578. { NULL, 0, NULL, 0 }
  579. };
  580. setprogname(argv[0]);
  581. setlocale(LC_ALL, "");
  582. srand(time(NULL));
  583. with_useragent = with_date = do_sign = 0;
  584. sb_init(&headers, 0);
  585. ctx.crypto_ctx = NULL;
  586. ctx.magic_ctx = initialize_magic();
  587. ctx.attachments = NULL;
  588. ctx.att_count = 0;
  589. ctx.recipients = NULL;
  590. ctx.rcp_count = 0;
  591. ctx.footer = NULL;
  592. ctx.input = stdin;
  593. while ( (c = getopt_long(argc, argv, "hvsE:a:ef:H:F:T:C:S:UD",
  594. options, NULL)) != -1 ) {
  595. switch ( c ) {
  596. case 'h':
  597. usage(EXIT_SUCCESS);
  598. break;
  599. case '?':
  600. usage(EXIT_FAILURE);
  601. break;
  602. case 'v':
  603. info();
  604. break;
  605. case 's':
  606. do_sign = 1;
  607. break;
  608. case 'E':
  609. if ( ctx.rcp_count % 10 == 0 )
  610. ctx.recipients = xrealloc(ctx.recipients, ctx.rcp_count + 10);
  611. ctx.recipients[ctx.rcp_count++] = optarg;
  612. break;
  613. case 'a':
  614. if ( ctx.att_count % 10 == 0 )
  615. ctx.attachments = xrealloc(ctx.attachments, ctx.att_count + 10);
  616. ctx.attachments[ctx.att_count++] = optarg;
  617. break;
  618. case 'e':
  619. ctx.input = NULL;
  620. break;
  621. case 'f':
  622. ctx.footer = optarg;
  623. break;
  624. case 'H':
  625. sb_addf(&headers, "%s\r\n", optarg);
  626. break;
  627. case 'F':
  628. sb_addf(&headers, "From: %s\r\n", optarg);
  629. break;
  630. case 'T':
  631. sb_addf(&headers, "To: %s\r\n", optarg);
  632. break;
  633. case 'C':
  634. sb_addf(&headers, "Cc: %s\r\n", optarg);
  635. break;
  636. case 'S':
  637. sb_addf(&headers, "Subject: %s\r\n", optarg);
  638. break;
  639. case 'U':
  640. with_useragent = 1;
  641. break;
  642. case 'D':
  643. with_date = 1;
  644. break;
  645. }
  646. }
  647. if ( do_sign || ctx.rcp_count )
  648. ctx.crypto_ctx = initialize_gpgme();
  649. /* Generate automatic headers */
  650. if ( with_date )
  651. sb_addf(&headers, "Date: %s\r\n", rfc2822_date());
  652. if ( with_useragent )
  653. sb_addf(&headers, "User-Agent: fmail %s\r\n", VERSION);
  654. /* Fire editor instead of reading from stdin? */
  655. if ( ! ctx.input )
  656. ctx.input = read_input_from_editor();
  657. /* Read user-provided headers. */
  658. read_headers(ctx.input, &headers);
  659. /* Write all headers. */
  660. fprintf(stdout, "%s", headers.buffer);
  661. free(headers.buffer);
  662. if ( do_sign || ctx.rcp_count ) {
  663. FILE *tmp = tmpfile();
  664. process_text_body(&ctx, tmp);
  665. fseek(tmp, 0, SEEK_SET);
  666. if ( ctx.rcp_count == 0 )
  667. sign_stream(ctx.crypto_ctx, tmp, stdout);
  668. else
  669. encrypt_stream(ctx.crypto_ctx, tmp, stdout, ctx.recipients, ctx.rcp_count, do_sign);
  670. fclose(tmp);
  671. gpgme_release(ctx.crypto_ctx);
  672. }
  673. else
  674. process_text_body(&ctx, stdout);
  675. fclose(ctx.input);
  676. if ( ctx.attachments )
  677. free(ctx.attachments);
  678. if ( ctx.recipients )
  679. free(ctx.recipients);
  680. magic_close(ctx.magic_ctx);
  681. return EXIT_SUCCESS;
  682. }