/* * Copyright (c) 1998 Sendmail, Inc. All rights reserved. * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ #ifndef lint static char sccsid[] = "@(#)util.c 8.159 (Berkeley) 7/1/98"; #endif /* not lint */ # include "sendmail.h" # include /* ** STRIPQUOTES -- Strip quotes & quote bits from a string. ** ** Runs through a string and strips off unquoted quote ** characters and quote bits. This is done in place. ** ** Parameters: ** s -- the string to strip. ** ** Returns: ** none. ** ** Side Effects: ** none. ** ** Called By: ** deliver */ void stripquotes(s) char *s; { register char *p; register char *q; register char c; if (s == NULL) return; p = q = s; do { c = *p++; if (c == '\\') c = *p++; else if (c == '"') continue; *q++ = c; } while (c != '\0'); } /* ** ADDQUOTES -- Adds quotes & quote bits to a string. ** ** Runs through a string and adds characters and quote bits. ** ** Parameters: ** s -- the string to modify. ** ** Returns: ** pointer to quoted string. ** ** Side Effects: ** none. ** */ char * addquotes(s) char *s; { int len = 0; char c; char *p = s, *q, *r; if (s == NULL) return NULL; /* Find length of quoted string */ while ((c = *p++) != '\0') { len++; if (c == '\\' || c == '"') len++; } q = r = xalloc(len + 3); p = s; /* add leading quote */ *q++ = '"'; while ((c = *p++) != '\0') { /* quote \ or " */ if (c == '\\' || c == '"') *q++ = '\\'; *q++ = c; } *q++ = '"'; *q = '\0'; return r; } /* ** RFC822_STRING -- Checks string for proper RFC822 string quoting. ** ** Runs through a string and verifies RFC822 special characters ** are only found inside comments, quoted strings, or backslash ** escaped. Also verified balanced quotes and parenthesis. ** ** Parameters: ** s -- the string to modify. ** ** Returns: ** TRUE -- if the string is RFC822 compliant. ** FALSE -- if the string is not RFC822 compliant. ** ** Side Effects: ** none. ** */ bool rfc822_string(s) char *s; { bool quoted = FALSE; int commentlev = 0; char *c = s; if (s == NULL) return FALSE; while (*c != '\0') { /* escaped character */ if (*c == '\\') { c++; if (*c == '\0') return FALSE; } else if (commentlev == 0 && *c == '"') quoted = !quoted; else if (!quoted) { if (*c == ')') { /* unbalanced ')' */ if (commentlev == 0) return FALSE; else commentlev--; } else if (*c == '(') commentlev++; else if (commentlev == 0 && strchr(MustQuoteChars, *c) != NULL) return FALSE; } c++; } /* unbalanced '"' or '(' */ if (quoted || commentlev != 0) return FALSE; else return TRUE; } /* ** XALLOC -- Allocate memory and bitch wildly on failure. ** ** THIS IS A CLUDGE. This should be made to give a proper ** error -- but after all, what can we do? ** ** Parameters: ** sz -- size of area to allocate. ** ** Returns: ** pointer to data region. ** ** Side Effects: ** Memory is allocated. */ char * xalloc(sz) register int sz; { register char *p; /* some systems can't handle size zero mallocs */ if (sz <= 0) sz = 1; p = malloc((unsigned) sz); if (p == NULL) { syserr("!Out of memory!!"); /* exit(EX_UNAVAILABLE); */ } return (p); } /* ** COPYPLIST -- copy list of pointers. ** ** This routine is the equivalent of newstr for lists of ** pointers. ** ** Parameters: ** list -- list of pointers to copy. ** Must be NULL terminated. ** copycont -- if TRUE, copy the contents of the vector ** (which must be a string) also. ** ** Returns: ** a copy of 'list'. ** ** Side Effects: ** none. */ char ** copyplist(list, copycont) char **list; bool copycont; { register char **vp; register char **newvp; for (vp = list; *vp != NULL; vp++) continue; vp++; newvp = (char **) xalloc((int) (vp - list) * sizeof *vp); bcopy((char *) list, (char *) newvp, (int) (vp - list) * sizeof *vp); if (copycont) { for (vp = newvp; *vp != NULL; vp++) *vp = newstr(*vp); } return (newvp); } /* ** COPYQUEUE -- copy address queue. ** ** This routine is the equivalent of newstr for address queues ** addresses marked with QDONTSEND aren't copied ** ** Parameters: ** addr -- list of address structures to copy. ** ** Returns: ** a copy of 'addr'. ** ** Side Effects: ** none. */ ADDRESS * copyqueue(addr) ADDRESS *addr; { register ADDRESS *newaddr; ADDRESS *ret; register ADDRESS **tail = &ret; while (addr != NULL) { if (!bitset(QDONTSEND, addr->q_flags)) { newaddr = (ADDRESS *) xalloc(sizeof(ADDRESS)); STRUCTCOPY(*addr, *newaddr); *tail = newaddr; tail = &newaddr->q_next; } addr = addr->q_next; } *tail = NULL; return ret; } /* ** PRINTAV -- print argument vector. ** ** Parameters: ** av -- argument vector. ** ** Returns: ** none. ** ** Side Effects: ** prints av. */ void printav(av) register char **av; { while (*av != NULL) { if (tTd(0, 44)) printf("\n\t%08lx=", (u_long) *av); else (void) putchar(' '); xputs(*av++); } (void) putchar('\n'); } /* ** LOWER -- turn letter into lower case. ** ** Parameters: ** c -- character to turn into lower case. ** ** Returns: ** c, in lower case. ** ** Side Effects: ** none. */ char lower(c) register char c; { return((isascii(c) && isupper(c)) ? tolower(c) : c); } /* ** XPUTS -- put string doing control escapes. ** ** Parameters: ** s -- string to put. ** ** Returns: ** none. ** ** Side Effects: ** output to stdout */ void xputs(s) register const char *s; { register int c; register struct metamac *mp; bool shiftout = FALSE; extern struct metamac MetaMacros[]; if (s == NULL) { printf("%s%s", TermEscape.te_rv_on, TermEscape.te_rv_off); return; } while ((c = (*s++ & 0377)) != '\0') { if (shiftout) { printf("%s", TermEscape.te_rv_off); shiftout = FALSE; } if (!isascii(c)) { if (c == MATCHREPL) { printf("%s$", TermEscape.te_rv_on); shiftout = TRUE; if (*s == '\0') continue; c = *s++ & 0377; goto printchar; } if (c == MACROEXPAND || c == MACRODEXPAND) { printf("%s$", TermEscape.te_rv_on); if (c == MACRODEXPAND) putchar('&'); shiftout = TRUE; if (*s == '\0') continue; if (strchr("=~&?", *s) != NULL) putchar(*s++); if (bitset(0200, *s)) printf("{%s}", macname(*s++ & 0377)); else printf("%c", *s++); continue; } for (mp = MetaMacros; mp->metaname != '\0'; mp++) { if ((mp->metaval & 0377) == c) { printf("%s$%c", TermEscape.te_rv_on, mp->metaname); shiftout = TRUE; break; } } if (c == MATCHCLASS || c == MATCHNCLASS) { if (bitset(0200, *s)) printf("{%s}", macname(*s++ & 0377)); else if (*s != '\0') printf("%c", *s++); } if (mp->metaname != '\0') continue; /* unrecognized meta character */ printf("%sM-", TermEscape.te_rv_on); shiftout = TRUE; c &= 0177; } printchar: if (isprint(c)) { putchar(c); continue; } /* wasn't a meta-macro -- find another way to print it */ switch (c) { case '\n': c = 'n'; break; case '\r': c = 'r'; break; case '\t': c = 't'; break; } if (!shiftout) { printf("%s", TermEscape.te_rv_on); shiftout = TRUE; } if (isprint(c)) { (void) putchar('\\'); (void) putchar(c); } else { (void) putchar('^'); (void) putchar(c ^ 0100); } } if (shiftout) printf("%s", TermEscape.te_rv_off); (void) fflush(stdout); } /* ** MAKELOWER -- Translate a line into lower case ** ** Parameters: ** p -- the string to translate. If NULL, return is ** immediate. ** ** Returns: ** none. ** ** Side Effects: ** String pointed to by p is translated to lower case. ** ** Called By: ** parse */ void makelower(p) register char *p; { register char c; if (p == NULL) return; for (; (c = *p) != '\0'; p++) if (isascii(c) && isupper(c)) *p = tolower(c); } /* ** BUILDFNAME -- build full name from gecos style entry. ** ** This routine interprets the strange entry that would appear ** in the GECOS field of the password file. ** ** Parameters: ** p -- name to build. ** login -- the login name of this user (for &). ** buf -- place to put the result. ** buflen -- length of buf. ** ** Returns: ** none. ** ** Side Effects: ** none. */ void buildfname(gecos, login, buf, buflen) register char *gecos; char *login; char *buf; int buflen; { register char *p; register char *bp = buf; if (*gecos == '*') gecos++; /* copy gecos, interpolating & to be full name */ for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++) { if (bp >= &buf[buflen - 1]) { /* buffer overflow -- just use login name */ snprintf(buf, buflen, "%s", login); return; } if (*p == '&') { /* interpolate full name */ snprintf(bp, buflen - (bp - buf), "%s", login); *bp = toupper(*bp); bp += strlen(bp); } else *bp++ = *p; } *bp = '\0'; } /* ** FIXCRLF -- fix in line. ** ** Looks for the combination and turns it into the ** UNIX canonical character. It only takes one line, ** i.e., it is assumed that the first found is the end ** of the line. ** ** Parameters: ** line -- the line to fix. ** stripnl -- if true, strip the newline also. ** ** Returns: ** none. ** ** Side Effects: ** line is changed in place. */ void fixcrlf(line, stripnl) char *line; bool stripnl; { register char *p; p = strchr(line, '\n'); if (p == NULL) return; if (p > line && p[-1] == '\r') p--; if (!stripnl) *p++ = '\n'; *p = '\0'; } /* ** PUTLINE -- put a line like fputs obeying SMTP conventions ** ** This routine always guarantees outputing a newline (or CRLF, ** as appropriate) at the end of the string. ** ** Parameters: ** l -- line to put. ** mci -- the mailer connection information. ** ** Returns: ** none ** ** Side Effects: ** output of l to fp. */ void putline(l, mci) register char *l; register MCI *mci; { putxline(l, strlen(l), mci, PXLF_MAPFROM); } /* ** PUTXLINE -- putline with flags bits. ** ** This routine always guarantees outputing a newline (or CRLF, ** as appropriate) at the end of the string. ** ** Parameters: ** l -- line to put. ** len -- the length of the line. ** mci -- the mailer connection information. ** pxflags -- flag bits: ** PXLF_MAPFROM -- map From_ to >From_. ** PXLF_STRIP8BIT -- strip 8th bit. ** PXLF_HEADER -- map bare newline in header to newline space. ** ** Returns: ** none ** ** Side Effects: ** output of l to fp. */ void putxline(l, len, mci, pxflags) register char *l; size_t len; register MCI *mci; int pxflags; { register char *p, *end; int slop = 0; size_t eol_len = strlen(mci->mci_mailer->m_eol); /* strip out 0200 bits -- these can look like TELNET protocol */ if (bitset(MCIF_7BIT, mci->mci_flags) || bitset(PXLF_STRIP8BIT, pxflags)) { register char svchar; for (p = l; (svchar = *p) != '\0'; ++p) if (bitset(0200, svchar)) *p = svchar &~ 0200; } end = l + len; do { /* find the end of the line */ p = memchr(l, '\n', end - l); if (p == NULL) p = end; if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d >>> ", (int) getpid()); /* check for line overflow */ while (mci->mci_mailer->m_linelimit > 0 && (p - l + slop) > mci->mci_mailer->m_linelimit) { char *l_base = l; register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1]; if (l[0] == '.' && slop == 0 && bitnset(M_XDOT, mci->mci_mailer->m_flags)) { (void) putc('.', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) (void) putc('.', TrafficLogFile); } else if (l[0] == 'F' && slop == 0 && bitset(PXLF_MAPFROM, pxflags) && strncmp(l, "From ", 5) == 0 && bitnset(M_ESCFROM, mci->mci_mailer->m_flags)) { (void) putc('>', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) (void) putc('>', TrafficLogFile); } while (l < q) { (void) putc(*l++, mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; } (void) putc('!', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; fputs(mci->mci_mailer->m_eol, mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen += eol_len; (void) putc(' ', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) { for (l = l_base; l < q; l++) (void) putc(*l, TrafficLogFile); fprintf(TrafficLogFile, "!\n%05d >>> ", (int) getpid()); } slop = 1; } /* output last part */ if (l[0] == '.' && slop == 0 && bitnset(M_XDOT, mci->mci_mailer->m_flags)) { (void) putc('.', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) (void) putc('.', TrafficLogFile); } else if (l[0] == 'F' && slop == 0 && bitset(PXLF_MAPFROM, pxflags) && strncmp(l, "From ", 5) == 0 && bitnset(M_ESCFROM, mci->mci_mailer->m_flags)) { (void) putc('>', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) (void) putc('>', TrafficLogFile); } for ( ; l < p; ++l) { if (TrafficLogFile != NULL) (void) putc(*l, TrafficLogFile); (void) putc(*l, mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; } if (TrafficLogFile != NULL) (void) putc('\n', TrafficLogFile); fputs(mci->mci_mailer->m_eol, mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen += eol_len; if (l < end && *l == '\n') { if (*++l != ' ' && *l != '\t' && *l != '\0' && bitset(PXLF_HEADER, pxflags)) { (void) putc(' ', mci->mci_out); if (!bitset(MCIF_INHEADER, mci->mci_flags)) mci->mci_contentlen++; if (TrafficLogFile != NULL) (void) putc(' ', TrafficLogFile); } } } while (l < end); } /* ** XUNLINK -- unlink a file, doing logging as appropriate. ** ** Parameters: ** f -- name of file to unlink. ** ** Returns: ** none. ** ** Side Effects: ** f is unlinked. */ void xunlink(f) char *f; { register int i; if (LogLevel > 98) sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f); i = unlink(f); if (i < 0 && LogLevel > 97) sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d", f, errno); } /* ** XFCLOSE -- close a file, doing logging as appropriate. ** ** Parameters: ** fp -- file pointer for the file to close ** a, b -- miscellaneous crud to print for debugging ** ** Returns: ** none. ** ** Side Effects: ** fp is closed. */ void xfclose(fp, a, b) FILE *fp; char *a, *b; { if (tTd(53, 99)) printf("xfclose(%lx) %s %s\n", (u_long) fp, a, b); #if XDEBUG if (fileno(fp) == 1) syserr("xfclose(%s %s): fd = 1", a, b); #endif if (fclose(fp) < 0 && tTd(53, 99)) printf("xfclose FAILURE: %s\n", errstring(errno)); } /* ** SFGETS -- "safe" fgets -- times out and ignores random interrupts. ** ** Parameters: ** buf -- place to put the input line. ** siz -- size of buf. ** fp -- file to read from. ** timeout -- the timeout before error occurs. ** during -- what we are trying to read (for error messages). ** ** Returns: ** NULL on error (including timeout). This will also leave ** buf containing a null string. ** buf otherwise. ** ** Side Effects: ** none. */ static jmp_buf CtxReadTimeout; static void readtimeout __P((time_t)); char * sfgets(buf, siz, fp, timeout, during) char *buf; int siz; FILE *fp; time_t timeout; char *during; { register EVENT *ev = NULL; register char *p; if (fp == NULL) { buf[0] = '\0'; return NULL; } /* set the timeout */ if (timeout != 0) { if (setjmp(CtxReadTimeout) != 0) { if (LogLevel > 1) sm_syslog(LOG_NOTICE, CurEnv->e_id, "timeout waiting for input from %.100s during %s", CurHostName ? CurHostName : "local", during); errno = 0; buf[0] = '\0'; #if XDEBUG checkfd012(during); #endif if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d <<< [TIMEOUT]\n", (int) getpid()); return (NULL); } ev = setevent(timeout, readtimeout, 0); } /* try to read */ p = NULL; while (!feof(fp) && !ferror(fp)) { errno = 0; p = fgets(buf, siz, fp); if (p != NULL || errno != EINTR) break; clearerr(fp); } /* clear the event if it has not sprung */ clrevent(ev); /* clean up the books and exit */ LineNumber++; if (p == NULL) { buf[0] = '\0'; if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d <<< [EOF]\n", (int) getpid()); return (NULL); } if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d <<< %s", (int) getpid(), buf); if (SevenBitInput) { for (p = buf; *p != '\0'; p++) *p &= ~0200; } else if (!HasEightBits) { for (p = buf; *p != '\0'; p++) { if (bitset(0200, *p)) { HasEightBits = TRUE; break; } } } return (buf); } /* ARGSUSED */ static void readtimeout(timeout) time_t timeout; { longjmp(CtxReadTimeout, 1); } /* ** FGETFOLDED -- like fgets, but know about folded lines. ** ** Parameters: ** buf -- place to put result. ** n -- bytes available. ** f -- file to read from. ** ** Returns: ** input line(s) on success, NULL on error or EOF. ** This will normally be buf -- unless the line is too ** long, when it will be xalloc()ed. ** ** Side Effects: ** buf gets lines from f, with continuation lines (lines ** with leading white space) appended. CRLF's are mapped ** into single newlines. Any trailing NL is stripped. */ char * fgetfolded(buf, n, f) char *buf; register int n; FILE *f; { register char *p = buf; char *bp = buf; register int i; n--; while ((i = getc(f)) != EOF) { if (i == '\r') { i = getc(f); if (i != '\n') { if (i != EOF) (void) ungetc(i, f); i = '\r'; } } if (--n <= 0) { /* allocate new space */ char *nbp; int nn; nn = (p - bp); if (nn < MEMCHUNKSIZE) nn *= 2; else nn += MEMCHUNKSIZE; nbp = xalloc(nn); bcopy(bp, nbp, p - bp); p = &nbp[p - bp]; if (bp != buf) free(bp); bp = nbp; n = nn - (p - bp); } *p++ = i; if (i == '\n') { LineNumber++; i = getc(f); if (i != EOF) (void) ungetc(i, f); if (i != ' ' && i != '\t') break; } } if (p == bp) return (NULL); if (p[-1] == '\n') p--; *p = '\0'; return (bp); } /* ** CURTIME -- return current time. ** ** Parameters: ** none. ** ** Returns: ** the current time. ** ** Side Effects: ** none. */ time_t curtime() { auto time_t t; (void) time(&t); return (t); } /* ** ATOBOOL -- convert a string representation to boolean. ** ** Defaults to "TRUE" ** ** Parameters: ** s -- string to convert. Takes "tTyY" as true, ** others as false. ** ** Returns: ** A boolean representation of the string. ** ** Side Effects: ** none. */ bool atobool(s) register char *s; { if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL) return (TRUE); return (FALSE); } /* ** ATOOCT -- convert a string representation to octal. ** ** Parameters: ** s -- string to convert. ** ** Returns: ** An integer representing the string interpreted as an ** octal number. ** ** Side Effects: ** none. */ int atooct(s) register char *s; { register int i = 0; while (*s >= '0' && *s <= '7') i = (i << 3) | (*s++ - '0'); return (i); } /* ** BITINTERSECT -- tell if two bitmaps intersect ** ** Parameters: ** a, b -- the bitmaps in question ** ** Returns: ** TRUE if they have a non-null intersection ** FALSE otherwise ** ** Side Effects: ** none. */ bool bitintersect(a, b) BITMAP a; BITMAP b; { int i; for (i = BITMAPBYTES / sizeof (int); --i >= 0; ) if ((a[i] & b[i]) != 0) return (TRUE); return (FALSE); } /* ** BITZEROP -- tell if a bitmap is all zero ** ** Parameters: ** map -- the bit map to check ** ** Returns: ** TRUE if map is all zero. ** FALSE if there are any bits set in map. ** ** Side Effects: ** none. */ bool bitzerop(map) BITMAP map; { int i; for (i = BITMAPBYTES / sizeof (int); --i >= 0; ) if (map[i] != 0) return (FALSE); return (TRUE); } /* ** STRCONTAINEDIN -- tell if one string is contained in another ** ** Parameters: ** a -- possible substring. ** b -- possible superstring. ** ** Returns: ** TRUE if a is contained in b. ** FALSE otherwise. */ bool strcontainedin(a, b) register char *a; register char *b; { int la; int lb; int c; la = strlen(a); lb = strlen(b); c = *a; if (isascii(c) && isupper(c)) c = tolower(c); for (; lb-- >= la; b++) { if (*b != c && isascii(*b) && isupper(*b) && tolower(*b) != c) continue; if (strncasecmp(a, b, la) == 0) return TRUE; } return FALSE; } /* ** CHECKFD012 -- check low numbered file descriptors ** ** File descriptors 0, 1, and 2 should be open at all times. ** This routine verifies that, and fixes it if not true. ** ** Parameters: ** where -- a tag printed if the assertion failed ** ** Returns: ** none */ void checkfd012(where) char *where; { #if XDEBUG register int i; for (i = 0; i < 3; i++) fill_fd(i, where); #endif /* XDEBUG */ } /* ** CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging ** ** Parameters: ** fd -- file descriptor to check. ** where -- tag to print on failure. ** ** Returns: ** none. */ void checkfdopen(fd, where) int fd; char *where; { #if XDEBUG struct stat st; if (fstat(fd, &st) < 0 && errno == EBADF) { syserr("checkfdopen(%d): %s not open as expected!", fd, where); printopenfds(TRUE); } #endif } /* ** CHECKFDS -- check for new or missing file descriptors ** ** Parameters: ** where -- tag for printing. If null, take a base line. ** ** Returns: ** none ** ** Side Effects: ** If where is set, shows changes since the last call. */ void checkfds(where) char *where; { int maxfd; register int fd; bool printhdr = TRUE; int save_errno = errno; static BITMAP baseline; extern int DtableSize; if (DtableSize > 256) maxfd = 256; else maxfd = DtableSize; if (where == NULL) clrbitmap(baseline); for (fd = 0; fd < maxfd; fd++) { struct stat stbuf; if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP) { if (!bitnset(fd, baseline)) continue; clrbitn(fd, baseline); } else if (!bitnset(fd, baseline)) setbitn(fd, baseline); else continue; /* file state has changed */ if (where == NULL) continue; if (printhdr) { sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: changed fds:", where); printhdr = FALSE; } dumpfd(fd, TRUE, TRUE); } errno = save_errno; } /* ** PRINTOPENFDS -- print the open file descriptors (for debugging) ** ** Parameters: ** logit -- if set, send output to syslog; otherwise ** print for debugging. ** ** Returns: ** none. */ #include void printopenfds(logit) bool logit; { register int fd; extern int DtableSize; for (fd = 0; fd < DtableSize; fd++) dumpfd(fd, FALSE, logit); } /* ** DUMPFD -- dump a file descriptor ** ** Parameters: ** fd -- the file descriptor to dump. ** printclosed -- if set, print a notification even if ** it is closed; otherwise print nothing. ** logit -- if set, send output to syslog instead of stdout. */ void dumpfd(fd, printclosed, logit) int fd; bool printclosed; bool logit; { register char *p; char *hp; #ifdef S_IFSOCK SOCKADDR sa; #endif auto SOCKADDR_LEN_T slen; int i; #if STAT64 > 0 struct stat64 st; #else struct stat st; #endif char buf[200]; p = buf; snprintf(p, SPACELEFT(buf, p), "%3d: ", fd); p += strlen(p); if ( #if STAT64 > 0 fstat64(fd, &st) #else fstat(fd, &st) #endif < 0) { if (errno != EBADF) { snprintf(p, SPACELEFT(buf, p), "CANNOT STAT (%s)", errstring(errno)); goto printit; } else if (printclosed) { snprintf(p, SPACELEFT(buf, p), "CLOSED"); goto printit; } return; } i = fcntl(fd, F_GETFL, NULL); if (i != -1) { snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i); p += strlen(p); } snprintf(p, SPACELEFT(buf, p), "mode=%o: ", st.st_mode); p += strlen(p); switch (st.st_mode & S_IFMT) { #ifdef S_IFSOCK case S_IFSOCK: snprintf(p, SPACELEFT(buf, p), "SOCK "); p += strlen(p); slen = sizeof sa; if (getsockname(fd, &sa.sa, &slen) < 0) snprintf(p, SPACELEFT(buf, p), "(%s)", errstring(errno)); else { hp = hostnamebyanyaddr(&sa); if (sa.sa.sa_family == AF_INET) snprintf(p, SPACELEFT(buf, p), "%s/%d", hp, ntohs(sa.sin.sin_port)); else snprintf(p, SPACELEFT(buf, p), "%s", hp); } p += strlen(p); snprintf(p, SPACELEFT(buf, p), "->"); p += strlen(p); slen = sizeof sa; if (getpeername(fd, &sa.sa, &slen) < 0) snprintf(p, SPACELEFT(buf, p), "(%s)", errstring(errno)); else { hp = hostnamebyanyaddr(&sa); if (sa.sa.sa_family == AF_INET) snprintf(p, SPACELEFT(buf, p), "%s/%d", hp, ntohs(sa.sin.sin_port)); else snprintf(p, SPACELEFT(buf, p), "%s", hp); } break; #endif case S_IFCHR: snprintf(p, SPACELEFT(buf, p), "CHR: "); p += strlen(p); goto defprint; case S_IFBLK: snprintf(p, SPACELEFT(buf, p), "BLK: "); p += strlen(p); goto defprint; #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) case S_IFIFO: snprintf(p, SPACELEFT(buf, p), "FIFO: "); p += strlen(p); goto defprint; #endif #ifdef S_IFDIR case S_IFDIR: snprintf(p, SPACELEFT(buf, p), "DIR: "); p += strlen(p); goto defprint; #endif #ifdef S_IFLNK case S_IFLNK: snprintf(p, SPACELEFT(buf, p), "LNK: "); p += strlen(p); goto defprint; #endif default: defprint: if (sizeof st.st_ino > sizeof (long)) snprintf(p, SPACELEFT(buf, p), "dev=%d/%d, ino=%s, nlink=%d, u/gid=%d/%d, ", major(st.st_dev), minor(st.st_dev), quad_to_string(st.st_ino), st.st_nlink, st.st_uid, st.st_gid); else snprintf(p, SPACELEFT(buf, p), "dev=%d/%d, ino=%lu, nlink=%d, u/gid=%d/%d, ", major(st.st_dev), minor(st.st_dev), (unsigned long) st.st_ino, st.st_nlink, st.st_uid, st.st_gid); if (sizeof st.st_size > sizeof (long)) snprintf(p, SPACELEFT(buf, p), "size=%s", quad_to_string(st.st_size)); else snprintf(p, SPACELEFT(buf, p), "size=%lu", (unsigned long) st.st_size); break; } printit: if (logit) sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL, "%.800s", buf); else printf("%s\n", buf); } /* ** SHORTEN_HOSTNAME -- strip local domain information off of hostname. ** ** Parameters: ** host -- the host to shorten (stripped in place). ** ** Returns: ** none. */ void shorten_hostname(host) char host[]; { register char *p; char *mydom; int i; bool canon = FALSE; /* strip off final dot */ p = &host[strlen(host) - 1]; if (*p == '.') { *p = '\0'; canon = TRUE; } /* see if there is any domain at all -- if not, we are done */ p = strchr(host, '.'); if (p == NULL) return; /* yes, we have a domain -- see if it looks like us */ mydom = macvalue('m', CurEnv); if (mydom == NULL) mydom = ""; i = strlen(++p); if ((canon ? strcasecmp(p, mydom) : strncasecmp(p, mydom, i)) == 0 && (mydom[i] == '.' || mydom[i] == '\0')) *--p = '\0'; } /* ** PROG_OPEN -- open a program for reading ** ** Parameters: ** argv -- the argument list. ** pfd -- pointer to a place to store the file descriptor. ** e -- the current envelope. ** ** Returns: ** pid of the process -- -1 if it failed. */ int prog_open(argv, pfd, e) char **argv; int *pfd; ENVELOPE *e; { int pid; int i; int saveerrno; int fdv[2]; char *p, *q; char buf[MAXLINE + 1]; extern int DtableSize; if (pipe(fdv) < 0) { syserr("%s: cannot create pipe for stdout", argv[0]); return -1; } pid = fork(); if (pid < 0) { syserr("%s: cannot fork", argv[0]); close(fdv[0]); close(fdv[1]); return -1; } if (pid > 0) { /* parent */ close(fdv[1]); *pfd = fdv[0]; return pid; } /* child -- close stdin */ close(0); /* stdout goes back to parent */ close(fdv[0]); if (dup2(fdv[1], 1) < 0) { syserr("%s: cannot dup2 for stdout", argv[0]); _exit(EX_OSERR); } close(fdv[1]); /* stderr goes to transcript if available */ if (e->e_xfp != NULL) { if (dup2(fileno(e->e_xfp), 2) < 0) { syserr("%s: cannot dup2 for stderr", argv[0]); _exit(EX_OSERR); } } /* this process has no right to the queue file */ if (e->e_lockfp != NULL) close(fileno(e->e_lockfp)); /* run as default user */ endpwent(); if (setgid(DefGid) < 0 && geteuid() == 0) syserr("prog_open: setgid(%ld) failed", (long) DefGid); if (setuid(DefUid) < 0 && geteuid() == 0) syserr("prog_open: setuid(%ld) failed", (long) DefUid); /* run in some directory */ if (ProgMailer != NULL) p = ProgMailer->m_execdir; else p = NULL; for (; p != NULL; p = q) { q = strchr(p, ':'); if (q != NULL) *q = '\0'; expand(p, buf, sizeof buf, e); if (q != NULL) *q++ = ':'; if (buf[0] != '\0' && chdir(buf) >= 0) break; } if (p == NULL) { /* backup directories */ if (chdir("/tmp") < 0) (void) chdir("/"); } /* arrange for all the files to be closed */ for (i = 3; i < DtableSize; i++) { register int j; if ((j = fcntl(i, F_GETFD, 0)) != -1) (void) fcntl(i, F_SETFD, j | 1); } /* now exec the process */ execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron); /* woops! failed */ saveerrno = errno; syserr("%s: cannot exec", argv[0]); if (transienterror(saveerrno)) _exit(EX_OSERR); _exit(EX_CONFIG); return -1; /* avoid compiler warning on IRIX */ } /* ** GET_COLUMN -- look up a Column in a line buffer ** ** Parameters: ** line -- the raw text line to search. ** col -- the column number to fetch. ** delim -- the delimiter between columns. If null, ** use white space. ** buf -- the output buffer. ** buflen -- the length of buf. ** ** Returns: ** buf if successful. ** NULL otherwise. */ char * get_column(line, col, delim, buf, buflen) char line[]; int col; char delim; char buf[]; int buflen; { char *p; char *begin, *end; int i; char delimbuf[4]; if (delim == '\0') strcpy(delimbuf, "\n\t "); else { delimbuf[0] = delim; delimbuf[1] = '\0'; } p = line; if (*p == '\0') return NULL; /* line empty */ if (*p == delim && col == 0) return NULL; /* first column empty */ begin = line; if (col == 0 && delim == '\0') { while (*begin != '\0' && isascii(*begin) && isspace(*begin)) begin++; } for (i = 0; i < col; i++) { if ((begin = strpbrk(begin, delimbuf)) == NULL) return NULL; /* no such column */ begin++; if (delim == '\0') { while (*begin != '\0' && isascii(*begin) && isspace(*begin)) begin++; } } end = strpbrk(begin, delimbuf); if (end == NULL) i = strlen(begin); else i = end - begin; if (i >= buflen) i = buflen - 1; strncpy(buf, begin, i); buf[i] = '\0'; return buf; } /* ** CLEANSTRCPY -- copy string keeping out bogus characters ** ** Parameters: ** t -- "to" string. ** f -- "from" string. ** l -- length of space available in "to" string. ** ** Returns: ** none. */ void cleanstrcpy(t, f, l) register char *t; register char *f; int l; { /* check for newlines and log if necessary */ (void) denlstring(f, TRUE, TRUE); l--; while (l > 0 && *f != '\0') { if (isascii(*f) && (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL)) { l--; *t++ = *f; } f++; } *t = '\0'; } /* ** DENLSTRING -- convert newlines in a string to spaces ** ** Parameters: ** s -- the input string ** strict -- if set, don't permit continuation lines. ** logattacks -- if set, log attempted attacks. ** ** Returns: ** A pointer to a version of the string with newlines ** mapped to spaces. This should be copied. */ char * denlstring(s, strict, logattacks) char *s; bool strict; bool logattacks; { register char *p; int l; static char *bp = NULL; static int bl = 0; p = s; while ((p = strchr(p, '\n')) != NULL) if (strict || (*++p != ' ' && *p != '\t')) break; if (p == NULL) return s; l = strlen(s) + 1; if (bl < l) { /* allocate more space */ if (bp != NULL) free(bp); bp = xalloc(l); bl = l; } strcpy(bp, s); for (p = bp; (p = strchr(p, '\n')) != NULL; ) *p++ = ' '; if (logattacks) { sm_syslog(LOG_NOTICE, CurEnv->e_id, "POSSIBLE ATTACK from %.100s: newline in string \"%s\"", RealHostName == NULL ? "[UNKNOWN]" : RealHostName, shortenstring(bp, MAXSHORTSTR)); } return bp; } /* ** PATH_IS_DIR -- check to see if file exists and is a directory. ** ** There are some additional checks for security violations in ** here. This routine is intended to be used for the host status ** support. ** ** Parameters: ** pathname -- pathname to check for directory-ness. ** createflag -- if set, create directory if needed. ** ** Returns: ** TRUE -- if the indicated pathname is a directory ** FALSE -- otherwise */ int path_is_dir(pathname, createflag) char *pathname; bool createflag; { struct stat statbuf; #if HASLSTAT if (lstat(pathname, &statbuf) < 0) #else if (stat(pathname, &statbuf) < 0) #endif { if (errno != ENOENT || !createflag) return FALSE; if (mkdir(pathname, 0755) < 0) return FALSE; return TRUE; } if (!S_ISDIR(statbuf.st_mode)) { errno = ENOTDIR; return FALSE; } /* security: don't allow writable directories */ if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode)) { errno = EACCES; return FALSE; } return TRUE; } /* ** PROC_LIST_ADD -- add process id to list of our children ** ** Parameters: ** pid -- pid to add to list. ** ** Returns: ** none */ static pid_t *ProcListVec = NULL; static int ProcListSize = 0; #define NO_PID ((pid_t) 0) #ifndef PROC_LIST_SEG # define PROC_LIST_SEG 32 /* number of pids to alloc at a time */ #endif void proc_list_add(pid) pid_t pid; { int i; extern void proc_list_probe __P((void)); for (i = 0; i < ProcListSize; i++) { if (ProcListVec[i] == NO_PID) break; } if (i >= ProcListSize) { /* probe the existing vector to avoid growing infinitely */ proc_list_probe(); /* now scan again */ for (i = 0; i < ProcListSize; i++) { if (ProcListVec[i] == NO_PID) break; } } if (i >= ProcListSize) { /* grow process list */ pid_t *npv; npv = (pid_t *) xalloc(sizeof (pid_t) * (ProcListSize + PROC_LIST_SEG)); if (ProcListSize > 0) { bcopy(ProcListVec, npv, ProcListSize * sizeof (pid_t)); free(ProcListVec); } for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++) npv[i] = NO_PID; i = ProcListSize; ProcListSize += PROC_LIST_SEG; ProcListVec = npv; } ProcListVec[i] = pid; CurChildren++; } /* ** PROC_LIST_DROP -- drop pid from process list ** ** Parameters: ** pid -- pid to drop ** ** Returns: ** none. */ void proc_list_drop(pid) pid_t pid; { int i; for (i = 0; i < ProcListSize; i++) { if (ProcListVec[i] == pid) { ProcListVec[i] = NO_PID; break; } } if (CurChildren > 0) CurChildren--; } /* ** PROC_LIST_CLEAR -- clear the process list ** ** Parameters: ** none. ** ** Returns: ** none. */ void proc_list_clear() { int i; for (i = 0; i < ProcListSize; i++) ProcListVec[i] = NO_PID; CurChildren = 0; } /* ** PROC_LIST_PROBE -- probe processes in the list to see if they still exist ** ** Parameters: ** none ** ** Returns: ** none */ void proc_list_probe() { int i; for (i = 0; i < ProcListSize; i++) { if (ProcListVec[i] == NO_PID) continue; if (kill(ProcListVec[i], 0) < 0) { if (LogLevel > 3) sm_syslog(LOG_DEBUG, CurEnv->e_id, "proc_list_probe: lost pid %d", ProcListVec[i]); ProcListVec[i] = NO_PID; CurChildren--; } } if (CurChildren < 0) CurChildren = 0; } /* ** SM_STRCASECMP -- 8-bit clean version of strcasecmp ** ** Thank you, vendors, for making this all necessary. */ /* * Copyright (c) 1987, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)strcasecmp.c 8.1 (Berkeley) 6/4/93"; #endif /* LIBC_SCCS and not lint */ /* * This array is designed for mapping upper and lower case letter * together for a case independent comparison. The mappings are * based upon ascii character sequences. */ static const u_char charmap[] = { 0000, 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0020, 0021, 0022, 0023, 0024, 0025, 0026, 0027, 0030, 0031, 0032, 0033, 0034, 0035, 0036, 0037, 0040, 0041, 0042, 0043, 0044, 0045, 0046, 0047, 0050, 0051, 0052, 0053, 0054, 0055, 0056, 0057, 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, 0070, 0071, 0072, 0073, 0074, 0075, 0076, 0077, 0100, 0141, 0142, 0143, 0144, 0145, 0146, 0147, 0150, 0151, 0152, 0153, 0154, 0155, 0156, 0157, 0160, 0161, 0162, 0163, 0164, 0165, 0166, 0167, 0170, 0171, 0172, 0133, 0134, 0135, 0136, 0137, 0140, 0141, 0142, 0143, 0144, 0145, 0146, 0147, 0150, 0151, 0152, 0153, 0154, 0155, 0156, 0157, 0160, 0161, 0162, 0163, 0164, 0165, 0166, 0167, 0170, 0171, 0172, 0173, 0174, 0175, 0176, 0177, 0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207, 0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217, 0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227, 0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237, 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247, 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257, 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307, 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317, 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327, 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357, 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377, }; int sm_strcasecmp(s1, s2) const char *s1, *s2; { register const u_char *cm = charmap, *us1 = (const u_char *)s1, *us2 = (const u_char *)s2; while (cm[*us1] == cm[*us2++]) if (*us1++ == '\0') return (0); return (cm[*us1] - cm[*--us2]); } int sm_strncasecmp(s1, s2, n) const char *s1, *s2; register size_t n; { if (n != 0) { register const u_char *cm = charmap, *us1 = (const u_char *)s1, *us2 = (const u_char *)s2; do { if (cm[*us1] != cm[*us2++]) return (cm[*us1] - cm[*--us2]); if (*us1++ == '\0') break; } while (--n != 0); } return (0); }