#include "config.h"
#include "version.h"
#include "libspamc.h"
#include "utils.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#include <process.h>
#else
#include <syslog.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif
#ifdef SPAMC_SSL
#include <openssl/crypto.h>
#ifndef OPENSSL_VERSION_TEXT
#define OPENSSL_VERSION_TEXT "OpenSSL"
#endif
#endif
#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(__CYGWIN__))
extern int optind;
extern char *optarg;
#endif
#ifdef _WIN32
#include "replace/getopt.h"
char *__progname = "spamc";
#endif
int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK;
int use_exit_code = 0;
char **exec_argv;
static int timeout = 600;
void
print_version(void)
{
printf("%s version %s\n", "SpamAssassin Client", VERSION_STRING);
#ifdef SPAMC_SSL
printf(" compiled with SSL support (%s)\n", OPENSSL_VERSION_TEXT);
#endif
}
static void
usg(char *str)
{
printf("%s", str);
}
void
print_usage(void)
{
print_version();
usg("\n");
usg("Usage: spamc [options] [-e command [args]] < message\n");
usg("\n");
usg("Options:\n");
usg(" -d host Specify host to connect to.\n"
" [default: localhost]\n");
usg(" -H Randomize IP addresses for the looked-up\n"
" hostname.\n");
usg(" -p port Specify port for connection to spamd.\n"
" [default: 783]\n");
#ifdef SPAMC_SSL
usg(" -S Use SSL to talk to spamd.\n");
#endif
#ifndef _WIN32
usg(" -U path Connect to spamd via UNIX domain sockets.\n");
#endif
usg(" -t timeout Timeout in seconds for communications to\n"
" spamd. [default: 600]\n");
usg(" -s size Specify maximum message size, in bytes.\n"
" [default: 250k]\n");
usg(" -u username User for spamd to process this message under.\n"
" [default: current user]\n");
usg(" -B Assume input is a single BSMTP-formatted\n"
" message.\n");
usg(" -c Just print the summary line and set an exit\n"
" code.\n");
usg(" -y Just print the names of the tests hit.\n");
usg(" -r Print full report for messages identified as\n"
" spam.\n");
usg(" -R Print full report for all messages.\n");
usg(" -E Filter as normal, and set an exit code.\n");
usg(" -x Don't fallback safely.\n");
usg(" -l Log errors and warnings to stderr.\n");
#ifndef _WIN32
usg(" -e command [args] Pipe the output to the given command instead\n"
" of stdout. This must be the last option.\n");
#endif
usg(" -h Print this help message and exit.\n");
usg(" -V Print spamc version and exit.\n");
usg(" -f (Now default, ignored.)\n");
usg("\n");
}
int
read_args(int argc, char **argv,
int *max_size, char **username,
struct transport *ptrn)
{
#ifndef _WIN32
const char *opts = "-BcrRd:e:fyp:t:s:u:xSHU:ElhV";
#else
const char *opts = "-BcrRd:fyp:t:s:u:xSHElhV";
#endif
int opt;
int ret = EX_OK;
while ((opt = getopt(argc, argv, opts)) != -1)
{
switch (opt)
{
case 'B':
{
flags = (flags & ~SPAMC_MODE_MASK) | SPAMC_BSMTP_MODE;
break;
}
case 'c':
{
flags |= SPAMC_CHECK_ONLY;
break;
}
case 'd':
{
ptrn->type = TRANSPORT_TCP;
ptrn->hostname = optarg;
break;
}
#ifndef _WIN32
case 'e':
{
int i, j;
exec_argv = malloc(sizeof(*exec_argv) * (argc - optind + 2));
if (exec_argv == 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;
}
#endif
case 'f':
{
break;
}
case 'l':
{
flags |= SPAMC_LOG_TO_STDERR;
break;
}
case 'H':
{
flags |= SPAMC_RANDOMIZE_HOSTS;
break;
}
case 'p':
{
ptrn->port = (unsigned short)atoi(optarg);
break;
}
case 'r':
{
flags |= SPAMC_REPORT_IFSPAM;
break;
}
case 'E':
{
use_exit_code = 1;
break;
}
case 'R':
{
flags |= SPAMC_REPORT;
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 'u':
{
*username = optarg;
break;
}
#ifndef _WIN32
case 'U':
{
ptrn->type = TRANSPORT_UNIX;
ptrn->socketpath = optarg;
break;
}
#endif
case 'x':
{
flags &= (~SPAMC_SAFE_FALLBACK);
break;
}
case 'y':
{
flags |= SPAMC_SYMBOLS;
break;
}
case '?':
case ':':
{
libspamc_log(flags, LOG_ERR, "invalid usage");
ret = EX_USAGE;
}
case 'h':
{
print_usage();
if (ret == EX_OK)
ret = EX_TEMPFAIL;
return(ret);
}
case 'V':
{
print_version();
return(EX_TEMPFAIL);
}
}
}
return ret;
}
void
get_output_fd(int *fd)
{
#ifndef _WIN32
int pipe_fds[2];
pid_t pid;
#endif
if (*fd != -1)
return;
if (exec_argv == NULL) {
*fd = STDOUT_FILENO;
return;
}
#ifndef _WIN32
if (pipe(pipe_fds)) {
libspamc_log(flags, LOG_ERR, "pipe creation failed: %m");
exit(EX_OSERR);
}
pid = fork();
if (pid < 0) {
libspamc_log(flags, LOG_ERR, "fork failed: %m");
exit(EX_OSERR);
}
else if (pid == 0) {
close(pipe_fds[0]);
*fd = pipe_fds[1];
return;
}
close(pipe_fds[1]);
if (dup2(pipe_fds[0], STDIN_FILENO)) {
libspamc_log(flags, LOG_ERR, "redirection of stdin failed: %m");
exit(EX_OSERR);
}
close(pipe_fds[0]);
execv(exec_argv[0], exec_argv);
libspamc_log(flags, LOG_ERR, "exec failed: %m");
#else
libspamc_log(flags, LOG_CRIT, "THIS MUST NOT HAPPEN AS -e IS NOT SUPPORTED UNDER WINDOWS.");
#endif
exit(EX_OSERR);
}
int
get_current_user(char **username)
{
#ifndef _WIN32
struct passwd *curr_user;
#endif
if (*username != NULL) {
*username = strdup(*username);
if (username == NULL)
goto fail;
goto pass;
}
#ifndef _WIN32
errno = 0;
curr_user = getpwuid(geteuid());
if (curr_user == NULL) {
perror("getpwuid() failed");
goto fail;
}
*username = strdup(curr_user->pw_name);
if (*username == NULL) {
goto fail;
}
#endif
pass:
return EX_OK;
fail:
if (flags & SPAMC_CHECK_ONLY) {
printf("0/0\n");
return EX_NOTSPAM;
}
return EX_OSERR;
}
int
main(int argc, char *argv[])
{
int max_size;
char *username;
struct transport trans;
struct message m;
int out_fd = -1;
int result;
int ret;
transport_init(&trans);
#ifdef LIBSPAMC_UNIT_TESTS
do_libspamc_unit_tests();
#endif
#ifndef _WIN32
openlog("spamc", LOG_CONS | LOG_PID, LOG_MAIL);
signal(SIGPIPE, SIG_IGN);
#endif
max_size = 250 * 1024;
username = NULL;
if ((ret = read_args(argc, argv, &max_size, &username, &trans)) != EX_OK) {
if (ret == EX_TEMPFAIL )
ret = EX_OK;
goto finish;
}
ret = get_current_user(&username);
if (ret != EX_OK)
goto finish;
if ((flags & SPAMC_RANDOMIZE_HOSTS) != 0) {
srand(getpid() ^ time(NULL));
}
m.type = MESSAGE_NONE;
m.out = NULL;
m.raw = NULL;
m.priv = NULL;
m.max_len = max_size;
m.timeout = timeout;
m.is_spam = EX_NOHOST;
#ifdef _WIN32
setmode(STDIN_FILENO, O_BINARY);
setmode(STDOUT_FILENO, O_BINARY);
#endif
ret = transport_setup(&trans, flags);
if (ret == EX_OK) {
ret = message_read(STDIN_FILENO, flags, &m);
if (ret == EX_OK) {
ret = message_filter(&trans, username, flags, &m);
free(username); username = NULL;
if (ret == EX_OK) {
get_output_fd(&out_fd);
if (message_write(out_fd, &m) >= 0) {
result = m.is_spam;
if ((flags & SPAMC_CHECK_ONLY) && result != EX_TOOBIG) {
message_cleanup(&m);
ret = result;
}
else {
message_cleanup(&m);
if (use_exit_code && result != EX_TOOBIG) {
ret = result;
}
}
goto finish;
}
}
}
}
free(username);
get_output_fd(&out_fd);
result = m.is_spam;
if ((flags & SPAMC_CHECK_ONLY) && result != EX_TOOBIG) {
message_cleanup(&m);
ret = result;
}
else if (flags & SPAMC_CHECK_ONLY || flags & SPAMC_REPORT
|| flags & SPAMC_REPORT_IFSPAM) {
full_write(out_fd, 1, "0/0\n", 4);
message_cleanup(&m);
ret = EX_NOTSPAM;
}
else {
message_dump(STDIN_FILENO, out_fd, &m);
message_cleanup(&m);
if (ret == EX_TOOBIG) {
ret = 0;
}
else if (use_exit_code) {
ret = result;
}
else if (flags & SPAMC_SAFE_FALLBACK) {
ret = EX_OK;
}
}
finish:
#ifdef _WIN32
WSACleanup();
#endif
return ret;
}