$NetBSD: patch-srs-filter.c,v 1.1 2022/05/25 15:45:32 manu Exp $ - Update pidfile after forking - Process addresses with or without enclosing brackets - Add -u/--user option to use unprivilegied user - Add -A/--alias-cmd and -R/--aliad_regex to resolve aliases - Add -n/--reverse-null to ensable SRS reverse for null sender <> --- srs-filter.c.orig 2022-05-11 14:10:11.430909881 +0200 +++ srs-filter.c 2022-05-17 08:53:10.516904377 +0200 @@ -3,11 +3,13 @@ #include #include #include #include +#include #include #include #include +#include #include #include #include #include @@ -37,10 +39,16 @@ /* these should be read from command line or config file */ static int CONFIG_verbose = 0; static int CONFIG_forward = 0; static int CONFIG_reverse = 0; +static int CONFIG_reverse_null = 0; static char *CONFIG_socket = NULL; +static char *CONFIG_user = NULL; +static char *CONFIG_pidfile = NULL; static char **CONFIG_domains = NULL; +static char *CONFIG_alias_cmd = NULL; +static char *CONFIG_alias_regex_str = "^\\(.*\\)\\.\\.\\. deliverable"; +static regex_t CONFIG_alias_regex; static int CONFIG_spf_check = 0; static char *CONFIG_spf_heloname = NULL; static union { struct sockaddr_in in; @@ -70,9 +78,43 @@ srs_t *srs; SPF_server_t *spf; }; +static char *srs_milter_resolve_alias(char *recip) { + char *recip_alias = NULL; + char *alias_cmd = NULL; + FILE *f = NULL; + if (asprintf(&alias_cmd, CONFIG_alias_cmd, recip) < 0) + goto out; + + if ((f = popen(alias_cmd, "r")) == NULL) { + syslog(LOG_ERR, "failed to run alias-cmd %s: %s", alias_cmd, strerror(errno)); + goto out; + } + + do { + char line[4096]; + regmatch_t pm[2]; + + if (fgets(line, sizeof(line), f) == NULL) + break; + + if (regexec(&CONFIG_alias_regex, line, 2, pm, 0) == 0) { + recip_alias = strndup(line + pm[1].rm_so, pm[1].rm_eo - pm[1].rm_so); + break; + } + } while (!feof(f)); + +out: + if (f) + pclose(f); + + if (alias_cmd) + free(alias_cmd); + + return recip_alias; +} char *srs_milter_load_file_secrets(char ***CONFIG_srs_secrets, char *secrets_file) { int i, l; FILE *f; @@ -114,9 +156,9 @@ int is_local_addr(const char *addr) { int i, r; const char *dom; - if (!addr) + if (!addr || !*addr) return 0; if (!CONFIG_domains) return 0; @@ -227,17 +269,21 @@ static sfsistat xxfi_srs_milter_envfrom(SMFICTX* ctx, char** argv) { struct srs_milter_connection_data* cd = (struct srs_milter_connection_data*) smfi_getpriv(ctx); + int invalid_addr = 0; + int null_addr = 0; if (cd->state & SS_STATE_INVALID_CONN) return SMFIS_CONTINUE; if (CONFIG_verbose) syslog(LOG_DEBUG, "conn# %d[%i] - xxfi_srs_milter_envfrom(\"%s\")", cd->num, cd->state, argv[0]); - if (strlen(argv[0]) < 1 || strcmp(argv[0], "<>") == 0 || argv[0][0] != '<' || argv[0][strlen(argv[0])-1] != '>' || !strchr(argv[0], '@')) { + null_addr = (strcmp(argv[0], "<>") == 0); + invalid_addr = (argv[0][0] == '\0' || (!strchr(argv[0], '@' && !null_addr))); + if (invalid_addr || (null_addr && !CONFIG_reverse_null)) { cd->state |= SS_STATE_INVALID_MSG; if (CONFIG_verbose) syslog(LOG_DEBUG, "conn# %d[%i] - xxfi_srs_milter_envfrom(\"%s\"): skipping \"MAIL FROM: %s\"", cd->num, cd->state, argv[0], argv[0]); @@ -272,16 +318,20 @@ cd->recip_remote = 0; // strore MAIL FROM: address - cd->sender = (char *) malloc(strlen(argv[0])-1); + cd->sender = (char *) malloc(strlen(argv[0])+1); if (!cd->sender) { // memory allocation problem cd->state |= SS_STATE_INVALID_MSG; return SMFIS_CONTINUE; } - strncpy(cd->sender, argv[0]+1, strlen(argv[0])-2); - cd->sender[strlen(argv[0])-2] = '\0'; + if (argv[0][0] == '<') { + strncpy(cd->sender, argv[0]+1, strlen(argv[0])-2); + cd->sender[strlen(argv[0])-2] = '\0'; + } else { + strcpy(cd->sender, argv[0]); + } // store MAIL FROM: arguments { int argc = 0; @@ -316,16 +366,30 @@ syslog(LOG_DEBUG, "conn# %d[%i] - xxfi_srs_milter_envrcpt(\"%s\")", cd->num, cd->state, argv[0]); // get recipient address - char *recip = (char *) malloc(strlen(argv[0])-1); + char *recip = (char *) malloc(strlen(argv[0])+1); if (!recip) { // memory allocation problem cd->state |= SS_STATE_INVALID_MSG; return SMFIS_CONTINUE; } - strncpy(recip, argv[0]+1, strlen(argv[0])-2); - recip[strlen(argv[0])-2] = '\0'; + if (argv[0][0] == '<') { + strncpy(recip, argv[0]+1, strlen(argv[0])-2); + recip[strlen(argv[0])-2] = '\0'; + } else { + strcpy(recip, argv[0]); + } + + if (CONFIG_alias_cmd && is_local_addr(recip)) { + char *recip_alias; + + if ((recip_alias = srs_milter_resolve_alias(recip)) != NULL) { + syslog(LOG_DEBUG, "resolved recipient %s as %s", recip, recip_alias); + free(recip); + recip = recip_alias; + } + } if (!is_local_addr(recip)) { cd->recip_remote = 1; } else { @@ -345,9 +409,13 @@ if (!cd->recip) { // memory allocation problem cd->state |= SS_STATE_INVALID_MSG; } else { - cd->recip[argc] = strdup(argv[0]); + if (argv[0][0] == '<') { + cd->recip[argc] = strndup(argv[0] + 1, strlen(argv[0]) - 2); + } else { + cd->recip[argc] = strdup(argv[0]); + } cd->recip[argc+1] = NULL; if (!cd->recip[argc]) { // memory allocation problem cd->state |= SS_STATE_INVALID_MSG; @@ -669,8 +737,17 @@ syslog(LOG_ERR, "exiting parent process"); exit(EXIT_SUCCESS); } + /* update pidfile */ + if (CONFIG_pidfile) { + FILE *f; + + f = fopen(CONFIG_pidfile, "w"); + fprintf(f, "%i", (int) getpid()); + fclose(f); + } + /* Change the file mode mask */ umask(0); /* Open any logs here */ @@ -717,17 +794,27 @@ printf(" -s, --socket\n"); printf(" {unix|local}:/path/to/file -- a named pipe.\n"); printf(" inet:port@{hostname|ip-address} -- an IPV4 socket.\n"); printf(" inet6:port@{hostname|ip-address} -- an IPV6 socket.\n"); + printf(" -u, --user\n"); + printf(" unprivilegied user we should run as\n"); printf(" -t, --timeout\n"); printf(" milter timeout\n"); printf(" -f, --forward\n"); printf(" SRS encode the envelope sender of non-local-destined mail\n"); printf(" -r, --reverse\n"); printf(" SRS decode any envelope recipients of local SRS addresses\n"); + printf(" -n, --reverse-null\n"); + printf(" Also decode SRS addresses when sender is <>\n"); printf(" -m, --domain\n"); printf(" all local mail domains for that we accept mail\n"); printf(" starting domain name with \".\" match also all subdomains\n"); + printf(" -A, --alias-cmd\n"); + printf(" external command to resolve aliases\n"); + printf(" hint: \"sendmail -bv %%s\", %%s gets replaced by recipient\n"); + printf(" -R, --alias-regex\n"); + printf(" regex to extract resolved alias from alias-cmd output\n"); + printf(" default value matches sendmail -bv output\n"); printf(" -o, --srs-domain\n"); printf(" our SRS domain name\n"); printf(" -c, --srs-secret\n"); printf(" secret string for SRS hashing algorithm\n"); @@ -770,12 +857,16 @@ {"debug", no_argument, 0, 'd'}, {"verbose", no_argument, 0, 'v'}, {"pidfile", required_argument, 0, 'P'}, {"socket", required_argument, 0, 's'}, + {"user", required_argument, 0, 'u'}, {"timeout", required_argument, 0, 't'}, {"forward", no_argument, 0, 'f'}, {"reverse", no_argument, 0, 'r'}, + {"reverse-null", no_argument, 0, 'n'}, {"local-domain", required_argument, 0, 'm'}, + {"alias-cmd", required_argument, 0, 'A'}, + {"alias-regex", required_argument, 0, 'R'}, {"spf-check", no_argument, 0, 'k'}, {"spf-heloname", required_argument, 0, 'l'}, {"spf-address", required_argument, 0, 'a'}, {"srs-domain", required_argument, 0, 'o'}, @@ -791,9 +882,9 @@ }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "hdvP:s:t:f:r:mk:t:l:a:o:yc:C:wg:i:x:e:", + c = getopt_long(argc, argv, "hdvP:s:u:t:f:rnm:A:R:kt:l:a:o:yc:C:wg:i:x:e:", long_options, &option_index); /* Detect the end of the options. */ if (c == -1) @@ -823,17 +914,22 @@ CONFIG_verbose = 1; break; case 'P': - f = fopen(optarg, "w"); + CONFIG_pidfile = optarg; + f = fopen(CONFIG_pidfile, "w"); fprintf(f, "%i", (int) getpid()); fclose(f); break; case 's': CONFIG_socket = optarg; break; + case 'u': + CONFIG_user = optarg; + break; + case 't': if (optarg == NULL || *optarg == '\0') { fprintf(stderr, "ERROR: illegal timeout %s\n", optarg); exit(EXIT_FAILURE); @@ -851,8 +947,12 @@ case 'r': CONFIG_reverse = 1; break; + case 'n': + CONFIG_reverse_null = 1; + break; + case 'm': i = 0; if (!CONFIG_domains) { CONFIG_domains = (char **) malloc((i+2)*sizeof(char *)); @@ -863,8 +963,16 @@ CONFIG_domains[i] = optarg; CONFIG_domains[i+1] = NULL; break; + case 'A': + CONFIG_alias_cmd = optarg; + break; + + case 'R': + CONFIG_alias_regex_str = optarg; + break; + case 'k': CONFIG_spf_check = 1; break; @@ -941,8 +1049,32 @@ printf ("%s ", argv[optind++]); putchar ('\n'); } + if (CONFIG_user) { + struct passwd *pw; + + if ((pw = getpwnam(CONFIG_user)) == NULL) { + fprintf(stderr, "ERROR: inexistant user %s", CONFIG_user); + exit(EXIT_FAILURE); + } + + if (setgid(pw->pw_gid) != 0) { + fprintf(stderr, "ERROR: failed to switch to group %d", pw->pw_gid); + exit(EXIT_FAILURE); + } + + if (setuid(pw->pw_uid) != 0) { + fprintf(stderr, "ERROR: failed to switch to user %s", CONFIG_user); + exit(EXIT_FAILURE); + } + } + + if (regcomp(&CONFIG_alias_regex, CONFIG_alias_regex_str, REG_BASIC) != 0) { + fprintf(stderr, "ERROR: can't compile regex %s\n", CONFIG_alias_regex_str); + exit(EXIT_FAILURE); + } + if (pthread_key_create(&key, &srs_milter_thread_data_destructor)) { fprintf(stderr, "pthread_key_create failed"); exit(EXIT_FAILURE); }