X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b1c749bb7f147e7f9215fe6067c848cf02938b92..d896cef5f6f3ff1b7509fd832a4ee66403a7c57f:/src/src/rda.c diff --git a/src/src/rda.c b/src/src/rda.c index 1be7b1cc4..42b7b14a5 100644 --- a/src/src/rda.c +++ b/src/src/rda.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/rda.c,v 1.7 2005/06/16 14:10:13 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2005 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module contains code for extracting addresses from a forwarding list @@ -44,14 +42,14 @@ static BOOL match_tag(const uschar *s, const uschar *tag) { for (; *tag != 0; s++, tag++) - { if (*tag == ' ') { while (*s == ' ' || *s == '\t') s++; s--; } - else if (tolower(*s) != tolower(*tag)) break; - } + else + if (tolower(*s) != tolower(*tag)) break; + return (*tag == 0); } @@ -98,37 +96,37 @@ static int rda_exists(uschar *filename, uschar **error) { int rc, saved_errno; -uschar *slash; struct stat statbuf; +uschar * s; if ((rc = Ustat(filename, &statbuf)) >= 0) return FILE_EXIST; saved_errno = errno; -Ustrncpy(big_buffer, filename, big_buffer_size - 3); +s = string_copy(filename); sigalrm_seen = FALSE; if (saved_errno == ENOENT) { - slash = Ustrrchr(big_buffer, '/'); - Ustrcpy(slash+1, "."); + uschar * slash = Ustrrchr(s, '/'); + Ustrcpy(slash+1, US"."); - alarm(30); - rc = Ustat(big_buffer, &statbuf); + ALARM(30); + rc = Ustat(s, &statbuf); if (rc != 0 && errno == EACCES && !sigalrm_seen) { *slash = 0; - rc = Ustat(big_buffer, &statbuf); + rc = Ustat(s, &statbuf); } saved_errno = errno; - alarm(0); + ALARM_CLR(0); - DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc); + DEBUG(D_route) debug_printf("stat(%s)=%d\n", s, rc); } if (sigalrm_seen || rc != 0) { - *error = string_sprintf("failed to stat %s (%s)", big_buffer, - sigalrm_seen? "timeout" : strerror(saved_errno)); + *error = string_sprintf("failed to stat %s (%s)", s, + sigalrm_seen? "timeout" : strerror(saved_errno)); return FILE_EXIST_UNCLEAR; } @@ -252,11 +250,8 @@ if (!uid_ok) if (rdata->pw != NULL && statbuf.st_uid == rdata->pw->pw_uid) uid_ok = TRUE; else if (rdata->owners != NULL) - { - int i; - for (i = 1; i <= (int)(rdata->owners[0]); i++) + for (int i = 1; i <= (int)(rdata->owners[0]); i++) if (rdata->owners[i] == statbuf.st_uid) { uid_ok = TRUE; break; } - } } if (!gid_ok) @@ -264,11 +259,8 @@ if (!gid_ok) if (rdata->pw != NULL && statbuf.st_gid == rdata->pw->pw_gid) gid_ok = TRUE; else if (rdata->owngroups != NULL) - { - int i; - for (i = 1; i <= (int)(rdata->owngroups[0]); i++) + for (int i = 1; i <= (int)(rdata->owngroups[0]); i++) if (rdata->owngroups[i] == statbuf.st_gid) { gid_ok = TRUE; break; } - } } if (!uid_ok || !gid_ok) @@ -289,7 +281,7 @@ if (statbuf.st_size > MAX_FILTER_SIZE) /* Read the file in one go in order to minimize the time we have it open. */ -filebuf = store_get(statbuf.st_size + 1); +filebuf = store_get(statbuf.st_size + 1, is_tainted(filename)); if (fread(filebuf, 1, statbuf.st_size, fwd) != statbuf.st_size) { @@ -302,14 +294,14 @@ filebuf[statbuf.st_size] = 0; DEBUG(D_route) debug_printf(OFF_T_FMT " bytes read from %s\n", statbuf.st_size, filename); -fclose(fwd); +(void)fclose(fwd); return filebuf; /* Return an error: the string is already set up. */ ERROR_RETURN: *yield = FF_ERROR; -fclose(fwd); +(void)fclose(fwd); return NULL; } @@ -327,6 +319,7 @@ Arguments: options the options bits include_directory restrain to this directory sieve_vacation_directory passed to sieve_interpret + sieve_enotify_mailto_owner passed to sieve_interpret sieve_useraddress passed to sieve_interpret sieve_subaddress passed to sieve_interpret generated where to hang generated addresses @@ -344,21 +337,22 @@ Returns: a suitable return for rda_interpret() static int rda_extract(redirect_block *rdata, int options, uschar *include_directory, - uschar *sieve_vacation_directory, uschar *sieve_useraddress, - uschar *sieve_subaddress, address_item **generated, uschar **error, - error_block **eblockp, int *filtertype) + uschar *sieve_vacation_directory, uschar *sieve_enotify_mailto_owner, + uschar *sieve_useraddress, uschar *sieve_subaddress, + address_item **generated, uschar **error, error_block **eblockp, + int *filtertype) { uschar *data; if (rdata->isfile) { - int yield; + int yield = 0; data = rda_get_file_contents(rdata, options, error, &yield); if (data == NULL) return yield; } else data = rdata->string; -*filtertype = system_filtering? FILTER_EXIM : rda_is_filter(data); +*filtertype = f.system_filtering ? FILTER_EXIM : rda_is_filter(data); /* Filter interpretation is done by a general function that is also called from the filter testing option (-bf). There are two versions: one for Exim filtering @@ -372,7 +366,7 @@ if (*filtertype != FILTER_FORWARD) int old_expand_forbid = expand_forbid; DEBUG(D_route) debug_printf("data is %s filter program\n", - (*filtertype == FILTER_EXIM)? "an Exim" : "a Sieve"); + *filtertype == FILTER_EXIM ? "an Exim" : "a Sieve"); /* RDO_FILTER is an "allow" bit */ @@ -383,8 +377,7 @@ if (*filtertype != FILTER_FORWARD) } expand_forbid = - (expand_forbid & ~RDO_FILTER_EXPANSIONS) | - (options & RDO_FILTER_EXPANSIONS); + expand_forbid & ~RDO_FILTER_EXPANSIONS | options & RDO_FILTER_EXPANSIONS; /* RDO_{EXIM,SIEVE}_FILTER are forbid bits */ @@ -405,7 +398,8 @@ if (*filtertype != FILTER_FORWARD) return FF_ERROR; } frc = sieve_interpret(data, options, sieve_vacation_directory, - sieve_useraddress, sieve_subaddress, generated, error); + sieve_enotify_mailto_owner, sieve_useraddress, sieve_subaddress, + generated, error); } expand_forbid = old_expand_forbid; @@ -432,22 +426,24 @@ return parse_forward_list(data, * Write string down pipe * *************************************************/ -/* This function is used for tranferring a string down a pipe between +/* This function is used for transferring a string down a pipe between processes. If the pointer is NULL, a length of zero is written. Arguments: fd the pipe s the string -Returns: nothing +Returns: -1 on error, else 0 */ -static void -rda_write_string(int fd, uschar *s) +static int +rda_write_string(int fd, const uschar *s) { int len = (s == NULL)? 0 : Ustrlen(s) + 1; -write(fd, &len, sizeof(int)); -if (s != NULL) write(fd, s, len); +return ( write(fd, &len, sizeof(int)) != sizeof(int) + || (s != NULL && write(fd, s, len) != len) + ) + ? -1 : 0; } @@ -471,11 +467,13 @@ rda_read_string(int fd, uschar **sp) int len; if (read(fd, &len, sizeof(int)) != sizeof(int)) return FALSE; -if (len == 0) *sp = NULL; else - { - *sp = store_get(len); - if (read(fd, *sp, len) != len) return FALSE; - } +if (len == 0) + *sp = NULL; +else + /* We know we have enough memory so disable the error on "len" */ + /* coverity[tainted_data] */ + /* We trust the data source, so untainted */ + if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE; return TRUE; } @@ -488,7 +486,7 @@ return TRUE; /* This function is passed a forward list string (unexpanded) or the name of a file (unexpanded) whose contents are the forwarding list. The list may in fact be a filter program if it starts with "#Exim filter" or "#Sieve filter". Other -types of filter, with different inital tag strings, may be introduced in due +types of filter, with different initial tag strings, may be introduced in due course. The job of the function is to process the forwarding list or filter. It is @@ -511,7 +509,8 @@ Arguments: options options to pass to the extraction functions, plus ENOTDIR and EACCES handling bits include_directory restrain :include: to this directory - sieve_vacation_directory directory passed to sieve_interpret() + sieve_vacation_directory directory passed to sieve_interpret + sieve_enotify_mailto_owner passed to sieve_interpret sieve_useraddress passed to sieve_interpret sieve_subaddress passed to sieve_interpret ugid uid/gid to run under - if NULL, no change @@ -540,9 +539,10 @@ Returns: values from extraction function, or FF_NONEXIST: int rda_interpret(redirect_block *rdata, int options, uschar *include_directory, - uschar *sieve_vacation_directory, uschar *sieve_useraddress, - uschar *sieve_subaddress, ugid_block *ugid, address_item **generated, - uschar **error, error_block **eblockp, int *filtertype, uschar *rname) + uschar *sieve_vacation_directory, uschar *sieve_enotify_mailto_owner, + uschar *sieve_useraddress, uschar *sieve_subaddress, ugid_block *ugid, + address_item **generated, uschar **error, error_block **eblockp, + int *filtertype, uschar *rname) { int fd, rc, pfd[2]; int yield, status; @@ -552,22 +552,21 @@ uschar *data; uschar *readerror = US""; void (*oldsignal)(int); -DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n", - (rdata->isfile)? "file" : "string", rdata->string); +DEBUG(D_route) debug_printf("rda_interpret (%s): '%s'\n", + rdata->isfile ? "file" : "string", string_printing(rdata->string)); /* Do the expansions of the file name or data first, while still privileged. */ -data = expand_string(rdata->string); -if (data == NULL) +if (!(data = expand_string(rdata->string))) { - if (expand_string_forcedfail) return FF_NOTDELIVERED; + if (f.expand_string_forcedfail) return FF_NOTDELIVERED; *error = string_sprintf("failed to expand \"%s\": %s", rdata->string, expand_string_message); return FF_ERROR; } rdata->string = data; -DEBUG(D_route) debug_printf("expanded: %s\n", data); +DEBUG(D_route) debug_printf("expanded: '%s'\n", data); if (rdata->isfile && data[0] != '/') { @@ -586,8 +585,8 @@ if (!ugid->uid_set || /* Either there's no uid, or */ Ustrstr(data, ":include:") == NULL)) /* and there's no :include: */ { return rda_extract(rdata, options, include_directory, - sieve_vacation_directory, sieve_useraddress, sieve_subaddress, - generated, error, eblockp, filtertype); + sieve_vacation_directory, sieve_enotify_mailto_owner, sieve_useraddress, + sieve_subaddress, generated, error, eblockp, filtertype); } /* We need to run the processing code in a sub-process. However, if we can @@ -607,15 +606,19 @@ if (pipe(pfd) != 0) /* Ensure that SIGCHLD is set to SIG_DFL before forking, so that the child process can be waited for. We sometimes get here with it set otherwise. Save -the old state for resetting on the wait. */ +the old state for resetting on the wait. Ensure that all cached resources are +freed so that the subprocess starts with a clean slate and doesn't interfere +with the parent process. */ oldsignal = signal(SIGCHLD, SIG_DFL); +search_tidyup(); + if ((pid = fork()) == 0) { header_line *waslast = header_last; /* Save last header */ fd = pfd[pipe_write]; - close(pfd[pipe_read]); + (void)close(pfd[pipe_read]); exim_setugid(ugid->uid, ugid->gid, FALSE, rname); /* Addresses can get rewritten in filters; if we are not root or the exim @@ -626,65 +629,72 @@ if ((pid = fork()) == 0) { DEBUG(D_rewrite) debug_printf("turned off address rewrite logging (not " "root or exim in this process)\n"); - log_write_selector &= ~L_address_rewrite; + BIT_CLEAR(log_selector, log_selector_size, Li_address_rewrite); } /* Now do the business */ yield = rda_extract(rdata, options, include_directory, - sieve_vacation_directory, sieve_useraddress, sieve_subaddress, generated, - error, eblockp, filtertype); + sieve_vacation_directory, sieve_enotify_mailto_owner, sieve_useraddress, + sieve_subaddress, generated, error, eblockp, filtertype); /* Pass back whether it was a filter, and the return code and any overall error text via the pipe. */ - write(fd, filtertype, sizeof(int)); - write(fd, &yield, sizeof(int)); - rda_write_string(fd, *error); + if ( write(fd, filtertype, sizeof(int)) != sizeof(int) + || write(fd, &yield, sizeof(int)) != sizeof(int) + || rda_write_string(fd, *error) != 0 + ) + goto bad; /* Pass back the contents of any syntax error blocks if we have a pointer */ - if (eblockp != NULL) + if (eblockp) { - error_block *ep; - for (ep = *eblockp; ep != NULL; ep = ep->next) - { - rda_write_string(fd, ep->text1); - rda_write_string(fd, ep->text2); - } - rda_write_string(fd, NULL); /* Indicates end of eblocks */ + for (error_block * ep = *eblockp; ep; ep = ep->next) + if ( rda_write_string(fd, ep->text1) != 0 + || rda_write_string(fd, ep->text2) != 0 + ) + goto bad; + if (rda_write_string(fd, NULL) != 0) /* Indicates end of eblocks */ + goto bad; } /* If this is a system filter, we have to pass back the numbers of any original header lines that were removed, and then any header lines that were added but not subsequently removed. */ - if (system_filtering) + if (f.system_filtering) { int i = 0; - header_line *h; - for (h = header_list; h != waslast->next; i++, h = h->next) - { - if (h->type == htype_old) write(fd, &i, sizeof(i)); - } + for (header_line * h = header_list; h != waslast->next; i++, h = h->next) + if ( h->type == htype_old + && write(fd, &i, sizeof(i)) != sizeof(i) + ) + goto bad; + i = -1; - write(fd, &i, sizeof(i)); + if (write(fd, &i, sizeof(i)) != sizeof(i)) + goto bad; while (waslast != header_last) { waslast = waslast->next; if (waslast->type != htype_old) - { - rda_write_string(fd, waslast->text); - write(fd, &(waslast->type), sizeof(waslast->type)); - } + if ( rda_write_string(fd, waslast->text) != 0 + || write(fd, &(waslast->type), sizeof(waslast->type)) + != sizeof(waslast->type) + ) + goto bad; } - rda_write_string(fd, NULL); /* Indicates end of added headers */ + if (rda_write_string(fd, NULL) != 0) /* Indicates end of added headers */ + goto bad; } /* Write the contents of the $n variables */ - write(fd, filter_n, sizeof(filter_n)); + if (write(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n)) + goto bad; /* If the result was DELIVERED or NOTDELIVERED, we pass back the generated addresses, and their associated information, through the pipe. This is @@ -695,55 +705,72 @@ if ((pid = fork()) == 0) if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || yield == FF_FAIL || yield == FF_FREEZE) { - address_item *addr; - for (addr = *generated; addr != NULL; addr = addr->next) + for (address_item * addr = *generated; addr; addr = addr->next) { int reply_options = 0; - - rda_write_string(fd, addr->address); - write(fd, &(addr->mode), sizeof(addr->mode)); - write(fd, &(addr->flags), sizeof(addr->flags)); - rda_write_string(fd, addr->p.errors_address); - - if (addr->pipe_expandn != NULL) - { - uschar **pp; - for (pp = addr->pipe_expandn; *pp != NULL; pp++) - rda_write_string(fd, *pp); - } - rda_write_string(fd, NULL); - - if (addr->reply == NULL) - write(fd, &reply_options, sizeof(int)); /* 0 means no reply */ + int ig_err = addr->prop.ignore_error ? 1 : 0; + + if ( rda_write_string(fd, addr->address) != 0 + || write(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode) + || write(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags) + || rda_write_string(fd, addr->prop.errors_address) != 0 + || write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err) + ) + goto bad; + + if (addr->pipe_expandn) + for (uschar ** pp = addr->pipe_expandn; *pp; pp++) + if (rda_write_string(fd, *pp) != 0) + goto bad; + if (rda_write_string(fd, NULL) != 0) + goto bad; + + if (!addr->reply) + { + if (write(fd, &reply_options, sizeof(int)) != sizeof(int)) /* 0 means no reply */ + goto bad; + } else { reply_options |= REPLY_EXISTS; if (addr->reply->file_expand) reply_options |= REPLY_EXPAND; if (addr->reply->return_message) reply_options |= REPLY_RETURN; - write(fd, &reply_options, sizeof(int)); - write(fd, &(addr->reply->expand_forbid), sizeof(int)); - write(fd, &(addr->reply->once_repeat), sizeof(time_t)); - rda_write_string(fd, addr->reply->to); - rda_write_string(fd, addr->reply->cc); - rda_write_string(fd, addr->reply->bcc); - rda_write_string(fd, addr->reply->from); - rda_write_string(fd, addr->reply->reply_to); - rda_write_string(fd, addr->reply->subject); - rda_write_string(fd, addr->reply->headers); - rda_write_string(fd, addr->reply->text); - rda_write_string(fd, addr->reply->file); - rda_write_string(fd, addr->reply->logfile); - rda_write_string(fd, addr->reply->oncelog); + if ( write(fd, &reply_options, sizeof(int)) != sizeof(int) + || write(fd, &(addr->reply->expand_forbid), sizeof(int)) + != sizeof(int) + || write(fd, &(addr->reply->once_repeat), sizeof(time_t)) + != sizeof(time_t) + || rda_write_string(fd, addr->reply->to) != 0 + || rda_write_string(fd, addr->reply->cc) != 0 + || rda_write_string(fd, addr->reply->bcc) != 0 + || rda_write_string(fd, addr->reply->from) != 0 + || rda_write_string(fd, addr->reply->reply_to) != 0 + || rda_write_string(fd, addr->reply->subject) != 0 + || rda_write_string(fd, addr->reply->headers) != 0 + || rda_write_string(fd, addr->reply->text) != 0 + || rda_write_string(fd, addr->reply->file) != 0 + || rda_write_string(fd, addr->reply->logfile) != 0 + || rda_write_string(fd, addr->reply->oncelog) != 0 + ) + goto bad; } } - rda_write_string(fd, NULL); /* Marks end of addresses */ + if (rda_write_string(fd, NULL) != 0) /* Marks end of addresses */ + goto bad; } - /* OK, this process is now done. Must use _exit() and not exit() !! */ + /* OK, this process is now done. Free any cached resources. Must use _exit() + and not exit() !! */ + +out: + (void)close(fd); + search_tidyup(); + exim_underbar_exit(0); - close(fd); - _exit(0); +bad: + DEBUG(D_rewrite) debug_printf("rda_interpret: failed write to pipe\n"); + goto out; } /* Back in the main process: panic if the fork did not succeed. */ @@ -755,7 +782,7 @@ if (pid < 0) writing end must be closed first, as otherwise read() won't return zero on an empty pipe. Afterwards, close the reading end. */ -close(pfd[pipe_write]); +(void)close(pfd[pipe_write]); /* Read initial data, including yield and contents of *error */ @@ -764,34 +791,29 @@ if (read(fd, filtertype, sizeof(int)) != sizeof(int) || read(fd, &yield, sizeof(int)) != sizeof(int) || !rda_read_string(fd, error)) goto DISASTER; -DEBUG(D_route) - debug_printf("rda_interpret: subprocess yield=%d error=%s\n", yield, *error); - /* Read the contents of any syntax error blocks if we have a pointer */ -if (eblockp != NULL) +if (eblockp) { - uschar *s; error_block *e; - error_block **p = eblockp; - for (;;) + for (error_block ** p = eblockp; ; p = &e->next) { + uschar *s; if (!rda_read_string(fd, &s)) goto DISASTER; - if (s == NULL) break; - e = store_get(sizeof(error_block)); + if (!s) break; + e = store_get(sizeof(error_block), FALSE); e->next = NULL; e->text1 = s; if (!rda_read_string(fd, &s)) goto DISASTER; e->text2 = s; *p = e; - p = &(e->next); } } /* If this is a system filter, read the identify of any original header lines that were removed, and then read data for any new ones that were added. */ -if (system_filtering) +if (f.system_filtering) { int hn = 0; header_line *h = header_list; @@ -804,8 +826,7 @@ if (system_filtering) while (hn < n) { hn++; - h = h->next; - if (h == NULL) goto DISASTER_NO_HEADER; + if (!(h = h->next)) goto DISASTER_NO_HEADER; } h->type = htype_old; } @@ -815,7 +836,7 @@ if (system_filtering) uschar *s; int type; if (!rda_read_string(fd, &s)) goto DISASTER; - if (s == NULL) break; + if (!s) break; if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER; header_add(type, "%s", s); } @@ -844,7 +865,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || /* First string is the address; NULL => end of addresses */ if (!rda_read_string(fd, &recipient)) goto DISASTER; - if (recipient == NULL) break; + if (!recipient) break; /* Hang on the end of the chain */ @@ -854,9 +875,13 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || /* Next comes the mode and the flags fields */ - if (read(fd, &(addr->mode), sizeof(addr->mode)) != sizeof(addr->mode) || - read(fd, &(addr->flags), sizeof(addr->flags)) != sizeof(addr->flags) || - !rda_read_string(fd, &(addr->p.errors_address))) goto DISASTER; + if ( read(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode) + || read(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags) + || !rda_read_string(fd, &addr->prop.errors_address) + || read(fd, &i, sizeof(i)) != sizeof(i) + ) + goto DISASTER; + addr->prop.ignore_error = (i != 0); /* Next comes a possible setting for $thisaddress and any numerical variables for pipe expansion, terminated by a NULL string. The maximum @@ -875,7 +900,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || if (i > 0) { - addr->pipe_expandn = store_get((i+1) * sizeof(uschar **)); + addr->pipe_expandn = store_get((i+1) * sizeof(uschar *), FALSE); addr->pipe_expandn[i] = NULL; while (--i >= 0) addr->pipe_expandn[i] = expandn[i]; } @@ -885,7 +910,7 @@ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || if (read(fd, &reply_options, sizeof(int)) != sizeof(int)) goto DISASTER; if ((reply_options & REPLY_EXISTS) != 0) { - addr->reply = store_get(sizeof(reply_item)); + addr->reply = store_get(sizeof(reply_item), FALSE); addr->reply->file_expand = (reply_options & REPLY_EXPAND) != 0; addr->reply->return_message = (reply_options & REPLY_RETURN) != 0; @@ -923,6 +948,9 @@ while ((rc = wait(&status)) != pid) } } +DEBUG(D_route) + debug_printf("rda_interpret: subprocess yield=%d error=%s\n", yield, *error); + if (had_disaster) { *error = string_sprintf("internal problem in %s: failure to transfer " @@ -940,7 +968,7 @@ else if (status != 0) } FINAL_EXIT: -close(fd); +(void)close(fd); signal(SIGCHLD, oldsignal); /* restore */ return yield;