| 1 | /*$Id: ezmlm-receipt.c,v 1.10 1999/02/05 04:57:44 lindberg Exp $*/ |
| 2 | /*$Name: ezmlm-idx-0324 $*/ |
| 3 | /* Handles receipts and bounces from sublists at the main list */ |
| 4 | /* Set up instead of ezmlm-return in DIR/bouncer of main list */ |
| 5 | |
| 6 | #include <sys/types.h> |
| 7 | #include "direntry.h" |
| 8 | #include "stralloc.h" |
| 9 | #include "str.h" |
| 10 | #include "env.h" |
| 11 | #include "slurp.h" |
| 12 | #include "getconf.h" |
| 13 | #include "strerr.h" |
| 14 | #include "byte.h" |
| 15 | #include "case.h" |
| 16 | #include "quote.h" |
| 17 | #include "getln.h" |
| 18 | #include "substdio.h" |
| 19 | #include "error.h" |
| 20 | #include "readwrite.h" |
| 21 | #include "fmt.h" |
| 22 | #include "now.h" |
| 23 | #include "seek.h" |
| 24 | #include "idx.h" |
| 25 | #include "errtxt.h" |
| 26 | |
| 27 | #define FATAL "ezmlm-receipt: fatal: " |
| 28 | #define INFO "ezmlm-receipt: info: " |
| 29 | |
| 30 | void die_usage() |
| 31 | { |
| 32 | strerr_die1x(100,"ezmlm-receipt: usage: ezmlm-receipt [-dD] dir"); |
| 33 | } |
| 34 | |
| 35 | void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); } |
| 36 | |
| 37 | void die_badaddr() |
| 38 | { |
| 39 | strerr_die2x(100,FATAL,ERR_BAD_ADDRESS); |
| 40 | } |
| 41 | void die_trash() |
| 42 | { |
| 43 | strerr_die2x(0,INFO,"trash address"); |
| 44 | } |
| 45 | |
| 46 | stralloc line = {0}; |
| 47 | stralloc quoted = {0}; |
| 48 | stralloc intro = {0}; |
| 49 | stralloc bounce = {0}; |
| 50 | stralloc header = {0}; |
| 51 | stralloc failure = {0}; |
| 52 | stralloc paragraph = {0}; |
| 53 | stralloc ddir = {0}; |
| 54 | stralloc outhost = {0}; |
| 55 | stralloc outlocal = {0}; |
| 56 | stralloc inlocal = {0}; |
| 57 | stralloc tagline = {0}; |
| 58 | stralloc listaddr = {0}; |
| 59 | stralloc fndate = {0}; |
| 60 | stralloc fndir = {0}; |
| 61 | stralloc fndatenew = {0}; |
| 62 | |
| 63 | void die_datenew() |
| 64 | { strerr_die4sys(111,FATAL,ERR_WRITE,fndatenew.s,": "); } |
| 65 | void die_msgin() |
| 66 | { strerr_die2sys(111,FATAL,ERR_READ_INPUT); } |
| 67 | |
| 68 | char strnum[FMT_ULONG]; |
| 69 | char inbuf[1024]; |
| 70 | substdio ssin; |
| 71 | |
| 72 | char outbuf[256]; /* small - rarely used */ |
| 73 | substdio ssout; |
| 74 | |
| 75 | unsigned long when; |
| 76 | unsigned long addrno = 0L; |
| 77 | |
| 78 | char *sender; |
| 79 | char *dir; |
| 80 | char *workdir; |
| 81 | void **psql = (void **) 0; |
| 82 | stralloc listno = {0}; |
| 83 | |
| 84 | |
| 85 | void doit(addr,msgnum,when,bounce) |
| 86 | /* Just stores address\0nsgnum\0 followed by bounce. File name is */ |
| 87 | /* dttt.ppp[.n], where 'ttt' is a time stamp, 'ppp' the pid, and 'n' the */ |
| 88 | /* number when there are more than 1 addresses in a pre-VERP bounce. In */ |
| 89 | /* this case, the first one is just dttt.ppp, the decond dttt.ppp.2, etc. */ |
| 90 | /* For a main list, bounces come from sublists. They are rare and serious. */ |
| 91 | char *addr; |
| 92 | unsigned long msgnum; |
| 93 | unsigned long when; |
| 94 | stralloc *bounce; |
| 95 | { |
| 96 | int fd; |
| 97 | unsigned int pos; |
| 98 | DIR *bouncedir; |
| 99 | direntry *d; |
| 100 | unsigned int no; |
| 101 | |
| 102 | if (!stralloc_copys(&fndir,workdir)) die_nomem(); |
| 103 | if (!stralloc_cats(&fndir,"/bounce")) die_nomem(); |
| 104 | if (!stralloc_0(&fndir)) die_nomem(); |
| 105 | bouncedir = opendir(fndir.s); |
| 106 | if (!bouncedir) |
| 107 | if (errno != error_noent) |
| 108 | strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": "); |
| 109 | else |
| 110 | strerr_die3x(111,FATAL,fndir.s,ERR_NOEXIST); |
| 111 | |
| 112 | no = MAX_MAIN_BOUNCES; /* no more than this many allowed */ |
| 113 | while (no && (d = readdir(bouncedir))) { |
| 114 | if (str_equal(d->d_name,".")) continue; |
| 115 | if (str_equal(d->d_name,"..")) continue; |
| 116 | --no; |
| 117 | } |
| 118 | closedir(bouncedir); |
| 119 | if (!no) /* max no of bounces exceeded */ |
| 120 | strerr_die2x(0,INFO,ERR_MAX_BOUNCE); |
| 121 | /* save bounce */ |
| 122 | if (!stralloc_copys(&fndate,workdir)) die_nomem(); |
| 123 | if (!stralloc_cats(&fndate,"/bounce/d")) die_nomem(); |
| 124 | pos = fndate.len - 1; |
| 125 | if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,when))) die_nomem(); |
| 126 | if (!stralloc_cats(&fndate,".")) die_nomem(); |
| 127 | if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,(unsigned long) getpid()))) |
| 128 | die_nomem(); |
| 129 | if (addrno) { /* so that pre-VERP bounces make a d... file per address */ |
| 130 | /* for the first one we use the std-style fname */ |
| 131 | if (!stralloc_cats(&fndate,".")) die_nomem(); |
| 132 | if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,addrno))) die_nomem(); |
| 133 | } |
| 134 | addrno++; /* get ready for next */ |
| 135 | if (!stralloc_0(&fndate)) die_nomem(); |
| 136 | if (!stralloc_copy(&fndatenew,&fndate)) die_nomem(); |
| 137 | fndatenew.s[pos] = 'D'; |
| 138 | |
| 139 | fd = open_trunc(fndatenew.s); |
| 140 | if (fd == -1) die_datenew(); |
| 141 | substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf)); |
| 142 | if (substdio_puts(&ssout,addr) == -1) die_datenew(); |
| 143 | if (substdio_put(&ssout,"",1) == -1) die_datenew(); |
| 144 | if (substdio_put(&ssout,strnum,fmt_ulong(strnum,msgnum)) == -1) |
| 145 | die_datenew(); |
| 146 | if (substdio_put(&ssout,"",1) == -1) die_datenew(); |
| 147 | |
| 148 | if (substdio_puts(&ssout,"Return-Path: <") == -1) die_datenew(); |
| 149 | if (!quote2("ed,sender)) die_nomem(); |
| 150 | if (substdio_put(&ssout,quoted.s,quoted.len) == -1) die_datenew(); |
| 151 | if (substdio_puts(&ssout,">\n") == -1) die_datenew(); |
| 152 | if (substdio_put(&ssout,bounce->s,bounce->len) == -1) die_datenew(); |
| 153 | if (substdio_flush(&ssout) == -1) die_datenew(); |
| 154 | if (fsync(fd) == -1) die_datenew(); |
| 155 | if (close(fd) == -1) die_datenew(); /* NFS stupidity */ |
| 156 | if (rename(fndatenew.s,fndate.s) == -1) |
| 157 | strerr_die6sys(111,FATAL,ERR_MOVE,fndatenew.s," to ",fndate.s,": "); |
| 158 | } |
| 159 | |
| 160 | void main(argc,argv) |
| 161 | int argc; |
| 162 | char **argv; |
| 163 | { |
| 164 | char *local; |
| 165 | char *host; |
| 166 | char *action; |
| 167 | char *def; |
| 168 | int flagdig = 1; |
| 169 | int flaghaveintro; |
| 170 | int flaghaveheader; |
| 171 | int match; |
| 172 | unsigned long msgnum; |
| 173 | unsigned int i; |
| 174 | unsigned int len; |
| 175 | char *cp; |
| 176 | |
| 177 | umask(022); |
| 178 | sig_pipeignore(); |
| 179 | |
| 180 | when = (unsigned long) now(); |
| 181 | |
| 182 | dir = argv[1]; |
| 183 | if (!dir) die_usage(); |
| 184 | if (*dir == '-') { |
| 185 | if (dir[1] == 'd') { |
| 186 | flagdig = 2; |
| 187 | } else if (dir[1] == 'D') { |
| 188 | flagdig = 0; |
| 189 | } else |
| 190 | die_usage(); |
| 191 | dir = argv[2]; |
| 192 | if (!dir) die_usage(); |
| 193 | } |
| 194 | if (chdir(dir) == -1) |
| 195 | strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": "); |
| 196 | |
| 197 | sender = env_get("SENDER"); |
| 198 | def = env_get("DEFAULT"); |
| 199 | local = env_get("LOCAL"); |
| 200 | |
| 201 | getconf_line(&outhost,"outhost",1,FATAL,dir); |
| 202 | getconf_line(&outlocal,"outlocal",1,FATAL,dir); |
| 203 | workdir = dir; |
| 204 | if (def) { /* qmail>=1.02 */ |
| 205 | action = def; /* now see if -digest-return- */ |
| 206 | if (flagdig == 1) { |
| 207 | flagdig = 0; |
| 208 | if (str_len(local) >= str_len(def) + 14) |
| 209 | if (str_start(local + str_len(local) - 14 - str_len(def),"digest-")) |
| 210 | flagdig = 2; |
| 211 | } |
| 212 | } else { /* older version of qmail */ |
| 213 | getconf_line(&inlocal,"inlocal",1,FATAL,dir); |
| 214 | if (inlocal.len > str_len(local)) die_badaddr(); |
| 215 | if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr(); |
| 216 | action = local + inlocal.len; |
| 217 | if (flagdig == 1) { |
| 218 | flagdig = 0; |
| 219 | if (str_start(action,"-digest")) { |
| 220 | flagdig = 2; |
| 221 | action += 7; |
| 222 | } |
| 223 | } |
| 224 | if (!str_start(action,"-return-")) die_badaddr(); |
| 225 | action += 8; |
| 226 | } |
| 227 | if (flagdig) { |
| 228 | if (!stralloc_copys(&ddir,dir)) die_nomem(); |
| 229 | if (!stralloc_cats(&ddir,"/digest")) die_nomem(); |
| 230 | if (!stralloc_0(&ddir)) die_nomem(); |
| 231 | workdir = ddir.s; |
| 232 | if (!stralloc_cats(&outlocal,"-digest")) die_nomem(); |
| 233 | } |
| 234 | if (!*action) die_trash(); |
| 235 | |
| 236 | substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); |
| 237 | |
| 238 | if (!case_diffs(action,"receipt")) { |
| 239 | host = sender + str_rchr(sender,'@'); |
| 240 | if (*host) |
| 241 | *(host++) = '\0'; |
| 242 | cp = sender; |
| 243 | /* check recipient in case it's a bounce*/ |
| 244 | while (*(cp++)) { /* decode sender */ |
| 245 | cp += str_chr(cp,'-'); |
| 246 | if (case_starts(cp,"-return-")) { |
| 247 | if (!scan_ulong(cp + 8,&msgnum)) |
| 248 | strerr_die2x(100,FATAL,"bad VERP format for receipt"); |
| 249 | *cp = '\0'; |
| 250 | if (!stralloc_copys(&listaddr,sender)) die_nomem(); |
| 251 | if (!stralloc_append(&listaddr,"@")) die_nomem(); |
| 252 | if (!stralloc_cats(&listaddr,host)) die_nomem(); |
| 253 | if (!stralloc_0(&listaddr)) die_nomem(); |
| 254 | break; |
| 255 | } |
| 256 | } |
| 257 | for(;;) { /* Get X-tag from hdr*/ |
| 258 | if (getln(&ssin,&line,&match,'\n') == -1) die_msgin(); |
| 259 | if (!match) |
| 260 | break; |
| 261 | |
| 262 | if (line.len == 1) break; |
| 263 | if (case_startb(line.s,line.len,TXT_TAG)) { |
| 264 | len = str_len(TXT_TAG); |
| 265 | if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem(); |
| 266 | /* NOTE: tagline is dirty! We quote it for sql and rely on */ |
| 267 | /* std log clean for maillog */ |
| 268 | break; |
| 269 | } |
| 270 | } |
| 271 | /* feedback ok even if not sub. Will be filtered by subreceipt*/ |
| 272 | /* For instance, main list feedback is ok, but !issub. */ |
| 273 | subreceipt(workdir,msgnum,&tagline,listaddr.s,2,INFO,FATAL); |
| 274 | closesql(); |
| 275 | _exit(0); |
| 276 | } |
| 277 | /* not receipt - maybe bounce */ |
| 278 | /* no need to lock. dttt.pid can be assumed */ |
| 279 | /* to be unique and if not would be over- */ |
| 280 | /* written even with lock */ |
| 281 | action += scan_ulong(action,&msgnum); |
| 282 | if (*action != '-') die_badaddr(); |
| 283 | ++action; |
| 284 | /* scan bounce for tag. It'll be in the BODY! */ |
| 285 | for (;;) { |
| 286 | if (getln(&ssin,&line,&match,'\n') == -1) |
| 287 | strerr_die2sys(111,FATAL,ERR_READ_INPUT); |
| 288 | if (!match) break; |
| 289 | if (case_startb(line.s,line.len,TXT_TAG)) { |
| 290 | len = str_len(TXT_TAG); |
| 291 | if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem(); |
| 292 | /* NOTE: tagline is dirty! We quote it for sql and rely on */ |
| 293 | /* std log clean for maillog */ |
| 294 | break; |
| 295 | } |
| 296 | } |
| 297 | if (seek_begin(0) == -1) |
| 298 | strerr_die2sys(111,FATAL,ERR_SEEK_INPUT); |
| 299 | substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf)); |
| 300 | |
| 301 | if (*action) { /* normal bounce */ |
| 302 | |
| 303 | if (slurpclose(0,&bounce,1024) == -1) die_msgin(); |
| 304 | i = str_rchr(action,'='); |
| 305 | if (!stralloc_copyb(&listaddr,action,i)) die_nomem(); |
| 306 | if (action[i]) { |
| 307 | if (!stralloc_cats(&listaddr,"@")) die_nomem(); |
| 308 | if (!stralloc_cats(&listaddr,action + i + 1)) die_nomem(); |
| 309 | } |
| 310 | if (!stralloc_0(&listaddr)) die_nomem(); |
| 311 | /* don't check for sub, since issub() doesn't see sublists */ |
| 312 | switch (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL)) { |
| 313 | case -1: strerr_die2x(0,INFO,ERR_COOKIE); |
| 314 | case -2: strerr_die2x(0,INFO,ERR_NOT_ACTIVE); |
| 315 | default: doit(listaddr.s,msgnum,when,&bounce); |
| 316 | } |
| 317 | closesql(); |
| 318 | _exit(0); |
| 319 | } /* pre-VERP bounce, in QSBMF format */ |
| 320 | |
| 321 | flaghaveheader = 0; |
| 322 | flaghaveintro = 0; |
| 323 | |
| 324 | for (;;) { |
| 325 | if (!stralloc_copys(¶graph,"")) die_nomem(); |
| 326 | for (;;) { |
| 327 | if (getln(&ssin,&line,&match,'\n') == -1) |
| 328 | strerr_die2sys(111,FATAL,ERR_READ_INPUT); |
| 329 | if (!match) die_trash(); |
| 330 | if (!stralloc_cat(¶graph,&line)) die_nomem(); |
| 331 | if (line.len <= 1) break; |
| 332 | } |
| 333 | |
| 334 | if (!flaghaveheader) { |
| 335 | if (!stralloc_copy(&header,¶graph)) die_nomem(); |
| 336 | flaghaveheader = 1; |
| 337 | continue; |
| 338 | } |
| 339 | |
| 340 | if (!flaghaveintro) { |
| 341 | if (paragraph.s[0] == '-' && paragraph.s[1] == '-') |
| 342 | continue; /* skip MIME boundary if it exists */ |
| 343 | if (paragraph.len < 15) die_trash(); |
| 344 | if (str_diffn(paragraph.s,"Hi. This is the",15)) die_trash(); |
| 345 | if (!stralloc_copy(&intro,¶graph)) die_nomem(); |
| 346 | flaghaveintro = 1; |
| 347 | continue; |
| 348 | } |
| 349 | |
| 350 | if (paragraph.s[0] == '-') |
| 351 | break; |
| 352 | |
| 353 | if (paragraph.s[0] == '<') { /* find address */ |
| 354 | if (!stralloc_copy(&failure,¶graph)) die_nomem(); |
| 355 | |
| 356 | if (!stralloc_copy(&bounce,&header)) die_nomem(); |
| 357 | if (!stralloc_cat(&bounce,&intro)) die_nomem(); |
| 358 | if (!stralloc_cat(&bounce,&failure)) die_nomem(); |
| 359 | |
| 360 | i = byte_chr(failure.s,failure.len,'\n'); |
| 361 | if (i < 3) die_trash(); |
| 362 | |
| 363 | if (!stralloc_copyb(&listaddr,failure.s + 1,i - 3)) die_nomem(); |
| 364 | if (byte_chr(listaddr.s,listaddr.len,'\0') == listaddr.len) { |
| 365 | if (!stralloc_0(&listaddr)) die_nomem(); |
| 366 | if (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL) == 0) |
| 367 | doit(listaddr.s,msgnum,when,&bounce); |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | closesql(); |
| 372 | _exit(0); |
| 373 | } |
| 374 | |