]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Cleaned up includes
[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 <stdlib.h>
14 #include <string.h>
15 #include <arpa/inet.h>
16
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>
22
23 /* Kerberos includes */
24 #ifdef HAVE_GSSAPI_GSSAPI_H
25 #include <gssapi/gssapi.h>
26 #else
27 #include <gssapi.h>
28 #endif // HAVE_GSSAPI_GSSAPI_H
29
30 static void log_status( char *s, OM_uint32 major_status,
31                         OM_uint32 minor_status )
32 {
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;
36
37     while (1) {
38         maj_status = gss_display_status( &min_status, major_status,
39                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
40                                          &maj_ctx, &msg );
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);
44
45         if (!maj_ctx)
46             break;
47     }
48     while (1) {
49         maj_status = gss_display_status( &min_status, minor_status,
50                                          GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
51                                          &min_ctx, &msg );
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);
55
56         if (!min_ctx)
57             break;
58     }
59 }
60
61
62 static void log_ctx_flags( OM_uint32 flags )
63 {
64 #ifdef DEBUG
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" );
77 #endif
78 }
79
80
81 /* get the username */
82 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
83 {
84     OM_uint32 major_status = 0, minor_status = 0;
85     gss_buffer_desc client_name_buffer;
86     char *p;
87     int namelen, ret=0;
88
89     /*
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.
95      */
96
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 );
101         return 1;
102     }
103
104     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
105
106     /* chop off realm */
107     p = strchr( client_name_buffer.value, '@' );
108     if (p)
109         *p = 0;
110     /* FIXME: chop off instance? */
111     p = strchr( client_name_buffer.value, '/' );
112     if (p)
113         *p = 0;
114
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);
121         ret = 1;
122     }
123     else {
124         /* copy stripped username to afpd's buffer */
125         strlcpy(username, client_name_buffer.value, ulen);
126     }
127
128     /* we're done with client_name_buffer, release it */
129     gss_release_buffer(&minor_status, &client_name_buffer );
130
131     return ret;
132 }
133
134 /* wrap afpd's sessionkey */
135 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
136 {
137     OM_uint32 status = 0;
138     int ret=0;
139     gss_buffer_desc sesskey_buff, wrap_buff;
140
141     /*
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.
146      */
147
148     sesskey_buff.value = sinfo->sessionkey;
149     sesskey_buff.length = sinfo->sessionkey_len;
150
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);
154
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 );
158         return 1;
159     }
160
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",
165             wrap_buff.length);
166         ret = 1;
167     } else {
168         /* cryptedkey is binary data */
169         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
170         sinfo->cryptedkey_len = wrap_buff.length;
171     }
172
173     /* we're done with buffer, release */
174     gss_release_buffer( &status, &wrap_buff );
175
176     return ret;
177 }
178
179 /*-------------*/
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)
183 {
184     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
185
186     /* Initialize autheticator buffer. */
187     authenticator_buff->length = 0;
188     authenticator_buff->value = NULL;
189
190     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
191         ticket_buffer->length);
192
193     /*
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.
197      */
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 );
202
203     if (major_status != GSS_S_COMPLETE) {
204         log_status( "accept_sec_context", major_status, minor_status );
205         return 1;
206     }
207     log_ctx_flags( ret_flags );
208     return 0;
209 }
210
211
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 )
216 {
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;
221     int ret = 0;
222
223     /*
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!
227      */
228     ticket_buffer.length = ticket_len;
229     ticket_buffer.value = ibuf;
230
231     if ((ret = accept_sec_context(&context_handle, &ticket_buffer, 
232                                   &client_name, &authenticator_buff)))
233         return ret;
234
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;
239
240     if ((ret = wrap_sessionkey(context_handle, sinfo)))
241         goto cleanup_client_name;
242
243     /* Authenticated, construct the reply using:
244      * authenticator length (u_int16_t)
245      * authenticator
246      */
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);
252
253     /* copy the authenticator value into the reply buffer */
254     memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
255     *rbuflen += authenticator_buff.length;
256
257 cleanup_client_name:
258     gss_release_name( &status, &client_name );
259
260 cleanup_context:
261     gss_release_buffer( &status, &authenticator_buff );
262     gss_delete_sec_context( &status, &context_handle, NULL );
263
264     return ret;
265 }
266
267 /* -------------------------- */
268
269 /*
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.
273  */
274 static int gss_login(void *obj, struct passwd **uam_pwd,
275                      char *ibuf, size_t ibuflen,
276                      char *rbuf, size_t *rbuflen)
277 {
278
279     uint16_t  temp16;
280
281     *rbuflen = 0;
282
283     /* The reply contains a two-byte ID value - note
284      * that Apple's implementation seems to always return 1 as well
285      */
286     temp16 = htons( 1 );
287     memcpy(rbuf, &temp16, sizeof(temp16));
288     *rbuflen += sizeof(temp16);
289     return AFPERR_AUTHCONT;
290 }
291
292 static int gss_logincont(void *obj, struct passwd **uam_pwd,
293                          char *ibuf, size_t ibuflen,
294                          char *rbuf, size_t *rbuflen)
295 {
296     struct passwd *pwd = NULL;
297     uint16_t login_id;
298     char *username;
299     uint16_t ticket_len;
300     char *p;
301     int rblen;
302     size_t userlen;
303     struct session_info *sinfo;
304
305     /* Apple's AFP 3.1 documentation specifies that this command
306      * takes the following format:
307      * pad (byte)
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)
311      * ticket
312      */
313
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)
326      * ticket
327      */
328
329     rblen = *rbuflen = 0;
330
331     if (ibuflen < 1 +sizeof(login_id)) {
332         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
333         return AFPERR_PARAM;
334     }
335     ibuf++, ibuflen--; /* ?? */
336
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 );
341
342     /* get the username buffer from apfd */
343     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
344         return AFPERR_MISC;
345
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)
348         return AFPERR_MISC;
349
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");
353         return AFPERR_MISC;
354     }
355
356     /* We skip past the 'username' parameter because all that matters is the ticket */
357     p = ibuf;
358     while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
359     if (ibuflen < 4) {
360         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
361         return AFPERR_PARAM;
362     }
363
364     ibuf++, ibuflen--; /* null termination */
365
366     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
367
368     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
369
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 );
374
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);
379         return AFPERR_PARAM;
380     }
381
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" );
385         *rbuflen = 0;
386         return AFPERR_MISC;
387     }
388
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;
396     }
397     if (uam_checkuser(pwd) < 0) {
398         LOG(log_info, logtype_uams, "%s not a valid user", username);
399         return AFPERR_NOTAUTH;
400     }
401     *rbuflen = rblen;
402     *uam_pwd = pwd;
403     return AFP_OK;
404 }
405
406 /* logout */
407 static void gss_logout() {
408 }
409
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)
413 {
414     return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
415 }
416
417 static int uam_setup(const char *path)
418 {
419     return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
420                         gss_login, gss_logincont, gss_logout, gss_login_ext);
421 }
422
423 static void uam_cleanup(void)
424 {
425     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
426 }
427
428 UAM_MODULE_EXPORT struct uam_export uams_gss = {
429     UAM_MODULE_SERVER,
430     UAM_MODULE_VERSION,
431     uam_setup, uam_cleanup
432 };