mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 16:01:29 +00:00
1117 lines
31 KiB
C
1117 lines
31 KiB
C
/* <@LICENSE>
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to you under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
* </@LICENSE>
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "version.h"
|
|
#include "libspamc.h"
|
|
#include "utils.h"
|
|
#include "spamc.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "getopt.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
|
|
|
|
/* SunOS 4.1.4 patch from Tom Lipkis <tal@pss.com> */
|
|
#if (defined(__sun__) && defined(__sparc__) && !defined(__svr4__)) /* SunOS */ \
|
|
|| (defined(__sgi)) /* IRIX */ \
|
|
|| (defined(__osf__)) /* Digital UNIX */ \
|
|
|| (defined(hpux) || defined(__hpux)) /* HPUX */ \
|
|
|| (defined(__CYGWIN__)) /* CygWin, Win32 */
|
|
|
|
extern int spamc_optind;
|
|
extern char *spamc_optarg;
|
|
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
char *__progname = "spamc";
|
|
#endif
|
|
|
|
|
|
/* safe fallback defaults to on now - CRH */
|
|
int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK | SPAMC_TLSV1;
|
|
|
|
/* global to control whether we should exit(0)/exit(1) on ham/spam */
|
|
int use_exit_code = 0;
|
|
|
|
/* Aug 14, 2002 bj: global to hold -e command */
|
|
char **exec_argv;
|
|
|
|
static int timeout = 600;
|
|
static int connect_timeout = 0; /* Sep 8, 2008 mrgus: separate connect timeout */
|
|
|
|
|
|
void
|
|
check_malloc (void *ptr)
|
|
{
|
|
if(ptr == NULL) {
|
|
libspamc_log(flags, LOG_ERR,
|
|
"Error allocating memory using malloc\n");
|
|
/* this is really quite serious. we can't do anything. die */
|
|
exit(EX_OSERR);
|
|
}
|
|
}
|
|
|
|
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, --dest host[,host2]\n"
|
|
" Specify one or more hosts to connect to.\n"
|
|
" [default: localhost]\n");
|
|
usg(" -H , --randomize Randomize IP addresses for the looked-up\n"
|
|
" hostname.\n");
|
|
usg(" -p, --port port Specify port for connection to spamd.\n"
|
|
" [default: 783]\n");
|
|
#ifdef SPAMC_SSL
|
|
usg(" -S, --ssl Use SSL to talk to spamd.\n");
|
|
usg(" --ssl-cert cert Authenticate using SSL client certificate.\n");
|
|
usg(" --ssl-key key Specify an SSL client key PEM file.\n");
|
|
usg(" --ssl-ca-file file Specify the location of the CA PEM file.\n");
|
|
usg(" --ssl-ca-path path Specify a directory containin CA files.\n");
|
|
#endif
|
|
#ifndef _WIN32
|
|
usg(" -U, --socket path Connect to spamd via UNIX domain sockets.\n");
|
|
#endif
|
|
usg(" -F, --config path Use this configuration file.\n");
|
|
usg(" -t, --timeout timeout\n"
|
|
" Timeout in seconds for communications to\n"
|
|
" spamd. [default: 600]\n");
|
|
usg(" -n, --connect-timeout timeout\n"
|
|
" Timeout in seconds when opening a connection to\n"
|
|
" spamd. [default: 600]\n");
|
|
usg(" --filter-retries retries\n"
|
|
" Retry filtering this many times if the spamd\n"
|
|
" process fails (usually times out) [default: 1]\n");
|
|
usg(" --filter-retry-sleep sleep\n"
|
|
" Sleep for this time between failed filter\n"
|
|
" attempts, in seconds [default: 1]\n");
|
|
usg(" --connect-retries retries\n"
|
|
" Try connecting to spamd tcp socket this many times\n"
|
|
" [default: 3]\n");
|
|
usg(" --retry-sleep sleep Sleep for this time between attempts to\n"
|
|
" connect to spamd, in seconds [default: 1]\n");
|
|
usg(" -s, --max-size size Specify maximum message size, in bytes.\n"
|
|
" [default: 500k]\n");
|
|
usg(" -u, --username username\n"
|
|
" User for spamd to process this message under.\n"
|
|
" [default: current user]\n");
|
|
|
|
usg(" -L, --learntype learntype\n"
|
|
" Learn message as spam, ham or forget to\n"
|
|
" forget or unlearn the message.\n");
|
|
|
|
usg(" -C, --reporttype reporttype\n"
|
|
" Report message to collaborative filtering\n"
|
|
" databases. Report type should be 'report' for\n"
|
|
" spam or 'revoke' for ham.\n");
|
|
|
|
usg(" -B, --bsmtp Assume input is a single BSMTP-formatted\n"
|
|
" message.\n");
|
|
|
|
usg(" -c, --check Just print the summary line and set an exit\n"
|
|
" code.\n");
|
|
usg(" -y, --tests Just print the names of the tests hit.\n");
|
|
usg(" -r, --full-spam Print full report for messages identified as\n"
|
|
" spam.\n");
|
|
usg(" -R, --full Print full report for all messages.\n");
|
|
usg(" --headers Rewrite only the message headers.\n");
|
|
usg(" -E, --exitcode Filter as normal, and set an exit code.\n");
|
|
|
|
usg(" -x, --no-safe-fallback\n"
|
|
" Don't fallback safely.\n");
|
|
usg(" -X, --unavailable-tempfail\n"
|
|
" When using -x, turn 'unavailable' error into\n"
|
|
" 'tempfail'. This may be useful for an MTAs\n"
|
|
" to defer emails with a temporary SMTP error\n"
|
|
" instead of bouncing with a permanent SMTP\n"
|
|
" error.\n");
|
|
usg(" -l, --log-to-stderr Log errors and warnings to stderr.\n");
|
|
#ifndef _WIN32
|
|
usg(" -e, --pipe-to command [args]\n"
|
|
" Pipe the output to the given command instead\n"
|
|
" of stdout. This must be the last option.\n");
|
|
#endif
|
|
usg(" -h, --help Print this help message and exit.\n");
|
|
usg(" -V, --version Print spamc version and exit.\n");
|
|
usg(" -K Keepalive check of spamd.\n");
|
|
#ifdef HAVE_ZLIB_H
|
|
usg(" -z Compress mail message sent to spamd.\n");
|
|
#endif
|
|
usg(" -f (Now default, ignored.)\n");
|
|
usg(" -4 Use IPv4 only for connecting to server.\n");
|
|
usg(" -6 Use IPv6 only for connecting to server.\n");
|
|
|
|
usg("\n");
|
|
}
|
|
|
|
/**
|
|
* Does the command line parsing for argv[].
|
|
*
|
|
* Returns EX_OK or EX_TEMPFAIL if successful. EX_TEMPFAIL is a kludge for
|
|
* the cases where we want in main to return immediately; we can't exit()
|
|
* because on Windows WSACleanup() needs to be called.
|
|
*/
|
|
int
|
|
read_args(int argc, char **argv,
|
|
int *max_size, char **username, int *extratype,
|
|
struct transport *ptrn)
|
|
{
|
|
#ifndef _WIN32
|
|
const char *opts = "-BcrR46d:e:fyp:n:t:s:u:L:C:xXzSHU:ElhVKF:0:1:2";
|
|
#else
|
|
const char *opts = "-BcrR46d:fyp:n:t:s:u:L:C:xXzSHElhVKF:0:1:2";
|
|
#endif
|
|
int opt;
|
|
int ret = EX_OK;
|
|
int longind = 1;
|
|
|
|
static struct option longoptions[] = {
|
|
{ "dest", required_argument, 0, 'd' },
|
|
{ "randomize", no_argument, 0, 'H' },
|
|
{ "port", required_argument, 0, 'p' },
|
|
{ "ssl", optional_argument, 0, 'S' },
|
|
{ "ssl-cert", optional_argument, 0, 5 },
|
|
{ "ssl-key", optional_argument, 0, 6 },
|
|
{ "ssl-ca-file", optional_argument, 0, 7 },
|
|
{ "ssl-ca-path", optional_argument, 0, 8 },
|
|
{ "socket", required_argument, 0, 'U' },
|
|
{ "config", required_argument, 0, 'F' },
|
|
{ "timeout", required_argument, 0, 't' },
|
|
{ "connect-timeout", required_argument, 0, 'n'},
|
|
{ "connect-retries", required_argument, 0, 0 },
|
|
{ "retry-sleep", required_argument, 0, 1 },
|
|
{ "filter-retries", required_argument, 0, 3 },
|
|
{ "filter-retry-sleep", required_argument, 0, 4 },
|
|
{ "max-size", required_argument, 0, 's' },
|
|
{ "username", required_argument, 0, 'u' },
|
|
{ "learntype", required_argument, 0, 'L' },
|
|
{ "reporttype", required_argument, 0, 'C' },
|
|
{ "bsmtp", no_argument, 0, 'B' },
|
|
{ "check", no_argument, 0, 'c' },
|
|
{ "tests", no_argument, 0, 'y' },
|
|
{ "full-spam", no_argument, 0, 'r' },
|
|
{ "full", no_argument, 0, 'R' },
|
|
{ "headers", no_argument, 0, 2 },
|
|
{ "exitcode", no_argument, 0, 'E' },
|
|
{ "no-safe-fallback", no_argument, 0, 'x' },
|
|
{ "unavailable-tempfail", no_argument, 0, 'X' },
|
|
{ "log-to-stderr", no_argument, 0, 'l' },
|
|
{ "pipe-to", required_argument, 0, 'e' },
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "version", no_argument, 0, 'V' },
|
|
{ "compress", no_argument, 0, 'z' },
|
|
{ 0, 0, 0, 0} /* last element _must_ be all zeroes */
|
|
};
|
|
|
|
#ifdef SPAMC_SSL
|
|
ptrn->ssl_cert_file = 0;
|
|
ptrn->ssl_key_file = 0;
|
|
ptrn->ssl_ca_file = 0;
|
|
ptrn->ssl_ca_path = 0;
|
|
#endif
|
|
|
|
while ((opt = spamc_getopt_long(argc, argv, opts, longoptions,
|
|
&longind)) != -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 = spamc_optarg; /* fix the ptr to point to this string */
|
|
break;
|
|
}
|
|
#ifndef _WIN32
|
|
case 'e':
|
|
{
|
|
int i, j;
|
|
|
|
/* Allocate memory for the necessary pointers needed to
|
|
* store the remaining arguments.
|
|
*/
|
|
exec_argv = malloc(sizeof(*exec_argv) * (argc - spamc_optind + 2));
|
|
if (exec_argv == NULL) {
|
|
return EX_OSERR;
|
|
}
|
|
|
|
for (i = 0, j = spamc_optind - 1; j < argc; i++, j++) {
|
|
exec_argv[i] = argv[j];
|
|
}
|
|
exec_argv[i] = NULL;
|
|
|
|
return EX_OK;
|
|
}
|
|
#endif
|
|
case 'f':
|
|
{
|
|
/* obsolete, backward compat */
|
|
break;
|
|
}
|
|
case 'K':
|
|
{
|
|
flags |= SPAMC_PING;
|
|
break;
|
|
}
|
|
case 'l':
|
|
{
|
|
flags |= SPAMC_LOG_TO_STDERR;
|
|
break;
|
|
}
|
|
case 'H':
|
|
{
|
|
flags |= SPAMC_RANDOMIZE_HOSTS;
|
|
break;
|
|
}
|
|
case 'p':
|
|
{
|
|
ptrn->port = (unsigned short)atoi(spamc_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(spamc_optarg);
|
|
break;
|
|
}
|
|
#ifdef SPAMC_SSL
|
|
case 'S':
|
|
{
|
|
flags |= SPAMC_USE_SSL;
|
|
if(spamc_optarg) {
|
|
libspamc_log(flags, LOG_ERR,
|
|
"Explicit specification of an SSL/TLS version no longer supported.");
|
|
ret = EX_USAGE;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case 't':
|
|
{
|
|
timeout = atoi(spamc_optarg);
|
|
if(!connect_timeout) {
|
|
connect_timeout = timeout; /* Sep 8, 2008 mrgus: default to timeout if not specified */
|
|
}
|
|
break;
|
|
}
|
|
case 'n':
|
|
{
|
|
connect_timeout = atoi(spamc_optarg);
|
|
break;
|
|
}
|
|
case 'u':
|
|
{
|
|
*username = spamc_optarg;
|
|
break;
|
|
}
|
|
case 'L':
|
|
{
|
|
flags |= SPAMC_LEARN;
|
|
if (strcmp(spamc_optarg,"spam") == 0) {
|
|
*extratype = 0;
|
|
}
|
|
else if (strcmp(spamc_optarg,"ham") == 0) {
|
|
*extratype = 1;
|
|
}
|
|
else if (strcmp(spamc_optarg,"forget") == 0) {
|
|
*extratype = 2;
|
|
}
|
|
else {
|
|
libspamc_log(flags, LOG_ERR, "Please specify a legal learn type");
|
|
ret = EX_USAGE;
|
|
}
|
|
break;
|
|
}
|
|
case 'C':
|
|
{
|
|
flags |= SPAMC_REPORT_MSG;
|
|
if (strcmp(spamc_optarg,"report") == 0) {
|
|
*extratype = 0;
|
|
}
|
|
else if (strcmp(spamc_optarg,"revoke") == 0) {
|
|
*extratype = 1;
|
|
}
|
|
else {
|
|
libspamc_log(flags, LOG_ERR, "Please specify a legal report type");
|
|
ret = EX_USAGE;
|
|
}
|
|
break;
|
|
}
|
|
#ifndef _WIN32
|
|
case 'U':
|
|
{
|
|
ptrn->type = TRANSPORT_UNIX;
|
|
ptrn->socketpath = spamc_optarg;
|
|
break;
|
|
}
|
|
#endif
|
|
case 'x':
|
|
{
|
|
flags &= (~SPAMC_SAFE_FALLBACK);
|
|
break;
|
|
}
|
|
case 'X':
|
|
{
|
|
/* Only activates if -x is also used */
|
|
if (!(flags & SPAMC_SAFE_FALLBACK)) {
|
|
flags |= SPAMC_UNAVAIL_TEMPFAIL;
|
|
} else {
|
|
libspamc_log(flags, LOG_ERR, "This option is only valid if -x is set first");
|
|
}
|
|
break;
|
|
}
|
|
case 'y':
|
|
{
|
|
flags |= SPAMC_SYMBOLS;
|
|
break;
|
|
}
|
|
|
|
case '?':
|
|
case ':':
|
|
{
|
|
libspamc_log(flags, LOG_ERR, "invalid usage");
|
|
ret = EX_USAGE;
|
|
/* FALLTHROUGH */
|
|
}
|
|
case 'h':
|
|
{
|
|
print_usage();
|
|
if (ret == EX_OK)
|
|
ret = EX_TEMPFAIL;
|
|
return(ret);
|
|
}
|
|
case 'V':
|
|
{
|
|
print_version();
|
|
return(EX_TEMPFAIL);
|
|
}
|
|
case 'z':
|
|
{
|
|
#ifdef HAVE_ZLIB_H
|
|
flags |= SPAMC_USE_ZLIB;
|
|
#else
|
|
libspamc_log(flags, LOG_ERR, "spamc -z support not available");
|
|
ret = EX_USAGE;
|
|
#endif
|
|
break;
|
|
}
|
|
case '4':
|
|
{
|
|
flags |= SPAMC_USE_INET4;
|
|
flags &= ~SPAMC_USE_INET6;
|
|
break;
|
|
}
|
|
case '6':
|
|
{
|
|
flags |= SPAMC_USE_INET6;
|
|
flags &= ~SPAMC_USE_INET4;
|
|
break;
|
|
}
|
|
case 0:
|
|
{
|
|
ptrn->connect_retries = atoi(spamc_optarg);
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
ptrn->retry_sleep = atoi(spamc_optarg);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
flags |= SPAMC_HEADERS;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
ptrn->filter_retries = atoi(spamc_optarg);
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
ptrn->filter_retry_sleep = atoi(spamc_optarg);
|
|
break;
|
|
}
|
|
#ifdef SPAMC_SSL
|
|
case 5:
|
|
{
|
|
flags |= SPAMC_CLIENT_SSL_CERT;
|
|
ptrn->ssl_cert_file = spamc_optarg;
|
|
break;
|
|
}
|
|
case 6:
|
|
{
|
|
flags |= SPAMC_CLIENT_SSL_CERT;
|
|
ptrn->ssl_key_file = spamc_optarg;
|
|
break;
|
|
}
|
|
case 7:
|
|
{
|
|
ptrn->ssl_ca_file = spamc_optarg;
|
|
break;
|
|
}
|
|
case 8:
|
|
{
|
|
ptrn->ssl_ca_path = spamc_optarg;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef SPAMC_SSL
|
|
if ((flags & SPAMC_CLIENT_SSL_CERT)
|
|
&& !(ptrn->ssl_cert_file && ptrn->ssl_key_file)) {
|
|
libspamc_log(flags, LOG_ERR,
|
|
"--ssl-cert and --ssl-key must be used together");
|
|
ret = EX_USAGE;
|
|
}
|
|
#endif
|
|
|
|
if (*max_size > SPAMC_MAX_MESSAGE_LEN) {
|
|
libspamc_log(flags, LOG_ERR, "-s parameter is beyond max of %d",
|
|
SPAMC_MAX_MESSAGE_LEN);
|
|
ret = EX_USAGE;
|
|
}
|
|
|
|
if ( !(flags & (SPAMC_USE_INET4 | SPAMC_USE_INET6)) ) {
|
|
/* allow any protocol family (INET or INET6) by default */
|
|
flags |= SPAMC_USE_INET4;
|
|
flags |= SPAMC_USE_INET6;
|
|
}
|
|
|
|
/* learning action has to block some parameters */
|
|
if (flags & SPAMC_LEARN) {
|
|
if (flags & SPAMC_CHECK_ONLY) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes check only");
|
|
ret = EX_USAGE;
|
|
}
|
|
if (flags & SPAMC_PING) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes ping");
|
|
ret = EX_USAGE;
|
|
}
|
|
if (flags & SPAMC_REPORT_IFSPAM) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes report if spam");
|
|
ret = EX_USAGE;
|
|
}
|
|
if (flags & SPAMC_REPORT) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes report");
|
|
ret = EX_USAGE;
|
|
}
|
|
if (flags & SPAMC_SYMBOLS) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes symbols");
|
|
ret = EX_USAGE;
|
|
}
|
|
if (flags & SPAMC_REPORT_MSG) {
|
|
libspamc_log(flags, LOG_ERR, "Learning excludes reporting to collaborative filtering databases");
|
|
ret = EX_USAGE;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* combine_args() :: parses spamc.conf for options, and combines those
|
|
* with options passed via command line
|
|
*
|
|
* lines beginning with # or blank lines are ignored
|
|
*
|
|
* returns EX_OK on success, EX_NOINPUT on absence of a config file (success),
|
|
* and EX_CONFIG on failure
|
|
*/
|
|
int
|
|
combine_args(char *config_file, int argc, char **argv,
|
|
int *combo_argc, char **combo_argv)
|
|
{
|
|
FILE *config;
|
|
char option[CONFIG_MAX_LINE_SIZE];
|
|
int i, count = 0;
|
|
char *tok = NULL;
|
|
int is_user_defined_p = 1;
|
|
|
|
if (config_file == NULL) {
|
|
config_file = CONFIG_FILE;
|
|
is_user_defined_p = 0;
|
|
}
|
|
|
|
if ((config = fopen(config_file, "r")) == NULL) {
|
|
if (is_user_defined_p == 1) {
|
|
/* if the config file was user defined we should issue an error */
|
|
fprintf(stderr,"Failed to open config file: %s\n", config_file);
|
|
|
|
return EX_CONFIG;
|
|
}
|
|
return EX_NOINPUT;
|
|
}
|
|
|
|
while (!feof(config) && fgets(option, CONFIG_MAX_LINE_SIZE, config)) {
|
|
int option_l = strlen(option);
|
|
|
|
count++; /* increment the line counter */
|
|
|
|
if (option_l < 1 || option[0] == '#' || option[0] == '\n') {
|
|
continue;
|
|
}
|
|
if (option[option_l-1] != '\n') {
|
|
if (option_l < CONFIG_MAX_LINE_SIZE-1) {
|
|
fprintf(stderr,"Line not terminated with a newline in %s\n",
|
|
config_file);
|
|
} else {
|
|
fprintf(stderr,"Exceeded max line size (%d) in %s\n",
|
|
CONFIG_MAX_LINE_SIZE-2, config_file);
|
|
}
|
|
fclose(config);
|
|
return EX_CONFIG;
|
|
}
|
|
|
|
tok = option;
|
|
while((tok = strtok(tok, " ")) != NULL) {
|
|
if(tok[0] == '\n') break;
|
|
for(i=strlen(tok); i>0; i--) {
|
|
if(tok[i] == '\n')
|
|
tok[i] = '\0';
|
|
}
|
|
if (*combo_argc >= COMBO_ARGV_SIZE) {
|
|
fprintf(stderr,"Exceeded max number of arguments (%d) in %s\n",
|
|
COMBO_ARGV_SIZE, config_file);
|
|
fclose(config);
|
|
return EX_CONFIG;
|
|
}
|
|
combo_argv[*combo_argc] = strdup(tok);
|
|
check_malloc(combo_argv[*combo_argc]);
|
|
/* TODO: leaked. not a big deal since spamc exits quickly */
|
|
tok = NULL;
|
|
*combo_argc+=1;
|
|
}
|
|
}
|
|
|
|
fclose(config);
|
|
|
|
/* note: not starting at 0, that's the command name */
|
|
for(i=1; i<argc; i++) {
|
|
if (*combo_argc >= COMBO_ARGV_SIZE) {
|
|
fprintf(stderr,"Exceeded max number of arguments (%d) in %s\n",
|
|
COMBO_ARGV_SIZE, config_file);
|
|
return EX_CONFIG;
|
|
}
|
|
combo_argv[*combo_argc] = strdup(argv[i]);
|
|
check_malloc(combo_argv[*combo_argc]);
|
|
/* TODO: leaked. not a big deal since spamc exits quickly */
|
|
*combo_argc+=1;
|
|
}
|
|
return EX_OK;
|
|
}
|
|
|
|
void
|
|
get_output_fd(int *fd)
|
|
{
|
|
#ifndef _WIN32
|
|
int pipe_fds[2];
|
|
pid_t pid;
|
|
#endif
|
|
|
|
if (*fd != -1)
|
|
return;
|
|
|
|
/* If we aren't told to feed our output to an external app, we simply
|
|
* write to stdout.
|
|
*/
|
|
if (exec_argv == NULL) {
|
|
*fd = STDOUT_FILENO;
|
|
return;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
/* Create a pipe for communication between child and parent. */
|
|
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) {
|
|
/* This is the child process:
|
|
* Normally you'd expect the parent process here, however that would
|
|
* screw up an invoker waiting on the death of the parent. So instead,
|
|
* we fork a child to feed the data and have the parent exec the new
|
|
* program.
|
|
*/
|
|
close(pipe_fds[0]);
|
|
*fd = pipe_fds[1];
|
|
return;
|
|
}
|
|
|
|
/* This is the parent process (see above) */
|
|
close(pipe_fds[1]);
|
|
if (dup2(pipe_fds[0], STDIN_FILENO)) {
|
|
libspamc_log(flags, LOG_ERR, "redirection of stdin failed: %m");
|
|
exit(EX_OSERR);
|
|
}
|
|
/* No point in leaving extra fds lying around. */
|
|
close(pipe_fds[0]);
|
|
|
|
/* Now execute the command specified. */
|
|
execv(exec_argv[0], exec_argv);
|
|
|
|
/* Whoa, something failed... */
|
|
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);
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines the username of the uid spamc is running under.
|
|
*
|
|
* If the program's caller didn't identify the user to run as, use the
|
|
* current user for this. Note that we're not talking about UNIX perm-
|
|
* issions, but giving SpamAssassin a username so it can do per-user
|
|
* configuration (welcomelists & the like).
|
|
*
|
|
* Allocates memory for the username, returns EX_OK if successful.
|
|
*/
|
|
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
|
|
|
|
/* Get the passwd information for the effective uid spamc is running
|
|
* under. Setting errno to zero is recommended in the manpage.
|
|
*/
|
|
errno = 0;
|
|
curr_user = getpwuid(geteuid());
|
|
if (curr_user == NULL) {
|
|
perror("getpwuid() failed");
|
|
goto fail;
|
|
}
|
|
|
|
/* Since "curr_user" points to static library data, we don't wish to
|
|
* risk some other part of the system overwriting it, so we copy the
|
|
* username to our own buffer -- then this won't arise as a problem.
|
|
*/
|
|
*username = strdup(curr_user->pw_name);
|
|
if (*username == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
#endif
|
|
|
|
pass:
|
|
return EX_OK;
|
|
|
|
fail:
|
|
/* FIXME: The handling of SPAMC_CHECK_ONLY should probably be moved to
|
|
* the end of main()
|
|
*/
|
|
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 = EX_SOFTWARE;
|
|
int ret = EX_SOFTWARE;
|
|
int ret_conf = EX_SOFTWARE;
|
|
int extratype = 0;
|
|
int islearned = 0;
|
|
int isreported = 0;
|
|
|
|
/* these are to hold CLI and config options combined, to be passed
|
|
* to read_args() */
|
|
char *combo_argv[COMBO_ARGV_SIZE];
|
|
int combo_argc;
|
|
|
|
int i;
|
|
char *config_file = NULL;
|
|
|
|
transport_init(&trans);
|
|
|
|
#ifdef LIBSPAMC_UNIT_TESTS
|
|
/* unit test support; divert execution. will not return */
|
|
do_libspamc_unit_tests();
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
openlog("spamc", LOG_CONS | LOG_PID, LOG_MAIL);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
/* set some defaults */
|
|
max_size = 500 * 1024;
|
|
username = NULL;
|
|
|
|
combo_argc = 1;
|
|
combo_argv[0] = strdup(argv[0]);
|
|
check_malloc(combo_argv[0]);
|
|
/* TODO: leaked. not a big deal since spamc exits quickly */
|
|
|
|
for(i=0; i<argc; i++) {
|
|
if(strncmp(argv[i], "-F", 2) == 0) {
|
|
config_file = argv[i+1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
ret_conf = combine_args(config_file, argc, argv, &combo_argc, combo_argv);
|
|
|
|
if (ret_conf == EX_OK) {
|
|
/* Parse the combined arguments of command line and config file */
|
|
if ((ret = read_args(combo_argc, combo_argv, &max_size, &username,
|
|
&extratype, &trans)) != EX_OK)
|
|
{
|
|
if (ret == EX_TEMPFAIL) ret = EX_OK;
|
|
goto finish;
|
|
}
|
|
}
|
|
else if (ret_conf == EX_NOINPUT) { /* no config file read */
|
|
/* parse only command line arguments (default behaviour) */
|
|
if ((ret = read_args(argc, argv, &max_size, &username,
|
|
&extratype, &trans)) != EX_OK)
|
|
{
|
|
if (ret == EX_TEMPFAIL) ret = EX_OK;
|
|
goto finish;
|
|
}
|
|
}
|
|
else { /* ret_conf == EX_CONFIG. or some other error */
|
|
ret = EX_CONFIG;
|
|
goto finish;
|
|
}
|
|
|
|
ret = get_current_user(&username);
|
|
if (ret != EX_OK)
|
|
goto finish;
|
|
|
|
if ((flags & SPAMC_RANDOMIZE_HOSTS) != 0) {
|
|
/* we don't need strong randomness; this is just so we pick
|
|
* a random host for loadbalancing.
|
|
*/
|
|
srand(getpid() ^ (unsigned int)time(NULL));
|
|
}
|
|
|
|
/**********************************************************************
|
|
* SET UP TRANSPORT
|
|
*
|
|
* This takes the user parameters and digs up what it can about how
|
|
* we connect to the spam daemon. Mainly this involves lookup up the
|
|
* hostname and getting the IP addresses to connect to.
|
|
*/
|
|
m.type = MESSAGE_NONE;
|
|
m.out = NULL;
|
|
m.outbuf = NULL;
|
|
m.raw = NULL;
|
|
m.priv = NULL;
|
|
m.max_len = max_size;
|
|
m.timeout = timeout;
|
|
m.connect_timeout = connect_timeout; /* Sep 8, 2008 mrgus: separate connect timeout */
|
|
m.is_spam = EX_NOHOST; /* default err code if can't reach the daemon */
|
|
#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) {
|
|
|
|
if (flags & SPAMC_LEARN) {
|
|
int msg_class = 0;
|
|
unsigned int tellflags = 0;
|
|
unsigned int didtellflags = 0;
|
|
|
|
if ((extratype == 0) || (extratype == 1)) {
|
|
if (extratype == 0) {
|
|
msg_class = SPAMC_MESSAGE_CLASS_SPAM;
|
|
}
|
|
else {
|
|
msg_class = SPAMC_MESSAGE_CLASS_HAM;
|
|
}
|
|
tellflags |= SPAMC_SET_LOCAL;
|
|
}
|
|
else {
|
|
tellflags |= SPAMC_REMOVE_LOCAL;
|
|
}
|
|
|
|
ret = message_tell(&trans, username, flags, &m, msg_class,
|
|
tellflags, &didtellflags);
|
|
|
|
if (ret == EX_OK) {
|
|
if ((extratype == 0) || (extratype == 1)) {
|
|
if (didtellflags & SPAMC_SET_LOCAL) {
|
|
islearned = 1;
|
|
}
|
|
}
|
|
else {
|
|
if (didtellflags & SPAMC_REMOVE_LOCAL) {
|
|
islearned = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (flags & SPAMC_REPORT_MSG) {
|
|
int msg_class = 0;
|
|
unsigned int tellflags = 0;
|
|
unsigned int didtellflags = 0;
|
|
|
|
if (extratype == 0) {
|
|
msg_class = SPAMC_MESSAGE_CLASS_SPAM;
|
|
tellflags |= SPAMC_SET_REMOTE;
|
|
tellflags |= SPAMC_SET_LOCAL;
|
|
}
|
|
else {
|
|
msg_class = SPAMC_MESSAGE_CLASS_HAM;
|
|
tellflags |= SPAMC_SET_LOCAL;
|
|
tellflags |= SPAMC_REMOVE_REMOTE;
|
|
}
|
|
|
|
ret = message_tell(&trans, username, flags, &m, msg_class,
|
|
tellflags, &didtellflags);
|
|
|
|
if (ret == EX_OK) {
|
|
if (extratype == 0) {
|
|
if (didtellflags & SPAMC_SET_REMOTE) {
|
|
isreported = 1;
|
|
}
|
|
}
|
|
else {
|
|
if (didtellflags & SPAMC_REMOVE_REMOTE) {
|
|
isreported = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ret = message_filter(&trans, username, flags, &m);
|
|
}
|
|
|
|
free(username); username = NULL;
|
|
|
|
if (ret == EX_OK) {
|
|
|
|
get_output_fd(&out_fd);
|
|
|
|
if (flags & SPAMC_LEARN) {
|
|
if (islearned == 1) {
|
|
printf("Message successfully un/learned\n");
|
|
}
|
|
else {
|
|
printf("Message was already un/learned\n");
|
|
}
|
|
message_cleanup(&m);
|
|
goto finish;
|
|
}
|
|
else if (flags & SPAMC_REPORT_MSG) {
|
|
if (isreported == 1) {
|
|
printf("Message successfully reported/revoked\n");
|
|
}
|
|
else {
|
|
printf("Unable to report/revoke message\n");
|
|
}
|
|
message_cleanup(&m);
|
|
goto finish;
|
|
}
|
|
else 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);
|
|
|
|
/* FAIL: */
|
|
result = m.is_spam;
|
|
if (ret != EX_OK) {
|
|
result = ret;
|
|
}
|
|
|
|
if (flags & (SPAMC_LEARN|SPAMC_PING) ) {
|
|
get_output_fd(&out_fd);
|
|
message_cleanup(&m);
|
|
}
|
|
else {
|
|
if (flags & (SPAMC_CHECK_ONLY | SPAMC_REPORT | SPAMC_REPORT_IFSPAM)) {
|
|
get_output_fd(&out_fd);
|
|
full_write(out_fd, 1, "0/0\n", 4);
|
|
}
|
|
else if (flags & SPAMC_SYMBOLS) {
|
|
/* bug 4991: -y should only output a blank line on connection failure */
|
|
get_output_fd(&out_fd);
|
|
full_write(out_fd, 1, "\n", 1);
|
|
}
|
|
else {
|
|
/* bug 5412: spamc -x should not output the message on error */
|
|
if ((flags & SPAMC_SAFE_FALLBACK) || result == EX_TOOBIG) {
|
|
get_output_fd(&out_fd);
|
|
message_dump(STDIN_FILENO, out_fd, &m, flags);
|
|
}
|
|
/* else, do NOT get_output_fd() (bug 5478) */
|
|
}
|
|
|
|
message_cleanup(&m);
|
|
if (ret == EX_TOOBIG) {
|
|
ret = EX_OK; /* too big always means exit(0) -- bug 5412 */
|
|
}
|
|
else if (flags & SPAMC_SAFE_FALLBACK) {
|
|
ret = EX_OK;
|
|
}
|
|
else if (use_exit_code) {
|
|
ret = result;
|
|
}
|
|
|
|
/* If -x and -X are used, change from EX_UNAVAILABLE TO EX_TEMPFAIL - bug 6717 */
|
|
if ((!(flags & SPAMC_SAFE_FALLBACK)) && (flags & SPAMC_UNAVAIL_TEMPFAIL) && (ret == EX_UNAVAILABLE)) {
|
|
ret = EX_TEMPFAIL;
|
|
}
|
|
}
|
|
|
|
finish:
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
return ret;
|
|
}
|