A mail formatter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

794 lines
19 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 void
  113. qp_encode_stream(FILE *in, FILE *out)
  114. {
  115. int c;
  116. unsigned n;
  117. n = 0;
  118. while ( ! feof(in) ) {
  119. c = fgetc(in);
  120. if ( n >= 72 ) {
  121. fprintf(out, "=\r\n");
  122. n = 0;
  123. }
  124. if ( c == '\t' || c == ' ' ) {
  125. int next = fgetc(in);
  126. if ( next == '\n' ) {
  127. fprintf(out, "=%02X", c);
  128. n += 3;
  129. }
  130. else {
  131. fputc(c, out);
  132. n += 1;
  133. }
  134. ungetc(next, in);
  135. }
  136. else if ( c == '=' ) {
  137. fprintf(out, "=%02X", c);
  138. n += 3;
  139. }
  140. else if ( isprint(c) ) {
  141. fputc(c, out);
  142. n += 1;
  143. }
  144. else if ( c == '\n' ) {
  145. fprintf(out, "\r\n");
  146. n = 0;
  147. }
  148. else if ( c != EOF ) {
  149. fprintf(out, "=%02X", c);
  150. n += 3;
  151. }
  152. }
  153. }
  154. static void
  155. base64_encode_stream(FILE *in, FILE *out)
  156. {
  157. unsigned char buffer[3];
  158. int n, j;
  159. j = 0;
  160. while ( (n = fread(buffer, 1, 3, in)) > 0 ) {
  161. char quartet[4];
  162. if ( n < 3 )
  163. buffer[2] = 0;
  164. if ( n < 2 )
  165. buffer[1] = 0;
  166. quartet[0] = base64_chars[buffer[0] >> 2];
  167. quartet[1] = base64_chars[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xf0) >> 4)];
  168. quartet[2] = (unsigned char) (n > 1 ? base64_chars[((buffer[1] & 0x0f) << 2) | ((buffer[2] & 0x0c) >> 6)] : '=' );
  169. quartet[3] = (unsigned char) (n > 2 ? base64_chars[buffer[2] & 0x3f] : '=');
  170. j += 1;
  171. fwrite(quartet, 1, 4, out);
  172. if ( j == 16 ) {
  173. fprintf(out, "\r\n");
  174. j = 0;
  175. }
  176. }
  177. }
  178. static char *
  179. rfc2822_date(void)
  180. {
  181. time_t timestamp;
  182. struct tm *timestruct;
  183. const char *rfc2822_format = "%a, %d %b %Y %H:%M:%S %z";
  184. static char buffer[32];
  185. timestamp = time(NULL);
  186. timestruct = localtime(&timestamp);
  187. strftime(buffer, sizeof(buffer), rfc2822_format, timestruct);
  188. return buffer;
  189. }
  190. static void
  191. read_headers(FILE *in, string_buffer_t *headers)
  192. {
  193. int c, empty_line;
  194. size_t n;
  195. n = empty_line = 0;
  196. while ( ! empty_line ) {
  197. c = fgetc(in);
  198. if ( c == '\n' ) {
  199. if ( n == 0 )
  200. empty_line = 1;
  201. else {
  202. sb_add(headers, "\r\n");
  203. n = 0;
  204. }
  205. }
  206. else if ( c == EOF ) {
  207. /* EOF before reaching the end of headers, abort. */
  208. errx(EXIT_FAILURE, "cannot read mail headers");
  209. }
  210. else {
  211. sb_addc(headers, c);
  212. n += 1;
  213. }
  214. }
  215. }
  216. static void process_attachment(const char *, magic_t, FILE *);
  217. static void
  218. process_text_body(fmail_ctx_t *ctx, FILE *out)
  219. {
  220. char boundary[32];
  221. unsigned i;
  222. if ( ctx->att_count > 0 ) {
  223. generate_boundary(boundary, sizeof(boundary));
  224. fprintf(out, "Content-Type: multipart/mixed;\r\n"
  225. " boundary=\"%s\"\r\n"
  226. "\r\n"
  227. "--%s\r\n",
  228. boundary, boundary);
  229. }
  230. fprintf(out, "Content-Type: text/plain; charset=\"utf-8\"\r\n"
  231. "Content-Transfer-Encoding: quoted-printable\r\n"
  232. "Content-Disposition: inline\r\n\r\n");
  233. qp_encode_stream(ctx->input, out);
  234. fprintf(out, "\r\n");
  235. if ( ctx->footer ) {
  236. FILE *f;
  237. if ( ! (f = fopen(ctx->footer, "r")) )
  238. err(EXIT_FAILURE, "cannot open footer file '%s'", ctx->footer);
  239. fprintf(out, "-- \r\n");
  240. qp_encode_stream(f, out);
  241. fprintf(out, "\r\n");
  242. fclose(f);
  243. }
  244. for ( i = 0; i < ctx->att_count; i++ ) {
  245. fprintf(out, "--%s\r\n", boundary);
  246. process_attachment(ctx->attachments[i], ctx->magic_ctx, out);
  247. if ( i == ctx->att_count - 1 )
  248. fprintf(out, "--%s--\r\n", boundary);
  249. }
  250. }
  251. /* Crypto stuff. */
  252. gpgme_ctx_t
  253. initialize_gpgme(void)
  254. {
  255. gpgme_ctx_t ctx;
  256. gpg_error_t gerr;
  257. gpgme_check_version(NULL);
  258. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  259. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  260. if ( (gerr = gpgme_new(&ctx)) != GPG_ERR_NO_ERROR )
  261. errx(EXIT_FAILURE, "cannot initialize GPGME: %s",
  262. gpgme_strerror(gerr));
  263. gpgme_set_armor(ctx, 1);
  264. return ctx;
  265. }
  266. static const char *
  267. hash_algo_to_string(gpgme_hash_algo_t algo)
  268. {
  269. switch ( algo ) {
  270. case GPGME_MD_MD5: return "pgp-md5";
  271. case GPGME_MD_SHA1: return "pgp-sha1";
  272. case GPGME_MD_RMD160: return "pgp-ripemd160";
  273. case GPGME_MD_MD2: return "pgp-md2";
  274. case GPGME_MD_TIGER: return "pgp-tiger192";
  275. case GPGME_MD_HAVAL: return "pgp-haval-5-160";
  276. case GPGME_MD_SHA256: return "pgp-sha256";
  277. case GPGME_MD_SHA384: return "pgp-sha384";
  278. case GPGME_MD_SHA512: return "pgp-sha512";
  279. case GPGME_MD_SHA224: return "pgp-sha224";
  280. case GPGME_MD_MD4: return "pgp-md4";
  281. case GPGME_MD_CRC32: return "pgp-crc32";
  282. case GPGME_MD_CRC32_RFC1510: return "pgp-crc32-rfc1510";
  283. case GPGME_MD_CRC24_RFC2440: return "pgp-crc24-rfc2440";
  284. case GPGME_MD_NONE: return "";
  285. }
  286. return ""; /* FIXME: What to do here? */
  287. }
  288. static void
  289. sign_stream(gpgme_ctx_t ctx, FILE *in, FILE *out)
  290. {
  291. gpgme_data_t gin, gout;
  292. gpgme_sign_result_t result;
  293. gpgme_error_t gerr;
  294. char boundary[32], buffer[512];
  295. int n;
  296. gpgme_data_new_from_stream(&gin, in);
  297. gpgme_data_new(&gout);
  298. gerr = gpgme_op_sign(ctx, gin, gout, GPGME_SIG_MODE_DETACH);
  299. if ( gerr != GPG_ERR_NO_ERROR )
  300. errx(EXIT_FAILURE, "signing failed: %s", gpgme_strerror(gerr));
  301. result = gpgme_op_sign_result(ctx);
  302. generate_boundary(boundary, sizeof(boundary));
  303. fprintf(out, "Content-Type: multipart/signed;\r\n"
  304. " boundary=\"%s\";\r\n"
  305. " protocol=\"application/pgp-signature\";\r\n"
  306. " micalg=%s\r\n"
  307. "\r\n"
  308. "--%s\r\n",
  309. boundary,
  310. hash_algo_to_string(result->signatures->hash_algo),
  311. boundary);
  312. fseek(in, 0, SEEK_SET);
  313. while ( (n = fread(buffer, 1, sizeof(buffer), in)) > 0 )
  314. fwrite(buffer, 1, n, out);
  315. fprintf(out, "\r\n"
  316. "--%s\r\n"
  317. "Content-Type: application/pgp-signature; name=signature.asc\r\n"
  318. "\r\n",
  319. boundary);
  320. gpgme_data_seek(gout, 0, SEEK_SET);
  321. while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
  322. int i = 0;
  323. while ( i < n ) {
  324. if ( buffer[i] == '\n' )
  325. fputc('\r', out);
  326. fputc(buffer[i], out);
  327. i += 1;
  328. }
  329. }
  330. fprintf(out, "\r\n--%s--\r\n", boundary);
  331. gpgme_data_release(gin);
  332. gpgme_data_release(gout);
  333. }
  334. static gpgme_key_t *
  335. get_recipient_keys(gpgme_ctx_t ctx, const char **recipients, size_t nr)
  336. {
  337. gpgme_key_t *keys, key;
  338. gpgme_error_t gerr;
  339. unsigned i, j, k;
  340. keys = NULL;
  341. for ( i = j = gerr = 0; i < nr && ! gerr ; i++ ) {
  342. k = j;
  343. gerr = gpgme_op_keylist_start (ctx, recipients[i], 0);
  344. while ( ! gerr ) {
  345. gerr = gpgme_op_keylist_next (ctx, &key);
  346. if ( gerr )
  347. break;
  348. if ( j % 10 == 0 )
  349. keys = xrealloc(keys, j + 10);
  350. keys[j++] = key;
  351. }
  352. if ( gpgme_err_code(gerr) == GPG_ERR_EOF )
  353. gerr = 0;
  354. if ( k == j )
  355. errx(EXIT_FAILURE, "no key found for recipient %s", recipients[i]);
  356. }
  357. if ( gerr != 0 && gpgme_err_code(gerr) != GPG_ERR_EOF)
  358. errx(EXIT_FAILURE, "cannot get recipient keys: %s", gpgme_strerror(gerr));
  359. /* GpgME expects a NULL-terminated array of keys. */
  360. if ( j % 10 == 0 )
  361. keys = xrealloc(keys, j + 1);
  362. keys[j++] = NULL;
  363. return keys;
  364. }
  365. static void
  366. encrypt_stream(gpgme_ctx_t ctx,
  367. FILE *in,
  368. FILE *out,
  369. const char **recipients,
  370. size_t nr,
  371. int sign)
  372. {
  373. gpgme_data_t gin, gout;
  374. gpgme_key_t *keys, *key;
  375. gpgme_error_t gerr;
  376. char boundary[32], buffer[512];
  377. int n;
  378. keys = get_recipient_keys(ctx, recipients, nr);
  379. gpgme_data_new_from_stream(&gin, in);
  380. gpgme_data_new(&gout);
  381. if ( sign )
  382. gerr = gpgme_op_encrypt_sign(ctx, keys, 0, gin, gout);
  383. else
  384. gerr = gpgme_op_encrypt(ctx, keys, 0, gin, gout);
  385. if ( gerr != GPG_ERR_NO_ERROR )
  386. errx(EXIT_FAILURE, "encrypting failed: %s", gpgme_strerror(gerr));
  387. generate_boundary(boundary, sizeof(boundary));
  388. fprintf(out, "Content-Type: multipart/encrypted;\r\n"
  389. " boundary=\"%s\";\r\n"
  390. " protocol=\"application/pgp-encrypted\r\n"
  391. "\r\n"
  392. "--%s\r\n"
  393. "Content-Type: application/pgp-encrypted\r\n"
  394. "\r\n"
  395. "Version: 1\r\n"
  396. "\r\n"
  397. "--%s\r\n"
  398. "Content-Type: application/octet-stream\r\n"
  399. "\r\n",
  400. boundary, boundary, boundary);
  401. gpgme_data_seek(gout, 0, SEEK_SET);
  402. while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
  403. int i = 0;
  404. while ( i < n ) {
  405. if ( buffer[i] == '\n' )
  406. fputc('\r', out);
  407. fputc(buffer[i], out);
  408. i += 1;
  409. }
  410. }
  411. fprintf(out, "\r\n--%s--\r\n", boundary);
  412. gpgme_data_release(gin);
  413. gpgme_data_release(gout);
  414. for ( key = keys; *key != NULL; key++ )
  415. gpgme_key_release(*key);
  416. free(keys);
  417. }
  418. /* Attachments stuff. */
  419. static magic_t
  420. initialize_magic(void)
  421. {
  422. magic_t ctx;
  423. if ( ! (ctx = magic_open(MAGIC_SYMLINK | MAGIC_MIME)) )
  424. err(EXIT_FAILURE, "cannot obtain libmagic cookie");
  425. if ( magic_load(ctx, NULL) == -1 )
  426. err(EXIT_FAILURE, "cannot load default magic database");
  427. return ctx;
  428. }
  429. static const char *
  430. get_basename(const char *filename)
  431. {
  432. char *last_slash = strrchr(filename, '/');
  433. return last_slash ? last_slash + 1 : filename;
  434. }
  435. static void
  436. process_attachment(const char *filename, magic_t ctx, FILE *out)
  437. {
  438. const char *mime;
  439. int binary;
  440. FILE *f;
  441. if ( (mime = magic_file(ctx, filename)) ) {
  442. const char *last_eq;
  443. last_eq = strrchr(mime, '=');
  444. binary = strcmp(last_eq, "=binary") ? 0 : 1;
  445. }
  446. else {
  447. mime = "application/octet-stream";
  448. binary = 1;
  449. }
  450. fprintf(out, "Content-Type: %s\r\n", mime);
  451. fprintf(out, "Content-Transfer-Encoding: %s\r\n", binary ? "base64" : "quoted-printable");
  452. fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", get_basename(filename));
  453. f = fopen(filename, "r");
  454. if ( binary )
  455. base64_encode_stream(f, out);
  456. else
  457. qp_encode_stream(f, out);
  458. fclose(f);
  459. fprintf(out, "\r\n");
  460. }
  461. /* Read mail from editor. */
  462. static const char *
  463. get_editor(void)
  464. {
  465. const char *editor;
  466. if ( ! (editor = getenv("EDITOR")) )
  467. if ( ! (editor = getenv("VISUAL")) )
  468. editor = "vi";
  469. return editor;
  470. }
  471. static int
  472. is_usable_as_tmp_dir(const char *dirname)
  473. {
  474. struct stat st_buf;
  475. if ( stat(dirname, &st_buf) != -1 )
  476. if ( S_ISDIR(st_buf.st_mode) )
  477. if ( access(dirname, R_OK | W_OK) != -1 )
  478. return 1;
  479. return 0;
  480. }
  481. static const char *
  482. get_tmp_dir(void)
  483. {
  484. const char *tmp;
  485. if ( ! ((tmp = getenv("TMPDIR")) && is_usable_as_tmp_dir(tmp)) )
  486. if ( ! ((tmp = getenv("TMP")) && is_usable_as_tmp_dir(tmp)) )
  487. tmp = is_usable_as_tmp_dir("/tmp") ? "/tmp" : ".";
  488. return tmp;
  489. }
  490. static FILE *
  491. read_input_from_editor(void)
  492. {
  493. char *command, *filename;
  494. int fd;
  495. FILE *f;
  496. xasprintf(&filename, "%s/fmailXXXXXX", get_tmp_dir());
  497. if ( (fd = mkstemp(filename)) == -1 )
  498. err(EXIT_FAILURE, "cannot create temporary file");
  499. xasprintf(&command, "%s %s", get_editor(), filename);
  500. if ( system(command) == -1 )
  501. err(EXIT_FAILURE, "cannot execute editor");
  502. if ( (f = fdopen(fd, "r")) == NULL )
  503. err(EXIT_FAILURE, "cannot open temporary file");
  504. unlink(filename);
  505. free(filename);
  506. free(command);
  507. return f;
  508. }
  509. /* Main function. */
  510. int
  511. main(int argc, char *argv[])
  512. {
  513. char c, with_useragent, with_date;
  514. string_buffer_t headers;
  515. fmail_ctx_t ctx;
  516. int do_sign;
  517. struct option options[] = {
  518. { "help", 0, NULL, 'h' },
  519. { "version", 0, NULL, 'v' },
  520. { "sign", 0, NULL, 's' },
  521. { "encrypt", 1, NULL, 'E' },
  522. { "attach", 1, NULL, 'a' },
  523. { "edit", 0, NULL, 'e' },
  524. { "footer", 1, NULL, 'f' },
  525. { "header", 1, NULL, 'H' },
  526. { "from", 1, NULL, 'F' },
  527. { "to", 1, NULL, 'T' },
  528. { "cc", 1, NULL, 'C' },
  529. { "subject", 1, NULL, 'S' },
  530. { "user-agent", 0, NULL, 'U' },
  531. { "date", 0, NULL, 'D' },
  532. { NULL, 0, NULL, 0 }
  533. };
  534. setprogname(argv[0]);
  535. setlocale(LC_ALL, "");
  536. srand(time(NULL));
  537. with_useragent = with_date = do_sign = 0;
  538. sb_init(&headers, 0);
  539. ctx.crypto_ctx = NULL;
  540. ctx.magic_ctx = initialize_magic();
  541. ctx.attachments = NULL;
  542. ctx.att_count = 0;
  543. ctx.recipients = NULL;
  544. ctx.rcp_count = 0;
  545. ctx.footer = NULL;
  546. ctx.input = stdin;
  547. while ( (c = getopt_long(argc, argv, "hvsE:a:ef:H:F:T:C:S:UD",
  548. options, NULL)) != -1 ) {
  549. switch ( c ) {
  550. case 'h':
  551. usage(EXIT_SUCCESS);
  552. break;
  553. case '?':
  554. usage(EXIT_FAILURE);
  555. break;
  556. case 'v':
  557. info();
  558. break;
  559. case 's':
  560. do_sign = 1;
  561. break;
  562. case 'E':
  563. if ( ctx.rcp_count % 10 == 0 )
  564. ctx.recipients = xrealloc(ctx.recipients, ctx.rcp_count + 10);
  565. ctx.recipients[ctx.rcp_count++] = optarg;
  566. break;
  567. case 'a':
  568. if ( ctx.att_count % 10 == 0 )
  569. ctx.attachments = xrealloc(ctx.attachments, ctx.att_count + 10);
  570. ctx.attachments[ctx.att_count++] = optarg;
  571. break;
  572. case 'e':
  573. ctx.input = NULL;
  574. break;
  575. case 'f':
  576. ctx.footer = optarg;
  577. break;
  578. case 'H':
  579. sb_addf(&headers, "%s\r\n", optarg);
  580. break;
  581. case 'F':
  582. sb_addf(&headers, "From: %s\r\n", optarg);
  583. break;
  584. case 'T':
  585. sb_addf(&headers, "To: %s\r\n", optarg);
  586. break;
  587. case 'C':
  588. sb_addf(&headers, "Cc: %s\r\n", optarg);
  589. break;
  590. case 'S':
  591. sb_addf(&headers, "Subject: %s\r\n", optarg);
  592. break;
  593. case 'U':
  594. with_useragent = 1;
  595. break;
  596. case 'D':
  597. with_date = 1;
  598. break;
  599. }
  600. }
  601. if ( do_sign || ctx.rcp_count )
  602. ctx.crypto_ctx = initialize_gpgme();
  603. /* Generate automatic headers */
  604. if ( with_date )
  605. sb_addf(&headers, "Date: %s\r\n", rfc2822_date());
  606. if ( with_useragent )
  607. sb_addf(&headers, "User-Agent: fmail %s\r\n", VERSION);
  608. /* Fire editor instead of reading from stdin? */
  609. if ( ! ctx.input )
  610. ctx.input = read_input_from_editor();
  611. /* Read user-provided headers. */
  612. read_headers(ctx.input, &headers);
  613. /* Write all headers. */
  614. fprintf(stdout, "%s", headers.buffer);
  615. free(headers.buffer);
  616. if ( do_sign || ctx.rcp_count ) {
  617. FILE *tmp = tmpfile();
  618. process_text_body(&ctx, tmp);
  619. fseek(tmp, 0, SEEK_SET);
  620. if ( ctx.rcp_count == 0 )
  621. sign_stream(ctx.crypto_ctx, tmp, stdout);
  622. else
  623. encrypt_stream(ctx.crypto_ctx, tmp, stdout, ctx.recipients, ctx.rcp_count, do_sign);
  624. fclose(tmp);
  625. gpgme_release(ctx.crypto_ctx);
  626. }
  627. else
  628. process_text_body(&ctx, stdout);
  629. fclose(ctx.input);
  630. if ( ctx.attachments )
  631. free(ctx.attachments);
  632. if ( ctx.recipients )
  633. free(ctx.recipients);
  634. magic_close(ctx.magic_ctx);
  635. return EXIT_SUCCESS;
  636. }