/* * Copyright (c) 1983, 1995-1997 Eric P. Allman * Copyright (c) 1988, 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. */ #ifndef lint static char sccsid[] = "@(#)err.c 8.65 (Berkeley) 10/18/97"; #endif /* not lint */ # include "sendmail.h" # include /* ** SYSERR -- Print error message. ** ** Prints an error message via printf to the diagnostic output. ** ** If the first character of the syserr message is `!' it will ** log this as an ALERT message and exit immediately. This can ** leave queue files in an indeterminate state, so it should not ** be used lightly. ** ** Parameters: ** fmt -- the format string. If it does not begin with ** a three-digit SMTP reply code, either 554 or ** 451 is assumed depending on whether errno ** is set. ** (others) -- parameters ** ** Returns: ** none ** Through TopFrame if QuickAbort is set. ** ** Side Effects: ** increments Errors. ** sets ExitStat. */ char MsgBuf[BUFSIZ*2]; /* text of most recent message */ char HeldMessageBuf[sizeof MsgBuf]; /* for held messages */ extern void putoutmsg __P((char *, bool, bool)); extern void puterrmsg __P((char *)); static void fmtmsg __P((char *, const char *, const char *, int, const char *, va_list)); #if NAMED_BIND && !defined(NO_DATA) # define NO_DATA NO_ADDRESS #endif void /*VARARGS1*/ #ifdef __STDC__ syserr(const char *fmt, ...) #else syserr(fmt, va_alist) const char *fmt; va_dcl #endif { register char *p; int olderrno = errno; bool panic; char *uname; struct passwd *pw; char ubuf[80]; VA_LOCAL_DECL panic = *fmt == '!'; if (panic) { fmt++; HoldErrs = FALSE; } /* format and output the error message */ if (olderrno == 0) p = "554"; else p = "451"; VA_START(fmt); fmtmsg(MsgBuf, (char *) NULL, p, olderrno, fmt, ap); VA_END; puterrmsg(MsgBuf); /* save this message for mailq printing */ if (!panic && CurEnv != NULL) { if (CurEnv->e_message != NULL) free(CurEnv->e_message); CurEnv->e_message = newstr(MsgBuf + 4); } /* determine exit status if not already set */ if (ExitStat == EX_OK) { if (olderrno == 0) ExitStat = EX_SOFTWARE; else ExitStat = EX_OSERR; if (tTd(54, 1)) printf("syserr: ExitStat = %d\n", ExitStat); } pw = sm_getpwuid(getuid()); if (pw != NULL) uname = pw->pw_name; else { uname = ubuf; snprintf(ubuf, sizeof ubuf, "UID%d", getuid()); } if (LogLevel > 0) sm_syslog(panic ? LOG_ALERT : LOG_CRIT, CurEnv == NULL ? NOQID : CurEnv->e_id, "SYSERR(%s): %.900s", uname, &MsgBuf[4]); switch (olderrno) { case EBADF: case ENFILE: case EMFILE: case ENOTTY: #ifdef EFBIG case EFBIG: #endif #ifdef ESPIPE case ESPIPE: #endif #ifdef EPIPE case EPIPE: #endif #ifdef ENOBUFS case ENOBUFS: #endif #ifdef ESTALE case ESTALE: #endif printopenfds(TRUE); mci_dump_all(TRUE); break; } if (panic) { #ifdef XLA xla_all_end(); #endif if (tTd(0, 1)) abort(); exit(EX_OSERR); } errno = 0; if (QuickAbort) longjmp(TopFrame, 2); } /* ** USRERR -- Signal user error. ** ** This is much like syserr except it is for user errors. ** ** Parameters: ** fmt -- the format string. If it does not begin with ** a three-digit SMTP reply code, 501 is assumed. ** (others) -- printf strings ** ** Returns: ** none ** Through TopFrame if QuickAbort is set. ** ** Side Effects: ** increments Errors. */ /*VARARGS1*/ void #ifdef __STDC__ usrerr(const char *fmt, ...) #else usrerr(fmt, va_alist) const char *fmt; va_dcl #endif { VA_LOCAL_DECL if (SuprErrs) return; VA_START(fmt); fmtmsg(MsgBuf, CurEnv->e_to, "501", 0, fmt, ap); VA_END; /* save this message for mailq printing */ switch (MsgBuf[0]) { case '4': case '8': if (CurEnv->e_message != NULL) break; /* fall through.... */ case '5': case '6': if (CurEnv->e_message != NULL) free(CurEnv->e_message); if (MsgBuf[0] == '6') { char buf[MAXLINE]; snprintf(buf, sizeof buf, "Postmaster warning: %.*s", sizeof buf - 22, MsgBuf + 4); CurEnv->e_message = newstr(buf); } else { CurEnv->e_message = newstr(MsgBuf + 4); } break; } puterrmsg(MsgBuf); if (LogLevel > 3 && LogUsrErrs) sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", &MsgBuf[4]); if (QuickAbort) longjmp(TopFrame, 1); } /* ** MESSAGE -- print message (not necessarily an error) ** ** Parameters: ** msg -- the message (printf fmt) -- it can begin with ** an SMTP reply code. If not, 050 is assumed. ** (others) -- printf arguments ** ** Returns: ** none ** ** Side Effects: ** none. */ /*VARARGS2*/ void #ifdef __STDC__ message(const char *msg, ...) #else message(msg, va_alist) const char *msg; va_dcl #endif { VA_LOCAL_DECL errno = 0; VA_START(msg); fmtmsg(MsgBuf, CurEnv->e_to, "050", 0, msg, ap); VA_END; putoutmsg(MsgBuf, FALSE, FALSE); /* save this message for mailq printing */ switch (MsgBuf[0]) { case '4': case '8': if (CurEnv->e_message != NULL) break; /* fall through.... */ case '5': if (CurEnv->e_message != NULL) free(CurEnv->e_message); CurEnv->e_message = newstr(MsgBuf + 4); break; } } /* ** NMESSAGE -- print message (not necessarily an error) ** ** Just like "message" except it never puts the to... tag on. ** ** Parameters: ** msg -- the message (printf fmt) -- if it begins ** with a three digit SMTP reply code, that is used, ** otherwise 050 is assumed. ** (others) -- printf arguments ** ** Returns: ** none ** ** Side Effects: ** none. */ /*VARARGS2*/ void #ifdef __STDC__ nmessage(const char *msg, ...) #else nmessage(msg, va_alist) const char *msg; va_dcl #endif { VA_LOCAL_DECL errno = 0; VA_START(msg); fmtmsg(MsgBuf, (char *) NULL, "050", 0, msg, ap); VA_END; putoutmsg(MsgBuf, FALSE, FALSE); /* save this message for mailq printing */ switch (MsgBuf[0]) { case '4': case '8': if (CurEnv->e_message != NULL) break; /* fall through.... */ case '5': if (CurEnv->e_message != NULL) free(CurEnv->e_message); CurEnv->e_message = newstr(MsgBuf + 4); break; } } /* ** PUTOUTMSG -- output error message to transcript and channel ** ** Parameters: ** msg -- message to output (in SMTP format). ** holdmsg -- if TRUE, don't output a copy of the message to ** our output channel. ** heldmsg -- if TRUE, this is a previously held message; ** don't log it to the transcript file. ** ** Returns: ** none. ** ** Side Effects: ** Outputs msg to the transcript. ** If appropriate, outputs it to the channel. ** Deletes SMTP reply code number as appropriate. */ void putoutmsg(msg, holdmsg, heldmsg) char *msg; bool holdmsg; bool heldmsg; { char msgcode = msg[0]; /* display for debugging */ if (tTd(54, 8)) printf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "", heldmsg ? " (held)" : ""); /* map warnings to something SMTP can handle */ if (msgcode == '6') msg[0] = '5'; else if (msgcode == '8') msg[0] = '4'; /* output to transcript if serious */ if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL && strchr("45", msg[0]) != NULL) fprintf(CurEnv->e_xfp, "%s\n", msg); if (LogLevel >= 15 && (OpMode == MD_SMTP || OpMode == MD_DAEMON)) sm_syslog(LOG_INFO, CurEnv->e_id, "--> %s%s", msg, holdmsg ? " (held)" : ""); if (msgcode == '8') msg[0] = '0'; /* output to channel if appropriate */ if (!Verbose && msg[0] == '0') return; if (holdmsg) { /* save for possible future display */ msg[0] = msgcode; snprintf(HeldMessageBuf, sizeof HeldMessageBuf, "%s", msg); return; } (void) fflush(stdout); if (OutChannel == NULL) return; /* if DisConnected, OutChannel now points to the transcript */ if (!DisConnected && (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP)) fprintf(OutChannel, "%s\r\n", msg); else fprintf(OutChannel, "%s\n", &msg[4]); if (TrafficLogFile != NULL) fprintf(TrafficLogFile, "%05d >>> %s\n", (int) getpid(), (OpMode == MD_SMTP || OpMode == MD_DAEMON) ? msg : &msg[4]); if (msg[3] == ' ') (void) fflush(OutChannel); if (!ferror(OutChannel) || DisConnected) return; /* ** Error on output -- if reporting lost channel, just ignore it. ** Also, ignore errors from QUIT response (221 message) -- some ** rude servers don't read result. */ if (InChannel == NULL || feof(InChannel) || ferror(InChannel) || strncmp(msg, "221", 3) == 0) return; /* can't call syserr, 'cause we are using MsgBuf */ HoldErrs = TRUE; if (LogLevel > 0) sm_syslog(LOG_CRIT, CurEnv->e_id, "SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s", CurHostName == NULL ? "NO-HOST" : CurHostName, shortenstring(msg, 203), errstring(errno)); } /* ** PUTERRMSG -- like putoutmsg, but does special processing for error messages ** ** Parameters: ** msg -- the message to output. ** ** Returns: ** none. ** ** Side Effects: ** Sets the fatal error bit in the envelope as appropriate. */ void puterrmsg(msg) char *msg; { char msgcode = msg[0]; /* output the message as usual */ putoutmsg(msg, HoldErrs, FALSE); /* be careful about multiple error messages */ if (OnlyOneError) HoldErrs = TRUE; /* signal the error */ Errors++; if (CurEnv == NULL) return; if (msgcode == '6') { /* notify the postmaster */ CurEnv->e_flags |= EF_PM_NOTIFY; } else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags)) { /* mark long-term fatal errors */ CurEnv->e_flags |= EF_FATALERRS; } } /* ** FMTMSG -- format a message into buffer. ** ** Parameters: ** eb -- error buffer to get result. ** to -- the recipient tag for this message. ** num -- arpanet error number. ** en -- the error number to display. ** fmt -- format of string. ** a, b, c, d, e -- arguments. ** ** Returns: ** none. ** ** Side Effects: ** none. */ static void fmtmsg(eb, to, num, eno, fmt, ap) register char *eb; const char *to; const char *num; int eno; const char *fmt; va_list ap; { char del; int l; int spaceleft = sizeof MsgBuf; /* output the reply code */ if (isdigit(fmt[0]) && isdigit(fmt[1]) && isdigit(fmt[2])) { num = fmt; fmt += 4; } if (num[3] == '-') del = '-'; else del = ' '; (void) snprintf(eb, spaceleft, "%3.3s%c", num, del); eb += 4; spaceleft -= 4; /* output the file name and line number */ if (FileName != NULL) { (void) snprintf(eb, spaceleft, "%s: line %d: ", shortenstring(FileName, 83), LineNumber); eb += (l = strlen(eb)); spaceleft -= l; } /* output the "to" person */ if (to != NULL && to[0] != '\0' && strncmp(num, "551", 3) != 0 && strncmp(num, "251", 3) != 0) { (void) snprintf(eb, spaceleft, "%s... ", shortenstring(to, 203)); spaceleft -= strlen(eb); while (*eb != '\0') *eb++ &= 0177; } /* output the message */ (void) vsnprintf(eb, spaceleft, fmt, ap); spaceleft -= strlen(eb); while (*eb != '\0') *eb++ &= 0177; /* output the error code, if any */ if (eno != 0) (void) snprintf(eb, spaceleft, ": %s", errstring(eno)); } /* ** BUFFER_ERRORS -- arrange to buffer future error messages ** ** Parameters: ** none ** ** Returns: ** none. */ void buffer_errors() { HeldMessageBuf[0] = '\0'; HoldErrs = TRUE; } /* ** FLUSH_ERRORS -- flush the held error message buffer ** ** Parameters: ** print -- if set, print the message, otherwise just ** delete it. ** ** Returns: ** none. */ void flush_errors(print) bool print; { if (print && HeldMessageBuf[0] != '\0') putoutmsg(HeldMessageBuf, FALSE, TRUE); HeldMessageBuf[0] = '\0'; HoldErrs = FALSE; } /* ** ERRSTRING -- return string description of error code ** ** Parameters: ** errnum -- the error number to translate ** ** Returns: ** A string description of errnum. ** ** Side Effects: ** none. */ const char * errstring(errnum) int errnum; { char *dnsmsg; char *bp; static char buf[MAXLINE]; # if !HASSTRERROR && !defined(ERRLIST_PREDEFINED) extern char *sys_errlist[]; extern int sys_nerr; # endif # if SMTP extern char *SmtpPhase; # endif /* SMTP */ /* ** Handle special network error codes. ** ** These are 4.2/4.3bsd specific; they should be in daemon.c. */ dnsmsg = NULL; switch (errnum) { # if defined(DAEMON) && defined(ETIMEDOUT) case ETIMEDOUT: case ECONNRESET: bp = buf; #if HASSTRERROR snprintf(bp, SPACELEFT(buf, bp), "%s", strerror(errnum)); #else snprintf(bp, SPACELEFT(buf, bp), "%s", sys_errlist[errnum]); #endif bp += strlen(bp); if (CurHostName != NULL) { if (errnum == ETIMEDOUT) { snprintf(bp, SPACELEFT(buf, bp), " with "); bp += strlen(bp); } else { bp = buf; snprintf(bp, SPACELEFT(buf, bp), "Connection reset by "); bp += strlen(bp); } snprintf(bp, SPACELEFT(buf, bp), "%s", shortenstring(CurHostName, 203)); bp += strlen(buf); } if (SmtpPhase != NULL) { snprintf(bp, SPACELEFT(buf, bp), " during %s", SmtpPhase); } return (buf); case EHOSTDOWN: if (CurHostName == NULL) break; (void) snprintf(buf, sizeof buf, "Host %s is down", shortenstring(CurHostName, 203)); return (buf); case ECONNREFUSED: if (CurHostName == NULL) break; (void) snprintf(buf, sizeof buf, "Connection refused by %s", shortenstring(CurHostName, 203)); return (buf); # endif # if NAMED_BIND case HOST_NOT_FOUND + E_DNSBASE: dnsmsg = "host not found"; break; case TRY_AGAIN + E_DNSBASE: dnsmsg = "host name lookup failure"; break; case NO_RECOVERY + E_DNSBASE: dnsmsg = "non-recoverable error"; break; case NO_DATA + E_DNSBASE: dnsmsg = "no data known"; break; # endif case EPERM: /* SunOS gives "Not owner" -- this is the POSIX message */ return "Operation not permitted"; /* ** Error messages used internally in sendmail. */ case E_SM_OPENTIMEOUT: return "Timeout on file open"; case E_SM_NOSLINK: return "Symbolic links not allowed"; case E_SM_NOHLINK: return "Hard links not allowed"; case E_SM_REGONLY: return "Regular files only"; case E_SM_ISEXEC: return "Executable files not allowed"; case E_SM_WWDIR: return "World writable directory"; case E_SM_GWDIR: return "Group writable directory"; case E_SM_FILECHANGE: return "File changed after open"; case E_SM_WWFILE: return "World writable file"; case E_SM_GWFILE: return "Group writable file"; } if (dnsmsg != NULL) { bp = buf; strcpy(bp, "Name server: "); bp += strlen(bp); if (CurHostName != NULL) { snprintf(bp, SPACELEFT(buf, bp), "%s: ", shortenstring(CurHostName, 203)); bp += strlen(bp); } snprintf(bp, SPACELEFT(buf, bp), "%s", dnsmsg); return buf; } #if HASSTRERROR return strerror(errnum); #else if (errnum > 0 && errnum < sys_nerr) return (sys_errlist[errnum]); (void) snprintf(buf, sizeof buf, "Error %d", errnum); return (buf); #endif }