static void perl_vp_to_svpvn_element(REQUEST *request, AV *av, VALUE_PAIR const *vp, int *i, const char *hash_name, const char *list_name) { size_t len; char buffer[1024]; switch (vp->da->type) { case PW_TYPE_STRING: RDEBUG("$%s{'%s'}[%i] = &%s:%s -> '%s'", hash_name, vp->da->name, *i, list_name, vp->da->name, vp->vp_strvalue); av_push(av, newSVpvn(vp->vp_strvalue, vp->vp_length)); break; case PW_TYPE_OCTETS: if (RDEBUG_ENABLED) { char *hex; hex = fr_abin2hex(request, vp->vp_octets, vp->vp_length); RDEBUG("$%s{'%s'}[%i] = &%s:%s -> 0x%s", hash_name, vp->da->name, *i, list_name, vp->da->name, hex); talloc_free(hex); } av_push(av, newSVpvn((char const *)vp->vp_octets, vp->vp_length)); break; default: len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, 0); RDEBUG("$%s{'%s'}[%i] = &%s:%s -> '%s'", hash_name, vp->da->name, *i, list_name, vp->da->name, buffer); av_push(av, newSVpvn(buffer, truncate_len(len, sizeof(buffer)))); break; } (*i)++; }
static void perl_vp_to_svpvn_element(REQUEST *request, AV *av, VALUE_PAIR const *vp, int *i, const char *hash_name, const char *list_name) { SV *sv; switch (vp->vp_type) { case FR_TYPE_STRING: RDEBUG2("$%s{'%s'}[%i] = &%s:%s -> '%s'", hash_name, vp->da->name, *i, list_name, vp->da->name, vp->vp_strvalue); sv = newSVpvn(vp->vp_strvalue, vp->vp_length); break; case FR_TYPE_OCTETS: RDEBUG2("$%s{'%s'}[%i] = &%s:%s -> 0x%pH", hash_name, vp->da->name, *i, list_name, vp->da->name, &vp->data); sv = newSVpvn((char const *)vp->vp_octets, vp->vp_length); break; default: { char buffer[1024]; size_t len; len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); RDEBUG2("$%s{'%s'}[%i] = &%s:%s -> '%s'", hash_name, vp->da->name, *i, list_name, vp->da->name, buffer); sv = newSVpvn(buffer, truncate_len(len, sizeof(buffer))); } break; } if (!sv) return; SvTAINT(sv); av_push(av, sv); (*i)++; }
/** Write the result of a set operation back to net-snmp * * Writes "DONE\n" on success, or an error as described in man snmpd.conf * on error. * * @param fd to write to. * @param error attribute. * @param head of list of attributes to convert and write. * @return * - 0 on success. * - -1 on failure. */ static int radsnmp_set_response(int fd, fr_dict_attr_t const *error, VALUE_PAIR *head) { VALUE_PAIR *vp; char buffer[64]; size_t len; struct iovec io_vector[2]; char newline[] = "\n"; vp = fr_pair_find_by_da(head, error, TAG_NONE); if (!vp) { if (write(fd, "DONE\n", 5) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; } len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); if (is_truncated(len, sizeof(buffer))) { assert(0); return -1; } io_vector[0].iov_base = buffer; io_vector[0].iov_len = len; io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; DEBUG2("said: %s", buffer); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; }
/* * get the vps and put them in perl hash * If one VP have multiple values it is added as array_ref * Example for this is Cisco-AVPair that holds multiple values. * Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'} */ static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, HV *rad_hv, const char *hash_name, const char *list_name) { VALUE_PAIR *vp; hv_undef(rad_hv); fr_cursor_t cursor; RINDENT(); fr_pair_list_sort(vps, fr_pair_cmp_by_da_tag); for (vp = fr_cursor_init(&cursor, vps); vp; vp = fr_cursor_next(&cursor)) { VALUE_PAIR *next; char const *name; char namebuf[256]; /* * Tagged attributes are added to the hash with name * <attribute>:<tag>, others just use the normal attribute * name as the key. */ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag); name = namebuf; } else { name = vp->da->name; } /* * We've sorted by type, then tag, so attributes of the * same type/tag should follow on from each other. */ if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) { int i = 0; AV *av; av = newAV(); perl_vp_to_svpvn_element(request, av, vp, &i, hash_name, list_name); do { perl_vp_to_svpvn_element(request, av, next, &i, hash_name, list_name); fr_cursor_next(&cursor); } while ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)); (void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0); continue; } /* * It's a normal single valued attribute */ switch (vp->vp_type) { case FR_TYPE_STRING: RDEBUG2("$%s{'%s'} = &%s:%s -> '%pV'", hash_name, vp->da->name, list_name, vp->da->name, &vp->data); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->vp_length), 0); break; case FR_TYPE_OCTETS: RDEBUG2("$%s{'%s'} = &%s:%s -> %pV", hash_name, vp->da->name, list_name, vp->da->name, &vp->data); (void)hv_store(rad_hv, name, strlen(name), newSVpvn((char const *)vp->vp_octets, vp->vp_length), 0); break; default: { char buffer[1024]; size_t len; len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); RDEBUG2("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name, list_name, vp->da->name, buffer); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(buffer, truncate_len(len, sizeof(buffer))), 0); } break; } } REXDENT(); }
/** 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 true 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 failure. */ pid_t radius_start_program(char const *cmd, REQUEST *request, bool exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, bool 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 const **argv_p; char *argv[MAX_ARGV], **argv_start = argv; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; size_t envlen = 0; TALLOC_CTX *input_ctx = NULL; /* * Stupid array decomposition... * * If we do memcpy(&argv_p, &argv, sizeof(argv_p)) src ends up being a char ** * pointing to the value of the first element. */ memcpy(&argv_p, &argv_start, sizeof(argv_p)); argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv_p, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { ERROR("Invalid command '%s'", cmd); return -1; } if (DEBUG_ENABLED3) { for (i = 0; i < argc; i++) DEBUG3("arg[%d] %s", i, argv[i]); } #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { ERROR("Couldn't open pipe to child: %s", fr_syserror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { ERROR("Couldn't open pipe from child: %s", fr_syserror(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; char buffer[1024]; input_ctx = talloc_new(request); /* * 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. */ for (vp = fr_cursor_init(&cursor, &input_pairs); vp && (envlen < ((sizeof(envp) / sizeof(*envp)) - 1)); vp = fr_cursor_next(&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); fr_pair_value_snprint(buffer + n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); DEBUG3("export %s", buffer); envp[envlen++] = talloc_strdup(input_ctx, buffer); } fr_cursor_init(&cursor, radius_list(request, PAIR_LIST_CONTROL)); while ((envlen < ((sizeof(envp) / sizeof(*envp)) - 1)) && (vp = fr_cursor_next_by_num(&cursor, 0, PW_EXEC_EXPORT, TAG_ANY))) { DEBUG3("export %s", vp->vp_strvalue); memcpy(&envp[envlen++], &vp->vp_strvalue, sizeof(*envp)); } /* * NULL terminate for execve */ 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) { ERROR("Failed opening /dev/null: %s\n", fr_syserror(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 (rad_debug_lvl == 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[]'. * * Note: execve(), unlike system(), treats all the space * delimited arguments as literals, so there's no need * to perform additional escaping. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(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 */ talloc_free(input_ctx); /* * Parent process. */ if (pid < 0) { ERROR("Couldn't fork %s: %s", argv[0], fr_syserror(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[1]); } 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) { ERROR("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 }
/** Write the result of a get or getnext operation back to net-snmp * * Returns three lines of output per attribute: * - OID string * - type * - value * * Index attributes (num 0) must be in order of depth (shallowest first). * * If no attributes were written, will write "NONE\n" to inform net-snmp * that no value was available at the specified OID. * * @param fd to write to. * @param root of the SNMP portion of the main dictionary. * @param type attribute. * @param head of list of attributes to convert and write. * @return * - >=0 on success (the number of varbind responses written). * - -1 on failure. */ static int radsnmp_get_response(int fd, fr_dict_attr_t const *root, fr_dict_attr_t const *type, VALUE_PAIR *head) { fr_cursor_t cursor; VALUE_PAIR *vp, *type_vp; fr_dict_attr_t const *parent = root; unsigned int written = 0; ssize_t slen; size_t len; char type_buff[32]; /* type */ size_t type_len = 0; char oid_buff[256]; char value_buff[128]; char *p = oid_buff, *end = p + sizeof(oid_buff); struct iovec io_vector[6]; char newline[] = "\n"; type_buff[0] = '\0'; /* * Print first part of OID string. */ slen = snprintf(oid_buff, sizeof(oid_buff), "%u.", parent->attr); if (is_truncated((size_t)slen, sizeof(oid_buff))) { oob: fr_strerror_printf("OID Buffer too small"); return -1; } p += slen; /* * @fixme, this is very dependent on ordering * * This code should be reworked when we have proper * attribute grouping to coalesce all related index * attributes under a single request OID. */ for (vp = fr_cursor_init(&cursor, &head); vp; vp = fr_cursor_next(&cursor)) { fr_dict_attr_t const *common; /* * We only care about TLV attributes beneath our root */ if (!fr_dict_parent_common(root, vp->da, true)) continue; /* * Sanity checks to ensure we're processing attributes * in the right order. */ common = fr_dict_parent_common(parent, vp->da, true); if (!common) { fr_strerror_printf("Out of order index attributes. \"%s\" is not a child of \"%s\"", vp->da->name, parent->name); return -1; } /* * Index attribute */ if (vp->da->attr == 0) { /* * Print OID from last index/root up to the parent of * the index attribute. */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da->parent); if (slen < 0) return -1; if (vp->vp_type != FR_TYPE_UINT32) { fr_strerror_printf("Index attribute \"%s\" is not of type \"integer\"", vp->da->name); return -1; } if (slen >= (end - p)) goto oob; p += slen; /* * Add the value of the index attribute as the next * OID component. */ len = snprintf(p, end - p, ".%i.", vp->vp_uint32); if (is_truncated(len, end - p)) goto oob; p += len; /* * Set the parent to be the attribute representing * the entry. */ parent = fr_dict_attr_child_by_num(vp->da->parent, 1); continue; } /* * Actual TLV attribute */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da); if (slen < 0) return -1; /* * Next attribute should be the type */ type_vp = fr_cursor_next(&cursor); if (!type_vp || (type_vp->da != type)) { fr_strerror_printf("No %s found in response, or occurred out of order", type->name); return -1; } type_len = fr_pair_value_snprint(type_buff, sizeof(type_buff), type_vp, '\0'); /* * Build up the vector * * This represents output for a single varbind attribute */ io_vector[0].iov_base = oid_buff; io_vector[0].iov_len = strlen(oid_buff); io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; io_vector[2].iov_base = type_buff; io_vector[2].iov_len = type_len; io_vector[3].iov_base = newline; io_vector[3].iov_len = 1; switch (vp->vp_type) { case FR_TYPE_OCTETS: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; case FR_TYPE_STRING: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; default: /* * We call fr_value_box_snprint with a NULL da pointer * because we always need return integer values not * value aliases. */ len = fr_value_box_snprint(value_buff, sizeof(value_buff), &vp->data, '\0'); if (is_truncated(len, sizeof(value_buff))) { fr_strerror_printf("Insufficient fixed value buffer"); return -1; } io_vector[4].iov_base = value_buff; io_vector[4].iov_len = len; break; } io_vector[5].iov_base = newline; io_vector[5].iov_len = 1; DEBUG2("said: %s", (char *)io_vector[0].iov_base); DEBUG2("said: %s", (char *)io_vector[2].iov_base); DEBUG2("said: %s", (char *)io_vector[4].iov_base); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing varbind result: %s", fr_syserror(errno)); return -1; } /* * Reset in case we're encoding multiple values */ parent = root; p = oid_buff; type_buff[0] = '\0'; written++; } if (!written && (write(fd, "NONE\n", 5)) < 0) { fr_strerror_printf("Failed writing get response: %s", fr_syserror(errno)); return -1; } return written; }
/** Add session controls to a connection as per draft-wahl-ldap-session * * @note the RFC states that the username identifier, must be the authenticated * user id, not the purported one. As order of operations is configurable, * we're going to leave that up to the server admin to satisfy that * requirement * * For once the RFC is pretty helpful about what should be inserted into the * various values, and maps out RADIUS attributes to formatOIDs, so none of * this is configurable. * * @param conn to add controls to. * @param request to draw attributes from. */ int fr_ldap_control_add_session_tracking(fr_ldap_connection_t *conn, REQUEST *request) { /* * The OpenLDAP guys didn't declare the formatOID parameter to * ldap_create_session_tracking_control as const *sigh*. */ static char username_oid[] = LDAP_CONTROL_X_SESSION_TRACKING_USERNAME; static char acctsessionid_oid[] = LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_SESSION_ID; static char acctmultisessionid_oid[] = LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_MULTI_SESSION_ID; int ret; char ipaddress[INET6_ADDRSTRLEN]; char *username = NULL; char *acctsessionid = NULL; char *acctmultisessionid = NULL; char *hostname; LDAPControl *username_control = NULL; LDAPControl *acctsessionid_control = NULL; LDAPControl *acctmultisessionid_control = NULL; struct berval tracking_id; fr_cursor_t cursor; VALUE_PAIR const *vp; memcpy(&hostname, main_config->name, sizeof(hostname)); /* const / non-const issues */ for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { if (fr_dict_attr_is_top_level(vp->da)) switch (vp->da->attr) { case FR_NAS_IP_ADDRESS: case FR_NAS_IPV6_ADDRESS: fr_pair_value_snprint(ipaddress, sizeof(ipaddress), vp, '\0'); break; case FR_USER_NAME: memcpy(&username, &vp->vp_strvalue, sizeof(username)); break; case FR_ACCT_SESSION_ID: memcpy(&acctsessionid, &vp->vp_strvalue, sizeof(acctsessionid)); break; case FR_ACCT_MULTI_SESSION_ID: memcpy(&acctmultisessionid, &vp->vp_strvalue, sizeof(acctmultisessionid)); break; } } if (username) { tracking_id.bv_val = username; tracking_id.bv_len = talloc_array_length(username) - 1; ret = ldap_create_session_tracking_control(conn->handle, ipaddress, hostname, username_oid, &tracking_id, &username_control); if (ret != LDAP_SUCCESS) { REDEBUG("Failed creating username session tracking control: %s", ldap_err2string(ret)); error: if (username_control) ldap_control_free(username_control); if (acctsessionid_control) ldap_control_free(acctsessionid_control); if (acctmultisessionid_control) ldap_control_free(acctmultisessionid_control); return -1; } } if (acctsessionid) { tracking_id.bv_val = acctsessionid; tracking_id.bv_len = talloc_array_length(acctsessionid) - 1; ret = ldap_create_session_tracking_control(conn->handle, ipaddress, hostname, acctsessionid_oid, &tracking_id, &acctsessionid_control); if (ret != LDAP_SUCCESS) { REDEBUG("Failed creating acctsessionid session tracking control: %s", ldap_err2string(ret)); goto error; } } if (acctmultisessionid) { tracking_id.bv_val = acctmultisessionid; tracking_id.bv_len = talloc_array_length(acctmultisessionid) - 1; ret = ldap_create_session_tracking_control(conn->handle, ipaddress, hostname, acctmultisessionid_oid, &tracking_id, &acctmultisessionid_control); if (ret != LDAP_SUCCESS) { REDEBUG("Failed creating acctmultisessionid session tracking control: %s", ldap_err2string(ret)); goto error; } } if ((conn->serverctrls_cnt + 3) >= LDAP_MAX_CONTROLS) { REDEBUG("Insufficient space to add session tracking controls"); goto error; } if (username_control && (fr_ldap_control_add_server(conn, username_control, true) < 0)) goto error; if (acctsessionid_control && (fr_ldap_control_add_server(conn, acctsessionid_control, true) < 0)) { conn->serverctrls_cnt--; conn->serverctrls[conn->serverctrls_cnt].control = NULL; goto error; } if (acctmultisessionid_control && (fr_ldap_control_add_server(conn, acctmultisessionid_control, true) < 0)) { conn->serverctrls_cnt--; conn->serverctrls[conn->serverctrls_cnt].control = NULL; conn->serverctrls_cnt--; conn->serverctrls[conn->serverctrls_cnt].control = NULL; goto error; } return 0; }
/** Unpack data * * Example: %{unpack:&Class 0 integer} * * Expands Class, treating octet at offset 0 (bytes 0-3) as an "integer". */ static ssize_t unpack_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char **out, size_t outlen) { char *data_name, *data_size, *data_type; char *p; size_t len, input_len; int offset; PW_TYPE type; DICT_ATTR const *da; VALUE_PAIR *vp, *cast; uint8_t const *input; char buffer[256]; uint8_t blob[256]; /* * FIXME: copy only the fields here, as we parse them. */ strlcpy(buffer, fmt, sizeof(buffer)); p = buffer; while (isspace((int) *p)) p++; /* skip leading spaces */ data_name = p; while (*p && !isspace((int) *p)) p++; if (!*p) { error: REDEBUG("Format string should be '<data> <offset> <type>' e.g. '&Class 1 integer'"); nothing: return -1; } while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_size = p; while (*p && !isspace((int) *p)) p++; if (!*p) GOTO_ERROR; while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_type = p; while (*p && !isspace((int) *p)) p++; if (*p) GOTO_ERROR; /* anything after the type is an error */ /* * Attribute reference */ if (*data_name == '&') { if (radius_get_vp(&vp, request, data_name) < 0) goto nothing; if ((vp->da->type != PW_TYPE_OCTETS) && (vp->da->type != PW_TYPE_STRING)) { REDEBUG("unpack requires the input attribute to be 'string' or 'octets'"); goto nothing; } input = vp->vp_octets; input_len = vp->vp_length; } else if ((data_name[0] == '0') && (data_name[1] == 'x')) { /* * Hex data. */ len = strlen(data_name + 2); if ((len & 0x01) != 0) { RDEBUG("Invalid hex string in '%s'", data_name); goto nothing; } input = blob; input_len = fr_hex2bin(blob, sizeof(blob), data_name + 2, len); } else { GOTO_ERROR; } offset = (int) strtoul(data_size, &p, 10); if (*p) { REDEBUG("unpack requires a decimal number, not '%s'", data_size); goto nothing; } type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID); if (type == PW_TYPE_INVALID) { REDEBUG("Invalid data type '%s'", data_type); goto nothing; } /* * Output must be a non-zero limited size. */ if ((dict_attr_sizes[type][0] == 0) || (dict_attr_sizes[type][0] != dict_attr_sizes[type][1])) { REDEBUG("unpack requires fixed-size output type, not '%s'", data_type); goto nothing; } if (input_len < (offset + dict_attr_sizes[type][0])) { REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name); goto nothing; } da = dict_attrbyvalue(PW_CAST_BASE + type, 0); if (!da) { REDEBUG("Cannot decode type '%s'", data_type); goto nothing; } cast = fr_pair_afrom_da(request, da); if (!cast) goto nothing; memcpy(&(cast->data), input + offset, dict_attr_sizes[type][0]); cast->vp_length = dict_attr_sizes[type][0]; /* * Hacks */ switch (type) { case PW_TYPE_SIGNED: case PW_TYPE_INTEGER: case PW_TYPE_DATE: cast->vp_integer = ntohl(cast->vp_integer); break; case PW_TYPE_SHORT: cast->vp_short = ((input[offset] << 8) | input[offset + 1]); break; case PW_TYPE_INTEGER64: cast->vp_integer64 = ntohll(cast->vp_integer64); break; default: break; } len = fr_pair_value_snprint(*out, outlen, cast, 0); talloc_free(cast); if (is_truncated(len, outlen)) { REDEBUG("Insufficient buffer space to unpack data"); goto nothing; } return len; }