/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:queue.c,v 1.100 2005/10/11 04:57:12 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* queue.c:
* operations on the queue, such as reading them and writing to
* the queue through the spooling functions, and scanning for work.
*
* external functions: queue_message, read_message, write_body,
* log_incoming, check_grade, scan_queue,
* swallow_smtp
*/
#include "defs.h"
#include <sys/types.h>
#include <sys/param.h> /* for MAXLOGNAME if avail... */
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h>
#include <time.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#include <pcre.h>
#include "smail.h"
#include "smailarch.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "log.h"
#include "field.h"
#include "match.h"
#include "header.h"
#include "spool.h"
#include "exitcodes.h"
#include "debug.h"
#include "extern.h"
#include "smailport.h"
/*
* maximum length of a user name
*/
#ifndef MAXUSER
# if defined(LOGIN_NAME_MAX)
# define MAXUSER (LOGIN_NAME_MAX-1)
# elif defined(MAXLOGNAME)
# define MAXUSER MAXLOGNAME
# else
# define MAXUSER 16 /* default for NetBSD anyway.... */
# endif
#endif
#if (MAXUSER >= BUFSIZ)
# include "Something is wrong: MAXUSER >= BUFSIZ!" /* compile-time error! */
#endif
/* variables exported from this file */
int msg_grade; /* grade level for this message */
unsigned long int msg_body_size = 0; /* size of message body */
off_t start_msg_headers = 0; /* where the message headers begin */
int sender_is_trusted = FALSE; /* TRUE if sender address and Sender: are trusted */
/* variables local to this file */
static off_t start_msg_body; /* where message body begins */
/* functions local to this file */
static int queue_envelope __P((struct addr *, char **));
static int queue_message_internal __P((FILE *, enum dot_usage, char **));
static int queue_message_with_dots __P((FILE *, enum dot_usage, char **));
static int queue_message_simple __P((FILE *, char **));
static int fixup_local_sender __P((char **));
static int put_line __P((char *, char **));
static int read_from_lines __P((void));
static char *forward_token __P((char *));
static char *backward_token __P((char *, char *));
static void finish_sender __P((char *, char *));
static int queue_compare __P((const void *, const void *));
/*
* queue_message - spool a message.
*
* spool a message from a stdio file, possibly obeying the hidden-dot
* protocol from the input. It will always turn cr/lf into just a
* newline, while never requiring this to be at the end of lines.
*
* If a caller wishes to log any errors that occured during spooling,
* it must call log_spool_errors.
*
* return SUCCEED or FAIL.
*/
int
queue_message(f, dusage, list, errorp)
register FILE *f; /* input file */
enum dot_usage dusage; /* how to treat dots */
struct addr *list; /* list of recipient addr structures */
char **errorp; /* return error message here */
{
DEBUG(DBG_QUEUE_HI, "queue_message(): called...\n");
/* create the spool file */
if (creat_spool() == FAIL) {
*errorp = "spool file creat error";
if (dusage == SMTP_DOTS) {
(void) swallow_smtp(f);
}
return FAIL;
}
errno = 0;
*errorp = NULL;
/* fill it with stuff */
if (queue_envelope(list, errorp) == FAIL ||
queue_message_internal(f, dusage, errorp) == FAIL ||
write_spool(errorp) == FAIL ||
fixup_local_sender(errorp) == FAIL)
{
if (*errorp == NULL) {
*errorp = "unknown spool file error";
}
unlink_spool();
return FAIL;
}
/*
* If we got this far then any system errors above are irrelevant and/or
* unrelated.
*/
errno = 0;
/*
* This seems to be the most logical place to scan the incoming content
* with the header_checks and body_checks PCRE lists.
*
* We could try to do it as we read it, buffer by buffer maybe, so as to be
* able to avoid ever having to write junk we don't want to our local disk,
* but we don't necessarily have newline terminated buffers. At least if
* we do it before the fsync() we'll avoid forcing I/Os that we'll not
* need, and if they happen to trickle out while we're scanning, well that
* _should_ mean there's ample disk bandwidth that it doesn't matter.
*
* However doing it here will allow us to implement separate header_checks
* and body_checks like some other mailers do, which might make sense since
* the kinds of patterns we are likely to want to reject in headers will be
* very different from those for message bodies.
*
* We should also consider implementing a watchdog timer so that we don't
* exceed the minimum SMTP delay for a command response. If the timer goes
* off and we're not done scanning, well then we'll just have to deliver
* the message.
*/
if (header_checks_always_list) {
int rc = egrep_spool_headers(header_checks_always_list, errorp);
if (rc != MATCH_NOMATCH) {
DEBUG2(DBG_QUEUE_LO, "queue_message(): header_checks_always %s: %s\n",
(rc == MATCH_FAIL) ? "matching failed" : "matched", *errorp);
unlink_spool();
/* temporary, or permanent reject? */
errno = (rc == MATCH_FAIL) ? ERANGE : 0; /* XXX EPIPE? */
return FAIL;
}
}
if (header_checks_list) {
int rc = egrep_spool_headers(header_checks_list, errorp);
switch (rc) {
case MATCH_MATCHED:
DEBUG1(DBG_QUEUE_LO, "queue_message(): %s\n", *errorp);
if (recipients && bypass_content_filtering_recipients) {
struct addr *keep;
keep = keep_matching_addrs(recipients, bypass_content_filtering_recipients);
if (keep) {
DEBUG(DBG_QUEUE_LO, "queue_message(): not rejecting due to header_checks, some recipient(s) matched bypass_content_filtering_recipients\n");
recipients = keep;
*errorp = NULL;
} else {
unlink_spool();
/* we have to make sure this looks like a content filter reject! */
errno = 0;
return FAIL;
}
} else {
unlink_spool();
/* we have to make sure this looks like a content filter reject! */
errno = 0;
return FAIL;
}
break;
case MATCH_FAIL:
DEBUG1(DBG_QUEUE_LO, "queue_message(): body_checks matching failed %s\n", *errorp);
errno = ERANGE; /* XXX EPIPE? */
return FAIL;
}
}
if (body_checks_always_list) {
int rc = egrep_spool_body(body_checks_always_list, errorp);
if (rc != MATCH_NOMATCH) {
DEBUG2(DBG_QUEUE_LO, "queue_message(): body_checks_always %s: %s\n",
(rc == MATCH_FAIL) ? "matching failed" : "matched", *errorp);
unlink_spool();
/* temporary, or permanent reject? */
errno = (rc == MATCH_FAIL) ? ERANGE : 0; /* XXX EPIPE? */
return FAIL;
}
}
if (body_checks_list) {
int rc = egrep_spool_body(body_checks_list, errorp);
switch (rc) {
case MATCH_MATCHED:
DEBUG1(DBG_QUEUE_LO, "queue_message(): %s\n", *errorp);
if (recipients && bypass_content_filtering_recipients) {
struct addr *keep;
keep = keep_matching_addrs(recipients, bypass_content_filtering_recipients);
if (keep) {
DEBUG(DBG_QUEUE_LO, "queue_message(): not rejecting due to body_checks, some recipient(s) matched bypass_content_filtering_recipients\n");
recipients = keep;
*errorp = NULL;
} else {
unlink_spool();
/* we have to make sure this looks like a content filter reject! */
errno = 0;
return FAIL;
}
} else {
unlink_spool();
/* we have to make sure this looks like a content filter reject! */
errno = 0;
return FAIL;
}
break;
case MATCH_FAIL:
DEBUG1(DBG_QUEUE_LO, "queue_message(): body_checks matching failed %s\n", *errorp);
errno = ERANGE; /* XXX EPIPE? */
return FAIL;
}
}
#ifdef HAVE_FSYNC
if (fsync(spoolfile) < 0) {
*errorp = "fsync write failure";
unlink_spool();
return FAIL;
}
#endif
/*
* Update the spool file's last_modified time again to take into account
* any excessive time the header and body checks above chewed up.
*/
touch(input_spool_fn, (time_t) 0, (time_t) 0);
msg_mtime = time((time_t *) NULL);
return SUCCEED;
}
/*
* swallow_smtp - read and throw away SMTP message contents
*
* Read lines up to a line containing a single '.'. Do not do
* anything with these lines. Return SUCCEED if we successfully read
* up to a line containing only a single '.'. Return FAIL otherwise.
*
* This is used to read in a complete message from an SMTP transaction
* which will not be using because of some error.
*
* XXX perhaps this should count bytes and add them to msg_size so that we can
* tell how much the client was hoping to send.
*/
int
swallow_smtp(f)
register FILE *f; /* input file */
{
register int c;
for (;;) {
if ((c = getc(f)) == EOF) return FAIL;
if (c == '.') {
if ((c = getc(f)) == EOF) return FAIL;
if (c == '\n') {
return SUCCEED;
}
if (c == '\r') {
if ((c = getc(f)) == EOF) return FAIL;
if (c == '\n') {
return SUCCEED;
}
}
}
while (c != '\n') {
if ((c = getc(f)) == EOF) return FAIL;
}
}
}
/*
* queue_envelope - write out header filler and arguments for spool file
*
* save some space for the login username, which is computed last, and
* write out any arguments, to the spool file. The form used is one smail
* command argument per line, allowing the use of process_args() in main
* to decode the envelope information.
*/
static int
queue_envelope(list, errorp)
struct addr *list; /* input recipient list */
char **errorp; /* return error message here */
{
register struct addr *cur; /* current recipient addr to process */
char *p = NULL; /* temp */
char buf[BUFSIZ]; /* temp storage (> sizeof("%u %u")) */
#if (MAXUSER >= BUFSIZ)
# include "ERROR: (MAXUSER >= BUFSIZ)!!!"
#endif
/* XXX this should always be true now we call compute_local_sender() in intialize_state() */
if (local_sender) {
if (put_line(local_sender, errorp) == EOF) {
return FAIL;
}
} else {
/*
* put MAXUSER spaces in a buffer to be used as padding for where the
* real local sender ID will be written.
*/
(void) memset(buf, ' ', (size_t) MAXUSER);
buf[MAXUSER] = '\0';
if (put_line(buf, errorp) == EOF) {
return FAIL;
}
}
/*
* write out the real uid and effective gid, as numbers. Validation
* of trusted_users will be done from the ids stored here.
*/
#if 0
assert(sizeof(real_uid) <= sizeof(unsigned int));
assert(sizeof(prog_egid) <= sizeof(unsigned int));
#endif
(void) sprintf(buf, "%u %u", (unsigned int) real_uid, (unsigned int) prog_egid);
if (put_line(buf, errorp) == EOF) {
return FAIL;
}
/*
* write out important state information in the form of command arguments.
*/
if (extract_addresses) {
/* signal that addresses are to be taken from the header */
if (put_line("-t", errorp) == EOF) {
return FAIL;
}
}
/* record the delivery mode that was specified for the message */
switch (deliver_mode) {
case BACKGROUND:
p = "-odb";
break;
case FOREGROUND:
p = "-odf";
break;
case QUEUE_MESSAGE:
p = "-odq";
break;
case DELIVER_DEFAULT: /* XXX impossible! */
p = "";
break;
}
if (put_line(p, errorp) == EOF) {
return FAIL;
}
if (queue_only) {
if (put_line("-Q", errorp) == EOF) {
return FAIL;
}
}
/* tell which form of error recover is being used */
switch (error_processing) {
case MAIL_BACK:
p = "-oem";
break;
case WRITE_BACK:
p = "-oew";
break;
case TERMINAL:
p = "-oep";
break;
case DEV_NULL:
p = "-oeq";
break;
case ERROR_DEFAULT: /* XXX impossible! */
p = "-oem";
break;
}
if (put_line(p, errorp) == EOF) {
return FAIL;
}
if (dont_deliver) {
if (put_line("-N", errorp) == EOF) {
return FAIL;
}
}
if (hop_count >= 0) {
(void) sprintf(buf, "-h%d", hop_count);
if (put_line(buf, errorp) == EOF) {
return FAIL;
}
}
if (! do_aliasing) {
if (put_line("-n", errorp) == EOF) {
return FAIL;
}
}
if (me_too) {
if (put_line("-om", errorp) == EOF) {
return FAIL;
}
}
/*
* write the sender's full name, if one was given explicitly. If it was
* not given explicitly, we would not yet know what it is.
*/
if (sender_name) {
if (put_line("-F", errorp) == FAIL ||
put_line(sender_name, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write the sender address, if one was given explicitly. If it was
* not given explicitly, we would not yet know what it is.
*/
if (sender) {
if (put_line("-f", errorp) == FAIL ||
put_line(sender, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write the sending host, host address, host port, and sending protocol,
* each if known
*/
if (sender_host) {
if (put_line("-oMs", errorp) == FAIL ||
put_line(sender_host, errorp) == FAIL)
{
return FAIL;
}
}
if (sender_host_invalid) {
if (put_line("-oMn", errorp) == FAIL ||
put_line(sender_host_invalid, errorp) == FAIL)
{
return FAIL;
}
}
if (sender_host_addr) {
if (put_line("-oMa", errorp) == FAIL ||
put_line(sender_host_addr, errorp) == FAIL)
{
return FAIL;
}
}
if (sender_host_really) {
if (put_line("-oMR", errorp) == FAIL ||
put_line(sender_host_really, errorp) == FAIL)
{
return FAIL;
}
}
if (sender_host_really_invalid) {
if (put_line("-oMN", errorp) == FAIL ||
put_line(sender_host_really_invalid, errorp) == FAIL)
{
return FAIL;
}
}
if (peer_is_localhost) {
if (put_line("-oMI", errorp) == FAIL) {
return FAIL;
}
}
if (sender_host_port) {
if (put_line("-oMp", errorp) == FAIL ||
put_line(sender_host_port, errorp) == FAIL)
{
return FAIL;
}
}
if (sender_proto) {
if (put_line("-oMr", errorp) == FAIL ||
put_line(sender_proto, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write the ident of the sending person if known
*/
if (ident_sender) {
if (put_line("-oMu", errorp) == FAIL ||
put_line(ident_sender, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write the method used to determine the ident of sender if known
*/
if (ident_method) {
if (put_line("-oMv", errorp) == FAIL ||
put_line(ident_method, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write out the local SMTP endpoint info, if known
*/
if (smtp_local_addr) {
if (put_line("-oMl", errorp) == FAIL ||
put_line(smtp_local_addr, errorp) == FAIL)
{
return FAIL;
}
}
if (smtp_local_port) {
if (put_line("-oML", errorp) == FAIL ||
put_line(smtp_local_port, errorp) == FAIL)
{
return FAIL;
}
}
/*
* write out the invoked program, too
*/
if (put_line("-oMP", errorp) == FAIL ||
put_line(program, errorp) == FAIL)
{
return FAIL;
}
/*
* write out all of the recipient addresses. If the -t flag was given,
* this is the list of addresses that are explicitly not recipients.
*/
for (cur = list; cur; cur = cur->succ) {
if (put_line(cur->in_addr, errorp) == FAIL) {
return FAIL;
}
}
/* envelope ends in one blank line */
if (PUTSPOOL('\n', errorp) == EOF) {
return FAIL;
}
/* remember where the envelope ends... tell_spool() doesn't work when writing. */
start_msg_headers = msg_foffset + msg_max - msg_buf;
DEBUG1(DBG_QUEUE_HI, "queue_envelope(): message headers start at %lu\n", (unsigned long int) start_msg_headers);
return SUCCEED;
}
/*
* queue_message_internal - read the message into a spool file.
*
* Call either queue_message_with_dots() or queue_message_simple() to
* spool a message to a spool file.
*/
static int
queue_message_internal(f, dusage, errorp)
FILE *f;
enum dot_usage dusage;
char **errorp; /* optionally store error here */
{
*errorp = NULL;
if (dusage == NO_DOT_PROTOCOL) {
return queue_message_simple(f, errorp);
} /* else */
return queue_message_with_dots(f, dusage, errorp);
}
/*
* queue_message_with_dots - read in a message and write it to the
* spool file, with a dot protocol.
*
* Read in a message using one of the dot protocols (HIDDEN_DOTS,
* DOT_ENDS_MESSAGE or SMTP_DOTS) specified in 'dusage'. Note the
* implementation here is tightly dependent on dusage having only these three
* possible values. This function must never see the fourth possible value,
* NO_DOT_PROTOCOL, so it may only be called by the protective wrapper function
* queue_message_intarnal(), defined above.)
*
* All of these protocols end a message when a dot is found on a line by
* itself.
*
* The HIDDEN_DOTS protocol drops an initial dot from a line that contains
* other data (i.e. a dot that isn't followed by end-of-line).
*
* The SMTP_DOTS protocol also drops an initial dot from a line that contains
* other data, just like HIDDEN_DOTS, except that with SMTP_DOTS a message must
* be terminated by a '.', with an encounter of EOF being an error.
*
* NOTE: also with SMTP_DOTS, the entire message is read from the input even if
* a write to the spool file encounters an error. Internally this is
* implemented by not calling PUTSPOOL() if put_success has been set to FAIL.
*
* All of these protocols treat a <CR> <LF> sequence as equivalent to a single
* newline in order to properly handle pure SMTP, though by implication they
* also violate RFC 2822 section 2.3.7 by recognizing a bare <LF> as the end of
* a line:
*
* Conforming implementations MUST NOT recognize or generate any other
* character or character sequence as a line terminator. Limits MAY be
* imposed on line lengths by servers (see section 4.5.3).
*
* and further in section 4.1.1.4 (DATA):
*
* The custom of accepting lines ending only in <LF>, as a concession to
* non-conforming behavior on the part of some UNIX systems, has proven
* to cause more interoperability problems than it solves, and SMTP
* server systems MUST NOT do this, even in the name of improved
* robustness. In particular, the sequence "<LF>.<LF>" (bare line
* feeds, without carriage returns) MUST NOT be treated as equivalent to
* <CRLF>.<CRLF> as the end of mail data indication.
*
* The authors of the above clearly consider worry of message truncation when
* raw message data contains the "<LF>.<LF>" sequence as more troublesome than
* the alternative of screwing up human users at the most critical point when
* they're trying to diagnose problems (as typically a human tester will send
* only newline-terminated input). However their worry was ill-founded since
* these days any client-SMTP wanting to prevent message truncation can very
* easily use MIME (QP or BASE64) encoding to guarantee transparency without
* the hassle of trying to guess whether the dot following a bare <LF> needs
* dot-stuffing to be protected.
*
* return SUCCEED or FAIL.
*/
static int
queue_message_with_dots(f, dusage, errorp)
register FILE *f; /* input file */
enum dot_usage dusage; /* how to treat dots */
char **errorp; /* optionally store error here */
{
register int empty = TRUE;
register int c;
register int put_success = SUCCEED; /* set to FAIL if PUTSPOOL() fails */
DEBUG2(DBG_QUEUE_LO, "Enter your message, with all headers%s, end with a '.' on a line by itself%s....\n",
dusage != DOT_ENDS_MESSAGE ? ", escaping all leading dots with another dot" : "",
dusage != SMTP_DOTS ? " or with EOF" : "")
/* read lines until dot on a line by itself, or EOF... */
for (;;) {
/* read the first character (of a line) */
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
continue;
}
DEBUG2(DBG_QUEUE_LO, "%sGot EOF%s.\n",
empty ? "ERROR: " : "",
empty ? " before first char of first line" : "");
goto read_eof;
}
break;
}
/* look for a dot at the beginning of the line */
if (c == '.') {
/* read the next char to see if the dot is on a line by itself */
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
continue;
}
DEBUG(DBG_QUEUE_LO, "ERROR: Got EOF (after <.>).\n");
goto read_eof;
}
break;
}
/*
* a plain <LF> terminates a line
*
* Note we do violate RFC 2822 section 4.1.1.4 by allowing this
* lone <LF> to terminate a line.
*/
if (c == '\n') {
/* make sure errno != 0 if we encountered a write error */
if (empty) {
*errorp = "No message input before dot";
DEBUG1(DBG_QUEUE_LO, "%s<LF>.\n", *errorp);
errno = ENOENT;
put_success = FAIL;
} else if (put_success == FAIL && errno == 0) {
errno = EIO; /* XXX got a better idea? */
DEBUG(DBG_QUEUE_LO, "ERROR: Failed to queue message input (after <.><LF>).\n");
} else {
DEBUG(DBG_QUEUE_LO, "Got '.'<LF>.\n");
}
return put_success;
}
/* <CRLF> (also) terminates a line */
if (c == '\r') {
/* look to see what follows the <CR>... */
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
continue;
}
DEBUG(DBG_QUEUE_LO, "ERROR: Got EOF (after <.><CR>).\n");
goto read_eof;
}
break;
}
/* if it's <LF> then we have found a dot on a line by itself */
if (c == '\n') {
/* make sure errno != 0 if we encountered a write error */
if (empty) {
*errorp = "No message input before '.'";
DEBUG1(DBG_QUEUE_LO, "%s<CR><LF>.\n", *errorp);
errno = ENOENT;
put_success = FAIL;
} else if (put_success == FAIL && errno == 0) {
errno = EIO; /* XXX got a better idea? */
DEBUG(DBG_QUEUE_LO, "ERROR: Failed to queue message input (after '.'<CR><LF>).\n");
} else {
DEBUG(DBG_QUEUE_LO, "Got '.'<CR><LF>.\n");
}
return put_success;
}
/*
* if the .<CR> is not followed by a newline then:
*
* if doing DOT_ENDS_MESSAGE then write out both the dot
* and the <CR> (dot was followed by <CR>, but not <CRLF>,
* so it's not alone on a line by itself);
*
* else if doing either SMTP_DOTS or HIDDEN_DOTS then
* write out just the <CR> (i.e. strip the "hidden" dot);
*
* and if either fails, and if doing HIDDEN_DOTS or
* DOT_ENDS_MESSAGE then return FAIL immediately (but SMTP_DOTS
* must read whole message so keep on trucking but remember
* that there was a failure)
*/
if (put_success == SUCCEED &&
((dusage == DOT_ENDS_MESSAGE &&
PUTSPOOL('.', errorp) == FAIL) ||
PUTSPOOL('\r', errorp) == FAIL)) {
if (dusage != SMTP_DOTS) {
/* errno from PUTSPOOL() should be safe here */
return FAIL;
} else {
put_success = FAIL;
}
}
} else /* (c != '\r') */ {
/*
* if doing SMTP_DOTS or HIDDEN_DOTS then don't write out the dot
*/
if (put_success == SUCCEED &&
(dusage == DOT_ENDS_MESSAGE && PUTSPOOL('.', errorp) == FAIL))
{
/* XXX should we return FAIL now if not doing SMTP_DOTS? */
put_success = FAIL;
}
}
}
empty = FALSE;
/* read the rest of the line... */
for (;;) {
/*
* read until we find a <LF> or <CR>
*
* Note once again we violate RFC 2822 section 4.1.1.4 by allowing
* a lone <LF> to terminate a line, though in this instance where
* the line did not begin with a dot, there's no worry of
* transparancy problems.
*/
while (c != '\n' && c != '\r') {
if (put_success == SUCCEED && PUTSPOOL(c, errorp) == FAIL) {
/* XXX should we return FAIL now if not doing SMTP_DOTS? */
put_success = FAIL;
}
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
continue;
}
DEBUG(DBG_QUEUE_LO, "ERROR: Got EOF in the middle of a line.\n");
goto read_eof;
}
break;
}
}
if (c == '\r') {
/* always look for a <LF> after a <CR>... */
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
continue;
}
DEBUG(DBG_QUEUE_LO, "ERROR: Got EOF after <CR> in a line.\n");
goto read_eof;
}
break;
}
if (c != '\n') {
/* NOT the end of the line -- stuff out the bare <CR>... */
if (put_success == SUCCEED && PUTSPOOL('\r', errorp) == FAIL) {
/* XXX should we return FAIL now if not doing SMTP_DOTS? */
put_success = FAIL;
}
/*
* don't fall through to the PUTSPOOL() below -- otherwise
* the one at the top of the 'while' above would duplicate
* this <CR>...
*/
continue;
}
}
if (put_success == SUCCEED && PUTSPOOL(c, errorp) == FAIL) {
/* XXX should we return now if not doing SMTP_DOTS? */
put_success = FAIL;
}
/*
* end-of-the-line detected: go back to the outer loop
*/
break;
}
}
read_eof:
if (empty) {
*errorp = "No message input found";
DEBUG1(DBG_QUEUE_LO, "%s.\n", *errorp);
errno = ENOENT;
return FAIL;
}
if (dusage != SMTP_DOTS) {
/* make sure errno != 0 if we encountered a write error */
if (put_success == FAIL && errno == 0) {
errno = EIO; /* XXX got a better idea? */
DEBUG(DBG_QUEUE_LO, "ERROR: Failed to queue message input.\n");
} else {
DEBUG(DBG_QUEUE_LO, "Read and queued message.\n");
}
return put_success;
}
/* else if doing SMTP_DOTS then EOF is always an error */
if (errno == 0) {
errno = EIO; /* XXX got a better idea? */
}
*errorp = "Unexpected end of file on message input";
DEBUG1(DBG_QUEUE_LO, "%s.\n", *errorp);
return FAIL;
}
/*
* queue_message_simple - read in a message with no dot protocol.
*
* Read in a message without any specific protocol. No CR/LF mapping
* is done and the end of message is given only with an EOF.
*
* Return SUCCEED or FAIL.
*/
static int
queue_message_simple(f, errorp)
register FILE *f;
char **errorp; /* return error message here */
{
register int empty = TRUE;
register int c;
DEBUG(DBG_QUEUE_LO, "Enter your message, with headers, end with EOF....\n");
for (;;) {
for (;;) {
errno = 0;
if ((c = getc(f)) == EOF) {
if (errno == EINTR) {
/*
* note at this point SIGINT (and SIGALRM, etc.) should be
* trapped by a handler that never returns, so this should
* only keep us reading on SIGHUP, which we might get by
* accident if the wrong process is asked to reload config
* files.
*/
DEBUG(DBG_QUEUE_LO, "\n(Continuing after EINTR, end with EOF....)\n");
continue;
}
}
break;
}
if (c == EOF) {
break;
}
empty = FALSE;
if (PUTSPOOL(c, errorp) == FAIL) {
return FAIL;
}
}
if (empty) {
*errorp = "No message input";
errno = ENOENT;
return FAIL;
}
return SUCCEED;
}
/*
* fixup_local_sender - insert the login username at the start of the spoolfile
*
* to get the message into the spool file as fast as possible, the login name
* of the user that ran smail is computed last and written into the space left
* for it after everything else is through.
*
* This is ugly, and rather silly too now that we set local_sender early before
* ever accepting the message....
*/
static int
fixup_local_sender(errorp)
char **errorp; /* return error message here */
{
register int i; /* bytes to write */
/*
* truncate to MAXUSER chars, if for some reason we got a
* larger user name, since that's all the room we left...
*/
i = strlen(local_sender);
if (i > MAXUSER) {
i = MAXUSER;
}
/*
* write the name to disk
*/
(void) lseek(spoolfile, (off_t) 1, SEEK_SET);
/*
* Note 'i' is best left as a signed int as write() will either be declared
* int or ssize_t and thus we need a signed comparision to avoid seeing a
* '-1' error as a very large number and thinking the write was successful.
*/
if (write(spoolfile, local_sender, (size_t) i) < i) {
*errorp = xprintf("fixup_local_sender(): write() failed: %s", strerror(errno));
return FAIL;
}
/*
* if the current incore spool file region is still at the beginning of the
* file, then make a copy there too to keep it in sync.
*/
if (msg_foffset == 0) {
(void) memcpy(msg_buf + 1, local_sender, (size_t) i);
}
return SUCCEED;
}
/*
* put_line - write a complete line to the spool file
*
* the string passed is assumed to be an atomic argument for smail, which
* should end in a newline. To guard against newlines in the text of the
* string to end an argument, newline is encoded as \n and \ is encoded as
* \\.
*/
static int
put_line(s, errorp)
register char *s;
char **errorp; /* return error message here */
{
if (PUTSPOOL('!', errorp) == EOF) {
return FAIL;
}
while (*s) {
if (*s == '\\') {
/* encode \ as \\ and newline as \n */
if (PUTSPOOL('\\', errorp) == EOF || PUTSPOOL('\\', errorp) == EOF) {
return FAIL;
}
} else if (*s == '\n') {
if (PUTSPOOL('\\', errorp) == EOF || PUTSPOOL('n', errorp) == EOF) {
return FAIL;
}
} else {
if (PUTSPOOL(*s, errorp) == EOF) {
return FAIL;
}
}
s++;
}
if (PUTSPOOL('\n', errorp) == EOF) {
return FAIL;
}
return SUCCEED;
}
/*
* read_message - scan through an open spool file
*
* read through the current open spool file to grab From_ and Header:
* information. Putting the sender address in the sender variable,
* and storing the header information in the headers variable as
* side effects.
*
* return the argument vector in *argv
*
* return NULL on failure. The reason for failure will be logged
* in the system log file and the spool file should be closed and
* ignored for now.
*/
char **
read_message()
{
size_t a = 32; /* vectors allocated thus far */
size_t i; /* temp index */
register int c; /* character from spool file or EOF */
register char **argv; /* computed argument vector */
int first = FALSE; /* first char on a line? */
char ls[MAXUSER + 1]; /* original local_sender from spoolfile */
DEBUG(DBG_QUEUE_HI, "read_message() called...\n");
/* initialize state before reading any new values from the spool file */
initialize_state();
i = 0;
memset(ls, '\0', sizeof(ls));
/* seek to the beginning of the region */
if (seek_spool((off_t) 0L) == FAIL) {
/* errors logged in seek_spool() */
return NULL;
}
/* a silly check to validate spool file format */
c = GETSPOOL();
if (c != '!') {
DEBUG1(DBG_QUEUE_LO, "read_message(): got '%c', not '!'\n", c);
write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "invalid spool file: %s/%s: bad spoolfile header",
spool_dir, input_spool_fn);
return NULL;
}
/* get the original local_sender value from the spool file */
while ((c = GETSPOOL()) != '\n' && c != ' ' && c != EOF && c != READ_FAIL && i < MAXUSER) {
ls[i++] = c;
}
/* ignore the rest of the first line */
while (c != '\n' && c != EOF && c != READ_FAIL) {
c = GETSPOOL();
if (c != ' ' && c != '\n' && c != EOF && c != READ_FAIL) {
DEBUG1(DBG_QUEUE_LO, "ignoring extra non-space char after local_sender: '%c'\n", c);
}
}
ls[i] = '\0';
DEBUG1(DBG_QUEUE_HI, "read_message(): read spoolfile local_sender: '%s'\n", ls);
if (local_sender) {
xfree(local_sender);
}
if (!ls[0]) {
/* XXX maybe this 'error' is worthy of logging? */
DEBUG1(DBG_QUEUE_LO, "no original local_sender in spool file, using $nobody (%s)\n", nobody);
local_sender = COPY_STRING(nobody);
} else {
local_sender = COPY_STRING(ls);
}
/* read the original real UID and effective GID under which the spooling process was run */
if (c != READ_FAIL && c != EOF) {
char buf[BUFSIZ]; /* temp input buffer */
char *p2 = buf; /* temp for scanning buf */
unsigned int stored_uid; /* in case uid_t is a (unsigned) short */
unsigned int stored_gid; /* in case gid_t is a (unsigned) short */
while ((c = GETSPOOL()) != '\n' && c != EOF && c != READ_FAIL && (size_t) (p2-buf) < sizeof(buf)) {
*p2++ = c;
}
*p2 = '\0';
p2 = buf;
if (*p2++ != '!') { /* XXX should be an error if missing! */
DEBUG1(DBG_QUEUE_LO, "read_message(): got '%c', not '!'\n", (int) *(p2-1));
write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "invalid spool file: %s/%s: no '!' before 'UID EGID'",
spool_dir, input_spool_fn);
return NULL;
}
(void) sscanf(p2, "%u %u", &stored_uid, &stored_gid);
#if 0
assert(sizeof(real_uid) == sizeof(stored_uid));
assert(sizeof(prog_egid) == sizeof(stored_gid));
#endif
real_uid = stored_uid;
prog_egid = stored_gid;
}
DEBUG2(DBG_QUEUE_HI, "read_message(): set from spoolfile: real_uid = %u, prog_egid = %u\n",
(unsigned int) real_uid, (unsigned int) prog_egid);
if (c == READ_FAIL) {
write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "read failed: %s/%s: %s",
spool_dir, input_spool_fn, strerror(errno));
return NULL;
}
if (c == EOF) {
write_log(WRITE_LOG_SYS, "unexpected end of file: %s/%s: no command parameters found",
spool_dir, input_spool_fn);
return NULL;
}
/*
* note the real_uid and prog_euid credentials used by trusted_invoker()
* are already over-written by those of the original invoker as stored in
* the spool file
*/
if (!trusted_invoker()) {
/*
* the original invoker is not trusted, sender is initially set to be
* the original local_sender
*
* Note that messages received by SMTP from any remote peer will be
* given a local_sender value of $nobody but their recorded real_uid
* and prog_euid will still be that of the user listening on the SMTP
* port (normally 0,0 (until the day when smtpd finally drops root and
* runs as nobody, at which time it will spoof the "smail" real-UID)),
* so the sender address (and any Sender: headers) specified by SMTP
* clients will still be trusted.
*/
DEBUG1(DBG_QUEUE_MID, "original runtime user is not trusted, initializing sender to original local_sender: <%s>\n", local_sender);
if (!sender) {
sender = COPY_STRING(local_sender);
islocal = TRUE; /* current new sender is local */
sender_is_trusted = FALSE; /* turn off trust of 'sender' */
}
} else {
sender_is_trusted = TRUE; /* we can trust $sender */
}
/*
* if the sender address was already specified and the originator of the
* message is trusted to supply a sender address, see if it is in local
* form or not.
*/
if (sender && sender_is_trusted) {
char *error; /* temp to store error message */
islocal = (parse_address(sender, (char **) NULL, &error, (int *) NULL) == LOCAL);
}
/*
* read the argument vectors stored in the spool file
*/
DEBUG1(DBG_QUEUE_HI, "now reading arg vectors from spool file (at %lu)\n", (unsigned long) tell_spool());
argv = (char **)xmalloc(a * sizeof(char *));
i = 0;
first = TRUE;
while ((c = GETSPOOL()) != EOF && c != '\n') {
struct str str;
register struct str *sp = &str; /* temp string */
/* read one vector */
STR_INIT(sp);
do {
if (first) {
first = FALSE;
if (c == '!') {
continue;
}
}
if (c == '\\') {
c = GETSPOOL();
if (c == EOF) {
STR_FREE(sp);
write_log(WRITE_LOG_SYS, "unexpected end of file: %s/%s",
spool_dir, input_spool_fn);
return NULL; /* no message */
} else if (c == 'n') {
c = '\n';
} else if (c != '\\') {
write_log(WRITE_LOG_SYS, "format error in: %s/%s",
spool_dir, input_spool_fn);
return NULL; /* format error */
}
}
STR_NEXT(sp, c);
} while ((c = GETSPOOL()) != EOF && c != '\n');
/*
* finish off the argument string and store it in the vector
*/
STR_NEXT(sp, '\0');
STR_DONE(sp);
DEBUG1(DBG_QUEUE_HI, "read vector '%v' from spool file\n", STR(sp));
argv[i++] = STR(sp);
/* do we need a larger vector? */
if (i >= a) {
a += 32;
argv = (char **) xrealloc((char *) argv, a * sizeof(char *));
}
first = TRUE; /* look for '!' again */
}
/* finish off the arguments vector */
argv[i] = NULL;
/*
* login name and arguments processed, scan for From_ lines, if
* any exist. As a side effect, this may set the sender if it
* has not been set already.
*/
if (read_from_lines() == FAIL) {
/* read_from_lines logged the error */
return NULL;
}
/*
* If the sender is still not known by now, then use the original
* local_sender, or Postmaster if nothing else is available.
*
* Note this may yet be overridden later if a -f or -r parameter is present
* in argv.
*/
if (sender == NULL) {
if (ls[0]) {
sender = COPY_STRING(ls);
} else {
/* love to gang up on that postmaster */
DEBUG(DBG_QUEUE_LO, "no login user in spool file, setting sender to 'Postmaster'\n");
sender = COPY_STRING("Postmaster");
}
islocal = TRUE; /* sender appears to be local */
}
/*
* now grab the header lines.
*/
DEBUG1(DBG_QUEUE_HI, "calling read_header (offset=%lu)\n", (unsigned long) tell_spool());
if (read_header() == FAIL) {
/* read_header logged the error */
return NULL;
}
/* save the start position for the message */
start_msg_body = tell_spool();
msg_body_size = (unsigned long) (msg_size - start_msg_body);
DEBUG2(DBG_SPOOL_HI, "body starts at %lu, body size: %lu\n", (unsigned long) start_msg_body, msg_body_size);
/* all done, return the arguments */
return argv;
}
/*
* read_from_lines - scan From_ lines and build up a sender
*
* Scan through to the end of the From_ lines (if any exist).
* As a side effect, build up a path to the sender, if the
* sender is not already known.
*
* return SUCCESS or FAIL, and log reason for failure.
*/
static int
read_from_lines()
{
struct str pstr;
register struct str *path = &pstr; /* build up sender path here */
struct str lstr;
register struct str *line = &lstr; /* input line */
int c; /* input character */
register char *lp; /* temp for processing line */
struct str ustr;
register struct str *user = &ustr; /* user name from From_ line */
char *mark; /* mark point in line */
off_t line_mark; /* seek position for start of line */
DEBUG1(DBG_SPOOL_HI, "read_from_lines called (offset=%lu)\n", (unsigned long) tell_spool());
/* if we already know the sender, then don't bother computing it */
if (sender == NULL) {
islocal = TRUE; /* set FALSE if we find otherwise */
STR_INIT(user);
STR_INIT(path);
}
STR_INIT(line);
/*
* loop until we have read all of the From_ lines
*/
for (;;) {
/* read one complete line */
STR_CLEAR(line);
line_mark = tell_spool();
while ((c = GETSPOOL()) != EOF && c != READ_FAIL && c != '\n') {
STR_NEXT(line, c);
}
if (c == READ_FAIL) {
write_log(WRITE_LOG_SYS, "read failed: %s/%s: %s",
spool_dir, input_spool_fn, strerror(errno));
}
if (c != EOF) {
STR_NEXT(line, c);
}
STR_NEXT(line, '\0');
lp = STR(line);
if (STR_LEN(line) < 20) {
/* less than 20 chars? couldn't possibly be a From_ line */
break;
}
/* skip beginning > character */
if (*lp == '>') {
lp++;
}
/* do we have a From line? */
if (strncmp(lp, "From", (size_t) 4) != 0 || !isspace((int) lp[4])) {
/* no, end of From_ lines */
break;
}
if (sender) {
continue; /* we know sender scan next line */
}
/* extract the user */
lp += 5;
while (isspace((int) *lp)) { /* scan for start of user name */
lp++;
}
mark = lp; /* mark the start of the user name */
/* skip over the user name token */
lp = forward_token(lp);
*lp = '\0';
STR_CLEAR(user);
STR_CAT(user, mark);
/* search for a remote from host */
lp = STR(line) + STR_LEN(line) - 2;
while (isspace((int) *lp)) {
lp--;
}
lp[1] = '\0'; /* terminate end of potential host */
lp = backward_token(STR(line), lp); /* back to start of host (?) */
mark = lp; /* host may be here */
lp = backward_token(STR(line), lp-1); /* back to start of from (?) */
if (strncmp(lp, "from", (size_t) 4) != 0 || !isspace((int) lp[4])) {
STR_CLEAR(path); /* no remote from host, toss path */
DEBUG1(DBG_SPOOL_HI, "no remote from host in '%s', toss path\n",
STR(line));
continue; /* next From_ line */
}
lp = backward_token(STR(line), lp-1); /* back to start of remote (?) */
if (strncmp(lp, "remote", (size_t) 6) != 0 || !isspace((int) lp[6])) {
STR_CLEAR(path); /* no remote from host, toss path */
DEBUG1(DBG_SPOOL_HI, "no remote from host in '%s', toss path\n",
STR(line));
continue; /* next From_ line */
}
/*
* found a remote_from host, add it to the sender path
*/
if (STR_LEN(path) > 0) {
STR_NEXT(path, '!'); /* ! is separator */
}
STR_CAT(path, mark);
islocal = FALSE; /* not a local message */
if (sender_host == NULL && sender_proto == NULL) {
sender_host = COPY_STRING(mark);
sender_proto = "uucp";
}
}
STR_FREE(line); /* finished processing input line */
/*
* if we are building up a sender, then finish doing so
*/
if (sender == NULL) {
STR_NEXT(path, '\0'); /* terminate path and user */
STR_NEXT(user, '\0');
finish_sender(STR(path), STR(user));
STR_FREE(path); /* free the storage used */
STR_FREE(user);
}
/*
* seek back to the beginning of the last line read, which was not
* a From_ line
*/
if (seek_spool((off_t) line_mark) == FAIL) {
write_log(WRITE_LOG_SYS, "seek failed on: %s/%s: %s",
spool_dir, input_spool_fn, strerror(errno));
return FAIL;
}
return SUCCEED;
}
/*
* forward_token - scan past the following token
*
* return the position of the character after the first token after p.
* a token ends in a space, but can include any chars in quotes, or
* after a \.
*/
static char *
forward_token(p)
register char *p; /* start search here */
{
register int inquote = FALSE; /* not in a quote */
while (isspace((int) *p)) /* scan to start of token */
p++;
/* loop exits by return when done */
for (;;) {
switch (*p++) {
case '\\':
if (*p)
p++;
break;
case ' ':
case '\t':
case '\n':
if (!inquote) {
return p-1; /* past end of token */
}
break;
case '\0':
return p-1; /* past end of token */
case '"': /* start or end of a quote */
inquote = !inquote;
break;
}
}
}
/*
* backward_token - scan to beginning of previous token
*
* return the position of the first character of a group
* of non white-space characters before p.
*
* NOTE: we don't take into account text in quotes in scanning
* backwards.
*/
static char *
backward_token(s,p)
register char *s; /* string starts here */
register char *p; /* start search here */
{
/* scan before end of token */
while (isspace((int) *p)) {
if (p == s) {
return p;
}
p--;
}
/* scan to start of token */
while (*p && !isspace((int) *p)) {
if (p == s) {
return p;
}
p--;
}
return p+1;
}
/*
* finish_sender - build the sender address
*
* Build the sender address out of the path and user computed in
* read_from_lines.
*
* Note this is only ever called if sender is NULL.
*/
static void
finish_sender(path, user)
register char *path; /* path to sender */
register char *user; /* user name of sender */
{
char *target = NULL; /* parsed target */
char *remainder; /* parsed remainder */
register int form; /* form from parse_address */
if (user[0] == '\0') {
return; /* didn't find anything */
}
/* parse the user address, which may yield more path information */
form = parse_address(user, &target, &remainder, (int *)0);
if (form == FAIL) {
/*
* failed to parse user, store an error for logging later
*/
write_log(WRITE_LOG_MLOG, "Xnotice: <> Error parsing sender address <%s>: %s", user, remainder);
write_log(WRITE_LOG_PANIC, "Error parsing sender address <%s>: %s", user, remainder);
sender = COPY_STRING("Postmaster");
} else if (form == LOCAL && path[0] == '\0' && !islocal) {
/*
* message was flagged as non-local, but we no longer have any
* host information, send it to the postmaster
*/
write_log(WRITE_LOG_MLOG, "Xnotice: <> Path to sender unknown: sender = %s", user);
write_log(WRITE_LOG_PANIC, "Path to sender unknown: sender = %s", user);
sender = COPY_STRING("Postmaster");
} else if (path[0] == '\0' && form == LOCAL) {
/*
* simple case: no path, simple user, just make a copy of user
*/
sender = COPY_STRING(remainder);
} else if (path[0] == '\0' && form == UUCP_ROUTE) {
/*
* user is in !-route form, so put the user back together
*/
sender = xprintf("%s!%s", target, remainder);
} else if (form == LOCAL) {
/*
* simple user form, add it to path
*/
sender = xprintf("%s!%s", path, remainder);
} else if (form == UUCP_ROUTE) {
/*
* user is in !-route form, put it back together and add to path
*/
sender = xprintf("%s!%s!%s", path, target, remainder);
} else {
/*
* the difficult case, need to construct a !-route for user
*/
char *error; /* store error here */
char *route = build_uucp_route(remainder, &error, 0);
if (route == NULL) {
/* found an error building the route */
write_log(WRITE_LOG_MLOG, "Xnotice: <> Error building sender: %s", error);
write_log(WRITE_LOG_PANIC, "Error building sender: %s", error);
} else if (path[0] == '\0') {
/*
* no path, just put the user back together
*/
sender = xprintf("%s!%s", target, route);
} else {
/*
* put user back together and add to path
*/
sender = xprintf("%s!%s!%s", path, target, route);
}
}
}
/*
* write_body - write the message body to a stdio FILE pointer.
*
* the body of the message will be sent to the given file. Pass
* the the transport flags to send_spool in writing.
*
* return SUCCEED, WRITE_FAIL or READ_FAIL.
*/
int
write_body(f, flags)
FILE *f; /* write to this file */
unsigned long flags; /* transport flags */
{
if (seek_spool(start_msg_body) == FAIL) {
write_log(WRITE_LOG_SYS, "seek failed: %s/%s: %s",
spool_dir, input_spool_fn, strerror(errno));
return READ_FAIL;
}
return send_spool(f, flags);
}
/*
* check_grade - set grade for message to value from Precedence: field
*
* If a Precedence: field is specified for a message and this grade
* is not the message's current grade, then change the grade for the
* message, which will involve moving the file to a new name.
*/
void
check_grade()
{
charplist_t *hq; /* temp for scanning headers */
/* grab the current grade */
msg_grade = message_id[strlen(message_id) - 1];
/* find the precedence field (or not) */
for (hq = header; hq; hq = hq->succ) {
if (HDREQ("precedence", hq->text)) {
/* found the precedence header */
int grade = parse_precedence(strchr(hq->text, ':') + 1);
if (grade && msg_grade != grade) {
/*
* change the grade to the new grade, if we fail to
* change the grade, don't sweat it too badly.
*/
(void) new_grade(grade);
msg_grade = grade;
}
break;
}
}
}
/*
* log_incoming - put a message in the log file about the incoming message
*
* look for a message-id header field and log it, if it exists, otherwise
* announce new mail without a previous message-id.
*/
void
log_incoming()
{
charplist_t *hq; /* temp for scanning headers */
char *old_id = NULL; /* previous message-id */
char *trim_old_id = NULL; /* trimmed previous message-id */
char *resent_mid = NULL; /* point to Resent-Message-Id field */
char *mid = NULL; /* point to Message-Id field */
char *host_string = NULL; /* combined host name and inet addr */
time_t now;
unsigned long int delay;
for (hq = header; hq; hq = hq->succ) {
if (HDREQ("resent-message-id", hq->text)) {
resent_mid = hq->text;
break;
}
if (HDREQ("message-id", hq->text)) {
mid = hq->text;
}
}
if (resent_mid) {
old_id = strchr(resent_mid, ':');
} else if (mid) {
old_id = strchr(mid, ':');
}
if (sender_host) {
char *sender_really_different = NULL;
char *sender_host_not_literal = NULL;
if (sender_host_really) {
if (strcmpic(sender_host, sender_host_really) != 0) {
sender_really_different = sender_host_really;
}
}
if (!sender_host_addr || (strcmpic(sender_host, sender_host_addr) != 0)) {
sender_host_not_literal = sender_host;
}
host_string = xprintf("%s%s%s%s%s%s%s",
sender_host_not_literal ? sender_host_not_literal : "",
sender_really_different ? "(" : "",
sender_really_different ? sender_really_different : "",
sender_really_different ? ")" : "",
sender_host_addr ? "[" : "",
sender_host_addr ? sender_host_addr : "",
sender_host_addr ? "]" : "");
} else if (sender_host_addr) {
host_string = xprintf("[%s]", sender_host_addr);
}
if (old_id) {
register char *p;
trim_old_id = xmalloc((size_t) strlen(old_id));
for (p = trim_old_id, old_id++; *old_id; old_id++) {
if (!isspace((int) *old_id)) {
*p++ = *old_id;
}
}
*p = '\0';
}
now = time((time_t *) NULL);
if (now >= message_date()) {
delay = now - message_date();
} else {
DEBUG2(DBG_NOTIFY_LO, "log_incoming(): is the clock running backwards? now:%lu < message_date:%lu\n",
(unsigned long int) now, message_date());
delay = 1;
}
#if (SMAIL_LOG_STYLE == 2)
/* Default new log style (as of 3.1.29) */
/* This is SMAIL_LOG_STYLE=2 */
write_log(WRITE_LOG_SYS, "Received FROM:%s%s%s%s%s PROGRAM:%s%s%s SIZE:%lu%s%s%s%s DELAY:%lu",
sender,
host_string ? " HOST:" : "",
host_string ? host_string : "",
sender_proto ? " PROTOCOL:" : "",
sender_proto ? sender_proto: "",
program,
trim_old_id ? " ORIG-ID:" : "",
trim_old_id ? trim_old_id : "",
(unsigned long) msg_size,
ident_sender ? " IDENT:" : "",
ident_sender ? ident_sender : "",
ident_method ? " ID-METHOD:" : "",
ident_method ? ident_method : "",
delay);
#else
# if (!defined(SMAIL_LOG_STYLE)) || (SMAIL_LOG_STYLE == 1)
/* Old log style (as used by 3.1.28) */
/* This is SMAIL_LOG_STYLE=1 */
write_log(WRITE_LOG_SYS, "received%s%s%s%s%s%s%s%s%s%s%s%lu%s%s%s%s%s%s%lu%s",
"\n|\t from: ",
sender,
host_string ? "\n|\t host: " : "",
host_string ? host_string : "",
sender_proto ? "\n|\t protocol: " : "",
sender_proto ? sender_proto : "",
"\n|\t program: ",
program,
trim_old_id ? "\n|\t orig-id: " : "",
trim_old_id ? trim_old_id : "",
"\n|\t size: ",
(unsigned long) msg_size,
" bytes",
ident_sender ? "\n|\t ident: " : "",
ident_sender ? ident_sender : "",
ident_method ? "\n|\t ident-by: " : "",
ident_method ? ident_method : "",
"\n|\tarr-delay: ",
(unsigned long) delay,
" seconds");
# else
# include "ERROR - SMAIL_LOG_STYLE set to invalid value."
# endif
#endif
if (host_string) {
xfree(host_string);
}
if (trim_old_id) {
xfree(trim_old_id);
}
}
/*
* scan_spool_dirs - scan for work and return spool files in sorted order
*
* look through all of the spool directories, and sort by precedence and
* by creation date.
*
* the returned filename vector contains data which may be reused on
* subsequent calls to scan_spool_dirs().
*/
char **
scan_spool_dirs(frozen)
int frozen; /* look for frozen messages instead? */
{
static char **mv = NULL; /* vector of messages */
size_t mc = 0; /* count of messages */
static unsigned int mv_size = 32; /* allocated vector entries */
char *dn; /* current directory to scan */
static struct str str; /* storage for filenames */
register size_t i; /* temp index */
/* get the initial vector and string storage allocation */
if (mv == NULL) {
mv = (char **) xmalloc((size_t) mv_size * sizeof(*mv));
STR_INIT(&str);
} else {
STR_CHECK(&str);
STR_CLEAR(&str);
}
/* loop through all spool directories looking for work */
for (dn = strcolon(spool_dirs); dn; dn = strcolon((char *)NULL)) {
char *fn; /* filename from directory */
char *in_dn = xprintf(frozen ? "%s/error" : "%s/input", dn);
DEBUG1(DBG_QUEUE_HI, "scan_spool_dirs() looking in %s\n", in_dn);
for (fn = scan_dir(in_dn); fn; fn = scan_dir((char *)NULL)) {
int entry_grade;
/* see if the file matches the form of a spool file */
if (! isdigit((int) fn[0]) || strlen(fn) != SPOOL_FN_LEN) {
DEBUG1(DBG_QUEUE_MID, "scan_spool_dirs() ignoring %s\n", fn);
continue; /* nope */
}
/*
* If we are scanning the queue BUT recipients is also set, then we
* use the contents of recipients to select out required messages
* to process. Yes this is a bit of a bass-ackwards algorithm for
* this case, but....
*/
if (parameters) {
struct addr * ptr;
char * msgid;
int seen = FALSE;
for (ptr = parameters; (ptr != NULL); ptr = ptr->succ) {
msgid = ptr->in_addr;
if (*msgid == 'm') /* Skip leading m if specified */
msgid++;
if (strchr(msgid, '/')) /* use basename if path specified */
msgid = strrchr(msgid, '/') + 1;
if ((seen = EQ(fn, msgid)))
break;
}
if (!seen) {
DEBUG1(DBG_QUEUE_HI, "scan_spool_dirs() skipping %s (not in cmd-line list)\n", fn);
continue;
}
}
/* See if the grade is within the range being processed */
/* But do not do this if printing the queue */
if (operation_mode != PRINT_QUEUE) {
entry_grade = fn[strlen(fn) - 1];
if ((entry_grade < min_runq_grade) || (entry_grade > max_runq_grade)) {
DEBUG4(DBG_QUEUE_HI, "scan_spool_dirs() skipping %s (grade '%c' not in runq range '%c'-'%c')\n",
fn, entry_grade, min_runq_grade, max_runq_grade);
continue; /* outside grade range */
}
}
/* add to the list of files */
if (mc >= mv_size - 1) {
/* note: we allow, in advance, for the last NULL entry */
mv_size += 32; /* get more entries */
mv = (char **) xrealloc((char *) mv, (size_t) (mv_size * sizeof(*mv)));
}
/* use offsets for now, to be converted to (char *) later */
set_ptr(mv[mc++], STR_LEN(&str));
str_printf(&str, "%s/%s%N", in_dn, fn); /* %N is nul byte */
}
xfree(in_dn);
}
/* close off the vectors */
mv[mc] = NULL;
/* convert the offsets in mv to (char *)'s */
for (i = 0; i < mc; i++) {
mv[i] = STR(&str) + get_ptr(mv[i]);
}
/* sort it */
(void) qsort((char *)mv, mc, sizeof(*mv), queue_compare);
return mv;
}
/*
* queue_compare - compare two filenames which represent queue files
*/
static int
queue_compare(x, y)
const void *x;
const void *y;
{
const char *a = *((const char * const *) x);
const char *b = *((const char * const *) y);
int a_grade = a[strlen(a) - 1];
int b_grade = b[strlen(b) - 1];
if (a_grade != b_grade) {
return a_grade - b_grade;
}
return strcmpic(strrchr(a, '/'), strrchr(b, '/'));
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1