/* * Based on amavis-milter.c,v 1.1.2.3.2.40 2003/06/06 12:34:58 lhecking Exp */ /* * sendmail/milter client for amavis * amavisd version * * Author: Geoff Winkless * Additional work and patches by: * Gregory Ade * Anne Bennett * Thomas Biege * Pierre-Yves Bonnetain * Lars Hecking * Rainer Link * Dale Perkel * Julio Sanchez * */ /* * Add some copyright notice here ... * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SM_GEN_H # include "sm/gen.h" #endif #include "libmilter/mfapi.h" #ifndef HAVE_SM_GEN_BOOL_TYPE typedef int bool; #endif #define BUFFLEN 255 /* Must be the same as the buffer length for recv() in amavisd */ #define SOCKBUFLEN 8192 #ifndef RUNTIME_DIR # define RUNTIME_DIR "/var/amavis" #endif #ifndef AMAVISD_SOCKET # define AMAVISD_SOCKET RUNTIME_DIR ## "/amavisd.sock" #endif #define D_TEMPLATE "/amavis-milter-XXXXXXXX" #define F_TEMPLATE "/email.txt" #define DEVNULL "/dev/null" /* #ifndef AMAVIS_USER * # define AMAVIS_USER "amavis" * #endif * #ifndef MILTER_SOCKET_GROUP * # define MILTER_SOCKET_GROUP "amavis" * #endif */ /* Extracted from the code for better configurability * These will be set by configure/make eventually */ #ifndef X_HEADER_TAG # define X_HEADER_TAG "X-Virus-Scanned" #endif #ifndef X_HEADER_LINE # define X_HEADER_LINE "by amavisd-milter (http://www.amavis.org/)" #endif #define DBG_NONE 0 #define DBG_FATAL 1 #define DBG_WARN 2 #define DBG_INFO 3 #define DBG_DEBUG 4 typedef struct llstrct { char *str; struct llstrct *next; } ll; struct mlfiPriv { struct in_addr client_addr; char *mlfi_fname; FILE *mlfi_fp; char *mlfi_envfrom; ll mlfi_envto; ll *mlfi_thisenvto; int mlfi_numto; }; static int verbosity; static int AM_DAEMON = 1; static struct group *miltergroup; static gid_t amavis_gid; static struct utsname amavis_uts; static int enable_x_header = 1; /* enabled by default */ static void amavis_syslog(const int, const char *, ...); static char *amavis_mkdtemp(char *); static int group_member(const char *); static void freeenvto(ll *); static sfsistat clearpriv(SMFICTX *, sfsistat, int); static int allocmem(SMFICTX *); static sfsistat mlfi_connect(SMFICTX *, char *, _SOCK_ADDR *); static sfsistat mlfi_envto(SMFICTX *, char **); static sfsistat mlfi_envfrom(SMFICTX *, char **); static sfsistat mlfi_header(SMFICTX *, char *, char *); static sfsistat mlfi_eoh(SMFICTX *); static sfsistat mlfi_body(SMFICTX *, u_char *, size_t); static sfsistat mlfi_eom(SMFICTX *); static sfsistat mlfi_close(SMFICTX *); static sfsistat mlfi_abort(SMFICTX *); static sfsistat mlfi_cleanup(SMFICTX *, bool); static void amavis_syslog(const int level, const char *fmt, ...) { time_t tmpt; char *timestamp; char buf[512]; va_list ap; if (level > verbosity) return; buf[0] = 0; va_start(ap, fmt); if (AM_DAEMON == 0) { tmpt = time(NULL); timestamp = ctime(&tmpt); /* A 26 character string according ctime(3c) * we cut off the trailing \n\0 */ timestamp[24] = 0; snprintf(buf,sizeof(buf)-1,"%s %s amavis-milter[%ld]: ", timestamp, (amavis_uts.nodename ? amavis_uts.nodename : "localhost"), (long) getpid()); } vsnprintf(buf+strlen(buf),sizeof(buf)-strlen(buf)-1,fmt,ap); va_end(ap); if (AM_DAEMON == 0) { fprintf(stderr,"%s\n",buf); } openlog("amavis-milter", LOG_PID|LOG_CONS, LOG_MAIL); syslog(LOG_NOTICE,"%s\n",buf); closelog(); } static char * amavis_mkdtemp(char *s) { char *stt; int count = 0; #ifdef HAVE_MKDTEMP return mkdtemp(s); #else /* magic number alert */ while (count++ < 20) { # ifdef HAVE_MKTEMP stt = mktemp(s); # else /* relies on template format */ stt = strrchr(s, '-') + 1; if (stt) { /* more magic number alert */ snprintf(stt, strlen(s) - 1 - (stt - s), "%08d", lrand48() / 215); stt = s; } else { /* invalid template */ return NULL; } # endif if (stt) { if (!mkdir(s, S_IRWXU)) { return s; } else { continue; } } } return NULL; #endif /* HAVE_MKDTEMP */ } static int group_member(const char *group) { int i, r, rc = -1; gid_t *grouplist = 0; if (!(miltergroup = getgrnam(group))) { perror("getgrnam"); return rc; } if ((r = getgroups(0, grouplist)) < 0) { perror("getgroups"); } else if ((grouplist = malloc (r*sizeof(gid_t))) == NULL) { perror("malloc"); r = 0; } else if ((r = getgroups(r, grouplist)) < 0) { perror("getgroups"); free(grouplist); } for (i=0;igr_gid == grouplist[i]) { rc = 0; break; } } if (grouplist) free(grouplist); return rc; } #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) static void freeenvto(ll * envto) { ll *new; while (envto) { new = envto->next; if (envto->str) { free(envto->str); envto->str = NULL; } if (envto) free(envto); envto = new; } } static sfsistat clearpriv(SMFICTX *ctx, sfsistat retme, int clearall) { /* release private memory and return retme */ struct mlfiPriv *priv = MLFIPRIV; amavis_syslog(DBG_INFO, "Clearing priv (clearall=%d)", clearall); if (priv) { if (priv->mlfi_fname) { amavis_syslog(DBG_INFO, "clearing fname"); free(priv->mlfi_fname); priv->mlfi_fname = NULL; } if (priv->mlfi_envfrom) { amavis_syslog(DBG_INFO, "clearing envfrom"); free(priv->mlfi_envfrom); priv->mlfi_envfrom = NULL; } if (priv->mlfi_envto.next) { amavis_syslog(DBG_INFO, "clearing multi-envto"); freeenvto(priv->mlfi_envto.next); priv->mlfi_envto.next = NULL; } if (priv->mlfi_envto.str) { amavis_syslog(DBG_INFO, "clearing envto"); free(priv->mlfi_envto.str); priv->mlfi_envto.str = NULL; } amavis_syslog(DBG_INFO, "clearing priv"); free(priv); priv = NULL; if (smfi_setpriv(ctx, priv) != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(clearpriv)smfi_setpriv failed"); } } return retme; } /* * allocate some private memory if not already allocated * returns 0 if ok, 1 if not */ static int allocmem(SMFICTX * ctx) { struct mlfiPriv *priv = MLFIPRIV; if (priv == NULL) { amavis_syslog(DBG_INFO, "(allocmem)priv was null"); priv = malloc(sizeof *priv); if (priv == NULL) { /* can't accept this message right now */ amavis_syslog(DBG_FATAL, "failed to malloc %d bytes for private store: %s", sizeof(*priv), strerror(errno)); return 1; } amavis_syslog(DBG_INFO, "malloced priv - now using memset()"); memset(priv, 0, sizeof *priv); amavis_syslog(DBG_INFO, "malloced priv successfully"); if (smfi_setpriv(ctx, priv) != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(allocmem)smfi_setpriv failed"); } } else { amavis_syslog(DBG_WARN, "allocmem tried but priv was already set"); amavis_syslog(DBG_WARN, "priv->client_addr.s_addr is %d", priv->client_addr.s_addr); } return 0; } static sfsistat mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * gen_hostaddr) { struct mlfiPriv *priv; struct sockaddr_in *hostaddr; hostaddr = (struct sockaddr_in *) gen_hostaddr; if (hostaddr) { amavis_syslog(DBG_INFO, "hostname is %s, addr is %d.%d.%d.%d", hostname, (hostaddr->sin_addr.s_addr) & 0xff, (hostaddr->sin_addr.s_addr >> 8) & 0xff, (hostaddr->sin_addr.s_addr >> 16) & 0xff, (hostaddr->sin_addr.s_addr >> 24) & 0xff); } amavis_syslog(DBG_INFO, "checking allocmem"); if (allocmem(ctx)) return SMFIS_TEMPFAIL; priv = MLFIPRIV; if (hostaddr) { priv->client_addr.s_addr = hostaddr->sin_addr.s_addr; } else { priv->client_addr.s_addr = 0; } /* not really needed but nice */ if (smfi_setpriv(ctx, priv) != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(mlfi_connect)smfi_setpriv failed"); } return SMFIS_CONTINUE; } static sfsistat mlfi_envto(SMFICTX * ctx, char **envto) { struct mlfiPriv *priv; if (allocmem(ctx)) return SMFIS_TEMPFAIL; priv = MLFIPRIV; if (!(priv->mlfi_thisenvto)) { /* first one... */ priv->mlfi_thisenvto = &(priv->mlfi_envto); priv->mlfi_numto = 1; } else { priv->mlfi_numto++; if ((priv->mlfi_thisenvto->next = malloc(sizeof(ll))) == NULL) return (SMFIS_TEMPFAIL); priv->mlfi_thisenvto = priv->mlfi_thisenvto->next; priv->mlfi_thisenvto->next = NULL; } if ((priv->mlfi_thisenvto->str = strdup(*envto)) == NULL) return (SMFIS_TEMPFAIL); amavis_syslog(DBG_INFO, "added %s as recip", *envto); return SMFIS_CONTINUE; } static sfsistat mlfi_envfrom(SMFICTX * ctx, char **envfrom) { struct mlfiPriv *priv; struct stat StatBuf; char *messagepath; if (allocmem(ctx)) return SMFIS_TEMPFAIL; priv = MLFIPRIV; /* tmp dir */ messagepath = malloc(strlen(RUNTIME_DIR) + strlen(D_TEMPLATE) + strlen(F_TEMPLATE) + 1); if (messagepath == NULL) { amavis_syslog(DBG_FATAL, "Failed to allocate memory for temp file name: %s", strerror(errno)); return SMFIS_TEMPFAIL; } strcpy(messagepath, RUNTIME_DIR); strcat(messagepath, D_TEMPLATE); /* umask(0077); */ umask(0007); if (amavis_mkdtemp(messagepath) == NULL) { amavis_syslog(DBG_FATAL, "Failed to create temp dir %s: %s", messagepath, strerror(errno)); return SMFIS_TEMPFAIL; } /* if (chown(messagepath, (uid_t)-1, amavis_gid) < 0) { * amavis_syslog(DBG_FATAL, "Failed to adjust %s group ownership (%d): %s", * messagepath, amavis_gid, strerror(errno)); * return SMFIS_TEMPFAIL; * } */ umask (0007); if (lstat(messagepath, &StatBuf) < 0) { amavis_syslog(DBG_FATAL, "Error while trying lstat(%s): %s", messagepath, strerror(errno)); return SMFIS_TEMPFAIL; } /* may be too restrictive for you, but's good to avoid problems */ if (!S_ISDIR(StatBuf.st_mode) || StatBuf.st_uid != geteuid() || StatBuf.st_gid != getegid() || !(StatBuf.st_mode & S_IRWXU)) { amavis_syslog(DBG_FATAL, "Security Warning: %s must be a directory owned by " "User %d and Group %d, and read-/write-able by the User " "only. Exit.", messagepath, geteuid(), getegid()); return SMFIS_TEMPFAIL; } /* there is still a race condition here if RUNTIME_DIR is writeable by the attacker :-\ */ /* tmp file name */ strcat(messagepath, F_TEMPLATE); amavis_syslog(DBG_INFO, "received: %s; from=%s", messagepath, *envfrom); priv->mlfi_fname = strdup(messagepath); free(messagepath); if (priv->mlfi_fname == NULL) { return SMFIS_TEMPFAIL; } priv->mlfi_envfrom = strdup(*envfrom); if (!priv->mlfi_envfrom) { free(priv->mlfi_fname); priv->mlfi_fname = NULL; return SMFIS_TEMPFAIL; } if ((priv->mlfi_fp = fopen(priv->mlfi_fname, "w+")) == NULL) { free(priv->mlfi_fname); priv->mlfi_fname = NULL; free(priv->mlfi_envfrom); priv->mlfi_envfrom = NULL; return SMFIS_TEMPFAIL; } /* save the private data */ if (smfi_setpriv(ctx, priv) != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(mlfi_envfrom)smfi_setpriv failed"); } /* continue processing */ return SMFIS_CONTINUE; } static sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) { /* write the header to the log file */ fprintf(MLFIPRIV->mlfi_fp, "%s: %s\n", headerf, headerv); /* continue processing */ return SMFIS_CONTINUE; } static sfsistat mlfi_eoh(SMFICTX *ctx) { /* output the blank line between the header and the body */ fprintf(MLFIPRIV->mlfi_fp, "\n"); /* continue processing */ return SMFIS_CONTINUE; } static sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) { /* output body block to log file */ u_char *d = bodyp, *s = bodyp; u_char *lastc = bodyp + bodylen - 1; /* convert crlf to lf */ while (s <= lastc) { if (s != lastc && *s == 13 && *(s+1) == 10) s++; *d++ = *s++; } bodylen = (size_t)(d - bodyp); if (bodylen && fwrite(bodyp, bodylen, 1, MLFIPRIV->mlfi_fp) <= 0) { /* write failed */ (void) mlfi_cleanup(ctx, 0); return SMFIS_TEMPFAIL; } /* continue processing */ return SMFIS_CONTINUE; } /* Simple "protocol" */ const char _EOT = '\3'; static sfsistat mlfi_eom(SMFICTX *ctx) { struct mlfiPriv *priv = MLFIPRIV; char buff[7]; int sock, r; char *sender; char retval; struct sockaddr_un saddr; if (!priv) { /* no priv object */ amavis_syslog(DBG_WARN, "couldn't scan - no priv object"); return clearpriv(ctx, SMFIS_TEMPFAIL, 0); } amavis_syslog(DBG_INFO, "mlfi_eom()"); /* close the file so we can run checks on it!!! */ if (priv->mlfi_fp) fclose(priv->mlfi_fp); /* AFAIK, AF_UNIX is obsolete. POSIX defines AF_LOCAL */ saddr.sun_family = AF_UNIX; if (strlen(AMAVISD_SOCKET)+1 > sizeof(saddr.sun_path)) { amavis_syslog(DBG_FATAL, "socket path too long: %d", strlen(AMAVISD_SOCKET)); exit(EX_TEMPFAIL); } strcpy(saddr.sun_path, AMAVISD_SOCKET); amavis_syslog(DBG_INFO, "allocate socket()"); r = (sock = socket(PF_UNIX, SOCK_STREAM, 0)); if (r < 0) { amavis_syslog(DBG_FATAL, "failed to allocate socket: %s", strerror(errno)); } if (r >= 0) { amavis_syslog(DBG_INFO, "mlfi_eom:connect"); r = connect(sock, (struct sockaddr *) (&saddr), sizeof(saddr)); if (r < 0) amavis_syslog(DBG_FATAL, "failed to connect(): %s", strerror(errno)); } if (r >= 0) { char *p = strrchr(priv->mlfi_fname, '/'); amavis_syslog(DBG_INFO, "mlfi_eom:sendfile"); /* amavisd wants the directory, not the filename */ *p = 0; r = send(sock, priv->mlfi_fname, strlen(priv->mlfi_fname), 0); *p = '/'; if (r < 0) amavis_syslog(DBG_FATAL, "failed to send() file name: %s", strerror(errno)); } if (r >= 0) { r = recv(sock, &retval, 1, 0); if (r < 0) amavis_syslog(DBG_FATAL, "failed to recv() file name confirmation: %s", strerror(errno)); } if (r >= 0) { size_t sender_l; sender = (strlen(priv->mlfi_envfrom) > 0) ? priv->mlfi_envfrom : "<>"; amavis_syslog(DBG_INFO, "sendfrom() %s", sender); sender_l = strlen(sender); if (sender_l > SOCKBUFLEN) { amavis_syslog(DBG_WARN, "Sender too long (%d), truncated to %d characters", sender_l, SOCKBUFLEN); sender_l = SOCKBUFLEN; } r = send(sock, sender, sender_l, 0); if (r < 0) amavis_syslog(DBG_FATAL, "failed to send() Sender: %s", strerror(errno)); else if (r < sender_l) amavis_syslog(DBG_WARN, "failed to send() complete Sender, truncated to %d characters ", r); } if (r >= 0) { r = recv(sock, &retval, 1, 0); if (r < 0) amavis_syslog(DBG_FATAL, "failed to recv() ok for Sender info: %s", strerror(errno)); } if (r >= 0) { int x; priv->mlfi_thisenvto = &(priv->mlfi_envto); for (x = 0; (r >= 0) && (x < priv->mlfi_numto); x++) { size_t recipient_l; amavis_syslog(DBG_INFO, "sendto() %s", priv->mlfi_thisenvto->str); recipient_l = strlen(priv->mlfi_thisenvto->str); if (recipient_l > SOCKBUFLEN) { amavis_syslog(DBG_WARN, "Recipient too long (%d), truncated to %d characters", recipient_l,SOCKBUFLEN); recipient_l = SOCKBUFLEN; } r = send(sock, priv->mlfi_thisenvto->str, recipient_l, 0); if (r < 0) amavis_syslog(DBG_FATAL, "failed to send() Recipient: %s", strerror(errno)); else { if (r < recipient_l) amavis_syslog(DBG_WARN, "failed to send() complete Recipient, truncated to %d characters ", r); r = recv(sock, &retval, 1, 0); if (r < 0) amavis_syslog(DBG_FATAL, "failed to recv() ok for recip info: %s", strerror(errno)); priv->mlfi_thisenvto = priv->mlfi_thisenvto->next; } } } if (r >= 0) { amavis_syslog(DBG_INFO, "send() EOT"); r = send(sock, &_EOT, 1, 0); /* send "end of args" msg */ if (r < 0) { amavis_syslog(DBG_FATAL, "failed to send() EOT: %s", strerror(errno)); } else { /* get result from amavisd */ r = recv(sock, buff, 6, 0); amavis_syslog(DBG_INFO, "received %s from daemon", buff); if (r < 0) amavis_syslog(DBG_FATAL, "Failed to recv() final result: %s", strerror(errno)); else if (!r) amavis_syslog(DBG_FATAL, "Failed to recv() final result: empty status string"); /* get back final result */ } } close(sock); if (r < 0) { amavis_syslog(DBG_FATAL, "communication failure"); return clearpriv(ctx, SMFIS_TEMPFAIL, 0); /* some point of the communication failed miserably - so give up */ } amavis_syslog(DBG_INFO, "finished conversation"); /* Protect against empty return string */ if (*buff) retval = atoi(buff); else retval = 1; amavis_syslog(DBG_INFO, "retval is %d", retval); if (retval == 99) { /* there was a virus * discard it so it doesn't go bouncing around!! */ amavis_syslog(DBG_WARN, "discarding mail"); return clearpriv(ctx, SMFIS_DISCARD, 0); } if (retval == EX_UNAVAILABLE) { /* REJECT handling */ /* by Didi Rieder and Mark Martinec */ amavis_syslog(DBG_WARN, "rejecting mail"); if (smfi_setreply(ctx, "550", "5.7.1", "Message content rejected") != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(mlfi_eom)smfi_setreply failed"); } return clearpriv(ctx, SMFIS_REJECT, 0); } if (retval == 0) { if (enable_x_header) { if (smfi_chgheader(ctx, X_HEADER_TAG, 1, X_HEADER_LINE) == MI_FAILURE) { amavis_syslog(DBG_INFO, "adding header"); if (smfi_addheader(ctx, X_HEADER_TAG, X_HEADER_LINE) != MI_SUCCESS) { /* Not sure what we need to do here */ amavis_syslog(DBG_WARN, "(mlfi_eom)smfi_addheader failed"); } } else amavis_syslog(DBG_INFO, "header already present"); } amavis_syslog(DBG_WARN, "returning ACCEPT"); return clearpriv(ctx, SMFIS_ACCEPT, 0); } /* if we got any exit status but 0, we didn't check the file... * so don't add the header. We return TEMPFAIL instead */ amavis_syslog(DBG_WARN, "returning TEMPFAIL"); return clearpriv(ctx, SMFIS_TEMPFAIL, 0); } static sfsistat mlfi_close(SMFICTX *ctx) { return clearpriv(ctx, SMFIS_ACCEPT, 1); } static sfsistat mlfi_abort(SMFICTX *ctx) { return mlfi_cleanup(ctx, 0); } static sfsistat mlfi_cleanup(SMFICTX *ctx, bool ok) { sfsistat rstat = SMFIS_CONTINUE; struct mlfiPriv *priv = MLFIPRIV; char *p; if (!priv) return rstat; /* close the archive file */ if (priv->mlfi_fp != NULL && fclose(priv->mlfi_fp) == EOF) { /* failed; we have to wait until later */ rstat = SMFIS_TEMPFAIL; (void) unlink(priv->mlfi_fname); *(strrchr(priv->mlfi_fname, '/')) = 0; rmdir(priv->mlfi_fname); } else if (ok) { p = strrchr(priv->mlfi_fname, '/'); if (p == NULL) p = priv->mlfi_fname; else p++; } else { /* message was aborted -- delete the archive file */ (void) unlink(priv->mlfi_fname); *strrchr(priv->mlfi_fname, '/') = 0; rmdir(priv->mlfi_fname); } /* return status */ amavis_syslog(DBG_INFO, "cleanup called"); return clearpriv(ctx, rstat, 0); } struct smfiDesc smfilter = { "amavis-milter", /* filter name */ SMFI_VERSION, /* version code -- do not change */ SMFIF_ADDHDRS|SMFIF_CHGHDRS,/* flags */ mlfi_connect, /* connection info filter */ NULL, /* SMTP HELO command filter */ mlfi_envfrom, /* envelope sender filter */ mlfi_envto, /* envelope recipient filter */ mlfi_header, /* header filter */ mlfi_eoh, /* end of header */ mlfi_body, /* body block filter */ mlfi_eom, /* end of message */ mlfi_abort, /* message aborted */ mlfi_close /* connection cleanup */ }; void usage(void) { fprintf(stderr, "usage:\n"); fprintf(stderr, " amavis-milter -p local: [-d] [-v]\n"); fprintf(stderr, " amavis-milter -p inet:port@0.0.0.0 [-d] [-v]\n"); fprintf(stderr, " amavis-milter -h\n"); fprintf(stderr, "\n"); fprintf(stderr, "-p specifies a milter socket on which amavis-milter\n"); fprintf(stderr, " will listen for connections from sendmail.\n"); fprintf(stderr, " The argument is passed directly to libmilter, see sendmail milter\n"); fprintf(stderr, " documentation for details. The socket specified must match the\n"); fprintf(stderr, " INPUT_MAIL_FILTER macro call in the sendmail configuration file.\n"); fprintf(stderr, "-d debug: disables daemonisation and turns log level fully up (-vvvv) \n"); fprintf(stderr, "-v increases logging level by one, may be specified up to 4 times\n"); fprintf(stderr, "-h help: displays this usage text and exits\n"); fprintf(stderr, "\n"); fprintf(stderr, "Options -g, -x, -D are allowed for compatibility but ignored.\n"); fprintf(stderr, "\n"); fprintf(stderr, "This helper prgram (milter daemon) is normally started as:\n"); fprintf(stderr, "# su amavis -c '/usr/local/sbin/amavis-milter -p local:/var/amavis/amavis-milter.sock'\n"); }; int main(int argc, char *argv[]) { struct passwd *userinfo; /* amavis uid */ int c, i; char *p, *milter_socket = NULL, *milter_socket_group = NULL; /* const char *args = "dg:p:vx"; */ const char *args = ":hdg:p:Dvx"; /* some mix of old and new options!!! */ pid_t pid; int devnull; #if !defined(HAVE_MKDTEMP) && !defined(HAVE_MKTEMP) int mypid = getpid(); srand48(time(NULL) ^ (mypid + (mypid << 15))); #endif umask(0007); /* Process command line options */ while ((c = getopt(argc, argv, args)) != -1) { switch (c) { case 'd': /* don't daemonise, log to stderr */ verbosity = DBG_DEBUG; /* full debugging log level */ AM_DAEMON = 0; break; case 'g': /* name of milter socket group owner */ if (optarg == NULL || *optarg == '\0') { fprintf(stderr, "%s: Illegal group: %s\n", argv[0], optarg); } fprintf(stderr, "%s: group specification ignored (not implemented)\n", argv[0]); milter_socket_group = strdup(optarg); break; case 'p': /* socket name - see smfi_setconn man page */ if (optarg == NULL || *optarg == '\0') { fprintf(stderr, "%s: Illegal conn: %s\n", argv[0], optarg); exit(EXIT_FAILURE); } milter_socket = strdup(optarg); break; case 'v': verbosity++; break; case 'D': AM_DAEMON = 1; /* which is also a default, unless debugging */ break; case 'x': /* enable_x_header++; */ /* older versions */ /* enable_x_header = 0;*/ /* since 1.1.2.3.2.40 */ fprintf(stderr, "%s: option -x ignored to avoid confusion with older versions\n", argv[0]); break; case 'h': usage(); exit(EXIT_SUCCESS); break; default: usage(); exit(EXIT_FAILURE); } } if (smfi_register(smfilter) == MI_FAILURE) { fprintf(stderr, "%s: smfi_register failed\n", argv[0]); exit(EXIT_FAILURE); } uname(&amavis_uts); /* check user and group */ /* if (!(userinfo = getpwnam(AMAVIS_USER))) { * perror("getpwnam"); * exit(EXIT_FAILURE); * } * amavis_gid = userinfo->pw_gid; * if (!milter_socket_group) { * milter_socket_group = strdup(MILTER_SOCKET_GROUP); * if (!milter_socket_group) { * perror("strdup"); * exit(EXIT_FAILURE); * } * } * if (group_member(milter_socket_group) < 0) { * fprintf(stderr, "%s not member of %s group\n", AMAVIS_USER, milter_socket_group); * exit(EXIT_FAILURE); * } */ if (!milter_socket) { fprintf(stderr, "%s: no milter socket specified (missing option -p)\n\n", argv[0]); usage(); exit(EXIT_FAILURE); } /* check socket */ if ((p = strchr(milter_socket,'/'))) { /* Unlink any existing file that might be in place of * the socket we want to create. This might not exactly * be safe, or friendly, but I'll deal with that later. * Be nice and issue a warning if we have a problem, but * other than that, ignore it. */ if (unlink(p) < 0) { /* perror("Cannot unlink socket"); */ amavis_syslog(DBG_INFO, "INFO: Cannot unlink old socket %s: %s", milter_socket, strerror(errno)); } } /* Errors are detected in smfi_main */ if (smfi_setconn(milter_socket) != MI_SUCCESS) { amavis_syslog(DBG_FATAL, "(main)smfi_setconn failed"); } /* See if we're supposed to become a daemonized process */ if (AM_DAEMON == 1) { /* 2001/11/09 Anne Bennett: daemonize properly. * OK, let's be a real daemon. Taken from page 417 * of Stevens' "Advanced Programming in the UNIX Environment". */ /* Step 1: Fork and have parent exit. This not only * backgrounds us but makes sure we are not a process group * leader. */ /* Fork ourselves into the background, and see if it worked */ if ((pid = fork()) > 0) { amavis_syslog(DBG_INFO, "amavis-milter forked into background"); /* We are the parent; exit. */ exit(EXIT_SUCCESS); } else if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } /* OK, we're backgrounded. * Step 2: Call setsid to create a new session. This makes * sure among other things that we have no controlling * terminal. */ if (setsid() < (pid_t)0) { amavis_syslog(DBG_FATAL, "setsid() returned error: %s", strerror(errno)); exit(EXIT_FAILURE); } /* Step 3: Set the working directory appropriately. */ if (chdir("/") < 0 ) { amavis_syslog(DBG_FATAL, "chdir(/) returned error: %s", strerror(errno)); exit(EXIT_FAILURE); } /* Step 4: Close all file descriptors. */ for (i = 0; i < _POSIX_OPEN_MAX ; i++) { close(i); } /* Open /dev/null read-only (fd 0 = STDIN) */ if ((devnull = open(DEVNULL, O_RDONLY, 0)) < 0) { amavis_syslog(DBG_FATAL, "Could not open %s as STDIN: %s", DEVNULL, strerror(errno)); exit(EXIT_FAILURE); } if (devnull != 0) { amavis_syslog(DBG_FATAL, "Got wrong file descriptor as STDIN: %s != 0", DEVNULL); exit(EXIT_FAILURE); } /* Open /dev/null write-only (fd 1 = STDOUT) */ if ((devnull = open(DEVNULL, O_WRONLY, 0)) < 0) { amavis_syslog(DBG_FATAL, "Could not open %s as STDOUT: %s", DEVNULL, strerror(errno)); exit(EXIT_FAILURE); } if (devnull != 1) { amavis_syslog(DBG_FATAL, "Got wrong file descriptor as STDOUT: %s != 1", DEVNULL); exit(EXIT_FAILURE); } /* Open /dev/null write-only (fd 2 = STDERR) */ if ((devnull = open(DEVNULL, O_WRONLY, 0)) < 0) { amavis_syslog(DBG_FATAL, "Could not open %s as STDERR: %s", DEVNULL, strerror(errno)); exit(EXIT_FAILURE); } if (devnull != 2) { amavis_syslog(DBG_FATAL, "Got wrong file descriptor as STDERR: %s != 2", DEVNULL); exit(EXIT_FAILURE); } } /* change process group id */ if (miltergroup && (setgid(miltergroup->gr_gid)) < 0) { amavis_syslog(DBG_FATAL, "setgid(%d): %s", miltergroup->gr_gid, strerror(errno)); exit(EX_UNAVAILABLE); } /* hand control over to libmilter */ amavis_syslog(DBG_INFO, "Starting, handing off to smfi_main"); return smfi_main(); } /* eof */