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};
72 char fntmptph
[80 + FMT_ULONG
* 2];
73 char fnnewtph
[80 + FMT_ULONG
* 2];
74 void tryunlinktmp() { unlink(fntmptph
); }
75 void sigalrm() { tryunlinktmp(); _exit(3); }
77 void maildir_child(dir
)
90 sig_alarmcatch(sigalrm
);
91 if (chdir(dir
) == -1) { if (error_temp(errno
)) _exit(1); _exit(2); }
94 gethostname(host
,sizeof(host
));
95 for (loop
= 0;;++loop
)
99 s
+= fmt_str(s
,"tmp/");
100 s
+= fmt_ulong(s
,time
); *s
++ = '.';
101 s
+= fmt_ulong(s
,pid
); *s
++ = '.';
102 s
+= fmt_strn(s
,host
,sizeof(host
)); *s
++ = 0;
103 if (stat(fntmptph
,&st
) == -1) if (errno
== error_noent
) break;
104 /* really should never get to this point */
105 if (loop
== 2) _exit(1);
108 str_copy(fnnewtph
,fntmptph
);
109 byte_copy(fnnewtph
,3,"new");
112 fd
= open_excl(fntmptph
);
113 if (fd
== -1) _exit(1);
115 substdio_fdbuf(&ss
,read
,0,buf
,sizeof(buf
));
116 substdio_fdbuf(&ssout
,write
,fd
,outbuf
,sizeof(outbuf
));
117 if (substdio_put(&ssout
,rpline
.s
,rpline
.len
) == -1) goto fail
;
118 if (substdio_put(&ssout
,dtline
.s
,dtline
.len
) == -1) goto fail
;
120 switch(substdio_copy(&ssout
,&ss
))
122 case -2: tryunlinktmp(); _exit(4);
126 if (substdio_flush(&ssout
) == -1) goto fail
;
127 if (fsync(fd
) == -1) goto fail
;
128 if (close(fd
) == -1) goto fail
; /* NFS dorks */
130 if (link(fntmptph
,fnnewtph
) == -1) goto fail
;
131 /* if it was error_exist, almost certainly successful; i hate NFS */
132 tryunlinktmp(); _exit(0);
134 fail
: tryunlinktmp(); _exit(1);
137 /* end child process */
145 if (seek_begin(0) == -1) temp_rewind();
147 switch(child
= fork())
156 wait_pid(&wstat
,child
);
157 if (wait_crashed(wstat
))
159 switch(wait_exitcode(wstat
))
162 case 2: strerr_die1x(111,"Unable to chdir to maildir. (#4.2.1)");
163 case 3: strerr_die1x(111,"Timeout on maildir delivery. (#4.3.0)");
164 case 4: strerr_die1x(111,"Unable to read message. (#4.3.0)");
165 default: strerr_die1x(111,"Temporary error on maildir delivery. (#4.3.0)");
179 if (seek_begin(0) == -1) temp_rewind();
181 fd
= open_append(fn
);
183 strerr_die5x(111,"Unable to open ",fn
,": ",error_str(errno
),". (#4.2.1)");
185 sig_alarmcatch(temp_slowlock
);
187 flaglocked
= (lock_ex(fd
) != -1);
194 substdio_fdbuf(&ss
,read
,0,buf
,sizeof(buf
));
195 substdio_fdbuf(&ssout
,write
,fd
,outbuf
,sizeof(outbuf
));
196 if (substdio_put(&ssout
,ufline
.s
,ufline
.len
)) goto writeerrs
;
197 if (substdio_put(&ssout
,rpline
.s
,rpline
.len
)) goto writeerrs
;
198 if (substdio_put(&ssout
,dtline
.s
,dtline
.len
)) goto writeerrs
;
201 if (getln(&ss
,&messline
,&match
,'\n') != 0)
203 strerr_warn3("Unable to read message: ",error_str(errno
),". (#4.3.0)",0);
204 if (flaglocked
) seek_trunc(fd
,pos
); close(fd
);
207 if (!match
&& !messline
.len
) break;
208 if (gfrom(messline
.s
,messline
.len
))
209 if (substdio_bput(&ssout
,">",1)) goto writeerrs
;
210 if (substdio_bput(&ssout
,messline
.s
,messline
.len
)) goto writeerrs
;
213 if (substdio_bputs(&ssout
,"\n")) goto writeerrs
;
217 if (substdio_bputs(&ssout
,"\n")) goto writeerrs
;
218 if (substdio_flush(&ssout
)) goto writeerrs
;
219 if (fsync(fd
) == -1) goto writeerrs
;
224 strerr_warn5("Unable to write ",fn
,": ",error_str(errno
),". (#4.3.0)",0);
225 if (flaglocked
) seek_trunc(fd
,pos
);
230 void mailprogram(prog
)
237 if (seek_begin(0) == -1) temp_rewind();
239 switch(child
= fork())
244 args
[0] = "/bin/sh"; args
[1] = "-c"; args
[2] = prog
; args
[3] = 0;
247 strerr_die3x(111,"Unable to run /bin/sh: ",error_str(errno
),". (#4.3.0)");
250 wait_pid(&wstat
,child
);
251 if (wait_crashed(wstat
))
253 switch(wait_exitcode(wstat
))
256 case 64: case 65: case 70: case 76: case 77: case 78: case 112: _exit(100);
258 case 99: flag99
= 1; break;
263 unsigned long mailforward_qp
= 0;
265 void mailforward(recips
)
273 if (seek_begin(0) == -1) temp_rewind();
274 substdio_fdbuf(&ss
,read
,0,buf
,sizeof(buf
));
276 if (qmail_open(&qqt
) == -1) temp_fork();
277 mailforward_qp
= qmail_qp(&qqt
);
278 qmail_put(&qqt
,dtline
.s
,dtline
.len
);
281 if (getln(&ss
,&messline
,&match
,'\n') != 0) { qmail_fail(&qqt
); break; }
282 qmail_put(&qqt
,messline
.s
,messline
.len
);
285 qmail_from(&qqt
,ueo
.s
);
286 while (*recips
) qmail_to(&qqt
,*recips
++);
287 qqx
= qmail_close(&qqt
);
289 strerr_die3x(*qqx
== 'D' ?
100 : 111,"Unable to forward message: ",qqx
+ 1,".");
297 if (seek_begin(0) == -1) temp_rewind();
298 substdio_fdbuf(&ss
,read
,0,buf
,sizeof(buf
));
301 if (getln(&ss
,&messline
,&match
,'\n') != 0) temp_read();
303 if (messline
.len
<= 1)
305 if (messline
.len
== dtline
.len
)
306 if (!str_diffn(messline
.s
,dtline
.s
,dtline
.len
))
307 strerr_die1x(100,"This message is looping: it already has my Delivered-To line. (#5.4.6)");
315 if (stat(".",&st
) == -1)
316 strerr_die3x(111,"Unable to stat home directory: ",error_str(errno
),". (#4.3.0)");
317 if (st
.st_mode
& auto_patrn
)
318 strerr_die1x(111,"Uh-oh: home directory is writable. (#4.7.0)");
319 if (st
.st_mode
& 01000)
321 strerr_die1x(111,"Home directory is sticky: user is editing his .qmail file. (#4.2.1)");
323 strerr_warn1("Warning: home directory is sticky.",0);
331 if (!stralloc_copys(&qme
,".qmail")) temp_nomem();
332 if (!stralloc_cats(&qme
,dash
)) temp_nomem();
333 if (!stralloc_cat(&qme
,&safeext
)) temp_nomem();
334 if (!stralloc_cats(&qme
,dashowner
)) temp_nomem();
335 if (!stralloc_0(&qme
)) temp_nomem();
336 if (stat(qme
.s
,&st
) == -1)
338 if (error_temp(errno
)) temp_qmail(qme
.s
);
344 int qmeexists(fd
,cutable
)
350 if (!stralloc_0(&qme
)) temp_nomem();
352 *fd
= open_read(qme
.s
);
354 if (error_temp(errno
)) temp_qmail(qme
.s
);
355 if (errno
== error_perm
) temp_qmail(qme
.s
);
356 if (errno
== error_acces
) temp_qmail(qme
.s
);
360 if (fstat(*fd
,&st
) == -1) temp_qmail(qme
.s
);
361 if ((st
.st_mode
& S_IFMT
) == S_IFREG
) {
362 if (st
.st_mode
& auto_patrn
)
363 strerr_die1x(111,"Uh-oh: .qmail file is writable. (#4.7.0)");
364 *cutable
= !!(st
.st_mode
& 0100);
372 /* "-/" "": "-/" "-/default" */
373 /* "-/" "a": "-/a" "-/default" */
374 /* "-/" "a-": "-/a-" "-/a-default" "-/default" */
375 /* "-/" "a-b": "-/a-b" "-/a-default" "-/default" */
376 /* "-/" "a-b-": "-/a-b-" "-/a-b-default" "-/a-default" "-/default" */
377 /* "-/" "a-b-c": "-/a-b-c" "-/a-b-default" "-/a-default" "-/default" */
379 void qmesearch(fd
,cutable
)
385 if (!stralloc_copys(&qme
,".qmail")) temp_nomem();
386 if (!stralloc_cats(&qme
,dash
)) temp_nomem();
387 if (!stralloc_cat(&qme
,&safeext
)) temp_nomem();
388 if (qmeexists(fd
,cutable
)) {
389 if (safeext
.len
>= 7) {
391 if (!byte_diff("default",7,safeext
.s
+ i
))
392 if (i
<= str_len(ext
)) /* paranoia */
393 if (!env_put2("DEFAULT",ext
+ i
)) temp_nomem();
398 for (i
= safeext
.len
;i
>= 0;--i
)
399 if (!i
|| (safeext
.s
[i
- 1] == '-')) {
400 if (!stralloc_copys(&qme
,".qmail")) temp_nomem();
401 if (!stralloc_cats(&qme
,dash
)) temp_nomem();
402 if (!stralloc_catb(&qme
,safeext
.s
,i
)) temp_nomem();
403 if (!stralloc_cats(&qme
,"default")) temp_nomem();
404 if (qmeexists(fd
,cutable
)) {
405 if (i
<= str_len(ext
)) /* paranoia */
406 if (!env_put2("DEFAULT",ext
+ i
)) temp_nomem();
414 unsigned long count_file
= 0;
415 unsigned long count_forward
= 0;
416 unsigned long count_program
= 0;
417 char count_buf
[FMT_ULONG
];
421 substdio_puts(subfdoutsmall
,"did ");
422 substdio_put(subfdoutsmall
,count_buf
,fmt_ulong(count_buf
,count_file
));
423 substdio_puts(subfdoutsmall
,"+");
424 substdio_put(subfdoutsmall
,count_buf
,fmt_ulong(count_buf
,count_forward
));
425 substdio_puts(subfdoutsmall
,"+");
426 substdio_put(subfdoutsmall
,count_buf
,fmt_ulong(count_buf
,count_program
));
427 substdio_puts(subfdoutsmall
,"\n");
430 substdio_puts(subfdoutsmall
,"qp ");
431 substdio_put(subfdoutsmall
,count_buf
,fmt_ulong(count_buf
,mailforward_qp
));
432 substdio_puts(subfdoutsmall
,"\n");
434 substdio_flush(subfdoutsmall
);
437 void sayit(type
,cmd
,len
)
442 substdio_puts(subfdoutsmall
,type
);
443 substdio_put(subfdoutsmall
,cmd
,len
);
444 substdio_putsflush(subfdoutsmall
,"\n");
458 datetime_sec starttime
;
465 if (!env_init()) temp_nomem();
468 while ((opt
= getopt(argc
,argv
,"nN")) != opteof
)
471 case 'n': flagdoit
= 0; break;
472 case 'N': flagdoit
= 1; break;
479 if (!(user
= *argv
++)) usage();
480 if (!(homedir
= *argv
++)) usage();
481 if (!(local
= *argv
++)) usage();
482 if (!(dash
= *argv
++)) usage();
483 if (!(ext
= *argv
++)) usage();
484 if (!(host
= *argv
++)) usage();
485 if (!(sender
= *argv
++)) usage();
486 if (!(aliasempty
= *argv
++)) usage();
489 if (homedir
[0] != '/') usage();
490 if (chdir(homedir
) == -1)
491 strerr_die5x(111,"Unable to switch to ",homedir
,": ",error_str(errno
),". (#4.3.0)");
494 if (!env_put2("HOST",host
)) temp_nomem();
495 if (!env_put2("HOME",homedir
)) temp_nomem();
496 if (!env_put2("USER",user
)) temp_nomem();
497 if (!env_put2("LOCAL",local
)) temp_nomem();
499 if (!stralloc_copys(&envrecip
,local
)) temp_nomem();
500 if (!stralloc_cats(&envrecip
,"@")) temp_nomem();
501 if (!stralloc_cats(&envrecip
,host
)) temp_nomem();
503 if (!stralloc_copy(&foo
,&envrecip
)) temp_nomem();
504 if (!stralloc_0(&foo
)) temp_nomem();
505 if (!env_put2("RECIPIENT",foo
.s
)) temp_nomem();
507 if (!stralloc_copys(&dtline
,"Delivered-To: ")) temp_nomem();
508 if (!stralloc_cat(&dtline
,&envrecip
)) temp_nomem();
509 for (i
= 0;i
< dtline
.len
;++i
) if (dtline
.s
[i
] == '\n') dtline
.s
[i
] = '_';
510 if (!stralloc_cats(&dtline
,"\n")) temp_nomem();
512 if (!stralloc_copy(&foo
,&dtline
)) temp_nomem();
513 if (!stralloc_0(&foo
)) temp_nomem();
514 if (!env_put2("DTLINE",foo
.s
)) temp_nomem();
519 if (!env_put2("SENDER",sender
)) temp_nomem();
521 if (!quote2(&foo
,sender
)) temp_nomem();
522 if (!stralloc_copys(&rpline
,"Return-Path: <")) temp_nomem();
523 if (!stralloc_cat(&rpline
,&foo
)) temp_nomem();
524 for (i
= 0;i
< rpline
.len
;++i
) if (rpline
.s
[i
] == '\n') rpline
.s
[i
] = '_';
525 if (!stralloc_cats(&rpline
,">\n")) temp_nomem();
527 if (!stralloc_copy(&foo
,&rpline
)) temp_nomem();
528 if (!stralloc_0(&foo
)) temp_nomem();
529 if (!env_put2("RPLINE",foo
.s
)) temp_nomem();
531 if (!stralloc_copys(&ufline
,"From ")) temp_nomem();
534 int len
; int i
; char ch
;
536 len
= str_len(sender
);
537 if (!stralloc_readyplus(&ufline
,len
)) temp_nomem();
538 for (i
= 0;i
< len
;++i
)
541 if ((ch
== ' ') || (ch
== '\t') || (ch
== '\n')) ch
= '-';
542 ufline
.s
[ufline
.len
+ i
] = ch
;
547 if (!stralloc_cats(&ufline
,"MAILER-DAEMON")) temp_nomem();
548 if (!stralloc_cats(&ufline
," ")) temp_nomem();
550 if (!stralloc_cats(&ufline
,myctime(starttime
))) temp_nomem();
552 if (!stralloc_copy(&foo
,&ufline
)) temp_nomem();
553 if (!stralloc_0(&foo
)) temp_nomem();
554 if (!env_put2("UFLINE",foo
.s
)) temp_nomem();
557 if (!env_put2("EXT",x
)) temp_nomem();
558 x
+= str_chr(x
,'-'); if (*x
) ++x
;
559 if (!env_put2("EXT2",x
)) temp_nomem();
560 x
+= str_chr(x
,'-'); if (*x
) ++x
;
561 if (!env_put2("EXT3",x
)) temp_nomem();
562 x
+= str_chr(x
,'-'); if (*x
) ++x
;
563 if (!env_put2("EXT4",x
)) temp_nomem();
565 if (!stralloc_copys(&safeext
,ext
)) temp_nomem();
566 case_lowerb(safeext
.s
,safeext
.len
);
567 for (i
= 0;i
< safeext
.len
;++i
)
568 if (safeext
.s
[i
] == '.')
572 i
= byte_rchr(host
,i
,'.');
573 if (!stralloc_copyb(&foo
,host
,i
)) temp_nomem();
574 if (!stralloc_0(&foo
)) temp_nomem();
575 if (!env_put2("HOST2",foo
.s
)) temp_nomem();
576 i
= byte_rchr(host
,i
,'.');
577 if (!stralloc_copyb(&foo
,host
,i
)) temp_nomem();
578 if (!stralloc_0(&foo
)) temp_nomem();
579 if (!env_put2("HOST3",foo
.s
)) temp_nomem();
580 i
= byte_rchr(host
,i
,'.');
581 if (!stralloc_copyb(&foo
,host
,i
)) temp_nomem();
582 if (!stralloc_0(&foo
)) temp_nomem();
583 if (!env_put2("HOST4",foo
.s
)) temp_nomem();
586 qmesearch(&fd
,&flagforwardonly
);
589 strerr_die1x(100,"Sorry, no mailbox here by that name. (#5.1.1)");
591 if (!stralloc_copys(&ueo
,sender
)) temp_nomem();
592 if (str_diff(sender
,""))
593 if (str_diff(sender
,"#@[]"))
594 if (qmeox("-owner") == 0)
596 if (qmeox("-owner-default") == 0)
598 if (!stralloc_copys(&ueo
,local
)) temp_nomem();
599 if (!stralloc_cats(&ueo
,"-owner-@")) temp_nomem();
600 if (!stralloc_cats(&ueo
,host
)) temp_nomem();
601 if (!stralloc_cats(&ueo
,"-@[]")) temp_nomem();
605 if (!stralloc_copys(&ueo
,local
)) temp_nomem();
606 if (!stralloc_cats(&ueo
,"-owner@")) temp_nomem();
607 if (!stralloc_cats(&ueo
,host
)) temp_nomem();
610 if (!stralloc_0(&ueo
)) temp_nomem();
611 if (!env_put2("NEWSENDER",ueo
.s
)) temp_nomem();
613 if (!stralloc_ready(&cmds
,0)) temp_nomem();
616 if (slurpclose(fd
,&cmds
,256) == -1) temp_nomem();
620 if (!stralloc_copys(&cmds
,aliasempty
)) temp_nomem();
623 if (!cmds
.len
|| (cmds
.s
[cmds
.len
- 1] != '\n'))
624 if (!stralloc_cats(&cmds
,"\n")) temp_nomem();
628 for (j
= 0;j
< cmds
.len
;++j
)
629 if (cmds
.s
[j
] == '\n')
631 switch(cmds
.s
[i
]) { case '#': case '.': case '/': case '|': break;
632 default: ++numforward
; }
636 recips
= (char **) alloc((numforward
+ 1) * sizeof(char *));
637 if (!recips
) temp_nomem();
643 for (j
= 0;j
< cmds
.len
;++j
)
644 if (cmds
.s
[j
] == '\n')
648 while ((k
> i
) && (cmds
.s
[k
- 1] == ' ') || (cmds
.s
[k
- 1] == '\t'))
654 strerr_die1x(111,"Uh-oh: first line of .qmail file is blank. (#4.2.1)");
660 if (flagforwardonly
) strerr_die1x(111,"Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)");
661 if (cmds
.s
[k
- 1] == '/')
662 if (flagdoit
) maildir(cmds
.s
+ i
);
663 else sayit("maildir ",cmds
.s
+ i
,k
- i
);
665 if (flagdoit
) mailfile(cmds
.s
+ i
);
666 else sayit("mbox ",cmds
.s
+ i
,k
- i
);
670 if (flagforwardonly
) strerr_die1x(111,"Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)");
671 if (flagdoit
) mailprogram(cmds
.s
+ i
+ 1);
672 else sayit("program ",cmds
.s
+ i
+ 1,k
- i
- 1);
675 if (str_equal(cmds
.s
+ i
+ 1,"list"))
682 if (flagdoit
) recips
[numforward
++] = cmds
.s
+ i
;
683 else sayit("forward ",cmds
.s
+ i
,k
- i
);
690 if (numforward
) if (flagdoit
)
692 recips
[numforward
] = 0;