2 * Copyright (c) 1990,1993 Regents of The University of Michigan.
3 * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu)
4 * Copyright (c) 2003 The Reed Institute
5 * Copyright (c) 2004 Bjoern Fernhomberg
6 * All Rights Reserved. See COPYRIGHT.
11 #endif /* HAVE_CONFIG_H */
16 #include <arpa/inet.h>
18 #include <atalk/logger.h>
19 #include <atalk/afp.h>
20 #include <atalk/uam.h>
21 #include <atalk/util.h>
22 #include <atalk/compat.h>
24 /* Kerberos includes */
25 #ifdef HAVE_GSSAPI_GSSAPI_H
26 #include <gssapi/gssapi.h>
29 #endif // HAVE_GSSAPI_GSSAPI_H
31 static void log_status( char *s, OM_uint32 major_status,
32 OM_uint32 minor_status )
34 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
35 OM_uint32 min_status, maj_status;
36 OM_uint32 maj_ctx = 0, min_ctx = 0;
39 maj_status = gss_display_status( &min_status, major_status,
40 GSS_C_GSS_CODE, GSS_C_NULL_OID,
42 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
43 (int)msg.length, msg.value, strerror(errno));
44 gss_release_buffer(&min_status, &msg);
50 maj_status = gss_display_status( &min_status, minor_status,
51 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
53 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
54 (int)msg.length, msg.value, strerror(errno));
55 gss_release_buffer(&min_status, &msg);
63 static void log_ctx_flags( OM_uint32 flags )
66 if (flags & GSS_C_DELEG_FLAG)
67 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
68 if (flags & GSS_C_MUTUAL_FLAG)
69 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
70 if (flags & GSS_C_REPLAY_FLAG)
71 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
72 if (flags & GSS_C_SEQUENCE_FLAG)
73 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
74 if (flags & GSS_C_CONF_FLAG)
75 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
76 if (flags & GSS_C_INTEG_FLAG)
77 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
82 /* get the username */
83 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
85 OM_uint32 major_status = 0, minor_status = 0;
86 gss_buffer_desc client_name_buffer;
91 * To extract the unix username, use gss_display_name on client_name.
92 * We do rely on gss_display_name returning a zero terminated string.
93 * The username returned contains the realm and possibly an instance.
94 * We only want the username for afpd, so we have to strip those from
95 * the username before copying it to afpd's buffer.
98 major_status = gss_display_name( &minor_status, *client_name,
99 &client_name_buffer, (gss_OID *)NULL );
100 if (major_status != GSS_S_COMPLETE) {
101 log_status( "display_name", major_status, minor_status );
105 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
108 p = strchr( client_name_buffer.value, '@' );
111 /* FIXME: chop off instance? */
112 p = strchr( client_name_buffer.value, '/' );
116 /* check if this username fits into afpd's username buffer */
117 namelen = strlen(client_name_buffer.value);
118 if ( namelen >= ulen ) {
119 /* The username is too long for afpd's buffer, bail out */
120 LOG(log_error, logtype_uams,
121 "get_client_username: username `%s' too long", client_name_buffer.value);
125 /* copy stripped username to afpd's buffer */
126 strlcpy(username, client_name_buffer.value, ulen);
129 /* we're done with client_name_buffer, release it */
130 gss_release_buffer(&minor_status, &client_name_buffer );
135 /* wrap afpd's sessionkey */
136 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
138 OM_uint32 status = 0;
140 gss_buffer_desc sesskey_buff, wrap_buff;
143 * gss_wrap afpd's session_key.
144 * This is needed fo OS X 10.3 clients. They request this information
145 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
146 * See AFP 3.1 specs, page 77.
149 sesskey_buff.value = sinfo->sessionkey;
150 sesskey_buff.length = sinfo->sessionkey_len;
152 /* gss_wrap the session key with the default mechanism.
153 Require both confidentiality and integrity services */
154 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
156 if ( status != GSS_S_COMPLETE) {
157 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
158 log_status( "GSS wrap", 0, status );
162 /* store the wrapped session key in afpd's session_info struct */
163 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
164 LOG(log_error, logtype_uams,
165 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
169 /* cryptedkey is binary data */
170 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
171 sinfo->cryptedkey_len = wrap_buff.length;
174 /* we're done with buffer, release */
175 gss_release_buffer( &status, &wrap_buff );
181 static int accept_sec_context (gss_ctx_id_t *context,
182 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
183 gss_buffer_desc *authenticator_buff)
185 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
187 /* Initialize autheticator buffer. */
188 authenticator_buff->length = 0;
189 authenticator_buff->value = NULL;
191 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
192 ticket_buffer->length);
195 * Try to accept the secondary context using the token in ticket_buffer.
196 * We don't care about the principals or mechanisms used, nor for the time.
197 * We don't act as a proxy either.
199 major_status = gss_accept_sec_context( &minor_status, context,
200 GSS_C_NO_CREDENTIAL, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
201 client_name, NULL, authenticator_buff,
202 &ret_flags, NULL, NULL );
204 if (major_status != GSS_S_COMPLETE) {
205 log_status( "accept_sec_context", major_status, minor_status );
208 log_ctx_flags( ret_flags );
213 /* return 0 on success */
214 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
215 char *rbuf, int *rbuflen, char *username, int ulen,
216 struct session_info *sinfo )
218 OM_uint32 status = 0;
219 gss_name_t client_name;
220 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
221 gss_buffer_desc ticket_buffer, authenticator_buff;
225 * Try to accept the secondary context, using the ticket/token the
226 * client sent us. Ticket is stored at current ibuf position.
227 * Don't try to release ticket_buffer later, it points into ibuf!
229 ticket_buffer.length = ticket_len;
230 ticket_buffer.value = ibuf;
232 if ((ret = accept_sec_context(&context_handle, &ticket_buffer,
233 &client_name, &authenticator_buff)))
236 /* We succesfully acquired the secondary context, now get the
237 username for afpd and gss_wrap the sessionkey */
238 if ((ret = get_client_username(username, ulen, &client_name)))
239 goto cleanup_client_name;
241 if ((ret = wrap_sessionkey(context_handle, sinfo)))
242 goto cleanup_client_name;
244 /* Authenticated, construct the reply using:
245 * authenticator length (uint16_t)
248 /* copy the authenticator length into the reply buffer */
249 uint16_t auth_len = htons( authenticator_buff.length );
250 memcpy( rbuf, &auth_len, sizeof(auth_len) );
251 *rbuflen += sizeof(auth_len);
252 rbuf += sizeof(auth_len);
254 /* copy the authenticator value into the reply buffer */
255 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
256 *rbuflen += authenticator_buff.length;
259 gss_release_name( &status, &client_name );
262 gss_release_buffer( &status, &authenticator_buff );
263 gss_delete_sec_context( &status, &context_handle, NULL );
268 /* -------------------------- */
271 * For the gss uam, this function only needs to return a two-byte
272 * login-session id. None of the data provided by the client up to this
273 * point is trustworthy as we'll have a signed ticket to parse in logincont.
275 static int gss_login(void *obj, struct passwd **uam_pwd,
276 char *ibuf, size_t ibuflen,
277 char *rbuf, size_t *rbuflen)
284 /* The reply contains a two-byte ID value - note
285 * that Apple's implementation seems to always return 1 as well
288 memcpy(rbuf, &temp16, sizeof(temp16));
289 *rbuflen += sizeof(temp16);
290 return AFPERR_AUTHCONT;
293 static int gss_logincont(void *obj, struct passwd **uam_pwd,
294 char *ibuf, size_t ibuflen,
295 char *rbuf, size_t *rbuflen)
297 struct passwd *pwd = NULL;
304 struct session_info *sinfo;
306 /* Apple's AFP 3.1 documentation specifies that this command
307 * takes the following format:
309 * id returned in LoginExt response (uint16_t)
310 * username (format unspecified) padded, when necessary, to end on an even boundary
311 * ticket length (uint16_t)
315 /* Observation of AFP clients in the wild indicate that the actual
316 * format of this request is as follows:
317 * pad (byte) [consumed before login_ext is called]
318 * ?? (byte) - always observed to be 0
319 * id returned in LoginExt response (uint16_t)
320 * username, encoding unspecified, null terminated C string,
321 * padded when the terminating null is an even numbered byte.
322 * The packet is formated such that the username begins on an
323 * odd numbered byte. Eg if the username is 3 characters and the
324 * terminating null makes 4, expect to pad the the result.
325 * The encoding of this string is unknown.
326 * ticket length (uint16_t)
330 rblen = *rbuflen = 0;
332 if (ibuflen < 1 +sizeof(login_id)) {
333 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
336 ibuf++, ibuflen--; /* ?? */
338 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
339 memcpy( &login_id, ibuf, sizeof(login_id) );
340 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
341 login_id = ntohs( login_id );
343 /* get the username buffer from apfd */
344 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
347 /* get the session_info structure from afpd. We need the session key */
348 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
351 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
352 /* Should never happen. Most likely way too old afpd version */
353 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
357 /* We skip past the 'username' parameter because all that matters is the ticket */
359 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
361 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
365 ibuf++, ibuflen--; /* null termination */
367 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
369 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
371 /* get the length of the ticket the client sends us */
372 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
373 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
374 ticket_len = ntohs( ticket_len );
376 /* a little bounds checking */
377 if (ticket_len > ibuflen) {
378 LOG(log_info, logtype_uams,
379 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
383 /* now try to authenticate */
384 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
385 LOG(log_info, logtype_uams, "do_gss_auth failed" );
390 /* We use the username we got back from the gssapi client_name.
391 Should we compare this to the username the client sent in the clear?
392 We know the character encoding of the cleartext username (UTF8), what
393 encoding is the gssapi name in? */
394 if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
395 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
396 return AFPERR_NOTAUTH;
398 if (uam_checkuser(pwd) < 0) {
399 LOG(log_info, logtype_uams, "%s not a valid user", username);
400 return AFPERR_NOTAUTH;
408 static void gss_logout() {
411 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
412 char *ibuf, size_t ibuflen,
413 char *rbuf, size_t *rbuflen)
415 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
418 static int uam_setup(const char *path)
420 return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
421 gss_login, gss_logincont, gss_logout, gss_login_ext);
424 static void uam_cleanup(void)
426 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
429 UAM_MODULE_EXPORT struct uam_export uams_gss = {
432 uam_setup, uam_cleanup