diff options
Diffstat (limited to 'src/master/master_ent.c')
-rw-r--r-- | src/master/master_ent.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/src/master/master_ent.c b/src/master/master_ent.c new file mode 100644 index 0000000..5edc308 --- /dev/null +++ b/src/master/master_ent.c @@ -0,0 +1,648 @@ +/*++ +/* NAME +/* master_ent 3 +/* SUMMARY +/* Postfix master - config file access +/* SYNOPSIS +/* #include "master.h" +/* +/* void fset_master_ent(path) +/* char *path; +/* +/* void set_master_ent() +/* +/* MASTER_SERV *get_master_ent() +/* +/* void end_master_ent() +/* +/* void print_master_ent(entry) +/* MASTER_SERV *entry; +/* +/* void free_master_ent(entry) +/* MASTER_SERV *entry; +/* DESCRIPTION +/* This module implements a simple programmatic interface +/* for accessing Postfix master process configuration files. +/* +/* fset_master_ent() specifies the location of the master process +/* configuration file. The pathname is copied. +/* +/* set_master_ent() opens the configuration file. It is an error +/* to call this routine while the configuration file is still open. +/* It is an error to open a configuration file without specifying +/* its name to fset_master_ent(). +/* +/* get_master_ent() reads the next entry from an open configuration +/* file and returns the parsed result. A null result means the end +/* of file was reached. +/* +/* print_master_ent() prints the specified service entry. +/* +/* end_master_ent() closes an open configuration file. It is an error +/* to call this routine when the configuration file is not open. +/* +/* free_master_ent() destroys the memory used for a parsed configuration +/* file entry. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: memory allocation +/* failure. +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <netinet/in.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility libraries. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <argv.h> +#include <stringops.h> +#include <readlline.h> +#include <inet_addr_list.h> +#include <host_port.h> +#include <inet_addr_host.h> +#include <sock_addr.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <match_service.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <own_inet_addr.h> +#include <wildcard_inet_addr.h> +#include <mail_conf.h> +#include <compat_level.h> + +/* Local stuff. */ + +#include "master_proto.h" +#include "master.h" + +static char *master_path; /* config file name */ +static VSTREAM *master_fp; /* config file pointer */ +static int master_line_last; /* config file line number */ +static int master_line; /* config file line number */ +static ARGV *master_disable; /* disabled service patterns */ + +static char master_blanks[] = CHARS_SPACE; /* field delimiters */ + +/* fset_master_ent - specify configuration file pathname */ + +void fset_master_ent(char *path) +{ + if (master_path != 0) + myfree(master_path); + master_path = mystrdup(path); +} + +/* set_master_ent - open configuration file */ + +void set_master_ent() +{ + const char *myname = "set_master_ent"; + char *disable; + + if (master_fp != 0) + msg_panic("%s: configuration file still open", myname); + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + if ((master_fp = vstream_fopen(master_path, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", master_path); + master_line_last = 0; + if (master_disable != 0) + msg_panic("%s: service disable list still exists", myname); + if (inet_proto_info()->ai_family_list[0] == 0) { + msg_warn("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + msg_warn("disabling all type \"inet\" services in master.cf"); + disable = concatenate(MASTER_XPORT_NAME_INET, ",", + var_master_disable, (char *) 0); + master_disable = match_service_init(disable); + myfree(disable); + } else + master_disable = match_service_init(var_master_disable); +} + +/* end_master_ent - close configuration file */ + +void end_master_ent() +{ + const char *myname = "end_master_ent"; + + if (master_fp == 0) + msg_panic("%s: configuration file not open", myname); + if (vstream_fclose(master_fp) != 0) + msg_fatal("%s: close configuration file: %m", myname); + master_fp = 0; + if (master_disable == 0) + msg_panic("%s: no service disable list", myname); + match_service_free(master_disable); + master_disable = 0; +} + +/* master_conf_context - plot the target range */ + +static const char *master_conf_context(void) +{ + static VSTRING *context_buf = 0; + + if (context_buf == 0) + context_buf = vstring_alloc(100); + vstring_sprintf(context_buf, "%s: line %d", master_path, master_line); + return (vstring_str(context_buf)); +} + +/* fatal_with_context - print fatal error with file/line context */ + +static NORETURN PRINTFLIKE(1, 2) fatal_with_context(char *format,...) +{ + const char *myname = "fatal_with_context"; + VSTRING *vp = vstring_alloc(100); + va_list ap; + + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + + va_start(ap, format); + vstring_vsprintf(vp, format, ap); + va_end(ap); + msg_fatal("%s: %s", master_conf_context(), vstring_str(vp)); +} + +/* fatal_invalid_field - report invalid field value */ + +static NORETURN fatal_invalid_field(char *name, char *value) +{ + fatal_with_context("field \"%s\": bad value: \"%s\"", name, value); +} + +/* get_str_ent - extract string field */ + +static char *get_str_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + if ((value = mystrtok(bufp, master_blanks)) == 0) + fatal_with_context("missing \"%s\" field", name); + if (strcmp(value, "-") == 0) { + if (def_val == 0) + fatal_with_context("field \"%s\" has no default value", name); + if (warn_compat_break_chroot && strcmp(name, "chroot") == 0) + msg_info("%s: using backwards-compatible default setting " + "%s=%s", master_conf_context(), name, def_val); + return (def_val); + } else { + return (value); + } +} + +/* get_bool_ent - extract boolean field */ + +static int get_bool_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + value = get_str_ent(bufp, name, def_val); + if (strcmp("y", value) == 0) { + return (1); + } else if (strcmp("n", value) == 0) { + return (0); + } else { + fatal_invalid_field(name, value); + } + /* NOTREACHED */ +} + +/* get_int_ent - extract integer field */ + +static int get_int_ent(char **bufp, char *name, char *def_val, int min_val) +{ + char *value; + int n; + + value = get_str_ent(bufp, name, def_val); + if (!ISDIGIT(*value) || (n = atoi(value)) < min_val) + fatal_invalid_field(name, value); + return (n); +} + +/* get_master_ent - read entry from configuration file */ + +MASTER_SERV *get_master_ent() +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *junk = vstring_alloc(100); + MASTER_SERV *serv; + char *cp; + char *name; + char *host = 0; + char *port = 0; + char *transport; + int private; + int unprivileged; /* passed on to child */ + int chroot; /* passed on to child */ + char *command; + int n; + char *bufp; + char *atmp; + const char *parse_err; + static char *saved_interfaces = 0; + char *err; + + if (master_fp == 0) + msg_panic("get_master_ent: config file not open"); + if (master_disable == 0) + msg_panic("get_master_ent: no service disable list"); + + /* + * XXX We cannot change the inet_interfaces setting for a running master + * process. Listening sockets are inherited by child processes so that + * closing and reopening those sockets in the master does not work. + * + * Another problem is that library routines still cache results that are + * based on the old inet_interfaces setting. It is too much trouble to + * recompute everything. + * + * In order to keep our data structures consistent we ignore changes in + * inet_interfaces settings, and issue a warning instead. + */ + if (saved_interfaces == 0) + saved_interfaces = mystrdup(var_inet_interfaces); + + /* + * Skip blank lines and comment lines. + */ + for (;;) { + if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) { + vstring_free(buf); + vstring_free(junk); + return (0); + } + bufp = vstring_str(buf); + if ((cp = mystrtok(&bufp, master_blanks)) == 0) + continue; + name = cp; + transport = get_str_ent(&bufp, "transport type", (char *) 0); + vstring_sprintf(junk, "%s/%s", name, transport); + if (match_service_match(master_disable, vstring_str(junk)) == 0) + break; + } + + /* + * Parse one logical line from the configuration file. Initialize service + * structure members in order. + */ + serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV)); + serv->next = 0; + + /* + * Flags member. + */ + serv->flags = 0; + + /* + * All servers busy warning timer. + */ + serv->busy_warn_time = 0; + + /* + * Service name. Syntax is transport-specific. + */ + serv->ext_name = mystrdup(name); + + /* + * Transport type: inet (wild-card listen or virtual) or unix. + */ +#define STR_SAME !strcmp + + if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) { + if (!STR_SAME(saved_interfaces, var_inet_interfaces)) { + msg_warn("service %s: ignoring %s change", + serv->ext_name, VAR_INET_INTERFACES); + msg_warn("to change %s, stop and start Postfix", + VAR_INET_INTERFACES); + } + serv->type = MASTER_SERV_TYPE_INET; + atmp = mystrdup(name); + if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0) + fatal_with_context("%s in \"%s\"", parse_err, name); + if (*host) { + serv->flags |= MASTER_FLAG_INETHOST;/* host:port */ + MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *) + mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv))); + inet_addr_list_init(MASTER_INET_ADDRLIST(serv)); + if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0) + fatal_with_context("bad hostname or network address: %s", name); + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } else { + MASTER_INET_ADDRLIST(serv) = + strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ? + own_inet_addr_list() : /* virtual */ + wildcard_inet_addr_list(); /* wild-card */ + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } + MASTER_INET_PORT(serv) = mystrdup(port); + for (n = 0; /* see below */ ; n++) { + if (n >= MASTER_INET_ADDRLIST(serv)->used) { + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + break; + } + if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n))) + break; + } + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) { + serv->type = MASTER_SERV_TYPE_UNIX; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) { + serv->type = MASTER_SERV_TYPE_UXDG; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) { + serv->type = MASTER_SERV_TYPE_FIFO; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; +#ifdef MASTER_SERV_TYPE_PASS + } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) { + serv->type = MASTER_SERV_TYPE_PASS; + serv->listen_fd_count = 1; + /* If this is a connection screener, remote clients are likely. */ +#endif + } else { + fatal_with_context("bad transport type: %s", transport); + } + + /* + * Service class: public or private. + */ + private = get_bool_ent(&bufp, "private", "y"); + + /* + * Derive an internal service name. The name may depend on service + * attributes such as privacy. + */ + if (serv->type == MASTER_SERV_TYPE_INET) { + MAI_HOSTADDR_STR host_addr; + MAI_SERVPORT_STR serv_port; + struct addrinfo *res0; + + if (private) + fatal_with_context("inet service cannot be private"); + + /* + * Canonicalize endpoint names so that we correctly handle "reload" + * requests after someone changes "25" into "smtp" or vice versa. + */ + if (*host == 0) + host = 0; + /* Canonicalize numeric host and numeric or symbolic service. */ + if (hostaddr_to_sockaddr(host, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + host ? &host_addr : (MAI_HOSTADDR_STR *) 0, + &serv_port, 0); + serv->name = (host ? concatenate("[", host_addr.buf, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Canonicalize numeric or symbolic service. */ + else if (hostaddr_to_sockaddr((char *) 0, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + (MAI_HOSTADDR_STR *) 0, &serv_port, 0); + serv->name = (host ? concatenate("[", host, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Bad service name? */ + else + serv->name = mystrdup(name); + myfree(atmp); + } else if (serv->type == MASTER_SERV_TYPE_UNIX) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_UXDG) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_FIFO) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#ifdef MASTER_SERV_TYPE_PASS + } else if (serv->type == MASTER_SERV_TYPE_PASS) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#endif + } else { + msg_panic("bad transport type: %d", serv->type); + } + + /* + * Listen socket(s). XXX We pre-allocate storage because the number of + * sockets is frozen anyway once we build the command-line vector below. + */ + if (serv->listen_fd_count == 0) { + fatal_with_context("no valid IP address found: %s", name); + } + serv->listen_fd = (int *) mymalloc(sizeof(int) * serv->listen_fd_count); + for (n = 0; n < serv->listen_fd_count; n++) + serv->listen_fd[n] = -1; + + /* + * Privilege level. Default is to restrict process privileges to those of + * the mail owner. + */ + unprivileged = get_bool_ent(&bufp, "unprivileged", "y"); + + /* + * Chroot. Default is to restrict file system access to the mail queue. + * XXX Chroot cannot imply unprivileged service (for example, the pickup + * service runs chrooted but needs privileges to open files as the user). + */ + chroot = get_bool_ent(&bufp, "chroot", compat_level + < compat_level_from_string(COMPAT_LEVEL_1, msg_panic) ? + "y" : "n"); + + /* + * Wakeup timer. XXX should we require that var_proc_limit == 1? Right + * now, the only services that have a wakeup timer also happen to be the + * services that have at most one running instance: local pickup and + * local delivery. + */ + serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0); + + /* + * Find out if the wakeup time is conditional, i.e., wakeup triggers + * should not be sent until the service has actually been used. + */ + if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?') + serv->flags |= MASTER_FLAG_CONDWAKE; + + /* + * Concurrency limit. Zero means no limit. + */ + vstring_sprintf(junk, "%d", var_proc_limit); + serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0); + + /* + * Path to command, + */ + command = get_str_ent(&bufp, "command", (char *) 0); + serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0); + + /* + * Idle and total process count. + */ + serv->avail_proc = 0; + serv->total_proc = 0; + + /* + * Backoff time in case a service is broken. + */ + serv->throttle_delay = var_throttle_time; + + /* + * Shared channel for child status updates. + */ + serv->status_fd[0] = serv->status_fd[1] = -1; + + /* + * Child process structures. + */ + serv->children = 0; + + /* + * Command-line vector. Add "-n service_name" when the process name + * basename differs from the service name. Always add the transport. + */ + serv->args = argv_alloc(0); + argv_add(serv->args, command, (char *) 0); + if (serv->max_proc == 1) + argv_add(serv->args, "-l", (char *) 0); + if (serv->max_proc == 0) + argv_add(serv->args, "-z", (char *) 0); + if (strcmp(basename(command), name) != 0) + argv_add(serv->args, "-n", name, (char *) 0); + argv_add(serv->args, "-t", transport, (char *) 0); + if (master_detach == 0) + argv_add(serv->args, "-d", (char *) 0); + if (msg_verbose) + argv_add(serv->args, "-v", (char *) 0); + if (unprivileged) + argv_add(serv->args, "-u", (char *) 0); + if (chroot) + argv_add(serv->args, "-c", (char *) 0); + if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) { + argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0); + serv->stress_param_val = + serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1; + serv->stress_param_val[0] = 0; + } else + serv->stress_param_val = 0; + serv->stress_expire_time = 0; + if (serv->listen_fd_count > 1) + argv_add(serv->args, "-s", + vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)), + (char *) 0); + while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) { + if (*cp == CHARS_BRACE[0] + && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) + fatal_with_context("%s", err); + argv_add(serv->args, cp, (char *) 0); + } + argv_terminate(serv->args); + + /* + * Cleanup. + */ + vstring_free(buf); + vstring_free(junk); + return (serv); +} + +/* print_master_ent - show service entry contents */ + +void print_master_ent(MASTER_SERV *serv) +{ + char **cpp; + + msg_info("====start service entry"); + msg_info("flags: %d", serv->flags); + msg_info("name: %s", serv->name); + msg_info("type: %s", + serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX : + serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO : + serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET : +#ifdef MASTER_SERV_TYPE_PASS + serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS : +#endif + serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG : + "unknown transport type"); + msg_info("listen_fd_count: %d", serv->listen_fd_count); + msg_info("wakeup: %d", serv->wakeup_time); + msg_info("max_proc: %d", serv->max_proc); + msg_info("path: %s", serv->path); + for (cpp = serv->args->argv; *cpp; cpp++) + msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp); + msg_info("avail_proc: %d", serv->avail_proc); + msg_info("total_proc: %d", serv->total_proc); + msg_info("throttle_delay: %d", serv->throttle_delay); + msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]); + msg_info("children: 0x%lx", (long) serv->children); + msg_info("next: 0x%lx", (long) serv->next); + msg_info("====end service entry"); +} + +/* free_master_ent - destroy process entry */ + +void free_master_ent(MASTER_SERV *serv) +{ + + /* + * Undo what get_master_ent() created. + */ + if (serv->flags & MASTER_FLAG_INETHOST) { + inet_addr_list_free(MASTER_INET_ADDRLIST(serv)); + myfree((void *) MASTER_INET_ADDRLIST(serv)); + } + if (serv->type == MASTER_SERV_TYPE_INET) + myfree(MASTER_INET_PORT(serv)); + myfree(serv->ext_name); + myfree(serv->name); + myfree(serv->path); + argv_free(serv->args); + myfree((void *) serv->listen_fd); + myfree((void *) serv); +} |