1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2005-2021 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 3 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, see
16    <http://www.gnu.org/licenses/>. */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <pwd.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef HAVE_STRINGS_H
30 # include <strings.h>
31 #endif
32 
33 #include <mailutils/list.h>
34 #include <mailutils/iterator.h>
35 #include <mailutils/mailbox.h>
36 #include <mailutils/radius.h>
37 #include <mailutils/cstr.h>
38 #include <mailutils/wordsplit.h>
39 #include <mailutils/mu_auth.h>
40 #include <mailutils/error.h>
41 #include <mailutils/errno.h>
42 #include <mailutils/nls.h>
43 #include <mailutils/io.h>
44 #include <mailutils/cctype.h>
45 #include <mailutils/cli.h>
46 #include <mailutils/stream.h>
47 #include <mailutils/stdstream.h>
48 
49 #ifdef ENABLE_RADIUS
50 
51 #include <radius/radius.h>
52 #include <radius/debug.h>
53 
54 static int radius_auth_enabled;
55 
56 static int MU_User_Name;
57 static int MU_UID;
58 static int MU_GID;
59 static int MU_GECOS;
60 static int MU_Dir;
61 static int MU_Shell;
62 static int MU_Mailbox;
63 
64 static grad_avp_t *auth_request;
65 static grad_avp_t *getpwnam_request;
66 static grad_avp_t *getpwuid_request;
67 
68 int
get_attribute(int * pattr,char * name)69 get_attribute (int *pattr, char *name)
70 {
71   grad_dict_attr_t *attr = grad_attr_name_to_dict (name);
72   if (!attr)
73     {
74       mu_error (_("RADIUS attribute %s not defined"), name);
75       return 1;
76     }
77   *pattr = attr->value;
78   return 0;
79 }
80 
81 enum parse_state
82   {
83     state_lhs,
84     state_op,
85     state_rhs,
86     state_delim,
87     state_err
88   };
89 
90 static int
cb_request(void * data,mu_config_value_t * val)91 cb_request (void *data, mu_config_value_t *val)
92 {
93   grad_avp_t **plist = data;
94   size_t i;
95   struct mu_wordsplit ws;
96   enum parse_state state;
97   grad_locus_t loc;
98   char *name;
99   struct mu_locus_range locus;
100 
101   if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
102     return 1;
103 
104   ws.ws_delim = ",";
105   if (mu_wordsplit (val->v.string, &ws,
106 		    MU_WRDSF_DEFFLAGS|MU_WRDSF_DELIM|MU_WRDSF_RETURN_DELIMS))
107     {
108       mu_error (_("cannot parse input `%s': %s"), val->v.string,
109 		mu_wordsplit_strerror (&ws));
110       return 1;
111     }
112 
113   if (mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
114 		       MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE,
115 		       &locus) == 0)
116     {
117       loc.file = (char*) locus.beg.mu_file;
118       loc.line = locus.beg.mu_line;
119     }
120   else
121     {
122       loc.file = "<unknown>";
123       loc.line = 0;
124     }
125 
126   for (i = 0, state = state_lhs; state != state_err && i < ws.ws_wordc; i++)
127     {
128       grad_avp_t *pair;
129 
130       switch (state)
131 	{
132 	case state_lhs:
133 	  name = ws.ws_wordv[i];
134 	  state = state_op;
135 	  break;
136 
137 	case state_op:
138 	  //op = ws.ws_wordv[i];
139 	  state = state_rhs;
140 	  break;
141 
142 	case state_rhs:
143 	  loc.line = i; /* Just to keep track of error location */
144 	  pair = grad_create_pair (&loc, name, grad_operator_equal,
145 				   ws.ws_wordv[i]);
146 	  if (!pair)
147 	    {
148 	      mu_error (_("cannot create radius A/V pair `%s'"), name);
149 	      state = state_err;
150 	    }
151 	  else
152 	    {
153 	      grad_avl_merge (plist, &pair);
154 	      state = state_delim;
155 	    }
156 	  break;
157 
158 	case state_delim:
159 	  if (strcmp (ws.ws_wordv[i], ","))
160 	    {
161 	      mu_error (_("expected `,' but found `%s'"), ws.ws_wordv[i]);
162 	      state = state_err;
163 	    }
164 	  else
165 	    state = state_lhs;
166 	  break;
167 
168 	default:
169 	  abort ();
170 	}
171     }
172 
173   mu_wordsplit_free (&ws);
174   mu_locus_range_deinit (&locus);
175 
176   if (state == state_err)
177     return 1;
178 
179   if (state != state_delim && state != state_delim)
180     {
181       mu_error (_("malformed radius A/V list"));
182       return 1;
183     }
184 
185   return 0;
186 }
187 
188 static int
cb_config_dir(void * data,mu_config_value_t * val)189 cb_config_dir (void *data, mu_config_value_t *val)
190 {
191   if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
192     return 1;
193   grad_config_dir = grad_estrdup (val->v.string);
194   return 0;
195 }
196 
197 static struct mu_cfg_param mu_radius_param[] = {
198   { "auth", mu_cfg_callback, &auth_request, 0, cb_request,
199     N_("Radius request for authorization."),
200     N_("request: string") },
201   { "getpwnam", mu_cfg_callback, &getpwnam_request, 0, cb_request,
202     N_("Radius request for getpwnam."),
203     N_("request: string") },
204   { "getpwuid", mu_cfg_callback, &getpwuid_request, 0, cb_request,
205     N_("Radius request for getpwuid."),
206     N_("request: string") },
207   { "directory", mu_cfg_callback, NULL, 0, cb_config_dir,
208     N_("Set radius configuration directory."),
209     N_("dir: string") },
210   { NULL }
211 };
212 
213 struct mu_cli_capa mu_cli_capa_radius = {
214   "radius",
215   NULL,
216   mu_radius_param
217 };
218 
219 /* Assume radius support is needed if any of the above requests is
220    defined. Actually, all of them should be, but it is the responsibility
221    of init to check for consistency of the configuration */
222 
223 #define NEED_RADIUS_P() \
224   (auth_request || getpwnam_request || getpwuid_request)
225 
226 static void
mu_grad_logger(int level,const grad_request_t * req,const grad_locus_t * loc,const char * func_name,int en,const char * fmt,va_list ap)227 mu_grad_logger(int level,
228 	       const grad_request_t *req,
229 	       const grad_locus_t *loc,
230 	       const char *func_name, int en,
231 	       const char *fmt, va_list ap)
232 {
233   static int mlevel[] = {
234     MU_DIAG_EMERG,
235     MU_DIAG_ALERT,
236     MU_DIAG_CRIT,
237     MU_DIAG_ERROR,
238     MU_DIAG_WARNING,
239     MU_DIAG_NOTICE,
240     MU_DIAG_INFO,
241     MU_DIAG_DEBUG
242   };
243 
244   char *pfx = NULL;
245   if (loc)
246     {
247       if (func_name)
248 	mu_asprintf (&pfx, "%s:%lu:%s: %s",
249 		     loc->file, (unsigned long) loc->line, func_name, fmt);
250       else
251 	mu_asprintf (&pfx, "%s:%lu: %s",
252 		     loc->file, (unsigned long) loc->line, fmt);
253     }
254   mu_diag_voutput (mlevel[level & GRAD_LOG_PRIMASK], pfx ? pfx : fmt, ap);
255   if (pfx)
256     free (pfx);
257 }
258 
259 static void
module_init(void * ptr)260 module_init (void *ptr)
261 {
262   if (!NEED_RADIUS_P ())
263     return;
264 
265   grad_set_logger (mu_grad_logger);
266 
267   grad_path_init ();
268   srand (time (NULL) + getpid ());
269 
270   if (grad_dict_init ())
271     {
272       mu_error (_("cannot read radius dictionaries"));
273       return;
274     }
275 
276   /* Check whether mailutils attributes are defined */
277   if (get_attribute (&MU_User_Name, "MU-User-Name")
278       || get_attribute (&MU_UID, "MU-UID")
279       || get_attribute (&MU_GID, "MU-GID")
280       || get_attribute (&MU_GECOS, "MU-GECOS")
281       || get_attribute (&MU_Dir, "MU-Dir")
282       || get_attribute (&MU_Shell, "MU-Shell")
283       || get_attribute (&MU_Mailbox, "MU-Mailbox"))
284     return;
285 
286   radius_auth_enabled = 1;
287 }
288 
289 static char *
_expand_query(const char * query,const char * ustr,const char * passwd)290 _expand_query (const char *query, const char *ustr, const char *passwd)
291 {
292   char *ret;
293   int rc;
294 
295   rc = mu_str_vexpand (&ret, query,
296 		       "user", ustr,
297 		       "passwd", passwd,
298 		       NULL);
299   if (rc)
300     {
301       if (rc == MU_ERR_FAILURE)
302 	{
303 	  mu_error (_("cannot expand line `%s': %s"), query, ret);
304 	  free (ret);
305 	}
306       else
307 	mu_error (_("cannot expand line `%s': %s"), query, mu_strerror (rc));
308       return NULL;
309     }
310 
311   return ret;
312 }
313 
314 
315 static grad_avp_t *
create_request(grad_avp_t * template,const char * ustr,const char * passwd)316 create_request (grad_avp_t *template, const char *ustr, const char *passwd)
317 {
318   grad_avp_t *newp, *p;
319 
320   newp = grad_avl_dup (template);
321   for (p = newp; p; p = p->next)
322     {
323       if (p->type == GRAD_TYPE_STRING)
324 	{
325 	  char *value = _expand_query (p->avp_strvalue, ustr, passwd);
326 	  grad_free (p->avp_strvalue);
327 	  p->avp_strvalue = value;
328 	  p->avp_strlength = strlen (value);
329 	}
330     }
331   return newp;
332 }
333 
334 
335 
336 grad_request_t *
send_request(grad_avp_t * pairs,int code,const char * user,const char * passwd)337 send_request (grad_avp_t *pairs, int code,
338 	      const char *user, const char *passwd)
339 {
340   grad_avp_t *plist = create_request (pairs, user, passwd);
341   if (plist)
342     {
343       grad_server_queue_t *queue = grad_client_create_queue (1, 0, 0);
344       grad_request_t *reply = grad_client_send (queue,
345 						GRAD_PORT_AUTH, code,
346 						plist);
347       grad_client_destroy_queue (queue);
348       grad_avl_free (plist);
349       return reply;
350     }
351   return NULL;
352 }
353 
354 #define DEFAULT_HOME_PREFIX "/home/"
355 #define DEFAULT_SHELL "/dev/null"
356 
357 int
decode_reply(grad_request_t * reply,const char * user_name,char * password,struct mu_auth_data ** return_data)358 decode_reply (grad_request_t *reply, const char *user_name, char *password,
359 	      struct mu_auth_data **return_data)
360 {
361   grad_avp_t *p;
362   int rc;
363 
364   uid_t uid = -1;
365   gid_t gid = -1;
366   char *gecos = "RADIUS User";
367   char *dir = NULL;
368   char *shell = NULL;
369   char *mailbox = NULL;
370 
371   p = grad_avl_find (reply->avlist, MU_User_Name);
372   if (p)
373     user_name = p->avp_strvalue;
374 
375   p = grad_avl_find (reply->avlist, MU_UID);
376   if (p)
377     uid = p->avp_lvalue;
378   else
379     {
380       mu_error (_("radius server did not return UID for `%s'"),  user_name);
381       return -1;
382     }
383 
384   p = grad_avl_find (reply->avlist, MU_GID);
385   if (p)
386     gid = p->avp_lvalue;
387   else
388     {
389       mu_error (_("radius server did not return GID for `%s'"),  user_name);
390       return -1;
391     }
392 
393   p = grad_avl_find (reply->avlist, MU_GECOS);
394   if (p)
395     gecos = p->avp_strvalue;
396 
397   p = grad_avl_find (reply->avlist, MU_Dir);
398   if (p)
399     dir = strdup (p->avp_strvalue);
400   else /* Try to provide a reasonable default */
401     {
402       dir = malloc (sizeof DEFAULT_HOME_PREFIX + strlen (user_name));
403       if (!dir) /* FIXME: Error code */
404 	return 1;
405       strcat (strcpy (dir, DEFAULT_HOME_PREFIX), user_name);
406     }
407 
408   p = grad_avl_find (reply->avlist, MU_Shell);
409   if (p)
410     shell = p->avp_strvalue;
411   else
412     shell = DEFAULT_SHELL;
413 
414   p = grad_avl_find (reply->avlist, MU_Mailbox);
415   if (p)
416     mailbox = strdup (p->avp_strvalue);
417   else
418     {
419       rc = mu_construct_user_mailbox_url (&mailbox, user_name);
420       if (rc)
421 	return rc;
422     }
423 
424   rc = mu_auth_data_alloc (return_data,
425 			   user_name,
426 			   password,
427 			   uid,
428 			   gid,
429 			   gecos,
430 			   dir,
431 			   shell,
432 			   mailbox,
433 			   1);
434 
435   free (dir);
436   free (mailbox);
437   return rc;
438 }
439 
440 int
mu_radius_authenticate(struct mu_auth_data ** return_data MU_ARG_UNUSED,const void * key,void * func_data MU_ARG_UNUSED,void * call_data)441 mu_radius_authenticate (struct mu_auth_data **return_data MU_ARG_UNUSED,
442 			const void *key,
443 			void *func_data MU_ARG_UNUSED, void *call_data)
444 {
445   int rc;
446   grad_request_t *reply;
447   const struct mu_auth_data *auth_data = key;
448 
449   if (!radius_auth_enabled)
450     return ENOSYS;
451 
452   if (!auth_request)
453     {
454       mu_error (_("radius request for auth is not specified"));
455       return EINVAL;
456     }
457 
458   reply = send_request (auth_request, RT_ACCESS_REQUEST,
459 			auth_data->name, (char*) call_data);
460   if (!reply)
461     return EAGAIN;
462 
463   switch (reply->code) {
464   case RT_ACCESS_ACCEPT:
465     rc = 0;
466     break;
467 
468   case RT_ACCESS_CHALLENGE:
469     /* Should return another code here? */
470   default:
471     rc = MU_ERR_AUTH_FAILURE;
472   }
473 
474   grad_request_free (reply);
475 
476   return rc;
477 }
478 
479 static int
mu_auth_radius_user_by_name(struct mu_auth_data ** return_data,const void * key,void * unused_func_data,void * unused_call_data)480 mu_auth_radius_user_by_name (struct mu_auth_data **return_data,
481 			     const void *key,
482 			     void *unused_func_data, void *unused_call_data)
483 {
484   int rc = MU_ERR_AUTH_FAILURE;
485   grad_request_t *reply;
486 
487   if (!radius_auth_enabled)
488     return ENOSYS;
489 
490   if (!getpwnam_request)
491     {
492       mu_error (_("radius request for getpwnam is not specified"));
493       return MU_ERR_FAILURE;
494     }
495 
496   reply = send_request (getpwnam_request, RT_ACCESS_REQUEST, key, NULL);
497   if (!reply)
498     {
499       mu_error (_("radius server did not respond"));
500       rc = EAGAIN;
501     }
502   else
503     {
504       if (reply->code != RT_ACCESS_ACCEPT)
505 	mu_error (_("%s: server returned %s"),
506 		  (char*) key,
507 		  grad_request_code_to_name (reply->code));
508       else
509 	rc = decode_reply (reply, key, "x", return_data);
510 
511       grad_request_free (reply);
512     }
513   return rc;
514 }
515 
516 static int
mu_auth_radius_user_by_uid(struct mu_auth_data ** return_data,const void * key,void * func_data,void * call_data)517 mu_auth_radius_user_by_uid (struct mu_auth_data **return_data,
518 			    const void *key,
519 			    void *func_data, void *call_data)
520 {
521   int rc = MU_ERR_AUTH_FAILURE;
522   grad_request_t *reply;
523   char uidstr[64];
524 
525   if (!radius_auth_enabled)
526     return ENOSYS;
527 
528   if (!key)
529     return EINVAL;
530 
531   if (!getpwuid_request)
532     {
533       mu_error (_("radius request for getpwuid is not specified"));
534       return MU_ERR_FAILURE;
535     }
536 
537   snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key);
538   reply = send_request (getpwuid_request, RT_ACCESS_REQUEST, uidstr, NULL);
539   if (!reply)
540     {
541       mu_error (_("radius server did not respond"));
542       rc = EAGAIN;
543     }
544   if (reply->code != RT_ACCESS_ACCEPT)
545     {
546       mu_error (_("uid %s: server returned %s"), uidstr,
547 		grad_request_code_to_name (reply->code));
548     }
549   else
550     rc = decode_reply (reply, uidstr, "x", return_data);
551 
552   grad_request_free (reply);
553   return rc;
554 }
555 
556 struct mu_auth_module mu_auth_radius_module = {
557   .name = "radius",
558   .cfg  = mu_radius_param,
559   .commit = module_init,
560   .handler = {
561     [mu_auth_authenticate] = mu_radius_authenticate,
562     [mu_auth_getpwnam] = mu_auth_radius_user_by_name,
563     [mu_auth_getpwuid] = mu_auth_radius_user_by_uid
564   }
565 };
566 #else
567 struct mu_auth_module mu_auth_radius_module = {
568   .name = "radius"
569 };
570 #endif
571 
572