/* * Copyright (c) 1983, 1995 Eric P. Allman * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. * * %sccs.include.redist.c% */ #ifndef lint static char sccsid[] = "@(#)util.c 8.78 (Berkeley) 06/21/95"; #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'); } /* ** 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%08x=", *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; extern struct metamac MetaMacros[]; if (s == NULL) { printf(""); return; } while ((c = (*s++ & 0377)) != '\0') { if (!isascii(c)) { if (c == MATCHREPL) { putchar('$'); continue; } if (c == MACROEXPAND) { putchar('$'); if (bitset(0200, *s)) printf("{%s}", macname(*s++ & 0377)); continue; } for (mp = MetaMacros; mp->metaname != '\0'; mp++) { if ((mp->metaval & 0377) == c) { printf("$%c", mp->metaname); break; } } if (mp->metaname != '\0') continue; (void) putchar('\\'); c &= 0177; } 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; default: (void) putchar('^'); (void) putchar(c ^ 0100); continue; } (void) putchar('\\'); (void) putchar(c); } (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. ** ** Returns: ** none. ** ** Side Effects: ** none. */ void buildfname(gecos, login, buf) register char *gecos; char *login; char *buf; { register char *p; register char *bp = buf; int l; if (*gecos == '*') gecos++; /* find length of final string */ l = 0; for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++) { if (*p == '&') l += strlen(login); else l++; } /* now fill in buf */ for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++) { if (*p == '&') { (void) strcpy(bp, login); *bp = toupper(*bp); while (*bp != '\0') bp++; } else *bp++ = *p; } *bp = '\0'; } /* ** SAFEFILE -- return true if a file exists and is safe for a user. ** ** Parameters: ** fn -- filename to check. ** uid -- user id to compare against. ** gid -- group id to compare against. ** uname -- user name to compare against (used for group ** sets). ** flags -- modifiers: ** SFF_MUSTOWN -- "uid" must own this file. ** SFF_NOSLINK -- file cannot be a symbolic link. ** mode -- mode bits that must match. ** st -- if set, points to a stat structure that will ** get the stat info for the file. ** ** Returns: ** 0 if fn exists, is owned by uid, and matches mode. ** An errno otherwise. The actual errno is cleared. ** ** Side Effects: ** none. */ #include #ifndef S_IXOTH # define S_IXOTH (S_IEXEC >> 6) #endif #ifndef S_IXGRP # define S_IXGRP (S_IEXEC >> 3) #endif #ifndef S_IXUSR # define S_IXUSR (S_IEXEC) #endif #define ST_MODE_NOFILE 0171147 /* unlikely to occur */ int safefile(fn, uid, gid, uname, flags, mode, st) char *fn; uid_t uid; gid_t gid; char *uname; int flags; int mode; struct stat *st; { register char *p; register struct group *gr = NULL; struct stat stbuf; if (tTd(54, 4)) printf("safefile(%s, uid=%d, gid=%d, flags=%x, mode=%o):\n", fn, uid, gid, flags, mode); errno = 0; if (st == NULL) st = &stbuf; if (!bitset(SFF_NOPATHCHECK, flags) || (uid == 0 && !bitset(SFF_ROOTOK, flags))) { /* check the path to the file for acceptability */ for (p = fn; (p = strchr(++p, '/')) != NULL; *p = '/') { *p = '\0'; if (stat(fn, &stbuf) < 0) break; if (uid == 0 && bitset(S_IWGRP|S_IWOTH, stbuf.st_mode)) message("051 WARNING: writable directory %s", fn); if (uid == 0 && !bitset(SFF_ROOTOK, flags)) { if (bitset(S_IXOTH, stbuf.st_mode)) continue; break; } if (stbuf.st_uid == uid && bitset(S_IXUSR, stbuf.st_mode)) continue; if (stbuf.st_gid == gid && bitset(S_IXGRP, stbuf.st_mode)) continue; #ifndef NO_GROUP_SET if (uname != NULL && ((gr != NULL && gr->gr_gid == stbuf.st_gid) || (gr = getgrgid(stbuf.st_gid)) != NULL)) { register char **gp; for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++) if (strcmp(*gp, uname) == 0) break; if (gp != NULL && *gp != NULL && bitset(S_IXGRP, stbuf.st_mode)) continue; } #endif if (!bitset(S_IXOTH, stbuf.st_mode)) break; } if (p != NULL) { int ret = errno; if (ret == 0) ret = EACCES; if (tTd(54, 4)) printf("\t[dir %s] %s\n", fn, errstring(ret)); *p = '/'; return ret; } } #ifdef HASLSTAT if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st) : stat(fn, st)) < 0) #else if (stat(fn, st) < 0) #endif { int ret = errno; if (tTd(54, 4)) printf("\t%s\n", errstring(ret)); errno = 0; if (!bitset(SFF_CREAT, flags)) return ret; /* check to see if legal to create the file */ p = strrchr(fn, '/'); if (p == NULL) return ENOTDIR; *p = '\0'; if (stat(fn, &stbuf) >= 0) { int md = S_IWRITE|S_IEXEC; if (stbuf.st_uid != uid) md >>= 6; if ((stbuf.st_mode & md) != md) errno = EACCES; } ret = errno; if (tTd(54, 4)) printf("\t[final dir %s uid %d mode %o] %s\n", fn, stbuf.st_uid, stbuf.st_mode, errstring(ret)); *p = '/'; st->st_mode = ST_MODE_NOFILE; return ret; } #ifdef S_ISLNK if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode)) { if (tTd(54, 4)) printf("\t[slink mode %o]\tEPERM\n", st->st_mode); return EPERM; } #endif if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode)) { if (tTd(54, 4)) printf("\t[non-reg mode %o]\tEPERM\n", st->st_mode); return EPERM; } if (bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && bitset(0111, st->st_mode)) { if (tTd(29, 5)) printf("failed (mode %o: x bits)\n", st->st_mode); return EPERM; } if (bitset(SFF_SETUIDOK, flags)) { #ifdef SUID_ROOT_FILES_OK if (bitset(S_ISUID, st->st_mode)) #else if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0) #endif { uid = st->st_uid; uname = NULL; } #ifdef SUID_ROOT_FILES_OK if (bitset(S_ISGID, st->st_mode)) #else if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0) #endif gid = st->st_gid; } if (uid == 0 && !bitset(SFF_ROOTOK, flags)) mode >>= 6; else if (st->st_uid != uid) { mode >>= 3; if (st->st_gid == gid) ; #ifndef NO_GROUP_SET else if (uname != NULL && ((gr != NULL && gr->gr_gid == st->st_gid) || (gr = getgrgid(st->st_gid)) != NULL)) { register char **gp; for (gp = gr->gr_mem; *gp != NULL; gp++) if (strcmp(*gp, uname) == 0) break; if (*gp == NULL) mode >>= 3; } #endif else mode >>= 3; } if (tTd(54, 4)) printf("\t[uid %d, stat %o, mode %o] ", st->st_uid, st->st_mode, mode); if ((st->st_uid == uid || st->st_uid == 0 || !bitset(SFF_MUSTOWN, flags)) && (st->st_mode & mode) == mode) { if (tTd(54, 4)) printf("\tOK\n"); return 0; } if (tTd(54, 4)) printf("\tEACCES\n"); return EACCES; } /* ** SAFEFOPEN -- do a file open with extra checking ** ** Parameters: ** fn -- the file name to open. ** omode -- the open-style mode flags. ** cmode -- the create-style mode flags. ** sff -- safefile flags. ** ** Returns: ** Same as fopen. */ #ifndef O_ACCMODE # define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) #endif FILE * safefopen(fn, omode, cmode, sff) char *fn; int omode; int cmode; int sff; { int rval; FILE *fp; int smode; struct stat stb, sta; if (bitset(O_CREAT, omode)) sff |= SFF_CREAT; smode = 0; switch (omode & O_ACCMODE) { case O_RDONLY: smode = S_IREAD; break; case O_WRONLY: smode = S_IWRITE; break; case O_RDWR: smode = S_IREAD|S_IWRITE; break; default: smode = 0; break; } if (bitset(SFF_OPENASROOT, sff)) rval = safefile(fn, 0, 0, NULL, sff, smode, &stb); else rval = safefile(fn, RealUid, RealGid, RealUserName, sff, smode, &stb); if (rval != 0) { errno = rval; return NULL; } if (stb.st_mode == ST_MODE_NOFILE) omode |= O_EXCL; fp = dfopen(fn, omode, cmode); if (fp == NULL) return NULL; if (bitset(O_EXCL, omode)) return fp; if (fstat(fileno(fp), &sta) < 0 || sta.st_nlink != stb.st_nlink || sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino || sta.st_uid != stb.st_uid || sta.st_gid != stb.st_gid) { syserr("554 cannot open: file %s changed after open", fn); fclose(fp); errno = EPERM; return NULL; } return fp; } /* ** 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'; } /* ** DFOPEN -- determined file open ** ** This routine has the semantics of fopen, except that it will ** keep trying a few times to make this happen. The idea is that ** on very loaded systems, we may run out of resources (inodes, ** whatever), so this tries to get around it. */ struct omodes { int mask; int mode; char *farg; } OpenModes[] = { O_ACCMODE, O_RDONLY, "r", O_ACCMODE|O_APPEND, O_WRONLY, "w", O_ACCMODE|O_APPEND, O_WRONLY|O_APPEND, "a", O_TRUNC, 0, "w+", O_APPEND, O_APPEND, "a+", 0, 0, "r+", }; FILE * dfopen(filename, omode, cmode) char *filename; int omode; int cmode; { register int tries; int fd; register struct omodes *om; struct stat st; for (om = OpenModes; om->mask != 0; om++) if ((omode & om->mask) == om->mode) break; for (tries = 0; tries < 10; tries++) { sleep((unsigned) (10 * tries)); errno = 0; fd = open(filename, omode, cmode); if (fd >= 0) break; switch (errno) { case ENFILE: /* system file table full */ case EINTR: /* interrupted syscall */ #ifdef ETXTBSY case ETXTBSY: /* Apollo: net file locked */ #endif continue; } break; } if (fd >= 0 && fstat(fd, &st) >= 0 && S_ISREG(st.st_mode)) { int locktype; /* lock the file to avoid accidental conflicts */ if ((omode & O_ACCMODE) != O_RDONLY) locktype = LOCK_EX; else locktype = LOCK_SH; (void) lockfile(fd, filename, NULL, locktype); errno = 0; } if (fd < 0) return NULL; else return fdopen(fd, om->farg); } /* ** 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, 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. ** mci -- the mailer connection information. ** pxflags -- flag bits: ** PXLF_MAPFROM -- map From_ to >From_. ** PXLF_STRIP8BIT -- strip 8th bit. ** ** Returns: ** none ** ** Side Effects: ** output of l to fp. */ void putxline(l, mci, pxflags) register char *l; register MCI *mci; int pxflags; { register char *p; register char svchar; int slop = 0; /* strip out 0200 bits -- these can look like TELNET protocol */ if (bitset(MCIF_7BIT, mci->mci_flags) || bitset(PXLF_STRIP8BIT, pxflags)) { for (p = l; (svchar = *p) != '\0'; ++p) if (bitset(0200, svchar)) *p = svchar &~ 0200; } do { /* find the end of the line */ p = strchr(l, '\n'); if (p == NULL) p = &l[strlen(l)]; if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d >>> ", getpid()); /* check for line overflow */ while (mci->mci_mailer->m_linelimit > 0 && (p - l + slop) > mci->mci_mailer->m_linelimit) { register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1]; svchar = *q; *q = '\0'; if (l[0] == '.' && slop == 0 && bitnset(M_XDOT, mci->mci_mailer->m_flags)) { (void) putc('.', mci->mci_out); 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 (TrafficLogFile != NULL) (void) putc('>', TrafficLogFile); } fputs(l, mci->mci_out); (void) putc('!', mci->mci_out); fputs(mci->mci_mailer->m_eol, mci->mci_out); (void) putc(' ', mci->mci_out); if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%s!\n%05d >>> ", l, getpid()); *q = svchar; l = q; slop = 1; } /* output last part */ if (l[0] == '.' && slop == 0 && bitnset(M_XDOT, mci->mci_mailer->m_flags)) { (void) putc('.', mci->mci_out); if (TrafficLogFile != NULL) (void) putc('.', TrafficLogFile); } if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%.*s\n", p - l, l); for ( ; l < p; ++l) (void) putc(*l, mci->mci_out); fputs(mci->mci_mailer->m_eol, mci->mci_out); if (*l == '\n') ++l; } while (l[0] != '\0'); } /* ** 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; # ifdef LOG if (LogLevel > 98) syslog(LOG_DEBUG, "%s: unlink %s", CurEnv->e_id, f); # endif /* LOG */ i = unlink(f); # ifdef LOG if (i < 0 && LogLevel > 97) syslog(LOG_DEBUG, "%s: unlink-fail %d", f, errno); # endif /* LOG */ } /* ** 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(%x) %s %s\n", 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(); 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) { # ifdef LOG syslog(LOG_NOTICE, "timeout waiting for input from %s during %s\n", CurHostName? CurHostName: "local", during); # endif errno = 0; usrerr("451 timeout waiting for input during %s", during); buf[0] = '\0'; #if XDEBUG checkfd012(during); #endif 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", getpid()); return (NULL); } if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d <<< %s", 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); } 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); } /* ** WAITFOR -- wait for a particular process id. ** ** Parameters: ** pid -- process id to wait for. ** ** Returns: ** status of pid. ** -1 if pid never shows up. ** ** Side Effects: ** none. */ int waitfor(pid) int pid; { #ifdef WAITUNION union wait st; #else auto int st; #endif int i; do { errno = 0; i = wait(&st); } while ((i >= 0 || errno == EINTR) && i != pid); if (i < 0) return -1; #ifdef WAITUNION return st.w_status; #else return st; #endif } /* ** 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; struct stat stbuf; for (i = 0; i < 3; i++) { if (fstat(i, &stbuf) < 0 && errno != EOPNOTSUPP) { /* oops.... */ int fd; syserr("%s: fd %d not open", where, i); fd = open("/dev/null", i == 0 ? O_RDONLY : O_WRONLY, 0666); if (fd != i) { (void) dup2(fd, i); (void) close(fd); } } } #endif /* XDEBUG */ } /* ** 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; char *fmtstr; SOCKADDR sa; auto int slen; struct stat st; char buf[200]; extern char *hostnamebyanyaddr(); p = buf; sprintf(p, "%3d: ", fd); p += strlen(p); if (fstat(fd, &st) < 0) { if (printclosed || errno != EBADF) { sprintf(p, "CANNOT STAT (%s)", errstring(errno)); goto printit; } return; } slen = fcntl(fd, F_GETFL, NULL); if (slen != -1) { sprintf(p, "fl=0x%x, ", slen); p += strlen(p); } sprintf(p, "mode=%o: ", st.st_mode); p += strlen(p); switch (st.st_mode & S_IFMT) { #ifdef S_IFSOCK case S_IFSOCK: sprintf(p, "SOCK "); p += strlen(p); slen = sizeof sa; if (getsockname(fd, &sa.sa, &slen) < 0) sprintf(p, "(%s)", errstring(errno)); else { hp = hostnamebyanyaddr(&sa); if (sa.sa.sa_family == AF_INET) sprintf(p, "%s/%d", hp, ntohs(sa.sin.sin_port)); else sprintf(p, "%s", hp); } p += strlen(p); sprintf(p, "->"); p += strlen(p); slen = sizeof sa; if (getpeername(fd, &sa.sa, &slen) < 0) sprintf(p, "(%s)", errstring(errno)); else { hp = hostnamebyanyaddr(&sa); if (sa.sa.sa_family == AF_INET) sprintf(p, "%s/%d", hp, ntohs(sa.sin.sin_port)); else sprintf(p, "%s", hp); } break; #endif case S_IFCHR: sprintf(p, "CHR: "); p += strlen(p); goto defprint; case S_IFBLK: sprintf(p, "BLK: "); p += strlen(p); goto defprint; #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) case S_IFIFO: sprintf(p, "FIFO: "); p += strlen(p); goto defprint; #endif #ifdef S_IFDIR case S_IFDIR: sprintf(p, "DIR: "); p += strlen(p); goto defprint; #endif #ifdef S_IFLNK case S_IFLNK: sprintf(p, "LNK: "); p += strlen(p); goto defprint; #endif default: defprint: if (sizeof st.st_size > sizeof (long)) fmtstr = "dev=%d/%d, ino=%d, nlink=%d, u/gid=%d/%d, size=%qd"; else fmtstr = "dev=%d/%d, ino=%d, nlink=%d, u/gid=%d/%d, size=%ld"; sprintf(p, fmtstr, major(st.st_dev), minor(st.st_dev), st.st_ino, st.st_nlink, st.st_uid, st.st_gid, st.st_size); break; } printit: #ifdef LOG if (logit) syslog(LOG_DEBUG, "%s", buf); else #endif printf("%s\n", buf); } /* ** SHORTENSTRING -- return short version of a string ** ** If the string is already short, just return it. If it is too ** long, return the head and tail of the string. ** ** Parameters: ** s -- the string to shorten. ** m -- the max length of the string. ** ** Returns: ** Either s or a short version of s. */ #ifndef MAXSHORTSTR # define MAXSHORTSTR 203 #endif char * shortenstring(s, m) register const char *s; int m; { int l; static char buf[MAXSHORTSTR + 1]; l = strlen(s); if (l < m) return (char *) s; if (m > MAXSHORTSTR) m = MAXSHORTSTR; else if (m < 10) { if (m < 5) { strncpy(buf, s, m); buf[m] = '\0'; return buf; } strncpy(buf, s, m - 3); strcpy(buf + m - 3, "..."); return buf; } m = (m - 3) / 2; strncpy(buf, s, m); strcpy(buf + m, "..."); strcpy(buf + m + 3, s + l - m); return 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(); setgid(DefGid); setuid(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); } /* ** 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. ** ** Returns: ** buf if successful. ** NULL otherwise. */ char * get_column(line, col, delim, buf) char line[]; int col; char delim; char buf[]; { char *p; char *begin, *end; int i; char delimbuf[3]; 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 && 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 && isspace(*begin)) begin++; } } end = strpbrk(begin, delimbuf); if (end == NULL) { strcpy(buf, begin); } else { strncpy(buf, begin, end - begin); buf[end - begin] = '\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; { #ifdef LOG /* check for newlines and log if necessary */ (void) denlstring(f, TRUE, TRUE); #endif 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++ = ' '; /* #ifdef LOG if (logattacks) { syslog(LOG_NOTICE, "POSSIBLE ATTACK from %s: newline in string \"%s\"", RealHostName == NULL ? "[UNKNOWN]" : RealHostName, shortenstring(bp, 80)); } #endif */ return bp; }