/src/freeradius-server/src/lib/server/util.c
Line | Count | Source |
1 | | /* |
2 | | * util.c Various utility functions. |
3 | | * |
4 | | * Version: $Id: 32c720404e74d8c5f1beb14bd6ffaf0ca6c85120 $ |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
19 | | * |
20 | | |
21 | | */ |
22 | | |
23 | | RCSID("$Id: 32c720404e74d8c5f1beb14bd6ffaf0ca6c85120 $") |
24 | | |
25 | | #include <freeradius-devel/server/base.h> |
26 | | |
27 | | #include <freeradius-devel/util/base16.h> |
28 | | #include <freeradius-devel/util/skip.h> |
29 | | #include <freeradius-devel/util/perm.h> |
30 | | #include <freeradius-devel/util/cap.h> |
31 | | |
32 | | |
33 | | #include <fcntl.h> |
34 | | |
35 | | static bool suid_down_permanent = false; //!< Record whether we've permanently dropped privilledges |
36 | | |
37 | | /* |
38 | | * The signal() function in Solaris 2.5.1 sets SA_NODEFER in |
39 | | * sa_flags, which causes grief if signal() is called in the |
40 | | * handler before the cause of the signal has been cleared. |
41 | | * (Infinite recursion). |
42 | | * |
43 | | * The same problem appears on HPUX, so we avoid it, if we can. |
44 | | * |
45 | | * Using sigaction() to reset the signal handler fixes the problem, |
46 | | * so where available, we prefer that solution. |
47 | | */ |
48 | | |
49 | | void (*reset_signal(int signo, void (*func)(int)))(int) |
50 | 0 | { |
51 | 0 | #ifdef HAVE_SIGACTION |
52 | 0 | struct sigaction act, oact; |
53 | |
|
54 | 0 | memset(&act, 0, sizeof(act)); |
55 | 0 | act.sa_handler = func; |
56 | 0 | sigemptyset(&act.sa_mask); |
57 | 0 | act.sa_flags = 0; |
58 | 0 | #ifdef SA_INTERRUPT /* SunOS */ |
59 | 0 | act.sa_flags |= SA_INTERRUPT; |
60 | 0 | #endif |
61 | 0 | if (sigaction(signo, &act, &oact) < 0) |
62 | 0 | return SIG_ERR; |
63 | 0 | return oact.sa_handler; |
64 | | #else |
65 | | |
66 | | /* |
67 | | * re-set by calling the 'signal' function, which |
68 | | * may cause infinite recursion and core dumps due to |
69 | | * stack growth. |
70 | | * |
71 | | * However, the system is too dumb to implement sigaction(), |
72 | | * so we don't have a choice. |
73 | | */ |
74 | | signal(signo, func); |
75 | | |
76 | | return NULL; |
77 | | #endif |
78 | 0 | } |
79 | | |
80 | | /** Ensures that a filename cannot walk up the directory structure |
81 | | * |
82 | | * Also sanitizes control chars. |
83 | | * |
84 | | * @param out Output buffer. |
85 | | * @param in string to escape. |
86 | | * @param len Size of the output buffer. |
87 | | */ |
88 | | static ssize_t rad_filename_make_safe(char *out, char const *in, size_t len) |
89 | 0 | { |
90 | 0 | char const *q, *end; |
91 | 0 | char *p = out; |
92 | |
|
93 | 0 | q = in; |
94 | 0 | end = in + len; |
95 | | |
96 | | /* |
97 | | * Escape '.foo', as it could be used to either read the local directory, walk back up the |
98 | | * directory hierarchy, or else to create "dot" files. |
99 | | */ |
100 | 0 | if (len > 1) { |
101 | 0 | if (*q == '.') { |
102 | 0 | *(p++) = '_'; |
103 | 0 | q++; |
104 | 0 | } |
105 | 0 | } |
106 | |
|
107 | 0 | while (q < end) { |
108 | 0 | if (*q < ' ') { |
109 | 0 | *(p++) = '_'; |
110 | 0 | q++; |
111 | 0 | continue; |
112 | 0 | } |
113 | | |
114 | 0 | if (*q != '/') { |
115 | 0 | *(p++) = *(q++); |
116 | 0 | continue; |
117 | 0 | } |
118 | | |
119 | | /* |
120 | | * Mash slashes, too. |
121 | | */ |
122 | 0 | *(p++) = '_'; |
123 | 0 | q++; |
124 | 0 | continue; |
125 | 0 | } |
126 | 0 | *p = '\0'; |
127 | |
|
128 | 0 | return (p - out); |
129 | 0 | } |
130 | | |
131 | | int rad_filename_box_make_safe(fr_value_box_t *vb, UNUSED void *uxtc) |
132 | | { |
133 | | char *escaped; |
134 | | size_t len; |
135 | | |
136 | | if (vb->vb_length == 0) return 0; |
137 | | |
138 | | if (fr_value_box_is_safe_for(vb, rad_filename_box_make_safe)) return 1; |
139 | | |
140 | | /* |
141 | | * Allocate an output buffer, only ever the same or shorter than the input |
142 | | */ |
143 | | MEM(escaped = talloc_array(vb, char, vb->vb_length + 1)); |
144 | | |
145 | | len = rad_filename_make_safe(escaped, vb->vb_strvalue, vb->vb_length); |
146 | | |
147 | | fr_value_box_strdup_shallow_replace(vb, escaped, len); |
148 | | |
149 | | fr_value_box_mark_safe_for(vb, rad_filename_box_make_safe); |
150 | | |
151 | | return 1; |
152 | | } |
153 | | |
154 | | /** Escapes the raw string such that it should be safe to use as part of a file path |
155 | | * |
156 | | * This function is designed to produce a string that's still readable but portable |
157 | | * across the majority of file systems. |
158 | | * |
159 | | * For security reasons it cannot remove characters from the name, and must not allow |
160 | | * collisions to occur between different strings. |
161 | | * |
162 | | * With that in mind '-' has been chosen as the escape character, and will be double |
163 | | * escaped '-' -> '--' to avoid collisions. |
164 | | * |
165 | | * Escaping should be reversible if the original string needs to be extracted. |
166 | | * |
167 | | * @note function takes additional arguments so that it may be used as an xlat escape |
168 | | * function but it's fine to call it directly. |
169 | | * |
170 | | * @note OSX/Unix/NTFS/VFAT have a max filename size of 255 bytes. |
171 | | * |
172 | | * @param request Current request (may be NULL). |
173 | | * @param out Output buffer. |
174 | | * @param outlen Size of the output buffer. |
175 | | * @param in string to escape. |
176 | | * @param arg Context arguments (unused, should be NULL). |
177 | | */ |
178 | | ssize_t rad_filename_escape(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg) |
179 | 0 | { |
180 | 0 | size_t freespace = outlen; |
181 | |
|
182 | 0 | while (*in != '\0') { |
183 | 0 | size_t utf8_len; |
184 | | |
185 | | /* |
186 | | * Encode multibyte UTF8 chars |
187 | | */ |
188 | 0 | utf8_len = fr_utf8_char((uint8_t const *) in, -1); |
189 | 0 | if (utf8_len > 1) { |
190 | 0 | if (freespace <= (utf8_len * 3)) break; |
191 | | |
192 | 0 | switch (utf8_len) { |
193 | 0 | case 2: |
194 | 0 | snprintf(out, freespace, "-%02x-%02x", (uint8_t)in[0], (uint8_t)in[1]); |
195 | 0 | break; |
196 | | |
197 | 0 | case 3: |
198 | 0 | snprintf(out, freespace, "-%02x-%02x-%02x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2]); |
199 | 0 | break; |
200 | | |
201 | 0 | case 4: |
202 | 0 | snprintf(out, freespace, "-%02x-%02x-%02x-%02x", (uint8_t)in[0], (uint8_t)in[1], (uint8_t)in[2], (uint8_t)in[3]); |
203 | 0 | break; |
204 | 0 | } |
205 | | |
206 | 0 | freespace -= (utf8_len * 3); |
207 | 0 | out += (utf8_len * 3); |
208 | 0 | in += utf8_len; |
209 | |
|
210 | 0 | continue; |
211 | 0 | } |
212 | | |
213 | | /* |
214 | | * Safe chars |
215 | | */ |
216 | 0 | if (((*in >= 'A') && (*in <= 'Z')) || |
217 | 0 | ((*in >= 'a') && (*in <= 'z')) || |
218 | 0 | ((*in >= '0') && (*in <= '9')) || |
219 | 0 | (*in == '_')) { |
220 | 0 | if (freespace <= 1) break; |
221 | | |
222 | 0 | *out++ = *in++; |
223 | 0 | freespace--; |
224 | 0 | continue; |
225 | 0 | } |
226 | 0 | if (freespace <= 2) break; |
227 | | |
228 | | /* |
229 | | * Double escape '-' (like \\) |
230 | | */ |
231 | 0 | if (*in == '-') { |
232 | 0 | *out++ = '-'; |
233 | 0 | *out++ = '-'; |
234 | |
|
235 | 0 | freespace -= 2; |
236 | 0 | in++; |
237 | 0 | continue; |
238 | 0 | } |
239 | | |
240 | | /* |
241 | | * Unsafe chars get escaped as -XX. |
242 | | */ |
243 | 0 | snprintf(out, freespace, "-%02x", (uint8_t) in[0]); |
244 | 0 | in++; |
245 | 0 | out += 3; |
246 | 0 | freespace -= 3; |
247 | 0 | } |
248 | 0 | *out = '\0'; |
249 | |
|
250 | 0 | return outlen - freespace; |
251 | 0 | } |
252 | | |
253 | | int rad_filename_box_escape(fr_value_box_t *vb, UNUSED void *uxtc) |
254 | 0 | { |
255 | 0 | char *escaped; |
256 | 0 | size_t len; |
257 | |
|
258 | 0 | if (vb->vb_length == 0) return 0; |
259 | | |
260 | 0 | fr_assert(!fr_value_box_is_safe_for(vb, rad_filename_box_escape)); |
261 | | |
262 | | /* |
263 | | * Allocate an output buffer, if every character is escaped, |
264 | | * it will be 3 times the input |
265 | | */ |
266 | 0 | MEM(escaped = talloc_array(vb, char, vb->vb_length * 3 + 1)); |
267 | |
|
268 | 0 | len = rad_filename_escape(NULL, escaped, (vb->vb_length * 3 + 1), vb->vb_strvalue, NULL); |
269 | | |
270 | | /* |
271 | | * If the escaped length == input length, no changes were done. |
272 | | */ |
273 | 0 | if (len == vb->vb_length) { |
274 | 0 | talloc_free(escaped); |
275 | 0 | return 0; |
276 | 0 | } |
277 | | |
278 | 0 | fr_value_box_strdup_shallow_replace(vb, escaped, len); |
279 | |
|
280 | 0 | return 0; |
281 | 0 | } |
282 | | |
283 | | |
284 | | /* |
285 | | * Copy a quoted string. |
286 | | */ |
287 | | static int rad_copy_string(char *to, char const *from) |
288 | 0 | { |
289 | 0 | int length = 0; |
290 | 0 | char quote = *from; |
291 | |
|
292 | 0 | do { |
293 | 0 | if (*from == '\\') { |
294 | 0 | *(to++) = *(from++); |
295 | 0 | length++; |
296 | 0 | } |
297 | 0 | *(to++) = *(from++); |
298 | 0 | length++; |
299 | 0 | } while (*from && (*from != quote)); |
300 | |
|
301 | 0 | if (*from != quote) return -1; /* not properly quoted */ |
302 | | |
303 | 0 | *(to++) = quote; |
304 | 0 | length++; |
305 | 0 | *to = '\0'; |
306 | |
|
307 | 0 | return length; |
308 | 0 | } |
309 | | |
310 | | /* |
311 | | * Copy a quoted string but without the quotes. The length |
312 | | * returned is the number of chars written; the number of |
313 | | * characters consumed is 2 more than this. |
314 | | */ |
315 | | static int rad_copy_string_bare(char *to, char const *from) |
316 | 0 | { |
317 | 0 | int length = 0; |
318 | 0 | char quote = *from; |
319 | |
|
320 | 0 | from++; |
321 | 0 | while (*from && (*from != quote)) { |
322 | 0 | if (*from == '\\') { |
323 | 0 | *(to++) = *(from++); |
324 | 0 | length++; |
325 | 0 | } |
326 | 0 | *(to++) = *(from++); |
327 | 0 | length++; |
328 | 0 | } |
329 | |
|
330 | 0 | if (*from != quote) return -1; /* not properly quoted */ |
331 | | |
332 | 0 | *to = '\0'; |
333 | |
|
334 | 0 | return length; |
335 | 0 | } |
336 | | |
337 | | |
338 | | /* |
339 | | * Copy a %{} string. |
340 | | */ |
341 | | static int rad_copy_variable(char *to, char const *from) |
342 | 0 | { |
343 | 0 | int length = 0; |
344 | 0 | int sublen; |
345 | |
|
346 | 0 | *(to++) = *(from++); |
347 | 0 | length++; |
348 | |
|
349 | 0 | while (*from) { |
350 | 0 | switch (*from) { |
351 | 0 | case '"': |
352 | 0 | case '\'': |
353 | 0 | sublen = rad_copy_string(to, from); |
354 | 0 | if (sublen < 0) return sublen; |
355 | 0 | from += sublen; |
356 | 0 | to += sublen; |
357 | 0 | length += sublen; |
358 | 0 | break; |
359 | | |
360 | 0 | case '}': /* end of variable expansion */ |
361 | 0 | *(to++) = *(from++); |
362 | 0 | *to = '\0'; |
363 | 0 | length++; |
364 | 0 | return length; /* proper end of variable */ |
365 | | |
366 | 0 | case '\\': |
367 | 0 | *(to++) = *(from++); |
368 | 0 | *(to++) = *(from++); |
369 | 0 | length += 2; |
370 | 0 | break; |
371 | | |
372 | 0 | case '%': /* start of variable expansion */ |
373 | 0 | if (from[1] == '{') { |
374 | 0 | *(to++) = *(from++); |
375 | 0 | length++; |
376 | |
|
377 | 0 | sublen = rad_copy_variable(to, from); |
378 | 0 | if (sublen < 0) return sublen; |
379 | 0 | from += sublen; |
380 | 0 | to += sublen; |
381 | 0 | length += sublen; |
382 | 0 | break; |
383 | 0 | } /* else FIXME: catch %%{ ?*/ |
384 | | |
385 | 0 | FALL_THROUGH; |
386 | 0 | default: |
387 | 0 | *(to++) = *(from++); |
388 | 0 | length++; |
389 | 0 | break; |
390 | 0 | } |
391 | 0 | } /* loop over the input string */ |
392 | | |
393 | | /* |
394 | | * We ended the string before a trailing '}' |
395 | | */ |
396 | | |
397 | 0 | return -1; |
398 | 0 | } |
399 | | |
400 | | uint32_t rad_pps(uint32_t *past, uint32_t *present, time_t *then, struct timeval *now) |
401 | 0 | { |
402 | 0 | uint32_t pps; |
403 | |
|
404 | 0 | if (*then != now->tv_sec) { |
405 | 0 | *then = now->tv_sec; |
406 | 0 | *past = *present; |
407 | 0 | *present = 0; |
408 | 0 | } |
409 | | |
410 | | /* |
411 | | * Bootstrap PPS by looking at a percentage of |
412 | | * the previous PPS. This lets us take a moving |
413 | | * count, without doing a moving average. If |
414 | | * we're a fraction "f" (0..1) into the current |
415 | | * second, we can get a good guess for PPS by |
416 | | * doing: |
417 | | * |
418 | | * PPS = pps_now + pps_old * (1 - f) |
419 | | * |
420 | | * It's an instantaneous measurement, rather than |
421 | | * a moving average. This will hopefully let it |
422 | | * respond better to sudden spikes. |
423 | | * |
424 | | * Doing the calculations by thousands allows us |
425 | | * to not overflow 2^32, AND to not underflow |
426 | | * when we divide by USEC. |
427 | | */ |
428 | 0 | pps = USEC - now->tv_usec; /* useconds left in previous second */ |
429 | 0 | pps /= 1000; /* scale to milliseconds */ |
430 | 0 | pps *= *past; /* multiply by past count to get fraction */ |
431 | 0 | pps /= 1000; /* scale to usec again */ |
432 | 0 | pps += *present; /* add in current count */ |
433 | |
|
434 | 0 | return pps; |
435 | 0 | } |
436 | | |
437 | | /** Split string into words and expand each one |
438 | | * |
439 | | * @param request Current request. |
440 | | * @param cmd string to split. |
441 | | * @param max_argc the maximum number of arguments to split into. |
442 | | * @param argv Where to write the pointers into argv_buf. |
443 | | * @param can_fail If false, stop processing if any of the xlat expansions fail. |
444 | | * @param argv_buflen size of argv_buf. |
445 | | * @param argv_buf temporary buffer we used to mangle/expand cmd. |
446 | | * Pointers to offsets of this buffer will be written to argv. |
447 | | * @return argc or -1 on failure. |
448 | | */ |
449 | | |
450 | | int rad_expand_xlat(request_t *request, char const *cmd, |
451 | | int max_argc, char const *argv[], bool can_fail, |
452 | | size_t argv_buflen, char *argv_buf) |
453 | 0 | { |
454 | 0 | char const *from; |
455 | 0 | char *to; |
456 | 0 | int argc = -1; |
457 | 0 | int i; |
458 | 0 | int left; |
459 | 0 | size_t len = strlen(cmd); |
460 | |
|
461 | 0 | if (len > (argv_buflen - 1)) { |
462 | 0 | fr_strerror_const("Expansion string is too long for output buffer"); |
463 | 0 | return -1; |
464 | 0 | } |
465 | | |
466 | | /* |
467 | | * Check for bad escapes. |
468 | | */ |
469 | 0 | if ((len > 0) && (cmd[len - 1] == '\\')) { |
470 | 0 | fr_strerror_const("Expansion string ends with a trailing backslash - invalid escape sequence"); |
471 | 0 | return -1; |
472 | 0 | } |
473 | | |
474 | 0 | strlcpy(argv_buf, cmd, argv_buflen); |
475 | | |
476 | | /* |
477 | | * Split the string into argv's BEFORE doing xlat_eval... |
478 | | */ |
479 | 0 | from = cmd; |
480 | 0 | to = argv_buf; |
481 | 0 | argc = 0; |
482 | 0 | while (*from) { |
483 | 0 | int length; |
484 | |
|
485 | 0 | fr_skip_whitespace(from); |
486 | |
|
487 | 0 | argv[argc] = to; |
488 | 0 | argc++; |
489 | |
|
490 | 0 | if (argc >= (max_argc - 1)) break; |
491 | | |
492 | | /* |
493 | | * Copy the argv over to our buffer. |
494 | | */ |
495 | 0 | while (*from && (*from != ' ') && (*from != '\t')) { |
496 | 0 | if (to >= argv_buf + argv_buflen - 1) { |
497 | 0 | fr_strerror_const("Expansion string is too long for output buffer"); |
498 | 0 | return -1; |
499 | 0 | } |
500 | | |
501 | 0 | switch (*from) { |
502 | 0 | case '"': |
503 | 0 | case '\'': |
504 | 0 | length = rad_copy_string_bare(to, from); |
505 | 0 | if (length < 0) { |
506 | 0 | fr_strerror_const("Invalid quoted string in expansion"); |
507 | 0 | return -1; |
508 | 0 | } |
509 | 0 | from += length+2; |
510 | 0 | to += length; |
511 | 0 | break; |
512 | | |
513 | 0 | case '%': |
514 | 0 | if (from[1] == '{') { |
515 | 0 | *(to++) = *(from++); |
516 | |
|
517 | 0 | length = rad_copy_variable(to, from); |
518 | 0 | if (length < 0) { |
519 | 0 | fr_strerror_const("Invalid variable in expansion"); |
520 | 0 | return -1; |
521 | 0 | } |
522 | 0 | from += length; |
523 | 0 | to += length; |
524 | 0 | } else { /* FIXME: catch %%{ ? */ |
525 | 0 | *(to++) = *(from++); |
526 | 0 | } |
527 | 0 | break; |
528 | | |
529 | 0 | case '\\': |
530 | 0 | if (from[1] == ' ') from++; |
531 | 0 | FALL_THROUGH; |
532 | |
|
533 | 0 | default: |
534 | 0 | *(to++) = *(from++); |
535 | 0 | } |
536 | 0 | } /* end of string, or found a space */ |
537 | | |
538 | 0 | *(to++) = '\0'; /* terminate the string */ |
539 | 0 | } |
540 | | |
541 | | /* |
542 | | * We have to have SOMETHING, at least. |
543 | | */ |
544 | 0 | if (argc <= 0) { |
545 | 0 | fr_strerror_const("Expansion string is empty"); |
546 | 0 | return -1; |
547 | 0 | } |
548 | | |
549 | | /* |
550 | | * Expand each string, as appropriate. |
551 | | */ |
552 | 0 | left = argv_buf + argv_buflen - to; |
553 | 0 | for (i = 0; i < argc; i++) { |
554 | 0 | int sublen; |
555 | | |
556 | | /* |
557 | | * Don't touch argv's which won't be translated. |
558 | | */ |
559 | 0 | if (strchr(argv[i], '%') == NULL) continue; |
560 | | |
561 | 0 | if (!request) continue; |
562 | | |
563 | 0 | sublen = xlat_eval(to, left - 1, request, argv[i], NULL, NULL); |
564 | 0 | if (sublen <= 0) { |
565 | 0 | if (can_fail) { |
566 | | /* |
567 | | * Fail to be backwards compatible. |
568 | | * |
569 | | * It's yucky, but it won't break anything, |
570 | | * and it won't cause security problems. |
571 | | */ |
572 | 0 | sublen = 0; |
573 | 0 | } else { |
574 | 0 | fr_strerror_const("Failed expanding substring"); |
575 | 0 | return -1; |
576 | 0 | } |
577 | 0 | } |
578 | | |
579 | 0 | argv[i] = to; |
580 | 0 | to += sublen; |
581 | 0 | *(to++) = '\0'; |
582 | 0 | left -= sublen; |
583 | 0 | left--; |
584 | |
|
585 | 0 | if (left <= 0) { |
586 | 0 | fr_strerror_const("Ran out of space while expanding arguments"); |
587 | 0 | return -1; |
588 | 0 | } |
589 | 0 | } |
590 | 0 | argv[argc] = NULL; |
591 | |
|
592 | 0 | return argc; |
593 | 0 | } |
594 | | |
595 | | #ifdef HAVE_SETUID |
596 | | static bool doing_setuid = false; |
597 | | static uid_t suid_down_uid = (uid_t)-1; |
598 | | |
599 | | /** Set the uid and gid used when dropping privileges |
600 | | * |
601 | | * @note if this function hasn't been called, rad_suid_down will have no effect. |
602 | | * |
603 | | * @param uid to drop down to. |
604 | | */ |
605 | | void rad_suid_set_down_uid(uid_t uid) |
606 | 0 | { |
607 | 0 | suid_down_uid = uid; |
608 | 0 | doing_setuid = true; |
609 | 0 | } |
610 | | |
611 | | # if defined(HAVE_SETRESUID) && defined (HAVE_GETRESUID) |
612 | | void rad_suid_up(void) |
613 | 0 | { |
614 | 0 | uid_t ruid, euid, suid; |
615 | |
|
616 | 0 | if (getresuid(&ruid, &euid, &suid) < 0) { |
617 | 0 | ERROR("Failed getting saved UID's"); |
618 | 0 | fr_exit_now(EXIT_FAILURE); |
619 | 0 | } |
620 | |
|
621 | 0 | if (setresuid(-1, suid, -1) < 0) { |
622 | 0 | ERROR("Failed switching to privileged user"); |
623 | 0 | fr_exit_now(EXIT_FAILURE); |
624 | 0 | } |
625 | |
|
626 | 0 | if (geteuid() != suid) { |
627 | 0 | ERROR("Switched to unknown UID"); |
628 | 0 | fr_exit_now(EXIT_FAILURE); |
629 | 0 | } |
630 | 0 | } |
631 | | |
632 | | void rad_suid_down(void) |
633 | 0 | { |
634 | 0 | if (!doing_setuid) return; |
635 | | |
636 | 0 | if (setresuid(-1, suid_down_uid, geteuid()) < 0) { |
637 | 0 | struct passwd *passwd; |
638 | 0 | char const *name; |
639 | |
|
640 | 0 | name = (fr_perm_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown" : passwd->pw_name; |
641 | 0 | ERROR("Failed switching to uid %s: %s", name, fr_syserror(errno)); |
642 | 0 | talloc_free(passwd); |
643 | 0 | fr_exit_now(EXIT_FAILURE); |
644 | 0 | } |
645 | |
|
646 | 0 | if (geteuid() != suid_down_uid) { |
647 | 0 | ERROR("Failed switching uid: UID is incorrect"); |
648 | 0 | fr_exit_now(EXIT_FAILURE); |
649 | 0 | } |
650 | |
|
651 | 0 | fr_reset_dumpable(); |
652 | 0 | } |
653 | | |
654 | | void rad_suid_down_permanent(void) |
655 | | { |
656 | | if (!doing_setuid) return; |
657 | | |
658 | | if (setresuid(suid_down_uid, suid_down_uid, suid_down_uid) < 0) { |
659 | | struct passwd *passwd; |
660 | | char const *name; |
661 | | |
662 | | name = (fr_perm_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown" : passwd->pw_name; |
663 | | ERROR("Failed in permanent switch to uid %s: %s", name, fr_syserror(errno)); |
664 | | talloc_free(passwd); |
665 | | fr_exit_now(EXIT_FAILURE); |
666 | | } |
667 | | |
668 | | if (geteuid() != suid_down_uid) { |
669 | | ERROR("Switched to unknown uid"); |
670 | | fr_exit_now(EXIT_FAILURE); |
671 | | } |
672 | | |
673 | | /* |
674 | | * Shut down most of the interesting things which might get abused. |
675 | | */ |
676 | | if ((fr_cap_disable(CAP_SETUID, CAP_EFFECTIVE) < 0) || |
677 | | (fr_cap_disable(CAP_SETUID, CAP_INHERITABLE) < 0) || |
678 | | (fr_cap_disable(CAP_SETUID, CAP_PERMITTED) < 0)) { |
679 | | ERROR("Failed disabling CAP_SUID"); |
680 | | fr_exit_now(EXIT_FAILURE); |
681 | | } |
682 | | |
683 | | #ifdef HAVE_GRP_H |
684 | | if ((fr_cap_disable(CAP_SETGID, CAP_EFFECTIVE) < 0) || |
685 | | (fr_cap_disable(CAP_SETGID, CAP_INHERITABLE) < 0) || |
686 | | (fr_cap_disable(CAP_SETGID, CAP_PERMITTED) < 0)) { |
687 | | ERROR("Failed disabling CAP_SGID"); |
688 | | fr_exit_now(EXIT_FAILURE); |
689 | | } |
690 | | #endif |
691 | | |
692 | | fr_reset_dumpable(); |
693 | | |
694 | | suid_down_permanent = true; |
695 | | } |
696 | | # else |
697 | | /* |
698 | | * Much less secure... |
699 | | */ |
700 | | void rad_suid_up(void) |
701 | | { |
702 | | if (!doing_setuid) return; |
703 | | |
704 | | if (seteuid(0) < 0) { |
705 | | ERROR("Failed switching up to euid 0: %s", fr_syserror(errno)); |
706 | | fr_exit_now(EXIT_FAILURE); |
707 | | } |
708 | | |
709 | | } |
710 | | |
711 | | void rad_suid_down(void) |
712 | | { |
713 | | if (!doing_setuid) return; |
714 | | |
715 | | if (geteuid() == suid_down_uid) return; |
716 | | |
717 | | if (seteuid(suid_down_uid) < 0) { |
718 | | struct passwd *passwd; |
719 | | char const *name; |
720 | | |
721 | | name = (fr_perm_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown": passwd->pw_name; |
722 | | ERROR("Failed switching to euid %s: %s", name, fr_syserror(errno)); |
723 | | talloc_free(passwd); |
724 | | fr_exit_now(EXIT_FAILURE); |
725 | | } |
726 | | |
727 | | fr_reset_dumpable(); |
728 | | } |
729 | | |
730 | | void rad_suid_down_permanent(void) |
731 | | { |
732 | | if (!doing_setuid) return; |
733 | | |
734 | | /* |
735 | | * Already done. Don't do anything else. |
736 | | */ |
737 | | if (getuid() == suid_down_uid) return; |
738 | | |
739 | | /* |
740 | | * We're root, but running as a normal user. Fix that, |
741 | | * so we can call setuid(). |
742 | | */ |
743 | | if (geteuid() == suid_down_uid) { |
744 | | rad_suid_up(); |
745 | | } |
746 | | |
747 | | if (setuid(suid_down_uid) < 0) { |
748 | | struct passwd *passwd; |
749 | | char const *name; |
750 | | |
751 | | name = (fr_perm_getpwuid(NULL, &passwd, suid_down_uid) < 0) ? "unknown": passwd->pw_name; |
752 | | ERROR("Failed switching permanently to uid %s: %s", name, fr_syserror(errno)); |
753 | | talloc_free(passwd); |
754 | | fr_exit_now(EXIT_FAILURE); |
755 | | } |
756 | | |
757 | | fr_reset_dumpable(); |
758 | | |
759 | | suid_down_permanent = true; |
760 | | } |
761 | | # endif /* HAVE_SETRESUID && HAVE_GETRESUID */ |
762 | | #else /* HAVE_SETUID */ |
763 | | void rad_suid_set_down_uid(uid_t uid) |
764 | | { |
765 | | } |
766 | | |
767 | | void rad_suid_up(void) |
768 | | { |
769 | | } |
770 | | |
771 | | void rad_suid_down(void) |
772 | | { |
773 | | fr_reset_dumpable(); |
774 | | } |
775 | | |
776 | | void rad_suid_down_permanent(void) |
777 | | { |
778 | | fr_reset_dumpable(); |
779 | | } |
780 | | #endif /* HAVE_SETUID */ |
781 | | |
782 | | /** Return whether we've permanently dropped root privileges |
783 | | * |
784 | | * @return |
785 | | * - true if root privileges have been dropped. |
786 | | * - false if root privileges have not been dropped. |
787 | | */ |
788 | | bool rad_suid_is_down_permanent(void) |
789 | 0 | { |
790 | 0 | return suid_down_permanent; |
791 | 0 | } |
792 | | |
793 | | /** Alter the effective user id |
794 | | * |
795 | | * @param uid to set |
796 | | * @return |
797 | | * - 0 on success. |
798 | | * - -1 on failure. |
799 | | */ |
800 | | int rad_seuid(uid_t uid) |
801 | 0 | { |
802 | 0 | if (seteuid(uid) < 0) { |
803 | 0 | int sete_errno = errno; /* errno sets overwritten by fr_perm_getpwuid */ |
804 | 0 | struct passwd *passwd; |
805 | |
|
806 | 0 | if (fr_perm_getpwuid(NULL, &passwd, uid) < 0) return -1; |
807 | 0 | fr_strerror_printf("%s", fr_syserror(sete_errno)); |
808 | 0 | talloc_free(passwd); |
809 | |
|
810 | 0 | return -1; |
811 | 0 | } |
812 | 0 | return 0; |
813 | 0 | } |
814 | | |
815 | | /** Alter the effective user id |
816 | | * |
817 | | * @param gid to set |
818 | | * @return |
819 | | * - 0 on success. |
820 | | * - -1 on failure. |
821 | | */ |
822 | | int rad_segid(gid_t gid) |
823 | 0 | { |
824 | 0 | if (setegid(gid) < 0) { |
825 | 0 | int sete_errno = errno; /* errno sets overwritten by fr_perm_getgrgid */ |
826 | 0 | struct group *group; |
827 | |
|
828 | 0 | if (fr_perm_getgrgid(NULL, &group, gid) < 0) return -1; |
829 | 0 | fr_strerror_printf("%s", fr_syserror(sete_errno)); |
830 | 0 | talloc_free(group); |
831 | |
|
832 | 0 | return -1; |
833 | 0 | } |
834 | 0 | return 0; |
835 | 0 | } |