Backport panic_action
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 27 Jan 2014 17:47:55 +0000 (17:47 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 27 Jan 2014 17:50:18 +0000 (17:50 +0000)
Seeing as were probably going to end up supporting v2.x.x for a while longer...

13 files changed:
configure
configure.in
raddb/Makefile
raddb/panic.gdb [new file with mode: 0644]
raddb/radiusd.conf.in
src/include/autoconf.h.in
src/include/libradius.h
src/include/radiusd.h
src/lib/Makefile
src/lib/debug.c [new file with mode: 0644]
src/lib/misc.c
src/main/mainconfig.c
src/main/radiusd.c

index c602126..a0a0a12 100755 (executable)
--- a/configure
+++ b/configure
@@ -621,7 +621,7 @@ else
   lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
   for dir in $PATH /usr/ucb; do
     IFS="$lt_save_ifs"
-    if (test -f "$dir/echo" || test -f "$dir/echo$ac_exeext") &&
+    if (test -f $dir/echo || test -f $dir/echo$ac_exeext) &&
        test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' &&
        echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` &&
        test "X$echo_testing_string" = "X$echo_test_string"; then
@@ -21237,6 +21237,7 @@ for ac_header in \
        unistd.h \
        crypt.h \
        errno.h \
+       execinfo.h \
        resource.h \
        sys/resource.h \
        getopt.h \
index ce618cb..5ed4ec3 100644 (file)
@@ -671,6 +671,7 @@ AC_CHECK_HEADERS( \
        unistd.h \
        crypt.h \
        errno.h \
+       execinfo.h \
        resource.h \
        sys/resource.h \
        getopt.h \
index 01d3f03..d96366c 100644 (file)
@@ -12,7 +12,7 @@ include ../Make.inc
 FILES = acct_users attrs attrs.access_reject attrs.accounting_response \
        attrs.pre-proxy clients.conf dictionary eap.conf templates.conf \
        experimental.conf hints huntgroups ldap.attrmap                 \
-       policy.txt preproxy_users proxy.conf radiusd.conf               \
+       panic.gdb policy.txt preproxy_users proxy.conf radiusd.conf     \
        sql.conf sqlippool.conf users   policy.conf attrs.access_challenge
 
 #
diff --git a/raddb/panic.gdb b/raddb/panic.gdb
new file mode 100644 (file)
index 0000000..3ae253a
--- /dev/null
@@ -0,0 +1,4 @@
+info locals
+info args
+thread apply all bt full
+quit
index 3064419..6aeee1a 100644 (file)
@@ -167,6 +167,39 @@ pidfile = ${run_dir}/${name}.pid
 #user = radius
 #group = radius
 
+#  panic_action: Command to execute if the server dies unexpectedly.
+#
+#  FOR PRODUCTION SYSTEMS, ACTIONS SHOULD ALWAYS EXIT.
+#  AN INTERACTIVE ACTION MEANS THE SERVER IS NOT RESPONDING TO REQUESTS.
+#  AN INTERACTICE ACTION MEANS THE SERVER WILL NOT RESTART.
+#
+#  The panic action is a command which will be executed if the server
+#  receives a fatal, non user generated signal, i.e. SIGSEGV, SIGBUS,
+#  SIGABRT or SIGFPE.
+#
+#  This can be used to start an interactive debugging session so
+#  that information regarding the current state of the server can
+#  be acquired.
+#
+#  The following string substitutions are available:
+#  - %e   The currently executing program e.g. /sbin/radiusd
+#  - %p   The PID of the currently executing program e.g. 12345
+#
+#  Standard ${} substitutions are also allowed.
+#
+#  An example panic action for opening an interactive session in GDB would be:
+#
+#panic_action = "gdb %e %p"
+#
+#  Again, don't use that on a production system.
+#
+#  An example panic action for opening an automated session in GDB would be:
+#
+#panic_action = "gdb -silent -x ${raddbdir}/panic.gdb %e %p > ${logdir}/gdb-%e-%p.log 2>&1"
+#
+#  That command can be used on a production system.
+#
+
 #  max_request_time: The maximum time (in seconds) to handle a request.
 #
 #  Requests which take more time than this to process may be killed, and
@@ -547,7 +580,7 @@ $INCLUDE proxy.conf
 
 # CLIENTS CONFIGURATION
 #
-#  Client configuration is defined in "clients.conf".  
+#  Client configuration is defined in "clients.conf".
 #
 
 #  The 'clients.conf' file contains all of the information from the old
@@ -752,7 +785,7 @@ instantiate {
        #  listed in any other section.  See 'doc/rlm_expr' for
        #  more information.
        #
-       #  rlm_expr is also responsible for registering many 
+       #  rlm_expr is also responsible for registering many
        #  other xlat functions such as md5, sha1 and lc.
        #
        #  We do not recommend removing it's listing here.
index 0f5c76f..d54e83d 100644 (file)
@@ -50,6 +50,9 @@
 /* Define to 1 if you have the <errno.h> header file. */
 #undef HAVE_ERRNO_H
 
+/* Define to 1 if you have the <execinfo.h> header file. */
+#undef HAVE_EXECINFO_H
+
 /* Define to 1 if you have the <fcntl.h> header file. */
 #undef HAVE_FCNTL_H
 
index 9afc841..0547a36 100644 (file)
@@ -402,6 +402,8 @@ void                fr_printf_log(const char *, ...)
 /*
  *     Several handy miscellaneous functions.
  */
+int            fr_fault_setup(char const *cmd, char const *program);
+int            fr_set_signal(int sig, sig_t func);
 const char *   ip_ntoa(char *, uint32_t);
 char           *ifid_ntoa(char *buffer, size_t size, uint8_t *ifid);
 uint8_t                *ifid_aton(const char *ifid_str, uint8_t *ifid);
index 904920d..c175a16 100644 (file)
@@ -381,6 +381,7 @@ typedef struct main_config_t {
        int             post_proxy_authorize;
 #endif
        int             debug_memory;
+       const char      *panic_action;
 } MAIN_CONFIG_T;
 
 #define DEBUG  if(debug_flag)log_debug
index 57e1f38..a715b01 100644 (file)
@@ -6,11 +6,11 @@
 
 include ../../Make.inc
 
-SRCS           = dict.c filters.c hash.c hmac.c hmacsha1.c isaac.c log.c \
-                 misc.c missing.c md4.c md5.c print.c radius.c rbtree.c \
-                 sha1.c snprintf.c strlcat.c strlcpy.c token.c udpfromto.c \
-                 valuepair.c fifo.c packet.c event.c getaddrinfo.c vqp.c \
-                 heap.c dhcp.c base64.c
+SRCS           = debug.c dict.c filters.c hash.c hmac.c hmacsha1.c isaac.c \
+                 log.c misc.c missing.c md4.c md5.c print.c radius.c \
+                 rbtree.c sha1.c snprintf.c strlcat.c strlcpy.c token.c \
+                 udpfromto.c valuepair.c fifo.c packet.c event.c \
+                 getaddrinfo.c vqp.c heap.c dhcp.c base64.c
 
 LT_OBJS                = $(SRCS:.c=.lo)
 
diff --git a/src/lib/debug.c b/src/lib/debug.c
new file mode 100644 (file)
index 0000000..2cada22
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ *   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
+ */
+
+/**
+ * @file debug.c
+ * @brief Various functions to aid in debugging
+ *
+ * @copyright 2013  The FreeRADIUS server project
+ * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+#include <freeradius-devel/libradius.h>
+
+/*
+ *     runtime backtrace functions are not POSIX but are included in
+ *     glibc, OSX >= 10.5 and various BSDs
+ */
+#ifdef HAVE_EXECINFO_H
+#  include <execinfo.h>
+#  define MAX_BT_FRAMES 128
+#endif
+
+static char panic_action[512];
+
+/** Prints a simple backtrace (if execinfo is available) and calls panic_action if set.
+ *
+ * @param sig caught
+ */
+static void NEVER_RETURNS _fr_fault(int sig)
+{
+       char cmd[sizeof(panic_action) + 20];
+       char *out = cmd;
+       size_t left = sizeof(cmd), ret;
+
+       char const *p = panic_action;
+       char const *q;
+
+       int code;
+
+       fprintf(stderr, "FATAL SIGNAL: %s\n", strsignal(sig));
+
+       /*
+        *      Produce a simple backtrace - They've very basic but at least give us an
+        *      idea of the area of the code we hit the issue in.
+        */
+#ifdef HAVE_EXECINFO_H
+       size_t frame_count, i;
+       void *stack[MAX_BT_FRAMES];
+       char **frames;
+
+       frame_count = backtrace(stack, MAX_BT_FRAMES);
+       frames = backtrace_symbols(stack, frame_count);
+
+       fprintf(stderr, "Backtrace of last %zu frames:\n", frame_count);
+       for (i = 0; i < frame_count; i++) {
+               fprintf(stderr, "%s\n", frames[i]);
+               /* Leak the backtrace strings, freeing may lead to undefined behaviour... */
+       }
+#endif
+
+       /* No panic action set... */
+       if (panic_action[0] == '\0') {
+               fprintf(stderr, "No panic action set\n");
+               _exit(1);
+       }
+
+       /* Substitute %p for the current PID (useful for attaching a debugger) */
+       while ((q = strstr(p, "%p"))) {
+               out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid());
+               if (left <= ret) {
+               oob:
+                       fprintf(stderr, "Panic action too long\n");
+                       _exit(1);
+               }
+               left -= ret;
+               p = q + 2;
+       }
+       if (strlen(p) >= left) goto oob;
+       strlcpy(out, p, left);
+
+       fprintf(stderr, "Calling: %s\n", cmd);
+       code = system(cmd);
+       fprintf(stderr, "Panic action exited with %i\n", code);
+
+       _exit(1);
+}
+
+/** 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 int 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;
+}
index 3528acb..8746e25 100644 (file)
@@ -32,6 +32,34 @@ RCSID("$Id$")
 int            fr_dns_lookups = 0;
 int            fr_debug_flag = 0;
 
+/** Sets a signal handler using sigaction if available, else signal
+ *
+ * @param sig to set handler for.
+ * @param func handler to set.
+ */
+int fr_set_signal(int sig, sig_t func)
+{
+#ifdef HAVE_SIGACTION
+       struct sigaction act;
+
+       memset(&act, 0, sizeof(act));
+       act.sa_flags = 0;
+       sigemptyset(&act.sa_mask);
+       act.sa_handler = func;
+
+       if (sigaction(sig, &act, NULL) < 0) {
+               fr_strerror_printf("Failed setting signal %i handler via sigaction(): %s", sig, strerror(errno));
+               return -1;
+       }
+#else
+       if (signal(sig, func) < 0) {
+               fr_strerror_printf("Failed setting signal %i handler via signal(): %s", sig, strerror(errno));
+               return -1;
+       }
+#endif
+       return 0;
+}
+
 /*
  *     Return an IP address in standard dot notation
  *
index 57592a1..cdb9fcd 100644 (file)
@@ -236,6 +236,7 @@ static const CONF_PARSER server_config[] = {
        { "run_dir",            PW_TYPE_STRING_PTR, 0, &run_dir,           "${localstatedir}/run/${name}"},
        { "libdir",             PW_TYPE_STRING_PTR, 0, &radlib_dir,        "${prefix}/lib"},
        { "radacctdir",         PW_TYPE_STRING_PTR, 0, &radacct_dir,       "${logdir}/radacct" },
+       { "panic_action",       PW_TYPE_STRING_PTR, 0, &mainconfig.panic_action, NULL},
        { "hostname_lookups",   PW_TYPE_BOOLEAN,    0, &fr_dns_lookups,      "no" },
        { "max_request_time", PW_TYPE_INTEGER, 0, &mainconfig.max_request_time, Stringify(MAX_REQUEST_TIME) },
        { "cleanup_delay", PW_TYPE_INTEGER, 0, &mainconfig.cleanup_delay, Stringify(CLEANUP_DELAY) },
index c68fdbf..a4ab503 100644 (file)
@@ -99,6 +99,14 @@ int main(int argc, char *argv[])
        int flag = 0;
        int from_child[2] = {-1, -1};
 
+       /*
+        *      If the server was built with debugging enabled always install
+        *      the basic fatal signal handlers.
+        */
+#ifndef NDEBUG
+       fr_fault_setup(getenv("PANIC_ACTION"), argv[0]);
+#endif
+
 #ifdef HAVE_SIGACTION
        struct sigaction act;
 #endif
@@ -275,6 +283,15 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       /* Set the panic action (if required) */
+       if (mainconfig.panic_action &&
+#ifndef NDEBUG
+           !getenv("PANIC_ACTION") &&
+#endif
+           (fr_fault_setup(mainconfig.panic_action, argv[0]) < 0)) {
+               exit(EXIT_FAILURE);
+       }
+
 #ifndef __MINGW32__
        /*
         *  Disconnect from session