#include "config.h"
#include "libspamc.h"
#include "utils.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#ifdef HAVE_SYSEXITS_H
#include <sysexits.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#if (defined(__sun__) && defined(__sparc__) && !defined(__svr4__)) \
|| (defined(__sgi)) \
|| (defined(__osf__)) \
|| (defined(hpux) || defined(__hpux)) \
|| (defined(_WIN32) || defined(__CYGWIN__))
extern int optind;
extern char *optarg;
#endif
int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK;
char **exec_argv;
static int timeout = 600;
void print_usage(void)
{
printf("Usage: spamc [options] < message\n\n");
printf("-B: BSMTP mode - expect input to be a single SMTP-formatted message\n");
printf("-c: check only - print score/threshold and exit code set to 0 if message is not spam, 1 if spam\n");
printf("-r: report if spam - print report for spam messages\n");
printf("-R: report - print report for all messages\n");
printf("-y: symbols - print only the names of the tests hit\n");
printf("-d host: specify host to connect to [default: localhost]\n");
printf("-e command [args]: Command to output to instead of stdout. MUST BE THE LAST OPTION.\n");
printf("-f: fallback safely - in case of comms error, dump original message unchanges instead of setting exitcode\n");
printf("-h: print this help message\n");
printf("-p port: specify port for connection [default: 783]\n");
printf("-s size: specify max message size, any bigger and it will be returned w/out processing [default: 250k]\n");
#ifdef SPAMC_SSL
printf("-S: use SSL to talk to spamd\n");
#endif
printf("-u username: specify the username for spamd to process this message under\n");
printf("-x: don't fallback safely - in a comms error, exit with a TEMPFAIL error code\n");
printf("-t: timeout in seconds to read from spamd. 0 disables. [default: 600]\n\n");
printf("-H: randomize the IP addresses in the looked-up hostname\n");
printf("-U path: use UNIX domain socket with path\n");
}
int
read_args(int argc, char **argv, int *max_size, const char **username,
struct transport *ptrn)
{
int opt, i, j;
while(-1 != (opt = getopt(argc,argv,"-BcrRd:e:fhyp:t:s:u:xSHU:")))
{
switch(opt)
{
case 'H':
{
flags |= SPAMC_RANDOMIZE_HOSTS;
break;
}
case 'U':
{
ptrn->type = TRANSPORT_UNIX;
ptrn->socketpath = optarg;
break;
}
case 'B':
{
flags = (flags & ~SPAMC_MODE_MASK) | SPAMC_BSMTP_MODE;
break;
}
case 'c':
{
flags |= SPAMC_CHECK_ONLY;
break;
}
case 'r':
{
flags |= SPAMC_REPORT_IFSPAM;
break;
}
case 'R':
{
flags |= SPAMC_REPORT;
break;
}
case 'y':
{
flags |= SPAMC_SYMBOLS;
break;
}
case 'd':
{
ptrn->type = TRANSPORT_TCP;
ptrn->hostname = optarg;
break;
}
case 'e':
{
if((exec_argv=malloc(sizeof(*exec_argv)*(argc-optind+2)))==NULL)
return EX_OSERR;
for(i=0, j=optind-1; j<argc; i++, j++){
exec_argv[i]=argv[j];
}
exec_argv[i]=NULL;
return EX_OK;
}
case 'p':
{
ptrn->port = atoi(optarg);
break;
}
case 'f':
{
flags |= SPAMC_SAFE_FALLBACK;
break;
}
case 'x':
{
flags &= (~SPAMC_SAFE_FALLBACK);
break;
}
case 'u':
{
*username = optarg;
break;
}
case 's':
{
*max_size = atoi(optarg);
break;
}
#ifdef SPAMC_SSL
case 'S':
{
flags |= SPAMC_USE_SSL;
break;
}
#endif
case 't':
{
timeout = atoi(optarg);
break;
}
case '?': {
syslog (LOG_ERR, "invalid usage");
}
case 'h':
case 1:
{
print_usage();
exit(EX_USAGE);
}
}
}
return EX_OK;
}
void get_output_fd(int *fd){
int fds[2];
pid_t pid;
if(*fd!=-1) return;
if(exec_argv==NULL){
*fd=STDOUT_FILENO;
return;
}
if(pipe(fds)){
syslog(LOG_ERR, "pipe creation failed: %m");
exit(EX_OSERR);
}
pid=fork();
if(pid<0){
syslog(LOG_ERR, "fork failed: %m");
exit(EX_OSERR);
} else if(pid==0){
close(fds[0]);
*fd=fds[1];
return;
}
close(fds[1]);
if(dup2(fds[0], STDIN_FILENO)){
syslog(LOG_ERR, "redirection of stdin failed: %m");
exit(EX_OSERR);
}
close(fds[0]);
execv(exec_argv[0], exec_argv);
syslog(LOG_ERR, "exec failed: %m");
exit(EX_OSERR);
}
int main (int argc, char **argv) {
int max_size = 250*1024;
const char *username = NULL;
int ret;
struct message m;
int out_fd;
struct transport trans;
int result;
transport_init(&trans);
#ifdef LIBSPAMC_UNIT_TESTS
do_libspamc_unit_tests();
#endif
openlog ("spamc", LOG_CONS|LOG_PID, LOG_MAIL);
signal (SIGPIPE, SIG_IGN);
read_args(argc,argv, &max_size, &username, &trans);
if(NULL == username)
{
static char userbuf[256];
struct passwd *curr_user;
curr_user = getpwuid(geteuid());
if (curr_user == NULL) {
perror ("getpwuid failed");
if(flags&SPAMC_CHECK_ONLY) { printf("0/0\n"); return EX_NOTSPAM; } else { return EX_OSERR; }
}
memset(userbuf, 0, sizeof userbuf);
strncpy(userbuf, curr_user->pw_name, sizeof userbuf - 1);
userbuf[sizeof userbuf - 1] = '\0';
username = userbuf;
}
if ((flags & SPAMC_RANDOMIZE_HOSTS) != 0) {
srand(getpid() ^ time(NULL));
}
if ( (ret = transport_setup(&trans, flags)) != EX_OK )
goto FAIL;
out_fd=-1;
m.type = MESSAGE_NONE;
m.max_len = max_size;
m.timeout = timeout;
ret=message_read(STDIN_FILENO, flags, &m);
if(ret!=EX_OK) goto FAIL;
ret=message_filter(&trans, username, flags, &m);
if(ret!=EX_OK) goto FAIL;
get_output_fd(&out_fd);
if(message_write(out_fd, &m)<0) {
goto FAIL;
}
result = m.is_spam;
if ((flags&SPAMC_CHECK_ONLY) && result != EX_TOOBIG) {
message_cleanup (&m);
return result;
} else {
message_cleanup (&m);
return ret;
}
FAIL:
get_output_fd(&out_fd);
result = m.is_spam;
if((flags&SPAMC_CHECK_ONLY) && result != EX_TOOBIG) {
message_cleanup (&m);
return result;
} else if(flags&SPAMC_CHECK_ONLY || flags&SPAMC_REPORT || flags&SPAMC_REPORT_IFSPAM) {
full_write(out_fd, "0/0\n", 4);
message_cleanup (&m);
return EX_NOTSPAM;
} else {
message_dump(STDIN_FILENO, out_fd, &m);
message_cleanup (&m);
if (ret == EX_TOOBIG) {
return 0;
} else if (flags & SPAMC_SAFE_FALLBACK) {
return EX_OK;
} else {
return ret;
}
}
}