]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Merge remote branch 'origin/product-2-2' into develop
[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 <stdbool.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <arpa/inet.h>
18
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>
24
25 /* Kerberos includes */
26 #ifdef HAVE_GSSAPI_GSSAPI_H
27 #include <gssapi/gssapi.h>
28 #else
29 #include <gssapi.h>
30 #endif // HAVE_GSSAPI_GSSAPI_H
31
32 #define LOG_UAMS(log_level, ...) \
33     LOG(log_level, logtype_uams, __VA_ARGS__)
34
35 #define LOG_LOGINCONT(log_level, ...) \
36     LOG_UAMS(log_level, "FPLoginCont: " __VA_ARGS__)
37
38 static void log_status(char *s,
39                        OM_uint32 major_status,
40                        OM_uint32 minor_status)
41 {
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;
45
46     while (1) {
47         maj_status = gss_display_status( &min_status, major_status,
48                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
49                                          &maj_ctx, &msg );
50         LOG_UAMS(log_error, "%s %.*s (error %s)",
51                  s, msg.length, msg.value, strerror(errno));
52         gss_release_buffer(&min_status, &msg);
53
54         if (!maj_ctx)
55             break;
56     }
57
58     while (1) {
59         maj_status = gss_display_status( &min_status, minor_status,
60                                          GSS_C_MECH_CODE, GSS_C_NULL_OID,
61                                          &min_ctx, &msg );
62         LOG_UAMS(log_error, "%s %.*s (error %s)",
63                  s, msg.length, msg.value, strerror(errno));
64         gss_release_buffer(&min_status, &msg);
65
66         if (!min_ctx)
67             break;
68     }
69 }
70
71 static void log_ctx_flags(OM_uint32 flags)
72 {
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");
85 }
86
87 static void log_service_name(gss_ctx_id_t context)
88 {
89     OM_uint32 major_status = 0, minor_status = 0;
90     gss_name_t service_name;
91     gss_buffer_desc service_name_buffer;
92
93     major_status = gss_inquire_context(&minor_status,
94                                        context,
95                                        NULL,
96                                        &service_name,
97                                        NULL,
98                                        NULL,
99                                        NULL,
100                                        NULL,
101                                        NULL);
102     if (major_status != GSS_S_COMPLETE) {
103         log_status("gss_inquire_context", major_status, minor_status);
104         return;
105     }
106
107     major_status = gss_display_name(&minor_status,
108                                     service_name,
109                                     &service_name_buffer,
110                                     NULL);
111     if (major_status == GSS_S_COMPLETE) {
112         LOG_LOGINCONT(log_debug,
113                       "service principal is `%s'",
114                       service_name_buffer.value);
115
116         gss_release_buffer(&minor_status, &service_name_buffer);
117     } else
118         log_status("gss_display_name", major_status, minor_status);
119
120     gss_release_name(&minor_status, &service_name);
121 }
122
123 static int get_client_username(char *username,
124                                int ulen,
125                                gss_name_t *client_name)
126 {
127     OM_uint32 major_status = 0, minor_status = 0;
128     gss_buffer_desc client_name_buffer;
129     char *p;
130     int ret = 0;
131
132     /*
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.
138      */
139
140     major_status = gss_display_name(&minor_status,
141                                     *client_name,
142                                     &client_name_buffer,
143                                     NULL);
144     if (major_status != GSS_S_COMPLETE) {
145         log_status("gss_display_name", major_status, minor_status);
146         return 1;
147     }
148
149     LOG_LOGINCONT(log_debug,
150                   "user principal is `%s'",
151                   client_name_buffer.value);
152
153     /* chop off realm */
154     p = strchr(client_name_buffer.value, '@');
155     if (p)
156         *p = 0;
157     /* FIXME: chop off instance? */
158     p = strchr(client_name_buffer.value, '/');
159     if (p)
160         *p = 0;
161
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);
169         ret = 1;
170     } else {
171         /* copy stripped username to afpd's buffer */
172         strlcpy(username, client_name_buffer.value, ulen);
173     }
174
175     gss_release_buffer(&minor_status, &client_name_buffer);
176
177     return ret;
178 }
179
180 /* wrap afpd's sessionkey */
181 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
182 {
183     OM_uint32 major_status = 0, minor_status = 0;
184     gss_buffer_desc sesskey_buff, wrap_buff;
185     int ret = 0;
186
187     /*
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.
192      */
193     sesskey_buff.value = sinfo->sessionkey;
194     sesskey_buff.length = sinfo->sessionkey_len;
195
196     /* gss_wrap the session key with the default mechanism.
197        Require both confidentiality and integrity services */
198     major_status = gss_wrap(&minor_status,
199                             context,
200                             true,
201                             GSS_C_QOP_DEFAULT,
202                             &sesskey_buff,
203                             NULL,
204                             &wrap_buff);
205
206     if (major_status != GSS_S_COMPLETE) {
207         log_status("gss_wrap", major_status, minor_status);
208         return 1;
209     }
210
211     /* store the wrapped session key in afpd's session_info struct */
212     if (NULL == (sinfo->cryptedkey = malloc(wrap_buff.length))) {
213         LOG_UAMS(log_error,
214                  "wrap_sessionkey: out of memory tyring to allocate %u bytes",
215                  wrap_buff.length);
216         ret = 1;
217     } else {
218         /* cryptedkey is binary data */
219         memcpy(sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
220         sinfo->cryptedkey_len = wrap_buff.length;
221     }
222
223     /* we're done with buffer, release */
224     gss_release_buffer(&minor_status, &wrap_buff);
225
226     return ret;
227 }
228
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)
233 {
234     OM_uint32 major_status = 0, minor_status = 0, flags = 0;
235
236     /* Initialize autheticator buffer. */
237     authenticator_buff->length = 0;
238     authenticator_buff->value = NULL;
239
240     LOG_LOGINCONT(log_debug,
241                   "accepting context (ticketlen: %u)",
242                   ticket_buffer->length);
243
244     /*
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.
248      */
249     major_status = gss_accept_sec_context(&minor_status,
250                                           context,
251                                           GSS_C_NO_CREDENTIAL,
252                                           ticket_buffer,
253                                           GSS_C_NO_CHANNEL_BINDINGS,
254                                           client_name,
255                                           NULL,
256                                           authenticator_buff,
257                                           &flags,
258                                           NULL,
259                                           NULL);
260
261     if (major_status != GSS_S_COMPLETE) {
262         log_status("gss_accept_sec_context", major_status, minor_status);
263         return 1;
264     }
265
266     log_ctx_flags(flags);
267     return 0;
268 }
269
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 )
275 {
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;
280     int ret = 0;
281
282     /*
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!
286      */
287     ticket_buffer.length = ibuflen;
288     ticket_buffer.value = ibuf;
289
290     if ((ret = accept_sec_context(&context,
291                                   &ticket_buffer,
292                                   &client_name,
293                                   &authenticator_buff)))
294         return ret;
295     log_service_name(context);
296
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;
301
302     if ((ret = wrap_sessionkey(context, sinfo)))
303         goto cleanup_client_name;
304
305     /* Authenticated, construct the reply using:
306      * authenticator length (uint16_t)
307      * authenticator
308      */
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);
314
315     /* copy the authenticator value into the reply buffer */
316     memcpy(rbuf, authenticator_buff.value, authenticator_buff.length);
317     *rbuflen += authenticator_buff.length;
318
319 cleanup_client_name:
320     gss_release_name(&status, &client_name);
321
322 cleanup_context:
323     gss_release_buffer(&status, &authenticator_buff);
324     gss_delete_sec_context(&status, &context, NULL);
325
326     return ret;
327 }
328
329 /* -------------------------- */
330
331 /*
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.
335  */
336 static int gss_login(void *obj,
337                      struct passwd **uam_pwd,
338                      char *ibuf, size_t ibuflen,
339                      char *rbuf, size_t *rbuflen)
340 {
341     *rbuflen = 0;
342
343     /* The reply contains a two-byte ID value - note
344      * that Apple's implementation seems to always return 1 as well
345      */
346     uint16_t temp16 = htons(1);
347     memcpy(rbuf, &temp16, sizeof(temp16));
348     *rbuflen += sizeof(temp16);
349
350     return AFPERR_AUTHCONT;
351 }
352
353 static int gss_logincont(void *obj,
354                          struct passwd **uam_pwd,
355                          char *ibuf, size_t ibuflen,
356                          char *rbuf, size_t *rbuflen)
357 {
358     struct passwd *pwd = NULL;
359     uint16_t login_id;
360     char *username;
361     uint16_t ticket_len;
362     char *p;
363     int rblen;
364     size_t userlen;
365     struct session_info *sinfo;
366
367     /* Apple's AFP 3.1 documentation specifies that this command
368      * takes the following format:
369      * pad (byte)
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)
374      * ticket
375      */
376
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)
389      * ticket
390      */
391
392     rblen = *rbuflen = 0;
393
394     if (ibuflen < 1 +sizeof(login_id)) {
395         LOG_LOGINCONT(log_info, "received incomplete packet");
396         return AFPERR_PARAM;
397     }
398     ibuf++, ibuflen--; /* ?? */
399
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);
404
405     /* get the username buffer from apfd */
406     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0)
407         return AFPERR_MISC;
408
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)
411         return AFPERR_MISC;
412
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");
416         return AFPERR_MISC;
417     }
418
419     /* We skip past the 'username' parameter because all that matters is the ticket */
420     p = ibuf;
421     while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
422     if (ibuflen < 4) {
423         LOG_LOGINCONT(log_info, "user is %s, no ticket", p);
424         return AFPERR_PARAM;
425     }
426
427     ibuf++, ibuflen--; /* null termination */
428
429     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
430
431     LOG_LOGINCONT(log_debug, "client thinks user is %s", p);
432
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);
437
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);
443         return AFPERR_PARAM;
444     }
445
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" );
449         *rbuflen = 0;
450         return AFPERR_MISC;
451     }
452
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;
460     }
461     if (uam_checkuser(pwd) < 0) {
462         LOG_LOGINCONT(log_info, "`%s'' not a valid user", username);
463         return AFPERR_NOTAUTH;
464     }
465
466     *rbuflen = rblen;
467     *uam_pwd = pwd;
468     return AFP_OK;
469 }
470
471 /* logout */
472 static void gss_logout() {
473 }
474
475 static int gss_login_ext(void *obj,
476                          char *uname,
477                          struct passwd **uam_pwd,
478                          char *ibuf, size_t ibuflen,
479                          char *rbuf, size_t *rbuflen)
480 {
481     return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
482 }
483
484 static int uam_setup(const char *path)
485 {
486     return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
487                         gss_login, gss_logincont, gss_logout, gss_login_ext);
488 }
489
490 static void uam_cleanup(void)
491 {
492     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
493 }
494
495 UAM_MODULE_EXPORT struct uam_export uams_gss = {
496     UAM_MODULE_SERVER,
497     UAM_MODULE_VERSION,
498     uam_setup,
499     uam_cleanup
500 };