]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Register just UAM_SERVER_LOGIN_EXT
[netatalk.git] / etc / uams / uams_gss.c
1 /*
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.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif /* HAVE_CONFIG_H */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #ifdef HAVE_UNISTD_H
16 #include <unistd.h>
17 #endif /* HAVE_UNISTD_H */
18 #include <string.h>
19 #include <errno.h>
20 #include <arpa/inet.h>
21
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>
27
28 /* Kerberos includes */
29 #ifdef HAVE_GSSAPI_GSSAPI_H
30 #include <gssapi/gssapi.h>
31 #else
32 #include <gssapi.h>
33 #endif // HAVE_GSSAPI_GSSAPI_H
34
35 static void log_status( char *s, OM_uint32 major_status,
36                         OM_uint32 minor_status )
37 {
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;
41
42     while (1) {
43         maj_status = gss_display_status( &min_status, major_status,
44                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
45                                          &maj_ctx, &msg );
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);
49
50         if (!maj_ctx)
51             break;
52     }
53     while (1) {
54         maj_status = gss_display_status( &min_status, minor_status,
55                                          GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
56                                          &min_ctx, &msg );
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);
60
61         if (!min_ctx)
62             break;
63     }
64 }
65
66
67 static void log_ctx_flags( OM_uint32 flags )
68 {
69 #ifdef DEBUG
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" );
82 #endif
83 }
84
85
86 /* get the username */
87 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
88 {
89     OM_uint32 major_status = 0, minor_status = 0;
90     gss_buffer_desc client_name_buffer;
91     char *p;
92     int namelen, ret=0;
93
94     /*
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.
100      */
101
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 );
106         return 1;
107     }
108
109     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
110
111     /* chop off realm */
112     p = strchr( client_name_buffer.value, '@' );
113     if (p)
114         *p = 0;
115     /* FIXME: chop off instance? */
116     p = strchr( client_name_buffer.value, '/' );
117     if (p)
118         *p = 0;
119
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);
126         ret = 1;
127     }
128     else {
129         /* copy stripped username to afpd's buffer */
130         strlcpy(username, client_name_buffer.value, ulen);
131     }
132
133     /* we're done with client_name_buffer, release it */
134     gss_release_buffer(&minor_status, &client_name_buffer );
135
136     return ret;
137 }
138
139 /* wrap afpd's sessionkey */
140 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
141 {
142     OM_uint32 status = 0;
143     int ret=0;
144     gss_buffer_desc sesskey_buff, wrap_buff;
145
146     /*
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.
151      */
152
153     sesskey_buff.value = sinfo->sessionkey;
154     sesskey_buff.length = sinfo->sessionkey_len;
155
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);
159
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 );
163         return 1;
164     }
165
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",
170             wrap_buff.length);
171         ret = 1;
172     } else {
173         /* cryptedkey is binary data */
174         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
175         sinfo->cryptedkey_len = wrap_buff.length;
176     }
177
178     /* we're done with buffer, release */
179     gss_release_buffer( &status, &wrap_buff );
180
181     return ret;
182 }
183
184 /*-------------*/
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)
188 {
189     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
190
191     /* Initialize autheticator buffer. */
192     authenticator_buff->length = 0;
193     authenticator_buff->value = NULL;
194
195     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
196         ticket_buffer->length);
197
198     /*
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.
202      */
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 );
207
208     if (major_status != GSS_S_COMPLETE) {
209         log_status( "accept_sec_context", major_status, minor_status );
210         return 1;
211     }
212     log_ctx_flags( ret_flags );
213     return 0;
214 }
215
216
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 )
221 {
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;
226     int ret = 0;
227
228     /*
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!
232      */
233     ticket_buffer.length = ticket_len;
234     ticket_buffer.value = ibuf;
235
236     if ((ret = accept_sec_context(&context_handle, &ticket_buffer, 
237                                   &client_name, &authenticator_buff)))
238         return ret;
239
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;
244
245     if ((ret = wrap_sessionkey(context_handle, sinfo)))
246         goto cleanup_client_name;
247
248     /* Authenticated, construct the reply using:
249      * authenticator length (u_int16_t)
250      * authenticator
251      */
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);
257
258     /* copy the authenticator value into the reply buffer */
259     memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
260     *rbuflen += authenticator_buff.length;
261
262 cleanup_client_name:
263     gss_release_name( &status, &client_name );
264
265 cleanup_context:
266     gss_release_buffer( &status, &authenticator_buff );
267     gss_delete_sec_context( &status, &context_handle, NULL );
268
269     return ret;
270 }
271
272 /* -------------------------- */
273
274 /*
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.
278  */
279 static int gss_login(void *obj, struct passwd **uam_pwd,
280                      char *ibuf, size_t ibuflen,
281                      char *rbuf, size_t *rbuflen)
282 {
283
284     uint16_t  temp16;
285
286     *rbuflen = 0;
287
288     /* The reply contains a two-byte ID value - note
289      * that Apple's implementation seems to always return 1 as well
290      */
291     temp16 = htons( 1 );
292     memcpy(rbuf, &temp16, sizeof(temp16));
293     *rbuflen += sizeof(temp16);
294     return AFPERR_AUTHCONT;
295 }
296
297 static int gss_logincont(void *obj, struct passwd **uam_pwd,
298                          char *ibuf, size_t ibuflen,
299                          char *rbuf, size_t *rbuflen)
300 {
301     struct passwd *pwd = NULL;
302     uint16_t login_id;
303     char *username;
304     uint16_t ticket_len;
305     char *p;
306     int rblen;
307     size_t userlen;
308     struct session_info *sinfo;
309
310     /* Apple's AFP 3.1 documentation specifies that this command
311      * takes the following format:
312      * pad (byte)
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)
316      * ticket
317      */
318
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)
331      * ticket
332      */
333
334     rblen = *rbuflen = 0;
335
336     if (ibuflen < 1 +sizeof(login_id)) {
337         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
338         return AFPERR_PARAM;
339     }
340     ibuf++, ibuflen--; /* ?? */
341
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 );
346
347     /* get the username buffer from apfd */
348     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
349         return AFPERR_MISC;
350
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)
353         return AFPERR_MISC;
354
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");
358         return AFPERR_MISC;
359     }
360
361     /* We skip past the 'username' parameter because all that matters is the ticket */
362     p = ibuf;
363     while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
364     if (ibuflen < 4) {
365         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
366         return AFPERR_PARAM;
367     }
368
369     ibuf++, ibuflen--; /* null termination */
370
371     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
372
373     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
374
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 );
379
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);
384         return AFPERR_PARAM;
385     }
386
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" );
390         *rbuflen = 0;
391         return AFPERR_MISC;
392     }
393
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;
401     }
402     if (uam_checkuser(pwd) < 0) {
403         LOG(log_info, logtype_uams, "%s not a valid user", username);
404         return AFPERR_NOTAUTH;
405     }
406     *rbuflen = rblen;
407     *uam_pwd = pwd;
408     return AFP_OK;
409 }
410
411 /* logout */
412 static void gss_logout() {
413 }
414
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)
418 {
419     return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
420 }
421
422 static int uam_setup(const char *path)
423 {
424     return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
425                         gss_login, gss_logincont, gss_logout, gss_login_ext);
426 }
427
428 static void uam_cleanup(void)
429 {
430     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
431 }
432
433 UAM_MODULE_EXPORT struct uam_export uams_gss = {
434     UAM_MODULE_SERVER,
435     UAM_MODULE_VERSION,
436     uam_setup, uam_cleanup
437 };