From 0ea95737509844797b92adc062c010e06c9632b2 Mon Sep 17 00:00:00 2001 From: Karl Heuer Date: Mon, 6 Apr 1998 22:16:30 +0000 Subject: [PATCH] Integrate support for GSS-API authentication, implemented by Kevin L. Mitchel . --- lib-src/Makefile.in | 17 +- lib-src/pop.c | 998 +++++++++++++++++++++++++++++++++++++++++++- lib-src/pop.h | 25 ++ 3 files changed, 1016 insertions(+), 24 deletions(-) diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in index 24730fb42f..e4d2179967 100644 --- a/lib-src/Makefile.in +++ b/lib-src/Makefile.in @@ -205,12 +205,21 @@ MOVE_FLAGS= # ifdef HAVE_LIBCOM_ERR COM_ERRLIB = -lcom_err # endif -#endif /* KERBEROS +# ifdef HAVE_LIBGSSAPI_KRB5 + GSSAPI_KRB5LIB = -lgssapi_krb5 +# endif +#endif /* KERBEROS */ + +#ifdef HAVE_LIBGSSAPI + GSSAPILIB = -lgssapi +#endif -/* If HESIOD is defined, set this to "-lhesiod". */ -HESIODLIB= +#ifdef HESIOD +HESIODLIB= -lhesiod +#endif -MOVE_LIBS=$(KRB4LIB) $(DESLIB) $(KRB5LIB) $(CRYPTOLIB) $(COM_ERRLIB) $(HESIODLIB) +MOVE_LIBS=$(GSSAPI_KRB5LIB) $(GSSAPILIB) $(KRB4LIB) $(DESLIB) $(KRB5LIB) \ + $(CRYPTOLIB) $(COM_ERRLIB) $(HESIODLIB) #ifdef HAVE_LIBMAIL LIBMAIL=-lmail diff --git a/lib-src/pop.c b/lib-src/pop.c index 69e6e46523..499e8ef916 100644 --- a/lib-src/pop.c +++ b/lib-src/pop.c @@ -161,6 +161,57 @@ static char *find_crlf _P((char *, int)); #define KPOP_SERVICE "kpop" #endif +#ifdef GSSAPI +# ifdef HAVE_GSSAPI_H +# include +# else +# include +# endif +#define GSSAPI_SERVICE "pop" +static int pop_auth (/* popserver server, char *user, + char *host, int flags */); +static void gen_gss_error (/* char *msg, OM_uint32 major, OM_uint32 minor */); +struct _pop_gssapi +{ + int gss_flags; /* encryption? integrity protection? */ + OM_uint32 max_size; /* max size we can send the server */ + gss_ctx_id_t gss_context; /* the security context */ +}; +#define GSSAPI_NOPROT 0x01 +#define GSSAPI_INTEGRITY 0x02 +#define GSSAPI_PRIVACY 0x04 +#define GSSAPI_NEEDWRAP (GSSAPI_INTEGRITY|GSSAPI_PRIVACY) +#define GSSAPI_PROTECTION (GSSAPI_NOPROT|GSSAPI_INTEGRITY|GSSAPI_PRIVACY) +#define GSSAPI_RCVBUF 1024 +#define GSSAPI_SVC_TYPE {10, "\052\206\110\206\367\022\001\002\001\004"} +#define Gssapi(data) ((struct _pop_gssapi *) (data)) + +static int b64_decode (/* char *enc, gss_buffer_t dec */); +static int b64_encode (/* gss_buffer_t dec, char **enc */); +#define B64_SUCCESS 0 +#define B64_BADPARAM 1 +#define B64_BADCHAR 2 +#define B64_BADPAD 3 +#define B64_BADLEN 4 +#define B64_NOMEM 5 +static char *b64_error[] = +{ + "Success", + "Bad parameters", + "Bad characters in encoding", + "Bad padding in encoding", + "Bad length", + "Out of memory" +}; + +/* + * This function is only needed if you are using the GSSAPI protection + * mechanisms; it keeps trying until it has read the requested number + * bytes from the passed-in fd. + */ +static int fullread (/* int fd, char *buf, int nbytes */); +#endif /* GSSAPI */ + char pop_error[ERROR_MAX]; int pop_debug = 0; @@ -269,10 +320,22 @@ pop_open (host, username, password, flags) } /* Determine the password */ -#ifdef KERBEROS -#define DONT_NEED_PASSWORD (! (flags & POP_NO_KERBEROS)) +#if defined(KERBEROS) || defined(GSSAPI) +# ifdef KERBEROS +# define NO_KERBEROS POP_NO_KERBEROS +# else +# define NO_KERBEROS 0 +# endif /* KERBEROS */ + +# ifdef GSSAPI +# define NO_GSSAPI POP_NO_GSSAPI +# else +# define NO_GSSAPI 0 +# endif /* GSSAPI */ + +# define DONT_NEED_PASSWORD (! (flags & (NO_KERBEROS | NO_GSSAPI))) #else -#define DONT_NEED_PASSWORD 0 +# define DONT_NEED_PASSWORD 0 #endif if ((! password) && (! DONT_NEED_PASSWORD)) @@ -288,7 +351,7 @@ pop_open (host, username, password, flags) } } if (password) - flags |= POP_NO_KERBEROS; + flags |= POP_NO_KERBEROS | (!(flags & POP_NO_NOPROT) ? POP_NO_GSSAPI : 0); else password = username; @@ -316,10 +379,46 @@ pop_open (host, username, password, flags) server->buffer_size = GETLINE_MIN; server->in_multi = 0; server->trash_started = 0; + server->extra = 0; if (getok (server)) return (0); +#ifdef GSSAPI + /* + * unless forbidden to use GSSAPI, try the GSSAPI AUTH mechanism..first. + */ + pop_error[0] = '\0'; /* so we can detect errors later... */ + if (! (flags & POP_NO_GSSAPI)) + { + int ret; + + ret = pop_auth (server, username, host, flags); + if (ret == 0) + { + return (server); + } + else if (ret == -2) + { + pop_close (server); + return (0); + } + } +#endif /* GSSAPI */ + /* + * POP_NO_NOPROT is used in the case that we want protection; if + * the authentication negotiation failed, then we want to fail now. + */ + if ((flags & POP_NO_NOPROT)) + { + pop_close (server); +#ifdef GSSAPI + if (pop_error[0] == '\0') +#endif + strcpy (pop_error, "Unable to provide protection"); + return (0); + } + /* * I really shouldn't use the pop_error variable like this, but.... */ @@ -1004,6 +1103,17 @@ pop_quit (server) if (server->buffer) free (server->buffer); +#ifdef GSSAPI + if (server->extra) + { + OM_uint32 minor; + + if (Gssapi (server->extra)->gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&minor, &(Gssapi (server->extra)->gss_context), + GSS_C_NO_BUFFER); + free ((char *) server->extra); + } +#endif /* GSSAPI */ free ((char *) server); return (ret); @@ -1332,22 +1442,102 @@ pop_getline (server, line) while (1) { - /* There's a "- 1" here to leave room for the null that we put - at the end of the read data below. We put the null there so - that find_crlf knows where to stop when we call it. */ - if (server->data == server->buffer_size - 1) +#ifdef GSSAPI + /* + * We might be playing with a protected connection. If we are, then + * we need to first read a chunk of ciphertext from the server, + * unwrap it, and stuff it into the buffer. + */ + if (server->extra && + ((Gssapi (server->extra)->gss_flags) & GSSAPI_NEEDWRAP)) { - server->buffer_size += GETLINE_INCR; - server->buffer = (char *)realloc (server->buffer, server->buffer_size); - if (! server->buffer) + char rcvbuf[GSSAPI_RCVBUF]; + OM_uint32 major, minor, length; + gss_buffer_desc in_tok, out_tok; + struct _pop_gssapi *gss_data = Gssapi (server->extra); + + ret = fullread (server->file, (char *) &length, sizeof (length)); + + if (ret == sizeof (length)) { - strcpy (pop_error, "Out of memory in pop_getline"); - pop_trash (server); - return (-1); + in_tok.length = ntohl (length); + + if (in_tok.length <= GSSAPI_RCVBUF) + { + ret = fullread (server->file, rcvbuf, in_tok.length); + + if (ret == in_tok.length) + { + in_tok.value = (void *) rcvbuf; + + major = gss_unwrap (&minor, gss_data->gss_context, + &in_tok, &out_tok, 0, 0); + + if (major != GSS_S_COMPLETE) + { + pop_trash (server); + gen_gss_error ("unwrapping", major, minor); + return (-1); + } + + while (server->data + out_tok.length >= + server->buffer_size - 1) + server->buffer_size += GETLINE_INCR; + + server->buffer = (char *)realloc (server->buffer, + server->buffer_size); + + if (! server->buffer) + { + gss_release_buffer (&minor, &out_tok); + pop_trash (server); + strcpy (pop_error, "Out of memory in pop_getline"); + return (-1); + } + + bcopy (out_tok.value, server->buffer + server->data, + out_tok.length); + + ret = out_tok.length; + + gss_release_buffer (&minor, &out_tok); + } + else + ret = 0; /* force detection of unexpected EOF */ + } + else + { + pop_trash (server); + strcpy (pop_error, "Token from server too long in pop_getline"); + return (-1); + } } + else + ret = 0; /* force detection of unexpected EOF */ } - ret = RECV (server->file, server->buffer + server->data, - server->buffer_size - server->data - 1, 0); + else + { +#endif /* GSSAPI */ + /* There's a "- 1" here to leave room for the null that we put + at the end of the read data below. We put the null there so + that find_crlf knows where to stop when we call it. */ + if (server->data == server->buffer_size - 1) + { + server->buffer_size += GETLINE_INCR; + server->buffer = (char *)realloc (server->buffer, + server->buffer_size); + if (! server->buffer) + { + strcpy (pop_error, "Out of memory in pop_getline"); + pop_trash (server); + return (-1); + } + } + ret = RECV (server->file, server->buffer + server->data, + server->buffer_size - server->data - 1, 0); +#ifdef GSSAPI + } +#endif /* GSSAPI */ if (ret < 0) { strcpy (pop_error, GETLINE_ERROR); @@ -1391,6 +1581,37 @@ pop_getline (server, line) /* NOTREACHED */ } +#ifdef GSSAPI +/* + * Function: fullread + * + * Purpose: Just like read, but keeps trying until the specified number + * number of bytes has been read into the buffer. This function is + * only needed if you are using the GSSAPI protection mechanisms. + * + * Return value: Same as read. Pop_error is not set. + */ +static int +fullread (fd, buf, nbytes) + int fd; + char *buf; + int nbytes; +{ + char *cp; + int ret; + + cp = buf; + + while (nbytes > 0 && (ret = RECV (fd, cp, nbytes, 0)) > 0) + { + cp += ret; + nbytes -= ret; + } + + return (ret); +} +#endif /* GSSAPI */ + /* * Function: sendline * @@ -1417,11 +1638,87 @@ sendline (server, line) #define SENDLINE_ERROR "Error writing to POP server: " int ret; - ret = fullwrite (server->file, line, strlen (line)); - if (ret >= 0) - { /* 0 indicates that a blank line was written */ - ret = fullwrite (server->file, "\r\n", 2); +#ifdef GSSAPI + /* + * We might be playing with a protected connection. If we are, then we + * need to build our full plaintext, parse it into chunks small enough + * for the server to swallow, wrap each one, and send it over the net as + * specified by the RFC. + */ + if (server->extra && ((Gssapi (server->extra)->gss_flags) & GSSAPI_NEEDWRAP)) + { + char *sendbuf, *ptr; + OM_uint32 major, minor, length; + gss_buffer_desc in_tok, out_tok; + int len = 0, tot_len; + struct _pop_gssapi *gss_data = Gssapi (server->extra); + + sendbuf = malloc (strlen (line) + 3); + + if (! sendbuf) + { + pop_trash (server); + strcpy (pop_error, "Out of memory in sendline"); + return (-1); + } + + tot_len = sprintf (sendbuf, "%s\r\n", line); + + for (ptr = sendbuf; tot_len > 0; tot_len -= len, ptr += len) + { + len = ((tot_len > gss_data->max_size) ? + gss_data->max_size : tot_len); + + in_tok.value = (void *) ptr; + in_tok.length = len; + + major = gss_wrap (&minor, gss_data->gss_context, + (gss_data->gss_flags & GSSAPI_PRIVACY) ? 1 : 0, + GSS_C_QOP_DEFAULT, &in_tok, 0, &out_tok); + + if (major != GSS_S_COMPLETE) + { + free (sendbuf); + pop_trash (server); + gen_gss_error ("wrapping", major, minor); + return (-1); + } + + /* + * "Once the protection mechanism is in effect, the stream of + * command and response octets is processed into buffers of + * ciphertext. Each buffer is transferred over the connection + * as a stream of octets prepended with a four octet field in + * network byte order that represents the length of the + * following data." - RFC 1734, section 2 + */ + length = htonl (out_tok.length); + ret = fullwrite (server->file, (char *) &length, sizeof (length)); + if (ret == sizeof (length)) + { + ret = fullwrite (server->file, (char *) out_tok.value, + out_tok.length); + } + + gss_release_buffer (&minor, &out_tok); + + if (ret < 0) + break; + } + + free (sendbuf); } + else + { +#endif /* GSSAPI */ + ret = fullwrite (server->file, line, strlen (line)); + if (ret >= 0) + { /* 0 indicates that a blank line was written */ + ret = fullwrite (server->file, "\r\n", 2); + } +#ifdef GSSAPI + } +#endif /* GSSAPI */ if (ret < 0) { @@ -1589,6 +1886,19 @@ pop_trash (server) free (server->buffer); server->buffer = 0; } +#ifdef GSSAPI + if (server->extra) + { + OM_uint32 minor; + + if (Gssapi (server->extra)->gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&minor, + &(Gssapi (server->extra)->gss_context), + GSS_C_NO_BUFFER); + free ((char *) server->extra); + server->extra = 0; + } +#endif /* GSSAPI */ } #ifdef WINDOWSNT @@ -1597,6 +1907,654 @@ pop_trash (server) #endif } +#ifdef GSSAPI +/* + * Function: pop_auth + * + * Purpose: To perform a GSSAPI authentication handshake with a POP server. + * If the negotiation is successful, it will return 0; otherwise, it + * will fill in pop_error with the error message and return either -1, + * indicating a potentially recoverable error, or -2, indicating an + * unrecoverable error. + * + * Side effects: The server may choose to close the connection if the + * handshake fails. The connection will be trashed if the error is + * unrecoverable. + */ +static int +pop_auth (server, username, host, flags) + popserver server; + char *username, *host; + int flags; +{ + int gss_flags, ret; + char *fromserver; + OM_uint32 max_size, t_flags; + gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + gss_buffer_desc in_tok, out_tok; + gss_name_t svc_name; + OM_uint32 major, minor, t_minor; + + /* calculate usable protection mechanisms */ + gss_flags = (GSSAPI_PROTECTION & + ~(((flags & POP_NO_NOPROT) ? GSSAPI_NOPROT : 0) | + ((flags & POP_NO_INTEG) ? GSSAPI_INTEGRITY : 0) | + ((flags & POP_NO_ENCRYPT) ? GSSAPI_PRIVACY : 0))); + + if (gss_flags == 0) + { + strcpy (pop_error, "Unable to provide selected protection level"); + return (-1); + } + + /* import service name of pop server */ + in_tok.value = (void *) malloc (strlen (host) + sizeof (GSSAPI_SERVICE) + 2); + + if (! in_tok.value) + { + strcpy (pop_error, "Out of memory in pop_auth"); + return (-1); + } + + sprintf ((char *) in_tok.value, "%s@%s", GSSAPI_SERVICE, host); + in_tok.length = strlen ((char *) in_tok.value); + + { + gss_OID_desc svc_name_oid = GSSAPI_SVC_TYPE; + + major = gss_import_name (&minor, &in_tok, &svc_name_oid, &svc_name); + } + + free ((char *) in_tok.value); + + if (major != GSS_S_COMPLETE) + { + gen_gss_error ("parsing name", major, minor); + return (-1); + } + + /* begin GSSAPI authentication handshake */ + if (sendline (server, "AUTH GSSAPI") || (pop_getline (server, &fromserver) < 0)) + { + gss_release_name (&t_minor, &svc_name); + return (-1); + } + + do + { + /* sanity-check server response */ + if (strncmp (fromserver, "+ ", 2)) + { + gss_release_name (&t_minor, &svc_name); + if (gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + if (0 == strncmp (fromserver, "-ERR", 4)) + { + strncpy (pop_error, fromserver, ERROR_MAX); + return (-1); + } + else + { + pop_trash (server); + strcpy (pop_error, + "Unexpected response from POP server in pop_auth"); + return (-2); + } + } + + if (strlen (fromserver) > 2) + { + /* base 64 decode the response... */ + ret = b64_decode (fromserver + 2, &in_tok); + if (ret != B64_SUCCESS) + { + gss_release_name (&t_minor, &svc_name); + if (gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&t_minor, &gss_context, + GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, b64_error[ret]); + return (-1); + } + } + else + { + in_tok.length = 0; + in_tok.value = 0; + } + + /* call init_sec_context */ + major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &gss_context, + svc_name, GSS_C_NULL_OID, + GSS_C_MUTUAL_FLAG, 0, + GSS_C_NO_CHANNEL_BINDINGS, + in_tok.length ? & in_tok : GSS_C_NO_BUFFER, + 0, &out_tok, 0, 0); + + if (in_tok.length != 0) + free ((char *) in_tok.value); + + /* check for error */ + if (GSS_ERROR (major)) + { + gss_release_name (&t_minor, &svc_name); + if (gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + gen_gss_error ("in init_sec_context", major, minor); + return (-1); + } + + if (out_tok.length != 0) + { + /* base 64 encode output token, if any */ + ret = b64_encode (&out_tok, &fromserver); + + gss_release_buffer (&t_minor, &out_tok); + + if (ret != B64_SUCCESS) + { + gss_release_name (&t_minor, &svc_name); + if (gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&t_minor, &gss_context, + GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, b64_error[ret]); + return (-1); + } + + /* send output token... */ + ret = sendline (server, fromserver); + + free (fromserver); + } + else + /* empty output token... */ + ret = sendline (server, ""); + + /* get next token from server */ + if (ret || (pop_getline (server, &fromserver) < 0)) + { + gss_release_name (&t_minor, &svc_name); + if (gss_context != GSS_C_NO_CONTEXT) + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + return (-1); + } + } while ((major & GSS_S_CONTINUE_NEEDED)); + + /* release name... */ + gss_release_name (&t_minor, &svc_name); + + /* get final response from server */ + if (strncmp (fromserver, "+ ", 2)) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + if (0 == strncmp (fromserver, "-ERR", 4)) + { + strncpy (pop_error, fromserver, ERROR_MAX); + return (-1); + } + else + { + pop_trash (server); + strcpy (pop_error, + "Unexpected response from POP server in pop_auth"); + return (-2); + } + } + + /* base 64 decode... */ + ret = b64_decode (fromserver + 2, &in_tok); + if (ret != B64_SUCCESS) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, b64_error[ret]); + return (-1); + } + + /* unwrap... */ + major = gss_unwrap (&minor, gss_context, &in_tok, &out_tok, 0, 0); + + free ((char *) in_tok.value); + + if (major != GSS_S_COMPLETE || out_tok.length != sizeof (t_flags)) + { + if (out_tok.length != 0) + gss_release_buffer (&t_minor, &out_tok); + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + gen_gss_error ("in gss_unwrap", major, minor); + return (-1); + } + + /* get and check flags/size */ + bcopy ((void *) out_tok.value, (void *) &t_flags, sizeof (t_flags)); + + gss_release_buffer (&t_minor, &out_tok); + + max_size = ntohl (t_flags); + + t_flags = ((max_size & 0xFF000000) >> 24) & gss_flags; + max_size &= 0x00FFFFFF; + + if ((t_flags & GSSAPI_PRIVACY)) + gss_flags = GSSAPI_PRIVACY; + + else if ((t_flags & GSSAPI_INTEGRITY)) + gss_flags = GSSAPI_INTEGRITY; + + else if ((t_flags & GSSAPI_NOPROT)) + gss_flags = GSSAPI_NOPROT; + + else + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, "Server does not provide selected protection level"); + return (-1); + } + + if (max_size == 0) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, "Bad server max length"); + return (-1); + } + + if ((gss_flags & GSSAPI_NEEDWRAP)) + { + major = gss_wrap_size_limit (&t_minor, gss_context, + (gss_flags & GSSAPI_PRIVACY) ? 1 : 0, + GSS_C_QOP_DEFAULT, + (max_size < GSSAPI_RCVBUF) ? max_size : + GSSAPI_RCVBUF, &max_size); + if (major != GSS_S_COMPLETE) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + gen_gss_error ("getting max size", major, minor); + return (-1); + } + } + + /* generate return flags */ + { + OM_uint32 tmp; + + tmp = (((gss_flags << 24) & 0xFF000000) | (GSSAPI_RCVBUF & 0x00FFFFFF)); + t_flags = ntohl (tmp); + } + + in_tok.length = sizeof (t_flags) + strlen (username); + in_tok.value = (void *) malloc (in_tok.length); + + if (! in_tok.value) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, "Out of memory in pop_auth"); + return (-1); + } + + bcopy ((void *) &t_flags, in_tok.value, sizeof (t_flags)); + bcopy ((void *) username, + (void *) (((char *) in_tok.value) + sizeof (t_flags)), + in_tok.length - sizeof (t_flags)); + + /* wrap result */ + major = gss_wrap (&minor, gss_context, 0, GSS_C_QOP_DEFAULT, + &in_tok, 0, &out_tok); + + free ((char *) in_tok.value); + + if (major != GSS_S_COMPLETE || out_tok.length == 0) + { + if (out_tok.length != 0) + gss_release_buffer (&t_minor, &out_tok); + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + gen_gss_error ("in gss_wrap", major, minor); + return (-1); + } + + /* base 64 encode... */ + ret = b64_encode (&out_tok, &fromserver); + + gss_release_buffer (&t_minor, &out_tok); + + if (ret != B64_SUCCESS) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + sendline (server, "*"); + strcpy (pop_error, b64_error[ret]); + return (-1); + } + + /* send to server */ + ret = sendline (server, fromserver); + + free (fromserver); + + /* see if the server likes me... */ + if (ret || getok (server)) + { + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + return (-1); + } + + /* stash context */ + { + struct _pop_gssapi *gss_data; + + gss_data = (struct _pop_gssapi *) malloc (sizeof (struct _pop_gssapi)); + + if (! gss_data) + { + pop_trash (server); + gss_delete_sec_context (&t_minor, &gss_context, GSS_C_NO_BUFFER); + strcpy (pop_error, "Out of memory in pop_auth"); + return (-2); + } + + gss_data->gss_flags = gss_flags; + gss_data->max_size = max_size; + gss_data->gss_context = gss_context; + + server->extra = gss_data; + } + + return (0); +} + +/* + * Add as much error text to pop_error as will fit, but only put complete + * messages + */ +static void +gen_gss_error (msg, major, minor) + char *msg; + OM_uint32 major, minor; +{ + char *p = pop_error, *t, *saved; + int max = ERROR_MAX - 1; /* for \0 */ + OM_uint32 t_minor, msg_ctx = 0; + gss_buffer_desc gss_msg; + + while (*msg && max) + { + *p++ = *msg++; + max--; + } + + if (max >= 2) + { + saved = p; + *p++ = ':'; + *p++ = ' '; + max -= 2; + } + else + { + *p = '\0'; + return; + } + + do + { + gss_display_status (&t_minor, major, GSS_C_GSS_CODE, GSS_C_NO_OID, + &msg_ctx, &gss_msg); + for (t = (char *) gss_msg.value; *t && max; max--) + { + *p++ = *t++; + } + gss_release_buffer (&t_minor, &gss_msg); + if (max == 0) + { + *saved = '\0'; + return; + } + } while (msg_ctx); + + saved = p; + + do + { + gss_display_status (&t_minor, minor, GSS_C_MECH_CODE, GSS_C_NO_OID, + &msg_ctx, &gss_msg); + for (t = (char *) gss_msg.value; *t && max; max--) + { + *p++ = *t++; + } + gss_release_buffer (&t_minor, &gss_msg); + if (max == 0) + { + *saved = '\0'; + return; + } + } while (msg_ctx); + + *p = '\0'; + return; +} + +/* + * table-based base64 decoding function; takes 4 characters from in and + * writes from 1 to 3 bytes to out, storing the amount written in len + */ +static int +b64_d (in, out, len) + char *in, *out; + int *len; +{ + int decodearray[] = + { + 0x3e, -1, -1, -1, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, + 0x3b, 0x3c, 0x3d, -1, -1, -1, -1, -1, -1, -1, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + -1, -1, -1, -1, -1, -1, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 + }; + + int d; + + if (!in || !out || !len) + return (B64_BADPARAM); + + if (*in < '+' || *in > 'z') + return (B64_BADCHAR); + + d = decodearray[*(in++) - '+']; + if (d == -1) + return (B64_BADCHAR); + *out = d << 2; + + if (*in < '+' || *in > 'z') + return (B64_BADCHAR); + + d = decodearray[*(in++) - '+']; + if (d == -1) + return (B64_BADCHAR); + *(out++) |= d >> 4; + *out = (d & 15) << 4; + + if (*in < '+' || *in > 'z') + return (B64_BADCHAR); + else if (*in == '=') + if (*(in + 1) != '=') + return (B64_BADPAD); + else + { + *len = 1; + return (B64_SUCCESS); + } + + d = decodearray[*(in++) - '+']; + if (d == -1) + return (B64_BADCHAR); + *(out++) |= d >> 2; + *out = (d & 3) << 6; + + if (*in < '+' || *in > 'z') + return (B64_BADCHAR); + else if (*in == '=') + { + *len = 2; + return (B64_SUCCESS); + } + + d = decodearray[*in - '+']; + if (d == -1) + return (B64_BADCHAR); + *out |= d; + + *len = 3; + return (B64_SUCCESS); +} + +/* + * simple base64 encoding function that takes from 0 to 3 bytes and + * outputs 4 encoded characters, with appropriate padding + */ +static int +b64_e (in, out, len) + unsigned char *in, *out; + int len; +{ + unsigned char codearray[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + if (!in || !out || len <= 0 || len > 3) + return (B64_BADPARAM); + + *(out++) = codearray[((*in) >> 2)]; + + if (--len == 0) + { + *(out++) = codearray[(((*in) & 3) << 4)]; + *(out++) = '='; + *out = '='; + return (B64_SUCCESS); + } + + *(out++) = codearray[(((*in) & 3) << 4) | ((*(in + 1)) >> 4)]; + in++; + + if (--len == 0) + { + *(out++) = codearray[(((*in) & 15) << 2)]; + *out = '='; + return (B64_SUCCESS); + } + + *(out++) = codearray[(((*in) & 15) << 2) | ((*(in + 1)) >> 6)]; + *out = codearray[((*(in + 1)) & 63)]; + + return (B64_SUCCESS); +} + +/* + * given an input string, generate an output gss_buffer_t containing the + * decoded data and correct length; works by repeatedly driving b64_d () + * over the input string + */ +static int +b64_decode (enc, dec) + char *enc; + gss_buffer_t dec; +{ + char *tmp; + int inlen, outlen = 0, t_len, ret; + + if (!enc || !dec) + return (B64_BADPARAM); + + dec->value = 0; + dec->length = 0; + + inlen = strlen (enc); + if ((inlen % 4)) + return (B64_BADLEN); + + dec->value = (void *) (tmp = (char *) malloc ((inlen / 4) * 3)); + + if (! tmp) + return (B64_NOMEM); + + for (; inlen; inlen -= 4) + { + ret = b64_d (enc, tmp, &t_len); + if (ret != B64_SUCCESS) + { + free ((char *) dec->value); + dec->value = 0; + return (ret); + } + else if (t_len != 3) + { + dec->length = outlen + t_len; + return (B64_SUCCESS); + } + else + { + enc += 4; + tmp += t_len; + outlen += t_len; + } + } + + dec->length = outlen; + return (B64_SUCCESS); +} + +/* + * given a gss_buffer_t, generate an encoded string containing the data. + * works by repeatedly driving b64_e () over the contents of the buffer_t + */ +static int +b64_encode (dec, enc) + gss_buffer_t dec; + char **enc; +{ + unsigned char *tmp, *in; + int ret, len; + + if (!dec || !enc) + return (B64_BADPARAM); + + in = (unsigned char *) dec->value; + len = dec->length; + *enc = (char *) (tmp = (unsigned char *) malloc (((len * 4) / 3) + 5)); + + if (! tmp) + return (B64_NOMEM); + + do + { + ret = b64_e (in, tmp, len >= 3 ? 3 : len); + if (ret != B64_SUCCESS) + { + free (*enc); + *enc = 0; + return (ret); + } + else + { + in += 3; + tmp += 4; + } + } while ((len -= 3) > 0); + + *tmp = '\0'; + + return (B64_SUCCESS); +} + +#endif /* GSSAPI */ + /* Return a pointer to the first CRLF in IN_STRING, which can contain embedded nulls and has LEN characters in it not including the final null, or 0 if it does not contain one. */ diff --git a/lib-src/pop.h b/lib-src/pop.h index cfd333ccaf..7a56947a36 100644 --- a/lib-src/pop.h +++ b/lib-src/pop.h @@ -36,6 +36,7 @@ struct _popserver int buffer_size, buffer_index; int in_multi; int trash_started; + void *extra; }; typedef struct _popserver *popserver; @@ -47,6 +48,30 @@ typedef struct _popserver *popserver; #define POP_NO_KERBEROS (1<<0) #define POP_NO_HESIOD (1<<1) #define POP_NO_GETPASS (1<<2) +#define POP_NO_GSSAPI (1<<3) /* don't use the GSSAPI */ +#define POP_NO_NOPROT (1<<4) /* prohibit no protection; this *only* */ + /* makes sense if you use GSSAPI */ +#define POP_NO_INTEG (1<<5) /* don't use plain integrity */ +#define POP_NO_ENCRYPT (1<<6) /* don't use encryption */ + +/* + * GSSAPI documentation + * + * This version will attempt to perform a GSSAPI handshake first; if this + * fails, then it will attempt standard POP authentication. Note that + * library conflicts may prevent the use of this with the Kerberos + * kpop hack. + * + * If you specify POP_NO_NOPROT and this library is unable to provide either + * integrity protection or encryption, pop_open() will fail. The pop_open() + * call will attempt the highest level protection available; i.e., if both + * server and client support encryption (and you do not provide the + * POP_NO_ENCRYPT flag), that will be used; if both server and client support + * integrity protection (and you do not provide the POP_NO_INTEG flag), that + * will be used. If neither of these are available, and you have not + * specified the POP_NO_NOPROT flag, then this will be a normal, unprotected + * connection. + */ #ifdef __STDC__ #define _ARGS(a) a -- 2.20.1