/** Registers signal handlers to execute panic_action on fatal signal * * May be called multiple time to change the panic_action/program. * * @param cmd to execute on fault. If present %p will be substituted * for the parent PID before the command is executed, and %e * will be substituted for the currently running program. * @param program Name of program currently executing (argv[0]). * @return 0 on success -1 on failure. */ int fr_fault_setup(char const *cmd, char const *program) { static bool setup = false; char *out = panic_action; size_t left = sizeof(panic_action), ret; char const *p = cmd; char const *q; if (cmd) { /* Substitute %e for the current program */ while ((q = strstr(p, "%e"))) { out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : ""); if (left <= ret) { oob: fr_strerror_printf("Panic action too long"); return -1; } left -= ret; p = q + 2; } if (strlen(p) >= left) goto oob; strlcpy(out, p, left); } else { *panic_action = '\0'; } /* Unsure what the side effects of changing the signal handler mid execution might be */ if (!setup) { #ifdef SIGSEGV if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1; #endif #ifdef SIGBUS if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1; #endif #ifdef SIGABRT if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1; #endif #ifdef SIGFPE if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1; #endif } setup = true; return 0; }
/** Registers signal handlers to execute panic_action on fatal signal * * May be called multiple time to change the panic_action/program. * * @param cmd to execute on fault. If present %p will be substituted * for the parent PID before the command is executed, and %e * will be substituted for the currently running program. * @param program Name of program currently executing (argv[0]). * @return 0 on success -1 on failure. */ int fr_fault_setup(char const *cmd, char const *program) { static bool setup = false; char *out = panic_action; size_t left = sizeof(panic_action); char const *p = cmd; char const *q; if (cmd) { size_t ret; /* Substitute %e for the current program */ while ((q = strstr(p, "%e"))) { out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : ""); if (left <= ret) { oob: fr_strerror_printf("Panic action too long"); return -1; } left -= ret; p = q + 2; } if (strlen(p) >= left) goto oob; strlcpy(out, p, left); } else { *panic_action = '\0'; } /* * Check for administrator sanity. */ if (fr_fault_check_permissions() < 0) return -1; /* Unsure what the side effects of changing the signal handler mid execution might be */ if (!setup) { char *env; fr_debug_state_t debug_state; /* * Installing signal handlers interferes with some debugging * operations. Give the developer control over whether the * signal handlers are installed or not. */ env = getenv("DEBUG"); if (!env || (strcmp(env, "no") == 0)) { debug_state = DEBUG_STATE_NOT_ATTACHED; } else if (!strcmp(env, "auto") || !strcmp(env, "yes")) { /* * Figure out if we were started under a debugger */ if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); debug_state = fr_debug_state; } else { debug_state = DEBUG_STATE_ATTACHED; } talloc_set_log_fn(_fr_talloc_log); /* * These signals can't be properly dealt with in the debugger * if we set our own signal handlers. */ switch (debug_state) { default: #ifndef NDEBUG FR_FAULT_LOG("Debugger check failed: %s", fr_strerror()); FR_FAULT_LOG("Signal processing in debuggers may not work as expected"); #endif /* FALL-THROUGH */ case DEBUG_STATE_NOT_ATTACHED: #ifdef SIGABRT if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1; /* * Use this instead of abort so we get a * full backtrace with broken versions of LLDB */ talloc_set_abort_fn(_fr_talloc_fault); #endif #ifdef SIGILL if (fr_set_signal(SIGILL, fr_fault) < 0) return -1; #endif #ifdef SIGFPE if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1; #endif #ifdef SIGSEGV if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1; #endif break; case DEBUG_STATE_ATTACHED: break; } /* * Needed for memory reports */ { TALLOC_CTX *tmp; bool *marker; tmp = talloc(NULL, bool); talloc_null_ctx = talloc_parent(tmp); talloc_free(tmp); /* * Disable null tracking on exit, else valgrind complains */ talloc_autofree_ctx = talloc_autofree_context(); marker = talloc(talloc_autofree_ctx, bool); talloc_set_destructor(marker, _fr_disable_null_tracking); } #if defined(HAVE_MALLOPT) && !defined(NDEBUG) /* * If were using glibc malloc > 2.4 this scribbles over * uninitialised and freed memory, to make memory issues easier * to track down. */ if (!getenv("TALLOC_FREE_FILL")) mallopt(M_PERTURB, 0x42); mallopt(M_CHECK_ACTION, 3); #endif #if defined(HAVE_EXECINFO) && defined(__GNUC__) && !defined(NDEBUG) /* * We need to pre-load lgcc_s, else we can get into a deadlock * in fr_fault, as backtrace() attempts to dlopen it. * * Apparently there's a performance impact of loading lgcc_s, * so only do it if this is a debug build. * * See: https://sourceware.org/bugzilla/show_bug.cgi?id=16159 */ { void *stack[10]; backtrace(stack, 10); } #endif }
int main(int argc, char **argv) { int c; char filesecret[256]; FILE *fp; int force_af = AF_UNSPEC; radsnmp_conf_t *conf; int ret; int sockfd; TALLOC_CTX *autofree = talloc_autofree_context(); fr_log_fp = stderr; conf = talloc_zero(autofree, radsnmp_conf_t); conf->proto = IPPROTO_UDP; conf->dict_dir = DICTDIR; conf->raddb_dir = RADDBDIR; conf->secret = talloc_strdup(conf, "testing123"); conf->timeout.tv_sec = 3; conf->retries = 5; #ifndef NDEBUG if (fr_fault_setup(autofree, getenv("PANIC_ACTION"), argv[0]) < 0) { fr_perror("radsnmp"); exit(EXIT_FAILURE); } #endif talloc_set_log_stderr(); while ((c = getopt(argc, argv, "46c:d:D:f:Fhi:l:n:p:P:qr:sS:t:vx")) != -1) switch (c) { case '4': force_af = AF_INET; break; case '6': force_af = AF_INET6; break; case 'D': conf->dict_dir = optarg; break; case 'd': conf->raddb_dir = optarg; break; case 'l': { int log_fd; if (strcmp(optarg, "stderr") == 0) { fr_log_fp = stderr; /* stdout goes to netsnmp */ break; } log_fd = open(optarg, O_WRONLY | O_APPEND | O_CREAT, 0640); if (log_fd < 0) { fprintf(stderr, "radsnmp: Failed to open log file %s: %s\n", optarg, fr_syserror(errno)); exit(EXIT_FAILURE); } fr_log_fp = fdopen(log_fd, "a"); } break; case 'P': conf->proto_str = optarg; if (strcmp(conf->proto_str, "tcp") != 0) { if (strcmp(conf->proto_str, "udp") != 0) usage(); } else { conf->proto = IPPROTO_TCP; } break; case 'r': if (!isdigit((int) *optarg)) usage(); conf->retries = atoi(optarg); if ((conf->retries == 0) || (conf->retries > 1000)) usage(); break; case 'S': { char *p; fp = fopen(optarg, "r"); if (!fp) { ERROR("Error opening %s: %s", optarg, fr_syserror(errno)); exit(EXIT_FAILURE); } if (fgets(filesecret, sizeof(filesecret), fp) == NULL) { ERROR("Error reading %s: %s", optarg, fr_syserror(errno)); exit(EXIT_FAILURE); } fclose(fp); /* truncate newline */ p = filesecret + strlen(filesecret) - 1; while ((p >= filesecret) && (*p < ' ')) { *p = '\0'; --p; } if (strlen(filesecret) < 2) { ERROR("Secret in %s is too short", optarg); exit(EXIT_FAILURE); } talloc_free(conf->secret); conf->secret = talloc_strdup(conf, filesecret); } break; case 't': if (fr_timeval_from_str(&conf->timeout, optarg) < 0) { ERROR("Failed parsing timeout value %s", fr_strerror()); exit(EXIT_FAILURE); } break; case 'v': DEBUG("%s", radsnmp_version); exit(0); case 'x': fr_debug_lvl++; break; case 'h': default: usage(); } argc -= (optind - 1); argv += (optind - 1); if ((argc < 2) || ((conf->secret == NULL) && (argc < 3))) { ERROR("Insufficient arguments"); usage(); } /* * Mismatch between the binary and the libraries it depends on */ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { fr_perror("radsnmp"); return EXIT_FAILURE; } if (fr_dict_autoload(radsnmp_dict) < 0) { fr_perror("radsnmp"); exit(EXIT_FAILURE); } if (fr_dict_attr_autoload(radsnmp_dict_attr) < 0) { fr_perror("radsnmp"); exit(EXIT_FAILURE); } if (fr_dict_read(dict_freeradius, conf->raddb_dir, FR_DICTIONARY_FILE) == -1) { fr_perror("radsnmp"); exit(EXIT_FAILURE); } fr_strerror(); /* Clear the error buffer */ if (fr_log_fp) setvbuf(fr_log_fp, NULL, _IONBF, 0); /* * Get the request type */ if (!isdigit((int) argv[2][0])) { int code; code = fr_str2int(fr_request_types, argv[2], -1); if (code < 0) { ERROR("Unrecognised request type \"%s\"", argv[2]); usage(); } conf->code = (unsigned int)code; } else { conf->code = atoi(argv[2]); } /* * Resolve hostname. */ if (fr_inet_pton_port(&conf->server_ipaddr, &conf->server_port, argv[1], -1, force_af, true, true) < 0) { ERROR("%s", fr_strerror()); exit(EXIT_FAILURE); } /* * Add the secret */ if (argv[3]) { talloc_free(conf->secret); conf->secret = talloc_strdup(conf, argv[3]); } conf->snmp_root = fr_dict_attr_child_by_num(attr_vendor_specific, VENDORPEC_FREERADIUS); if (!conf->snmp_root) { ERROR("Incomplete dictionary: Missing definition for Extended-Attribute-1(%i)." "Vendor-Specific(%i).FreeRADIUS(%i)", attr_extended_attribute_1->attr, attr_vendor_specific->attr, VENDORPEC_FREERADIUS); dict_error: talloc_free(conf); exit(EXIT_FAILURE); } conf->snmp_oid_root = fr_dict_attr_child_by_num(conf->snmp_root, 1); if (!conf->snmp_oid_root) { ERROR("Incomplete dictionary: Missing definition for 1.Extended-Attribute-1(%i)." "Vendor-Specific(%i).FreeRADIUS(%i).FreeRADIUS-Iso(%i)", attr_extended_attribute_1->attr, attr_vendor_specific->attr, VENDORPEC_FREERADIUS, 1); goto dict_error; } switch (conf->proto) { case IPPROTO_TCP: sockfd = fr_socket_client_tcp(NULL, &conf->server_ipaddr, conf->server_port, true); break; default: case IPPROTO_UDP: sockfd = fr_socket_client_udp(NULL, NULL, &conf->server_ipaddr, conf->server_port, true); break; } if (sockfd < 0) { ERROR("Failed connecting to server %s:%hu", "foo", conf->server_port); ret = 1; goto finish; } fr_set_signal(SIGPIPE, rs_signal_stop); fr_set_signal(SIGINT, rs_signal_stop); fr_set_signal(SIGTERM, rs_signal_stop); #ifdef SIGQUIT fr_set_signal(SIGQUIT, rs_signal_stop); #endif DEBUG("%s - Starting pass_persist read loop", radsnmp_version); ret = radsnmp_send_recv(conf, sockfd); DEBUG("Read loop done"); finish: if (fr_log_fp) fflush(fr_log_fp); /* * Everything should be parented from conf */ talloc_free(conf); /* * ...except the dictionaries */ fr_dict_autofree(radsnmp_dict); return ret; }
/* * The main guy. */ int main(int argc, char *argv[]) { int rcode = EXIT_SUCCESS; int status; int argval; bool spawn_flag = true; bool write_pid = false; bool display_version = false; int flag = 0; int from_child[2] = {-1, -1}; fr_state_t *state = NULL; /* * We probably don't want to free the talloc autofree context * directly, so we'll allocate a new context beneath it, and * free that before any leak reports. */ TALLOC_CTX *autofree = talloc_init("main"); #ifdef OSFC2 set_auth_parameters(argc, argv); #endif if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) progname = argv[0]; else progname++; #ifdef WIN32 { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 0), &wsaData)) { fprintf(stderr, "%s: Unable to initialize socket library.\n", progname); exit(EXIT_FAILURE); } } #endif rad_debug_lvl = 0; set_radius_dir(autofree, RADIUS_DIR); /* * Ensure that the configuration is initialized. */ memset(&main_config, 0, sizeof(main_config)); main_config.myip.af = AF_UNSPEC; main_config.port = 0; main_config.name = "radiusd"; main_config.daemonize = true; /* * Don't put output anywhere until we get told a little * more. */ default_log.dst = L_DST_NULL; default_log.fd = -1; main_config.log_file = NULL; /* Process the options. */ while ((argval = getopt(argc, argv, "Cd:D:fhi:l:mMn:p:PstvxX")) != EOF) { switch (argval) { case 'C': check_config = true; spawn_flag = false; main_config.daemonize = false; break; case 'd': set_radius_dir(autofree, optarg); break; case 'D': main_config.dictionary_dir = talloc_typed_strdup(autofree, optarg); break; case 'f': main_config.daemonize = false; break; case 'h': usage(0); break; case 'l': if (strcmp(optarg, "stdout") == 0) { goto do_stdout; } main_config.log_file = strdup(optarg); default_log.dst = L_DST_FILES; default_log.fd = open(main_config.log_file, O_WRONLY | O_APPEND | O_CREAT, 0640); if (default_log.fd < 0) { fprintf(stderr, "radiusd: Failed to open log file %s: %s\n", main_config.log_file, fr_syserror(errno)); exit(EXIT_FAILURE); } fr_log_fp = fdopen(default_log.fd, "a"); break; case 'i': if (ip_hton(&main_config.myip, AF_UNSPEC, optarg, false) < 0) { fprintf(stderr, "radiusd: Invalid IP Address or hostname \"%s\"\n", optarg); exit(EXIT_FAILURE); } flag |= 1; break; case 'n': main_config.name = optarg; break; case 'm': main_config.debug_memory = true; break; case 'M': main_config.memory_report = true; main_config.debug_memory = true; break; case 'p': { unsigned long port; port = strtoul(optarg, 0, 10); if ((port == 0) || (port > UINT16_MAX)) { fprintf(stderr, "radiusd: Invalid port number \"%s\"\n", optarg); exit(EXIT_FAILURE); } main_config.port = (uint16_t) port; flag |= 2; } break; case 'P': /* Force the PID to be written, even in -f mode */ write_pid = true; break; case 's': /* Single process mode */ spawn_flag = false; main_config.daemonize = false; break; case 't': /* no child threads */ spawn_flag = false; break; case 'v': display_version = true; break; case 'X': spawn_flag = false; main_config.daemonize = false; rad_debug_lvl += 2; main_config.log_auth = true; main_config.log_auth_badpass = true; main_config.log_auth_goodpass = true; do_stdout: fr_log_fp = stdout; default_log.dst = L_DST_STDOUT; default_log.fd = STDOUT_FILENO; break; case 'x': rad_debug_lvl++; break; default: usage(1); break; } } /* * Mismatch between the binary and the libraries it depends on. */ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { fr_perror("radiusd"); exit(EXIT_FAILURE); } if (rad_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) exit(EXIT_FAILURE); /* * Mismatch between build time OpenSSL and linked SSL, better to die * here than segfault later. */ #ifdef HAVE_OPENSSL_CRYPTO_H if (ssl_check_consistency() < 0) exit(EXIT_FAILURE); #endif if (flag && (flag != 0x03)) { fprintf(stderr, "radiusd: The options -i and -p cannot be used individually.\n"); exit(EXIT_FAILURE); } /* * According to the talloc peeps, no two threads may modify any part of * a ctx tree with a common root without synchronisation. * * So we can't run with a null context and threads. */ if (main_config.memory_report) { if (spawn_flag) { fprintf(stderr, "radiusd: The server cannot produce memory reports (-M) in threaded mode\n"); exit(EXIT_FAILURE); } talloc_enable_null_tracking(); } else { talloc_disable_null_tracking(); } /* * Better here, so it doesn't matter whether we get passed -xv or -vx. */ if (display_version) { /* Don't print timestamps */ rad_debug_lvl += 2; fr_log_fp = stdout; default_log.dst = L_DST_STDOUT; default_log.fd = STDOUT_FILENO; INFO("%s: %s", progname, radiusd_version); version_print(); exit(EXIT_SUCCESS); } if (rad_debug_lvl) version_print(); /* * Under linux CAP_SYS_PTRACE is usually only available before setuid/setguid, * so we need to check whether we can attach before calling those functions * (in main_config_init()). */ fr_store_debug_state(); /* * Initialising OpenSSL once, here, is safer than having individual modules do it. */ #ifdef HAVE_OPENSSL_CRYPTO_H tls_global_init(); #endif /* * Read the configuration files, BEFORE doing anything else. */ if (main_config_init() < 0) exit(EXIT_FAILURE); /* * This is very useful in figuring out why the panic_action didn't fire. */ INFO("%s", fr_debug_state_to_msg(fr_debug_state)); /* * Check for vulnerabilities in the version of libssl were linked against. */ #if defined(HAVE_OPENSSL_CRYPTO_H) && defined(ENABLE_OPENSSL_VERSION_CHECK) if (tls_global_version_check(main_config.allow_vulnerable_openssl) < 0) exit(EXIT_FAILURE); #endif fr_talloc_fault_setup(); /* * Set the panic action (if required) */ { char const *panic_action = NULL; panic_action = getenv("PANIC_ACTION"); if (!panic_action) panic_action = main_config.panic_action; if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) { fr_perror("radiusd"); exit(EXIT_FAILURE); } } #ifndef __MINGW32__ /* * Disconnect from session */ if (main_config.daemonize) { pid_t pid; int devnull; /* * Really weird things happen if we leave stdin open and call things like * system() later. */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { ERROR("Failed opening /dev/null: %s", fr_syserror(errno)); exit(EXIT_FAILURE); } dup2(devnull, STDIN_FILENO); close(devnull); if (pipe(from_child) != 0) { ERROR("Couldn't open pipe for child status: %s", fr_syserror(errno)); exit(EXIT_FAILURE); } pid = fork(); if (pid < 0) { ERROR("Couldn't fork: %s", fr_syserror(errno)); exit(EXIT_FAILURE); } /* * The parent exits, so the child can run in the background. * * As the child can still encounter an error during initialisation * we do a blocking read on a pipe between it and the parent. * * Just before entering the event loop the child will send a success * or failure message to the parent, via the pipe. */ if (pid > 0) { uint8_t ret = 0; int stat_loc; /* So the pipe is correctly widowed if the child exits */ close(from_child[1]); /* * The child writes a 0x01 byte on success, and closes * the pipe on error. */ if ((read(from_child[0], &ret, 1) < 0)) { ret = 0; } /* For cleanliness... */ close(from_child[0]); /* Don't turn children into zombies */ if (!ret) { waitpid(pid, &stat_loc, WNOHANG); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } /* so the pipe is correctly widowed if the parent exits?! */ close(from_child[0]); # ifdef HAVE_SETSID setsid(); # endif } #endif /* * Ensure that we're using the CORRECT pid after forking, NOT the one * we started with. */ radius_pid = getpid(); /* * Initialize any event loops just enough so module instantiations can * add fd/event to them, but do not start them yet. * * This has to be done post-fork in case we're using kqueue, where the * queue isn't inherited by the child process. */ if (!radius_event_init(autofree)) exit(EXIT_FAILURE); /* * Load the modules */ if (modules_init(main_config.config) < 0) exit(EXIT_FAILURE); /* * Redirect stderr/stdout as appropriate. */ if (radlog_init(&default_log, main_config.daemonize) < 0) { ERROR("%s", fr_strerror()); exit(EXIT_FAILURE); } event_loop_started = true; /* * Start the event loop(s) and threads. */ radius_event_start(main_config.config, spawn_flag); /* * Now that we've set everything up, we can install the signal * handlers. Before this, if we get any signal, we don't know * what to do, so we might as well do the default, and die. */ #ifdef SIGPIPE signal(SIGPIPE, SIG_IGN); #endif if ((fr_set_signal(SIGHUP, sig_hup) < 0) || (fr_set_signal(SIGTERM, sig_fatal) < 0)) { ERROR("%s", fr_strerror()); exit(EXIT_FAILURE); } /* * If we're debugging, then a CTRL-C will cause the server to die * immediately. Use SIGTERM to shut down the server cleanly in * that case. */ if (main_config.debug_memory || (rad_debug_lvl == 0)) { if ((fr_set_signal(SIGINT, sig_fatal) < 0) #ifdef SIGQUIT || (fr_set_signal(SIGQUIT, sig_fatal) < 0) #endif ) { ERROR("%s", fr_strerror()); exit(EXIT_FAILURE); } } /* * Everything seems to have loaded OK, exit gracefully. */ if (check_config) { DEBUG("Configuration appears to be OK"); /* for -C -m|-M */ if (main_config.debug_memory) goto cleanup; exit(EXIT_SUCCESS); } #ifdef WITH_STATS radius_stats_init(0); #endif /* * Write the PID always if we're running as a daemon. */ if (main_config.daemonize) write_pid = true; /* * Write the PID after we've forked, so that we write the correct one. */ if (write_pid) { FILE *fp; fp = fopen(main_config.pid_file, "w"); if (fp != NULL) { /* * @fixme What about following symlinks, * and having it over-write a normal file? */ fprintf(fp, "%d\n", (int) radius_pid); fclose(fp); } else { ERROR("Failed creating PID file %s: %s", main_config.pid_file, fr_syserror(errno)); exit(EXIT_FAILURE); } } exec_trigger(NULL, NULL, "server.start", false); /* * Inform the parent (who should still be waiting) that the rest of * initialisation went OK, and that it should exit with a 0 status. * If we don't get this far, then we just close the pipe on exit, and the * parent gets a read failure. */ if (main_config.daemonize) { if (write(from_child[1], "\001", 1) < 0) { WARN("Failed informing parent of successful start: %s", fr_syserror(errno)); } close(from_child[1]); } /* * Clear the libfreeradius error buffer. */ fr_strerror(); /* * Initialise the state rbtree (used to link multiple rounds of challenges). */ state = fr_state_init(NULL, 0); /* * Process requests until HUP or exit. */ while ((status = radius_event_process()) == 0x80) { #ifdef WITH_STATS radius_stats_init(1); #endif main_config_hup(); } if (status < 0) { ERROR("Exiting due to internal error: %s", fr_strerror()); rcode = EXIT_FAILURE; } else { INFO("Exiting normally"); } /* * Ignore the TERM signal: we're about to die. */ signal(SIGTERM, SIG_IGN); /* * Fire signal and stop triggers after ignoring SIGTERM, so handlers are * not killed with the rest of the process group, below. */ if (status == 2) exec_trigger(NULL, NULL, "server.signal.term", true); exec_trigger(NULL, NULL, "server.stop", false); /* * Send a TERM signal to all associated processes * (including us, which gets ignored.) */ #ifndef __MINGW32__ if (spawn_flag) kill(-radius_pid, SIGTERM); #endif /* * We're exiting, so we can delete the PID file. * (If it doesn't exist, we can ignore the error returned by unlink) */ if (main_config.daemonize) unlink(main_config.pid_file); radius_event_free(); cleanup: /* * Detach any modules. */ modules_free(); xlat_free(); /* modules may have xlat's */ fr_state_delete(state); /* * Free the configuration items. */ main_config_free(); #ifdef WIN32 WSACleanup(); #endif #ifdef HAVE_OPENSSL_CRYPTO_H tls_global_cleanup(); #endif /* * So we don't see autofreed memory in the talloc report */ talloc_free(autofree); if (main_config.memory_report) { INFO("Allocated memory at time of report:"); fr_log_talloc_report(NULL); } return rcode; }