1*510586acSclaudio /* $OpenBSD: enqueue.c,v 1.122 2024/01/20 09:01:03 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
qp_encoded_write(FILE * fp,char * buf)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
enqueue(int argc,char * argv[],FILE * ofp)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
get_responses(FILE * fin,int n)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
send_line(FILE * fp,int v,char * fmt,...)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
build_from(char * fake_from,struct passwd * pw)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
parse_message(FILE * fin,int get_from,int tflag,FILE * fout)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
parse_addr(char * s,size_t len,int is_from)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
parse_addr_terminal(int is_from)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 *
qualify_addr(char * in)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
rcpt_add(char * addr)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
open_connection(void)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
78783d9e0c8Sjacekm while (ibuf->w.queued)
788ffb968afSkrw if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
78983d9e0c8Sjacekm err(1, "write error");
79083d9e0c8Sjacekm
79183d9e0c8Sjacekm while (1) {
792369c9387Sclaudio if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
79383d9e0c8Sjacekm errx(1, "imsg_read error");
79483d9e0c8Sjacekm if (n == 0)
79583d9e0c8Sjacekm errx(1, "pipe closed");
79683d9e0c8Sjacekm
79783d9e0c8Sjacekm if ((n = imsg_get(ibuf, &imsg)) == -1)
79883d9e0c8Sjacekm errx(1, "imsg_get error");
79983d9e0c8Sjacekm if (n == 0)
80083d9e0c8Sjacekm continue;
80183d9e0c8Sjacekm
80221dda8f7Sjacekm switch (imsg.hdr.type) {
80321dda8f7Sjacekm case IMSG_CTL_OK:
80421dda8f7Sjacekm break;
80521dda8f7Sjacekm case IMSG_CTL_FAIL:
80621dda8f7Sjacekm errx(1, "server disallowed submission request");
80721dda8f7Sjacekm default:
80821dda8f7Sjacekm errx(1, "unexpected imsg reply type");
80921dda8f7Sjacekm }
81021dda8f7Sjacekm
811*510586acSclaudio fd = imsg_get_fd(&imsg);
81283d9e0c8Sjacekm imsg_free(&imsg);
81383d9e0c8Sjacekm
81483d9e0c8Sjacekm break;
81583d9e0c8Sjacekm }
81683d9e0c8Sjacekm
81783d9e0c8Sjacekm return fd;
81883d9e0c8Sjacekm }
81983d9e0c8Sjacekm
820f70d44e6Seric static int
enqueue_offline(int argc,char * argv[],FILE * ifile,FILE * ofile)8218351d18bSgilles enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
82225a5298cSjacekm {
8238351d18bSgilles int i, ch;
824299c4efeSeric
82525a5298cSjacekm for (i = 1; i < argc; i++) {
82625a5298cSjacekm if (strchr(argv[i], '|') != NULL) {
82725a5298cSjacekm warnx("%s contains illegal character", argv[i]);
8286507df71Sgilles ftruncate(fileno(ofile), 0);
829f70d44e6Seric exit(EX_SOFTWARE);
83025a5298cSjacekm }
831da01648bSmillert if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
832da01648bSmillert goto write_error;
83325a5298cSjacekm }
83425a5298cSjacekm
835da01648bSmillert if (fputc('\n', ofile) == EOF)
836da01648bSmillert goto write_error;
83725a5298cSjacekm
838da01648bSmillert while ((ch = fgetc(ifile)) != EOF) {
839da01648bSmillert if (fputc(ch, ofile) == EOF)
840da01648bSmillert goto write_error;
84125a5298cSjacekm }
84225a5298cSjacekm
843f70d44e6Seric if (ferror(ifile)) {
84425a5298cSjacekm warn("read error");
8456507df71Sgilles ftruncate(fileno(ofile), 0);
846f70d44e6Seric exit(EX_UNAVAILABLE);
84725a5298cSjacekm }
84825a5298cSjacekm
849da01648bSmillert if (fclose(ofile) == EOF)
850da01648bSmillert goto write_error;
85125a5298cSjacekm
852f70d44e6Seric return (EX_TEMPFAIL);
853da01648bSmillert write_error:
854da01648bSmillert warn("write error");
855da01648bSmillert ftruncate(fileno(ofile), 0);
856da01648bSmillert exit(EX_UNAVAILABLE);
85725a5298cSjacekm }
858ec5f626bSgilles
859ec5f626bSgilles static int
savedeadletter(struct passwd * pw,FILE * in)860ec5f626bSgilles savedeadletter(struct passwd *pw, FILE *in)
861ec5f626bSgilles {
862953aae25Sderaadt char buffer[PATH_MAX];
863ec5f626bSgilles FILE *fp;
8641baa1adfSsunil char *buf = NULL;
8651baa1adfSsunil size_t sz = 0;
8661baa1adfSsunil ssize_t len;
867ec5f626bSgilles
868ec5f626bSgilles (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
869ec5f626bSgilles
870ec5f626bSgilles if (fseek(in, 0, SEEK_SET) != 0)
871ec5f626bSgilles return 0;
872ec5f626bSgilles
8734f7aa44fSgilles if ((fp = fopen(buffer, "w")) == NULL)
874ec5f626bSgilles return 0;
875ec5f626bSgilles
876ec5f626bSgilles /* add From */
877ec5f626bSgilles if (!msg.saw_from)
878ec5f626bSgilles fprintf(fp, "From: %s%s<%s>\n",
879ec5f626bSgilles msg.fromname ? msg.fromname : "",
880ec5f626bSgilles msg.fromname ? " " : "",
881ec5f626bSgilles msg.from);
882ec5f626bSgilles
883ec5f626bSgilles /* add Date */
884ec5f626bSgilles if (!msg.saw_date)
885ec5f626bSgilles fprintf(fp, "Date: %s\n", time_to_text(timestamp));
886ec5f626bSgilles
887ec5f626bSgilles if (msg.need_linesplit) {
888ec5f626bSgilles /* we will always need to mime encode for long lines */
889ec5f626bSgilles if (!msg.saw_mime_version)
890ec5f626bSgilles fprintf(fp, "MIME-Version: 1.0\n");
891ec5f626bSgilles if (!msg.saw_content_type)
892ec5f626bSgilles fprintf(fp, "Content-Type: text/plain; "
893ec5f626bSgilles "charset=unknown-8bit\n");
894ec5f626bSgilles if (!msg.saw_content_disposition)
895ec5f626bSgilles fprintf(fp, "Content-Disposition: inline\n");
896ec5f626bSgilles if (!msg.saw_content_transfer_encoding)
897ec5f626bSgilles fprintf(fp, "Content-Transfer-Encoding: "
898ec5f626bSgilles "quoted-printable\n");
899ec5f626bSgilles }
900ec5f626bSgilles
901ec5f626bSgilles /* add separating newline */
902ec5f626bSgilles if (msg.noheader)
903ec5f626bSgilles fprintf(fp, "\n");
904ec5f626bSgilles
9051baa1adfSsunil while ((len = getline(&buf, &sz, in)) != -1) {
906ec5f626bSgilles if (buf[len - 1] == '\n')
907ec5f626bSgilles buf[len - 1] = '\0';
908ec5f626bSgilles fprintf(fp, "%s\n", buf);
909ec5f626bSgilles }
910ec5f626bSgilles
9111baa1adfSsunil free(buf);
912ec5f626bSgilles fprintf(fp, "\n");
913ec5f626bSgilles fclose(fp);
914ec5f626bSgilles return 1;
915ec5f626bSgilles }
916