From: Arran Cudbard-Bell Date: Wed, 28 Apr 2021 20:49:15 +0000 (-0500) Subject: Add radlock to diagnose config locking issues X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d06c170a09757bd67a8892dd23f08ae9a24a96d2;p=thirdparty%2Ffreeradius-server.git Add radlock to diagnose config locking issues --- diff --git a/redhat/freeradius.spec b/redhat/freeradius.spec index b3193850079..6aa899f0b76 100644 --- a/redhat/freeradius.spec +++ b/redhat/freeradius.spec @@ -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 diff --git a/src/bin/all.mk b/src/bin/all.mk index 656f2a04bb2..ca90bc85243 100644 --- a/src/bin/all.mk +++ b/src/bin/all.mk @@ -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 index 00000000000..d1c8b786846 --- /dev/null +++ b/src/bin/radlock.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_GETOPT_H +# include +#endif +#include + +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 [lock|trylock|unlock|remove|info|perm]\n"); + fprintf(stderr, " -u Desired user.\n"); + fprintf(stderr, " -g Desired group.\n"); + fprintf(stderr, " -m 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 index 00000000000..2dd2038dc14 --- /dev/null +++ b/src/bin/radlock.mk @@ -0,0 +1,6 @@ +TARGET := radlock +SOURCES := radlock.c + +TGT_PREREQS := libfreeradius-util.a + +TGT_LDLIBS := $(LIBS)