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>
25 /* Kerberos includes */
26 #ifdef HAVE_GSSAPI_GSSAPI_H
27 #include <gssapi/gssapi.h>
30 #endif // HAVE_GSSAPI_GSSAPI_H
32 #define LOG_UAMS(log_level, ...) \
33 LOG(log_level, logtype_uams, __VA_ARGS__)
35 #define LOG_LOGINCONT(log_level, ...) \
36 LOG_UAMS(log_level, "FPLoginCont: " __VA_ARGS__)
38 static void log_status(char *s,
39 OM_uint32 major_status,
40 OM_uint32 minor_status)
42 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
43 OM_uint32 min_status, maj_status;
44 OM_uint32 maj_ctx = 0, min_ctx = 0;
47 maj_status = gss_display_status( &min_status, major_status,
48 GSS_C_GSS_CODE, GSS_C_NULL_OID,
50 LOG_UAMS(log_error, "%s %.*s (error %s)",
51 s, msg.length, msg.value, strerror(errno));
52 gss_release_buffer(&min_status, &msg);
59 maj_status = gss_display_status( &min_status, minor_status,
60 GSS_C_MECH_CODE, GSS_C_NULL_OID,
62 LOG_UAMS(log_error, "%s %.*s (error %s)",
63 s, msg.length, msg.value, strerror(errno));
64 gss_release_buffer(&min_status, &msg);
71 static void log_ctx_flags(OM_uint32 flags)
73 if (flags & GSS_C_DELEG_FLAG)
74 LOG_LOGINCONT(log_debug, "context flag: GSS_C_DELEG_FLAG");
75 if (flags & GSS_C_MUTUAL_FLAG)
76 LOG_LOGINCONT(log_debug, "context flag: GSS_C_MUTUAL_FLAG");
77 if (flags & GSS_C_REPLAY_FLAG)
78 LOG_LOGINCONT(log_debug, "context flag: GSS_C_REPLAY_FLAG");
79 if (flags & GSS_C_SEQUENCE_FLAG)
80 LOG_LOGINCONT(log_debug, "context flag: GSS_C_SEQUENCE_FLAG");
81 if (flags & GSS_C_CONF_FLAG)
82 LOG_LOGINCONT(log_debug, "context flag: GSS_C_CONF_FLAG");
83 if (flags & GSS_C_INTEG_FLAG)
84 LOG_LOGINCONT(log_debug, "context flag: GSS_C_INTEG_FLAG");
87 static void log_service_name(gss_ctx_id_t context)
89 OM_uint32 major_status = 0, minor_status = 0;
90 gss_name_t service_name;
91 gss_buffer_desc service_name_buffer;
93 major_status = gss_inquire_context(&minor_status,
102 if (major_status != GSS_S_COMPLETE) {
103 log_status("gss_inquire_context", major_status, minor_status);
107 major_status = gss_display_name(&minor_status,
109 &service_name_buffer,
111 if (major_status == GSS_S_COMPLETE) {
112 LOG_LOGINCONT(log_debug,
113 "service principal is `%s'",
114 service_name_buffer.value);
116 gss_release_buffer(&minor_status, &service_name_buffer);
118 log_status("gss_display_name", major_status, minor_status);
120 gss_release_name(&minor_status, &service_name);
123 static int get_client_username(char *username,
125 gss_name_t *client_name)
127 OM_uint32 major_status = 0, minor_status = 0;
128 gss_buffer_desc client_name_buffer;
133 * To extract the unix username, use gss_display_name on client_name.
134 * We do rely on gss_display_name returning a zero terminated string.
135 * The username returned contains the realm and possibly an instance.
136 * We only want the username for afpd, so we have to strip those from
137 * the username before copying it to afpd's buffer.
140 major_status = gss_display_name(&minor_status,
144 if (major_status != GSS_S_COMPLETE) {
145 log_status("gss_display_name", major_status, minor_status);
149 LOG_LOGINCONT(log_debug,
150 "user principal is `%s'",
151 client_name_buffer.value);
154 p = strchr(client_name_buffer.value, '@');
157 /* FIXME: chop off instance? */
158 p = strchr(client_name_buffer.value, '/');
162 /* check if this username fits into afpd's username buffer */
163 size_t cnblen = strlen(client_name_buffer.value);
164 if (cnblen >= ulen) {
165 /* The username is too long for afpd's buffer, bail out */
166 LOG_LOGINCONT(log_info,
167 "username `%s' too long (%d)",
168 client_name_buffer.value, cnblen);
171 /* copy stripped username to afpd's buffer */
172 strlcpy(username, client_name_buffer.value, ulen);
175 gss_release_buffer(&minor_status, &client_name_buffer);
180 /* wrap afpd's sessionkey */
181 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
183 OM_uint32 major_status = 0, minor_status = 0;
184 gss_buffer_desc sesskey_buff, wrap_buff;
188 * gss_wrap afpd's session_key.
189 * This is needed fo OS X 10.3 clients. They request this information
190 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
191 * See AFP 3.1 specs, page 77.
193 sesskey_buff.value = sinfo->sessionkey;
194 sesskey_buff.length = sinfo->sessionkey_len;
196 /* gss_wrap the session key with the default mechanism.
197 Require both confidentiality and integrity services */
198 major_status = gss_wrap(&minor_status,
206 if (major_status != GSS_S_COMPLETE) {
207 log_status("gss_wrap", major_status, minor_status);
211 /* store the wrapped session key in afpd's session_info struct */
212 if (NULL == (sinfo->cryptedkey = malloc(wrap_buff.length))) {
214 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
218 /* cryptedkey is binary data */
219 memcpy(sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
220 sinfo->cryptedkey_len = wrap_buff.length;
223 /* we're done with buffer, release */
224 gss_release_buffer(&minor_status, &wrap_buff);
229 static int accept_sec_context(gss_ctx_id_t *context,
230 gss_buffer_desc *ticket_buffer,
231 gss_name_t *client_name,
232 gss_buffer_desc *authenticator_buff)
234 OM_uint32 major_status = 0, minor_status = 0, flags = 0;
236 /* Initialize autheticator buffer. */
237 authenticator_buff->length = 0;
238 authenticator_buff->value = NULL;
240 LOG_LOGINCONT(log_debug,
241 "accepting context (ticketlen: %u)",
242 ticket_buffer->length);
245 * Try to accept the secondary context using the token in ticket_buffer.
246 * We don't care about the principals or mechanisms used, nor for the time.
247 * We don't act as a proxy either.
249 major_status = gss_accept_sec_context(&minor_status,
253 GSS_C_NO_CHANNEL_BINDINGS,
261 if (major_status != GSS_S_COMPLETE) {
262 log_status("gss_accept_sec_context", major_status, minor_status);
266 log_ctx_flags(flags);
270 static int do_gss_auth(void *obj,
271 char *ibuf, size_t ibuflen,
272 char *rbuf, int *rbuflen,
273 char *username, size_t ulen,
274 struct session_info *sinfo )
276 OM_uint32 status = 0;
277 gss_name_t client_name;
278 gss_ctx_id_t context = GSS_C_NO_CONTEXT;
279 gss_buffer_desc ticket_buffer, authenticator_buff;
283 * Try to accept the secondary context, using the ticket/token the
284 * client sent us. Ticket is stored at current ibuf position.
285 * Don't try to release ticket_buffer later, it points into ibuf!
287 ticket_buffer.length = ibuflen;
288 ticket_buffer.value = ibuf;
290 if ((ret = accept_sec_context(&context,
293 &authenticator_buff)))
295 log_service_name(context);
297 /* We succesfully acquired the secondary context, now get the
298 username for afpd and gss_wrap the sessionkey */
299 if ((ret = get_client_username(username, ulen, &client_name)))
300 goto cleanup_client_name;
302 if ((ret = wrap_sessionkey(context, sinfo)))
303 goto cleanup_client_name;
305 /* Authenticated, construct the reply using:
306 * authenticator length (uint16_t)
309 /* copy the authenticator length into the reply buffer */
310 uint16_t auth_len = htons(authenticator_buff.length);
311 memcpy(rbuf, &auth_len, sizeof(auth_len));
312 *rbuflen += sizeof(auth_len);
313 rbuf += sizeof(auth_len);
315 /* copy the authenticator value into the reply buffer */
316 memcpy(rbuf, authenticator_buff.value, authenticator_buff.length);
317 *rbuflen += authenticator_buff.length;
320 gss_release_name(&status, &client_name);
323 gss_release_buffer(&status, &authenticator_buff);
324 gss_delete_sec_context(&status, &context, NULL);
329 /* -------------------------- */
332 * For the gss uam, this function only needs to return a two-byte
333 * login-session id. None of the data provided by the client up to this
334 * point is trustworthy as we'll have a signed ticket to parse in logincont.
336 static int gss_login(void *obj,
337 struct passwd **uam_pwd,
338 char *ibuf, size_t ibuflen,
339 char *rbuf, size_t *rbuflen)
343 /* The reply contains a two-byte ID value - note
344 * that Apple's implementation seems to always return 1 as well
346 uint16_t temp16 = htons(1);
347 memcpy(rbuf, &temp16, sizeof(temp16));
348 *rbuflen += sizeof(temp16);
350 return AFPERR_AUTHCONT;
353 static int gss_logincont(void *obj,
354 struct passwd **uam_pwd,
355 char *ibuf, size_t ibuflen,
356 char *rbuf, size_t *rbuflen)
358 struct passwd *pwd = NULL;
365 struct session_info *sinfo;
367 /* Apple's AFP 3.1 documentation specifies that this command
368 * takes the following format:
370 * id returned in LoginExt response (uint16_t)
371 * username (format unspecified)
372 * padded, when necessary, to end on an even boundary
373 * ticket length (uint16_t)
377 /* Observation of AFP clients in the wild indicate that the actual
378 * format of this request is as follows:
379 * pad (byte) [consumed before login_ext is called]
380 * ?? (byte) - always observed to be 0
381 * id returned in LoginExt response (uint16_t)
382 * username, encoding unspecified, null terminated C string,
383 * padded when the terminating null is an even numbered byte.
384 * The packet is formated such that the username begins on an
385 * odd numbered byte. Eg if the username is 3 characters and the
386 * terminating null makes 4, expect to pad the the result.
387 * The encoding of this string is unknown.
388 * ticket length (uint16_t)
392 rblen = *rbuflen = 0;
394 if (ibuflen < 1 +sizeof(login_id)) {
395 LOG_LOGINCONT(log_info, "received incomplete packet");
398 ibuf++, ibuflen--; /* ?? */
400 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
401 memcpy(&login_id, ibuf, sizeof(login_id));
402 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
403 login_id = ntohs(login_id);
405 /* get the username buffer from apfd */
406 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0)
409 /* get the session_info structure from afpd. We need the session key */
410 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0)
413 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
414 /* Should never happen. Most likely way too old afpd version */
415 LOG_LOGINCONT(log_error, "internal error: afpd's sessionkey not set");
419 /* We skip past the 'username' parameter because all that matters is the ticket */
421 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
423 LOG_LOGINCONT(log_info, "user is %s, no ticket", p);
427 ibuf++, ibuflen--; /* null termination */
429 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
431 LOG_LOGINCONT(log_debug, "client thinks user is %s", p);
433 /* get the length of the ticket the client sends us */
434 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
435 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
436 ticket_len = ntohs(ticket_len);
438 /* a little bounds checking */
439 if (ticket_len > ibuflen) {
440 LOG_LOGINCONT(log_info,
441 "invalid ticket length (%u > %u)",
442 ticket_len, ibuflen);
446 /* now try to authenticate */
447 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
448 LOG_LOGINCONT(log_info, "do_gss_auth() failed" );
453 /* We use the username we got back from the gssapi client_name.
454 Should we compare this to the username the client sent in the clear?
455 We know the character encoding of the cleartext username (UTF8), what
456 encoding is the gssapi name in? */
457 if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
458 LOG_LOGINCONT(log_info, "uam_getname() failed for %s", username);
459 return AFPERR_NOTAUTH;
461 if (uam_checkuser(pwd) < 0) {
462 LOG_LOGINCONT(log_info, "`%s'' not a valid user", username);
463 return AFPERR_NOTAUTH;
472 static void gss_logout() {
475 static int gss_login_ext(void *obj,
477 struct passwd **uam_pwd,
478 char *ibuf, size_t ibuflen,
479 char *rbuf, size_t *rbuflen)
481 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
484 static int uam_setup(const char *path)
486 return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
487 gss_login, gss_logincont, gss_logout, gss_login_ext);
490 static void uam_cleanup(void)
492 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
495 UAM_MODULE_EXPORT struct uam_export uams_gss = {