int dict_load_file_xt(const char *dict_name, const char *path) { VSTREAM *fp; struct stat st; time_t before; time_t after; /* * Read the file again if it is hot. This may result in reading a partial * parameter name when a file changes in the middle of a read. */ for (before = time((time_t *) 0); /* see below */ ; before = after) { if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) return (0); dict_load_fp(dict_name, fp); if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", path); if (vstream_ferror(fp) || vstream_fclose(fp)) msg_fatal("read %s: %m", path); after = time((time_t *) 0); if (st.st_mtime < before - 1 || st.st_mtime > after) break; if (msg_verbose > 1) msg_info("pausing to let %s cool down", path); doze(300000); } return (1); }
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); }
void load_file(const char *path, LOAD_FILE_FN action, void *context) { VSTREAM *fp; struct stat st; time_t before; time_t after; /* * Read the file again if it is hot. This may result in reading a partial * parameter name or missing end marker when a file changes in the middle * of a read. */ for (before = time((time_t *) 0); /* see below */ ; before = after) { if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path); action(fp, context); if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", path); if (vstream_ferror(fp) || vstream_fclose(fp)) msg_fatal("read %s: %m", path); after = time((time_t *) 0); if (st.st_mtime < before - 1 || st.st_mtime > after) break; if (msg_verbose) msg_info("pausing to let %s cool down", path); doze(300000); } }
void pcf_read_master(int fail_on_open_error) { const char *myname = "pcf_read_master"; char *path; VSTRING *buf; VSTREAM *fp; const char *err; int entry_count = 0; int line_count; int last_line = 0; /* * Sanity check. */ if (pcf_master_table != 0) msg_panic("%s: master table is already initialized", myname); /* * Get the location of master.cf. */ if (var_config_dir == 0) pcf_set_config_dir(); path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); /* * Initialize the in-memory master table. */ pcf_master_table = (PCF_MASTER_ENT *) mymalloc(sizeof(*pcf_master_table)); /* * Skip blank lines and comment lines. Degrade gracefully if master.cf is * not available, and master.cf is not the primary target. */ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) { if (fail_on_open_error) msg_fatal("open %s: %m", path); msg_warn("open %s: %m", path); } else { buf = vstring_alloc(100); while (readllines(buf, fp, &last_line, &line_count) != 0) { pcf_master_table = (PCF_MASTER_ENT *) myrealloc((void *) pcf_master_table, (entry_count + 2) * sizeof(*pcf_master_table)); if ((err = pcf_parse_master_entry(pcf_master_table + entry_count, STR(buf))) != 0) msg_fatal("file %s: line %d: %s", path, line_count, err); entry_count += 1; } vstream_fclose(fp); vstring_free(buf); } /* * Null-terminate the master table and clean up. */ pcf_master_table[entry_count].argv = 0; myfree(path); }
DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) { DICT_CIDR *dict_cidr; VSTREAM *map_fp; VSTRING *line_buffer = vstring_alloc(100); VSTRING *why = vstring_alloc(100); DICT_CIDR_ENTRY *rule; DICT_CIDR_ENTRY *last_rule = 0; int lineno = 0; /* * Sanity checks. */ if (open_flags != O_RDONLY) msg_fatal("%s:%s map requires O_RDONLY access mode", DICT_TYPE_CIDR, mapname); /* * XXX Eliminate unnecessary queries by setting a flag that says "this * map matches network addresses only". */ dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname, sizeof(*dict_cidr)); dict_cidr->dict.lookup = dict_cidr_lookup; dict_cidr->dict.close = dict_cidr_close; dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; dict_cidr->head = 0; if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", mapname); while (readlline(line_buffer, map_fp, &lineno)) { rule = dict_cidr_parse_rule(vstring_str(line_buffer), why); if (rule == 0) { msg_warn("cidr map %s, line %d: %s: skipping this rule", mapname, lineno, vstring_str(why)); continue; } if (last_rule == 0) dict_cidr->head = rule; else last_rule->cidr_info.next = &(rule->cidr_info); last_rule = rule; } /* * Clean up. */ if (vstream_fclose(map_fp)) msg_fatal("cidr map %s: read error: %m", mapname); vstring_free(line_buffer); vstring_free(why); return (DICT_DEBUG (&dict_cidr->dict)); }
main(void) { VSTRING *vp = vstring_alloc(1); VSTREAM *fp; if ((fp = vstream_fopen(TEXT_VSTREAM, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", TEXT_VSTREAM); while (vstring_fgets(vp, fp)) vstream_fprintf(VSTREAM_OUT, "%s", vstring_str(vp)); vstream_fclose(fp); vstream_fflush(VSTREAM_OUT); vstring_free(vp); }
static VSTREAM *safe_open_create(const char *path, int flags, int mode, struct stat * st, uid_t user, uid_t group, VSTRING *why) { VSTREAM *fp; /* * Create a non-existing file. This relies on O_CREAT | O_EXCL to not * follow symbolic links. */ if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) { vstring_sprintf(why, "cannot create file exclusively: %m"); return (0); } /* * Optionally change ownership after creating a new file. If there is a * problem we should not attempt to delete the file. Something else may * have opened the file in the mean time. */ #define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1) if (CHANGE_OWNER(user, group) && fchown(vstream_fileno(fp), user, group) < 0) { msg_warn("%s: cannot change file ownership: %m", path); } /* * Optionally look up the file attributes. */ if (st != 0 && fstat(vstream_fileno(fp), st) < 0) msg_fatal("%s: bad open file status: %m", path); /* * We are almost there... */ else { return (fp); } /* * End up here in case of trouble. */ vstream_fclose(fp); return (0); }
void mail_conf_checkdir(const char *config_dir) { VSTRING *buf; VSTREAM *fp; char *path; char *name; char *value; char *cp; int found = 0; /* * If running set-[ug]id, require that a non-default configuration * directory name is blessed as a bona fide configuration directory in * the default main.cf file. */ path = concatenate(DEF_CONFIG_DIR, "/", "main.cf", (char *) 0); if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) msg_fatal("open file %s: %m", path); buf = vstring_alloc(1); while (found == 0 && readlline(buf, fp, (int *) 0)) { if (split_nameval(vstring_str(buf), &name, &value) == 0 && (strcmp(name, VAR_CONFIG_DIRS) == 0 || strcmp(name, VAR_MULTI_CONF_DIRS) == 0)) { while (found == 0 && (cp = mystrtok(&value, CHARS_COMMA_SP)) != 0) if (strcmp(cp, config_dir) == 0) found = 1; } } if (vstream_fclose(fp)) msg_fatal("read file %s: %m", path); vstring_free(buf); if (found == 0) { msg_error("unauthorized configuration directory name: %s", config_dir); msg_fatal("specify \"%s = %s\" or \"%s = %s\" in %s", VAR_CONFIG_DIRS, config_dir, VAR_MULTI_CONF_DIRS, config_dir, path); } myfree(path); }
static void postmap(char *map_type, char *path_name, int postmap_flags, int open_flags, int dict_flags) { VSTREAM *NOCLOBBER source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; int last_line; char *key; char *value; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { /* Incremental mode. */ source_fp = VSTREAM_IN; vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); } else { /* Create database. */ if (strcmp(map_type, DICT_TYPE_PROXY) == 0) msg_fatal("can't create maps via the proxy service"); dict_flags |= DICT_FLAG_BULK_UPDATE; if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postmap does not * allow privilege escalation. */ if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, optionally create it when it does not exist, * optionally truncate it when it does exist, and lock out any * spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Trap "exceptions" so that we can restart a bulk-mode update after a * recoverable error. */ for (;;) { if (dict_isjmp(mkmap->dict) != 0 && dict_setjmp(mkmap->dict) != 0 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* * Add records to the database. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { /* * First some UTF-8 checks sans casefolding. */ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) && !allascii(STR(line_buffer)) && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { msg_warn("%s, line %d: non-UTF-8 input \"%s\"" " -- ignoring this line", VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); continue; } /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ key = STR(line_buffer); value = key + strcspn(key, CHARS_SPACE); if (*value) *value++ = 0; while (ISSPACE(*value)) value++; trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; /* * Enforce the "key whitespace value" format. Disallow missing * keys or missing values. */ if (*key == 0 || *value == 0) { msg_warn("%s, line %d: expected format: key whitespace value", VSTREAM_PATH(source_fp), lineno); continue; } if (key[strlen(key) - 1] == ':') msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?", VSTREAM_PATH(source_fp), lineno); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, key, value); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } break; } /* * Close the mapping database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }
void dict_open_dlinfo(const char *path) { char *myname="dict_open_dlinfo"; VSTREAM *conf_fp=vstream_fopen(path,O_RDONLY,0); VSTRING *buf = vstring_alloc(100); char *cp; ARGV *argv; MVECT vector; int nelm=0; int linenum=0; dict_dlinfo=(DLINFO*)mvect_alloc(&vector,sizeof(DLINFO),3,NULL,NULL); if (!conf_fp) { msg_warn("%s: cannot open %s. No dynamic maps will be allowed.", myname, path); } else { while (vstring_get_nonl(buf,conf_fp) != VSTREAM_EOF) { cp = vstring_str(buf); linenum++; if (*cp == '#' || *cp == '\0') continue; argv = argv_split(cp, " \t"); if (argv->argc != 3 && argv->argc != 4) { msg_fatal("%s: Expected \"pattern .so-name open-function [mkmap-function]\" at line %d", myname, linenum); } if (STREQ(argv->argv[0],"*")) { msg_warn("%s: wildcard dynamic map entry no longer supported.", myname); continue; } if (argv->argv[1][0] != '/') { msg_fatal("%s: .so name must begin with a \"/\" at line %d", myname, linenum); } if (nelm >= vector.nelm) { dict_dlinfo=(DLINFO*)mvect_realloc(&vector,vector.nelm+3); } dict_dlinfo[nelm].pattern = mystrdup(argv->argv[0]); dict_dlinfo[nelm].soname = mystrdup(argv->argv[1]); dict_dlinfo[nelm].openfunc = mystrdup(argv->argv[2]); if (argv->argc==4) dict_dlinfo[nelm].mkmapfunc = mystrdup(argv->argv[3]); else dict_dlinfo[nelm].mkmapfunc = NULL; nelm++; argv_free(argv); } } if (nelm >= vector.nelm) { dict_dlinfo=(DLINFO*)mvect_realloc(&vector,vector.nelm+1); } dict_dlinfo[nelm].pattern = NULL; dict_dlinfo[nelm].soname = NULL; dict_dlinfo[nelm].openfunc = NULL; dict_dlinfo[nelm].mkmapfunc = NULL; if (conf_fp) vstream_fclose(conf_fp); vstring_free(buf); }
int main(int argc, char **argv) { SESSION *session; char *host; char *port; char *path; int path_len; int sessions = 1; int ch; int i; char *buf; const char *parse_err; struct addrinfo *res; int aierr; const char *protocols = INET_PROTO_NAME_ALL; INET_PROTO_INFO *proto_info; char *message_file = 0; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case 'A': allow_reject = 1; break; case 'c': count++; break; case 'C': if ((connect_count = atoi(optarg)) <= 0) msg_fatal("bad connection count: %s", optarg); break; case 'd': disconnect = 0; break; case 'f': sender = optarg; break; case 'F': if (message_file == 0 && message_length > 0) msg_fatal("-l option cannot be used with -F"); message_file = optarg; break; case 'l': if (message_file != 0) msg_fatal("-l option cannot be used with -F"); if ((message_length = atoi(optarg)) <= 0) msg_fatal("bad message length: %s", optarg); break; case 'L': talk_lmtp = 1; break; case 'm': if ((message_count = atoi(optarg)) <= 0) msg_fatal("bad message count: %s", optarg); break; case 'M': if (*optarg == '[') { if (!valid_mailhost_literal(optarg, DO_GRIPE)) msg_fatal("bad address literal: %s", optarg); } else { if (!valid_hostname(optarg, DO_GRIPE)) msg_fatal("bad hostname: %s", optarg); } var_myhostname = optarg; break; case 'N': number_rcpts = 1; break; case 'o': send_helo_first = 0; send_headers = 0; break; case 'r': if ((recipients = atoi(optarg)) <= 0) msg_fatal("bad recipient count: %s", optarg); break; case 'R': if (fixed_delay > 0) msg_fatal("do not use -w and -R options at the same time"); if ((random_delay = atoi(optarg)) <= 0) msg_fatal("bad random delay: %s", optarg); break; case 's': if ((sessions = atoi(optarg)) <= 0) msg_fatal("bad session count: %s", optarg); break; case 'S': subject = optarg; break; case 't': recipient = optarg; break; case 'T': if ((inet_windowsize = atoi(optarg)) <= 0) msg_fatal("bad TCP window size: %s", optarg); break; case 'v': msg_verbose++; break; case 'w': if (random_delay > 0) msg_fatal("do not use -w and -R options at the same time"); if ((fixed_delay = atoi(optarg)) <= 0) msg_fatal("bad fixed delay: %s", optarg); break; default: usage(argv[0]); } } if (argc - optind != 1) usage(argv[0]); if (random_delay > 0) srand(getpid()); /* * Initialize the message content, SMTP encoded. smtp_fputs() will append * another \r\n but we don't care. */ if (message_file != 0) { VSTREAM *fp; VSTRING *buf = vstring_alloc(100); VSTRING *msg = vstring_alloc(100); if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", message_file); while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { if (*vstring_str(buf) == '.') VSTRING_ADDCH(msg, '.'); vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); vstring_memcat(msg, "\r\n", 2); } if (vstream_ferror(fp)) msg_fatal("read %s: %m", message_file); vstream_fclose(fp); vstring_free(buf); message_length = VSTRING_LEN(msg); message_data = vstring_export(msg); send_headers = 0; } else if (message_length > 0) { message_data = mymalloc(message_length); memset(message_data, 'X', message_length); for (i = 80; i < message_length; i += 80) { message_data[i - 80] = "0123456789"[(i / 80) % 10]; message_data[i - 2] = '\r'; message_data[i - 1] = '\n'; } } /* * Translate endpoint address to internal form. */ proto_info = inet_proto_init("protocols", protocols); if (strncmp(argv[optind], "unix:", 5) == 0) { path = argv[optind] + 5; path_len = strlen(path); if (path_len >= (int) sizeof(sun.sun_path)) msg_fatal("unix-domain name too long: %s", path); memset((char *) &sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; #ifdef HAS_SUN_LEN sun.sun_len = path_len + 1; #endif memcpy(sun.sun_path, path, path_len); sa = (struct sockaddr *) & sun; sa_length = sizeof(sun); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; buf = mystrdup(argv[optind]); if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) msg_fatal("%s: %s", argv[optind], parse_err); if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); myfree(buf); sa = (struct sockaddr *) & ss; if (res->ai_addrlen > sizeof(ss)) msg_fatal("address length %d > buffer length %d", (int) res->ai_addrlen, (int) sizeof(ss)); memcpy((char *) sa, res->ai_addr, res->ai_addrlen); sa_length = res->ai_addrlen; #ifdef HAS_SA_LEN sa->sa_len = sa_length; #endif freeaddrinfo(res); } /* * Make sure the SMTP server cannot run us out of memory by sending * never-ending lines of text. */ if (buffer == 0) { buffer = vstring_alloc(100); vstring_ctl(buffer, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0); } /* * Make sure we have sender and recipient addresses. */ if (var_myhostname == 0) var_myhostname = get_hostname(); if (sender == 0 || recipient == 0) { vstring_sprintf(buffer, "foo@%s", var_myhostname); defaddr = mystrdup(vstring_str(buffer)); if (sender == 0) sender = defaddr; if (recipient == 0) recipient = defaddr; } /* * Start sessions. */ while (sessions-- > 0) { session = (SESSION *) mymalloc(sizeof(*session)); session->stream = 0; session->xfer_count = 0; session->connect_count = connect_count; session->next = 0; session_count++; startup(session); } for (;;) { event_loop(-1); if (session_count <= 0 && message_count <= 0) { if (count) { VSTREAM_PUTC('\n', VSTREAM_OUT); vstream_fflush(VSTREAM_OUT); } exit(0); } } }
static void postmap(char *map_type, char *path_name, int postmap_flags, int open_flags, int dict_flags) { VSTREAM *source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; char *key; char *value; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { source_fp = VSTREAM_IN; vstream_control(source_fp, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); } else if (strcmp(map_type, DICT_TYPE_PROXY) == 0) { msg_fatal("can't create maps via the proxy service"); } else if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postmap does not * allow privilege escalation. */ if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, optionally create it when it does not exist, * optionally truncate it when it does exist, and lock out any * spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Add records to the database. */ lineno = 0; while (readlline(line_buffer, source_fp, &lineno)) { /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ key = STR(line_buffer); value = key + strcspn(key, " \t\r\n"); if (*value) *value++ = 0; while (ISSPACE(*value)) value++; trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; /* * Enforce the "key whitespace value" format. Disallow missing keys * or missing values. */ if (*key == 0 || *value == 0) { msg_warn("%s, line %d: expected format: key whitespace value", VSTREAM_PATH(source_fp), lineno); continue; } if (key[strlen(key) - 1] == ':') msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?", VSTREAM_PATH(source_fp), lineno); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, key, value); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } /* * Close the mapping database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }
void pcf_edit_master(int mode, int argc, char **argv) { const char *myname = "pcf_edit_master"; char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *line_buf = vstring_alloc(100); VSTRING *parse_buf = vstring_alloc(100); int lineno; PCF_MASTER_ENT *new_entry; VSTRING *full_entry_buf = vstring_alloc(100); char *cp; char *pattern; int service_name_type_matched; const char *err; PCF_MASTER_EDIT_REQ *edit_reqs; PCF_MASTER_EDIT_REQ *req; int num_reqs = argc; const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#"; char *service_name; char *service_type; /* * Sanity check. */ if (num_reqs <= 0) msg_panic("%s: empty argument list", myname); /* * Preprocessing: split pattern=value, then split the pattern components. */ edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof(*edit_reqs) * num_reqs); for (req = edit_reqs; *argv != 0; req++, argv++) { req->match_count = 0; req->raw_text = *argv; cp = req->parsed_text = mystrdup(req->raw_text); if (strchr(cp, '\n') != 0) msg_fatal("%s accept no multi-line input", edit_opts); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("%s accept no comment input", edit_opts); /* Separate the pattern from the value. */ if (mode & PCF_EDIT_CONF) { if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); if ((mode & PCF_MASTER_PARAM) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("whitespace in parameter value: \"%s\"", req->raw_text); } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { if (strchr(cp, '=') != 0) msg_fatal("-X or -# requires names without value"); pattern = cp; trimblanks(pattern, 0); req->edit_value = 0; } else { msg_panic("%s: unknown mode %d", myname, mode); } #define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM) /* * Split name/type or name/type/whatever pattern into components. */ switch (mode & PCF_MASTER_MASK) { case PCF_MASTER_ENTRY: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 2, 2)) == 0) msg_fatal("-Me, -MX or -M# requires service_name/type"); break; case PCF_MASTER_FLD: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Fe or -FX requires service_name/type/field_name"); req->field_number = pcf_parse_field_pattern(req->service_pattern->argv[2]); if (pcf_is_magic_field_pattern(req->field_number)) msg_fatal("-Fe does not accept wild-card field name"); if ((mode & PCF_EDIT_CONF) && req->field_number < PCF_MASTER_FLD_CMD && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Fe does not accept whitespace in non-command field"); break; case PCF_MASTER_PARAM: if ((req->service_pattern = pcf_parse_service_pattern(pattern, 3, 3)) == 0) msg_fatal("-Pe or -PX requires service_name/type/parameter"); req->param_pattern = req->service_pattern->argv[2]; if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) msg_fatal("-Pe does not accept wild-card parameter name"); if ((mode & PCF_EDIT_CONF) && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) msg_fatal("-Pe does not accept whitespace in parameter value"); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ pcf_set_config_dir(); path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing service entries on * the fly. */ service_name_type_matched = 0; new_entry = 0; lineno = 0; while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0) { vstring_strcpy(line_buf, STR(parse_buf)); /* * Copy, skip or replace continued text. */ if (cp > STR(parse_buf)) { if (service_name_type_matched == 0) vstream_fputs(STR(line_buf), dst); else if (mode & PCF_COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(line_buf)); } /* * Copy or replace (start of) logical line. */ else { service_name_type_matched = 0; /* * Parse out the service name and type. */ if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0 || (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0) msg_fatal("file %s: line %d: specify service name and type " "on the same line", path, lineno); if (strchr(service_name, '=')) msg_fatal("file %s: line %d: service name syntax \"%s\" is " "unsupported with %s", path, lineno, service_name, edit_opts); if (service_type[strcspn(service_type, "=/")] != 0) msg_fatal("file %s: line %d: " "service type syntax \"%s\" is unsupported with %s", path, lineno, service_type, edit_opts); /* * Match each service pattern. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, service_name, service_type)) { service_name_type_matched = 1; /* Sticky flag */ req->match_count += 1; /* * Generate replacement master.cf entries. */ if ((mode & PCF_EDIT_CONF) || ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) { switch (mode & PCF_MASTER_MASK) { /* * Replace master.cf entry field or parameter * value. */ case PCF_MASTER_FLD: case PCF_MASTER_PARAM: if (new_entry == 0) { /* Gobble up any continuation lines. */ pcf_gobble_cf_line(full_entry_buf, line_buf, src, dst, &lineno); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, STR(full_entry_buf))) != 0) msg_fatal("file %s: line %d: %s", path, lineno, err); } if (mode & PCF_MASTER_FLD) { pcf_edit_master_field(new_entry, req->field_number, req->edit_value); } else { pcf_edit_master_param(new_entry, mode, req->param_pattern, req->edit_value); } break; /* * Replace entire master.cf entry. */ case PCF_MASTER_ENTRY: if (new_entry != 0) pcf_free_master_entry(new_entry); new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); break; default: msg_panic("%s: unknown edit mode %d", myname, mode); } } } } /* * Pass through or replace the current input line. */ if (new_entry) { pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); new_entry = 0; } else if (service_name_type_matched == 0) { vstream_fputs(STR(line_buf), dst); } else if (mode & PCF_COMMENT_OUT) { vstream_fprintf(dst, "#%s", STR(line_buf)); } } } /* * Postprocessing: when editing entire service entries, generate new * entries for services not found. Otherwise (editing fields or * parameters), "service not found" is a fatal error. */ for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { if (req->match_count == 0) { if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) { new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) msg_fatal("%s: \"%s\"", err, req->raw_text); pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); pcf_free_master_entry(new_entry); } else if ((mode & PCF_MASTER_ENTRY) == 0) { msg_warn("unmatched service_name/type: \"%s\"", req->raw_text); } } } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(line_buf); vstring_free(parse_buf); vstring_free(full_entry_buf); for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { argv_free(req->service_pattern); myfree(req->parsed_text); } myfree((char *) edit_reqs); }
static void postalias(char *map_type, char *path_name, int postalias_flags, int open_flags, int dict_flags) { VSTREAM *NOCLOBBER source_fp; VSTRING *line_buffer; MKMAP *mkmap; int lineno; int last_line; VSTRING *key_buffer; VSTRING *value_buffer; TOK822 *tok_list; TOK822 *key_list; TOK822 *colon; TOK822 *value_list; struct stat st; mode_t saved_mask; /* * Initialize. */ line_buffer = vstring_alloc(100); key_buffer = vstring_alloc(100); value_buffer = vstring_alloc(100); if ((open_flags & O_TRUNC) == 0) { /* Incremental mode. */ source_fp = VSTREAM_IN; vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); } else { /* Create database. */ if (strcmp(map_type, DICT_TYPE_PROXY) == 0) msg_fatal("can't create maps via the proxy service"); dict_flags |= DICT_FLAG_BULK_UPDATE; if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", path_name); } if (fstat(vstream_fileno(source_fp), &st) < 0) msg_fatal("fstat %s: %m", path_name); /* * Turn off group/other read permissions as indicated in the source file. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) saved_mask = umask(022 | (~st.st_mode & 077)); /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postalias does not * allow privilege escalation. */ if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 && (st.st_uid != geteuid() || st.st_gid != getegid())) set_eugid(st.st_uid, st.st_gid); /* * Open the database, create it when it does not exist, truncate it when * it does exist, and lock out any spectators. */ mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* * And restore the umask, in case it matters. */ if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) umask(saved_mask); /* * Trap "exceptions" so that we can restart a bulk-mode update after a * recoverable error. */ for (;;) { if (dict_isjmp(mkmap->dict) != 0 && dict_setjmp(mkmap->dict) != 0 && vstream_fseek(source_fp, SEEK_SET, 0) < 0) msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); /* * Add records to the database. */ last_line = 0; while (readllines(line_buffer, source_fp, &last_line, &lineno)) { /* * First some UTF-8 checks sans casefolding. */ if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) && !allascii(STR(line_buffer)) && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { msg_warn("%s, line %d: non-UTF-8 input \"%s\"" " -- ignoring this line", VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); continue; } /* * Tokenize the input, so that we do the right thing when a * quoted localpart contains special characters such as "@", ":" * and so on. */ if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0) continue; /* * Enforce the key:value format. Disallow missing keys, * multi-address keys, or missing values. In order to specify an * empty string or value, enclose it in double quotes. */ if ((colon = tok822_find_type(tok_list, ':')) == 0 || colon->prev == 0 || colon->next == 0 || tok822_rfind_type(colon, ',')) { msg_warn("%s, line %d: need name:value pair", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Key must be local. XXX We should use the Postfix rewriting and * resolving services to handle all address forms correctly. * However, we can't count on the mail system being up when the * alias database is being built, so we're guessing a bit. */ if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) { msg_warn("%s, line %d: name must be local", VSTREAM_PATH(source_fp), lineno); tok822_free_tree(tok_list); continue; } /* * Split the input into key and value parts, and convert from * token representation back to string representation. Convert * the key to internal (unquoted) form, because the resolver * produces addresses in internal form. Convert the value to * external (quoted) form, because it will have to be re-parsed * upon lookup. Discard the token representation when done. */ key_list = tok_list; tok_list = 0; value_list = tok822_cut_after(colon); tok822_unlink(colon); tok822_free(colon); tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL); tok822_free_tree(key_list); tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL); tok822_free_tree(value_list); /* * Store the value under a case-insensitive key. */ mkmap_append(mkmap, STR(key_buffer), STR(value_buffer)); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); } break; } /* * Update or append sendmail and NIS signatures. */ if ((open_flags & O_TRUNC) == 0) mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE; /* * Sendmail compatibility: add the @:@ signature to indicate that the * database is complete. This might be needed by NIS clients running * sendmail. */ mkmap_append(mkmap, "@", "@"); if (mkmap->dict->error) msg_fatal("table %s:%s: write error: %m", mkmap->dict->type, mkmap->dict->name); /* * NIS compatibility: add time and master info. Unlike other information, * this information MUST be written without a trailing null appended to * key or value. */ mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL; mkmap->dict->flags |= DICT_FLAG_TRY0NULL; vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0)); #if (defined(HAS_NIS) || defined(HAS_NISPLUS)) mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX; mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer)); mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname); #endif /* * Close the alias database, and release the lock. */ mkmap_close(mkmap); /* * Cleanup. We're about to terminate, but it is a good sanity check. */ vstring_free(value_buffer); vstring_free(key_buffer); vstring_free(line_buffer); if (source_fp != VSTREAM_IN) vstream_fclose(source_fp); }
int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_maildir"; char *newdir; char *tmpdir; char *curdir; char *tmpfile; char *newfile; DSN_BUF *why = state.msg_attr.why; VSTRING *buf; VSTREAM *dst; int mail_copy_status; int deliver_status; int copy_flags; struct stat st; struct timeval starttime; GETTIMEOFDAY(&starttime); /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Don't deliver trace-only requests. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to maildir"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Initialize. Assume the operation will fail. Set the delivered * attribute to reflect the final recipient. */ if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.rcpt.address; mail_copy_status = MAIL_COPY_STAT_WRITE; buf = vstring_alloc(100); copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT; newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); /* * Create and write the file as the recipient, so that file quota work. * Create any missing directories on the fly. The file name is chosen * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: * * "A unique name has three pieces, separated by dots. On the left is the * result of time(). On the right is the result of gethostname(). In the * middle is something that doesn't repeat within one second on a single * host. I fork a new process for each delivery, so I just use the * process ID. If you're delivering several messages from one process, * use starttime.pid_count.host, where starttime is the time that your * process started, and count is the number of messages you've * delivered." * * Well, that stopped working on fast machines, and on operating systems * that randomize process ID values. When creating a file in tmp/ we use * the process ID because it still is an exclusive resource. When moving * the file to new/ we use the device number and inode number. I do not * care if this breaks on a remote AFS file system, because people should * know better. * * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: * * A unique name has three pieces, separated by dots. On the left is the * result of time() or the second counter from gettimeofday(). On the * right is the result of gethostname(). (To deal with invalid host * names, replace / with \057 and : with \072.) In the middle is a * delivery identifier, discussed below. * * [...] * * Modern delivery identifiers are created by concatenating enough of the * following strings to guarantee uniqueness: * * [...] * * In, where n is (in hexadecimal) the UNIX inode number of this file. * Unfortunately, inode numbers aren't always available through NFS. * * Vn, where n is (in hexadecimal) the UNIX device number of this file. * Unfortunately, device numbers aren't always available through NFS. * (Device numbers are also not helpful with the standard UNIX * filesystem: a maildir has to be within a single UNIX device for link() * and rename() to work.) * * Mn, where n is (in decimal) the microsecond counter from the same * gettimeofday() used for the left part of the unique name. * * Pn, where n is (in decimal) the process ID. * * [...] */ set_eugid(usr_attr.uid, usr_attr.gid); vstring_sprintf(buf, "%lu.P%d.%s", (unsigned long) starttime.tv_sec, var_pid, get_hostname()); tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); newfile = 0; if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 && (errno != ENOENT || make_dirs(tmpdir, 0700) < 0 || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", tmpfile); } else if (fstat(vstream_fileno(dst), &st) < 0) { /* * Coverity 200604: file descriptor leak in code that never executes. * Code replaced by msg_fatal(), as it is not worthwhile to continue * after an impossible error condition. */ msg_fatal("fstat %s: %m", tmpfile); } else { vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", (unsigned long) starttime.tv_sec, (unsigned long) st.st_dev, (unsigned long) st.st_ino, (unsigned long) starttime.tv_usec, get_hostname()); newfile = concatenate(newdir, STR(buf), (char *) 0); if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), dst, copy_flags, "\n", why)) == 0) { if (sane_link(tmpfile, newfile) < 0 && (errno != ENOENT || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 || sane_link(tmpfile, newfile) < 0)) { dsb_simple(why, mbox_dsn(errno, "4.2.0"), "create maildir file %s: %m", newfile); mail_copy_status = MAIL_COPY_STAT_WRITE; } } if (unlink(tmpfile) < 0) msg_warn("remove %s: %m", tmpfile); } set_eugid(var_owner_uid, var_owner_gid); /* * The maildir location is controlled by the mail administrator. If * delivery fails, try again later. We would just bounce when the maildir * location possibly under user control. */ if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { deliver_status = DEL_STAT_DEFER; } else if (mail_copy_status != 0) { if (errno == EACCES) { msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", (long) usr_attr.uid, (long) usr_attr.gid, STR(why->reason)); msg_warn("perhaps you need to create the maildirs in advance"); } vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); deliver_status = (STR(why->status)[0] == '4' ? defer_append : bounce_append) (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { dsb_simple(why, "2.0.0", "delivered to maildir"); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); } vstring_free(buf); myfree(newdir); myfree(tmpdir); myfree(curdir); myfree(tmpfile); if (newfile) myfree(newfile); return (deliver_status); }
static VSTREAM *safe_open_exist(const char *path, int flags, struct stat * fstat_st, VSTRING *why) { struct stat local_statbuf; struct stat lstat_st; int saved_errno; VSTREAM *fp; /* * Open an existing file. */ if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) { saved_errno = errno; vstring_sprintf(why, "cannot open file: %m"); errno = saved_errno; return (0); } /* * Examine the modes from the open file: it must have exactly one hard * link (so that someone can't lure us into clobbering a sensitive file * by making a hard link to it), and it must be a non-symlink file. */ if (fstat_st == 0) fstat_st = &local_statbuf; if (fstat(vstream_fileno(fp), fstat_st) < 0) { msg_fatal("%s: bad open file status: %m", path); } else if (fstat_st->st_nlink != 1) { vstring_sprintf(why, "file has %d hard links", (int) fstat_st->st_nlink); errno = EPERM; } else if (S_ISDIR(fstat_st->st_mode)) { vstring_sprintf(why, "file is a directory"); errno = EISDIR; } /* * Look up the file again, this time using lstat(). Compare the fstat() * (open file) modes with the lstat() modes. If there is any difference, * either we followed a symlink while opening an existing file, someone * quickly changed the number of hard links, or someone replaced the file * after the open() call. The link and mode tests aren't really necessary * in daemon processes. Set-uid programs, on the other hand, can be * slowed down by arbitrary amounts, and there it would make sense to * compare even more file attributes, such as the inode generation number * on systems that have one. * * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks * owned by a non-root user. This would open a security hole when * delivering mail to a world-writable mailbox directory. */ else if (lstat(path, &lstat_st) < 0) { vstring_sprintf(why, "file status changed unexpectedly: %m"); errno = EPERM; } else if (S_ISLNK(lstat_st.st_mode)) { if (lstat_st.st_uid == 0) return (fp); vstring_sprintf(why, "file is a symbolic link"); errno = EPERM; } else if (fstat_st->st_dev != lstat_st.st_dev || fstat_st->st_ino != lstat_st.st_ino #ifdef HAS_ST_GEN || fstat_st->st_gen != lstat_st.st_gen #endif || fstat_st->st_nlink != lstat_st.st_nlink || fstat_st->st_mode != lstat_st.st_mode) { vstring_sprintf(why, "file status changed unexpectedly"); errno = EPERM; } /* * We are almost there... */ else { return (fp); } /* * End up here in case of fstat()/lstat() problems or inconsistencies. */ vstream_fclose(fp); return (0); }
DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) { DICT_PCRE *dict_pcre; VSTREAM *map_fp; struct stat st; VSTRING *line_buffer; DICT_PCRE_RULE *last_rule = 0; DICT_PCRE_RULE *rule; int lineno = 0; int nesting = 0; char *p; /* * Sanity checks. */ if (open_flags != O_RDONLY) return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_PCRE, mapname)); /* * Open the configuration file. */ if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) return (dict_surrogate(DICT_TYPE_PCRE, mapname, open_flags, dict_flags, "open %s: %m", mapname)); if (fstat(vstream_fileno(map_fp), &st) < 0) msg_fatal("fstat %s: %m", mapname); line_buffer = vstring_alloc(100); dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname, sizeof(*dict_pcre)); dict_pcre->dict.lookup = dict_pcre_lookup; dict_pcre->dict.close = dict_pcre_close; dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN; if (dict_flags & DICT_FLAG_FOLD_MUL) dict_pcre->dict.fold_buf = vstring_alloc(10); dict_pcre->head = 0; dict_pcre->expansion_buf = 0; if (dict_pcre_init == 0) { pcre_malloc = (void *(*) (size_t)) mymalloc; pcre_free = (void (*) (void *)) myfree; dict_pcre_init = 1; } dict_pcre->dict.owner.uid = st.st_uid; dict_pcre->dict.owner.status = (st.st_uid != 0); /* * Parse the pcre table. */ while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; /* Trim space at end */ if (*p == 0) continue; rule = dict_pcre_parse_rule(mapname, lineno, p, nesting, dict_flags); if (rule == 0) continue; if (rule->op == DICT_PCRE_OP_IF) { nesting++; } else if (rule->op == DICT_PCRE_OP_ENDIF) { nesting--; } if (last_rule == 0) dict_pcre->head = rule; else last_rule->next = rule; last_rule = rule; } if (nesting) msg_warn("pcre map %s, line %d: more IFs than ENDIFs", mapname, lineno); vstring_free(line_buffer); vstream_fclose(map_fp); return (DICT_DEBUG (&dict_pcre->dict)); }
EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode) { struct stat before_lock; struct stat after_lock; int saved_errno; EDIT_FILE *ep; /* * Initialize. Do not bother to optimize for the error case. */ EDIT_FILE_ALLOC(ep, path, mode); /* * As long as the output file can be opened under the temporary pathname, * this code can loop or block forever. * * Applications that are concerned about deadlock should protect the * edit_file_open() call with a watchdog timer. */ for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) { /* * Try to open the output file under the temporary pathname. This * succeeds or fails immediately. To avoid creating a shared-lock DOS * opportunity after we crash, we create the output file with no * group or other permissions, and set the final permissions at the * end (this is one reason why we try to get exclusive control over * the output file instead of the original file). We postpone file * truncation until we have obtained exclusive control over the file * content and temporary pathname. If the open operation fails, we * give up immediately. The caller can retry the call if desirable. * * XXX If we replace the vstream_fopen() call by safe_open(), then we * should replace the stat() call below by lstat(). */ if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC), EDIT_FILE_MODE)) == 0) { saved_errno = errno; EDIT_FILE_FREE(ep); errno = saved_errno; return (0); } /* * At this point we may have opened an existing output file that was * already locked. Try to lock the open file exclusively. This may * take some time. */ if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("lock %s: %m", ep->tmp_path); /* * At this point we have an exclusive lock, but some other process * may have renamed or removed the output file while we were waiting * for the lock. If that is the case, back out and try again. */ if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0) msg_fatal("open %s: %m", ep->tmp_path); if (stat(ep->tmp_path, &after_lock) < 0 || before_lock.st_dev != after_lock.st_dev || before_lock.st_ino != after_lock.st_ino #ifdef HAS_ST_GEN || before_lock.st_gen != after_lock.st_gen #endif /* No need to compare st_rdev or st_nlink here. */ ) { continue; } /* * At this point we have exclusive control over the output file * content and its temporary pathname (within the rules of the * cooperative protocol). But wait, there is more. * * There are many opportunies for trouble when opening a pre-existing * output file. Here are just a few. * * - Victor observes that a system crash in the middle of the * final-phase rename() operation may result in the output file * having both the temporary pathname and the final pathname. In that * case we must not write to the output file. * * - Wietse observes that crashes may also leave the output file in * other inconsistent states. To avoid permission-related trouble, we * simply refuse to work with an output file that has the wrong * temporary permissions. This won't stop the shared-lock DOS if we * crash after changing the file permissions, though. * * To work around these crash-related problems, remove the temporary * pathname, back out, and try again. */ if (!S_ISREG(after_lock.st_mode) #ifndef EDIT_FILE_REUSE_AFTER_CRASH || after_lock.st_size > 0 #endif || after_lock.st_nlink > 1 || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) { if (unlink(ep->tmp_path) < 0 && errno != ENOENT) msg_fatal("unlink %s: %m", ep->tmp_path); continue; } /* * Settle the final details. */ #ifdef EDIT_FILE_REUSE_AFTER_CRASH if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0) msg_fatal("truncate %s: %m", ep->tmp_path); #endif return (ep); } }
int main(int argc, char **argv) { VSTRING *buffer; VSTREAM *fp; int ch; int fd; struct stat st; int flags = 0; static char *queue_names[] = { MAIL_QUEUE_MAILDROP, MAIL_QUEUE_INCOMING, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_DEFERRED, MAIL_QUEUE_HOLD, MAIL_QUEUE_SAVED, 0, }; char **cpp; int tries; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal("open /dev/null: %m"); /* * Set up logging. */ msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "bc:dehoqv")) > 0) { switch (ch) { case 'b': flags |= PC_FLAG_PRINT_BODY; break; case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'd': flags |= PC_FLAG_PRINT_RTYPE_DEC; break; case 'e': flags |= PC_FLAG_PRINT_ENV; break; case 'h': flags |= PC_FLAG_PRINT_HEADER; break; case 'o': flags |= PC_FLAG_PRINT_OFFSET; break; case 'q': flags |= PC_FLAG_SEARCH_QUEUE; break; case 'v': msg_verbose++; break; default: usage(argv[0]); } } if ((flags & PC_MASK_PRINT_ALL) == 0) flags |= PC_MASK_PRINT_ALL; /* * Further initialization... */ mail_conf_read(); /* * Initialize. */ buffer = vstring_alloc(10); /* * If no file names are given, copy stdin. */ if (argc == optind) { vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); postcat(VSTREAM_IN, buffer, flags); } /* * Copy the named queue files in the specified order. */ else if (flags & PC_FLAG_SEARCH_QUEUE) { if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); while (optind < argc) { if (!mail_queue_id_ok(argv[optind])) msg_fatal("bad mail queue ID: %s", argv[optind]); for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++) for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++) fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0); if (fp == 0) msg_fatal("open queue file %s: %m", argv[optind]); postcat(fp, buffer, flags); if (vstream_fclose(fp)) msg_warn("close %s: %m", argv[optind]); optind++; } } /* * Copy the named files in the specified order. */ else { while (optind < argc) { if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", argv[optind]); postcat(fp, buffer, flags); if (vstream_fclose(fp)) msg_warn("close %s: %m", argv[optind]); optind++; } } /* * Clean up. */ vstring_free(buffer); exit(0); }
void edit_parameters(int mode, int argc, char **argv) { char *path; EDIT_FILE *ep; VSTREAM *src; VSTREAM *dst; VSTRING *buf = vstring_alloc(100); VSTRING *key = vstring_alloc(10); char *cp; char *edit_key; char *edit_val; HTABLE *table; struct cvalue { char *value; int found; }; struct cvalue *cvalue; HTABLE_INFO **ht_info; HTABLE_INFO **ht; int interesting; const char *err; /* * Store command-line parameters for quick lookup. */ table = htable_create(argc); while ((cp = *argv++) != 0) { if (strchr(cp, '\n') != 0) msg_fatal("-e or -# accepts no multi-line input"); while (ISSPACE(*cp)) cp++; if (*cp == '#') msg_fatal("-e or -# accepts no comment input"); if (mode & EDIT_MAIN) { if ((err = split_nameval(cp, &edit_key, &edit_val)) != 0) msg_fatal("%s: \"%s\"", err, cp); } else if (mode & COMMENT_OUT) { if (*cp == 0) msg_fatal("-# requires non-blank parameter names"); if (strchr(cp, '=') != 0) msg_fatal("-# requires parameter names only"); edit_key = mystrdup(cp); trimblanks(edit_key, 0); edit_val = 0; } else { msg_panic("edit_parameters: unknown mode %d", mode); } cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); cvalue->value = edit_val; cvalue->found = 0; htable_enter(table, edit_key, (char *) cvalue); } /* * Open a temp file for the result. This uses a deterministic name so we * don't leave behind thrash with random names. */ set_config_dir(); path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); dst = ep->tmp_fp; /* * Open the original file for input. */ if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { /* OK to delete, since we control the temp file name exclusively. */ (void) unlink(ep->tmp_path); msg_fatal("open %s for reading: %m", path); } /* * Copy original file to temp file, while replacing parameters on the * fly. Issue warnings for names found multiple times. */ #define STR(x) vstring_str(x) interesting = 0; while (vstring_get(buf, src) != VSTREAM_EOF) { for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) /* void */ ; /* Copy comment, all-whitespace, or empty line. */ if (*cp == '#' || *cp == 0) { vstream_fputs(STR(buf), dst); } /* Copy, skip or replace continued text. */ else if (cp > STR(buf)) { if (interesting == 0) vstream_fputs(STR(buf), dst); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", STR(buf)); } /* Copy or replace start of logical line. */ else { vstring_strncpy(key, cp, strcspn(cp, " \t\r\n=")); cvalue = (struct cvalue *) htable_find(table, STR(key)); if ((interesting = !!cvalue) != 0) { if (cvalue->found++ == 1) msg_warn("%s: multiple entries for \"%s\"", path, STR(key)); if (mode & EDIT_MAIN) vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); else if (mode & COMMENT_OUT) vstream_fprintf(dst, "#%s", cp); else msg_panic("edit_parameters: unknown mode %d", mode); } else { vstream_fputs(STR(buf), dst); } } } /* * Generate new entries for parameters that were not found. */ if (mode & EDIT_MAIN) { for (ht_info = ht = htable_list(table); *ht; ht++) { cvalue = (struct cvalue *) ht[0]->value; if (cvalue->found == 0) vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value); } myfree((char *) ht_info); } /* * When all is well, rename the temp file to the original one. */ if (vstream_fclose(src)) msg_fatal("read %s: %m", path); if (edit_file_close(ep) != 0) msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); /* * Cleanup. */ myfree(path); vstring_free(buf); vstring_free(key); htable_free(table, myfree); }
void read_master(int fail_on_open_error) { const char *myname = "read_master"; char *path; VSTRING *buf; ARGV *argv; VSTREAM *fp; int entry_count = 0; int line_count = 0; /* * Sanity check. */ if (master_table != 0) msg_panic("%s: master table is already initialized", myname); /* * Get the location of master.cf. */ if (var_config_dir == 0) set_config_dir(); path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); /* * We can't use the master daemon's master_ent routines in their current * form. They convert everything to internal form, and they skip disabled * services. * * The postconf command needs to show default fields as "-", and needs to * know about all service names so that it can generate service-dependent * parameter names (transport-dependent etc.). */ #define MASTER_BLANKS " \t\r\n" /* XXX */ /* * Initialize the in-memory master table. */ master_table = (PC_MASTER_ENT *) mymalloc(sizeof(*master_table)); /* * Skip blank lines and comment lines. Degrade gracefully if master.cf is * not available, and master.cf is not the primary target. */ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) { if (fail_on_open_error) msg_fatal("open %s: %m", path); msg_warn("open %s: %m", path); } else { buf = vstring_alloc(100); while (readlline(buf, fp, &line_count) != 0) { master_table = (PC_MASTER_ENT *) myrealloc((char *) master_table, (entry_count + 2) * sizeof(*master_table)); argv = argv_split(STR(buf), MASTER_BLANKS); if (argv->argc < PC_MASTER_MIN_FIELDS) msg_fatal("file %s: line %d: bad field count", path, line_count); normalize_options(argv); master_table[entry_count].name_space = concatenate(argv->argv[0], ".", argv->argv[1], (char *) 0); master_table[entry_count].argv = argv; master_table[entry_count].valid_names = 0; master_table[entry_count].all_params = 0; entry_count += 1; } vstream_fclose(fp); vstring_free(buf); } /* * Null-terminate the master table and clean up. */ master_table[entry_count].argv = 0; myfree(path); }
static ARGV *match_list_parse(ARGV *list, char *string, int init_match) { const char *myname = "match_list_parse"; VSTRING *buf = vstring_alloc(10); VSTREAM *fp; const char *delim = " ,\t\r\n"; char *bp = string; char *start; char *item; char *map_type_name_flags; int match; #define OPEN_FLAGS O_RDONLY #define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX) #define STR(x) vstring_str(x) /* * /filename contents are expanded in-line. To support !/filename we * prepend the negation operator to each item from the file. */ while ((start = mystrtokq(&bp, delim, "{}")) != 0) { if (*start == '#') { msg_warn("%s: comment at end of line is not supported: %s %s", myname, start, bp); break; } for (match = init_match, item = start; *item == '!'; item++) match = !match; if (*item == 0) msg_fatal("%s: no pattern after '!'", myname); if (*item == '/') { /* /file/name */ if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); /* XXX Should increment existing map refcount. */ if (dict_handle(STR(buf)) == 0) dict_register(STR(buf), dict_surrogate(DICT_TYPE_NOFILE, item, OPEN_FLAGS, DICT_FLAGS, "open file %s: %m", item)); argv_add(list, STR(buf), (char *) 0); } else { while (vstring_fgets(buf, fp)) if (vstring_str(buf)[0] != '#') list = match_list_parse(list, vstring_str(buf), match); if (vstream_fclose(fp)) msg_fatal("%s: read file %s: %m", myname, item); } } else if (MATCH_DICTIONARY(item)) { /* type:table */ vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); map_type_name_flags = STR(buf) + (match == 0); /* XXX Should increment existing map refcount. */ if (dict_handle(map_type_name_flags) == 0) dict_register(map_type_name_flags, dict_open(item, OPEN_FLAGS, DICT_FLAGS)); argv_add(list, STR(buf), (char *) 0); } else { /* other pattern */ argv_add(list, match ? item : STR(vstring_sprintf(buf, "!%s", item)), (char *) 0); } } vstring_free(buf); return (list); }
DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) { DICT_THASH *dict_thash; VSTREAM *fp = 0; struct stat st; time_t before; time_t after; VSTRING *line_buffer = 0; int lineno; char *key; char *value; HTABLE *table; HTABLE_INFO *ht; /* * Let the optimizer worry about eliminating redundant code. */ #define DICT_THASH_OPEN_RETURN(d) { \ DICT *__d = (d); \ if (fp != 0) \ vstream_fclose(fp); \ if (line_buffer != 0) \ vstring_free(line_buffer); \ return (__d); \ } while (0) /* * Sanity checks. */ if (open_flags != O_RDONLY) DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_THASH, path)); /* * Read the flat text file into in-memory hash. Read the file again if it * may have changed while we were reading. */ for (before = time((time_t *) 0); /* see below */ ; before = after) { if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) { DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, open_flags, dict_flags, "open database %s: %m", path)); } if (line_buffer == 0) line_buffer = vstring_alloc(100); lineno = 0; table = htable_create(13); while (readlline(line_buffer, fp, &lineno)) { /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ key = STR(line_buffer); value = key + strcspn(key, " \t\r\n"); if (*value) *value++ = 0; while (ISSPACE(*value)) value++; trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; /* * Enforce the "key whitespace value" format. Disallow missing * keys or missing values. */ if (*key == 0 || *value == 0) { msg_warn("%s, line %d: expected format: key whitespace value" " -- ignoring this line", path, lineno); continue; } if (key[strlen(key) - 1] == ':') msg_warn("%s, line %d: record is in \"key: value\" format;" " is this an alias file?", path, lineno); /* * Optionally fold the key. */ if (dict_flags & DICT_FLAG_FOLD_FIX) lowercase(key); /* * Store the value under the key. Handle duplicates * appropriately. */ if ((ht = htable_locate(table, key)) != 0) { if (dict_flags & DICT_FLAG_DUP_IGNORE) { /* void */ ; } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { myfree(ht->value); ht->value = mystrdup(value); } else if (dict_flags & DICT_FLAG_DUP_WARN) { msg_warn("%s, line %d: duplicate entry: \"%s\"", path, lineno, key); } else { msg_fatal("%s, line %d: duplicate entry: \"%s\"", path, lineno, key); } } else { htable_enter(table, key, mystrdup(value)); } } /* * See if the source file is hot. */ if (fstat(vstream_fileno(fp), &st) < 0) msg_fatal("fstat %s: %m", path); if (vstream_fclose(fp)) msg_fatal("read %s: %m", path); fp = 0; /* DICT_THASH_OPEN_RETURN() */ after = time((time_t *) 0); if (st.st_mtime < before - 1 || st.st_mtime > after) break; /* * Yes, it is hot. Discard the result and read the file again. */ htable_free(table, myfree); if (msg_verbose > 1) msg_info("pausing to let file %s cool down", path); doze(300000); } /* * Create the in-memory table. */ dict_thash = (DICT_THASH *) dict_alloc(DICT_TYPE_THASH, path, sizeof(*dict_thash)); dict_thash->dict.lookup = dict_thash_lookup; dict_thash->dict.sequence = dict_thash_sequence; dict_thash->dict.close = dict_thash_close; dict_thash->dict.flags = dict_flags | DICT_FLAG_DUP_WARN | DICT_FLAG_FIXED; if (dict_flags & DICT_FLAG_FOLD_FIX) dict_thash->dict.fold_buf = vstring_alloc(10); dict_thash->info = 0; dict_thash->table = table; dict_thash->dict.owner.uid = st.st_uid; dict_thash->dict.owner.status = (st.st_uid != 0); DICT_THASH_OPEN_RETURN(DICT_DEBUG (&dict_thash->dict)); }
DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) { const char myname[] = "dict_cidr_open"; DICT_CIDR *dict_cidr; VSTREAM *map_fp = 0; struct stat st; VSTRING *line_buffer = 0; VSTRING *why = 0; DICT_CIDR_ENTRY *rule; DICT_CIDR_ENTRY *last_rule = 0; int last_line = 0; int lineno; int nesting = 0; DICT_CIDR_ENTRY **rule_stack = 0; MVECT mvect; /* * Let the optimizer worry about eliminating redundant code. */ #define DICT_CIDR_OPEN_RETURN(d) do { \ DICT *__d = (d); \ if (map_fp != 0 && vstream_fclose(map_fp)) \ msg_fatal("cidr map %s: read error: %m", mapname); \ if (line_buffer != 0) \ vstring_free(line_buffer); \ if (why != 0) \ vstring_free(why); \ return (__d); \ } while (0) /* * Sanity checks. */ if (open_flags != O_RDONLY) DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_CIDR, mapname)); /* * Open the configuration file. */ if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0) DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, open_flags, dict_flags, "open %s: %m", mapname)); if (fstat(vstream_fileno(map_fp), &st) < 0) msg_fatal("fstat %s: %m", mapname); line_buffer = vstring_alloc(100); why = vstring_alloc(100); /* * XXX Eliminate unnecessary queries by setting a flag that says "this * map matches network addresses only". */ dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname, sizeof(*dict_cidr)); dict_cidr->dict.lookup = dict_cidr_lookup; dict_cidr->dict.close = dict_cidr_close; dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; dict_cidr->head = 0; dict_cidr->dict.owner.uid = st.st_uid; dict_cidr->dict.owner.status = (st.st_uid != 0); while (readllines(line_buffer, map_fp, &last_line, &lineno)) { rule = dict_cidr_parse_rule(vstring_str(line_buffer), lineno, nesting, why); if (rule == 0) { msg_warn("cidr map %s, line %d: %s: skipping this rule", mapname, lineno, vstring_str(why)); continue; } if (rule->cidr_info.op == CIDR_MATCH_OP_IF) { if (rule_stack == 0) rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect, sizeof(*rule_stack), nesting + 1, (MVECT_FN) 0, (MVECT_FN) 0); else rule_stack = (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1); rule_stack[nesting] = rule; nesting++; } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) { DICT_CIDR_ENTRY *if_rule; if (nesting-- <= 0) /* Already handled in dict_cidr_parse_rule(). */ msg_panic("%s: ENDIF without IF", myname); if_rule = rule_stack[nesting]; if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF) msg_panic("%s: unexpected rule stack element type %d", myname, if_rule->cidr_info.op); if_rule->cidr_info.block_end = &(rule->cidr_info); } if (last_rule == 0) dict_cidr->head = rule; else last_rule->cidr_info.next = &(rule->cidr_info); last_rule = rule; } while (nesting-- > 0) msg_warn("cidr map %s, line %d: IF has no matching ENDIF", mapname, rule_stack[nesting]->lineno); if (rule_stack) (void) mvect_free(&mvect); DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict)); }