From 50b37d4a27d3295a29afca2286f1a5a086142cec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:49:46 +0200 Subject: Adding upstream version 3.2.1+dfsg. Signed-off-by: Daniel Baumann --- src/main/util.c | 1732 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1732 insertions(+) create mode 100644 src/main/util.c (limited to 'src/main/util.c') diff --git a/src/main/util.c b/src/main/util.c new file mode 100644 index 0000000..b216cc9 --- /dev/null +++ b/src/main/util.c @@ -0,0 +1,1732 @@ +/* + * util.c Various utility functions. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include +#include + +#include + +#include +#include + +/* + * The signal() function in Solaris 2.5.1 sets SA_NODEFER in + * sa_flags, which causes grief if signal() is called in the + * handler before the cause of the signal has been cleared. + * (Infinite recursion). + * + * The same problem appears on HPUX, so we avoid it, if we can. + * + * Using sigaction() to reset the signal handler fixes the problem, + * so where available, we prefer that solution. + */ + +void (*reset_signal(int signo, void (*func)(int)))(int) +{ +#ifdef HAVE_SIGACTION + struct sigaction act, oact; + + memset(&act, 0, sizeof(act)); + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; +#ifdef SA_INTERRUPT /* SunOS */ + act.sa_flags |= SA_INTERRUPT; +#endif + if (sigaction(signo, &act, &oact) < 0) + return SIG_ERR; + return oact.sa_handler; +#else + + /* + * re-set by calling the 'signal' function, which + * may cause infinite recursion and core dumps due to + * stack growth. + * + * However, the system is too dumb to implement sigaction(), + * so we don't have a choice. + */ + signal(signo, func); + + return NULL; +#endif +} + +/* + * Per-request data, added by modules... + */ +struct request_data_t { + request_data_t *next; + + void *unique_ptr; + int unique_int; + void *opaque; + bool free_opaque; +}; + +/* + * Add opaque data (with a "free" function) to a REQUEST. + * + * The unique ptr is meant to be a module configuration, + * and the unique integer allows the caller to have multiple + * opaque data associated with a REQUEST. + */ +int request_data_add(REQUEST *request, void *unique_ptr, int unique_int, void *opaque, bool free_opaque) +{ + request_data_t *this, **last, *next; + + /* + * Some simple sanity checks. + */ + if (!request || !opaque) return -1; + + this = next = NULL; + for (last = &(request->data); + *last != NULL; + last = &((*last)->next)) { + if (((*last)->unique_ptr == unique_ptr) && + ((*last)->unique_int == unique_int)) { + this = *last; + next = this->next; + + /* + * If caller requires custom behaviour on free + * they must set a destructor. + */ + if (this->opaque && this->free_opaque) talloc_free(this->opaque); + + break; /* replace the existing entry */ + } + } + + /* + * Only alloc new memory if we're not replacing + * an existing entry. + */ + if (!this) this = talloc_zero(request, request_data_t); + if (!this) return -1; + + this->next = next; + this->unique_ptr = unique_ptr; + this->unique_int = unique_int; + this->opaque = opaque; + this->free_opaque = free_opaque; + + *last = this; + + return 0; +} + +/* + * Get opaque data from a request. + */ +void *request_data_get(REQUEST *request, void *unique_ptr, int unique_int) +{ + request_data_t **last; + + if (!request) return NULL; + + for (last = &(request->data); + *last != NULL; + last = &((*last)->next)) { + if (((*last)->unique_ptr == unique_ptr) && + ((*last)->unique_int == unique_int)) { + request_data_t *this; + void *ptr; + + this = *last; + ptr = this->opaque; + + /* + * Remove the entry from the list, and free it. + */ + *last = this->next; + talloc_free(this); + + return ptr; /* don't free it, the caller does that */ + } + } + + return NULL; /* wasn't found, too bad... */ +} + +/* + * Get opaque data from a request without removing it. + */ +void *request_data_reference(REQUEST *request, void *unique_ptr, int unique_int) +{ + request_data_t **last; + + for (last = &(request->data); + *last != NULL; + last = &((*last)->next)) { + if (((*last)->unique_ptr == unique_ptr) && + ((*last)->unique_int == unique_int)) { + return (*last)->opaque; + } + } + + return NULL; /* wasn't found, too bad... */ +} + +/** Create possibly many directories. + * + * @note that the input directory name is NOT treated as a constant. This is so that + * if an error is returned, the 'directory' ptr points to the name of the file + * which caused the error. + * + * @param dir path to directory to create. + * @param mode for new directories. + * @param uid to set on new directories, may be -1 to use effective uid. + * @param gid to set on new directories, may be -1 to use effective gid. + * @return 0 on success, -1 on error. Error available as errno. + */ +int rad_mkdir(char *dir, mode_t mode, uid_t uid, gid_t gid) +{ + int rcode, fd; + char *p; + + /* + * Try to make the dir. If it exists, chmod it. + * If a path doesn't exist, that's OK. Otherwise + * return with an error. + * + * Directories permissions are initially set so + * that only we should have access. This prevents + * an attacker removing them and swapping them + * out for a link to somewhere else. + * We change them to the correct permissions later. + */ + rcode = mkdir(dir, 0700); + if (rcode < 0) { + switch (errno) { + case EEXIST: + return 0; /* don't change permissions */ + + case ENOENT: + break; + + default: + return rcode; + } + + /* + * A component in the dir path doesn't + * exist. Look for the LAST dir name. Try + * to create that. If there's an error, we leave + * the dir path as the one at which the + * error occured. + */ + p = strrchr(dir, FR_DIR_SEP); + if (!p || (p == dir)) return -1; + + *p = '\0'; + rcode = rad_mkdir(dir, mode, uid, gid); + if (rcode < 0) return rcode; + + /* + * Reset the dir path, and try again to + * make the dir. + */ + *p = FR_DIR_SEP; + rcode = mkdir(dir, 0700); + if (rcode < 0) return rcode; + } /* else we successfully created the dir */ + + /* + * Set the permissions on the directory we created + * this should never fail unless there's a race. + */ + fd = open(dir, O_DIRECTORY); + if (fd < 0) return -1; + + rcode = fchmod(fd, mode); + if (rcode < 0) { + close(fd); + return rcode; + } + + if ((uid != (uid_t)-1) || (gid != (gid_t)-1)) { + rad_suid_up(); + rcode = fchown(fd, uid, gid); + rad_suid_down(); + } + close(fd); + + return rcode; +} + +/** Ensures that a filename cannot walk up the directory structure + * + * Also sanitizes control chars. + * + * @param request Current request (may be NULL). + * @param out Output buffer. + * @param outlen Size of the output buffer. + * @param in string to escape. + * @param arg Context arguments (unused, should be NULL). + */ +size_t rad_filename_make_safe(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg) +{ + char const *q = in; + char *p = out; + size_t left = outlen; + + while (*q) { + if (*q != '/') { + if (left < 2) break; + + /* + * Smash control characters and spaces to + * something simpler. + */ + if (*q < ' ') { + *(p++) = '_'; + q++; + continue; + } + + *(p++) = *(q++); + left--; + continue; + } + + /* + * For now, allow slashes in the expanded + * filename. This allows the admin to set + * attributes which create sub-directories. + * Unfortunately, it also allows users to send + * attributes which *may* end up creating + * sub-directories. + */ + if (left < 2) break; + *(p++) = *(q++); + + /* + * Get rid of ////../.././///.///..// + */ + redo: + /* + * Get rid of //// + */ + if (*q == '/') { + q++; + goto redo; + } + + /* + * Get rid of /./././ + */ + if ((q[0] == '.') && + (q[1] == '/')) { + q += 2; + goto redo; + } + + /* + * Get rid of /../../../ + */ + if ((q[0] == '.') && (q[1] == '.') && + (q[2] == '/')) { + q += 3; + goto redo; + } + } + *p = '\0'; + + return (p - out); +} + +/** Escapes the raw string such that it should be safe to use as part of a file path + * + * This function is designed to produce a string that's still readable but portable + * across the majority of file systems. + * + * For security reasons it cannot remove characters from the name, and must not allow + * collisions to occur between different strings. + * + * With that in mind '-' has been chosen as the escape character, and will be double + * escaped '-' -> '--' to avoid collisions. + * + * Escaping should be reversible if the original string needs to be extracted. + * + * @note function takes additional arguments so that it may be used as an xlat escape + * function but it's fine to call it directly. + * + * @note OSX/Unix/NTFS/VFAT have a max filename size of 255 bytes. + * + * @param request Current request (may be NULL). + * @param out Output buffer. + * @param outlen Size of the output buffer. + * @param in string to escape. + * @param arg Context arguments (unused, should be NULL). + */ +size_t rad_filename_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg) +{ + size_t freespace = outlen; + + while (*in != '\0') { + size_t utf8_len; + + /* + * Encode multibyte UTF8 chars + */ + utf8_len = fr_utf8_char((uint8_t const *) in, -1); + if (utf8_len > 1) { + if (freespace <= (utf8_len * 3)) break; + + switch (utf8_len) { + case 2: + snprintf(out, freespace, "-%x-%x", in[0], in[1]); + break; + + case 3: + snprintf(out, freespace, "-%x-%x-%x", in[0], in[1], in[2]); + break; + + case 4: + snprintf(out, freespace, "-%x-%x-%x-%x", in[0], in[1], in[2], in[3]); + break; + } + + freespace -= (utf8_len * 3); + out += (utf8_len * 3); + in += utf8_len; + + continue; + } + + /* + * Safe chars + */ + if (((*in >= 'A') && (*in <= 'Z')) || + ((*in >= 'a') && (*in <= 'z')) || + ((*in >= '0') && (*in <= '9')) || + (*in == '_')) { + if (freespace <= 1) break; + + *out++ = *in++; + freespace--; + continue; + } + if (freespace <= 2) break; + + /* + * Double escape '-' (like \\) + */ + if (*in == '-') { + *out++ = '-'; + *out++ = '-'; + + freespace -= 2; + in++; + continue; + } + + /* + * Unsafe chars + */ + *out++ = '-'; + fr_bin2hex(out, (uint8_t const *)in++, 1); + out += 2; + freespace -= 3; + } + *out = '\0'; + + return outlen - freespace; +} + +/** Converts data stored in a file name back to its original form + * + * @param out Where to write the unescaped string (may be the same as in). + * @param outlen Length of the output buffer. + * @param in Input filename. + * @param inlen Length of input. + * @return number of bytes written to output buffer, or offset where parse error + * occurred on failure. + */ +ssize_t rad_filename_unescape(char *out, size_t outlen, char const *in, size_t inlen) +{ + char const *p, *end = in + inlen; + size_t freespace = outlen; + + for (p = in; p < end; p++) { + if (freespace <= 1) break; + + if (((*p >= 'A') && (*p <= 'Z')) || + ((*p >= 'a') && (*p <= 'z')) || + ((*p >= '0') && (*p <= '9')) || + (*p == '_')) { + *out++ = *p; + freespace--; + continue; + } + + if (p[0] == '-') { + /* + * End of input, '-' needs at least one extra char after + * it to be valid. + */ + if ((end - p) < 2) return in - p; + if (p[1] == '-') { + p++; + *out++ = '-'; + freespace--; + continue; + } + + /* + * End of input, '-' must be followed by + * but there aren't enough chars left + */ + if ((end - p) < 3) return in - p; + + /* + * If hex2bin returns 0 the next two chars weren't hexits. + */ + if (fr_hex2bin((uint8_t *) out, 1, in, 1) == 0) return in - (p + 1); + in += 2; + out++; + freespace--; + } + + return in - p; /* offset we found the bad char at */ + } + *out = '\0'; + + return outlen - freespace; /* how many bytes were written */ +} + +/* + * Allocate memory, or exit. + * + * This call ALWAYS succeeds! + */ +void *rad_malloc(size_t size) +{ + void *ptr = malloc(size); + + if (ptr == NULL) { + ERROR("no memory"); + fr_exit(1); + } + + return ptr; +} + + +void rad_const_free(void const *ptr) +{ + void *tmp; + if (!ptr) return; + + memcpy(&tmp, &ptr, sizeof(tmp)); + talloc_free(tmp); +} + + +/* + * Logs an error message and aborts the program + * + */ + +void NEVER_RETURNS rad_assert_fail(char const *file, unsigned int line, char const *expr) +{ + ERROR("ASSERT FAILED %s[%u]: %s", file, line, expr); + fr_fault(SIGABRT); + fr_exit_now(1); +} + +/* + * Free a REQUEST struct. + */ +static int _request_free(REQUEST *request) +{ + rad_assert(!request->in_request_hash); +#ifdef WITH_PROXY + rad_assert(!request->in_proxy_hash); +#endif + rad_assert(!request->ev); + +#ifdef WITH_COA + rad_assert(request->coa == NULL); +#endif + +#ifndef NDEBUG + request->magic = 0x01020304; /* set the request to be nonsense */ +#endif + request->client = NULL; +#ifdef WITH_PROXY + request->home_server = NULL; +#endif + + /* + * This is parented separately. + */ + if (request->state_ctx) { + talloc_free(request->state_ctx); + } + + return 0; +} + +/* + * Create a new REQUEST data structure. + */ +REQUEST *request_alloc(TALLOC_CTX *ctx) +{ + REQUEST *request; + + request = talloc_zero(ctx, REQUEST); + if (!request) return NULL; + talloc_set_destructor(request, _request_free); +#ifndef NDEBUG + request->magic = REQUEST_MAGIC; +#endif +#ifdef WITH_PROXY + request->proxy = NULL; +#endif + request->reply = NULL; +#ifdef WITH_PROXY + request->proxy_reply = NULL; +#endif + request->config = NULL; + request->username = NULL; + request->password = NULL; + request->timestamp = time(NULL); + request->log.lvl = rad_debug_lvl; /* Default to global debug level */ + + request->module = ""; + request->component = ""; + request->log.func = vradlog_request; + + request->state_ctx = talloc_init("session-state"); + + return request; +} + + +/* + * Create a new REQUEST, based on an old one. + * + * This function allows modules to inject fake requests + * into the server, for tunneled protocols like TTLS & PEAP. + */ +REQUEST *request_alloc_fake(REQUEST *request) +{ + REQUEST *fake; + + fake = request_alloc(request); + if (!fake) return NULL; + + fake->number = request->number; +#ifdef HAVE_PTHREAD_H + fake->child_pid = request->child_pid; +#endif + fake->parent = request; + fake->root = request->root; + fake->client = request->client; + + /* + * For new server support. + * + * FIXME: Key instead off of a "virtual server" data structure. + * + * FIXME: Permit different servers for inner && outer sessions? + */ + fake->server = request->server; + + fake->packet = rad_alloc(fake, true); + if (!fake->packet) { + talloc_free(fake); + return NULL; + } + + fake->reply = rad_alloc(fake, false); + if (!fake->reply) { + talloc_free(fake); + return NULL; + } + + fake->master_state = REQUEST_ACTIVE; + fake->child_state = REQUEST_RUNNING; + + /* + * Fill in the fake request. + */ + fake->packet->sockfd = -1; + fake->packet->src_ipaddr = request->packet->src_ipaddr; + fake->packet->src_port = request->packet->src_port; + fake->packet->dst_ipaddr = request->packet->dst_ipaddr; + fake->packet->dst_port = 0; + + /* + * This isn't STRICTLY required, as the fake request MUST NEVER + * be put into the request list. However, it's still reasonable + * practice. + */ + fake->packet->id = fake->number & 0xff; + fake->packet->code = request->packet->code; + fake->timestamp = request->timestamp; + fake->packet->timestamp = request->packet->timestamp; + + /* + * Required for new identity support + */ + fake->listener = request->listener; + + /* + * Fill in the fake reply, based on the fake request. + */ + fake->reply->sockfd = fake->packet->sockfd; + fake->reply->src_ipaddr = fake->packet->dst_ipaddr; + fake->reply->src_port = fake->packet->dst_port; + fake->reply->dst_ipaddr = fake->packet->src_ipaddr; + fake->reply->dst_port = fake->packet->src_port; + fake->reply->id = fake->packet->id; + fake->reply->code = 0; /* UNKNOWN code */ + + /* + * Copy debug information. + */ + memcpy(&(fake->log), &(request->log), sizeof(fake->log)); + fake->log.indent = 0; /* Apart from the indent which we reset */ + + return fake; +} + +#ifdef WITH_COA +static int null_handler(UNUSED REQUEST *request) +{ + return 0; +} + +REQUEST *request_alloc_coa(REQUEST *request) +{ + if (!request || request->coa) return NULL; + + /* + * Originate CoA requests only when necessary. + */ + if ((request->packet->code != PW_CODE_ACCESS_REQUEST) && + (request->packet->code != PW_CODE_ACCOUNTING_REQUEST)) return NULL; + + request->coa = request_alloc_fake(request); + if (!request->coa) return NULL; + + request->coa->handle = null_handler; + request->coa->options = RAD_REQUEST_OPTION_COA; /* is a CoA packet */ + request->coa->packet->code = 0; /* unknown, as of yet */ + request->coa->child_state = REQUEST_RUNNING; + request->coa->proxy = rad_alloc(request->coa, false); + if (!request->coa->proxy) { + TALLOC_FREE(request->coa); + return NULL; + } + + return request->coa; +} +#endif + +/* + * Copy a quoted string. + */ +int rad_copy_string(char *to, char const *from) +{ + int length = 0; + char quote = *from; + + do { + if (*from == '\\') { + *(to++) = *(from++); + length++; + } + *(to++) = *(from++); + length++; + } while (*from && (*from != quote)); + + if (*from != quote) return -1; /* not properly quoted */ + + *(to++) = quote; + length++; + *to = '\0'; + + return length; +} + +/* + * Copy a quoted string but without the quotes. The length + * returned is the number of chars written; the number of + * characters consumed is 2 more than this. + */ +int rad_copy_string_bare(char *to, char const *from) +{ + int length = 0; + char quote = *from; + + from++; + while (*from && (*from != quote)) { + if (*from == '\\') { + *(to++) = *(from++); + length++; + } + *(to++) = *(from++); + length++; + } + + if (*from != quote) return -1; /* not properly quoted */ + + *to = '\0'; + + return length; +} + + +/* + * Copy a %{} string. + */ +int rad_copy_variable(char *to, char const *from) +{ + int length = 0; + int sublen; + + *(to++) = *(from++); + length++; + + while (*from) { + switch (*from) { + case '"': + case '\'': + sublen = rad_copy_string(to, from); + if (sublen < 0) return sublen; + from += sublen; + to += sublen; + length += sublen; + break; + + case '}': /* end of variable expansion */ + *(to++) = *(from++); + *to = '\0'; + length++; + return length; /* proper end of variable */ + + case '\\': + *(to++) = *(from++); + *(to++) = *(from++); + length += 2; + break; + + case '%': /* start of variable expansion */ + if (from[1] == '{') { + *(to++) = *(from++); + length++; + + sublen = rad_copy_variable(to, from); + if (sublen < 0) return sublen; + from += sublen; + to += sublen; + length += sublen; + break; + } /* else FIXME: catch %%{ ?*/ + + /* FALL-THROUGH */ + default: + *(to++) = *(from++); + length++; + break; + } + } /* loop over the input string */ + + /* + * We ended the string before a trailing '}' + */ + + return -1; +} + +#ifndef USEC +#define USEC 1000000 +#endif + +uint32_t rad_pps(uint32_t *past, uint32_t *present, time_t *then, struct timeval *now) +{ + uint32_t pps; + + if (*then != now->tv_sec) { + *then = now->tv_sec; + *past = *present; + *present = 0; + } + + /* + * Bootstrap PPS by looking at a percentage of + * the previous PPS. This lets us take a moving + * count, without doing a moving average. If + * we're a fraction "f" (0..1) into the current + * second, we can get a good guess for PPS by + * doing: + * + * PPS = pps_now + pps_old * (1 - f) + * + * It's an instantaneous measurement, rather than + * a moving average. This will hopefully let it + * respond better to sudden spikes. + * + * Doing the calculations by thousands allows us + * to not overflow 2^32, AND to not underflow + * when we divide by USEC. + */ + pps = USEC - now->tv_usec; /* useconds left in previous second */ + pps /= 1000; /* scale to milliseconds */ + pps *= *past; /* multiply by past count to get fraction */ + pps /= 1000; /* scale to usec again */ + pps += *present; /* add in current count */ + + return pps; +} + +/** Split string into words and expand each one + * + * @param request Current request. + * @param cmd string to split. + * @param max_argc the maximum number of arguments to split into. + * @param argv Where to write the pointers into argv_buf. + * @param can_fail If false, stop processing if any of the xlat expansions fail. + * @param argv_buflen size of argv_buf. + * @param argv_buf temporary buffer we used to mangle/expand cmd. + * Pointers to offsets of this buffer will be written to argv. + * @return argc or -1 on failure. + */ + +int rad_expand_xlat(REQUEST *request, char const *cmd, + int max_argc, char const *argv[], bool can_fail, + size_t argv_buflen, char *argv_buf) +{ + char const *from; + char *to; + int argc = -1; + int i; + int left; + + if (strlen(cmd) > (argv_buflen - 1)) { + ERROR("rad_expand_xlat: Command line is too long"); + return -1; + } + + /* + * Check for bad escapes. + */ + if (cmd[strlen(cmd) - 1] == '\\') { + ERROR("rad_expand_xlat: Command line has final backslash, without a following character"); + return -1; + } + + strlcpy(argv_buf, cmd, argv_buflen); + + /* + * Split the string into argv's BEFORE doing radius_xlat... + */ + from = cmd; + to = argv_buf; + argc = 0; + while (*from) { + int length; + + /* + * Skip spaces. + */ + if ((*from == ' ') || (*from == '\t')) { + from++; + continue; + } + + argv[argc] = to; + argc++; + + if (argc >= (max_argc - 1)) break; + + /* + * Copy the argv over to our buffer. + */ + while (*from && (*from != ' ') && (*from != '\t')) { + if (to >= argv_buf + argv_buflen - 1) { + ERROR("rad_expand_xlat: Ran out of space in command line"); + return -1; + } + + switch (*from) { + case '"': + case '\'': + length = rad_copy_string_bare(to, from); + if (length < 0) { + ERROR("rad_expand_xlat: Invalid string passed as argument"); + return -1; + } + from += length+2; + to += length; + break; + + case '%': + if (from[1] == '{') { + *(to++) = *(from++); + + length = rad_copy_variable(to, from); + if (length < 0) { + ERROR("rad_expand_xlat: Invalid variable expansion passed as argument"); + return -1; + } + from += length; + to += length; + } else { /* FIXME: catch %%{ ? */ + *(to++) = *(from++); + } + break; + + case '\\': + if (from[1] == ' ') from++; + /* FALL-THROUGH */ + + default: + *(to++) = *(from++); + } + } /* end of string, or found a space */ + + *(to++) = '\0'; /* terminate the string */ + } + + /* + * We have to have SOMETHING, at least. + */ + if (argc <= 0) { + ERROR("rad_expand_xlat: Empty command line"); + return -1; + } + + /* + * Expand each string, as appropriate. + */ + left = argv_buf + argv_buflen - to; + for (i = 0; i < argc; i++) { + int sublen; + + /* + * Don't touch argv's which won't be translated. + */ + if (strchr(argv[i], '%') == NULL) continue; + + if (!request) continue; + + sublen = radius_xlat(to, left - 1, request, argv[i], NULL, NULL); + if (sublen <= 0) { + if (can_fail) { + /* + * Fail to be backwards compatible. + * + * It's yucky, but it won't break anything, + * and it won't cause security problems. + */ + sublen = 0; + } else { + ERROR("rad_expand_xlat: xlat failed"); + return -1; + } + } + + argv[i] = to; + to += sublen; + *(to++) = '\0'; + left -= sublen; + left--; + + if (left <= 0) { + ERROR("rad_expand_xlat: Ran out of space while expanding arguments"); + return -1; + } + } + argv[argc] = NULL; + + return argc; +} + +#ifndef NDEBUG +/* + * Verify a packet. + */ +static void verify_packet(char const *file, int line, REQUEST *request, RADIUS_PACKET *packet, char const *name) +{ + TALLOC_CTX *parent; + + if (!packet) { + fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%i]: RADIUS_PACKET %s pointer was NULL", file, line, name); + fr_assert(0); + fr_exit_now(0); + } + + parent = talloc_parent(packet); + if (parent != request) { + ERROR("CONSISTENCY CHECK FAILED %s[%i]: Expected RADIUS_PACKET %s to be parented by %p (%s), " + "but parented by %p (%s)", file, line, name, request, talloc_get_name(request), + parent, parent ? talloc_get_name(parent) : "NULL"); + + fr_log_talloc_report(packet); + if (parent) fr_log_talloc_report(parent); + + rad_assert(0); + } + + VERIFY_PACKET(packet); + + if (!packet->vps) return; + +#ifdef WITH_VERIFY_PTR + fr_pair_list_verify(file, line, packet, packet->vps, name); +#endif +} +/* + * Catch horrible talloc errors. + */ +void verify_request(char const *file, int line, REQUEST *request) +{ + if (!request) { + fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%i]: REQUEST pointer was NULL", file, line); + fr_assert(0); + fr_exit_now(0); + } + + (void) talloc_get_type_abort(request, REQUEST); + +#ifdef WITH_VERIFY_PTR + fr_pair_list_verify(file, line, request, request->config, "config"); + fr_pair_list_verify(file, line, request->state_ctx, request->state, "state"); +#endif + + if (request->packet) verify_packet(file, line, request, request->packet, "request"); + if (request->reply) verify_packet(file, line, request, request->reply, "reply"); +#ifdef WITH_PROXY + if (request->proxy) verify_packet(file, line, request, request->proxy, "proxy-request"); + if (request->proxy_reply) verify_packet(file, line, request, request->proxy_reply, "proxy-reply"); +#endif + +#ifdef WITH_COA + if (request->coa) { + void *parent; + + (void) talloc_get_type_abort(request->coa, REQUEST); + parent = talloc_parent(request->coa); + + rad_assert(parent == request); + + verify_request(file, line, request->coa); + } +#endif +} +#endif + +/** Convert mode_t into humanly readable permissions flags + * + * @author Jonathan Leffler. + * + * @param mode to convert. + * @param out Where to write the string to, must be exactly 10 bytes long. + */ +void rad_mode_to_str(char out[10], mode_t mode) +{ + static char const *rwx[] = {"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"}; + + strcpy(&out[0], rwx[(mode >> 6) & 0x07]); + strcpy(&out[3], rwx[(mode >> 3) & 0x07]); + strcpy(&out[6], rwx[(mode & 7)]); + if (mode & S_ISUID) out[2] = (mode & 0100) ? 's' : 'S'; + if (mode & S_ISGID) out[5] = (mode & 0010) ? 's' : 'l'; + if (mode & S_ISVTX) out[8] = (mode & 0100) ? 't' : 'T'; + out[9] = '\0'; +} + +void rad_mode_to_oct(char out[5], mode_t mode) +{ + out[0] = '0' + ((mode >> 9) & 0x07); + out[1] = '0' + ((mode >> 6) & 0x07); + out[2] = '0' + ((mode >> 3) & 0x07); + out[3] = '0' + (mode & 0x07); + out[4] = '\0'; +} + +/** Resolve a uid to a passwd entry + * + * Resolves a uid to a passwd entry. The memory to hold the + * passwd entry is talloced under ctx, and must be freed when no + * longer required. + * + * @param ctx to allocate passwd entry in. + * @param out Where to write pointer to entry. + * @param uid to resolve. + * @return 0 on success, -1 on error. + */ +int rad_getpwuid(TALLOC_CTX *ctx, struct passwd **out, uid_t uid) +{ + static size_t len; + uint8_t *buff; + int ret; + + *out = NULL; + + /* + * We assume this won't change between calls, + * and that the value is the same, so races don't + * matter. + */ + if (len == 0) { +#ifdef _SC_GETPW_R_SIZE_MAX + long int sc_len; + + sc_len = sysconf(_SC_GETPW_R_SIZE_MAX); + if (sc_len <= 0) sc_len = 1024; + len = (size_t)sc_len; +#else + len = 1024; +#endif + } + + buff = talloc_array(ctx, uint8_t, sizeof(struct passwd) + len); + if (!buff) return -1; + + /* + * In some cases we may need to dynamically + * grow the string buffer. + */ + while ((ret = getpwuid_r(uid, (struct passwd *)buff, (char *)(buff + sizeof(struct passwd)), + talloc_array_length(buff) - sizeof(struct passwd), out)) == ERANGE) { + buff = talloc_realloc_size(ctx, buff, talloc_array_length(buff) * 2); + if (!buff) { + talloc_free(buff); + return -1; + } + } + + if ((ret != 0) || !*out) { + fr_strerror_printf("Failed resolving UID: %s", fr_syserror(ret)); + talloc_free(buff); + errno = ret; + return -1; + } + + talloc_set_type(buff, struct passwd); + *out = (struct passwd *)buff; + + return 0; +} + +/** Resolve a username to a passwd entry + * + * Resolves a username to a passwd entry. The memory to hold the + * passwd entry is talloced under ctx, and must be freed when no + * longer required. + * + * @param ctx to allocate passwd entry in. + * @param out Where to write pointer to entry. + * @param name to resolve. + * @return 0 on success, -1 on error. + */ +int rad_getpwnam(TALLOC_CTX *ctx, struct passwd **out, char const *name) +{ + static size_t len; + uint8_t *buff; + int ret; + + *out = NULL; + + /* + * We assume this won't change between calls, + * and that the value is the same, so races don't + * matter. + */ + if (len == 0) { +#ifdef _SC_GETPW_R_SIZE_MAX + long int sc_len; + + sc_len = sysconf(_SC_GETPW_R_SIZE_MAX); + if (sc_len <= 0) sc_len = 1024; + len = (size_t)sc_len; +#else + sc_len = 1024; +#endif + } + + buff = talloc_array(ctx, uint8_t, sizeof(struct passwd) + len); + if (!buff) return -1; + + /* + * In some cases we may need to dynamically + * grow the string buffer. + */ + while ((ret = getpwnam_r(name, (struct passwd *)buff, (char *)(buff + sizeof(struct passwd)), + talloc_array_length(buff) - sizeof(struct passwd), out)) == ERANGE) { + buff = talloc_realloc_size(ctx, buff, talloc_array_length(buff) * 2); + if (!buff) { + talloc_free(buff); + return -1; + } + } + + if ((ret != 0) || !*out) { + fr_strerror_printf("Failed resolving UID: %s", fr_syserror(ret)); + talloc_free(buff); + errno = ret; + return -1; + } + + talloc_set_type(buff, struct passwd); + *out = (struct passwd *)buff; + + return 0; +} + +/** Resolve a gid to a group database entry + * + * Resolves a gid to a group database entry. The memory to hold the + * group entry is talloced under ctx, and must be freed when no + * longer required. + * + * @param ctx to allocate passwd entry in. + * @param out Where to write pointer to entry. + * @param gid to resolve. + * @return 0 on success, -1 on error. + */ +int rad_getgrgid(TALLOC_CTX *ctx, struct group **out, gid_t gid) +{ + static size_t len; + uint8_t *buff; + int ret; + + *out = NULL; + + /* + * We assume this won't change between calls, + * and that the value is the same, so races don't + * matter. + */ + if (len == 0) { +#ifdef _SC_GETGR_R_SIZE_MAX + long int sc_len; + + sc_len = sysconf(_SC_GETGR_R_SIZE_MAX); + if (sc_len <= 0) sc_len = 1024; + len = (size_t)sc_len; +#else + sc_len = 1024; +#endif + } + + buff = talloc_array(ctx, uint8_t, sizeof(struct group) + len); + if (!buff) return -1; + + /* + * In some cases we may need to dynamically + * grow the string buffer. + */ + while ((ret = getgrgid_r(gid, (struct group *)buff, (char *)(buff + sizeof(struct group)), + talloc_array_length(buff) - sizeof(struct group), out)) == ERANGE) { + buff = talloc_realloc_size(ctx, buff, talloc_array_length(buff) * 2); + if (!buff) { + talloc_free(buff); + return -1; + } + } + + if ((ret != 0) || !*out) { + fr_strerror_printf("Failed resolving GID: %s", fr_syserror(ret)); + talloc_free(buff); + errno = ret; + return -1; + } + + talloc_set_type(buff, struct group); + *out = (struct group *)buff; + + return 0; +} + +/** Resolve a group name to a group database entry + * + * Resolves a group name to a group database entry. + * The memory to hold the group entry is talloced under ctx, + * and must be freed when no longer required. + * + * @param ctx to allocate passwd entry in. + * @param out Where to write pointer to entry. + * @param name to resolve. + * @return 0 on success, -1 on error. + */ +int rad_getgrnam(TALLOC_CTX *ctx, struct group **out, char const *name) +{ + static size_t len; + uint8_t *buff; + int ret; + + *out = NULL; + + /* + * We assume this won't change between calls, + * and that the value is the same, so races don't + * matter. + */ + if (len == 0) { +#ifdef _SC_GETGR_R_SIZE_MAX + long int sc_len; + + sc_len = sysconf(_SC_GETGR_R_SIZE_MAX); + if (sc_len <= 0) sc_len = 1024; + len = (size_t)sc_len; +#else + len = 1024; +#endif + } + + buff = talloc_array(ctx, uint8_t, sizeof(struct group) + len); + if (!buff) return -1; + + /* + * In some cases we may need to dynamically + * grow the string buffer. + */ + while ((ret = getgrnam_r(name, (struct group *)buff, (char *)(buff + sizeof(struct group)), + talloc_array_length(buff) - sizeof(struct group), out)) == ERANGE) { + buff = talloc_realloc_size(ctx, buff, talloc_array_length(buff) * 2); + if (!buff) { + talloc_free(buff); + return -1; + } + } + + if ((ret != 0) || !*out) { + fr_strerror_printf("Failed resolving GID: %s", fr_syserror(ret)); + talloc_free(buff); + errno = ret; + return -1; + } + + talloc_set_type(buff, struct group); + *out = (struct group *)buff; + + return 0; +} + +/** Resolve a group name to a GID + * + * @param ctx TALLOC_CTX for temporary allocations. + * @param name of group. + * @param out where to write gid. + * @return 0 on success, -1 on error; + */ +int rad_getgid(TALLOC_CTX *ctx, gid_t *out, char const *name) +{ + int ret; + struct group *result; + + ret = rad_getgrnam(ctx, &result, name); + if (ret < 0) return -1; + + *out = result->gr_gid; + talloc_free(result); + return 0; +} + +/** Print uid to a string + * + * @note The reason for taking a fixed buffer is pure laziness. + * It means the caller doesn't have to free the string. + * + * @note Will always \0 terminate the buffer, even on error. + * + * @param ctx TALLOC_CTX for temporary allocations. + * @param out Where to write the uid string. + * @param outlen length of output buffer. + * @param uid to resolve. + * @return 0 on success, -1 on failure. + */ +int rad_prints_uid(TALLOC_CTX *ctx, char *out, size_t outlen, uid_t uid) +{ + struct passwd *result; + + rad_assert(outlen > 0); + + *out = '\0'; + + if (rad_getpwuid(ctx, &result, uid) < 0) return -1; + strlcpy(out, result->pw_name, outlen); + talloc_free(result); + + return 0; +} + +/** Print gid to a string + * + * @note The reason for taking a fixed buffer is pure laziness. + * It means the caller doesn't have to free the string. + * + * @note Will always \0 terminate the buffer, even on error. + * + * @param ctx TALLOC_CTX for temporary allocations. + * @param out Where to write the uid string. + * @param outlen length of output buffer. + * @param gid to resolve. + * @return 0 on success, -1 on failure. + */ +int rad_prints_gid(TALLOC_CTX *ctx, char *out, size_t outlen, gid_t gid) +{ + struct group *result; + + rad_assert(outlen > 0); + + *out = '\0'; + + if (rad_getgrgid(ctx, &result, gid) < 0) return -1; + strlcpy(out, result->gr_name, outlen); + talloc_free(result); + + return 0; +} + +#ifdef HAVE_SETUID +static bool doing_setuid = false; +static uid_t suid_down_uid = (uid_t)-1; + +/** Set the uid and gid used when dropping privileges + * + * @note if this function hasn't been called, rad_suid_down will have no effect. + * + * @param uid to drop down to. + */ +void rad_suid_set_down_uid(uid_t uid) +{ + suid_down_uid = uid; + doing_setuid = true; +} + +# if defined(HAVE_SETRESUID) && defined (HAVE_GETRESUID) +void rad_suid_up(void) +{ + uid_t ruid, euid, suid; + + if (getresuid(&ruid, &euid, &suid) < 0) { + ERROR("Failed getting saved UID's"); + fr_exit_now(1); + } + + if (setresuid(-1, suid, -1) < 0) { + ERROR("Failed switching to privileged user"); + fr_exit_now(1); + } + + if (geteuid() != suid) { + ERROR("Switched to unknown UID"); + fr_exit_now(1); + } +} + +void rad_suid_down(void) +{ + if (!doing_setuid) return; + + if (setresuid(-1, suid_down_uid, geteuid()) < 0) { + struct passwd *passwd; + char const *name; + + name = (rad_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown" : passwd->pw_name; + ERROR("Failed switching to uid %s: %s", name, fr_syserror(errno)); + talloc_free(passwd); + fr_exit_now(1); + } + + if (geteuid() != suid_down_uid) { + ERROR("Failed switching uid: UID is incorrect"); + fr_exit_now(1); + } + + fr_reset_dumpable(); +} + +void rad_suid_down_permanent(void) +{ + if (!doing_setuid) return; + + if (setresuid(suid_down_uid, suid_down_uid, suid_down_uid) < 0) { + struct passwd *passwd; + char const *name; + + name = (rad_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown" : passwd->pw_name; + ERROR("Failed in permanent switch to uid %s: %s", name, fr_syserror(errno)); + talloc_free(passwd); + fr_exit_now(1); + } + + if (geteuid() != suid_down_uid) { + ERROR("Switched to unknown uid"); + fr_exit_now(1); + } + + fr_reset_dumpable(); +} +# else +/* + * Much less secure... + */ +void rad_suid_up(void) +{ + if (!doing_setuid) return; + + if (seteuid(0) < 0) { + ERROR("Failed switching up to euid 0: %s", fr_syserror(errno)); + fr_exit_now(1); + } + +} + +void rad_suid_down(void) +{ + if (!doing_setuid) return; + + if (geteuid() == suid_down_uid) return; + + if (seteuid(suid_down_uid) < 0) { + struct passwd *passwd; + char const *name; + + name = (rad_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown": passwd->pw_name; + ERROR("Failed switching to euid %s: %s", name, fr_syserror(errno)); + talloc_free(passwd); + fr_exit_now(1); + } + + fr_reset_dumpable(); +} + +void rad_suid_down_permanent(void) +{ + if (!doing_setuid) return; + + /* + * Already done. Don't do anything else. + */ + if (getuid() == suid_down_uid) return; + + /* + * We're root, but running as a normal user. Fix that, + * so we can call setuid(). + */ + if (geteuid() == suid_down_uid) { + rad_suid_up(); + } + + if (setuid(suid_down_uid) < 0) { + struct passwd *passwd; + char const *name; + + name = (rad_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown": passwd->pw_name; + ERROR("Failed switching permanently to uid %s: %s", name, fr_syserror(errno)); + talloc_free(passwd); + fr_exit_now(1); + } + + fr_reset_dumpable(); +} +# endif /* HAVE_SETRESUID && HAVE_GETRESUID */ +#else /* HAVE_SETUID */ +void rad_suid_set_down_uid(uid_t uid) +{ +} +void rad_suid_up(void) +{ +} +void rad_suid_down(void) +{ + fr_reset_dumpable(); +} +void rad_suid_down_permanent(void) +{ + fr_reset_dumpable(); +} +#endif /* HAVE_SETUID */ + +/** Alter the effective user id + * + * @param uid to set + * @return 0 on success -1 on failure. + */ +int rad_seuid(uid_t uid) +{ + if (seteuid(uid) < 0) { + struct passwd *passwd; + + if (rad_getpwuid(NULL, &passwd, uid) < 0) return -1; + fr_strerror_printf("Failed setting euid to %s", passwd->pw_name); + talloc_free(passwd); + + return -1; + } + return 0; +} + +/** Alter the effective user id + * + * @param gid to set + * @return 0 on success -1 on failure. + */ +int rad_segid(gid_t gid) +{ + if (setegid(gid) < 0) { + struct group *group; + + if (rad_getgrgid(NULL, &group, gid) < 0) return -1; + fr_strerror_printf("Failed setting egid to %s", group->gr_name); + talloc_free(group); + + return -1; + } + return 0; +} + +/** Determine the elapsed time between two timevals + * + * @param end timeval nearest to the present + * @param start timeval furthest from the present + * @param elapsed Where to write the elapsed time + */ +void rad_tv_sub(struct timeval const *end, struct timeval const *start, struct timeval *elapsed) +{ + elapsed->tv_sec = end->tv_sec - start->tv_sec; + if (elapsed->tv_sec > 0) { + elapsed->tv_sec--; + elapsed->tv_usec = USEC; + } else { + elapsed->tv_usec = 0; + } + elapsed->tv_usec += end->tv_usec; + elapsed->tv_usec -= start->tv_usec; + + if (elapsed->tv_usec >= USEC) { + elapsed->tv_usec -= USEC; + elapsed->tv_sec++; + } +} -- cgit v1.2.3