1 /*
2 ** Copyright 2001-2010 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5
6 #include "config.h"
7 #include "dbobj.h"
8 #include "liblock/config.h"
9 #include "liblock/liblock.h"
10 #include "unicode/unicode.h"
11 #include "numlib/numlib.h"
12 #include <string.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <stdio.h>
17 #include <time.h>
18 #if HAVE_LOCALE_H
19 #include <locale.h>
20 #endif
21 #include <langinfo.h>
22 #if HAVE_STRINGS_H
23 #include <strings.h>
24 #endif
25 #include <ctype.h>
26 #include "rfc822/rfc822.h"
27 #include "rfc2045/rfc2045.h"
28 #include "rfc2045/rfc2045charset.h"
29 #include <sys/types.h>
30 #include "mywait.h"
31 #include <signal.h>
32 #if HAVE_SYSEXITS_H
33 #include <sysexits.h>
34 #endif
35
36 #ifndef EX_TEMPFAIL
37 #define EX_TEMPFAIL 75
38 #endif
39
40 static const char *recips=0;
41 static const char *dbfile=0;
42 static char *charset=RFC2045CHARSET;
43 static unsigned interval=1;
44 static char *sender;
45
46 struct header {
47 struct header *next;
48 char *buf;
49 } ;
50
51 static struct header *header_list;
52
53 static struct header *extra_headers=0;
54
55
rfc2045_error(const char * str)56 void rfc2045_error(const char *str)
57 {
58 fprintf(stderr, "%s\n", str);
59 exit(1);
60 }
61
usage()62 static void usage()
63 {
64 fprintf(stderr,
65 "Usage: mailbot [ options ] [ $MAILER arg arg... ]\n"
66 "\n"
67 " -t filename - text autoresponse\n"
68 " -c charset - text MIME character set (default %s)\n"
69 " -m filename - text autoresponse with a MIME header\n"
70 " -r addr1,addr2... - any 'addr' required in a To/Cc header\n",
71 charset);
72
73 fprintf(stderr,
74 " -d $pathname - database to prevent duplicate autoresponses\n"
75 " -D x - at least 'x' days before dupes (default: 1)\n"
76 " -s subject - Subject: on autoresponses\n"
77 " -A \"Header: stuff\" - Additional header on the autoresponse\n"
78 " -M recipient - format autoresponse as a DSN from 'recipient'\n"
79 " -n - only show the resulting message, do not send it\n"
80 " $MAILER arg arg... - run $MAILER (sendmail) to mail the autoresponse\n"
81 );
82 exit(EX_TEMPFAIL);
83 }
84
read_headers(FILE * tmpfp)85 static void read_headers(FILE *tmpfp)
86 {
87 char buf[BUFSIZ];
88 struct header **lasthdr= &header_list, *prevhdr=0;
89
90 while (fgets(buf, sizeof(buf), tmpfp))
91 {
92 size_t l=strlen(buf);
93
94 if (l > 0 && buf[l-1] == '\n')
95 --l;
96 if (l > 0 && buf[l-1] == '\r')
97 --l;
98 buf[l]=0;
99
100 if (l == 0)
101 {
102 /* Eat rest of message from stdin */
103
104 while (getc(stdin) != EOF)
105 ;
106 break;
107 }
108
109 if (isspace((int)(unsigned char)buf[0]) && prevhdr)
110 {
111 if ( (prevhdr->buf=
112 realloc( prevhdr->buf,
113 strlen (prevhdr->buf)+2+strlen(buf)))
114 == NULL)
115 {
116 perror("malloc");
117 exit(EX_TEMPFAIL);
118 }
119 strcat(strcat( prevhdr->buf, "\n"), buf);
120 }
121 else
122 {
123 if ((*lasthdr=(struct header *)
124 malloc(sizeof(struct header))) == NULL ||
125 ((*lasthdr)->buf=strdup(buf)) == NULL)
126 {
127 perror("malloc");
128 exit(EX_TEMPFAIL);
129 }
130
131 prevhdr= *lasthdr;
132 lasthdr= &(*lasthdr)->next;
133 }
134 }
135
136 *lasthdr=NULL;
137 }
138
hdr(const char * hdrname)139 const char *hdr(const char *hdrname)
140 {
141 struct header *h;
142 size_t l=strlen(hdrname);
143
144 for (h=header_list; h; h=h->next)
145 {
146 if (strncasecmp(h->buf, hdrname, l) == 0 &&
147 h->buf[l] == ':')
148 {
149 const char *p=h->buf+l+1;
150
151 while (*p && isspace((int)(unsigned char)*p))
152 ++p;
153 return (p);
154 }
155 }
156
157 return ("");
158 }
159
160 /*
161 ** Get the sender's address
162 */
163
check_sender()164 static void check_sender()
165 {
166 const char *h=hdr("reply-to");
167 struct rfc822t *t;
168 struct rfc822a *a;
169
170 if (!h || !*h)
171 h=hdr("from");
172
173 if (!h || !*h)
174 exit(0);
175
176 t=rfc822t_alloc_new(h, NULL, NULL);
177
178 if (!t || !(a=rfc822a_alloc(t)))
179 {
180 perror("malloc");
181 exit(EX_TEMPFAIL);
182 }
183
184 if (a->naddrs <= 0)
185 exit (0);
186 sender=rfc822_getaddr(a, 0);
187 rfc822a_free(a);
188 rfc822t_free(t);
189
190 if (!sender || !*sender)
191 exit(0);
192 }
193
194 /*
195 ** Do not autorespond to DSNs
196 */
197
check_dsn()198 static void check_dsn()
199 {
200 static const char ct[]="multipart/report;";
201
202 const char *p=hdr("content-type");
203
204 if (strncasecmp(p, ct, sizeof(ct)-1) == 0)
205 exit(0);
206
207 p=hdr("precedence");
208
209 if (strncasecmp(p, "junk", 4) == 0 ||
210 strncasecmp(p, "bulk", 4) == 0 ||
211 strncasecmp(p, "list", 4) == 0)
212 exit(0); /* Just in case */
213
214 p=hdr("auto-submitted");
215
216 if (*p && strcmp(p, "no"))
217 exit(0);
218
219 p=hdr("list-id");
220
221 if (*p)
222 exit(0);
223 }
224
225 /*
226 ** Check for a required recipient
227 */
228
check_recips()229 static void check_recips()
230 {
231 char *buf;
232 struct rfc822t *t;
233 struct rfc822a *a;
234 struct header *h;
235
236 if (!recips || !*recips)
237 return;
238
239 buf=strdup(recips);
240 if (!buf)
241 {
242 perror("strdup");
243 exit(EX_TEMPFAIL);
244 }
245
246 for (h=header_list; h; h=h->next)
247 {
248 int i;
249
250 if (strncasecmp(h->buf, "to:", 3) &&
251 strncasecmp(h->buf, "cc:", 3))
252 continue;
253
254 t=rfc822t_alloc_new(h->buf+3, NULL, NULL);
255 if (!t || !(a=rfc822a_alloc(t)))
256 {
257 perror("malloc");
258 exit(EX_TEMPFAIL);
259 }
260
261 for (i=0; i<a->naddrs; i++)
262 {
263 char *p=rfc822_getaddr(a, i);
264 char *q;
265
266 strcpy(buf, recips);
267
268 for (q=buf; (q=strtok(q, ", ")) != 0; q=0)
269 {
270 if (p && strcasecmp(p, q) == 0)
271 {
272 free(p);
273 free(buf);
274 rfc822a_free(a);
275 rfc822t_free(t);
276 return;
277 }
278 }
279
280 free(p);
281 }
282 rfc822a_free(a);
283 rfc822t_free(t);
284 }
285 free(buf);
286 exit(0);
287 }
288
289 /*
290 ** Check the dupe database.
291 */
292
293 #ifdef DbObj
check_db()294 static void check_db()
295 {
296 char *dbname;
297 char *lockname;
298 int lockfd;
299 struct dbobj db;
300 time_t now;
301 char *sender_key, *p;
302
303 size_t val_len;
304 char *val;
305
306 if (!dbfile || !*dbfile)
307 return;
308
309 sender_key=strdup(sender);
310 dbname=malloc(strlen(dbfile)+ sizeof( "." DBNAME));
311 lockname=malloc(strlen(dbfile)+ sizeof(".lock"));
312
313 for (p=sender_key; *p; p++)
314 *p=tolower((int)(unsigned char)*p);
315
316 if (!dbname || !lockname || !sender)
317 {
318 perror("malloc");
319 exit(EX_TEMPFAIL);
320 }
321
322 strcat(strcpy(dbname, dbfile), "." DBNAME);
323 strcat(strcpy(lockname, dbfile), ".lock");
324
325 lockfd=open(lockname, O_RDWR|O_CREAT, 0666);
326
327 if (lockfd < 0 || ll_lock_ex(lockfd))
328 {
329 perror(lockname);
330 exit(EX_TEMPFAIL);
331 }
332
333 dbobj_init(&db);
334
335 if (dbobj_open(&db, dbname, "C") < 0)
336 {
337 perror(dbname);
338 exit(EX_TEMPFAIL);
339 }
340
341 time(&now);
342
343 val=dbobj_fetch(&db, sender_key, strlen(sender_key), &val_len, "");
344
345 if (val)
346 {
347 time_t t;
348
349 if (val_len >= sizeof(t))
350 {
351 memcpy(&t, val, sizeof(t));
352
353 if (t >= now - interval * 60 * 60 * 24)
354 {
355 free(val);
356 dbobj_close(&db);
357 close(lockfd);
358 exit(0);
359 }
360 }
361 free(val);
362 }
363
364 dbobj_store(&db, sender_key, strlen(sender_key),
365 (void *)&now, sizeof(now), "R");
366 dbobj_close(&db);
367 close(lockfd);
368 }
369 #endif
370
opensendmail(int argn,int argc,char ** argv)371 static void opensendmail(int argn, int argc, char **argv)
372 {
373 char **newargv;
374 int i;
375
376 if (argn >= argc)
377 {
378 static char *sendmail_argv[]={"sendmail", "-f", ""};
379
380 argn=0;
381 argc=3;
382 argv=sendmail_argv;
383 }
384
385 newargv=(char **)malloc( sizeof(char *)*(argc-argn+1));
386 if (!newargv)
387 {
388 perror("malloc");
389 exit(EX_TEMPFAIL);
390 }
391
392 for (i=0; argn+i < argc; i++)
393 newargv[i]=argv[argn+i];
394 newargv[i]=0;
395 signal(SIGCHLD, SIG_DFL);
396
397 execvp(newargv[0], newargv);
398 perror(newargv[0]);
399 exit(EX_TEMPFAIL);
400 }
401
savemessage(FILE * tmpfp)402 static struct rfc2045 *savemessage(FILE *tmpfp)
403 {
404 struct rfc2045 *rfcp=rfc2045_alloc();
405 char buf[BUFSIZ];
406 int n;
407
408 if (!rfcp)
409 {
410 perror("rfc2045_alloc");
411 exit(1);
412 }
413
414 while ((n=fread(buf, 1, sizeof(buf), stdin)) > 0)
415 {
416 if (fwrite(buf, n, 1, tmpfp) != 1)
417 {
418 perror("fwrite(tempfile)");
419 exit(1);
420 }
421
422 rfc2045_parse(rfcp, buf, n);
423 }
424
425 if (n < 0)
426 {
427 perror("tempfile");
428 exit(1);
429 }
430 return rfcp;
431 }
432
433
434 struct mimeautoreply_s {
435 struct rfc2045_mkreplyinfo info;
436 FILE *outf;
437
438 FILE *contentf;
439 };
440
mimeautoreply_write_func(const char * str,size_t cnt,void * ptr)441 static void mimeautoreply_write_func(const char *str, size_t cnt, void *ptr)
442 {
443 if (cnt &&
444 fwrite(str, cnt, 1, ((struct mimeautoreply_s *)ptr)->outf) != 1)
445 {
446 perror("tmpfile");
447 exit(1);
448 }
449 }
450
mimeautoreply_writesig_func(void * ptr)451 static void mimeautoreply_writesig_func(void *ptr)
452 {
453 }
454
mimeautoreply_myaddr_func(const char * addr,void * ptr)455 static int mimeautoreply_myaddr_func(const char *addr, void *ptr)
456 {
457 return 0;
458 }
459
copy_headers(void * ptr)460 static void copy_headers(void *ptr)
461 {
462 struct mimeautoreply_s *p=(struct mimeautoreply_s *)ptr;
463 char buf[BUFSIZ];
464
465 static const char ct[]="Content-Transfer-Encoding:";
466
467 while (fgets(buf, sizeof(buf), p->contentf) != NULL)
468 {
469 if (buf[0] == '\n')
470 break;
471
472 if (strncasecmp(buf, ct, sizeof(ct)-1) == 0)
473 continue;
474
475 mimeautoreply_write_func(buf, strlen(buf), ptr);
476
477 while (strchr(buf, '\n') == NULL)
478 {
479 if (fgets(buf, sizeof(buf), p->contentf) == NULL)
480 break;
481
482 mimeautoreply_write_func(buf, strlen(buf), ptr);
483 }
484 }
485 }
486
copy_body(void * ptr)487 static void copy_body(void *ptr)
488 {
489 struct mimeautoreply_s *p=(struct mimeautoreply_s *)ptr;
490 char buf[BUFSIZ];
491
492 while (fgets(buf, sizeof(buf), p->contentf) != NULL)
493 {
494 mimeautoreply_write_func(buf, strlen(buf), ptr);
495 }
496 }
497
main(int argc,char ** argv)498 int main(int argc, char **argv)
499 {
500 int argn;
501 FILE *tmpfp;
502 struct rfc2045 *rfcp;
503 struct mimeautoreply_s replyinfo;
504 const char *subj=0;
505 const char *txtfile=0, *mimefile=0;
506 const char *mimedsn=0;
507 int nosend=0;
508 const char *replymode="reply";
509 int replytoenvelope=0;
510 int donotquote=0;
511 const char *forwardsep="--- Forwarded message ---";
512 const char *replysalut="%F writes:";
513 const struct unicode_info *uinfo;
514
515 setlocale(LC_ALL, "");
516 charset=strdup(nl_langinfo(CODESET));
517
518 if (!charset)
519 {
520 perror("malloc");
521 exit(1);
522 }
523
524 sender=NULL;
525 for (argn=1; argn < argc; argn++)
526 {
527 char optc;
528 char *optarg;
529
530 if (argv[argn][0] != '-')
531 break;
532
533 if (strcmp(argv[argn], "--") == 0)
534 {
535 ++argn;
536 break;
537 }
538
539 optc=argv[argn][1];
540 optarg=argv[argn]+2;
541
542 if (!*optarg)
543 optarg=NULL;
544
545 switch (optc) {
546 case 'c':
547 if (!optarg && argn+1 < argc)
548 optarg=argv[++argn];
549
550 if (optarg && *optarg)
551 {
552 charset=strdup(optarg);
553
554 if (charset == NULL ||
555 unicode_find(charset) == NULL)
556 {
557 fprintf(stderr, "Unknown charset: %s\n",
558 charset);
559 exit(1);
560 }
561 }
562 continue;
563 case 't':
564 if (!optarg && argn+1 < argc)
565 optarg=argv[++argn];
566
567 txtfile=optarg;
568 continue;
569 case 'm':
570 if (!optarg && argn+1 < argc)
571 optarg=argv[++argn];
572
573 mimefile=optarg;
574 continue;
575 case 'r':
576 if (!optarg && argn+1 < argc)
577 optarg=argv[++argn];
578
579 recips=optarg;
580 continue;
581 case 'M':
582 if (!optarg && argn+1 < argc)
583 optarg=argv[++argn];
584
585 mimedsn=optarg;
586 continue;
587 case 'd':
588 if (!optarg && argn+1 < argc)
589 optarg=argv[++argn];
590
591 dbfile=optarg;
592 continue;
593 case 'e':
594 replytoenvelope=1;
595 continue;
596 case 'T':
597 if (!optarg && argn+1 < argc)
598 optarg=argv[++argn];
599
600 if (optarg && *optarg)
601 replymode=optarg;
602 continue;
603 case 'N':
604 donotquote=1;
605 continue;
606 case 'F':
607 if (!optarg && argn+1 < argc)
608 optarg=argv[++argn];
609
610 if (optarg && *optarg)
611 forwardsep=optarg;
612 continue;
613 case 'S':
614 if (!optarg && argn+1 < argc)
615 optarg=argv[++argn];
616
617 if (optarg && *optarg)
618 replysalut=optarg;
619 continue;
620 case 'D':
621 if (!optarg && argn+1 < argc)
622 optarg=argv[++argn];
623
624 interval=optarg ? atoi(optarg):1;
625 continue;
626 case 'A':
627 if (!optarg && argn+1 < argc)
628 optarg=argv[++argn];
629
630 if (optarg)
631 {
632 struct header **h;
633
634 for (h= &extra_headers; *h;
635 h= &(*h)->next)
636 ;
637
638 if ((*h=malloc(sizeof(struct header))) == 0 ||
639 ((*h)->buf=strdup(optarg)) == 0)
640 {
641 perror("malloc");
642 exit(EX_TEMPFAIL);
643 }
644 (*h)->next=0;
645 }
646 continue;
647 case 's':
648 if (!optarg && argn+1 < argc)
649 optarg=argv[++argn];
650
651 subj=optarg;
652 continue;
653
654 case 'f':
655 if (optarg && *optarg)
656 {
657 sender=strdup(optarg);
658 }
659 else
660 {
661 sender=getenv("SENDER");
662 if (!sender)
663 continue;
664 sender=strdup(sender);
665 }
666 if (sender == NULL)
667 {
668 perror("malloc");
669 exit(1);
670 }
671 continue;
672 case 'n':
673 nosend=1;
674 continue;
675 default:
676 usage();
677 }
678 }
679
680 if (!txtfile && !mimefile)
681 usage();
682
683 if (txtfile && mimefile)
684 usage();
685
686 tmpfp=tmpfile();
687
688 if (!tmpfp)
689 {
690 perror("tmpfile");
691 exit(1);
692 }
693
694 rfcp=savemessage(tmpfp);
695
696 if (fseek(tmpfp, 0L, SEEK_SET) < 0)
697 {
698 perror("fseek(tempfile)");
699 exit(1);
700 }
701
702 read_headers(tmpfp);
703
704 if (sender == NULL || *sender == 0)
705 check_sender();
706
707 check_dsn();
708 check_recips();
709 #ifdef DbObj
710 check_db();
711 #endif
712
713 uinfo=unicode_find(charset);
714
715 memset(&replyinfo, 0, sizeof(replyinfo));
716
717 replyinfo.info.fd=fileno(tmpfp);
718 replyinfo.info.rfc2045partp=rfcp;
719 replyinfo.info.voidarg=&replyinfo;
720
721 replyinfo.info.write_func=mimeautoreply_write_func;
722
723 replyinfo.info.writesig_func=mimeautoreply_writesig_func;
724
725 replyinfo.info.myaddr_func=mimeautoreply_myaddr_func;
726
727 replyinfo.info.replymode=replymode;
728 replyinfo.info.replytoenvelope=replytoenvelope;
729 replyinfo.info.donotquote=donotquote;
730
731 replyinfo.info.replysalut=replysalut;
732 replyinfo.info.forwarddescr="Forwarded message";
733 replyinfo.info.mailinglists="";
734 replyinfo.info.charset=(uinfo ? uinfo:&unicode_ISO8859_1)->chset;
735 replyinfo.info.subject=subj;
736 replyinfo.info.forwardsep=forwardsep;
737
738 if (mimedsn && *mimedsn)
739 {
740 replyinfo.info.dsnfrom=mimedsn;
741 replyinfo.info.replymode="replydsn";
742 }
743
744 if (mimefile)
745 {
746 if ((replyinfo.contentf=fopen(mimefile, "r")) == NULL)
747 {
748 perror(mimefile);
749 exit(1);
750 }
751
752 {
753 struct rfc2045 *rfcp=rfc2045_alloc();
754 static const char mv[]="Mime-Version: 1.0\n";
755 char buf[BUFSIZ];
756 int l;
757 const char *content_type;
758 const char *content_transfer_encoding;
759 const char *charset;
760
761 rfc2045_parse(rfcp, mv, sizeof(mv)-1);
762
763 while ((l=fread(buf, 1, sizeof(buf), replyinfo.contentf)
764 ) > 0)
765 {
766 rfc2045_parse(rfcp, buf, l);
767 }
768
769 if (l < 0 ||
770 fseek(replyinfo.contentf, 0L, SEEK_SET) < 0)
771 {
772 perror(mimefile);
773 exit(1);
774 }
775
776 rfc2045_mimeinfo(rfcp, &content_type,
777 &content_transfer_encoding,
778 &charset);
779
780 if (strcasecmp(content_type, "text/plain"))
781 {
782 fprintf(stderr,
783 "%s must specify text/plain MIME type\n",
784 mimefile);
785 exit(1);
786 }
787 if (charset == NULL ||
788 (uinfo=unicode_find(charset)) == NULL)
789 {
790 fprintf(stderr, "Unknown charset in %s\n",
791 mimefile);
792 exit(1);
793 }
794 replyinfo.info.charset=uinfo->chset;
795 rfc2045_free(rfcp);
796 }
797 replyinfo.info.content_set_charset=copy_headers;
798 replyinfo.info.content_specify=copy_body;
799 }
800 else if (txtfile)
801 {
802 if ((replyinfo.contentf=fopen(txtfile, "r")) == NULL)
803 {
804 perror(mimefile);
805 exit(1);
806 }
807 replyinfo.info.content_specify=copy_body;
808 }
809
810 if (replyinfo.contentf)
811 fcntl(fileno(replyinfo.contentf), F_SETFD, FD_CLOEXEC);
812
813 if (nosend)
814 replyinfo.outf=stdout;
815 else
816 {
817 replyinfo.outf=tmpfile();
818
819 if (replyinfo.outf == NULL)
820 {
821 perror("tmpfile");
822 exit(1);
823 }
824 }
825
826 {
827 struct header *h;
828
829 for (h=extra_headers; h; h=h->next)
830 fprintf(replyinfo.outf, "%s\n", h->buf);
831 }
832 fprintf(replyinfo.outf,
833 "Precedence: junk\n"
834 "Auto-Submitted: auto-replied\n");
835
836 if (rfc2045_makereply_unicode(&replyinfo.info) < 0 ||
837 fflush(replyinfo.outf) < 0 || ferror(replyinfo.outf) ||
838 (!nosend &&
839 (
840 fseek(replyinfo.outf, 0L, SEEK_SET) < 0 ||
841 (close(0), dup(fileno(replyinfo.outf))) < 0)
842 ))
843 {
844 perror("tempfile");
845 exit(1);
846 }
847 fclose(replyinfo.outf);
848 fcntl(0, F_SETFD, 0);
849
850 free(charset);
851 rfc2045_free(rfcp);
852
853 if (!nosend)
854 opensendmail(argn, argc, argv);
855 return (0);
856 }
857