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 #endif /* HAVE_UNISTD_H */
20 #include <arpa/inet.h>
22 #include <atalk/logger.h>
23 #include <atalk/afp.h>
24 #include <atalk/uam.h>
25 #include <atalk/util.h>
26 #include <atalk/compat.h>
28 /* Kerberos includes */
29 #ifdef HAVE_GSSAPI_GSSAPI_H
30 #include <gssapi/gssapi.h>
33 #endif // HAVE_GSSAPI_GSSAPI_H
35 static void log_status( char *s, OM_uint32 major_status,
36 OM_uint32 minor_status )
38 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
39 OM_uint32 min_status, maj_status;
40 OM_uint32 maj_ctx = 0, min_ctx = 0;
43 maj_status = gss_display_status( &min_status, major_status,
44 GSS_C_GSS_CODE, GSS_C_NULL_OID,
46 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
47 (int)msg.length, msg.value, strerror(errno));
48 gss_release_buffer(&min_status, &msg);
54 maj_status = gss_display_status( &min_status, minor_status,
55 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
57 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
58 (int)msg.length, msg.value, strerror(errno));
59 gss_release_buffer(&min_status, &msg);
67 static void log_ctx_flags( OM_uint32 flags )
70 if (flags & GSS_C_DELEG_FLAG)
71 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
72 if (flags & GSS_C_MUTUAL_FLAG)
73 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
74 if (flags & GSS_C_REPLAY_FLAG)
75 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
76 if (flags & GSS_C_SEQUENCE_FLAG)
77 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
78 if (flags & GSS_C_CONF_FLAG)
79 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
80 if (flags & GSS_C_INTEG_FLAG)
81 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
86 /* get the username */
87 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
89 OM_uint32 major_status = 0, minor_status = 0;
90 gss_buffer_desc client_name_buffer;
95 * To extract the unix username, use gss_display_name on client_name.
96 * We do rely on gss_display_name returning a zero terminated string.
97 * The username returned contains the realm and possibly an instance.
98 * We only want the username for afpd, so we have to strip those from
99 * the username before copying it to afpd's buffer.
102 major_status = gss_display_name( &minor_status, *client_name,
103 &client_name_buffer, (gss_OID *)NULL );
104 if (major_status != GSS_S_COMPLETE) {
105 log_status( "display_name", major_status, minor_status );
109 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
112 p = strchr( client_name_buffer.value, '@' );
115 /* FIXME: chop off instance? */
116 p = strchr( client_name_buffer.value, '/' );
120 /* check if this username fits into afpd's username buffer */
121 namelen = strlen(client_name_buffer.value);
122 if ( namelen >= ulen ) {
123 /* The username is too long for afpd's buffer, bail out */
124 LOG(log_error, logtype_uams,
125 "get_client_username: username `%s' too long", client_name_buffer.value);
129 /* copy stripped username to afpd's buffer */
130 strlcpy(username, client_name_buffer.value, ulen);
133 /* we're done with client_name_buffer, release it */
134 gss_release_buffer(&minor_status, &client_name_buffer );
139 /* wrap afpd's sessionkey */
140 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
142 OM_uint32 status = 0;
144 gss_buffer_desc sesskey_buff, wrap_buff;
147 * gss_wrap afpd's session_key.
148 * This is needed fo OS X 10.3 clients. They request this information
149 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
150 * See AFP 3.1 specs, page 77.
153 sesskey_buff.value = sinfo->sessionkey;
154 sesskey_buff.length = sinfo->sessionkey_len;
156 /* gss_wrap the session key with the default mechanism.
157 Require both confidentiality and integrity services */
158 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
160 if ( status != GSS_S_COMPLETE) {
161 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
162 log_status( "GSS wrap", 0, status );
166 /* store the wrapped session key in afpd's session_info struct */
167 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
168 LOG(log_error, logtype_uams,
169 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
173 /* cryptedkey is binary data */
174 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
175 sinfo->cryptedkey_len = wrap_buff.length;
178 /* we're done with buffer, release */
179 gss_release_buffer( &status, &wrap_buff );
185 static int accept_sec_context (gss_ctx_id_t *context,
186 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
187 gss_buffer_desc *authenticator_buff)
189 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
191 /* Initialize autheticator buffer. */
192 authenticator_buff->length = 0;
193 authenticator_buff->value = NULL;
195 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
196 ticket_buffer->length);
199 * Try to accept the secondary context using the token in ticket_buffer.
200 * We don't care about the principals or mechanisms used, nor for the time.
201 * We don't act as a proxy either.
203 major_status = gss_accept_sec_context( &minor_status, context,
204 GSS_C_NO_CREDENTIAL, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
205 client_name, NULL, authenticator_buff,
206 &ret_flags, NULL, NULL );
208 if (major_status != GSS_S_COMPLETE) {
209 log_status( "accept_sec_context", major_status, minor_status );
212 log_ctx_flags( ret_flags );
217 /* return 0 on success */
218 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
219 char *rbuf, int *rbuflen, char *username, int ulen,
220 struct session_info *sinfo )
222 OM_uint32 status = 0;
223 gss_name_t client_name;
224 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
225 gss_buffer_desc ticket_buffer, authenticator_buff;
229 * Try to accept the secondary context, using the ticket/token the
230 * client sent us. Ticket is stored at current ibuf position.
231 * Don't try to release ticket_buffer later, it points into ibuf!
233 ticket_buffer.length = ticket_len;
234 ticket_buffer.value = ibuf;
236 if ((ret = accept_sec_context(&context_handle, &ticket_buffer,
237 &client_name, &authenticator_buff)))
240 /* We succesfully acquired the secondary context, now get the
241 username for afpd and gss_wrap the sessionkey */
242 if ((ret = get_client_username(username, ulen, &client_name)))
243 goto cleanup_client_name;
245 if ((ret = wrap_sessionkey(context_handle, sinfo)))
246 goto cleanup_client_name;
248 /* Authenticated, construct the reply using:
249 * authenticator length (u_int16_t)
252 /* copy the authenticator length into the reply buffer */
253 u_int16_t auth_len = htons( authenticator_buff.length );
254 memcpy( rbuf, &auth_len, sizeof(auth_len) );
255 *rbuflen += sizeof(auth_len);
256 rbuf += sizeof(auth_len);
258 /* copy the authenticator value into the reply buffer */
259 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
260 *rbuflen += authenticator_buff.length;
263 gss_release_name( &status, &client_name );
266 gss_release_buffer( &status, &authenticator_buff );
267 gss_delete_sec_context( &status, &context_handle, NULL );
272 /* -------------------------- */
275 * For the gss uam, this function only needs to return a two-byte
276 * login-session id. None of the data provided by the client up to this
277 * point is trustworthy as we'll have a signed ticket to parse in logincont.
279 static int gss_login(void *obj, struct passwd **uam_pwd,
280 char *ibuf, size_t ibuflen,
281 char *rbuf, size_t *rbuflen)
288 /* The reply contains a two-byte ID value - note
289 * that Apple's implementation seems to always return 1 as well
292 memcpy(rbuf, &temp16, sizeof(temp16));
293 *rbuflen += sizeof(temp16);
294 return AFPERR_AUTHCONT;
297 static int gss_logincont(void *obj, struct passwd **uam_pwd,
298 char *ibuf, size_t ibuflen,
299 char *rbuf, size_t *rbuflen)
301 struct passwd *pwd = NULL;
308 struct session_info *sinfo;
310 /* Apple's AFP 3.1 documentation specifies that this command
311 * takes the following format:
313 * id returned in LoginExt response (uint16_t)
314 * username (format unspecified) padded, when necessary, to end on an even boundary
315 * ticket length (uint16_t)
319 /* Observation of AFP clients in the wild indicate that the actual
320 * format of this request is as follows:
321 * pad (byte) [consumed before login_ext is called]
322 * ?? (byte) - always observed to be 0
323 * id returned in LoginExt response (uint16_t)
324 * username, encoding unspecified, null terminated C string,
325 * padded when the terminating null is an even numbered byte.
326 * The packet is formated such that the username begins on an
327 * odd numbered byte. Eg if the username is 3 characters and the
328 * terminating null makes 4, expect to pad the the result.
329 * The encoding of this string is unknown.
330 * ticket length (uint16_t)
334 rblen = *rbuflen = 0;
336 if (ibuflen < 1 +sizeof(login_id)) {
337 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
340 ibuf++, ibuflen--; /* ?? */
342 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
343 memcpy( &login_id, ibuf, sizeof(login_id) );
344 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
345 login_id = ntohs( login_id );
347 /* get the username buffer from apfd */
348 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
351 /* get the session_info structure from afpd. We need the session key */
352 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
355 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
356 /* Should never happen. Most likely way too old afpd version */
357 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
361 /* We skip past the 'username' parameter because all that matters is the ticket */
363 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
365 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
369 ibuf++, ibuflen--; /* null termination */
371 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
373 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
375 /* get the length of the ticket the client sends us */
376 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
377 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
378 ticket_len = ntohs( ticket_len );
380 /* a little bounds checking */
381 if (ticket_len > ibuflen) {
382 LOG(log_info, logtype_uams,
383 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
387 /* now try to authenticate */
388 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
389 LOG(log_info, logtype_uams, "do_gss_auth failed" );
394 /* We use the username we got back from the gssapi client_name.
395 Should we compare this to the username the client sent in the clear?
396 We know the character encoding of the cleartext username (UTF8), what
397 encoding is the gssapi name in? */
398 if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
399 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
400 return AFPERR_NOTAUTH;
402 if (uam_checkuser(pwd) < 0) {
403 LOG(log_info, logtype_uams, "%s not a valid user", username);
404 return AFPERR_NOTAUTH;
412 static void gss_logout() {
415 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
416 char *ibuf, size_t ibuflen,
417 char *rbuf, size_t *rbuflen)
419 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
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