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 */
17 #include <arpa/inet.h>
19 #include <atalk/logger.h>
20 #include <atalk/afp.h>
21 #include <atalk/uam.h>
22 #include <atalk/util.h>
23 #include <atalk/compat.h>
24 #include <atalk/globals.h>
26 /* Kerberos includes */
27 #ifdef HAVE_GSSAPI_GSSAPI_H
28 #include <gssapi/gssapi.h>
31 #endif // HAVE_GSSAPI_GSSAPI_H
34 #ifdef HAVE_KRB5_KRB5_H
35 #include <krb5/krb5.h>
38 #endif /* HAVE_KRB5_KRB5_H */
39 #endif /* HAVE_KERBEROS */
41 #define LOG_UAMS(log_level, ...) \
42 LOG(log_level, logtype_uams, __VA_ARGS__)
44 #define LOG_LOGINCONT(log_level, ...) \
45 LOG_UAMS(log_level, "FPLoginCont: " __VA_ARGS__)
47 static void log_status(char *s,
48 OM_uint32 major_status,
49 OM_uint32 minor_status)
51 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
52 OM_uint32 min_status, maj_status;
53 OM_uint32 maj_ctx = 0, min_ctx = 0;
56 maj_status = gss_display_status( &min_status, major_status,
57 GSS_C_GSS_CODE, GSS_C_NULL_OID,
59 LOG_UAMS(log_error, "%s %.*s (error %s)",
60 s, msg.length, msg.value, strerror(errno));
61 gss_release_buffer(&min_status, &msg);
68 maj_status = gss_display_status( &min_status, minor_status,
69 GSS_C_MECH_CODE, GSS_C_NULL_OID,
71 LOG_UAMS(log_error, "%s %.*s (error %s)",
72 s, msg.length, msg.value, strerror(errno));
73 gss_release_buffer(&min_status, &msg);
80 static void log_ctx_flags(OM_uint32 flags)
82 if (flags & GSS_C_DELEG_FLAG)
83 LOG_LOGINCONT(log_debug, "context flag: GSS_C_DELEG_FLAG");
84 if (flags & GSS_C_MUTUAL_FLAG)
85 LOG_LOGINCONT(log_debug, "context flag: GSS_C_MUTUAL_FLAG");
86 if (flags & GSS_C_REPLAY_FLAG)
87 LOG_LOGINCONT(log_debug, "context flag: GSS_C_REPLAY_FLAG");
88 if (flags & GSS_C_SEQUENCE_FLAG)
89 LOG_LOGINCONT(log_debug, "context flag: GSS_C_SEQUENCE_FLAG");
90 if (flags & GSS_C_CONF_FLAG)
91 LOG_LOGINCONT(log_debug, "context flag: GSS_C_CONF_FLAG");
92 if (flags & GSS_C_INTEG_FLAG)
93 LOG_LOGINCONT(log_debug, "context flag: GSS_C_INTEG_FLAG");
96 static void log_service_name(gss_ctx_id_t context)
98 OM_uint32 major_status = 0, minor_status = 0;
99 gss_name_t service_name;
100 gss_buffer_desc service_name_buffer;
102 major_status = gss_inquire_context(&minor_status,
111 if (major_status != GSS_S_COMPLETE) {
112 log_status("gss_inquire_context", major_status, minor_status);
116 major_status = gss_display_name(&minor_status,
118 &service_name_buffer,
120 if (major_status == GSS_S_COMPLETE) {
121 LOG_LOGINCONT(log_debug,
122 "service principal is `%s'",
123 service_name_buffer.value);
125 gss_release_buffer(&minor_status, &service_name_buffer);
127 log_status("gss_display_name", major_status, minor_status);
129 gss_release_name(&minor_status, &service_name);
132 static int get_client_username(char *username,
134 gss_name_t *client_name)
136 OM_uint32 major_status = 0, minor_status = 0;
137 gss_buffer_desc client_name_buffer;
142 * To extract the unix username, use gss_display_name on client_name.
143 * We do rely on gss_display_name returning a zero terminated string.
144 * The username returned contains the realm and possibly an instance.
145 * We only want the username for afpd, so we have to strip those from
146 * the username before copying it to afpd's buffer.
149 major_status = gss_display_name(&minor_status,
153 if (major_status != GSS_S_COMPLETE) {
154 log_status("gss_display_name", major_status, minor_status);
158 LOG_LOGINCONT(log_debug,
159 "user principal is `%s'",
160 client_name_buffer.value);
163 p = strchr(client_name_buffer.value, '@');
166 /* FIXME: chop off instance? */
167 p = strchr(client_name_buffer.value, '/');
171 /* check if this username fits into afpd's username buffer */
172 size_t cnblen = strlen(client_name_buffer.value);
173 if (cnblen >= ulen) {
174 /* The username is too long for afpd's buffer, bail out */
175 LOG_LOGINCONT(log_info,
176 "username `%s' too long (%d)",
177 client_name_buffer.value, cnblen);
180 /* copy stripped username to afpd's buffer */
181 strlcpy(username, client_name_buffer.value, ulen);
184 gss_release_buffer(&minor_status, &client_name_buffer);
189 /* wrap afpd's sessionkey */
190 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
192 OM_uint32 major_status = 0, minor_status = 0;
193 gss_buffer_desc sesskey_buff, wrap_buff;
197 * gss_wrap afpd's session_key.
198 * This is needed fo OS X 10.3 clients. They request this information
199 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
200 * See AFP 3.1 specs, page 77.
202 sesskey_buff.value = sinfo->sessionkey;
203 sesskey_buff.length = sinfo->sessionkey_len;
205 /* gss_wrap the session key with the default mechanism.
206 Require both confidentiality and integrity services */
207 major_status = gss_wrap(&minor_status,
215 if (major_status != GSS_S_COMPLETE) {
216 log_status("gss_wrap", major_status, minor_status);
220 /* store the wrapped session key in afpd's session_info struct */
221 if (NULL == (sinfo->cryptedkey = malloc(wrap_buff.length))) {
223 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
227 /* cryptedkey is binary data */
228 memcpy(sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
229 sinfo->cryptedkey_len = wrap_buff.length;
232 /* we're done with buffer, release */
233 gss_release_buffer(&minor_status, &wrap_buff);
238 static int accept_sec_context(gss_ctx_id_t *context,
239 gss_buffer_desc *ticket_buffer,
240 gss_name_t *client_name,
241 gss_buffer_desc *authenticator_buff)
243 OM_uint32 major_status = 0, minor_status = 0, flags = 0;
245 /* Initialize autheticator buffer. */
246 authenticator_buff->length = 0;
247 authenticator_buff->value = NULL;
249 LOG_LOGINCONT(log_debug,
250 "accepting context (ticketlen: %u)",
251 ticket_buffer->length);
254 * Try to accept the secondary context using the token in ticket_buffer.
255 * We don't care about the principals or mechanisms used, nor for the time.
256 * We don't act as a proxy either.
258 major_status = gss_accept_sec_context(&minor_status,
262 GSS_C_NO_CHANNEL_BINDINGS,
270 if (major_status != GSS_S_COMPLETE) {
271 log_status("gss_accept_sec_context", major_status, minor_status);
275 log_ctx_flags(flags);
279 static int do_gss_auth(void *obj,
280 char *ibuf, size_t ibuflen,
281 char *rbuf, int *rbuflen,
282 char *username, size_t ulen,
283 struct session_info *sinfo )
285 OM_uint32 status = 0;
286 gss_name_t client_name;
287 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
288 gss_buffer_desc ticket_buffer, authenticator_buff;
292 * Try to accept the secondary context, using the ticket/token the
293 * client sent us. Ticket is stored at current ibuf position.
294 * Don't try to release ticket_buffer later, it points into ibuf!
296 ticket_buffer.length = ibuflen;
297 ticket_buffer.value = ibuf;
299 if ((ret = accept_sec_context(&context,
302 &authenticator_buff)))
304 log_service_name(context);
306 /* We succesfully acquired the secondary context, now get the
307 username for afpd and gss_wrap the sessionkey */
308 if ((ret = get_client_username(username, ulen, &client_name)))
309 goto cleanup_client_name;
311 if ((ret = wrap_sessionkey(context, sinfo)))
312 goto cleanup_client_name;
314 /* Authenticated, construct the reply using:
315 * authenticator length (uint16_t)
318 /* copy the authenticator length into the reply buffer */
319 uint16_t auth_len = htons(authenticator_buff.length);
320 memcpy(rbuf, &auth_len, sizeof(auth_len));
321 *rbuflen += sizeof(auth_len);
322 rbuf += sizeof(auth_len);
324 /* copy the authenticator value into the reply buffer */
325 memcpy(rbuf, authenticator_buff.value, authenticator_buff.length);
326 *rbuflen += authenticator_buff.length;
329 gss_release_name(&status, &client_name);
330 gss_release_buffer(&status, &authenticator_buff);
331 gss_delete_sec_context(&status, &context, NULL);
336 /* -------------------------- */
339 * For the gss uam, this function only needs to return a two-byte
340 * login-session id. None of the data provided by the client up to this
341 * point is trustworthy as we'll have a signed ticket to parse in logincont.
343 static int gss_login(void *obj,
344 struct passwd **uam_pwd,
345 char *ibuf, size_t ibuflen,
346 char *rbuf, size_t *rbuflen)
350 /* The reply contains a two-byte ID value - note
351 * that Apple's implementation seems to always return 1 as well
353 uint16_t temp16 = htons(1);
354 memcpy(rbuf, &temp16, sizeof(temp16));
355 *rbuflen += sizeof(temp16);
357 return AFPERR_AUTHCONT;
360 static int gss_logincont(void *obj,
361 struct passwd **uam_pwd,
362 char *ibuf, size_t ibuflen,
363 char *rbuf, size_t *rbuflen)
365 struct passwd *pwd = NULL;
372 struct session_info *sinfo;
374 /* Apple's AFP 3.1 documentation specifies that this command
375 * takes the following format:
377 * id returned in LoginExt response (uint16_t)
378 * username (format unspecified)
379 * padded, when necessary, to end on an even boundary
380 * ticket length (uint16_t)
384 /* Observation of AFP clients in the wild indicate that the actual
385 * format of this request is as follows:
386 * pad (byte) [consumed before login_ext is called]
387 * ?? (byte) - always observed to be 0
388 * id returned in LoginExt response (uint16_t)
389 * username, encoding unspecified, null terminated C string,
390 * padded when the terminating null is an even numbered byte.
391 * The packet is formated such that the username begins on an
392 * odd numbered byte. Eg if the username is 3 characters and the
393 * terminating null makes 4, expect to pad the the result.
394 * The encoding of this string is unknown.
395 * ticket length (uint16_t)
399 rblen = *rbuflen = 0;
401 if (ibuflen < 1 +sizeof(login_id)) {
402 LOG_LOGINCONT(log_info, "received incomplete packet");
405 ibuf++, ibuflen--; /* ?? */
407 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
408 memcpy(&login_id, ibuf, sizeof(login_id));
409 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
410 login_id = ntohs(login_id);
412 /* get the username buffer from apfd */
413 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0)
416 /* get the session_info structure from afpd. We need the session key */
417 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0)
420 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
421 /* Should never happen. Most likely way too old afpd version */
422 LOG_LOGINCONT(log_error, "internal error: afpd's sessionkey not set");
426 /* We skip past the 'username' parameter because all that matters is the ticket */
428 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
430 LOG_LOGINCONT(log_info, "user is %s, no ticket", p);
434 ibuf++, ibuflen--; /* null termination */
436 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
438 LOG_LOGINCONT(log_debug, "client thinks user is %s", p);
440 /* get the length of the ticket the client sends us */
441 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
442 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
443 ticket_len = ntohs(ticket_len);
445 /* a little bounds checking */
446 if (ticket_len > ibuflen) {
447 LOG_LOGINCONT(log_info,
448 "invalid ticket length (%u > %u)",
449 ticket_len, ibuflen);
453 /* now try to authenticate */
454 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
455 LOG_LOGINCONT(log_info, "do_gss_auth() failed" );
460 /* We use the username we got back from the gssapi client_name.
461 Should we compare this to the username the client sent in the clear?
462 We know the character encoding of the cleartext username (UTF8), what
463 encoding is the gssapi name in? */
464 if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
465 LOG_LOGINCONT(log_info, "uam_getname() failed for %s", username);
466 return AFPERR_NOTAUTH;
468 if (uam_checkuser(pwd) < 0) {
469 LOG_LOGINCONT(log_info, "`%s'' not a valid user", username);
470 return AFPERR_NOTAUTH;
479 static void gss_logout() {
482 static int gss_login_ext(void *obj,
484 struct passwd **uam_pwd,
485 char *ibuf, size_t ibuflen,
486 char *rbuf, size_t *rbuflen)
488 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
491 static int set_principal(AFPObj *obj, char *principal)
493 size_t len = strlen(principal);
496 LOG(log_error, logtype_afpd, "set_principal: principal '%s' too long (max=255)", principal, len);
500 /* We store: 1 byte number principals (1) + 1 byte principal name length + zero terminated principal */
501 if ((obj->options.k5principal = malloc(len + 3)) == NULL) {
502 LOG(log_error, logtype_afpd, "set_principal: OOM");
506 LOG(log_info, logtype_afpd, "Using AFP Kerberos service principal name: %s", principal);
508 obj->options.k5principal[0] = 1;
509 obj->options.k5principal[1] = (unsigned char)len;
510 obj->options.k5principal_buflen = len + 2;
511 strncpy(obj->options.k5principal + 2, principal, len);
516 static int gss_create_principal(AFPObj *obj)
520 krb5_context context;
522 const char *error_msg;
524 krb5_keytab_entry entry;
525 krb5_principal service_principal;
527 krb5_kt_cursor cursor;
529 if (krb5_init_context(&context)) {
530 LOG(log_error, logtype_afpd, "gss_create_principal: failed to intialize a krb5_context");
533 if ((ret = krb5_kt_default(context, &keytab)))
536 if (obj->options.k5service && obj->options.fqdn && obj->options.k5realm) {
537 LOG(log_debug, logtype_afpd, "gss_create_principal: using service principal specified in options");
539 if ((ret = krb5_build_principal(context,
541 strlen(obj->options.k5realm),
542 obj->options.k5realm,
543 obj->options.k5service,
548 if ((ret = krb5_kt_get_entry(context,
551 0, // kvno - wildcard
552 0, // enctype - wildcard
553 &entry)) == KRB5_KT_NOTFOUND) {
554 krb5_unparse_name(context, service_principal, &principal);
555 LOG(log_error, logtype_afpd, "gss_create_principal: specified service principal '%s' not found in keytab", principal);
556 #ifdef HAVE_KRB5_FREE_UNPARSED_NAME
557 krb5_free_unparsed_name(context, principal);
559 krb5_xfree(principal);
563 krb5_free_principal(context, service_principal);
567 LOG(log_debug, logtype_afpd, "gss_create_principal: using first entry from keytab as service principal");
568 if ((ret = krb5_kt_start_seq_get(context, keytab, &cursor)))
570 ret = krb5_kt_next_entry(context, keytab, &entry, &cursor);
571 krb5_kt_end_seq_get(context, keytab, &cursor);
576 krb5_unparse_name(context, entry.principal, &principal);
577 #ifdef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS
578 krb5_free_keytab_entry_contents(context, &entry);
579 #elif defined(HAVE_KRB5_KT_FREE_ENTRY)
580 krb5_kt_free_entry(context, &entry);
582 set_principal(obj, principal);
589 error_msg = krb5_get_error_message(context, ret);
590 LOG(log_note, logtype_afpd, "Can't get principal from default keytab: %s",
592 #ifdef HAVE_KRB5_FREE_ERROR_MESSAGE
593 krb5_free_error_message(context, error_msg);
595 krb5_xfree(error_msg);
600 krb5_kt_close(context, keytab);
601 krb5_free_context(context);
603 #else /* ! HAVE_KERBEROS */
605 if (!options->k5service || !options->fqdn || !options->k5realm)
609 size_t len = snprintf(principal, sizeof(principal), "%s/%s@%s",
610 options->k5service, options->fqdn, options->k5realm);
611 (void)set_principal(&obj, principal);
613 #endif /* HAVE_KERBEROS */
619 static int uam_setup(void *handle, const char *path)
621 AFPObj *obj = (AFPObj *)handle;
622 if (gss_create_principal(obj) != 0)
625 return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
626 gss_login, gss_logincont, gss_logout, gss_login_ext);
629 static void uam_cleanup(void)
631 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
634 UAM_MODULE_EXPORT struct uam_export uams_gss = {