--- /dev/null
+/* ========================================================================
+ * Copyright 1988-2006 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: GSSAPI authenticator
+ *
+ * Author: Mark Crispin
+ * Networks and Distributed Computing
+ * Computing & Communications
+ * University of Washington
+ * Administration Building, AG-44
+ * Seattle, WA 98195
+ * Internet: MRC@CAC.Washington.EDU
+ *
+ * Date: 12 January 1998
+ * Last Edited: 30 August 2006
+ */
+
+
+long auth_gssapi_valid (void);
+long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder,
+ char *service,NETMBX *mb,void *stream,
+ unsigned long *trial,char *user);
+long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal,
+ authrespond_t responder,char *service,NETMBX *mb,
+ void *stream,char *user,kinit_t ki);
+char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[]);
+
+
+AUTHENTICATOR auth_gss = {
+ AU_SECURE | AU_AUTHUSER, /* secure authenticator */
+ "GSSAPI", /* authenticator name */
+ auth_gssapi_valid, /* check if valid */
+ auth_gssapi_client, /* client method */
+ auth_gssapi_server, /* server method */
+ NIL /* next authenticator */
+};
+
+#define AUTH_GSSAPI_P_NONE 1
+#define AUTH_GSSAPI_P_INTEGRITY 2
+#define AUTH_GSSAPI_P_PRIVACY 4
+
+#define AUTH_GSSAPI_C_MAXSIZE 8192
+
+#define SERVER_LOG(x,y) syslog (LOG_ALERT,x,y)
+\f
+/* Check if GSSAPI valid on this system
+ * Returns: T if valid, NIL otherwise
+ */
+
+long auth_gssapi_valid (void)
+{
+ char tmp[MAILTMPLEN];
+ OM_uint32 smn;
+ gss_buffer_desc buf;
+ gss_name_t name;
+ /* make service name */
+ sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ mylocalhost ());
+ buf.length = strlen (buf.value = tmp);
+ /* see if can build a name */
+ if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&name) !=
+ GSS_S_COMPLETE) return NIL;
+ /* remove server method if no keytab */
+ if (!kerberos_server_valid ()) auth_gss.server = NIL;
+ gss_release_name (&smn,&name);/* finished with name */
+ return LONGT;
+}
+\f
+/* Client authenticator
+ * Accepts: challenger function
+ * responder function
+ * SASL service name
+ * parsed network mailbox structure
+ * stream argument for functions
+ * pointer to current trial count
+ * returned user name
+ * Returns: T if success, NIL otherwise, number of trials incremented if retry
+ */
+
+long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder,
+ char *service,NETMBX *mb,void *stream,
+ unsigned long *trial,char *user)
+{
+ gss_buffer_desc chal;
+ kinit_t ki = (kinit_t) mail_parameters (NIL,GET_KINIT,NIL);
+ long ret = NIL;
+ *trial = 65535; /* never retry */
+ /* get initial (empty) challenge */
+ if (chal.value = (*challenger) (stream,(unsigned long *) &chal.length)) {
+ if (chal.length) { /* abort if challenge non-empty */
+ mm_log ("Server bug: non-empty initial GSSAPI challenge",WARN);
+ (*responder) (stream,NIL,0);
+ ret = LONGT; /* will get a BAD response back */
+ }
+ else if (mb->authuser[0] && strcmp (mb->authuser,myusername ())) {
+ mm_log ("Can't use Kerberos: invalid /authuser",WARN);
+ (*responder) (stream,NIL,0);
+ ret = LONGT; /* will get a BAD response back */
+ }
+ else ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
+ stream,user,ki);
+ }
+ return ret;
+}
+\f
+/* Client authenticator worker function
+ * Accepts: challenger function
+ * responder function
+ * SASL service name
+ * parsed network mailbox structure
+ * stream argument for functions
+ * returned user name
+ * kinit function pointer if should retry with kinit
+ * Returns: T if success, NIL otherwise
+ */
+
+long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal,
+ authrespond_t responder,char *service,NETMBX *mb,
+ void *stream,char *user,kinit_t ki)
+{
+ char tmp[MAILTMPLEN];
+ OM_uint32 smj,smn,dsmj,dsmn;
+ OM_uint32 mctx = 0;
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+ gss_buffer_desc resp,buf;
+ long i;
+ int conf;
+ gss_qop_t qop;
+ gss_name_t crname = NIL;
+ blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
+ void *data;
+ long ret = NIL;
+ sprintf (tmp,"%s@%s",service,mb->host);
+ buf.length = strlen (buf.value = tmp);
+ /* get service name */
+ if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname) !=
+ GSS_S_COMPLETE) {
+ mm_log ("Can't import Kerberos service name",WARN);
+ (*responder) (stream,NIL,0);
+ }
+ else {
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ /* negotiate with KDC */
+ smj = gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,NIL,
+ GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG |
+ GSS_C_REPLAY_FLAG,0,GSS_C_NO_CHANNEL_BINDINGS,
+ GSS_C_NO_BUFFER,NIL,&resp,NIL,NIL);
+ (*bn) (BLOCK_NONSENSITIVE,data);
+\f
+ /* while continuation needed */
+ while (smj == GSS_S_CONTINUE_NEEDED) {
+ if (chal.value) fs_give ((void **) &chal.value);
+ /* send response, get next challenge */
+ i = (*responder) (stream,resp.value,resp.length) &&
+ (chal.value = (*challenger) (stream,(unsigned long *) &chal.length));
+ gss_release_buffer (&smn,&resp);
+ if (i) { /* negotiate continuation with KDC */
+ data = (*bn) (BLOCK_SENSITIVE,NIL);
+ switch (smj = /* make sure continuation going OK */
+ gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,
+ crname,GSS_C_NO_OID,GSS_C_INTEG_FLAG |
+ GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0,
+ GSS_C_NO_CHANNEL_BINDINGS,&chal,NIL,
+ &resp,NIL,NIL)) {
+ case GSS_S_CONTINUE_NEEDED:
+ case GSS_S_COMPLETE:
+ break;
+ default: /* error, don't need context any more */
+ gss_delete_sec_context (&smn,&ctx,NIL);
+ }
+ (*bn) (BLOCK_NONSENSITIVE,data);
+ }
+ else { /* error in continuation */
+ mm_log ("Error in negotiating Kerberos continuation",WARN);
+ (*responder) (stream,NIL,0);
+ /* don't need context any more */
+ gss_delete_sec_context (&smn,&ctx,NIL);
+ break;
+ }
+ }
+\f
+ switch (smj) { /* done - deal with final condition */
+ case GSS_S_COMPLETE:
+ if (chal.value) fs_give ((void **) &chal.value);
+ /* get prot mechanisms and max size */
+ if ((*responder) (stream,resp.value ? resp.value : "",resp.length) &&
+ (chal.value = (*challenger) (stream,(unsigned long *)&chal.length))&&
+ (gss_unwrap (&smn,ctx,&chal,&resp,&conf,&qop) == GSS_S_COMPLETE) &&
+ (resp.length >= 4) && (*((char *) resp.value) & AUTH_GSSAPI_P_NONE)){
+ /* make copy of flags and length */
+ memcpy (tmp,resp.value,4);
+ gss_release_buffer (&smn,&resp);
+ /* no session protection */
+ tmp[0] = AUTH_GSSAPI_P_NONE;
+ /* install user name */
+ strcpy (tmp+4,strcpy (user,mb->user[0] ? mb->user : myusername ()));
+ buf.value = tmp; buf.length = strlen (user) + 4;
+ /* successful negotiation */
+ switch (smj = gss_wrap (&smn,ctx,NIL,qop,&buf,&conf,&resp)) {
+ case GSS_S_COMPLETE:
+ if ((*responder) (stream,resp.value,resp.length)) ret = T;
+ gss_release_buffer (&smn,&resp);
+ break;
+ default:
+ do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ mctx = 0;
+ case GSS_S_CONTINUE_NEEDED:
+ sprintf (tmp,"Unknown gss_wrap failure: %s",(char *) resp.value);
+ mm_log (tmp,WARN);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value);
+ mm_log (tmp,WARN);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ (*responder) (stream,NIL,0);
+ }
+ }
+ /* flush final challenge */
+ if (chal.value) fs_give ((void **) &chal.value);
+ /* don't need context any more */
+ gss_delete_sec_context (&smn,&ctx,NIL);
+ break;
+\f
+ case GSS_S_CREDENTIALS_EXPIRED:
+ if (chal.value) fs_give ((void **) &chal.value);
+ /* retry if application kinits */
+ if (ki && (*ki) (mb->host,"Kerberos credentials expired"))
+ ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
+ stream,user,NIL);
+ else { /* application can't kinit */
+ sprintf (tmp,"Kerberos credentials expired (try running kinit) for %s",
+ mb->host);
+ mm_log (tmp,WARN);
+ (*responder) (stream,NIL,0);
+ }
+ break;
+ case GSS_S_FAILURE:
+ if (chal.value) fs_give ((void **) &chal.value);
+ do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE: /* end of message, can kinit? */
+ if (ki && kerberos_try_kinit (smn) &&
+ (*ki) (mb->host,(char *) resp.value)) {
+ gss_release_buffer (&dsmn,&resp);
+ ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
+ stream,user,NIL);
+ break; /* done */
+ }
+ else (*responder) (stream,NIL,0);
+ case GSS_S_CONTINUE_NEEDED:
+ sprintf (tmp,kerberos_try_kinit (smn) ?
+ "Kerberos error: %.80s (try running kinit) for %.80s" :
+ "GSSAPI failure: %s for %.80s",(char *) resp.value,mb->host);
+ mm_log (tmp,WARN);
+ gss_release_buffer (&dsmn,&resp);
+ } while (dsmj == GSS_S_CONTINUE_NEEDED);
+ break;
+\f
+ default: /* miscellaneous errors */
+ if (chal.value) fs_give ((void **) &chal.value);
+ do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ mctx = 0;
+ case GSS_S_CONTINUE_NEEDED:
+ sprintf (tmp,"Unknown GSSAPI failure: %s",(char *) resp.value);
+ mm_log (tmp,WARN);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value);
+ mm_log (tmp,WARN);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ (*responder) (stream,NIL,0);
+ break;
+ }
+ /* finished with credentials name */
+ if (crname) gss_release_name (&smn,&crname);
+ }
+ return ret; /* return status */
+}
+\f
+/* Server authenticator
+ * Accepts: responder function
+ * argument count
+ * argument vector
+ * Returns: authenticated user name or NIL
+ */
+
+char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[])
+{
+ char *ret = NIL;
+ char tmp[MAILTMPLEN];
+ unsigned long maxsize = htonl (AUTH_GSSAPI_C_MAXSIZE);
+ int conf;
+ OM_uint32 smj,smn,dsmj,dsmn,flags;
+ OM_uint32 mctx = 0;
+ gss_name_t crname,name;
+ gss_OID mech;
+ gss_buffer_desc chal,resp,buf;
+ gss_cred_id_t crd;
+ gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+ gss_qop_t qop = GSS_C_QOP_DEFAULT;
+ /* make service name */
+ sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
+ tcp_serverhost ());
+ buf.length = strlen (buf.value = tmp);
+ /* acquire credentials */
+ if ((gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname)) ==
+ GSS_S_COMPLETE) {
+ if ((smj = gss_acquire_cred (&smn,crname,0,NIL,GSS_C_ACCEPT,&crd,NIL,NIL))
+ == GSS_S_COMPLETE) {
+ if (resp.value = (*responder) ("",0,(unsigned long *) &resp.length)) {
+ do { /* negotiate authentication */
+ smj = gss_accept_sec_context (&smn,&ctx,crd,&resp,
+ GSS_C_NO_CHANNEL_BINDINGS,&name,&mech,
+ &chal,&flags,NIL,NIL);
+ /* don't need response any more */
+ fs_give ((void **) &resp.value);
+ switch (smj) { /* how did it go? */
+ case GSS_S_COMPLETE: /* successful */
+ case GSS_S_CONTINUE_NEEDED:
+ if (chal.value) { /* send challenge, get next response */
+ resp.value = (*responder) (chal.value,chal.length,
+ (unsigned long *) &resp.length);
+ gss_release_buffer (&smn,&chal);
+ }
+ break;
+ }
+ }
+ while (resp.value && resp.length && (smj == GSS_S_CONTINUE_NEEDED));
+\f
+ /* successful exchange? */
+ if ((smj == GSS_S_COMPLETE) &&
+ (gss_display_name (&smn,name,&buf,&mech) == GSS_S_COMPLETE)) {
+ /* send security and size */
+ memcpy (resp.value = tmp,(void *) &maxsize,resp.length = 4);
+ tmp[0] = AUTH_GSSAPI_P_NONE;
+ if (gss_wrap (&smn,ctx,NIL,qop,&resp,&conf,&chal) == GSS_S_COMPLETE){
+ resp.value = (*responder) (chal.value,chal.length,
+ (unsigned long *) &resp.length);
+ gss_release_buffer (&smn,&chal);
+ if (gss_unwrap (&smn,ctx,&resp,&chal,&conf,&qop)==GSS_S_COMPLETE) {
+ /* client request valid */
+ if (chal.value && (chal.length > 4) &&
+ (chal.length < (MAILTMPLEN - 1)) &&
+ memcpy (tmp,chal.value,chal.length) &&
+ (tmp[0] & AUTH_GSSAPI_P_NONE)) {
+ /* tie off authorization ID */
+ tmp[chal.length] = '\0';
+ ret = kerberos_login (tmp+4,buf.value,argc,argv);
+ }
+ /* done with user name */
+ gss_release_buffer (&smn,&chal);
+ }
+ /* finished with response */
+ fs_give ((void **) &resp.value);
+ }
+ /* don't need name buffer any more */
+ gss_release_buffer (&smn,&buf);
+ }
+ /* don't need client name any more */
+ gss_release_name (&smn,&name);
+ /* don't need context any more */
+ if (ctx != GSS_C_NO_CONTEXT) gss_delete_sec_context (&smn,&ctx,NIL);
+ }
+ /* finished with credentials */
+ gss_release_cred (&smn,&crd);
+ }
+\f
+ else { /* can't acquire credentials! */
+ if (gss_display_name (&dsmn,crname,&buf,&mech) == GSS_S_COMPLETE)
+ SERVER_LOG ("Failed to acquire credentials for %s",buf.value);
+ if (smj != GSS_S_FAILURE) do
+ switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ mctx = 0;
+ case GSS_S_CONTINUE_NEEDED:
+ SERVER_LOG ("Unknown GSSAPI failure: %s",resp.value);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
+ GSS_C_NO_OID,&mctx,&resp)) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ SERVER_LOG ("GSSAPI mechanism status: %s",resp.value);
+ gss_release_buffer (&dsmn,&resp);
+ }
+ while (dsmj == GSS_S_CONTINUE_NEEDED);
+ }
+ /* finished with credentials name */
+ gss_release_name (&smn,&crname);
+ }
+ return ret; /* return status */
+}