/* #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 #include /* for MAXLOGNAME if avail... */ #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef __STDC__ # include #else # include #endif #if defined(HAVE_UNISTD_H) # include #endif #include #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 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 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 , 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 "." (bare line * feeds, without carriage returns) MUST NOT be treated as equivalent to * . as the end of mail data indication. * * The authors of the above clearly consider worry of message truncation when * raw message data contains the "." 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 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 terminates a line * * Note we do violate RFC 2822 section 4.1.1.4 by allowing this * lone 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.\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 <.>).\n"); } else { DEBUG(DBG_QUEUE_LO, "Got '.'.\n"); } return put_success; } /* (also) terminates a line */ if (c == '\r') { /* look to see what follows the ... */ 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; } /* if it's 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.\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 '.').\n"); } else { DEBUG(DBG_QUEUE_LO, "Got '.'.\n"); } return put_success; } /* * if the . is not followed by a newline then: * * if doing DOT_ENDS_MESSAGE then write out both the dot * and the (dot was followed by , but not , * so it's not alone on a line by itself); * * else if doing either SMTP_DOTS or HIDDEN_DOTS then * write out just the (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 or * * Note once again we violate RFC 2822 section 4.1.1.4 by allowing * a lone 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 after a ... */ for (;;) { errno = 0; if ((c = getc(f)) == EOF) { if (errno == EINTR) { continue; } DEBUG(DBG_QUEUE_LO, "ERROR: Got EOF after in a line.\n"); goto read_eof; } break; } if (c != '\n') { /* NOT the end of the line -- stuff out the bare ... */ 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 ... */ 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: */