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);
}
Exemple #2
0
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);
}
Exemple #3
0
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);
}
Exemple #4
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);
    }
}
Exemple #5
0
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);
}
Exemple #6
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);
    }
}
Exemple #7
0
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);
    }
}
Exemple #8
0
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);
}
Exemple #9
0
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);
}
Exemple #10
0
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);
}
Exemple #12
0
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));
}
Exemple #13
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);
}
Exemple #14
0
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);
}
Exemple #15
0
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);
}