1*ef2e27a1Sclaudio /* $OpenBSD: enqueue.c,v 1.126 2024/11/21 13:26:25 claudio Exp $ */ 2f607a12cSgilles 3f607a12cSgilles /* 483d9e0c8Sjacekm * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> 5195a632dSjacekm * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 665c4fdfbSgilles * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> 7f607a12cSgilles * 8f607a12cSgilles * Permission to use, copy, modify, and distribute this software for any 9f607a12cSgilles * purpose with or without fee is hereby granted, provided that the above 10f607a12cSgilles * copyright notice and this permission notice appear in all copies. 11f607a12cSgilles * 12f607a12cSgilles * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13f607a12cSgilles * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14f607a12cSgilles * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15f607a12cSgilles * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1683d9e0c8Sjacekm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 1783d9e0c8Sjacekm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 1883d9e0c8Sjacekm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19f607a12cSgilles */ 20f607a12cSgilles 21f607a12cSgilles #include <ctype.h> 22f607a12cSgilles #include <err.h> 2392b2d8b8Shenning #include <errno.h> 24f607a12cSgilles #include <pwd.h> 25f607a12cSgilles #include <stdlib.h> 26f607a12cSgilles #include <string.h> 270dcffd0dSop #include <time.h> 28f607a12cSgilles #include <unistd.h> 29f607a12cSgilles 30f607a12cSgilles #include "smtpd.h" 31f607a12cSgilles 32f607a12cSgilles extern struct imsgbuf *ibuf; 33f607a12cSgilles 3483d9e0c8Sjacekm void usage(void); 35be925435Sgilles static void build_from(char *, struct passwd *); 36be925435Sgilles static int parse_message(FILE *, int, int, FILE *); 37be925435Sgilles static void parse_addr(char *, size_t, int); 38be925435Sgilles static void parse_addr_terminal(int); 39be925435Sgilles static char *qualify_addr(char *); 40be925435Sgilles static void rcpt_add(char *); 41be925435Sgilles static int open_connection(void); 42ec5f626bSgilles static int get_responses(FILE *, int); 43ceeebefeSbenno static int send_line(FILE *, int, char *, ...) 44ceeebefeSbenno __attribute__((__format__ (printf, 3, 4))); 458351d18bSgilles static int enqueue_offline(int, char *[], FILE *, FILE *); 46ec5f626bSgilles static int savedeadletter(struct passwd *, FILE *); 47f70d44e6Seric 488351d18bSgilles extern int srv_connected(void); 4983d9e0c8Sjacekm 5083d9e0c8Sjacekm enum headerfields { 5183d9e0c8Sjacekm HDR_NONE, 5283d9e0c8Sjacekm HDR_FROM, 5383d9e0c8Sjacekm HDR_TO, 5483d9e0c8Sjacekm HDR_CC, 5583d9e0c8Sjacekm HDR_BCC, 5683d9e0c8Sjacekm HDR_SUBJECT, 5783d9e0c8Sjacekm HDR_DATE, 5835e36fb6Sgilles HDR_MSGID, 5935e36fb6Sgilles HDR_MIME_VERSION, 6035e36fb6Sgilles HDR_CONTENT_TYPE, 6135e36fb6Sgilles HDR_CONTENT_DISPOSITION, 6235e36fb6Sgilles HDR_CONTENT_TRANSFER_ENCODING, 6335e36fb6Sgilles HDR_USER_AGENT 6483d9e0c8Sjacekm }; 6583d9e0c8Sjacekm 6683d9e0c8Sjacekm struct { 6783d9e0c8Sjacekm char *word; 6883d9e0c8Sjacekm enum headerfields type; 6983d9e0c8Sjacekm } keywords[] = { 7083d9e0c8Sjacekm { "From:", HDR_FROM }, 7183d9e0c8Sjacekm { "To:", HDR_TO }, 7283d9e0c8Sjacekm { "Cc:", HDR_CC }, 7383d9e0c8Sjacekm { "Bcc:", HDR_BCC }, 7483d9e0c8Sjacekm { "Subject:", HDR_SUBJECT }, 7583d9e0c8Sjacekm { "Date:", HDR_DATE }, 7635e36fb6Sgilles { "Message-Id:", HDR_MSGID }, 7735e36fb6Sgilles { "MIME-Version:", HDR_MIME_VERSION }, 7835e36fb6Sgilles { "Content-Type:", HDR_CONTENT_TYPE }, 7935e36fb6Sgilles { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, 8035e36fb6Sgilles { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, 8135e36fb6Sgilles { "User-Agent:", HDR_USER_AGENT }, 8283d9e0c8Sjacekm }; 8383d9e0c8Sjacekm 842ea25afeSeric #define LINESPLIT 990 8583d9e0c8Sjacekm #define SMTP_LINELEN 1000 8683d9e0c8Sjacekm #define TIMEOUTMSG "Timeout\n" 8783d9e0c8Sjacekm 8883d9e0c8Sjacekm #define WSP(c) (c == ' ' || c == '\t') 8983d9e0c8Sjacekm 9083d9e0c8Sjacekm int verbose = 0; 914c5b19cfSsunil static char host[HOST_NAME_MAX+1]; 9283d9e0c8Sjacekm char *user = NULL; 9383d9e0c8Sjacekm time_t timestamp; 9483d9e0c8Sjacekm 9583d9e0c8Sjacekm struct { 9683d9e0c8Sjacekm int fd; 9783d9e0c8Sjacekm char *from; 9883d9e0c8Sjacekm char *fromname; 9983d9e0c8Sjacekm char **rcpts; 100fe95d8d0Seric char *dsn_notify; 101fe95d8d0Seric char *dsn_ret; 102fe95d8d0Seric char *dsn_envid; 10383d9e0c8Sjacekm int rcpt_cnt; 1042ea25afeSeric int need_linesplit; 10583d9e0c8Sjacekm int saw_date; 10683d9e0c8Sjacekm int saw_msgid; 10783d9e0c8Sjacekm int saw_from; 10835e36fb6Sgilles int saw_mime_version; 10935e36fb6Sgilles int saw_content_type; 11035e36fb6Sgilles int saw_content_disposition; 11135e36fb6Sgilles int saw_content_transfer_encoding; 11235e36fb6Sgilles int saw_user_agent; 113ec5f626bSgilles int noheader; 11483d9e0c8Sjacekm } msg; 11583d9e0c8Sjacekm 11683d9e0c8Sjacekm struct { 117d2241734Schl uint quote; 118d2241734Schl uint comment; 119d2241734Schl uint esc; 120d2241734Schl uint brackets; 12183d9e0c8Sjacekm size_t wpos; 12283d9e0c8Sjacekm char buf[SMTP_LINELEN]; 12383d9e0c8Sjacekm } pstate; 12483d9e0c8Sjacekm 1252e7fd8aeSmartijn #define QP_TEST_WRAP(fp, buf, linelen, size) do { \ 1262e7fd8aeSmartijn if (((linelen) += (size)) + 1 > 76) { \ 12787cc67beSeric fprintf((fp), "=\r\n"); \ 1282e7fd8aeSmartijn if (buf[0] == '.') \ 1292e7fd8aeSmartijn fprintf((fp), "."); \ 1302e7fd8aeSmartijn (linelen) = (size); \ 1312e7fd8aeSmartijn } \ 1322e7fd8aeSmartijn } while (0) 1335bc4f8deSgilles 1342e7fd8aeSmartijn /* RFC 2045 section 6.7 */ 1352e7fd8aeSmartijn static void 1362e7fd8aeSmartijn qp_encoded_write(FILE *fp, char *buf) 1372e7fd8aeSmartijn { 1382e7fd8aeSmartijn size_t linelen = 0; 1392e7fd8aeSmartijn 1402e7fd8aeSmartijn for (;buf[0] != '\0' && buf[0] != '\n'; buf++) { 1412e7fd8aeSmartijn /* 1422e7fd8aeSmartijn * Point 3: Any TAB (HT) or SPACE characters on an encoded line 1432e7fd8aeSmartijn * MUST thus be followed on that line by a printable character. 1442e7fd8aeSmartijn * 1452e7fd8aeSmartijn * Ergo, only encode if the next character is EOL. 1462e7fd8aeSmartijn */ 1472e7fd8aeSmartijn if (buf[0] == ' ' || buf[0] == '\t') { 1482e7fd8aeSmartijn if (buf[1] == '\n') { 1492e7fd8aeSmartijn QP_TEST_WRAP(fp, buf, linelen, 3); 1500cfe015eSeric fprintf(fp, "=%2X", *buf & 0xff); 1512e7fd8aeSmartijn } else { 1522e7fd8aeSmartijn QP_TEST_WRAP(fp, buf, linelen, 1); 1535bc4f8deSgilles fprintf(fp, "%c", *buf & 0xff); 1545bc4f8deSgilles } 1552e7fd8aeSmartijn /* 1562e7fd8aeSmartijn * Point 1, with exclusion of point 2, skip EBCDIC NOTE. 1572e7fd8aeSmartijn * Do this after whitespace check, else they would match here. 1582e7fd8aeSmartijn */ 1592e7fd8aeSmartijn } else if (!((buf[0] >= 33 && buf[0] <= 60) || 1602e7fd8aeSmartijn (buf[0] >= 62 && buf[0] <= 126))) { 1612e7fd8aeSmartijn QP_TEST_WRAP(fp, buf, linelen, 3); 1620cfe015eSeric fprintf(fp, "=%2X", *buf & 0xff); 1632e7fd8aeSmartijn /* Point 2: 33 through 60 inclusive, and 62 through 126 */ 1642e7fd8aeSmartijn } else { 1652e7fd8aeSmartijn QP_TEST_WRAP(fp, buf, linelen, 1); 16635e36fb6Sgilles fprintf(fp, "%c", *buf); 16735e36fb6Sgilles } 16835e36fb6Sgilles } 16987cc67beSeric fprintf(fp, "\r\n"); 1702e7fd8aeSmartijn } 17135e36fb6Sgilles 172f607a12cSgilles int 1738351d18bSgilles enqueue(int argc, char *argv[], FILE *ofp) 174f607a12cSgilles { 175ec5f626bSgilles int i, ch, tflag = 0; 1761baa1adfSsunil char *fake_from = NULL, *buf = NULL; 177f607a12cSgilles struct passwd *pw; 178230bac0bSgilles FILE *fp = NULL, *fout; 1791baa1adfSsunil size_t sz = 0, envid_sz = 0; 1801baa1adfSsunil ssize_t len; 18135e36fb6Sgilles char *line; 182084579e0Smillert int inheaders = 1; 183f70d44e6Seric int save_argc; 184f70d44e6Seric char **save_argv; 1852a157de5Sgilles int no_getlogin = 0; 186f607a12cSgilles 187c1392a69Seric memset(&msg, 0, sizeof(msg)); 18883d9e0c8Sjacekm time(×tamp); 189f607a12cSgilles 190f70d44e6Seric save_argc = argc; 191f70d44e6Seric save_argv = argv; 192f70d44e6Seric 19305d60b42Sjacekm while ((ch = getopt(argc, argv, 194f6168539Sgilles "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { 195f607a12cSgilles switch (ch) { 196f607a12cSgilles case 'f': 19783d9e0c8Sjacekm fake_from = optarg; 198f607a12cSgilles break; 19983d9e0c8Sjacekm case 'F': 20083d9e0c8Sjacekm msg.fromname = optarg; 20183d9e0c8Sjacekm break; 202fe95d8d0Seric case 'N': 203fe95d8d0Seric msg.dsn_notify = optarg; 204fe95d8d0Seric break; 205f6168539Sgilles case 'r': 206f6168539Sgilles fake_from = optarg; 207f6168539Sgilles break; 208fe95d8d0Seric case 'R': 209fe95d8d0Seric msg.dsn_ret = optarg; 210fe95d8d0Seric break; 2112a157de5Sgilles case 'S': 2122a157de5Sgilles no_getlogin = 1; 2132a157de5Sgilles break; 21483d9e0c8Sjacekm case 't': 21583d9e0c8Sjacekm tflag = 1; 21683d9e0c8Sjacekm break; 21783d9e0c8Sjacekm case 'v': 21883d9e0c8Sjacekm verbose = 1; 21983d9e0c8Sjacekm break; 220fe95d8d0Seric case 'V': 221fe95d8d0Seric msg.dsn_envid = optarg; 222fe95d8d0Seric break; 22383d9e0c8Sjacekm /* all remaining: ignored, sendmail compat */ 22405d60b42Sjacekm case 'A': 22583d9e0c8Sjacekm case 'B': 22683d9e0c8Sjacekm case 'b': 22783d9e0c8Sjacekm case 'E': 22883d9e0c8Sjacekm case 'e': 22983d9e0c8Sjacekm case 'i': 23005d60b42Sjacekm case 'L': 23183d9e0c8Sjacekm case 'm': 2328b05dd90Sgilles case 'o': 23383d9e0c8Sjacekm case 'p': 23483d9e0c8Sjacekm case 'x': 235f607a12cSgilles break; 23605d60b42Sjacekm case 'q': 237e5b07014Sgilles /* XXX: implement "process all now" */ 238f70d44e6Seric return (EX_SOFTWARE); 239f607a12cSgilles default: 240f607a12cSgilles usage(); 241f607a12cSgilles } 242f607a12cSgilles } 243f607a12cSgilles 244f607a12cSgilles argc -= optind; 245f607a12cSgilles argv += optind; 246f607a12cSgilles 247f70d44e6Seric if (getmailname(host, sizeof(host)) == -1) 24836e884f4Ssunil errx(EX_NOHOST, "getmailname"); 2492a157de5Sgilles if (no_getlogin) { 2502a157de5Sgilles if ((pw = getpwuid(getuid())) == NULL) 2512a157de5Sgilles user = "anonymous"; 2522a157de5Sgilles if (pw != NULL) 253118c16f3Sgilles user = xstrdup(pw->pw_name); 2542a157de5Sgilles } 2552a157de5Sgilles else { 25658c9f649Smillert uid_t ruid = getuid(); 25758c9f649Smillert 25858c9f649Smillert if ((user = getlogin()) != NULL && *user != '\0') { 25958c9f649Smillert if ((pw = getpwnam(user)) == NULL || 26058c9f649Smillert (ruid != 0 && ruid != pw->pw_uid)) 26158c9f649Smillert pw = getpwuid(ruid); 26258c9f649Smillert } else if ((pw = getpwuid(ruid)) == NULL) { 26383d9e0c8Sjacekm user = "anonymous"; 26458c9f649Smillert } 265118c16f3Sgilles user = xstrdup(pw ? pw->pw_name : user); 2662a157de5Sgilles } 267f607a12cSgilles 26883d9e0c8Sjacekm build_from(fake_from, pw); 2697f5b54e4Sgilles 27083d9e0c8Sjacekm while (argc > 0) { 27183d9e0c8Sjacekm rcpt_add(argv[0]); 27283d9e0c8Sjacekm argv++; 27383d9e0c8Sjacekm argc--; 274f607a12cSgilles } 275f607a12cSgilles 276729f2a4cSmartijn if ((fp = tmpfile()) == NULL) 277729f2a4cSmartijn err(EX_UNAVAILABLE, "tmpfile"); 278729f2a4cSmartijn 279ec5f626bSgilles msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); 280e2447230Sjacekm 281e0764761Seric if (msg.rcpt_cnt == 0) 282f70d44e6Seric errx(EX_SOFTWARE, "no recipients"); 283195a632dSjacekm 284195a632dSjacekm /* init session */ 285e2447230Sjacekm rewind(fp); 286195a632dSjacekm 2878351d18bSgilles /* check if working in offline mode */ 288f70d44e6Seric /* If the server is not running, enqueue the message offline */ 289f70d44e6Seric 290ee59291bSgilles if (!srv_connected()) { 291ee59291bSgilles if (pledge("stdio", NULL) == -1) 292ee59291bSgilles err(1, "pledge"); 2934af06d43Smmcc 2948351d18bSgilles return (enqueue_offline(save_argc, save_argv, fp, ofp)); 295ee59291bSgilles } 296f70d44e6Seric 297e0764761Seric if ((msg.fd = open_connection()) == -1) 298f70d44e6Seric errx(EX_UNAVAILABLE, "server too busy"); 299195a632dSjacekm 3007cf9226eSgilles if (pledge("stdio wpath cpath", NULL) == -1) 301ee59291bSgilles err(1, "pledge"); 302ee59291bSgilles 303e0764761Seric fout = fdopen(msg.fd, "a+"); 304e0764761Seric if (fout == NULL) 305f70d44e6Seric err(EX_UNAVAILABLE, "fdopen"); 306e0764761Seric 307e0764761Seric /* 308e0764761Seric * We need to call get_responses after every command because we don't 309e0764761Seric * support PIPELINING on the server-side yet. 310e0764761Seric */ 311e0764761Seric 312e0764761Seric /* banner */ 313ec5f626bSgilles if (!get_responses(fout, 1)) 314ec5f626bSgilles goto fail; 315e0764761Seric 31687cc67beSeric if (!send_line(fout, verbose, "EHLO localhost\r\n")) 317084579e0Smillert goto fail; 318ec5f626bSgilles if (!get_responses(fout, 1)) 319ec5f626bSgilles goto fail; 320e0764761Seric 321fe95d8d0Seric if (msg.dsn_envid != NULL) 322fe95d8d0Seric envid_sz = strlen(msg.dsn_envid); 323fe95d8d0Seric 32487cc67beSeric if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n", 325fe95d8d0Seric msg.from, 326fe95d8d0Seric msg.dsn_ret ? "RET=" : "", 327fe95d8d0Seric msg.dsn_ret ? msg.dsn_ret : "", 328fe95d8d0Seric envid_sz ? "ENVID=" : "", 329084579e0Smillert envid_sz ? msg.dsn_envid : "")) 330084579e0Smillert goto fail; 331ec5f626bSgilles if (!get_responses(fout, 1)) 332ec5f626bSgilles goto fail; 333e0764761Seric 334e0764761Seric for (i = 0; i < msg.rcpt_cnt; i++) { 33587cc67beSeric if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n", 336fe95d8d0Seric msg.rcpts[i], 337fe95d8d0Seric msg.dsn_notify ? "NOTIFY=" : "", 338084579e0Smillert msg.dsn_notify ? msg.dsn_notify : "")) 339084579e0Smillert goto fail; 340ec5f626bSgilles if (!get_responses(fout, 1)) 341ec5f626bSgilles goto fail; 342e0764761Seric } 343e0764761Seric 34487cc67beSeric if (!send_line(fout, verbose, "DATA\r\n")) 345084579e0Smillert goto fail; 346ec5f626bSgilles if (!get_responses(fout, 1)) 347ec5f626bSgilles goto fail; 348195a632dSjacekm 349195a632dSjacekm /* add From */ 35087cc67beSeric if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n", 351084579e0Smillert msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", 352084579e0Smillert msg.from)) 353084579e0Smillert goto fail; 354195a632dSjacekm 355195a632dSjacekm /* add Date */ 35687cc67beSeric if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n", 357084579e0Smillert time_to_text(timestamp))) 358084579e0Smillert goto fail; 359195a632dSjacekm 3602ea25afeSeric if (msg.need_linesplit) { 36135e36fb6Sgilles /* we will always need to mime encode for long lines */ 362084579e0Smillert if (!msg.saw_mime_version && !send_line(fout, 0, 36387cc67beSeric "MIME-Version: 1.0\r\n")) 364084579e0Smillert goto fail; 365084579e0Smillert if (!msg.saw_content_type && !send_line(fout, 0, 36687cc67beSeric "Content-Type: text/plain; charset=unknown-8bit\r\n")) 367084579e0Smillert goto fail; 368084579e0Smillert if (!msg.saw_content_disposition && !send_line(fout, 0, 36987cc67beSeric "Content-Disposition: inline\r\n")) 370084579e0Smillert goto fail; 371084579e0Smillert if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, 37287cc67beSeric "Content-Transfer-Encoding: quoted-printable\r\n")) 373084579e0Smillert goto fail; 3742ea25afeSeric } 37535e36fb6Sgilles 376195a632dSjacekm /* add separating newline */ 377084579e0Smillert if (msg.noheader) { 37887cc67beSeric if (!send_line(fout, 0, "\r\n")) 379084579e0Smillert goto fail; 380084579e0Smillert inheaders = 0; 381084579e0Smillert } 382195a632dSjacekm 383e0764761Seric for (;;) { 3841baa1adfSsunil if ((len = getline(&buf, &sz, fp)) == -1) { 3851baa1adfSsunil if (feof(fp)) 386e0764761Seric break; 3871baa1adfSsunil else 3881baa1adfSsunil err(EX_UNAVAILABLE, "getline"); 3891baa1adfSsunil } 3901baa1adfSsunil 391e0764761Seric /* newlines have been normalized on first parsing */ 392e0764761Seric if (buf[len-1] != '\n') 393f70d44e6Seric errx(EX_SOFTWARE, "expect EOL"); 39487cc67beSeric len--; 395740da2c9Sjacekm 39635e36fb6Sgilles if (buf[0] == '.') { 397084579e0Smillert if (fputc('.', fout) == EOF) 398084579e0Smillert goto fail; 39935e36fb6Sgilles } 40035e36fb6Sgilles 40135e36fb6Sgilles line = buf; 40235e36fb6Sgilles 40317f785b9Sgilles if (inheaders) { 40417f785b9Sgilles if (strncasecmp("from ", line, 5) == 0) 40517f785b9Sgilles continue; 40617f785b9Sgilles if (strncasecmp("return-path: ", line, 13) == 0) 40717f785b9Sgilles continue; 40817f785b9Sgilles } 40917f785b9Sgilles 410ec5f626bSgilles if (msg.saw_content_transfer_encoding || msg.noheader || 4117bdbba2fSeric inheaders || !msg.need_linesplit) { 41287cc67beSeric if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) 413084579e0Smillert goto fail; 414a1b2432dSgilles if (inheaders && buf[0] == '\n') 415a1b2432dSgilles inheaders = 0; 41635e36fb6Sgilles continue; 41735e36fb6Sgilles } 41835e36fb6Sgilles 41935e36fb6Sgilles /* we don't have a content transfer encoding, use our default */ 4202e7fd8aeSmartijn qp_encoded_write(fout, line); 421e0764761Seric } 4221baa1adfSsunil free(buf); 42387cc67beSeric if (!send_line(fout, verbose, ".\r\n")) 424084579e0Smillert goto fail; 425ec5f626bSgilles if (!get_responses(fout, 1)) 426ec5f626bSgilles goto fail; 427f4ef9244Sjacekm 42887cc67beSeric if (!send_line(fout, verbose, "QUIT\r\n")) 429084579e0Smillert goto fail; 430ec5f626bSgilles if (!get_responses(fout, 1)) 431ec5f626bSgilles goto fail; 432e0764761Seric 433fb9a2a56Sgilles fclose(fp); 434e0764761Seric fclose(fout); 435e0764761Seric 436f70d44e6Seric exit(EX_OK); 437ec5f626bSgilles 438ec5f626bSgilles fail: 439ec5f626bSgilles if (pw) 440ec5f626bSgilles savedeadletter(pw, fp); 441ec5f626bSgilles exit(EX_SOFTWARE); 442f4ef9244Sjacekm } 443f4ef9244Sjacekm 444ec5f626bSgilles static int 445e0764761Seric get_responses(FILE *fin, int n) 446f4ef9244Sjacekm { 4471baa1adfSsunil char *buf = NULL; 4481baa1adfSsunil size_t sz = 0; 4491baa1adfSsunil ssize_t len; 4501baa1adfSsunil int e, ret = 0; 451f4ef9244Sjacekm 452e0764761Seric fflush(fin); 453ec5f626bSgilles if ((e = ferror(fin))) { 454ec5f626bSgilles warnx("ferror: %d", e); 4551baa1adfSsunil goto err; 456ec5f626bSgilles } 457e0764761Seric 458e0764761Seric while (n) { 4591baa1adfSsunil if ((len = getline(&buf, &sz, fin)) == -1) { 4601baa1adfSsunil if (ferror(fin)) { 4611baa1adfSsunil warn("getline"); 4621baa1adfSsunil goto err; 4631baa1adfSsunil } else if (feof(fin)) 464195a632dSjacekm break; 4651baa1adfSsunil else 4661baa1adfSsunil err(EX_UNAVAILABLE, "getline"); 467ec5f626bSgilles } 468e0764761Seric 469e0764761Seric /* account for \r\n linebreaks */ 470e0764761Seric if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 471e0764761Seric buf[--len - 1] = '\n'; 472e0764761Seric 473ec5f626bSgilles if (len < 4) { 474ec5f626bSgilles warnx("bad response"); 4751baa1adfSsunil goto err; 476ec5f626bSgilles } 477e0764761Seric 478e0764761Seric if (verbose) 47918427eecStodd printf("<<< %.*s", (int)len, buf); 480e0764761Seric 481e0764761Seric if (buf[3] == '-') 482e0764761Seric continue; 483ec5f626bSgilles if (buf[0] != '2' && buf[0] != '3') { 484ec5f626bSgilles warnx("command failed: %.*s", (int)len, buf); 4851baa1adfSsunil goto err; 486ec5f626bSgilles } 487e0764761Seric n--; 488195a632dSjacekm } 4891baa1adfSsunil 4901baa1adfSsunil ret = 1; 4911baa1adfSsunil err: 4921baa1adfSsunil free(buf); 4931baa1adfSsunil return ret; 494f607a12cSgilles } 495f607a12cSgilles 49618427eecStodd static int 49718427eecStodd send_line(FILE *fp, int v, char *fmt, ...) 49818427eecStodd { 499084579e0Smillert int ret = 0; 50018427eecStodd va_list ap; 50118427eecStodd 50218427eecStodd va_start(ap, fmt); 503084579e0Smillert if (vfprintf(fp, fmt, ap) >= 0) 504084579e0Smillert ret = 1; 50582614934Seric va_end(ap); 50682614934Seric 507084579e0Smillert if (ret && v) { 50882614934Seric printf(">>> "); 509084579e0Smillert va_start(ap, fmt); 510084579e0Smillert vprintf(fmt, ap); 51118427eecStodd va_end(ap); 51282614934Seric } 51382614934Seric 51418427eecStodd return (ret); 51518427eecStodd } 51618427eecStodd 517be925435Sgilles static void 51883d9e0c8Sjacekm build_from(char *fake_from, struct passwd *pw) 51983d9e0c8Sjacekm { 52083d9e0c8Sjacekm char *p; 52183d9e0c8Sjacekm 52283d9e0c8Sjacekm if (fake_from == NULL) 52383d9e0c8Sjacekm msg.from = qualify_addr(user); 52483d9e0c8Sjacekm else { 52583d9e0c8Sjacekm if (fake_from[0] == '<') { 52683d9e0c8Sjacekm if (fake_from[strlen(fake_from) - 1] != '>') 52783d9e0c8Sjacekm errx(1, "leading < but no trailing >"); 52883d9e0c8Sjacekm fake_from[strlen(fake_from) - 1] = 0; 529118c16f3Sgilles p = xstrdup(fake_from + 1); 53083d9e0c8Sjacekm 53183d9e0c8Sjacekm msg.from = qualify_addr(p); 53283d9e0c8Sjacekm free(p); 53383d9e0c8Sjacekm } else 53483d9e0c8Sjacekm msg.from = qualify_addr(fake_from); 53583d9e0c8Sjacekm } 53683d9e0c8Sjacekm 53783d9e0c8Sjacekm if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 5382b486ed1Sjacekm int len, apos; 539f607a12cSgilles 54083d9e0c8Sjacekm len = strcspn(pw->pw_gecos, ","); 5412b486ed1Sjacekm if ((p = memchr(pw->pw_gecos, '&', len))) { 5422b486ed1Sjacekm apos = p - pw->pw_gecos; 5432b486ed1Sjacekm if (asprintf(&msg.fromname, "%.*s%s%.*s", 5442b486ed1Sjacekm apos, pw->pw_gecos, 5452b486ed1Sjacekm pw->pw_name, 5462b486ed1Sjacekm len - apos - 1, p + 1) == -1) 547f607a12cSgilles err(1, NULL); 548fc3a8311Seric msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); 5492b486ed1Sjacekm } else { 5502b486ed1Sjacekm if (asprintf(&msg.fromname, "%.*s", len, 5512b486ed1Sjacekm pw->pw_gecos) == -1) 5522b486ed1Sjacekm err(1, NULL); 5532b486ed1Sjacekm } 554f607a12cSgilles } 555f607a12cSgilles } 55683d9e0c8Sjacekm 557be925435Sgilles static int 558e2447230Sjacekm parse_message(FILE *fin, int get_from, int tflag, FILE *fout) 55983d9e0c8Sjacekm { 5601baa1adfSsunil char *buf = NULL; 5611baa1adfSsunil size_t sz = 0; 5621baa1adfSsunil ssize_t len; 563d2241734Schl uint i, cur = HDR_NONE; 564d2241734Schl uint header_seen = 0, header_done = 0; 56583d9e0c8Sjacekm 566c1392a69Seric memset(&pstate, 0, sizeof(pstate)); 56783d9e0c8Sjacekm for (;;) { 5681baa1adfSsunil if ((len = getline(&buf, &sz, fin)) == -1) { 5691baa1adfSsunil if (feof(fin)) 57083d9e0c8Sjacekm break; 5711baa1adfSsunil else 5721baa1adfSsunil err(EX_UNAVAILABLE, "getline"); 5731baa1adfSsunil } 57483d9e0c8Sjacekm 57583d9e0c8Sjacekm /* account for \r\n linebreaks */ 57683d9e0c8Sjacekm if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 57783d9e0c8Sjacekm buf[--len - 1] = '\n'; 57883d9e0c8Sjacekm 57983d9e0c8Sjacekm if (len == 1 && buf[0] == '\n') /* end of header */ 58083d9e0c8Sjacekm header_done = 1; 58183d9e0c8Sjacekm 58283d9e0c8Sjacekm if (!WSP(buf[0])) { /* whitespace -> continuation */ 58383d9e0c8Sjacekm if (cur == HDR_FROM) 58483d9e0c8Sjacekm parse_addr_terminal(1); 58583d9e0c8Sjacekm if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 58683d9e0c8Sjacekm parse_addr_terminal(0); 58783d9e0c8Sjacekm cur = HDR_NONE; 58883d9e0c8Sjacekm } 58983d9e0c8Sjacekm 5902ea25afeSeric /* not really exact, if we are still in headers */ 5912ea25afeSeric if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) 5922ea25afeSeric msg.need_linesplit = 1; 5932ea25afeSeric 59483d9e0c8Sjacekm for (i = 0; !header_done && cur == HDR_NONE && 59567e51f35Seric i < nitems(keywords); i++) 5961baa1adfSsunil if ((size_t)len > strlen(keywords[i].word) && 59783d9e0c8Sjacekm !strncasecmp(buf, keywords[i].word, 59883d9e0c8Sjacekm strlen(keywords[i].word))) 59983d9e0c8Sjacekm cur = keywords[i].type; 60083d9e0c8Sjacekm 60183d9e0c8Sjacekm if (cur != HDR_NONE) 60283d9e0c8Sjacekm header_seen = 1; 60383d9e0c8Sjacekm 60483d9e0c8Sjacekm if (cur != HDR_BCC) { 605084579e0Smillert if (!send_line(fout, 0, "%.*s", (int)len, buf)) 606e2447230Sjacekm err(1, "write error"); 607084579e0Smillert if (buf[len - 1] != '\n') { 608084579e0Smillert if (fputc('\n', fout) == EOF) 609084579e0Smillert err(1, "write error"); 610084579e0Smillert } 61183d9e0c8Sjacekm } 61283d9e0c8Sjacekm 61383d9e0c8Sjacekm /* 61483d9e0c8Sjacekm * using From: as envelope sender is not sendmail compatible, 61583d9e0c8Sjacekm * but I really want it that way - maybe needs a knob 61683d9e0c8Sjacekm */ 61783d9e0c8Sjacekm if (cur == HDR_FROM) { 61883d9e0c8Sjacekm msg.saw_from++; 61983d9e0c8Sjacekm if (get_from) 62083d9e0c8Sjacekm parse_addr(buf, len, 1); 62183d9e0c8Sjacekm } 62283d9e0c8Sjacekm 62383d9e0c8Sjacekm if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 62483d9e0c8Sjacekm parse_addr(buf, len, 0); 62583d9e0c8Sjacekm 62683d9e0c8Sjacekm if (cur == HDR_DATE) 62783d9e0c8Sjacekm msg.saw_date++; 62883d9e0c8Sjacekm if (cur == HDR_MSGID) 62983d9e0c8Sjacekm msg.saw_msgid++; 63035e36fb6Sgilles if (cur == HDR_MIME_VERSION) 63135e36fb6Sgilles msg.saw_mime_version = 1; 63235e36fb6Sgilles if (cur == HDR_CONTENT_TYPE) 63335e36fb6Sgilles msg.saw_content_type = 1; 63435e36fb6Sgilles if (cur == HDR_CONTENT_DISPOSITION) 63535e36fb6Sgilles msg.saw_content_disposition = 1; 63635e36fb6Sgilles if (cur == HDR_CONTENT_TRANSFER_ENCODING) 63735e36fb6Sgilles msg.saw_content_transfer_encoding = 1; 63835e36fb6Sgilles if (cur == HDR_USER_AGENT) 63935e36fb6Sgilles msg.saw_user_agent = 1; 64083d9e0c8Sjacekm } 64183d9e0c8Sjacekm 6421baa1adfSsunil free(buf); 64383d9e0c8Sjacekm return (!header_seen); 64483d9e0c8Sjacekm } 64583d9e0c8Sjacekm 646be925435Sgilles static void 64783d9e0c8Sjacekm parse_addr(char *s, size_t len, int is_from) 64883d9e0c8Sjacekm { 64983d9e0c8Sjacekm size_t pos = 0; 65083d9e0c8Sjacekm int terminal = 0; 65183d9e0c8Sjacekm 65283d9e0c8Sjacekm /* unless this is a continuation... */ 65383d9e0c8Sjacekm if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 65483d9e0c8Sjacekm /* ... skip over everything before the ':' */ 65583d9e0c8Sjacekm for (; pos < len && s[pos] != ':'; pos++) 65683d9e0c8Sjacekm ; /* nothing */ 65783d9e0c8Sjacekm /* ... and check & reset parser state */ 65883d9e0c8Sjacekm parse_addr_terminal(is_from); 65983d9e0c8Sjacekm } 66083d9e0c8Sjacekm 66183d9e0c8Sjacekm /* skip over ':' ',' ';' and whitespace */ 66283d9e0c8Sjacekm for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 66383d9e0c8Sjacekm s[pos] == ',' || s[pos] == ';'); pos++) 66483d9e0c8Sjacekm ; /* nothing */ 66583d9e0c8Sjacekm 66683d9e0c8Sjacekm for (; pos < len; pos++) { 66783d9e0c8Sjacekm if (!pstate.esc && !pstate.quote && s[pos] == '(') 66883d9e0c8Sjacekm pstate.comment++; 66983d9e0c8Sjacekm if (!pstate.comment && !pstate.esc && s[pos] == '"') 67083d9e0c8Sjacekm pstate.quote = !pstate.quote; 67183d9e0c8Sjacekm 67283d9e0c8Sjacekm if (!pstate.comment && !pstate.quote && !pstate.esc) { 67383d9e0c8Sjacekm if (s[pos] == ':') { /* group */ 67483d9e0c8Sjacekm for (pos++; pos < len && WSP(s[pos]); pos++) 67583d9e0c8Sjacekm ; /* nothing */ 67683d9e0c8Sjacekm pstate.wpos = 0; 67783d9e0c8Sjacekm } 67883d9e0c8Sjacekm if (s[pos] == '\n' || s[pos] == '\r') 67983d9e0c8Sjacekm break; 68083d9e0c8Sjacekm if (s[pos] == ',' || s[pos] == ';') { 68183d9e0c8Sjacekm terminal = 1; 68283d9e0c8Sjacekm break; 68383d9e0c8Sjacekm } 68483d9e0c8Sjacekm if (s[pos] == '<') { 68583d9e0c8Sjacekm pstate.brackets = 1; 68683d9e0c8Sjacekm pstate.wpos = 0; 68783d9e0c8Sjacekm } 68883d9e0c8Sjacekm if (pstate.brackets && s[pos] == '>') 68983d9e0c8Sjacekm terminal = 1; 69083d9e0c8Sjacekm } 69183d9e0c8Sjacekm 69283d9e0c8Sjacekm if (!pstate.comment && !terminal && (!(!(pstate.quote || 69383d9e0c8Sjacekm pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 69483d9e0c8Sjacekm if (pstate.wpos >= sizeof(pstate.buf)) 69583d9e0c8Sjacekm errx(1, "address exceeds buffer size"); 69683d9e0c8Sjacekm pstate.buf[pstate.wpos++] = s[pos]; 69783d9e0c8Sjacekm } 69883d9e0c8Sjacekm 69983d9e0c8Sjacekm if (!pstate.quote && pstate.comment && s[pos] == ')') 70083d9e0c8Sjacekm pstate.comment--; 70183d9e0c8Sjacekm 702640f55f5Ssunil if (!pstate.esc && !pstate.comment && s[pos] == '\\') 70383d9e0c8Sjacekm pstate.esc = 1; 70483d9e0c8Sjacekm else 70583d9e0c8Sjacekm pstate.esc = 0; 70683d9e0c8Sjacekm } 70783d9e0c8Sjacekm 70883d9e0c8Sjacekm if (terminal) 70983d9e0c8Sjacekm parse_addr_terminal(is_from); 71083d9e0c8Sjacekm 71183d9e0c8Sjacekm for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 71283d9e0c8Sjacekm ; /* nothing */ 71383d9e0c8Sjacekm 71483d9e0c8Sjacekm if (pos < len) 71583d9e0c8Sjacekm parse_addr(s + pos, len - pos, is_from); 71683d9e0c8Sjacekm } 71783d9e0c8Sjacekm 718be925435Sgilles static void 71983d9e0c8Sjacekm parse_addr_terminal(int is_from) 72083d9e0c8Sjacekm { 72183d9e0c8Sjacekm if (pstate.comment || pstate.quote || pstate.esc) 72283d9e0c8Sjacekm errx(1, "syntax error in address"); 72383d9e0c8Sjacekm if (pstate.wpos) { 72483d9e0c8Sjacekm if (pstate.wpos >= sizeof(pstate.buf)) 72583d9e0c8Sjacekm errx(1, "address exceeds buffer size"); 72683d9e0c8Sjacekm pstate.buf[pstate.wpos] = '\0'; 72783d9e0c8Sjacekm if (is_from) 72883d9e0c8Sjacekm msg.from = qualify_addr(pstate.buf); 72983d9e0c8Sjacekm else 73083d9e0c8Sjacekm rcpt_add(pstate.buf); 73183d9e0c8Sjacekm pstate.wpos = 0; 73283d9e0c8Sjacekm } 73383d9e0c8Sjacekm } 73483d9e0c8Sjacekm 735be925435Sgilles static char * 73683d9e0c8Sjacekm qualify_addr(char *in) 73783d9e0c8Sjacekm { 73883d9e0c8Sjacekm char *out; 73983d9e0c8Sjacekm 7405d0beaf0Sjacekm if (strlen(in) > 0 && strchr(in, '@') == NULL) { 74183d9e0c8Sjacekm if (asprintf(&out, "%s@%s", in, host) == -1) 74283d9e0c8Sjacekm err(1, "qualify asprintf"); 74383d9e0c8Sjacekm } else 744118c16f3Sgilles out = xstrdup(in); 74583d9e0c8Sjacekm 74683d9e0c8Sjacekm return (out); 74783d9e0c8Sjacekm } 74883d9e0c8Sjacekm 749be925435Sgilles static void 75083d9e0c8Sjacekm rcpt_add(char *addr) 75183d9e0c8Sjacekm { 75283d9e0c8Sjacekm void *nrcpts; 753fc01a027Stodd char *p; 754fc01a027Stodd int n; 755fc01a027Stodd 756fc01a027Stodd n = 1; 757fc01a027Stodd p = addr; 758fc01a027Stodd while ((p = strchr(p, ',')) != NULL) { 759fc01a027Stodd n++; 760fc01a027Stodd p++; 761fc01a027Stodd } 76283d9e0c8Sjacekm 76371671d2bSderaadt if ((nrcpts = reallocarray(msg.rcpts, 76471671d2bSderaadt msg.rcpt_cnt + n, sizeof(char *))) == NULL) 76583d9e0c8Sjacekm err(1, "rcpt_add realloc"); 76683d9e0c8Sjacekm msg.rcpts = nrcpts; 767fc01a027Stodd 768fc01a027Stodd while (n--) { 769fc01a027Stodd if ((p = strchr(addr, ',')) != NULL) 770fc01a027Stodd *p++ = '\0'; 77183d9e0c8Sjacekm msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 7721c6ac251Seric if (p == NULL) 7731c6ac251Seric break; 774fc01a027Stodd addr = p; 775fc01a027Stodd } 77683d9e0c8Sjacekm } 77783d9e0c8Sjacekm 778be925435Sgilles static int 77983d9e0c8Sjacekm open_connection(void) 78083d9e0c8Sjacekm { 78183d9e0c8Sjacekm struct imsg imsg; 78283d9e0c8Sjacekm int fd; 78383d9e0c8Sjacekm int n; 78483d9e0c8Sjacekm 785aa1d5973Seric imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); 78683d9e0c8Sjacekm 787dd7efffeSclaudio if (imsgbuf_flush(ibuf) == -1) 78883d9e0c8Sjacekm err(1, "write error"); 78983d9e0c8Sjacekm 79083d9e0c8Sjacekm while (1) { 79116b0c81bSclaudio if ((n = imsgbuf_read(ibuf)) == -1) 792*ef2e27a1Sclaudio err(1, "read error"); 79383d9e0c8Sjacekm if (n == 0) 79483d9e0c8Sjacekm errx(1, "pipe closed"); 79583d9e0c8Sjacekm 79683d9e0c8Sjacekm if ((n = imsg_get(ibuf, &imsg)) == -1) 79783d9e0c8Sjacekm errx(1, "imsg_get error"); 79883d9e0c8Sjacekm if (n == 0) 79983d9e0c8Sjacekm continue; 80083d9e0c8Sjacekm 80121dda8f7Sjacekm switch (imsg.hdr.type) { 80221dda8f7Sjacekm case IMSG_CTL_OK: 80321dda8f7Sjacekm break; 80421dda8f7Sjacekm case IMSG_CTL_FAIL: 80521dda8f7Sjacekm errx(1, "server disallowed submission request"); 80621dda8f7Sjacekm default: 80721dda8f7Sjacekm errx(1, "unexpected imsg reply type"); 80821dda8f7Sjacekm } 80921dda8f7Sjacekm 810510586acSclaudio fd = imsg_get_fd(&imsg); 81183d9e0c8Sjacekm imsg_free(&imsg); 81283d9e0c8Sjacekm 81383d9e0c8Sjacekm break; 81483d9e0c8Sjacekm } 81583d9e0c8Sjacekm 81683d9e0c8Sjacekm return fd; 81783d9e0c8Sjacekm } 81883d9e0c8Sjacekm 819f70d44e6Seric static int 8208351d18bSgilles enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) 82125a5298cSjacekm { 8228351d18bSgilles int i, ch; 823299c4efeSeric 82425a5298cSjacekm for (i = 1; i < argc; i++) { 82525a5298cSjacekm if (strchr(argv[i], '|') != NULL) { 82625a5298cSjacekm warnx("%s contains illegal character", argv[i]); 8276507df71Sgilles ftruncate(fileno(ofile), 0); 828f70d44e6Seric exit(EX_SOFTWARE); 82925a5298cSjacekm } 830da01648bSmillert if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) 831da01648bSmillert goto write_error; 83225a5298cSjacekm } 83325a5298cSjacekm 834da01648bSmillert if (fputc('\n', ofile) == EOF) 835da01648bSmillert goto write_error; 83625a5298cSjacekm 837da01648bSmillert while ((ch = fgetc(ifile)) != EOF) { 838da01648bSmillert if (fputc(ch, ofile) == EOF) 839da01648bSmillert goto write_error; 84025a5298cSjacekm } 84125a5298cSjacekm 842f70d44e6Seric if (ferror(ifile)) { 84325a5298cSjacekm warn("read error"); 8446507df71Sgilles ftruncate(fileno(ofile), 0); 845f70d44e6Seric exit(EX_UNAVAILABLE); 84625a5298cSjacekm } 84725a5298cSjacekm 848da01648bSmillert if (fclose(ofile) == EOF) 849da01648bSmillert goto write_error; 85025a5298cSjacekm 851f70d44e6Seric return (EX_TEMPFAIL); 852da01648bSmillert write_error: 853da01648bSmillert warn("write error"); 854da01648bSmillert ftruncate(fileno(ofile), 0); 855da01648bSmillert exit(EX_UNAVAILABLE); 85625a5298cSjacekm } 857ec5f626bSgilles 858ec5f626bSgilles static int 859ec5f626bSgilles savedeadletter(struct passwd *pw, FILE *in) 860ec5f626bSgilles { 861953aae25Sderaadt char buffer[PATH_MAX]; 862ec5f626bSgilles FILE *fp; 8631baa1adfSsunil char *buf = NULL; 8641baa1adfSsunil size_t sz = 0; 8651baa1adfSsunil ssize_t len; 866ec5f626bSgilles 867ec5f626bSgilles (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); 868ec5f626bSgilles 869ec5f626bSgilles if (fseek(in, 0, SEEK_SET) != 0) 870ec5f626bSgilles return 0; 871ec5f626bSgilles 8724f7aa44fSgilles if ((fp = fopen(buffer, "w")) == NULL) 873ec5f626bSgilles return 0; 874ec5f626bSgilles 875ec5f626bSgilles /* add From */ 876ec5f626bSgilles if (!msg.saw_from) 877ec5f626bSgilles fprintf(fp, "From: %s%s<%s>\n", 878ec5f626bSgilles msg.fromname ? msg.fromname : "", 879ec5f626bSgilles msg.fromname ? " " : "", 880ec5f626bSgilles msg.from); 881ec5f626bSgilles 882ec5f626bSgilles /* add Date */ 883ec5f626bSgilles if (!msg.saw_date) 884ec5f626bSgilles fprintf(fp, "Date: %s\n", time_to_text(timestamp)); 885ec5f626bSgilles 886ec5f626bSgilles if (msg.need_linesplit) { 887ec5f626bSgilles /* we will always need to mime encode for long lines */ 888ec5f626bSgilles if (!msg.saw_mime_version) 889ec5f626bSgilles fprintf(fp, "MIME-Version: 1.0\n"); 890ec5f626bSgilles if (!msg.saw_content_type) 891ec5f626bSgilles fprintf(fp, "Content-Type: text/plain; " 892ec5f626bSgilles "charset=unknown-8bit\n"); 893ec5f626bSgilles if (!msg.saw_content_disposition) 894ec5f626bSgilles fprintf(fp, "Content-Disposition: inline\n"); 895ec5f626bSgilles if (!msg.saw_content_transfer_encoding) 896ec5f626bSgilles fprintf(fp, "Content-Transfer-Encoding: " 897ec5f626bSgilles "quoted-printable\n"); 898ec5f626bSgilles } 899ec5f626bSgilles 900ec5f626bSgilles /* add separating newline */ 901ec5f626bSgilles if (msg.noheader) 902ec5f626bSgilles fprintf(fp, "\n"); 903ec5f626bSgilles 9041baa1adfSsunil while ((len = getline(&buf, &sz, in)) != -1) { 905ec5f626bSgilles if (buf[len - 1] == '\n') 906ec5f626bSgilles buf[len - 1] = '\0'; 907ec5f626bSgilles fprintf(fp, "%s\n", buf); 908ec5f626bSgilles } 909ec5f626bSgilles 9101baa1adfSsunil free(buf); 911ec5f626bSgilles fprintf(fp, "\n"); 912ec5f626bSgilles fclose(fp); 913ec5f626bSgilles return 1; 914ec5f626bSgilles } 915