2 * $Id: uams_gss.c,v 1.2.2.3 2004-06-15 00:35:06 bfernhomberg Exp $
\r
4 * Copyright (c) 1990,1993 Regents of The University of Michigan.
\r
5 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
\r
6 * Copyright (c) 2003 The Reed Institute
\r
7 * All Rights Reserved. See COPYRIGHT.
\r
10 #ifdef HAVE_CONFIG_H
\r
12 #endif /* HAVE_CONFIG_H */
\r
17 #ifdef HAVE_UNISTD_H
\r
19 #endif /* HAVE_UNISTD_H */
\r
24 #else /* STDC_HEADERS */
\r
26 #define strchr index
\r
27 #define strrchr index
\r
28 #endif /* HAVE_STRCHR */
\r
29 char *strchr (), *strrchr ();
\r
31 #define memcpy(d,s,n) bcopy ((s), (d), (n))
\r
32 #define memmove(d,s,n) bcopy ((s), (d), (n))
\r
33 #endif /* ! HAVE_MEMCPY */
\r
34 #endif /* STDC_HEADERS */
\r
37 #include <atalk/logger.h>
\r
38 #include <atalk/afp.h>
\r
39 #include <atalk/uam.h>
\r
41 /* Kerberos includes */
\r
47 #if HAVE_GSSAPI_GSSAPI_H
\r
48 #include <gssapi/gssapi.h>
\r
51 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
\r
52 #include <gssapi/gssapi_generic.h>
\r
55 #if HAVE_GSSAPI_GSSAPI_KRB5_H
\r
56 #include <gssapi/gssapi_krb5.h>
\r
60 #include <com_err.h>
\r
63 /* We work around something I don't entirely understand... */
\r
64 /* BF: This is a Heimdal/MIT compatibility fix */
\r
65 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
\r
66 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
\r
69 #define MIN(a, b) ((a > b) ? b : a)
\r
71 static void log_status( char *s, OM_uint32 major_status,
\r
72 OM_uint32 minor_status )
\r
74 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
\r
75 OM_uint32 min_status, maj_status;
\r
76 OM_uint32 maj_ctx = 0, min_ctx = 0;
\r
79 maj_status = gss_display_status( &min_status, major_status,
\r
80 GSS_C_GSS_CODE, GSS_C_NULL_OID,
\r
82 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
\r
83 (int)msg.length, msg.value, strerror(errno));
\r
84 gss_release_buffer(&min_status, &msg);
\r
90 maj_status = gss_display_status( &min_status, minor_status,
\r
91 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
\r
93 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
\r
94 (int)msg.length, msg.value, strerror(errno));
\r
95 gss_release_buffer(&min_status, &msg);
\r
103 void log_ctx_flags( OM_uint32 flags )
\r
105 if (flags & GSS_C_DELEG_FLAG)
\r
106 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
\r
107 if (flags & GSS_C_MUTUAL_FLAG)
\r
108 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
\r
109 if (flags & GSS_C_REPLAY_FLAG)
\r
110 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
\r
111 if (flags & GSS_C_SEQUENCE_FLAG)
\r
112 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
\r
113 if (flags & GSS_C_CONF_FLAG)
\r
114 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
\r
115 if (flags & GSS_C_INTEG_FLAG)
\r
116 LOG(log_info, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
\r
119 /* return 0 on success */
\r
120 static int do_gss_auth( char *service, char *ibuf, int ticket_len,
\r
121 char *rbuf, int *rbuflen, char *username, int ulen,
\r
122 struct session_info *sinfo )
\r
124 OM_uint32 major_status = 0, minor_status = 0;
\r
125 gss_name_t server_name;
\r
126 gss_cred_id_t server_creds;
\r
127 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
\r
128 gss_buffer_desc ticket_buffer, authenticator_buff, sesskey_buff, wrap_buff;
\r
129 gss_name_t client_name;
\r
130 OM_uint32 ret_flags;
\r
133 * princ specifies the principal to be used.
\r
134 * The user specifies it when configuring afpd anyway,
\r
135 * we should be able to detect it.
\r
137 gss_buffer_desc s_princ_buffer;
\r
139 s_princ_buffer.value = service;
\r
140 s_princ_buffer.length = strlen( service ) + 1;
\r
143 * gss_import_name (need a way to get this from afpd)
\r
144 * gss_acquire_credential
\r
145 * gss_accept_sec_context
\r
148 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: importing name" );
\r
149 major_status = gss_import_name( &minor_status,
\r
151 GSS_C_NT_HOSTBASED_SERVICE,
\r
153 if (major_status != GSS_S_COMPLETE) {
\r
154 log_status( "import_name", major_status, minor_status );
\r
159 LOG(log_debug, logtype_uams,
\r
160 "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)",
\r
161 (int)geteuid(), getenv( "KRB5_KTNAME") );
\r
163 major_status = gss_acquire_cred( &minor_status, server_name,
\r
164 GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
\r
165 &server_creds, NULL, NULL );
\r
166 if (major_status != GSS_S_COMPLETE) {
\r
167 log_status( "acquire_cred", major_status, minor_status );
\r
172 /* The GSSAPI docs say that this should be done in a big "do" loop,
\r
173 * but Apple's implementation doesn't seem to support this behavior.
\r
175 ticket_buffer.length = ticket_len;
\r
176 ticket_buffer.value = ibuf;
\r
177 authenticator_buff.length = 0;
\r
178 authenticator_buff.value = NULL;
\r
179 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: accepting context (ticketlen: %u, value: %X)", ticket_buffer.length, ticket_buffer.value );
\r
180 major_status = gss_accept_sec_context( &minor_status, &context_handle,
\r
181 server_creds, &ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
\r
182 &client_name, NULL, &authenticator_buff,
\r
183 &ret_flags, NULL, NULL );
\r
185 if (major_status == GSS_S_COMPLETE) {
\r
186 gss_buffer_desc client_name_buffer;
\r
189 log_ctx_flags( ret_flags );
\r
191 /* use gss_display_name on client_name */
\r
192 major_status = gss_display_name( &minor_status, client_name,
\r
193 &client_name_buffer, (gss_OID *)NULL );
\r
194 if (major_status == GSS_S_COMPLETE) {
\r
196 sesskey_buff.value = sinfo->sessionkey;
\r
197 sesskey_buff.length = sinfo->sessionkey_len;
\r
199 gss_wrap (&minor_status, context_handle, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
\r
200 if ( minor_status == GSS_S_COMPLETE) {
\r
201 sinfo->cryptedkey = malloc ( wrap_buff.length );
\r
202 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
\r
203 sinfo->cryptedkey_len = wrap_buff.length;
\r
206 log_status( "GSS wrap", major_status, minor_status );
\r
209 if (wrap_buff.value)
\r
210 gss_release_buffer( &minor_status, &wrap_buff );
\r
212 u_int16_t auth_len = htons( authenticator_buff.length );
\r
213 /* save the username... note that doing it this way is
\r
214 * not the best idea: if a principal is truncated, a user could be
\r
217 memcpy( username, client_name_buffer.value,
\r
218 MIN(client_name_buffer.length, ulen - 1));
\r
219 username[MIN(client_name_buffer.length, ulen - 1)] = 0;
\r
221 LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: user is %s!", username );
\r
222 /* copy the authenticator length into the reply buffer */
\r
223 memcpy( rbuf, &auth_len, sizeof(auth_len) );
\r
224 *rbuflen += sizeof(auth_len), rbuf += sizeof(auth_len);
\r
226 /* copy the authenticator value into the reply buffer */
\r
227 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
\r
228 *rbuflen += authenticator_buff.length;
\r
230 gss_release_buffer( &minor_status, &client_name_buffer );
\r
232 log_status( "display_name", major_status, minor_status );
\r
239 /* Clean up after ourselves */
\r
240 gss_release_name( &minor_status, &client_name );
\r
242 /* This will SIGSEGV, as it would free ibuf */
\r
243 /*gss_release_buffer( &minor_status, &ticket_buffer );*/
\r
245 if ( authenticator_buff.value)
\r
246 gss_release_buffer( &minor_status, &authenticator_buff );
\r
248 gss_delete_sec_context( &minor_status,
\r
249 &context_handle, NULL );
\r
251 log_status( "accept_sec_context", major_status, minor_status );
\r
254 gss_release_cred( &minor_status, &server_creds );
\r
257 gss_release_name( &minor_status, &server_name );
\r
262 /* -------------------------- */
\r
263 static int gss_login(void *obj, struct passwd **uam_pwd,
\r
264 char *ibuf, int ibuflen,
\r
265 char *rbuf, int *rbuflen)
\r
272 /* The reply contains a two-byte ID value - note
\r
273 * that Apple's implementation seems to always return 1 as well
\r
275 temp16 = htons( 1 );
\r
276 memcpy(rbuf, &temp16, sizeof(temp16));
\r
277 *rbuflen += sizeof(temp16);
\r
278 return AFPERR_AUTHCONT;
\r
281 static int gss_logincont(void *obj, struct passwd **uam_pwd,
\r
282 char *ibuf, int ibuflen,
\r
283 char *rbuf, int *rbuflen)
\r
285 struct passwd *pwd = NULL;
\r
286 u_int16_t login_id;
\r
288 u_int16_t ticket_len;
\r
292 int userlen, servicelen;
\r
293 struct session_info *sinfo;
\r
295 /* Apple's AFP 3.1 documentation specifies that this command
\r
296 * takes the following format:
\r
298 * id returned in LoginExt response (u_int16_t)
\r
299 * username (format unspecified) padded, when necessary, to end on an even boundary
\r
300 * ticket length (u_int16_t)
\r
304 /* Observation of AFP clients in the wild indicate that the actual
\r
305 * format of this request is as follows:
\r
306 * pad (byte) [consumed before login_ext is called]
\r
307 * ?? (byte) - always observed to be 0
\r
308 * id returned in LoginExt response (u_int16_t)
\r
309 * username, encoding unspecified, null terminated C string,
\r
310 * padded when the terminating null is an even numbered byte.
\r
311 * The packet is formated such that the username begins on an
\r
312 * odd numbered byte. Eg if the username is 3 characters and the
\r
313 * terminating null makes 4, expect to pad the the result.
\r
314 * The encoding of this string is unknown.
\r
315 * ticket length (u_int16_t)
\r
319 rblen = *rbuflen = 0;
\r
321 ibuf++, ibuflen--; /* ?? */
\r
323 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
\r
324 memcpy( &login_id, ibuf, sizeof(login_id) );
\r
325 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
\r
326 login_id = ntohs( login_id );
\r
328 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
\r
329 return AFPERR_MISC;
\r
331 if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
\r
332 return AFPERR_MISC;
\r
334 if (service == NULL)
\r
335 return AFPERR_MISC;
\r
337 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
\r
338 return AFPERR_MISC;
\r
340 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
\r
341 LOG(log_info, logtype_uams, "internal error: sessionkey not set");
\r
342 return AFPERR_PARAM;
\r
345 /* We skip past the 'username' parameter because all that matters is the ticket */
\r
347 while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
\r
349 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
\r
350 return AFPERR_PARAM;
\r
353 ibuf++, ibuflen--; /* null termination */
\r
355 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
\r
357 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
\r
359 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
\r
360 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
\r
361 ticket_len = ntohs( ticket_len );
\r
363 if (ticket_len > ibuflen) {
\r
364 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: invalid ticket length");
\r
365 return AFPERR_PARAM;
\r
368 if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
\r
369 char *at = strchr( username, '@' );
\r
371 // Chop off the realm name
\r
374 if((pwd = uam_getname( obj, username, userlen )) == NULL) {
\r
375 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
\r
376 return AFPERR_PARAM;
\r
378 if (uam_checkuser(pwd) < 0) {
\r
379 LOG(log_info, logtype_uams, "%s not a valid user", username);
\r
380 return AFPERR_NOTAUTH;
\r
386 LOG(log_info, logtype_uams, "do_gss_auth failed" );
\r
388 return AFPERR_MISC;
\r
393 * For the krb5 uam, this function only needs to return a two-byte
\r
394 * login-session id. None of the data provided by the client up to this
\r
395 * point is trustworthy as we'll have a signed ticket to parse in logincont.
\r
397 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
\r
398 char *ibuf, int ibuflen,
\r
399 char *rbuf, int *rbuflen)
\r
405 /* The reply contains a two-byte ID value - note
\r
406 * that Apple's implementation seems to always return 1 as well
\r
408 temp16 = htons( 1 );
\r
409 memcpy(rbuf, &temp16, sizeof(temp16));
\r
410 *rbuflen += sizeof(temp16);
\r
411 return AFPERR_AUTHCONT;
\r
415 static void gss_logout() {
\r
418 int uam_setup(const char *path)
\r
420 if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
\r
421 gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
\r
422 if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
\r
423 gss_login, gss_logincont, gss_logout) < 0)
\r
429 static void uam_cleanup(void)
\r
431 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
\r
434 UAM_MODULE_EXPORT struct uam_export uams_gss = {
\r
436 UAM_MODULE_VERSION,
\r
437 uam_setup, uam_cleanup
\r