From 221e4bda865f30ce604386b5266c564e4cd5d95e Mon Sep 17 00:00:00 2001 From: Matthias Andree Date: Mon, 14 Aug 2006 23:27:23 +0000 Subject: Add IMAP AUTH=EXTERNAL support. BerliOS Patch #1095. Courtesy of Götz 'nimrill' Babin-Ebell. This patch also makes --sslproto arguments case insensitive. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn path=/branches/BRANCH_6-3/; revision=4896 --- NEWS | 4 ++++ TODO.txt | 1 - driver.c | 2 +- fetchmail.h | 4 ++++ fetchmail.man | 35 +++++++++++++++++++++++++---------- imap.c | 38 ++++++++++++++++++++++++++++++++++++-- options.c | 4 +++- pop3.c | 4 ++-- rcfile_l.l | 1 + socket.c | 48 +++++++++++++++++++++++++++++++++++++++++++----- socket.h | 2 +- 11 files changed, 120 insertions(+), 23 deletions(-) diff --git a/NEWS b/NEWS index 48d6fc40..6248106c 100644 --- a/NEWS +++ b/NEWS @@ -125,6 +125,10 @@ fetchmail 6.3.5 (not yet released): or IMAP connection to TLS security with STLS/STARTTLS. * fetchmail now supports foo@example.org=bar user mappings for multidrop boxes. * switch setjmp/longjmp to sigsetjmp/siglongjmp +* IMAP now supports the EXTERNAL authentication method, courtesy of + Götz 'nimrill' Babin-Ebell, BerliOS patch #1095 with minor changes. +* The sslproto keywords are now case insensitive, courtesy of + Götz 'nimrill' Babin-Ebell, BerliOS patch #1095. # TRANSLATION UPDATES: * Russian/ru (Pavel Maryanov), Vietnamese/vi (Clytie Siddall) diff --git a/TODO.txt b/TODO.txt index 5ef066da..b078cf95 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,7 +5,6 @@ CODE: - check strict envelope N Received parsing, see mail from Admin Att on fetchmail-users - 6.3.4-pending-deletes.patch -- merge BerliOS IMAP AUTHENTICATE EXTERNAL patch - fetchmail -s with running daemon complains rather than silently restarting daemon diff --git a/driver.c b/driver.c index d3c8c970..a2468f23 100644 --- a/driver.c +++ b/driver.c @@ -1090,7 +1090,7 @@ static int do_session( /* Note: We pass the realhost name over for certificate verification. We may want to make this configurable */ if (ctl->use_ssl && SSLOpen(mailserver_socket,ctl->sslcert,ctl->sslkey,ctl->sslproto,ctl->sslcertck, - ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1) + ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname,&ctl->remotename) == -1) { report(stderr, GT_("SSL connection failed.\n")); err = PS_SOCKET; diff --git a/fetchmail.h b/fetchmail.h index cfe6a634..fec71d0b 100644 --- a/fetchmail.h +++ b/fetchmail.h @@ -81,6 +81,7 @@ char *strstr(const char *, const char *); #define A_GSSAPI 7 /* authenticate with GSSAPI */ #define A_SSH 8 /* authentication at session level */ #define A_MSN 9 /* same as NTLM with keyword MSN */ +#define A_EXTERNAL 10 /* external authentication (client cert) */ /* some protocols or authentication types (KERBEROS, GSSAPI, SSH) don't * require a password */ @@ -90,6 +91,7 @@ char *strstr(const char *, const char *); || (ctl)->server.authenticate == A_KERBEROS_V5 \ || (ctl)->server.authenticate == A_GSSAPI \ || (ctl)->server.authenticate == A_SSH \ + || (ctl)->server.authenticate == A_EXTERNAL \ || (ctl)->server.protocol == P_ETRN) /* @@ -119,6 +121,8 @@ char *strstr(const char *, const char *); #define DIGESTLEN 33 /* length of MD5 digest */ /* exit code values */ +/* NOTE THAT PS_SUCCESS MUST ALWAYS BE 0 - SOME PARTS OF THE CODE + * RELY ON THIS VALUE! */ #define PS_SUCCESS 0 /* successful receipt of messages */ #define PS_NOMAIL 1 /* no mail available */ #define PS_SOCKET 2 /* socket I/O woes */ diff --git a/fetchmail.man b/fetchmail.man index 8144bfba..7be2abdf 100644 --- a/fetchmail.man +++ b/fetchmail.man @@ -384,6 +384,10 @@ require it, some servers may request it but not require it, and some servers may not request it at all. It may be the same file as the private key (combined key and certificate file) but this is not recommended. +.sp +.B NOTE: +If you use client authentication, the user name is fetched from the +certificate's CommonName and overrides the name set with \-\-user. .TP .B \-\-sslkey (Keyword: sslkey) @@ -402,11 +406,11 @@ server. This can cause some complications in daemon mode. .TP .B \-\-sslproto (Keyword: sslproto) -Forces an SSL or TLS protocol. Possible values are '\fBssl2\fR', -\&'\fBssl3\fR', '\fBssl23\fR', and '\fBtls1\fR'. Try this if the default +Forces an SSL or TLS protocol. Possible values are '\fBSSL2\fR', +\&'\fBSSL3\fR', '\fBSSL23\fR', and '\fBTLS1\fR'. Try this if the default handshake does not work for your server. To defeat automatic TLSv1 negotiation when the server advertises STARTTLS or STLS, use \fB''\fR or -\&'\fBssl23\fR'. This option, even if the argument is the empty string, +\&'\fBSSL23\fR'. This option, even if the argument is the empty string, will also suppress the diagnostic 'SERVER: opportunistic upgrade to TLS.' message in verbose mode. The default is to try appropriate protocols depending on context. @@ -728,19 +732,22 @@ This option permits you to specify an authentication type (see USER AUTHENTICATION below for details). The possible values are \fBany\fR, \&\fBpassword\fR, \fBkerberos_v5\fR, \fBkerberos\fR (or, for excruciating exactness, \fBkerberos_v4\fR), \fBgssapi\fR, -\fBcram\-md5\fR, \fBotp\fR, \fBntlm\fR, \fBmsn\fR (only for POP3) and -\fBssh\fR. When \fBany\fR (the default) is specified, fetchmail tries -first methods that don't require a password (GSSAPI, KERBEROS\ IV, +\fBcram\-md5\fR, \fBotp\fR, \fBntlm\fR, \fBmsn\fR (only for POP3), +\fBexternal\fR (only IMAP) and \fBssh\fR. +When \fBany\fR (the default) is specified, fetchmail tries +first methods that don't require a password (EXTERNAL, GSSAPI, KERBEROS\ IV, KERBEROS\ 5); then it looks for methods that mask your password (CRAM-MD5, X\-OTP - note that NTLM and MSN are not autoprobed for POP3 and MSN is only supported for POP3); and only if the server doesn't support any of those will it ship your password en clair. Other values may be used to force various authentication methods -(\fBssh\fR suppresses authentication and is thus good for IMAP PREAUTH). +(\fBssh\fR suppresses authentication and is thus useful for IMAP PREAUTH). +(\fBexternal\fR suppresses authentication and is thus useful for IMAP EXTERNAL). Any value other than \fBpassword\fR, \fBcram\-md5\fR, \fBntlm\fR, \&\fBmsn\fR or \fBotp\fR suppresses fetchmail's normal inquiry for a password. Specify \fBssh\fR when you are using an end-to-end secure -connection such as an ssh tunnel; specify \fBgssapi\fR or +connection such as an ssh tunnel; specify \fBexternal\fR when you use +TLS with client authentication and specify \fBgssapi\fR or \&\fBkerberos_v4\fR if you are using a protocol variant that employs GSSAPI or K4. Choosing KPOP protocol automatically selects Kerberos authentication. This option does not work with ETRN. @@ -1009,6 +1016,13 @@ In this case you can declare the authentication value 'ssh' on that site entry to stop \fI.fetchmail\fR from asking you for a password when it starts up. .PP +If you use client authentication with \fITLS1\fR and your IMAP daemon +returns the \fIAUTH=EXTERNAL\fR response, fetchmail will notice this +and will use the authentication shortcut and will not send the +passphrase. In this case you can declare the authentication value 'external' + on that site to stop \fIfetchmail\fR from asking you for a password +when it starts up. +.PP If you are using POP3, and the server issues a one-time-password challenge conforming to RFC1938, \fIfetchmail\fR will use your password as a pass phrase to generate the required response. This @@ -1585,7 +1599,7 @@ tracepolls \& \& T{ Add poll tracing information to the Received header T} principal \& \& T{ -Set Kerberos principal (only useful with imap and kerberos) +Set Kerberos principal (only useful with IMAP and kerberos) T} esmtpname \& \& T{ Set name for RFC2554 authentication to the ESMTP server. @@ -1987,7 +2001,8 @@ Legal protocol identifiers for use with the 'protocol' keyword are: .PP Legal authentication types are 'any', 'password', 'kerberos', \&'kerberos_v4', 'kerberos_v5' and 'gssapi', 'cram\-md5', 'otp', 'msn' -(only for POP3), 'ntlm', 'ssh'. The 'password' type specifies +(only for POP3), 'ntlm', 'ssh', 'external' (only IMAP). +The 'password' type specifies authentication by normal transmission of a password (the password may be plain text or subject to protocol-specific encryption as in APOP); \&'kerberos' tells \fIfetchmail\fR to try to get a Kerberos ticket at the diff --git a/imap.c b/imap.c index d1ee1c2b..d397542f 100644 --- a/imap.c +++ b/imap.c @@ -347,6 +347,24 @@ static void capa_probe(int sock, struct query *ctl) peek_capable = (imap_version >= IMAP4); } +static int do_authcert (int sock, char *command, const char *name) +/* do authentication "external" (authentication provided by client cert) */ +{ + char buf[256]; + + if (name && name[0]) + { + size_t len = strlen(name); + if ((len / 3) + ((len % 3) ? 4 : 0) < sizeof(buf)) + to64frombits (buf, name, strlen(name)); + else + return PS_AUTHFAIL; /* buffer too small. */ + } + else + buf[0]=0; + return gen_transact(sock, "%s EXTERNAL %s",command,buf); +} + static int imap_getauth(int sock, struct query *ctl, char *greeting) /* apply for connection authorization */ { @@ -378,7 +396,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting) } #ifdef SSL_ENABLE - if ((!ctl->sslproto || !strcmp(ctl->sslproto,"tls1")) + if ((!ctl->sslproto || !strcasecmp(ctl->sslproto,"tls1")) && !ctl->use_ssl && strstr(capabilities, "STARTTLS")) { @@ -393,7 +411,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting) if (ok == PS_SUCCESS && SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint, - realhost,ctl->server.pollname) == -1) + realhost,ctl->server.pollname,&ctl->remotename) == -1) { if (!ctl->sslproto && !ctl->wehaveauthed) { @@ -430,6 +448,22 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting) */ ok = PS_AUTHFAIL; + if ((ctl->server.authenticate == A_ANY + || ctl->server.authenticate == A_EXTERNAL) + && strstr(capabilities, "AUTH=EXTERNAL")) + { + ok = do_authcert(sock, "AUTHENTICATE", ctl->remotename); + if (ok) + { + /* SASL cancellation of authentication */ + gen_send(sock, "*"); + if (ctl->server.authenticate != A_ANY) + return ok; + } else { + return ok; + } + } + #ifdef GSSAPI if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_GSSAPI) diff --git a/options.c b/options.c index 25ea005e..87a308c7 100644 --- a/options.c +++ b/options.c @@ -362,6 +362,8 @@ int parsecmdline (int argc /** argument count */, ctl->server.authenticate = A_KERBEROS_V4; else if (strcmp(optarg, "ssh") == 0) ctl->server.authenticate = A_SSH; + else if (strcasecmp(optarg, "external") == 0) + ctl->server.authenticate = A_EXTERNAL; else if (strcmp(optarg, "otp") == 0) ctl->server.authenticate = A_OTP; else if (strcmp(optarg, "opie") == 0) @@ -610,7 +612,7 @@ int parsecmdline (int argc /** argument count */, P(GT_(" --sslcertck do strict server certificate check (recommended)\n")); P(GT_(" --sslcertpath path to ssl certificates\n")); P(GT_(" --sslfingerprint fingerprint that must match that of the server's cert.\n")); - P(GT_(" --sslproto force ssl protocol (ssl2/ssl3/tls1)\n")); + P(GT_(" --sslproto force ssl protocol (SSL2/SSL3/TLS1)\n")); #endif P(GT_(" --plugin specify external command to open connection\n")); P(GT_(" --plugout specify external command to open smtp connection\n")); diff --git a/pop3.c b/pop3.c index e32103d7..0b201289 100644 --- a/pop3.c +++ b/pop3.c @@ -441,7 +441,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting) #ifdef SSL_ENABLE if (has_ssl && !ctl->use_ssl - && (!ctl->sslproto || !strcmp(ctl->sslproto,"tls1"))) + && (!ctl->sslproto || !strcasecmp(ctl->sslproto,"tls1"))) { char *realhost; @@ -453,7 +453,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting) if (ok == PS_SUCCESS && SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint, - realhost,ctl->server.pollname) == -1) + realhost,ctl->server.pollname,&ctl->remotename) == -1) { if (!ctl->sslproto && !ctl->wehaveauthed) { diff --git a/rcfile_l.l b/rcfile_l.l index 7ae3db96..4b0b0fed 100644 --- a/rcfile_l.l +++ b/rcfile_l.l @@ -99,6 +99,7 @@ kerberos(_v)?4 { SETSTATE(0); yylval.proto = A_KERBEROS_V4; return AUTHTYPE;} kerberos(_v)?5 { SETSTATE(0); yylval.proto = A_KERBEROS_V5; return AUTHTYPE;} kerberos { SETSTATE(0); yylval.proto = A_KERBEROS_V4; return AUTHTYPE;} ssh { SETSTATE(0); yylval.proto = A_SSH; return AUTHTYPE;} +external { SETSTATE(0); yylval.proto = A_EXTERNAL; return AUTHTYPE;} (otp|opie) { SETSTATE(0); yylval.proto = A_OTP; return AUTHTYPE;} cram(-md5)? { SETSTATE(0); yylval.proto = A_CRAM_MD5; return AUTHTYPE;} msn { SETSTATE(0); yylval.proto = A_MSN; return AUTHTYPE;} diff --git a/socket.c b/socket.c index 52e9f1cf..b4b0ce6b 100644 --- a/socket.c +++ b/socket.c @@ -810,12 +810,43 @@ static int SSL_ck_verify_callback( int ok_return, X509_STORE_CTX *ctx ) return SSL_verify_callback(ok_return, ctx, 1); } + +/* get commonName from certificate set in file. + * commonName is stored in buffer namebuffer, limited with namebufferlen + */ +static const char *SSLCertGetCN(const char *mycert, + char *namebuffer, size_t namebufferlen) +{ + const char *ret = NULL; + BIO *certBio = NULL; + X509 *x509_cert = NULL; + X509_NAME *certname = NULL; + + if (namebuffer && namebufferlen > 0) { + namebuffer[0] = 0x00; + certBio = BIO_new_file(mycert,"r"); + if (certBio) { + x509_cert = PEM_read_bio_X509(certBio,NULL,NULL,NULL); + BIO_free(certBio); + } + if (x509_cert) { + certname = X509_get_subject_name(x509_cert); + if (certname && + X509_NAME_get_text_by_NID(certname, NID_commonName, + namebuffer, namebufferlen) > 0) + ret = namebuffer; + X509_free(x509_cert); + } + } + return ret; +} + /* performs initial SSL handshake over the connected socket * uses SSL *ssl global variable, which is currently defined * in this file */ int SSLOpen(int sock, char *mycert, char *mykey, char *myproto, int certck, char *certpath, - char *fingerprint, char *servercname, char *label) + char *fingerprint, char *servercname, char *label, char **remotename) { struct stat randstat; int i; @@ -851,13 +882,13 @@ int SSLOpen(int sock, char *mycert, char *mykey, char *myproto, int certck, char /* Be picky and make sure the memory is cleared */ memset( _ssl_context, 0, sizeof( _ssl_context ) ); if(myproto) { - if(!strcmp("ssl2",myproto)) { + if(!strcasecmp("ssl2",myproto)) { _ctx = SSL_CTX_new(SSLv2_client_method()); - } else if(!strcmp("ssl3",myproto)) { + } else if(!strcasecmp("ssl3",myproto)) { _ctx = SSL_CTX_new(SSLv3_client_method()); - } else if(!strcmp("tls1",myproto)) { + } else if(!strcasecmp("tls1",myproto)) { _ctx = SSL_CTX_new(TLSv1_client_method()); - } else if (!strcmp("ssl23",myproto)) { + } else if (!strcasecmp("ssl23",myproto)) { myproto = NULL; } else { fprintf(stderr,GT_("Invalid SSL protocol '%s' specified, using default (SSLv23).\n"), myproto); @@ -906,10 +937,17 @@ int SSLOpen(int sock, char *mycert, char *mykey, char *myproto, int certck, char * he does NOT have a separate certificate and private key file then * assume that it's a combined key and certificate file. */ + char buffer[256]; + if( !mykey ) mykey = mycert; if( !mycert ) mycert = mykey; + + if (SSLCertGetCN(mycert, buffer, sizeof(buffer))) { + free(*remotename); + *remotename = xstrdup(buffer); + } SSL_use_certificate_file(_ssl_context[sock], mycert, SSL_FILETYPE_PEM); SSL_use_RSAPrivateKey_file(_ssl_context[sock], mykey, SSL_FILETYPE_PEM); } diff --git a/socket.h b/socket.h index ca5877fe..1ebb2aa0 100644 --- a/socket.h +++ b/socket.h @@ -69,7 +69,7 @@ int UnixOpen(const char *path); #ifdef SSL_ENABLE int SSLOpen(int sock, char *mycert, char *mykey, char *myproto, int certck, char *certpath, - char *fingerprint, char *servercname, char *label); + char *fingerprint, char *servercname, char *label, char **remotename); #endif /* SSL_ENABLE */ #endif /* SOCKET__ */ -- cgit v1.2.3