]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add radlock to diagnose config locking issues
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 28 Apr 2021 20:49:15 +0000 (15:49 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 28 Apr 2021 20:54:10 +0000 (15:54 -0500)
redhat/freeradius.spec
src/bin/all.mk
src/bin/radlock.c [new file with mode: 0644]
src/bin/radlock.mk [new file with mode: 0644]

index b3193850079748ee8a64b8245d6b2884a3cdc9dd..6aa899f0b76bc33ad18edeed2f9cf5d0c716afb4 100644 (file)
@@ -860,6 +860,7 @@ fi
 /usr/bin/radcrypt
 /usr/bin/radict
 /usr/bin/radlast
+/usr/bin/radlock
 /usr/bin/radsniff
 /usr/bin/radsnmp
 /usr/bin/radsqlrelay
index 656f2a04bb2feb451728e745f5fa085c4137c926..ca90bc852438e005c36e561bde9bd900c9a5aa5f 100644 (file)
@@ -2,10 +2,11 @@ SUBMAKEFILES := \
     radclient.mk \
     radict.mk \
     radiusd.mk \
+    radlast.mk \
+    radlock.mk \
     radsniff.mk \
-    radwho.mk \
     radsnmp.mk \
-    radlast.mk \
+    radwho.mk \
     radtest.mk \
     radzap.mk \
     dhcpclient.mk \
diff --git a/src/bin/radlock.c b/src/bin/radlock.c
new file mode 100644 (file)
index 0000000..d1c8b78
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ *   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
+ */
+
+/**
+ * $Id$
+ *
+ * @file radlock.c
+ * @brief Utility to examine semaphores used to provide exclusive running rights for a process
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/autoconf.h>
+#include <freeradius-devel/util/perm.h>
+#include <freeradius-devel/util/sem.h>
+#include <freeradius-devel/util/value.h>
+#include <freeradius-devel/util/syserror.h>
+#include <freeradius-devel/util/strerror.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+
+#ifdef HAVE_GETOPT_H
+#  include <getopt.h>
+#endif
+#include <assert.h>
+
+DIAG_OFF(unused-macros)
+#define INFO(fmt, ...)         fprintf(stdout, fmt "\n", ## __VA_ARGS__)
+DIAG_ON(unused-macros)
+
+typedef enum {
+       RADLOCK_INVALID = 0,
+       RADLOCK_LOCK,           //!< Acquire the semaphore if it's at 0.
+       RADLOCK_TRYLOCK,        //!< Try and lock the semaphore and return if we can't.
+       RADLOCK_UNLOCK,         //!< Unlock the semaphore.
+       RADLOCK_REMOVE,         //!< Remove the semaphore.
+       RADLOCK_INFO,           //!< Information about the semaphore.
+       RADLOCK_PERM            //!< Modify permissions for a given semaphore.
+} fr_radlock_action_t;
+
+static fr_table_num_sorted_t const radlock_action_table[] = {
+       { L("info"),            RADLOCK_INFO    },
+       { L("lock"),            RADLOCK_LOCK    },
+       { L("perm"),            RADLOCK_PERM    },
+       { L("remove"),          RADLOCK_REMOVE  },
+       { L("trylock"),         RADLOCK_TRYLOCK },
+       { L("unlock"),          RADLOCK_UNLOCK  }
+};
+static size_t radlock_action_table_len = NUM_ELEMENTS(radlock_action_table);
+
+static void usage(int ret)
+{
+       fprintf(stderr, "usage: radlock <file> [lock|trylock|unlock|remove|info|perm]\n");
+       fprintf(stderr, "  -u <uid>         Desired user.\n");
+       fprintf(stderr, "  -g <gid>         Desired group.\n");
+       fprintf(stderr, "  -m <perm>        Octal permissions string.\n");
+       fprintf(stderr, "  -h               This help text.\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "Simple utility to query the locking state of a config file\n");
+       fr_exit_now(ret);
+}
+
+#define EXIT_WITH_FAILURE exit(EXIT_FAILURE)
+#define EXIT_WITH_SUCCESS exit(EXIT_SUCCESS)
+
+/**
+ *
+ * @hidecallgraph
+ */
+int main(int argc, char *argv[])
+{
+       char                    c;
+       fr_radlock_action_t     action;
+       char const              *file;
+       uid_t                   uid = geteuid();
+       bool                    uid_set = false;
+       gid_t                   gid = getegid();
+       bool                    gid_set = false;
+       long                    mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
+       bool                    mode_set = false;
+       int                     sem_id;
+
+       TALLOC_CTX              *autofree;
+
+       autofree = talloc_autofree_context();
+
+#ifndef NDEBUG
+       if (fr_fault_setup(autofree, getenv("PANIC_ACTION"), argv[0]) < 0) {
+               fr_perror("radict");
+               fr_exit(EXIT_FAILURE);
+       }
+#endif
+
+       talloc_set_log_stderr();
+
+       while ((c = getopt(argc, argv, "u:g:m:h")) != -1) switch (c) {
+               case 'u':
+                       if (fr_perm_uid_from_str(autofree, &uid, optarg) < 0) {
+                               fr_perror("radlock");
+                               EXIT_WITH_FAILURE;
+                       }
+                       uid_set = true;
+                       break;
+
+               case 'g':
+                       if (fr_perm_uid_from_str(autofree, &gid, optarg) < 0) {
+                               fr_perror("radlock");
+                               EXIT_WITH_FAILURE;
+                       }
+                       gid_set = true;
+                       break;
+
+               case 'm':
+                       mode = strtol(optarg, NULL, 0); /* 0 base plus 0 prefix = octal */
+                       if (errno == EINVAL) {
+                               fr_perror("radlock - Bad mode value");
+                               EXIT_WITH_FAILURE;
+                       }
+                       mode_set = true;
+                       break;
+
+               case 'h':
+               default:
+                       usage(EXIT_SUCCESS);
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (argc == 0) {
+               fr_perror("radlock - Need file to operate on");
+               usage(64);
+       };
+
+       if (argc == 1) {
+               fr_perror("radlock - Need action, must be one of (lock|trylock|unlock|remove|info|perm)");
+               usage(64);
+       }
+
+       file = argv[0];
+       action = fr_table_value_by_str(radlock_action_table, argv[1], RADLOCK_INVALID);
+       if (action == RADLOCK_INVALID) {
+               fr_perror("radlock - Action must be one of (lock|trylock|unlock|remove|info|perm), got %s", argv[1]);
+               usage(64);
+       }
+
+       if (action == RADLOCK_PERM) {
+               fr_perror("radlock - At least one of -u, -g, -m must be specified");
+               usage(64);
+       }
+
+       /*
+        *      Mismatch between the binary and the libraries it depends on
+        */
+       if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
+               fr_perror("radlock");
+               EXIT_WITH_FAILURE;
+       }
+
+       switch (action) {
+       case RADLOCK_LOCK:
+       case RADLOCK_TRYLOCK:
+               sem_id = fr_sem_get(file, 0, uid, gid, false, false);
+               if (sem_id < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+               switch (fr_sem_wait(sem_id, file, false, action == RADLOCK_TRYLOCK)) {
+               case 1: /* Already locked */
+               {
+                       pid_t pid;
+
+                       fr_sem_pid(&pid, sem_id);
+                       fr_perror("radlock - Can't lock \"%s\" already held by PID %u", file, pid);
+                       EXIT_WITH_FAILURE;
+               }
+
+               case 0:
+                       EXIT_WITH_SUCCESS;
+
+               default:
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+               break;
+
+       case RADLOCK_UNLOCK:
+               sem_id = fr_sem_get(file, 0, uid, gid, false, true);
+               if (sem_id == -4) EXIT_WITH_SUCCESS;
+               if (sem_id < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+
+       again:
+               switch (fr_sem_post(sem_id, file, false)) {
+               case 1: /* already unlocked */
+                       EXIT_WITH_SUCCESS;
+
+               case 0:
+                       goto again;
+
+               default:
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+               break;
+
+       case RADLOCK_REMOVE:
+               sem_id = fr_sem_get(file, 0, uid, gid, false, true);
+               if (sem_id == -4) EXIT_WITH_SUCCESS;
+
+               if (fr_sem_close(sem_id, file) < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+               break;
+
+       case RADLOCK_INFO:
+       {
+               struct semid_ds info;
+               char            buff[10];
+               unsigned int    perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+               bool            dead = false;
+               pid_t           pid;
+               int             ret;
+               int             value;
+               char const      *uid_str, *gid_str, *cuid_str, *cgid_str;
+
+               sem_id = fr_sem_get(file, 0, uid, gid, false, true);
+               if (sem_id == -4) EXIT_WITH_FAILURE;
+               if (sem_id < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+
+               if (semctl(sem_id, 0, IPC_STAT, &info) < 0) {
+                       fr_perror("radlock - Failed getting lock info for \"%s\": %s", file, fr_syserror(errno));
+                       EXIT_WITH_FAILURE;
+               }
+
+               if (fr_sem_pid(&pid, sem_id) < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+
+               ret = kill(sem_id, 0);
+               if ((ret < 0) && (errno == ESRCH)) dead = true;
+
+               uid_str = fr_perm_uid_to_str(autofree, info.sem_perm.uid);
+               if (!uid_str) uid_str = "";
+
+               gid_str = fr_perm_gid_to_str(autofree, info.sem_perm.gid);
+               if (!gid_str) gid_str = "";
+
+               cuid_str = fr_perm_uid_to_str(autofree, info.sem_perm.cuid);
+               if (!cuid_str) cuid_str = "";
+
+               cgid_str = fr_perm_gid_to_str(autofree, info.sem_perm.cgid);
+               if (!cgid_str) cgid_str = "";
+
+               value = semctl(sem_id, 0, GETVAL);
+
+               INFO("Locking information for \"%s\"", file);
+               INFO("\tKey           : 0x%x", info.sem_perm._key);
+               INFO("\tsemid         : %u", sem_id);
+               INFO("\tPermissions   : %s", fr_perm_mode_to_str(buff, info.sem_perm.mode & perm));
+               INFO("\tValue         : %u (%s)", value, value > 0 ? "locked" : "unlocked");
+               INFO("Last Modified:");
+               INFO("\tPID           : %u (%s)", pid, dead ? "dead" : "alive");
+               INFO("\tUser          : %s (%u)", uid_str, info.sem_perm.uid);
+               INFO("\tGroup         : %s (%u)", gid_str, info.sem_perm.gid);
+               INFO("\tTime          : %s",
+                    fr_asprintf(autofree, "%pV", fr_box_date(fr_time_from_sec(info.sem_otime))));
+               INFO("Created:");
+               INFO("\tUser          : %s (%u)", cuid_str, info.sem_perm.cuid);
+               INFO("\tGroup         : %s (%u)", cgid_str, info.sem_perm.cgid);
+               INFO("\tTime          : %s",
+                    fr_asprintf(autofree, "%pV", fr_box_date(fr_time_from_sec(info.sem_ctime))));
+               EXIT_WITH_SUCCESS;
+       }
+               break;
+
+       case RADLOCK_PERM:
+       {
+               struct semid_ds info;
+
+               sem_id = fr_sem_get(file, 0, uid, gid, false, false);   /* Will create if does not already exist */
+               if (sem_id < 0) {
+                       fr_perror("radlock");
+                       EXIT_WITH_FAILURE;
+               }
+
+               if (semctl(sem_id, 0, IPC_STAT, &info) < 0) {
+                       fr_perror("radlock - Failed getting lock info for \"%s\": %s",
+                                 fr_syserror(errno), file);
+                       EXIT_WITH_FAILURE;
+               }
+
+               if (uid_set) info.sem_perm.uid = uid;
+               if (gid_set) info.sem_perm.gid = gid;
+               if (mode_set) info.sem_perm.mode = mode;
+
+               if (semctl(sem_id, 0, IPC_SET, &info) < 0) {
+                       fr_perror("radlock - Failed setting lock permissions for \"%s\": %s",
+                                 fr_syserror(errno), file);
+                       EXIT_WITH_FAILURE;
+               }
+       }
+               break;
+
+       case RADLOCK_INVALID:
+               usage(64);
+               break;
+       }
+
+       return 0;
+}
diff --git a/src/bin/radlock.mk b/src/bin/radlock.mk
new file mode 100644 (file)
index 0000000..2dd2038
--- /dev/null
@@ -0,0 +1,6 @@
+TARGET         := radlock
+SOURCES                := radlock.c
+
+TGT_PREREQS    := libfreeradius-util.a
+
+TGT_LDLIBS     := $(LIBS)