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
38static 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
71static 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
87static 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
123static 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 */
181static 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
229static 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
270static 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
319cleanup_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 */
334static 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
351static 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 */
470static void gss_logout() {
471}
472
473static 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
482static 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
488static void uam_cleanup(void)
489{
490    uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
491}
492
493UAM_MODULE_EXPORT struct uam_export uams_gss = {
494    UAM_MODULE_SERVER,
495    UAM_MODULE_VERSION,
496    uam_setup,
497    uam_cleanup
498};
499