]> Pileus Git - ~andy/fetchmail/commitdiff
GSSAPI support.
authorEric S. Raymond <esr@thyrsus.com>
Mon, 15 Dec 1997 02:42:30 +0000 (02:42 -0000)
committerEric S. Raymond <esr@thyrsus.com>
Mon, 15 Dec 1997 02:42:30 +0000 (02:42 -0000)
svn path=/trunk/; revision=1569

NEWS
configure.in
env.c
fetchmail.c
fetchmail.h
fetchmail.man
imap.c
rcfile_l.l

diff --git a/NEWS b/NEWS
index bd38d6e53f12a36fce0d1b35b075e9c44fb1ec05..f337a9abc99b8ff4b94cd7b00209037c96fe0f60 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,7 @@
   when user's pop3 host isn't the same as his SMTP host.)
 * Allow -c -F to work to flush messages without retrieval.
 * Read from /etc/fetchmailrc before ~/.fetchmailrc?
+* Do is_host_alias checks by comparing IP addresses rather than names?
 
                                Other TO-DO items:
 
@@ -18,6 +19,7 @@
 fetchmail-4.3.5 ()
 * Added Kent Robotti's fetchsetup configuration script.
 * Corrected buggy  handling of `expunge 0'.
+* GSSAPI support from Brendan Cully <brendan@kublai.com>.
 
 fetchmail-4.3.4 (Fri Dec  5 12:39:31 EST 1997)
 * Yet another attempt on the Compuserve RPA moving target.
index c9a10f1bdd854bc788e2013007dbe8ee5b268079..677ef6f30ef84241d8ee8be1c2ae24e8715b024f 100644 (file)
@@ -196,6 +196,35 @@ then
     AC_DEFINE(OPIE,1)
 fi
 
+###    use option --with-gssapi=DIR to compile in GSSAPI support
+AC_ARG_WITH(gssapi,
+       [  --with-gssapi[=DIR]  compile in GSSAPI support using libraries in DIR])
+if test "$with-gssapi" = "yes"
+then
+    GSSAPIDIR="/usr /usr/local /usr/athena"
+else
+    GSSAPIDIR="$with_gssapi"
+fi
+if test "$GSSAPIDIR" != "" -a "$GSSAPIDIR" != "no"
+then
+    AC_MSG_CHECKING([for gssapi])
+    for curgssapidir in $GSSAPIDIR
+    do
+        if test -f $curgssapidir/include/gssapi/gssapi.h
+        then
+            CEFLAGS="$CEFLAGS -DGSSAPI -I$curgssapidir/include"
+            LDEFLAGS="$LDEFLAGS -L$curgssapidir/lib"
+            LIBS="$LIBS -lgssapi_krb5 -lkrb5"
+            AC_MSG_RESULT([in $curgssapidir])
+            GSSAPIFOUND="yes"
+        fi
+    done
+    if test "$GSSAPIFOUND" != "yes"
+    then
+        AC_MSG_ERROR([not found])
+    fi
+fi
+
 ###    use option --with-kerberos=DIR to point at a Kerberos directory
 AC_ARG_WITH(kerberos,
        [  --with-kerberos=DIR  point fetchmail compilation at a Kerberos directory])
diff --git a/env.c b/env.c
index 874a094d473836f3d6eb1e8c80df5d54e27d8d59..a77b9ad8495065628b2b1f1bf47f1cf87776bce9 100644 (file)
--- a/env.c
+++ b/env.c
@@ -97,6 +97,7 @@ char *showproto(int proto)
     case P_POP3: return("POP3"); break;
     case P_IMAP: return("IMAP"); break;
     case P_IMAP_K4: return("IMAP-K4"); break;
+    case P_IMAP_GSS: return("IMAP-GSS"); break;
     case P_APOP: return("APOP"); break;
     case P_RPOP: return("RPOP"); break;
     case P_ETRN: return("ETRN"); break;
index b620467a665b1714a190e551bc334dd92d225630..7f78eae6a985f00f9b4d1da8fa934bffa4b868ca 100644 (file)
@@ -295,7 +295,7 @@ int main (int argc, char **argv)
     for (ctl = querylist; ctl; ctl = ctl->next)
        if (ctl->active && !(implicitmode && ctl->server.skip)&&!ctl->password)
        {
-           if (ctl->server.preauthenticate == A_KERBEROS_V4 || ctl->server.protocol == P_IMAP_K4)
+           if (ctl->server.preauthenticate == A_KERBEROS_V4 || ctl->server.protocol == P_IMAP_K4 || ctl->server.protocol == P_IMAP_GSS)
                /* Server won't care what the password is, but there
                   must be some non-null string here.  */
                ctl->password = ctl->remotename;
@@ -311,7 +311,7 @@ int main (int argc, char **argv)
                    ctl->password = xstrdup(p->password);
            }
 
-           if (ctl->server.protocol != P_ETRN && ctl->server.protocol != P_IMAP_K4 && !ctl->password)
+           if (ctl->server.protocol != P_ETRN && ctl->server.protocol != P_IMAP_K4 && ctl->server.protocol != P_IMAP_GSS && !ctl->password)
            {
                (void) sprintf(tmpbuf, "Enter password for %s@%s: ",
                               ctl->remotename, ctl->server.pollname);
@@ -874,6 +874,7 @@ static int query_host(struct query *ctl)
        break;
     case P_IMAP:
     case P_IMAP_K4:
+    case P_IMAP_GSS:
 #ifdef IMAP_ENABLE
        return(doIMAP(ctl));
 #else
index 5fafe8bffa3c55181d3d3466e96d2748cea8c689..55162750125083522c3c6518ed0b95deb3c3e1b7 100644 (file)
@@ -11,6 +11,7 @@
 #define                P_APOP          6
 #define                P_RPOP          7
 #define                P_ETRN          8
+#define                P_IMAP_GSS      9
 
 #define                KPOP_PORT       1109
 
index 566bc920807530573b3c21ca4ce10bc009db445c..83cb6f9181717aeef4295ab60c875bc2ba9f9039 100644 (file)
@@ -166,6 +166,9 @@ IMAP2bis, IMAP4, or IMAP4rev1 (\fIfetchmail\fR autodetects their capabilities).
 .IP IMAP-K4
 IMAP4, or IMAP4rev1 (\fIfetchmail\fR autodetects their capabilities)
 with RFC 1731 Kerberos v4 authentication.
+.IP IMAP-GSS
+IMAP4, or IMAP4rev1 (\fIfetchmail\fR autodetects their capabilities)
+with RFC 1731 GSSAPI authentication.
 .IP ETRN
 Use the ESMTP ETRN option.
 .RE
@@ -487,6 +490,13 @@ ticket from the mailserver at the start of each query.
 If you use IMAP-K4, \fIfetchmail\fR will expect the IMAP server to have
 RFC1731-conformant AUTHENTICATE KERBEROS_V4 capability, and will use it.
 .PP
+If you use IMAP-GSS, \fIfetchmail\fR will expect the IMAP server to have
+RFC1731-conformant AUTHENTICATE GSSAPI capability, and will use it. 
+Currently this has only been tested over Kerberos V, so you're expected
+to already have a ticket-granting ticket. You may pass a username different
+from your principal name using the standard \fB--user\fR command or by
+the \fI.fetchmailrc\fR option \fBuser\fR.
+.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
@@ -773,7 +783,7 @@ Specify DNS name of mailserver, overriding poll name
 T}
 proto[col]     -p      T{
 Specify protocol (case insensitive):
-POP2, POP3, IMAP, IMAP-K4, APOP, KPOP
+POP2, POP3, IMAP, IMAP-K4, IMAP-GSS, APOP, KPOP
 T}
 port           -P      T{
 Specify TCP/IP service port
@@ -1071,6 +1081,7 @@ Legal protocol identifiers for use with the `protocol' keyword are:
     pop3 (or POP3)
     imap (or IMAP)
     imap-k4 (or IMAP-K4)
+    imap-gss (or IMAP-GSS)
     apop (or APOP)
     kpop (or KPOP)
 
@@ -1457,7 +1468,7 @@ are technically legal but bizarre.  Strange uses of quoting and
 embedded comments are likely to confuse it.
 .PP
 Use of any of the supported protocols other than POP3 with OTP or RPA, APOP,
-KPOP, IMAP-K4, or ETRN requires that the program send unencrypted
+KPOP, IMAP-K4, IMAP-GSS, or ETRN requires that the program send unencrypted
 passwords over the TCP/IP connection to the mailserver.  This creates
 a risk that name/password pairs might be snaffled with a packet
 sniffer or more sophisticated monitoring software.  Under Linux, the
diff --git a/imap.c b/imap.c
index 5fc84bc7ece83ed9561a4ea273974ef0f53545f2..0ad0aa2bb0723e6831944ddbdaa0b4bef5813455 100644 (file)
--- a/imap.c
+++ b/imap.c
 #include <krb.h>
 #endif /* KERBEROS_V4 */
 
+#ifdef GSSAPI
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#endif
+
 #ifndef strstr         /* glibc-2.1 declares this as a macro */
 extern char *strstr(); /* needed on sysV68 R3V7.1. */
 #endif /* strstr */
@@ -173,7 +178,7 @@ static int do_rfc1731(int sock, char *truename)
       }
     }
 
-    strncpy(srvrealm, krb_realmofhost(srvinst), (sizeof srvrealm)-1);
+    strncpy(srvrealm, (char *)krb_realmofhost(srvinst), (sizeof srvrealm)-1);
     srvrealm[(sizeof srvrealm)-1] = '\0';
     if (p = strchr(srvinst, '.')) {
       *p = '\0';
@@ -328,6 +333,165 @@ static int do_rfc1731(int sock, char *truename)
 }
 #endif /* KERBEROS_V4 */
 
+#ifdef GSSAPI
+#define GSSAUTH_P_NONE      1
+#define GSSAUTH_P_INTEGRITY 2
+#define GSSAUTH_P_PRIVACY   4
+
+static int do_gssauth(int sock, char *hostname, char *username)
+{
+    gss_buffer_desc request_buf, send_token;
+    gss_buffer_t sec_token;
+    gss_name_t target_name;
+    gss_ctx_id_t context;
+    gss_OID mech_name;
+    gss_qop_t quality;
+    int cflags;
+    OM_uint32 maj_stat, min_stat;
+    char buf1[8192], buf2[8192], server_conf_flags;
+    unsigned long buf_size;
+    int result;
+
+    /* first things first: get an imap ticket for host */
+    sprintf(buf1, "imap@%s", hostname);
+    request_buf.value = buf1;
+    request_buf.length = strlen(buf1) + 1;
+    maj_stat = gss_import_name(&min_stat, &request_buf, gss_nt_service_name,
+        &target_name);
+    if (maj_stat != GSS_S_COMPLETE) {
+        error(0, -1, "Couldn't get service name for [%s]", buf1);
+        return PS_AUTHFAIL;
+    }
+    else if (outlevel == O_VERBOSE) {
+        maj_stat = gss_display_name(&min_stat, target_name, &request_buf,
+            &mech_name);
+        error(0, 0, "Using service name [%s]",request_buf.value);
+        maj_stat = gss_release_buffer(&min_stat, &request_buf);
+    }
+
+    gen_send(sock, "AUTHENTICATE GSSAPI");
+
+    /* upon receipt of the GSSAPI authentication request, server returns
+     * null data ready response. */
+    if (result = gen_recv(sock, buf1, sizeof buf1)) {
+        return result;
+    }
+
+    /* now start the security context initialisation loop... */
+    sec_token = GSS_C_NO_BUFFER;
+    context = GSS_C_NO_CONTEXT;
+    if (outlevel == O_VERBOSE)
+        error(0,0,"Sending credentials");
+    do {
+        maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, 
+            &context, target_name, NULL, 0, 0, NULL, sec_token, NULL,
+           &send_token, &cflags, NULL);
+        if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
+            error(0, -1,"Error exchanging credentials");
+            gss_release_name(&min_stat, &target_name);
+            /* wake up server and await NO response */
+            SockWrite(sock, "\r\n", 2);
+            if (result = gen_recv(sock, buf1, sizeof buf1))
+                return result;
+            return PS_AUTHFAIL;
+        }
+        to64frombits(buf1, send_token.value, send_token.length);
+        gss_release_buffer(&min_stat, &send_token);
+        SockWrite(sock, buf1, strlen(buf1));
+        SockWrite(sock, "\r\n", 2);
+        if (outlevel == O_VERBOSE)
+            error(0,0,"IMAP> %s", buf1);
+        if (maj_stat == GSS_S_CONTINUE_NEEDED) {
+           if (result = gen_recv(sock, buf1, sizeof buf1)) {
+               gss_release_name(&min_stat, &target_name);
+               return result;
+           }
+           request_buf.length = from64tobits(buf2, buf1 + 2);
+           request_buf.value = buf2;
+           sec_token = &request_buf;
+        }
+    } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+    gss_release_name(&min_stat, &target_name);
+
+    /* get security flags and buffer size */
+    if (result = gen_recv(sock, buf1, sizeof buf1)) {
+        return result;
+    }
+    request_buf.length = from64tobits(buf2, buf1 + 2);
+    request_buf.value = buf2;
+
+    maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token,
+        &cflags, &quality);
+    if (maj_stat != GSS_S_COMPLETE) {
+        error(0,-1,"Couldn't unwrap security level data");
+        gss_release_buffer(&min_stat, &send_token);
+        return PS_AUTHFAIL;
+    }
+    if (outlevel == O_VERBOSE)
+        error(0,0,"Credential exchange complete");
+    /* first octet is security levels supported. We want none, for now */
+    server_conf_flags = ((char *)send_token.value)[0];
+    if ( !(((char *)send_token.value)[0] & GSSAUTH_P_NONE) ) {
+        error(0,-1,"Server requires integrity and/or privacy");
+        gss_release_buffer(&min_stat, &send_token);
+        return PS_AUTHFAIL;
+    }
+    ((char *)send_token.value)[0] = 0;
+    buf_size = ntohl(*((long *)send_token.value));
+    /* we don't care about buffer size if we don't wrap data */
+    gss_release_buffer(&min_stat, &send_token);
+    if (outlevel == O_VERBOSE) {
+        error(0,0,"Unwrapped security level flags: %s%s%s",
+            server_conf_flags & GSSAUTH_P_NONE ? "N" : "-",
+            server_conf_flags & GSSAUTH_P_INTEGRITY ? "I" : "-",
+            server_conf_flags & GSSAUTH_P_PRIVACY ? "C" : "-");
+        error(0,0,"Maximum GSS token size is %ld",buf_size);
+    }
+
+    /* now respond in kind (hack!!!) */
+    buf_size = htonl(buf_size); /* do as they do... only matters if we do enc */
+    memcpy(buf1, &buf_size, 4);
+    buf1[0] = GSSAUTH_P_NONE;
+    strcpy(buf1+4, username); /* server decides if princ is user */
+    request_buf.length = 4 + strlen(username) + 1;
+    request_buf.value = buf1;
+    maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf,
+        &cflags, &send_token);
+    if (maj_stat != GSS_S_COMPLETE) {
+        error(0,-1,"Error creating security level request");
+        return PS_AUTHFAIL;
+    }
+    to64frombits(buf1, send_token.value, send_token.length);
+    if (outlevel == O_VERBOSE) {
+        error(0,0,"Requesting authorisation as %s", username);
+        error(0,0,"IMAP> %s",buf1);
+    }
+    SockWrite(sock, buf1, strlen(buf1));
+    SockWrite(sock, "\r\n", 2);
+
+    /* we should be done. Get status and finish up */
+    if (result = gen_recv(sock, buf1, sizeof buf1))
+        return result;
+    if (strstr(buf1, "OK")) {
+        /* flush security context */
+        if (outlevel == O_VERBOSE)
+            error(0, 0, "Releasing GSS credentials");
+        maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token);
+        if (maj_stat != GSS_S_COMPLETE) {
+            error(0, -1, "Error releasing credentials");
+            return PS_AUTHFAIL;
+        }
+        /* send_token may contain a notification to the server to flush
+         * credentials. RFC 1731 doesn't specify what to do, and since this
+         * support is only for authentication, we'll assume the server
+         * knows enough to flush its own credentials */
+        return PS_SUCCESS;
+    }
+
+    return PS_AUTHFAIL;
+}      
+#endif /* GSSAPI */
+
 int imap_getauth(int sock, struct query *ctl, char *greeting)
 /* apply for connection authorization */
 {
@@ -367,6 +531,23 @@ int imap_getauth(int sock, struct query *ctl, char *greeting)
            return(ok);
     }
 
+#ifdef GSSAPI
+    if (strstr(capabilities, "AUTH=GSSAPI"))
+    {
+        if (ctl->server.protocol == P_IMAP_GSS)
+        {
+            if (outlevel == O_VERBOSE)
+                error(0, 0, "GSS authentication is supported");
+            return do_gssauth(sock, ctl->server.truename, ctl->remotename);
+        }
+    }
+    else if (ctl->server.protocol == P_IMAP_GSS)
+    {
+        error(0,-1, "Required GSS capability not supported by server");
+        return(PS_AUTHFAIL);
+    }
+#endif /* GSSAPI */
+
 #ifdef KERBEROS_V4
     if (strstr(capabilities, "AUTH=KERBEROS_V4"))
     {
index 3184da1b0161611c7e4a59240e2e6305596022d0..010190e6638a0c98f2cdce8f4df0a6447d902145 100644 (file)
@@ -90,6 +90,7 @@ options               {/* EMPTY */}
 (pop2)|(POP2)  { yylval.proto = P_POP2;  return PROTO; }
 (pop3)|(POP3)  { yylval.proto = P_POP3;  return PROTO; }
 (imap-k4)|(IMAP-K4)   { yylval.proto = P_IMAP_K4;  return PROTO; }
+(imap-gss)|(IMAP-GSS) { yylval.proto = P_IMAP_GSS;  return PROTO; }
 (imap)|(IMAP)  { yylval.proto = P_IMAP;  return PROTO; }
 (apop)|(APOP)   { yylval.proto = P_APOP;  return PROTO; }
 (etrn)|(ETRN)   { yylval.proto = P_ETRN;  return PROTO; }