The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
radlock.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: a34f01ab7d1045baa8c09d7e8d9fb2809ecd1e5b $
19  *
20  * @file radlock.c
21  * @brief Utility to examine semaphores used to provide exclusive running rights for a process
22  *
23  * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24  */
25 RCSID("$Id: a34f01ab7d1045baa8c09d7e8d9fb2809ecd1e5b $")
26 
27 #include <freeradius-devel/autoconf.h>
28 #include <freeradius-devel/util/perm.h>
29 #include <freeradius-devel/util/sem.h>
30 #include <freeradius-devel/util/value.h>
31 #include <freeradius-devel/util/syserror.h>
32 #include <freeradius-devel/util/strerror.h>
33 
34 #include <ctype.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <sys/ipc.h>
38 #include <sys/sem.h>
39 
40 #ifdef HAVE_GETOPT_H
41 # include <getopt.h>
42 #endif
43 #include <assert.h>
44 
45 DIAG_OFF(unused-macros)
46 #define INFO(fmt, ...) fprintf(stdout, fmt "\n", ## __VA_ARGS__)
47 DIAG_ON(unused-macros)
48 
49 typedef enum {
51  RADLOCK_LOCK, //!< Acquire the semaphore if it's at 0.
52  RADLOCK_TRYLOCK, //!< Try and lock the semaphore and return if we can't.
53  RADLOCK_UNLOCK, //!< Unlock the semaphore.
54  RADLOCK_REMOVE, //!< Remove the semaphore.
55  RADLOCK_INFO, //!< Information about the semaphore.
56  RADLOCK_PERM //!< Modify permissions for a given semaphore.
58 
60  { L("info"), RADLOCK_INFO },
61  { L("lock"), RADLOCK_LOCK },
62  { L("perm"), RADLOCK_PERM },
63  { L("remove"), RADLOCK_REMOVE },
64  { L("trylock"), RADLOCK_TRYLOCK },
65  { L("unlock"), RADLOCK_UNLOCK }
66 };
68 
69 static NEVER_RETURNS void usage(int ret)
70 {
71  fprintf(stderr, "usage: radlock <file> [lock|trylock|unlock|remove|info|perm]\n");
72  fprintf(stderr, " -u <uid> Desired user.\n");
73  fprintf(stderr, " -g <gid> Desired group.\n");
74  fprintf(stderr, " -m <perm> Octal permissions string.\n");
75  fprintf(stderr, " -h This help text.\n");
76  fprintf(stderr, "\n");
77  fprintf(stderr, "Simple utility to query the locking state of a config file\n");
78  fr_exit_now(ret);
79 }
80 
81 #define EXIT_WITH_FAILURE exit(EXIT_FAILURE)
82 #define EXIT_WITH_SUCCESS exit(EXIT_SUCCESS)
83 
84 /**
85  *
86  * @hidecallgraph
87  */
88 int main(int argc, char *argv[])
89 {
90  char c;
91  fr_radlock_action_t action;
92  char const *file;
93  uid_t uid = geteuid();
94  bool uid_set = false;
95  gid_t gid = getegid();
96  bool gid_set = false;
97  long mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
98  bool mode_set = false;
99  int sem_id;
100 
101  TALLOC_CTX *autofree;
102 
104 
105 #ifndef NDEBUG
106  if (fr_fault_setup(autofree, getenv("PANIC_ACTION"), argv[0]) < 0) {
107  fr_perror("radict");
108  fr_exit(EXIT_FAILURE);
109  }
110 #endif
111 
112  talloc_set_log_stderr();
113 
114  while ((c = getopt(argc, argv, "u:g:m:h")) != -1) switch (c) {
115  case 'u':
116  if (fr_perm_uid_from_str(autofree, &uid, optarg) < 0) {
117  fr_perror("radlock");
119  }
120  uid_set = true;
121  break;
122 
123  case 'g':
124  if (fr_perm_uid_from_str(autofree, &gid, optarg) < 0) {
125  fr_perror("radlock");
127  }
128  gid_set = true;
129  break;
130 
131  case 'm':
132  mode = strtol(optarg, NULL, 0); /* 0 base plus 0 prefix = octal */
133  if (errno == EINVAL) {
134  fr_perror("radlock - Bad mode value");
136  }
137  mode_set = true;
138  break;
139 
140  case 'h':
141  default:
142  usage(EXIT_SUCCESS);
143  }
144  argc -= optind;
145  argv += optind;
146 
147  if (argc == 0) {
148  fr_perror("radlock - Need file to operate on");
149  usage(64);
150  }
151 
152  if (argc == 1) {
153  fr_perror("radlock - Need action, must be one of (lock|trylock|unlock|remove|info|perm)");
154  usage(64);
155  }
156 
157  file = argv[0];
159  if (action == RADLOCK_INVALID) {
160  fr_perror("radlock - Action must be one of (lock|trylock|unlock|remove|info|perm), got %s", argv[1]);
161  usage(64);
162  }
163 
164  if (action == RADLOCK_PERM) {
165  fr_perror("radlock - At least one of -u, -g, -m must be specified");
166  usage(64);
167  }
168 
169  /*
170  * Mismatch between the binary and the libraries it depends on
171  */
173  fr_perror("radlock");
175  }
176 
177  switch (action) {
178  case RADLOCK_LOCK:
179  case RADLOCK_TRYLOCK:
180  sem_id = fr_sem_get(file, 0, uid, gid, false, false);
181  if (sem_id < 0) {
182  fr_perror("radlock");
184  }
185  switch (fr_sem_wait(sem_id, file, false, action == RADLOCK_TRYLOCK)) {
186  case 1: /* Already locked */
187  {
188  pid_t pid;
189 
190  fr_sem_pid(&pid, sem_id);
191  fr_perror("radlock - Can't lock \"%s\" already held by PID %u", file, pid);
193  }
194 
195  case 0:
197 
198  default:
199  break;
200  }
201  fr_perror("radlock");
203 
204  case RADLOCK_UNLOCK:
205  sem_id = fr_sem_get(file, 0, uid, gid, false, true);
206  if (sem_id == -4) EXIT_WITH_SUCCESS;
207  if (sem_id < 0) {
208  fr_perror("radlock");
210  }
211 
212  again:
213  switch (fr_sem_post(sem_id, file, false)) {
214  case 1: /* already unlocked */
216 
217  case 0:
218  goto again;
219 
220  default:
221  break;
222  }
223  fr_perror("radlock");
225 
226  case RADLOCK_REMOVE:
227  sem_id = fr_sem_get(file, 0, uid, gid, false, true);
228  if (sem_id == -4) EXIT_WITH_SUCCESS;
229 
230  if (fr_sem_close(sem_id, file) < 0) {
231  fr_perror("radlock");
233  }
235 
236  case RADLOCK_INFO:
237  {
238  struct semid_ds info;
239  char buff[10];
240  unsigned int perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
241  bool dead = false;
242  pid_t pid;
243  int ret;
244  int value;
245  char const *uid_str, *gid_str, *cuid_str, *cgid_str;
246 
247  sem_id = fr_sem_get(file, 0, uid, gid, false, true);
248  if (sem_id == -4) EXIT_WITH_FAILURE;
249  if (sem_id < 0) {
250  fr_perror("radlock");
252  }
253 
254  if (semctl(sem_id, 0, IPC_STAT, &info) < 0) {
255  fr_perror("radlock - Failed getting lock info for \"%s\": %s", file, fr_syserror(errno));
257  }
258 
259  if (fr_sem_pid(&pid, sem_id) < 0) {
260  fr_perror("radlock");
262  }
263 
264  ret = kill(sem_id, 0);
265  if ((ret < 0) && (errno == ESRCH)) dead = true;
266 
267  uid_str = fr_perm_uid_to_str(autofree, info.sem_perm.uid);
268  if (!uid_str) uid_str = "";
269 
270  gid_str = fr_perm_gid_to_str(autofree, info.sem_perm.gid);
271  if (!gid_str) gid_str = "";
272 
273  cuid_str = fr_perm_uid_to_str(autofree, info.sem_perm.cuid);
274  if (!cuid_str) cuid_str = "";
275 
276  cgid_str = fr_perm_gid_to_str(autofree, info.sem_perm.cgid);
277  if (!cgid_str) cgid_str = "";
278 
279  value = semctl(sem_id, 0, GETVAL);
280 
281  INFO("Locking information for \"%s\"", file);
282  INFO("\tsemid : %u", sem_id);
283  INFO("\tPermissions : %s", fr_perm_mode_to_str(buff, info.sem_perm.mode & perm));
284  INFO("\tValue : %u (%s)", value, value > 0 ? "locked" : "unlocked");
285  INFO("Last Modified:");
286  INFO("\tPID : %u (%s)", pid, dead ? "dead" : "alive");
287  INFO("\tUser : %s (%u)", uid_str, info.sem_perm.uid);
288  INFO("\tGroup : %s (%u)", gid_str, info.sem_perm.gid);
289  INFO("\tTime : %s",
290  fr_asprintf(autofree, "%pV", fr_box_time(fr_time_from_sec(info.sem_otime))));
291  INFO("Created:");
292  INFO("\tUser : %s (%u)", cuid_str, info.sem_perm.cuid);
293  INFO("\tGroup : %s (%u)", cgid_str, info.sem_perm.cgid);
294  INFO("\tTime : %s",
295  fr_asprintf(autofree, "%pV", fr_box_time(fr_time_from_sec(info.sem_ctime))));
296  }
298 
299  case RADLOCK_PERM:
300  {
301  struct semid_ds info;
302 
303  sem_id = fr_sem_get(file, 0, uid, gid, false, false); /* Will create if does not already exist */
304  if (sem_id < 0) {
305  fr_perror("radlock");
307  }
308 
309  if (semctl(sem_id, 0, IPC_STAT, &info) < 0) {
310  fr_perror("radlock - Failed getting lock info for \"%s\": %s",
311  fr_syserror(errno), file);
313  }
314 
315  if (uid_set) info.sem_perm.uid = uid;
316  if (gid_set) info.sem_perm.gid = gid;
317  if (mode_set) info.sem_perm.mode = mode;
318 
319  if (semctl(sem_id, 0, IPC_SET, &info) < 0) {
320  fr_perror("radlock - Failed setting lock permissions for \"%s\": %s",
321  fr_syserror(errno), file);
323  }
324  }
326 
327  case RADLOCK_INVALID:
328  usage(64);
329  }
330 
331  return 0;
332 }
int const char * file
Definition: acutest.h:702
#define RCSID(id)
Definition: build.h:444
#define NEVER_RETURNS
Should be placed before the function return type.
Definition: build.h:311
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define DIAG_ON(_x)
Definition: build.h:419
#define NUM_ELEMENTS(_t)
Definition: build.h:335
#define DIAG_OFF(_x)
Definition: build.h:418
int fr_fault_setup(TALLOC_CTX *ctx, char const *cmd, char const *program)
Registers signal handlers to execute panic_action on fatal signal.
Definition: debug.c:1215
#define fr_exit(_x)
Exit, producing a log message in debug builds.
Definition: debug.h:226
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition: debug.h:232
Test enumeration values.
Definition: dict_test.h:92
char const * fr_perm_mode_to_str(char out[static 10], mode_t mode)
Convert mode_t into humanly readable permissions flags.
Definition: perm.c:36
char * fr_perm_gid_to_str(TALLOC_CTX *ctx, uid_t gid)
Print gid to a string.
Definition: perm.c:386
int fr_perm_uid_from_str(TALLOC_CTX *ctx, uid_t *out, char const *name)
Resolve a user name to a GID.
Definition: perm.c:323
char * fr_perm_uid_to_str(TALLOC_CTX *ctx, uid_t uid)
Print uid to a string.
Definition: perm.c:366
char * fr_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Special version of asprintf which implements custom format specifiers.
Definition: print.c:876
static TALLOC_CTX * autofree
Definition: radclient-ng.c:104
static NEVER_RETURNS void usage(int ret)
Definition: radlock.c:69
fr_radlock_action_t
Definition: radlock.c:49
@ RADLOCK_TRYLOCK
Try and lock the semaphore and return if we can't.
Definition: radlock.c:52
@ RADLOCK_UNLOCK
Unlock the semaphore.
Definition: radlock.c:53
@ RADLOCK_PERM
Modify permissions for a given semaphore.
Definition: radlock.c:56
@ RADLOCK_LOCK
Acquire the semaphore if it's at 0.
Definition: radlock.c:51
@ RADLOCK_INVALID
Definition: radlock.c:50
@ RADLOCK_REMOVE
Remove the semaphore.
Definition: radlock.c:54
@ RADLOCK_INFO
Information about the semaphore.
Definition: radlock.c:55
int main(int argc, char *argv[])
Definition: radlock.c:88
static fr_table_num_sorted_t const radlock_action_table[]
Definition: radlock.c:59
#define EXIT_WITH_SUCCESS
Definition: radlock.c:82
#define EXIT_WITH_FAILURE
Definition: radlock.c:81
#define INFO(fmt,...)
Definition: radlock.c:46
static size_t radlock_action_table_len
Definition: radlock.c:67
int fr_sem_post(int sem_id, char const *file, bool undo_on_exit)
Decrement the semaphore by 1.
Definition: sem.c:182
int fr_sem_wait(int sem_id, char const *file, bool undo_on_exit, bool nonblock)
Wait for a semaphore to reach 0, then increment it by 1.
Definition: sem.c:252
int fr_sem_close(int sem_id, char const *file)
Remove the semaphore, this helps with permissions issues.
Definition: sem.c:329
int fr_sem_pid(pid_t *pid, int sem_id)
Return the PID of the process that last operated on the semaphore.
Definition: sem.c:53
int fr_sem_get(char const *file, int proj_id, uid_t uid, gid_t gid, bool check_perm, bool must_exist)
Returns a semid for the semaphore associated with the file.
Definition: sem.c:421
static char buff[sizeof("18446744073709551615")+3]
Definition: size_tests.c:41
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition: table.h:134
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition: talloc.h:51
static fr_time_t fr_time_from_sec(time_t when)
Convert a time_t (wallclock time) to a fr_time_t (internal time)
Definition: time.h:856
void fr_perror(char const *fmt,...)
Print the current error to stderr with a prefix.
Definition: strerror.c:733
int fr_check_lib_magic(uint64_t magic)
Check if the application linking to the library has the correct magic number.
Definition: version.c:40
#define RADIUSD_MAGIC_NUMBER
Definition: version.h:81
#define fr_box_time(_val)
Definition: value.h:319