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 */
15 #include <arpa/inet.h>
17 #include <atalk/logger.h>
18 #include <atalk/afp.h>
19 #include <atalk/uam.h>
20 #include <atalk/util.h>
21 #include <atalk/compat.h>
23 /* Kerberos includes */
24 #ifdef HAVE_GSSAPI_GSSAPI_H
25 #include <gssapi/gssapi.h>
28 #endif // HAVE_GSSAPI_GSSAPI_H
30 static void log_status( char *s, OM_uint32 major_status,
31 OM_uint32 minor_status )
33 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
34 OM_uint32 min_status, maj_status;
35 OM_uint32 maj_ctx = 0, min_ctx = 0;
38 maj_status = gss_display_status( &min_status, major_status,
39 GSS_C_GSS_CODE, GSS_C_NULL_OID,
41 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
42 (int)msg.length, msg.value, strerror(errno));
43 gss_release_buffer(&min_status, &msg);
49 maj_status = gss_display_status( &min_status, minor_status,
50 GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
52 LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
53 (int)msg.length, msg.value, strerror(errno));
54 gss_release_buffer(&min_status, &msg);
62 static void log_ctx_flags( OM_uint32 flags )
65 if (flags & GSS_C_DELEG_FLAG)
66 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
67 if (flags & GSS_C_MUTUAL_FLAG)
68 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
69 if (flags & GSS_C_REPLAY_FLAG)
70 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
71 if (flags & GSS_C_SEQUENCE_FLAG)
72 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
73 if (flags & GSS_C_CONF_FLAG)
74 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
75 if (flags & GSS_C_INTEG_FLAG)
76 LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
81 /* get the username */
82 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
84 OM_uint32 major_status = 0, minor_status = 0;
85 gss_buffer_desc client_name_buffer;
90 * To extract the unix username, use gss_display_name on client_name.
91 * We do rely on gss_display_name returning a zero terminated string.
92 * The username returned contains the realm and possibly an instance.
93 * We only want the username for afpd, so we have to strip those from
94 * the username before copying it to afpd's buffer.
97 major_status = gss_display_name( &minor_status, *client_name,
98 &client_name_buffer, (gss_OID *)NULL );
99 if (major_status != GSS_S_COMPLETE) {
100 log_status( "display_name", major_status, minor_status );
104 LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
107 p = strchr( client_name_buffer.value, '@' );
110 /* FIXME: chop off instance? */
111 p = strchr( client_name_buffer.value, '/' );
115 /* check if this username fits into afpd's username buffer */
116 namelen = strlen(client_name_buffer.value);
117 if ( namelen >= ulen ) {
118 /* The username is too long for afpd's buffer, bail out */
119 LOG(log_error, logtype_uams,
120 "get_client_username: username `%s' too long", client_name_buffer.value);
124 /* copy stripped username to afpd's buffer */
125 strlcpy(username, client_name_buffer.value, ulen);
128 /* we're done with client_name_buffer, release it */
129 gss_release_buffer(&minor_status, &client_name_buffer );
134 /* wrap afpd's sessionkey */
135 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
137 OM_uint32 status = 0;
139 gss_buffer_desc sesskey_buff, wrap_buff;
142 * gss_wrap afpd's session_key.
143 * This is needed fo OS X 10.3 clients. They request this information
144 * with type 8 (kGetKerberosSessionKey) on FPGetSession.
145 * See AFP 3.1 specs, page 77.
148 sesskey_buff.value = sinfo->sessionkey;
149 sesskey_buff.length = sinfo->sessionkey_len;
151 /* gss_wrap the session key with the default mechanism.
152 Require both confidentiality and integrity services */
153 gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
155 if ( status != GSS_S_COMPLETE) {
156 LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
157 log_status( "GSS wrap", 0, status );
161 /* store the wrapped session key in afpd's session_info struct */
162 if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
163 LOG(log_error, logtype_uams,
164 "wrap_sessionkey: out of memory tyring to allocate %u bytes",
168 /* cryptedkey is binary data */
169 memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
170 sinfo->cryptedkey_len = wrap_buff.length;
173 /* we're done with buffer, release */
174 gss_release_buffer( &status, &wrap_buff );
180 static int accept_sec_context (gss_ctx_id_t *context,
181 gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
182 gss_buffer_desc *authenticator_buff)
184 OM_uint32 major_status = 0, minor_status = 0, ret_flags;
186 /* Initialize autheticator buffer. */
187 authenticator_buff->length = 0;
188 authenticator_buff->value = NULL;
190 LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
191 ticket_buffer->length);
194 * Try to accept the secondary context using the token in ticket_buffer.
195 * We don't care about the principals or mechanisms used, nor for the time.
196 * We don't act as a proxy either.
198 major_status = gss_accept_sec_context( &minor_status, context,
199 GSS_C_NO_CREDENTIAL, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
200 client_name, NULL, authenticator_buff,
201 &ret_flags, NULL, NULL );
203 if (major_status != GSS_S_COMPLETE) {
204 log_status( "accept_sec_context", major_status, minor_status );
207 log_ctx_flags( ret_flags );
212 /* return 0 on success */
213 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
214 char *rbuf, int *rbuflen, char *username, int ulen,
215 struct session_info *sinfo )
217 OM_uint32 status = 0;
218 gss_name_t client_name;
219 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
220 gss_buffer_desc ticket_buffer, authenticator_buff;
224 * Try to accept the secondary context, using the ticket/token the
225 * client sent us. Ticket is stored at current ibuf position.
226 * Don't try to release ticket_buffer later, it points into ibuf!
228 ticket_buffer.length = ticket_len;
229 ticket_buffer.value = ibuf;
231 if ((ret = accept_sec_context(&context_handle, &ticket_buffer,
232 &client_name, &authenticator_buff)))
235 /* We succesfully acquired the secondary context, now get the
236 username for afpd and gss_wrap the sessionkey */
237 if ((ret = get_client_username(username, ulen, &client_name)))
238 goto cleanup_client_name;
240 if ((ret = wrap_sessionkey(context_handle, sinfo)))
241 goto cleanup_client_name;
243 /* Authenticated, construct the reply using:
244 * authenticator length (u_int16_t)
247 /* copy the authenticator length into the reply buffer */
248 u_int16_t auth_len = htons( authenticator_buff.length );
249 memcpy( rbuf, &auth_len, sizeof(auth_len) );
250 *rbuflen += sizeof(auth_len);
251 rbuf += sizeof(auth_len);
253 /* copy the authenticator value into the reply buffer */
254 memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
255 *rbuflen += authenticator_buff.length;
258 gss_release_name( &status, &client_name );
261 gss_release_buffer( &status, &authenticator_buff );
262 gss_delete_sec_context( &status, &context_handle, NULL );
267 /* -------------------------- */
270 * For the gss uam, this function only needs to return a two-byte
271 * login-session id. None of the data provided by the client up to this
272 * point is trustworthy as we'll have a signed ticket to parse in logincont.
274 static int gss_login(void *obj, struct passwd **uam_pwd,
275 char *ibuf, size_t ibuflen,
276 char *rbuf, size_t *rbuflen)
283 /* The reply contains a two-byte ID value - note
284 * that Apple's implementation seems to always return 1 as well
287 memcpy(rbuf, &temp16, sizeof(temp16));
288 *rbuflen += sizeof(temp16);
289 return AFPERR_AUTHCONT;
292 static int gss_logincont(void *obj, struct passwd **uam_pwd,
293 char *ibuf, size_t ibuflen,
294 char *rbuf, size_t *rbuflen)
296 struct passwd *pwd = NULL;
303 struct session_info *sinfo;
305 /* Apple's AFP 3.1 documentation specifies that this command
306 * takes the following format:
308 * id returned in LoginExt response (uint16_t)
309 * username (format unspecified) padded, when necessary, to end on an even boundary
310 * ticket length (uint16_t)
314 /* Observation of AFP clients in the wild indicate that the actual
315 * format of this request is as follows:
316 * pad (byte) [consumed before login_ext is called]
317 * ?? (byte) - always observed to be 0
318 * id returned in LoginExt response (uint16_t)
319 * username, encoding unspecified, null terminated C string,
320 * padded when the terminating null is an even numbered byte.
321 * The packet is formated such that the username begins on an
322 * odd numbered byte. Eg if the username is 3 characters and the
323 * terminating null makes 4, expect to pad the the result.
324 * The encoding of this string is unknown.
325 * ticket length (uint16_t)
329 rblen = *rbuflen = 0;
331 if (ibuflen < 1 +sizeof(login_id)) {
332 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
335 ibuf++, ibuflen--; /* ?? */
337 /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
338 memcpy( &login_id, ibuf, sizeof(login_id) );
339 ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
340 login_id = ntohs( login_id );
342 /* get the username buffer from apfd */
343 if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
346 /* get the session_info structure from afpd. We need the session key */
347 if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
350 if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
351 /* Should never happen. Most likely way too old afpd version */
352 LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
356 /* We skip past the 'username' parameter because all that matters is the ticket */
358 while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
360 LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
364 ibuf++, ibuflen--; /* null termination */
366 if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
368 LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
370 /* get the length of the ticket the client sends us */
371 memcpy(&ticket_len, ibuf, sizeof(ticket_len));
372 ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
373 ticket_len = ntohs( ticket_len );
375 /* a little bounds checking */
376 if (ticket_len > ibuflen) {
377 LOG(log_info, logtype_uams,
378 "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
382 /* now try to authenticate */
383 if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
384 LOG(log_info, logtype_uams, "do_gss_auth failed" );
389 /* We use the username we got back from the gssapi client_name.
390 Should we compare this to the username the client sent in the clear?
391 We know the character encoding of the cleartext username (UTF8), what
392 encoding is the gssapi name in? */
393 if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
394 LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
395 return AFPERR_NOTAUTH;
397 if (uam_checkuser(pwd) < 0) {
398 LOG(log_info, logtype_uams, "%s not a valid user", username);
399 return AFPERR_NOTAUTH;
407 static void gss_logout() {
410 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
411 char *ibuf, size_t ibuflen,
412 char *rbuf, size_t *rbuflen)
414 return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
417 static int uam_setup(const char *path)
419 return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
420 gss_login, gss_logincont, gss_logout, gss_login_ext);
423 static void uam_cleanup(void)
425 uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
428 UAM_MODULE_EXPORT struct uam_export uams_gss = {
431 uam_setup, uam_cleanup