/* * 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", (uint8_t)in[0], (uint8_t)in[1]); break; case 3: snprintf(out, freespace, "-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2]); break; case 4: snprintf(out, freespace, "-%x-%x-%x-%x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2], (uint8_t)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++; } }