/* * This is a wraper for radius_axlat * Now users are able to get data that is accessible only via xlat * e.g. %{client:...} * Call syntax is radiusd::xlat(string), string will be handled the * same way it is described in EXPANSIONS section of man unlang */ static XS(XS_radiusd_xlat) { dXSARGS; char *in_str; char *expanded; ssize_t slen; SV *rad_requestp_sv; REQUEST *request; if (items != 1) croak("Usage: radiusd::xlat(string)"); rad_requestp_sv = get_sv("RAD___REQUESTP", 0); if (rad_requestp_sv == NULL) croak("Can not evalue xlat, RAD___REQUESTP is not set!"); request = INT2PTR(REQUEST *, SvIV(rad_requestp_sv)); in_str = (char *) SvPV(ST(0), PL_na); expanded = NULL; slen = radius_axlat(&expanded, request, in_str, NULL, NULL); if (slen < 0) { REDEBUG("Error parsing xlat '%s'", in_str); XSRETURN_UNDEF; } XST_mPV(0, expanded); talloc_free(expanded); XSRETURN(1); }
/* * Set the SQL user name. * * We don't call the escape function here. The resulting string * will be escaped later in the queries xlat so we don't need to * escape it twice. (it will make things wrong if we have an * escape candidate character in the username) */ int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username) { char *expanded = NULL; VALUE_PAIR *vp = NULL; char const *sqluser; ssize_t len; if (username != NULL) { sqluser = username; } else if (inst->config->query_user[0] != '\0') { sqluser = inst->config->query_user; } else { return 0; } len = radius_axlat(&expanded, request, sqluser, NULL, NULL); if (len < 0) { return -1; } vp = pairalloc(request->packet, inst->sql_user); if (!vp) { talloc_free(expanded); return -1; } pairstrsteal(vp, expanded); RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue); vp->op = T_OP_SET; pairmove(request, &request->packet->vps, &vp); /* needs to be pair move else op is not respected */ return 0; }
/** Perform a single sqlippool query * * Mostly wrapper around sql_query which does some special sqlippool sequence substitutions and expands * the format string. * * @param fmt sql query to expand. * @param handle sql connection handle. * @param data Instance of rlm_sqlippool. * @param request Current request. * @param param ip address string. * @param param_len ip address string len. * @return 0 on success or < 0 on error. */ static int sqlippool_command(char const *fmt, rlm_sql_handle_t **handle, rlm_sqlippool_t *data, REQUEST *request, char *param, int param_len) { char query[MAX_QUERY_LEN]; char *expanded = NULL; int ret; /* * If we don't have a command, do nothing. */ if (!fmt || !*fmt) return 0; /* * @todo this needs to die (should just be done in xlat expansion) */ sqlippool_expand(query, sizeof(query), fmt, data, param, param_len); if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) return -1; ret = data->sql_inst->sql_query(data->sql_inst, request, handle, expanded); if (ret < 0){ talloc_free(expanded); return -1; } talloc_free(expanded); if (*handle) (data->sql_inst->module->sql_finish_query)(*handle, data->sql_inst->config); return 0; }
/* * Query the database expecting a single result row */ static int CC_HINT(nonnull (1, 3, 4, 5)) sqlippool_query1(char *out, int outlen, char const *fmt, rlm_sql_handle_t *handle, rlm_sqlippool_t *data, REQUEST *request, char *param, int param_len) { char query[MAX_QUERY_LEN]; char *expanded = NULL; int rlen, retval; /* * @todo this needs to die (should just be done in xlat expansion) */ sqlippool_expand(query, sizeof(query), fmt, data, param, param_len); *out = '\0'; /* * Do an xlat on the provided string */ if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) { return 0; } retval = data->sql_inst->sql_select_query(data->sql_inst, request, &handle, expanded); talloc_free(expanded); if (retval != 0){ REDEBUG("database query error on '%s'", query); return 0; } if (data->sql_inst->sql_fetch_row(data->sql_inst, request, &handle) < 0) { REDEBUG("Failed fetching query result"); goto finish; } if (!handle->row) { REDEBUG("SQL query did not return any results"); goto finish; } if (!handle->row[0]) { REDEBUG("The first column of the result was NULL"); goto finish; } rlen = strlen(handle->row[0]); if (rlen >= outlen) { RDEBUG("insufficient string space"); goto finish; } strcpy(out, handle->row[0]); retval = rlen; finish: (data->sql_inst->module->sql_finish_select_query)(handle, data->sql_inst->config); return retval; }
static char *radius_expand_tmpl(REQUEST *request, value_pair_tmpl_t const *vpt) { char *buffer = NULL; VALUE_PAIR *vp; rad_assert(vpt->type != VPT_TYPE_LIST); switch (vpt->type) { case VPT_TYPE_LITERAL: EVAL_DEBUG("TMPL LITERAL"); buffer = talloc_strdup(request, vpt->name); break; case VPT_TYPE_EXEC: EVAL_DEBUG("TMPL EXEC"); buffer = talloc_array(request, char, 1024); if (radius_exec_program(vpt->name, request, 1, buffer, 1024, NULL, NULL, 0) != 0) { talloc_free(buffer); return NULL; } break; case VPT_TYPE_REGEX: EVAL_DEBUG("TMPL REGEX"); if (strchr(vpt->name, '%') == NULL) { buffer = talloc_strdup(request, vpt->name); break; } /* FALL-THROUGH */ case VPT_TYPE_XLAT: EVAL_DEBUG("TMPL XLAT"); buffer = NULL; if (radius_axlat(&buffer, request, vpt->name, NULL, NULL) == 0) { return NULL; } break; case VPT_TYPE_ATTR: EVAL_DEBUG("TMPL ATTR"); vp = radius_vpt_get_vp(request, vpt); if (!vp) return NULL; buffer = vp_aprint(request, vp); break; case VPT_TYPE_DATA: rad_assert(0 == 1); /* FALL-THROUGH */ default: buffer = NULL; break; } EVAL_DEBUG("Expand tmpl --> %s", buffer); return buffer; }
/* * Query the database expecting a single result row */ static int sqlippool_query1(char *out, int outlen, char const *fmt, rlm_sql_handle_t *handle, rlm_sqlippool_t *data, REQUEST *request, char *param, int param_len) { char query[MAX_QUERY_LEN]; char *expanded = NULL; int rlen, retval; /* * @todo this needs to die (should just be done in xlat expansion) */ sqlippool_expand(query, sizeof(query), fmt, data, param, param_len); rad_assert(request != NULL); *out = '\0'; /* * Do an xlat on the provided string */ if (radius_axlat(&expanded, request, query, data->sql_inst->sql_escape_func, data->sql_inst) < 0) { return 0; } retval = data->sql_inst->sql_select_query(&handle, data->sql_inst, expanded); talloc_free(expanded); if (retval != 0) { REDEBUG("database query error on '%s'", query); return 0; } if (!data->sql_inst->sql_fetch_row(&handle, data->sql_inst)) { if (handle->row) { if (handle->row[0]) { if ((rlen = strlen(handle->row[0])) < outlen) { strcpy(out, handle->row[0]); retval = rlen; } else { RDEBUG("insufficient string space"); } } else { RDEBUG("row[0] returned NULL"); } } else { RDEBUG("SQL query did not return any results"); } } else { RDEBUG("SQL query did not succeed"); } (data->sql_inst->module->sql_finish_select_query)(handle, data->sql_inst->config); return retval; }
/* * See if the counter matches. */ static int sqlcounter_cmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *req , VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) { rlm_sqlcounter_t *inst = instance; uint64_t counter; char *p; char query[MAX_QUERY_LEN]; char *expanded = NULL; size_t len; len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, query); if (len >= sizeof(query) - 1) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p = query + len; /* first, expand %k, %b and %e in query */ len = sqlcounter_expand(p, p - query, inst->query, inst); if (len <= 0) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p += len; if ((p - query) < 2) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p[0] = '}'; p[1] = '\0'; /* Finally, xlat resulting SQL query */ if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } counter = strtoull(expanded, NULL, 10); talloc_free(expanded); if (counter < check->vp_integer64) { return -1; } if (counter > check->vp_integer64) { return 1; } return 0; }
static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request, rlm_sql_grouplist_t **phead) { char *expanded = NULL; int num_groups = 0; rlm_sql_row_t row; rlm_sql_grouplist_t *entry; int ret; /* NOTE: sql_set_user should have been run before calling this function */ entry = *phead = NULL; if (!inst->config->groupmemb_query || (inst->config->groupmemb_query[0] == 0)) { return 0; } if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) { return -1; } ret = rlm_sql_select_query(handle, inst, expanded); talloc_free(expanded); if (ret != RLM_SQL_OK) { return -1; } while (rlm_sql_fetch_row(handle, inst) == 0) { row = (*handle)->row; if (!row) break; if (!row[0]){ RDEBUG("row[0] returned NULL"); (inst->module->sql_finish_select_query)(*handle, inst->config); talloc_free(entry); return -1; } if (!*phead) { *phead = talloc_zero(*handle, rlm_sql_grouplist_t); entry = *phead; } else { entry->next = talloc_zero(*phead, rlm_sql_grouplist_t); entry = entry->next; } entry->next = NULL; entry->name = talloc_typed_strdup(entry, row[0]); num_groups++; } (inst->module->sql_finish_select_query)(*handle, inst->config); return num_groups; }
/* * If we have something to log, then we log it. * Otherwise we return the retcode as soon as possible */ static int do_logging(REQUEST *request, char const *str, int rcode) { char *expanded = NULL; if (!str || !*str) return rcode; if (radius_axlat(&expanded, request, str, NULL, NULL) < 0) { return rcode; } pair_make_config("Module-Success-Message", expanded, T_OP_SET); talloc_free(expanded); return rcode; }
/* * Log the query to a file. */ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section, char const *query) { int fd; char const *filename = NULL; char *expanded = NULL; size_t len; bool failed = false; /* Write the log message outside of the critical region */ if (section) { filename = section->logfile; } else { filename = inst->config->logfile; } if (!filename) { return; } if (radius_axlat(&expanded, request, filename, NULL, NULL) < 0) { return; } fd = exfile_open(inst->ef, filename, 0640, true); if (fd < 0) { ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); talloc_free(expanded); return; } len = strlen(query); if ((write(fd, query, len) < 0) || (write(fd, ";\n", 2) < 0)) { failed = true; } if (failed) { ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); } talloc_free(expanded); exfile_close(inst->ef, fd); }
/* * See if the counter matches. */ static int sqlcounter_cmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *req , VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) { rlm_sqlcounter_t *inst = instance; uint64_t counter; char query[MAX_QUERY_LEN], subst[MAX_QUERY_LEN]; char *expanded = NULL; size_t len; /* First, expand %k, %b and %e in query */ if (sqlcounter_expand(subst, sizeof(subst), inst->query, inst) <= 0) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Then combine that with the name of the module were using to do the query */ len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, subst); if (len >= sizeof(query) - 1) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Finally, xlat resulting SQL query */ if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } if (sscanf(expanded, "%" PRIu64, &counter) != 1) { RDEBUG2("No integer found in string \"%s\"", expanded); } talloc_free(expanded); if (counter < check->vp_integer64) { return -1; } if (counter > check->vp_integer64) { return 1; } return 0; }
/* * Log the query to a file. */ void rlm_sql_query_log(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section, char const *query) { int fd; char const *filename = NULL; char *expanded = NULL; if (section) { filename = section->logfile; } else { filename = inst->config->logfile; } if (!filename) { return; } if (radius_axlat(&expanded, request, filename, NULL, NULL) < 0) { return; } fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd < 0) { ERROR("rlm_sql (%s): Couldn't open logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); talloc_free(expanded); return; } if ((rad_lockfd(fd, MAX_QUERY_LEN) < 0) || (write(fd, query, strlen(query)) < 0) || (write(fd, ";\n", 2) < 0)) { ERROR("rlm_sql (%s): Failed writing to logfile '%s': %s", inst->config->xlat_name, expanded, fr_syserror(errno)); } talloc_free(expanded); close(fd); /* and release the lock */ }
/** Expand the RHS of a template * * @note Length of expanded string can be found with talloc_array_length(*out) - 1 * * @param out where to write a pointer to the newly allocated buffer. * @param request Current request. * @param vpt to evaluate. * @return -1 on error, else 0. */ static int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt) { VALUE_PAIR *vp; *out = NULL; rad_assert(vpt->type != VPT_TYPE_LIST); switch (vpt->type) { case VPT_TYPE_LITERAL: EVAL_DEBUG("TMPL LITERAL"); *out = talloc_strdup(request, vpt->name); break; case VPT_TYPE_EXEC: EVAL_DEBUG("TMPL EXEC"); *out = talloc_array(request, char, 1024); if (radius_exec_program(request, vpt->name, true, false, *out, 1024, EXEC_TIMEOUT, NULL, NULL) != 0) { TALLOC_FREE(*out); return -1; } break; case VPT_TYPE_REGEX: EVAL_DEBUG("TMPL REGEX"); if (strchr(vpt->name, '%') == NULL) { *out = talloc_strdup(request, vpt->name); break; } /* FALL-THROUGH */ case VPT_TYPE_XLAT: EVAL_DEBUG("TMPL XLAT"); /* Error in expansion, this is distinct from zero length expansion */ if (radius_axlat(out, request, vpt->name, NULL, NULL) < 0) { rad_assert(!*out); return -1; } break; case VPT_TYPE_ATTR: EVAL_DEBUG("TMPL ATTR"); if (radius_vpt_get_vp(&vp, request, vpt) < 0) { return -2; } *out = vp_aprint(request, vp); if (!*out) { return -1; } break; case VPT_TYPE_DATA: rad_assert(0 == 1); /* FALL-THROUGH */ default: break; } EVAL_DEBUG("Expand tmpl --> %s", *out); return 0; }
/* * See if a user is already logged in. Sets request->simul_count to the * current session count for this user and sets request->simul_mpp to 2 * if it looks like a multilink attempt based on the requested IP * address, otherwise leaves request->simul_mpp alone. * * Check twice. If on the first pass the user exceeds his * max. number of logins, do a second pass and validate all * logins by querying the terminal server (using eg. SNMP). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; struct radutmp u; int fd = -1; VALUE_PAIR *vp; uint32_t ipno = 0; char const *call_num = NULL; rlm_radutmp_t *inst = instance; char *expanded = NULL; ssize_t len; /* * Get the filename, via xlat. */ if (radius_axlat(&expanded, request, inst->filename, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } fd = open(expanded, O_RDWR); if (fd < 0) { /* * If the file doesn't exist, then no users * are logged in. */ if (errno == ENOENT) { request->simul_count=0; return RLM_MODULE_OK; } /* * Error accessing the file. */ ERROR("rlm_radumtp: Error accessing file %s: %s", expanded, strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } TALLOC_FREE(expanded); len = radius_axlat(&expanded, request, inst->username, NULL, NULL); if (len < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!len) { rcode = RLM_MODULE_NOOP; goto finish; } /* * WTF? This is probably wrong... we probably want to * be able to check users across multiple session accounting * methods. */ request->simul_count = 0; /* * Loop over utmp, counting how many people MAY be logged in. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { ++request->simul_count; } } /* * The number of users logged in is OK, * OR, we've been told to not check the NAS. */ if ((request->simul_count < request->simul_max) || !inst->check_nas) { rcode = RLM_MODULE_OK; goto finish; } lseek(fd, (off_t)0, SEEK_SET); /* * Setup some stuff, like for MPP detection. */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { ipno = vp->vp_ipaddr; } if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { call_num = vp->vp_strvalue; } /* * lock the file while reading/writing. */ rad_lockfd(fd, LOCK_LEN); /* * FIXME: If we get a 'Start' for a user/nas/port which is * listed, but for which we did NOT get a 'Stop', then * it's not a duplicate session. This happens with * static IP's like DSL. */ request->simul_count = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(expanded, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(expanded, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { char session_id[sizeof(u.session_id) + 1]; char utmp_login[sizeof(u.login) + 1]; strlcpy(session_id, u.session_id, sizeof(session_id)); /* * The login name MAY fill the whole field, * and thus won't be zero-filled. * * Note that we take the user name from * the utmp file, as that's the canonical * form. The 'login' variable may contain * a string which is an upper/lowercase * version of u.login. When we call the * routine to check the terminal server, * the NAS may be case sensitive. * * e.g. We ask if "bob" is using a port, * and the NAS says "no", because "BOB" * is using the port. */ memset(utmp_login, 0, sizeof(utmp_login)); memcpy(utmp_login, u.login, sizeof(u.login)); /* * rad_check_ts may take seconds * to return, and we don't want * to block everyone else while * that's happening. */ rad_unlockfd(fd, LOCK_LEN); rcode = rad_check_ts(u.nas_address, u.nas_port, utmp_login, session_id); rad_lockfd(fd, LOCK_LEN); if (rcode == 0) { /* * Stale record - zap it. */ session_zap(request, u.nas_address, u.nas_port, expanded, session_id, u.framed_address, u.proto, 0); } else if (rcode == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (strchr("SCPA", u.proto) && ipno && u.framed_address == ipno) { request->simul_mpp = 2; } else if (strchr("SCPA", u.proto) && call_num && !strncmp(u.caller_id, call_num,16)) { request->simul_mpp = 2; } } else { RWDEBUG("Failed to check the terminal server for user '%s'.", utmp_login); rcode = RLM_MODULE_FAIL; goto finish; } } } finish: talloc_free(expanded); if (fd > -1) { close(fd); /* and implicitely release the locks */ } return rcode; }
/* * Store logins in the RADIUS utmp file. */ static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; struct radutmp ut, u; vp_cursor_t cursor; VALUE_PAIR *vp; int status = -1; int protocol = -1; time_t t; int fd = -1; int port_seen = 0; int off; rlm_radutmp_t *inst = instance; char ip_name[32]; /* 255.255.255.255 */ char const *nas; NAS_PORT *cache; int r; char *filename = NULL; char *expanded = NULL; if (request->packet->src_ipaddr.af != AF_INET) { DEBUG("rlm_radutmp: IPv6 not supported!"); return RLM_MODULE_NOOP; } /* * Which type is this. */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { RDEBUG("No Accounting-Status-Type record."); return RLM_MODULE_NOOP; } status = vp->vp_integer; /* * Look for weird reboot packets. * * ComOS (up to and including 3.5.1b20) does not send * standard PW_STATUS_ACCOUNTING_XXX messages. * * Check for: o no Acct-Session-Time, or time of 0 * o Acct-Session-Id of "00000000". * * We could also check for NAS-Port, that attribute * should NOT be present (but we don't right now). */ if ((status != PW_STATUS_ACCOUNTING_ON) && (status != PW_STATUS_ACCOUNTING_OFF)) do { int check1 = 0; int check2 = 0; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) == NULL || vp->vp_date == 0) check1 = 1; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY)) != NULL && vp->length == 8 && memcmp(vp->vp_strvalue, "00000000", 8) == 0) check2 = 1; if (check1 == 0 || check2 == 0) { break; } INFO("rlm_radutmp: converting reboot records."); if (status == PW_STATUS_STOP) status = PW_STATUS_ACCOUNTING_OFF; if (status == PW_STATUS_START) status = PW_STATUS_ACCOUNTING_ON; } while(0); time(&t); memset(&ut, 0, sizeof(ut)); ut.porttype = 'A'; ut.nas_address = htonl(INADDR_NONE); /* * First, find the interesting attributes. */ for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor)) { if (!vp->da->vendor) switch (vp->da->attr) { case PW_LOGIN_IP_HOST: case PW_FRAMED_IP_ADDRESS: ut.framed_address = vp->vp_ipaddr; break; case PW_FRAMED_PROTOCOL: protocol = vp->vp_integer; break; case PW_NAS_IP_ADDRESS: ut.nas_address = vp->vp_ipaddr; break; case PW_NAS_PORT: ut.nas_port = vp->vp_integer; port_seen = 1; break; case PW_ACCT_DELAY_TIME: ut.delay = vp->vp_integer; break; case PW_ACCT_SESSION_ID: /* * If length > 8, only store the * last 8 bytes. */ off = vp->length - sizeof(ut.session_id); /* * Ascend is br0ken - it adds a \0 * to the end of any string. * Compensate. */ if (vp->length > 0 && vp->vp_strvalue[vp->length - 1] == 0) off--; if (off < 0) off = 0; memcpy(ut.session_id, vp->vp_strvalue + off, sizeof(ut.session_id)); break; case PW_NAS_PORT_TYPE: if (vp->vp_integer <= 4) ut.porttype = porttypes[vp->vp_integer]; break; case PW_CALLING_STATION_ID: if(inst->caller_id_ok) strlcpy(ut.caller_id, vp->vp_strvalue, sizeof(ut.caller_id)); break; } } /* * If we didn't find out the NAS address, use the * originator's IP address. */ if (ut.nas_address == htonl(INADDR_NONE)) { ut.nas_address = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr; nas = request->client->shortname; } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == ut.nas_address) { /* might be a client, might not be. */ nas = request->client->shortname; } else { /* * The NAS isn't a client, it's behind * a proxy server. In that case, just * get the IP address. */ nas = ip_ntoa(ip_name, ut.nas_address); } /* * Set the protocol field. */ if (protocol == PW_PPP) { ut.proto = 'P'; } else if (protocol == PW_SLIP) { ut.proto = 'S'; } else { ut.proto = 'T'; } ut.time = t - ut.delay; /* * Get the utmp filename, via xlat. */ filename = NULL; if (radius_axlat(&filename, request, inst->filename, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } /* * See if this was a reboot. * * Hmm... we may not want to zap all of the users when the NAS comes up, because of issues with receiving * UDP packets out of order. */ if (status == PW_STATUS_ACCOUNTING_ON && (ut.nas_address != htonl(INADDR_NONE))) { RIDEBUG("NAS %s restarted (Accounting-On packet seen)", nas); rcode = radutmp_zap(request, filename, ut.nas_address, ut.time); goto finish; } if (status == PW_STATUS_ACCOUNTING_OFF && (ut.nas_address != htonl(INADDR_NONE))) { RIDEBUG("NAS %s rebooted (Accounting-Off packet seen)", nas); rcode = radutmp_zap(request, filename, ut.nas_address, ut.time); goto finish; } /* * If we don't know this type of entry pretend we succeeded. */ if (status != PW_STATUS_START && status != PW_STATUS_STOP && status != PW_STATUS_ALIVE) { REDEBUG("NAS %s port %u unknown packet type %d)", nas, ut.nas_port, status); rcode = RLM_MODULE_NOOP; goto finish; } /* * Translate the User-Name attribute, or whatever else they told us to use. */ if (radius_axlat(&expanded, request, inst->username, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } strlcpy(ut.login, expanded, RUT_NAMESIZE); TALLOC_FREE(expanded); /* * Perhaps we don't want to store this record into * radutmp. We skip records: * * - without a NAS-Port (telnet / tcp access) * - with the username "!root" (console admin login) */ if (!port_seen) { RWDEBUG2("No NAS-Port seen. Cannot do anything. Checkrad will probably not work!"); rcode = RLM_MODULE_NOOP; goto finish; } if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) { RDEBUG2("Not recording administrative user"); rcode = RLM_MODULE_NOOP; goto finish; } /* * Enter into the radutmp file. */ fd = open(filename, O_RDWR|O_CREAT, inst->permission); if (fd < 0) { REDEBUG("Error accessing file %s: %s", filename, strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * Find the entry for this NAS / portno combination. */ if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address, ut.nas_port)) != NULL) { lseek(fd, (off_t)cache->offset, SEEK_SET); } r = 0; off = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { off += sizeof(u); if ((u.nas_address != ut.nas_address) || (u.nas_port != ut.nas_port)) { continue; } /* * Don't compare stop records to unused entries. */ if (status == PW_STATUS_STOP && u.type == P_IDLE) { continue; } if ((status == PW_STATUS_STOP) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) != 0) { /* * Don't complain if this is not a * login record (some clients can * send _only_ logout records). */ if (u.type == P_LOGIN) { RWDEBUG("Logout entry for NAS %s port %u has wrong ID", nas, u.nas_port); } r = -1; break; } if ((status == PW_STATUS_START) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.time >= ut.time) { if (u.type == P_LOGIN) { INFO("rlm_radutmp: Login entry for NAS %s port %u duplicate", nas, u.nas_port); r = -1; break; } RWDEBUG("Login entry for NAS %s port %u wrong order", nas, u.nas_port); r = -1; break; } /* * FIXME: the ALIVE record could need some more checking, but anyway I'd * rather rewrite this mess -- miquels. */ if ((status == PW_STATUS_ALIVE) && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.type == P_LOGIN) { /* * Keep the original login time. */ ut.time = u.time; } if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { RWDEBUG("negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); off = 0; } else { off -= sizeof(u); } r = 1; break; } /* read the file until we find a match */ /* * Found the entry, do start/update it with * the information from the packet. */ if ((r >= 0) && (status == PW_STATUS_START || status == PW_STATUS_ALIVE)) { /* * Remember where the entry was, because it's * easier than searching through the entire file. */ if (!cache) { cache = talloc_zero(inst, NAS_PORT); if (cache) { cache->nasaddr = ut.nas_address; cache->port = ut.nas_port; cache->offset = off; cache->next = inst->nas_port_list; inst->nas_port_list = cache; } } ut.type = P_LOGIN; if (write(fd, &ut, sizeof(u)) < 0) { REDEBUG("Failed writing: %s", strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } } /* * The user has logged off, delete the entry by * re-writing it in place. */ if (status == PW_STATUS_STOP) { if (r > 0) { u.type = P_IDLE; u.time = ut.time; u.delay = ut.delay; if (write(fd, &u, sizeof(u)) < 0) { REDEBUG("Failed writing: %s", strerror(errno)); rcode = RLM_MODULE_FAIL; goto finish; } } else if (r == 0) { RWDEBUG("Logout for NAS %s port %u, but no Login record", nas, ut.nas_port); } } finish: talloc_free(filename); if (fd > -1) { close(fd); /* and implicitely release the locks */ } return rcode; }
/* * Generic function for failing between a bunch of queries. * * Uses the same principle as rlm_linelog, expanding the 'reference' config * item using xlat to figure out what query it should execute. * * If the reference matches multiple config items, and a query fails or * doesn't update any rows, the next matching config item is used. * */ static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; int sql_ret; int numaffected = 0; CONF_ITEM *item; CONF_PAIR *pair; char const *attr = NULL; char const *value; char path[MAX_STRING_LEN]; char *p = path; char *expanded = NULL; rad_assert(section); if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } /* * If we can't find a matching config item we do * nothing so return RLM_MODULE_NOOP. */ item = cf_reference_item(NULL, section->cs, path); if (!item) { RWDEBUG("No such configuration item %s", path); rcode = RLM_MODULE_NOOP; goto finish; } if (cf_item_is_section(item)){ RWDEBUG("Sections are not supported as references"); rcode = RLM_MODULE_NOOP; goto finish; } pair = cf_item_to_pair(item); attr = cf_pair_attr(pair); RDEBUG2("Using query template '%s'", attr); handle = fr_connection_get(inst->pool); if (!handle) { rcode = RLM_MODULE_FAIL; goto finish; } sql_set_user(inst, request, NULL); while (true) { value = cf_pair_value(pair); if (!value) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; goto finish; } if (radius_axlat(&expanded, request, value, inst->sql_escape_func, handle) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!*expanded) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; talloc_free(expanded); goto finish; } rlm_sql_query_log(inst, request, section, expanded); sql_ret = rlm_sql_query(inst, request, &handle, expanded); TALLOC_FREE(expanded); RDEBUG("SQL query returned: %s", fr_int2str(sql_rcode_table, sql_ret, "<INVALID>")); switch (sql_ret) { /* * Query was a success! Now we just need to check if it did anything. */ case RLM_SQL_OK: break; /* * A general, unrecoverable server fault. */ case RLM_SQL_ERROR: /* * If we get RLM_SQL_RECONNECT it means all connections in the pool * were exhausted, and we couldn't create a new connection, * so we do not need to call fr_connection_release. */ case RLM_SQL_RECONNECT: rcode = RLM_MODULE_FAIL; goto finish; /* * Query was invalid, this is a terminal error, but we still need * to do cleanup, as the connection handle is still valid. */ case RLM_SQL_QUERY_INVALID: rcode = RLM_MODULE_INVALID; goto finish; /* * Driver found an error (like a unique key constraint violation) * that hinted it might be a good idea to try an alternative query. */ case RLM_SQL_ALT_QUERY: goto next; } rad_assert(handle); /* * We need to have updated something for the query to have been * counted as successful. */ numaffected = (inst->module->sql_affected_rows)(handle, inst->config); (inst->module->sql_finish_query)(handle, inst->config); RDEBUG("%i record(s) updated", numaffected); if (numaffected > 0) break; /* A query succeeded, were done! */ next: /* * We assume all entries with the same name form a redundant * set of queries. */ pair = cf_pair_find_next(section->cs, pair, attr); if (!pair) { RDEBUG("No additional queries configured"); rcode = RLM_MODULE_NOOP; goto finish; } RDEBUG("Trying next query..."); } finish: talloc_free(expanded); fr_connection_release(inst->pool, handle); sql_unset_user(inst, request); return rcode; }
/* * Generate a challenge to be presented to the user. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_otp_t *inst = (rlm_otp_t *) instance; char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */ int auth_type_found; /* Early exit if Auth-Type != inst->name */ { VALUE_PAIR *vp; auth_type_found = 0; vp = pairfind(request->config_items, PW_AUTHTYPE, 0, TAG_ANY); if (vp) { auth_type_found = 1; if (strcmp(vp->vp_strvalue, inst->name)) { return RLM_MODULE_NOOP; } } } /* The State attribute will be present if this is a response. */ if (pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY) != NULL) { DEBUG("rlm_otp: autz: Found response to Access-Challenge"); return RLM_MODULE_OK; } /* User-Name attribute required. */ if (!request->username) { RWDEBUG("Attribute \"User-Name\" " "required for authentication"); return RLM_MODULE_INVALID; } if (otp_pwe_present(request) == 0) { RWDEBUG("Attribute " "\"User-Password\" or equivalent required " "for authentication"); return RLM_MODULE_INVALID; } /* * We used to check for special "challenge" and "resync" passcodes * here, but these are complicated to explain and application is * limited. More importantly, since we've removed all actual OTP * code (now we ask otpd), it's awkward for us to support them. * Should the need arise to reinstate these options, the most * likely choice is to duplicate some otpd code here. */ if (inst->allow_sync && !inst->allow_async) { /* This is the token sync response. */ if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_OK; } /* * Generate a random challenge. */ otp_async_challenge(challenge, inst->challenge_len); /* * Create the State attribute, which will be returned to * us along with the response. * * We will need this to verify the response. * * It must be hmac protected to prevent insertion of arbitrary * State by an inside attacker. * * If we won't actually use the State (server config doesn't * allow async), we just use a trivial State. * * We always create at least a trivial State, so mod_authorize() * can quickly pass on to mod_authenticate(). */ { int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64. char gen_state[OTP_MAX_RADSTATE_LEN]; size_t len; VALUE_PAIR *vp; len = otp_gen_state(gen_state, challenge, inst->challenge_len, 0, now, inst->hmac_key); vp = paircreate(request->reply, PW_STATE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairmemcpy(vp, (uint8_t const *) gen_state, len); pairadd(&request->reply->vps, vp); } /* * Add the challenge to the reply. */ { VALUE_PAIR *vp; char *expanded = NULL; ssize_t len; /* * First add the internal OTP challenge attribute to * the reply list. */ vp = paircreate(request->reply, PW_OTP_CHALLENGE, 0); if (!vp) { return RLM_MODULE_FAIL; } pairstrcpy(vp, challenge); vp->op = T_OP_SET; pairadd(&request->reply->vps, vp); /* * Then add the message to the user to they known * what the challenge value is. */ len = radius_axlat(&expanded, request, inst->chal_prompt, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } vp = paircreate(request->reply, PW_REPLY_MESSAGE, 0); if (!vp) { talloc_free(expanded); return RLM_MODULE_FAIL; } (void) talloc_steal(vp, expanded); vp->vp_strvalue = expanded; vp->length = len; vp->op = T_OP_SET; vp->type = VT_DATA; pairadd(&request->reply->vps, vp); } /* * Mark the packet as an Access-Challenge packet. * The server will take care of sending it to the user. */ request->reply->code = PW_CODE_ACCESS_CHALLENGE; DEBUG("rlm_otp: Sending Access-Challenge"); if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_HANDLED; }
/** Convert a map to a VALUE_PAIR. * * @param[out] out Where to write the VALUE_PAIR(s). * @param[in] request structure (used only for talloc) * @param[in] map the map. The LHS (dst) has to be VPT_TYPE_ATTR or VPT_TYPE_LIST. * @param[in] ctx unused * @return 0 on success, -1 on failure, -2 on attribute not found/equivalent */ int radius_map2vp(VALUE_PAIR **out, REQUEST *request, value_pair_map_t const *map, UNUSED void *ctx) { int rcode = 0; VALUE_PAIR *vp = NULL, *found, **from = NULL; DICT_ATTR const *da; REQUEST *context = request; vp_cursor_t cursor; rad_assert(request != NULL); rad_assert(map != NULL); *out = NULL; /* * Special case for !*, we don't need to parse RHS as this is a unary operator. */ if (map->op == T_OP_CMP_FALSE) { /* * Were deleting all the attributes in a list. This isn't like the other * mappings because lists aren't represented as attributes (yet), * so we can't return a <list> attribute with the !* operator for * radius_pairmove() to consume, and need to do the work here instead. */ if (map->dst->type == VPT_TYPE_LIST) { if (radius_request(&context, map->dst->request) == 0) { from = radius_list(context, map->dst->list); } if (!from) return -2; pairfree(from); /* @fixme hacky! */ if (map->dst->list == PAIR_LIST_REQUEST) { context->username = NULL; context->password = NULL; } return 0; } /* Not a list, but an attribute, radius_pairmove() will perform that actual delete */ vp = pairalloc(request, map->dst->da); if (!vp) return -1; vp->op = map->op; *out = vp; return 0; } /* * List to list found, this is a special case because we don't need * to allocate any attributes, just finding the current list, and change * the op. */ if ((map->dst->type == VPT_TYPE_LIST) && (map->src->type == VPT_TYPE_LIST)) { if (radius_request(&context, map->src->request) == 0) { from = radius_list(context, map->src->list); } if (!from) return -2; found = paircopy(request, *from); /* * List to list copy is invalid if the src list has no attributes. */ if (!found) return -2; for (vp = fr_cursor_init(&cursor, &found); vp; vp = fr_cursor_next(&cursor)) { vp->op = T_OP_ADD; } *out = found; return 0; } /* * Deal with all non-list operations. */ da = map->dst->da ? map->dst->da : map->src->da; switch (map->src->type) { case VPT_TYPE_XLAT: case VPT_TYPE_XLAT_STRUCT: case VPT_TYPE_LITERAL: case VPT_TYPE_DATA: vp = pairalloc(request, da); if (!vp) return -1; vp->op = map->op; break; default: break; } /* * And parse the RHS */ switch (map->src->type) { ssize_t slen; char *str; case VPT_TYPE_XLAT_STRUCT: rad_assert(map->dst->da); /* Need to know where were going to write the new attribute */ rad_assert(map->src->xlat != NULL); str = NULL; slen = radius_axlat_struct(&str, request, map->src->xlat, NULL, NULL); if (slen < 0) { rcode = slen; goto error; } /* * We do the debug printing because radius_axlat_struct * doesn't have access to the original string. It's been * mangled during the parsing to xlat_exp_t */ RDEBUG2("EXPAND %s", map->src->name); RDEBUG2(" --> %s", str); rcode = pairparsevalue(vp, str); talloc_free(str); if (!rcode) { pairfree(&vp); rcode = -1; goto error; } break; case VPT_TYPE_XLAT: rad_assert(map->dst->da); /* Need to know where were going to write the new attribute */ str = NULL; slen = radius_axlat(&str, request, map->src->name, NULL, NULL); if (slen < 0) { rcode = slen; goto error; } rcode = pairparsevalue(vp, str); talloc_free(str); if (!rcode) { pairfree(&vp); rcode = -1; goto error; } break; case VPT_TYPE_LITERAL: if (!pairparsevalue(vp, map->src->name)) { rcode = -2; goto error; } break; case VPT_TYPE_ATTR: rad_assert(!map->dst->da || (map->src->da->type == map->dst->da->type) || (map->src->da->type == PW_TYPE_OCTETS) || (map->dst->da->type == PW_TYPE_OCTETS)); /* * Special case, destination is a list, found all instance of an attribute. */ if (map->dst->type == VPT_TYPE_LIST) { context = request; if (radius_request(&context, map->src->request) == 0) { from = radius_list(context, map->src->list); } /* * Can't add the attribute if the list isn't * valid. */ if (!from) { rcode = -2; goto error; } found = paircopy2(request, *from, map->src->da->attr, map->src->da->vendor, TAG_ANY); if (!found) { REDEBUG("Attribute \"%s\" not found in request", map->src->name); rcode = -2; goto error; } for (vp = fr_cursor_init(&cursor, &found); vp; vp = fr_cursor_next(&cursor)) { vp->op = T_OP_ADD; } *out = found; return 0; } if (radius_vpt_get_vp(&found, request, map->src) < 0) { REDEBUG("Attribute \"%s\" not found in request", map->src->name); rcode = -2; goto error; } /* * Copy the data over verbatim, assuming it's * actually data. */ vp = paircopyvpdata(request, da, found); if (!vp) { return -1; } vp->op = map->op; break; case VPT_TYPE_DATA: rad_assert(map->src && map->src->da); rad_assert(map->dst && map->dst->da); rad_assert(map->src->da->type == map->dst->da->type); memcpy(&vp->data, map->src->vpd, sizeof(vp->data)); vp->length = map->src->length; break; /* * This essentially does the same as rlm_exec xlat, except it's non-configurable. * It's only really here as a convenience for people who expect the contents of * backticks to be executed in a shell. * * exec string is xlat expanded and arguments are shell escaped. */ case VPT_TYPE_EXEC: return radius_mapexec(out, request, map); default: rad_assert(0); /* Should of been caught at parse time */ error: pairfree(&vp); return rcode; } *out = vp; return 0; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t mod_authorize(UNUSED void *instance, UNUSED REQUEST *request) { rlm_sqlcounter_t *inst = instance; int rcode = RLM_MODULE_NOOP; uint64_t counter, res; DICT_ATTR const *dattr; VALUE_PAIR *key_vp, *check_vp; VALUE_PAIR *reply_item; char msg[128]; char *p; char query[MAX_QUERY_LEN]; char *expanded = NULL; size_t len; /* * Before doing anything else, see if we have to reset * the counters. */ if (inst->reset_time && (inst->reset_time <= request->timestamp)) { /* * Re-set the next time and prev_time for this counters range */ inst->last_reset = inst->reset_time; find_next_reset(inst,request->timestamp); } /* * Look for the key. User-Name is special. It means * The REAL username, after stripping. */ RDEBUG2("Entering module authorize code"); key_vp = ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) ? request->username : pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY); if (!key_vp) { RDEBUG2("Could not find Key value pair"); return rcode; } /* * Look for the check item */ if ((dattr = dict_attrbyname(inst->check_name)) == NULL) { return rcode; } /* DEBUG2("rlm_sqlcounter: Found Check item attribute %d", dattr->attr); */ if ((check_vp = pairfind(request->config_items, dattr->attr, dattr->vendor, TAG_ANY)) == NULL) { RDEBUG2("Could not find Check item value pair"); return rcode; } len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, query); if (len >= sizeof(query) - 1) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p = query + len; /* first, expand %k, %b and %e in query */ len = sqlcounter_expand(p, p - query, inst->query, inst); if (len <= 0) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p += len; if ((p - query) < 2) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } p[0] = '}'; p[1] = '\0'; /* Finally, xlat resulting SQL query */ if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } if (sscanf(expanded, "%" PRIu64, &counter) != 1) { RDEBUG2("No integer found in string \"%s\"", expanded); return RLM_MODULE_NOOP; } talloc_free(expanded); /* * Check if check item > counter */ if (check_vp->vp_integer64 <= counter) { RDEBUG2("(Check item - counter) is less than zero"); /* * User is denied access, send back a reply message */ snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset); pairmake_reply("Reply-Message", msg, T_OP_EQ); REDEBUG("Maximum %s usage time reached", inst->reset); rcode = RLM_MODULE_REJECT; RDEBUG2("Rejected user %s, check_item=%" PRIu64 ", counter=%" PRIu64, key_vp->vp_strvalue, check_vp->vp_integer64, counter); return RLM_MODULE_REJECT; } res = check_vp->vp_integer64 - counter; RDEBUG2("Check item is greater than query result"); /* * We are assuming that simultaneous-use=1. But * even if that does not happen then our user * could login at max for 2*max-usage-time Is * that acceptable? */ /* * If we are near a reset then add the next * limit, so that the user will not need to login * again. Do this only for Session-Timeout. */ if ((inst->reply_attr->attr == PW_SESSION_TIMEOUT) && inst->reset_time && ((int) res >= (inst->reset_time - request->timestamp))) { res = inst->reset_time - request->timestamp; res += check_vp->vp_integer; } /* * Limit the reply attribute to the minimum of * the existing value, or this new one. */ reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY); if (reply_item) { if (reply_item->vp_integer64 > res) { reply_item->vp_integer64 = res; } } else { reply_item = radius_paircreate(request, &request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor); reply_item->vp_integer64 = res; } RDEBUG2("Authorized user %s, check_item=%" PRIu64 ", counter=%" PRIu64 , key_vp->vp_strvalue, check_vp->vp_integer64, counter); RDEBUG2("Sent Reply-Item for user %s, Type=%s, value=%" PRIu64, key_vp->vp_strvalue, inst->reply_name, reply_item->vp_integer64); return RLM_MODULE_OK; }
/** Convert a map to a VALUE_PAIR. * * @param[out] out Where to write the VALUE_PAIR(s). * @param[in] request structure (used only for talloc) * @param[in] map the map. The LHS (dst) has to be VPT_TYPE_ATTR or VPT_TYPE_LIST. * @param[in] ctx unused * @return 0 on success, -1 on failure, -2 on attribute not found/equivalent */ int radius_map2vp(VALUE_PAIR **out, REQUEST *request, value_pair_map_t const *map, UNUSED void *ctx) { int rcode = 0; VALUE_PAIR *vp = NULL, *found, **from = NULL; DICT_ATTR const *da; REQUEST *context; vp_cursor_t cursor; rad_assert(request != NULL); rad_assert(map != NULL); *out = NULL; /* * Special case for !*, we don't need to parse the value, just allocate an attribute with * the right operator. */ if (map->op == T_OP_CMP_FALSE) { vp = pairalloc(request, map->dst->da); if (!vp) return -1; vp->op = map->op; *out = vp; return 0; } /* * List to list found, this is a special case because we don't need * to allocate any attributes, just found the current list, and change * the op. */ if ((map->dst->type == VPT_TYPE_LIST) && (map->src->type == VPT_TYPE_LIST)) { from = radius_list(request, map->src->list); if (!from) return -2; found = paircopy(request, *from); /* * List to list copy is invalid if the src list has no attributes. */ if (!found) return -2; for (vp = paircursor(&cursor, &found); vp; vp = pairnext(&cursor)) { vp->op = T_OP_ADD; } *out = found; return 0; } /* * Deal with all non-list founding operations. */ da = map->dst->da ? map->dst->da : map->src->da; switch (map->src->type) { case VPT_TYPE_XLAT: case VPT_TYPE_LITERAL: case VPT_TYPE_DATA: vp = pairalloc(request, da); if (!vp) return -1; vp->op = map->op; break; default: break; } /* * And parse the RHS */ switch (map->src->type) { case VPT_TYPE_XLAT: rad_assert(map->dst->da); /* Need to know where were going to write the new attribute */ /* * Don't call unnecessary expansions */ if (strchr(map->src->name, '%') != NULL) { ssize_t slen; char *str = NULL; slen = radius_axlat(&str, request, map->src->name, NULL, NULL); if (slen < 0) { rcode = slen; goto error; } rcode = pairparsevalue(vp, str); talloc_free(str); if (!rcode) { pairfree(&vp); rcode = -1; goto error; } break; } /* FALL-THROUGH */ case VPT_TYPE_LITERAL: if (!pairparsevalue(vp, map->src->name)) { rcode = -2; goto error; } break; case VPT_TYPE_ATTR: rad_assert(!map->dst->da || (map->src->da->type == map->dst->da->type) || (map->src->da->type == PW_TYPE_OCTETS) || (map->dst->da->type == PW_TYPE_OCTETS)); context = request; if (radius_request(&context, map->src->request) == 0) { from = radius_list(context, map->src->list); } /* * Can't add the attribute if the list isn't * valid. */ if (!from) { rcode = -2; goto error; } /* * Special case, destination is a list, found all instance of an attribute. */ if (map->dst->type == VPT_TYPE_LIST) { found = paircopy2(request, *from, map->src->da->attr, map->src->da->vendor, TAG_ANY); if (!found) { REDEBUG("Attribute \"%s\" not found in request", map->src->name); rcode = -2; goto error; } for (vp = paircursor(&cursor, &found); vp; vp = pairnext(&cursor)) { vp->op = T_OP_ADD; } *out = found; return 0; } /* * FIXME: allow tag references? */ found = pairfind(*from, map->src->da->attr, map->src->da->vendor, TAG_ANY); if (!found) { REDEBUG("Attribute \"%s\" not found in request", map->src->name); rcode = -2; goto error; } /* * Copy the data over verbatim, assuming it's * actually data. */ // rad_assert(found->type == VT_DATA); vp = paircopyvpdata(request, da, found); if (!vp) { return -1; } vp->op = map->op; break; case VPT_TYPE_DATA: rad_assert(map->src->da->type == map->dst->da->type); memcpy(&vp->data, map->src->vpd, sizeof(vp->data)); vp->length = map->src->length; break; /* * This essentially does the same as rlm_exec xlat, except it's non-configurable. * It's only really here as a convenience for people who expect the contents of * backticks to be executed in a shell. * * exec string is xlat expanded and arguments are shell escaped. */ case VPT_TYPE_EXEC: return radius_mapexec(out, request, map); default: rad_assert(0); /* Should of been caught at parse time */ error: pairfree(&vp); return rcode; } *out = vp; return 0; }
static rlm_rcode_t mod_authorize(void *instance, REQUEST * request) { rlm_rcode_t rcode = RLM_MODULE_NOOP; rlm_sql_t *inst = instance; rlm_sql_handle_t *handle; VALUE_PAIR *check_tmp = NULL; VALUE_PAIR *reply_tmp = NULL; VALUE_PAIR *user_profile = NULL; bool user_found = false; bool dofallthrough = true; int rows; char *expanded = NULL; rad_assert(request != NULL); rad_assert(request->packet != NULL); rad_assert(request->reply != NULL); /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Reserve a socket * * After this point use goto error or goto release to cleanup socket temporary pairlists and * temporary attributes. */ handle = sql_get_socket(inst); if (!handle) { rcode = RLM_MODULE_FAIL; goto error; } /* * Query the check table to find any conditions associated with this user/realm/whatever... */ if (inst->config->authorize_check_query && (inst->config->authorize_check_query[0] != '\0')) { if (radius_axlat(&expanded, request, inst->config->authorize_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) { goto skipreply; } /* * Only do this if *some* check pairs were returned */ RDEBUG2("User found in radcheck table"); user_found = true; if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) { goto skipreply; } RDEBUG2("Check items matched"); radius_pairmove(request, &request->config_items, check_tmp, true); rcode = RLM_MODULE_OK; } if (inst->config->authorize_reply_query && (inst->config->authorize_reply_query[0] != '\0')) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) { goto skipreply; } if (!inst->config->read_groups) { dofallthrough = fallthrough(reply_tmp); } RDEBUG2("User found in radreply table"); user_found = true; radius_pairmove(request, &request->reply->vps, reply_tmp, true); rcode = RLM_MODULE_OK; } skipreply: /* * Clear out the pairlists */ pairfree(&check_tmp); pairfree(&reply_tmp); /* * dofallthrough is set to 1 by default so that if the user information * is not found, we will still process groups. If the user information, * however, *is* found, Fall-Through must be set in order to process * the groups as well. */ if (dofallthrough) { rlm_rcode_t ret; RDEBUG3("... falling-through to group processing"); ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * Repeat the above process with the default profile or User-Profile */ if (dofallthrough) { rlm_rcode_t ret; /* * Check for a default_profile or for a User-Profile. */ RDEBUG3("... falling-through to profile processing"); user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY); char const *profile = user_profile ? user_profile->vp_strvalue : inst->config->default_profile; if (!profile || !*profile) { goto release; } RDEBUG2("Checking profile %s", profile); if (sql_set_user(inst, request, profile) < 0) { REDEBUG("Error setting profile"); rcode = RLM_MODULE_FAIL; goto error; } ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * At this point the key (user) hasn't be found in the check table, the reply table * or the group mapping table, and there was no matching profile. */ release: if (!user_found) { rcode = RLM_MODULE_NOTFOUND; } error: sql_release_socket(inst, handle); pairfree(&check_tmp); pairfree(&reply_tmp); return rcode; }
/* * Generic function for failing between a bunch of queries. * * Uses the same principle as rlm_linelog, expanding the 'reference' config * item using xlat to figure out what query it should execute. * * If the reference matches multiple config items, and a query fails or * doesn't update any rows, the next matching config item is used. * */ static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; int sql_ret; int numaffected = 0; CONF_ITEM *item; CONF_PAIR *pair; char const *attr = NULL; char const *value; char path[MAX_STRING_LEN]; char *p = path; char *expanded = NULL; rad_assert(section); if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } item = cf_reference_item(NULL, section->cs, path); if (!item) { rcode = RLM_MODULE_FAIL; goto finish; } if (cf_item_is_section(item)){ REDEBUG("Sections are not supported as references"); rcode = RLM_MODULE_FAIL; goto finish; } pair = cf_itemtopair(item); attr = cf_pair_attr(pair); RDEBUG2("Using query template '%s'", attr); handle = sql_get_socket(inst); if (!handle) { rcode = RLM_MODULE_FAIL; goto finish; } sql_set_user(inst, request, NULL); while (true) { value = cf_pair_value(pair); if (!value) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; goto finish; } if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!*expanded) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; talloc_free(expanded); goto finish; } rlm_sql_query_log(inst, request, section, expanded); /* * If rlm_sql_query cannot use the socket it'll try and * reconnect. Reconnecting will automatically release * the current socket, and try to select a new one. * * If we get RLM_SQL_RECONNECT it means all connections in the pool * were exhausted, and we couldn't create a new connection, * so we do not need to call sql_release_socket. */ sql_ret = rlm_sql_query(&handle, inst, expanded); TALLOC_FREE(expanded); if (sql_ret == RLM_SQL_RECONNECT) { rcode = RLM_MODULE_FAIL; goto finish; } rad_assert(handle); /* * Assume all other errors are incidental, and just meant our * operation failed and its not a client or SQL syntax error. * * @fixme We should actually be able to distinguish between key * constraint violations (which we expect) and other errors. */ if (sql_ret == RLM_SQL_OK) { numaffected = (inst->module->sql_affected_rows)(handle, inst->config); if (numaffected > 0) { break; /* A query succeeded, were done! */ } RDEBUG("No records updated"); } (inst->module->sql_finish_query)(handle, inst->config); /* * We assume all entries with the same name form a redundant * set of queries. */ pair = cf_pair_find_next(section->cs, pair, attr); if (!pair) { RDEBUG("No additional queries configured"); rcode = RLM_MODULE_NOOP; goto finish; } RDEBUG("Trying next query..."); } (inst->module->sql_finish_query)(handle, inst->config); finish: talloc_free(expanded); sql_release_socket(inst, handle); return rcode; }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, sql_fall_through_t *do_fall_through) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request->packet != NULL); /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); rcode = RLM_MODULE_NOTFOUND; *do_fall_through = FALL_THROUGH_DEFAULT; goto finish; } rad_assert(head); RDEBUG2("User found in the group table"); /* * Add the Sql-Group attribute to the request list so we know * which group we're retrieving attributes for */ sql_group = pairmake_packet("Sql-Group", NULL, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating Sql-Group attribute"); rcode = RLM_MODULE_FAIL; goto finish; } entry = head; do { next: rad_assert(entry != NULL); pairstrcpy(sql_group, entry->name); if (inst->config->authorize_group_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to * process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { pairfree(&check_tmp); entry = entry->next; goto next; /* != continue */ } RDEBUG2("Group \"%s\": Conditional check items matched", entry->name); rcode = RLM_MODULE_OK; RDEBUG2("Group \"%s\": Merging assignment check items", entry->name); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(L_DBG_LVL_2, request, vp, NULL); } REXDENT(); radius_pairmove(request, &request->config_items, check_tmp, true); check_tmp = NULL; } if (inst->config->authorize_group_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *do_fall_through = fall_through(reply_tmp); RDEBUG2("Group \"%s\": Merging reply items", entry->name); rcode = RLM_MODULE_OK; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL); radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; /* * If there's no reply query configured, then we assume * FALL_THROUGH_NO, which is the same as the users file if you * had no reply attributes. */ } else { *do_fall_through = FALL_THROUGH_DEFAULT; } entry = entry->next; } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES)); finish: talloc_free(head); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); return rcode; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_sqlcounter_t *inst = instance; int rcode = RLM_MODULE_NOOP; uint64_t counter, res; DICT_ATTR const *da; VALUE_PAIR *key_vp, *limit; VALUE_PAIR *reply_item; char msg[128]; char query[MAX_QUERY_LEN], subst[MAX_QUERY_LEN]; char *expanded = NULL; size_t len; /* * Before doing anything else, see if we have to reset * the counters. */ if (inst->reset_time && (inst->reset_time <= request->timestamp)) { /* * Re-set the next time and prev_time for this counters range */ inst->last_reset = inst->reset_time; find_next_reset(inst,request->timestamp); } /* * Look for the key. User-Name is special. It means * The REAL username, after stripping. */ if ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) { key_vp = request->username; } else { key_vp = pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY); } if (!key_vp) { RWDEBUG2("Couldn't find key attribute 'request:%s'", inst->key_attr->name); return rcode; } /* * Look for the check item */ if ((da = dict_attrbyname(inst->limit_name)) == NULL) { return rcode; } limit = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (limit == NULL) { RWDEBUG2("Couldn't find control attribute 'control:%s'", inst->limit_name); return rcode; } /* First, expand %k, %b and %e in query */ if (sqlcounter_expand(subst, sizeof(subst), inst->query, inst) <= 0) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Then combine that with the name of the module were using to do the query */ len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, subst); if (len >= (sizeof(query) - 1)) { REDEBUG("Insufficient query buffer space"); return RLM_MODULE_FAIL; } /* Finally, xlat resulting SQL query */ if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) { return RLM_MODULE_FAIL; } talloc_free(expanded); if (sscanf(expanded, "%" PRIu64, &counter) != 1) { RDEBUG2("No integer found in result string \"%s\". May be first session, setting counter to 0", expanded); counter = 0; } /* * Check if check item > counter */ if (limit->vp_integer64 <= counter) { /* User is denied access, send back a reply message */ snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset); pairmake_reply("Reply-Message", msg, T_OP_EQ); REDEBUG2("Maximum %s usage time reached", inst->reset); REDEBUG2("Rejecting user, control:%s value (%" PRIu64 ") is less than counter value (%" PRIu64 ")", inst->limit_name, limit->vp_integer64, counter); return RLM_MODULE_REJECT; } res = limit->vp_integer64 - counter; RDEBUG2("Allowing user, control:%s value (%" PRIu64 ") is greater than counter value (%" PRIu64 ")", inst->limit_name, limit->vp_integer64, counter); /* * We are assuming that simultaneous-use=1. But * even if that does not happen then our user * could login at max for 2*max-usage-time Is * that acceptable? */ /* * If we are near a reset then add the next * limit, so that the user will not need to login * again. Do this only for Session-Timeout. */ if (((inst->reply_attr->vendor == 0) && (inst->reply_attr->attr == PW_SESSION_TIMEOUT)) && inst->reset_time && ((int) res >= (inst->reset_time - request->timestamp))) { res = (inst->reset_time - request->timestamp); res += limit->vp_integer; } /* * Limit the reply attribute to the minimum of the existing value, or this new one. */ reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY); if (reply_item) { if (reply_item->vp_integer64 <= res) { RDEBUG2("Leaving existing reply:%s value of %" PRIu64, inst->reply_attr->name, reply_item->vp_integer64); return RLM_MODULE_OK; } } else { reply_item = radius_paircreate(request->reply, &request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor); } reply_item->vp_integer64 = res; RDEBUG2("Setting reply:%s value to %" PRIu64, inst->reply_name, reply_item->vp_integer64); return RLM_MODULE_OK; }
static rlm_rcode_t mod_checksimul(void *instance, REQUEST * request) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; rlm_sql_t *inst = instance; rlm_sql_row_t row; int check = 0; uint32_t ipno = 0; char const *call_num = NULL; VALUE_PAIR *vp; int ret; uint32_t nas_addr = 0; int nas_port = 0; char *expanded = NULL; /* If simul_count_query is not defined, we don't do any checking */ if (!inst->config->simul_count_query || (inst->config->simul_count_query[0] == '\0')) { return RLM_MODULE_NOOP; } if((!request->username) || (request->username->length == '\0')) { REDEBUG("Zero Length username not permitted"); return RLM_MODULE_INVALID; } if(sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) { return RLM_MODULE_FAIL; } /* initialize the sql socket */ handle = sql_get_socket(inst); if (!handle) { talloc_free(expanded); return RLM_MODULE_FAIL; } if (rlm_sql_select_query(&handle, inst, expanded)) { rcode = RLM_MODULE_FAIL; goto finish; } ret = rlm_sql_fetch_row(&handle, inst); if (ret != 0) { rcode = RLM_MODULE_FAIL; goto finish; } row = handle->row; if (!row) { rcode = RLM_MODULE_FAIL; goto finish; } request->simul_count = atoi(row[0]); (inst->module->sql_finish_select_query)(handle, inst->config); TALLOC_FREE(expanded); if(request->simul_count < request->simul_max) { rcode = RLM_MODULE_OK; goto finish; } /* * Looks like too many sessions, so let's start verifying * them, unless told to rely on count query only. */ if (!inst->config->simul_verify_query || (inst->config->simul_verify_query[0] == '\0')) { rcode = RLM_MODULE_OK; goto finish; } if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if(rlm_sql_select_query(&handle, inst, expanded)) { goto finish; } /* * Setup some stuff, like for MPP detection. */ request->simul_count = 0; if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { ipno = vp->vp_ipaddr; } if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { call_num = vp->vp_strvalue; } while (rlm_sql_fetch_row(&handle, inst) == 0) { row = handle->row; if (!row) { break; } if (!row[2]){ RDEBUG("Cannot zap stale entry. No username present in entry"); rcode = RLM_MODULE_FAIL; goto finish; } if (!row[1]){ RDEBUG("Cannot zap stale entry. No session id in entry"); rcode = RLM_MODULE_FAIL; goto finish; } if (row[3]) { nas_addr = inet_addr(row[3]); } if (row[4]) { nas_port = atoi(row[4]); } check = rad_check_ts(nas_addr, nas_port, row[2], row[1]); if (check == 0) { /* * Stale record - zap it. */ if (inst->config->deletestalesessions == true) { uint32_t framed_addr = 0; char proto = 0; int sess_time = 0; if (row[5]) framed_addr = inet_addr(row[5]); if (row[7]){ if (strcmp(row[7], "PPP") == 0) proto = 'P'; else if (strcmp(row[7], "SLIP") == 0) proto = 'S'; } if (row[8]) sess_time = atoi(row[8]); session_zap(request, nas_addr, nas_port, row[2], row[1], framed_addr, proto, sess_time); } } else if (check == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (row[5] && ipno && inet_addr(row[5]) == ipno) { request->simul_mpp = 2; } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) { request->simul_mpp = 2; } } else { /* * Failed to check the terminal server for * duplicate logins: return an error. */ REDEBUG("Failed to check the terminal server for user '%s'.", row[2]); rcode = RLM_MODULE_FAIL; goto finish; } } finish: (inst->module->sql_finish_select_query)(handle, inst->config); sql_release_socket(inst, handle); talloc_free(expanded); /* * The Auth module apparently looks at request->simul_count, * not the return value of this module when deciding to deny * a call for too many sessions. */ return rcode; }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_NOOP; rlm_sql_t *inst = instance; rlm_sql_handle_t *handle; VALUE_PAIR *check_tmp = NULL; VALUE_PAIR *reply_tmp = NULL; VALUE_PAIR *user_profile = NULL; bool user_found = false; sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT; int rows; char *expanded = NULL; rad_assert(request->packet != NULL); rad_assert(request->reply != NULL); if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query && !inst->config->read_groups && !inst->config->read_profiles) { RWDEBUG("No authorization checks configured, returning noop"); return RLM_MODULE_NOOP; } /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Reserve a socket * * After this point use goto error or goto release to cleanup socket temporary pairlists and * temporary attributes. */ handle = sql_get_socket(inst); if (!handle) { rcode = RLM_MODULE_FAIL; goto error; } /* * Query the check table to find any conditions associated with this user/realm/whatever... */ if (inst->config->authorize_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; if (radius_axlat(&expanded, request, inst->config->authorize_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(request, inst, &handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */ /* * Only do this if *some* check pairs were returned */ RDEBUG2("User found in radcheck table"); user_found = true; if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) { pairfree(&check_tmp); check_tmp = NULL; goto skipreply; } RDEBUG2("Conditional check items matched, merging assignment check items"); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(2, request, vp); } REXDENT(); radius_pairmove(request, &request->config_items, check_tmp, true); rcode = RLM_MODULE_OK; check_tmp = NULL; } if (inst->config->authorize_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(request->reply, inst, &handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) goto skipreply; do_fall_through = fall_through(reply_tmp); RDEBUG2("User found in radreply table, merging reply items"); user_found = true; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp); radius_pairmove(request, &request->reply->vps, reply_tmp, true); rcode = RLM_MODULE_OK; reply_tmp = NULL; } skipreply: if ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) { rlm_rcode_t ret; RDEBUG3("... falling-through to group processing"); ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * Repeat the above process with the default profile or User-Profile */ if ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) { rlm_rcode_t ret; /* * Check for a default_profile or for a User-Profile. */ RDEBUG3("... falling-through to profile processing"); user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY); char const *profile = user_profile ? user_profile->vp_strvalue : inst->config->default_profile; if (!profile || !*profile) { goto release; } RDEBUG2("Checking profile %s", profile); if (sql_set_user(inst, request, profile) < 0) { REDEBUG("Error setting profile"); rcode = RLM_MODULE_FAIL; goto error; } ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * At this point the key (user) hasn't be found in the check table, the reply table * or the group mapping table, and there was no matching profile. */ release: if (!user_found) { rcode = RLM_MODULE_NOTFOUND; } sql_release_socket(inst, handle); sql_unset_user(inst, request); return rcode; error: pairfree(&check_tmp); pairfree(&reply_tmp); sql_unset_user(inst, request); sql_release_socket(inst, handle); return rcode; }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *handle, bool *dofallthrough) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request != NULL); rad_assert(request->packet != NULL); /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); rcode = RLM_MODULE_NOTFOUND; goto finish; } RDEBUG2("User found in the group table"); for (entry = head; entry != NULL && (*dofallthrough != 0); entry = entry->next) { /* * Add the Sql-Group attribute to the request list so we know * which group we're retrieving attributes for */ sql_group = pairmake_packet("Sql-Group", entry->name, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating Sql-Group attribute"); rcode = RLM_MODULE_FAIL; goto finish; } if (inst->config->authorize_group_check_query && (inst->config->authorize_group_check_query != '\0')) { /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { pairfree(&check_tmp); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); continue; } RDEBUG2("Group \"%s\" check items matched", entry->name); rcode = RLM_MODULE_OK; radius_pairmove(request, &request->config_items, check_tmp, true); check_tmp = NULL; } if (inst->config->authorize_group_reply_query && (inst->config->authorize_group_reply_query != '\0')) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *dofallthrough = fallthrough(reply_tmp); RDEBUG2("Group \"%s\" reply items processed", entry->name); rcode = RLM_MODULE_OK; radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; } pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); } finish: talloc_free(head); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); return rcode; }
/** Expand the RHS of a template * * @note Length of expanded string can be found with talloc_array_length(*out) - 1 * * @param out where to write a pointer to the newly allocated buffer. * @param request Current request. * @param vpt to evaluate. * @return -1 on error, else 0. */ int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt) { VALUE_PAIR *vp; *out = NULL; rad_assert(vpt->type != TMPL_TYPE_LIST); switch (vpt->type) { case TMPL_TYPE_LITERAL: EVAL_DEBUG("TMPL LITERAL"); *out = talloc_typed_strdup(request, vpt->name); break; case TMPL_TYPE_EXEC: EVAL_DEBUG("TMPL EXEC"); *out = talloc_array(request, char, 1024); if (radius_exec_program(request, vpt->name, true, false, *out, 1024, EXEC_TIMEOUT, NULL, NULL) != 0) { TALLOC_FREE(*out); return -1; } break; case TMPL_TYPE_REGEX: EVAL_DEBUG("TMPL REGEX"); /* Error in expansion, this is distinct from zero length expansion */ if (radius_axlat(out, request, vpt->name, NULL, NULL) < 0) { rad_assert(!*out); return -1; } break; case TMPL_TYPE_XLAT: EVAL_DEBUG("TMPL XLAT"); /* Error in expansion, this is distinct from zero length expansion */ if (radius_axlat(out, request, vpt->name, NULL, NULL) < 0) { rad_assert(!*out); return -1; } break; case TMPL_TYPE_XLAT_STRUCT: EVAL_DEBUG("TMPL XLAT_STRUCT"); /* Error in expansion, this is distinct from zero length expansion */ if (radius_axlat_struct(out, request, vpt->tmpl_xlat, NULL, NULL) < 0) { rad_assert(!*out); return -1; } RDEBUG2("EXPAND %s", vpt->name); /* xlat_struct doesn't do this */ RDEBUG2(" --> %s", *out); break; case TMPL_TYPE_ATTR: { int ret; EVAL_DEBUG("TMPL ATTR"); ret = tmpl_find_vp(&vp, request, vpt); if (ret < 0) return -2; *out = vp_aprint_value(request, vp, false); if (!*out) return -1; } break; case TMPL_TYPE_DATA: case TMPL_TYPE_REGEX_STRUCT: rad_assert(0 == 1); /* FALL-THROUGH */ default: break; } EVAL_DEBUG("Expand tmpl --> %s", *out); return 0; }
/** Modify user's object in LDAP * * Process a modifcation map to update a user object in the LDAP directory. * * @param inst rlm_ldap instance. * @param request Current request. * @param section that holds the map to process. * @return one of the RLM_MODULE_* values. */ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; ldap_handle_t *conn = NULL; LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP]; LDAPMod **modify = mod_p; char *passed[LDAP_MAX_ATTRMAP * 2]; int i, total = 0, last_pass = 0; char *expanded[LDAP_MAX_ATTRMAP]; int last_exp = 0; char const *attr; char const *value; char const *dn; /* * Build our set of modifications using the update sections in * the config. */ CONF_ITEM *ci; CONF_PAIR *cp; CONF_SECTION *cs; FR_TOKEN op; char path[MAX_STRING_LEN]; char *p = path; rad_assert(section); /* * Locate the update section were going to be using */ if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) { goto error; } ci = cf_reference_item(NULL, section->cs, path); if (!ci) { goto error; } if (!cf_item_is_section(ci)){ REDEBUG("Reference must resolve to a section"); goto error; } cs = cf_section_sub_find(cf_itemtosection(ci), "update"); if (!cs) { REDEBUG("Section must contain 'update' subsection"); goto error; } /* * Iterate over all the pairs, building our mods array */ for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { bool do_xlat = false; if (total == LDAP_MAX_ATTRMAP) { REDEBUG("Modify map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { REDEBUG("Entry is not in \"ldap-attribute = value\" format"); goto error; } /* * Retrieve all the information we need about the pair */ cp = cf_itemtopair(ci); value = cf_pair_value(cp); attr = cf_pair_attr(cp); op = cf_pair_operator(cp); if (!value || (*value == '\0')) { RDEBUG("Empty value string, skipping attribute \"%s\"", attr); continue; } switch (cf_pair_value_type(cp)) { case T_BARE_WORD: case T_SINGLE_QUOTED_STRING: break; case T_BACK_QUOTED_STRING: case T_DOUBLE_QUOTED_STRING: do_xlat = true; break; default: rad_assert(0); goto error; } if (op == T_OP_CMP_FALSE) { passed[last_pass] = NULL; } else if (do_xlat) { char *exp = NULL; if (radius_axlat(&exp, request, value, NULL, NULL) <= 0) { RDEBUG("Skipping attribute \"%s\"", attr); talloc_free(exp); continue; } expanded[last_exp++] = exp; passed[last_pass] = exp; /* * Static strings */ } else { memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass])); } passed[last_pass + 1] = NULL; mod_s[total].mod_values = &(passed[last_pass]); last_pass += 2; switch (op) { /* * T_OP_EQ is *NOT* supported, it is impossible to * support because of the lack of transactions in LDAP */ case T_OP_ADD: mod_s[total].mod_op = LDAP_MOD_ADD; break; case T_OP_SET: mod_s[total].mod_op = LDAP_MOD_REPLACE; break; case T_OP_SUB: case T_OP_CMP_FALSE: mod_s[total].mod_op = LDAP_MOD_DELETE; break; #ifdef LDAP_MOD_INCREMENT case T_OP_INCRM: mod_s[total].mod_op = LDAP_MOD_INCREMENT; break; #endif default: REDEBUG("Operator '%s' is not supported for LDAP modify operations", fr_int2str(fr_tokens, op, "<INVALID>")); goto error; } /* * Now we know the value is ok, copy the pointers into * the ldapmod struct. */ memcpy(&(mod_s[total].mod_type), &attr, sizeof(mod_s[total].mod_type)); mod_p[total] = &(mod_s[total]); total++; } if (total == 0) { rcode = RLM_MODULE_NOOP; goto release; } mod_p[total] = NULL; conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode); if (!dn || (rcode != RLM_MODULE_OK)) { goto error; } status = rlm_ldap_modify(inst, request, &conn, dn, modify); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_REJECT: case LDAP_PROC_BAD_DN: rcode = RLM_MODULE_INVALID; break; default: rcode = RLM_MODULE_FAIL; break; }; release: error: /* * Free up any buffers we allocated for xlat expansion */ for (i = 0; i < last_exp; i++) { talloc_free(expanded[i]); } mod_conn_release(inst, conn); return rcode; }
/** Expand values in an attribute map where needed * */ int rlm_ldap_map_xlat(REQUEST *request, value_pair_map_t const *maps, rlm_ldap_map_xlat_t *expanded) { value_pair_map_t const *map; unsigned int total = 0; VALUE_PAIR *found, **from = NULL; REQUEST *context; for (map = maps; map != NULL; map = map->next) { switch (map->src->type) { case VPT_TYPE_XLAT: { ssize_t len; char *exp = NULL; len = radius_axlat(&exp, request, map->src->name, NULL, NULL); if (len < 0) { RDEBUG("Expansion of LDAP attribute \"%s\" failed", map->src->name); goto error; } expanded->attrs[total++] = exp; break; } case VPT_TYPE_ATTR: context = request; if (radius_request(&context, map->src->vpt_request) == 0) { from = radius_list(context, map->src->vpt_list); } if (!from) continue; found = pairfind(*from, map->src->vpt_da->attr, map->src->vpt_da->vendor, TAG_ANY); if (!found) continue; expanded->attrs[total++] = talloc_typed_strdup(request, found->vp_strvalue); break; case VPT_TYPE_EXEC: { char answer[1024]; VALUE_PAIR **input_pairs = NULL; int result; input_pairs = radius_list(request, PAIR_LIST_REQUEST); result = radius_exec_program(request, map->src->name, true, true, answer, sizeof(answer), EXEC_TIMEOUT, input_pairs ? *input_pairs : NULL, NULL); if (result != 0) { return -1; } expanded->attrs[total++] = talloc_typed_strdup(request, answer); } break; case VPT_TYPE_LITERAL: expanded->attrs[total++] = map->src->name; break; default: rad_assert(0); error: expanded->attrs[total] = NULL; rlm_ldap_map_xlat_free(expanded); return -1; } } rad_assert(total < LDAP_MAX_ATTRMAP); expanded->attrs[total] = NULL; expanded->count = total; expanded->maps = maps; return 0; }