On Fri, Apr 09, 2010 at 06:36:10AM -0500, Mike Abbott wrote:

> Attached please find a patch that adds support to postfix-2.7.0 for RFC
> 4468 - Submission BURL.

> --- postfix-2.7.0/src/global/ehlo_mask.c      2008-01-08 14:36:13.000000000 
> -0600
> --- postfix-2.7.0/src/global/ehlo_mask.h      2005-05-17 12:41:50.000000000 
> -0500

OK

> --- postfix-2.7.0/src/global/mail_params.h    2010-01-17 14:54:35.000000000 
> -0600
> +/* APPLE - burl */
> +#define VAR_IMAP_SUBMIT_CRED_FILE            "imap_submit_cred_file"
> +#define DEF_IMAP_SUBMIT_CRED_FILE            ""
> +#if defined(USE_SASL_AUTH) && defined(USE_TLS)
> +extern char *var_imap_submit_cred_file;
> +#endif

This should be a lookup table, not a plain file. Barring a compelling
reason to the contrary, fields should be white-space separated. It would
not be too tragic if white-space in passwords were not supported, but
if desired, that field can go last, and the parser can stop looking for
white-space once the password is reached.

The "extern" declaration should probably not be #ifdefed.

> --- postfix-2.7.0/src/smtpd/Makefile.in       2009-11-03 18:32:46.000000000 
> -0600

>       smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c \
> -     smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c
> +     smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c \
> +     smtpd_imap.c imap-url.c

C-source modules that extend "smtpd" (and only "smtpd", otherwise they
go into "global/") should have names that start with "smtpd_". So this
one should be "smtpd_imap_url.c".

> +     smtpd_resolve.h imap-url.h

Likewise "<smtpd_imap_url.h>"

> --- postfix-2.7.0/src/smtpd/imap-url.c        1969-12-31 18:00:00.000000000 
> -0600
> +++ postfix-2.7.0+burl/src/smtpd/imap-url.c   2010-03-08 11:05:06.000000000 
> -0600
> @@ -0,0 +1,615 @@
> +/*
> + * Copyright (c) 2010 Apple Inc. All rights reserved.
> + * 
> + * Redistribution and use in source and binary forms, with or without  
> + * modification, are permitted provided that the following conditions  
> + * are met:
> + * 1.  Redistributions of source code must retain the above copyright  
> + * notice, this list of conditions and the following disclaimer.
> [...]

I am not a lawyer, but it it not obvious that the license terms are
compatible with the Postfix license. You can and should assert Apple
copyright, but I think you need to release the code under the Postfix
(not the proposed 3-clause BSD-like) license. Wietse?

> +#define      LOWALPHA        "abcdefghijklmnopqrstuvwxyz"    /* RFC 1738 */
> +#define      HIALPHA         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"    /* RFC 1738 */
> +#define      ALPHA           LOWALPHA""HIALPHA               /* RFC 1738 */
> +#define      DIGIT           "0123456789"                    /* RFC 1738 */
> +#define      SAFE            "$-_.+"                         /* RFC 1738 */
> +#define      EXTRA           "!*'(),"                        /* RFC 1738 */
> +#define      UNRESERVED      ALPHA""DIGIT""SAFE""EXTRA       /* RFC 1738 */
> +#define      ESCAPE          "%"                             /* RFC 1738 */
> +#define      UCHAR           UNRESERVED""ESCAPE              /* RFC 1738 */
> +#define      ACHAR           UCHAR"&=~"                      /* RFC 2192 */
> +#define      BCHAR           ACHAR":@/"                      /* RFC 2192 */
> +#define      HEXDIG          DIGIT"ABCDEFabcdef"             /* RFC 2234 */
> +#define      HOST_CHARS      ALPHA".:-"DIGIT                 /* RFC 1738 */
> +#define      DATETIME_CHARS  DIGIT".+-:TZ"                   /* RFC 3339 */

This looks ugly, Postfix already has a _UCHAR_ macro that is too similar
to your UCHAR, and runs with a clean environment that specifies LANG=C,
so using <ctype.h> is IMHO a lot cleaner than all these macros that
duplicate ctype.h functionality. One or special lists of metacharacters
would be fine, but this seems excessive.

> +// parse an RFC 2192+4467 URL into its parts
> +void imap_url_parse(const char *url, struct imap_url_parts *parts)

Postfix code must compile with ANSI-C compilers, not just "gcc". So C++
style comments are not acceptable.

This parser should probably be a state-machine that moves forward along
the input string, with state functions that parse an element, and return
the next state (function pointer). The syntax validation of each element
can then co-reside with the parser for that element.

> +bool imap_url_quoted_validate(const char *s)
> +{
> +     const unsigned char *cp;
> +
> +     if (*s != '"')
> +             return FALSE;
> +
> +     init_allowed();
> +
> +     for (cp = (unsigned char *) s + 1; *cp; cp++) {
> +             if (*cp == '\\') {
> +                     if (!quoted_specials_allowed[cp[1] & 0xff])
> +                             return FALSE;
> +                     ++cp;

For example, this only barely works, because "quoted_specials_allowed"
is zero for ASCII NUL ('\0'). Otherwise, a terminal "\" would run past
the end of the input string.

> +bool imap_url_hostport_validate(const char *s)

Postfix already has functions for parsing host names and host:port
syntax. You should try to re-use these.

Even if the overall parsing strategy is not changed, this code is fragile
and needs to be simplified. I am not going to dissect the parser further,
I think it needs a rewrite. If someone sees a way to improve it rather
than rewrite it, I am open to suggestions.

> --- postfix-2.7.0/src/smtpd/smtpd.c   2010-02-13 19:50:21.000000000 -0600
> +    in_stream = state->client;

> +#if defined(USE_SASL_AUTH) && defined(USE_TLS)
> +    if (burl) {
> +     url = argv[1].strval;
> +     len = strlen(url);
> +     if (len >= 2 && url[0] == '"' && url[len - 1] == '"')
> +         url = mystrndup(url + 1, len - 2);
> +     in_stream = imap_open(state, url);

The IMAP input stream timeouts should be adjusted to match the SMTP server
timeout, (which may stress dependent, ...). Either pass var_smtpd_timeout
to imap_open, or introduce a new config variable for the imap timeout.

> +         url = NULL;
> +     }
> +     if (in_stream == NULL) {
> +         /* must fail the entire transaction */
> +         chat_reset(state, var_smtpd_hist_thrsh);
> +         mail_reset(state);
> +         rcpt_reset(state);
> +         return -1;
> +     }

Why no response to the client? This puts the SMTP protocol out of
sync, since the client's BURL command gets no response...
Why are we resetting the chat history?

> +     case SMTP_ERR_EOF:
> +         smtpd_chat_reply(state, "554 4.6.6 EOF from IMAP server");
> +         vstream_longjmp(state->client, SMTP_ERR_QUIET);
> +         break;

Why is the DSN code 4.X.X when the SMTP reply code is 5XX? Is this a
permanent or a transient error code? 

> +     case SMTP_ERR_TIME:
> +         smtpd_chat_reply(state, "554 4.6.6 Timeout from IMAP server");
> +         vstream_longjmp(state->client, SMTP_ERR_QUIET);
> +         break;

Ditto.


> +/* APPLE - burl */
> +#if defined(USE_SASL_AUTH) && defined(USE_TLS)
> +    imap_read_config();
> +#endif
>  }

With a cred table, this will not be necessary, but the table will need
to be opened in the pre-jail code.

> --- postfix-2.7.0/src/smtpd/smtpd_imap.c      1969-12-31 18:00:00.000000000 
> -0600

> +static TLS_APPL_STATE *tls_ctx = NULL;
> ...
> +static TLS_SESS_STATE *imap_starttls(VSTREAM *stream,
> +                                  const struct imap_server *is)

The TLS application context for accessing the IMAP servers should be
initialized pre-jail. It may need access to private keys or other
root-only material at some point in the future. At the time that a
particular IMAP server needs to be accessed, you should only be creating
TLS session contexts.

> +     tls_ctx = TLS_CLIENT_INIT(&init_props,
> +                               log_level = 0,
> +                               verifydepth = DEF_SMTP_TLS_SCERT_VD,
> +                                             /* XXX TLS_MGR_SCACHE_IMAP? */

The verification depth needs to be configurable.

        smtpd_imap_tls_... = ...

> +                               cache_type = TLS_MGR_SCACHE_SMTPD,

The cache type is fine.

> +                               cert_file = "",
> +                               key_file = "",
> +                               dcert_file = "",
> +                               dkey_file = "",
> +                               eccert_file = "",
> +                               eckey_file = "",
> +                               CAfile = "",
> +                               CApath = "",

These need to be configurable, and in particular, administrators should
be able to enable certificate checks and non-empty CAfile/CApath. The
former may outside the chroot jail. Hence, pre-jail initialization.

> +                               fpt_dgst = DEF_SMTP_TLS_FPT_DGST);

This should be configurable or default to "sha1" not the legacy "md5",
since there are no backwards-compatibility issues here.

> +    sess_ctx = TLS_CLIENT_START(&start_props,
> +                             ctx = tls_ctx,
> +                             stream = stream,
> +                             log_level = 0,

        smtp_imap_tls_loglevel = ...

> +                             timeout = 30,

                smtpd_imap_timeout = ${stress?10:300}

        pass SMTP server or appropriate other timeout from caller...

> +                             tls_level = TLS_LEV_ENCRYPT,

        Configurable: smtp_imap_tls_security_level = verify

> +                             nexthop = "",
> +                             host = is->hostport,

    Use the imap server name in the URL as both hostname and nexthop,
    remove the the port from the name, this is needed for certificate
    validation.

> +                             namaddr = is->hostport,

    This is fine.

> +                             serverid = is->hostport,

    The serverid is used to identify a remote TLS session cache. With
    SMTP servers behind load-balancers, we learn the server identty
    from the remote EHLO response that precedes STARTTLS.

    So when the IMAP server access method is IMAP + STARTTLS (rather
    than "imaps", the server id should include the hostname from
    the IMAP server banner:

        * OK [CAPABILITY IMAP4 IMAP4rev1 LITERAL+ ID STARTTLS AUTH=LOGIN 
AUTH=GSSAPI SASL-IR] imap.example.com Cyrus IMAP <version> server ready

    It should include the hostport data, in addition to this, since the
    name in the banner is not always reliable, but salting the serverid
    with the remote banner name makes TLS session caching much more
    effective.

> +                             protocols = DEF_SMTP_TLS_MAND_PROTO,

        smtpd_imap_tls_protocols = $smtpd_tls_mandatory_protocols

> +                             cipher_grade = DEF_SMTP_TLS_MAND_CIPH,

        smtpd_imap_tls_ciphers = $smtp_tls_mandatory_ciphers

> +                             cipher_exclusions = "SSLv2, aNULL, ADH, eNULL",

        None of these are needed by default. To make this user configurable:

        smtpd_imap_tls_exclude_ciphers =

> +                             matchargv = NULL,

        The verification policy should be specified in the imap policy
        table entry.

> +                             fpt_dgst = DEF_SMTP_TLS_FPT_DGST);

        The digest algorithm should be "sha1" by default, but should
        be configurable:

            smtpd_imap_tls_fingerprint_digest = sha1


> +VSTREAM *imap_open(SMTPD_STATE *state, const char *url)
> +{
> +         fd = inet_connect(hostport, BLOCKING, 30);

The timeout should not be hard-coded:

        smtpd_imap_connect_timeout = 30

> +             smtp_timeout_setup(stream, 30);

        smtpd_imap_timeout = 300

> +    sess_ctx = NULL;

This should be in SMTPD_STATE, rather than a static variable...

> +    /* negotiate SSL now if applicable (IMAPS) */

> +    if (port == 993) {
> +     sess_ctx = imap_starttls(stream, is);
> +     if (sess_ctx == NULL) {
> +         vstream_fpurge(stream, VSTREAM_PURGE_BOTH);
> +         vstream_longjmp(stream, -1);
> +     }
> +    }

Is it illegal to run "imaps" on ports other than "993", I would expect

        imaps://host:port/...

to work, so the "imaps" code-path should be independent of the port?

> +    /* read server greeting */
> +    if (smtp_get(response, stream, 0) != '\n' || VSTRING_LEN(response) < 4 ||
> +     strncasecmp(vstring_str(response), "* OK", 4) != 0) {
> +     msg_warn("bad greeting from IMAP server %s: %s", is->hostport,
> +              vstring_str(response));
> +     vstream_longjmp(stream, -1);
> +    }

Get the serverid salt here, if we are doing "STARTTLS". Handle certificate
validation policy, by examining the TLS session flag bits to see whether
appropriate security was established. Disconnect gracefully from the
IMAP server if security criteria are not met.

> +    /* determine which authentication mechanism to use; prefer PLAIN */
> +    plain = FALSE;
> +    if (imap_capable_of(stream, is, request, response, "AUTH=PLAIN", FALSE))
> +     plain = TRUE;
> +    else if (!imap_capable_of(stream, is, request, response,
> +                           "AUTH=X-PLAIN-SUBMIT", FALSE)) {
> +     msg_warn("IMAP server %s supports neither "
> +              "AUTH=PLAIN nor AUTH=X-PLAIN-SUBMIT.  can't log in.",
> +              is->hostport);
> +     vstream_longjmp(stream, -1);
> +    }

Should perhaps support "external" if IMAP server offered it, and we have
client certs.

-- 
        Viktor.

P.S. Morgan Stanley is looking for a New York City based, Senior Unix
system/email administrator to architect and sustain our perimeter email
environment.  If you are interested, please drop me a note.

Reply via email to