]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Remove all Appletalk stuff
[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
30 #if HAVE_GSSAPI_H
31 #include <gssapi.h>
32 #endif
33
34 #if HAVE_GSSAPI_GSSAPI_H
35 #include <gssapi/gssapi.h>
36 #endif
37
38 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
39 #include <gssapi/gssapi_generic.h>
40 #endif
41
42 #if HAVE_GSSAPI_GSSAPI_KRB5_H
43 #include <gssapi/gssapi_krb5.h>
44 #endif
45
46 #if HAVE_COM_ERR_H
47 #include <com_err.h>
48 #endif
49
50 /* We work around something I don't entirely understand... */
51 /* BF: This is a Heimdal/MIT compatibility fix */
52 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
53 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
54 #endif
55
56 #ifdef MIN
57 #undef MIN
58 #endif
59
60 #define MIN(a, b) ((a > b) ? b : a)
61
62 static void log_status( char *s, OM_uint32 major_status,
63                         OM_uint32 minor_status )
64 {
65     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
66     OM_uint32 min_status, maj_status;
67     OM_uint32 maj_ctx = 0, min_ctx = 0;
68
69     while (1) {
70         maj_status = gss_display_status( &min_status, major_status,
71                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
72                                          &maj_ctx, &msg );
73         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
74             (int)msg.length, msg.value, strerror(errno));
75         gss_release_buffer(&min_status, &msg);
76
77         if (!maj_ctx)
78             break;
79     }
80     while (1) {
81         maj_status = gss_display_status( &min_status, minor_status,
82                                          GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
83                                          &min_ctx, &msg );
84         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
85             (int)msg.length, msg.value, strerror(errno));
86         gss_release_buffer(&min_status, &msg);
87
88         if (!min_ctx)
89             break;
90     }
91 }
92
93
94 static void log_ctx_flags( OM_uint32 flags )
95 {
96 #ifdef DEBUG
97     if (flags & GSS_C_DELEG_FLAG)
98         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
99     if (flags & GSS_C_MUTUAL_FLAG)
100         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
101     if (flags & GSS_C_REPLAY_FLAG)
102         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
103     if (flags & GSS_C_SEQUENCE_FLAG)
104         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
105     if (flags & GSS_C_CONF_FLAG)
106         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
107     if (flags & GSS_C_INTEG_FLAG)
108         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
109 #endif
110 }
111
112 static void log_principal(gss_name_t server_name)
113 {
114 #if 0
115     /* FIXME: must call gss_canonicalize_name before gss_export_name */
116     OM_uint32 major_status = 0, minor_status = 0;
117     gss_buffer_desc exported_name;
118
119     /* Only for debugging purposes, check the gssapi internal representation */
120     major_status = gss_export_name(&minor_status, server_name, &exported_name);
121     LOG(log_debug, logtype_uams, "log_principal: exported server name is %s", (char*) exported_name.value);
122     gss_release_buffer( &minor_status, &exported_name );
123 #endif
124 }
125
126 /* get the principal from afpd and import it into server_name */
127 static int get_afpd_principal(void *obj, gss_name_t *server_name)
128 {
129     OM_uint32 major_status = 0, minor_status = 0;
130     char *fqdn, *service, *principal, *p;
131     size_t fqdnlen=0, servicelen=0;
132     size_t principal_length;
133     gss_buffer_desc s_princ_buffer;
134
135     /* get all the required information from afpd */
136     if (uam_afpserver_option(obj, UAM_OPTION_FQDN, (void*) &fqdn, &fqdnlen) < 0)
137         return 1;
138     LOG(log_debug, logtype_uams, "get_afpd_principal: fqdn: %s", fqdn);
139
140     if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
141         return 1;
142     LOG(log_debug, logtype_uams, "get_afpd_principal: service: %s", service);
143
144     /* we need all the info, log error and return if one's missing */
145     if (!service || !servicelen || !fqdn || !fqdnlen) {
146         LOG(log_error, logtype_uams,
147             "get_afpd_principal: could not retrieve required information from afpd.");
148         return 1;
149     }
150
151     /* allocate memory to hold the temporary principal string */
152     principal_length = servicelen + 1 + fqdnlen + 1;
153     if ( NULL == (principal = (char*) malloc( principal_length)) ) {
154         LOG(log_error, logtype_uams,
155             "get_afpd_principal: out of memory allocating %u bytes",
156             principal_length);
157         return 1;
158     }
159
160     /*
161      * Build the principal string.
162      * Format: 'service@fqdn'
163      */
164     strlcpy( principal, service, principal_length);
165     strlcat( principal, "@", principal_length);
166
167     /*
168      * The fqdn we get from afpd may contain a port.
169      * We need to strip the port from fqdn for principal.
170      */
171     if ((p = strchr(fqdn, ':')))
172         *p = '\0';
173
174     strlcat( principal, fqdn, principal_length);
175     if (p)
176         *p = ':';
177     /*
178      * Import our principal into the gssapi internal representation
179      * stored in server_name.
180      */
181     s_princ_buffer.value = principal;
182     s_princ_buffer.length = strlen( principal ) + 1;
183
184     LOG(log_debug, logtype_uams, "get_afpd_principal: importing principal `%s'", principal);
185     major_status = gss_import_name( &minor_status,
186                                     &s_princ_buffer,
187                                     GSS_C_NT_HOSTBASED_SERVICE,
188                                     server_name );
189
190     /*
191      * Get rid of malloc'ed memmory.
192      * Don't release the s_princ_buffer, we free principal instead.
193      */
194     free(principal);
195
196     if (major_status != GSS_S_COMPLETE) {
197         /* Importing our service principal failed, bail out. */
198         log_status( "import_principal", major_status, minor_status );
199         return 1;
200     }
201     return 0;
202 }
203
204
205 /* get the username */
206 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
207 {
208     OM_uint32 major_status = 0, minor_status = 0;
209     gss_buffer_desc client_name_buffer;
210     char *p;
211     int namelen, ret=0;
212
213     /*
214      * To extract the unix username, use gss_display_name on client_name.
215      * We do rely on gss_display_name returning a zero terminated string.
216      * The username returned contains the realm and possibly an instance.
217      * We only want the username for afpd, so we have to strip those from
218      * the username before copying it to afpd's buffer.
219      */
220
221     major_status = gss_display_name( &minor_status, *client_name,
222                                      &client_name_buffer, (gss_OID *)NULL );
223     if (major_status != GSS_S_COMPLETE) {
224         log_status( "display_name", major_status, minor_status );
225         return 1;
226     }
227
228     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
229
230     /* chop off realm */
231     p = strchr( client_name_buffer.value, '@' );
232     if (p)
233         *p = 0;
234     /* FIXME: chop off instance? */
235     p = strchr( client_name_buffer.value, '/' );
236     if (p)
237         *p = 0;
238
239     /* check if this username fits into afpd's username buffer */
240     namelen = strlen(client_name_buffer.value);
241     if ( namelen >= ulen ) {
242         /* The username is too long for afpd's buffer, bail out */
243         LOG(log_error, logtype_uams,
244             "get_client_username: username `%s' too long", client_name_buffer.value);
245         ret = 1;
246     }
247     else {
248         /* copy stripped username to afpd's buffer */
249         strlcpy(username, client_name_buffer.value, ulen);
250     }
251
252     /* we're done with client_name_buffer, release it */
253     gss_release_buffer(&minor_status, &client_name_buffer );
254
255     return ret;
256 }
257
258 /* wrap afpd's sessionkey */
259 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
260 {
261     OM_uint32 status = 0;
262     int ret=0;
263     gss_buffer_desc sesskey_buff, wrap_buff;
264
265     /*
266      * gss_wrap afpd's session_key.
267      * This is needed fo OS X 10.3 clients. They request this information
268      * with type 8 (kGetKerberosSessionKey) on FPGetSession.
269      * See AFP 3.1 specs, page 77.
270      */
271
272     sesskey_buff.value = sinfo->sessionkey;
273     sesskey_buff.length = sinfo->sessionkey_len;
274
275     /* gss_wrap the session key with the default machanism.
276        Require both confidentiality and integrity services */
277     gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
278
279     if ( status != GSS_S_COMPLETE) {
280         LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
281         log_status( "GSS wrap", 0, status );
282         return 1;
283     }
284
285     /* store the wrapped session key in afpd's session_info struct */
286     if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
287         LOG(log_error, logtype_uams,
288             "wrap_sessionkey: out of memory tyring to allocate %u bytes",
289             wrap_buff.length);
290         ret = 1;
291     } else {
292         /* cryptedkey is binary data */
293         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
294         sinfo->cryptedkey_len = wrap_buff.length;
295     }
296
297     /* we're done with buffer, release */
298     gss_release_buffer( &status, &wrap_buff );
299
300     return ret;
301 }
302
303 /*-------------*/
304 static int acquire_credentials (gss_name_t *server_name, gss_cred_id_t *server_creds)
305 {
306     OM_uint32 major_status = 0, minor_status = 0;
307     char *envp;
308
309     if ((envp = getenv("KRB5_KTNAME")))
310         LOG(log_debug, logtype_uams,
311             "acquire credentials: acquiring credentials (uid = %d, keytab = %s)",
312             (int)geteuid(), envp);
313     else
314         LOG(log_debug, logtype_uams,
315             "acquire credentials: acquiring credentials (uid = %d) - $KRB5_KTNAME not found in env",
316             (int)geteuid());
317         
318     /*
319      * Acquire credentials usable for accepting context negotiations.
320      * Credentials are for server_name, have an indefinite lifetime,
321      * have no specific mechanisms, are to be used for accepting context
322      * negotiations and are to be placed in server_creds.
323      * We don't care about the mechanisms or about the time for which they are valid.
324      */
325     major_status = gss_acquire_cred( &minor_status, *server_name,
326                                      GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
327                                      server_creds, NULL, NULL );
328
329     if (major_status != GSS_S_COMPLETE) {
330         log_status( "acquire_cred", major_status, minor_status );
331         return 1;
332     }
333
334     return 0;
335 }
336
337 /*-------------*/
338 static int accept_sec_context (gss_ctx_id_t *context, gss_cred_id_t server_creds,
339                                gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
340                                gss_buffer_desc *authenticator_buff)
341 {
342     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
343
344     /* Initialize autheticator buffer. */
345     authenticator_buff->length = 0;
346     authenticator_buff->value = NULL;
347
348     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
349         ticket_buffer->length);
350
351     /*
352      * Try to accept the secondary context using the tocken in ticket_buffer.
353      * We don't care about the mechanisms used, nor for the time.
354      * We don't act as a proxy either.
355      */
356     major_status = gss_accept_sec_context( &minor_status, context,
357                                            server_creds, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
358                                            client_name, NULL, authenticator_buff,
359                                            &ret_flags, NULL, NULL );
360
361     if (major_status != GSS_S_COMPLETE) {
362         log_status( "accept_sec_context", major_status, minor_status );
363         return 1;
364     }
365     log_ctx_flags( ret_flags );
366     return 0;
367 }
368
369
370 /* return 0 on success */
371 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
372                        char *rbuf, int *rbuflen, char *username, int ulen,
373                        struct session_info *sinfo )
374 {
375     OM_uint32 status = 0;
376     gss_name_t server_name, client_name;
377     gss_cred_id_t server_creds;
378     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
379     gss_buffer_desc ticket_buffer, authenticator_buff;
380     int ret = 0;
381
382     /* import our principal name from afpd */
383     if (get_afpd_principal(obj, &server_name) != 0) {
384         return 1;
385     }
386     log_principal(server_name);
387
388     /* Now we have to acquire our credentials */
389     if ((ret = acquire_credentials (&server_name, &server_creds)))
390         goto cleanup_vars;
391
392     /*
393      * Try to accept the secondary context, using the ticket/token the
394      * client sent us. Ticket is stored at current ibuf position.
395      * Don't try to release ticket_buffer later, it points into ibuf!
396      */
397     ticket_buffer.length = ticket_len;
398     ticket_buffer.value = ibuf;
399
400     ret = accept_sec_context (&context_handle, server_creds, &ticket_buffer,
401                               &client_name, &authenticator_buff);
402
403     if (!ret) {
404         /* We succesfully acquired the secondary context, now get the
405            username for afpd and gss_wrap the sessionkey */
406         if ( 0 == (ret = get_client_username(username, ulen, &client_name)) ) {
407             ret = wrap_sessionkey(context_handle, sinfo);
408         }
409
410         if (!ret) {
411             /* FIXME: Is copying the authenticator really necessary?
412                Where is this documented? */
413             u_int16_t auth_len = htons( authenticator_buff.length );
414
415             /* copy the authenticator length into the reply buffer */
416             memcpy( rbuf, &auth_len, sizeof(auth_len) );
417             *rbuflen += sizeof(auth_len);
418             rbuf += sizeof(auth_len);
419
420             /* copy the authenticator value into the reply buffer */
421             memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
422             *rbuflen += authenticator_buff.length;
423         }
424
425         /* Clean up after ourselves */
426         gss_release_name( &status, &client_name );
427         if ( authenticator_buff.value)
428             gss_release_buffer( &status, &authenticator_buff );
429
430         gss_delete_sec_context( &status, &context_handle, NULL );
431     }
432     gss_release_cred( &status, &server_creds );
433
434 cleanup_vars:
435     gss_release_name( &status, &server_name );
436
437     return ret;
438 }
439
440 /* -------------------------- */
441 static int gss_login(void *obj, struct passwd **uam_pwd,
442                      char *ibuf, size_t ibuflen,
443                      char *rbuf, size_t *rbuflen)
444 {
445
446     u_int16_t  temp16;
447
448     *rbuflen = 0;
449
450     /* The reply contains a two-byte ID value - note
451      * that Apple's implementation seems to always return 1 as well
452      */
453     temp16 = htons( 1 );
454     memcpy(rbuf, &temp16, sizeof(temp16));
455     *rbuflen += sizeof(temp16);
456     return AFPERR_AUTHCONT;
457 }
458
459 static int gss_logincont(void *obj, struct passwd **uam_pwd,
460                          char *ibuf, size_t ibuflen,
461                          char *rbuf, size_t *rbuflen)
462 {
463     struct passwd *pwd = NULL;
464     u_int16_t login_id;
465     char *username;
466     u_int16_t ticket_len;
467     char *p;
468     int rblen;
469     size_t userlen;
470     struct session_info *sinfo;
471
472     /* Apple's AFP 3.1 documentation specifies that this command
473      * takes the following format:
474      * pad (byte)
475      * id returned in LoginExt response (u_int16_t)
476      * username (format unspecified) padded, when necessary, to end on an even boundary
477      * ticket length (u_int16_t)
478      * ticket
479      */
480
481     /* Observation of AFP clients in the wild indicate that the actual
482      * format of this request is as follows:
483      * pad (byte) [consumed before login_ext is called]
484      * ?? (byte) - always observed to be 0
485      * id returned in LoginExt response (u_int16_t)
486      * username, encoding unspecified, null terminated C string,
487      *   padded when the terminating null is an even numbered byte.
488      *   The packet is formated such that the username begins on an
489      *   odd numbered byte. Eg if the username is 3 characters and the
490      *   terminating null makes 4, expect to pad the the result.
491      *   The encoding of this string is unknown.
492      * ticket length (u_int16_t)
493      * ticket
494      */
495
496     rblen = *rbuflen = 0;
497
498     if (ibuflen < 1 +sizeof(login_id)) {
499         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
500         return AFPERR_PARAM;
501     }
502     ibuf++, ibuflen--; /* ?? */
503
504     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
505     memcpy( &login_id, ibuf, sizeof(login_id) );
506     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
507     login_id = ntohs( login_id );
508
509     /* get the username buffer from apfd */
510     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
511         return AFPERR_MISC;
512
513     /* get the session_info structure from afpd. We need the session key */
514     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
515         return AFPERR_MISC;
516
517     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
518         /* Should never happen. Most likely way too old afpd version */
519         LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
520         return AFPERR_MISC;
521     }
522
523     /* We skip past the 'username' parameter because all that matters is the ticket */
524     p = ibuf;
525     while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
526     if (ibuflen < 4) {
527         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
528         return AFPERR_PARAM;
529     }
530
531     ibuf++, ibuflen--; /* null termination */
532
533     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
534
535     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
536
537     /* get the length of the ticket the client sends us */
538     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
539     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
540     ticket_len = ntohs( ticket_len );
541
542     /* a little bounds checking */
543     if (ticket_len > ibuflen) {
544         LOG(log_info, logtype_uams,
545             "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
546         return AFPERR_PARAM;
547     }
548
549     /* now try to authenticate */
550     if (!do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
551         /* We use the username we got back from the gssapi client_name.
552            Should we compare this to the username the client sent in the clear?
553            We know the character encoding of the cleartext username (UTF8), what
554            encoding is the gssapi name in? */
555         if((pwd = uam_getname( obj, username, userlen )) == NULL) {
556             LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
557             return AFPERR_NOTAUTH;
558         }
559         if (uam_checkuser(pwd) < 0) {
560             LOG(log_info, logtype_uams, "%s not a valid user", username);
561             return AFPERR_NOTAUTH;
562         }
563         *rbuflen = rblen;
564         *uam_pwd = pwd;
565         return AFP_OK;
566     } else {
567         LOG(log_info, logtype_uams, "do_gss_auth failed" );
568         *rbuflen = 0;
569         return AFPERR_MISC;
570     }
571 }
572
573 /*
574  * For the krb5 uam, this function only needs to return a two-byte
575  * login-session id. None of the data provided by the client up to this
576  * point is trustworthy as we'll have a signed ticket to parse in logincont.
577  */
578 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
579                          char *ibuf, size_t ibuflen,
580                          char *rbuf, size_t *rbuflen)
581 {
582     u_int16_t  temp16;
583
584     *rbuflen = 0;
585
586     /* The reply contains a two-byte ID value - note
587      * that Apple's implementation seems to always return 1 as well
588      */
589     temp16 = htons( 1 );
590     memcpy(rbuf, &temp16, sizeof(temp16));
591     *rbuflen += sizeof(temp16);
592     return AFPERR_AUTHCONT;
593 }
594
595 /* logout */
596 static void gss_logout() {
597 }
598
599 int uam_setup(const char *path)
600 {
601     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
602                      gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
603         if (uam_register(UAM_SERVER_LOGIN, path, "Client Krb v2",
604                          gss_login, gss_logincont, gss_logout) < 0)
605             return -1;
606
607     return 0;
608 }
609
610 static void uam_cleanup(void)
611 {
612     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
613 }
614
615 UAM_MODULE_EXPORT struct uam_export uams_gss = {
616     UAM_MODULE_SERVER,
617     UAM_MODULE_VERSION,
618     uam_setup, uam_cleanup
619 };