/* $Id: agent_shared.c,v 1.86 2011/07/11 22:05:48 sbajic Exp $ */ /* DSPAM COPYRIGHT (C) 2002-2012 DSPAM PROJECT This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /* * agent_shared.c - shared agent-based components * * DESCRIPTION * agent-based components shared between the full dspam agent (dspam) * and the lightweight client agent (dspamc) */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #include #endif #include #include #include #ifdef _WIN32 #include #include #define WIDEXITED(x) 1 #define WEXITSTATUS(x) (x) #include #else #include #include #endif #include "util.h" #include "read_config.h" #ifdef DAEMON #include "daemon.h" #include "dspam.h" #endif #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #include "agent_shared.h" #include "language.h" #include "buffer.h" char * __pw_name = NULL; uid_t __pw_uid; /* * initialize_atx(AGENT_CTX *) * * DESCRIPTION * initializes an existing agent context * * INPUT ARGUMENTS * ATX agent context to initialize * * RETURN VALUES * returns 0 on success */ int initialize_atx(AGENT_CTX *ATX) { memset(ATX, 0, sizeof(AGENT_CTX)); ATX->training_buffer = 0; ATX->train_pristine = 0; ATX->classification = DSR_NONE; ATX->source = DSS_NONE; ATX->operating_mode = DSM_PROCESS; ATX->fork = 1; ATX->users = nt_create (NT_CHAR); if (ATX->users == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } #ifdef TRUSTED_USER_SECURITY if (!__pw_name) { LOG(LOG_ERR, ERR_AGENT_RUNTIME_USER); exit(EXIT_FAILURE); } LOGDEBUG("checking trusted user list for %s(%d)", __pw_name, __pw_uid); if (__pw_uid == 0) ATX->trusted = 1; else ATX->trusted = _ds_match_attribute(agent_config, "Trust", __pw_name); if (!ATX->trusted) nt_add (ATX->users, __pw_name); #endif return 0; } /* * process_arguments(AGENT_CTX *, int argc, char *argv[]) * * DESCRIPTION * master commandline argument process loop * * INPUT ARGUMENTS * ATX agent context * argc number of arguments provided * argv array of arguments * * RETURN VALUES * returns 0 on success, EINVAL when invalid options specified */ int process_arguments(AGENT_CTX *ATX, int argc, char **argv) { int flag_u = 0, flag_r = 0; int client = (_ds_read_attribute(agent_config, "ClientHost") != NULL); char *ptrptr; int i; #ifdef DEBUG ATX->debug_args[0] = 0; #endif ATX->client_args[0] = 0; for (i=0; idebug_args, argv[i], sizeof (ATX->debug_args)); strlcat (ATX->debug_args, " ", sizeof (ATX->debug_args)); #endif /* Terminate user/rcpt lists */ if ((flag_u || flag_r) && (argv[i][0] == '-' || argv[i][0] == 0 || !strcmp(argv[i], "--"))) { flag_u = flag_r = 0; if (!strcmp(argv[i], "--")) continue; } if (!strcmp (argv[i], "--user")) { flag_u = 1; continue; } if (!strcmp (argv[i], "--rcpt-to")) { if (!ATX->recipients) { ATX->recipients = nt_create(NT_CHAR); if (ATX->recipients == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } } flag_r = 1; continue; } /* Build arg list to pass to server (when in client/server mode) */ if (client && !flag_u && !flag_r && i>0) { if (argv[i][0] == 0) strlcat(ATX->client_args, "\"", sizeof(ATX->client_args)); strlcat (ATX->client_args, argv[i], sizeof(ATX->client_args)); if (argv[i][0] == 0) strlcat(ATX->client_args, "\"", sizeof(ATX->client_args)); strlcat (ATX->client_args, " ", sizeof(ATX->client_args)); } if (!strcmp (argv[i], "--debug")) { #ifdef DEBUG if (DO_DEBUG == 0) DO_DEBUG = 1; #endif continue; } #if defined(DAEMON) && !defined(_DSPAMC_H) if (!strcmp (argv[i], "--client")) { ATX->client_mode = 1; continue; } #ifdef TRUSTED_USER_SECURITY if (!strcmp (argv[i], "--daemon") && ATX->trusted) #else if (!strcmp (argv[i], "--daemon")) #endif { ATX->operating_mode = DSM_DAEMON; continue; } #endif if (!strcmp (argv[i], "--nofork")) { ATX->fork = 0; continue; } if (!strncmp (argv[i], "--mode=", 7)) { char *mode = strchr(argv[i], '=')+1; if (process_mode(ATX, mode)) return EINVAL; ATX->flags |= DAF_FIXED_TR_MODE; continue; } /* Build RCPT TO list */ if (flag_r) { if (argv[i] != NULL && strlen (argv[i]) < MAX_USERNAME_LENGTH) { char user[MAX_USERNAME_LENGTH]; if (_ds_match_attribute(agent_config, "Broken", "case")) lc(user, argv[i]); else strcpy(user, argv[i]); #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted && strcmp(user, __pw_name)) { LOG(LOG_ERR, ERR_TRUSTED_USER, __pw_uid, __pw_name); return EINVAL; } if (ATX->trusted) { #endif if (_ds_validate_address(user) == 1) { nt_add (ATX->recipients, user); } else { LOG(LOG_ERR, "Invalid email address: %s", user); return EINVAL; } #ifdef TRUSTED_USER_SECURITY } #endif } continue; } /* Build process user list */ if (flag_u) { if (argv[i] != NULL && strlen (argv[i]) < MAX_USERNAME_LENGTH) { if (strstr(argv[i], "../") != NULL || strstr(argv[i], "..\\") != NULL) { LOG(LOG_ERR, "Illegal username ('../' or '..\\' not allowed in username)"); return EINVAL; } else { char user[MAX_USERNAME_LENGTH]; if (_ds_match_attribute(agent_config, "Broken", "case")) lc(user, argv[i]); else strcpy(user, argv[i]); #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted && strcmp(user, __pw_name)) { LOG(LOG_ERR, ERR_TRUSTED_USER, __pw_uid, __pw_name); return EINVAL; } if (ATX->trusted) #endif nt_add (ATX->users, user); } } continue; } if (!strncmp (argv[i], "--mail-from=", 12)) { strlcpy(ATX->mailfrom, strchr(argv[i], '=')+1, sizeof(ATX->mailfrom)); LOGDEBUG("MAIL FROM: %s", ATX->mailfrom); continue; } if (!strncmp (argv[i], "--profile=", 10)) { #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted) { LOG(LOG_ERR, ERR_TRUSTED_PRIV, "--profile", __pw_uid, __pw_name); return EINVAL; } #endif if (!_ds_match_attribute(agent_config, "Profile", argv[i]+10)) { LOG(LOG_ERR,ERR_AGENT_NO_SUCH_PROFILE, argv[i]+10); return EINVAL; } else { _ds_overwrite_attribute(agent_config, "DefaultProfile", argv[i]+10); } continue; } if (!strncmp (argv[i], "--signature=", 12)) { strlcpy(ATX->signature, strchr(argv[i], '=')+1, sizeof(ATX->signature)); continue; } if (!strncmp (argv[i], "--class=", 8)) { char *ptr = strchr(argv[i], '=')+1; char *spam = _ds_read_attribute(agent_config, "ClassAliasSpam"); char *nonspam = _ds_read_attribute(agent_config, "ClassAliasNonspam"); if (!strcmp(ptr, "spam") || (spam && !strcmp(ptr, spam))) { ATX->classification = DSR_ISSPAM; } else if (!strcmp(ptr, "innocent") || !strcmp(ptr, "nonspam") || (nonspam && !strcmp(ptr, nonspam))) { ATX->classification = DSR_ISINNOCENT; } else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_CLASS, ptr); return EINVAL; } continue; } if (!strncmp (argv[i], "--source=", 9)) { char *ptr = strchr(argv[i], '=')+1; if (!strcmp(ptr, "corpus")) ATX->source = DSS_CORPUS; else if (!strcmp(ptr, "inoculation")) ATX->source = DSS_INOCULATION; else if (!strcmp(ptr, "error")) ATX->source = DSS_ERROR; else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_SOURCE, ptr); return EINVAL; } continue; } if (!strcmp (argv[i], "--classify")) { ATX->operating_mode = DSM_CLASSIFY; ATX->training_mode = DST_NOTRAIN; continue; } if (!strcmp (argv[i], "--process")) { ATX->operating_mode = DSM_PROCESS; continue; } if (!strncmp (argv[i], "--deliver=", 10)) { char *dup = strdup(strchr(argv[i], '=')+1); char *ptr; if (dup == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } ptr = strtok_r(dup, ",", &ptrptr); while(ptr != NULL) { if (!strcmp(ptr, "stdout")) { ATX->flags |= DAF_DELIVER_SPAM; ATX->flags |= DAF_DELIVER_INNOCENT; ATX->flags |= DAF_STDOUT; } else if (!strcmp(ptr, "spam")) ATX->flags |= DAF_DELIVER_SPAM; else if (!strcmp(ptr, "innocent") || !strcmp(ptr, "nonspam")) ATX->flags |= DAF_DELIVER_INNOCENT; else if (!strcmp(ptr, "summary")) ATX->flags |= DAF_SUMMARY; else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_DELIVER, ptr); free(dup); return EINVAL; } ptr = strtok_r(NULL, ",", &ptrptr); } free(dup); continue; } if (!strncmp (argv[i], "--feature=", 10)) { ATX->feature = 1; process_features(ATX, strchr(argv[i], '=')+1); continue; } if (!strcmp (argv[i], "--stdout")) { ATX->flags |= DAF_STDOUT; continue; } if (!strcmp (argv[i], "--help")) { fprintf (stderr, "%s\n", SYNTAX); exit(EXIT_SUCCESS); } if (!strcmp (argv[i], "--version")) { printf ("\nDSPAM Anti-Spam Suite %s (agent/library)\n\n", VERSION); printf ("Copyright (C) 2002-2012 DSPAM Project\n"); printf ("http://dspam.sourceforge.net.\n\n"); printf ("DSPAM may be copied only under the terms of the GNU Affero General Public\n"); printf ("License, a copy of which can be found with the DSPAM distribution kit.\n\n"); #ifdef TRUSTED_USER_SECURITY if (ATX->trusted) { #endif printf("Configuration parameters: %s\n\n", CONFIGURE_ARGS); #ifdef TRUSTED_USER_SECURITY } #endif exit (EXIT_SUCCESS); } /* Append all unknown arguments as mailer args */ if (i>0 #ifdef TRUSTED_USER_SECURITY && ATX->trusted #endif ) { if (argv[i][0] == 0) strlcat (ATX->mailer_args, "\"\"", sizeof (ATX->mailer_args)); else strlcat (ATX->mailer_args, argv[i], sizeof (ATX->mailer_args)); strlcat (ATX->mailer_args, " ", sizeof (ATX->mailer_args)); } } return 0; } /* * process_features(AGENT_CTX *, const char *) * * DESCRIPTION * convert --feature= stdin into agent context values * * INPUT ARGUMENTS * ATX agent context * in remainder of --feature= stdin * * RETURN VALUES * returns 0 on success, EINVAL when invalid options specified * */ int process_features(AGENT_CTX *ATX, const char *in) { char *ptr, *dup, *ptrptr; int ret = 0; if (!in || in[0]==0) return 0; dup = strdup(in); if (dup == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } ptr = strtok_r(dup, ",", &ptrptr); while(ptr != NULL) { if (!strncmp(ptr, "no",2)) ATX->flags |= DAF_NOISE; else if (!strncmp(ptr, "wh", 2)) ATX->flags |= DAF_WHITELIST; else if (!strncmp(ptr, "tb=", 3)) { ATX->training_buffer = atoi(strchr(ptr, '=')+1); if (ATX->training_buffer < 0 || ATX->training_buffer > 10) { LOG(LOG_ERR, ERR_AGENT_TB_INVALID); ret = EINVAL; } } else { LOG(LOG_ERR, ERR_AGENT_NO_SUCH_FEATURE, ptr); ret = EINVAL; } ptr = strtok_r(NULL, ",", &ptrptr); } free(dup); return ret; } /* * process_mode(AGENT_CTX *, const char *) * * DESCRIPTION * convert --mode= stdin into training mode * * INPUT ARGUMENTS * ATX agent context * mode remainder of --mode= stdin * * RETURN VALUES * returns 0 on success, EINVAL when invalid mode specified */ int process_mode(AGENT_CTX *ATX, const char *mode) { if (!mode) return EINVAL; if (!strcmp(mode, "toe")) ATX->training_mode = DST_TOE; else if (!strcmp(mode, "teft")) ATX->training_mode = DST_TEFT; else if (!strcmp(mode, "tum")) ATX->training_mode = DST_TUM; else if (!strcmp(mode, "notrain")) ATX->training_mode = DST_NOTRAIN; else if (!strcmp(mode, "unlearn")) { ATX->training_mode = DST_TEFT; ATX->flags |= DAF_UNLEARN; } else { LOG(LOG_ERR, ERR_AGENT_TR_MODE_INVALID, mode); return EINVAL; } return 0; } /* * apply_defaults(AGENT_CTX *) * * DESCRIPTION * apply default values from dspam.conf in absence of other options * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * returns 0 on success */ int apply_defaults(AGENT_CTX *ATX) { /* Training mode */ if (!(ATX->flags & DAF_FIXED_TR_MODE)) { char *v = _ds_read_attribute(agent_config, "TrainingMode"); if (process_mode(ATX, v)) { LOG(LOG_ERR, ERR_AGENT_NO_TR_MODE); return EINVAL; } } /* Default delivery agent */ if ( ! (ATX->flags & DAF_STDOUT) && ATX->operating_mode != DSM_CLASSIFY && (ATX->flags & DAF_DELIVER_INNOCENT || ATX->flags & DAF_DELIVER_SPAM)) { char key[32]; #ifdef TRUSTED_USER_SECURITY if (!ATX->trusted) strcpy(key, "UntrustedDeliveryAgent"); else #endif strcpy(key, "TrustedDeliveryAgent"); char *value = _ds_read_attribute(agent_config, key); if (value) { char *trimmed_value = ALLTRIM(strdup(value)); if (trimmed_value && *trimmed_value == '\0') { LOG(LOG_ERR, ERR_AGENT_NO_AGENT, key); free(trimmed_value); return EINVAL; } if (trimmed_value) free(trimmed_value); char fmt[sizeof(ATX->mailer_args)]; snprintf(fmt, sizeof(fmt), "%s ", value); #ifdef TRUSTED_USER_SECURITY if (ATX->trusted) #endif strlcat(fmt, ATX->mailer_args, sizeof(fmt)); strcpy(ATX->mailer_args, fmt); } else if (!_ds_read_attribute(agent_config, "DeliveryHost")) { LOG(LOG_ERR, ERR_AGENT_NO_AGENT, key); return EINVAL; } } /* Default quarantine agent */ if (_ds_read_attribute(agent_config, "QuarantineAgent")) { snprintf(ATX->spam_args, sizeof(ATX->spam_args), "%s ", _ds_read_attribute(agent_config, "QuarantineAgent")); } else { LOGDEBUG("No QuarantineAgent option found. Using standard quarantine."); } /* Features */ if (!ATX->feature && _ds_find_attribute(agent_config, "Feature")) { attribute_t attrib = _ds_find_attribute(agent_config, "Feature"); while(attrib != NULL) { process_features(ATX, attrib->value); attrib = attrib->next; } } return 0; } /* * check_configuration(AGENT_CTX *) * * DESCRIPTION * sanity-check agent configuration * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * returns 0 on success, EINVAL on invalid configuration */ int check_configuration(AGENT_CTX *ATX) { if (ATX->classification != DSR_NONE && ATX->operating_mode == DSM_CLASSIFY) { LOG(LOG_ERR, ERR_AGENT_CLASSIFY_CLASS); return EINVAL; } if (ATX->classification != DSR_NONE && ATX->source == DSS_NONE && !(ATX->flags & DAF_UNLEARN)) { LOG(LOG_ERR, ERR_AGENT_NO_SOURCE); return EINVAL; } if (ATX->source != DSS_NONE && ATX->classification == DSR_NONE) { LOG(LOG_ERR, ERR_AGENT_NO_CLASS); return EINVAL; } if (ATX->operating_mode == DSM_NONE) { LOG(LOG_ERR, ERR_AGENT_NO_OP_MODE); return EINVAL; } if (!_ds_match_attribute(agent_config, "ParseToHeaders", "on")) { if (ATX->users->items == 0) { LOG(LOG_ERR, ERR_AGENT_USER_UNDEFINED); return EINVAL; } } return 0; } /* * read_stdin(AGENT_CTX *) * * DESCRIPTION * read message from stdin and perform any inline configuration * (such as servicing 'ParseToHeaders' functions) * * INPUT ARGUMENTS * ATX agent context * * RETURN VALUES * buffer structure containing the message */ buffer * read_stdin(AGENT_CTX *ATX) { int body = 0, line = 1; char buf[1024]; buffer *msg; msg = buffer_create(NULL); if (msg == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return NULL; } if (_ds_match_attribute(agent_config, "DataSource", "document")) { buffer_cat(msg, ": \n\n"); body = 1; } /* Only read the message if no signature was provided on commandline */ if (ATX->signature[0] == 0) { while ((fgets (buf, sizeof (buf), stdin)) != NULL) { /* Strip CR/LFs for admittedly broken mail servers */ if (_ds_match_attribute(agent_config, "Broken", "lineStripping")) { size_t len = strlen(buf); while (len>1 && buf[len-2]==13) { buf[len-2] = buf[len-1]; buf[len-1] = 0; len--; } } /* * Don't include first line of message if it's a quarantine header added * by dspam at time of quarantine */ if (line==1 && !strncmp(buf, "From QUARANTINE", 15)) continue; /* * Parse the "To" headers and adjust the operating mode and user when * an email is sent to spam-* or notspam-* address. Behavior must be * configured in dspam.conf */ if (_ds_match_attribute(agent_config, "ParseToHeaders", "on")) { if (buf[0] == 0) body = 1; if (!body && !strncasecmp(buf, "To: ", 4)) process_parseto(ATX, buf); } if (buffer_cat (msg, buf)) { LOG (LOG_CRIT, ERR_MEM_ALLOC); goto bail; } /* * Use the original user id if we are reversing a false positive * (this is only necessary when using shared,managed groups */ if (!strncasecmp (buf, "X-DSPAM-User: ", 14) && ATX->operating_mode == DSM_PROCESS && ATX->classification == DSR_ISINNOCENT && ATX->source == DSS_ERROR) { char user[MAX_USERNAME_LENGTH]; strlcpy (user, buf + 14, sizeof (user)); chomp (user); nt_destroy (ATX->users); ATX->users = nt_create (NT_CHAR); if (ATX->users == NULL) { LOG(LOG_CRIT, ERR_MEM_ALLOC); goto bail; } LOGDEBUG("found username %s in X-DSPAM-User header", user); nt_add (ATX->users, user); } line++; } } if (!msg->used) { if (ATX->signature[0] != 0) { buffer_cat(msg, "\n\n"); } else { LOG (LOG_INFO, "empty message (no data received)"); goto bail; } } return msg; bail: LOGDEBUG("read_stdin() failure"); buffer_destroy(msg); return NULL; } /* * process_parseto(AGENT_CTX *, const char *) * * DESCRIPTION * processes the To: line of a message to provide parseto services * * INPUT ARGUMENTS * ATX agent context * buf To: line * * RETURN VALUES * returns 0 on success */ int process_parseto(AGENT_CTX *ATX, const char *buf) { char *y = NULL; char *x; char *h = NULL; char *buffer; char *ptrptr; if (!buf || strncmp(buf+2,":",1) != 0) return EINVAL; buffer = strdup (buf+3); h = strtok_r (buffer, "\n", &ptrptr); while (h != NULL) { /* check for spam alias */ x = strstr(h, "classification = DSR_ISSPAM; ATX->source = DSS_ERROR; } } else { /* check for nonspam alias */ x = strstr(h, "= 9) { y = strdup(x+9); if (_ds_match_attribute(agent_config, "ChangeModeOnParse", "on")) { ATX->classification = DSR_ISINNOCENT; ATX->source = DSS_ERROR; } } } /* do not continue if we found a spam/nonspam alias */ if (y) break; /* get next line from 'To' header */ h = strtok_r (NULL, "\n", &ptrptr); if (h && h[0] != 32 && h[0] != 9) { /* we are not any more in the 'To' header */ break; } } free (buffer); if (y && (_ds_match_attribute(agent_config, "ChangeUserOnParse", "on") || _ds_match_attribute(agent_config, "ChangeUserOnParse", "full") || _ds_match_attribute(agent_config, "ChangeUserOnParse", "user"))) { char *z; if (_ds_match_attribute(agent_config, "ChangeUserOnParse", "full")) { z = strtok_r(y, ">, \t\r\n", &ptrptr); } else { if (strstr(x, "@")) z = strtok_r(y, "@", &ptrptr); else z = NULL; } if (z) { nt_destroy(ATX->users); ATX->users = nt_create(NT_CHAR); if (!ATX->users) { LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } nt_add (ATX->users, z); } } if (y) free(y); return 0; } int init_pwent_cache(void) { struct passwd *pwent; pwent = getpwuid(getuid()); if (pwent == NULL) { return 0; } else { __pw_name = strdup(pwent->pw_name); __pw_uid = pwent->pw_uid; } return 1; }