diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-08-12 06:37:51 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2005-08-12 06:37:51 +0000 |
commit | 2d1b5a5e83edea9395e43cabc6455b6a57d4188b (patch) | |
tree | 2e17f4f3e253512e9676ecb532d89ab4edbfd3a1 | |
parent | b5423ab5aaae372d7864ab94aca620f38abd9122 (diff) | |
download | mailutils-2d1b5a5e83edea9395e43cabc6455b6a57d4188b.tar.gz mailutils-2d1b5a5e83edea9395e43cabc6455b6a57d4188b.tar.bz2 |
Radius authentication/authorization.
-rw-r--r-- | auth/radius.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/auth/radius.c b/auth/radius.c new file mode 100644 index 000000000..8a5f09754 --- /dev/null +++ b/auth/radius.c @@ -0,0 +1,571 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2005 Free Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STRINGS_H +# include <strings.h> +#endif + +#include <mailutils/list.h> +#include <mailutils/iterator.h> +#include <mailutils/mailbox.h> +#include <mailutils/argp.h> +#include <mailutils/argcv.h> +#include <mailutils/mu_auth.h> +#include <mailutils/error.h> +#include <mailutils/errno.h> +#include <mailutils/nls.h> + +#ifdef ENABLE_RADIUS + +#include <radius/radius.h> + +#define ARG_AUTH_REQUEST 256 +#define ARG_GETPWNAM_REQUEST 257 +#define ARG_GETPWUID_REQUEST 258 +#define ARG_RADIUS_DIR 259 + +static struct argp_option mu_radius_argp_option[] = { + { "radius-auth-request", ARG_AUTH_REQUEST, N_("REQUEST"), 0, + N_("Radius request to authenitcate the user"), 0 }, + { "radius-getpwnam-request", ARG_GETPWNAM_REQUEST, N_("REQUEST"), 0, + N_("Radius request to retrieve a passwd entry based on username"), 0 }, + { "radius-getpwuid-request", ARG_GETPWUID_REQUEST, N_("REQUEST"), 0, + N_("Radius request to retrieve a passwd entry based on UID"), 0 }, + { "radius-directory", ARG_RADIUS_DIR, N_("DIR"), 0, + N_("Set path to the radius configuration directory"), 0 }, + { NULL } +}; + +static char *auth_request_str; +static grad_avp_t *auth_request; + +static char *getpwnam_request_str; +static grad_avp_t *getpwnam_request; + +static char *getpwuid_request_str; +static grad_avp_t *getpwuid_request; + +static int MU_User_Name; +static int MU_UID; +static int MU_GID; +static int MU_GECOS; +static int MU_Dir; +static int MU_Shell; +static int MU_Mailbox; + +void +get_attribute (int *pattr, char *name, struct argp_state *state) +{ + grad_dict_attr_t *attr = grad_attr_name_to_dict (name); + if (!attr) + argp_error (state, _("Radius attribute %s not defined"), name); + *pattr = attr->value; +} + +enum parse_state + { + state_lhs, + state_op, + state_rhs, + state_delim + }; + +int +parse_pairlist (grad_avp_t **plist, const char *input, + struct argp_state *argp_state) +{ + int rc; + int i, argc; + char **argv; + enum parse_state state; + grad_locus_t loc; + char *name; + char *op; /* FIXME: It is actually ignored. Should it be? */ + + if (!input) + return 1; + + if ((rc = argcv_get (input, ",", NULL, &argc, &argv))) + argp_error (argp_state, _("Cannot parse input `%s': %s"), + input, mu_strerror (rc)); + + loc.file = "<command line>"; /* FIXME */ + loc.line = 0; + + for (i = 0, state = state_lhs; i < argc; i++) + { + grad_avp_t *pair; + + switch (state) + { + case state_lhs: + name = argv[i]; + state = state_op; + break; + + case state_op: + op = argv[i]; + state = state_rhs; + break; + + case state_rhs: + loc.line = i; /* Just to keep track of error location */ + pair = grad_create_pair (&loc, name, grad_operator_equal, argv[i]); + if (!pair) + argp_error (argp_state, _("cannot create radius A/V pair `%s'"), + name); + grad_avl_merge (plist, &pair); + state = state_delim; + break; + + case state_delim: + if (strcmp (argv[i], ",")) + argp_error (argp_state, _("expected `,' but found `%s'"), argv[i]); + state = state_lhs; + } + } + + if (state != state_delim && state != state_delim) + argp_error (argp_state, _("malformed radius A/V list")); + + argcv_free (argc, argv); + return 0; +} + +static void +init (struct argp_state *state) +{ + grad_path_init (); + srand (time (NULL) + getpid ()); + + if (grad_dict_init ()) + argp_error (state, _("Cannot read radius dictionaries")); + + /* Check whether mailutils attributes are defined */ + get_attribute (&MU_User_Name, "MU-User-Name", state); + get_attribute (&MU_UID, "MU-UID", state); + get_attribute (&MU_GID, "MU-GID", state); + get_attribute (&MU_GECOS, "MU-GECOS", state); + get_attribute (&MU_Dir, "MU-Dir", state); + get_attribute (&MU_Shell, "MU-Shell", state); + get_attribute (&MU_Mailbox, "MU-Mailbox", state); + + /* Parse saved requests */ + parse_pairlist (&auth_request, auth_request_str, state); + parse_pairlist (&getpwnam_request, getpwnam_request_str, state); + parse_pairlist (&getpwuid_request, getpwuid_request_str, state); +} + +static error_t +mu_radius_argp_parser (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case ARG_AUTH_REQUEST: + auth_request_str = arg; + break; + + case ARG_GETPWNAM_REQUEST: + getpwnam_request_str = arg; + break; + + case ARG_GETPWUID_REQUEST: + getpwuid_request_str = arg; + break; + + case ARG_RADIUS_DIR: + radius_dir = grad_estrdup(arg); + break; + + case ARGP_KEY_FINI: + init (state); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +char * +_expand_query (const char *query, const char *ustr, const char *passwd) +{ + char *p, *q, *res; + int len; + + if (!query) + return NULL; + + /* Compute resulting query length */ + for (len = 0, p = (char *) query; *p; ) + { + if (*p == '%') + { + if (p[1] == 'u') + { + len += ustr ? strlen (ustr) : 2; + p += 2; + } + else if (p[1] == 'p') + { + len += passwd ? strlen (passwd) : 2; + p += 2; + } + else if (p[1] == '%') + { + len++; + p += 2; + } + else + { + len++; + p++; + } + } + else + { + len++; + p++; + } + } + + res = grad_emalloc (len + 1); + if (!res) + return res; + + for (p = (char *) query, q = res; *p; ) + { + if (*p == '%') + { + switch (*++p) + { + case 'u': + if (ustr) + { + strcpy (q, ustr); + q += strlen (q); + } + else + { + *q++ = '%'; + *q++ = 'u'; + } + p++; + break; + + case 'p': + if (passwd) + { + strcpy (q, passwd); + q += strlen (q); + } + else + { + *q++ = '%'; + *q++ = 'u'; + } + p++; + break; + + case '%': + *q++ = *p++; + break; + + default: + *q++ = *p++; + } + } + else + *q++ = *p++; + } + *q = 0; + return res; +} + + +static grad_avp_t * +create_request (grad_avp_t *template, const char *ustr, const char *passwd) +{ + grad_avp_t *newp, *p; + + newp = grad_avl_dup (template); + for (p = newp; p; p = p->next) + { + if (p->type == GRAD_TYPE_STRING) + { + char *value = _expand_query (p->avp_strvalue, ustr, passwd); + grad_free (p->avp_strvalue); + p->avp_strvalue = value; + p->avp_strlength = strlen (value); + } + } + return newp; +} + + + +grad_request_t * +send_request (grad_avp_t *pairs, int code, + const char *user, const char *passwd) +{ + grad_avp_t *plist = create_request (pairs, user, passwd); + if (plist) + { + grad_server_queue_t *queue = grad_client_create_queue (1, 0, 0); + grad_request_t *reply = grad_client_send (queue, + GRAD_PORT_AUTH, code, + plist); + grad_client_destroy_queue (queue); + grad_avl_free (plist); + return reply; + } + return NULL; +} + +#define DEFAULT_HOME_PREFIX "/home/" +#define DEFAULT_SHELL "/dev/null" + +int +decode_reply (grad_request_t *reply, const char *user_name, char *password, + struct mu_auth_data **return_data) +{ + grad_avp_t *p; + int rc; + + uid_t uid = -1; + gid_t gid = -1; + char *gecos = "RADIUS User"; + char *dir = NULL; + char *shell = NULL; + char *mailbox = NULL; + + p = grad_avl_find (reply->avlist, MU_User_Name); + if (p) + user_name = p->avp_strvalue; + + p = grad_avl_find (reply->avlist, MU_UID); + if (p) + uid = p->avp_lvalue; + else + { + mu_error (_("Radius did not return UID for `%s'"), user_name); + return -1; + } + + p = grad_avl_find (reply->avlist, MU_GID); + if (p) + gid = p->avp_lvalue; + else + { + mu_error (_("Radius did not return GID for `%s'"), user_name); + return -1; + } + + p = grad_avl_find (reply->avlist, MU_GECOS); + if (p) + gecos = p->avp_strvalue; + + p = grad_avl_find (reply->avlist, MU_Dir); + if (p) + dir = strdup (p->avp_strvalue); + else /* Try to provide a reasonable default */ + { + dir = malloc (sizeof DEFAULT_HOME_PREFIX + strlen (user_name)); + if (!dir) /* FIXME: Error code */ + return 1; + strcat (strcpy (dir, DEFAULT_HOME_PREFIX), user_name); + } + + p = grad_avl_find (reply->avlist, MU_Shell); + if (p) + shell = p->avp_strvalue; + else + shell = DEFAULT_SHELL; + + p = grad_avl_find (reply->avlist, MU_Mailbox); + if (p) + mailbox = strdup (p->avp_strvalue); + else + { + rc = mu_construct_user_mailbox_url (&mailbox, user_name); + if (rc) + return rc; + } + + rc = mu_auth_data_alloc (return_data, + user_name, + password, + uid, + gid, + gecos, + dir, + shell, + mailbox, + 1); + + free (dir); + free (mailbox); + return rc; +} + +int +mu_radius_authenticate (struct mu_auth_data **return_data ARG_UNUSED, + const void *key, + void *func_data ARG_UNUSED, void *call_data) +{ + int rc; + grad_request_t *reply; + const struct mu_auth_data *auth_data = key; + + if (!auth_request) + { + mu_error (_("--radius-auth-request is not specified")); + return 1; + } + + reply = send_request (auth_request, RT_ACCESS_REQUEST, + auth_data->name, (char*) call_data); + rc = !reply || reply->code != RT_ACCESS_ACCEPT; + grad_request_free (reply); + return rc; +} + +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) +{ + int rc = 1; + grad_request_t *reply; + + if (!getpwnam_request) + { + mu_error (_("--radius-getpwnam-request is not specified")); + return 1; + } + + reply = send_request (getpwnam_request, RT_ACCESS_REQUEST, key, NULL); + if (!reply) + mu_error (_("radius server did not respond")); + else if (reply->code != RT_ACCESS_ACCEPT) + mu_error (_("%s: server returned %s"), + (char*) key, + grad_request_code_to_name (reply->code)); + else + rc = decode_reply (reply, key, "x", return_data); + grad_request_free (reply); + return rc; +} + +static int +mu_auth_radius_user_by_uid (struct mu_auth_data **return_data, + const void *key, + void *func_data, void *call_data) +{ + int rc = 1; + grad_request_t *reply; + char uidstr[64]; + + if (!key) + { + errno = EINVAL; + return 1; + } + + if (!getpwuid_request) + { + mu_error (_("--radius-getpwuid-request is not specified")); + return 1; + } + + snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key); + reply = send_request (getpwuid_request, RT_ACCESS_REQUEST, uidstr, NULL); + if (reply->code != RT_ACCESS_ACCEPT) + { + mu_error (_("uid %s: server returned %s"), uidstr, + grad_request_code_to_name (reply->code)); + } + else + { + rc = decode_reply (reply, uidstr, "x", return_data); + } + grad_request_free (reply); + return rc; +} + +struct argp mu_radius_argp = { + mu_radius_argp_option, + mu_radius_argp_parser, +}; + +#else +static int +mu_radius_authenticate (struct mu_auth_data **return_data ARG_UNUSED, + const void *key, + void *func_data ARG_UNUSED, void *call_data) +{ + errno = ENOSYS; + return 1; +} + +static int +mu_auth_radius_user_by_name (struct mu_auth_data **return_data ARG_UNUSED, + const void *key ARG_UNUSED, + void *func_data ARG_UNUSED, + void *call_data ARG_UNUSED) +{ + errno = ENOSYS; + return 1; +} + +static int +mu_auth_radius_user_by_uid (struct mu_auth_data **return_data, + const void *key, + void *func_data, void *call_data) +{ + errno = ENOSYS; + return 1; +} +#endif + +struct mu_auth_module mu_auth_radius_module = { + "radius", +#ifdef ENABLE_RADIUS + &mu_radius_argp, +#else + NULL, +#endif + mu_radius_authenticate, + NULL, + mu_auth_radius_user_by_name, + NULL, + mu_auth_radius_user_by_uid, + NULL +}; + |