static int attr_filter_getfile(TALLOC_CTX *ctx, char const *filename, PAIR_LIST **pair_list) { vp_cursor_t cursor; int rcode; PAIR_LIST *attrs = NULL; PAIR_LIST *entry; VALUE_PAIR *vp; rcode = pairlist_read(ctx, filename, &attrs, 1); if (rcode < 0) { return -1; } /* * Walk through the 'attrs' file list. */ entry = attrs; while (entry) { entry->check = entry->reply; entry->reply = NULL; for (vp = paircursor(&cursor, &entry->check); vp; vp = pairnext(&cursor)) { /* * If it's NOT a vendor attribute, * and it's NOT a wire protocol * and we ignore Fall-Through, * then bitch about it, giving a good warning message. */ if ((vp->da->vendor == 0) && (vp->da->attr > 0xff) && (vp->da->attr > 1000)) { WDEBUG("[%s]:%d Check item \"%s\"\n\tfound in filter list for realm \"%s\".\n", filename, entry->lineno, vp->da->name, entry->name); } } entry = entry->next; } *pair_list = attrs; return 0; }
/* * Allow single attribute values to be retrieved from the dhcp. */ static size_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { vp_cursor_t cursor; VALUE_PAIR *vp, *head = NULL; int decoded = 0; while (isspace((int) *fmt)) fmt++; if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) { *out = '\0'; return 0; } if ((fr_dhcp_decode_options(request->packet, vp->vp_octets, vp->length, &head) < 0) || (!head)) { RWDEBUG("DHCP option decoding failed"); goto fail; } for (vp = paircursor(&cursor, &head); vp; vp = pairnext(&cursor)) { decoded++; } pairmove(request->packet, &(request->packet->vps), &head); /* Free any unmoved pairs */ pairfree(&head); fail: snprintf(out, freespace, "%i", decoded); return strlen(out); }
/** Print a list of valuepairs to stderr or error log. * * @param[in] vp to print. */ void debug_pair_list(VALUE_PAIR *vp) { vp_cursor_t cursor; if (!vp || !debug_flag || !fr_log_fp) return; for (vp = paircursor(&cursor, &vp); vp; vp = pairnext(&cursor)) { /* * Take this opportunity to verify all the VALUE_PAIRs are still valid. */ if (!talloc_get_type(vp, VALUE_PAIR)) { ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp)); log_talloc_report(vp); rad_assert(0); } vp_print(fr_log_fp, vp); } fflush(fr_log_fp); }
/** Print a list of valuepairs to the request list. * * @param[in] level Debug level (1-4). * @param[in] request to read logging params from. * @param[in] vp to print. */ void rdebug_pair_list(int level, REQUEST *request, VALUE_PAIR *vp) { vp_cursor_t cursor; char buffer[256]; if (!vp || !request || !request->radlog) return; for (vp = paircursor(&cursor, &vp); vp; vp = pairnext(&cursor)) { /* * Take this opportunity to verify all the VALUE_PAIRs are still valid. */ if (!talloc_get_type(vp, VALUE_PAIR)) { REDEBUG("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp)); log_talloc_report(vp); rad_assert(0); } vp_prints(buffer, sizeof(buffer), vp); request->radlog(L_DBG, level, request, "\t%s", buffer); } }
static int rs_filter_packet(RADIUS_PACKET *packet) { vp_cursor_t cursor, check_cursor; VALUE_PAIR *check_item; VALUE_PAIR *vp; unsigned int pass, fail; int compare; pass = fail = 0; for (vp = paircursor(&cursor, &packet->vps); vp; vp = pairnext(&cursor)) { for (check_item = paircursor(&check_cursor, &filter_vps); check_item; check_item = pairnext(&check_cursor)) if ((check_item->da == vp->da) && (check_item->op != T_OP_SET)) { compare = paircmp(check_item, vp); if (compare == 1) pass++; else fail++; } } if ((fail == 0) && (pass != 0)) { /* * Cache authentication requests, as the replies * may not match the RADIUS filter. */ if ((packet->code == PW_CODE_AUTHENTICATION_REQUEST) || (packet->code == PW_CODE_ACCOUNTING_REQUEST)) { rbtree_deletebydata(filter_tree, packet); if (!rbtree_insert(filter_tree, packet)) { oom: ERROR("Out of memory"); exit(1); } } return 0; /* matched */ } /* * Don't create erroneous matches. */ if ((packet->code == PW_CODE_AUTHENTICATION_REQUEST) || (packet->code == PW_CODE_ACCOUNTING_REQUEST)) { rbtree_deletebydata(filter_tree, packet); return 1; } /* * Else see if a previous Access-Request * matched. If so, also print out the * matching accept, reject, or challenge. */ if ((packet->code == PW_CODE_AUTHENTICATION_ACK) || (packet->code == PW_CODE_AUTHENTICATION_REJECT) || (packet->code == PW_CODE_ACCESS_CHALLENGE) || (packet->code == PW_CODE_ACCOUNTING_RESPONSE)) { RADIUS_PACKET *reply; /* * This swaps the various fields. */ reply = rad_alloc_reply(NULL, packet); if (!reply) goto oom; compare = 1; if (rbtree_finddata(filter_tree, reply)) { compare = 0; } rad_free(&reply); return compare; } return 1; }
static int do_python(REQUEST *request, PyObject *pFunc, char const *funcname) { vp_cursor_t cursor; VALUE_PAIR *vp; PyObject *pRet = NULL; PyObject *pArgs = NULL; int tuplelen; int ret; PyGILState_STATE gstate; /* Return with "OK, continue" if the function is not defined. */ if (!pFunc) return RLM_MODULE_OK; /* Default return value is "OK, continue" */ ret = RLM_MODULE_OK; /* * We will pass a tuple containing (name, value) tuples * We can safely use the Python function to build up a * tuple, since the tuple is not used elsewhere. * * Determine the size of our tuple by walking through the packet. * If request is NULL, pass None. */ tuplelen = 0; if (request != NULL) { for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor)) { tuplelen++; } } gstate = PyGILState_Ensure(); if (tuplelen == 0) { Py_INCREF(Py_None); pArgs = Py_None; } else { int i = 0; if ((pArgs = PyTuple_New(tuplelen)) == NULL) goto failed; for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor), i++) { PyObject *pPair; /* The inside tuple has two only: */ if ((pPair = PyTuple_New(2)) == NULL) goto failed; if (mod_populate_vptuple(pPair, vp) == 0) { /* Put the tuple inside the container */ PyTuple_SET_ITEM(pArgs, i, pPair); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(pArgs, i, Py_None); Py_DECREF(pPair); } } } /* Call Python function. */ pRet = PyObject_CallFunctionObjArgs(pFunc, pArgs, NULL); if (!pRet) goto failed; if (!request) goto okay; /* * The function returns either: * 1. (returnvalue, replyTuple, configTuple), where * - returnvalue is one of the constants RLM_* * - replyTuple and configTuple are tuples of string * tuples of size 2 * * 2. the function return value alone * * 3. None - default return value is set * * xxx This code is messy! */ if (PyTuple_CheckExact(pRet)) { PyObject *pTupleInt; if (PyTuple_GET_SIZE(pRet) != 3) { ERROR("rlm_python:%s: tuple must be (return, replyTuple, configTuple)", funcname); goto failed; } pTupleInt = PyTuple_GET_ITEM(pRet, 0); if (!PyInt_CheckExact(pTupleInt)) { ERROR("rlm_python:%s: first tuple element not an integer", funcname); goto failed; } /* Now have the return value */ ret = PyInt_AsLong(pTupleInt); /* Reply item tuple */ mod_vptuple(request->reply, &request->reply->vps, PyTuple_GET_ITEM(pRet, 1), funcname); /* Config item tuple */ mod_vptuple(request, &request->config_items, PyTuple_GET_ITEM(pRet, 2), funcname); } else if (PyInt_CheckExact(pRet)) { /* Just an integer */ ret = PyInt_AsLong(pRet); } else if (pRet == Py_None) { /* returned 'None', return value defaults to "OK, continue." */ ret = RLM_MODULE_OK; } else { /* Not tuple or None */ ERROR("rlm_python:%s: function did not return a tuple or None", funcname); goto failed; } okay: Py_DECREF(pArgs); Py_DECREF(pRet); PyGILState_Release(gstate); return ret; failed: mod_error(); Py_XDECREF(pArgs); Py_XDECREF(pRet); PyGILState_Release(gstate); return -1; }
/* * 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; }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request Current reuqest * @param exec_wait set to 1 if you want to read from or write to child * @param[in,out] input_fd pointer to int, receives the stdin file. * descriptor. Set to NULL and the child will have /dev/null on stdin * @param[in,out] output_fd pinter to int, receives the stdout file * descriptor. Set to NULL and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param shell_escape values before passing them as arguments. * @return PID of the child process, -1 on error. */ pid_t radius_start_program(char const *cmd, REQUEST *request, int exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, int shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char *argv[MAX_ARGV]; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { RDEBUG("invalid command line '%s'.", cmd); return -1; } #ifndef NDEBUG if (debug_flag > 2) { RDEBUG3("executing cmd %s", cmd); for (i = 0; i < argc; i++) { RDEBUG3("\t[%d] %s", i, argv[i]); } } #endif #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { RDEBUG("Couldn't open pipe to child: %s", strerror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { RDEBUG("Couldn't open pipe from child: %s", strerror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; int envlen; char buffer[1024]; /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ envlen = 0; for (vp = paircursor(&cursor, &input_pairs); vp; vp = pairnext(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); envp[envlen++] = strdup(buffer); /* * Don't add too many attributes. */ if (envlen == (MAX_ENVP - 1)) break; } envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { RDEBUG("Failed opening /dev/null: %s\n", strerror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (debug_flag == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should take 'char const * const argv[]'. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], strerror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ for (i = 0; envp[i] != NULL; i++) { free(envp[i]); } /* * Parent process. */ if (pid < 0) { RDEBUG("Couldn't fork %s: %s", argv[0], strerror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[0]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { RDEBUG("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }
/* * The pairmove() function in src/lib/valuepair.c does all sorts of * extra magic that we don't want here. * * FIXME: integrate this with the code calling it, so that we * only paircopy() those attributes that we're really going to * use. */ void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from) { int i, j, count, from_count, to_count, tailto; vp_cursor_t cursor; VALUE_PAIR *vp, *next, **last; VALUE_PAIR **from_list, **to_list; int *edited = NULL; REQUEST *fixup = NULL; /* * Set up arrays for editing, to remove some of the * O(N^2) dependencies. This also makes it easier to * insert and remove attributes. * * It also means that the operators apply ONLY to the * attributes in the original list. With the previous * implementation of pairmove(), adding two attributes * via "+=" and then "=" would mean that the second one * wasn't added, because of the existence of the first * one in the "to" list. This implementation doesn't * have that bug. * * Also, the previous implementation did NOT implement * "-=" correctly. If two of the same attributes existed * in the "to" list, and you tried to subtract something * matching the *second* value, then the pairdelete() * function was called, and the *all* attributes of that * number were deleted. With this implementation, only * the matching attributes are deleted. */ count = 0; for (vp = paircursor(&cursor, &from); vp; vp = pairnext(&cursor)) count++; from_list = rad_malloc(sizeof(*from_list) * count); for (vp = paircursor(&cursor, to); vp; vp = pairnext(&cursor)) count++; to_list = rad_malloc(sizeof(*to_list) * count); /* * Move the lists to the arrays, and break the list * chains. */ from_count = 0; for (vp = from; vp != NULL; vp = next) { next = vp->next; from_list[from_count++] = vp; vp->next = NULL; } to_count = 0; for (vp = *to; vp != NULL; vp = next) { next = vp->next; to_list[to_count++] = vp; vp->next = NULL; } tailto = to_count; edited = rad_malloc(sizeof(*edited) * to_count); memset(edited, 0, sizeof(*edited) * to_count); RDEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count); /* * Now that we have the lists initialized, start working * over them. */ for (i = 0; i < from_count; i++) { int found; RDEBUG4("::: Examining %s", from_list[i]->da->name); /* * Attribute should be appended, OR the "to" list * is empty, and we're supposed to replace or * "add if not existing". */ if (from_list[i]->op == T_OP_ADD) goto append; found = false; for (j = 0; j < to_count; j++) { if (edited[j] || !to_list[j] || !from_list[i]) continue; /* * Attributes aren't the same, skip them. */ if (from_list[i]->da != to_list[j]->da) { continue; } /* * We don't use a "switch" statement here * because we want to break out of the * "for" loop over 'j' in most cases. */ /* * Over-write the FIRST instance of the * matching attribute name. We free the * one in the "to" list, and move over * the one in the "from" list. */ if (from_list[i]->op == T_OP_SET) { RDEBUG4("::: OVERWRITING %s FROM %d TO %d", to_list[j]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; break; } /* * Add the attribute only if it does not * exist... but it exists, so we stop * looking. */ if (from_list[i]->op == T_OP_EQ) { found = true; break; } /* * Delete every attribute, independent * of its value. */ if (from_list[i]->op == T_OP_CMP_FALSE) { goto delete; } /* * Delete all matching attributes from * "to" */ if ((from_list[i]->op == T_OP_SUB) || (from_list[i]->op == T_OP_CMP_EQ) || (from_list[i]->op == T_OP_LE) || (from_list[i]->op == T_OP_GE)) { int rcode; int old_op = from_list[i]->op; /* * Check for equality. */ from_list[i]->op = T_OP_CMP_EQ; /* * If equal, delete the one in * the "to" list. */ rcode = radius_compare_vps(NULL, from_list[i], to_list[j]); /* * We may want to do more * subtractions, so we re-set the * operator back to it's original * value. */ from_list[i]->op = old_op; switch (old_op) { case T_OP_CMP_EQ: if (rcode != 0) goto delete; break; case T_OP_SUB: if (rcode == 0) { delete: RDEBUG4("::: DELETING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = NULL; } break; /* * Enforce <=. If it's * >, replace it. */ case T_OP_LE: if (rcode > 0) { RDEBUG4("::: REPLACING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; } break; case T_OP_GE: if (rcode < 0) { RDEBUG4("::: REPLACING %s FROM %d TO %d", from_list[i]->da->name, i, j); pairfree(&to_list[j]); to_list[j] = from_list[i]; from_list[i] = NULL; edited[j] = true; } break; } continue; } rad_assert(0 == 1); /* panic! */ }
/** Convert value_pair_map_t to VALUE_PAIR(s) and add them to a REQUEST. * * Takes a single value_pair_map_t, resolves request and list identifiers * to pointers in the current request, then attempts to retrieve module * specific value(s) using callback, and adds the resulting values to the * correct request/list. * * @param request The current request. * @param map specifying destination attribute and location and src identifier. * @param func to retrieve module specific values and convert them to * VALUE_PAIRS. * @param ctx to be passed to func. * @param src name to be used in debugging if different from map value. * @return -1 if the operation failed, -2 in the source attribute wasn't valid, 0 on success. */ int radius_map2request(REQUEST *request, value_pair_map_t const *map, UNUSED char const *src, radius_tmpl_getvalue_t func, void *ctx) { int rcode; vp_cursor_t cursor; VALUE_PAIR **list, *vp, *head = NULL; char buffer[1024]; if (radius_request(&request, map->dst->request) < 0) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } list = radius_list(request, map->dst->list); if (!list) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } /* * The callback should either return -1 to signify operations error, -2 when it can't find the * attribute or list being referenced, or 0 to signify success. * It may return "sucess", but still have no VPs to work with. * Only if it returned an error code should it not write anything to the head pointer. */ rcode = func(&head, request, map, ctx); if (rcode < 0) { rad_assert(!head); return rcode; } if (!head) return 0; VERIFY_VP(head); if (debug_flag) for (vp = paircursor(&cursor, &head); vp; vp = pairnext(&cursor)) { char *value; switch (map->src->type) { /* * Just print the value being assigned */ default: case VPT_TYPE_LITERAL: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = buffer; break; case VPT_TYPE_XLAT: vp_prints_value(buffer, sizeof(buffer), vp, '"'); value = buffer; break; case VPT_TYPE_DATA: vp_prints_value(buffer, sizeof(buffer), vp, 0); value = buffer; break; /* * Just printing the value doesn't make sense, but we still * want to know what it was... */ case VPT_TYPE_LIST: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = talloc_asprintf(request, "&%s%s -> %s", map->src->name, vp->da->name, buffer); break; case VPT_TYPE_ATTR: vp_prints_value(buffer, sizeof(buffer), vp, '\''); value = talloc_asprintf(request, "&%s -> %s", map->src->name, buffer); break; } RDEBUG("\t\t%s %s %s", map->dst->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), value); if (value != buffer) talloc_free(value); } /* * Use pairmove so the operator is respected */ radius_pairmove(request, list, head); return 0; }
static rlm_rcode_t do_python(rlm_python_t *inst, REQUEST *request, PyObject *pFunc, char const *funcname, bool worker) { vp_cursor_t cursor; VALUE_PAIR *vp; PyObject *pRet = NULL; PyObject *pArgs = NULL; int tuplelen; int ret; PyGILState_STATE gstate; PyThreadState *prev_thread_state = NULL; /* -Wuninitialized */ memset(&gstate, 0, sizeof(gstate)); /* -Wuninitialized */ /* Return with "OK, continue" if the function is not defined. */ if (!pFunc) return RLM_MODULE_NOOP; #ifdef HAVE_PTHREAD_H gstate = PyGILState_Ensure(); if (worker) { PyThreadState *my_thread_state; my_thread_state = fr_thread_local_init(local_thread_state, do_python_cleanup); if (!my_thread_state) { my_thread_state = PyThreadState_New(inst->main_thread_state->interp); if (!my_thread_state) { REDEBUG("Failed initialising local PyThreadState on first run"); PyGILState_Release(gstate); return RLM_MODULE_FAIL; } ret = fr_thread_local_set(local_thread_state, my_thread_state); if (ret != 0) { REDEBUG("Failed storing PyThreadState in TLS: %s", fr_syserror(ret)); PyThreadState_Clear(my_thread_state); PyThreadState_Delete(my_thread_state); PyGILState_Release(gstate); return RLM_MODULE_FAIL; } } prev_thread_state = PyThreadState_Swap(my_thread_state); /* Swap in our local thread state */ } #endif /* Default return value is "OK, continue" */ ret = RLM_MODULE_OK; /* * We will pass a tuple containing (name, value) tuples * We can safely use the Python function to build up a * tuple, since the tuple is not used elsewhere. * * Determine the size of our tuple by walking through the packet. * If request is NULL, pass None. */ tuplelen = 0; if (request != NULL) { for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor)) { tuplelen++; } } if (tuplelen == 0) { Py_INCREF(Py_None); pArgs = Py_None; } else { int i = 0; if ((pArgs = PyTuple_New(tuplelen)) == NULL) { ret = RLM_MODULE_FAIL; goto finish; } for (vp = paircursor(&cursor, &request->packet->vps); vp; vp = pairnext(&cursor), i++) { PyObject *pPair; /* The inside tuple has two only: */ if ((pPair = PyTuple_New(2)) == NULL) { ret = RLM_MODULE_FAIL; goto finish; } if (mod_populate_vptuple(pPair, vp) == 0) { /* Put the tuple inside the container */ PyTuple_SET_ITEM(pArgs, i, pPair); } else { Py_INCREF(Py_None); PyTuple_SET_ITEM(pArgs, i, Py_None); Py_DECREF(pPair); } } } /* Call Python function. */ pRet = PyObject_CallFunctionObjArgs(pFunc, pArgs, NULL); if (!pRet) { ret = RLM_MODULE_FAIL; goto finish; } if (!request) goto finish; /* * The function returns either: * 1. (returnvalue, replyTuple, configTuple), where * - returnvalue is one of the constants RLM_* * - replyTuple and configTuple are tuples of string * tuples of size 2 * * 2. the function return value alone * * 3. None - default return value is set * * xxx This code is messy! */ if (PyTuple_CheckExact(pRet)) { PyObject *pTupleInt; if (PyTuple_GET_SIZE(pRet) != 3) { ERROR("rlm_python:%s: tuple must be (return, replyTuple, configTuple)", funcname); ret = RLM_MODULE_FAIL; goto finish; } pTupleInt = PyTuple_GET_ITEM(pRet, 0); if (!PyInt_CheckExact(pTupleInt)) { ERROR("rlm_python:%s: first tuple element not an integer", funcname); ret = RLM_MODULE_FAIL; goto finish; } /* Now have the return value */ ret = PyInt_AsLong(pTupleInt); /* Reply item tuple */ mod_vptuple(request->reply, &request->reply->vps, PyTuple_GET_ITEM(pRet, 1), funcname); /* Config item tuple */ mod_vptuple(request, &request->config_items, PyTuple_GET_ITEM(pRet, 2), funcname); } else if (PyInt_CheckExact(pRet)) { /* Just an integer */ ret = PyInt_AsLong(pRet); } else if (pRet == Py_None) { /* returned 'None', return value defaults to "OK, continue." */ ret = RLM_MODULE_OK; } else { /* Not tuple or None */ ERROR("rlm_python:%s: function did not return a tuple or None", funcname); ret = RLM_MODULE_FAIL; goto finish; } finish: if (pArgs) { Py_DECREF(pArgs); } if (pRet) { Py_DECREF(pRet); } #ifdef HAVE_PTHREAD_H if (worker) { PyThreadState_Swap(prev_thread_state); } PyGILState_Release(gstate); #endif return ret; }
/* * Initialize the request. */ static int request_init(char const *filename) { FILE *fp; vp_cursor_t cursor; VALUE_PAIR *vp; int filedone = 0; /* * Determine where to read the VP's from. */ if (filename) { fp = fopen(filename, "r"); if (!fp) { fprintf(stderr, "dhcpclient: Error opening %s: %s\n", filename, fr_syserror(errno)); return 0; } } else { fp = stdin; } request = rad_alloc(NULL, 0); /* * Read the VP's. */ request->vps = readvp2(NULL, fp, &filedone, "dhcpclient:"); if (!request->vps) { rad_free(&request); if (fp != stdin) fclose(fp); return 1; } /* * Fix / set various options */ for (vp = paircursor(&cursor, &request->vps); vp; vp = pairnext(&cursor)) { switch (vp->da->attr) { default: break; /* * Allow it to set the packet type in * the attributes read from the file. */ case PW_PACKET_TYPE: request->code = vp->vp_integer; break; case PW_PACKET_DST_PORT: request->dst_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_DST_IP_ADDRESS: request->dst_ipaddr.af = AF_INET; request->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_DST_IPV6_ADDRESS: request->dst_ipaddr.af = AF_INET6; request->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; case PW_PACKET_SRC_PORT: request->src_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_SRC_IP_ADDRESS: request->src_ipaddr.af = AF_INET; request->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_SRC_IPV6_ADDRESS: request->src_ipaddr.af = AF_INET6; request->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; } /* switch over the attribute */ } /* loop over the VP's we read in */ if (fp != stdin) fclose(fp); /* * And we're done. */ return 1; }
/* * Common attr_filter checks */ static rlm_rcode_t attr_filter_common(void *instance, REQUEST *request, RADIUS_PACKET *packet) { rlm_attr_filter_t *inst = instance; vp_cursor_t cursor; vp_cursor_t out; VALUE_PAIR *vp; VALUE_PAIR *output, *input; VALUE_PAIR *check_item; PAIR_LIST *pl; int found = 0; int pass, fail = 0; char const *keyname = NULL; char buffer[256]; if (!packet) return RLM_MODULE_NOOP; if (!inst->key) { VALUE_PAIR *namepair; namepair = pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY); if (!namepair) { return (RLM_MODULE_NOOP); } keyname = namepair->vp_strvalue; } else { int len; len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } if (len == 0) { return RLM_MODULE_NOOP; } keyname = buffer; } /* * Head of the output list */ output = NULL; paircursor(&out, &output); /* * Find the attr_filter profile entry for the entry. */ for (pl = inst->attrs; pl; pl = pl->next) { int fall_through = 0; int relax_filter = inst->relaxed; /* * If the current entry is NOT a default, * AND the realm does NOT match the current entry, * then skip to the next entry. */ if ((strcmp(pl->name, "DEFAULT") != 0) && (strcmp(keyname, pl->name) != 0)) { continue; } RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno); found = 1; for (check_item = paircursor(&cursor, &pl->check); check_item; check_item = pairnext(&cursor)) { if (!check_item->da->vendor && (check_item->da->attr == PW_FALL_THROUGH) && (check_item->vp_integer == 1)) { fall_through = 1; continue; } else if (!check_item->da->vendor && check_item->da->attr == PW_RELAX_FILTER) { relax_filter = check_item->vp_integer; continue; } /* * If it is a SET operator, add the attribute to * the output list without checking it. */ if (check_item->op == T_OP_SET ) { vp = paircopyvp(packet, check_item); if (!vp) { goto error; } radius_xlat_do(request, vp); pairinsert(&out, vp); } } /* * Iterate through the input items, comparing * each item to every rule, then moving it to the * output list only if it matches all rules * for that attribute. IE, Idle-Timeout is moved * only if it matches all rules that describe an * Idle-Timeout. */ for (input = paircursor(&cursor, &packet->vps); input; input = pairnext(&cursor)) { /* reset the pass,fail vars for each reply item */ pass = fail = 0; /* * reset the check_item pointer to * beginning of the list */ for (check_item = pairfirst(&cursor); check_item; check_item = pairnext(&cursor)) { /* * Vendor-Specific is special, and * matches any VSA if the comparison * is always true. */ if ((check_item->da->attr == PW_VENDOR_SPECIFIC) && (input->da->vendor != 0) && (check_item->op == T_OP_CMP_TRUE)) { pass++; continue; } if (vp->da->attr == check_item->da->attr) { check_pair(check_item, input, &pass, &fail); } } /* * Only move attribute if it passed all rules, * or if the config says we should copy unmatched * attributes ('relaxed' mode). */ if (fail == 0 && (pass > 0 || relax_filter)) { if (!pass) { RDEBUG3("Attribute (%s) allowed by relaxed mode", vp->da->name); } vp = paircopyvp(packet, vp); if (!vp) { goto error; } pairinsert(&out, vp); } } /* If we shouldn't fall through, break */ if (!fall_through) { break; } } /* * No entry matched. We didn't do anything. */ if (!found) { rad_assert(!output); return RLM_MODULE_NOOP; } /* * Replace the existing request list with our filtered one */ pairfree(&packet->vps); packet->vps = output; if (request->packet->code == PW_AUTHENTICATION_REQUEST) { request->username = pairfind(request->packet->vps, PW_STRIPPED_USER_NAME, 0, TAG_ANY); if (!request->username) request->username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); } return RLM_MODULE_UPDATED; error: pairfree(&output); return RLM_MODULE_FAIL; }
/** Compare two pair lists except for the password information. * * For every element in "check" at least one matching copy must be present * in "reply". * * @param[in] request Current request. * @param[in] req_list request valuepairs. * @param[in] check Check/control valuepairs. * @param[in,out] rep_list Reply value pairs. * * @return 0 on match. */ int paircompare(REQUEST *request, VALUE_PAIR *req_list, VALUE_PAIR *check, VALUE_PAIR **rep_list) { vp_cursor_t cursor; VALUE_PAIR *check_item; VALUE_PAIR *auth_item; DICT_ATTR const *from; int result = 0; int compare; bool first_only; for (check_item = paircursor(&cursor, &check); check_item; check_item = pairnext(&cursor)) { /* * If the user is setting a configuration value, * then don't bother comparing it to any attributes * sent to us by the user. It ALWAYS matches. */ if ((check_item->op == T_OP_SET) || (check_item->op == T_OP_ADD)) { continue; } if (!check_item->da->vendor) switch (check_item->da->attr) { /* * Attributes we skip during comparison. * These are "server" check items. */ case PW_CRYPT_PASSWORD: case PW_AUTH_TYPE: case PW_AUTZ_TYPE: case PW_ACCT_TYPE: case PW_SESSION_TYPE: case PW_STRIP_USER_NAME: continue; break; /* * IF the password attribute exists, THEN * we can do comparisons against it. If not, * then the request did NOT contain a * User-Password attribute, so we CANNOT do * comparisons against it. * * This hack makes CHAP-Password work.. */ case PW_USER_PASSWORD: if (check_item->op == T_OP_CMP_EQ) { WDEBUG("Found User-Password == \"...\"."); WDEBUG("Are you sure you don't mean Cleartext-Password?"); WDEBUG("See \"man rlm_pap\" for more information"); } if (pairfind(req_list, PW_USER_PASSWORD, 0, TAG_ANY) == NULL) { continue; } break; } /* * See if this item is present in the request. */ first_only = otherattr(check_item->da, &from); auth_item = req_list; try_again: if (!first_only) { while (auth_item != NULL) { if ((auth_item->da == from) || (!from)) { break; } auth_item = auth_item->next; } } /* * Not found, it's not a match. */ if (auth_item == NULL) { /* * Didn't find it. If we were *trying* * to not find it, then we succeeded. */ if (check_item->op == T_OP_CMP_FALSE) { continue; } else { return -1; } } /* * Else we found it, but we were trying to not * find it, so we failed. */ if (check_item->op == T_OP_CMP_FALSE) { return -1; } /* * We've got to xlat the string before doing * the comparison. */ radius_xlat_do(request, check_item); /* * OK it is present now compare them. */ compare = radius_callback_compare(request, auth_item, check_item, check, rep_list); switch (check_item->op) { case T_OP_EQ: default: INFO("Invalid operator for item %s: " "reverting to '=='", check_item->da->name); /* FALL-THROUGH */ case T_OP_CMP_TRUE: case T_OP_CMP_FALSE: case T_OP_CMP_EQ: if (compare != 0) result = -1; break; case T_OP_NE: if (compare == 0) result = -1; break; case T_OP_LT: if (compare >= 0) result = -1; break; case T_OP_GT: if (compare <= 0) result = -1; break; case T_OP_LE: if (compare > 0) result = -1; break; case T_OP_GE: if (compare < 0) result = -1; break; #ifdef HAVE_REGEX_H case T_OP_REG_EQ: case T_OP_REG_NE: if (compare != 0) result = -1; break; #endif } /* switch over the operator of the check item */ /* * This attribute didn't match, but maybe there's * another of the same attribute, which DOES match. */ if ((result != 0) && (!first_only)) { auth_item = auth_item->next; result = 0; goto try_again; } } /* for every entry in the check item list */ return result; }
/** 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; }
/* * given a radius request with many attribues in the EAP-SIM range, build * them all into a single EAP-SIM body. * */ int map_eapsim_basictypes(RADIUS_PACKET *r, eap_packet_t *ep) { VALUE_PAIR *vp; int encoded_size; uint8_t *encodedmsg, *attr; unsigned int id, eapcode; uint8_t *macspace; uint8_t const *append; int appendlen; unsigned char subtype; vp_cursor_t cursor; macspace = NULL; append = NULL; appendlen = 0; /* * encodedmsg is now an EAP-SIM message. * it might be too big for putting into an EAP-Type-SIM * */ subtype = (vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_SUBTYPE, 0, TAG_ANY)) ? vp->vp_integer : eapsim_start; id = (vp = pairfind(r->vps, ATTRIBUTE_EAP_ID, 0, TAG_ANY)) ? vp->vp_integer : ((int)getpid() & 0xff); eapcode = (vp = pairfind(r->vps, ATTRIBUTE_EAP_CODE, 0, TAG_ANY)) ? vp->vp_integer : PW_EAP_REQUEST; /* * take a walk through the attribute list to see how much space * that we need to encode all of this. */ encoded_size = 0; for (vp = paircursor(&cursor, &r->vps); vp; vp = pairnext(&cursor)) { int roundedlen; int vplen; if ((vp->da->attr < ATTRIBUTE_EAP_SIM_BASE) || (vp->da->attr >= (ATTRIBUTE_EAP_SIM_BASE + 256))) { continue; } vplen = vp->length; /* * the AT_MAC attribute is a bit different, when we get to this * attribute, we pull the contents out, save it for later * processing, set the size to 16 bytes (plus 2 bytes padding). * * At this point, we only care about the size. */ if(vp->da->attr == ATTRIBUTE_EAP_SIM_BASE + PW_EAP_SIM_MAC) { vplen = 18; } /* round up to next multiple of 4, after taking in * account the type and length bytes */ roundedlen = (vplen + 2 + 3) & ~3; encoded_size += roundedlen; } if (ep->code != PW_EAP_SUCCESS) { ep->code = eapcode; } ep->id = (id & 0xff); ep->type.num = PW_EAP_SIM; /* * if no attributes were found, do very little. * */ if (encoded_size == 0) { encodedmsg = talloc_array(ep, uint8_t, 3); /* FIX: could be NULL */ encodedmsg[0] = subtype; encodedmsg[1] = 0; encodedmsg[2] = 0; ep->type.length = 3; ep->type.data = encodedmsg; return 0; } /* * figured out the length, so allocate some space for the results. * * Note that we do not bother going through an "EAP" stage, which * is a bit strange compared to the unmap, which expects to see * an EAP-SIM virtual attributes. * * EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead. * * SIM code adds a subtype, and 2 bytes of reserved = 3. * */ encoded_size += 3; encodedmsg = talloc_array(ep, uint8_t, encoded_size); if (!encodedmsg) { return 0; } memset(encodedmsg, 0, encoded_size); /* * now walk the attributes again, sticking them in. * * we go three bytes into the encoded message, because there are two * bytes of reserved, and we will fill the "subtype" in later. * */ attr = encodedmsg+3; for (vp = pairfirst(&cursor); vp; vp = pairnext(&cursor)) { int roundedlen; if(vp->da->attr < ATTRIBUTE_EAP_SIM_BASE || vp->da->attr >= ATTRIBUTE_EAP_SIM_BASE + 256) { continue; } /* * the AT_MAC attribute is a bit different, when we get to this * attribute, we pull the contents out, save it for later * processing, set the size to 16 bytes (plus 2 bytes padding). * * At this point, we put in zeros, and remember where the * sixteen bytes go. */ if(vp->da->attr == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) { roundedlen = 20; memset(&attr[2], 0, 18); macspace = &attr[4]; append = vp->vp_octets; appendlen = vp->length; } else { roundedlen = (vp->length + 2 + 3) & ~3; memset(attr, 0, roundedlen); memcpy(&attr[2], vp->vp_strvalue, vp->length); } attr[0] = vp->da->attr - ATTRIBUTE_EAP_SIM_BASE; attr[1] = roundedlen >> 2; attr += roundedlen; } encodedmsg[0] = subtype; ep->type.length = encoded_size; ep->type.data = encodedmsg; /* * if macspace was set and we have a key, * then we should calculate the HMAC-SHA1 of the resulting EAP-SIM * packet, appended with the value of append. */ vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_KEY, 0, TAG_ANY); if(macspace != NULL && vp != NULL) { unsigned char *buffer; eap_packet_raw_t *hdr; uint16_t hmaclen, total_length = 0; unsigned char sha1digest[20]; total_length = EAP_HEADER_LEN + 1 + encoded_size; hmaclen = total_length + appendlen; buffer = talloc_array(r, uint8_t, hmaclen); hdr = (eap_packet_raw_t *) buffer; if (!hdr) { talloc_free(encodedmsg); return 0; } hdr->code = eapcode & 0xFF; hdr->id = (id & 0xFF); total_length = htons(total_length); memcpy(hdr->length, &total_length, sizeof(total_length)); hdr->data[0] = PW_EAP_SIM; /* copy the data */ memcpy(&hdr->data[1], encodedmsg, encoded_size); /* copy the nonce */ memcpy(&hdr->data[encoded_size+1], append, appendlen); /* HMAC it! */ fr_hmac_sha1(buffer, hmaclen, vp->vp_octets, vp->length, sha1digest); /* done with the buffer, free it */ free(buffer); /* now copy the digest to where it belongs in the AT_MAC */ /* note that it is truncated to 128-bits */ memcpy(macspace, sha1digest, 16); } /* if we had an AT_MAC and no key, then fail */ if ((macspace != NULL) && !vp) { if (encodedmsg != NULL) { talloc_free(encodedmsg); } return 0; } return 1; }
/** Print out attribute info * * Prints out all instances of a current attribute, or all attributes in a list. * * At higher debugging levels, also prints out alternative decodings of the same * value. This is helpful to determine types for unknown attributes of long * passed vendors, or just crazy/broken NAS. * * It's also useful for exposing issues in the packet decoding functions, as in * some cases they get fed random garbage data. * * This expands to a zero length string. */ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, UNUSED size_t outlen) { VALUE_PAIR *vp, **vps; value_pair_tmpl_t vpt; vp_cursor_t cursor; char buffer[1024]; if (!RDEBUG_ENABLED2) { *out = '\0'; return -1; } while (isspace((int) *fmt)) fmt++; if (*fmt == '&') fmt++; if (radius_parse_attr(fmt, &vpt, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { return -1; } vps = radius_list(request, vpt.list); if (!vps) { return -2; } RIDEBUG("Attributes matching \"%s\"", fmt); vp = paircursor(&cursor, vps); if (vpt.da) { vp = pairfindnext(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } while (vp) { DICT_ATTR *dac = NULL; DICT_VENDOR *dv; VALUE_PAIR *vpc = NULL; FR_NAME_NUMBER const *type; vp_prints_value(buffer, sizeof(buffer), vp, '\''); if (vp->da->flags.has_tag) { RIDEBUG2("\t%s:%s:%i %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, vp->tag, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } else { RIDEBUG2("\t%s:%s %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } if (!RDEBUG_ENABLED3) { goto next_vp; } if (vp->da->vendor) { dv = dict_vendorbyvalue(vp->da->vendor); RDEBUG3("\t\tvendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown"); } RDEBUG3("\t\ttype : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")); RDEBUG3("\t\tlength : %zu", vp->length); dac = talloc_memdup(request, vp->da, sizeof(DICT_ATTR)); if (!dac) { return -1; } dac->flags.vp_free = 0; if (!RDEBUG_ENABLED4) { goto next_vp; } type = dict_attr_types; while (type->name) { int pad; ssize_t len; uint8_t *data = NULL; vpc = NULL; if ((PW_TYPE) type->number == vp->da->type) { goto next_type; } switch (type->number) { case PW_TYPE_INVALID: /* Not real type */ case PW_TYPE_MAX: /* Not real type */ case PW_TYPE_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_LONG_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_TLV: /* Not safe/appropriate */ case PW_TYPE_VSA: /* @fixme We need special behaviour for these */ goto next_type; default: break; } dac->type = type->number; data = talloc_array(request, uint8_t , vp->length); len = rad_vp2data(vp, data, vp->length); if (data2vp(NULL, NULL, NULL, dac, data, len, len, &vpc) < 0) { goto next_type; } /* * data2vp has knowledge of expected format lengths, if the length * from rad_vp2data doesn't match, it encodes the attribute * as raw octets. This results in many useless debug lines with * the same hex string. */ if ((type->number != PW_TYPE_OCTETS) && (vpc->da->type == PW_TYPE_OCTETS)) { goto next_type; } if (!vp_prints_value(buffer, sizeof(buffer), vpc, '\'')) { goto next_type; } if ((pad = (11 - strlen(type->name))) < 0) { pad = 0; } /* * @fixme: if the value happens to decode as a VSA * (someone put a VSA into a VSA?), we probably to print * extended info for that/reparse */ RDEBUG4("\t\tas %s%*s: %s", type->name, pad, " ", buffer); next_type: talloc_free(data); pairbasicfree(vpc); type++; } next_vp: talloc_free(dac); if (vpt.da) { vp = pairfindnext(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } else { vp = pairnext(&cursor); } } *out = '\0'; return 0; }
/* * Initialize a radclient data structure and add it to * the global linked list. */ static int radclient_init(char const *filename) { FILE *fp; vp_cursor_t cursor; VALUE_PAIR *vp; radclient_t *radclient; int filedone = 0; int packet_number = 1; assert(filename != NULL); /* * Determine where to read the VP's from. */ if (strcmp(filename, "-") != 0) { fp = fopen(filename, "r"); if (!fp) { fprintf(stderr, "radclient: Error opening %s: %s\n", filename, strerror(errno)); return 0; } } else { fp = stdin; } /* * Loop until the file is done. */ do { /* * Allocate it. */ radclient = malloc(sizeof(*radclient)); if (!radclient) { goto oom; } memset(radclient, 0, sizeof(*radclient)); radclient->request = rad_alloc(NULL, 1); if (!radclient->request) { goto oom; } #ifdef WITH_TCP radclient->request->src_ipaddr = client_ipaddr; radclient->request->src_port = client_port; radclient->request->dst_ipaddr = server_ipaddr; radclient->request->dst_port = server_port; #endif radclient->filename = filename; radclient->request->id = -1; /* allocate when sending */ radclient->packet_number = packet_number++; /* * Read the VP's. */ radclient->request->vps = readvp2(NULL, fp, &filedone, "radclient:"); if (!radclient->request->vps) { rad_free(&radclient->request); free(radclient); if (fp != stdin) fclose(fp); return 1; } /* * Keep a copy of the the User-Password attribute. */ if ((vp = pairfind(radclient->request->vps, PW_USER_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(radclient->password, vp->vp_strvalue, sizeof(radclient->password)); /* * Otherwise keep a copy of the CHAP-Password attribute. */ } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(radclient->password, vp->vp_strvalue, sizeof(radclient->password)); } else if ((vp = pairfind(radclient->request->vps, PW_MSCHAP_PASSWORD, 0, TAG_ANY)) != NULL) { strlcpy(radclient->password, vp->vp_strvalue, sizeof(radclient->password)); } else { radclient->password[0] = '\0'; } /* * Fix up Digest-Attributes issues */ for (vp = paircursor(&cursor, &radclient->request->vps); vp; vp = pairnext(&cursor)) { /* * Double quoted strings get marked up as xlat expansions, * but we don't support that in radclient. */ if (vp->type == VT_XLAT) { vp->vp_strvalue = vp->value.xlat; vp->value.xlat = NULL; vp->type = VT_DATA; } if (!vp->da->vendor) switch (vp->da->attr) { default: break; /* * Allow it to set the packet type in * the attributes read from the file. */ case PW_PACKET_TYPE: radclient->request->code = vp->vp_integer; break; case PW_PACKET_DST_PORT: radclient->request->dst_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_DST_IP_ADDRESS: radclient->request->dst_ipaddr.af = AF_INET; radclient->request->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_DST_IPV6_ADDRESS: radclient->request->dst_ipaddr.af = AF_INET6; radclient->request->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; case PW_PACKET_SRC_PORT: radclient->request->src_port = (vp->vp_integer & 0xffff); break; case PW_PACKET_SRC_IP_ADDRESS: radclient->request->src_ipaddr.af = AF_INET; radclient->request->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr; break; case PW_PACKET_SRC_IPV6_ADDRESS: radclient->request->src_ipaddr.af = AF_INET6; radclient->request->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; break; case PW_DIGEST_REALM: case PW_DIGEST_NONCE: case PW_DIGEST_METHOD: case PW_DIGEST_URI: case PW_DIGEST_QOP: case PW_DIGEST_ALGORITHM: case PW_DIGEST_BODY_DIGEST: case PW_DIGEST_CNONCE: case PW_DIGEST_NONCE_COUNT: case PW_DIGEST_USER_NAME: /* overlapping! */ { DICT_ATTR const *da; uint8_t *p; p = talloc_array(vp, uint8_t, vp->length + 2); memcpy(p + 2, vp->vp_octets, vp->length); p[0] = vp->da->attr - PW_DIGEST_REALM + 1; vp->length += 2; p[1] = vp->length; // talloc_free(vp->vp_octets); vp->vp_octets = p; da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0); if (!da) { goto oom; } vp->da = da; } break; } } /* loop over the VP's we read in */ /* * Add it to the tail of the list. */ if (!radclient_head) { assert(radclient_tail == NULL); radclient_head = radclient; radclient->prev = NULL; } else { assert(radclient_tail->next == NULL); radclient_tail->next = radclient; radclient->prev = radclient_tail; } radclient_tail = radclient; radclient->next = NULL; } while (!filedone); /* loop until the file is done. */ if (fp != stdin) fclose(fp); /* * And we're done. */ return 1; oom: fprintf(stderr, "radclient: Out of memory\n"); free(radclient); if (fp != stdin) fclose(fp); return 0; }
static int getusersfile(TALLOC_CTX *ctx, char const *filename, fr_hash_table_t **pht, char *compat_mode_str) { int rcode; PAIR_LIST *users = NULL; PAIR_LIST *entry, *next; fr_hash_table_t *ht, *tailht; int order = 0; if (!filename) { *pht = NULL; return 0; } rcode = pairlist_read(ctx, filename, &users, 1); if (rcode < 0) { return -1; } /* * Walk through the 'users' file list, if we're debugging, * or if we're in compat_mode. */ if ((debug_flag) || (strcmp(compat_mode_str, "cistron") == 0)) { VALUE_PAIR *vp; int compat_mode = false; if (strcmp(compat_mode_str, "cistron") == 0) { compat_mode = true; } entry = users; while (entry) { vp_cursor_t cursor; if (compat_mode) { DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...", filename, entry->lineno, entry->name); } /* * Look for improper use of '=' in the * check items. They should be using * '==' for on-the-wire RADIUS attributes, * and probably ':=' for server * configuration items. */ for (vp = paircursor(&cursor, &entry->check); vp; vp = pairnext(&cursor)) { /* * Ignore attributes which are set * properly. */ if (vp->op != T_OP_EQ) { continue; } /* * If it's a vendor attribute, * or it's a wire protocol, * ensure it has '=='. */ if ((vp->da->vendor != 0) || (vp->da->attr < 0x100)) { if (!compat_mode) { WDEBUG("[%s]:%d Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s", filename, entry->lineno, vp->da->name, vp->da->name, entry->name); } else { DEBUG("\tChanging '%s =' to '%s =='", vp->da->name, vp->da->name); } vp->op = T_OP_CMP_EQ; continue; } /* * Cistron Compatibility mode. * * Re-write selected attributes * to be '+=', instead of '='. * * All others get set to '==' */ if (compat_mode) { /* * Non-wire attributes become += * * On the write attributes * become == */ if ((vp->da->attr >= 0x100) && (vp->da->attr <= 0xffff) && (vp->da->attr != PW_HINT) && (vp->da->attr != PW_HUNTGROUP_NAME)) { DEBUG("\tChanging '%s =' to '%s +='", vp->da->name, vp->da->name); vp->op = T_OP_ADD; } else { DEBUG("\tChanging '%s =' to '%s =='", vp->da->name, vp->da->name); vp->op = T_OP_CMP_EQ; } } } /* end of loop over check items */ /* * Look for server configuration items * in the reply list. * * It's a common enough mistake, that it's * worth doing. */ for (vp = paircursor(&cursor, &entry->reply); vp; vp = pairnext(&cursor)) { /* * If it's NOT a vendor attribute, * and it's NOT a wire protocol * and we ignore Fall-Through, * then bitch about it, giving a * good warning message. */ if ((vp->da->vendor == 0) && (vp->da->attr > 0xff) && (vp->da->attr > 1000)) { WDEBUG("[%s]:%d Check item \"%s\"\n" "\tfound in reply item list for user \"%s\".\n" "\tThis attribute MUST go on the first line" " with the other check items", filename, entry->lineno, vp->da->name, entry->name); } } entry = entry->next; } } ht = fr_hash_table_create(pairlist_hash, pairlist_cmp, my_pairlist_free); if (!ht) { pairlist_free(&users); return -1; } tailht = fr_hash_table_create(pairlist_hash, pairlist_cmp, NULL); if (!tailht) { fr_hash_table_free(ht); pairlist_free(&users); return -1; } /* * Now that we've read it in, put the entries into a hash * for faster access. */ for (entry = users; entry != NULL; entry = next) { PAIR_LIST *tail; next = entry->next; entry->next = NULL; entry->order = order++; /* * Insert it into the hash table, and remember * the tail of the linked list. */ tail = fr_hash_table_finddata(tailht, entry); if (!tail) { /* * Insert it into the head & tail. */ if (!fr_hash_table_insert(ht, entry) || !fr_hash_table_insert(tailht, entry)) { pairlist_free(&next); fr_hash_table_free(ht); fr_hash_table_free(tailht); return -1; } } else { tail->next = entry; if (!fr_hash_table_replace(tailht, entry)) { pairlist_free(&next); fr_hash_table_free(ht); fr_hash_table_free(tailht); return -1; } } } fr_hash_table_free(tailht); *pht = ht; return 0; }
int main(int argc, char *argv[]) { fr_pcap_t *in = NULL, *in_p; fr_pcap_t **in_head = ∈ fr_pcap_t *out = NULL; int ret = 1; /* Exit status */ int limit = -1; /* How many packets to sniff */ char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */ int port = 1812; char buffer[1024]; int opt; FR_TOKEN parsecode; char const *radius_dir = RADIUS_DIR; rs_stats_t stats; fr_debug_flag = 1; log_dst = stdout; talloc_set_log_stderr(); conf = talloc_zero(NULL, rs_t); if (!fr_assert(conf)) { exit (1); } /* * We don't really want probes taking down machines */ #ifdef HAVE_TALLOC_SET_MEMLIMIT /* * @fixme causes hang in talloc steal */ //talloc_set_memlimit(conf, 524288000); /* 50 MB */ #endif /* * Set some defaults */ conf->print_packet = true; conf->limit = -1; conf->promiscuous = true; #ifdef HAVE_COLLECTDC_H conf->stats.prefix = RS_DEFAULT_PREFIX; #endif conf->radius_secret = RS_DEFAULT_SECRET; /* * Get options */ while ((opt = getopt(argc, argv, "c:d:DFf:hi:I:mp:qr:s:Svw:xXW:T:P:O:")) != EOF) { switch (opt) { case 'c': limit = atoi(optarg); if (limit <= 0) { fprintf(stderr, "radsniff: Invalid number of packets \"%s\"", optarg); exit(1); } break; case 'd': radius_dir = optarg; break; case 'D': { pcap_if_t *all_devices = NULL; pcap_if_t *dev_p; if (pcap_findalldevs(&all_devices, errbuf) < 0) { ERROR("Error getting available capture devices: %s", errbuf); goto finish; } int i = 1; for (dev_p = all_devices; dev_p; dev_p = dev_p->next) { INFO("%i.%s", i++, dev_p->name); } ret = 0; goto finish; } case 'F': conf->from_stdin = true; conf->to_stdout = true; break; case 'f': conf->pcap_filter = optarg; break; case 'h': usage(0); break; case 'i': *in_head = fr_pcap_init(conf, optarg, PCAP_INTERFACE_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; conf->from_dev = true; break; case 'I': *in_head = fr_pcap_init(conf, optarg, PCAP_FILE_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; conf->from_file = true; break; case 'm': conf->promiscuous = false; break; case 'p': port = atoi(optarg); break; case 'q': if (fr_debug_flag > 0) { fr_debug_flag--; } break; case 'r': conf->radius_filter = optarg; break; case 's': conf->radius_secret = optarg; break; case 'S': conf->do_sort = true; break; case 'v': #ifdef HAVE_COLLECTDC_H INFO("%s, %s, collectdclient version %s", radsniff_version, pcap_lib_version(), lcc_version_string()); #else INFO("%s %s", radsniff_version, pcap_lib_version()); #endif exit(0); break; case 'w': out = fr_pcap_init(conf, optarg, PCAP_FILE_OUT); conf->to_file = true; break; case 'x': case 'X': fr_debug_flag++; break; case 'W': conf->stats.interval = atoi(optarg); conf->print_packet = false; if (conf->stats.interval <= 0) { ERROR("Stats interval must be > 0"); usage(64); } break; case 'T': conf->stats.timeout = atoi(optarg); if (conf->stats.timeout <= 0) { ERROR("Timeout value must be > 0"); usage(64); } break; #ifdef HAVE_COLLECTDC_H case 'P': conf->stats.prefix = optarg; break; case 'O': conf->stats.collectd = optarg; conf->stats.out = RS_STATS_OUT_COLLECTD; break; #endif default: usage(64); } } /* What's the point in specifying -F ?! */ if (conf->from_stdin && conf->from_file && conf->to_file) { usage(64); } /* Can't read from both... */ if (conf->from_file && conf->from_dev) { usage(64); } /* Reading from file overrides stdin */ if (conf->from_stdin && (conf->from_file || conf->from_dev)) { conf->from_stdin = false; } /* Writing to file overrides stdout */ if (conf->to_file && conf->to_stdout) { conf->to_stdout = false; } if (conf->to_stdout) { out = fr_pcap_init(conf, "stdout", PCAP_STDIO_OUT); if (!out) { goto finish; } } if (conf->from_stdin) { *in_head = fr_pcap_init(conf, "stdin", PCAP_STDIO_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; } if (conf->stats.interval && !conf->stats.out) { conf->stats.out = RS_STATS_OUT_STDIO; } if (conf->stats.timeout == 0) { conf->stats.timeout = RS_DEFAULT_TIMEOUT; } /* * If were writing pcap data stdout we *really* don't want to send * logging there as well. */ log_dst = conf->to_stdout ? stderr : stdout; #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN) if (conf->from_stdin || conf->to_stdout) { ERROR("PCAP streams not supported"); goto finish; } #endif if (!conf->pcap_filter) { snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d", port, port + 1, 3799); conf->pcap_filter = buffer; } if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) { fr_perror("radsniff"); ret = 64; goto finish; } fr_strerror(); /* Clear out any non-fatal errors */ if (conf->radius_filter) { vp_cursor_t cursor; VALUE_PAIR *vp; parsecode = userparse(NULL, conf->radius_filter, &filter_vps); if (parsecode == T_OP_INVALID) { ERROR("Invalid RADIUS filter \"%s\" (%s)", conf->radius_filter, fr_strerror()); ret = 64; goto finish; } if (!filter_vps) { ERROR("Empty RADIUS filter \"%s\"", conf->radius_filter); ret = 64; goto finish; } for (vp = paircursor(&cursor, &filter_vps); vp; vp = pairnext(&cursor)) { /* * xlat expansion isn't support hered */ if (vp->type == VT_XLAT) { vp->type = VT_DATA; vp->vp_strvalue = vp->value.xlat; } } } /* * Setup the request tree */ request_tree = rbtree_create((rbcmp) rs_packet_cmp, _rb_rad_free, 0); if (!request_tree) { ERROR("Failed creating request tree"); goto finish; } /* * Get the default capture device */ if (!conf->from_stdin && !conf->from_file && !conf->from_dev) { pcap_if_t *all_devices; /* List of all devices libpcap can listen on */ pcap_if_t *dev_p; if (pcap_findalldevs(&all_devices, errbuf) < 0) { ERROR("Error getting available capture devices: %s", errbuf); goto finish; } if (!all_devices) { ERROR("No capture files specified and no live interfaces available"); ret = 64; goto finish; } for (dev_p = all_devices; dev_p; dev_p = dev_p->next) { /* Don't use the any devices, it's horribly broken */ if (!strcmp(dev_p->name, "any")) continue; *in_head = fr_pcap_init(conf, dev_p->name, PCAP_INTERFACE_IN); in_head = &(*in_head)->next; } conf->from_auto = true; conf->from_dev = true; INFO("Defaulting to capture on all interfaces"); } /* * Print captures values which will be used */ if (fr_debug_flag > 2) { DEBUG2("Sniffing with options:"); if (conf->from_dev) { char *buff = fr_pcap_device_names(conf, in, ' '); DEBUG2(" Device(s) : [%s]", buff); talloc_free(buff); } if (conf->to_file || conf->to_stdout) { DEBUG2(" Writing to : [%s]", out->name); } if (conf->limit > 0) { DEBUG2(" Capture limit (packets) : [%" PRIu64 "]", conf->limit); } DEBUG2(" PCAP filter : [%s]", conf->pcap_filter); DEBUG2(" RADIUS secret : [%s]", conf->radius_secret); if (filter_vps){ DEBUG2(" RADIUS filter :"); vp_printlist(log_dst, filter_vps); } } /* * Open our interface to collectd */ #ifdef HAVE_COLLECTDC_H if (conf->stats.out == RS_STATS_OUT_COLLECTD) { size_t i; rs_stats_tmpl_t *tmpl, **next; if (rs_stats_collectd_open(conf) < 0) { exit(1); } next = &conf->stats.tmpl; for (i = 0; i < (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes)); i++) { tmpl = rs_stats_collectd_init_latency(conf, next, conf, "exchanged", &stats.exchange[rs_useful_codes[i]], rs_useful_codes[i]); if (!tmpl) { ERROR("Error allocating memory for stats template"); goto finish; } next = &(tmpl->next); } } #endif /* * This actually opens the capture interfaces/files (we just allocated the memory earlier) */ { fr_pcap_t *tmp; fr_pcap_t **tmp_p = &tmp; for (in_p = in; in_p; in_p = in_p->next) { in_p->promiscuous = conf->promiscuous; if (fr_pcap_open(in_p) < 0) { if (!conf->from_auto) { ERROR("Failed opening pcap handle for %s", in_p->name); goto finish; } DEBUG("Failed opening pcap handle: %s", fr_strerror()); continue; } if (conf->pcap_filter) { if (fr_pcap_apply_filter(in_p, conf->pcap_filter) < 0) { ERROR("Failed applying filter"); goto finish; } } *tmp_p = in_p; tmp_p = &(in_p->next); } *tmp_p = NULL; in = tmp; } /* * Open our output interface (if we have one); */ if (out) { if (fr_pcap_open(out) < 0) { ERROR("Failed opening pcap output"); goto finish; } } /* * Setup signal handlers so we always exit gracefully, ensuring output buffers are always * flushed. */ { #ifdef HAVE_SIGACTION struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = rs_cleanup; sigaction(SIGINT, &action, NULL); sigaction(SIGQUIT, &action, NULL); #else signal(SIGINT, rs_cleanup); # ifdef SIGQUIT signal(SIGQUIT, rs_cleanup); # endif #endif } /* * Setup and enter the main event loop. Who needs libev when you can roll your own... */ { struct timeval now; rs_update_t update; char *buff; memset(&stats, 0, sizeof(stats)); memset(&update, 0, sizeof(update)); events = fr_event_list_create(conf, _rs_event_status); if (!events) { ERROR(); goto finish; } for (in_p = in; in_p; in_p = in_p->next) { rs_event_t *event; event = talloc_zero(events, rs_event_t); event->list = events; event->in = in_p; event->out = out; event->stats = &stats; if (!fr_event_fd_insert(events, 0, in_p->fd, rs_got_packet, event)) { ERROR("Failed inserting file descriptor"); goto finish; } } buff = fr_pcap_device_names(conf, in, ' '); INFO("Sniffing on (%s)", buff); talloc_free(buff); gettimeofday(&now, NULL); /* * Insert our stats processor */ if (conf->stats.interval) { update.list = events; update.stats = &stats; update.in = in; now.tv_sec += conf->stats.interval; now.tv_usec = 0; fr_event_insert(events, rs_stats_process, (void *) &update, &now, NULL); } ret = fr_event_loop(events); /* Enter the main event loop */ } INFO("Done sniffing"); finish: cleanup = true; /* * Free all the things! This also closes all the sockets and file descriptors */ talloc_free(conf); return ret; }