static uschar * decode_mimeword(uschar *string, BOOL lencheck, uschar **q1ptr, uschar **q2ptr, uschar **endptr, size_t *dlenptr, uschar **dptrptr) { uschar *mimeword; for (;; string = mimeword + 2) { int encoding; int dlen = -1; if ((mimeword = Ustrstr(string, "=?")) == NULL || (*q1ptr = Ustrchr(mimeword+2, '?')) == NULL || (*q2ptr = Ustrchr(*q1ptr+1, '?')) == NULL || (*endptr = Ustrstr(*q2ptr+1, "?=")) == NULL) return NULL; /* We have found =?xxx?xxx?xxx?= in the string. Optionally check the length, and that the second field is just one character long. If not, continue the loop to search again. We must start just after the initial =? because we might have found =?xxx=?xxx?xxx?xxx?=. */ if ((lencheck && *endptr - mimeword > 73) || *q2ptr - *q1ptr != 2) continue; /* Get the encoding letter, and decode the data string. */ encoding = toupper((*q1ptr)[1]); **endptr = 0; if (encoding == 'B') dlen = b64decode(*q2ptr+1, dptrptr); else if (encoding == 'Q') dlen = rfc2047_qpdecode(*q2ptr+1, dptrptr); **endptr = '?'; /* restore */ /* If the decoding succeeded, we are done. Set the length of the decoded string, and pass back the initial pointer. Otherwise, the loop continues. */ if (dlen >= 0) { *dlenptr = (size_t)dlen; return mimeword; } } /* Control should never actually get here */ }
static BOOL set_up_shell_command(const uschar ***argvptr, uschar *cmd, BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname) { const uschar **argv; *argvptr = argv = store_get((4)*sizeof(uschar *)); argv[0] = US"/bin/sh"; argv[1] = US"-c"; /* We have to take special action to handle the special "variable" called $pipe_addresses, which is not recognized by the normal expansion function. */ DEBUG(D_transport) debug_printf("shell pipe command before expansion:\n %s\n", cmd); if (expand_arguments) { uschar *s = cmd; uschar *p = Ustrstr(cmd, "pipe_addresses"); if (p != NULL && ( (p > cmd && p[-1] == '$') || (p > cmd + 1 && p[-2] == '$' && p[-1] == '{' && p[14] == '}'))) { address_item *ad; uschar *q = p + 14; int size = Ustrlen(cmd) + 64; int offset; if (p[-1] == '{') { q++; p--; } s = store_get(size); offset = p - cmd - 1; Ustrncpy(s, cmd, offset); for (ad = addr; ad != NULL; ad = ad->next) { if (ad != addr) string_cat(s, &size, &offset, US" ", 1); string_cat(s, &size, &offset, ad->address, Ustrlen(ad->address)); } string_cat(s, &size, &offset, q, Ustrlen(q)); s[offset] = 0; } /* Allow $recipients in the expansion iff it comes from a system filter */ enable_dollar_recipients = addr != NULL && addr->parent != NULL && Ustrcmp(addr->parent->address, "system-filter") == 0; argv[2] = expand_string(s); enable_dollar_recipients = FALSE; if (argv[2] == NULL) { addr->transport_return = search_find_defer? DEFER : expand_fail; addr->message = string_sprintf("Expansion of command \"%s\" " "in %s transport failed: %s", cmd, tname, expand_string_message); return FALSE; } DEBUG(D_transport) debug_printf("shell pipe command after expansion:\n %s\n", argv[2]); } else argv[2] = cmd; argv[3] = (uschar *)0; return TRUE; }
void read_log(void) { struct stat statdata; uschar buffer[log_buffer_len]; /* If log is not yet open, skip all of this. */ if (LOG != NULL) { fseek(LOG, log_position, SEEK_SET); while (Ufgets(buffer, log_buffer_len, LOG) != NULL) { uschar *id; uschar *p = buffer; void *reset_point; int length = Ustrlen(buffer); int i; /* Skip totally blank lines (paranoia: there shouldn't be any) */ while (*p == ' ' || *p == '\t') p++; if (*p == '\n') continue; /* We should now have a complete log entry in the buffer; check it for various regular expression matches and take appropriate action. Get the current store point so we can reset to it. */ reset_point = store_get(0); /* First, update any stripchart data values, noting that the zeroth stripchart is the queue length, which is handled elsewhere, and the 1st may the a size monitor. */ for (i = stripchart_varstart; i < stripchart_number; i++) { if (pcre_exec(stripchart_regex[i], NULL, CS buffer, length, 0, PCRE_EOPT, NULL, 0) >= 0) stripchart_total[i]++; } /* Munge the log entry and display shortened form on one line. We omit the date and show only the time. Remove any time zone offset. Take note of the presence of [pid]. */ if (pcre_exec(yyyymmdd_regex,NULL,CS buffer,length,0,PCRE_EOPT,NULL,0) >= 0) { int pidlength = 0; if ((buffer[20] == '+' || buffer[20] == '-') && isdigit(buffer[21]) && buffer[25] == ' ') memmove(buffer + 20, buffer + 26, Ustrlen(buffer + 26) + 1); if (buffer[20] == '[') { while (Ustrchr("[]0123456789", buffer[20+pidlength++]) != NULL); } id = string_copyn(buffer + 20 + pidlength, MESSAGE_ID_LENGTH); show_log("%s", buffer+11); } else { id = US""; show_log("%s", buffer); } /* Deal with frozen and unfrozen messages */ if (strstric(buffer, US"frozen", FALSE) != NULL) { queue_item *qq = find_queue(id, queue_noop, 0); if (qq != NULL) { if (strstric(buffer, US"unfrozen", FALSE) != NULL) qq->frozen = FALSE; else qq->frozen = TRUE; } } /* Notice defer messages, and add the destination if it isn't already on the list for this message, with a pointer to the parent if we can. */ if ((p = Ustrstr(buffer, "==")) != NULL) { queue_item *qq = find_queue(id, queue_noop, 0); if (qq != NULL) { dest_item *d; uschar *q, *r; p += 2; while (isspace(*p)) p++; q = p; while (*p != 0 && !isspace(*p)) { if (*p++ != '\"') continue; while (*p != 0) { if (*p == '\\') p += 2; else if (*p++ == '\"') break; } } *p++ = 0; if ((r = strstric(q, qualify_domain, FALSE)) != NULL && *(--r) == '@') *r = 0; /* If we already have this destination, as tested case-insensitively, do not add it to the destinations list. */ d = find_dest(qq, q, dest_add, TRUE); if (d->parent == NULL) { while (isspace(*p)) p++; if (*p == '<') { dest_item *dd; q = ++p; while (*p != 0 && *p != '>') p++; *p = 0; if ((p = strstric(q, qualify_domain, FALSE)) != NULL && *(--p) == '@') *p = 0; dd = find_dest(qq, q, dest_noop, FALSE); if (dd != NULL && dd != d) d->parent = dd; } } } } store_reset(reset_point); } } /* We have to detect when the log file is changed, and switch to the new file. In practice, for non-datestamped files, this means that some deliveries might go unrecorded, since they'll be written to the old file, but this usually happens in the middle of the night, and I don't think the hassle of keeping track of two log files is worth it. First we check the datestamped name of the log file if necessary; if it is different to the file we currently have open, go for the new file. As happens in Exim itself, we leave in the following inode check, even when datestamping because it does no harm and will cope should a file actually be renamed for some reason. The test for a changed log file is to look up the inode of the file by name and compare it with the saved inode of the file we currently are processing. This accords with the usual interpretation of POSIX and other Unix specs that imply "one file, one inode". However, it appears that on some Digital systems, if an open file is unlinked, a new file may be created with the same inode while the old file remains in existence. This can happen if the old log file is renamed, processed in some way, and then deleted. To work round this, also test for a link count of zero on the currently open file. */ if (log_datestamping) { uschar log_file_wanted[256]; string_format(log_file_wanted, sizeof(log_file_wanted), CS log_file); if (Ustrcmp(log_file_wanted, log_file_open) != 0) { if (LOG != NULL) { fclose(LOG); LOG = NULL; } Ustrcpy(log_file_open, log_file_wanted); } } if (LOG == NULL || (fstat(fileno(LOG), &statdata) == 0 && statdata.st_nlink == 0) || (Ustat(log_file, &statdata) == 0 && log_inode != statdata.st_ino)) { FILE *TEST; /* Experiment shows that sometimes you can't immediately open the new log file - presumably immediately after the old one is renamed and before the new one exists. Therefore do a trial open first to be sure. */ if ((TEST = fopen(CS log_file_open, "r")) != NULL) { if (LOG != NULL) fclose(LOG); LOG = TEST; fstat(fileno(LOG), &statdata); log_inode = statdata.st_ino; } } /* Save the position we have got to in the log. */ if (LOG != NULL) log_position = ftell(LOG); }
int rda_interpret(redirect_block *rdata, int options, uschar *include_directory, 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; BOOL had_disaster = FALSE; pid_t pid; uschar *data; uschar *readerror = US""; void (*oldsignal)(int); DEBUG(D_route) debug_printf("rda_interpret (%s): %s\n", (rdata->isfile)? "file" : "string", rdata->string); /* Do the expansions of the file name or data first, while still privileged. */ data = expand_string(rdata->string); if (data == NULL) { if (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); if (rdata->isfile && data[0] != '/') { *error = string_sprintf("\"%s\" is not an absolute path", data); return FF_ERROR; } /* If no uid/gid are supplied, or if we have a data string which does not start with #Exim filter or #Sieve filter, and does not contain :include:, do all the work in this process. Note that for a system filter, we always have a file, so the work is done in this process only if no user is supplied. */ if (!ugid->uid_set || /* Either there's no uid, or */ (!rdata->isfile && /* We've got the data, and */ rda_is_filter(data) == FILTER_FORWARD && /* It's not a filter script, */ Ustrstr(data, ":include:") == NULL)) /* and there's no :include: */ { return rda_extract(rdata, options, include_directory, 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 determine the non-existence of a file first, we can decline without having to create the sub-process. */ if (rdata->isfile && rda_exists(data, error) == FILE_NOT_EXIST) return FF_NONEXIST; /* If the file does exist, or we can't tell (non-root mounted NFS directory) we have to create the subprocess to do everything as the given user. The results of processing are passed back via a pipe. */ if (pipe(pfd) != 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "creation of pipe for filter or " ":include: failed for %s: %s", rname, strerror(errno)); /* 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. 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]; (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 user (and we probably are not), turn off rewrite logging, because we cannot write to the log now. */ if (ugid->uid != root_uid && ugid->uid != exim_uid) { DEBUG(D_rewrite) debug_printf("turned off address rewrite logging (not " "root or exim in this process)\n"); 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_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. */ 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) { error_block *ep; for (ep = *eblockp; ep != NULL; 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) { 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)) != sizeof(i) ) goto bad; i = -1; if (write(fd, &i, sizeof(i)) != sizeof(i)) goto bad; while (waslast != header_last) { waslast = waslast->next; if (waslast->type != htype_old) if ( rda_write_string(fd, waslast->text) != 0 || write(fd, &(waslast->type), sizeof(waslast->type)) != sizeof(waslast->type) ) goto bad; } if (rda_write_string(fd, NULL) != 0) /* Indicates end of added headers */ goto bad; } /* Write the contents of the $n variables */ 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 just tedious, but it seems to be the only safe way. We do this also for FAIL and FREEZE, because a filter is allowed to set up deliveries that are honoured before freezing or failing. */ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || yield == FF_FAIL || yield == FF_FREEZE) { address_item *addr; for (addr = *generated; addr != NULL; addr = addr->next) { int reply_options = 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 ) goto bad; if (addr->pipe_expandn != NULL) { uschar **pp; for (pp = addr->pipe_expandn; *pp != NULL; pp++) if (rda_write_string(fd, *pp) != 0) goto bad; } if (rda_write_string(fd, NULL) != 0) goto bad; if (addr->reply == NULL) { 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; 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; } } if (rda_write_string(fd, NULL) != 0) /* Marks end of addresses */ goto bad; } /* OK, this process is now done. Free any cached resources. Must use _exit() and not exit() !! */ out: (void)close(fd); search_tidyup(); _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. */ if (pid < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for %s", rname); /* Read the pipe to get the data from the filter/forward. Our copy of the writing end must be closed first, as otherwise read() won't return zero on an empty pipe. Afterwards, close the reading end. */ (void)close(pfd[pipe_write]); /* Read initial data, including yield and contents of *error */ fd = pfd[pipe_read]; if (read(fd, filtertype, sizeof(int)) != sizeof(int) || read(fd, &yield, sizeof(int)) != sizeof(int) || !rda_read_string(fd, error)) goto DISASTER; /* Read the contents of any syntax error blocks if we have a pointer */ if (eblockp != NULL) { uschar *s; error_block *e; error_block **p = eblockp; for (;;) { if (!rda_read_string(fd, &s)) goto DISASTER; if (s == NULL) break; e = store_get(sizeof(error_block)); 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) { int hn = 0; header_line *h = header_list; for (;;) { int n; if (read(fd, &n, sizeof(int)) != sizeof(int)) goto DISASTER; if (n < 0) break; while (hn < n) { hn++; h = h->next; if (h == NULL) goto DISASTER_NO_HEADER; } h->type = htype_old; } for (;;) { uschar *s; int type; if (!rda_read_string(fd, &s)) goto DISASTER; if (s == NULL) break; if (read(fd, &type, sizeof(type)) != sizeof(type)) goto DISASTER; header_add(type, "%s", s); } } /* Read the values of the $n variables */ if (read(fd, filter_n, sizeof(filter_n)) != sizeof(filter_n)) goto DISASTER; /* If the yield is DELIVERED, NOTDELIVERED, FAIL, or FREEZE there may follow addresses and data to go with them. Keep them in the same order in the generated chain. */ if (yield == FF_DELIVERED || yield == FF_NOTDELIVERED || yield == FF_FAIL || yield == FF_FREEZE) { address_item **nextp = generated; for (;;) { int i, reply_options; address_item *addr; uschar *recipient; uschar *expandn[EXPAND_MAXN + 2]; /* First string is the address; NULL => end of addresses */ if (!rda_read_string(fd, &recipient)) goto DISASTER; if (recipient == NULL) break; /* Hang on the end of the chain */ addr = deliver_make_addr(recipient, FALSE); *nextp = addr; nextp = &(addr->next); /* 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->prop.errors_address))) goto DISASTER; /* Next comes a possible setting for $thisaddress and any numerical variables for pipe expansion, terminated by a NULL string. The maximum number of numericals is EXPAND_MAXN. Note that we put filter_thisaddress into the zeroth item in the vector - this is sorted out inside the pipe transport. */ for (i = 0; i < EXPAND_MAXN + 1; i++) { uschar *temp; if (!rda_read_string(fd, &temp)) goto DISASTER; if (i == 0) filter_thisaddress = temp; /* Just in case */ expandn[i] = temp; if (temp == NULL) break; } if (i > 0) { addr->pipe_expandn = store_get((i+1) * sizeof(uschar **)); addr->pipe_expandn[i] = NULL; while (--i >= 0) addr->pipe_expandn[i] = expandn[i]; } /* Then an int containing reply options; zero => no reply data. */ 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->file_expand = (reply_options & REPLY_EXPAND) != 0; addr->reply->return_message = (reply_options & REPLY_RETURN) != 0; if (read(fd,&(addr->reply->expand_forbid),sizeof(int)) != sizeof(int) || read(fd,&(addr->reply->once_repeat),sizeof(time_t)) != sizeof(time_t) || !rda_read_string(fd, &(addr->reply->to)) || !rda_read_string(fd, &(addr->reply->cc)) || !rda_read_string(fd, &(addr->reply->bcc)) || !rda_read_string(fd, &(addr->reply->from)) || !rda_read_string(fd, &(addr->reply->reply_to)) || !rda_read_string(fd, &(addr->reply->subject)) || !rda_read_string(fd, &(addr->reply->headers)) || !rda_read_string(fd, &(addr->reply->text)) || !rda_read_string(fd, &(addr->reply->file)) || !rda_read_string(fd, &(addr->reply->logfile)) || !rda_read_string(fd, &(addr->reply->oncelog))) goto DISASTER; } } } /* All data has been transferred from the sub-process. Reap it, close the reading end of the pipe, and we are done. */ WAIT_EXIT: while ((rc = wait(&status)) != pid) { if (rc < 0 && errno == ECHILD) /* Process has vanished */ { log_write(0, LOG_MAIN, "redirection process %d vanished unexpectedly", pid); goto FINAL_EXIT; } } 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 " "data from subprocess: status=%04x%s%s%s", rname, status, readerror, (*error == NULL)? US"" : US": error=", (*error == NULL)? US"" : *error); log_write(0, LOG_MAIN|LOG_PANIC, "%s", *error); } else if (status != 0) { log_write(0, LOG_MAIN|LOG_PANIC, "internal problem in %s: unexpected status " "%04x from redirect subprocess (but data correctly received)", rname, status); } FINAL_EXIT: (void)close(fd); signal(SIGCHLD, oldsignal); /* restore */ return yield; /* Come here if the data indicates removal of a header that we can't find */ DISASTER_NO_HEADER: readerror = US" readerror=bad header identifier"; had_disaster = TRUE; yield = FF_ERROR; goto WAIT_EXIT; /* Come here is there's a shambles in transferring the data over the pipe. The value of errno should still be set. */ DISASTER: readerror = string_sprintf(" readerror='%s'", strerror(errno)); had_disaster = TRUE; yield = FF_ERROR; goto WAIT_EXIT; }