/* * ProFTPD: mod_radius -- a module for RADIUS authentication and accounting * * Copyright (c) 2001-2005 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, TJ Saunders gives permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. * * This is mod_radius, contrib software for proftpd 1.2 and above. * For more information contact TJ Saunders . * * This module is based in part on code in Alan DeKok's (aland@freeradius.org) * mod_auth_radius for Apache, in part on the FreeRADIUS project's code. * * $Id: mod_radius.c,v 1.36 2005/09/19 21:35:38 castaglia Exp $ */ #define MOD_RADIUS_VERSION "mod_radius/0.8.1" #include "conf.h" #include "privs.h" /* RADIUS information */ /* From RFC2865, RFC2866 */ #define RADIUS_AUTH_PORT 1812 #define RADIUS_ACCT_PORT 1813 #define RADIUS_PASSWD_LEN 16 #define RADIUS_VECTOR_LEN 16 /* From RFC2138 */ #define RADIUS_STRING_LEN 254 /* RADIUS attribute structures */ typedef struct { unsigned char type; unsigned char length; unsigned char data[1]; } radius_attrib_t; /* RADIUS packet header */ typedef struct { unsigned char code; unsigned char id; unsigned short length; unsigned char digest[RADIUS_VECTOR_LEN]; unsigned char data[2]; char _pad[PR_TUNABLE_BUFFER_SIZE]; } radius_packet_t; #define RADIUS_HEADER_LEN 20 /* RADIUS ID Definitions (see RFC2865) */ #define RADIUS_AUTH_REQUEST 1 #define RADIUS_AUTH_ACCEPT 2 #define RADIUS_AUTH_REJECT 3 #define RADIUS_ACCT_REQUEST 4 #define RADIUS_ACCT_RESPONSE 5 #define RADIUS_ACCT_STATUS 6 #define RADIUS_AUTH_CHALLENGE 11 /* RADIUS Attribute Definitions (see RFC2865) */ #define RADIUS_USER_NAME 1 #define RADIUS_PASSWORD 2 #define RADIUS_NAS_IP_ADDRESS 4 #define RADIUS_NAS_PORT 5 #define RADIUS_SERVICE_TYPE 6 #define RADIUS_OLD_PASSWORD 17 #define RADIUS_REPLY_MESSAGE 18 #define RADIUS_STATE 24 #define RADIUS_VENDOR_SPECIFIC 26 #define RADIUS_SESSION_TIMEOUT 27 #define RADIUS_IDLE_TIMEOUT 28 #define RADIUS_CALLING_STATION_ID 31 #define RADIUS_NAS_IDENTIFIER 32 #define RADIUS_ACCT_STATUS_TYPE 40 #define RADIUS_ACCT_INPUT_OCTETS 42 #define RADIUS_ACCT_OUTPUT_OCTETS 43 #define RADIUS_ACCT_SESSION_ID 44 #define RADIUS_ACCT_AUTHENTIC 45 #define RADIUS_ACCT_SESSION_TIME 46 #define RADIUS_ACCT_TERMINATE_CAUSE 49 #define RADIUS_NAS_PORT_TYPE 61 /* RADIUS service types */ #define RADIUS_SVC_LOGIN 1 #define RADIUS_SVC_AUTHENTICATE_ONLY 8 /* RADIUS status types */ #define RADIUS_ACCT_STATUS_START 1 #define RADIUS_ACCT_STATUS_STOP 2 #define RADIUS_ACCT_STATUS_ALIVE 3 /* RADIUS NAS port types */ #define RADIUS_NAS_PORT_TYPE_VIRTUAL 5 /* RADIUS authentication types */ #define RADIUS_AUTH_NONE 0 #define RADIUS_AUTH_RADIUS 1 #define RADIUS_AUTH_LOCAL 2 /* The RFC says 4096 octets max, and most packets are less than 256. * However, this number is just larger than the maximum MTU of just * most types of networks, except maybe for gigabit ethernet. */ #define RADIUS_PACKET_LEN 1600 /* Miscellaneous default values */ #define DEFAULT_RADIUS_TIMEOUT 30 typedef struct radius_server_obj { /* Next server in line */ struct radius_server_obj *next; /* Memory pool for this object */ pool *pool; /* RADIUS server IP address */ pr_netaddr_t *addr; /* RADIUS server port */ unsigned short port; /* RADIUS server shared secret */ unsigned char *secret; /* How long to wait for RADIUS responses */ unsigned int timeout; } radius_server_t; module radius_module; static pool *radius_pool = NULL; static unsigned char radius_engine = FALSE; static radius_server_t *radius_acct_server = NULL; static radius_server_t *radius_auth_server = NULL; static int radius_logfd = -1; static char *radius_logname = NULL; static struct sockaddr radius_local_sock, radius_remote_sock; /* For tracking various values not stored in the session struct */ static char *radius_realm = NULL; static time_t radius_session_start = 0; static off_t radius_session_bytes_in = 0; static off_t radius_session_bytes_out = 0; static int radius_session_authtype = RADIUS_AUTH_LOCAL; static unsigned char radius_auth_ok = FALSE; static unsigned char radius_auth_reject = FALSE; /* "Fake" user/group information for RADIUS users. */ static unsigned char radius_have_user_info = FALSE; static struct passwd radius_passwd; static unsigned char radius_have_group_info = FALSE; static char *radius_prime_group_name = NULL; static unsigned int radius_addl_group_count = 0; static char **radius_addl_group_names = NULL; static char *radius_addl_group_names_str = NULL; static gid_t *radius_addl_group_ids = NULL; static char *radius_addl_group_ids_str = NULL; /* Other info */ static unsigned char radius_have_other_info = FALSE; /* Vendor information, defaults to Unix (Vendor-Id of 4) */ static const char *radius_vendor_name = "Unix"; static unsigned int radius_vendor_id = 4; /* Custom VSA IDs that may be used for server-supplied RadiusUserInfo * parameters. */ static int radius_uid_attr_id = 0; static int radius_gid_attr_id = 0; static int radius_home_attr_id = 0; static int radius_shell_attr_id = 0; /* Custom VSA IDs that may be used for server-supplied RadiusGroupInfo * parameters. */ static int radius_prime_group_name_attr_id = 0; static int radius_addl_group_names_attr_id = 0; static int radius_addl_group_ids_attr_id = 0; /* For tracking the ID of the last accounting packet (to prevent the * same ID from being reused). */ static unsigned char radius_last_acct_pkt_id = 0; /* Convenience macros. */ #define RADIUS_IS_VAR(str) \ str[0] == '$' && str[1] == '(' && str[strlen(str)-1] == ')' /* Function prototypes. */ static void radius_add_attrib(radius_packet_t *, unsigned char, const unsigned char *, size_t); static void radius_add_passwd(radius_packet_t *, unsigned char, const char *, char *); static void radius_build_packet(radius_packet_t *, const char *, const char *, char *); static unsigned char radius_chk_var(char *); static int radius_closelog(void); static int radius_close_socket(int); static void radius_get_acct_digest(radius_packet_t *, char *); static radius_attrib_t *radius_get_attrib(radius_packet_t *, unsigned char); static void radius_get_rnd_digest(radius_packet_t *); static radius_attrib_t *radius_get_vendor_attrib(radius_packet_t *, unsigned char); static int radius_log(const char *, ...); static radius_server_t *radius_make_server(pool *); static int radius_openlog(void); static int radius_open_socket(void); static unsigned char radius_parse_gids_str(pool *, char *, gid_t **, unsigned int *); static unsigned char radius_parse_groups_str(pool *, char *, char ***, unsigned int *); static void radius_parse_var(char *, int *, char **); static void radius_process_accpt_packet(radius_packet_t *); static void radius_process_group_info(config_rec *); static void radius_process_user_info(config_rec *); static radius_packet_t *radius_recv_packet(int, unsigned int); static int radius_send_packet(int, radius_packet_t *, radius_server_t *); static unsigned char radius_start_accting(void); static unsigned char radius_stop_accting(void); static int radius_verify_packet(radius_packet_t *, radius_packet_t *, char *); /* Support functions */ static char *radius_argsep(char **arg) { char *ret = NULL, *dst = NULL; char quote_mode = 0; if (!arg || !*arg || !**arg) return NULL; while (**arg && isspace((int) **arg)) (*arg)++; if (!**arg) return NULL; ret = dst = *arg; if (**arg == '\"') { quote_mode++; (*arg)++; } while (**arg && **arg != ',' && (quote_mode ? (**arg != '\"') : (!isspace((int) **arg)))) { if (**arg == '\\' && quote_mode) { /* escaped char */ if (*((*arg) + 1)) *dst = *(++(*arg)); } *dst++ = **arg; ++(*arg); } if (**arg) (*arg)++; *dst = '\0'; return ret; } /* Check a "$(attribute-id:default)" string for validity. */ static unsigned char radius_chk_var(char *var) { int id = 0; char *tmp = NULL; /* Must be at least six characters. */ if (strlen(var) < 7) return FALSE; /* Must start with '$(', and end with ')'. */ if (!RADIUS_IS_VAR(var)) return FALSE; /* Must have a ':'. */ if ((tmp = strchr(var, ':')) == NULL) return FALSE; /* ':' must be between '(' and ')'. */ if (tmp < (var + 3) || tmp > &var[strlen(var)-2]) return FALSE; /* Parse out the component int/string. */ radius_parse_var(var, &id, NULL); /* Int must be greater than zero. */ if (id < 1) return FALSE; return TRUE; } /* Separate the given "$(attribute-id:default)" string into its constituent * custom attribute ID (int) and default (string) components. */ static void radius_parse_var(char *var, int *attr_id, char **attr_default) { pool *tmp_pool = make_sub_pool(radius_pool); char *var_cpy = pstrdup(tmp_pool, var), *tmp = NULL; /* First, strip off the "$()" variable characters. */ var_cpy[strlen(var_cpy)-1] = '\0'; var_cpy += 2; /* Find the delimiting ':' */ tmp = strchr(var_cpy, ':'); *tmp++ = '\0'; if (attr_id) *attr_id = atoi(var_cpy); if (attr_default) { tmp = strchr(var, ':'); /* Note: this works because the calling of this function by * radius_chk_var(), which occurs during the parsing process, uses * a NULL for this portion, so that the string stored in the config_rec * is not actually manipulated, as is done here. */ var[strlen(var)-1] = '\0'; *attr_default = ++tmp; } /* Clean up. */ destroy_pool(tmp_pool); } static unsigned char radius_parse_gids_str(pool *p, char *gids_str, gid_t **gids, unsigned int *ngids) { char *val = NULL; array_header *group_ids = make_array(p, 0, sizeof(gid_t *)); /* Add each GID to the array. */ while ((val = radius_argsep(&gids_str)) != NULL) { gid_t gid; char *endp = NULL; /* Make sure the given ID is a valid number. */ gid = strtoul(val, &endp, 10); if (endp && *endp) { pr_log_pri(PR_LOG_NOTICE, "RadiusGroupInfo badly formed group ID: %s", val); return FALSE; } /* Push the ID into the ID array. */ *((gid_t *) push_array(group_ids)) = gid; } *gids = (gid_t *) group_ids->elts; *ngids = group_ids->nelts; return TRUE; } static unsigned char radius_parse_groups_str(pool *p, char *groups_str, char ***groups, unsigned int *ngroups) { char *name = NULL; array_header *group_names = make_array(p, 0, sizeof(char **)); /* Add each name to the array. */ while ((name = radius_argsep(&groups_str)) != NULL) { char *tmp = pstrdup(p, name); /* Push the name into the name array. */ *((char **) push_array(group_names)) = tmp; } *groups = (char **) group_names->elts; *ngroups = group_names->nelts; return TRUE; } static void radius_process_accpt_packet(radius_packet_t *packet) { /* First, parse the packet for any non-RadiusUserInfo attributes, * such as timeouts. None are currently implemented, but...this would * be the place to do it. */ /* radius_log("parsing packet for custom attribute IDs"); */ /* Now, parse the packet for any server-supplied RadiusUserInfo attributes, * if RadiusUserInfo is indeed in effect. */ if (!radius_have_user_info && !radius_have_group_info) /* Return now if there's no reason for doing extra work. */ return; if (radius_uid_attr_id || radius_gid_attr_id || radius_home_attr_id || radius_shell_attr_id) { radius_log("parsing packet for RadiusUserInfo attributes"); /* These custom values will been supplied in the configuration file, and * set when the RadiusUserInfo config_rec is retrieved, during * session initialization. */ if (radius_uid_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_uid_attr_id); if (attrib) { int uid = -1; /* Parse the attribute value into an int, then cast it into the * radius_passwd.pw_uid field. Make sure it's a sane UID * (ie non-negative). */ /* Dare we trust attrib->length? */ memcpy(&uid, attrib->data, attrib->length); uid = ntohl(uid); if (uid < 0) radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal user ID: '%u'", radius_vendor_name, radius_uid_attr_id, uid); else { radius_passwd.pw_uid = uid; radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "user ID: '%u'", radius_vendor_name, radius_uid_attr_id, radius_passwd.pw_uid); } } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "user ID: defaulting to '%u'", radius_vendor_name, radius_uid_attr_id, radius_passwd.pw_uid); } if (radius_gid_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_gid_attr_id); if (attrib) { int gid = -1; /* Parse the attribute value into an int, then cast it into the * radius_passwd.pw_gid field. Make sure it's a sane GID * (ie non-negative). */ /* Dare we trust attrib->length? */ memcpy(&gid, attrib->data, attrib->length); gid = ntohl(gid); if (gid < 0) radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal group ID: '%u'", radius_vendor_name, radius_gid_attr_id, gid); else { radius_passwd.pw_gid = gid; radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "group ID: '%u'", radius_vendor_name, radius_gid_attr_id, radius_passwd.pw_gid); } } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "group ID: defaulting to '%u'", radius_vendor_name, radius_gid_attr_id, radius_passwd.pw_gid); } if (radius_home_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_home_attr_id); if (attrib) { /* RADIUS strings are not NUL-terminated. */ char *home = pcalloc(radius_pool, attrib->length + 1); /* Parse the attribute value into a string of the necessary length, * then replace radius_passwd.pw_dir with it. Make sure it's a sane * home directory (ie starts with a '/'). */ /* Dare we trust attrib->length? */ memcpy(home, attrib->data, attrib->length); if (*home != '/') radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal home: '%s'", radius_vendor_name, radius_home_attr_id, home); else { radius_passwd.pw_dir = home; radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "home directory: '%s'", radius_vendor_name, radius_home_attr_id, radius_passwd.pw_dir); } } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "home directory: defaulting to '%s'", radius_vendor_name, radius_home_attr_id, radius_passwd.pw_dir); } if (radius_shell_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_shell_attr_id); if (attrib) { /* RADIUS strings are not NUL-terminated. */ char *shell = pcalloc(radius_pool, attrib->length + 1); /* Parse the attribute value into a string of the necessary length, * then replace radius_passwd.pw_shell with it. Make sure it's a sane * shell (ie starts with a '/'). */ /* Dare we trust attrib->length? */ memcpy(shell, attrib->data, attrib->length); if (*shell != '/') radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal shell: '%s'", radius_vendor_name, radius_shell_attr_id, shell); else { radius_passwd.pw_shell = shell; radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "shell: '%s'", radius_vendor_name, radius_shell_attr_id, radius_passwd.pw_shell); } } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "shell: defaulting to '%s'", radius_vendor_name, radius_shell_attr_id, radius_passwd.pw_shell); } } if (radius_prime_group_name_attr_id || radius_addl_group_names_attr_id || radius_addl_group_ids_attr_id) { unsigned int ngroups = 0, ngids = 0; char **groups = NULL; gid_t *gids = NULL; radius_log("parsing packet for RadiusGroupInfo attributes"); if (radius_prime_group_name_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_prime_group_name_attr_id); if (attrib) { /* RADIUS strings are not NUL-terminated. */ char *group_name = pcalloc(radius_pool, attrib->length + 1); /* Dare we trust attrib->length? */ memcpy(group_name, attrib->data, attrib->length); radius_prime_group_name = group_name; radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "primary group name: '%s'", radius_vendor_name, radius_prime_group_name_attr_id, radius_prime_group_name); } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "prime group name: defaulting to '%s'", radius_vendor_name, radius_prime_group_name_attr_id, radius_prime_group_name); } if (radius_addl_group_names_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_addl_group_names_attr_id); if (attrib) { /* RADIUS strings are not NUL-terminated. */ char *group_names = pcalloc(radius_pool, attrib->length + 1); char *group_names_str = NULL; /* Dare we trust attrib->length? */ memcpy(group_names, attrib->data, attrib->length); /* Make a copy of the string, for logging purposes. The parsing * of the original string will consume it. */ group_names_str = pstrdup(radius_pool, group_names); if (!radius_parse_groups_str(radius_pool, group_names, &groups, &ngroups)) radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal additional group names: '%s'", radius_vendor_name, radius_addl_group_names_attr_id, group_names_str); else radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "additional group names: '%s'", radius_vendor_name, radius_addl_group_names_attr_id, group_names_str); } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "additional group names: defaulting to '%s'", radius_vendor_name, radius_addl_group_names_attr_id, radius_addl_group_names_str); } if (radius_addl_group_ids_attr_id) { radius_attrib_t *attrib = radius_get_vendor_attrib(packet, radius_addl_group_ids_attr_id); if (attrib) { /* RADIUS strings are not NUL-terminated. */ char *group_ids = pcalloc(radius_pool, attrib->length + 1); char *group_ids_str = NULL; /* Dare we trust attrib->length? */ memcpy(group_ids, attrib->data, attrib->length); /* Make a copy of the string, for logging purposes. The parsing * of the original string will consume it. */ group_ids_str = pstrdup(radius_pool, group_ids); if (!radius_parse_gids_str(radius_pool, group_ids, &gids, &ngids)) radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "illegal additional group IDs: '%s'", radius_vendor_name, radius_addl_group_ids_attr_id, group_ids_str); else radius_log("packet includes '%s' Vendor-Specific Attribute %d for " "additional group IDs: '%s'", radius_vendor_name, radius_addl_group_ids_attr_id, group_ids_str); } else radius_log("packet lacks '%s' Vendor-Specific Attribute %d for " "additional group IDs: defaulting to '%s'", radius_vendor_name, radius_addl_group_ids_attr_id, radius_addl_group_ids_str); } /* One last RadiusGroupInfo check: does the number of returned group * names match the number of returned group IDs? */ if (ngroups == ngids) { radius_have_group_info = TRUE; radius_addl_group_count = ngroups; radius_addl_group_names = groups; radius_addl_group_ids = gids; } else radius_log("server provided mismatched number of group names (%u) " "and group IDs (%u), ignoring them", ngroups, ngids); } } static void radius_process_group_info(config_rec *c) { char *param = NULL; unsigned char have_illegal_value = FALSE; /* Parse out any configured attribute/defaults here. The stored strings will * already have been sanitized by the configuration handler, so I don't * need to worry about that here. */ param = (char *) c->argv[0]; if (RADIUS_IS_VAR(param)) { radius_parse_var(param, &radius_prime_group_name_attr_id, &radius_prime_group_name); } else radius_prime_group_name = param; /* If the group count (c->argv[1]) is zero, then I know that the data * are VSA variable strings. Otherwise, the group information has * already been parsed. */ if (*((unsigned int *) c->argv[1]) == 0) { unsigned int ngroups = 0, ngids = 0; char **groups = NULL; gid_t *gids = NULL; radius_parse_var((char *) c->argv[2], &radius_addl_group_names_attr_id, &radius_addl_group_names_str); /* Now, parse the default value provided. */ if (!radius_parse_groups_str(c->pool, radius_addl_group_names_str, &groups, &ngroups)) { radius_log("badly formatted RadiusGroupInfo default additional " "group names"); have_illegal_value = TRUE; } radius_parse_var((char *) c->argv[3], &radius_addl_group_ids_attr_id, &radius_addl_group_ids_str); /* Similarly, parse the default value provided. */ if (!radius_parse_gids_str(c->pool, radius_addl_group_ids_str, &gids, &ngids)) { radius_log("badly formatted RadiusGroupInfo default additional " "group IDs"); have_illegal_value = TRUE; } if (!have_illegal_value && ngroups != ngids) { radius_log("mismatched number of RadiusGroupInfo default additional " "group names (%u) and IDs (%u)", ngroups, ngids); have_illegal_value = TRUE; } if (!have_illegal_value) { radius_have_group_info = TRUE; radius_addl_group_count = ngroups; radius_addl_group_names = groups; radius_addl_group_ids = gids; } } else { radius_have_group_info = TRUE; radius_addl_group_count = *((unsigned int *) c->argv[1]); radius_addl_group_names = (char **) c->argv[2]; radius_addl_group_ids = (gid_t *) c->argv[3]; } if (have_illegal_value) { radius_have_group_info = FALSE; radius_log("error with RadiusGroupInfo parameters, ignoring them"); } } static void radius_process_user_info(config_rec *c) { char *param = NULL; unsigned char have_illegal_value = FALSE; /* radius_passwd.pw_name will be filled in later, after successful * authentication. radius_passwd.pw_gecos will always be NULL, as there * is no practical need for this information. */ radius_passwd.pw_passwd = NULL; radius_passwd.pw_gecos = NULL; /* Parse out any configured attribute/defaults here. The stored strings will * already have been sanitized by the configuration handler, so I don't * need to worry about that here. */ /* Process the UID string. */ param = (char *) c->argv[0]; if (RADIUS_IS_VAR(param)) { char *endp = NULL, *value = NULL; radius_parse_var(param, &radius_uid_attr_id, &value); radius_passwd.pw_uid = (uid_t) strtoul(value, &endp, 10); if (radius_passwd.pw_uid == (uid_t) -1) { radius_log("illegal RadiusUserInfo default UID value: -1 not allowed"); have_illegal_value = TRUE; } if (endp && *endp) { radius_log("illegal RadiusUserInfo default UID value: '%s' not a number", value); have_illegal_value = TRUE; } } else { char *endp = NULL; radius_passwd.pw_uid = (uid_t) strtoul(param, &endp, 10); if (radius_passwd.pw_uid == (uid_t) -1) { radius_log("illegal RadiusUserInfo UID value: -1 not allowed"); have_illegal_value = TRUE; } if (endp && *endp) { radius_log("illegal RadiusUserInfo UID value: '%s' not a number", param); have_illegal_value = TRUE; } } /* Process the GID string. */ param = (char *) c->argv[1]; if (RADIUS_IS_VAR(param)) { char *endp = NULL, *value = NULL; radius_parse_var(param, &radius_gid_attr_id, &value); radius_passwd.pw_gid = (gid_t) strtoul(value, &endp, 10); if (radius_passwd.pw_gid == (gid_t) -1) { radius_log("illegal RadiusUserInfo default GID value: -1 not allowed"); have_illegal_value = TRUE; } if (endp && *endp) { radius_log("illegal RadiusUserInfo default GID value: '%s' not a number", value); have_illegal_value = TRUE; } } else { char *endp = NULL; radius_passwd.pw_gid = (gid_t) strtoul(param, &endp, 10); if (radius_passwd.pw_gid == (gid_t) -1) { radius_log("illegal RadiusUserInfo GID value: -1 not allowed"); have_illegal_value = TRUE; } if (endp && *endp) { radius_log("illegal RadiusUserInfo GID value: '%s' not a number", param); have_illegal_value = TRUE; } } /* Parse the home directory string. */ param = (char *) c->argv[2]; if (RADIUS_IS_VAR(param)) { radius_parse_var(param, &radius_home_attr_id, &radius_passwd.pw_dir); if (*radius_passwd.pw_dir != '/') { radius_log("illegal RadiusUserInfo default home value: '%s' " "not an absolute path", radius_passwd.pw_dir); have_illegal_value = TRUE; } } else /* Param already checked in this case. */ radius_passwd.pw_dir = param; /* Process the shell string. */ param = (char *) c->argv[3]; if (RADIUS_IS_VAR(param)) { radius_parse_var(param, &radius_shell_attr_id, &radius_passwd.pw_shell); if (*radius_passwd.pw_shell != '/') { radius_log("illegal RadiusUserInfo default shell value: '%s' " "not an absolute path", radius_passwd.pw_shell); have_illegal_value = TRUE; } } else /* Param already checked in this case. */ radius_passwd.pw_shell = param; if (!have_illegal_value) radius_have_user_info = TRUE; else radius_log("error with RadiusUserInfo parameters, ignoring them"); } static unsigned char *radius_xor(unsigned char *p, unsigned char *q, size_t len) { register int i = 0; unsigned char *tmp = p; for (i = 0; i < len; i++) *(p++) ^= *(q++); return tmp; } /* Built-in MD5 */ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. * All rights reserved. * * License to copy and use this software is granted provided that it * is identified as the "RSA Data Security, Inc. MD5 Message-Digest * Algorithm" in all material mentioning or referencing this software * or this function. * * License is also granted to make and use derivative works provided * that such works are identified as "derived from the RSA Data * Security, Inc. MD5 Message-Digest Algorithm" in all material * mentioning or referencing the derived work. * * RSA Data Security, Inc. makes no representations concerning either * the merchantability of this software or the suitability of this * software for any particular purpose. It is provided "as is" * without express or implied warranty of any kind. * * These notices must be retained in any copies of any part of this * documentation and/or software. */ /* MD5 context */ typedef struct { /* state (ABCD) */ unsigned long state[4]; /* number of bits, module 2^64 (LSB first) */ unsigned long count[2]; /* input buffer */ unsigned char buffer[64]; } MD5_CTX; static void MD5Init(MD5_CTX *); static void MD5Update(MD5_CTX *, unsigned char *, unsigned int); static void MD5Final(unsigned char[16], MD5_CTX *); /* Note: these MD5 routines are taken from RFC 1321 */ #ifdef HAVE_MEMCPY # define MD5_memcpy(a, b, c) memcpy((a), (b), (c)) # define MD5_memset(a, b, c) memset((a), (b), (c)) #endif /* Constants for MD5Transform routine. */ #define S11 7 #define S12 12 #define S13 17 #define S14 22 #define S21 5 #define S22 9 #define S23 14 #define S24 20 #define S31 4 #define S32 11 #define S33 16 #define S34 23 #define S41 6 #define S42 10 #define S43 15 #define S44 21 static void MD5Transform(unsigned long[4], unsigned char[64]); static void Encode(unsigned char *, unsigned long *, unsigned int); static void Decode(unsigned long *, unsigned char *, unsigned int); #ifndef HAVE_MEMCPY static void MD5_memcpy(unsigned char *, unsigned char *, unsigned int); static void MD5_memset(unsigned char *, int, unsigned int); #endif static unsigned char PADDING[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* F, G, H and I are basic MD5 functions. */ #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | (~z))) /* ROTATE_LEFT rotates x left n bits. */ #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. * Rotation is separate from addition to prevent recomputation. */ #define FF(a, b, c, d, x, s, ac) { \ (a) += F ((b), (c), (d)) + (x) + (unsigned long)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define GG(a, b, c, d, x, s, ac) { \ (a) += G ((b), (c), (d)) + (x) + (unsigned long)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define HH(a, b, c, d, x, s, ac) { \ (a) += H ((b), (c), (d)) + (x) + (unsigned long)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define II(a, b, c, d, x, s, ac) { \ (a) += I ((b), (c), (d)) + (x) + (unsigned long)(ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } /* MD5 initialization. Begins an MD5 operation, writing a new context. */ static void MD5Init(MD5_CTX *context) { context->count[0] = context->count[1] = 0; /* Load magic initialization constants. */ context->state[0] = 0x67452301; context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; } /* MD5 block update operation. Continues an MD5 message-digest * operation, processing another message block, and updating the * context. */ static void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen) { unsigned int i, index, partLen; /* Compute number of bytes mod 64 */ index = (unsigned int)((context->count[0] >> 3) & 0x3F); /* Update number of bits */ if ((context->count[0] += ((unsigned long)inputLen << 3)) < ((unsigned long)inputLen << 3)) context->count[1]++; context->count[1] += ((unsigned long)inputLen >> 29); partLen = 64 - index; /* Transform as many times as possible */ if (inputLen >= partLen) { MD5_memcpy((unsigned char *) &context->buffer[index], (unsigned char *) input, partLen); MD5Transform(context->state, context->buffer); for (i = partLen; i + 63 < inputLen; i += 64) MD5Transform(context->state, &input[i]); index = 0; } else i = 0; /* Buffer remaining input */ MD5_memcpy((unsigned char *) &context->buffer[index], (unsigned char *) &input[i], inputLen-i); } /* MD5 finalization. Ends an MD5 message-digest operation, writing the * the message digest and zeroizing the context. */ static void MD5Final(unsigned char digest[16], MD5_CTX *context) { unsigned char bits[8]; unsigned int index, padLen; /* Save number of bits */ Encode (bits, context->count, 8); /* Pad out to 56 mod 64. */ index = (unsigned int) ((context->count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); MD5Update(context, PADDING, padLen); /* Append length (before padding) */ MD5Update(context, bits, 8); /* Store state in digest */ Encode(digest, context->state, 16); /* Zeroize sensitive information. */ MD5_memset((unsigned char *) context, 0, sizeof(*context)); } /* MD5 basic transformation. Transforms state based on block. */ static void MD5Transform(unsigned long state[4], unsigned char block[64]) { unsigned long a = state[0], b = state[1], c = state[2], d = state[3], x[16]; Decode(x, block, 64); /* Round 1 */ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ /* Round 2 */ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ /* Round 3 */ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ /* Round 4 */ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; /* Zeroize sensitive information. */ MD5_memset((unsigned char *) x, 0, sizeof(x)); } /* Encodes input (unsigned long) into output (unsigned char). Assumes len is * a multiple of 4. */ static void Encode(unsigned char *output, unsigned long *input, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) { output[j] = (unsigned char)(input[i] & 0xff); output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); } } /* Decodes input (unsigned char) into output (unsigned long). Assumes len is * a multiple of 4. */ static void Decode(unsigned long *output, unsigned char *input, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) output[i] = ((unsigned long)input[j]) | (((unsigned long)input[j+1]) << 8) | (((unsigned long)input[j+2]) << 16) | (((unsigned long)input[j+3]) << 24); } #ifndef HAVE_MEMCPY /* Note: Replace "for loop" with standard memcpy if possible. */ static void MD5_memcpy(unsigned char *output, unsigned char *input, unsigned int len) { unsigned int i; for (i = 0; i < len; i++) output[i] = input[i]; } /* Note: Replace "for loop" with standard memset if possible. */ static void MD5_memset(unsigned char *output, int value, unsigned int len) { unsigned int i; for (i = 0; i < len; i++) ((char *)output)[i] = (char)value; } #endif /* Logging */ static int radius_closelog(void) { /* sanity check */ if (radius_logfd != -1) { close(radius_logfd); radius_logfd = -1; radius_logname = NULL; } return 0; } static int radius_log(const char *fmt, ...) { char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; time_t timestamp = time(NULL); struct tm *t = NULL; va_list msg; /* sanity check */ if (!radius_logname) return 0; t = localtime(×tamp); /* prepend the timestamp */ strftime(buf, sizeof(buf), "%b %d %H:%M:%S ", t); buf[sizeof(buf) - 1] = '\0'; /* prepend a small header */ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), MOD_RADIUS_VERSION "[%u]: ", (unsigned int) getpid()); buf[sizeof(buf) - 1] = '\0'; /* affix the message */ va_start(msg, fmt); vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, msg); va_end(msg); buf[sizeof(buf) - 1] = '\0'; buf[strlen(buf)] = '\n'; if (write(radius_logfd, buf, strlen(buf)) < 0) return -1; return 0; } static int radius_openlog(void) { int res = 0; /* Sanity checks */ if ((radius_logname = (char *) get_param_ptr(main_server->conf, "RadiusLog", FALSE)) == NULL) return 0; if (!strcasecmp(radius_logname, "none")) { radius_logname = NULL; return 0; } pr_signals_block(); PRIVS_ROOT res = pr_log_openfile(radius_logname, &radius_logfd, 0640); PRIVS_RELINQUISH pr_signals_unblock(); return res; } /* RADIUS routines */ /* Add an attribute to a RADIUS packet. */ static void radius_add_attrib(radius_packet_t *packet, unsigned char type, const unsigned char *data, size_t datalen) { radius_attrib_t *attrib = NULL; attrib = (radius_attrib_t *) ((unsigned char *) packet + ntohs(packet->length)); attrib->type = type; /* Total size of the attribute. The "+ 2" takes into account the size * of the attribute identifier. */ attrib->length = datalen + 2; /* Increment the size of the given packet. */ packet->length = htons(ntohs(packet->length) + attrib->length); memcpy(attrib->data, data, datalen); } /* Add a RADIUS password attribute to the packet. */ static void radius_add_passwd(radius_packet_t *packet, unsigned char type, const char *passwd, char *secret) { MD5_CTX ctx, secret_ctx; radius_attrib_t *attrib = NULL; unsigned char calculated[RADIUS_VECTOR_LEN]; char pwhash[PR_TUNABLE_BUFFER_SIZE]; size_t pwlen = strlen(passwd); char *digest = NULL; register unsigned int i = 0; if (pwlen == 0) { pwlen = RADIUS_PASSWD_LEN; } if ((pwlen & (RADIUS_PASSWD_LEN - 1)) != 0) { /* Round up the length. */ pwlen += (RADIUS_PASSWD_LEN - 1); /* Truncate the length, as necessary. */ pwlen &= ~(RADIUS_PASSWD_LEN - 1); } /* Clear the buffers. */ memset(pwhash, '\0', sizeof(pwhash)); memcpy(pwhash, passwd, pwlen); /* Find the password attribute. */ attrib = radius_get_attrib(packet, RADIUS_PASSWORD); if (type == RADIUS_PASSWORD) digest = packet->digest; else digest = attrib->data; /* Encrypt the password. Password: c[0] = p[0] ^ MD5(secret + digest) */ MD5Init(&secret_ctx); MD5Update(&secret_ctx, secret, strlen(secret)); /* Save this hash for later. */ ctx = secret_ctx; MD5Update(&ctx, digest, RADIUS_VECTOR_LEN); /* Set the calculated digest. */ MD5Final(calculated, &ctx); /* XOR the results. */ radius_xor(pwhash, calculated, RADIUS_PASSWD_LEN); /* For each step through: e[i] = p[i] ^ MD5(secret + e[i-1]) */ for (i = 1; i < (pwlen >> 4); i++) { /* Start with the old value of the MD5 sum. */ ctx = secret_ctx; MD5Update(&ctx, &pwhash[(i-1) * RADIUS_PASSWD_LEN], RADIUS_PASSWD_LEN); /* Set the calculated digest. */ MD5Final(calculated, &ctx); /* XOR the results. */ radius_xor(&pwhash[i * RADIUS_PASSWD_LEN], calculated, RADIUS_PASSWD_LEN); } if (type == RADIUS_OLD_PASSWORD) attrib = radius_get_attrib(packet, RADIUS_OLD_PASSWORD); if (!attrib) radius_add_attrib(packet, type, pwhash, pwlen); else /* Overwrite the packet data. */ memcpy(attrib->data, pwhash, pwlen); } static void radius_get_acct_digest(radius_packet_t *packet, char *secret) { MD5_CTX ctx; /* Clear the current digest (not needed yet for accounting packets) */ memset(packet->digest, 0, RADIUS_VECTOR_LEN); MD5Init(&ctx); /* Add the packet data to the mix. */ MD5Update(&ctx, (unsigned char *) packet, ntohs(packet->length)); /* Add the secret to the mix. */ MD5Update(&ctx, secret, strlen(secret)); /* Set the calculated digest in place in the packet. */ MD5Final(packet->digest, &ctx); } /* Obtain a random digest. */ static void radius_get_rnd_digest(radius_packet_t *packet) { MD5_CTX ctx; struct timeval tv; struct timezone tz; /* Use the time of day with the best resolution the system can give us, * often close to microsecond accuracy. */ gettimeofday(&tv, &tz); /* Add in some (possibly) hard to guess information. */ tv.tv_sec ^= getpid() * getppid(); /* Use MD5 to obtain (hopefully) cryptographically strong pseudo-random * numbers */ MD5Init(&ctx); MD5Update(&ctx, (unsigned char *) &tv, sizeof(tv)); MD5Update(&ctx, (unsigned char *) &tz, sizeof(tz)); /* Set the calculated digest in the space provided. */ MD5Final(packet->digest, &ctx); } /* RADIUS packet manipulation functions. */ /* Find an attribute in a RADIUS packet. Note that the packet length * is always kept in network byte order. */ static radius_attrib_t *radius_get_attrib(radius_packet_t *packet, unsigned char type) { radius_attrib_t *attrib = (radius_attrib_t *) &packet->data; int len = ntohs(packet->length) - RADIUS_HEADER_LEN; while (attrib->type != type) { if (attrib->length == 0 || (len -= attrib->length) <= 0) { /* Requested attribute not found. */ return NULL; } /* Examine the next attribute in the packet. */ attrib = (radius_attrib_t *) ((char *) attrib + attrib->length); } return attrib; } /* Find a Vendor-Specific Attribute (VSA) in a RADIUS packet. Note that * the packet length is always kept in network byte order. * * In this first cut, the looked-for vendor is hardcoded to be Unix * (Vendor-Id of 4). */ static radius_attrib_t *radius_get_vendor_attrib(radius_packet_t *packet, unsigned char type) { radius_attrib_t *attrib = (radius_attrib_t *) &packet->data; int len = ntohs(packet->length) - RADIUS_HEADER_LEN; while (attrib) { unsigned int vendor_id = 0; radius_attrib_t *vsa = NULL; if (attrib->length <= 0) return NULL; if (attrib->type != RADIUS_VENDOR_SPECIFIC) { len -= attrib->length; attrib = (radius_attrib_t *) ((char *) attrib + attrib->length); continue; } /* The first four octets (bytes) of data will contain the Vendor-Id. */ memcpy(&vendor_id, attrib->data, sizeof(unsigned int)); vendor_id = ntohl(vendor_id); if (vendor_id != radius_vendor_id) { len -= attrib->length; attrib = (radius_attrib_t *) ((char *) attrib + attrib->length); continue; } /* Parse the data value for this attribute into a VSA structure. */ vsa = (radius_attrib_t *) ((char *) attrib->data + sizeof(int)); /* Does this VSA have the type requested? */ if (vsa->type != type) { len -= attrib->length; attrib = (radius_attrib_t *) ((char *) attrib + attrib->length); continue; } /* Adjust the VSA length (I'm not sure why this is necessary, but a reading * of the FreeRADIUS sources show it to be. Weird.) */ vsa->length -= 2; return vsa; } return NULL; } /* Build a RADIUS packet, initializing some of the header and adding * common attributes. */ static void radius_build_packet(radius_packet_t *packet, const char *user, const char *passwd, char *secret) { unsigned int nas_port_type = htonl(RADIUS_NAS_PORT_TYPE_VIRTUAL); int nas_port = htonl(main_server->ServerPort); char *caller_id = NULL; /* Set the packet length. */ packet->length = htons(RADIUS_HEADER_LEN); /* Obtain a random digest. */ radius_get_rnd_digest(packet); /* Set the ID for the packet. */ packet->id = packet->digest[0]; /* Add the user attribute. */ radius_add_attrib(packet, RADIUS_USER_NAME, user, strlen(user)); /* Add the password attribute, if given. */ if (passwd) radius_add_passwd(packet, RADIUS_PASSWORD, passwd, secret); else if (packet->code != RADIUS_ACCT_REQUEST) /* Add a NULL password if necessary. */ radius_add_passwd(packet, RADIUS_PASSWORD, "", secret); /* Add a NAS identifier attribute of the service name: 'ftp'. */ radius_add_attrib(packet, RADIUS_NAS_IDENTIFIER, "ftp", 3); #ifndef PR_USE_IPV6 /* Add a NAS IP address attribute. */ radius_add_attrib(packet, RADIUS_NAS_IP_ADDRESS, (unsigned char *) &((struct in_addr *) pr_netaddr_get_inaddr(pr_netaddr_get_sess_local_addr()))->s_addr, sizeof(((struct in_addr *) pr_netaddr_get_inaddr(pr_netaddr_get_sess_local_addr()))->s_addr)); #endif /* PR_USE_IPV6 */ /* Add a NAS port attribute. */ radius_add_attrib(packet, RADIUS_NAS_PORT, (unsigned char *) &nas_port, sizeof(int)); /* Add a NAS port type attribute. */ radius_add_attrib(packet, RADIUS_NAS_PORT_TYPE, (unsigned char *) &nas_port_type, sizeof(int)); /* Add the calling station ID attribute (this is the IP of the connecting * client). */ caller_id = (char *) pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr()); radius_add_attrib(packet, RADIUS_CALLING_STATION_ID, caller_id, strlen(caller_id)); } static radius_server_t *radius_make_server(pool *parent_pool) { pool *server_pool = NULL; radius_server_t *server = NULL; /* sanity check */ if (!parent_pool) return NULL; /* allocate a subpool for the new rec */ server_pool = make_sub_pool(parent_pool); /* allocate the rec from the subpool */ server = (radius_server_t *) pcalloc(server_pool, sizeof(radius_server_t)); /* set the members to sane default values */ server->pool = server_pool; server->addr = NULL; server->port = RADIUS_AUTH_PORT; server->secret = NULL; server->timeout = DEFAULT_RADIUS_TIMEOUT; server->next = NULL; return server; } static int radius_open_socket(void) { int sockfd = -1; struct sockaddr_in *radius_sockaddr_in = NULL; unsigned short local_port = 0; /* Obtain a socket descriptor. */ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { radius_log("notice: unable to open socket for communication: %s", strerror(errno)); return -1; } /* Set the members appropriately to bind to the descriptor. */ memset((void *) &radius_local_sock, 0, sizeof(radius_local_sock)); radius_sockaddr_in = (struct sockaddr_in *) &radius_local_sock; radius_sockaddr_in->sin_family = AF_INET; radius_sockaddr_in->sin_addr.s_addr = INADDR_ANY; /* Use our process ID as a local port for RADIUS. */ local_port = (getpid() & 0x7fff) + 1024; do { pr_signals_handle(); local_port++; radius_sockaddr_in->sin_port = htons(local_port); } while ((bind(sockfd, &radius_local_sock, sizeof(radius_local_sock)) < 0) && (local_port < USHRT_MAX)); if (local_port >= USHRT_MAX) { close(sockfd); radius_log("notice: unable to bind to socket: no open local ports"); return -1; } /* Done */ return sockfd; } static int radius_close_socket(int sockfd) { return close(sockfd); } static radius_packet_t *radius_recv_packet(int sockfd, unsigned int timeout) { static unsigned char recvbuf[RADIUS_PACKET_LEN]; radius_packet_t *packet = NULL; int res = 0, recvlen = -1, sockaddrlen = sizeof(struct sockaddr); struct timeval tv; fd_set rset; /* receive the response, waiting as necessary */ memset(recvbuf, '\0', sizeof(recvbuf)); tv.tv_sec = timeout; tv.tv_usec = 0; FD_ZERO(&rset); FD_SET(sockfd, &rset); res = select(sockfd + 1, &rset, NULL, NULL, &tv); if (res == 0) { radius_log("server failed to respond in %u seconds", timeout); return NULL; } else if (res < 0) { radius_log("error: unable to receive response: %s", strerror(errno)); return NULL; } if ((recvlen = recvfrom(sockfd, (char *) recvbuf, RADIUS_PACKET_LEN, 0, &radius_remote_sock, &sockaddrlen)) < 0) { radius_log("error reading packet: %s", strerror(errno)); return NULL; } packet = (radius_packet_t *) recvbuf; /* Make sure the packet is of valid length. */ if (ntohs(packet->length) != recvlen || ntohs(packet->length) > RADIUS_PACKET_LEN) { radius_log("received corrupted packet"); return NULL; } return packet; } static int radius_send_packet(int sockfd, radius_packet_t *packet, radius_server_t *server) { struct sockaddr_in *radius_sockaddr_in = (struct sockaddr_in *) &radius_remote_sock; /* Set the members appropriately to send to the descriptor. */ memset((void *) &radius_remote_sock, '\0', sizeof(radius_remote_sock)); radius_sockaddr_in->sin_family = AF_INET; radius_sockaddr_in->sin_addr.s_addr = pr_netaddr_get_addrno(server->addr); radius_sockaddr_in->sin_port = htons(server->port); if (sendto(sockfd, (char *) packet, ntohs(packet->length), 0, &radius_remote_sock, sizeof(struct sockaddr_in)) < 0) { radius_log("error: unable to send packet: %s", strerror(errno)); return -1; } radius_log("sending packet to %s:%u", inet_ntoa(radius_sockaddr_in->sin_addr), ntohs(radius_sockaddr_in->sin_port)); return 0; } static unsigned char radius_start_accting(void) { int sockfd = -1, acct_status = 0, acct_authentic = 0; radius_packet_t *request = NULL, *response = NULL; radius_server_t *acct_server = NULL; unsigned char recvd_response = FALSE, *authenticated = NULL; /* Check to see if RADIUS accounting should be done. */ if (!radius_engine || !radius_acct_server) return TRUE; /* Only do accounting for authenticated users. */ authenticated = get_param_ptr(main_server->conf, "authenticated", FALSE); if (!authenticated || *authenticated == FALSE) return TRUE; /* Allocate a packet. */ request = (radius_packet_t *) pcalloc(radius_pool, sizeof(radius_packet_t)); /* Open a RADIUS socket */ sockfd = radius_open_socket(); if (sockfd < 0) { radius_log("socket open failed"); return FALSE; } /* Loop through the list of servers, trying each one until the packet is * successfully sent. */ acct_server = radius_acct_server; while (acct_server) { char pid[10] = {'\0'}; pr_signals_handle(); /* Clear the packet. */ memset(request, '\0', sizeof(radius_packet_t)); /* Build the packet. */ request->code = RADIUS_ACCT_REQUEST; radius_build_packet(request, radius_realm ? pstrcat(radius_pool, session.user, radius_realm, NULL) : session.user, NULL, acct_server->secret); radius_last_acct_pkt_id = request->id; /* Add accounting attributes. */ acct_status = htonl(RADIUS_ACCT_STATUS_START); radius_add_attrib(request, RADIUS_ACCT_STATUS_TYPE, (unsigned char *) &acct_status, sizeof(int)); snprintf(pid, sizeof(pid), "%08d", (int) getpid()); radius_add_attrib(request, RADIUS_ACCT_SESSION_ID, pid, strlen(pid)); acct_authentic = htonl(RADIUS_AUTH_LOCAL); radius_add_attrib(request, RADIUS_ACCT_AUTHENTIC, (unsigned char *) &acct_authentic, sizeof(int)); /* Calculate the signature. */ radius_get_acct_digest(request, acct_server->secret); /* Send the request. */ radius_log("sending start acct request packet"); if (radius_send_packet(sockfd, request, acct_server) < 0) { radius_log("packet send failed"); acct_server = acct_server->next; continue; } /* Receive the response. */ radius_log("receiving acct response packet"); if ((response = radius_recv_packet(sockfd, acct_server->timeout)) == NULL) { radius_log("packet receive failed"); acct_server = acct_server->next; continue; } radius_log("packet receive succeeded"); recvd_response = TRUE; break; } /* Close the socket. */ if (radius_close_socket(sockfd) < 0) radius_log("socket close failed"); if (recvd_response) { /* Verify the response. */ radius_log("verifying packet"); if (radius_verify_packet(request, response, acct_server->secret) < 0) return FALSE; /* Handle the response. */ switch (response->code) { case RADIUS_ACCT_RESPONSE: radius_log("accounting started for user '%s'", session.user); return TRUE; default: radius_log("notice: server returned unknown response code: %02x", response->code); return FALSE; } } else radius_log("error: no acct servers responded"); /* Default return value. */ return FALSE; } static unsigned char radius_stop_accting(void) { int sockfd = -1, acct_status = 0, acct_authentic = 0, now = 0; radius_packet_t *request = NULL, *response = NULL; radius_server_t *acct_server = NULL; unsigned char recvd_response = FALSE, *authenticated = NULL; /* Check to see if RADIUS accounting should be done. */ if (!radius_engine || !radius_acct_server) return TRUE; /* Only do accounting for authenticated users. */ authenticated = get_param_ptr(main_server->conf, "authenticated", FALSE); if (!authenticated || *authenticated == FALSE) return TRUE; /* Allocate a packet. */ request = (radius_packet_t *) pcalloc(radius_pool, sizeof(radius_packet_t)); /* Open a RADIUS socket */ sockfd = radius_open_socket(); if (sockfd < 0) { radius_log("socket open failed"); return FALSE; } /* Loop through the list of servers, trying each one until the packet is * successfully sent. */ acct_server = radius_acct_server; while (acct_server) { char pid[10] = {'\0'}; pr_signals_handle(); /* Clear the packet. */ memset(request, '\0', sizeof(radius_packet_t)); /* Build the packet. */ request->code = RADIUS_ACCT_REQUEST; radius_build_packet(request, radius_realm ? pstrcat(radius_pool, session.user, radius_realm, NULL) : session.user, NULL, acct_server->secret); /* Use the ID of the last accounting packet sent, plus one. Be sure * to handle the datatype overflow case. */ if ((request->id = radius_last_acct_pkt_id + 1) == 0) request->id = 1; /* Add accounting attributes. */ acct_status = htonl(RADIUS_ACCT_STATUS_STOP); radius_add_attrib(request, RADIUS_ACCT_STATUS_TYPE, (unsigned char *) &acct_status, sizeof(int)); snprintf(pid, sizeof(pid), "%08d", (int) getpid()); radius_add_attrib(request, RADIUS_ACCT_SESSION_ID, pid, strlen(pid)); acct_authentic = htonl(RADIUS_AUTH_LOCAL); radius_add_attrib(request, RADIUS_ACCT_AUTHENTIC, (unsigned char *) &acct_authentic, sizeof(int)); now = htonl(time(NULL) - radius_session_start); radius_add_attrib(request, RADIUS_ACCT_SESSION_TIME, (unsigned char *) &now, sizeof(int)); radius_session_bytes_in = htonl(radius_session_bytes_in); radius_add_attrib(request, RADIUS_ACCT_INPUT_OCTETS, (unsigned char *) &radius_session_bytes_in, sizeof(int)); radius_session_bytes_out = htonl(radius_session_bytes_out); radius_add_attrib(request, RADIUS_ACCT_OUTPUT_OCTETS, (unsigned char *) &radius_session_bytes_out, sizeof(int)); /* Calculate the signature. */ radius_get_acct_digest(request, acct_server->secret); /* Send the request. */ radius_log("sending stop acct request packet"); if (radius_send_packet(sockfd, request, acct_server) < 0) { radius_log("packet send failed"); return FALSE; } /* Receive the response. */ radius_log("receiving acct response packet"); if ((response = radius_recv_packet(sockfd, acct_server->timeout)) == NULL) { radius_log("packet receive failed"); return FALSE; } radius_log("packet receive succeeded"); recvd_response = TRUE; break; } /* Close the socket. */ if (radius_close_socket(sockfd) < 0) radius_log("socket close failed"); if (recvd_response) { /* Verify the response. */ radius_log("verifying packet"); if (radius_verify_packet(request, response, acct_server->secret) < 0) return FALSE; /* Handle the response. */ switch (response->code) { case RADIUS_ACCT_RESPONSE: radius_log("accounting ended for user '%s'", session.user); return TRUE; default: radius_log("notice: server returned unknown response code: %02x", response->code); return FALSE; } } else radius_log("error: no acct servers responded"); /* Default return value. */ return FALSE; } /* Verify the response packet from the server. */ static int radius_verify_packet(radius_packet_t *req_packet, radius_packet_t *resp_packet, char *secret) { MD5_CTX ctx; unsigned char calculated[RADIUS_VECTOR_LEN] = {'\0'}; unsigned char replied[RADIUS_VECTOR_LEN] = {'\0'}; /* sanity check */ if (!req_packet || !resp_packet || !secret) { errno = EINVAL; return -1; } /* NOTE: add checks for too-big, too-small packets, invalid packet->length * values, etc. */ /* Check that the packet IDs match. */ if (resp_packet->id != req_packet->id) { radius_log("packet verification failed: response packet ID %d does not " "match the request packet ID %d", resp_packet->id, req_packet->id); return -1; } /* Make sure the buffers are void of junk values. */ memset(calculated, '\0', sizeof(calculated)); memset(replied, '\0', sizeof(replied)); /* Save a copy of the response's digest. */ memcpy(replied, resp_packet->digest, RADIUS_VECTOR_LEN); /* Copy in the digest sent in the request. */ memcpy(resp_packet->digest, req_packet->digest, RADIUS_VECTOR_LEN); /* Re-calculate a digest from the given packet, and compare it against * the provided response digest: * MD5(response packet header + digest + response packet data + secret) */ MD5Init(&ctx); MD5Update(&ctx, (unsigned char *) resp_packet, ntohs(resp_packet->length)); if (*secret) MD5Update(&ctx, secret, strlen(secret)); /* Set the calculated digest. */ MD5Final(calculated, &ctx); /* Do the digests match properly? */ if (memcmp(calculated, replied, RADIUS_VECTOR_LEN) != 0) { radius_log("packet verification failed: mismatched digests"); return -1; } return 0; } /* Authentication handlers */ MODRET radius_auth(cmd_rec *cmd) { /* This authentication check has already been performed; I just need * to report the results of that check now. */ if (radius_auth_ok) { session.auth_mech = "mod_radius.c"; return HANDLED(cmd); } else if (radius_auth_reject) return ERROR_INT(cmd, PR_AUTH_BADPWD); /* Default return value. */ return DECLINED(cmd); } MODRET radius_check(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_name2uid(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_name2gid(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_uid2name(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_gid2name(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_endgrent(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_getgrnam(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_getgrent(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_getgrgid(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_getgroups(cmd_rec *cmd) { if (radius_have_group_info) { array_header *gids = NULL, *groups = NULL; register unsigned int i = 0; /* Return the faked group information. */ /* Don't forget to include the primary GID (with accompanying name!) * in the returned info -- if provided. Otherwise, well...the user * is out of luck. */ /* Check for NULL values */ if (cmd->argv[1]) { gids = (array_header *) cmd->argv[1]; if (radius_have_user_info) *((gid_t *) push_array(gids)) = radius_passwd.pw_gid; for (i = 0; i < radius_addl_group_count; i++) *((gid_t *) push_array(gids)) = radius_addl_group_ids[i]; } if (cmd->argv[2]) { groups = (array_header *) cmd->argv[2]; if (radius_have_user_info) *((char **) push_array(groups)) = radius_prime_group_name; for (i = 0; i < radius_addl_group_count; i++) *((char **) push_array(groups)) = radius_addl_group_names[i]; } /* Increment this count, for the sake of proper reporting back to the * getgroups() caller. */ if (radius_have_user_info) radius_addl_group_count++; return mod_create_data(cmd, (void *) &radius_addl_group_count); } return DECLINED(cmd); } MODRET radius_setgrent(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_endpwent(cmd_rec *cmd) { return DECLINED(cmd); } MODRET radius_getpwnam(cmd_rec *cmd) { if (radius_have_user_info) { if (!radius_passwd.pw_name) radius_passwd.pw_name = pstrdup(radius_pool, cmd->argv[0]); if (!strcmp(cmd->argv[0], radius_passwd.pw_name)) /* Return the faked user information. */ return mod_create_data(cmd, &radius_passwd); } /* Default response */ return DECLINED(cmd); } MODRET radius_getpwent(cmd_rec *cmd) { if (radius_have_user_info) /* Return the faked user information. */ return mod_create_data(cmd, &radius_passwd); /* Default response */ return DECLINED(cmd); } MODRET radius_getpwuid(cmd_rec *cmd) { if (radius_have_user_info) /* Check that given UID matches faked UID before returning. */ if (*((uid_t *) cmd->argv[0]) == radius_passwd.pw_uid) /* Return the faked user information. */ return mod_create_data(cmd, &radius_passwd); /* Default response */ return DECLINED(cmd); } MODRET radius_setpwent(cmd_rec *cmd) { return DECLINED(cmd); } /* Command handlers */ /* Perform the check with the RADIUS auth server(s) now, prior to the * actual handling of the PASS command by mod_auth, so that any of the * RadiusUserInfo parameters can be supplied by the RADIUS server. * * NOTE: first draft, does not honor UserAlias'd names (eg it uses the * username as supplied by the client. */ MODRET radius_pre_pass(cmd_rec *cmd) { int sockfd = -1; radius_packet_t *request = NULL, *response = NULL; radius_server_t *auth_server = NULL; unsigned char recvd_response = FALSE; unsigned long service; char *user; /* Check to see whether RADIUS authentication should even be done. */ if (!radius_engine || !radius_auth_server) return DECLINED(cmd); user = get_param_ptr(cmd->server->conf, C_USER, FALSE); if (!user) { radius_log("missing prerequisite USER command, declining to handle PASS"); pr_response_add_err(R_503, "Login with " C_USER " first"); return ERROR(cmd); } /* Allocate a packet. */ request = (radius_packet_t *) pcalloc(cmd->tmp_pool, sizeof(radius_packet_t)); /* Open a RADIUS socket */ sockfd = radius_open_socket(); if (sockfd < 0) { radius_log("socket open failed"); return DECLINED(cmd); } /* Clear the OK flag. */ radius_auth_ok = FALSE; /* If mod_radius expects to find VSAs in the returned packet, it needs * to send a service type of Login, otherwise, use the Authenticate-Only * service type. */ if (radius_have_user_info || radius_have_group_info || radius_have_other_info) service = htonl(RADIUS_SVC_LOGIN); else service = htonl(RADIUS_SVC_AUTHENTICATE_ONLY); /* Loop through the list of servers, trying each one until the packet is * successfully sent. */ auth_server = radius_auth_server; while (auth_server) { pr_signals_handle(); /* Clear the packet. */ memset(request, '\0', sizeof(radius_packet_t)); /* Build the packet. */ request->code = RADIUS_AUTH_REQUEST; radius_build_packet(request, radius_realm ? pstrcat(radius_pool, user, radius_realm, NULL) : user, cmd->arg, auth_server->secret); radius_add_attrib(request, RADIUS_SERVICE_TYPE, (unsigned char *) &service, sizeof(service)); /* Send the request. */ radius_log("sending auth request packet"); if (radius_send_packet(sockfd, request, auth_server) < 0) { radius_log("packet send failed"); auth_server = auth_server->next; continue; } /* Receive the response. */ radius_log("receiving auth response packet"); if ((response = radius_recv_packet(sockfd, auth_server->timeout)) == NULL) { radius_log("packet receive failed"); auth_server = auth_server->next; continue; } radius_log("packet receive succeeded"); recvd_response = TRUE; break; } /* Close the socket. */ if (radius_close_socket(sockfd) < 0) radius_log("socket close failed"); if (recvd_response) { /* Verify the response. */ radius_log("verifying packet"); if (radius_verify_packet(request, response, auth_server->secret) < 0) return DECLINED(cmd); /* Handle the response */ switch (response->code) { case RADIUS_AUTH_ACCEPT: radius_log("authentication successful for user '%s'", user); radius_session_authtype = htonl(RADIUS_AUTH_RADIUS); /* Process the packet for custom attributes */ radius_process_accpt_packet(response); radius_auth_ok = TRUE; break; case RADIUS_AUTH_REJECT: radius_log("authentication failed for user '%s'", user); radius_auth_ok = FALSE; radius_auth_reject = TRUE; break; case RADIUS_AUTH_CHALLENGE: /* Just log this case for now. */ radius_log("authentication challenged for user '%s'", user); break; default: radius_log("notice: server returned unknown response code: %02x", response->code); break; } } else radius_log("error: no auth servers responded"); return DECLINED(cmd); } MODRET radius_post_pass(cmd_rec *cmd) { /* Check to see if RADIUS accounting should be done. */ if (!radius_engine || !radius_acct_server) return DECLINED(cmd); /* Fill in the username in the faked user info, if need be. */ if (radius_have_user_info) radius_passwd.pw_name = session.user; if (!radius_start_accting()) radius_log("error: unable to start accounting"); return DECLINED(cmd); } MODRET radius_post_retr(cmd_rec *cmd) { radius_session_bytes_out += session.xfer.total_bytes; return DECLINED(cmd); } MODRET radius_post_stor(cmd_rec *cmd) { radius_session_bytes_in += session.xfer.total_bytes; return DECLINED(cmd); } /* Configuration handlers */ /* usage: RadiusAcctServer server[:port] shared-secret [timeout] */ MODRET set_radiusacctserver(cmd_rec *cmd) { config_rec *c = NULL; radius_server_t *radius_server = NULL; unsigned short server_port = 0; char *port = NULL; if (cmd->argc-1 < 2 || cmd->argc-1 > 3) CONF_ERROR(cmd, "missing parameters"); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* Check to see if there's a port specified in the server name */ if ((port = strchr(cmd->argv[1], ':')) != NULL) { /* Separate the server name from the port */ *(port++) = '\0'; if ((server_port = (unsigned short) atoi(port)) < 1024) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "port number must be greater " "than 1023", NULL)); } if (pr_netaddr_get_addr(cmd->tmp_pool, cmd->argv[1], NULL) == NULL) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to resolve server address: ", cmd->argv[1], NULL)); /* Allocate a RADIUS server rec and populate the members */ radius_server = radius_make_server(radius_pool); radius_server->addr = pr_netaddr_get_addr(radius_server->pool, cmd->argv[1], NULL); radius_server->port = (server_port ? server_port : RADIUS_ACCT_PORT); radius_server->secret = pstrdup(radius_server->pool, cmd->argv[2]); if (cmd->argc-1 == 3) if ((radius_server->timeout = atoi(cmd->argv[3])) < 0) CONF_ERROR(cmd, "timeout must be greater than or equal to zero"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(radius_server_t *)); *((radius_server_t **) c->argv[0]) = radius_server; return HANDLED(cmd); } /* usage: RadiusAuthServer server[:port] [timeout] */ MODRET set_radiusauthserver(cmd_rec *cmd) { config_rec *c = NULL; radius_server_t *radius_server = NULL; unsigned short server_port = 0; char *port = NULL; if (cmd->argc-1 < 2 || cmd->argc-1 > 3) CONF_ERROR(cmd, "missing parameters"); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* Check to see if there's a port specified in the server name */ if ((port = strchr(cmd->argv[1], ':')) != NULL) { /* Separate the server name from the port */ *(port++) = '\0'; if ((server_port = (unsigned short) atoi(port)) < 1024) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "port number must be greater " "than 1023", NULL)); } if (pr_netaddr_get_addr(cmd->tmp_pool, cmd->argv[1], NULL) == NULL) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable resolve server address: ", cmd->argv[1], NULL)); /* OK, allocate a RADIUS server rec and populate the members */ radius_server = radius_make_server(radius_pool); radius_server->addr = pr_netaddr_get_addr(radius_server->pool, cmd->argv[1], NULL); radius_server->port = (server_port ? server_port : RADIUS_AUTH_PORT); radius_server->secret = pstrdup(radius_server->pool, cmd->argv[2]); if (cmd->argc-1 == 3) if ((radius_server->timeout = atoi(cmd->argv[3])) < 0) CONF_ERROR(cmd, "timeout must be greater than or equal to zero"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(radius_server_t *)); *((radius_server_t **) c->argv[0]) = radius_server; return HANDLED(cmd); } /* usage: RadiusEngine on|off */ MODRET set_radiusengine(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if ((bool = get_boolean(cmd, 1)) == -1) CONF_ERROR(cmd, "expected Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = bool; return HANDLED(cmd); } /* usage: RadiusGroupInfo primary-name addl-names add-ids */ MODRET set_radiusgroupinfo(cmd_rec *cmd) { config_rec *c = NULL; unsigned char group_names_vsa = FALSE; unsigned char group_ids_vsa = FALSE; CHECK_ARGS(cmd, 3); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); group_names_vsa = radius_chk_var(cmd->argv[2]); group_ids_vsa = radius_chk_var(cmd->argv[3]); if ((group_names_vsa && !group_ids_vsa) || (!group_names_vsa && group_ids_vsa)) CONF_ERROR(cmd, "supplemental group names/IDs parameters must both either be VSA variables, or both not be variables"); /* There will be four parameters to this config_rec: * * primary-group-name, addl-group-count, addl-group-names, addl-group-ids */ c = add_config_param(cmd->argv[0], 4, NULL, NULL, NULL, NULL); c->argv[0] = pstrdup(c->pool, cmd->argv[1]); c->argv[1] = pcalloc(c->pool, sizeof(unsigned int)); if (group_names_vsa && group_ids_vsa) { /* As VSA variables, the group names and IDs won't be resolved until * session time, so just store the variable strings as is. */ *((unsigned int *) c->argv[1]) = 0; c->argv[2] = pstrdup(c->pool, cmd->argv[2]); c->argv[3] = pstrdup(c->pool, cmd->argv[3]); } else { unsigned int ngroups = 0, ngids = 0; char **groups = NULL; gid_t *gids = NULL; if (!radius_parse_groups_str(c->pool, cmd->argv[2], &groups, &ngroups)) CONF_ERROR(cmd, "badly formatted group names"); if (!radius_parse_gids_str(c->pool, cmd->argv[3], &gids, &ngids)) CONF_ERROR(cmd, "badly formatted group IDs"); if (ngroups != ngids) CONF_ERROR(cmd, "mismatched number of group names and IDs"); *((unsigned int *) c->argv[1]) = ngroups; c->argv[2] = (void *) groups; c->argv[3] = (void *) gids; } return HANDLED(cmd); } /* usage: RadiusLog file|"none" */ MODRET set_radiuslog(cmd_rec *cmd) { if (cmd->argc-1 != 1) CONF_ERROR(cmd, "wrong number of parameters"); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return HANDLED(cmd); } /* usage: RadiusRealm string */ MODRET set_radiusrealm(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return HANDLED(cmd); } /* usage: RadiusUserInfo uid gid home shell */ MODRET set_radiususerinfo(cmd_rec *cmd) { CHECK_ARGS(cmd, 4); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (!radius_chk_var(cmd->argv[1])) { char *endp = NULL; /* Make sure it's a number, at least. */ if (strtoul(cmd->argv[1], &endp, 10) < 0) CONF_ERROR(cmd, "negative UID not allowed"); if (endp && *endp) CONF_ERROR(cmd, "invalid UID parameter: not a number"); } if (!radius_chk_var(cmd->argv[2])) { char *endp = NULL; /* Make sure it's a number, at least. */ if (strtoul(cmd->argv[2], &endp, 10) < 0) CONF_ERROR(cmd, "negative GID not allowed"); if (endp && *endp) CONF_ERROR(cmd, "invalid GID parameter: not a number"); } if (!radius_chk_var(cmd->argv[3])) { /* Make sure the path is absolute, at least. */ if (*(cmd->argv[3]) != '/') CONF_ERROR(cmd, "home relative path not allowed"); } if (!radius_chk_var(cmd->argv[4])) { /* Make sure the path is absolute, at least. */ if (*(cmd->argv[4]) != '/') CONF_ERROR(cmd, "shell relative path not allowed"); } add_config_param_str(cmd->argv[0], 4, cmd->argv[1], cmd->argv[2], cmd->argv[3], cmd->argv[4]); return HANDLED(cmd); } /* usage: RadiusVendor name id */ MODRET set_radiusvendor(cmd_rec *cmd) { config_rec *c = NULL; long id = 0; char *tmp = NULL; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* Make sure that the given vendor ID number is valid. */ id = strtol(cmd->argv[2], &tmp, 10); if (tmp && *tmp) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": vendor id '", cmd->argv[2], "' is not a valid number", NULL)); if (id < 0) CONF_ERROR(cmd, "vendor id must be a positive number"); c = add_config_param(cmd->argv[0], 2, NULL, NULL); c->argv[0] = pstrdup(c->pool, cmd->argv[1]); c->argv[1] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[1]) = id; return HANDLED(cmd); } /* Event handlers */ static void radius_exit_ev(const void *event_data, void *user_data) { if (!radius_stop_accting()) radius_log("error: unable to stop accounting"); radius_closelog(); return; } #if defined(PR_SHARED_MODULE) static void radius_mod_unload_ev(const void *event_data, void *user_data) { if (strcmp("mod_radius.c", (const char *) event_data) == 0) { pr_event_unregister(&radius_module, NULL, NULL); if (radius_pool) { destroy_pool(radius_pool); radius_pool = NULL; } } } #endif /* PR_SHARED_MODULE */ static void radius_restart_ev(const void *event_data, void *user_data) { /* Re-allocate the pool used by this module. */ if (radius_pool) destroy_pool(radius_pool); radius_pool = make_sub_pool(permanent_pool); pr_pool_tag(radius_pool, MOD_RADIUS_VERSION); return; } /* Initialization routines */ static int radius_sess_init(void) { int res = 0; config_rec *c = NULL; radius_server_t **current_server = NULL; res = radius_openlog(); if (res < 0) { if (res == -1) pr_log_pri(PR_LOG_NOTICE, "notice: unable to open RadiusLog: %s", strerror(errno)); else if (res == PR_LOG_WRITABLE_DIR) pr_log_pri(PR_LOG_NOTICE, "notice: unable to open RadiusLog: " "parent directory is world writeable"); else if (res == PR_LOG_SYMLINK) pr_log_pri(PR_LOG_NOTICE, "notice: unable to open RadiusLog: " "cannot log to a symbolic link"); } /* Is RadiusEngine on? */ radius_engine = FALSE; if ((c = find_config(main_server->conf, CONF_PARAM, "RadiusEngine", FALSE)) != NULL) { if (*((int *) c->argv[0]) == TRUE) radius_engine = TRUE; } if (!radius_engine) { radius_log("RadiusEngine not enabled"); radius_closelog(); return 0; } /* Initialize session variables */ time(&radius_session_start); radius_session_bytes_in = 0; radius_session_bytes_out = 0; if ((c = find_config(main_server->conf, CONF_PARAM, "RadiusVendor", FALSE)) != NULL) { radius_vendor_name = c->argv[0]; radius_vendor_id = *((unsigned int *) c->argv[1]); radius_log("RadiusVendor '%s' (Vendor-Id %u) configured", radius_vendor_name, radius_vendor_id); } /* Find any configured RADIUS servers for this session */ c = find_config(main_server->conf, CONF_PARAM, "RadiusAcctServer", FALSE); /* Point to the start of the accounting server list. */ current_server = &radius_acct_server; while (c) { *current_server = *((radius_server_t **) c->argv[0]); current_server = &(*current_server)->next; c = find_config_next(c, c->next, CONF_PARAM, "RadiusAcctServer", FALSE); } if (!radius_acct_server) radius_log("notice: no configured RadiusAcctServers, no accounting"); c = find_config(main_server->conf, CONF_PARAM, "RadiusAuthServer", FALSE); /* Point to the start of the authentication server list. */ current_server = &radius_auth_server; while (c) { *current_server = *((radius_server_t **) c->argv[0]); current_server = &(*current_server)->next; c = find_config_next(c, c->next, CONF_PARAM, "RadiusAuthServer", FALSE); } if (!radius_auth_server) radius_log("notice: no configured RadiusAuthServers, no authentication"); /* Prepare any configured fake user information. */ if ((c = find_config(main_server->conf, CONF_PARAM, "RadiusUserInfo", FALSE)) != NULL) { /* Process the parameter string stored in the found config_rec. */ radius_process_user_info(c); /* Only use the faked information if authentication via RADIUS is * possible. The radius_have_user_info flag will be set to * TRUE by radius_process_user_info(), unless there was some * illegal value. */ if (!radius_auth_server) radius_have_user_info = FALSE; } /* Prepare any configured fake group information. */ if ((c = find_config(main_server->conf, CONF_PARAM, "RadiusGroupInfo", FALSE)) != NULL) { /* Process the parameter string stored in the found config_rec. */ radius_process_group_info(c); /* Only use the faked information if authentication via RADIUS is * possible. The radius_have_group_info flag will be set to * TRUE by radius_process_group_info(), unless there was some * illegal value. */ if (!radius_auth_server) radius_have_group_info = FALSE; } /* Check for a configured RadiusRealm. If present, use username + realm * in RADIUS packets as the user name, else just use the username. */ if ((radius_realm = get_param_ptr(main_server->conf, "RadiusRealm", FALSE)) != NULL) radius_log("using RadiusRealm '%s'", radius_realm); pr_event_register(&radius_module, "core.exit", radius_exit_ev, NULL); return 0; } static int radius_init(void) { /* Allocate a pool for this module's use. */ radius_pool = make_sub_pool(permanent_pool); pr_pool_tag(radius_pool, MOD_RADIUS_VERSION); #if defined(PR_SHARED_MODULE) pr_event_register(&radius_module, "core.module-unload", radius_mod_unload_ev, NULL); #endif /* PR_SHARED_MODULE */ /* Register a restart handler, to cleanup the pool. */ pr_event_register(&radius_module, "core.restart", radius_restart_ev, NULL); return 0; } /* Module API tables */ static conftable radius_conftab[] = { { "RadiusAcctServer", set_radiusacctserver, NULL }, { "RadiusAuthServer", set_radiusauthserver, NULL }, { "RadiusEngine", set_radiusengine, NULL }, { "RadiusGroupInfo", set_radiusgroupinfo, NULL }, { "RadiusLog", set_radiuslog, NULL }, { "RadiusRealm", set_radiusrealm, NULL }, { "RadiusUserInfo", set_radiususerinfo, NULL }, { "RadiusVendor", set_radiusvendor, NULL }, { NULL } }; static cmdtable radius_cmdtab[] = { { POST_CMD, C_APPE, G_NONE, radius_post_stor, FALSE, FALSE }, { POST_CMD_ERR, C_APPE, G_NONE, radius_post_stor, FALSE, FALSE }, { PRE_CMD, C_PASS, G_NONE, radius_pre_pass, FALSE, FALSE, CL_AUTH }, { POST_CMD, C_PASS, G_NONE, radius_post_pass, FALSE, FALSE, CL_AUTH }, { POST_CMD, C_RETR, G_NONE, radius_post_retr, FALSE, FALSE }, { POST_CMD_ERR, C_RETR, G_NONE, radius_post_retr, FALSE, FALSE }, { POST_CMD, C_STOR, G_NONE, radius_post_stor, FALSE, FALSE }, { POST_CMD_ERR, C_STOR, G_NONE, radius_post_stor, FALSE, FALSE }, { POST_CMD, C_STOU, G_NONE, radius_post_stor, FALSE, FALSE }, { POST_CMD_ERR, C_STOU, G_NONE, radius_post_stor, FALSE, FALSE }, { 0, NULL } }; static authtable radius_authtab[] = { { 0, "setpwent", radius_setpwent }, { 0, "setgrent", radius_setgrent }, { 0, "endpwent", radius_endpwent }, { 0, "endgrent", radius_endgrent }, { 0, "getpwent", radius_getpwent }, { 0, "getgrent", radius_getgrent }, { 0, "getpwnam", radius_getpwnam }, { 0, "getgrnam", radius_getgrnam }, { 0, "getpwuid", radius_getpwuid }, { 0, "getgrgid", radius_getgrgid }, { 0, "getgroups", radius_getgroups }, { 0, "auth", radius_auth }, { 0, "check", radius_check }, { 0, "uid2name", radius_uid2name }, { 0, "gid2name", radius_gid2name }, { 0, "name2uid", radius_name2uid }, { 0, "name2gid", radius_name2gid }, { 0, NULL } }; module radius_module = { /* Always NULL */ NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "radius", /* Module configuration handler table */ radius_conftab, /* Module command handler table */ radius_cmdtab, /* Module authentication handler table */ radius_authtab, /* Module initialization function */ radius_init, /* Module session initialization function */ radius_sess_init, /* Module version */ MOD_RADIUS_VERSION };