]> arthur.barton.de Git - netatalk.git/blob - etc/uams/uams_gss.c
Use stdint.h
[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 <stdint.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <arpa/inet.h>
17
18 #include <atalk/logger.h>
19 #include <atalk/afp.h>
20 #include <atalk/uam.h>
21 #include <atalk/util.h>
22 #include <atalk/compat.h>
23
24 /* Kerberos includes */
25 #ifdef HAVE_GSSAPI_GSSAPI_H
26 #include <gssapi/gssapi.h>
27 #else
28 #include <gssapi.h>
29 #endif // HAVE_GSSAPI_GSSAPI_H
30
31 static void log_status( char *s, OM_uint32 major_status,
32                         OM_uint32 minor_status )
33 {
34     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
35     OM_uint32 min_status, maj_status;
36     OM_uint32 maj_ctx = 0, min_ctx = 0;
37
38     while (1) {
39         maj_status = gss_display_status( &min_status, major_status,
40                                          GSS_C_GSS_CODE, GSS_C_NULL_OID,
41                                          &maj_ctx, &msg );
42         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
43             (int)msg.length, msg.value, strerror(errno));
44         gss_release_buffer(&min_status, &msg);
45
46         if (!maj_ctx)
47             break;
48     }
49     while (1) {
50         maj_status = gss_display_status( &min_status, minor_status,
51                                          GSS_C_MECH_CODE, GSS_C_NULL_OID, // gss_mech_krb5,
52                                          &min_ctx, &msg );
53         LOG(log_info, logtype_uams, "uams_gss.c :do_gss_auth: %s %.*s (error %s)", s,
54             (int)msg.length, msg.value, strerror(errno));
55         gss_release_buffer(&min_status, &msg);
56
57         if (!min_ctx)
58             break;
59     }
60 }
61
62
63 static void log_ctx_flags( OM_uint32 flags )
64 {
65 #ifdef DEBUG
66     if (flags & GSS_C_DELEG_FLAG)
67         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_DELEG_FLAG" );
68     if (flags & GSS_C_MUTUAL_FLAG)
69         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_MUTUAL_FLAG" );
70     if (flags & GSS_C_REPLAY_FLAG)
71         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_REPLAY_FLAG" );
72     if (flags & GSS_C_SEQUENCE_FLAG)
73         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_SEQUENCE_FLAG" );
74     if (flags & GSS_C_CONF_FLAG)
75         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_CONF_FLAG" );
76     if (flags & GSS_C_INTEG_FLAG)
77         LOG(log_debug, logtype_uams, "uams_gss.c :context flag: GSS_C_INTEG_FLAG" );
78 #endif
79 }
80
81
82 /* get the username */
83 static int get_client_username(char *username, int ulen, gss_name_t *client_name)
84 {
85     OM_uint32 major_status = 0, minor_status = 0;
86     gss_buffer_desc client_name_buffer;
87     char *p;
88     int namelen, ret=0;
89
90     /*
91      * To extract the unix username, use gss_display_name on client_name.
92      * We do rely on gss_display_name returning a zero terminated string.
93      * The username returned contains the realm and possibly an instance.
94      * We only want the username for afpd, so we have to strip those from
95      * the username before copying it to afpd's buffer.
96      */
97
98     major_status = gss_display_name( &minor_status, *client_name,
99                                      &client_name_buffer, (gss_OID *)NULL );
100     if (major_status != GSS_S_COMPLETE) {
101         log_status( "display_name", major_status, minor_status );
102         return 1;
103     }
104
105     LOG(log_debug, logtype_uams, "get_client_username: user is `%s'", client_name_buffer.value);
106
107     /* chop off realm */
108     p = strchr( client_name_buffer.value, '@' );
109     if (p)
110         *p = 0;
111     /* FIXME: chop off instance? */
112     p = strchr( client_name_buffer.value, '/' );
113     if (p)
114         *p = 0;
115
116     /* check if this username fits into afpd's username buffer */
117     namelen = strlen(client_name_buffer.value);
118     if ( namelen >= ulen ) {
119         /* The username is too long for afpd's buffer, bail out */
120         LOG(log_error, logtype_uams,
121             "get_client_username: username `%s' too long", client_name_buffer.value);
122         ret = 1;
123     }
124     else {
125         /* copy stripped username to afpd's buffer */
126         strlcpy(username, client_name_buffer.value, ulen);
127     }
128
129     /* we're done with client_name_buffer, release it */
130     gss_release_buffer(&minor_status, &client_name_buffer );
131
132     return ret;
133 }
134
135 /* wrap afpd's sessionkey */
136 static int wrap_sessionkey(gss_ctx_id_t context, struct session_info *sinfo)
137 {
138     OM_uint32 status = 0;
139     int ret=0;
140     gss_buffer_desc sesskey_buff, wrap_buff;
141
142     /*
143      * gss_wrap afpd's session_key.
144      * This is needed fo OS X 10.3 clients. They request this information
145      * with type 8 (kGetKerberosSessionKey) on FPGetSession.
146      * See AFP 3.1 specs, page 77.
147      */
148
149     sesskey_buff.value = sinfo->sessionkey;
150     sesskey_buff.length = sinfo->sessionkey_len;
151
152     /* gss_wrap the session key with the default mechanism.
153        Require both confidentiality and integrity services */
154     gss_wrap (&status, context, 1, GSS_C_QOP_DEFAULT, &sesskey_buff, NULL, &wrap_buff);
155
156     if ( status != GSS_S_COMPLETE) {
157         LOG(log_error, logtype_uams, "wrap_sessionkey: failed to gss_wrap sessionkey");
158         log_status( "GSS wrap", 0, status );
159         return 1;
160     }
161
162     /* store the wrapped session key in afpd's session_info struct */
163     if ( NULL == (sinfo->cryptedkey = malloc ( wrap_buff.length )) ) {
164         LOG(log_error, logtype_uams,
165             "wrap_sessionkey: out of memory tyring to allocate %u bytes",
166             wrap_buff.length);
167         ret = 1;
168     } else {
169         /* cryptedkey is binary data */
170         memcpy (sinfo->cryptedkey, wrap_buff.value, wrap_buff.length);
171         sinfo->cryptedkey_len = wrap_buff.length;
172     }
173
174     /* we're done with buffer, release */
175     gss_release_buffer( &status, &wrap_buff );
176
177     return ret;
178 }
179
180 /*-------------*/
181 static int accept_sec_context (gss_ctx_id_t *context,
182                                gss_buffer_desc *ticket_buffer, gss_name_t *client_name,
183                                gss_buffer_desc *authenticator_buff)
184 {
185     OM_uint32 major_status = 0, minor_status = 0, ret_flags;
186
187     /* Initialize autheticator buffer. */
188     authenticator_buff->length = 0;
189     authenticator_buff->value = NULL;
190
191     LOG(log_debug, logtype_uams, "accept_context: accepting context (ticketlen: %u)",
192         ticket_buffer->length);
193
194     /*
195      * Try to accept the secondary context using the token in ticket_buffer.
196      * We don't care about the principals or mechanisms used, nor for the time.
197      * We don't act as a proxy either.
198      */
199     major_status = gss_accept_sec_context( &minor_status, context,
200                                            GSS_C_NO_CREDENTIAL, ticket_buffer, GSS_C_NO_CHANNEL_BINDINGS,
201                                            client_name, NULL, authenticator_buff,
202                                            &ret_flags, NULL, NULL );
203
204     if (major_status != GSS_S_COMPLETE) {
205         log_status( "accept_sec_context", major_status, minor_status );
206         return 1;
207     }
208     log_ctx_flags( ret_flags );
209     return 0;
210 }
211
212
213 /* return 0 on success */
214 static int do_gss_auth(void *obj, char *ibuf, int ticket_len,
215                        char *rbuf, int *rbuflen, char *username, int ulen,
216                        struct session_info *sinfo )
217 {
218     OM_uint32 status = 0;
219     gss_name_t client_name;
220     gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
221     gss_buffer_desc ticket_buffer, authenticator_buff;
222     int ret = 0;
223
224     /*
225      * Try to accept the secondary context, using the ticket/token the
226      * client sent us. Ticket is stored at current ibuf position.
227      * Don't try to release ticket_buffer later, it points into ibuf!
228      */
229     ticket_buffer.length = ticket_len;
230     ticket_buffer.value = ibuf;
231
232     if ((ret = accept_sec_context(&context_handle, &ticket_buffer, 
233                                   &client_name, &authenticator_buff)))
234         return ret;
235
236     /* We succesfully acquired the secondary context, now get the
237        username for afpd and gss_wrap the sessionkey */
238     if ((ret = get_client_username(username, ulen, &client_name)))
239         goto cleanup_client_name;
240
241     if ((ret = wrap_sessionkey(context_handle, sinfo)))
242         goto cleanup_client_name;
243
244     /* Authenticated, construct the reply using:
245      * authenticator length (uint16_t)
246      * authenticator
247      */
248     /* copy the authenticator length into the reply buffer */
249     uint16_t auth_len = htons( authenticator_buff.length );
250     memcpy( rbuf, &auth_len, sizeof(auth_len) );
251     *rbuflen += sizeof(auth_len);
252     rbuf += sizeof(auth_len);
253
254     /* copy the authenticator value into the reply buffer */
255     memcpy( rbuf, authenticator_buff.value, authenticator_buff.length );
256     *rbuflen += authenticator_buff.length;
257
258 cleanup_client_name:
259     gss_release_name( &status, &client_name );
260
261 cleanup_context:
262     gss_release_buffer( &status, &authenticator_buff );
263     gss_delete_sec_context( &status, &context_handle, NULL );
264
265     return ret;
266 }
267
268 /* -------------------------- */
269
270 /*
271  * For the gss uam, this function only needs to return a two-byte
272  * login-session id. None of the data provided by the client up to this
273  * point is trustworthy as we'll have a signed ticket to parse in logincont.
274  */
275 static int gss_login(void *obj, struct passwd **uam_pwd,
276                      char *ibuf, size_t ibuflen,
277                      char *rbuf, size_t *rbuflen)
278 {
279
280     uint16_t  temp16;
281
282     *rbuflen = 0;
283
284     /* The reply contains a two-byte ID value - note
285      * that Apple's implementation seems to always return 1 as well
286      */
287     temp16 = htons( 1 );
288     memcpy(rbuf, &temp16, sizeof(temp16));
289     *rbuflen += sizeof(temp16);
290     return AFPERR_AUTHCONT;
291 }
292
293 static int gss_logincont(void *obj, struct passwd **uam_pwd,
294                          char *ibuf, size_t ibuflen,
295                          char *rbuf, size_t *rbuflen)
296 {
297     struct passwd *pwd = NULL;
298     uint16_t login_id;
299     char *username;
300     uint16_t ticket_len;
301     char *p;
302     int rblen;
303     size_t userlen;
304     struct session_info *sinfo;
305
306     /* Apple's AFP 3.1 documentation specifies that this command
307      * takes the following format:
308      * pad (byte)
309      * id returned in LoginExt response (uint16_t)
310      * username (format unspecified) padded, when necessary, to end on an even boundary
311      * ticket length (uint16_t)
312      * ticket
313      */
314
315     /* Observation of AFP clients in the wild indicate that the actual
316      * format of this request is as follows:
317      * pad (byte) [consumed before login_ext is called]
318      * ?? (byte) - always observed to be 0
319      * id returned in LoginExt response (uint16_t)
320      * username, encoding unspecified, null terminated C string,
321      *   padded when the terminating null is an even numbered byte.
322      *   The packet is formated such that the username begins on an
323      *   odd numbered byte. Eg if the username is 3 characters and the
324      *   terminating null makes 4, expect to pad the the result.
325      *   The encoding of this string is unknown.
326      * ticket length (uint16_t)
327      * ticket
328      */
329
330     rblen = *rbuflen = 0;
331
332     if (ibuflen < 1 +sizeof(login_id)) {
333         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: received incomplete packet");
334         return AFPERR_PARAM;
335     }
336     ibuf++, ibuflen--; /* ?? */
337
338     /* 2 byte ID from LoginExt -- always '00 01' in this implementation */
339     memcpy( &login_id, ibuf, sizeof(login_id) );
340     ibuf += sizeof(login_id), ibuflen -= sizeof(login_id);
341     login_id = ntohs( login_id );
342
343     /* get the username buffer from apfd */
344     if (uam_afpserver_option(obj, UAM_OPTION_USERNAME, (void *) &username, &userlen) < 0)
345         return AFPERR_MISC;
346
347     /* get the session_info structure from afpd. We need the session key */
348     if (uam_afpserver_option(obj, UAM_OPTION_SESSIONINFO, (void *)&sinfo, NULL) < 0)
349         return AFPERR_MISC;
350
351     if (sinfo->sessionkey == NULL || sinfo->sessionkey_len == 0) {
352         /* Should never happen. Most likely way too old afpd version */
353         LOG(log_info, logtype_uams, "internal error: afpd's sessionkey not set");
354         return AFPERR_MISC;
355     }
356
357     /* We skip past the 'username' parameter because all that matters is the ticket */
358     p = ibuf;
359     while ( *ibuf && ibuflen ) { ibuf++, ibuflen--; }
360     if (ibuflen < 4) {
361         LOG(log_info, logtype_uams, "uams_gss.c :LoginCont: user is %s, no ticket", p);
362         return AFPERR_PARAM;
363     }
364
365     ibuf++, ibuflen--; /* null termination */
366
367     if ((ibuf - p + 1) % 2) ibuf++, ibuflen--; /* deal with potential padding */
368
369     LOG(log_debug, logtype_uams, "uams_gss.c :LoginCont: client thinks user is %s", p);
370
371     /* get the length of the ticket the client sends us */
372     memcpy(&ticket_len, ibuf, sizeof(ticket_len));
373     ibuf += sizeof(ticket_len); ibuflen -= sizeof(ticket_len);
374     ticket_len = ntohs( ticket_len );
375
376     /* a little bounds checking */
377     if (ticket_len > ibuflen) {
378         LOG(log_info, logtype_uams,
379             "uams_gss.c :LoginCont: invalid ticket length (%u > %u)", ticket_len, ibuflen);
380         return AFPERR_PARAM;
381     }
382
383     /* now try to authenticate */
384     if (do_gss_auth(obj, ibuf, ticket_len, rbuf, &rblen, username, userlen, sinfo)) {
385         LOG(log_info, logtype_uams, "do_gss_auth failed" );
386         *rbuflen = 0;
387         return AFPERR_MISC;
388     }
389
390     /* We use the username we got back from the gssapi client_name.
391        Should we compare this to the username the client sent in the clear?
392        We know the character encoding of the cleartext username (UTF8), what
393        encoding is the gssapi name in? */
394     if ((pwd = uam_getname( obj, username, userlen )) == NULL) {
395         LOG(log_info, logtype_uams, "uam_getname() failed for %s", username);
396         return AFPERR_NOTAUTH;
397     }
398     if (uam_checkuser(pwd) < 0) {
399         LOG(log_info, logtype_uams, "%s not a valid user", username);
400         return AFPERR_NOTAUTH;
401     }
402     *rbuflen = rblen;
403     *uam_pwd = pwd;
404     return AFP_OK;
405 }
406
407 /* logout */
408 static void gss_logout() {
409 }
410
411 static int gss_login_ext(void *obj, char *uname, struct passwd **uam_pwd,
412                          char *ibuf, size_t ibuflen,
413                          char *rbuf, size_t *rbuflen)
414 {
415     return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
416 }
417
418 static int uam_setup(const char *path)
419 {
420     return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
421                         gss_login, gss_logincont, gss_logout, gss_login_ext);
422 }
423
424 static void uam_cleanup(void)
425 {
426     uam_unregister(UAM_SERVER_LOGIN_EXT, "Client Krb v2");
427 }
428
429 UAM_MODULE_EXPORT struct uam_export uams_gss = {
430     UAM_MODULE_SERVER,
431     UAM_MODULE_VERSION,
432     uam_setup, uam_cleanup
433 };