static void proxymap_open_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int reply_status; int reply_flags; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_END) != 2) { reply_status = PROXY_STAT_BAD; reply_flags = 0; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_flags = 0; } else { reply_status = PROXY_STAT_OK; reply_flags = dict->flags; } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, reply_flags, ATTR_TYPE_END); }
static void verify_service(VSTREAM *client_stream, char *unused_service, char **argv) { VSTRING *request = vstring_alloc(10); /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This routine runs whenever a client connects to the socket dedicated * to the address verification service. All connection-management stuff * is handled by the common code in multi_server.c. */ if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REQ, request), ATTR_TYPE_END) == 1) { if (STREQ(STR(request), VRFY_REQ_UPDATE)) { verify_update_service(client_stream); } else if (STREQ(STR(request), VRFY_REQ_QUERY)) { verify_query_service(client_stream); } else { msg_warn("unrecognized request: \"%s\", ignored", STR(request)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), ATTR_TYPE_END); } } vstream_fflush(client_stream); vstring_free(request); }
int resolve_proto(RES_CONTEXT *context, VSTREAM *stream) { int flags; if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_ADDR, query, ATTR_TYPE_END) != 1) return (-1); resolve_addr(context, STR(query), channel, nexthop, nextrcpt, &flags); if (msg_verbose) msg_info("%s -> (`%s' `%s' `%s' `%d')", STR(query), STR(channel), STR(nexthop), STR(nextrcpt), flags); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel), ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop), ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt), ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write resolver reply: %m"); return (-1); } return (0); }
int rewrite_proto(VSTREAM *stream) { RWR_CONTEXT *context; TOK822 *tree; if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_RULE, ruleset, ATTR_TYPE_STR, MAIL_ATTR_ADDR, address, ATTR_TYPE_END) != 2) return (-1); if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0) context = &local_context; else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0) context = &remote_context; else { msg_warn("unknown context: %s", vstring_str(ruleset)); return (-1); } /* * Sanity check. An address is supposed to be in externalized form. */ if (*vstring_str(address) == 0) { msg_warn("rewrite_addr: null address"); vstring_strcpy(result, vstring_str(address)); } /* * Convert the address from externalized (quoted) form to token list, * rewrite it, and convert back. */ else { tree = tok822_scan_addr(vstring_str(address)); rewrite_tree(context, tree); tok822_externalize(result, tree, TOK822_STR_DEFL); tok822_free_tree(tree); } if (msg_verbose) msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset), vstring_str(address), vstring_str(result)); attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags, ATTR_TYPE_STR, MAIL_ATTR_ADDR, vstring_str(result), ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write rewrite reply: %m"); return (-1); } return (0); }
static void verify_update_service(VSTREAM *client_stream) { VSTRING *buf = vstring_alloc(10); VSTRING *addr = vstring_alloc(10); int addr_status; VSTRING *text = vstring_alloc(10); const char *status_name; const char *raw_data; long probed; long updated; if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, &addr_status), RECV_ATTR_STR(MAIL_ATTR_WHY, text), ATTR_TYPE_END) == 3) { /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ translit(STR(addr), ":", "_"); if ((status_name = verify_stat2name(addr_status)) == 0) { msg_warn("bad recipient status %d for recipient %s", addr_status, STR(addr)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), ATTR_TYPE_END); } else { /* * Robustness: don't allow a failed probe to clobber an OK * address before it expires. The failed probe is ignored so that * the address will be re-probed upon the next query. As long as * some probes succeed the address will remain cached as OK. */ if (addr_status == DEL_RCPT_STAT_OK || (raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 || STATUS_FROM_RAW_ENTRY(raw_data) != DEL_RCPT_STAT_OK) { probed = 0; updated = (long) time((time_t *) 0); verify_make_entry(buf, addr_status, probed, updated, STR(text)); if (msg_verbose) msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, probed, updated, STR(text)); dict_cache_update(verify_map, STR(addr), STR(buf)); } attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), ATTR_TYPE_END); } } vstring_free(buf); vstring_free(addr); vstring_free(text); }
static void proxymap_service(VSTREAM *client_stream, char *unused_service, char **argv) { /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Deadline enforcement. */ if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0) vstream_control(client_stream, VSTREAM_CTL_TIMEOUT, 1, VSTREAM_CTL_END); /* * This routine runs whenever a client connects to the socket dedicated * to the proxymap service. All connection-management stuff is handled by * the common code in multi_server.c. */ vstream_control(client_stream, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_REQ, request, ATTR_TYPE_END) == 1) { if (VSTREQ(request, PROXY_REQ_LOOKUP)) { proxymap_lookup_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_UPDATE)) { proxymap_update_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_DELETE)) { proxymap_delete_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) { proxymap_sequence_service(client_stream); } else if (VSTREQ(request, PROXY_REQ_OPEN)) { proxymap_open_service(client_stream); } else { msg_warn("unrecognized request: \"%s\", ignored", STR(request)); attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, PROXY_STAT_BAD, ATTR_TYPE_END); } } vstream_control(client_stream, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); vstream_fflush(client_stream); }
static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv) { int status = -1; #ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT static time_t last; time_t now; const char *table; #endif /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Client connections are long-lived. Be sure to refesh timely. */ #ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT if (server_flags == 0 && (now = event_time()) - last > 10) { if ((table = dict_changed_name()) != 0) { msg_info("table %s has changed -- restarting", table); if (multi_server_drain() == 0) server_flags = 1; } last = now; } #endif /* * This routine runs whenever a client connects to the UNIX-domain socket * dedicated to address rewriting. All connection-management stuff is * handled by the common code in multi_server.c. */ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_STR, MAIL_ATTR_REQ, command, ATTR_TYPE_END) == 1) { if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) { status = rewrite_proto(stream); } else if (strcmp(vstring_str(command), RESOLVE_REGULAR) == 0) { status = resolve_proto(&resolve_regular, stream); } else if (strcmp(vstring_str(command), RESOLVE_VERIFY) == 0) { status = resolve_proto(&resolve_verify, stream); } else { msg_warn("bad command %.30s", printable(vstring_str(command), '?')); } } if (status < 0) multi_server_disconnect(stream); }
static void proxymap_update_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int dict_status; int reply_status; /* * Process the request. * * XXX We don't close maps, so we must turn on synchronous update to ensure * that the on-disk data is in a consistent state between updates. * * XXX We ignore duplicates, because the proxymap server would abort * otherwise. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key, ATTR_TYPE_STR, MAIL_ATTR_VALUE, request_value, ATTR_TYPE_END) != 4) { reply_status = PROXY_STAT_BAD; } else if (proxy_writer == 0) { msg_warn("refusing %s update request on non-%s service", STR(request_map), MAIL_SERVICE_PROXYWRITE); reply_status = PROXY_STAT_DENY; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { /* void */ ; } else { dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK) | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE); dict_status = dict_put(dict, STR(request_key), STR(request_value)); if (dict_status == 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; } else { reply_status = PROXY_STAT_RETRY; } } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_END); }
static void post_mail_init(VSTREAM *stream, const char *sender, const char *recipient, int filter_class, int trace_flags, VSTRING *queue_id) { VSTRING *id = queue_id ? queue_id : vstring_alloc(100); struct timeval now; const char *date; int cleanup_flags = int_filt_flags(filter_class) | CLEANUP_FLAG_MASK_INTERNAL; GETTIMEOFDAY(&now); date = mail_date(now.tv_sec); /* * Negotiate with the cleanup service. Give up if we can't agree. */ if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, ATTR_TYPE_END) != 1 || attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, cleanup_flags, ATTR_TYPE_END) != 0) msg_fatal("unable to contact the %s service", var_cleanup_service); /* * Generate a minimal envelope section. The cleanup service will add a * size record. */ rec_fprintf(stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, REC_TYPE_TIME_ARG(now)); rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL); rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, trace_flags); rec_fputs(stream, REC_TYPE_FROM, sender); rec_fputs(stream, REC_TYPE_RCPT, recipient); rec_fputs(stream, REC_TYPE_MESG, ""); /* * Do the Received: and Date: header lines. This allows us to shave a few * cycles by using the expensive date conversion result for both. */ post_mail_fprintf(stream, "Received: by %s (%s)", var_myhostname, var_mail_name); post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date); post_mail_fprintf(stream, "Date: %s", date); if (queue_id == 0) vstring_free(id); }
static void proxymap_sequence_service(VSTREAM *client_stream) { int request_flags; DICT *dict; int request_func; const char *reply_key; const char *reply_value; int dict_status; int reply_status; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_INT, MAIL_ATTR_FUNC, &request_func, ATTR_TYPE_END) != 3 || (request_func != DICT_SEQ_FUN_FIRST && request_func != DICT_SEQ_FUN_NEXT)) { reply_status = PROXY_STAT_BAD; reply_key = reply_value = ""; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_key = reply_value = ""; } else { dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK)); dict_status = dict_seq(dict, request_func, &reply_key, &reply_value); if (dict_status == 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; reply_key = reply_value = ""; } else { reply_status = PROXY_STAT_RETRY; reply_key = reply_value = ""; } } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_STR, MAIL_ATTR_KEY, reply_key, ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value, ATTR_TYPE_END); }
TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream) { TLS_SESS_STATE *tls_context; tls_context = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context)); if (attr_scan(proxy_stream, ATTR_FLAG_STRICT, ATTR_TYPE_FUNC, tls_proxy_context_scan, (char *) tls_context, ATTR_TYPE_END) != 1) { tls_proxy_context_free(tls_context); return (0); } else { return (tls_context); } }
static int qmgr_deliver_initial_reply(VSTREAM *stream) { int stat; if (peekfd(vstream_fileno(stream)) < 0) { msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &stat, ATTR_TYPE_END) != 1) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else { return (stat ? DELIVER_STAT_DEFER : 0); } }
static int flush_request_receive(VSTREAM *client_stream, VSTRING *request) { int count; /* * Kluge: choose the protocol depending on the request size. */ if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { msg_warn("timeout while waiting for data from %s", VSTREAM_PATH(client_stream)); return (-1); } if ((count = peekfd(vstream_fileno(client_stream))) < 0) { msg_warn("cannot examine read buffer of %s: %m", VSTREAM_PATH(client_stream)); return (-1); } /* * Short request: master trigger. Use the string+null protocol. */ if (count <= 2) { if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { msg_warn("end-of-input while reading request from %s: %m", VSTREAM_PATH(client_stream)); return (-1); } } /* * Long request: real flush client. Use the attribute list protocol. */ else { if (attr_scan(client_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REQ, request), ATTR_TYPE_END) != 1) { return (-1); } } return (0); }
int post_mail_fclose(VSTREAM *cleanup) { int status = 0; /* * Send the message end marker only when there were no errors. */ if (vstream_ferror(cleanup) != 0) { status = CLEANUP_STAT_WRITE; } else { rec_fputs(cleanup, REC_TYPE_XTRA, ""); rec_fputs(cleanup, REC_TYPE_END, ""); if (vstream_fflush(cleanup) || attr_scan(cleanup, ATTR_FLAG_MISSING, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1) status = CLEANUP_STAT_WRITE; } (void) vstream_fclose(cleanup); return (status); }
int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why) { VSTREAM *stream; int request_status; int count = 0; /* * Do client-server plumbing. */ if (vrfy_clnt == 0) verify_clnt_init(); /* * Request status for this address. */ for (;;) { stream = clnt_stream_access(vrfy_clnt); errno = 0; count += 1; if (attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_QUERY, ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, ATTR_TYPE_END) != 0 || vstream_fflush(stream) || attr_scan(stream, ATTR_FLAG_MISSING, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status, ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 3) { if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT)) msg_warn("problem talking to service %s: %m", var_verify_service); } else { break; } sleep(1); clnt_stream_recover(vrfy_clnt); } return (request_status); }
static void proxymap_lookup_service(VSTREAM *client_stream) { int request_flags; DICT *dict; const char *reply_value; int reply_status; /* * Process the request. */ if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags, ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key, ATTR_TYPE_END) != 3) { reply_status = PROXY_STAT_BAD; reply_value = ""; } else if ((dict = proxy_map_find(STR(request_map), request_flags, &reply_status)) == 0) { reply_value = ""; } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) | (request_flags & DICT_FLAG_RQST_MASK)), (reply_value = dict_get(dict, STR(request_key))) != 0) { reply_status = PROXY_STAT_OK; } else if (dict->error == 0) { reply_status = PROXY_STAT_NOKEY; reply_value = ""; } else { reply_status = PROXY_STAT_RETRY; reply_value = ""; } /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status, ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value, ATTR_TYPE_END); }
int verify_clnt_update(const char *addr, int addr_status, const char *why) { VSTREAM *stream; int request_status; /* * Do client-server plumbing. */ if (vrfy_clnt == 0) verify_clnt_init(); /* * Send status for this address. Supply a default status if the address * verification service is unavailable. */ for (;;) { stream = clnt_stream_access(vrfy_clnt); errno = 0; if (attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REQ, VRFY_REQ_UPDATE, ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, ATTR_TYPE_INT, MAIL_ATTR_ADDR_STATUS, addr_status, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 0 || attr_scan(stream, ATTR_FLAG_MISSING, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &request_status, ATTR_TYPE_END) != 1) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("problem talking to service %s: %m", var_verify_service); } else { break; } sleep(1); clnt_stream_recover(vrfy_clnt); } return (request_status); }
int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize) { VSTREAM *fp; int stream_err; /* * Set up a temporary VSTREAM to receive the attributes. * * XXX We use one-character reads to simplify the implementation. */ fp = vstream_fdopen(fd, O_RDWR); vstream_control(fp, VSTREAM_CTL_BUFSIZE, bufsize, VSTREAM_CTL_TIMEOUT, timeout, VSTREAM_CTL_START_DEADLINE, VSTREAM_CTL_END); (void) attr_scan(fp, ATTR_FLAG_NONE, ATTR_TYPE_HASH, *attr = htable_create(1), ATTR_TYPE_END); stream_err = (vstream_feof(fp) || vstream_ferror(fp)); vstream_fdclose(fp); /* * Error reporting and recovery. */ if (stream_err) { htable_free(*attr, myfree); *attr = 0; return (-1); } else { if ((*attr)->used == 0) { htable_free(*attr, myfree); *attr = 0; } return (0); } }
static void tlsp_get_request_event(int event, void *context) { const char *myname = "tlsp_get_request_event"; TLSP_STATE *state = (TLSP_STATE *) context; VSTREAM *plaintext_stream = state->plaintext_stream; int plaintext_fd = vstream_fileno(plaintext_stream); static VSTRING *remote_endpt; static VSTRING *server_id; int req_flags; int timeout; int ready; /* * One-time initialization. */ if (remote_endpt == 0) { remote_endpt = vstring_alloc(10); server_id = vstring_alloc(10); } /* * At this point we still manually manage plaintext read/write/timeout * events. Turn off timer events. Below we disable read events on error, * and redefine read events on success. */ if (event != EVENT_TIME) event_cancel_timer(tlsp_get_request_event, (void *) state); else errno = ETIMEDOUT; /* * We must send some data, after receiving the request attributes and * before receiving the remote file descriptor. We can't assume * UNIX-domain socket semantics here. */ if (event != EVENT_READ || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_REMOTE_ENDPT, remote_endpt), RECV_ATTR_INT(MAIL_ATTR_FLAGS, &req_flags), RECV_ATTR_INT(MAIL_ATTR_TIMEOUT, &timeout), RECV_ATTR_STR(MAIL_ATTR_SERVER_ID, server_id), ATTR_TYPE_END) != 4) { msg_warn("%s: receive request attributes: %m", myname); event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * If the requested TLS engine is unavailable, hang up after making sure * that the plaintext peer has received our "sorry" indication. */ ready = ((req_flags & TLS_PROXY_FLAG_ROLE_SERVER) != 0 && tlsp_server_ctx != 0); if (attr_print(plaintext_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), ATTR_TYPE_END) != 0 || vstream_fflush(plaintext_stream) != 0 || ready == 0) { read_wait(plaintext_fd, TLSP_INIT_TIMEOUT); /* XXX */ event_disable_readwrite(plaintext_fd); tlsp_state_free(state); return; } /* * XXX We use the same fixed timeout throughout the entire session for * both plaintext and ciphertext communication. This timeout is just a * safety feature; the real timeout will be enforced by our plaintext * peer. */ else { state->remote_endpt = mystrdup(STR(remote_endpt)); state->server_id = mystrdup(STR(server_id)); msg_info("CONNECT %s %s", (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : "(bogus_direction)", state->remote_endpt); state->req_flags = req_flags; state->timeout = timeout + 10; /* XXX */ event_enable_read(plaintext_fd, tlsp_get_fd_event, (void *) state); event_request_timer(tlsp_get_fd_event, (void *) state, TLSP_INIT_TIMEOUT); return; } }
static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info, VSTRING *buf) { time_t now = time((time_t *) 0); int status; char *name; /* * Protect against time-warped time stamps. Warn about mail that has been * queued for an excessive amount of time. Allow for some time drift with * network clients that mount the maildrop remotely - especially clients * that can't get their daylight savings offsets right. */ #define DAY_SECONDS 86400 #define HOUR_SECONDS 3600 if (info->st.st_mtime > now + 2 * HOUR_SECONDS) { msg_warn("%s: message dated %ld seconds into the future", info->id, (long) (info->st.st_mtime - now)); info->st.st_mtime = now; } else if (info->st.st_mtime < now - DAY_SECONDS) { msg_warn("%s: message has been queued for %d days", info->id, (int) ((now - info->st.st_mtime) / DAY_SECONDS)); } /* * Add content inspection transport. See also postsuper(1). */ if (*var_filter_xport) rec_fprintf(cleanup, REC_TYPE_FILT, "%s", var_filter_xport); /* * Copy the message envelope segment. Allow only those records that we * expect to see in the envelope section. The envelope segment must * contain an envelope sender address. */ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_ENVELOPE)) != 0) return (status); if (info->sender == 0) { msg_warn("%s: uid=%ld: no envelope sender", info->id, (long) info->st.st_uid); return (REMOVE_MESSAGE_FILE); } /* * For messages belonging to $mail_owner also log the maildrop queue id. * This supports message tracking for mail requeued via "postsuper -r". */ #define MAIL_IS_REQUEUED(info) \ ((info)->st.st_uid == var_owner_uid && ((info)->st.st_mode & S_IROTH) == 0) if (MAIL_IS_REQUEUED(info)) { msg_info("%s: uid=%d from=<%s> orig_id=%s", info->id, (int) info->st.st_uid, info->sender, ((name = strrchr(info->path, '/')) != 0 ? name + 1 : info->path)); } else { msg_info("%s: uid=%d from=<%s>", info->id, (int) info->st.st_uid, info->sender); } /* * Message content segment. Send a dummy message length. Prepend a * Received: header to the message contents. For tracing purposes, * include the message file ownership, without revealing the login name. */ rec_fputs(cleanup, REC_TYPE_MESG, ""); rec_fprintf(cleanup, REC_TYPE_NORM, "Received: by %s (%s, from userid %ld)", var_myhostname, var_mail_name, (long) info->st.st_uid); rec_fprintf(cleanup, REC_TYPE_NORM, "\tid %s; %s", info->id, mail_date(info->st.st_mtime)); /* * Copy the message content segment. Allow only those records that we * expect to see in the message content section. */ if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_CONTENT)) != 0) return (status); /* * Send the segment with information extracted from message headers. * Permit a non-empty extracted segment, so that list manager software * can to output recipients after the message, and so that sysadmins can * re-inject messages after a change of configuration. */ rec_fputs(cleanup, REC_TYPE_XTRA, ""); if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0) return (status); /* * There are no errors. Send the end-of-data marker, and get the cleanup * service completion status. XXX Since the pickup service is unable to * bounce, the cleanup service can report only soft errors here. */ rec_fputs(cleanup, REC_TYPE_END, ""); if (attr_scan(cleanup, ATTR_FLAG_MISSING, RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), RECV_ATTR_STR(MAIL_ATTR_WHY, buf), ATTR_TYPE_END) != 2) return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); /* * Depending on the cleanup service completion status, delete the message * file, or try again later. Bounces are dealt with by the cleanup * service itself. The master process wakes up the cleanup service every * now and then. */ if (status) { return (cleanup_service_error_reason(info, status, vstring_str(buf))); } else { return (REMOVE_MESSAGE_FILE); } }
static int pickup_file(PICKUP_INFO *info) { VSTRING *buf = vstring_alloc(100); int status; VSTREAM *qfile; VSTREAM *cleanup; int cleanup_flags; /* * Open the submitted file. If we cannot open it, and we're not having a * file descriptor leak problem, delete the submitted file, so that we * won't keep complaining about the same file again and again. XXX * Perhaps we should save "bad" files elsewhere for further inspection. * XXX How can we delete a file when open() fails with ENOENT? */ qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0, (struct stat *) 0, -1, -1, buf); if (qfile == 0) { if (errno != ENOENT) msg_warn("open input file %s: %s", info->path, vstring_str(buf)); vstring_free(buf); if (errno == EACCES) msg_warn("if this file was created by Postfix < 1.1, then you may have to chmod a+r %s/%s", var_queue_dir, info->path); return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE); } /* * Contact the cleanup service and read the queue ID that it has * allocated. In case of trouble, request that the cleanup service * bounces its copy of the message. because the original input file is * not readable by the bounce service. * * If mail is re-injected with "postsuper -r", disable Milter applications. * If they were run before the mail was queued then there is no need to * run them again. Moreover, the queue file does not contain enough * information to reproduce the exact same SMTP events and Sendmail * macros that Milters received when the mail originally arrived in * Postfix. * * The actual message copying code is in a separate routine, so that it is * easier to implement the many possible error exits without forgetting * to close files, or to release memory. */ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_EXTERNAL, pickup_input_transp_mask); /* As documented in postsuper(1). */ if (MAIL_IS_REQUEUED(info)) cleanup_flags &= ~CLEANUP_FLAG_MILTER; else cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SENDMAIL); cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service); if (attr_scan(cleanup, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buf), ATTR_TYPE_END) != 1 || attr_print(cleanup, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags), ATTR_TYPE_END) != 0) { status = KEEP_MESSAGE_FILE; } else { info->id = mystrdup(vstring_str(buf)); status = pickup_copy(qfile, cleanup, info, buf); } vstream_fclose(qfile); vstream_fclose(cleanup); vstring_free(buf); return (status); }
static void tlsmgr_service(VSTREAM *client_stream, char *unused_service, char **argv) { static VSTRING *request = 0; static VSTRING *cache_type = 0; static VSTRING *cache_id = 0; static VSTRING *buffer = 0; int len; static char wakeup[] = { /* master wakeup request */ TRIGGER_REQ_WAKEUP, 0, }; TLSMGR_SCACHE *ent; int status = TLS_MGR_STAT_FAIL; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Initialize. We're select threaded, so we can use static buffers. */ if (request == 0) { request = vstring_alloc(10); cache_type = vstring_alloc(10); cache_id = vstring_alloc(10); buffer = vstring_alloc(10); } /* * This routine runs whenever a client connects to the socket dedicated * to the tlsmgr service (including wake up events sent by the master). * All connection-management stuff is handled by the common code in * multi_server.c. */ if (tlsmgr_request_receive(client_stream, request) == 0) { /* * Load session from cache. */ if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_END) == 2) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_LOOKUP); VSTRING_RESET(buffer); } else if (ent->cache_info == 0) { /* * Cache type valid, but not enabled */ VSTRING_RESET(buffer); } else { status = tls_scache_lookup(ent->cache_info, STR(cache_id), buffer) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Save session to cache. */ else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, buffer, ATTR_TYPE_END) == 3) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_UPDATE); } else if (ent->cache_info != 0) { status = tls_scache_update(ent->cache_info, STR(cache_id), STR(buffer), LEN(buffer)) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); } /* * Delete session from cache. */ else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id, ATTR_TYPE_END) == 2) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_DELETE); } else if (ent->cache_info != 0) { status = tls_scache_delete(ent->cache_info, STR(cache_id)) ? TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); } /* * RFC 5077 TLS session ticket keys */ else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYNAME, buffer, ATTR_TYPE_END) == 1) { if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) { msg_warn("invalid session ticket key name length: %ld", (long) LEN(buffer)); VSTRING_RESET(buffer); } else if (*smtpd_cache.cache_timeout <= 0) { status = TLS_MGR_STAT_ERR; VSTRING_RESET(buffer); } else { status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout); } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_KEYBUF, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Entropy request. */ else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) { if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, TLS_MGR_ATTR_SIZE, &len, ATTR_TYPE_END) == 1) { VSTRING_RESET(buffer); if (len <= 0 || len > 255) { msg_warn("bogus seed length \"%d\" in \"%s\" request", len, TLS_MGR_REQ_SEED); } else { VSTRING_SPACE(buffer, len); RAND_bytes((unsigned char *) STR(buffer), len); VSTRING_AT_OFFSET(buffer, len); /* XXX not part of the * official interface */ status = TLS_MGR_STAT_OK; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_DATA, TLS_MGR_ATTR_SEED, LEN(buffer), STR(buffer), ATTR_TYPE_END); } /* * Caching policy request. */ else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) { int cachable = 0; int timeout = 0; if (attr_scan(client_stream, ATTR_FLAG_STRICT, ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type, ATTR_TYPE_END) == 1) { for (ent = cache_table; ent->cache_label; ++ent) if (strcmp(ent->cache_label, STR(cache_type)) == 0) break; if (ent->cache_label == 0) { msg_warn("bogus cache type \"%s\" in \"%s\" request", STR(cache_type), TLS_MGR_REQ_POLICY); } else { cachable = (ent->cache_info != 0) ? 1 : 0; timeout = *ent->cache_timeout; status = TLS_MGR_STAT_OK; } } attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_INT, TLS_MGR_ATTR_CACHABLE, cachable, ATTR_TYPE_INT, TLS_MGR_ATTR_SESSTOUT, timeout, ATTR_TYPE_END); } /* * Master trigger. Normally, these triggers arrive only after some * other process requested the tlsmgr's service. The purpose is to * restart the tlsmgr after it aborted due to a fatal run-time error, * so that it can continue its housekeeping even while nothing is * using TLS. * * XXX Which begs the question, if TLS isn't used often, do we need a * tlsmgr background process? It could terminate when the session * caches are empty. */ else if (STREQ(STR(request), wakeup)) { if (msg_verbose) msg_info("received master trigger"); multi_server_disconnect(client_stream); return; /* NOT: vstream_fflush */ } } /* * Protocol error. */ else { attr_print(client_stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL, ATTR_TYPE_END); } vstream_fflush(client_stream); }
static void flush_service(VSTREAM *client_stream, char *unused_service, char **argv) { VSTRING *request = vstring_alloc(10); VSTRING *site = 0; VSTRING *queue_id = 0; static char wakeup[] = { /* master wakeup request */ TRIGGER_REQ_WAKEUP, 0, }; int status = FLUSH_STAT_BAD; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This routine runs whenever a client connects to the UNIX-domain socket * dedicated to the fast flush service. What we see below is a little * protocol to (1) read a request from the client (the name of the site) * and (2) acknowledge that we have received the request. * * All connection-management stuff is handled by the common code in * single_server.c. */ if (flush_request_receive(client_stream, request) == 0) { if (STREQ(STR(request), FLUSH_REQ_ADD)) { site = vstring_alloc(10); queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 2 && mail_queue_id_ok(STR(queue_id))) status = flush_add_service(STR(site), STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) { site = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), ATTR_TYPE_END) == 1) status = flush_send_service(STR(site), UNTHROTTLE_BEFORE); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) { queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 1) status = flush_send_file_service(STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_REFRESH) || STREQ(STR(request), wakeup)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(var_fflush_refresh); } else if (STREQ(STR(request), FLUSH_REQ_PURGE)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(0); } } else attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); vstring_free(request); if (site) vstring_free(site); if (queue_id) vstring_free(queue_id); }
MILTERS *milter_receive(VSTREAM *stream, int count) { MILTERS *milters; MILTER *head = 0; MILTER *tail = 0; MILTER *milter = 0; if (msg_verbose) msg_info("receive %d milters", count); /* * XXX We must instantiate a MILTERS structure even when the sender has * no active filters, otherwise the cleanup server would try to use its * own non_smtpd_milters settings. */ #define NO_MILTERS ((char *) 0) #define NO_TIMEOUTS 0, 0, 0 #define NO_PROTOCOL ((char *) 0) #define NO_ACTION ((char *) 0) #define NO_MACROS ((MILTER_MACROS *) 0) milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, NO_MACROS); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (milters); /* * Receive the global macro name lists. */ milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_scan, (void *) milters->macros, ATTR_TYPE_END) != 1) { milter_free(milters); return (0); } /* * Receive the filters. */ for (; count > 0; count--) { if ((milter = milter8_receive(stream, milters)) == 0) { msg_warn("cannot receive milters via service %s socket", VSTREAM_PATH(stream)); milter_free(milters); return (0); } if (head == 0) { /* Coverity: milter_free() depends on milters->milter_list. */ milters->milter_list = head = milter; } else { tail->next = milter; } tail = milter; } /* * Over to you. */ (void) attr_print(stream, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, 0, ATTR_TYPE_END); return (milters); }
int milter_send(MILTERS *milters, VSTREAM *stream) { MILTER *m; int status = 0; int count = 0; /* * XXX Optimization: send only the filters that are actually used in the * remote process. No point sending a filter that looks at HELO commands * to a cleanup server. For now we skip only the filters that are known * to be disabled (either in global error state or in global accept * state). * * XXX We must send *some* information, even when there are no active * filters, otherwise the cleanup server would try to apply its own * non_smtpd_milters settings. */ if (milters != 0) for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m)) count++; (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); if (msg_verbose) msg_info("send %d milters", count); /* * XXX Optimization: don't send or receive further information when there * aren't any active filters. */ if (count <= 0) return (0); /* * Send the filter macro name lists. */ (void) attr_print(stream, ATTR_FLAG_MORE, ATTR_TYPE_FUNC, milter_macros_print, (void *) milters->macros, ATTR_TYPE_END); /* * Send the filter instances. */ for (m = milters->milter_list; m != 0; m = m->next) if (m->active(m) && (status = m->send(m, stream)) != 0) break; /* * Over to you. */ if (status != 0 || attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status != 0) { msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); return (-1); } return (0); }
static void psc_dnsbl_receive(int event, void *context) { const char *myname = "psc_dnsbl_receive"; VSTREAM *stream = (VSTREAM *) context; PSC_DNSBL_SCORE *score; PSC_DNSBL_HEAD *head; PSC_DNSBL_SITE *site; ARGV *reply_argv; int request_id; int dnsbl_ttl; PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context); /* * Receive the DNSBL lookup result. * * This is preliminary code to explore the field. Later, DNSBL lookup will * be handled by an UDP-based DNS client that is built directly into some * Postfix daemon. * * Don't bother looking up the blocklist score when the client IP address is * not listed at the DNSBL. * * Don't panic when the blocklist score no longer exists. It may be deleted * when the client triggers a "drop" action after pregreet, when the * client does not pregreet and the DNSBL reply arrives late, or when the * client triggers a "drop" action after hanging up. */ if (event == EVENT_READ && attr_scan(stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl), RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client), RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id), RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr), RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl), ATTR_TYPE_END) == 5 && (score = (PSC_DNSBL_SCORE *) htable_find(dnsbl_score_cache, STR(reply_client))) != 0 && score->request_id == request_id) { /* * Run this response past all applicable DNSBL filters and update the * blocklist score for this client IP address. * * Don't panic when the DNSBL domain name is not found. The DNSBLOG * server may be messed up. */ if (msg_verbose > 1) msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"", myname, STR(reply_client), score->total, STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr)); head = (PSC_DNSBL_HEAD *) htable_find(dnsbl_site_cache, STR(reply_dnsbl)); if (head == 0) { /* Bogus domain. Do nothing. */ } else if (*STR(reply_addr) != 0) { /* DNS reputation record(s) found. */ reply_argv = 0; for (site = head->first; site != 0; site = site->next) { if (site->byte_codes == 0 || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv : (reply_argv = argv_split(STR(reply_addr), " ")))) { if (score->dnsbl_name == 0 || score->dnsbl_weight < site->weight) { score->dnsbl_name = head->safe_dnsbl; score->dnsbl_weight = site->weight; } score->total += site->weight; if (msg_verbose > 1) msg_info("%s: filter=\"%s\" weight=%d score=%d", myname, site->filter ? site->filter : "null", site->weight, score->total); } /* As with dnsblog(8), a value < 0 means no reply TTL. */ if (site->weight > 0) { if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl) score->fail_ttl = dnsbl_ttl; } else { if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl) score->pass_ttl = dnsbl_ttl; } } if (reply_argv != 0) argv_free(reply_argv); } else { /* No DNS reputation record found. */ for (site = head->first; site != 0; site = site->next) { /* As with dnsblog(8), a value < 0 means no reply TTL. */ if (site->weight > 0) { if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl) score->pass_ttl = dnsbl_ttl; } else { if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl) score->fail_ttl = dnsbl_ttl; } } } /* * Notify the requestor(s) that the result is ready to be picked up. * If this call isn't made, clients have to sit out the entire * pre-handshake delay. */ score->pending_lookups -= 1; if (score->pending_lookups == 0) PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT); } else if (event == EVENT_TIME) { msg_warn("dnsblog reply timeout %ds for %s", var_psc_dnsbl_tmout, (char *) vstream_context(stream)); } /* Here, score may be a null pointer. */ vstream_fclose(stream); }
static void verify_query_service(VSTREAM *client_stream) { VSTRING *addr = vstring_alloc(10); VSTRING *get_buf = 0; VSTRING *put_buf = 0; const char *raw_data; int addr_status; long probed; long updated; char *text; if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), ATTR_TYPE_END) == 1) { long now = (long) time((time_t *) 0); /* * Produce a default record when no usable record exists. * * If negative caching is disabled, purge an expired record from the * database. * * XXX Assume that a probe is lost if no response is received in 1000 * seconds. If this number is too small the queue will slowly fill up * with delayed probes. * * XXX Maintain a moving average for the probe turnaround time, and * allow probe "retransmission" when a probe is outstanding for, say * some minimal amount of time (1000 sec) plus several times the * observed probe turnaround time. This causes probing to back off * when the mail system becomes congested. */ #define POSITIVE_ENTRY_EXPIRED(addr_status, updated) \ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_exp < now) #define NEGATIVE_ENTRY_EXPIRED(addr_status, updated) \ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_exp < now) #define PROBE_TTL 1000 /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ translit(STR(addr), ":", "_"); if ((raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 /* not found */ || ((get_buf = vstring_alloc(10)), vstring_strcpy(get_buf, raw_data), /* malformed */ verify_parse_entry(STR(get_buf), &addr_status, &probed, &updated, &text) < 0) || (now - probed > PROBE_TTL /* safe to probe */ && (POSITIVE_ENTRY_EXPIRED(addr_status, updated) || NEGATIVE_ENTRY_EXPIRED(addr_status, updated)))) { addr_status = DEL_RCPT_STAT_TODO; probed = 0; updated = 0; text = "Address verification in progress"; if (raw_data != 0 && var_verify_neg_cache == 0) dict_cache_delete(verify_map, STR(addr)); } if (msg_verbose) msg_info("GOT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, probed, updated, text); /* * Respond to the client. */ attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status), SEND_ATTR_STR(MAIL_ATTR_WHY, text), ATTR_TYPE_END); /* * Send a new probe when the information needs to be refreshed. * * XXX For an initial proof of concept implementation, use synchronous * mail submission. This needs to be made async for high-volume * sites, which makes it even more interesting to eliminate duplicate * queries while a probe is being built. * * If negative caching is turned off, update the database only when * refreshing an existing entry. */ #define POSITIVE_REFRESH_NEEDED(addr_status, updated) \ (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_try < now) #define NEGATIVE_REFRESH_NEEDED(addr_status, updated) \ (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_try < now) if (now - probed > PROBE_TTL && (POSITIVE_REFRESH_NEEDED(addr_status, updated) || NEGATIVE_REFRESH_NEEDED(addr_status, updated))) { if (msg_verbose) msg_info("PROBE %s status=%d probed=%ld updated=%ld", STR(addr), addr_status, now, updated); post_mail_fopen_async(make_verify_sender_addr(), STR(addr), MAIL_SRC_MASK_VERIFY, DEL_REQ_FLAG_MTA_VRFY, SMTPUTF8_FLAG_NONE, (VSTRING *) 0, verify_post_mail_action, (void *) 0); if (updated != 0 || var_verify_neg_cache != 0) { put_buf = vstring_alloc(10); verify_make_entry(put_buf, addr_status, now, updated, text); if (msg_verbose) msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", STR(addr), addr_status, now, updated, text); dict_cache_update(verify_map, STR(addr), STR(put_buf)); } } } vstring_free(addr); if (get_buf) vstring_free(get_buf); if (put_buf) vstring_free(put_buf); }
static void bounce_service(VSTREAM *client, char *service_name, char **argv) { int command; int status; /* * Sanity check. This service takes no command-line arguments. The * service name should be usable as a subdirectory name. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); if (mail_queue_name_ok(service_name) == 0) msg_fatal("malformed service name: %s", service_name); /* * Read and validate the first parameter of the client request. Let the * request-specific protocol routines take care of the remainder. */ if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, ATTR_TYPE_NUM, MAIL_ATTR_NREQ, &command, 0) != 1) { msg_warn("malformed request"); status = -1; } else if (command == BOUNCE_CMD_VERP) { status = bounce_verp_proto(service_name, client); } else if (command == BOUNCE_CMD_FLUSH) { status = bounce_notify_proto(service_name, client, bounce_notify_service); } else if (command == BOUNCE_CMD_WARN) { status = bounce_notify_proto(service_name, client, bounce_warn_service); } else if (command == BOUNCE_CMD_TRACE) { status = bounce_notify_proto(service_name, client, bounce_trace_service); } else if (command == BOUNCE_CMD_APPEND) { status = bounce_append_proto(service_name, client); } else if (command == BOUNCE_CMD_ONE) { status = bounce_one_proto(service_name, client); } else { msg_warn("unknown command: %d", command); status = -1; } /* * When the request has completed, send the completion status to the * client. */ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, ATTR_TYPE_END); vstream_fflush(client); /* * When a cleanup trap was set, delete the log file in case of error. * This includes errors while sending the completion status to the * client. */ if (bounce_cleanup_path) { if (status || vstream_ferror(client)) bounce_cleanup_log(); bounce_cleanup_unregister(); } }
static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) { VSTRING *buf = vstring_alloc(100); CLEANUP_STATE *state; int flags; int type = 0; int status; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Open a queue file and initialize state. */ state = cleanup_open(src); /* * Send the queue id to the client. Read client processing options. If we * can't read the client processing options we can pretty much forget * about the whole operation. */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, state->queue_id, ATTR_TYPE_END); if (attr_scan(src, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_END) != 1) { state->errs |= CLEANUP_STAT_BAD; flags = 0; } cleanup_control(state, flags); /* * XXX Rely on the front-end programs to enforce record size limits. * * First, copy the envelope records to the queue file. Then, copy the * message content (headers and body). Finally, attach any information * extracted from message headers. */ while (CLEANUP_OUT_OK(state)) { if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) { state->errs |= CLEANUP_STAT_BAD; break; } if (REC_GET_HIDDEN_TYPE(type)) { msg_warn("%s: record type %d not allowed - discarding this message", state->queue_id, type); state->errs |= CLEANUP_STAT_BAD; break; } CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf)); if (type == REC_TYPE_END) break; } /* * Keep reading in case of problems, until the sender is ready to receive * our status report. */ if (CLEANUP_OUT_OK(state) == 0 && type > 0) { while (type != REC_TYPE_END && (type = rec_get(src, buf, 0)) > 0) /* void */ ; } /* * Log something to make timeout errors easier to debug. */ if (vstream_ftimeout(src)) msg_warn("%s: read timeout on %s", state->queue_id, VSTREAM_PATH(src)); /* * Finish this message, and report the result status to the client. */ status = cleanup_flush(state); /* in case state is modified */ attr_print(src, ATTR_FLAG_NONE, ATTR_TYPE_INT, MAIL_ATTR_STATUS, status, ATTR_TYPE_STR, MAIL_ATTR_WHY, (state->flags & CLEANUP_FLAG_SMTP_REPLY) && state->smtp_reply ? state->smtp_reply : state->reason ? state->reason : "", ATTR_TYPE_END); cleanup_free(state); /* * Cleanup. */ vstring_free(buf); }
VSTREAM *tls_proxy_open(const char *service, int flags, VSTREAM *peer_stream, const char *peer_addr, const char *peer_port, int timeout) { VSTREAM *tlsproxy_stream; int status; int fd; static VSTRING *tlsproxy_service = 0; static VSTRING *remote_endpt = 0; /* * Initialize. */ if (tlsproxy_service == 0) { tlsproxy_service = vstring_alloc(20); remote_endpt = vstring_alloc(20); } /* * Connect to the tlsproxy(8) daemon. */ vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service); if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING, TLSPROXY_INIT_TIMEOUT)) < 0) { msg_warn("connect to %s service: %m", STR(tlsproxy_service)); return (0); } /* * Initial handshake. Send the data attributes now, and send the client * file descriptor in a later transaction. * * XXX The formatted endpoint should be a state member. Then, we can * simplify all the format strings throughout the program. */ tlsproxy_stream = vstream_fdopen(fd, O_RDWR); vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port); attr_print(tlsproxy_stream, ATTR_FLAG_NONE, ATTR_TYPE_STR, MAIL_ATTR_REMOTE_ENDPT, STR(remote_endpt), ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, ATTR_TYPE_INT, MAIL_ATTR_TIMEOUT, timeout, ATTR_TYPE_END); if (vstream_fflush(tlsproxy_stream) != 0) { msg_warn("error sending request to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } /* * Receive the "TLS is available" indication. * * This may seem out of order, but we must have a read transaction between * sending the request attributes and sending the SMTP client file * descriptor. We can't assume UNIX-domain socket semantics here. */ if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status, ATTR_TYPE_END) != 1 || status == 0) { /* * The TLS proxy reports that the TLS engine is not available (due to * configuration error, or other causes). */ msg_warn("%s service role \"%s\" is not available", STR(tlsproxy_service), (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" : (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" : "bogus role"); vstream_fclose(tlsproxy_stream); return (0); } /* * Send the remote SMTP client file descriptor. */ if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), vstream_fileno(peer_stream)) < 0) { /* * Some error: drop the TLS proxy stream. */ msg_warn("sending file handle to %s service: %m", STR(tlsproxy_service)); vstream_fclose(tlsproxy_stream); return (0); } return (tlsproxy_stream); }