]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Support for the Client Krb v2 UAM in Apple's MacOS X 10.2 and later.
[netatalk.git] / etc / uams / uams_gss.c
1 /*
2  * $Id: uams_gss.c,v 1.1 2003-08-22 17:12:45 samnoble Exp $
3  *
4  * Copyright (c) 1990,1993 Regents of The University of Michigan.
5  * Copyright (c) 1999 Adrian Sun (asun@u.washington.edu) 
6  * Copyright (c) 2003 The Reed Institute
7  * All Rights Reserved.  See COPYRIGHT.
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif /* HAVE_CONFIG_H */
13
14 #ifndef ATACC
15 #include <stdio.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif /* HAVE_UNISTD_H */
20
21 /* STDC check */
22 #if STDC_HEADERS
23 #include <string.h>
24 #else /* STDC_HEADERS */
25 #ifndef HAVE_STRCHR
26 #define strchr index
27 #define strrchr index
28 #endif /* HAVE_STRCHR */
29 char *strchr (), *strrchr ();
30 #ifndef HAVE_MEMCPY
31 #define memcpy(d,s,n) bcopy ((s), (d), (n))
32 #define memmove(d,s,n) bcopy ((s), (d), (n))
33 #endif /* ! HAVE_MEMCPY */
34 #endif /* STDC_HEADERS */
35
36 #include <atalk/logger.h>
37
38 // #include <security/pam_appl.h>
39
40 #include <atalk/afp.h>
41 #include <atalk/uam.h>
42
43 #include <gssapi/gssapi.h>
44 #include <gssapi/gssapi_generic.h>
45 #include <gssapi/gssapi_krb5.h>
46
47 /* The following routine is derived from code found in some GSS
48  * documentation from SUN.
49  */
50 static void log_status( char *s, OM_uint32 major_status, 
51                         OM_uint32 minor_status )
52 {
53     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
54     OM_uint32 min_status, maj_status;
55     OM_uint32 maj_ctx = 0, min_ctx = 0;
56
57     while (1) {
58         maj_status = gss_display_status( &min_status, major_status,
59                                         GSS_C_GSS_CODE, GSS_C_NULL_OID,
60                                         &maj_ctx, &msg );
61         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
62                                 (int)msg.length, msg.value, strerror(errno));
63         gss_release_buffer(&min_status, &msg);
64
65         if (!maj_ctx)
66             break;
67     }
68     while (1) {
69         maj_status = gss_display_status( &min_status, minor_status,
70                                         GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
71                                         &min_ctx, &msg );
72         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s, 
73                                 (int)msg.length, msg.value, strerror(errno));
74         gss_release_buffer(&min_status, &msg);
75
76         if (!min_ctx)
77             break;
78     }
79 }
80
81
82 void log_ctx_flags( OM_uint32 flags )
83 {
84     if (flags & GSS_C_DELEG_FLAG)
85         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
86     if (flags & GSS_C_MUTUAL_FLAG)
87         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
88     if (flags & GSS_C_REPLAY_FLAG)
89         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
90     if (flags & GSS_C_SEQUENCE_FLAG)
91         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
92     if (flags & GSS_C_CONF_FLAG)
93         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
94     if (flags & GSS_C_INTEG_FLAG)
95         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
96 }
97 /* We work around something I don't entirely understand... */
98 #if !defined (GSS_C_NT_HOSTBASED_SERVICE)
99 #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
100 #endif
101
102 #define MIN(a, b) ((a > b) ? b : a)
103 /* return 0 on success */
104 static int do_gss_auth( char *service, char *ibuf, int ticket_len,
105                  char *rbuf, int *rbuflen, char *username, int ulen ) 
106 {
107     OM_uint32 major_status = 0, minor_status = 0;
108     gss_name_t server_name;
109     gss_cred_id_t server_creds;
110     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
111     gss_buffer_desc ticket_buffer, authenticator_buff;
112     gss_name_t client_name;
113     OM_uint32   ret_flags;
114     int ret = 0;
115     gss_buffer_desc s_princ_buffer;
116
117     s_princ_buffer.value = service;
118     s_princ_buffer.length = strlen( service ) + 1;
119     
120     LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: importing name" );
121     major_status = gss_import_name( &minor_status, 
122                     &s_princ_buffer, 
123                     GSS_C_NT_HOSTBASED_SERVICE,
124                     &server_name );
125     if (major_status != GSS_S_COMPLETE) {
126         log_status( "import_name", major_status, minor_status );
127         ret = 1;
128         goto cleanup_vars;
129     }
130
131     LOG(log_debug, logtype_uams, 
132         "uams_gss.c :do_gss_auth: acquiring credentials (uid = %d, keytab = %s)",
133         (int)geteuid(), getenv( "KRB5_KTNAME") );
134     major_status = gss_acquire_cred( &minor_status, server_name, 
135                         GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
136                         &server_creds, NULL, NULL );    
137     if (major_status != GSS_S_COMPLETE) {
138         log_status( "acquire_cred", major_status, minor_status );
139         ret = 1;
140         goto cleanup_vars;
141     }
142     
143     /* The GSSAPI docs say that this should be done in a big "do" loop,
144      * but Apple's implementation doesn't seem to support this behavior.
145      */
146     ticket_buffer.length = ticket_len;
147     ticket_buffer.value = ibuf;
148     authenticator_buff.length = 0;
149     authenticator_buff.value = NULL;
150     LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: accepting context" );
151     major_status = gss_accept_sec_context( &minor_status, &context_handle,
152                         server_creds, &ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
153                         &client_name, NULL, &authenticator_buff,
154                         &ret_flags, NULL, NULL );
155
156     if (major_status == GSS_S_COMPLETE) {
157         gss_buffer_desc client_name_buffer;
158
159         log_ctx_flags( ret_flags );
160         /* use gss_display_name on client_name */
161         major_status = gss_display_name( &minor_status, client_name,
162                                 &client_name_buffer, (gss_OID *)NULL );
163         if (major_status == GSS_S_COMPLETE) {
164             u_int16_t auth_len = htons( authenticator_buff.length );
165             /* save the username... note that doing it this way is
166              * not the best idea: if a principal is truncated, a user could be
167              * impersonated
168              */
169             memcpy( username, client_name_buffer.value, 
170                 MIN(client_name_buffer.length, ulen - 1));
171             username[MIN(client_name_buffer.length, ulen - 1)] = 0;
172         
173             LOG(log_debug, logtype_uams, "uams_gss.c :do_gss_auth: user is %s!", username );
174             /* copy the authenticator length into the reply buffer */
175             memcpy( rbuf, &auth_len, sizeof(auth_len) );
176             *rbuflen += sizeof(auth_len), rbuf += sizeof(auth_len);
177
178             /* copy the authenticator value into the reply buffer */
179             memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
180             *rbuflen += authenticator_buff.length;
181
182             gss_release_buffer( &minor_status, &client_name_buffer );
183         } else {
184             log_status( "display_name", major_status, minor_status );
185             ret = 1;
186         }
187
188
189         /* Clean up after ourselves */
190         gss_release_name( &minor_status, &client_name );
191         gss_release_buffer( &minor_status, &authenticator_buff );
192         gss_delete_sec_context( &minor_status, 
193                         &context_handle, NULL );
194     } else {
195         log_status( "accept_sec_context", major_status, minor_status );
196         ret = 1;
197     }
198     gss_release_cred( &minor_status, &server_creds );
199
200 cleanup_vars:
201     gss_release_name( &minor_status, &server_name );
202     
203     return ret;
204 }
205
206 /* -------------------------- */
207 static int gss_login(void *obj, struct passwd **uam_pwd,
208                      char *ibuf, int ibuflen,
209                      char *rbuf, int *rbuflen)
210 {
211
212     u_int16_t  temp16;
213
214     *rbuflen = 0;
215
216     /* The reply contains a two-byte ID value - note 
217      * that Apple's implementation seems to always return 1 as well
218      */
219     temp16 = htons( 1 );
220     memcpy(rbuf, &temp16, sizeof(temp16));
221     *rbuflen += sizeof(temp16);
222     return AFPERR_AUTHCONT;
223 }
224
225 static int gss_logincont(void *obj, struct passwd **uam_pwd,
226                      char *ibuf, int ibuflen,
227                      char *rbuf, int *rbuflen)
228 {
229     struct passwd *pwd = NULL;
230     u_int16_t login_id;
231     char *username;
232     u_int16_t ticket_len;
233     char *p;
234     int rblen;
235     char *service;
236     int userlen, servicelen;
237
238     /* Apple's AFP 3.1 documentation specifies that this command
239      * takes the following format:
240      * pad (byte)
241      * id returned in LoginExt response (u_int16_t)
242      * username (format unspecified) padded, when necessary, to end on an even boundary
243      * ticket length (u_int16_t)
244      * ticket
245      */
246
247     /* Observation of AFP clients in the wild indicate that the actual
248      * format of this request is as follows:
249      * pad (byte) [consumed before login_ext is called]
250      * ?? (byte) - always observed to be 0
251      * id returned in LoginExt response (u_int16_t)
252      * username, encoding unspecified, null terminated C string, 
253      *   padded when the terminating null is an even numbered byte.
254      *   The packet is formated such that the username begins on an 
255      *   odd numbered byte. Eg if the username is 3 characters and the
256      *   terminating null makes 4, expect to pad the the result.
257      *   The encoding of this string is unknown.
258      * ticket length (u_int16_t)
259      * ticket
260      */
261
262     rblen = *rbuflen = 0;
263
264     ibuf++, ibuflen--; /* ?? */
265
266     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
267     memcpy( &login_id, ibuf, sizeof(login_id) );
268     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
269     login_id = ntohs( login_id );
270
271     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
272         return AFPERR_MISC;
273
274     if (uam_afpserver_option(obj, UAM_OPTION_KRB5SERVICE, (void *)&service, &servicelen) < 0)
275         return AFPERR_MISC;
276
277     if (service == NULL) 
278         return AFPERR_MISC;
279
280     /* We skip past the 'username' parameter because all that matters is the ticket */
281     p = ibuf;
282     while( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
283     if (ibuflen < 4) {
284         LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
285         return AFPERR_PARAM;
286     }
287
288     ibuf++, ibuflen--; /* null termination */
289
290     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
291
292     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
293
294     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
295     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
296     ticket_len = ntohs( ticket_len );
297
298     if (ticket_len > ibuflen) {
299         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: invalid ticket length");
300         return AFPERR_PARAM;
301     }
302
303     if (!do_gss_auth(service, ibuf, ticket_len, rbuf, &rblen, username, userlen)) {
304         char *at = strchr( username, '@' );
305
306         // Chop off the realm name
307         if (at)
308             *at = '\0';
309         if((pwd = uam_getname( username, userlen )) == NULL) {
310             LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
311             return AFPERR_PARAM;
312         }
313         if (uam_checkuser(pwd) < 0) {
314             LOG(log_info, logtype_uams, "%s not a valid user", username);
315             return AFPERR_NOTAUTH;
316         }
317         *rbuflen = rblen;
318         *uam_pwd = pwd;
319         return AFP_OK;
320     } else {
321         LOG(log_info, logtype_uams, "do_gss_auth failed" );
322         *rbuflen = 0;
323         return AFPERR_MISC;
324     }
325 }
326
327 /*
328  * For the krb5 uam, this function only needs to return a two-byte
329  * login-session id. None of the data provided by the client up to this
330  * point is trustworthy as we'll have a signed ticket to parse in logincont.
331  */
332 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
333                      char *ibuf, int ibuflen,
334                      char *rbuf, int *rbuflen)
335 {
336     u_int16_t  temp16;
337
338     *rbuflen = 0;
339
340     /* The reply contains a two-byte ID value - note 
341      * that Apple's implementation seems to always return 1 as well
342      */
343     temp16 = htons( 1 );
344     memcpy(rbuf, &temp16, sizeof(temp16));
345     *rbuflen += sizeof(temp16);
346     return AFPERR_AUTHCONT;
347 }
348
349 /* logout */
350 static void gss_logout() {
351 }
352
353 static int uam_setup(const char *path)
354 {
355     if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2", 
356                    gss_login, gss_logincont, gss_logout, gss_login_ext) < 0)
357         return -1;
358
359   return 0;
360 }
361
362 static void uam_cleanup(void)
363 {
364   uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
365 }
366
367 UAM_MODULE_EXPORT struct uam_export uams_gss = {
368   UAM_MODULE_SERVER,
369   UAM_MODULE_VERSION,
370   uam_setup, uam_cleanup
371 };
372 #endif