Commit 2d1b5a5e 2d1b5a5e83edea9395e43cabc6455b6a57d4188b by Sergey Poznyakoff

Radius authentication/authorization.

1 parent b5423ab5
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