2 ** spost.c -- feed messages to sendmail
4 ** This is a simpler, faster, replacement for "post" for use
5 ** when "sendmail" is the transport system.
7 ** This code is Copyright (c) 2002, by the authors of nmh. See the
8 ** COPYRIGHT file in the root directory of the nmh distribution for
9 ** complete copyright information.
14 #include <h/addrsbr.h>
15 #include <h/aliasbr.h>
16 #include <h/dropsbr.h>
20 #define MAX_SM_FIELD 1476 /* < largest hdr field sendmail will accept */
22 struct swit switches[] = {
32 { "alias aliasfile", 0 },
42 { "dist", -4 }, /* interface from dist */
44 { "library directory", -7 },
49 /* flags for headers->flags */
50 #define HNOP 0x0000 /* just used to keep .set around */
51 #define HBAD 0x0001 /* bad header - don't let it through */
52 #define HADR 0x0002 /* header has an address field */
53 #define HSUB 0x0004 /* Subject: header */
54 #define HTRY 0x0008 /* try to send to addrs on header */
55 #define HBCC 0x0010 /* don't output this header */
56 #define HFCC 0x0020 /* FCC: type header */
57 #define HIGN 0x0040 /* ignore this header */
59 /* flags for headers->set */
60 #define MFRM 0x0001 /* we've seen a From: */
61 #define MDAT 0x0002 /* we've seen a Date: */
62 #define MRFM 0x0004 /* we've seen a Resent-From: */
63 #define MVIS 0x0008 /* we've seen sighted addrs */
64 #define MINV 0x0010 /* we've seen blind addrs */
65 #define MRDT 0x0020 /* we've seen a Resent-Date: */
73 static struct headers NHeaders[] = {
74 { "Return-Path", HBAD, 0 },
75 { "Received", HBAD, 0 },
76 { "Reply-To", HADR, 0 },
77 { "From", HADR, MFRM },
78 { "Sender", HADR|HBAD, 0 },
79 { "Date", HNOP, MDAT },
80 { "Subject", HSUB, 0 },
81 { "To", HADR|HTRY, MVIS },
82 { "Cc", HADR|HTRY, MVIS },
83 { "Bcc", HADR|HTRY|HBCC, MINV },
84 { "Message-Id", HBAD, 0 },
86 { "Envelope-From", HIGN, 0 },
90 static struct headers RHeaders[] = {
91 { "Resent-Reply-To", HADR, 0 },
92 { "Resent-From", HADR, MRFM },
93 { "Resent-Sender", HADR|HBAD, 0 },
94 { "Resent-Date", HNOP, MRDT },
95 { "Resent-Subject", HSUB, 0 },
96 { "Resent-To", HADR|HTRY, MVIS },
97 { "Resent-Cc", HADR|HTRY, MVIS },
98 { "Resent-Bcc", HADR|HTRY|HBCC, MINV },
99 { "Resent-Message-Id", HBAD, 0 },
100 { "Resent-Fcc", HFCC, 0 },
101 { "Reply-To", HADR, 0 },
103 { "Envelope-From", HIGN, 0 },
108 static int badmsg = 0; /* message has bad semantics */
109 static int verbose = 0; /* spell it out */
110 static int debug = 0; /* debugging post */
111 static int watch = 0; /* watch the delivery process */
112 static int aliasflg = 0; /* if going to process aliases */
114 static unsigned msgflags = 0; /* what we've seen */
122 static char *subject = NULL; /* the subject field for BCC'ing */
123 static char fccs[BUFSIZ] = "";
124 struct mailname *bccs = NULL; /* list of the bcc recipients */
126 static struct headers *hdrtab; /* table for the message we're doing */
127 static FILE *out; /* output (temp) file */
132 static void putfmt(char *, char *, FILE *);
133 static void finish_headers(FILE *);
134 static int get_header(char *, struct headers *);
135 static void putadr(char *, struct mailname *);
136 static int putone(char *, int, int);
137 static void process_fcc(char *);
138 static void fcc(char *, char *);
139 static void process_bccs(char *);
143 main(int argc, char **argv)
146 char *cp, *msg = NULL, **argp, **arguments;
147 char *sargv[16], buf[BUFSIZ], name[NAMESZ];
151 setlocale(LC_ALL, "");
153 invo_name = mhbasename(argv[0]);
155 /* foil search of user profile/context */
156 if (context_foil(NULL) == -1)
159 arguments = getarguments(invo_name, argc, argv, 0);
162 while ((cp = *argp++)) {
164 switch (smatch(++cp, switches)) {
166 ambigsw(cp, switches);
169 adios(NULL, "-%s unknown", cp);
172 snprintf(buf, sizeof(buf),
173 "%s [switches] file",
175 print_help(buf, switches, 1);
178 print_version(invo_name);
204 if (!(cp = *argp++) || *cp == '-')
205 adios(NULL, "missing argument to %s",
208 if ((state = alias(cp)) != AK_OK)
209 adios(NULL, "aliasing error in file %s - %s", cp, akerror(state));
216 if (!(cp = *argp++) || *cp == '-')
217 adios(NULL, "missing argument to %s",
219 /* create a minimal context */
220 if (context_foil(cp) == -1)
226 adios(NULL, "only one message at a time!");
232 adios(NULL, "usage: %s [switches] file", invo_name);
234 if ((in = fopen(msg, "r")) == NULL)
235 adios(msg, "unable to open");
241 tmpfil = getcpy(m_mktemp2("/tmp/", invo_name, NULL, &out));
244 hdrtab = (msgstate == normal) ? NHeaders : RHeaders;
246 for (compnum = 1, state = FLD;;) {
247 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
250 putfmt(name, buf, out);
256 while (state == FLDPLUS) {
257 state = m_getfld(state, name, buf,
261 putfmt(name, cp, out);
267 fprintf(out, "\n%s", buf);
268 while (state == BODY) {
269 state = m_getfld(state, name, buf,
281 adios(NULL, "message format error in component #%d",
285 adios(NULL, "getfld() returned %d", state);
304 *argp++ = "send-mail";
305 *argp++ = "-m"; /* send to me too */
306 *argp++ = "-t"; /* read msg for recipients */
307 *argp++ = "-i"; /* don't stop on "." */
308 if (watch || verbose) {
314 process_bccs(tmpfil);
315 if (!(msgflags & MVIS)) {
316 /* only Bcc rcpts: we're finished here */
323 ** re-open the temp file, unlink it and exec sendmail, giving it
324 ** the msg temp file as std in.
326 if (!freopen(tmpfil, "r", stdin)) {
327 adios(tmpfil, "can't reopen for sendmail");
331 execv(sendmail, sargv);
332 adios(sendmail, "can't exec");
337 /* DRAFT GENERATION */
340 putfmt(char *name, char *str, FILE *out)
346 /* remove all leading whitespace (even newlines) */
347 while (*str==' ' || *str=='\t' || *str=='\n') {
351 if ((i = get_header(name, hdrtab)) == NOTOK) {
352 /* no header we would care for: push it through */
353 fprintf(out, "%s: %s", name, str);
356 /* it's one of the interesting headers */
359 if (hdr->flags & HIGN || !*str) {
363 if (hdr->flags & HBAD) {
364 advise(NULL, "illegal header line -- %s:", name);
369 msgflags |= hdr->set;
371 if (hdr->flags & HFCC) {
376 if (hdr->flags & HBCC) {
377 struct mailname *mp = NULL;
379 /* Create list of Bcc addrs. */
380 while ((cp = getname(str))) {
381 mp = getm(cp, NULL, 0, AD_HOST, NULL);
382 mp->m_next = bccs; /* push */
388 if (aliasflg && hdr->flags & HTRY) {
390 ** This header contains address(es) that we have to do
391 ** alias expansion on. Because of the saved state in
392 ** getname we have to put all the addresses into a list.
394 struct mailname *f = NULL;
395 struct mailname *mp = NULL;
397 while ((cp = getname(str))) {
398 mp = getm(cp, NULL, 0, AD_HOST, NULL);
403 mp->m_next = f->m_next;
410 /* Now munch on the list, possibly expanding aliases */
416 ** The author(s) of spost decided that alias substitution wasn't
417 ** necessary for the non-HTRY headers. Unfortunately, one of
418 ** those headers is "From:", and having alias substitution work on
419 ** that is extremely useful for someone with a lot of POP3 email
420 ** accounts or aliases. post supports aliasing of "From:"...
422 ** Since "From:"-processing is incompletely implemented in this
423 ** unsupported and undocumented spost backend, I'm not going
424 ** to take the time to implement my new draft-From:-based email
425 ** address masquerading. If I do ever implement it here, I'd almost
426 ** certainly want to implement "From:" line alias processing as well.
427 ** -- Dan Harkless <dan-nmh@dilvish.speed.net>
430 ** Although there is no masquerading anymore in mmh, we might want
431 ** to have aliasing of From: addresses. Think about it.
432 ** -- meillo@marmaro.de 2012-02
435 if (hdr->flags & HSUB) {
436 subject = getcpy(str);
438 fprintf(out, "%s: %s", name, str);
443 ** Add yet missing headers.
446 finish_headers(FILE *out)
449 char from[BUFSIZ]; /* my network address */
450 char signature[BUFSIZ]; /* my signature */
451 char *resentstr = (msgstate == resent) ? "Resent-" : "";
453 if (!(msgflags & MDAT)) {
454 fprintf(out, "%sDate: %s\n", resentstr, dtimenow());
457 strncpy(from, getusername(), sizeof(from));
458 if ((cp = getfullname()) && *cp) {
459 snprintf(signature, sizeof(signature), "%s <%s>", cp, from);
461 snprintf(signature, sizeof(signature), "%s", from);
463 if (!(msgflags & MFRM)) {
464 fprintf(out, "%sFrom: %s\n", resentstr, signature);
466 /* In case the From: header contains multiple addresses. */
467 fprintf(out, "%sSender: %s\n", resentstr, from);
469 if (!(msgflags & MVIS)) {
470 fprintf(out, "%sBcc: Blind Distribution List: ;\n", resentstr);
474 adios(NULL, "re-format message and try again");
480 ** Return index of the requested header in the table, or NOTOK if missing.
483 get_header(char *header, struct headers *table)
487 for (h=table; h->value; h++) {
488 if (mh_strcasecmp(header, h->value)==0) {
498 ** output the address list for header "name". The address list
499 ** is a linked list of mailname structs. "nl" points to the head
500 ** of the list. Alias substitution should be done on nl.
503 putadr(char *name, struct mailname *nl)
505 struct mailname *mp, *mp2;
510 fprintf(out, "%s: ", name);
511 namelen = strlen(name) + 2;
514 for (mp = nl; mp; ) {
515 if (linepos > MAX_SM_FIELD) {
516 fprintf(out, "\n%s: ", name);
520 /* a local name - see if it's an alias */
521 cp = akvalue(mp->m_mbox);
522 if (cp == mp->m_mbox) {
523 /* wasn't an alias - use what the user typed */
524 linepos = putone(mp->m_text, linepos, namelen);
526 /* an alias - expand it */
527 while ((cp = getname(cp))) {
528 if (linepos > MAX_SM_FIELD) {
529 fprintf(out, "\n%s: ", name);
532 mp2 = getm(cp, NULL, 0, AD_HOST, NULL);
534 mp2->m_pers = getcpy(mp->m_mbox);
535 linepos = putone(adrformat(mp2), linepos, namelen);
537 linepos = putone(mp2->m_text,
545 /* not a local name - use what the user typed */
546 linepos = putone(mp->m_text, linepos, namelen);
556 putone(char *adr, int pos, int indent)
564 else if (linepos+len > OUTPUTLINELEN) {
565 fprintf(out, ",\n%*s", indent, "");
581 process_fcc(char *str)
586 if (strlen(str)+strlen(fccs) > sizeof fccs /2) {
587 adios(NULL, "Too much Fcc data");
589 /* todo: better have three states: SEPARATOR, PLUS, WORD */
590 for (cp=pp=str; *cp; cp++) {
599 if (*pp=='+' || *pp=='@') {
619 fcc(char *file, char *folders)
625 printf("%sFcc: %s\n", msgstate == resent ? "Resent-" : "",
629 if (100+strlen(file)+strlen(folders) > sizeof cmd) {
630 adios(NULL, "Too much Fcc data");
632 /* hack: read from /dev/null and refile(1) won't question us */
633 snprintf(cmd, sizeof cmd, "</dev/null refile -link -file '%s' %s",
635 status = system(cmd);
637 fprintf(stderr, "Skipped %sFcc %s: unable to system().\n",
638 msgstate == resent ? "Resent-" : "", folders);
639 } else if (status != 0) {
640 fprintf(stderr, "%sFcc %s: Problems occured.\n",
641 msgstate == resent ? "Resent-" : "", folders);
649 process_bccs(char *origmsg)
651 char *bccdraft = NULL;
653 struct mailname *mp = NULL;
656 for (mp=bccs; mp; mp=mp->m_next) {
658 ** Note: This draft file will be left existing by send(1),
659 ** although renamed with backup prefix.
660 ** TODO: We should have it removed eventually.
662 bccdraft = getcpy(m_mktemp2("/tmp/", invo_name, NULL, &out));
663 fprintf(out, "To: %s\n", mp->m_text);
664 fprintf(out, "Subject: [BCC] %s", subject ? subject : "");
665 fprintf(out, "%s: %s\n", attach_hdr, origmsg);
666 fprintf(out, "------------\n");
669 snprintf(buf, sizeof buf, "send %s", bccdraft);
670 if (system(buf) != 0) {
671 admonish(invo_name, "Problems to send Bcc to %s",
675 /* TODO: unlink renamed bcc draft after send(1) */