| 1 | /* $Id: ezmlm-send.c,v 1.77 1999/10/29 02:49:14 lindberg Exp $*/ |
| 2 | /* $Name: ezmlm-idx-040 $*/ |
| 3 | #include "stralloc.h" |
| 4 | #include "subfd.h" |
| 5 | #include "strerr.h" |
| 6 | #include "error.h" |
| 7 | #include "qmail.h" |
| 8 | #include "env.h" |
| 9 | #include "lock.h" |
| 10 | #include "sig.h" |
| 11 | #include "open.h" |
| 12 | #include "getln.h" |
| 13 | #include "case.h" |
| 14 | #include "scan.h" |
| 15 | #include "str.h" |
| 16 | #include "fmt.h" |
| 17 | #include "readwrite.h" |
| 18 | #include "exit.h" |
| 19 | #include "substdio.h" |
| 20 | #include "getconf.h" |
| 21 | #include "constmap.h" |
| 22 | #include "byte.h" |
| 23 | #include "sgetopt.h" |
| 24 | #include "quote.h" |
| 25 | #include "subscribe.h" |
| 26 | #include "mime.h" |
| 27 | #include "errtxt.h" |
| 28 | #include "makehash.h" |
| 29 | #include "cookie.h" |
| 30 | #include "idx.h" |
| 31 | #include "copy.h" |
| 32 | |
| 33 | int flagnoreceived = 1; /* suppress received headers by default. They*/ |
| 34 | /* are still archived. =0 => archived and */ |
| 35 | /* copied. */ |
| 36 | int flaglog = 1; /* for lists with mysql support, use tags */ |
| 37 | /* and log traffic to the database */ |
| 38 | #define FATAL "ezmlm-send: fatal: " |
| 39 | |
| 40 | void die_usage() |
| 41 | { |
| 42 | strerr_die1x(100,"ezmlm-send: usage: ezmlm-send [-cClLqQrR] [-h header] dir"); |
| 43 | } |
| 44 | void die_nomem() |
| 45 | { |
| 46 | strerr_die2x(111,FATAL,ERR_NOMEM); |
| 47 | } |
| 48 | |
| 49 | /* for writing new index file indexn later moved to index. */ |
| 50 | substdio ssindexn; |
| 51 | char indexnbuf[1024]; |
| 52 | |
| 53 | char strnum[FMT_ULONG]; |
| 54 | char szmsgnum[FMT_ULONG]; |
| 55 | char hash[HASHLEN]; |
| 56 | |
| 57 | stralloc fnadir = {0}; |
| 58 | stralloc fnaf = {0}; |
| 59 | stralloc fnif = {0}; |
| 60 | stralloc fnifn = {0}; |
| 61 | stralloc fnsub = {0}; |
| 62 | stralloc line = {0}; |
| 63 | stralloc qline = {0}; |
| 64 | stralloc lines = {0}; |
| 65 | stralloc subject = {0}; |
| 66 | stralloc from = {0}; |
| 67 | stralloc received = {0}; |
| 68 | stralloc prefix = {0}; |
| 69 | stralloc content = {0}; |
| 70 | stralloc boundary = {0}; |
| 71 | stralloc charset = {0}; |
| 72 | stralloc dcprefix = {0}; |
| 73 | stralloc dummy = {0}; |
| 74 | stralloc qmqpservers = {0}; |
| 75 | |
| 76 | void die_indexn() |
| 77 | { |
| 78 | strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": "); |
| 79 | } |
| 80 | |
| 81 | void *psql = (void *) 0; |
| 82 | |
| 83 | unsigned long innum; |
| 84 | unsigned long outnum; |
| 85 | unsigned long msgnum; |
| 86 | unsigned long hash_lo = 0L; |
| 87 | unsigned long hash_hi = 52L; |
| 88 | unsigned long msgsize = 0L; |
| 89 | unsigned long cumsize = 0L; /* cumulative archive size bytes / 256 */ |
| 90 | char flagcd = '\0'; /* no transfer-encoding for trailer */ |
| 91 | char encin = '\0'; |
| 92 | int flagindexed; |
| 93 | int flagfoundokpart; /* Found something to pass on. If multipart */ |
| 94 | /* we set to 0 and then set to 1 for any */ |
| 95 | /* acceptable mime part. If 0 -> reject */ |
| 96 | int flagreceived; |
| 97 | int flagprefixed; |
| 98 | unsigned int serial = 0; |
| 99 | int flagarchived; |
| 100 | int fdarchive; |
| 101 | int fdindex; |
| 102 | int fdindexn; |
| 103 | char hashout[COOKIE+1]; |
| 104 | |
| 105 | substdio ssarchive; |
| 106 | char archivebuf[1024]; |
| 107 | |
| 108 | int flagsublist; |
| 109 | stralloc sublist = {0}; |
| 110 | stralloc mailinglist = {0}; |
| 111 | stralloc outlocal = {0}; |
| 112 | stralloc outhost = {0}; |
| 113 | stralloc headerremove = {0}; |
| 114 | struct constmap headerremovemap; |
| 115 | stralloc mimeremove = {0}; |
| 116 | struct constmap mimeremovemap; |
| 117 | char *dir; |
| 118 | |
| 119 | struct qmail qq; |
| 120 | substdio ssin; |
| 121 | char inbuf[1024]; |
| 122 | substdio ssout; |
| 123 | char outbuf[1]; |
| 124 | |
| 125 | char textbuf[512]; |
| 126 | substdio sstext; |
| 127 | |
| 128 | unsigned int mywrite(fd,buf,len) |
| 129 | int fd; |
| 130 | char *buf; |
| 131 | unsigned int len; |
| 132 | { |
| 133 | qmail_put(&qq,buf,len); |
| 134 | return len; |
| 135 | } |
| 136 | |
| 137 | int subto(s,l) |
| 138 | char *s; |
| 139 | unsigned int l; |
| 140 | { |
| 141 | qmail_put(&qq,"T",1); |
| 142 | qmail_put(&qq,s,l); |
| 143 | qmail_put(&qq,"",1); |
| 144 | return (int) l; |
| 145 | } |
| 146 | |
| 147 | void die_archive() |
| 148 | { |
| 149 | strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": "); |
| 150 | } |
| 151 | void die_numnew() |
| 152 | { |
| 153 | strerr_die3sys(111,FATAL,ERR_CREATE,"numnew: "); |
| 154 | } |
| 155 | |
| 156 | void qa_put(buf,len) char *buf; unsigned int len; |
| 157 | { |
| 158 | qmail_put(&qq,buf,len); |
| 159 | if (flagarchived) |
| 160 | if (substdio_put(&ssarchive,buf,len) == -1) die_archive(); |
| 161 | } |
| 162 | |
| 163 | void qa_puts(buf) char *buf; |
| 164 | { |
| 165 | qmail_puts(&qq,buf); |
| 166 | if (flagarchived) |
| 167 | if (substdio_puts(&ssarchive,buf) == -1) die_archive(); |
| 168 | } |
| 169 | |
| 170 | int sublistmatch(sender) |
| 171 | char *sender; |
| 172 | { |
| 173 | unsigned int i; |
| 174 | unsigned int j; |
| 175 | |
| 176 | j = str_len(sender); |
| 177 | if (j < sublist.len) return 0; |
| 178 | |
| 179 | i = byte_rchr(sublist.s,sublist.len,'@'); |
| 180 | if (i == sublist.len) return 1; |
| 181 | |
| 182 | if (byte_diff(sublist.s,i,sender)) return 0; |
| 183 | if (case_diffb(sublist.s + i,sublist.len - i,sender + j - (sublist.len - i))) |
| 184 | return 0; |
| 185 | |
| 186 | return 1; |
| 187 | } |
| 188 | |
| 189 | substdio ssnumnew; |
| 190 | char numnewbuf[16]; |
| 191 | |
| 192 | char buf0[256]; |
| 193 | substdio ss0 = SUBSTDIO_FDBUF(read,0,buf0,sizeof(buf0)); |
| 194 | |
| 195 | void numwrite() |
| 196 | { /* this one deals with msgnum, not outnum! */ |
| 197 | int fd; |
| 198 | |
| 199 | fd = open_trunc("numnew"); |
| 200 | if (fd == -1) die_numnew(); |
| 201 | substdio_fdbuf(&ssnumnew,write,fd,numnewbuf,sizeof(numnewbuf)); |
| 202 | if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,msgnum)) == -1) |
| 203 | die_numnew(); |
| 204 | if (substdio_puts(&ssnumnew,":") == -1) die_numnew(); |
| 205 | if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,cumsize)) == -1) |
| 206 | die_numnew(); |
| 207 | |
| 208 | if (substdio_puts(&ssnumnew,"\n") == -1) die_numnew(); |
| 209 | if (substdio_flush(&ssnumnew) == -1) die_numnew(); |
| 210 | if (fsync(fd) == -1) die_numnew(); |
| 211 | if (close(fd) == -1) die_numnew(); /* NFS stupidity */ |
| 212 | if (rename("numnew","num") == -1) |
| 213 | strerr_die3sys(111,FATAL,ERR_MOVE,"numnew: "); |
| 214 | } |
| 215 | |
| 216 | stralloc mydtline = {0}; |
| 217 | |
| 218 | int idx_copy_insertsubject() |
| 219 | /* copies old index file up to but not including msg, then adds a line with */ |
| 220 | /* 'sub' trimmed of reply indicators, then closes the new index and moves it*/ |
| 221 | /* to the name 'index'. Errors are dealt with directly, and if the routine */ |
| 222 | /* returns, it was successful. 'fatal' points to a program-specific error */ |
| 223 | /* string. Sub is not destroyed, but from is!!! */ |
| 224 | /* returns 1 if reply-indicators were found, 0 otherwise. */ |
| 225 | /* no terminal \n or \0 in any of the strallocs! */ |
| 226 | { |
| 227 | char *cp; |
| 228 | unsigned long idx; |
| 229 | int match; |
| 230 | int r; |
| 231 | unsigned int pos; |
| 232 | |
| 233 | if (!stralloc_copys(&fnadir,"archive/")) die_nomem(); |
| 234 | if (!stralloc_catb(&fnadir,strnum,fmt_ulong(strnum,outnum / 100))) |
| 235 | die_nomem(); |
| 236 | if (!stralloc_copy(&fnif,&fnadir)) die_nomem(); |
| 237 | if (!stralloc_copy(&fnifn,&fnif)) die_nomem(); |
| 238 | if (!stralloc_cats(&fnif,"/index")) die_nomem(); |
| 239 | if (!stralloc_cats(&fnifn,"/indexn")) die_nomem(); |
| 240 | if (!stralloc_0(&fnif)) die_nomem(); |
| 241 | if (!stralloc_0(&fnifn)) die_nomem(); |
| 242 | if (!stralloc_0(&fnadir)) die_nomem(); |
| 243 | |
| 244 | /* may not exists since we run before ezmlm-send */ |
| 245 | if (mkdir(fnadir.s,0755) == -1) |
| 246 | if (errno != error_exist) |
| 247 | strerr_die4x(111,FATAL,ERR_CREATE,fnadir.s,": "); |
| 248 | |
| 249 | /* Open indexn */ |
| 250 | fdindexn = open_trunc(fnifn.s); |
| 251 | if (fdindexn == -1) |
| 252 | strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": "); |
| 253 | |
| 254 | /* set up buffers for indexn */ |
| 255 | substdio_fdbuf(&ssindexn,write,fdindexn,indexnbuf,sizeof(indexnbuf)); |
| 256 | |
| 257 | concatHDR(subject.s,subject.len,&lines,FATAL); /* make 1 line */ |
| 258 | decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL); /* decode mime */ |
| 259 | r = unfoldHDR(qline.s,qline.len,&lines,charset.s,&dcprefix,1,FATAL); |
| 260 | /* trim mime */ |
| 261 | |
| 262 | fdindex = open_read(fnif.s); |
| 263 | if (fdindex == -1) { |
| 264 | if (errno != error_noent) |
| 265 | strerr_die4x(111,FATAL,ERR_OPEN, fnif.s, ": "); |
| 266 | } else { |
| 267 | substdio_fdbuf(&ssin,read,fdindex,inbuf,sizeof(inbuf)); |
| 268 | for(;;) { |
| 269 | if (getln(&ssin,&qline,&match,'\n') == -1) |
| 270 | strerr_die4sys(111,FATAL,ERR_READ, fnif.s, ": "); |
| 271 | if (!match) |
| 272 | break; |
| 273 | pos = scan_ulong(qline.s,&idx); |
| 274 | if (!idx) /* "impossible!" */ |
| 275 | strerr_die2x(111,FATAL,ERR_BAD_INDEX); |
| 276 | if (idx >= outnum) |
| 277 | break; /* messages always come in order */ |
| 278 | if (substdio_put(&ssindexn,qline.s,qline.len) == -1) |
| 279 | die_indexn(); |
| 280 | if (qline.s[pos] == ':') { /* has author line */ |
| 281 | if (getln(&ssin,&qline,&match,'\n') == -1) |
| 282 | strerr_die4x(111,FATAL,ERR_READ, fnif.s, ": "); |
| 283 | if (!match && qline.s[0] != '\t') /* "impossible! */ |
| 284 | strerr_die2x(111,FATAL,ERR_BAD_INDEX); |
| 285 | if (substdio_put(&ssindexn,qline.s,qline.len) == -1) |
| 286 | die_indexn(); |
| 287 | } |
| 288 | } |
| 289 | close(fdindex); |
| 290 | } |
| 291 | if (!stralloc_copyb(&qline,strnum,fmt_ulong(strnum,outnum))) die_nomem(); |
| 292 | if (!stralloc_cats(&qline,": ")) die_nomem(); /* ':' for new ver */ |
| 293 | makehash(lines.s,lines.len,hash); |
| 294 | if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem(); |
| 295 | if (!stralloc_cats(&qline," ")) die_nomem(); |
| 296 | if (r & 1) /* reply */ |
| 297 | if (!stralloc_cats(&qline,"Re: ")) die_nomem(); |
| 298 | if (!stralloc_cat(&qline,&lines)) die_nomem(); |
| 299 | if (!stralloc_cats(&qline,"\n\t")) die_nomem(); |
| 300 | if (!stralloc_cat(&qline,&received)) die_nomem(); |
| 301 | if (!stralloc_cats(&qline,";")) die_nomem(); |
| 302 | |
| 303 | concatHDR(from.s,from.len,&lines,FATAL); |
| 304 | mkauthhash(lines.s,lines.len,hash); |
| 305 | |
| 306 | if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem(); |
| 307 | if (!stralloc_cats(&qline," ")) die_nomem(); |
| 308 | |
| 309 | decodeHDR(cp,author_name(&cp,lines.s,lines.len),&from,charset.s,FATAL); |
| 310 | (void) unfoldHDR(from.s,from.len,&lines,charset.s,&dcprefix,0,FATAL); |
| 311 | if (!stralloc_cat(&qline,&lines)) die_nomem(); |
| 312 | |
| 313 | if (!stralloc_cats(&qline,"\n")) die_nomem(); |
| 314 | if (substdio_put(&ssindexn,qline.s,qline.len) == -1) die_indexn(); |
| 315 | if (substdio_flush(&ssindexn) == -1) die_indexn(); |
| 316 | if (fsync(fdindexn) == -1) die_indexn(); |
| 317 | if (fchmod(fdindexn,MODE_ARCHIVE | 0700) == -1) die_indexn(); |
| 318 | if (close(fdindexn) == -1) die_indexn(); /* NFS stupidity */ |
| 319 | if (rename(fnifn.s,fnif.s) == -1) |
| 320 | strerr_die4x(111,FATAL,ERR_MOVE,fnifn.s,": "); |
| 321 | return r; |
| 322 | } |
| 323 | |
| 324 | void transferenc() |
| 325 | { |
| 326 | if (flagcd) { |
| 327 | qmail_puts(&qq,"\nContent-Transfer-Encoding: "); |
| 328 | if (flagcd == 'Q') |
| 329 | qmail_puts(&qq,"Quoted-printable\n\n"); |
| 330 | else |
| 331 | qmail_puts(&qq,"base64\n\n"); |
| 332 | } else |
| 333 | qmail_puts(&qq,"\n\n"); |
| 334 | } |
| 335 | |
| 336 | void getcharset() |
| 337 | { |
| 338 | if (getconf_line(&charset,"charset",0,FATAL,dir)) { |
| 339 | if (charset.len >= 2 && charset.s[charset.len - 2] == ':') { |
| 340 | if (charset.s[charset.len - 1] == 'B' || |
| 341 | charset.s[charset.len - 1] == 'Q') { |
| 342 | flagcd = charset.s[charset.len - 1]; |
| 343 | charset.s[charset.len - 2] = '\0'; |
| 344 | } |
| 345 | } |
| 346 | } else |
| 347 | if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem(); |
| 348 | |
| 349 | if (!stralloc_0(&charset)) die_nomem(); |
| 350 | } |
| 351 | |
| 352 | void main(argc,argv) |
| 353 | int argc; |
| 354 | char **argv; |
| 355 | { |
| 356 | unsigned long subs; |
| 357 | int fdlock; |
| 358 | char *sender; |
| 359 | char *mlheader = (char *) 0; |
| 360 | char *ret; |
| 361 | char *err; |
| 362 | int flagmlwasthere; |
| 363 | int flagqmqp = 0; /* don't use qmqp by default */ |
| 364 | int flaglistid = 0; /* no listid header added */ |
| 365 | int match; |
| 366 | unsigned int i; |
| 367 | int r,fd; |
| 368 | int flaginheader; |
| 369 | int flagbadfield; |
| 370 | int flagbadpart; |
| 371 | int flagseenext; |
| 372 | int flagsubline; |
| 373 | int flagfromline; |
| 374 | int flagcontline; |
| 375 | int flagarchiveonly; |
| 376 | int flagtrailer; |
| 377 | unsigned int pos; |
| 378 | int opt; |
| 379 | char *cp, *cpstart, *cpafter; |
| 380 | |
| 381 | umask(022); |
| 382 | sig_pipeignore(); |
| 383 | |
| 384 | while ((opt = getopt(argc,argv,"cCh:H:lLrRqQs:S:vV")) != opteof) |
| 385 | switch(opt) { |
| 386 | case 'c': case 'C': break; /* ignore for backwards compat */ |
| 387 | case 'h': |
| 388 | case 'H': mlheader = optarg; /* Alternative sublist check header */ |
| 389 | mlheader[str_chr(mlheader,':')] = '\0'; |
| 390 | break; |
| 391 | case 'l': flaglog = 1; break; |
| 392 | case 'L': flaglog = 0; break; |
| 393 | case 'r': flagnoreceived = 0; break; |
| 394 | case 'R': flagnoreceived = 1; break; |
| 395 | case 's': |
| 396 | case 'S': pos = scan_ulong(optarg,&hash_lo); |
| 397 | if (!optarg[pos++]) break; |
| 398 | (void) scan_ulong(optarg+pos,&hash_hi); |
| 399 | if (hash_hi > 52L) hash_hi = 52L; |
| 400 | if (hash_lo > hash_hi) hash_lo = hash_hi; |
| 401 | |
| 402 | break; |
| 403 | case 'q': flagqmqp = 0; break; |
| 404 | case 'Q': flagqmqp = 1; break; |
| 405 | case 'v': |
| 406 | case 'V': strerr_die2x(0, |
| 407 | "ezmlm-send version: ezmlm-0.53+",EZIDX_VERSION); |
| 408 | default: |
| 409 | die_usage(); |
| 410 | } |
| 411 | |
| 412 | |
| 413 | dir = argv[optind++]; |
| 414 | if (!dir) die_usage(); |
| 415 | |
| 416 | sender = env_get("SENDER"); |
| 417 | |
| 418 | if (chdir(dir) == -1) |
| 419 | strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": "); |
| 420 | |
| 421 | fdlock = open_append("lock"); |
| 422 | if (fdlock == -1) |
| 423 | strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: "); |
| 424 | if (lock_ex(fdlock) == -1) |
| 425 | strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: "); |
| 426 | |
| 427 | flagarchived = getconf_line(&line,"archived",0,FATAL,dir); |
| 428 | flagindexed = getconf_line(&line,"indexed",0,FATAL,dir); |
| 429 | getcharset(); |
| 430 | flagprefixed = getconf_line(&prefix,"prefix",0,FATAL,dir); |
| 431 | if (prefix.len) { /* encoding and serial # support */ |
| 432 | /* no sanity checks - you put '\n' or '\0' */ |
| 433 | /* into the coded string, you pay */ |
| 434 | |
| 435 | decodeHDR(prefix.s,prefix.len,&line,charset.s,FATAL); |
| 436 | (void) unfoldHDR(line.s,line.len,&dcprefix,charset.s,&dummy,0,FATAL); |
| 437 | if (!stralloc_copy(&dcprefix,&line)) die_nomem(); |
| 438 | serial = byte_rchr(prefix.s,prefix.len,'#'); |
| 439 | } |
| 440 | if ((fd = open_read("text/trailer")) == -1) { /* see if there is a trailer */ |
| 441 | if (errno == error_noent) flagtrailer = 0; |
| 442 | else strerr_die2sys(111,ERR_OPEN,"text/trailer: "); |
| 443 | } else { |
| 444 | close(fd); |
| 445 | flagtrailer = 1; |
| 446 | } |
| 447 | |
| 448 | getconf(&mimeremove,"mimeremove",0,FATAL,dir); |
| 449 | |
| 450 | if (getconf_line(&line,"num",0,FATAL,dir)) { /* Now non-FATAL, def=0 */ |
| 451 | if (!stralloc_0(&line)) die_nomem(); |
| 452 | cp = line.s + scan_ulong(line.s,&msgnum); |
| 453 | ++msgnum; |
| 454 | if (*cp++ == ':') |
| 455 | scan_ulong(cp,&cumsize); |
| 456 | } else |
| 457 | msgnum = 1L; /* if num not there */ |
| 458 | |
| 459 | getconf_line(&outhost,"outhost",1,FATAL,dir); |
| 460 | getconf_line(&outlocal,"outlocal",1,FATAL,dir); |
| 461 | set_cpoutlocal(&outlocal); |
| 462 | set_cpouthost(&outhost); |
| 463 | flagsublist = getconf_line(&sublist,"sublist",0,FATAL,dir); |
| 464 | |
| 465 | if (flagqmqp) { /* forward compatibility ;-) */ |
| 466 | if (!stralloc_copys(&line,QMQPSERVERS)) die_nomem(); |
| 467 | if (!stralloc_cats(&line,"/0")) die_nomem(); |
| 468 | if (!stralloc_0(&line)) die_nomem(); |
| 469 | (void) getconf_line(&qmqpservers,line.s,0,FATAL,dir); |
| 470 | } |
| 471 | |
| 472 | getconf(&headerremove,"headerremove",1,FATAL,dir); |
| 473 | if (!constmap_init(&headerremovemap,headerremove.s,headerremove.len,0)) |
| 474 | die_nomem(); |
| 475 | |
| 476 | if (!stralloc_copys(&mydtline,"Delivered-To: mailing list ")) die_nomem(); |
| 477 | if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem(); |
| 478 | if (!stralloc_cats(&mydtline,"@")) die_nomem(); |
| 479 | if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem(); |
| 480 | if (!stralloc_cats(&mydtline,"\n")) die_nomem(); |
| 481 | |
| 482 | if (sender) { |
| 483 | if (!*sender) |
| 484 | strerr_die2x(100,FATAL,ERR_BOUNCE); |
| 485 | if (str_equal(sender,"#@[]")) |
| 486 | strerr_die2x(100,FATAL,ERR_BOUNCE); |
| 487 | if (flagsublist) |
| 488 | if (!sublistmatch(sender)) |
| 489 | strerr_die2x(100,FATAL,ERR_NOT_PARENT); |
| 490 | } |
| 491 | innum = msgnum; /* innum = incoming */ |
| 492 | outnum = msgnum; /* outnum = outgoing */ |
| 493 | if (flagsublist && !flagarchived) { /* msgnum = archive */ |
| 494 | pos = byte_rchr(sublist.s,sublist.len,'@'); /* checked in sublistmatch */ |
| 495 | if (str_start(sender+pos,"-return-")) |
| 496 | pos += 8; |
| 497 | pos += scan_ulong(sender+pos,&innum); |
| 498 | if (!flagarchived && innum && sender[pos] == '-') |
| 499 | outnum = innum; |
| 500 | } |
| 501 | szmsgnum[fmt_ulong(szmsgnum,outnum)] = '\0'; |
| 502 | set_cpnum(szmsgnum); /* for copy */ |
| 503 | |
| 504 | if (flagarchived) { |
| 505 | if (!stralloc_copys(&fnadir,"archive/")) die_nomem(); |
| 506 | if (!stralloc_catb(&fnadir,strnum, |
| 507 | fmt_ulong(strnum,outnum / 100))) die_nomem(); |
| 508 | if (!stralloc_copy(&fnaf,&fnadir)) die_nomem(); |
| 509 | if (!stralloc_cats(&fnaf,"/")) die_nomem(); |
| 510 | if (!stralloc_catb(&fnaf,strnum,fmt_uint0(strnum, |
| 511 | (unsigned int) (outnum % 100),2))) die_nomem(); |
| 512 | if (!stralloc_0(&fnadir)) die_nomem(); |
| 513 | if (!stralloc_0(&fnaf)) die_nomem(); |
| 514 | |
| 515 | if (mkdir(fnadir.s,0755) == -1) |
| 516 | if (errno != error_exist) |
| 517 | strerr_die4sys(111,FATAL,ERR_CREATE,fnadir.s,": "); |
| 518 | fdarchive = open_trunc(fnaf.s); |
| 519 | if (fdarchive == -1) |
| 520 | strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": "); |
| 521 | |
| 522 | substdio_fdbuf(&ssarchive,write,fdarchive,archivebuf,sizeof(archivebuf)); |
| 523 | /* return-path to archive */ |
| 524 | if (!stralloc_copys(&line,"Return-Path: <")) die_nomem(); |
| 525 | if (sender) { /* same as qmail-local */ |
| 526 | if (!quote2(&qline,sender)) die_nomem(); |
| 527 | for (i = 0;i < qline.len;++i) if (qline.s[i] == '\n') qline.s[i] = '_'; |
| 528 | if (!stralloc_cat(&line,&qline)) die_nomem(); |
| 529 | } |
| 530 | if (!stralloc_cats(&line,">\n")) die_nomem(); |
| 531 | if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive(); |
| 532 | } |
| 533 | |
| 534 | if (flagqmqp) { |
| 535 | if (qmail_open(&qq,&qmqpservers) == -1) /* open qmqp */ |
| 536 | strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE); |
| 537 | } else if (qmail_open(&qq,(stralloc *) 0) == -1) /* open queue */ |
| 538 | strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE); |
| 539 | |
| 540 | if (!flagsublist) { |
| 541 | getconf_line(&mailinglist,"mailinglist",1,FATAL,dir); |
| 542 | qa_puts("Mailing-List: "); |
| 543 | qa_put(mailinglist.s,mailinglist.len); |
| 544 | if (getconf_line(&line,"listid",0,FATAL,dir)) { |
| 545 | flaglistid = 1; |
| 546 | qmail_puts(&qq,"\nList-ID: "); |
| 547 | qmail_put(&qq,line.s,line.len); |
| 548 | } |
| 549 | qa_puts("\n"); |
| 550 | } |
| 551 | copy(&qq,"headeradd",'H',FATAL); |
| 552 | qa_put(mydtline.s,mydtline.len); |
| 553 | |
| 554 | flagmlwasthere = 0; |
| 555 | flaginheader = 1; |
| 556 | flagfoundokpart = 1; |
| 557 | flagbadfield = 0; |
| 558 | flagbadpart = 0; |
| 559 | flagseenext = 0; |
| 560 | flagsubline = 0; |
| 561 | flagfromline = 0; |
| 562 | flagreceived = 0; |
| 563 | for (;;) { |
| 564 | if (getln(&ss0,&line,&match,'\n') == -1) |
| 565 | strerr_die2sys(111,FATAL,ERR_READ_INPUT); |
| 566 | if (flaginheader && match) { |
| 567 | if (line.len == 1) { /* end of header */ |
| 568 | flaginheader = 0; |
| 569 | if (flagindexed) /* std entry */ |
| 570 | r = idx_copy_insertsubject(); /* all indexed lists */ |
| 571 | if (flagprefixed && !flagsublist) { |
| 572 | qa_puts("Subject:"); |
| 573 | if (!flagindexed) { /* non-indexed prefixed lists */ |
| 574 | concatHDR(subject.s,subject.len,&lines,FATAL); |
| 575 | decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL); |
| 576 | r = unfoldHDR(qline.s,qline.len,&lines, |
| 577 | charset.s,&dcprefix,1,FATAL); |
| 578 | } |
| 579 | if (!(r & 2)) { |
| 580 | qmail_puts(&qq," "); |
| 581 | if (serial == prefix.len) |
| 582 | qmail_put(&qq,prefix.s,prefix.len); |
| 583 | else { |
| 584 | qmail_put(&qq,prefix.s,serial); |
| 585 | qmail_puts(&qq,szmsgnum); |
| 586 | qmail_put(&qq,prefix.s+serial+1,prefix.len-serial-1); |
| 587 | } |
| 588 | } |
| 589 | qa_put(subject.s,subject.len); |
| 590 | } |
| 591 | /* do other stuff to do with post header processing here */ |
| 592 | if (content.len) { /* get MIME boundary, if exists */ |
| 593 | concatHDR(content.s,content.len,&qline,FATAL); |
| 594 | if (!stralloc_copy(&content,&qline)) die_nomem(); |
| 595 | |
| 596 | if (flagtrailer && /* trailer only for some multipart */ |
| 597 | case_startb(content.s,content.len,"multipart/")) |
| 598 | if (!case_startb(content.s+10,content.len-10,"mixed") && |
| 599 | !case_startb(content.s+10,content.len-10,"digest") && |
| 600 | !case_startb(content.s+10,content.len-10,"parallel")) |
| 601 | flagtrailer = 0; |
| 602 | |
| 603 | cp = content.s; |
| 604 | cpafter = cp + content.len; /* check after each ';' */ |
| 605 | while ((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) { |
| 606 | ++cp; |
| 607 | while (cp < cpafter && |
| 608 | (*cp == ' ' || *cp == '\t' || *cp == '\n')) ++cp; |
| 609 | if (case_startb(cp,cpafter-cp,"boundary=")) { |
| 610 | cp += 9; /* after boundary= */ |
| 611 | if (*cp == '"') { /* quoted boundary */ |
| 612 | ++cp; |
| 613 | cpstart = cp; |
| 614 | while (cp < cpafter && *cp != '"') ++cp; |
| 615 | if (cp == cpafter) |
| 616 | strerr_die1x(100,ERR_MIME_QUOTE); |
| 617 | } else { /* non-quoted boundary */ |
| 618 | cpstart = cp; /* find terminator of boundary */ |
| 619 | while (cp < cpafter && *cp != ';' && |
| 620 | *cp != ' ' && *cp != '\t' && *cp != '\n') ++cp; |
| 621 | } |
| 622 | if (!stralloc_copys(&boundary,"--")) die_nomem(); |
| 623 | if (!stralloc_catb(&boundary,cpstart,cp-cpstart)) |
| 624 | die_nomem(); |
| 625 | flagfoundokpart = 0; |
| 626 | if (!constmap_init(&mimeremovemap,mimeremove.s,mimeremove.len,0)) |
| 627 | die_nomem(); |
| 628 | flagbadpart = 1; /* skip before first boundary */ |
| 629 | qa_puts("\n"); /* to make up for the lost '\n' */ |
| 630 | } |
| 631 | } |
| 632 | } |
| 633 | } else if ((*line.s != ' ') && (*line.s != '\t')) { |
| 634 | flagsubline = 0; |
| 635 | flagfromline = 0; |
| 636 | flagbadfield = 0; |
| 637 | flagarchiveonly = 0; |
| 638 | flagcontline = 0; |
| 639 | if (constmap(&headerremovemap,line.s,byte_chr(line.s,line.len,':'))) |
| 640 | flagbadfield = 1; |
| 641 | if ((flagnoreceived || !flagreceived) && |
| 642 | case_startb(line.s,line.len,"Received:")) { |
| 643 | if (!flagreceived) { /* get date from first rec'd */ |
| 644 | flagreceived = 1; /* line (done by qmail) */ |
| 645 | pos = byte_chr(line.s,line.len,';'); |
| 646 | if (pos != line.len) /* has '\n' */ |
| 647 | if (!stralloc_copyb(&received,line.s+pos+2,line.len - pos - 3)) |
| 648 | die_nomem(); |
| 649 | } else { /* suppress, but archive */ |
| 650 | flagarchiveonly = 1; /* but do not suppress the */ |
| 651 | flagbadfield = 1; /* top one added by qmail */ |
| 652 | } |
| 653 | } else if (case_startb(line.s,line.len,"Mailing-List:")) |
| 654 | flagmlwasthere = 1; /* sublists always ok ezmlm masters */ |
| 655 | else if (mlheader && case_startb(line.s,line.len,mlheader)) |
| 656 | flagmlwasthere = 1; /* mlheader treated as ML */ |
| 657 | else if ((mimeremove.len || flagtrailer) && /* else no MIME need*/ |
| 658 | case_startb(line.s,line.len,"Content-Type:")) { |
| 659 | if (!stralloc_copyb(&content,line.s+13,line.len-13)) die_nomem(); |
| 660 | flagcontline = 1; |
| 661 | } else if (case_startb(line.s,line.len,"Subject:")) { |
| 662 | if (!stralloc_copyb(&subject,line.s+8,line.len-8)) die_nomem(); |
| 663 | flagsubline = 1; |
| 664 | if (flagprefixed && !flagsublist) /* don't prefix for sublists */ |
| 665 | flagbadfield = 1; /* we'll print our own */ |
| 666 | } else if (flagtrailer && |
| 667 | case_startb(line.s,line.len,"Content-Transfer-Encoding:")) { |
| 668 | cp = line.s + 26; |
| 669 | cpafter = cp + line.len; |
| 670 | while (cp < cpafter && (*cp == ' ' || *cp == '\t')) ++cp; |
| 671 | if (case_startb(cp,cpafter-cp,"base64")) encin = 'B'; |
| 672 | else if (case_startb(cp,cpafter-cp,"Quoted-Printable")) encin = 'Q'; |
| 673 | } else if (flaglistid && case_startb(line.s,line.len,"list-id:")) |
| 674 | flagbadfield = 1; /* suppress if we added our own */ |
| 675 | else if (flagindexed) { |
| 676 | |
| 677 | if (case_startb(line.s,line.len,"From:")) { |
| 678 | flagfromline = 1; |
| 679 | if (!stralloc_copyb(&from,line.s+5,line.len-5)) die_nomem(); |
| 680 | } |
| 681 | } else if (line.len == mydtline.len) |
| 682 | if (!byte_diff(line.s,line.len,mydtline.s)) |
| 683 | strerr_die2x(100,FATAL,ERR_LOOPING); |
| 684 | } else { /* continuation lines */ |
| 685 | if (flagsubline) { |
| 686 | if (!stralloc_cat(&subject,&line)) die_nomem(); |
| 687 | } else if (flagfromline) { |
| 688 | if (!stralloc_cat(&from,&line)) die_nomem(); |
| 689 | } else if (flagcontline) { |
| 690 | if (!stralloc_cat(&content,&line)) die_nomem(); |
| 691 | } |
| 692 | } |
| 693 | } else /* body */ |
| 694 | msgsize += line.len; /* always for tstdig support */ |
| 695 | |
| 696 | if (!(flaginheader && flagbadfield)) { |
| 697 | if (boundary.len && line.len > boundary.len && |
| 698 | !str_diffn(line.s,boundary.s,boundary.len)) { |
| 699 | if (line.s[boundary.len] == '-' && line.s[boundary.len+1] == '-') { |
| 700 | flagbadpart = 0; /* end boundary should be output */ |
| 701 | if (flagtrailer) { |
| 702 | qmail_puts(&qq,"\n"); |
| 703 | qmail_put(&qq,boundary.s,boundary.len); |
| 704 | qmail_puts(&qq,"\nContent-Type: text/plain; charset="); |
| 705 | qmail_puts(&qq,charset.s); |
| 706 | transferenc(); /* trailer for multipart message */ |
| 707 | copy(&qq,"text/trailer",flagcd,FATAL); |
| 708 | if (flagcd == 'B') { /* need to do our own flushing */ |
| 709 | encodeB("",0,&qline,2,FATAL); |
| 710 | qmail_put(&qq,qline.s,qline.len); |
| 711 | } |
| 712 | } |
| 713 | } else { /* new part */ |
| 714 | flagbadpart = 1; /* skip lines */ |
| 715 | if (!stralloc_copy(&lines,&line)) die_nomem(); /* but save */ |
| 716 | flagseenext = 1; /* need to check Cont-type */ |
| 717 | } |
| 718 | } else if (flagseenext) { /* last was boundary, now stored */ |
| 719 | if (case_startb(line.s,line.len,"content-type:")) { |
| 720 | flagseenext = 0; /* done thinking about it */ |
| 721 | cp = line.s + 13; /* start of type */ |
| 722 | while (*cp == ' ' || *cp == '\t') ++cp; |
| 723 | cpstart = cp; /* end of type */ |
| 724 | while (*cp != '\n' && *cp != '\t' && *cp != ' ' && *cp != ';') ++cp; |
| 725 | if (constmap(&mimeremovemap,cpstart,cp-cpstart)) { |
| 726 | flagbadpart = 1; |
| 727 | } else { |
| 728 | flagfoundokpart = 1; |
| 729 | qa_put(lines.s,lines.len); /* saved lines */ |
| 730 | flagbadpart = 0; /* do this part */ |
| 731 | } |
| 732 | } else if (line.len == 1) { /* end of content desc */ |
| 733 | flagbadpart = 0; /* default type, so ok */ |
| 734 | flagseenext = 0; /* done thinking about it */ |
| 735 | } else /* save line in cont desc */ |
| 736 | if (!stralloc_cat(&lines,&line)) die_nomem(); |
| 737 | } |
| 738 | if (!flagbadpart) |
| 739 | qa_put(line.s,line.len); |
| 740 | |
| 741 | } else if (flagarchiveonly && flagarchived) /* received headers */ |
| 742 | if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive(); |
| 743 | if (!match) |
| 744 | break; |
| 745 | } |
| 746 | if (!boundary.len && flagtrailer) { |
| 747 | qmail_puts(&qq,"\n"); /* trailer for non-multipart message */ |
| 748 | if (!encin || encin == 'Q') { /* can add for QP, but not for base64 */ |
| 749 | copy(&qq,"text/trailer",encin,FATAL); |
| 750 | qmail_puts(&qq,"\n"); /* no need to flush for plain/QP */ |
| 751 | } |
| 752 | } |
| 753 | |
| 754 | cumsize += (msgsize + 128L) >> 8; /* round to 256 byte 'records' */ |
| 755 | /* check message tag */ |
| 756 | if (flagsublist) { /* sublists need tag if selected/suppt*/ |
| 757 | if (flaglog) |
| 758 | if ((ret = checktag(dir,innum,hash_lo+1L,"m",(char *) 0,hashout))) { |
| 759 | if (*ret) strerr_die2x(111,FATAL,ret); |
| 760 | else strerr_die2x(100,FATAL,ERR_NOT_PARENT); |
| 761 | } |
| 762 | if (!flagmlwasthere) /* sublists need ML header */ |
| 763 | strerr_die2x(100,FATAL,ERR_SUBLIST); |
| 764 | } else /* others are not allowed to have one */ |
| 765 | if (flagmlwasthere) |
| 766 | strerr_die2x(100,FATAL,ERR_MAILING_LIST); |
| 767 | if (!flagfoundokpart) /* all parts were on the strip list */ |
| 768 | strerr_die2x(100,FATAL,ERR_BAD_ALL); |
| 769 | |
| 770 | if (flagarchived) { |
| 771 | if (substdio_flush(&ssarchive) == -1) die_archive(); |
| 772 | if (fsync(fdarchive) == -1) die_archive(); |
| 773 | if (fchmod(fdarchive,MODE_ARCHIVE | 0700) == -1) die_archive(); |
| 774 | if (close(fdarchive) == -1) die_archive(); /* NFS stupidity */ |
| 775 | } |
| 776 | |
| 777 | if (flaglog) { |
| 778 | tagmsg(dir,innum,sender,"m",hashout,qq.msgbytes,53L,FATAL); |
| 779 | hashout[COOKIE] = '\0'; |
| 780 | } |
| 781 | |
| 782 | numwrite(); |
| 783 | if (!stralloc_copy(&line,&outlocal)) die_nomem(); |
| 784 | if (!stralloc_cats(&line,"-return-")) die_nomem(); |
| 785 | if (!stralloc_cats(&line,szmsgnum)) die_nomem(); |
| 786 | if (!stralloc_cats(&line,"-@")) die_nomem(); |
| 787 | if (!stralloc_cat(&line,&outhost)) die_nomem(); |
| 788 | if (!stralloc_cats(&line,"-@[]")) die_nomem(); |
| 789 | if (!stralloc_0(&line)) die_nomem(); |
| 790 | qmail_from(&qq,line.s); /* envelope sender */ |
| 791 | subs = putsubs(dir,hash_lo,hash_hi,subto,1,FATAL); /* subscribers */ |
| 792 | if (flagsublist) hash_lo++; |
| 793 | |
| 794 | if (*(err = qmail_close(&qq)) == '\0') { |
| 795 | if (flaglog) /* mysql logging */ |
| 796 | (void) logmsg(dir,outnum,hash_lo,subs,flagsublist ? 3 : 4); |
| 797 | closesql(); |
| 798 | strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0; |
| 799 | strerr_die2x(0,"ezmlm-send: info: qp ",strnum); |
| 800 | } else { |
| 801 | --msgnum; |
| 802 | cumsize -= (msgsize + 128L) >> 8; |
| 803 | numwrite(); |
| 804 | strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1); |
| 805 | } |
| 806 | } |