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