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.

786 lines
19 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*
  2. * fmail - Mail formatter
  3. * Copyright (C) 2011,2018 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) 2018 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. int i, j;
  340. keys = NULL;
  341. for ( i = j = gerr = 0; i < nr && ! gerr ; i++ ) {
  342. gerr = gpgme_op_keylist_start (ctx, recipients[i], 0);
  343. while ( ! gerr ) {
  344. gerr = gpgme_op_keylist_next (ctx, &key);
  345. if ( gerr )
  346. break;
  347. if ( j % 10 == 0 )
  348. keys = xrealloc(keys, j + 10);
  349. keys[j++] = key;
  350. }
  351. if ( gpgme_err_code(gerr) == GPG_ERR_EOF )
  352. gerr = 0;
  353. }
  354. if ( gerr != 0 && gpgme_err_code(gerr) != GPG_ERR_EOF)
  355. errx(EXIT_FAILURE, "cannot get recipient keys: %s", gpgme_strerror(gerr));
  356. /* GpgME expects a NULL-terminated array of keys. */
  357. if ( j % 10 == 0 )
  358. keys = xrealloc(keys, j + 1);
  359. keys[j++] = NULL;
  360. return keys;
  361. }
  362. static void
  363. encrypt_stream(gpgme_ctx_t ctx,
  364. FILE *in,
  365. FILE *out,
  366. const char **recipients,
  367. size_t nr)
  368. {
  369. gpgme_data_t gin, gout;
  370. gpgme_key_t *keys, *key;
  371. gpgme_error_t gerr;
  372. char boundary[32], buffer[512];
  373. int n;
  374. keys = get_recipient_keys(ctx, recipients, nr);
  375. gpgme_data_new_from_stream(&gin, in);
  376. gpgme_data_new(&gout);
  377. gerr = gpgme_op_encrypt(ctx, keys, 0, gin, gout);
  378. if ( gerr != GPG_ERR_NO_ERROR )
  379. errx(EXIT_FAILURE, "encrypting failed: %s", gpgme_strerror(gerr));
  380. generate_boundary(boundary, sizeof(boundary));
  381. fprintf(out, "Content-Type: multipart/encrypted;\r\n"
  382. " boundary=\"%s\";\r\n"
  383. " protocol=\"application/pgp-encrypted\r\n"
  384. "\r\n"
  385. "--%s\r\n"
  386. "Content-Type: application/pgp-encrypted\r\n"
  387. "\r\n"
  388. "Version: 1\r\n"
  389. "\r\n"
  390. "--%s\r\n"
  391. "Content-Type: application/octet-stream\r\n"
  392. "\r\n",
  393. boundary, boundary, boundary);
  394. gpgme_data_seek(gout, 0, SEEK_SET);
  395. while ( (n = gpgme_data_read(gout, buffer, sizeof(buffer))) > 0 ) {
  396. int i = 0;
  397. while ( i < n ) {
  398. if ( buffer[i] == '\n' )
  399. fputc('\r', out);
  400. fputc(buffer[i], out);
  401. i += 1;
  402. }
  403. }
  404. fprintf(out, "\r\n--%s--\r\n", boundary);
  405. gpgme_data_release(gin);
  406. gpgme_data_release(gout);
  407. for ( key = keys; *key != NULL; key++ )
  408. gpgme_key_release(*key);
  409. free(keys);
  410. }
  411. /* Attachments stuff. */
  412. static magic_t
  413. initialize_magic(void)
  414. {
  415. magic_t ctx;
  416. if ( ! (ctx = magic_open(MAGIC_SYMLINK | MAGIC_MIME)) )
  417. err(EXIT_FAILURE, "cannot obtain libmagic cookie");
  418. if ( magic_load(ctx, NULL) == -1 )
  419. err(EXIT_FAILURE, "cannot load default magic database");
  420. return ctx;
  421. }
  422. static const char *
  423. get_basename(const char *filename)
  424. {
  425. char *last_slash = strrchr(filename, '/');
  426. return last_slash ? last_slash + 1 : filename;
  427. }
  428. static void
  429. process_attachment(const char *filename, magic_t ctx, FILE *out)
  430. {
  431. const char *mime, basename;
  432. int binary;
  433. FILE *f;
  434. if ( (mime = magic_file(ctx, filename)) ) {
  435. const char *last_eq;
  436. last_eq = strrchr(mime, '=');
  437. binary = strcmp(last_eq, "=binary") ? 0 : 1;
  438. }
  439. else {
  440. mime = "application/octet-stream";
  441. binary = 1;
  442. }
  443. fprintf(out, "Content-Type: %s\r\n", mime);
  444. fprintf(out, "Content-Transfer-Encoding: %s\r\n", binary ? "base64" : "quoted-printable");
  445. fprintf(out, "Content-Disposition: attachment; filename=%s\r\n\r\n", get_basename(filename));
  446. f = fopen(filename, "r");
  447. if ( binary )
  448. base64_encode_stream(f, out);
  449. else
  450. qp_encode_stream(f, out);
  451. fclose(f);
  452. fprintf(out, "\r\n");
  453. }
  454. /* Read mail from editor. */
  455. static const char *
  456. get_editor(void)
  457. {
  458. const char *editor;
  459. if ( ! (editor = getenv("EDITOR")) )
  460. if ( ! (editor = getenv("VISUAL")) )
  461. editor = "vi";
  462. return editor;
  463. }
  464. static int
  465. is_usable_as_tmp_dir(const char *dirname)
  466. {
  467. struct stat st_buf;
  468. if ( stat(dirname, &st_buf) != -1 )
  469. if ( S_ISDIR(st_buf.st_mode) )
  470. if ( access(dirname, R_OK | W_OK) != -1 )
  471. return 1;
  472. return 0;
  473. }
  474. static const char *
  475. get_tmp_dir(void)
  476. {
  477. const char *tmp;
  478. if ( ! ((tmp = getenv("TMPDIR")) && is_usable_as_tmp_dir(tmp)) )
  479. if ( ! ((tmp = getenv("TMP")) && is_usable_as_tmp_dir(tmp)) )
  480. tmp = is_usable_as_tmp_dir("/tmp") ? "/tmp" : ".";
  481. return tmp;
  482. }
  483. static FILE *
  484. read_input_from_editor(void)
  485. {
  486. char *command, *filename;
  487. int fd;
  488. FILE *f;
  489. xasprintf(&filename, "%s/fmailXXXXXX", get_tmp_dir());
  490. if ( (fd = mkstemp(filename)) == -1 )
  491. err(EXIT_FAILURE, "cannot create temporary file");
  492. xasprintf(&command, "%s %s", get_editor(), filename);
  493. if ( system(command) == -1 )
  494. err(EXIT_FAILURE, "cannot execute editor");
  495. if ( (f = fdopen(fd, "r")) == NULL )
  496. err(EXIT_FAILURE, "cannot open temporary file");
  497. unlink(filename);
  498. free(filename);
  499. free(command);
  500. return f;
  501. }
  502. /* Main function. */
  503. int
  504. main(int argc, char *argv[])
  505. {
  506. char c, with_useragent, with_date;
  507. string_buffer_t *headers;
  508. fmail_ctx_t ctx;
  509. int do_sign;
  510. struct option options[] = {
  511. { "help", 0, NULL, 'h' },
  512. { "version", 0, NULL, 'v' },
  513. { "sign", 0, NULL, 's' },
  514. { "encrypt", 1, NULL, 'E' },
  515. { "attach", 1, NULL, 'a' },
  516. { "edit", 0, NULL, 'e' },
  517. { "footer", 1, NULL, 'f' },
  518. { "header", 1, NULL, 'H' },
  519. { "from", 1, NULL, 'F' },
  520. { "to", 1, NULL, 'T' },
  521. { "cc", 1, NULL, 'C' },
  522. { "subject", 1, NULL, 'S' },
  523. { "user-agent", 0, NULL, 'U' },
  524. { "date", 0, NULL, 'D' },
  525. { NULL, 0, NULL, 0 }
  526. };
  527. setprogname(argv[0]);
  528. setlocale(LC_ALL, "");
  529. srand(time(NULL));
  530. with_useragent = with_date = do_sign = 0;
  531. headers = sb_new(0);
  532. ctx.crypto_ctx = NULL;
  533. ctx.magic_ctx = initialize_magic();
  534. ctx.attachments = NULL;
  535. ctx.att_count = 0;
  536. ctx.recipients = NULL;
  537. ctx.rcp_count = 0;
  538. ctx.footer = NULL;
  539. ctx.input = stdin;
  540. while ( (c = getopt_long(argc, argv, "hvsE:a:ef:H:F:T:C:S:UD",
  541. options, NULL)) != -1 ) {
  542. switch ( c ) {
  543. case 'h':
  544. usage(EXIT_SUCCESS);
  545. break;
  546. case '?':
  547. usage(EXIT_FAILURE);
  548. break;
  549. case 'v':
  550. info();
  551. break;
  552. case 's':
  553. do_sign = 1;
  554. break;
  555. case 'E':
  556. if ( ctx.rcp_count % 10 == 0 )
  557. ctx.recipients = xrealloc(ctx.recipients, ctx.rcp_count + 10);
  558. ctx.recipients[ctx.rcp_count++] = optarg;
  559. break;
  560. case 'a':
  561. if ( ctx.att_count % 10 == 0 )
  562. ctx.attachments = xrealloc(ctx.attachments, ctx.att_count + 10);
  563. ctx.attachments[ctx.att_count++] = optarg;
  564. break;
  565. case 'e':
  566. ctx.input = NULL;
  567. break;
  568. case 'f':
  569. ctx.footer = optarg;
  570. break;
  571. case 'H':
  572. sb_addf(headers, "%s\r\n", optarg);
  573. break;
  574. case 'F':
  575. sb_addf(headers, "From: %s\r\n", optarg);
  576. break;
  577. case 'T':
  578. sb_addf(headers, "To: %s\r\n", optarg);
  579. break;
  580. case 'C':
  581. sb_addf(headers, "Cc: %s\r\n", optarg);
  582. break;
  583. case 'S':
  584. sb_addf(headers, "Subject: %s\r\n", optarg);
  585. break;
  586. case 'U':
  587. with_useragent = 1;
  588. break;
  589. case 'D':
  590. with_date = 1;
  591. break;
  592. }
  593. }
  594. if ( do_sign && ctx.rcp_count )
  595. errx(EXIT_FAILURE, "encrypt and sign is not currently supported");
  596. if ( do_sign || ctx.rcp_count )
  597. ctx.crypto_ctx = initialize_gpgme();
  598. /* Generate automatic headers */
  599. if ( with_date )
  600. sb_addf(headers, "Date: %s\r\n", rfc2822_date());
  601. if ( with_useragent )
  602. sb_addf(headers, "User-Agent: fmail %s\r\n", VERSION);
  603. /* Fire editor instead of reading from stdin? */
  604. if ( ! ctx.input )
  605. ctx.input = read_input_from_editor();
  606. /* Read user-provided headers. */
  607. read_headers(ctx.input, headers);
  608. /* Write all headers. */
  609. fprintf(stdout, "%s", sb_get(headers));
  610. if ( do_sign || ctx.rcp_count ) {
  611. FILE *tmp = tmpfile();
  612. process_text_body(&ctx, tmp);
  613. fseek(tmp, 0, SEEK_SET);
  614. if ( do_sign )
  615. sign_stream(ctx.crypto_ctx, tmp, stdout);
  616. else
  617. encrypt_stream(ctx.crypto_ctx, tmp, stdout, ctx.recipients, ctx.rcp_count);
  618. fclose(tmp);
  619. gpgme_release(ctx.crypto_ctx);
  620. }
  621. else
  622. process_text_body(&ctx, stdout);
  623. fclose(ctx.input);
  624. if ( ctx.attachments )
  625. free(ctx.attachments);
  626. if ( ctx.recipients )
  627. free(ctx.recipients);
  628. magic_close(ctx.magic_ctx);
  629. return EXIT_SUCCESS;
  630. }