Radius authentication/authorization.
Showing
1 changed file
with
571 additions
and
0 deletions
auth/radius.c
0 → 100644
1 | /* GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | Copyright (C) 2005 Free Software Foundation, Inc. | ||
3 | |||
4 | This library is free software; you can redistribute it and/or | ||
5 | modify it under the terms of the GNU Lesser General Public | ||
6 | License as published by the Free Software Foundation; either | ||
7 | version 2 of the License, or (at your option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | Lesser General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Lesser General | ||
15 | Public License along with this library; if not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
17 | Boston, MA 02110-1301 USA */ | ||
18 | |||
19 | #ifdef HAVE_CONFIG_H | ||
20 | # include <config.h> | ||
21 | #endif | ||
22 | |||
23 | #include <unistd.h> | ||
24 | #include <sys/types.h> | ||
25 | #include <pwd.h> | ||
26 | #include <errno.h> | ||
27 | #include <stdio.h> | ||
28 | #include <stdlib.h> | ||
29 | #include <string.h> | ||
30 | #ifdef HAVE_STRINGS_H | ||
31 | # include <strings.h> | ||
32 | #endif | ||
33 | |||
34 | #include <mailutils/list.h> | ||
35 | #include <mailutils/iterator.h> | ||
36 | #include <mailutils/mailbox.h> | ||
37 | #include <mailutils/argp.h> | ||
38 | #include <mailutils/argcv.h> | ||
39 | #include <mailutils/mu_auth.h> | ||
40 | #include <mailutils/error.h> | ||
41 | #include <mailutils/errno.h> | ||
42 | #include <mailutils/nls.h> | ||
43 | |||
44 | #ifdef ENABLE_RADIUS | ||
45 | |||
46 | #include <radius/radius.h> | ||
47 | |||
48 | #define ARG_AUTH_REQUEST 256 | ||
49 | #define ARG_GETPWNAM_REQUEST 257 | ||
50 | #define ARG_GETPWUID_REQUEST 258 | ||
51 | #define ARG_RADIUS_DIR 259 | ||
52 | |||
53 | static struct argp_option mu_radius_argp_option[] = { | ||
54 | { "radius-auth-request", ARG_AUTH_REQUEST, N_("REQUEST"), 0, | ||
55 | N_("Radius request to authenitcate the user"), 0 }, | ||
56 | { "radius-getpwnam-request", ARG_GETPWNAM_REQUEST, N_("REQUEST"), 0, | ||
57 | N_("Radius request to retrieve a passwd entry based on username"), 0 }, | ||
58 | { "radius-getpwuid-request", ARG_GETPWUID_REQUEST, N_("REQUEST"), 0, | ||
59 | N_("Radius request to retrieve a passwd entry based on UID"), 0 }, | ||
60 | { "radius-directory", ARG_RADIUS_DIR, N_("DIR"), 0, | ||
61 | N_("Set path to the radius configuration directory"), 0 }, | ||
62 | { NULL } | ||
63 | }; | ||
64 | |||
65 | static char *auth_request_str; | ||
66 | static grad_avp_t *auth_request; | ||
67 | |||
68 | static char *getpwnam_request_str; | ||
69 | static grad_avp_t *getpwnam_request; | ||
70 | |||
71 | static char *getpwuid_request_str; | ||
72 | static grad_avp_t *getpwuid_request; | ||
73 | |||
74 | static int MU_User_Name; | ||
75 | static int MU_UID; | ||
76 | static int MU_GID; | ||
77 | static int MU_GECOS; | ||
78 | static int MU_Dir; | ||
79 | static int MU_Shell; | ||
80 | static int MU_Mailbox; | ||
81 | |||
82 | void | ||
83 | get_attribute (int *pattr, char *name, struct argp_state *state) | ||
84 | { | ||
85 | grad_dict_attr_t *attr = grad_attr_name_to_dict (name); | ||
86 | if (!attr) | ||
87 | argp_error (state, _("Radius attribute %s not defined"), name); | ||
88 | *pattr = attr->value; | ||
89 | } | ||
90 | |||
91 | enum parse_state | ||
92 | { | ||
93 | state_lhs, | ||
94 | state_op, | ||
95 | state_rhs, | ||
96 | state_delim | ||
97 | }; | ||
98 | |||
99 | int | ||
100 | parse_pairlist (grad_avp_t **plist, const char *input, | ||
101 | struct argp_state *argp_state) | ||
102 | { | ||
103 | int rc; | ||
104 | int i, argc; | ||
105 | char **argv; | ||
106 | enum parse_state state; | ||
107 | grad_locus_t loc; | ||
108 | char *name; | ||
109 | char *op; /* FIXME: It is actually ignored. Should it be? */ | ||
110 | |||
111 | if (!input) | ||
112 | return 1; | ||
113 | |||
114 | if ((rc = argcv_get (input, ",", NULL, &argc, &argv))) | ||
115 | argp_error (argp_state, _("Cannot parse input `%s': %s"), | ||
116 | input, mu_strerror (rc)); | ||
117 | |||
118 | loc.file = "<command line>"; /* FIXME */ | ||
119 | loc.line = 0; | ||
120 | |||
121 | for (i = 0, state = state_lhs; i < argc; i++) | ||
122 | { | ||
123 | grad_avp_t *pair; | ||
124 | |||
125 | switch (state) | ||
126 | { | ||
127 | case state_lhs: | ||
128 | name = argv[i]; | ||
129 | state = state_op; | ||
130 | break; | ||
131 | |||
132 | case state_op: | ||
133 | op = argv[i]; | ||
134 | state = state_rhs; | ||
135 | break; | ||
136 | |||
137 | case state_rhs: | ||
138 | loc.line = i; /* Just to keep track of error location */ | ||
139 | pair = grad_create_pair (&loc, name, grad_operator_equal, argv[i]); | ||
140 | if (!pair) | ||
141 | argp_error (argp_state, _("cannot create radius A/V pair `%s'"), | ||
142 | name); | ||
143 | grad_avl_merge (plist, &pair); | ||
144 | state = state_delim; | ||
145 | break; | ||
146 | |||
147 | case state_delim: | ||
148 | if (strcmp (argv[i], ",")) | ||
149 | argp_error (argp_state, _("expected `,' but found `%s'"), argv[i]); | ||
150 | state = state_lhs; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | if (state != state_delim && state != state_delim) | ||
155 | argp_error (argp_state, _("malformed radius A/V list")); | ||
156 | |||
157 | argcv_free (argc, argv); | ||
158 | return 0; | ||
159 | } | ||
160 | |||
161 | static void | ||
162 | init (struct argp_state *state) | ||
163 | { | ||
164 | grad_path_init (); | ||
165 | srand (time (NULL) + getpid ()); | ||
166 | |||
167 | if (grad_dict_init ()) | ||
168 | argp_error (state, _("Cannot read radius dictionaries")); | ||
169 | |||
170 | /* Check whether mailutils attributes are defined */ | ||
171 | get_attribute (&MU_User_Name, "MU-User-Name", state); | ||
172 | get_attribute (&MU_UID, "MU-UID", state); | ||
173 | get_attribute (&MU_GID, "MU-GID", state); | ||
174 | get_attribute (&MU_GECOS, "MU-GECOS", state); | ||
175 | get_attribute (&MU_Dir, "MU-Dir", state); | ||
176 | get_attribute (&MU_Shell, "MU-Shell", state); | ||
177 | get_attribute (&MU_Mailbox, "MU-Mailbox", state); | ||
178 | |||
179 | /* Parse saved requests */ | ||
180 | parse_pairlist (&auth_request, auth_request_str, state); | ||
181 | parse_pairlist (&getpwnam_request, getpwnam_request_str, state); | ||
182 | parse_pairlist (&getpwuid_request, getpwuid_request_str, state); | ||
183 | } | ||
184 | |||
185 | static error_t | ||
186 | mu_radius_argp_parser (int key, char *arg, struct argp_state *state) | ||
187 | { | ||
188 | switch (key) | ||
189 | { | ||
190 | case ARG_AUTH_REQUEST: | ||
191 | auth_request_str = arg; | ||
192 | break; | ||
193 | |||
194 | case ARG_GETPWNAM_REQUEST: | ||
195 | getpwnam_request_str = arg; | ||
196 | break; | ||
197 | |||
198 | case ARG_GETPWUID_REQUEST: | ||
199 | getpwuid_request_str = arg; | ||
200 | break; | ||
201 | |||
202 | case ARG_RADIUS_DIR: | ||
203 | radius_dir = grad_estrdup(arg); | ||
204 | break; | ||
205 | |||
206 | case ARGP_KEY_FINI: | ||
207 | init (state); | ||
208 | break; | ||
209 | |||
210 | default: | ||
211 | return ARGP_ERR_UNKNOWN; | ||
212 | } | ||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | char * | ||
217 | _expand_query (const char *query, const char *ustr, const char *passwd) | ||
218 | { | ||
219 | char *p, *q, *res; | ||
220 | int len; | ||
221 | |||
222 | if (!query) | ||
223 | return NULL; | ||
224 | |||
225 | /* Compute resulting query length */ | ||
226 | for (len = 0, p = (char *) query; *p; ) | ||
227 | { | ||
228 | if (*p == '%') | ||
229 | { | ||
230 | if (p[1] == 'u') | ||
231 | { | ||
232 | len += ustr ? strlen (ustr) : 2; | ||
233 | p += 2; | ||
234 | } | ||
235 | else if (p[1] == 'p') | ||
236 | { | ||
237 | len += passwd ? strlen (passwd) : 2; | ||
238 | p += 2; | ||
239 | } | ||
240 | else if (p[1] == '%') | ||
241 | { | ||
242 | len++; | ||
243 | p += 2; | ||
244 | } | ||
245 | else | ||
246 | { | ||
247 | len++; | ||
248 | p++; | ||
249 | } | ||
250 | } | ||
251 | else | ||
252 | { | ||
253 | len++; | ||
254 | p++; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | res = grad_emalloc (len + 1); | ||
259 | if (!res) | ||
260 | return res; | ||
261 | |||
262 | for (p = (char *) query, q = res; *p; ) | ||
263 | { | ||
264 | if (*p == '%') | ||
265 | { | ||
266 | switch (*++p) | ||
267 | { | ||
268 | case 'u': | ||
269 | if (ustr) | ||
270 | { | ||
271 | strcpy (q, ustr); | ||
272 | q += strlen (q); | ||
273 | } | ||
274 | else | ||
275 | { | ||
276 | *q++ = '%'; | ||
277 | *q++ = 'u'; | ||
278 | } | ||
279 | p++; | ||
280 | break; | ||
281 | |||
282 | case 'p': | ||
283 | if (passwd) | ||
284 | { | ||
285 | strcpy (q, passwd); | ||
286 | q += strlen (q); | ||
287 | } | ||
288 | else | ||
289 | { | ||
290 | *q++ = '%'; | ||
291 | *q++ = 'u'; | ||
292 | } | ||
293 | p++; | ||
294 | break; | ||
295 | |||
296 | case '%': | ||
297 | *q++ = *p++; | ||
298 | break; | ||
299 | |||
300 | default: | ||
301 | *q++ = *p++; | ||
302 | } | ||
303 | } | ||
304 | else | ||
305 | *q++ = *p++; | ||
306 | } | ||
307 | *q = 0; | ||
308 | return res; | ||
309 | } | ||
310 | |||
311 | |||
312 | static grad_avp_t * | ||
313 | create_request (grad_avp_t *template, const char *ustr, const char *passwd) | ||
314 | { | ||
315 | grad_avp_t *newp, *p; | ||
316 | |||
317 | newp = grad_avl_dup (template); | ||
318 | for (p = newp; p; p = p->next) | ||
319 | { | ||
320 | if (p->type == GRAD_TYPE_STRING) | ||
321 | { | ||
322 | char *value = _expand_query (p->avp_strvalue, ustr, passwd); | ||
323 | grad_free (p->avp_strvalue); | ||
324 | p->avp_strvalue = value; | ||
325 | p->avp_strlength = strlen (value); | ||
326 | } | ||
327 | } | ||
328 | return newp; | ||
329 | } | ||
330 | |||
331 | |||
332 | |||
333 | grad_request_t * | ||
334 | send_request (grad_avp_t *pairs, int code, | ||
335 | const char *user, const char *passwd) | ||
336 | { | ||
337 | grad_avp_t *plist = create_request (pairs, user, passwd); | ||
338 | if (plist) | ||
339 | { | ||
340 | grad_server_queue_t *queue = grad_client_create_queue (1, 0, 0); | ||
341 | grad_request_t *reply = grad_client_send (queue, | ||
342 | GRAD_PORT_AUTH, code, | ||
343 | plist); | ||
344 | grad_client_destroy_queue (queue); | ||
345 | grad_avl_free (plist); | ||
346 | return reply; | ||
347 | } | ||
348 | return NULL; | ||
349 | } | ||
350 | |||
351 | #define DEFAULT_HOME_PREFIX "/home/" | ||
352 | #define DEFAULT_SHELL "/dev/null" | ||
353 | |||
354 | int | ||
355 | decode_reply (grad_request_t *reply, const char *user_name, char *password, | ||
356 | struct mu_auth_data **return_data) | ||
357 | { | ||
358 | grad_avp_t *p; | ||
359 | int rc; | ||
360 | |||
361 | uid_t uid = -1; | ||
362 | gid_t gid = -1; | ||
363 | char *gecos = "RADIUS User"; | ||
364 | char *dir = NULL; | ||
365 | char *shell = NULL; | ||
366 | char *mailbox = NULL; | ||
367 | |||
368 | p = grad_avl_find (reply->avlist, MU_User_Name); | ||
369 | if (p) | ||
370 | user_name = p->avp_strvalue; | ||
371 | |||
372 | p = grad_avl_find (reply->avlist, MU_UID); | ||
373 | if (p) | ||
374 | uid = p->avp_lvalue; | ||
375 | else | ||
376 | { | ||
377 | mu_error (_("Radius did not return UID for `%s'"), user_name); | ||
378 | return -1; | ||
379 | } | ||
380 | |||
381 | p = grad_avl_find (reply->avlist, MU_GID); | ||
382 | if (p) | ||
383 | gid = p->avp_lvalue; | ||
384 | else | ||
385 | { | ||
386 | mu_error (_("Radius did not return GID for `%s'"), user_name); | ||
387 | return -1; | ||
388 | } | ||
389 | |||
390 | p = grad_avl_find (reply->avlist, MU_GECOS); | ||
391 | if (p) | ||
392 | gecos = p->avp_strvalue; | ||
393 | |||
394 | p = grad_avl_find (reply->avlist, MU_Dir); | ||
395 | if (p) | ||
396 | dir = strdup (p->avp_strvalue); | ||
397 | else /* Try to provide a reasonable default */ | ||
398 | { | ||
399 | dir = malloc (sizeof DEFAULT_HOME_PREFIX + strlen (user_name)); | ||
400 | if (!dir) /* FIXME: Error code */ | ||
401 | return 1; | ||
402 | strcat (strcpy (dir, DEFAULT_HOME_PREFIX), user_name); | ||
403 | } | ||
404 | |||
405 | p = grad_avl_find (reply->avlist, MU_Shell); | ||
406 | if (p) | ||
407 | shell = p->avp_strvalue; | ||
408 | else | ||
409 | shell = DEFAULT_SHELL; | ||
410 | |||
411 | p = grad_avl_find (reply->avlist, MU_Mailbox); | ||
412 | if (p) | ||
413 | mailbox = strdup (p->avp_strvalue); | ||
414 | else | ||
415 | { | ||
416 | rc = mu_construct_user_mailbox_url (&mailbox, user_name); | ||
417 | if (rc) | ||
418 | return rc; | ||
419 | } | ||
420 | |||
421 | rc = mu_auth_data_alloc (return_data, | ||
422 | user_name, | ||
423 | password, | ||
424 | uid, | ||
425 | gid, | ||
426 | gecos, | ||
427 | dir, | ||
428 | shell, | ||
429 | mailbox, | ||
430 | 1); | ||
431 | |||
432 | free (dir); | ||
433 | free (mailbox); | ||
434 | return rc; | ||
435 | } | ||
436 | |||
437 | int | ||
438 | mu_radius_authenticate (struct mu_auth_data **return_data ARG_UNUSED, | ||
439 | const void *key, | ||
440 | void *func_data ARG_UNUSED, void *call_data) | ||
441 | { | ||
442 | int rc; | ||
443 | grad_request_t *reply; | ||
444 | const struct mu_auth_data *auth_data = key; | ||
445 | |||
446 | if (!auth_request) | ||
447 | { | ||
448 | mu_error (_("--radius-auth-request is not specified")); | ||
449 | return 1; | ||
450 | } | ||
451 | |||
452 | reply = send_request (auth_request, RT_ACCESS_REQUEST, | ||
453 | auth_data->name, (char*) call_data); | ||
454 | rc = !reply || reply->code != RT_ACCESS_ACCEPT; | ||
455 | grad_request_free (reply); | ||
456 | return rc; | ||
457 | } | ||
458 | |||
459 | static int | ||
460 | mu_auth_radius_user_by_name (struct mu_auth_data **return_data, | ||
461 | const void *key, | ||
462 | void *unused_func_data, void *unused_call_data) | ||
463 | { | ||
464 | int rc = 1; | ||
465 | grad_request_t *reply; | ||
466 | |||
467 | if (!getpwnam_request) | ||
468 | { | ||
469 | mu_error (_("--radius-getpwnam-request is not specified")); | ||
470 | return 1; | ||
471 | } | ||
472 | |||
473 | reply = send_request (getpwnam_request, RT_ACCESS_REQUEST, key, NULL); | ||
474 | if (!reply) | ||
475 | mu_error (_("radius server did not respond")); | ||
476 | else if (reply->code != RT_ACCESS_ACCEPT) | ||
477 | mu_error (_("%s: server returned %s"), | ||
478 | (char*) key, | ||
479 | grad_request_code_to_name (reply->code)); | ||
480 | else | ||
481 | rc = decode_reply (reply, key, "x", return_data); | ||
482 | grad_request_free (reply); | ||
483 | return rc; | ||
484 | } | ||
485 | |||
486 | static int | ||
487 | mu_auth_radius_user_by_uid (struct mu_auth_data **return_data, | ||
488 | const void *key, | ||
489 | void *func_data, void *call_data) | ||
490 | { | ||
491 | int rc = 1; | ||
492 | grad_request_t *reply; | ||
493 | char uidstr[64]; | ||
494 | |||
495 | if (!key) | ||
496 | { | ||
497 | errno = EINVAL; | ||
498 | return 1; | ||
499 | } | ||
500 | |||
501 | if (!getpwuid_request) | ||
502 | { | ||
503 | mu_error (_("--radius-getpwuid-request is not specified")); | ||
504 | return 1; | ||
505 | } | ||
506 | |||
507 | snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key); | ||
508 | reply = send_request (getpwuid_request, RT_ACCESS_REQUEST, uidstr, NULL); | ||
509 | if (reply->code != RT_ACCESS_ACCEPT) | ||
510 | { | ||
511 | mu_error (_("uid %s: server returned %s"), uidstr, | ||
512 | grad_request_code_to_name (reply->code)); | ||
513 | } | ||
514 | else | ||
515 | { | ||
516 | rc = decode_reply (reply, uidstr, "x", return_data); | ||
517 | } | ||
518 | grad_request_free (reply); | ||
519 | return rc; | ||
520 | } | ||
521 | |||
522 | struct argp mu_radius_argp = { | ||
523 | mu_radius_argp_option, | ||
524 | mu_radius_argp_parser, | ||
525 | }; | ||
526 | |||
527 | #else | ||
528 | static int | ||
529 | mu_radius_authenticate (struct mu_auth_data **return_data ARG_UNUSED, | ||
530 | const void *key, | ||
531 | void *func_data ARG_UNUSED, void *call_data) | ||
532 | { | ||
533 | errno = ENOSYS; | ||
534 | return 1; | ||
535 | } | ||
536 | |||
537 | static int | ||
538 | mu_auth_radius_user_by_name (struct mu_auth_data **return_data ARG_UNUSED, | ||
539 | const void *key ARG_UNUSED, | ||
540 | void *func_data ARG_UNUSED, | ||
541 | void *call_data ARG_UNUSED) | ||
542 | { | ||
543 | errno = ENOSYS; | ||
544 | return 1; | ||
545 | } | ||
546 | |||
547 | static int | ||
548 | mu_auth_radius_user_by_uid (struct mu_auth_data **return_data, | ||
549 | const void *key, | ||
550 | void *func_data, void *call_data) | ||
551 | { | ||
552 | errno = ENOSYS; | ||
553 | return 1; | ||
554 | } | ||
555 | #endif | ||
556 | |||
557 | struct mu_auth_module mu_auth_radius_module = { | ||
558 | "radius", | ||
559 | #ifdef ENABLE_RADIUS | ||
560 | &mu_radius_argp, | ||
561 | #else | ||
562 | NULL, | ||
563 | #endif | ||
564 | mu_radius_authenticate, | ||
565 | NULL, | ||
566 | mu_auth_radius_user_by_name, | ||
567 | NULL, | ||
568 | mu_auth_radius_user_by_uid, | ||
569 | NULL | ||
570 | }; | ||
571 |
-
Please register or sign in to post a comment