/* * Copyright 1990 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. */ /* * Copyright 1994-2003 by Matti Aarnio * * To really understand how headers (and their converted versions) * are processed you do need to draw a diagram. * Basically: * rp->desc->headers[] is index to ALL of the headers, and * rp->desc->headerscvt[] is index to ALL of the CONVERTED headers. * Elements on these arrays are "char *strings[]" which are the * actual headers. * There are multiple-kind headers depending upon how they have been * rewritten, and those do tack together for each recipients (rp->) * There * rp->newmsgheader is a pointer to an element on rp->desc->headers[] * rp->newmsgheadercvt is respectively an elt on rp->desc->headerscvt[] * * The routine-collection mimeheaders.c creates converted headers, * if the receiving system needs them. Converted data is created only * once per rewrite-rule group, so there should not be messages which * report "Received: ... convert XXXX convert XXXX convert XXXX; ..." * for as many times as there there are recipients for the message. * [mea@utu.fi] - 25-Jul-94 */ #include "hostenv.h" #include #include #include #include #ifdef HAVE_FCNTL_H # include #endif #include #include "ta.h" #include "mail.h" #include "zmalloc.h" #include "libz.h" #include "libc.h" #if defined(HAVE_MMAP) #include #endif #include extern int errno; #ifndef strrchr extern char *strrchr(); #endif static struct taddress *ctladdr __((struct ctldesc *d, char *cp)); int ta_use_mmap; #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif /* !MAXPATHLEN */ void ctlfree(dp,anyp) struct ctldesc *dp; void *anyp; { unsigned long lowlim = (unsigned long) dp->contents; unsigned long highlim = (unsigned long)(((char*)lowlim) + dp->contentsize); #if 0 fprintf(stderr,"# ctlfree(%p) (%p,%p] @%p\n", anyp, lowlim, highlim, __builtin_return_address(0)); #endif if (anyp && (((unsigned long)anyp) < lowlim || ((unsigned long)anyp) >= highlim)) free(anyp); /* It isn't within DP->CONTENTS data.. */ } void * ctlrealloc(dp,anyp,size) struct ctldesc *dp; void *anyp; size_t size; { void * lowlim = (void *) dp->contents; void * highlim = (void *)(((char*)lowlim) + dp->contentsize); void *anyp2; #if 0 fprintf(stderr,"# ctlrealloc(%p,%lu) (%p,%p] @%p\n", anyp, size, lowlim, highlim, __builtin_return_address(0)); #endif /* If old one isn't our local thing, delete it! */ if (anyp < lowlim || anyp >= highlim) return realloc(anyp, size); /* realloc(); it isn't within DP->CONTENTS data.. */ /* Allocate a new storage.. */ anyp2 = (void*) malloc(size); if (!anyp) return NULL; memcpy(anyp2,anyp,size); return anyp2; } void ctlclose(dp) struct ctldesc *dp; { struct taddress *ap; struct rcpt *rp; char ***msghpp; for (rp = dp->recipients; rp != NULL; rp = rp->next) { if (rp->lockoffset == 0) continue; diagnostic(NULL, rp, EX_TEMPFAIL, 0, "address was left locked!!"); } #ifdef HAVE_MMAP if (ta_use_mmap > 0) { if (dp->let_buffer != NULL) munmap((void*)dp->let_buffer, dp->let_end - dp->let_buffer); dp->let_buffer = dp->let_end = NULL; if (dp->ctlmap != NULL) munmap((void*)dp->ctlmap, dp->contentsize); dp->ctlmap = NULL; } else #endif { if (dp->let_buffer_size) free((void*)(dp->let_buffer)); dp->let_buffer = dp->let_end = NULL; } dp->let_buffer_size = 0; if (dp->ctlfd >= 0) close(dp->ctlfd); if (dp->msgfd >= 0) close(dp->msgfd); for (ap = dp->ta_chain; ap != NULL; ap = dp->ta_chain) { dp->ta_chain = ap->ta_next; free((char *)ap); } dp->ta_chain = dp->senders = NULL; for (rp = dp->rp_chain; rp != NULL; rp = dp->rp_chain) { dp->rp_chain = rp->rp_next; if (rp->top_received) free((void*)(rp->top_received)); rp->top_received = NULL; if (rp->lockoffset) { fprintf(stdout, "# undiagnosed: %s %d\n", dp->msgfile, rp->id); } rp->lockoffset = 0; free((void *)rp); } dp->recipients = NULL; /* Free ALL dp->msgheader's, if they have been reallocated. Don't free on individual recipients, only on this global set.. */ for (msghpp = dp->msgheaders; msghpp && *msghpp; ++msghpp) { char **msghp = *msghpp; for ( ; msghp && *msghp ; ++msghp ) ctlfree(dp,*msghp); free(*msghpp); } free(dp->msgheaders); dp->msgheaders = NULL; for (msghpp = dp->msgheaderscvt; msghpp && *msghpp; ++msghpp) { char **msghp = *msghpp; for ( ; msghp && *msghp ; ++msghp ) free(*msghp); /* These CVTs are always malloc()ed strings */ free(*msghpp); } free(dp->msgheaderscvt); dp->msgheaderscvt = NULL; if (dp->offset != NULL) free((void*)dp->offset); dp->offset = NULL; if (dp->contents != NULL) free((void*)dp->contents); dp->contents = NULL; if (dp->taspoolid) free((void*)dp->taspoolid); dp->taspoolid = NULL; free( (void*) dp ); } static void free_last_ap(d) struct ctldesc *d; /* Chain in for latter free()ing */ { struct taddress *ap = d->ta_chain; d->ta_chain = ap->ta_next; ap->ta_next = NULL; free((void*)ap); } static struct taddress * ctladdr(d,cp) struct ctldesc *d; /* Chain in for latter free()ing */ char *cp; { struct taddress *ap; ap = (struct taddress *)malloc(sizeof (struct taddress)); if (ap == NULL) return NULL; ap->link = NULL; /* Link in the free-up chain */ ap->ta_next = d->ta_chain; d->ta_chain = ap; /* While space: */ while (*cp == ' ' || *cp == '\t') ++cp; /* CHANNEL: */ ap->channel = cp; cp = skip821address(cp); if (*cp) *cp++ = '\0'; /* While space: */ while (*cp == ' ' || *cp == '\t') ++cp; /* HOST: */ ap->host = cp; /* While not space: */ cp = skip821address(cp); if (*cp) *cp++ = '\0'; /* While space: */ while (*cp == ' ' || *cp == '\t') ++cp; /* USER: */ ap->user = cp; cp = skip821address(cp); if (*cp) *cp++ = '\0'; /* PRIVILEGE: */ ap->misc = cp; return ap; } #ifdef __STDC__ struct ctldesc * ctlopen(const char *file, const char *channel, const char *host, int *exitflagp, int (*selectaddr)(const char *, const char *, void *), void *saparam) #else struct ctldesc * ctlopen(file, channel, host, exitflagp, selectaddr, saparam) const char *file, *channel, *host; int *exitflagp; int (*selectaddr) __((const char *, const char *, void *)); void *saparam; #endif { register char *s, *contents; char *mfpath, *delayslot; int i, n; struct taddress *ap; struct rcpt *rp = NULL, *prevrp = NULL; struct stat stbuf; char ***msgheaders = NULL; char ***msgheaderscvt = NULL; int headers_cnt; int headers_spc; int largest_headersize = 80; /* Some magic minimum.. */ char dirprefix[8]; char spoolid[30]; int mypid = getpid(); long format = 0; struct ctldesc *d; if (selectaddr == ctlsticky) ctlsticky(NULL,NULL,NULL); /* Reset the internal state.. */ d = (struct ctldesc *)malloc(sizeof(*d)); if (!d) return NULL; memset(d,0,sizeof(*d)); if (*file >= 'A') { char *p; /* Has some hash subdirectory in front of itself */ strncpy(dirprefix,file,sizeof(dirprefix)); dirprefix[sizeof(dirprefix)-1] = 0; p = strrchr(dirprefix,'/'); if (p) *++p = 0; /* "A/B/" */ } else dirprefix[0] = 0; d->msgfd = -1; /* The zero is not always good for your health .. */ d->ctlfd = open(file, O_RDWR, 0); if (d->ctlfd < 0) { char cwd[MAXPATHLEN], buf[MAXPATHLEN+MAXPATHLEN+100]; int e = errno; /* Save it over the getwd() */ #ifdef HAVE_GETCWD getcwd(cwd,MAXPATHLEN); #else getwd(cwd); #endif sprintf(buf, "Cannot open control file \"%%s\" from \"%s\" for \"%%s/%%s\" as uid %d!", cwd, (int)geteuid()); errno = e; if (host == NULL) host = "-"; warning(buf, file, channel, host); ctlclose(d); return NULL; } if (fstat(d->ctlfd, &stbuf) < 0) { warning("Cannot stat control file \"%s\"! (%m)", file); ctlclose(d); return NULL; } if (!S_ISREG(stbuf.st_mode)) { warning("Control file \"%s\" is not a regular file!", file); close(d->ctlfd); return NULL; } /* 4 is the minimum number of characters per line */ n = sizeof (long) * (stbuf.st_size / 4); d->contents = contents = s = malloc((u_int)stbuf.st_size+1); if (d->contents == NULL) { warning("Out of virtual memory!", (char *)NULL); exit(EX_SOFTWARE); } d->offset = (long *)malloc((u_int)n); if (d->offset == NULL) { warning("Out of virtual memory!", (char *)NULL); exit(EX_SOFTWARE); } fcntl(d->ctlfd, F_SETFD, 1); /* Close-on-exec */ #if defined(HAVE_MMAP) if (ta_use_mmap == 0) { /* uninitialized */ const char *s = getzenv("TA_USE_MMAP"); if (s && *s == '1') ta_use_mmap = 1; else ta_use_mmap = -1; } if (ta_use_mmap > 0) { #ifndef MAP_VARIABLE # define MAP_VARIABLE 0 #endif #ifndef MAP_FILE # define MAP_FILE 0 #endif /* We do recipient locking via MMAP_SHARED RD/WR ! Less syscalls.. */ d->ctlmap = (char *)mmap(NULL, stbuf.st_size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED|MAP_VARIABLE, d->ctlfd, 0); if ((int)d->ctlmap == -1) d->ctlmap = NULL; /* Failed ?? */ } #else d->ctlmap = NULL; #endif d->contentsize = (int) stbuf.st_size; contents[ d->contentsize ] = 0; /* Treat it as a long string.. */ if (read(d->ctlfd, contents, d->contentsize) != d->contentsize) { warning("Wrong size read from control file \"%s\"! (%m)", file); ctlclose(d); return NULL; } n = markoff(contents, d->contentsize, d->offset, file); if (n < 4) { int was_turnme = (contents[0] == _CF_TURNME); /* * If it is less than the minimum possible number of control * lines, then there is something wrong... */ ctlclose(d); /* Is it perhaps just the ETRN request file ? and manual expirer gave it to us ? Never mind then.. */ if (was_turnme) return NULL; warning("Truncated or illegal control file \"%s\"!", file); /* exit(EX_PROTOCOL); */ sleep(60); return NULL; } s = strrchr(file,'/'); /* In case the file in in a subdir.. */ if (s) d->ctlid = atol(s+1); else d->ctlid = atol(file); d->senders = NULL; d->recipients = NULL; d->ta_chain = NULL; d->rp_chain = NULL; d->rcpnts_total = 0; d->rcpnts_remaining = 0; d->rcpnts_failed = 0; d->logident = "none"; d->envid = NULL; d->dsnretmode = NULL; d->verbose = NULL; headers_cnt = 0; headers_spc = 2; for (i = 0; i < n; ++i) if (contents[ d->offset[i] ] == _CF_MSGHEADERS) ++headers_spc; msgheaders = (char***)malloc(sizeof(char***) * (headers_spc+1)); msgheaderscvt = (char***)malloc(sizeof(char***) * (headers_spc+1)); d->msgheaders = msgheaders; /* Original headers */ d->msgheaderscvt = msgheaderscvt; /* Modified set */ /* run through the file and set up the information we need */ for (i = 0; i < n; ++i) { if (*exitflagp && d->recipients == NULL) break; /* Shudder... we trash the memory block here.. */ s = contents + d->offset[i]; switch (*s) { case _CF_FORMAT: ++s; format = 0; sscanf(s,"%li",&format); if (format & (~_CF_FORMAT_KNOWN_SET)) { warning("Unsupported SCHEDULER file format flags seen: 0x%x at file '%s'", format, file); *exitflagp = 1; break; } break; case _CF_SENDER: ap = ctladdr(d,s+2); if (ap == NULL) { warning("Out of virtual memory!", (char *)NULL); *exitflagp = 1; break; } ap->link = d->senders; /* Test if this is "error"-channel.. If it is, ap->user points to NUL string. */ /* mea: altered the scheme, we must detect the "error" channel otherwise */ /* if (strcmp(ap->channel,"error")==0) ap->user = ""; */ d->senders = ap; break; case _CF_RECIPIENT: ++s; /* Calculate statistics .. Scheduler asks for it.. */ d->rcpnts_total += 1; if (*s == _CFTAG_NOTOK) { d->rcpnts_failed += 1; prevrp = NULL; } else if (*s != _CFTAG_OK) { d->rcpnts_remaining += 1; } if (*s != _CFTAG_NORMAL || d->senders == NULL) break; ++s; /* Unconditionally expecting _CF_FORMAT_TA_PID !! */ s += _CFTAG_RCPTPIDSIZE; delayslot = NULL; if ((format & _CF_FORMAT_DELAY1) || *s == ' ' || (*s >= '0' && *s <= '9')) { /* Newer DELAY data slot - _CFTAG_RCPTDELAYSIZE bytes */ delayslot = s; s += _CFTAG_RCPTDELAYSIZE; } ap = ctladdr(d,s); if (ap == NULL) { warning("Out of virtual memory!", (char *)NULL); *exitflagp = 1; break; } if ((channel != NULL && strcmp(channel, ap->channel) != 0) || (selectaddr != NULL && !(*selectaddr)(host, (const char *)ap->host, saparam)) || (selectaddr == NULL && host != NULL && cistrcmp(host,ap->host) !=0) || !lockaddr(d->ctlfd, d->ctlmap, d->offset[i]+1, _CFTAG_NORMAL, _CFTAG_LOCK, file, host, mypid)) { free_last_ap(d); break; } ap->link = d->senders; /* point at sender address */ rp = (struct rcpt *)malloc(sizeof (struct rcpt)); if (rp == NULL) { lockaddr(d->ctlfd, d->ctlmap, d->offset[i]+1, _CFTAG_LOCK, _CFTAG_DEFER, file, host, mypid); warning("Out of virtual memory!", (char *)NULL); *exitflagp = 1; free_last_ap(d); break; } memset(rp, 0, sizeof(*rp)); rp->rp_next = d->rp_chain; d->rp_chain = rp; rp->addr = ap; rp->delayslot = delayslot; rp->id = d->offset[i]; /* XX: XOR locks are different */ rp->lockoffset = rp->id + 1; rp->next = d->recipients; rp->desc = d; /* rp->orcpt = NULL; rp->inrcpt = NULL; rp->ezmlm = NULL; rp->notify = NULL; */ rp->notifyflgs = _DSN_NOTIFY_FAILURE; /* Default behaviour */ d->recipients = rp; rp->status = EX_OK; /* rp->newmsgheader = NULL; */ rp->drptoffset = -1; rp->headeroffset = -1; prevrp = rp; break; case _CF_RCPTNOTARY: /* IETF-NOTARY-DSN DATA */ ++s; if (prevrp != NULL) { prevrp->drptoffset = d->offset[i]; while (*s) { while (*s && (*s == ' ' || *s == '\t')) ++s; if (CISTREQN("NOTIFY=",s,7)) { char *p; s += 7; prevrp->notify = p = s; while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; prevrp->notifyflgs = 0; while (*p) { if (CISTREQN("NEVER",p,5)) { p += 5; prevrp->notifyflgs |= _DSN_NOTIFY_NEVER; } else if (CISTREQN("DELAY",p,5)) { p += 5; prevrp->notifyflgs |= _DSN_NOTIFY_DELAY; } else if (CISTREQN("SUCCESS",p,7)) { p += 7; prevrp->notifyflgs |= _DSN_NOTIFY_SUCCESS; } else if (CISTREQN("FAILURE",p,7)) { p += 7; prevrp->notifyflgs |= _DSN_NOTIFY_FAILURE; } else if (CISTREQN("TRACE",p,5)) { p += 5; prevrp->notifyflgs |= _DSN_NOTIFY_TRACE; } else break; /* Burp !? */ if (*p == ',') ++p; } continue; } if (CISTREQN("BY=",s,3)) { long val = 0; int neg = 0, cnt = 0; s += 3; if (*s == '-') neg = 1, ++s; while ('0' <= *s && *s <= '9') { val = val * 10L + (*s - '0'); ++cnt; ++s; } if (neg) val = -val; prevrp->deliverby = val; if (*s == ';') ++s; while (*s && *s != ' ' && *s != '\t') { switch (*s) { case 'R': case 'r': prevrp->deliverbyflgs |= _DELIVERBY_R; break; case 'N': case 'n': prevrp->deliverbyflgs |= _DELIVERBY_N; break; case 'T': case 't': prevrp->deliverbyflgs |= _DELIVERBY_T; break; default: break; } ++s; } while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; continue; } if (CISTREQN("ORCPT=",s,6)) { s += 6; prevrp->orcpt = s; while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; continue; } if (CISTREQN("INRCPT=",s,7)) { s += 7; prevrp->inrcpt = s; while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; continue; } if (CISTREQN("INFROM=",s,7)) { s += 7; prevrp->infrom = s; while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; continue; } if (CISTREQN("EZMLM=",s,6)) { s += 6; prevrp->ezmlm = s; while (*s && *s != ' ' && *s != '\t') ++s; if (*s) *s++ = 0; continue; } /* XX: BOO! Unknown value! */ while (*s && *s != ' ' && *s != '\t') ++s; } /* Previous entry added, no more..! */ prevrp = NULL; } break; case _CF_MSGHEADERS: { char **msgheader = NULL; char *ss; int headerlines = 0; int headerspace = 0; int headersize = strlen(s); if (headersize > largest_headersize) largest_headersize = headersize; /* position pointer at start of the header */ while (*s && *s != '\n') ++s; ++s; /* Collect all the headers into individual "lines", keep folding information ('\n' chars) in them, if some particular header happens to be a folded one.. */ while (*s) { if (headerlines >= headerspace) { headerspace += 8; msgheader = (char**)realloc((void*) msgheader, sizeof(void*) * (headerspace+1)); } ss = s; /* Scan the string, until we see a newline *not* followed by a SPACE, or a TAB. */ while (*ss) { while (*ss && *ss != '\n') ++ss; if (*ss == '\n' && (ss[1] == ' ' || ss[1] == '\t')) ++ss; else break; } if (*ss == '\n') *ss++ = '\0'; msgheader[headerlines++] = s; msgheader[headerlines ] = NULL; s = ss; } /* And the global connection.. */ msgheaders [headers_cnt] = msgheader; msgheaderscvt[headers_cnt] = NULL; /* fill in header * of recent recipients */ for (rp = d->recipients; rp != NULL && rp->newmsgheader == NULL; rp = rp->next) { rp->newmsgheader = &msgheaders [headers_cnt]; rp->newmsgheadercvt = &msgheaderscvt[headers_cnt]; rp->headeroffset = d->offset[i] + 2; } msgheaders [++headers_cnt] = NULL; msgheaderscvt[ headers_cnt] = NULL; } break; case _CF_MESSAGEID: d->msgfile = s+2; break; case _CF_DSNENVID: d->envid = s+2; break; case _CF_DSNRETMODE: d->dsnretmode = s+2; break; case _CF_BODYOFFSET: d->msgbodyoffset = (long)atoi(s+2); break; case _CF_LOGIDENT: d->logident = s+2; break; case _CF_VERBOSE: d->verbose = s+2; break; default: /* We don't use them all... */ break; } } /* Sometimes we bail out before terminating NULLs are added->. probably before anything is added-> */ msgheaders [headers_cnt] = NULL; msgheaderscvt[headers_cnt] = NULL; if (d->recipients == NULL) { ctlclose(d); return NULL; } #ifdef USE_ALLOCA mfpath = alloca((u_int)5 + sizeof(QUEUEDIR) + strlen(dirprefix) + strlen(d->msgfile)); #else mfpath = malloc((u_int)5 + sizeof(QUEUEDIR) + strlen(dirprefix) + strlen(d->msgfile)); #endif sprintf(mfpath, "../%s/%s%s", QUEUEDIR, dirprefix, d->msgfile); if ((d->msgfd = open(mfpath, O_RDONLY, 0)) < 0) { int e = errno; for (rp = d->recipients; rp != NULL; rp = rp->next) { diagnostic(NULL, rp, EX_UNAVAILABLE, 0, "message file is missing(!) -- possibly due to delivery scheduler restart. Consider resending your message"); } errno = e; warning("Cannot open message file \"%s\"! (errno=%d)", mfpath, errno); #ifndef USE_ALLOCA free(mfpath); #endif ctlclose(d); return NULL; } if (fstat(d->msgfd,&stbuf) < 0) { stbuf.st_mode = S_IFCHR; /* Make it to be something what it clearly can't be.. */ } if (!S_ISREG(stbuf.st_mode)) { for (rp = d->recipients; rp != NULL; rp = rp->next) { diagnostic(NULL, rp, EX_UNAVAILABLE, 0, "Message file is not a regular file!"); } warning("Cannot open message file \"%s\"! (%m)", mfpath); #ifndef USE_ALLOCA free(mfpath); #endif ctlclose(d); return NULL; } d->msginonumber = (long)stbuf.st_ino; fcntl(d->msgfd, F_SETFD, 1); /* Close-on-exec */ #if defined(HAVE_MMAP) if (ta_use_mmap > 0) { d->let_buffer = (char *)mmap(NULL, stbuf.st_size, PROT_READ, MAP_FILE|MAP_SHARED|MAP_VARIABLE, d->msgfd, 0); if ((long)d->let_buffer == -1L) { warning("Out of MMAP() memory! Tried to map in (r/o) %d bytes (%m)", stbuf.st_size); #ifndef USE_ALLOCA free(mfpath); #endif ctlclose(d); return NULL; } d->let_end = d->let_buffer + stbuf.st_size; d->let_buffer_size = 0; } else #endif { d->let_buffer_size = 63*1024; d->let_buffer = malloc(d->let_buffer_size + 8); d->let_end = d->let_buffer + d->let_buffer_size; } #ifndef USE_ALLOCA free(mfpath); #endif /* The message file mtime -- arrival of the message to the system */ d->msgmtime = stbuf.st_mtime; /* Estimate the size of the message file when sent out.. */ d->msgsizeestimate = stbuf.st_size - d->msgbodyoffset; d->msgsizeestimate += largest_headersize; /* A nice fudge factor, usually this is enough.. */ /* Add 3% for CRLFs.. -- assume average line length of 35 chars. */ d->msgsizeestimate += (3 * d->msgsizeestimate) / 100; taspoolid(spoolid, d->msgmtime, d->msginonumber); d->taspoolid = strdup(spoolid); if (!d->taspoolid) { ctlclose(d); return NULL; } return d; } int ctlsticky(spec_host, addr_host, cbparam) const char *spec_host, *addr_host; void *cbparam; { static const char *hostref = NULL; if (hostref == NULL) { if (spec_host != NULL) hostref = spec_host; else hostref = addr_host; } if (spec_host == NULL && addr_host == NULL) { hostref = NULL; return 0; } return cistrcmp(hostref, addr_host) == 0; }