void pcf_print_line(VSTREAM *fp, int mode, const char *fmt,...) { va_list ap; static VSTRING *buf = 0; char *start; char *next; int line_len = 0; int word_len; /* * One-off initialization. */ if (buf == 0) buf = vstring_alloc(100); /* * Format the text. */ va_start(ap, fmt); vstring_vsprintf(buf, fmt, ap); va_end(ap); /* * Normalize the whitespace. We don't use the line_wrap() routine because * 1) that function does not normalize whitespace between words and 2) we * want to normalize whitespace even when not wrapping lines. * * XXX Some parameters preserve whitespace: for example, smtpd_banner and * smtpd_reject_footer. If we have to preserve whitespace between words, * then perhaps readlline() can be changed to canonicalize whitespace * that follows a newline. */ for (start = STR(buf); *(start += strspn(start, PCF_SEPARATORS)) != 0; start = next) { word_len = strcspn(start, PCF_SEPARATORS); if (*(next = start + word_len) != 0) *next++ = 0; if (word_len > 0 && line_len > 0) { if ((mode & PCF_FOLD_LINE) == 0 || line_len + word_len < PCF_LINE_LIMIT) { vstream_fputs(" ", fp); line_len += 1; } else { vstream_fputs("\n" PCF_INDENT_TEXT, fp); line_len = PCF_INDENT_LEN; } } vstream_fputs(start, fp); line_len += word_len; } vstream_fputs("\n", fp); }
static void attr_print64_str(VSTREAM *fp, const char *str, int len) { static VSTRING *base64_buf; if (base64_buf == 0) base64_buf = vstring_alloc(10); base64_encode(base64_buf, str, len); vstream_fputs(STR(base64_buf), fp); }
int memcache_vprintf(VSTREAM *stream, const char *fmt, va_list ap) { /* * Do the I/O. */ vstream_vfprintf(stream, fmt, ap); vstream_fputs("\r\n", stream); if (vstream_ferror(stream)) return (-1); else return (0); }
static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst) { char *cp; for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) /* void */ ; /* Pass-through comment, all-whitespace, or empty line. */ if (*cp == '#' || *cp == 0) { vstream_fputs(STR(buf), dst); return (0); } else { return (cp); } }
int memcache_fwrite(VSTREAM *stream, const char *cp, ssize_t todo) { /* * Sanity check. */ if (todo < 0) msg_panic("memcache_fwrite: negative todo %ld", (long) todo); /* * Do the I/O. */ if (msg_verbose) msg_info("%s write: %.*s", VSTREAM_PATH(stream), (int) todo, cp); if (vstream_fwrite(stream, cp, todo) != todo || vstream_fputs("\r\n", stream) == VSTREAM_EOF) return (-1); else return (0); }
void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap) { int err; /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); vstream_vfprintf(stream, fmt, ap); vstream_fputs("\r\n", stream); err = vstream_ferror(stream); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (err != 0) { if (msg_verbose) msg_info("smtp_vprintf: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
void smtp_fputs(const char *cp, int todo, VSTREAM *stream) { unsigned err; if (todo < 0) msg_panic("smtp_fputs: negative todo %d", todo); /* * Do the I/O, protected against timeout. */ smtp_timeout_reset(stream); err = (vstream_fwrite(stream, cp, todo) != todo || vstream_fputs("\r\n", stream) == VSTREAM_EOF); smtp_timeout_detect(stream); /* * See if there was a problem. */ if (err != 0) { if (msg_verbose) msg_info("smtp_fputs: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } }
static void print_master_line(VSTREAM *fp, int mode, PC_MASTER_ENT *masterp) { char **argv = masterp->argv->argv; const char *arg; const char *aval; int arg_len; int line_len; int field; int in_daemon_options; static int column_goal[] = { 0, /* service */ 11, /* type */ 17, /* private */ 25, /* unpriv */ 33, /* chroot */ 41, /* wakeup */ 49, /* maxproc */ 57, /* command */ }; #define ADD_TEXT(text, len) do { \ vstream_fputs(text, fp); line_len += len; } \ while (0) #define ADD_SPACE ADD_TEXT(" ", 1) /* * Show the standard fields at their preferred column position. Use at * least one-space column separation. */ for (line_len = 0, field = 0; field < PC_MASTER_MIN_FIELDS; field++) { arg = argv[field]; if (line_len > 0) { do { ADD_SPACE; } while (line_len < column_goal[field]); } ADD_TEXT(arg, strlen(arg)); } /* * Format the daemon command-line options and non-option arguments. Here, * we have no data-dependent preference for column positions, but we do * have argument grouping preferences. */ in_daemon_options = 1; for ( /* void */ ; (arg = argv[field]) != 0; field++) { arg_len = strlen(arg); aval = 0; if (in_daemon_options) { /* * Try to show the generic options (-v -D) on the first line, and * non-options on a later line. */ if (arg[0] != '-' || strcmp(arg, "--") == 0) { in_daemon_options = 0; if ((mode & FOLD_LINE) && line_len > column_goal[PC_MASTER_MIN_FIELDS - 1]) { /* Force line wrap. */ line_len = LINE_LIMIT; } } /* * Special processing for options that require a value. */ else if (strchr(daemon_options_expecting_value, arg[1]) != 0 && (aval = argv[field + 1]) != 0) { /* * Optionally, expand $name in parameter value. */ if (strcmp(arg, "-o") == 0 && (mode & SHOW_EVAL) != 0) aval = expand_parameter_value((VSTRING *) 0, mode, aval, masterp); /* * Keep option and value on the same line. */ arg_len += strlen(aval) + 1; } } /* * Insert a line break when the next item won't fit. */ if (line_len > INDENT_LEN) { if ((mode & FOLD_LINE) == 0 || line_len + 1 + arg_len < LINE_LIMIT) { ADD_SPACE; } else { vstream_fputs("\n" INDENT_TEXT, fp); line_len = INDENT_LEN; } } ADD_TEXT(arg, strlen(arg)); if (aval) { ADD_SPACE; ADD_TEXT(aval, strlen(aval)); field += 1; } } vstream_fputs("\n", fp); }
static void print_master_line(int mode, ARGV *argv) { char *arg; char *aval; int line_len; int field; int in_daemon_options; static int column_goal[] = { 0, /* service */ 11, /* type */ 17, /* private */ 25, /* unpriv */ 33, /* chroot */ 41, /* wakeup */ 49, /* maxproc */ 57, /* command */ }; #define ADD_TEXT(text, len) do { \ vstream_fputs(text, VSTREAM_OUT); line_len += len; } \ while (0) #define ADD_SPACE ADD_TEXT(" ", 1) /* * Show the standard fields at their preferred column position. Use at * least one-space column separation. */ for (line_len = 0, field = 0; field < PC_MASTER_MIN_FIELDS; field++) { arg = argv->argv[field]; if (line_len > 0) { do { ADD_SPACE; } while (line_len < column_goal[field]); } ADD_TEXT(arg, strlen(arg)); } /* * Format the daemon command-line options and non-option arguments. Here, * we have no data-dependent preference for column positions, but we do * have argument grouping preferences. */ in_daemon_options = 1; for ( /* void */ ; argv->argv[field] != 0; field++) { arg = argv->argv[field]; if (in_daemon_options) { /* * Try to show the generic options (-v -D) on the first line, and * non-options on a later line. */ if (arg[0] != '-' || strcmp(arg, "--") == 0) { in_daemon_options = 0; if ((mode & FOLD_LINE) && line_len > column_goal[PC_MASTER_MIN_FIELDS - 1]) { vstream_fputs("\n" INDENT_TEXT, VSTREAM_OUT); line_len = INDENT_LEN; } } /* * Try to avoid breaking "-o name=value" over multiple lines if * it would fit on one line. */ else if ((mode & FOLD_LINE) && line_len > INDENT_LEN && strcmp(arg, "-o") == 0 && (aval = argv->argv[field + 1]) != 0 && INDENT_LEN + 3 + strlen(aval) < LINE_LIMIT) { vstream_fputs("\n" INDENT_TEXT, VSTREAM_OUT); line_len = INDENT_LEN; ADD_TEXT(arg, strlen(arg)); arg = aval; field += 1; } } /* * Insert a line break when the next argument won't fit (unless, of * course, we just inserted a line break). */ if (line_len > INDENT_LEN) { if ((mode & FOLD_LINE) == 0 || line_len + 1 + strlen(arg) < LINE_LIMIT) { ADD_SPACE; } else { vstream_fputs("\n" INDENT_TEXT, VSTREAM_OUT); line_len = INDENT_LEN; } } ADD_TEXT(arg, strlen(arg)); } vstream_fputs("\n", VSTREAM_OUT); }
int bounce_append_service(int unused_flags, char *service, char *queue_id, RECIPIENT *rcpt, DSN *dsn) { VSTRING *in_buf = vstring_alloc(100); VSTREAM *log; long orig_length; /* * This code is paranoid for a good reason. Once the bounce service takes * responsibility, the mail system will make no further attempts to * deliver this recipient. Whenever file access fails, assume that the * system is under stress or that something has been mis-configured, and * force a backoff by raising a fatal run-time error. */ log = mail_queue_open(service, queue_id, O_WRONLY | O_APPEND | O_CREAT, 0600); if (log == 0) msg_fatal("open file %s %s: %m", service, queue_id); /* * Lock out other processes to avoid truncating someone else's data in * case of trouble. */ if (deliver_flock(vstream_fileno(log), INTERNAL_LOCK, (VSTRING *) 0) < 0) msg_fatal("lock file %s %s: %m", service, queue_id); /* * Now, go for it. Append a record. Truncate the log to the original * length when the append operation fails. We use the plain stream-lf * file format because we do not need anything more complicated. As a * benefit, we can still recover some data when the file is a little * garbled. * * XXX addresses in defer logfiles are in printable quoted form, while * addresses in message envelope records are in raw unquoted form. This * may change once we replace the present ad-hoc bounce/defer logfile * format by one that is transparent for control etc. characters. See * also: showq/showq.c. * * While migrating from old format to new format, allow backwards * compatibility by writing an old-style record before the new-style * records. */ if ((orig_length = vstream_fseek(log, 0L, SEEK_END)) < 0) msg_fatal("seek file %s %s: %m", service, queue_id); #define NOT_NULL_EMPTY(s) ((s) != 0 && *(s) != 0) #define STR(x) vstring_str(x) vstream_fputs("\n", log); if (var_oldlog_compat) { vstream_fprintf(log, "<%s>: %s\n", *rcpt->address == 0 ? "" : STR(quote_822_local(in_buf, rcpt->address)), dsn->reason); } vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_RECIP, *rcpt->address ? STR(quote_822_local(in_buf, rcpt->address)) : "<>"); if (NOT_NULL_EMPTY(rcpt->orig_addr) && strcasecmp(rcpt->address, rcpt->orig_addr) != 0) vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_ORCPT, STR(quote_822_local(in_buf, rcpt->orig_addr))); if (rcpt->offset > 0) vstream_fprintf(log, "%s=%ld\n", MAIL_ATTR_OFFSET, rcpt->offset); if (NOT_NULL_EMPTY(rcpt->dsn_orcpt)) vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt); if (rcpt->dsn_notify != 0) vstream_fprintf(log, "%s=%d\n", MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify); if (NOT_NULL_EMPTY(dsn->status)) vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_STATUS, dsn->status); if (NOT_NULL_EMPTY(dsn->action)) vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ACTION, dsn->action); if (NOT_NULL_EMPTY(dsn->dtype) && NOT_NULL_EMPTY(dsn->dtext)) { vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTYPE, dsn->dtype); vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTEXT, dsn->dtext); } if (NOT_NULL_EMPTY(dsn->mtype) && NOT_NULL_EMPTY(dsn->mname)) { vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MTYPE, dsn->mtype); vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MNAME, dsn->mname); } if (NOT_NULL_EMPTY(dsn->reason)) vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_WHY, dsn->reason); vstream_fputs("\n", log); if (vstream_fflush(log) != 0 || fsync(vstream_fileno(log)) < 0) { #ifndef NO_TRUNCATE if (ftruncate(vstream_fileno(log), (off_t) orig_length) < 0) msg_fatal("truncate file %s %s: %m", service, queue_id); #endif msg_fatal("append file %s %s: %m", service, queue_id); } /* * Darn. If closing the log detects a problem, the only way to undo the * damage is to open the log once more, and to truncate the log to the * original length. But, this could happen only when the log is kept on a * remote file system, and that is not recommended practice anyway. */ if (vstream_fclose(log) != 0) msg_warn("append file %s %s: %m", service, queue_id); vstring_free(in_buf); return (0); }
int xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method, const char *init_response, VSTRING *reply) { const char *myname = "xsasl_dovecot_server_first"; XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; int i; char **cpp; #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) if (msg_verbose) msg_info("%s: sasl_method %s%s%s", myname, sasl_method, IFELSE(init_response, ", init_response ", ""), IFELSE(init_response, init_response, "")); if (server->mechanism_argv == 0) msg_panic("%s: no mechanism list", myname); for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) { if (*cpp == 0) { vstring_strcpy(reply, "Invalid authentication mechanism"); return XSASL_AUTH_FAIL; } if (strcasecmp(sasl_method, *cpp) == 0) break; } if (init_response) if (!is_valid_base64(init_response)) { vstring_strcpy(reply, "Invalid base64 data in initial response"); return XSASL_AUTH_FAIL; } for (i = 0; i < 2; i++) { if (!server->impl->sasl_stream) { if (xsasl_dovecot_server_connect(server->impl) < 0) return XSASL_AUTH_TEMP; } /* send the request */ server->last_request_id = ++server->impl->request_id_counter; /* XXX Encapsulate for logging. */ vstream_fprintf(server->impl->sasl_stream, "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s", server->last_request_id, sasl_method, server->service, server->server_addr, server->client_addr); if (server->tls_flag) /* XXX Encapsulate for logging. */ vstream_fputs("\tsecured", server->impl->sasl_stream); if (init_response) { /* * initial response is already base64 encoded, so we can send it * directly. */ /* XXX Encapsulate for logging. */ vstream_fprintf(server->impl->sasl_stream, "\tresp=%s", init_response); } /* XXX Encapsulate for logging. */ VSTREAM_PUTC('\n', server->impl->sasl_stream); if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF) break; if (i == 1) { vstring_strcpy(reply, "Can't connect to authentication server"); return XSASL_AUTH_TEMP; } /* * Reconnect and try again. */ xsasl_dovecot_server_disconnect(server->impl); } return xsasl_dovecot_handle_reply(server, reply); }
int mail_copy(const char *sender, const char *orig_rcpt, const char *delivered, VSTREAM *src, VSTREAM *dst, int flags, const char *eol, DSN_BUF *why) { const char *myname = "mail_copy"; VSTRING *buf; char *bp; off_t orig_length; int read_error; int write_error; int corrupt_error = 0; time_t now; int type; int prev_type; struct stat st; off_t size_limit; /* * Workaround 20090114. This will hopefully get someone's attention. The * problem with file_size_limit < message_size_limit is that mail will be * delivered again and again until someone removes it from the queue by * hand, because Postfix cannot mark a recipient record as "completed". */ if (fstat(vstream_fileno(src), &st) < 0) msg_fatal("fstat: %m"); if ((size_limit = get_file_limit()) < st.st_size) msg_panic("file size limit %lu < message size %lu. This " "causes large messages to be delivered repeatedly " "after they were submitted with \"sendmail -t\" " "or after recipients were added with the Milter " "SMFIR_ADDRCPT request", (unsigned long) size_limit, (unsigned long) st.st_size); /* * Initialize. */ #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if ((orig_length = vstream_fseek(dst, (off_t) 0, SEEK_END)) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(dst)); #endif buf = vstring_alloc(100); /* * Prepend a bunch of headers to the message. */ if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) { if (sender == 0) msg_panic("%s: null sender", myname); quote_822_local(buf, sender); if (flags & MAIL_COPY_FROM) { time(&now); vstream_fprintf(dst, "From %s %.24s%s", *sender == 0 ? MAIL_ADDR_MAIL_DAEMON : vstring_str(buf), asctime(localtime(&now)), eol); } if (flags & MAIL_COPY_RETURN_PATH) { vstream_fprintf(dst, "Return-Path: <%s>%s", *sender ? vstring_str(buf) : "", eol); } } if (flags & MAIL_COPY_ORIG_RCPT) { if (orig_rcpt == 0) msg_panic("%s: null orig_rcpt", myname); /* * An empty original recipient record almost certainly means that * original recipient processing was disabled. */ if (*orig_rcpt) { quote_822_local(buf, orig_rcpt); vstream_fprintf(dst, "X-Original-To: %s%s", vstring_str(buf), eol); } } if (flags & MAIL_COPY_DELIVERED) { if (delivered == 0) msg_panic("%s: null delivered", myname); quote_822_local(buf, delivered); vstream_fprintf(dst, "Delivered-To: %s%s", vstring_str(buf), eol); } /* * Copy the message. Escape lines that could be confused with the ugly * From_ line. Make sure that there is a blank line at the end of the * message so that the next ugly From_ can be found by mail reading * software. * * XXX Rely on the front-end services to enforce record size limits. */ #define VSTREAM_FWRITE_BUF(s,b) \ vstream_fwrite((s),vstring_str(b),VSTRING_LEN(b)) prev_type = REC_TYPE_NORM; while ((type = rec_get(src, buf, 0)) > 0) { if (type != REC_TYPE_NORM && type != REC_TYPE_CONT) break; bp = vstring_str(buf); if (prev_type == REC_TYPE_NORM) { if ((flags & MAIL_COPY_QUOTE) && *bp == 'F' && !strncmp(bp, "From ", 5)) VSTREAM_PUTC('>', dst); if ((flags & MAIL_COPY_DOT) && *bp == '.') VSTREAM_PUTC('.', dst); } if (VSTRING_LEN(buf) && VSTREAM_FWRITE_BUF(dst, buf) != VSTRING_LEN(buf)) break; if (type == REC_TYPE_NORM && vstream_fputs(eol, dst) == VSTREAM_EOF) break; prev_type = type; } if (vstream_ferror(dst) == 0) { if (var_fault_inj_code == 1) type = 0; if (type != REC_TYPE_XTRA) { /* XXX Where is the queue ID? */ msg_warn("bad record type: %d in message content", type); corrupt_error = mark_corrupt(src); } if (prev_type != REC_TYPE_NORM) vstream_fputs(eol, dst); if (flags & MAIL_COPY_BLANK) vstream_fputs(eol, dst); } vstring_free(buf); /* * Make sure we read and wrote all. Truncate the file to its original * length when the delivery failed. POSIX does not require ftruncate(), * so we may have a portability problem. Note that fclose() may fail even * while fflush and fsync() succeed. Think of remote file systems such as * AFS that copy the file back to the server upon close. Oh well, no * point optimizing the error case. XXX On systems that use flock() * locking, we must truncate the file file before closing it (and losing * the exclusive lock). */ read_error = vstream_ferror(src); write_error = vstream_fflush(dst); #ifdef HAS_FSYNC if ((flags & MAIL_COPY_TOFILE) != 0) write_error |= fsync(vstream_fileno(dst)); #endif if (var_fault_inj_code == 2) { read_error = 1; errno = ENOENT; } if (var_fault_inj_code == 3) { write_error = 1; errno = ENOENT; } #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) if (corrupt_error || read_error || write_error) /* Complain about ignored "undo" errors? So sue me. */ (void) ftruncate(vstream_fileno(dst), orig_length); #endif write_error |= vstream_fclose(dst); /* * Return the optional verbose error description. */ #define TRY_AGAIN_ERROR(errno) \ (errno == EAGAIN || errno == ESTALE) if (why && read_error) dsb_unix(why, TRY_AGAIN_ERROR(errno) ? "4.3.0" : "5.3.0", sys_exits_detail(EX_IOERR)->text, "error reading message: %m"); if (why && write_error) dsb_unix(why, mbox_dsn(errno, "5.3.0"), sys_exits_detail(EX_IOERR)->text, "error writing message: %m"); /* * Use flag+errno description when the optional verbose description is * not desired. */ return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) | (read_error ? MAIL_COPY_STAT_READ : 0) | (write_error ? MAIL_COPY_STAT_WRITE : 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); }
static void pcf_print_master_field(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp, int field) { char **argv = masterp->argv->argv; const char *arg; const char *aval; int arg_len; int line_len; int in_daemon_options; int need_parens; /* * Show the field value, or the first value in the case of a multi-column * field. */ #define ADD_CHAR(ch) ADD_TEXT((ch), 1) line_len = 0; if ((mode & PCF_HIDE_NAME) == 0) { ADD_TEXT(argv[0], strlen(argv[0])); ADD_CHAR(PCF_NAMESP_SEP_STR); ADD_TEXT(argv[1], strlen(argv[1])); ADD_CHAR(PCF_NAMESP_SEP_STR); ADD_TEXT(pcf_str_field_pattern(field), strlen(pcf_str_field_pattern(field))); } if ((mode & (PCF_HIDE_NAME | PCF_HIDE_VALUE)) == 0) { ADD_TEXT(" = ", 3); } if ((mode & PCF_HIDE_VALUE) == 0) { if (line_len > 0 && line_len + strlen(argv[field]) > PCF_LINE_LIMIT) { vstream_fputs("\n" PCF_INDENT_TEXT, fp); line_len = PCF_INDENT_LEN; } ADD_TEXT(argv[field], strlen(argv[field])); } /* * Format the daemon command-line options and non-option arguments. Here, * we have no data-dependent preference for column positions, but we do * have argument grouping preferences. */ if (field == PCF_MASTER_FLD_CMD && (mode & PCF_HIDE_VALUE) == 0) { in_daemon_options = 1; for (field += 1; (arg = argv[field]) != 0; field++) { arg_len = strlen(arg); aval = 0; need_parens = 0; if (in_daemon_options) { /* * We make no special case for generic options (-v -D) * options. */ if (arg[0] != '-' || strcmp(arg, "--") == 0) { in_daemon_options = 0; } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 && (aval = argv[field + 1]) != 0) { /* Force line break before option with value. */ line_len = PCF_LINE_LIMIT; /* * Optionally, expand $name in parameter value. */ if (strcmp(arg, "-o") == 0 && (mode & PCF_SHOW_EVAL) != 0) aval = pcf_expand_parameter_value((VSTRING *) 0, mode, aval, masterp); /* * Keep option and value on the same line. */ arg_len += strlen(aval) + 1; if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) arg_len += 2; } } else { need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; } /* * Insert a line break when the next item won't fit. */ if (line_len > PCF_INDENT_LEN) { if ((mode & PCF_FOLD_LINE) == 0 || line_len + 1 + arg_len < PCF_LINE_LIMIT) { ADD_SPACE; } else { vstream_fputs("\n" PCF_INDENT_TEXT, fp); line_len = PCF_INDENT_LEN; } } if (in_daemon_options == 0 && need_parens) ADD_TEXT("{", 1); ADD_TEXT(arg, strlen(arg)); if (in_daemon_options == 0 && need_parens) ADD_TEXT("}", 1); if (aval) { ADD_SPACE; if (need_parens) ADD_TEXT("{", 1); ADD_TEXT(aval, strlen(aval)); if (need_parens) ADD_TEXT("}", 1); field += 1; /* Force line break after option with value. */ line_len = PCF_LINE_LIMIT; } } } vstream_fputs("\n", fp); if (msg_verbose) vstream_fflush(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); }