]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Fix compiler diagnostics
[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     gss_release_buffer(&status, &authenticator_buff);
322     gss_delete_sec_context(&status, &context, NULL);
323
324     return ret;
325 }
326
327 /* -------------------------- */
328
329 /*
330  * For the gss uam, this function only needs to return a two-byte
331  * login-session id. None of the data provided by the client up to this
332  * point is trustworthy as we'll have a signed ticket to parse in logincont.
333  */
334 static int gss_login(void *obj,
335                      struct passwd **uam_pwd,
336                      char *ibuf, size_t ibuflen,
337                      char *rbuf, size_t *rbuflen)
338 {
339     *rbuflen = 0;
340
341     /* The reply contains a two-byte ID value - note
342      * that Apple's implementation seems to always return 1 as well
343      */
344     uint16_t temp16 = htons(1);
345     memcpy(rbuf, &temp16, sizeof(temp16));
346     *rbuflen += sizeof(temp16);
347
348     return AFPERR_AUTHCONT;
349 }
350
351 static int gss_logincont(void *obj,
352                          struct passwd **uam_pwd,
353                          char *ibuf, size_t ibuflen,
354                          char *rbuf, size_t *rbuflen)
355 {
356     struct passwd *pwd = NULL;
357     uint16_t login_id;
358     char *username;
359     uint16_t ticket_len;
360     char *p;
361     int rblen;
362     size_t userlen;
363     struct session_info *sinfo;
364
365     /* Apple's AFP 3.1 documentation specifies that this command
366      * takes the following format:
367      * pad (byte)
368      * id returned in LoginExt response (uint16_t)
369      * username (format unspecified)
370      *   padded, when necessary, to end on an even boundary
371      * ticket length (uint16_t)
372      * ticket
373      */
374
375     /* Observation of AFP clients in the wild indicate that the actual
376      * format of this request is as follows:
377      * pad (byte) [consumed before login_ext is called]
378      * ?? (byte) - always observed to be 0
379      * id returned in LoginExt response (uint16_t)
380      * username, encoding unspecified, null terminated C string,
381      *   padded when the terminating null is an even numbered byte.
382      *   The packet is formated such that the username begins on an
383      *   odd numbered byte. Eg if the username is 3 characters and the
384      *   terminating null makes 4, expect to pad the the result.
385      *   The encoding of this string is unknown.
386      * ticket length (uint16_t)
387      * ticket
388      */
389
390     rblen = *rbuflen = 0;
391
392     if (ibuflen < 1 +sizeof(login_id)) {
393         LOG_LOGINCONT(log_info, "received incomplete packet");
394         return AFPERR_PARAM;
395     }
396     ibuf++, ibuflen--; /* ?? */
397
398     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
399     memcpy(&login_id, ibuf, sizeof(login_id));
400     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
401     login_id = ntohs(login_id);
402
403     /* get the username buffer from apfd */
404     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, &username, &userlen) < 0)
405         return AFPERR_MISC;
406
407     /* get the session_info structure from afpd. We need the session key */
408     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, &sinfo, NULL) < 0)
409         return AFPERR_MISC;
410
411     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
412         /* Should never happen. Most likely way too old afpd version */
413         LOG_LOGINCONT(log_error, "internal error: afpd's sessionkey not set");
414         return AFPERR_MISC;
415     }
416
417     /* We skip past the 'username' parameter because all that matters is the ticket */
418     p = ibuf;
419     while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
420     if (ibuflen < 4) {
421         LOG_LOGINCONT(log_info, "user is %s, no ticket", p);
422         return AFPERR_PARAM;
423     }
424
425     ibuf++, ibuflen--; /* null termination */
426
427     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
428
429     LOG_LOGINCONT(log_debug, "client thinks user is %s", p);
430
431     /* get the length of the ticket the client sends us */
432     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
433     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
434     ticket_len = ntohs(ticket_len);
435
436     /* a little bounds checking */
437     if (ticket_len > ibuflen) {
438         LOG_LOGINCONT(log_info,
439                       "invalid ticket length (%u > %u)",
440                       ticket_len, ibuflen);
441         return AFPERR_PARAM;
442     }
443
444     /* now try to authenticate */
445     if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
446         LOG_LOGINCONT(log_info, "do_gss_auth() failed" );
447         *rbuflen = 0;
448         return AFPERR_MISC;
449     }
450
451     /* We use the username we got back from the gssapi client_name.
452        Should we compare this to the username the client sent in the clear?
453        We know the character encoding of the cleartext username (UTF8), what
454        encoding is the gssapi name in? */
455     if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
456         LOG_LOGINCONT(log_info, "uam_getname() failed for %s", username);
457         return AFPERR_NOTAUTH;
458     }
459     if (uam_checkuser(pwd) < 0) {
460         LOG_LOGINCONT(log_info, "`%s'' not a valid user", username);
461         return AFPERR_NOTAUTH;
462     }
463
464     *rbuflen = rblen;
465     *uam_pwd = pwd;
466     return AFP_OK;
467 }
468
469 /* logout */
470 static void gss_logout() {
471 }
472
473 static int gss_login_ext(void *obj,
474                          char *uname,
475                          struct passwd **uam_pwd,
476                          char *ibuf, size_t ibuflen,
477                          char *rbuf, size_t *rbuflen)
478 {
479     return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
480 }
481
482 static int uam_setup(const char *path)
483 {
484     return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
485                         gss_login, gss_logincont, gss_logout, gss_login_ext);
486 }
487
488 static void uam_cleanup(void)
489 {
490     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
491 }
492
493 UAM_MODULE_EXPORT struct uam_export uams_gss = {
494     UAM_MODULE_SERVER,
495     UAM_MODULE_VERSION,
496     uam_setup,
497     uam_cleanup
498 };