* New version 2.26
[alpine.git] / imap / src / c-client / auth_gss.c
blob0a3cfad478d82b126612fa819281a46ca300748a
1 /* ========================================================================
2 * Copyright 2020-2022 Eduardo Chappa
3 * Copyright 1988-2006 University of Washington
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
16 * Program: GSSAPI authenticator
18 * Author: Mark Crispin
19 * Networks and Distributed Computing
20 * Computing & Communications
21 * University of Washington
22 * Administration Building, AG-44
23 * Seattle, WA 98195
24 * Internet: MRC@CAC.Washington.EDU
26 * Date: 12 January 1998
27 * Last Edited: 30 August 2006
31 long auth_gssapi_valid (void);
32 long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder, char *base,
33 char *service,NETMBX *mb,void *stream, unsigned long port,
34 unsigned long *trial,char *user);
35 long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal,
36 authrespond_t responder,char *service,NETMBX *mb,
37 void *stream,char *user,kinit_t ki);
38 char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[]);
41 AUTHENTICATOR auth_gss = {
42 AU_SECURE | AU_AUTHUSER, /* secure authenticator */
43 "GSSAPI", /* authenticator name */
44 auth_gssapi_valid, /* check if valid */
45 auth_gssapi_client, /* client method */
46 auth_gssapi_server, /* server method */
47 NIL /* next authenticator */
50 #define AUTH_GSSAPI_P_NONE 1
51 #define AUTH_GSSAPI_P_INTEGRITY 2
52 #define AUTH_GSSAPI_P_PRIVACY 4
54 #define AUTH_GSSAPI_C_MAXSIZE 8192
56 #define SERVER_LOG(x,y) syslog (LOG_ALERT,x,y)
58 /* Check if GSSAPI valid on this system
59 * Returns: T if valid, NIL otherwise
62 long auth_gssapi_valid (void)
64 char tmp[MAILTMPLEN];
65 OM_uint32 smn;
66 gss_buffer_desc buf;
67 gss_name_t name;
68 /* make service name */
69 sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
70 mylocalhost ());
71 buf.length = strlen (buf.value = tmp);
72 /* see if can build a name */
73 if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&name) !=
74 GSS_S_COMPLETE) return NIL;
75 /* remove server method if no keytab */
76 if (!kerberos_server_valid ()) auth_gss.server = NIL;
77 gss_release_name (&smn,&name);/* finished with name */
78 return LONGT;
81 /* Client authenticator
82 * Accepts: challenger function
83 * responder function
84 * SASL service name
85 * parsed network mailbox structure
86 * stream argument for functions
87 * pointer to current trial count
88 * returned user name
89 * Returns: T if success, NIL otherwise, number of trials incremented if retry
92 long auth_gssapi_client (authchallenge_t challenger,authrespond_t
93 responder,char *base,
94 char *service,NETMBX *mb,void *stream,unsigned long port,
95 unsigned long *trial,char *user)
97 gss_buffer_desc chal;
98 kinit_t ki = (kinit_t) mail_parameters (NIL,GET_KINIT,NIL);
99 long ret = NIL;
100 *trial = 65535; /* never retry */
101 /* get initial (empty) challenge */
102 if ((chal.value = (*challenger) (stream,(unsigned long *) &chal.length)) != NULL) {
103 #if 0 /* ignore non-empty challenge */
104 if (chal.length) { /* abort if challenge non-empty */
105 mm_log ("Server bug: non-empty initial GSSAPI challenge",WARN);
106 (*responder) (stream,NIL,NIL,0);
107 ret = LONGT; /* will get a BAD response back */
109 else
110 #endif /* if 0 */
111 if (mb->authuser[0] && strcmp (mb->authuser,myusername ())) {
112 mm_log ("Can't use Kerberos: invalid /authuser",WARN);
113 (*responder) (stream,NIL,NIL,0);
114 ret = LONGT; /* will get a BAD response back */
116 else ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
117 stream,user,ki);
119 return ret;
122 /* Client authenticator worker function
123 * Accepts: challenger function
124 * responder function
125 * SASL service name
126 * parsed network mailbox structure
127 * stream argument for functions
128 * returned user name
129 * kinit function pointer if should retry with kinit
130 * Returns: T if success, NIL otherwise
133 long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal,
134 authrespond_t responder,char *service,NETMBX *mb,
135 void *stream,char *user,kinit_t ki)
137 char tmp[MAILTMPLEN];
138 OM_uint32 smj,smn,dsmj,dsmn;
139 OM_uint32 mctx = 0;
140 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
141 gss_buffer_desc resp,buf;
142 long i;
143 int conf;
144 gss_qop_t qop;
145 gss_name_t crname = NIL;
146 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
147 void *data;
148 long ret = NIL;
149 sprintf (tmp,"%s@%s",service,mb->host);
150 buf.length = strlen (buf.value = tmp);
151 /* get service name */
152 if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname) !=
153 GSS_S_COMPLETE) {
154 mm_log ("Can't import Kerberos service name",WARN);
155 (*responder) (stream,NIL,NIL,0);
157 else {
158 data = (*bn) (BLOCK_SENSITIVE,NIL);
159 /* negotiate with KDC */
160 smj = gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,NIL,
161 GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG |
162 GSS_C_REPLAY_FLAG,0,GSS_C_NO_CHANNEL_BINDINGS,
163 GSS_C_NO_BUFFER,NIL,&resp,NIL,NIL);
164 (*bn) (BLOCK_NONSENSITIVE,data);
166 /* while continuation needed */
167 while (smj == GSS_S_CONTINUE_NEEDED) {
168 if (chal.value) fs_give ((void **) &chal.value);
169 /* send response, get next challenge */
170 i = (*responder) (stream,NIL,resp.value,resp.length) &&
171 (chal.value = (*challenger) (stream,(unsigned long *) &chal.length));
172 gss_release_buffer (&smn,&resp);
173 if (i) { /* negotiate continuation with KDC */
174 data = (*bn) (BLOCK_SENSITIVE,NIL);
175 switch (smj = /* make sure continuation going OK */
176 gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,
177 crname,GSS_C_NO_OID,GSS_C_INTEG_FLAG |
178 GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0,
179 GSS_C_NO_CHANNEL_BINDINGS,&chal,NIL,
180 &resp,NIL,NIL)) {
181 case GSS_S_CONTINUE_NEEDED:
182 case GSS_S_COMPLETE:
183 break;
184 default: /* error, don't need context any more */
185 gss_delete_sec_context (&smn,&ctx,NIL);
187 (*bn) (BLOCK_NONSENSITIVE,data);
189 else { /* error in continuation */
190 mm_log ("Error in negotiating Kerberos continuation",WARN);
191 (*responder) (stream,NIL,NIL,0);
192 /* don't need context any more */
193 gss_delete_sec_context (&smn,&ctx,NIL);
194 break;
198 switch (smj) { /* done - deal with final condition */
199 case GSS_S_COMPLETE:
200 if (chal.value) fs_give ((void **) &chal.value);
201 /* get prot mechanisms and max size */
202 if ((*responder) (stream,NIL,resp.value ? resp.value : "",resp.length) &&
203 (chal.value = (*challenger) (stream,(unsigned long *)&chal.length))&&
204 (gss_unwrap (&smn,ctx,&chal,&resp,&conf,&qop) == GSS_S_COMPLETE) &&
205 (resp.length >= 4) && (*((char *) resp.value) & AUTH_GSSAPI_P_NONE)){
206 /* make copy of flags and length */
207 memcpy (tmp,resp.value,4);
208 gss_release_buffer (&smn,&resp);
209 /* no session protection */
210 tmp[0] = AUTH_GSSAPI_P_NONE;
211 /* install user name */
212 strcpy (tmp+4,strcpy (user,mb->user[0] ? mb->user : myusername ()));
213 buf.value = tmp; buf.length = strlen (user) + 4;
214 /* successful negotiation */
215 switch (smj = gss_wrap (&smn,ctx,NIL,qop,&buf,&conf,&resp)) {
216 case GSS_S_COMPLETE:
217 if ((*responder) (stream,NIL,resp.value,resp.length)) ret = T;
218 gss_release_buffer (&smn,&resp);
219 break;
220 default:
221 do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
222 GSS_C_NO_OID,&mctx,&resp)) {
223 case GSS_S_COMPLETE:
224 mctx = 0;
225 case GSS_S_CONTINUE_NEEDED:
226 sprintf (tmp,"Unknown gss_wrap failure: %s",(char *) resp.value);
227 mm_log (tmp,WARN);
228 gss_release_buffer (&dsmn,&resp);
230 while (dsmj == GSS_S_CONTINUE_NEEDED);
231 do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
232 GSS_C_NO_OID,&mctx,&resp)) {
233 case GSS_S_COMPLETE:
234 case GSS_S_CONTINUE_NEEDED:
235 sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value);
236 mm_log (tmp,WARN);
237 gss_release_buffer (&dsmn,&resp);
239 while (dsmj == GSS_S_CONTINUE_NEEDED);
240 (*responder) (stream,NIL,NIL,0);
243 /* flush final challenge */
244 if (chal.value) fs_give ((void **) &chal.value);
245 /* don't need context any more */
246 gss_delete_sec_context (&smn,&ctx,NIL);
247 break;
249 case GSS_S_CREDENTIALS_EXPIRED:
250 if (chal.value) fs_give ((void **) &chal.value);
251 /* retry if application kinits */
252 if (ki && (*ki) (mb->host,"Kerberos credentials expired"))
253 ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
254 stream,user,NIL);
255 else { /* application can't kinit */
256 sprintf (tmp,"Kerberos credentials expired (try running kinit) for %s",
257 mb->host);
258 mm_log (tmp,WARN);
259 (*responder) (stream,NIL,NIL,0);
261 break;
262 case GSS_S_FAILURE:
263 if (chal.value) fs_give ((void **) &chal.value);
264 do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
265 GSS_C_NO_OID,&mctx,&resp)) {
266 case GSS_S_COMPLETE: /* end of message, can kinit? */
267 if (ki && kerberos_try_kinit (smn) &&
268 (*ki) (mb->host,(char *) resp.value)) {
269 gss_release_buffer (&dsmn,&resp);
270 ret = auth_gssapi_client_work (challenger,chal,responder,service,mb,
271 stream,user,NIL);
272 break; /* done */
274 else (*responder) (stream,NIL,NIL,0);
275 case GSS_S_CONTINUE_NEEDED:
276 sprintf (tmp,kerberos_try_kinit (smn) ?
277 "Kerberos error: %.80s (try running kinit) for %.80s" :
278 "GSSAPI failure: %s for %.80s",(char *) resp.value,mb->host);
279 mm_log (tmp,WARN);
280 gss_release_buffer (&dsmn,&resp);
281 } while (dsmj == GSS_S_CONTINUE_NEEDED);
282 break;
284 default: /* miscellaneous errors */
285 if (chal.value) fs_give ((void **) &chal.value);
286 do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
287 GSS_C_NO_OID,&mctx,&resp)) {
288 case GSS_S_COMPLETE:
289 mctx = 0;
290 case GSS_S_CONTINUE_NEEDED:
291 sprintf (tmp,"Unknown GSSAPI failure: %s",(char *) resp.value);
292 mm_log (tmp,WARN);
293 gss_release_buffer (&dsmn,&resp);
295 while (dsmj == GSS_S_CONTINUE_NEEDED);
296 do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
297 GSS_C_NO_OID,&mctx,&resp)) {
298 case GSS_S_COMPLETE:
299 case GSS_S_CONTINUE_NEEDED:
300 sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value);
301 mm_log (tmp,WARN);
302 gss_release_buffer (&dsmn,&resp);
304 while (dsmj == GSS_S_CONTINUE_NEEDED);
305 (*responder) (stream,NIL,NIL,0);
306 break;
308 /* finished with credentials name */
309 if (crname) gss_release_name (&smn,&crname);
311 return ret; /* return status */
314 /* Server authenticator
315 * Accepts: responder function
316 * argument count
317 * argument vector
318 * Returns: authenticated user name or NIL
321 char *auth_gssapi_server (authresponse_t responder,int argc,char *argv[])
323 char *ret = NIL;
324 char tmp[MAILTMPLEN];
325 unsigned long maxsize = htonl (AUTH_GSSAPI_C_MAXSIZE);
326 int conf;
327 OM_uint32 smj,smn,dsmj,dsmn,flags;
328 OM_uint32 mctx = 0;
329 gss_name_t crname,name;
330 gss_OID mech;
331 gss_buffer_desc chal,resp,buf;
332 gss_cred_id_t crd;
333 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
334 gss_qop_t qop = GSS_C_QOP_DEFAULT;
335 /* make service name */
336 sprintf (tmp,"%s@%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
337 tcp_serverhost ());
338 buf.length = strlen (buf.value = tmp);
339 /* acquire credentials */
340 if ((gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname)) ==
341 GSS_S_COMPLETE) {
342 if ((smj = gss_acquire_cred (&smn,crname,0,NIL,GSS_C_ACCEPT,&crd,NIL,NIL))
343 == GSS_S_COMPLETE) {
344 if ((resp.value = (*responder) ("",0,(unsigned long *) &resp.length)) != NULL) {
345 do { /* negotiate authentication */
346 smj = gss_accept_sec_context (&smn,&ctx,crd,&resp,
347 GSS_C_NO_CHANNEL_BINDINGS,&name,&mech,
348 &chal,&flags,NIL,NIL);
349 /* don't need response any more */
350 fs_give ((void **) &resp.value);
351 switch (smj) { /* how did it go? */
352 case GSS_S_COMPLETE: /* successful */
353 case GSS_S_CONTINUE_NEEDED:
354 if (chal.value) { /* send challenge, get next response */
355 resp.value = (*responder) (chal.value,chal.length,
356 (unsigned long *) &resp.length);
357 gss_release_buffer (&smn,&chal);
359 break;
362 while (resp.value && resp.length && (smj == GSS_S_CONTINUE_NEEDED));
364 /* successful exchange? */
365 if ((smj == GSS_S_COMPLETE) &&
366 (gss_display_name (&smn,name,&buf,&mech) == GSS_S_COMPLETE)) {
367 /* send security and size */
368 memcpy (resp.value = tmp,(void *) &maxsize,resp.length = 4);
369 tmp[0] = AUTH_GSSAPI_P_NONE;
370 if (gss_wrap (&smn,ctx,NIL,qop,&resp,&conf,&chal) == GSS_S_COMPLETE){
371 resp.value = (*responder) (chal.value,chal.length,
372 (unsigned long *) &resp.length);
373 gss_release_buffer (&smn,&chal);
374 if (gss_unwrap (&smn,ctx,&resp,&chal,&conf,&qop)==GSS_S_COMPLETE) {
375 /* client request valid */
376 if (chal.value && (chal.length > 4) &&
377 (chal.length < (MAILTMPLEN - 1)) &&
378 memcpy (tmp,chal.value,chal.length) &&
379 (tmp[0] & AUTH_GSSAPI_P_NONE)) {
380 /* tie off authorization ID */
381 tmp[chal.length] = '\0';
382 ret = kerberos_login (tmp+4,buf.value,argc,argv);
384 /* done with user name */
385 gss_release_buffer (&smn,&chal);
387 /* finished with response */
388 fs_give ((void **) &resp.value);
390 /* don't need name buffer any more */
391 gss_release_buffer (&smn,&buf);
393 /* don't need client name any more */
394 gss_release_name (&smn,&name);
395 /* don't need context any more */
396 if (ctx != GSS_C_NO_CONTEXT) gss_delete_sec_context (&smn,&ctx,NIL);
398 /* finished with credentials */
399 gss_release_cred (&smn,&crd);
402 else { /* can't acquire credentials! */
403 if (gss_display_name (&dsmn,crname,&buf,&mech) == GSS_S_COMPLETE)
404 SERVER_LOG ("Failed to acquire credentials for %s",(char *) buf.value);
405 if (smj != GSS_S_FAILURE) do
406 switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE,
407 GSS_C_NO_OID,&mctx,&resp)) {
408 case GSS_S_COMPLETE:
409 mctx = 0;
410 case GSS_S_CONTINUE_NEEDED:
411 SERVER_LOG ("Unknown GSSAPI failure: %s",(char *)resp.value);
412 gss_release_buffer (&dsmn,&resp);
414 while (dsmj == GSS_S_CONTINUE_NEEDED);
415 do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE,
416 GSS_C_NO_OID,&mctx,&resp)) {
417 case GSS_S_COMPLETE:
418 case GSS_S_CONTINUE_NEEDED:
419 SERVER_LOG ("GSSAPI mechanism status: %s",(char *)resp.value);
420 gss_release_buffer (&dsmn,&resp);
422 while (dsmj == GSS_S_CONTINUE_NEEDED);
424 /* finished with credentials name */
425 gss_release_name (&smn,&crname);
427 return ret; /* return status */