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