27 #include "slurpclose.h"
30 #include "auto_patrn.h"
32 void usage() { strerr_die1x(100,"qmail-local: usage: qmail-local [ -nN ] user homedir local dash ext domain sender aliasempty"); }
34 void temp_nomem() { strerr_die1x(111,"Out of memory. (#4.3.0)"); }
35 void temp_rewind() { strerr_die1x(111,"Unable to rewind message. (#4.3.0)"); }
36 void temp_childcrashed() { strerr_die1x(111,"Aack, child crashed. (#4.3.0)"); }
37 void temp_fork() { strerr_die3x(111,"Unable to fork: ",error_str(errno),". (#4.3.0)"); }
38 void temp_read() { strerr_die3x(111,"Unable to read message: ",error_str(errno),". (#4.3.0)"); }
40 { strerr_die1x(111,"File has been locked for 30 seconds straight. (#4.3.0)"); }
41 void temp_qmail(fn) char *fn;
42 { strerr_die5x(111,"Unable to open ",fn,": ",error_str(errno),". (#4.3.0)"); }
56 stralloc safeext = {0};
57 stralloc ufline = {0};
58 stralloc rpline = {0};
59 stralloc envrecip = {0};
60 stralloc dtline = {0};
64 stralloc messline = {0};
66 stralloc qsender = {0};
67 stralloc tmpline = {0};
68 char *verhhost = (char *)0;
69 char *verhlocal = (char *)0;
70 int flagheader,flagdobody;
75 /* substitutes ##L => recipient local, ##H => recipient host if VERP sender */
76 /* returns 0 normally, -1 on out-of-memory */
79 char *cpnext,*cpafter;
81 if (!verhlocal) return 0; /* no VERP SENDER */
84 cpafter = cp + sa->len;
85 tmpline.len = 0; /* clear */
87 while (cp < cpafter && *cp++ != '#');
88 if (cp + 1 < cpafter && *cp == '#') { /* found '##' */
90 if (*cp == 'L') { /* ##L */
91 if (!stralloc_catb(&tmpline,cpnext,cp - cpnext - 2)) return -1;
94 if (!stralloc_cats(&tmpline,verhlocal)) return -1;
95 } else if (*cp == 'H') { /* ##H */
96 if (!stralloc_catb(&tmpline,cpnext,cp - cpnext - 2)) return -1;
99 if (!stralloc_cats(&tmpline,verhhost)) return -1;
103 if (tmpline.len) { /* true if we've done any substitutions */
104 if (!stralloc_catb(&tmpline,cpnext,cpafter - cpnext)) return -1;
105 if (!stralloc_copy(sa,&tmpline)) return -1;
117 char fntmptph[80 + FMT_ULONG * 2];
118 char fnnewtph[80 + FMT_ULONG * 2];
119 void tryunlinktmp() { unlink(fntmptph); }
120 void sigalrm() { tryunlinktmp(); _exit(3); }
122 void maildir_child(dir)
136 sig_alarmcatch(sigalrm);
137 if (chdir(dir) == -1) { if (error_temp(errno)) _exit(1); _exit(2); }
140 gethostname(host,sizeof(host));
141 for (loop = 0;;++loop)
145 s += fmt_str(s,"tmp/");
146 s += fmt_ulong(s,time); *s++ = '.';
147 s += fmt_ulong(s,pid); *s++ = '.';
148 s += fmt_strn(s,host,sizeof(host)); *s++ = 0;
149 if (stat(fntmptph,&st) == -1) if (errno == error_noent) break;
150 /* really should never get to this point */
151 if (loop == 2) _exit(1);
154 str_copy(fnnewtph,fntmptph);
155 byte_copy(fnnewtph,3,"new");
158 fd = open_excl(fntmptph);
159 if (fd == -1) _exit(1);
161 substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
162 substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
163 if (substdio_put(&ssout,rpline.s,rpline.len) == -1) goto fail;
164 if (substdio_put(&ssout,dtline.s,dtline.len) == -1) goto fail;
169 if (getln(&ss,&messline,&match,'\n') != 0)
170 { tryunlinktmp(); _exit(4); }
172 if (match && messline.len == 1) {
174 if (!flagdobody) verhlocal = (char *)0;
176 if (messline.s[0] == '#') { /* continue in body */
177 flagdobody = 1; /* remove leading '#' */
178 for (i = 1; i < messline.len; i++)
179 messline.s[i - 1] = messline.s[i];
180 messline.len--; /* always >= 1 from \n */
184 if (verhline(&messline) == -1) goto fail;
185 if (substdio_put(&ssout,messline.s,messline.len) == -1) goto fail;
188 if (substdio_flush(&ssout) == -1) goto fail;
189 if (fsync(fd) == -1) goto fail;
190 if (close(fd) == -1) goto fail; /* NFS dorks */
192 if (link(fntmptph,fnnewtph) == -1) goto fail;
193 /* if it was error_exist, almost certainly successful; i hate NFS */
194 tryunlinktmp(); _exit(0);
196 fail: tryunlinktmp(); _exit(1);
199 /* end child process */
207 if (seek_begin(0) == -1) temp_rewind();
209 switch(child = fork())
218 wait_pid(&wstat,child);
219 if (wait_crashed(wstat))
221 switch(wait_exitcode(wstat))
224 case 2: strerr_die1x(111,"Unable to chdir to maildir. (#4.2.1)");
225 case 3: strerr_die1x(111,"Timeout on maildir delivery. (#4.3.0)");
226 case 4: strerr_die1x(111,"Unable to read message. (#4.3.0)");
227 default: strerr_die1x(111,"Temporary error on maildir delivery. (#4.3.0)");
241 if (seek_begin(0) == -1) temp_rewind();
243 fd = open_append(fn);
245 strerr_die5x(111,"Unable to open ",fn,": ",error_str(errno),". (#4.2.1)");
247 sig_alarmcatch(temp_slowlock);
249 flaglocked = (lock_ex(fd) != -1);
256 substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
257 substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
260 if (substdio_put(&ssout,ufline.s,ufline.len)) goto writeerrs;
261 if (substdio_put(&ssout,rpline.s,rpline.len)) goto writeerrs;
262 if (substdio_put(&ssout,dtline.s,dtline.len)) goto writeerrs;
265 if (getln(&ss,&messline,&match,'\n') != 0)
267 strerr_warn3("Unable to read message: ",error_str(errno),". (#4.3.0)",0);
268 if (flaglocked) seek_trunc(fd,pos); close(fd);
271 if (!match && !messline.len) break;
272 if (gfrom(messline.s,messline.len))
273 if (substdio_bput(&ssout,">",1)) goto writeerrs;
275 if (match && messline.len == 1) {
276 if (!flagdobody) verhlocal = (char *)0;
279 if (messline.s[0] == '#') { /* continue in body */
280 flagdobody = 1; /* remove leading '#' */
281 for (i = 1; i < messline.len; i++)
282 messline.s[i - 1] = messline.s[i];
287 if (verhline(&messline) == -1) goto writeerrs;
288 if (substdio_bput(&ssout,messline.s,messline.len)) goto writeerrs;
291 if (substdio_bputs(&ssout,"\n")) goto writeerrs;
295 if (substdio_bputs(&ssout,"\n")) goto writeerrs;
296 if (substdio_flush(&ssout)) goto writeerrs;
297 if (fsync(fd) == -1) goto writeerrs;
302 strerr_warn5("Unable to write ",fn,": ",error_str(errno),". (#4.3.0)",0);
303 if (flaglocked) seek_trunc(fd,pos);
308 void mailprogram(prog)
315 if (seek_begin(0) == -1) temp_rewind();
317 switch(child = fork())
322 args[0] = "/bin/sh"; args[1] = "-c"; args[2] = prog; args[3] = 0;
325 strerr_die3x(111,"Unable to run /bin/sh: ",error_str(errno),". (#4.3.0)");
328 wait_pid(&wstat,child);
329 if (wait_crashed(wstat))
331 switch(wait_exitcode(wstat))
334 case 64: case 65: case 70: case 76: case 77: case 78: case 112: _exit(100);
336 case 99: flag99 = 1; break;
341 unsigned long mailforward_qp = 0;
343 void mailforward(recips)
351 if (seek_begin(0) == -1) temp_rewind();
352 substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
354 if (qmail_open(&qqt) == -1) temp_fork();
355 mailforward_qp = qmail_qp(&qqt);
356 qmail_put(&qqt,dtline.s,dtline.len);
360 if (getln(&ss,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; }
362 if (match && messline.len == 1) {
364 if (!flagdobody) verhlocal = (char *)0;
366 if (messline.s[0] == '#') { /* continue in body */
367 flagdobody = 1; /* remove leading '#' */
368 for (i = 1; i < messline.len; i++)
369 messline.s[i - 1] = messline.s[i];
374 if (verhline(&messline) == -1) { qmail_fail(&qqt); break; }
375 qmail_put(&qqt,messline.s,messline.len);
378 qmail_from(&qqt,ueo.s);
379 while (*recips) qmail_to(&qqt,*recips++);
380 qqx = qmail_close(&qqt);
382 strerr_die3x(*qqx == 'D' ? 100 : 111,"Unable to forward message: ",qqx + 1,".");
390 if (seek_begin(0) == -1) temp_rewind();
391 substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
394 if (getln(&ss,&messline,&match,'\n') != 0) temp_read();
396 if (messline.len <= 1)
398 if (messline.len == dtline.len)
399 if (!str_diffn(messline.s,dtline.s,dtline.len))
400 strerr_die1x(100,"This message is looping: it already has my Delivered-To line. (#5.4.6)");
408 if (stat(".",&st) == -1)
409 strerr_die3x(111,"Unable to stat home directory: ",error_str(errno),". (#4.3.0)");
410 if (st.st_mode & auto_patrn)
411 strerr_die1x(111,"Uh-oh: home directory is writable. (#4.7.0)");
412 if (st.st_mode & 01000)
414 strerr_die1x(111,"Home directory is sticky: user is editing his .qmail file. (#4.2.1)");
416 strerr_warn1("Warning: home directory is sticky.",0);
424 if (!stralloc_copys(&qme,".qmail")) temp_nomem();
425 if (!stralloc_cats(&qme,dash)) temp_nomem();
426 if (!stralloc_cat(&qme,&safeext)) temp_nomem();
427 if (!stralloc_cats(&qme,dashowner)) temp_nomem();
428 if (!stralloc_0(&qme)) temp_nomem();
429 if (stat(qme.s,&st) == -1)
431 if (error_temp(errno)) temp_qmail(qme.s);
437 int qmeexists(fd,cutable)
443 if (!stralloc_0(&qme)) temp_nomem();
445 *fd = open_read(qme.s);
447 if (error_temp(errno)) temp_qmail(qme.s);
448 if (errno == error_perm) temp_qmail(qme.s);
449 if (errno == error_acces) temp_qmail(qme.s);
453 if (fstat(*fd,&st) == -1) temp_qmail(qme.s);
454 if ((st.st_mode & S_IFMT) == S_IFREG) {
455 if (st.st_mode & auto_patrn)
456 strerr_die1x(111,"Uh-oh: .qmail file is writable. (#4.7.0)");
457 *cutable = !!(st.st_mode & 0100);
465 /* "-/" "": "-/" "-/default" */
466 /* "-/" "a": "-/a" "-/default" */
467 /* "-/" "a-": "-/a-" "-/a-default" "-/default" */
468 /* "-/" "a-b": "-/a-b" "-/a-default" "-/default" */
469 /* "-/" "a-b-": "-/a-b-" "-/a-b-default" "-/a-default" "-/default" */
470 /* "-/" "a-b-c": "-/a-b-c" "-/a-b-default" "-/a-default" "-/default" */
472 void qmesearch(fd,cutable)
478 if (!stralloc_copys(&qme,".qmail")) temp_nomem();
479 if (!stralloc_cats(&qme,dash)) temp_nomem();
480 if (!stralloc_cat(&qme,&safeext)) temp_nomem();
481 if (qmeexists(fd,cutable)) {
482 if (safeext.len >= 7) {
484 if (!byte_diff("default",7,safeext.s + i))
485 if (i <= str_len(ext)) /* paranoia */
486 if (!env_put2("DEFAULT",ext + i)) temp_nomem();
491 for (i = safeext.len;i >= 0;--i)
492 if (!i || (safeext.s[i - 1] == '-')) {
493 if (!stralloc_copys(&qme,".qmail")) temp_nomem();
494 if (!stralloc_cats(&qme,dash)) temp_nomem();
495 if (!stralloc_catb(&qme,safeext.s,i)) temp_nomem();
496 if (!stralloc_cats(&qme,"default")) temp_nomem();
497 if (qmeexists(fd,cutable)) {
498 if (i <= str_len(ext)) /* paranoia */
499 if (!env_put2("DEFAULT",ext + i)) temp_nomem();
507 unsigned long count_file = 0;
508 unsigned long count_forward = 0;
509 unsigned long count_program = 0;
510 char count_buf[FMT_ULONG];
514 substdio_puts(subfdoutsmall,"did ");
515 substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_file));
516 substdio_puts(subfdoutsmall,"+");
517 substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_forward));
518 substdio_puts(subfdoutsmall,"+");
519 substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_program));
520 substdio_puts(subfdoutsmall,"\n");
523 substdio_puts(subfdoutsmall,"qp ");
524 substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,mailforward_qp));
525 substdio_puts(subfdoutsmall,"\n");
527 substdio_flush(subfdoutsmall);
530 void sayit(type,cmd,len)
535 substdio_puts(subfdoutsmall,type);
536 substdio_put(subfdoutsmall,cmd,len);
537 substdio_putsflush(subfdoutsmall,"\n");
551 datetime_sec starttime;
559 if (!env_init()) temp_nomem();
562 while ((opt = getopt(argc,argv,"nN")) != opteof)
565 case 'n': flagdoit = 0; break;
566 case 'N': flagdoit = 1; break;
573 if (!(user = *argv++)) usage();
574 if (!(homedir = *argv++)) usage();
575 if (!(local = *argv++)) usage();
576 if (!(dash = *argv++)) usage();
577 if (!(ext = *argv++)) usage();
578 if (!(host = *argv++)) usage();
579 if (!(sender = *argv++)) usage();
580 if (!(aliasempty = *argv++)) usage();
583 if (homedir[0] != '/') usage();
584 if (chdir(homedir) == -1)
585 strerr_die5x(111,"Unable to switch to ",homedir,": ",error_str(errno),". (#4.3.0)");
588 if (!env_put2("HOST",host)) temp_nomem();
589 if (!env_put2("HOME",homedir)) temp_nomem();
590 if (!env_put2("USER",user)) temp_nomem();
591 if (!env_put2("LOCAL",local)) temp_nomem();
593 if (!stralloc_copys(&envrecip,local)) temp_nomem();
594 if (!stralloc_cats(&envrecip,"@")) temp_nomem();
595 if (!stralloc_cats(&envrecip,host)) temp_nomem();
597 if (!stralloc_copy(&foo,&envrecip)) temp_nomem();
598 if (!stralloc_0(&foo)) temp_nomem();
599 if (!env_put2("RECIPIENT",foo.s)) temp_nomem();
601 if (!stralloc_copys(&dtline,"Delivered-To: ")) temp_nomem();
602 if (!stralloc_cat(&dtline,&envrecip)) temp_nomem();
603 for (i = 0;i < dtline.len;++i) if (dtline.s[i] == '\n') dtline.s[i] = '_';
604 if (!stralloc_cats(&dtline,"\n")) temp_nomem();
606 if (!stralloc_copy(&foo,&dtline)) temp_nomem();
607 if (!stralloc_0(&foo)) temp_nomem();
608 if (!env_put2("DTLINE",foo.s)) temp_nomem();
613 if (!env_put2("SENDER",sender)) temp_nomem();
615 if (!quote2(&qsender,sender)) temp_nomem();
616 if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem();
617 if (!stralloc_cat(&rpline,&qsender)) temp_nomem();
618 for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_';
619 if (!stralloc_cats(&rpline,">\n")) temp_nomem();
621 if (!stralloc_copy(&foo,&rpline)) temp_nomem();
622 if (!stralloc_0(&foo)) temp_nomem();
623 if (!env_put2("RPLINE",foo.s)) temp_nomem();
625 i = byte_rchr(qsender.s,qsender.len,'@'); /* for VERH */
626 if (i != qsender.len) { /* got @ */
627 cplast = qsender.s + i;
629 if (qsender.s[i = str_rchr(qsender.s,'=')]) { /* got = */
631 cplast = qsender.s + i;
632 verhhost = qsender.s + i + 1;
633 i = str_rchr(qsender.s,'-');
634 if (qsender.s[i] == '-') {
636 if (case_starts(qsender.s + i + 1,"return-")) {
637 verhlocal = qsender.s + i + 9 + str_chr(qsender.s + i + 8,'-');
638 /* here to avoid work if not VERP */
639 /* verhhost not used if verhlocal=0 */
640 for (x = verhlocal; x < cplast; x++)
641 if (*x == '\n') *x = '_'; /* \n would ruin */
644 j = byte_rchr(qsender.s,i,'-');
652 if (!stralloc_copys(&ufline,"From ")) temp_nomem();
655 int len; int i; char ch;
657 len = str_len(sender);
658 if (!stralloc_readyplus(&ufline,len)) temp_nomem();
659 for (i = 0;i < len;++i)
662 if ((ch == ' ') || (ch == '\t') || (ch == '\n')) ch = '-';
663 ufline.s[ufline.len + i] = ch;
668 if (!stralloc_cats(&ufline,"MAILER-DAEMON")) temp_nomem();
669 if (!stralloc_cats(&ufline," ")) temp_nomem();
671 if (!stralloc_cats(&ufline,myctime(starttime))) temp_nomem();
673 if (!stralloc_copy(&foo,&ufline)) temp_nomem();
674 if (!stralloc_0(&foo)) temp_nomem();
675 if (!env_put2("UFLINE",foo.s)) temp_nomem();
678 if (!env_put2("EXT",x)) temp_nomem();
679 x += str_chr(x,'-'); if (*x) ++x;
680 if (!env_put2("EXT2",x)) temp_nomem();
681 x += str_chr(x,'-'); if (*x) ++x;
682 if (!env_put2("EXT3",x)) temp_nomem();
683 x += str_chr(x,'-'); if (*x) ++x;
684 if (!env_put2("EXT4",x)) temp_nomem();
686 if (!stralloc_copys(&safeext,ext)) temp_nomem();
687 case_lowerb(safeext.s,safeext.len);
688 for (i = 0;i < safeext.len;++i)
689 if (safeext.s[i] == '.')
693 i = byte_rchr(host,i,'.');
694 if (!stralloc_copyb(&foo,host,i)) temp_nomem();
695 if (!stralloc_0(&foo)) temp_nomem();
696 if (!env_put2("HOST2",foo.s)) temp_nomem();
697 i = byte_rchr(host,i,'.');
698 if (!stralloc_copyb(&foo,host,i)) temp_nomem();
699 if (!stralloc_0(&foo)) temp_nomem();
700 if (!env_put2("HOST3",foo.s)) temp_nomem();
701 i = byte_rchr(host,i,'.');
702 if (!stralloc_copyb(&foo,host,i)) temp_nomem();
703 if (!stralloc_0(&foo)) temp_nomem();
704 if (!env_put2("HOST4",foo.s)) temp_nomem();
707 qmesearch(&fd,&flagforwardonly);
710 strerr_die1x(100,"Sorry, no mailbox here by that name. (#5.1.1)");
712 if (!stralloc_copys(&ueo,sender)) temp_nomem();
713 if (str_diff(sender,""))
714 if (str_diff(sender,"#@[]"))
715 if (qmeox("-owner") == 0)
717 if (qmeox("-owner-default") == 0)
719 if (!stralloc_copys(&ueo,local)) temp_nomem();
720 if (!stralloc_cats(&ueo,"-owner-@")) temp_nomem();
721 if (!stralloc_cats(&ueo,host)) temp_nomem();
722 if (!stralloc_cats(&ueo,"-@[]")) temp_nomem();
726 if (!stralloc_copys(&ueo,local)) temp_nomem();
727 if (!stralloc_cats(&ueo,"-owner@")) temp_nomem();
728 if (!stralloc_cats(&ueo,host)) temp_nomem();
731 if (!stralloc_0(&ueo)) temp_nomem();
732 if (!env_put2("NEWSENDER",ueo.s)) temp_nomem();
734 if (!stralloc_ready(&cmds,0)) temp_nomem();
737 if (slurpclose(fd,&cmds,256) == -1) temp_nomem();
741 if (!stralloc_copys(&cmds,aliasempty)) temp_nomem();
744 if (!cmds.len || (cmds.s[cmds.len - 1] != '\n'))
745 if (!stralloc_cats(&cmds,"\n")) temp_nomem();
749 for (j = 0;j < cmds.len;++j)
750 if (cmds.s[j] == '\n')
752 switch(cmds.s[i]) { case '#': case '.': case '/': case '|': break;
753 default: ++numforward; }
757 recips = (char **) alloc((numforward + 1) * sizeof(char *));
758 if (!recips) temp_nomem();
764 for (j = 0;j < cmds.len;++j)
765 if (cmds.s[j] == '\n')
769 while ((k > i) && (cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t'))
775 strerr_die1x(111,"Uh-oh: first line of .qmail file is blank. (#4.2.1)");
781 if (flagforwardonly) strerr_die1x(111,"Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)");
782 if (cmds.s[k - 1] == '/')
783 if (flagdoit) maildir(cmds.s + i);
784 else sayit("maildir ",cmds.s + i,k - i);
786 if (flagdoit) mailfile(cmds.s + i);
787 else sayit("mbox ",cmds.s + i,k - i);
791 if (flagforwardonly) strerr_die1x(111,"Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)");
792 if (flagdoit) mailprogram(cmds.s + i + 1);
793 else sayit("program ",cmds.s + i + 1,k - i - 1);
796 if (str_equal(cmds.s + i + 1,"list"))
803 if (flagdoit) recips[numforward++] = cmds.s + i;
804 else sayit("forward ",cmds.s + i,k - i);
811 if (numforward) if (flagdoit)
813 recips[numforward] = 0;