From ee4ef8eeb6c160c9df7eb177e8d06f94765b47ff Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Mon, 3 Apr 2006 00:00:00 -0500 Subject: [PATCH] postfix-2.3-20060403 --- postfix/HISTORY | 28 ++++++++ postfix/README_FILES/SASL_README | 16 +++-- postfix/html/SASL_README.html | 38 ++++++----- postfix/html/pipe.8.html | 25 +++++-- postfix/html/postconf.5.html | 12 ++++ postfix/man/man5/postconf.5 | 6 ++ postfix/man/man8/pipe.8 | 15 +++- postfix/mantools/postlink | 1 + postfix/proto/SASL_README.html | 14 ++-- postfix/proto/postconf.proto | 8 +++ postfix/src/global/Makefile.in | 2 + postfix/src/global/db_common.c | 110 +++++++++++++++++------------- postfix/src/global/mail_params.h | 2 +- postfix/src/global/mail_version.h | 2 +- postfix/src/global/pipe_command.c | 65 +++++++++++++++--- postfix/src/global/pipe_command.h | 1 + postfix/src/pipe/pipe.c | 25 ++++++- postfix/src/util/myflock.c | 7 +- 18 files changed, 282 insertions(+), 95 deletions(-) diff --git a/postfix/HISTORY b/postfix/HISTORY index 76965c7d1..5f96028e9 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -12064,6 +12064,34 @@ Apologies for any names omitted. test. A filter timeout was mis-reported as lost connection. Found in code review. File: smtpd/smtpd_proxy.c. +20060327 + + Cleanup: the SQL and LDAP clients now log a warning when + they skip an empty lookup result, so that humans don't have + to wonder why Postfix doesn't find all the database entries. + File: global/db_common.c. + +20060328 + + Feature: configurable chroot directive for the pipe(8) + delivery agent, by Przemyslaw Wegrzyn. Files: + global/pipe_command.c, pipe/pipe.c. + + Bugfix: cut-and-paste error: lmtp_connection_cache_limit + was left with the name of smtp_connection_cache_limit. + Reported by Victor? File: src/global/mail_params.h. + +20060403 + + Cleanup: made fcntl/flock handling consistent with respect + to EINTR (reported by Carlo Contavalli). However, Postfix + is not meant to be signal safe. Only the master daemon + handles signals without terminating, and it uses only a + small subset of Postfix library routines. File: util/myflock.c. + + Bugfix: the pipe-to-command error message was lost when the + command could not be executed. File: global/pipe_command.c. + Wish list: Don't send xforward attributes to every site that announces diff --git a/postfix/README_FILES/SASL_README b/postfix/README_FILES/SASL_README index 21f0f0de6..e3c8d7cbb 100644 --- a/postfix/README_FILES/SASL_README +++ b/postfix/README_FILES/SASL_README @@ -181,11 +181,17 @@ that the Postfix queue is under /var/spool/postfix/. /some/where/dovecot.conf: auth default { - .. + mechanisms = plain login + passdb pam { + } + userdb passwd { + } socket listen { client { path = /var/spool/postfix/private/auth - mode = 0666 + mode = 0660 + user = postfix + group = postfix } } } @@ -447,7 +453,7 @@ CCrreeddiittss reject_authenticated_sender_login_mismatch and reject_unauthenticated_sender_login_mismatch, and revised the docs. * Wietse made another iteration through the code to add plug-in support for - multiple implementations. - * The Dovecot SMTP server plug-in was originally implemented by Timo Sirainen - of Procontrol, Finland. + multiple SASL implementations. + * The Dovecot SMTP server-only plug-in was originally implemented by Timo + Sirainen of Procontrol, Finland. diff --git a/postfix/html/SASL_README.html b/postfix/html/SASL_README.html index fb2cf5325..3c1825b2d 100644 --- a/postfix/html/SASL_README.html +++ b/postfix/html/SASL_README.html @@ -140,7 +140,7 @@ in the Postfix top-level directory:

  • The "-DDEF_SASL_SERVER" stuff is not necessary; it just makes Postfix configuration a little more convenient because you -don't have to specify the SASL plug-in type in the Postfix main.cf +don't have to specify the SASL plug-in type in the Postfix main.cf file.

  • If you also want support for LDAP or TLS, you will have to merge @@ -229,7 +229,7 @@ SMTP server

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_sasl_auth_enable = yes
     
    @@ -238,7 +238,7 @@ SMTP server
    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_recipient_restrictions = 
             permit_mynetworks permit_sasl_authenticated ...
     
    @@ -249,7 +249,7 @@ SMTP server
    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_sasl_authenticated_header = yes
     
    @@ -265,7 +265,7 @@ clients) use the following:

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         broken_sasl_auth_clients = yes
     
    @@ -281,7 +281,7 @@ Postfix runs chrooted:

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_sasl_type = dovecot
         smtpd_sasl_path = private/auth
     
    @@ -296,11 +296,17 @@ Postfix queue is under /var/spool/postfix/.

     /some/where/dovecot.conf:
         auth default {
    -      ..
    +      mechanisms = plain login
    +      passdb pam {
    +      }
    +      userdb passwd {
    +      }
           socket listen {
             client {
               path = /var/spool/postfix/private/auth
    -          mode = 0666
    +          mode = 0660
    +          user = postfix
    +          group = postfix
             }
           }
         }
    @@ -352,7 +358,7 @@ library for configuration can be set with: 

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_sasl_application_name = smtpd
     
    @@ -449,7 +455,7 @@ realm used by smtpd:

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtpd_sasl_local_domain = $myhostname
     
    @@ -593,7 +599,7 @@ table.

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtp_sasl_auth_enable = yes
         smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
         smtp_sasl_type = cyrus
    @@ -612,7 +618,7 @@ before it searches by destination, specify: 

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtp_sender_dependent_authentication = yes
         smtp_sasl_auth_enable = yes
         smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
    @@ -634,7 +640,7 @@ for example: 

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtp_sasl_security_options = noanonymous
     
    @@ -652,7 +658,7 @@ into consideration:

    -/etc/postfix/main.cf:
    +/etc/postfix/main.cf:
         smtp_sasl_mechanism_filter = !gssapi, !external, static:all
     
    @@ -682,9 +688,9 @@ of SuSE Rhein/Main AG. reject_unauthenticated_sender_login_mismatch, and revised the docs.
  • Wietse made another iteration through the code to add -plug-in support for multiple implementations. +plug-in support for multiple SASL implementations. -
  • The Dovecot SMTP server plug-in was originally implemented by +
  • The Dovecot SMTP server-only plug-in was originally implemented by Timo Sirainen of Procontrol, Finland. diff --git a/postfix/html/pipe.8.html b/postfix/html/pipe.8.html index 8eda4d5f3..893e76525 100644 --- a/postfix/html/pipe.8.html +++ b/postfix/html/pipe.8.html @@ -48,9 +48,22 @@ PIPE(8) PIPE(8) file at the end of a service definition. The syntax is as follows: - directory=pathname (optional, default: $queue_directory) - Change to the named directory before executing the - external command. Delivery is deferred in case of + chroot=pathname (optional) + Change the process root directory and working + directory to the named directory. This happens + before switching to the privileges specified with + the user attribute, and before executing the + optional directory=pathname directive. Delivery is + deferred in case of failure. + + This feature is available as of Postfix 2.3. + + directory=pathname (optional) + Change to the named directory before executing the + external command. The directory must be accessible + for the user specified with the user attribute (see + below). The default working directory is + $queue_directory. Delivery is deferred in case of failure. This feature is available as of Postfix 2.2. @@ -162,9 +175,9 @@ PIPE(8) PIPE(8) user=username (required) user=username:groupname - The external command is executed with the rights of - the specified username. The software refuses to - execute commands with root privileges, or with the + Execute the external command with the rights of the + specified username. The software refuses to exe- + cute commands with root privileges, or with the privileges of the mail system owner. If groupname is specified, the corresponding group ID is used instead of the group ID of username. diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 59557d39f..43f902d82 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -3154,6 +3154,18 @@ configuration parameter. See there for details.

    This feature is available in Postfix 2.3 and later.

    + + +
    lmtp_connection_cache_time_limit +(default: 2s)
    + +

    The LMTP-specific version of the +smtp_connection_cache_time_limit configuration parameter. +See there for details.

    + +

    This feature is available in Postfix 2.3 and later.

    + +
    lmtp_connection_reuse_time_limit diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index cfec35d0c..16f0323fd 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -1705,6 +1705,12 @@ The LMTP-specific version of the smtp_connection_cache_on_demand configuration parameter. See there for details. .PP This feature is available in Postfix 2.3 and later. +.SH lmtp_connection_cache_time_limit (default: 2s) +The LMTP-specific version of the +smtp_connection_cache_time_limit configuration parameter. +See there for details. +.PP +This feature is available in Postfix 2.3 and later. .SH lmtp_connection_reuse_time_limit (default: 300s) The LMTP-specific version of the smtp_connection_reuse_time_limit configuration parameter. See there for details. diff --git a/postfix/man/man8/pipe.8 b/postfix/man/man8/pipe.8 index c3f653824..40ccc713d 100644 --- a/postfix/man/man8/pipe.8 +++ b/postfix/man/man8/pipe.8 @@ -51,8 +51,19 @@ entry for the pipe-based delivery transport. .fi The external command attributes are given in the \fBmaster.cf\fR file at the end of a service definition. The syntax is as follows: -.IP "\fBdirectory=\fIpathname\fR (optional, default: \fB$queue_directory\fR)" +.IP "\fBchroot=\fIpathname\fR (optional)" +Change the process root directory and working directory to +the named directory. This happens before switching to the +privileges specified with the \fBuser\fR attribute, and +before executing the optional \fBdirectory=\fIpathname\fR +directive. Delivery is deferred in case of failure. +.sp +This feature is available as of Postfix 2.3. +.IP "\fBdirectory=\fIpathname\fR (optional)" Change to the named directory before executing the external command. +The directory must be accessible for the user specified with the +\fBuser\fR attribute (see below). +The default working directory is \fB$queue_directory\fR. Delivery is deferred in case of failure. .sp This feature is available as of Postfix 2.2. @@ -148,7 +159,7 @@ Messages greater in size than this limit (in bytes) will be returned to the sender as undeliverable. .IP "\fBuser\fR=\fIusername\fR (required)" .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" -The external command is executed with the rights of the +Execute the external command with the rights of the specified \fIusername\fR. The software refuses to execute commands with root privileges, or with the privileges of the mail system owner. If \fIgroupname\fR is specified, the diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index cb8c84173..b2c9e70ba 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -201,6 +201,7 @@ while (<>) { s;\blmtp_sasl_mechanism_filter\b;$&;g; s;\blmtp_host_lookup\b;$&;g; s;\blmtp_connection_cache_destinations\b;$&;g; + s;\blmtp_connection_cache_time_limit\b;$&;g; s;\blmtp_tls_per_site\b;$&;g; s;\blmtp_generic_maps\b;$&;g; s;\blmtp_pix_workaround_threshold_time\b;$&;g; diff --git a/postfix/proto/SASL_README.html b/postfix/proto/SASL_README.html index 591b61b0b..29ae2843c 100644 --- a/postfix/proto/SASL_README.html +++ b/postfix/proto/SASL_README.html @@ -296,11 +296,17 @@ Postfix queue is under /var/spool/postfix/.

     /some/where/dovecot.conf:
         auth default {
    -      ..
    +      mechanisms = plain login
    +      passdb pam {
    +      }
    +      userdb passwd {
    +      }
           socket listen {
             client {
               path = /var/spool/postfix/private/auth
    -          mode = 0666
    +          mode = 0660
    +          user = postfix
    +          group = postfix
             }
           }
         }
    @@ -682,9 +688,9 @@ reject_authenticated_sender_login_mismatch and
     reject_unauthenticated_sender_login_mismatch, and revised the docs.
     
     
  • Wietse made another iteration through the code to add -plug-in support for multiple implementations. +plug-in support for multiple SASL implementations. -
  • The Dovecot SMTP server plug-in was originally implemented by +
  • The Dovecot SMTP server-only plug-in was originally implemented by Timo Sirainen of Procontrol, Finland. diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index fcf27e1c8..4f5a2433e 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -9243,6 +9243,14 @@ See there for details.

    This feature is available in Postfix 2.3 and later.

    +%PARAM lmtp_connection_cache_time_limit 2s + +

    The LMTP-specific version of the +smtp_connection_cache_time_limit configuration parameter. +See there for details.

    + +

    This feature is available in Postfix 2.3 and later.

    + %PARAM smtpd_delay_open_until_valid_rcpt yes

    Postpone the start of an SMTP mail transaction until a valid diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 301412654..9b93f878e 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -1415,10 +1415,12 @@ own_inet_addr.o: mail_params.h own_inet_addr.o: own_inet_addr.c own_inet_addr.o: own_inet_addr.h pipe_command.o: ../../include/argv.h +pipe_command.o: ../../include/chroot_uid.h pipe_command.o: ../../include/clean_env.h pipe_command.o: ../../include/exec_command.h pipe_command.o: ../../include/iostuff.h pipe_command.o: ../../include/msg.h +pipe_command.o: ../../include/msg_vstream.h pipe_command.o: ../../include/set_eugid.h pipe_command.o: ../../include/set_ugid.h pipe_command.o: ../../include/stringops.h diff --git a/postfix/src/global/db_common.c b/postfix/src/global/db_common.c index 183f0db0f..811dd847c 100644 --- a/postfix/src/global/db_common.c +++ b/postfix/src/global/db_common.c @@ -278,7 +278,7 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, db_quote_callback_t quote_func) { char *myname = "db_common_expand"; - DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxArg; + DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg; const char *vdomain = 0; const char *kdomain = 0; char *vuser = 0; @@ -287,18 +287,28 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, int i; const char *cp; - /* Skip NULL or empty values */ - if (value == 0 || *value == 0) - return (0); + /* Skip NULL values, silently. */ + if (value == 0) + return (0); + /* Don't silenty skip empty query string or empty lookup results. */ + if (*value == 0) { + if (key) + msg_warn("table \"%s:%s\": empty lookup result for: \"%s\"" + " -- ignored", ctx->dict->type, ctx->dict->name, key); + else + msg_warn("table \"%s:%s\": empty query string" + " -- ignored", ctx->dict->type, ctx->dict->name); + return (0); + } if (key) { - /* This is a result template and the input value is the result */ + /* This is a result template and the input value is the result */ if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER)) if ((vdomain = strrchr(value, '@')) != 0) ++vdomain; - if ((!vdomain || !*vdomain) && (ctx->flags&DB_COMMON_VALUE_DOMAIN) != 0 - || vdomain == value + 1 && (ctx->flags&DB_COMMON_VALUE_USER) != 0) + if ((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0 + || vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0) return (0); /* The result format may use the local or domain part of the key */ @@ -307,50 +317,51 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, ++kdomain; /* - * The key should already be checked before the query. No harm if - * the query did not get optimized out, so we just issue a warning. + * The key should already be checked before the query. No harm if the + * query did not get optimized out, so we just issue a warning. */ - if ((!kdomain || !*kdomain) && (ctx->flags&DB_COMMON_KEY_DOMAIN) != 0 - || kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0) { + if ((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0 + || kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0) { msg_warn("%s: %s: lookup key '%s' skipped after query", myname, - ctx->dict->name, value); + ctx->dict->name, value); return (0); } } else { - /* This is a query template and the input value is the lookup key */ + /* This is a query template and the input value is the lookup key */ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER)) if ((vdomain = strrchr(value, '@')) != 0) ++vdomain; - if ((!vdomain || !*vdomain) && (ctx->flags&DB_COMMON_KEY_DOMAIN) != 0 - || vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0) + if ((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0 + || vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0) return (0); } if (ctx->nparts > 0) { - parts = argv_split(key ? kdomain : vdomain, "."); + parts = argv_split(key ? kdomain : vdomain, "."); + /* - * Filter out input keys whose domains lack enough labels - * to fill-in the query template. See below and also - * db_common_parse() which initializes ctx->nparts. + * Filter out input keys whose domains lack enough labels to fill-in + * the query template. See below and also db_common_parse() which + * initializes ctx->nparts. */ if (parts->argc < ctx->nparts) { argv_free(parts); return (0); } + /* - * Skip domains with leading, consecutive or trailing '.' - * separators among the required labels. + * Skip domains with leading, consecutive or trailing '.' separators + * among the required labels. */ for (i = 0; i < ctx->nparts; i++) - if (*parts->argv[parts->argc-i-1] == 0) { + if (*parts->argv[parts->argc - i - 1] == 0) { argv_free(parts); return (0); } } - if (VSTRING_LEN(result) > 0) - VSTRING_ADDCH(result, ','); + VSTRING_ADDCH(result, ','); #define QUOTE_VAL(d, q, v, buf) do { \ if (q) \ @@ -360,9 +371,9 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, } while (0) /* - * Replace all instances of %s with the address to look up. Replace - * %u with the user portion, and %d with the domain portion. "%%" - * expands to "%". lowercase -> addr, uppercase -> key + * Replace all instances of %s with the address to look up. Replace %u + * with the user portion, and %d with the domain portion. "%%" expands to + * "%". lowercase -> addr, uppercase -> key */ for (cp = format; *cp; cp++) { if (*cp == '%') { @@ -381,8 +392,7 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, if (vuser == 0) vuser = mystrndup(value, vdomain - value - 1); QUOTE_VAL(ctx->dict, quote_func, vuser, result); - } - else + } else QUOTE_VAL(ctx->dict, quote_func, value, result); break; @@ -391,7 +401,7 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, break; case 'S': - if (key) + if (key) QUOTE_VAL(ctx->dict, quote_func, key, result); else QUOTE_VAL(ctx->dict, quote_func, value, result); @@ -403,16 +413,14 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, if (kuser == 0) kuser = mystrndup(key, kdomain - key - 1); QUOTE_VAL(ctx->dict, quote_func, kuser, result); - } - else + } else QUOTE_VAL(ctx->dict, quote_func, key, result); } else { if (vdomain) { if (vuser == 0) vuser = mystrndup(value, vdomain - value - 1); QUOTE_VAL(ctx->dict, quote_func, vuser, result); - } - else + } else QUOTE_VAL(ctx->dict, quote_func, value, result); } break; @@ -424,18 +432,26 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, QUOTE_VAL(ctx->dict, quote_func, vdomain, result); break; - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - /* - * Interpolate %[1-9] components into the query string. - * By this point db_common_parse() has identified the - * highest component index, and (see above) keys with - * fewer components have been filtered out. The "parts" - * ARGV is guaranteed to be initialized and hold enough - * elements to satisfy the query template. + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + + /* + * Interpolate %[1-9] components into the query string. By + * this point db_common_parse() has identified the highest + * component index, and (see above) keys with fewer + * components have been filtered out. The "parts" ARGV is + * guaranteed to be initialized and hold enough elements to + * satisfy the query template. */ QUOTE_VAL(ctx->dict, quote_func, - parts->argv[parts->argc-(*cp-'0')], result); + parts->argv[parts->argc - (*cp - '0')], result); break; default: @@ -449,11 +465,11 @@ int db_common_expand(void *ctxArg, const char *format, const char *value, VSTRING_TERMINATE(result); if (vuser) - myfree(vuser); + myfree(vuser); if (kuser) - myfree(kuser); + myfree(kuser); if (parts) - argv_free(parts); + argv_free(parts); return (1); } diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 613ce47c9..0f6695db2 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -870,7 +870,7 @@ extern char *var_bestmx_transp; #define VAR_SMTP_CACHE_CONNT "smtp_connection_cache_time_limit" #define DEF_SMTP_CACHE_CONNT "2s" -#define VAR_LMTP_CACHE_CONNT "smtp_connection_cache_time_limit" +#define VAR_LMTP_CACHE_CONNT "lmtp_connection_cache_time_limit" #define DEF_LMTP_CACHE_CONNT "2s" extern int var_smtp_cache_conn; diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 45414bc44..fc5b98bc3 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20060325" +#define MAIL_RELEASE_DATE "20060403" #define MAIL_VERSION_NUMBER "2.3" #ifdef SNAPSHOT diff --git a/postfix/src/global/pipe_command.c b/postfix/src/global/pipe_command.c index 8939ba2bb..2ccbd2528 100644 --- a/postfix/src/global/pipe_command.c +++ b/postfix/src/global/pipe_command.c @@ -44,8 +44,14 @@ /* The command is specified as an argument vector. This vector is /* passed without further inspection to the \fIexecvp\fR() routine. /* One of PIPE_CMD_COMMAND or PIPE_CMD_ARGV must be specified. +/* .IP "PIPE_CMD_CHROOT (char *)" +/* Root and working directory for command execution. This takes +/* effect before PIPE_CMD_CWD. A null pointer means don't +/* change root and working directory anyway. Failure to change +/* directory causes mail delivery to be deferred. /* .IP "PIPE_CMD_CWD (char *)" -/* Working directory for command execution. A null pointer means +/* Working directory for command execution, after changing process +/* privileges to PIPE_CMD_UID and PIPE_CMD_GID. A null pointer means /* don't change directory anyway. Failure to change directory /* causes mail delivery to be deferred. /* .IP "PIPE_CMD_ENV (char **)" @@ -137,6 +143,7 @@ #include #include +#include #include #include #include @@ -144,6 +151,7 @@ #include #include #include +#include /* Global library. */ @@ -172,6 +180,7 @@ struct pipe_args { char **export; /* exportable environment */ char *shell; /* command shell */ char *cwd; /* preferred working directory */ + char *chroot; /* root directory */ }; static int pipe_command_timeout; /* command has timed out */ @@ -200,6 +209,7 @@ static void get_pipe_args(struct pipe_args * args, va_list ap) args->export = 0; args->shell = 0; args->cwd = 0; + args->chroot = 0; pipe_command_maxtime = var_command_maxtime; @@ -254,6 +264,9 @@ static void get_pipe_args(struct pipe_args * args, va_list ap) case PIPE_CMD_CWD: args->cwd = va_arg(ap, char *); break; + case PIPE_CMD_CHROOT: + args->chroot = va_arg(ap, char *); + break; default: msg_panic("%s: unknown key: %d", myname, key); } @@ -357,14 +370,22 @@ static int pipe_command_wait_or_kill(pid_t pid, WAIT_STATUS_T *statusp, int sig, static void pipe_child_cleanup(void) { - exit(EX_TEMPFAIL); + + /* + * WARNING: don't place code here. This code may run as mail_owner, as + * root, or as the user/group specified with the "user" attribute. The + * only safe action is to terminate. + * + * Future proofing. If you need exit() here then you broke Postfix. + */ + _exit(EX_TEMPFAIL); } /* pipe_command - execute command with extreme prejudice */ int pipe_command(VSTREAM *src, DSN_BUF *why,...) { - char *myname = "pipe_comand"; + char *myname = "pipe_command"; va_list ap; VSTREAM *cmd_in_stream; VSTREAM *cmd_out_stream; @@ -448,6 +469,28 @@ int pipe_command(VSTREAM *src, DSN_BUF *why,...) */ case 0: (void) msg_cleanup(pipe_child_cleanup); + + /* + * In order to chroot it is necessary to switch euid back to root. + * Right after chroot we call set_ugid() so all privileges will be + * dropped again. + * + * XXX For consistency we use chroot_uid() to change root+current + * directory. However, we must not use chroot_uid() to change process + * privileges (assuming a version that accepts numeric privileges). + * That would create a maintenance problem, because we would have two + * different code paths to set the external command's privileges. + */ + if (args.chroot) { + seteuid(0); + chroot_uid(args.chroot, (char *) 0); + } + + /* + * XXX If we put code before the set_ugid() call, then the code that + * changes root directory must switch back to the mail_owner UID, + * otherwise we'd be running with root privileges. + */ set_ugid(args.uid, args.gid); if (setsid() < 0) msg_warn("setsid failed: %m"); @@ -488,12 +531,15 @@ int pipe_command(VSTREAM *src, DSN_BUF *why,...) /* * Process plumbing. If possible, avoid running a shell. * - * From this point we would like to handle fatal errors ourselves - * (ENOMEM would probably be one of the few soft error conditions). - * For that we have to update exec_command() first so it returns an - * error indication instead of terminating the process. + * As a safety for buggy libraries, we close the syslog socket. + * Otherwise we could leak a file descriptor that was created by a + * privileged process. + * + * XXX To avoid losing fatal error messages we open a VSTREAM and + * capture the output in the parent process. */ closelog(); + msg_vstream_init(var_procname, VSTREAM_ERR); if (args.argv) { execvp(args.argv[0], args.argv); msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); @@ -605,7 +651,10 @@ int pipe_command(VSTREAM *src, DSN_BUF *why,...) return (sp->dsn[0] == '4' ? PIPE_STAT_DEFER : PIPE_STAT_BOUNCE); } - /* No "D.S.N text" or compatible status. Fake it. */ + + /* + * No "D.S.N text" or compatible status. Fake it. + */ else { sp = sys_exits_detail(WEXITSTATUS(wait_status)); dsb_unix(why, sp->dsn, diff --git a/postfix/src/global/pipe_command.h b/postfix/src/global/pipe_command.h index 694adb19d..339ac5ec0 100644 --- a/postfix/src/global/pipe_command.h +++ b/postfix/src/global/pipe_command.h @@ -41,6 +41,7 @@ #define PIPE_CMD_EXPORT 12 /* exportable environment */ #define PIPE_CMD_ORIG_RCPT 13 /* mail_copy() original recipient */ #define PIPE_CMD_CWD 14 /* working directory */ +#define PIPE_CMD_CHROOT 15 /* chroot() before exec() */ /* * Command completion status. diff --git a/postfix/src/pipe/pipe.c b/postfix/src/pipe/pipe.c index d8670c97a..b35b689fe 100644 --- a/postfix/src/pipe/pipe.c +++ b/postfix/src/pipe/pipe.c @@ -41,8 +41,19 @@ /* .fi /* The external command attributes are given in the \fBmaster.cf\fR /* file at the end of a service definition. The syntax is as follows: -/* .IP "\fBdirectory=\fIpathname\fR (optional, default: \fB$queue_directory\fR)" +/* .IP "\fBchroot=\fIpathname\fR (optional)" +/* Change the process root directory and working directory to +/* the named directory. This happens before switching to the +/* privileges specified with the \fBuser\fR attribute, and +/* before executing the optional \fBdirectory=\fIpathname\fR +/* directive. Delivery is deferred in case of failure. +/* .sp +/* This feature is available as of Postfix 2.3. +/* .IP "\fBdirectory=\fIpathname\fR (optional)" /* Change to the named directory before executing the external command. +/* The directory must be accessible for the user specified with the +/* \fBuser\fR attribute (see below). +/* The default working directory is \fB$queue_directory\fR. /* Delivery is deferred in case of failure. /* .sp /* This feature is available as of Postfix 2.2. @@ -138,7 +149,7 @@ /* be returned to the sender as undeliverable. /* .IP "\fBuser\fR=\fIusername\fR (required)" /* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" -/* The external command is executed with the rights of the +/* Execute the external command with the rights of the /* specified \fIusername\fR. The software refuses to execute /* commands with root privileges, or with the privileges of the /* mail system owner. If \fIgroupname\fR is specified, the @@ -474,6 +485,7 @@ typedef struct { gid_t gid; /* command privileges */ int flags; /* mail_copy() flags */ char *exec_dir; /* working directory */ + char *chroot_dir; /* chroot directory */ VSTRING *eol; /* output record delimiter */ VSTRING *null_sender; /* null sender expansion */ off_t size_limit; /* max size in bytes we will accept */ @@ -728,6 +740,7 @@ static void get_service_attr(PIPE_ATTR *attr, char **argv) attr->command = 0; attr->flags = 0; attr->exec_dir = 0; + attr->chroot_dir = 0; attr->eol = vstring_strcpy(vstring_alloc(1), "\n"); attr->null_sender = vstring_strcpy(vstring_alloc(1), MAIL_ADDR_MAIL_DAEMON); attr->size_limit = 0; @@ -807,6 +820,13 @@ static void get_service_attr(PIPE_ATTR *attr, char **argv) attr->exec_dir = mystrdup(*argv + sizeof("directory=") - 1); } + /* + * chroot=string + */ + else if (strncasecmp("chroot=", *argv, sizeof("chroot=") - 1) == 0) { + attr->chroot_dir = mystrdup(*argv + sizeof("chroot=") - 1); + } + /* * eol=string */ @@ -1105,6 +1125,7 @@ static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) PIPE_CMD_EOL, STR(attr.eol), PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_CWD, attr.exec_dir, + PIPE_CMD_CHROOT, attr.chroot_dir, PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr, PIPE_CMD_DELIVERED, rcpt_list->info[0].address, PIPE_CMD_END); diff --git a/postfix/src/util/myflock.c b/postfix/src/util/myflock.c index 65814b255..610f72ac5 100644 --- a/postfix/src/util/myflock.c +++ b/postfix/src/util/myflock.c @@ -107,7 +107,9 @@ int myflock(int fd, int lock_style, int operation) -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1 }; - status = flock(fd, lock_ops[operation]); + while ((status = flock(fd, lock_ops[operation])) < 0 + && errno == EINTR) + sleep(1); break; } #endif @@ -129,8 +131,7 @@ int myflock(int fd, int lock_style, int operation) lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT]; request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW; while ((status = fcntl(fd, request, &lock)) < 0 - && request == F_SETLKW - && (errno == EINTR || errno == ENOLCK || errno == EDEADLK)) + && errno == EINTR) sleep(1); break; }