scanmail.c 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. #include "common.h"
  2. #include "spam.h"
  3. int cflag;
  4. int debug;
  5. int hflag;
  6. int nflag;
  7. int sflag;
  8. int tflag;
  9. int vflag;
  10. Biobuf bin, bout, *cout;
  11. /* file names */
  12. char patfile[128];
  13. char linefile[128];
  14. char holdqueue[128];
  15. char copydir[128];
  16. char header[Hdrsize+2];
  17. char cmd[1024];
  18. char **qname;
  19. char **qdir;
  20. char *sender;
  21. String *recips;
  22. char* canon(Biobuf*, char*, char*, int*);
  23. int matcher(char*, Pattern*, char*, Resub*);
  24. int matchaction(int, char*, Resub*);
  25. Biobuf *opencopy(char*);
  26. Biobuf *opendump(char*);
  27. char *qmail(char**, char*, int, Biobuf*);
  28. void saveline(char*, char*, Resub*);
  29. int optoutofspamfilter(char*);
  30. void
  31. usage(void)
  32. {
  33. fprint(2, "missing or bad arguments to qer\n");
  34. exits("usage");
  35. }
  36. void
  37. regerror(char *s)
  38. {
  39. fprint(2, "scanmail: %s\n", s);
  40. }
  41. void *
  42. Malloc(long n)
  43. {
  44. void *p;
  45. p = malloc(n);
  46. if(p == 0)
  47. exits("malloc");
  48. return p;
  49. }
  50. void*
  51. Realloc(void *p, ulong n)
  52. {
  53. p = realloc(p, n);
  54. if(p == 0)
  55. exits("realloc");
  56. return p;
  57. }
  58. void
  59. main(int argc, char *argv[])
  60. {
  61. int i, n, nolines, optout;
  62. char **args, **a, *cp, *buf;
  63. char body[Bodysize+2];
  64. Resub match[1];
  65. Biobuf *bp;
  66. optout = 1;
  67. a = args = Malloc((argc+1)*sizeof(char*));
  68. sprint(patfile, "%s/patterns", UPASLIB);
  69. sprint(linefile, "%s/lines", UPASLOG);
  70. sprint(holdqueue, "%s/queue.hold", SPOOL);
  71. sprint(copydir, "%s/copy", SPOOL);
  72. *a++ = argv[0];
  73. for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
  74. switch(argv[0][1]){
  75. case 'c': /* save copy of message */
  76. cflag = 1;
  77. break;
  78. case 'd': /* debug */
  79. debug++;
  80. *a++ = argv[0];
  81. break;
  82. case 'h': /* queue held messages by sender domain */
  83. hflag = 1; /* -q flag must be set also */
  84. break;
  85. case 'n': /* NOHOLD mode */
  86. nflag = 1;
  87. break;
  88. case 'p': /* pattern file */
  89. if(argv[0][2] || argv[1] == 0)
  90. usage();
  91. argc--;
  92. argv++;
  93. strcpy(patfile, *argv);
  94. break;
  95. case 'q': /* queue name */
  96. if(argv[0][2] || argv[1] == 0)
  97. usage();
  98. *a++ = argv[0];
  99. argc--;
  100. argv++;
  101. qname = a;
  102. *a++ = argv[0];
  103. break;
  104. case 's': /* save copy of dumped message */
  105. sflag = 1;
  106. break;
  107. case 't': /* test mode - don't log match
  108. * and write message to /dev/null
  109. */
  110. tflag = 1;
  111. break;
  112. case 'v': /* vebose - print matches */
  113. vflag = 1;
  114. break;
  115. default:
  116. *a++ = argv[0];
  117. break;
  118. }
  119. }
  120. if(argc < 3)
  121. usage();
  122. Binit(&bin, 0, OREAD);
  123. bp = Bopen(patfile, OREAD);
  124. if(bp){
  125. parsepats(bp);
  126. Bterm(bp);
  127. }
  128. qdir = a;
  129. sender = argv[2];
  130. /* copy the rest of argv, acummulating the recipients as we go */
  131. for(i = 0; argv[i]; i++){
  132. *a++ = argv[i];
  133. if(i < 4) /* skip queue, 'mail', sender, dest sys */
  134. continue;
  135. /* recipients and smtp flags - skip the latter*/
  136. if(strcmp(argv[i], "-g") == 0){
  137. *a++ = argv[++i];
  138. continue;
  139. }
  140. if(recips)
  141. s_append(recips, ", ");
  142. else
  143. recips = s_new();
  144. s_append(recips, argv[i]);
  145. if(optout && !optoutofspamfilter(argv[i]))
  146. optout = 0;
  147. }
  148. *a = 0;
  149. /* construct a command string for matching */
  150. snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
  151. cmd[sizeof(cmd)-1] = 0;
  152. for(cp = cmd; *cp; cp++)
  153. *cp = tolower(*cp);
  154. /* canonicalize a copy of the header and body.
  155. * buf points to orginal message and n contains
  156. * number of bytes of original message read during
  157. * canonicalization.
  158. */
  159. *body = 0;
  160. *header = 0;
  161. buf = canon(&bin, header+1, body+1, &n);
  162. if (buf == 0)
  163. exits("read");
  164. /* if all users opt out, don't try matches */
  165. if(optout){
  166. if(cflag)
  167. cout = opencopy(sender);
  168. exits(qmail(args, buf, n, cout));
  169. }
  170. /* Turn off line logging, if command line matches */
  171. nolines = matchaction(Lineoff, cmd, match);
  172. for(i = 0; patterns[i].action; i++){
  173. /* Lineoff patterns were already done above */
  174. if(i == Lineoff)
  175. continue;
  176. /* don't apply "Line" patterns if excluded above */
  177. if(nolines && i == SaveLine)
  178. continue;
  179. /* apply patterns to the sender/recips, header and body */
  180. if(matchaction(i, cmd, match))
  181. break;
  182. if(matchaction(i, header+1, match))
  183. break;
  184. if(i == HoldHeader)
  185. continue;
  186. if(matchaction(i, body+1, match))
  187. break;
  188. }
  189. if(cflag && patterns[i].action == 0) /* no match found - save msg */
  190. cout = opencopy(sender);
  191. exits(qmail(args, buf, n, cout));
  192. }
  193. char*
  194. qmail(char **argv, char *buf, int n, Biobuf *cout)
  195. {
  196. Waitmsg *status;
  197. int i, pid, pipefd[2];
  198. char path[512];
  199. Biobuf *bp;
  200. pid = 0;
  201. if(tflag == 0){
  202. if(pipe(pipefd) < 0)
  203. exits("pipe");
  204. pid = fork();
  205. if(pid == 0){
  206. dup(pipefd[0], 0);
  207. for(i = sysfiles(); i >= 3; i--)
  208. close(i);
  209. snprint(path, sizeof(path), "%s/qer", UPASBIN);
  210. *argv=path;
  211. exec(path, argv);
  212. exits("exec");
  213. }
  214. Binit(&bout, pipefd[1], OWRITE);
  215. bp = &bout;
  216. } else
  217. bp = Bopen("/dev/null", OWRITE);
  218. while(n > 0){
  219. Bwrite(bp, buf, n);
  220. if(cout)
  221. Bwrite(cout, buf, n);
  222. n = Bread(&bin, buf, sizeof(buf)-1);
  223. }
  224. Bterm(bp);
  225. if(cout)
  226. Bterm(cout);
  227. if(tflag)
  228. return 0;
  229. close(pipefd[1]);
  230. close(pipefd[0]);
  231. for(;;){
  232. status = wait();
  233. if(status == nil || status->pid == pid)
  234. break;
  235. free(status);
  236. }
  237. if(status == nil)
  238. strcpy(buf, "wait failed");
  239. else{
  240. strcpy(buf, status->msg);
  241. free(status);
  242. }
  243. return buf;
  244. }
  245. char*
  246. canon(Biobuf *bp, char *header, char *body, int *n)
  247. {
  248. int hsize;
  249. char *raw;
  250. hsize = 0;
  251. *header = 0;
  252. *body = 0;
  253. raw = readmsg(bp, &hsize, n);
  254. if(raw){
  255. if(convert(raw, raw+hsize, header, Hdrsize, 0))
  256. conv64(raw+hsize, raw+*n, body, Bodysize); /* base64 */
  257. else
  258. convert(raw+hsize, raw+*n, body, Bodysize, 1); /* text */
  259. }
  260. return raw;
  261. }
  262. int
  263. matchaction(int action, char *message, Resub *m)
  264. {
  265. char *name;
  266. Pattern *p;
  267. if(message == 0 || *message == 0)
  268. return 0;
  269. name = patterns[action].action;
  270. p = patterns[action].strings;
  271. if(p)
  272. if(matcher(name, p, message, m))
  273. return 1;
  274. for(p = patterns[action].regexps; p; p = p->next)
  275. if(matcher(name, p, message, m))
  276. return 1;
  277. return 0;
  278. }
  279. int
  280. matcher(char *action, Pattern *p, char *message, Resub *m)
  281. {
  282. char *cp;
  283. String *s;
  284. for(cp = message; matchpat(p, cp, m); cp = m->ep){
  285. switch(p->action){
  286. case SaveLine:
  287. if(vflag)
  288. xprint(2, action, m);
  289. saveline(linefile, sender, m);
  290. break;
  291. case HoldHeader:
  292. case Hold:
  293. if(nflag)
  294. continue;
  295. if(vflag)
  296. xprint(2, action, m);
  297. *qdir = holdqueue;
  298. if(hflag && qname){
  299. cp = strchr(sender, '!');
  300. if(cp){
  301. *cp = 0;
  302. *qname = strdup(sender);
  303. *cp = '!';
  304. } else
  305. *qname = strdup(sender);
  306. }
  307. return 1;
  308. case Dump:
  309. if(vflag)
  310. xprint(2, action, m);
  311. *(m->ep) = 0;
  312. if(!tflag){
  313. s = s_new();
  314. s_append(s, sender);
  315. s = unescapespecial(s);
  316. syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp,
  317. s_to_c(s_restart(recips)));
  318. s_free(s);
  319. }
  320. tflag = 1;
  321. if(sflag)
  322. cout = opendump(sender);
  323. return 1;
  324. default:
  325. break;
  326. }
  327. }
  328. return 0;
  329. }
  330. void
  331. saveline(char *file, char *sender, Resub *rp)
  332. {
  333. char *p, *q;
  334. int i, c;
  335. Biobuf *bp;
  336. if(rp->sp == 0 || rp->ep == 0)
  337. return;
  338. /* back up approx 20 characters to whitespace */
  339. for(p = rp->sp, i = 0; *p && i < 20; i++, p--)
  340. ;
  341. while(*p && *p != ' ')
  342. p--;
  343. p++;
  344. /* grab about 20 more chars beyond the end of the match */
  345. for(q = rp->ep, i = 0; *q && i < 20; i++, q++)
  346. ;
  347. while(*q && *q != ' ')
  348. q++;
  349. c = *q;
  350. *q = 0;
  351. bp = sysopen(file, "al", 0644);
  352. if(bp){
  353. Bprint(bp, "%s-> %s\n", sender, p);
  354. Bterm(bp);
  355. }
  356. else if(debug)
  357. fprint(2, "can't save line: (%s) %s\n", sender, p);
  358. *q = c;
  359. }
  360. Biobuf*
  361. opendump(char *sender)
  362. {
  363. int i;
  364. ulong h;
  365. char buf[512];
  366. Biobuf *b;
  367. char *cp;
  368. cp = ctime(time(0));
  369. cp[7] = 0;
  370. cp[10] = 0;
  371. if(cp[8] == ' ')
  372. sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
  373. else
  374. sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
  375. cp = buf+strlen(buf);
  376. if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
  377. syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
  378. return 0;
  379. }
  380. h = 0;
  381. while(*sender)
  382. h = h*257 + *sender++;
  383. for(i = 0; i < 50; i++){
  384. h += lrand();
  385. sprint(cp, "/%lud", h);
  386. b = sysopen(buf, "wlc", 0644);
  387. if(b){
  388. if(vflag)
  389. fprint(2, "saving in %s\n", buf);
  390. return b;
  391. }
  392. }
  393. return 0;
  394. }
  395. Biobuf*
  396. opencopy(char *sender)
  397. {
  398. int i;
  399. ulong h;
  400. char buf[512];
  401. Biobuf *b;
  402. h = 0;
  403. while(*sender)
  404. h = h*257 + *sender++;
  405. for(i = 0; i < 50; i++){
  406. h += lrand();
  407. sprint(buf, "%s/%lud", copydir, h);
  408. b = sysopen(buf, "wlc", 0600);
  409. if(b)
  410. return b;
  411. }
  412. return 0;
  413. }
  414. int
  415. optoutofspamfilter(char *addr)
  416. {
  417. char *p, *f;
  418. int rv;
  419. p = strchr(addr, '!');
  420. if(p)
  421. p++;
  422. else
  423. p = addr;
  424. rv = 0;
  425. f = smprint("/mail/box/%s/nospamfiltering", p);
  426. if(f != nil){
  427. rv = access(f, 0)==0;
  428. free(f);
  429. }
  430. return rv;
  431. }