*** smap.c.2.1 Fri May 22 13:31:00 1998 --- smap.c Tue Jun 9 14:21:39 1998 *************** *** 1,17 **** ! /* * Copyright (c) 1993, 1997 Trusted Information Systems, Incorporated * All rights reserved. * * Redistribution and use are governed by the terms detailed in the * license document ("LICENSE") included with the toolkit. ! */ ! ! /* * Author: Marcus J. Ranum, Trusted Information Systems, Inc. ! */ static char RcsId[] = "$Header: /home/rmurphy/fwtk/fwtk/smap/RCS/smap.c,v 1.22 1998/01/13 22:56:06 rmurphy Exp $"; - #include #include #include --- 1,487 ---- ! /*********************************************************************\ ! ** ! ** smap - sendmail "wrapper". ! ** * Copyright (c) 1993, 1997 Trusted Information Systems, Incorporated * All rights reserved. * * Redistribution and use are governed by the terms detailed in the * license document ("LICENSE") included with the toolkit. ! ** ! ** Syntax: ! ** smap [ -daemon ] ! ** ! ** Description: ! ** ! ** First, the original author's description: ! ** the object of this program is to allow us to present an SMTP ! ** service for people to talk to, which is unprivileged, and runs ! ** in a chrooted directory. a secondary requirement is that the ! ** code be as simple as possible, to permit manual review. this ! ** code, therefore, contains no comments other than this one - ! ** comments being an indication that code is too complex to be ! ** trusted. ! ** ! ** files created in spool directory are locked and given mode 644. ! ** when smap is done gathering them, they are unlocked and given ! ** mode 755. smapd checks the mode and locking status to ensure ! ** that it's not dealing with a partial file. unlocked files with ! ** mode 644 are by definition partial. ! ** ! ** mjr. 1993 ! ** ! ** Annotation on this: ! ** This code was complex to begin with, and has grown much more ! ** complex. There being in real life no such thing as self- ! ** documenting code, this header comment tries to describe how ! ** this program works. ! ** ! ** The program starts either from 'inetd' or as a daemon. In the ! ** former case, the standard input and output are connected to a ! ** socket that 'inetd' has set up already, to allow commmunication ! ** with the remote process that wants to send mail. In the latter ! ** case, which is invoked by 'smap -daemon', the program opens the ! ** SMTP port itself, via an Internet socket, and waits for ! ** connections to be made. When a connection is made, a child ! ** copy is forked, which then closes its stdin, stdout, and ! ** stderr, and dup(2)s them from the socket. ! ** ! ** Before starting in on the e-mail, the program reads ! ** configuration modifiers from the configuration file, which by ! ** default is "/usr/local/etc/netperm-table". These are checked ! ** and, if valid, stored away for later use. The configuration ! ** modifiers are described below. ! ** ! ** If a timeout has been requested, then it is set. If a chroot ! ** directory has been specified, it is changed to and chrooted to. ! ** If a UID and GID have been specified, the process changes to ! ** use them. ! ** ! ** Once this is set up, the remote host's messages are read a line ! ** at a time. Lines are read by crlfgets(), and as the name ! ** implies, are supposed to be terminated by a CR+LF. There are ! ** systems that have terminated lines with just a LF, as Unix ! ** normally does; and there is a bug fix installed which handles ! ** this. ! ** ! ** The following SMTP commands are handled: ! ** HELO MAIL RCPT DATA RSET ! ** NOOP QUIT HELP VRFY EXPN ! ** In addition, it accepts but does nothing for: ! ** VERB ONEX DEBUG ! ** It does not accept EHLO, but does not log it as an error. ! ** All other attempts at commands are logged as errors. ! ** ! ** Normal mail messages will consist of: ! ** MAIL FROM: ! ** RCPT TO: ! ** [repeated as many times as necessary] ! ** DATA ! ** ... ! ** . [ends with a period on a line] ! ** If these don't occur in this order, this program complains and ! ** may exit. Otherwise, it creates a file to contain the message. ! ** The first lines will be "FROM" followed by the alleged sender; ! ** then "TO" followed by the recipient [one line per recipient]; ! ** then "BODY"; and then the body of the message, starting with a ! ** "Received: " line generated by this program, followed by the ! ** lines between the "DATA" field and the period on a line by ! ** itself. ! ** ! ** Normally, the input file will have the name "xmaXXXXXX", where ! ** XXXXXX is replaced by the PID or something else to make it a ! ** unique file name. When the input is done, the file is renamed ! ** to "smaXXXXXX", so that 'smapd' will see it and process it. ! ** ! ** This may be modified by some of the security options described ! ** below. ! ** ! ** The RSET command clears and re-opens the input file and clears ! ** the memory of any MAIL or RCPT commands that have been given. ! ** ! ** The HELP command returns the list of accepted commands (the ! ** first ten above). ! ** ! ** The commands HELO, NOOP, VRFY, EXPN, VERB, ONEX, and DEBUG are ! ** treated essentially as no-ops. HELO, if given a system name, ! ** will ditch whatever is stored in the char * variable 'rhost' ! ** and store the new system name there. [Since this variable is ! ** never used, this is a no-op.] VRFY and EXPN have been changed ! ** in 2.1 to echo any argument. The rest, except for EHLO, just ! ** respond in some random way. ! ** ! ** jsdy CRITIQUE: I don't like the change in VRFY and EXPN, ! ** because it doesn't "say what you mean", and because it may ! ** encourage cracker kiddies to keep boring in; but it's now 2.1 ! ** "standard behavior." ! ** ! ** RFC 821 return codes include: ! ** ! ** 500 Syntax error, command unrecognized ! ** [This may include errors such as command line too long] ! ** 501 Syntax error in parameters or arguments ! ** 502 Command not implemented ! ** 503 Bad sequence of commands ! ** 504 Command parameter not implemented ! ** ! ** 211 System status, or system help reply ! ** 214 Help message [Information on how to use the receiver or ! ** the meaning of a particular non-standard command; this ! ** reply is useful only to the human user] ! ** ! ** 220 Service ready ! ** 221 Service closing transmission channel ! ** 421 Service not available, closing transmission ! ** channel [This may be a reply to any command if the ! ** service knows it must shut down] ! ** ! ** 250 Requested mail action okay, completed ! ** 251 User not local; will forward to ! ** 450 Requested mail action not taken: mailbox unavailable ! ** [E.g., mailbox busy] ! ** 550 Requested action not taken: mailbox unavailable ! ** [E.g., mailbox not found, no access] ! ** 451 Requested action aborted: error in processing ! ** 551 User not local; please try ! ** 452 Requested action not taken: insufficient system storage ! ** 552 Requested mail action aborted: exceeded storage allocation ! ** 553 Requested action not taken: mailbox name not allowed ! ** [E.g., mailbox syntax incorrect] ! ** 354 Start mail input; end with . ! ** 554 Transaction failed ! ** ! ** Replies allowed by RFC 821 for each command are as follows. No ! ** other replies should be used. S == Success, F == Failure, ! ** E == Error, and I == Intermediate. The 421 reply (service not ! ** available, closing transmission channel) may be given to any ! ** command if the program knows it must shut down. ! ** ! ** CONNECTION ESTABLISHMENT ! ** S: 220; F: 421 ! ** HELO ! ** S: 250; E: 500, 501, 504, 421 ! ** MAIL ! ** S: 250; F: 552, 451, 452; E: 500, 501, 421 ! ** RCPT ! ** S: 250, 251; F: 550, 551, 552, 553, 450, 451, 452; ! ** E: 500, 501, 503, 421 ! ** DATA ! ** I: 354 -> data -> (S: 250; F: 552, 554, 451, 452); ! ** F: 451, 554; E: 500, 501, 503, 421 ! ** RSET ! ** S: 250; E: 500, 501, 504, 421 ! ** VRFY ! ** S: 250, 251; F: 550, 551, 553; ! ** E: 500, 501, 502, 504, 421 ! ** EXPN ! ** S: 250; F: 550; E: 500, 501, 502, 504, 421 ! ** HELP ! ** S: 211, 214; E: 500, 501, 502, 504, 421 ! ** NOOP ! ** S: 250; E: 500, 421 ! ** QUIT ! ** S: 221; E: 500 ! ** ! ** Configuration modifiers: ! ** ! ** The following modifiers may appear after "smap:" in the FWTK ! ** configuration file, usually at "/usr/local/etc/netperm-table". ! ** If the modifier is preceded by a "deny-", the PERM_DENY flag is ! ** set for that line. If the modifier is preceded by a "permit-", ! ** the PERM_ALLOW flag is set for that line. ! ** ! ** Any other modifiers will simply be ignored. ! ** ! ** Original TIS FWTK 2.1 options: ! ** ! ** directory ! ** "smap: directory /directory-name" designates the name of ! ** the directory to which the program should be chrooted. ! ** groupid ! ** "smap: groupid GID" gives the group ID as which the ! ** program should be running. The GID may be expressed as ! ** either a group name or as a number. ! ** maxbytes ! ** "smap: maxbytes ####", with a numeric argument, sets the ! ** maximum number of bytes allowed in the body of a ! ** message. ! ** maxrecip ! ** "smap: maxrecip ####", with a numeric argument, sets the ! ** maximum number of recipients allowed per message. ! ** timeout ! ** "smap: timeout ####", with a numeric argument, sets the ! ** maximum time (in seconds) to wait between reading one ! ** line from the network socket and reading the next line. ! ** If it takes longer than that number of seconds, the ! ** program will exit. ! ** userid ! ** "smap: userid UID" sets the user ID as which the program ! ** should be running. The UID may be expressed either as ! ** a user name or as a number. ! ** ! ** Add-Ons: ! ** ! ** broken-from ! ** "smap: broken-from address ..." designates "from" ! ** addresses that will always be allowed to send mail, ! ** and not have other checks applied to them. ! ** check-from-address ! ** "smap: check-from-address 1" causes the program to ! ** perform some extra checking to see whether the "from" ! ** address is OK. ! ** check-user-too ! ** "smap: check-user-too 1" causes the checking on the ! ** "from" address to check whether the full "from" address ! ** is specified in a "smap: spam ..." line, instead of ! ** just the domain. ! ** domains ! ** "smap: domains ..." specifies the DNS domains which are ! ** accepted as internal to the firewall. If you accept ! ** names in both @domain fashion and @host.domain fashion, ! ** you should probably list both "domain" and "*.domain". ! ** hosts ! ** "smap: hosts ..." specifies the mail hosts that are to ! ** be considered "internal". These hosts may send mail to ! ** anybody. Wildcards may be used. ! ** max-dirent ! ** "smap: max-dirent ####", with a numeric argument, ! ** specifies the maximum number of messages that may be ! ** stored in the "secure" directory. ! ** If the program has examined the "From: " address and ! ** decided that it's going to block it, and it is storing ! ** a certain number of bytes of each message in files in ! ** the "secure" directory, then this parameter gives the ! ** maximum number of messages that may be stored in this ! ** "secure" directory. ! ** max-email ! ** If, while checking out the "From: " address, the ! ** program decides that it doesn't want to accept this ! ** e-mail, then this parameter controls whether and how it ! ** stores the message for the security administrator. ! ** If this paramter is <= 0, then once thei program has ! ** seen a recipient [RCPT], it doesn't bother to store ! ** anything, but gives an error message and exits. ! ** If this parameter > 0, then this program waits until ! ** the DATA statement is seen. It then reads in only this ! ** many bytes of the message, storing it in a special ! ** "secure" directory, and then sends its error message. ! ** require-full-email ! ** "smap: require-full-email 1" causes this program to ! ** verify that hosts that are NOT local have an @host part, ! ** while checking the "from" address. ! ** scrub-spam ! ** "smap: scrub-spam" causes the program to initially ! ** check whether the "real" calling host is one of a set ! ** of "spam hosts". Later, if checking the passed from ! ** address, this causes the program to check the from ! ** address against a set of "spam domains". Both "spam ! ** hosts" and "spam domains" are specified under ! ** "smap: spam ..." in the configuration file. ! ** spam ! ** check_spamhost() and check_spamdomain() look for ! ** "smap: spam ..." lines containing host and domain ! ** names, respectively. ! ** spam-block ! ** "smap: spam-block deny sender-address recipient-address" ! ** will refuse all mail that comes from the sender to the ! ** recipient. Sender and recipient addresses in this line ! ** may have '*' wildcards. ! ** unknown-host ! ** "smap: unknown-host 1" allowing us to accept ! ** connections from a host whom we can't name [via ! ** peername()]. ! ** ! ** "Mandatory" Options ! ** ! ** All smap installations must have a directory, local hosts or ! ** domains ("hosts") from which mail is considered "local" and ! ** thus not relayed, and local hosts or domains ("domains") to ! ** which mail is considered local and therefore not relayed. ! ** ! ** It is STRONGLY RECOMMENDED that you have a userid and groupid. ! ** I (jsdy) have a maxbytes, and find it useful. ! ** ! ** The anti-spam patches need to be consolidated and reduced. ! ** ! ** Author: ! ** * Author: Marcus J. Ranum, Trusted Information Systems, Inc. ! ** ! ** Modifications: ! ** ! * Modified: 09 October 1996, Bruce R. Ellis ! * prevent remote machines from sending mail to other ! * remote machines ! * ! * Modified: 10 October 1996, Craig I. Hagan (hagan@cih.com) ! * changed how local host determination works, moved ! * configuration from code to netperm-table option. ! * ! * Provides Sender Host/RCPT verification to insure that origin ! * or destination is within local domain. If Sender Host is non- ! * local, connection is dropped if and when a non-local RCPT is ! * entered. ! * ! * Unimplemented commands now return appropriate message instead ! * on an incorrect answer - particularly EXPN and VRFY no longer ! * return "User unknown". ! * ! * HELO uses the resolver info rather than using the string sent ! * with the HELO command. ! ** ! ** ! * Modified: 14 February 1997, Ulrich Eckhardt ! * Added code to fetch some bytes of the E-Mail message if the domain ! * of the sender is invalid. Also added an option to allow an E-Mail ! * to pass smap, even if the domain is invalid. ! * Added the following configuration options to netperm-table ! * ! * max-email : How many bytes sould be saved from an invalid sender. ! * max-dirent : How many files sould be saved at maximum. ! * broken-from : Allows the sender address, even if domain is invalid. ! * ! * Modified 13 March 1997, Ulrich Eckhardt ! * Added code to send a replycode of 554 to some hosts, where spam - email ! * is coming from, because some spammers use our providers mail host as ! * a relay. ! * ! * I added therefore a new option to the configuration file : ! * kill-mail : Send to all hosts in this list a 554 error message instaed ! * 4xx ! * ! * I also changed the intro message a litle bit. If you dont like it, change line ! * 412. ! ** ! ** Modified: 14-apr 1997, Craig I. Hagan ! * Added code to clean by email addresses (user@site), re-used domain engine, ! * previously a domain of @user@domain would also have worked. ! * fixed a silly coredump on a syslog before an exit(1) ! ** ! ** Modified: 29 May 1997, Joseph S. D. Yao ! ** Re-ordered some things to better fit the original model of how ! ** this program worked. Added this header comment to actually ! ** DOCUMENT the original program and all of these changes. Fixed ! ** some little errors. Made return codes RFC821-compliant, ! ** ripping out all the made-up return codes. ! ** ! ** Last modified 06/09/98 14:21:36. Last retrieved 06/09/98 14:21:39. ! ** ! ** Compile conditionals: ! ** LOG_DAEMON If this is defined, the program is compiled to ! ** pass flags to a syslog daemon on openlog(). ! ** NO_GETSERVENT This must be defined if your system has no ! ** working getservent(3) routines. ! ** PROTECTINBOUND If this is defined, the from/to address pairs ! ** get one more security check against pairs defined by the ! ** "spam-block" configuration option. ! ** SO_KEEPALIVE If this socket option exists, it is set, to ! ** keep the socket from automatically timing out. ! ** SPECIALDOMAIN If this is defined, the "to" address ALWAYS ! ** has checkvalid() called on it, to make sure it's in the "local" ! ** domain(s), instead of just when the from address does not ! ** appear to be "local". ! ** SYSV Used to check some s5/BSD differences, such as ! ** strchr()/index() and wait3() vs. waitpid(). ! ** WHITEHOUSE_GOV Used by Hagan to comment out some of the very ! ** "whitehouse.gov"-specific code. ! ** ! ** Routines: ! ** #define safestring(s) (((s)==NULL)?"NULL":(s)) ! ** extern char *strtok(); ! ** static FILE *smapopen(int stop); ! ** static int crlfgets(); ! ** static void add_file_list(); ! ** static void smap_exit(); ! ** static void waitwaitwait(); ! ** static int InCheckList(char *str,Cfg *cfp, char *list); ! ** static int CheckDirEntry(Cfg *cfp); ! ** static int from_address_ok(char *ruser, Cfg *cfp, char *domain); ! ** static void ringring() ! ** main(ac,av) ! ** static int from_address_ok(char *ruser, Cfg *cfp, char *domain) ! ** static int CheckDirEntry(Cfg *cfp) ! ** static int chkstr(char *teststr, char *str) ! ** static int InCheckList(char *str,Cfg *cfp,char *list) ! ** static FILE * smapopen(int stop) ! ** crunchbody(in,out) ! ** static int crlfgets(buf,siz,fp) ! ** filesize(fd) ! ** static void add_file_list (tmpf) ! ** static void smap_exit (code) ! ** static void waitwaitwait() ! #ifdef SYSV ! ** #define index strchr ! ** #define rindex strrchr ! # else ! ** extern char *index(); ! ** extern char *rindex(); ! #endif ! ** extern char *strpbrk(); ! ** checkvalid(r,cfp) ! ** check_spamhost(Cfg *cfp) ! ** check_spamdomain(const char *domain, Cfg *cfp) ! ** check_hostname(Cfg *cfp) ! ** check_domain(char *atp,Cfg *cfp) ! #ifdef PROTECTINBOUND ! ** int check_protected_internal(sender, recip, cfp) ! #endif (*PROTECTINBOUND*) ! ** ! ** Declared: ! ** extern char *optarg; ! ** struct towho { char *who; struct towho *nxt; }; ! ** static struct towho *recip = (struct towho *)0; ! ** static char *ruser = (char *)0; ! ** static char *rhost = (char *)0; ! ** static char rladdr[512]; ! ** static char riaddr[512]; ! ** static char *rootdir = (char *)0; ! ** static int runuid = -1; ! ** static int rungid = -1; ! ** static char *tempfile = (char *)0; ! ** static int maxbytes = -1; ! ** static int maxrecip = -1; ! ** static int currecip = 0; ! ** static int timeout = PROXY_TIMEOUT; ! ** static int livetim = 0; ! ** static int msgsize = 0; ! ** static int failmsgsize = 0; ! ** static int maxdirent = 0; ! ** struct file_list { struct file_list *next; char *tmpfile; }; ! ** struct file_list *filelist = (struct file_list *) 0; ! ** static int check_user_too = FALSE; ! ** static int get_mail_ok = FALSE; ! ** static int from_host_local = FALSE; ! ** static int scrub_spam = FALSE; ! ** static int spam_kill_rej = 552; ! ** static int spam_rej = 451; ! ** static int spam_badaddr = 501; ! ** static int spam_termination = 554; ! ** static int check_from_address = FALSE; ! ** static int require_full_email = FALSE; ! ** static int unknown_host_ok = FALSE; ! ** static char queuedir[] = ""; ! ** static char queuefile[] = "xmaXXXXXX"; ! ** static char q_lock_char = 'x'; ! ** static char q_send_char = 's'; ! ** static char securedir[] = "secure"; ! ** static char securefile[] = "secXXXXXX"; ! ** static char defined_fwtk_version[] = FWTK_VERSION_MINOR; ! ** static char minimal_fwtk_version[] = "2.1"; ! ** static char version_addon[] = "+anti-relay+anti-spam"; ! ** char *myname = "smap"; ! #ifdef WHITEHOUSE_GOV ! ** char *bad = "551 Recipient must be in form of: user, user@eop.gov, or user@whitehouse.gov\r\n"; ! # else ! ** char *bad = "551 Recipient must be in the local domain(s).\r\n"; ! #endif(*WHITEHOUSE_GOV*) ! ** ! \*********************************************************************/ ! ! #ifndef lint static char RcsId[] = "$Header: /home/rmurphy/fwtk/fwtk/smap/RCS/smap.c,v 1.22 1998/01/13 22:56:06 rmurphy Exp $"; + static char SCCS_id[] = "@(#)smap.c 1.26 06/09/98"; + #endif/*lint*/ #include #include #include *************** *** 25,30 **** --- 495,506 ---- #include #include #include + #include + #include + #include + #include + #include + #include #include "firewall.h" *************** *** 47,52 **** --- 523,561 ---- mjr. 1993 */ + #define NUL '\0' + #define NL '\n' + #define CR '\r' + #define DIRC '/' + #define FLAGC '-' + #define MATCHC '*' + + #ifndef PROXY_TIMEOUT + #define PROXY_TIMEOUT (60 * 60 * 2) /* 2 hours */ + #endif/*PROXY_TIMEOUT*/ + + /* + ** The umask() for creating the input file. + ** Disallow execute permission (owner) + + ** read, write, execute permission (group) + + ** read, write, execute permission (other) + */ + #define INFILE_UMASK (S_IXUSR | S_IRWXG | S_IRWXO) + + #define TRUE (1) + #define FALSE (0) + + #define SMTP_PORT 25 + + #define safestring(s) (((s)==NULL)?"NULL":(s)) + + #ifndef NO_GETSERVENT + + static char smtp_name[] = "smtp"; + static char smtp_proto[] = "tcp"; + + #endif/*NO_GETSERVENT*/ + extern char *strtok(); extern char *optarg; *************** *** 68,90 **** static char *tempfile = (char *)0; static int maxbytes = -1; static int maxrecip = -1; - static int curbytes = 0; static int currecip = 0; static int timeout = PROXY_TIMEOUT; static int livetim = 0; static int msgsize = 0; ! static FILE *smapopen(); static int crlfgets(); static void add_file_list(); static void smap_exit(); static void waitwaitwait(); struct file_list { struct file_list *next; char *tmpfile; ! } *filelist = (struct file_list *) 0; static void ringring() { --- 577,677 ---- static char *tempfile = (char *)0; static int maxbytes = -1; static int maxrecip = -1; static int currecip = 0; static int timeout = PROXY_TIMEOUT; static int livetim = 0; static int msgsize = 0; + static int failmsgsize = 0; /* Maximum size of message when authorization failed */ + static int maxdirent = 0; /* Maximum number of files in the security directory */ ! ! static FILE *smapopen(int stop); static int crlfgets(); static void add_file_list(); static void smap_exit(); static void waitwaitwait(); + static int InCheckList(char *str,Cfg *cfp, char *list); + static int CheckDirEntry(Cfg *cfp); + static int from_address_ok(char *ruser, Cfg *cfp, char *domain); struct file_list { struct file_list *next; char *tmpfile; ! }; ! struct file_list *filelist = (struct file_list *) 0; + /* cih 97/04/14 */ + + /* do we check for user@domain as well as domain? */ + + static int check_user_too = FALSE; /* Boolean */ + + /* bre 96/10/09 */ + + static int get_mail_ok = FALSE; /* Boolean */ + static int from_host_local = FALSE; /* Boolean */ + + static int scrub_spam = FALSE; /* Boolean */ + + static int spam_kill_rej = 552; + static int spam_rej = 451; + static int spam_badaddr = 501; + static int spam_termination = 554; + + #define LYING_ERRNO spam_badaddr + #define NOTLOCAL_ERRNO spam_badaddr + + + /* bre 96/10/09 */ + + /* cih 96/10/25 */ + + static int check_from_address = FALSE; /* Boolean */ + static int require_full_email = FALSE; /* Boolean */ + + /* "smap: unknown-host 1" has us accept an unknown host. */ + static int unknown_host_ok = FALSE; + + /* + ** If the file is to be sent, it is put in directory "queuedir", named + ** "queuefile", and initially starts with 'q_lock_char'. It is moved + ** to start with 'q_send_char' when it's ready for the next daemon to + ** pick it up. + ** If the file is just to be stored for examination, it is put in + ** directory "securedir", named "securefile". + */ + static char queuedir[] = ""; + static char queuefile[] = "xmaXXXXXX"; + static char q_lock_char = 'x'; + static char q_send_char = 's'; + static char securedir[] = "secure"; + static char securefile[] = "secXXXXXX"; + + /* Minimal version of FWTK */ + static char defined_fwtk_version[] = FWTK_VERSION_MINOR; + static char minimal_fwtk_version[] = "2.1"; + static char version_addon[] = "+anti-relay+anti-spam"; + + char *myname = "smap"; + + /* + ** This function is called on an alarm signal, indicating that a + ** timeout has ben requested and has been reached. The variable + ** 'livetim' is set to 1 every time that a line is read. So, the + ** timeout action will only happen if the function has NEVER read a + ** line, or if it has been "timeout" seconds since the last call, and + ** no lines have been read. + ** + ** jsdy CRITIQUE: This function is called every 'timeout' seconds. If + ** even one line has been read, at the very beginning of the previous + ** 'timeout' period, it will wait another 'timeout' seconds to be + ** called again. I am not changing this, because this is the + ** "standard" behavior for 'smap' now. However, this is simple enough + ** that I would have expected to see an alarm(timeout) after every line + ** read, instead of setting a variable. That way, the program actually + ** would time out 'timeout' seconds after the last line was read, + ** instead of after somewhere between 'timeout' and 2*'timeout' seconds. + */ static void ringring() { *************** *** 108,133 **** char buf[BUFSIZ * 4]; char myhostname[512]; char *p; - int x; int opt; FILE *vout = (FILE *)0; int gotdata = 0; struct hostent *hp; #ifndef LOG_DAEMON ! openlog("smap",LOG_PID); #else ! openlog("smap",LOG_PID|LOG_NDELAY,LFAC); #endif ! if (ac > 1 && !strcmp(av[1], "-daemon")) { int sock, sockl; struct sockaddr_in sa; int pid; ! sa.sin_family = AF_INET; bzero((char *)&sa.sin_addr, sizeof(sa.sin_addr)); ! sa.sin_port = htons(25); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { syslog(LLEV, "fwtksyserr: Failed to create socket: %m"); --- 695,792 ---- char buf[BUFSIZ * 4]; char myhostname[512]; char *p; int opt; FILE *vout = (FILE *)0; int gotdata = 0; struct hostent *hp; + char *domain=NULL; + char *site; + char *tmpstr; + int sitelen; + int daemonflag = FALSE; + int port_used = -1; + char *fwtk_version = defined_fwtk_version; + int stop = 0; + + if (ac > 0) { + myname = strrchr(*av, DIRC); + if (myname == (char *) NULL) + myname = *av; + else + ++myname; + } + + if (atof(fwtk_version) < atof(minimal_fwtk_version)) + fwtk_version = minimal_fwtk_version; + #ifndef LOG_DAEMON ! openlog(myname,LOG_PID); #else ! openlog(myname,LOG_PID|LOG_NDELAY,LFAC); #endif ! while (--ac > 0) { ! ++av; ! if (strcmp(*av, "-daemon") == 0) { ! daemonflag = TRUE; ! continue; ! } ! if (strcmp(*av, "-port") == 0) { ! int i; ! ! if (--ac <= 0) { ! syslog(LLEV, ! "fwtksyserr: %s without port number.", ! *av); ! exit(1); ! } ! i = atoi(*++av); ! if (i <= 0) { ! syslog(LLEV, ! "fwtksyserr: bad port number %d.", ! i); ! exit(1); ! } ! port_used = i; ! } ! } ! ! if (daemonflag) { int sock, sockl; struct sockaddr_in sa; int pid; ! #ifndef NO_GETSERVENT ! struct servent *sp; ! #endif/*NO_GETSERVENT*/ ! sa.sin_family = AF_INET; bzero((char *)&sa.sin_addr, sizeof(sa.sin_addr)); ! ! /* Get the port - from the arg, or normal SMTP. */ ! if (port_used > 0) { ! sa.sin_port = htons(port_used); ! } else { ! #ifdef NO_GETSERVENT ! sa.sin_port = htons(SMTP_PORT); ! # else ! /* ! ** Get the SMTP service port number from the ! ** services file. Protocol could be NULL; but ! ** using smtp_proto forces use of the protocol ! ** we think we're using (TCP). ! */ ! sp = getservbyname(smtp_name, smtp_proto); ! if (sp == (struct servent *) NULL) { ! /* If fails, complain and use default. */ ! syslog(LLEV, "fwtksyserr: Can't read SMTP port number."); ! sa.sin_port = htons(SMTP_PORT); ! } else { ! sa.sin_port = htons(sp->s_port); ! } ! #endif/*NO_GETSERVENT*/ ! } ! sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { syslog(LLEV, "fwtksyserr: Failed to create socket: %m"); *************** *** 142,152 **** exit(1); } while (1) { - int cstat; signal (SIGCHLD, waitwaitwait); sockl = accept (sock, (struct sockaddr *)0, (int *)0); if (sockl < 0) { ! if (errno = EINTR) continue; syslog(LLEV,"fwtksyserr: Accept failed: %m"); exit(1); --- 801,810 ---- exit(1); } while (1) { signal (SIGCHLD, waitwaitwait); sockl = accept (sock, (struct sockaddr *)0, (int *)0); if (sockl < 0) { ! if (errno == EINTR) continue; syslog(LLEV,"fwtksyserr: Accept failed: %m"); exit(1); *************** *** 171,191 **** close(sock); } ! if(peername(0,rladdr,riaddr,sizeof(riaddr))) { ! syslog(LLEV,"cannot get remote host"); exit(1); - } else - syslog(LLEV,"connect host=%.512s/%.20s",rladdr,riaddr); ! if(gethostname(myhostname,sizeof(myhostname))) ! strcpy(myhostname,"amnesiac"); ! else if ((hp = gethostbyname(myhostname)) != 0) ! strcpy(myhostname,hp->h_name); ! if((cfp = cfg_read("smap")) == (Cfg *)-1) ! exit(1); if((cf = cfg_get("groupid",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: groupid must have one parameter, line %d",cf->ln); --- 829,972 ---- close(sock); } ! /* ! ** If we move this here, 'smap' will always read the ! ** configuration just before it processes the mail. Otherwise, ! ** in daemon mode it reads it once for all children - which is ! ** more efficient, but means it behaves differently! ! */ ! if((cfp = cfg_read("smap")) == (Cfg *)-1) exit(1); + /* + ** Now we spend the next few pages just reading in the + ** single-valued configuration parameters. + */ ! /* ! ** "smap: unknown-host 1" sets unknown_host_ok to TRUE, ! ** allowing us to accept connections from a host whom we can't ! ** name [via peername()]. ! */ ! if((cf=cfg_get("unknown-host",cfp)) !=(Cfg *)0) { ! if(cf->argc !=1) { ! syslog(LLEV,"fwtkcfgerr: unknown-host must have one parameter, line %d",cf->ln); ! exit(1); ! } ! unknown_host_ok = (atoi(cf->argv[0]) == 1); ! } ! /* ! ** "smap: check-from-address 1" sets check_from_address to ! ** TRUE, which causes the program to perform some extra ! ** checking in from_address_ok(). ! */ ! if((cf = cfg_get("check-from-address",cfp)) != (Cfg *)0) ! { ! if(cf->argc != 1) ! { ! syslog(LLEV,"fwtkcfgerr: check-from-address must have one parameter, line %d",cf->ln); ! exit(1); ! } ! check_from_address=(atoi(cf->argv[0]) == 1); ! } + /* + ** "smap: require-full-email 1" sets require_full_email to + ** TRUE, which causes from_address_ok() to verify that hosts + ** that are NOT local have an @host part. + */ + if((cf = cfg_get("require-full-email",cfp)) != (Cfg *)0) + { + if(cf->argc != 1) + { + syslog(LLEV,"fwtkcfgerr: require-full-email must have one parameter, line %d",cf->ln); + exit(1); + } + require_full_email=(atoi(cf->argv[0]) == 1); + } + + /* + ** "smap: check-user-too 1" sets check_user_too to TRUE, which + ** causes from_address_ok() to pass check_spamdomain the full + ** "from" address, instead of just the domain. + */ + if((cf = cfg_get("check-user-too",cfp)) != (Cfg *)0) + { + if(cf->argc != 1) + { + syslog(LLEV,"fwtkcfgerr: check-user-too must have one parameter, line %d",cf->ln); + exit(1); + } + check_user_too=(atoi(cf->argv[0]) == 1); + } + + /* + ** REMOVED all mods for changing SMTP return codes from the + ** configuration file. + */ + + /* + * Maximum datasize for E-Mail with "strange" Domain names + ** + ** "smap: max-email ####", with a numeric argument, sets the + ** integer variable 'failmsgsize' to that number. + ** + ** If various things are set, then while checking out the + ** "From: " address, the program may decide that it doesn't + ** want to accept this e-mail. + ** + ** If failmsgsize <= 0, then once it has seen a recipient + ** [RCPT], it doesn't bother to store anything, but gives an + ** error message and exits. + ** + ** If failmsgsize > 0, then it waits until the DATA statement + ** is seen. It then reads in only this much of the message, + ** storing it in a special "secure" directory, and then sends + ** its error message. This way, the system administrator can + ** go back and examine rejected messages. + */ + if((cf = cfg_get("max-email",cfp)) != (Cfg *)0) + { + if(cf->argc != 1) + { + syslog(LLEV,"fwtkcfgerr: max-email must have one parameter, line %d",cf->ln); + exit(1); + } + failmsgsize=atoi(cf->argv[0]); + } + + /* + ** if((cfp = cfg_read("smap")) == (Cfg *)-1) + ** exit(1); + */ + + /* + ** "smap: max-dirent ####", with a numeric argument, sets the + ** integer variable 'maxdirent' to that number. + ** + ** If the program has examined the "From: " address and decided + ** that it's going to block it, and it is storing 'failmsgsize' + ** bytes of each message, then this parameter gives the maximum + ** number of messages that can be stored in this "secure" + ** directory. + */ + if((cf = cfg_get("max-dirent",cfp)) != (Cfg *)0) { + if(cf->argc != 1) { + syslog(LLEV,"fwtkcfgerr: max-dirent must have one parameter, line %d",cf->ln); + exit(1); + } + maxdirent = atoi(cf->argv[0]); + } + + /* + ** "smap: groupid GID" sets the integer variable 'rungid' to the + ** GID as which the program should be running. The GID may be + ** expressed as a group name or as a number: mapgid() will + ** interpret it. + ** + ** Original FWTK 2.1 parameter. + */ if((cf = cfg_get("groupid",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: groupid must have one parameter, line %d",cf->ln); *************** *** 197,202 **** --- 978,991 ---- } } + /* + ** "smap: userid UID" sets the integer variable 'runuid' to the + ** UID as which the program should be running. The UID may be + ** expressed as a user name or as a number: mapuid() will + ** interpret it. + ** + ** Original FWTK 2.1 parameter. + */ if((cf = cfg_get("userid",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: userid must have one parameter, line %d",cf->ln); *************** *** 208,214 **** } } ! if((cf = cfg_get("directory",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: directory must have one parameter, line %d",cf->ln); --- 997,1009 ---- } } ! /* ! ** "smap: directory /directory-name" sets the character pointer ! ** variable 'rootdir' to the name of the directory to which the ! ** program should be chrooted. ! ** ! ** Original FWTK 2.1 parameter. ! */ if((cf = cfg_get("directory",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: directory must have one parameter, line %d",cf->ln); *************** *** 217,223 **** rootdir = cf->argv[0]; } ! if((cf = cfg_get("maxbytes",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: maxbytes must have one parameter, line %d",cf->ln); --- 1012,1024 ---- rootdir = cf->argv[0]; } ! /* ! ** "smap: maxbytes ####", with a numeric argument, sets the ! ** integer variable 'maxbytes' to the maximum number of bytes ! ** allowed in the body of a message. ! ** ! ** Original FWTK 2.1 parameter. ! */ if((cf = cfg_get("maxbytes",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: maxbytes must have one parameter, line %d",cf->ln); *************** *** 226,232 **** maxbytes = atoi(cf->argv[0]); } ! if((cf = cfg_get("maxrecip",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: maxrecip must have one parameter, line %d",cf->ln); --- 1027,1039 ---- maxbytes = atoi(cf->argv[0]); } ! /* ! ** "smap: maxrecip ####", with a numeric argument, sets the ! ** integer variable 'maxbytes' to the maximum number of ! ** recipients allowed per message. ! ** ! ** Original FWTK 2.1 parameter. ! */ if((cf = cfg_get("maxrecip",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: maxrecip must have one parameter, line %d",cf->ln); *************** *** 235,241 **** maxrecip = atoi(cf->argv[0]); } ! if((cf = cfg_get("timeout",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: timeout must have one parameter, line %d",cf->ln); --- 1042,1058 ---- maxrecip = atoi(cf->argv[0]); } ! /* ! ** "smap: timeout ####", with a numeric argument, sets the ! ** integer variable 'timeout' to the maximum time (in seconds) ! ** to wait between reading one line from the network socket and ! ** reading the next line. ! ** ! ** This now defaults to PROXY_TIMEOUT, which is currently ! ** (June 1998) 2 hours. ! ** ! ** Original FWTK 2.1 parameter. ! */ if((cf = cfg_get("timeout",cfp)) != (Cfg *)0) { if(cf->argc != 1) { syslog(LLEV,"fwtkcfgerr: timeout must have one parameter, line %d",cf->ln); *************** *** 243,253 **** --- 1060,1144 ---- } timeout = atoi(cf->argv[0]); } + + /* + ** "smap: scrub-spam" sets the integer/boolean variable + ** 'scrub_spam' TRUE. If set, the program initially checks + ** whether the "real" calling host is one of a set of "spam + ** hosts". Later, if checking the passed from address, this + ** causes the program to check the from address against a set + ** of "spam domains". Both "spam hosts" and "spam domains" are + ** specified under "smap: spam ..." in the configuration file. + ** + ** This is part of bre's mods [Bruce R. Ellis]. Moved with the + ** rest of the configuration initializations by jsdy [Joe Yao]. + */ + if((cf=cfg_get("scrub-spam",cfp)) !=(Cfg *)0) { + scrub_spam = TRUE; + } + + /* + ** Get the name of the remote host ("peer"). The name is + ** returned in rladdr; the IP address is returned in riaddr. + ** + ** If this returns non-zero, then the routine cannot get a + ** socket structure for the socket. If it returns zero, then + ** it found a socket structure. However, there still may have + ** been "something funny" about the address; in which case, + ** the routine will have logged a security alert and changed + ** the host name to "unknown". + ** + ** If the configuration allows "unknown" hosts, then this + ** doesn't exit at this point; otherwise, it does. + */ + if(peername(0,rladdr,riaddr,sizeof(riaddr))) { + syslog(LLEV,"cannot get remote host %s",riaddr); + if (!unknown_host_ok) + exit(1); + strcpy(rladdr,"unknown"); + } + + /* This would be where to put a selfname() call. */ + + /* Always report the connect. */ + syslog(LLEV,"connect host=%.512s/%.20s",rladdr,riaddr); + + /* + ** Get the host name. If the program can't get it, it calls + ** this machine "amnesiac". + ** + ** Look up a "hostname" entry. If found, use the primary name. + ** If not found, NBD: use the already gotten hostname. + ** + ** This is only used in the initial response (immediately + ** below), and in the inserted "Received: " line. + */ + if(gethostname(myhostname,sizeof(myhostname))) + strcpy(myhostname,"amnesiac"); + else if ((hp = gethostbyname(myhostname)) != 0) + strcpy(myhostname,hp->h_name); + + /* + ** If the configuration calls for a timeout, have this function + ** called at the specified intervals. + */ if (timeout > 0) { signal(SIGALRM,ringring); alarm(timeout); } + /* + ** If there is a directory specified, chdir(2) to it and then + ** chroot(2) to it. This way, files must be in that directory, + ** and files elsewhere on the file system should not be + ** accessible. + ** CRITIQUE: chdir() and chroot() errors are not distinguished. + ** This is probably not a heart-stopping problem. + ** NOTE: 'smapd' had better have the same "directory" entry! + ** NOTE: any files needed by this program or 'smapd', such as + ** .../zoneinfo/localtime, had better be copied down under this + ** new "root" directory. + */ if(rootdir != (char *)0) { chdir("/"); if((chdir(rootdir) || chroot(rootdir))) { *************** *** 258,342 **** } if(rungid != -1 && setgid(rungid)) { syslog(LLEV,"fwtksyserr: cannot set gid to %d: %m",rungid); exit(1); } if(runuid != -1 && setuid(runuid)) { syslog(LLEV,"fwtksyserr: cannot set uid to %d: %m",runuid); exit(1); } printf("220 %s SMTP/smap Ready.\r\n",myhostname); fflush(stdout); #ifdef SO_KEEPALIVE opt = 1; (void)setsockopt(fileno(stdin),SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt)); #endif while(1) { if(crlfgets(buf,sizeof(buf),stdin) < 0) { syslog(LLEV,"peer dropped connection: %m"); break; } if((p = strtok(buf," \r\t\n")) == (char *)0) { printf("500 Command unrecognized\r\n"); fflush(stdout); continue; } if(!strcasecmp(p,"MAIL")) { char *q; ! if((q = strtok((char *)0,"\r\n")) == (char *)0) { printf("501 Syntax error\r\n"); fflush(stdout); continue; } if(strncasecmp(q,"From:",5)) { printf("501 Syntax error\r\n"); fflush(stdout); continue; } /* block escape in names */ if(strchr(q, '`')) { printf("501 Syntax error\r\n"); fflush(stdout); continue; } q += 5; while(isspace(*q)) q++; if(ruser != (char *)0) free(ruser); ruser = malloc(strlen(q) + 1); if(ruser == (char *)0) { - printf("410 out of memory\r\n"); - fflush(stdout); syslog(LLEV,"fwtksyserr: out of memory: %m"); ! unlink(tempfile); smap_exit(1); } strcpy(ruser,q); ! printf("250 %s... Sender Ok\r\n",ruser); fflush(stdout); continue; } if(!strcasecmp(p,"RCPT")) { struct towho *nr; char *q; ! if(ruser == (char *)0) { printf("503 need MAIL From: first.\r\n"); fflush(stdout); --- 1149,1421 ---- } + /* If a GID was specified, run this program at that GID. */ if(rungid != -1 && setgid(rungid)) { syslog(LLEV,"fwtksyserr: cannot set gid to %d: %m",rungid); exit(1); } + /* If a UID was specified, run this program at that UID. */ if(runuid != -1 && setuid(runuid)) { syslog(LLEV,"fwtksyserr: cannot set uid to %d: %m",runuid); exit(1); } + /* We're ready! Sound the bells! Alert the media! */ printf("220 %s SMTP/smap Ready.\r\n",myhostname); fflush(stdout); #ifdef SO_KEEPALIVE + /* + ** SO_KEEPALIVE is a socket option that keeps otherwise idle + ** connections active for as long as the other side stays open. + */ opt = 1; (void)setsockopt(fileno(stdin),SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt)); #endif + /* bre 96/10/09 */ + + /* Check to see if mail is originating from within the local domain by + * examining the IP name that is returned by the resolver when + * smap is first started by inetd. + */ + + /* + ** This initially sets up rhost to point to a copy of the + ** calling host whose name was found by peername(). + ** + ** This value is never used, but I made it safe anyway - jsdy + */ + rhost = malloc(strlen(rladdr) + 1); + if (rhost != (char *) NULL) + strcpy(rhost, rladdr); + + /* + ** If the 'scrub_spam' flag is set from the config, and the + ** sending host's IP address is in the "spam:" list in the + ** config, then we reject the connection out-of-hand. + ** + ** At this point, the only allowed error code is 421. + */ + if(scrub_spam && check_spamhost(cfp)) { + int reject_code = 421; + + syslog(LLEV, + "security: %d rejecting all spam from host %s/%s", + reject_code, rladdr, riaddr); + printf("%d your site has been depermitted due to heavy spammage\n", + reject_code); + + fflush(stdout); + exit(1); + } + + /* + ** Check whether the IP address of the sending host is in the + ** "hosts: " config. This is used in checking both the + ** "From: " and "Rcpt: " fields. + */ + from_host_local = check_hostname(cfp); + + /* bre 96/10/09 */ + + /* + ** This is the main loop. It reads lines using crlfgets(), and + ** looks for one of the valid SMTP commands: + ** HELO MAIL RCPT DATA RSET + ** NOOP QUIT HELP VRFY EXPN + ** In addition, it accepts but does nothing for: + ** VERB ONEX DEBUG + ** It does not accept EHLO, but does not log it as an error. + ** + ** Within this loop, smapopen() may be called. + ** Henceforth, all exits must be via smap_exit(), to clean up + ** after the opened file. + */ while(1) { + /* + ** Read a line from the socket. If the program gets + ** nothing, it complains and drops out of the loop. + */ if(crlfgets(buf,sizeof(buf),stdin) < 0) { syslog(LLEV,"peer dropped connection: %m"); break; } + /* If there are no words, complain and continue. */ if((p = strtok(buf," \r\t\n")) == (char *)0) { printf("500 Command unrecognized\r\n"); + syslog(LLEV, + "fwtksyserr: Command unrecognized (no command)"); fflush(stdout); continue; } + /* + ** The first line of a valid message must be: + ** MAIL FROM: + ** This code tries to recognize that sequence. If it + ** recognizes it, then the address given for the sender + ** is stored in ruser. + ** Addresses containing a '`' character are stopped + ** instantly: the command is not even accepted. + ** The from_address_ok() routine is called to see + ** whether some of the security options have been set, + ** and if this from address triggers any of them. If + ** it does, then processing will continue just long + ** enough to record to whom the sender is sending mail, + ** and possibly to save some of the file in an + ** alternate location; but the file is never passed to + ** 'smapd' for delivery. + */ if(!strcasecmp(p,"MAIL")) { char *q; ! /* ! ** If there's nothing after "MAIL", complain ! ** and reject the line. ! */ if((q = strtok((char *)0,"\r\n")) == (char *)0) { printf("501 Syntax error\r\n"); + syslog(LLEV, + "fwtksyserr: Syntax error: MAIL (no args)"); fflush(stdout); continue; } + /* + ** If the next word doesn't start with "from:" + ** [or some case-independent version thereof], + ** then complain and reject the line. + */ if(strncasecmp(q,"From:",5)) { printf("501 Syntax error\r\n"); + syslog(LLEV, + "fwtksyserr: Syntax error: MAIL %s (no FROM:)", q); fflush(stdout); continue; } + /* block escape in names */ + /* + ** A shell escape - `...` - might under some + ** odd circumstances cause the enclosed string + ** to be executed by a shell. We don't want + ** that. Complain and reject the line. + */ if(strchr(q, '`')) { printf("501 Syntax error\r\n"); + syslog(LLEV, + "fwtksyserr: Syntax error: MAIL from \"%s\"", + q); fflush(stdout); continue; } + /* Skip over "from:" and any spaces. */ q += 5; while(isspace(*q)) q++; + + /* + ** If we already had an ruser ["should never + ** happen"], free the staorage space. + */ if(ruser != (char *)0) free(ruser); + + /* + ** Allocate storage space for the new "ruser". + ** If we can't get it, scream and die. + */ ruser = malloc(strlen(q) + 1); if(ruser == (char *)0) { syslog(LLEV,"fwtksyserr: out of memory: %m"); ! printf("452 out of memory\r\n"); ! fflush(stdout); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); smap_exit(1); } strcpy(ruser,q); ! ! /* Look for an "@ site/domain" part. */ ! site = strchr(ruser,'@'); ! ! /* ! ** If it exists, then skip over the "@". If ! ** there is a '<' ... '>' pair following that, ! ** focus in on that. (The characters are ! ** looked for separately, so they might not be ! ** paired, but they'll still be skipped over.) ! ** The 'site' pointer points into the 'ruser' ! ** string; so we don't want to stick a NUL in ! ** at random. We create a new string, 'domain', ! ** which contains just the "site" information. ! */ ! if (site != (char *) NULL) { ! /* Skip over '@'. */ ! ++site; ! ! /* Skip over anything before a '<'. */ ! tmpstr=strrchr(site,'<'); ! if(tmpstr) ! site=1+tmpstr; ! ! /* Skip over anything after a '>'. */ ! tmpstr=strchr(site,'>'); ! if(tmpstr) ! sitelen = tmpstr - site; ! else ! sitelen = strlen(site); ! ! domain = malloc(sitelen + 1); ! if (domain == (char *) NULL) { ! syslog(LLEV,"fwtksyserr: out of memory: %m"); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); ! printf("452 out of memory\r\n"); ! fflush(stdout); ! smap_exit(1); ! } ! strncpy(domain, site, sitelen); ! domain[sitelen] = NUL; ! } ! ! /* ! ** Call from_address_ok() to see whether some ! ** of the security options have been set, and ! ** if this from address triggers any of them. ! ** Stop is 1 if this is true, 0 otherwise. ! */ ! stop = from_address_ok(ruser, cfp, domain); ! ! /* ! ** Recipient count is zeroed for this message. ! ** Moved here from "DATA". ! */ ! currecip = 0; ! ! printf("250 %s... Sender Ok\r\n",safestring(ruser)); fflush(stdout); continue; } + /* + ** After the "MAIL FROM: " line, the next one + ** or more lines of a message should tell who are the + ** recipients that this server needs to worry about: + ** RCPT TO: + */ if(!strcasecmp(p,"RCPT")) { struct towho *nr; char *q; ! /* No RCPT without its previous MAIL! */ if(ruser == (char *)0) { printf("503 need MAIL From: first.\r\n"); fflush(stdout); *************** *** 343,348 **** --- 1422,1431 ---- continue; } + /* + ** If there's nothing after "RCPT", complain + ** and reject the line. + */ if((q = strtok((char *)0,"\r\n")) == (char *)0) { printf("501 Syntax error\r\n"); fflush(stdout); *************** *** 349,354 **** --- 1432,1442 ---- continue; } + /* + ** If the next word doesn't start with "to:" + ** [or some case-independent version thereof], + ** then complain and reject the line. + */ if(strncasecmp(q,"To:",3)) { printf("501 Syntax error\r\n"); fflush(stdout); *************** *** 355,364 **** --- 1443,1466 ---- continue; } + /* Skip over "to:" and any spaces. */ q += 3; while(isspace(*q)) q++; + + /* + ** Log "to" address if domain "from" address is + ** not permitted here. + */ + if (stop == 1) + syslog(LLEV,"security: to-address=%s",q); + #ifdef SPECIALDOMAIN + /* + ** If we require mail to be to our "special + ** domain", and it's not, then complain and + ** continue. + */ if(!checkvalid(q)) { syslog(LLEV,"securityalert: rejecting recip %.512s",q); fflush(stdout); *************** *** 366,398 **** } #endif if(maxrecip > 0 && currecip++ > maxrecip) { printf("550 too many recipients\r\n"); fflush(stdout); syslog(LLEV,"exiting - too many recipients"); ! unlink(tempfile); smap_exit(1); } nr = (struct towho *)malloc(sizeof(struct towho)); if(nr == (struct towho *)0) { - printf("410 out of memory\r\n"); - fflush(stdout); syslog(LLEV,"fwtksyserr: out of memory: %m"); ! unlink(tempfile); smap_exit(1); } nr->who = malloc(strlen(q) + 1); if(nr->who == (char *)0) { - printf("410 out of memory\r\n"); - fflush(stdout); syslog(LLEV,"fwtksyserr: out of memory: %m"); ! unlink(tempfile); smap_exit(1); } strcpy(nr->who,q); nr->nxt = recip; recip = nr; printf("250 %s OK\r\n",q); fflush(stdout); continue; --- 1468,1560 ---- } #endif + #ifdef PROTECTINBOUND + /* use this to block email from being delivered to specific + addresses inside your firewall, like internal mailing lists, + users that left the company, but managed to get themselves + on a lot of spammers lists, etc... + + Note that you could send a 450 instead of a 550 to force + the spammer to keep trying until the email times out. Not + very friendly, but oh well. This also give you the chance to scan + the logs for entries that indicate an address was blocked that + shouldn't have been. + + */ + if(check_protected_internal(ruser,q,cfp)) { + syslog(LLEV,"securityalert: spam match to: %s from: %s", q, ruser); + printf("550 you can not send to that mailbox\r\n"); + fflush(stdout); + continue; + } + #endif /*PROTECTINBOUND*/ + + /* + ** If a maximum number of recipients has been + ** specified, and the sender has gone over, + ** then this call fails. + */ if(maxrecip > 0 && currecip++ > maxrecip) { printf("550 too many recipients\r\n"); fflush(stdout); syslog(LLEV,"exiting - too many recipients"); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); smap_exit(1); } + /* + ** Add the recipient to the linked list of + ** recipients. + */ nr = (struct towho *)malloc(sizeof(struct towho)); if(nr == (struct towho *)0) { syslog(LLEV,"fwtksyserr: out of memory: %m"); ! printf("452 out of memory\r\n"); ! fflush(stdout); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); smap_exit(1); } nr->who = malloc(strlen(q) + 1); if(nr->who == (char *)0) { syslog(LLEV,"fwtksyserr: out of memory: %m"); ! printf("452 out of memory\r\n"); ! fflush(stdout); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); smap_exit(1); } strcpy(nr->who,q); nr->nxt = recip; recip = nr; + + /* bre 96/10/09 */ + /* + ** If the FROM host is not local, then the + ** recipient must be to an "inside" address. + ** If this is not true, complain and die. + */ + if (from_host_local) + { + get_mail_ok = TRUE; + } + else + { + get_mail_ok = checkvalid(q,cfp); + } + + if (! get_mail_ok) + { + printf("%d Mail not local. Closing connection.\r\n",NOTLOCAL_ERRNO); + syslog(LLEV,"fwtksyserr: Mail not local %s",q); + fflush(stdout); + break; + } + + /* bre 96/10/09 */ + + /* Good recipient. */ printf("250 %s OK\r\n",q); fflush(stdout); continue; *************** *** 399,435 **** } if(!strcasecmp(p,"DATA")) { struct towho *tp; ! long now; ! curbytes = 0; ! currecip = 0; if(recip == (struct towho *)0) { printf("503 need RCPT first.\r\n"); fflush(stdout); continue; } ! if((vout = smapopen()) == (FILE *)0) { syslog(LLEV,"fwtksyserr: cannot open temp file %m"); smap_exit(1); } printf("354 Enter mail, end with \".\" on a line by itself\r\n"); fflush(stdout); ! fprintf(vout,"FROM %s\n",ruser); for(tp = recip; tp != (struct towho *)0; tp = tp->nxt) fprintf(vout,"RCPT %s\n",tp->who); fprintf(vout,"BODY\n"); ! time(&now); ! fprintf(vout,"Received: from %s(%s) by %s via smap (%s)\n\tid %s; %s\n", ! rladdr,riaddr,myhostname, ! FWTK_VERSION_MINOR,tempfile, arpadate((char *)0)); msgsize = 0; gotdata = 0; switch(crunchbody(stdin,vout)) { case 1: printf("550 you could say goodbye\r\n"); --- 1561,1715 ---- } + /* + ** After the "MAIL FROM: " line, and any number + ** of "RCPT TO: lines, we get the mail + ** message itself, starting with just "DATA". If + ** convention is being followed, first header lines are + ** passed, then message body lines. + */ if(!strcasecmp(p,"DATA")) { struct towho *tp; ! struct towho *tp2; ! /* No DATA without its previous RCPT[s]! */ if(recip == (struct towho *)0) { printf("503 need RCPT first.\r\n"); + syslog(LLEV,"fwtksyserr: need RCPT first"); fflush(stdout); continue; } ! ! /* ! ** The below was moved here from the RCPT code, ! ** so we can report on all of the recipients, ! ** instead of just one. ! */ ! ! /* Don't want some data, so exit here */ ! if ((failmsgsize <= 0) && (stop == 1)) { ! int reject_code; ! ! reject_code = spam_termination; ! ! syslog(LLEV, ! "security: closing connection, unacceptable address/domain datafile %s %d", ! safestring(tempfile), ! reject_code); ! printf("%d Unacceptable address/domain: %s [%s]/\r\n", ! reject_code, ! safestring(ruser), ! safestring(domain)); ! ! fflush(stdout); ! smap_exit(1); ! } ! ! /* ! ** Create the mail input file. If (stop == 1), ! ** then this is put in a secure place and never ! ** delivered. Otherwise, it is put in the ! ** normal queueing place until it's full, and ! ** then sent off for 'smapd' to take care of ! ** delivering. ! */ ! vout = smapopen(stop); ! if (vout == (FILE *) NULL) { syslog(LLEV,"fwtksyserr: cannot open temp file %m"); smap_exit(1); } + /* Standard intermediate prompt. */ printf("354 Enter mail, end with \".\" on a line by itself\r\n"); fflush(stdout); ! /* ! ** Store the sender and all the recipients in ! ** the mail input file. ! */ ! fprintf(vout,"FROM %s\n",safestring(ruser)); for(tp = recip; tp != (struct towho *)0; tp = tp->nxt) fprintf(vout,"RCPT %s\n",tp->who); + + /* + ** Put the message body into the file, starting + ** with a "Received: " line for this hop. + */ fprintf(vout,"BODY\n"); ! fprintf(vout, ! "Received: from %s(%s) by %s via smap (", ! rladdr,riaddr,myhostname); ! ! /* ! ** If we're running a later 'smap' in an ! ** earlier FWTK environment, we give truth-in- ! ** advertising answer ... both! Plus an add-on ! ** for any patches [could be an empty string]. ! */ ! if (fwtk_version == defined_fwtk_version) { ! fprintf(vout, "%s", fwtk_version); ! } else { ! fprintf(vout, "%s/%s", ! defined_fwtk_version, ! fwtk_version); ! } ! fprintf(vout, ! "%s)\n\tid %s; %s\n", ! version_addon, safestring(tempfile), arpadate((char *)0)); + /* + ** 'msgsize' is set to zero in case of error. + ** When the file is full, it will be set to the + ** size of the file. + */ msgsize = 0; + + /* + ** Test this to see whether we actually got a + ** message. + */ gotdata = 0; + + /* + ** If, previously, we had determined that the + ** sender was unwelcome here, then we are just + ** storing the bytes for later examination. + ** Save the 'failmsgsize' as 'maxbytes', crunch + ** the message body, then complain and die. + */ + if (stop == 1) { /* Grab some bytes from the message */ + int reject_code; + + /* We want not the full size of the message */ + maxbytes = failmsgsize; + crunchbody(stdin,vout); + fclose(vout); + if (!CheckDirEntry(cfp)) { + if (tempfile != (char *) NULL) + (void) unlink(tempfile); + syslog(LLEV, + "security: limit for security directory %s", + safestring(tempfile)); + } + reject_code = spam_termination; + syslog(LLEV, + "security: closing connection, unacceptable address/domain datafile %s %d", + safestring(tempfile), + reject_code); + printf("%d Unacceptable address/domain: %s [%s]/\r\n", + reject_code, + safestring(ruser), + safestring(domain)); + fflush(stdout); + smap_exit(1); + } + + /* + ** Read the body, copy from stdin to vout, + ** return 0 for good, otherwise various error + ** values. + */ switch(crunchbody(stdin,vout)) { case 1: printf("550 you could say goodbye\r\n"); *************** *** 439,448 **** printf("552 Too much data\r\n"); fflush(stdout); syslog(LLEV,"exiting too much data"); ! unlink(tempfile); smap_exit(1); case 3: ! printf("450 System problem\r\n"); fflush(stdout); continue; default: --- 1719,1729 ---- printf("552 Too much data\r\n"); fflush(stdout); syslog(LLEV,"exiting too much data"); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); smap_exit(1); case 3: ! printf("451 System problem\r\n"); fflush(stdout); continue; default: *************** *** 450,469 **** /* .... fall through... */ } ! for(tp = recip; tp != (struct towho *)0; tp = tp->nxt) ! syslog(LLEV,"host=%.512s/%.20s bytes=%d from=%.100s to=%.100s",rladdr,riaddr,msgsize,ruser,tp->who); ! if(recip != (struct towho *)0) { ! currecip = 0; ! /* should really free them */ ! recip = (struct towho *)0; } if(!gotdata) { ! unlink(tempfile); syslog(LLEV,"SMTP QUIT with no message %.512s/%.20s",rladdr,riaddr); smap_exit(1); } add_file_list(tempfile); ! chmod(tempfile,0700); printf("250 Mail accepted\r\n"); fflush(stdout); syslog(LLEV,"exiting host=%.512s/%.20s bytes=%d",rladdr,riaddr,msgsize); --- 1731,1777 ---- /* .... fall through... */ } ! /* ! ** Log the from/to pairs ... while doing this, ! ** free up the recipient structures. ! */ ! for(tp = recip; tp != (struct towho *) NULL; ) { ! syslog(LLEV, ! "host=%.512s/%.20s bytes=%d from=%.100s to=%.100s %s", ! rladdr, riaddr, msgsize, ! safestring(ruser), ! safestring(tp->who), ! safestring(tempfile)); ! tp2 = tp->nxt; ! if (tp->who != (char *) NULL) ! free(tp->who); ! free(tp); ! tp = tp2; } + + /* All gone - zero holders. */ + currecip = 0; + recip = (struct towho *) NULL; + + /* + ** If crunchbody() didn't return with a + ** successful read of data, complain and die. + */ if(!gotdata) { ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); syslog(LLEV,"SMTP QUIT with no message %.512s/%.20s",rladdr,riaddr); smap_exit(1); } + + /* + ** Link the message file to a name that 'smapd' + ** will recognize and read; and add the + ** temporary name to the "filelist" to be + ** cleaned up on exit. + */ add_file_list(tempfile); ! printf("250 Mail accepted\r\n"); fflush(stdout); syslog(LLEV,"exiting host=%.512s/%.20s bytes=%d",rladdr,riaddr,msgsize); *************** *** 473,478 **** --- 1781,1791 ---- } + /* + ** The VRFY [verify] and EXPN [expand] commands should + ** tell what e-mail addresses are accepted here. Well, + ** almost all are - all local addresses, anyway. + */ if(!strcasecmp(p,"VRFY") || !strcasecmp(p,"EXPN")) { char *q; *************** *** 486,491 **** --- 1799,1808 ---- } + /* + ** The HELP command - obviously - helps the [human] + ** user. + */ if(!strcasecmp(p,"HELP")) { printf("214-Commands\r\n"); printf("214-HELO MAIL RCPT DATA RSET\r\n"); *************** *** 495,500 **** --- 1812,1818 ---- } + /* NOOP does nothing. */ if(!strcasecmp(p,"NOOP")) { printf("220 OK\r\n"); fflush(stdout); *************** *** 502,507 **** --- 1820,1826 ---- } + /* QUIT ends this program. */ if(!strcasecmp(p,"QUIT")) { printf("221 Closing connection\r\n"); fflush(stdout); *************** *** 509,541 **** } if(!strcasecmp(p,"RSET")) { recip = (struct towho *)0; ! ruser = (char *)0; if(vout != (FILE *)0) { ! unlink(tempfile); fclose(vout); ! if((vout = smapopen()) == (FILE *)0) { ! syslog(LLEV,"fwtksyserr: cannot open temp file %m"); ! smap_exit(1); } } printf("250 Reset state\r\n"); fflush(stdout); continue; } if(!strcasecmp(p,"HELO")) { char *q; - if((q = strtok((char *)0,"\r\n")) == (char *)0) { printf("250 Charmed, I'm sure.\r\n"); ! rhost = "??"; } else { printf("250 (%s) pleased to meet you.\r\n",q); rhost = malloc(strlen(q) + 1); ! strcpy(rhost,q); } fflush(stdout); continue; --- 1828,1902 ---- } + /* + ** RESET resets the state of the mail message, if any. + */ if(!strcasecmp(p,"RSET")) { + struct towho *tp, *tp2; + + /* If recip has entries, free them. */ + for (tp = recip; tp != (struct towho *) NULL; ) { + tp2 = tp->nxt; + if (tp->who != (char *) NULL) + free(tp->who); + free(tp); + tp = tp2; + } recip = (struct towho *)0; ! ! /* If ruser has a value, free it. */ ! if (ruser != (char *) NULL) { ! free(ruser); ! ruser = (char *) NULL; ! } ! ! /* If domain has a value, free it. */ ! if (domain != (char *) NULL) { ! free(domain); ! domain = (char *) NULL; ! } ! ! /* ! ** If the input file is open, close it. Free ! ** up the file name. ! */ if(vout != (FILE *)0) { ! lockun_fd(fileno(vout)); fclose(vout); ! vout = (FILE *) NULL; ! if (tempfile != (char *) NULL) { ! (void) unlink(tempfile); ! free(tempfile); ! tempfile = (char *) NULL; } } + printf("250 Reset state\r\n"); + syslog(LLEV,"fwtksyserr: Reset state"); fflush(stdout); continue; } + /* + ** "HELO" just introduces the conversation. The name + ** passed in, if any, is stored in rhost - and never + ** used. + */ if(!strcasecmp(p,"HELO")) { char *q; if((q = strtok((char *)0,"\r\n")) == (char *)0) { printf("250 Charmed, I'm sure.\r\n"); ! if (rhost != (char *) NULL) ! free(rhost); ! rhost = (char *) NULL; } else { printf("250 (%s) pleased to meet you.\r\n",q); + if (rhost != (char *) NULL) + free(rhost); rhost = malloc(strlen(q) + 1); ! if (rhost != (char *) NULL) ! strcpy(rhost, q); } fflush(stdout); continue; *************** *** 542,547 **** --- 1903,1909 ---- } + /* VERB is treated as a NO-OP internally. */ if(!strcasecmp(p,"VERB")) { printf("200 Verbose mode\r\n"); fflush(stdout); *************** *** 549,554 **** --- 1911,1917 ---- } + /* ONEX is treated as a NO-OP internally. */ if(!strcasecmp(p,"ONEX")) { printf("200 Only one transaction\r\n"); fflush(stdout); *************** *** 556,561 **** --- 1919,1925 ---- } + /* DEBUG is treated as a NO-OP internally. */ if(!strcasecmp(p,"DEBUG")) { printf("200 Debug set -NOT!\r\n"); fflush(stdout); *************** *** 562,591 **** continue; } printf("500 Command unrecognized\r\n"); fflush(stdout); } smap_exit(0); } static FILE * ! smapopen() { FILE *ret; int fd; char xuf[512]; ! strcpy(xuf,"xmaXXXXXX"); /* system V locking will have problems if mkstemp does not open for random access as well as write */ ! if((fd = mkstemp(xuf)) < 0) return((FILE *)0); ! chmod(xuf,0600); if((ret = fdopen(fd,"w")) == (FILE *)0) return((FILE *)0); lock_fd(fd); if((tempfile = malloc(strlen(xuf) + 1)) == (char *)0) { syslog(LLEV,"fwtksyserr: out of memory: %m"); unlink(xuf); --- 1926,2203 ---- continue; } + /* All other commands are unrecognized. */ printf("500 Command unrecognized\r\n"); + + /* + ** All unrecognized commands except ELHO [ESMTP] are + ** logged. + */ + if (strcasecmp(p,"EHLO") != 0) /* OK don't log the EHLO Command, it's a valid command */ + syslog(LLEV,"fwtksyserr: Command unrecognized %s",p); fflush(stdout); } smap_exit(0); } + /* + ** This routine checks whether the "from" address passed as 'ruser' is + ** OK from which to accept mail. The second argument is the already + ** read-in configuration pointer. The third argument is a pointer to + ** the char * that contains a copy of the domain name. + */ + static int from_address_ok(char *ruser, Cfg *cfp, char *domain) + { + char *tmpstr; + char answer[512]; + int goodaddr; + if (InCheckList(ruser,cfp,"broken-from")) + return(0); + if (require_full_email && domain == (char *) NULL && + !from_host_local) { + printf("%d From address does not include your host name\r\n", + LYING_ERRNO); + fflush(stdout); + syslog(LLEV,"security: bad from address, %s, from %s/%s",safestring(ruser), + rladdr,riaddr); + smap_exit(1); + } + + if (!check_from_address) + return(0); + + if (scrub_spam) { + if(check_user_too) + tmpstr = ruser; + else + tmpstr = domain; + + if (tmpstr != (char *) NULL && + check_spamdomain(tmpstr, cfp)) { + int reject_code; + + /* Send all mail from this host a 552 (spam_kill_rej) */ + reject_code = spam_kill_rej; + + printf("%d your site has been depermitted due to heavy spammage\r\n", + reject_code); + syslog(LLEV,"security: %d rejected spam from %s [%s] from host %s/%s", + reject_code, safestring(ruser), + safestring(domain), rladdr, riaddr); + fflush(stdout); + smap_exit(1); + } + } + + /* + ** Look for a "good address". A "good address" is a domain + ** such that two conditions are both fulfilled. + ** + ** First, "from_host_local" must be true [set by checking the + ** connection IP address against the "smap: hosts" lines] OR + ** the domain must NOT pass check_domain() [which checks the + ** domain against the "smap: domains" lines]. + ** + ** Second, the domain must have a valid A or MX record. + ** + ** If the initial domain does not pass this test, then this + ** loop looks at the next lower domain. For instance, if + ** marcus.ranum.tis.com fails, then it looks at ranum.tis.com, + ** then at tis.com, then at com. + ** + ** There is probably a flaw in this logic, but I'll look at + ** that later. jsdy + */ + goodaddr = FALSE; + while (domain != (char *) NULL && *domain != NUL) { + goodaddr = (from_host_local || + !check_domain(domain,cfp)) && + (res_query(domain,C_IN,T_A,(u_char *)&answer,512) > 0 || + res_query(domain,C_IN,T_MX,(u_char *)&answer,512) > 0); + + /* If I've found a good one, stop looking. */ + if (goodaddr) + break; + + /* If I haven't yet found one, lower the domain. */ + domain=strchr(domain,'.'); + if(domain && *domain) + domain++; + } + + /* + ** I do n o t change the domain outside this routine to the + ** domain that is the result of this loop. That it had been + ** happening is probably a bug. + */ + + if(!goodaddr) { + /* It's a bad address, but we'll receive the from header */ + syslog(LLEV,"security: rejected mail purporting to be from %s [%s] from host %s/%s",safestring(ruser),safestring(domain),rladdr,riaddr); + return(1); + } + + return(0); + } + + /* + * Check if Number of Files in security directory is ok + */ + + static int CheckDirEntry(Cfg *cfp) + { + Cfg cf; + DIR *dir; + int cnt = 0; + + if (maxdirent == 0) return TRUE; + dir = opendir(securedir); + if (dir == NULL) return FALSE; /* Some Problems accessing the dir */ + while(readdir(dir) != NULL) cnt++; + if (cnt > maxdirent) return FALSE; + return TRUE; + } + + /* + * Compare string with teststring. If teststring begins with a * + * do a substring compare + ** + ** Modified so that "*" may now be put anywhere in the string, as in + ** the shell; and not just at the beginning of strings. jsdy + */ + static int chkstr(char *teststr, char *str) + { + /* + ** Match any non-"*" characters exactly. + */ + while (*teststr != MATCHC) { + if (*teststr != *str) + return (FALSE); + if (*teststr == NUL) + return(TRUE); + ++teststr; + ++str; + } + + /* Skip over '*'s. */ + while (*teststr == MATCHC) + teststr++; + + /* + ** Try to match the rest of the pattern to any substring of the + ** string. + */ + for (;;) { + if (chkstr(teststr, str)) + return TRUE; /* String match */ + if (*str == NUL) + break; + str++; + } + return FALSE; + } + + /* + ** Check if the passed string is in the specified list + */ + static int InCheckList(char *str,Cfg *cfp,char *list) + { + Cfg *cf; + int x; + + /* The NULL string is in no list. */ + if (str == NULL) return FALSE; + + /* Get the first line of this list. */ + cf = cfg_get(list,cfp); + + /* + ** As long as there are lines, check each element on each line. + */ + while(cf != (Cfg *)0) { + /* Compare each element on the line to the string. */ + for(x = 0; x < cf->argc; x++) { + if(chkstr(cf->argv[x],str)) + return(TRUE); + } + /* Try for another line. */ + cf = cfg_get(list,(Cfg*)0); + } + + /* No element on any line matched. Sorry. */ + return(FALSE); + } + + /* + ** This function opens the input file, which will contain all of the + ** addressing and message data information. If 'stop' has been set by + ** the "from" name check, then this is just put into a "secure" holding + ** area until the security administrator can do with it whatever that + ** worthy wishes. Otherwise, it's put in the normal spool area, with a + ** name beginning with 'q_lock_char' and 0600 permissions. It's kept + ** there until the program has entered all of its information, at which + ** time the file name is changed to start with 'q_send_char', and the + ** permissions are changed to 0700. + ** On success, this program returns a writable FILE pointer for the + ** uniquely-named file, and leaves the name of the file in a malloc'ed + ** string at which 'tempfile' is pointing. On failure, the program + ** returns a NULL file pointer, and 'tempfile' is not changed. If no + ** memory can be allocated for the file name, the program dies. + */ static FILE * ! smapopen(int stop) { FILE *ret; int fd; char xuf[512]; + int mask; ! /* ! ** If this is put in another function, then one could ! ** conceivably override it where the original programmer forgot ! ** to call the other function - e.g., by sending a RSET. ! */ ! if (stop == 0) { ! /* Open the queue file in the default area. */ ! (void) sprintf(xuf, "%s%s%s", queuedir, ! *queuedir == NUL ? "" : "/", ! queuefile); ! } else { ! ! /* Open the file in a different secure directory. */ ! (void) sprintf(xuf, "%s%s%s", securedir, ! *securedir == NUL ? "" : "/", ! securefile); ! } ! /* system V locking will have problems if mkstemp does not open for random access as well as write */ ! ! mask = umask(INFILE_UMASK); /* makes it mode 0600 */ ! ! /* ! ** Create a unique file name from the template, and return a ! ** Unix file descriptor for that file. ! */ ! fd = mkstemp(xuf); ! ! if (mask >= 0) /* Put the old mask back. */ ! (void) umask(mask); ! ! /* If the file open failed, return a NULL FILE pointer. */ ! if (fd < 0) return((FILE *)0); ! ! /* Get a stdio file handle to return. */ if((ret = fdopen(fd,"w")) == (FILE *)0) return((FILE *)0); + + /* Lock the file, using "FWTK standard locking". */ lock_fd(fd); + + /* Get a buffer in which to store the file name. */ if((tempfile = malloc(strlen(xuf) + 1)) == (char *)0) { syslog(LLEV,"fwtksyserr: out of memory: %m"); unlink(xuf); *************** *** 592,602 **** smap_exit(1); } strcpy(tempfile,xuf); return(ret); } ! ! crunchbody(in,out) FILE *in; FILE *out; --- 2204,2237 ---- smap_exit(1); } strcpy(tempfile,xuf); + + /* Return the stdio file handle. */ return(ret); } ! /* ! ** This function "crunches" the body of the message. (For this ! ** program, body == any header lines + any message lines.) It reads in ! ** lines terminated by a CR/LF pair using crlfgets(). That function ! ** looks for a line with a "." by itself, and returns an EOF (1) if it ! ** sees one. On end of a message, this function closes the input file, ! ** unlocks it, and checks its size. ! ** A double dot starting a line signifies an escaped single dot, and is ! ** reduced in the input file. ! ** If crlfgets() returns an error, then this function returns an error ! ** with no further processing. Various other errors return error codes ! ** with various amounts of end processing done. ! ** This function also implements the restriction on message size via ! ** 'maxbytes'. If more than the allowed number of bytes is sent, then ! ** this program will stop saving lines after 'maxbytes' have been read, ! ** and will return an error code after end processing. ! ** ! ** re. maxbytes: ! ** Unlike the earlier way of handling this, now the program ! ** will read in ALL bytes of the message before sending the ! ** error [per RFC 821], so that the remote system won't try to ! ** re-transmit. ! */ crunchbody(in,out) FILE *in; FILE *out; *************** *** 604,652 **** char buf[BUFSIZ]; int x; int drop = 0; while(1) { x = crlfgets(buf, sizeof(buf), in); if (x < 0) return(1); if (x == 1) break; if(drop) continue; if(maxbytes > 0) { if ((curbytes += strlen(buf)) > maxbytes) { syslog(LLEV,"message larger than limit of %d bytes - discarding", maxbytes); drop = 1; } } if(buf[0] == '.' && buf[1] == '.') x = fprintf(out,"%s\n",&buf[1]); else x = fprintf(out,"%s\n",buf); if(x == -1 || ferror(out)) { - fclose(out); syslog(LLEV,"fwtksyserr: cannot fprintf: %m"); return(3); } } if(fflush(out) || ferror(out)) { ! syslog(LLEV,"fwtksyserr: cannot fprintf: %m"); return(3); } msgsize = filesize(fileno(out)); lockun_fd(fileno(out)); if (fclose(out)) { syslog(LLEV,"fwtksyserr: cannot fclose: %m"); return (1); } if (drop) return (2); return(0); } ! ! static int crlfgets(buf,siz,fp) char *buf; int siz; --- 2239,2344 ---- char buf[BUFSIZ]; int x; int drop = 0; + int curbytes = 0; + /* + ** Byte count is zeroed, preparatory to reading + ** the message body. + */ + curbytes = 0; + while(1) { + /* Get a line from the input file handle. */ x = crlfgets(buf, sizeof(buf), in); + + /* Error on input. */ if (x < 0) return(1); + + /* EOF (single dot on line) seen. */ if (x == 1) break; + + /* If we're not saving the rest, continue. */ if(drop) continue; + + /* If maxbytes is set, check file size. */ if(maxbytes > 0) { + /* + ** If it's over the limit, log a complaint and + ** flag that we're not saving the rest. + */ if ((curbytes += strlen(buf)) > maxbytes) { syslog(LLEV,"message larger than limit of %d bytes - discarding", maxbytes); drop = 1; } } + + /* + ** If there's an escaped dot, un-escape it; otherwise, + ** just write the whole line. + */ if(buf[0] == '.' && buf[1] == '.') x = fprintf(out,"%s\n",&buf[1]); else x = fprintf(out,"%s\n",buf); + + /* + ** If the write failed, close the file and return error. + */ if(x == -1 || ferror(out)) { syslog(LLEV,"fwtksyserr: cannot fprintf: %m"); + fclose(out); return(3); } } + + /* + ** If a write or the final flush failed, close the file and + ** return an error. + */ if(fflush(out) || ferror(out)) { ! syslog(LLEV,"fwtksyserr: cannot fflush: %m"); ! fclose(out); return(3); } + + /* Store the size of the message. */ msgsize = filesize(fileno(out)); + + /* Unlock the file, using "FWTK standard locking". */ lockun_fd(fileno(out)); + + /* If closing the file fails [??], return an error. */ if (fclose(out)) { syslog(LLEV,"fwtksyserr: cannot fclose: %m"); return (1); } + + /* If we ran over a size limit, return an error. */ if (drop) return (2); + + /* Return success. */ return(0); } ! /* ! ** Read a CR/LF-terminated line, lopping off the terminations. Return ! ** an error if there was a previous error or EOF read (making sure that ! ** 'errno' is re-set to its previous value). ! ** If reading an EOF and no chars are saved in the buffer, return an ! ** EOF (1). If reading an EOF and there are characters in the buffer, ! ** return those characters, deferring EOF processing until later. ! ** If ".\r\n" is seen, then return an EOF (1). However, if "." is seen ! ** alone on a line, then perhaps this is due to a buggy version of an ! ** MTA [SunOS sendmail, e.g.] sending a ".\n": log this and return an ! ** EOF. ! ** For all other lines, this function returns 0. ! */ ! static int crlfgets(buf,siz,fp) char *buf; int siz; *************** *** 655,668 **** int x; char *s = buf; int endline = 0; if(ferror(fp) || feof(fp)) return(-1); while(--siz) { if((x = fgetc(fp)) == EOF) { ! if(s == buf) return(-1); break; } if(x == '\n') { --- 2347,2367 ---- int x; char *s = buf; int endline = 0; + static int saverrno = 0; + /* Restore previous call's errno for next return. */ + errno = saverrno; + + /* check for earlier I/O errors or EOF. */ if(ferror(fp) || feof(fp)) return(-1); while(--siz) { if((x = fgetc(fp)) == EOF) { ! if (s == buf) { ! saverrno = errno; return(-1); + } break; } if(x == '\n') { *************** *** 677,686 **** *s++ = x; } *s = '\0'; livetim = 1; /* SunOS sendmail violates end-of-message spec and sends ".\0". * You can be correct or you can work ;-) ! */ if ((buf[0] == '.') && !endline) { if ((buf[1] == '\n') || (buf[1] == '\0')) { syslog(LLEV, "Caught pathological End of Message? buf[1] is %d", (int) buf[1]); --- 2376,2397 ---- *s++ = x; } *s = '\0'; + + /* + ** This is set after each line is read, to stave off a timeout. + */ livetim = 1; + /* SunOS sendmail violates end-of-message spec and sends ".\0". * You can be correct or you can work ;-) ! ** SunOS may actually send ".\n", since NL is never read into ! ** the buffer ... so the first case below actually "can never ! ** happen" [;-)]. ! ** In the code below, buf[0] always actually exists: either it ! ** was read in, or the line was blank, and it's a NUL character. ! ** If (buf[0] == '.'), then buf[1] is the same: either read in ! ** or set to NUL. ! */ if ((buf[0] == '.') && !endline) { if ((buf[1] == '\n') || (buf[1] == '\0')) { syslog(LLEV, "Caught pathological End of Message? buf[1] is %d", (int) buf[1]); *************** *** 687,696 **** --- 2398,2415 ---- endline = 1; } } + + /* Save any error number, for above. */ + saverrno = errno; + return (endline); } + /* + ** Check the file size via fstat() of the file whose file descriptor is + ** passed, and return the size, or -1 on fstat() error. + */ filesize(fd) int fd; { *************** *** 702,739 **** } ! usage() ! { ! printf("usage:\n"); ! return(1); ! } ! ! static void add_file_list (tmpf) char *tmpf; { struct file_list *fl = (struct file_list *) malloc(sizeof(struct file_list)); char *p = (char *)malloc(strlen(tmpf) + 1); if (p == (char *)0 || fl == (struct file_list *)0) smap_exit(1); strcpy(p, tmpf); fl->next = filelist; fl->tmpfile = p; filelist = fl; /* use link to make it visible to smapd keeping original as well */ ! p[0] = 's'; ! if (link(tmpf, p) && (errno != EEXIST)) { syslog(LLEV, "link from %.100s to %.100s failed, %m", tmpf, p); - p[0] = 'x'; smap_exit(1); } - p[0] = 'x'; } static void smap_exit (code) int code; --- 2421,2485 ---- } ! /* ! ** This function takes a file name as its argument. It malloc's a ! ** linked list structure and a string into which to copy the file name, ! ** and puts those at the head of the linked list 'filelist'. This list ! ** is used to delete files when smap_exit() is called. ! ** ! ** If either malloc() fails, this function simply exits. ! ** ! ** This function also passes the file to 'smapd'. It does this by ! ** changing its permission bits to 0700 - a flag to 'smapd' - and then ! ** renaming it to a file whose name starts with 'q_send_char'. This ! ** makes it a file for which 'smapd' is looking. ! */ static void add_file_list (tmpf) char *tmpf; { + int i; struct file_list *fl = (struct file_list *) malloc(sizeof(struct file_list)); char *p = (char *)malloc(strlen(tmpf) + 1); + /* If either malloc() failed, die. */ if (p == (char *)0 || fl == (struct file_list *)0) smap_exit(1); + /* Enqueue the new file name at the head. */ strcpy(p, tmpf); fl->next = filelist; fl->tmpfile = p; filelist = fl; + /* + ** This is a signal to 'smapd' that this file + ** may now be read. + */ + chmod(tmpf, 0700); + /* use link to make it visible to smapd keeping original as well */ ! p[0] = q_send_char; ! i = link(tmpf, p); ! p[0] = q_lock_char; ! ! /* ! ** If the link failed, complain and die. ! ** Earlier code made an exception if the target file already ! ** existed; but this too is an error. ! */ ! if (i < 0) { syslog(LLEV, "link from %.100s to %.100s failed, %m", tmpf, p); smap_exit(1); } } + /* + ** This function (a) unlinks all the files in the filelist, freeing the + ** allocated structures as it goes; (b) flushes the output (and sticks + ** around for a second to make sure that the flush works); and (c) + ** exits with the specified exit code. + */ static void smap_exit (code) int code; *************** *** 742,755 **** while (filelist != (struct file_list *)0) { fl = filelist->next; ! unlink(filelist->tmpfile); ! free(filelist->tmpfile); filelist = fl; } exit(code); } static void waitwaitwait() { --- 2488,2514 ---- while (filelist != (struct file_list *)0) { fl = filelist->next; ! if (filelist->tmpfile != (char *) NULL) { ! (void) unlink(filelist->tmpfile); ! free(filelist->tmpfile); ! } ! free(filelist); filelist = fl; } + + fflush(stdout); + sleep(1); + exit(code); } + /* + ** If this program is run as a daemon, it uses this function to catch + ** the signal saying that there are child processes returning. The + ** WNOHANG flag keeps it from waiting if there are no children for whom + ** to wait. + */ static void waitwaitwait() { *************** *** 764,780 **** } /* kludge for whitehouse.gov anti-spoofing bogosity */ ! #ifdef SPECIALDOMAIN ! #ifndef SYSV extern char *index(); extern char *rindex(); #endif extern char *strpbrk(); ! char *bad = "550 Recipient must be in form of: user, user@eop.gov, or user@whitehouse.gov\r\n"; ! checkvalid(r) char *r; { char *atp; char *jxp; --- 2523,2554 ---- } /* kludge for whitehouse.gov anti-spoofing bogosity */ ! /* activate by uncommenting the following line - cih */ ! /*#define WHITEHOUSE_GOV */ ! ! #ifdef SYSV ! #define index strchr ! #define rindex strrchr ! # else extern char *index(); extern char *rindex(); #endif extern char *strpbrk(); ! #ifdef WHITEHOUSE_GOV ! char *bad = "551 Recipient must be in form of: user, user@eop.gov, or user@whitehouse.gov\r\n"; ! # else ! char *bad = "551 Recipient must be in the local domain(s).\r\n"; ! #endif/*WHITEHOUSE_GOV*/ ! /* ! ** Check whether the recipient address in "r" is in a valid local ! ** domain, per either the compiled-in "WHITEHOUSE_GOV" specs or per the ! ** "domains" list in the config file. ! */ ! checkvalid(r,cfp) char *r; + Cfg *cfp; { char *atp; char *jxp; *************** *** 782,816 **** char *chsavp; int x; if((chop = malloc((x = strlen(r)) + 1)) == (char *)0) { ! unlink(tempfile); ! syslog(LLEV,"fwtksyserr: of memory: %m"); ! exit(1); } chsavp = chop; strcpy(chop,r); if(r[0] == '<') { if(chop[x - 1] == '>') chop[x - 1] = '\0'; chop++; } if((atp = rindex(chop,'@')) != (char *)0) { ! atp++; /* check if it ends in @whitehouse.gov || @eop.gov */ if(strcasecmp(atp,"whitehouse.gov") && strcasecmp(atp,"eop.gov")) goto bomb; /* now make sure there are no other routing chars */ - atp--; - *atp = '\0'; if((jxp = strpbrk(chop,"%@:[]!")) != (char *)0) { goto bomb; } } if((jxp = strpbrk(chop,"%@:[]!")) != (char *)0) goto bomb; --- 2556,2604 ---- char *chsavp; int x; + /* + ** Allocate a temporary buffer in which to chop up the address. + */ if((chop = malloc((x = strlen(r)) + 1)) == (char *)0) { ! syslog(LLEV,"fwtksyserr: out of memory: %m"); ! if (tempfile != (char *) NULL) ! (void) unlink(tempfile); ! smap_exit(1); } chsavp = chop; strcpy(chop,r); + /* If enclosed by <...>, remove the <>'s. */ if(r[0] == '<') { if(chop[x - 1] == '>') chop[x - 1] = '\0'; chop++; + x -= 2; } + /* Look at only the host/domain part. */ if((atp = rindex(chop,'@')) != (char *)0) { ! *atp++ = NUL; + /* the below was ifdef'ed out by cih */ + #ifdef WHITEHOUSE_GOV /* check if it ends in @whitehouse.gov || @eop.gov */ if(strcasecmp(atp,"whitehouse.gov") && strcasecmp(atp,"eop.gov")) goto bomb; + # else + /* Check if the host/domain is in the "domains" list. */ + if(!check_domain(atp,cfp)) + goto bomb; + #endif WHITEHOUSE_GOV + /* now make sure there are no other routing chars */ if((jxp = strpbrk(chop,"%@:[]!")) != (char *)0) { goto bomb; } } + if((jxp = strpbrk(chop,"%@:[]!")) != (char *)0) goto bomb; *************** *** 821,825 **** free(chsavp); return(0); } - #endif --- 2609,2850 ---- free(chsavp); return(0); } + + + /* bre 96/10/09 */ + + /* + ** The next few functions do very similar things. I won't try to + ** consolidate them ... yet. jsdy + *. + + /* + * check to see if the connector is in the list of spam hosts + * + ** This is only called if "smap: scrub-spam" is set. + ** + ** This routine uses the riaddr (IP address) discovered as the source + ** of this SMTP connection. It compares it against the names listed + ** as "smap: spam ...". If any matches - either as an IP address with + ** '*' wildcards, or as a host name with '*' wildcards [against which + ** the reverse-lookup of the IP address is compared ... this "should + ** work", because we verified the reverse lookup when we got the rladdr + ** / riaddr pair] - then we return "TRUE". E x c e p t if we read a + ** "smap: deny-spam ..."; then it returns FALSE. And if it's not on + ** the list, of course, we return FALSE ... + ** + ** I think that DENY bit is wrong. -jsdy + */ + check_spamhost(Cfg *cfp) + { + Cfg *cf; + int x; + + cf = cfg_get("spam",cfp); + while(cf != (Cfg *)0) { + for(x = 0; x < cf->argc; x++) { + if(cf->argv[x][0] == '-') + break; + if(hostmatch(cf->argv[x],riaddr)) { + if(cf->flags & PERM_DENY) { + return(FALSE); + } + return(TRUE); + } + } + + cf = cfg_get("spam",(Cfg*)0); + } + return(FALSE); + } + + /* + * check to see if the domain is in the list of spam domains + * (same config as spam-hosts) + * + ** This routine is called if check_from_address and scrub_spam are + ** true. If check_user_too is set, this is called on the WHOLE user + ** name; otherwise it is called on the domain ONLY. One or the other. + ** (This is probably an error - jsdy) + ** + ** This routine matches the argument passed as "domain" against the + ** names listed as "smap: spam ...". If any matches - either as an IP + ** address with '*' wildcards [against the IP address of the domain + ** passed, and if it's a user, it fails!], or as a host name with '*' + ** wildcards - then we return "TRUE". E x c e p t if we read a + ** "smap: deny-spam ..."; then it returns FALSE. And if it's not on + ** the list, of course, we return FALSE ... + ** + ** I think that DENY bit is wrong. -jsdy + */ + check_spamdomain(const char *domain, Cfg *cfp) + { + Cfg *cf; + int x; + + if (domain == (char *) NULL) + return(FALSE); + + cf = cfg_get("spam",cfp); + while(cf != (Cfg *)0) { + for(x = 0; x < cf->argc; x++) { + if(cf->argv[x][0] == '-') + break; + if(hostmatch(cf->argv[x],domain)) { + if(cf->flags & PERM_DENY) { + return(FALSE); + } + return(TRUE); + } + } + + cf = cfg_get("spam",(Cfg*)0); + } + return(FALSE); + } + + /* + * if the ip address/hostname is specified as local + * in the config file, then it may send to non-local addresses. + * cih 96/10/10 + * + ** This version of this program always sets 'from_host_local' from + ** this routine. + ** + ** This routine uses the riaddr (IP address) discovered as the source + ** of this SMTP connection. It compares it against the names listed + ** as "smap: hosts ...". If any matches - either as an IP address with + ** '*' wildcards, or as a host name with '*' wildcards [against which + ** the reverse-lookup of the IP address is compared ... this "should + ** work", because we verified the reverse lookup when we got the rladdr + ** / riaddr pair] - then we return "TRUE". E x c e p t if we read a + ** "smap: deny-hosts ..."; then it returns FALSE. And if it's not on + ** the list, of course, we return FALSE ... + */ + check_hostname(Cfg *cfp) + { + Cfg *cf; + int x; + + cf = cfg_get("hosts",cfp); + while(cf != (Cfg *)0) { + for(x = 0; x < cf->argc; x++) { + if(cf->argv[x][0] == '-') + break; + if(hostmatch(cf->argv[x],riaddr)) { + if(cf->flags & PERM_DENY) { + return(FALSE); + } + return(TRUE); + } + } + + cf = cfg_get("hosts",(Cfg*)0); + } + return(FALSE); + } + + + /* + * if the domain name is specified + * in the config file, then mail will be accepted to it from the outside + * cih 96/10/10 + * + ** This routine is called in two places. First, in checking from + ** addresses: the from address must be either on the local hosts list, + ** or it must NOT be a local domain, as specified here. [This may be + ** an error - I'll have to look at that.] Second, if the "from_host" + ** is not local - or always, if SPECIALDOMAIN is set on compile - then + ** this is called as part of checkvalid(), to make sure that the + ** recipient is in one of the local domains. + ** + ** This routine matches the argument passed as "atp" against the names + ** listed as "smap: domains ...". If any matches - either as an IP + ** address with '*' wildcards [against the IP address of the domain + ** passed, and if it's a user, it fails!], or as a host name with '*' + ** wildcards - then we return "TRUE". E x c e p t if we read a + ** "smap: deny-domains ..."; then it returns FALSE. And if it's not on + ** the list, of course, we return FALSE ... + */ + check_domain(char *atp,Cfg *cfp) + { + Cfg *cf; + int x; + + cf = cfg_get("domains",cfp); + while(cf != (Cfg *)0) { + for(x = 0; x < cf->argc; x++) { + if(cf->argv[x][0] == '-') + break; + if(hostmatch(cf->argv[x],atp)) { + if(cf->flags & PERM_DENY) { + syslog(LLEV,"deny host=%s/%s use of gateway",rladdr,riaddr); + return(FALSE); + } + syslog(LLEV,"permit host=%s/%s use of gateway",rladdr,riaddr); + return(TRUE); + } + } + + cf = cfg_get("domains",(Cfg*)0); + } + syslog(LLEV,"deny host=%s/%s use of gateway",rladdr,riaddr); + return(FALSE); + } + + + #ifdef PROTECTINBOUND + /* + ** "smap: spam-block deny sender-address recipient-address" will refuse + ** all mail that comes from the sender to the recipient. Sender and + ** recipient addresses in this line may have '*' wildcards. + ** This routine uses nacasematch(), which was only introduced in 2.*; + ** so this option will not be available to 1.3- sites. + */ + int + check_protected_internal(sender, recip, cfp) + char *sender; + char *recip; + Cfg *cfp; + + { + Cfg *cf; + static int first_time = TRUE; + + cf = cfg_get("spam-block", cfp); + while ( cf != (Cfg *)0) + { + if (cf->argc != 3) { + if (first_time) { + syslog(LLEV, "fwtkcfgerr: spam-block must have 3 arguments, line %d", cf->ln); + } + } + else + { + /* + ** Case-independent pattern matching is only in + ** FWTK 2.0 and ff.; so if your base is 1.3, + ** don't try to compile this in. + */ + if(nacasematch(cf->argv[1],sender) && nacasematch(cf->argv[2],recip)) { + /*take a conservative approach on the following, we only + want to deny if we are sure that the action was deny, if the + user mis-spells deny, then we'll go ahead and let it through. + We are much less likely to experience a problem with lost + email this way. + */ + if (strcasecmp(cf->argv[0],"deny") == 0) { + return(1); /*yes, it is a protected internal address*/ + } + else { + return(0); /*message allowed*/ + } + } + } + cf = cfg_get("spam-block", (Cfg *)0 ); + } + first_time = FALSE; + return(0); /*no matches found*/ + } + #endif /*PROTECTINBOUND*/