clamav-milter/clamav-milter.c
e3aaff8e
 /*
4c237bcf
  *  Copyright (C)2008 Sourcefire, Inc.
e3aaff8e
  *
4c237bcf
  *  Author: aCaB <acab@clamav.net>
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
4c237bcf
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
7908713f
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
4c237bcf
 #include <sys/types.h>
 #include <unistd.h>
e004f1c5
 #include <pwd.h>
3aa15b4c
 #include <grp.h>
4c237bcf
 #include <string.h>
aa633fa0
 #ifdef USE_SYSLOG
 #include <syslog.h>
 #endif
4c237bcf
 #include <time.h>
 #include <libmilter/mfapi.h>
eef726b0
 
4c237bcf
 #include "clamav.h"
e3aaff8e
 
4c237bcf
 #include "shared/output.h"
278dc6b3
 #include "shared/optparser.h"
4c237bcf
 #include "shared/misc.h"
ec34b80f
 #include "libclamav/default.h"
4c237bcf
 
 #include "connpool.h"
 #include "netcode.h"
 #include "clamfi.h"
3eb16511
 #include "whitelist.h"
4c237bcf
 
82d4fa94
 struct smfiDesc descr;
e004f1c5
 
4c237bcf
 int main(int argc, char **argv) {
278dc6b3
     char *my_socket, *pt;
     const struct optstruct *opt;
     struct optstruct *opts;
e9c4dd09
     time_t currtime;
e394c513
     mode_t umsk;
4c237bcf
     int ret;
 
82d4fa94
     memset(&descr, 0, sizeof(struct smfiDesc));
     descr.xxfi_name = "ClamAV";			/* filter name */
     descr.xxfi_version = SMFI_VERSION;		/* milter version */
3521624b
     descr.xxfi_flags = SMFIF_QUARANTINE;	/* flags */
82d4fa94
     descr.xxfi_connect = clamfi_connect;	/* connection info filter */
     descr.xxfi_envfrom = clamfi_envfrom;	/* envelope sender filter */
     descr.xxfi_envrcpt = clamfi_envrcpt;	/* envelope recipient filter */
     descr.xxfi_header = clamfi_header;		/* header filter */
     descr.xxfi_body = clamfi_body;		/* body block */
     descr.xxfi_eom = clamfi_eom;		/* end of message */
     descr.xxfi_abort = clamfi_abort;		/* message aborted */
 
278dc6b3
     opts = optparse(NULL, argc, argv, 1, OPT_MILTER, 0, NULL);
     if (!opts) {
 	mprintf("!Can't parse command line options\n");
a4371160
 	return 1;
4c237bcf
     }
 
278dc6b3
     if(optget(opts, "help")->enabled) {
4c237bcf
 	printf("Usage: %s [-c <config-file>]\n\n", argv[0]);
 	printf("    --help                   -h       Show this help\n");
 	printf("    --version                -V       Show version and exit\n");
 	printf("    --config-file <file>     -c       Read configuration from file\n\n");
278dc6b3
 	optfree(opts);
44d08756
 	return 0;
4c237bcf
     }
19c17946
 
     if(opts->filename) {
 	int x;
 	for(x = 0; opts->filename[x]; x++)
 	    mprintf("^Ignoring option %s\n", opts->filename[x]);
     }
4c237bcf
 
278dc6b3
     if(optget(opts, "version")->enabled) {
4c237bcf
 	printf("clamav-milter %s\n", get_version());
278dc6b3
 	optfree(opts);
4c237bcf
 	return 0;
     }
44ba5c0e
 
278dc6b3
     pt = strdup(optget(opts, "config-file")->strarg);
     if((opts = optparse(pt, 0, NULL, 1, OPT_MILTER, 0, opts)) == NULL) {
 	printf("%s: cannot parse config file %s\n", argv[0], pt);
 	free(pt);
4c237bcf
 	return 1;
     }
278dc6b3
     free(pt);
684d3122
 
278dc6b3
     if((opt = optget(opts, "Chroot"))->enabled) {
 	if(chdir(opt->strarg) != 0) {
 	    logg("!Cannot change directory to %s\n", opt->strarg);
f7203529
 	    return 1;
 	}
278dc6b3
 	if(chroot(opt->strarg) != 0) {
 	    logg("!chroot to %s failed. Are you root?\n", opt->strarg);
f7203529
 	    return 1;
 	}
     }
 
278dc6b3
     if(geteuid() == 0 && (opt = optget(opts, "User"))->enabled) {
4c237bcf
         struct passwd *user = NULL;
278dc6b3
 	if((user = getpwnam(opt->strarg)) == NULL) {
 	    fprintf(stderr, "ERROR: Can't get information about user %s.\n", opt->strarg);
 	    optfree(opts);
4c237bcf
 	    return 1;
684d3122
 	}
28071296
 
278dc6b3
 	if(optget(opts, "AllowSupplementaryGroups")->enabled) {
4c237bcf
 #ifdef HAVE_INITGROUPS
278dc6b3
 	    if(initgroups(opt->strarg, user->pw_gid)) {
4c237bcf
 		fprintf(stderr, "ERROR: initgroups() failed.\n");
278dc6b3
 		optfree(opts);
4c237bcf
 		return 1;
 	    }
4c5e69c8
 #else
4c237bcf
 	    mprintf("!AllowSupplementaryGroups: initgroups() is not available, please disable AllowSupplementaryGroups\n");
278dc6b3
 	    optfree(opts);
4c237bcf
 	    return 1;
4c5e69c8
 #endif
31ee8076
 	} else {
4c237bcf
 #ifdef HAVE_SETGROUPS
 	    if(setgroups(1, &user->pw_gid)) {
 		fprintf(stderr, "ERROR: setgroups() failed.\n");
278dc6b3
 		optfree(opts);
4c237bcf
 		return 1;
 	    }
8ac80fb8
 #endif
e3aaff8e
 	}
 
4c237bcf
 	if(setgid(user->pw_gid)) {
 	    fprintf(stderr, "ERROR: setgid(%d) failed.\n", (int) user->pw_gid);
278dc6b3
 	    optfree(opts);
4c237bcf
 	    return 1;
ebc4ec8f
 	}
66ff992e
 
4c237bcf
 	if(setuid(user->pw_uid)) {
 	    fprintf(stderr, "ERROR: setuid(%d) failed.\n", (int) user->pw_uid);
278dc6b3
 	    optfree(opts);
4c237bcf
 	    return 1;
66ff992e
 	}
4c237bcf
     }
ebc4ec8f
 
278dc6b3
     logg_lock = !optget(opts, "LogFileUnlock")->enabled;
     logg_time = optget(opts, "LogTime")->enabled;
     logg_size = optget(opts, "LogFileMaxSize")->numarg;
     logg_verbose = mprintf_verbose = optget(opts, "LogVerbose")->enabled;
fe3d8be8
 
278dc6b3
     if((opt = optget(opts, "LogFile"))->enabled) {
 	logg_file = opt->strarg;
58481352
 	if(!cli_is_abspath(logg_file)) {
4c237bcf
 	    fprintf(stderr, "ERROR: LogFile requires full path.\n");
 	    logg_close();
278dc6b3
 	    optfree(opts);
4c237bcf
 	    return 1;
fe3d8be8
 	}
4c237bcf
     } else
 	logg_file = NULL;
fe3d8be8
 
4c237bcf
 #if defined(USE_SYSLOG) && !defined(C_AIX)
278dc6b3
     if(optget(opts, "LogSyslog")->enabled) {
4c237bcf
 	int fac;
54a9f64e
 
278dc6b3
 	opt = optget(opts, "LogFacility");
 	if((fac = logg_facility(opt->strarg)) == -1) {
 	    logg("!LogFacility: %s: No such facility.\n", opt->strarg);
4c237bcf
 	    logg_close();
278dc6b3
 	    optfree(opts);
4c237bcf
 	    return 1;
736c8d91
 	}
f7ab4278
 
aa633fa0
 	openlog("clamav-milter", LOG_PID, fac);
4c237bcf
 	logg_syslog = 1;
     }
06bfd678
 #endif
7089d180
 
e9c4dd09
     time(&currtime);
     if(logg("#+++ Started at %s", ctime(&currtime))) {
 	fprintf(stderr, "ERROR: Can't initialize the internal logger\n");
 	logg_close();
 	optfree(opts);
 	return 1;
     }
278dc6b3
     if((opt = optget(opts, "TemporaryDirectory"))->enabled)
 	tempdir = opt->strarg;
87620def
 
278dc6b3
     if(localnets_init(opts) || init_actions(opts)) {
6840d862
 	logg_close();
278dc6b3
 	optfree(opts);
6840d862
 	return 1;
     }
 
278dc6b3
     if((opt = optget(opts, "Whitelist"))->enabled && whitelist_init(opt->strarg)) {
3eb16511
 	localnets_free();
 	logg_close();
278dc6b3
 	optfree(opts);
3eb16511
 	return 1;
     }
ce34c246
 
57aa0269
     if((opt = optget(opts, "SkipAuthenticated"))->enabled && smtpauth_init(opt->strarg)) {
 	localnets_free();
 	whitelist_free();
 	logg_close();
 	optfree(opts);
 	return 1;
     }
 
3521624b
     pt = optget(opts, "AddHeader")->strarg;
     if(strcasecmp(pt, "No")) {
ce34c246
 	char myname[255];
 
b955dae4
 	if(((opt = optget(opts, "ReportHostname"))->enabled && strncpy(myname, opt->strarg, sizeof(myname))) || !gethostname(myname, sizeof(myname))) {
ce34c246
 	    myname[sizeof(myname)-1] = '\0';
 	    snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s at %s", get_version(), myname);
b955dae4
 	} else
ce34c246
 	    snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s", get_version());
b955dae4
 	xvirushdr[sizeof(xvirushdr)-1] = '\0';
 
3521624b
 	descr.xxfi_flags |= SMFIF_ADDHDRS;
 
 	if(strcasecmp(pt, "Add")) { /* Replace or Yes */
 	    descr.xxfi_flags |= SMFIF_CHGHDRS;
 	    addxvirus = 1;
 	} else { /* Add */
 	    addxvirus = 2;
 	}
ce34c246
     }
e9747a42
     
278dc6b3
     if(!(my_socket = optget(opts, "MilterSocket")->strarg)) {
4c237bcf
 	logg("!Please configure the MilterSocket directive\n");
3eb16511
 	localnets_free();
 	whitelist_free();
4c237bcf
 	logg_close();
278dc6b3
 	optfree(opts);
7089d180
 	return 1;
4c237bcf
     }
41f5623e
 
     if(!optget(opts, "Foreground")->enabled) {
 	if(daemonize() == -1) {
 	    logg("!daemonize() failed\n");
 	    localnets_free();
 	    whitelist_free();
 	    cpool_free();
 	    logg_close();
 	    optfree(opts);
 	    return 1;
 	}
 	if(chdir("/") == -1)
 	    logg("^Can't change current working directory to root\n");
     }
 
4c237bcf
     if(smfi_setconn(my_socket) == MI_FAILURE) {
 	logg("!smfi_setconn failed\n");
3eb16511
 	localnets_free();
 	whitelist_free();
4c237bcf
 	logg_close();
278dc6b3
 	optfree(opts);
f7ab4278
 	return 1;
4c237bcf
     }
     if(smfi_register(descr) == MI_FAILURE) {
 	logg("!smfi_register failed\n");
3eb16511
 	localnets_free();
 	whitelist_free();
83ed1043
 	logg_close();
278dc6b3
 	optfree(opts);
4c237bcf
 	return 1;
     }
278dc6b3
     opt = optget(opts, "FixStaleSocket");
e394c513
     umsk = umask(0777); /* socket is created with 000 to avoid races */ 
278dc6b3
     if(smfi_opensocket(opt->enabled) == MI_FAILURE) {
4c237bcf
 	logg("!Failed to create socket %s\n", my_socket);
3eb16511
 	localnets_free();
 	whitelist_free();
040708a3
 	logg_close();
278dc6b3
 	optfree(opts);
19575eba
 	return 1;
4c237bcf
     }
e394c513
     umask(umsk); /* restore umask */
     if(strncmp(my_socket, "inet:", 5) && strncmp(my_socket, "inet6:", 6)) {
 	/* set group ownership and perms on the local socket */
 	char *sock_name = my_socket;
 	mode_t sock_mode;
 	if(!strncmp(my_socket, "unix:", 5))
 	    sock_name += 5;
 	if(!strncmp(my_socket, "local:", 6))
 	    sock_name += 6;
 	if(*my_socket == ':')
 	    sock_name ++;
 
 	if(optget(opts, "MilterSocketGroup")->enabled) {
 	    char *gname = optget(opts, "MilterSocketGroup")->strarg, *end;
 	    gid_t sock_gid = strtol(gname, &end, 10);
 	    if(*end) {
 		struct group *pgrp = getgrnam(gname);
 		if(!pgrp) {
 		    logg("!Unknown group %s\n", gname);
 		    localnets_free();
 		    whitelist_free();
 		    logg_close();
 		    optfree(opts);
 		    return 1;
 		}
 		sock_gid = pgrp->gr_gid;
 	    }
 	    if(chown(sock_name, -1, sock_gid)) {
 		logg("!Failed to change socket ownership to group %s\n", gname);
 		localnets_free();
 		whitelist_free();
 		logg_close();
 		optfree(opts);
 		return 1;
 	    }
 	}
 	if(optget(opts, "MilterSocketMode")->enabled) {
 	    char *end;
 	    sock_mode = strtol(optget(opts, "MilterSocketMode")->strarg, &end, 8);
 	    if(*end) {
 		logg("!Invalid MilterSocketMode %s\n", optget(opts, "MilterSocketMode")->strarg);
 		localnets_free();
 		whitelist_free();
 		logg_close();
 		optfree(opts);
 		return 1;
 	    }
 	} else
 	    sock_mode = 0777 & ~umsk;
 
 	if(chmod(sock_name, sock_mode & 0666)) {
 	    logg("!Cannot set milter socket permission to %s\n", optget(opts, "MilterSocketMode")->strarg);
 	    localnets_free();
 	    whitelist_free();
 	    logg_close();
 	    optfree(opts);
 	    return 1;
 	}
     }
835c9751
 
278dc6b3
     maxfilesize = optget(opts, "MaxFileSize")->numarg;
ec34b80f
     if(!maxfilesize) {
 	logg("^Invalid MaxFileSize, using default (%d)\n", CLI_DEFAULT_MAXFILESIZE);
 	maxfilesize = CLI_DEFAULT_MAXFILESIZE;
     }
278dc6b3
     readtimeout = optget(opts, "ReadTimeout")->numarg;
36f79c60
 
278dc6b3
     cpool_init(opts);
4c237bcf
     if (!cp) {
 	logg("!Failed to init the socket pool\n");
3eb16511
 	localnets_free();
 	whitelist_free();
4c237bcf
 	logg_close();
278dc6b3
 	optfree(opts);
4c237bcf
 	return 1;
     }	
530999cb
 
278dc6b3
     if((opt = optget(opts, "PidFile"))->enabled) {
87620def
 	FILE *fd;
cd0d6a0b
 	mode_t old_umask = umask(0002);
87620def
 
278dc6b3
 	if((fd = fopen(opt->strarg, "w")) == NULL) {
 	    logg("!Can't save PID in file %s\n", opt->strarg);
87620def
 	} else {
 	    if (fprintf(fd, "%u", (unsigned int)getpid())<0) {
278dc6b3
 	    	logg("!Can't save PID in file %s\n", opt->strarg);
87620def
 	    }
 	    fclose(fd);
 	}
 	umask(old_umask);
     }
530999cb
 
4c237bcf
     ret = smfi_main();
93928eab
 
278dc6b3
     optfree(opts);
bca134bc
 
4c237bcf
     logg_close();
     cpool_free();
6840d862
     localnets_free();
3eb16511
     whitelist_free();
 
4c237bcf
     return ret;
93928eab
 }
3eb16511
 
9fe789f8
 /*
4c237bcf
  * Local Variables:
  * mode: c
  * c-basic-offset: 4
  * tab-width: 8
  * End: 
  * vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8: 
9fe789f8
  */