Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * ProFTPD - FTP server daemon |
3 | | * Copyright (c) 2001-2023 The ProFTPD Project team |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of the GNU General Public License as published by |
7 | | * the Free Software Foundation; either version 2 of the License, or |
8 | | * (at your option) any later version. |
9 | | * |
10 | | * This program is distributed in the hope that it will be useful, |
11 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | * GNU General Public License for more details. |
14 | | * |
15 | | * You should have received a copy of the GNU General Public License |
16 | | * along with this program; if not, write to the Free Software |
17 | | * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. |
18 | | * |
19 | | * As a special exemption, The ProFTPD Project team and other respective |
20 | | * copyright holders give permission to link this program with OpenSSL, and |
21 | | * distribute the resulting executable, without including the source code for |
22 | | * OpenSSL in the source distribution. |
23 | | */ |
24 | | |
25 | | /* Controls API routines */ |
26 | | |
27 | | #include "conf.h" |
28 | | #include "privs.h" |
29 | | |
30 | | #if defined(HAVE_UCRED_H) |
31 | | # include <ucred.h> |
32 | | #endif /* !HAVE_UCRED_H */ |
33 | | |
34 | | #if defined(HAVE_SYS_UCRED_H) |
35 | | # include <sys/ucred.h> |
36 | | #endif /* !HAVE_SYS_UCRED_H */ |
37 | | |
38 | | #if defined(HAVE_SYS_UIO_H) |
39 | | # include <sys/uio.h> |
40 | | #endif /* !HAVE_SYS_UIO_H */ |
41 | | |
42 | | #if defined(PR_USE_CTRLS) |
43 | | |
44 | | #include "mod_ctrls.h" |
45 | | |
46 | 0 | #define CTRLS_REQ_ACTION_KEY "action" |
47 | 0 | #define CTRLS_REQ_ARGS_KEY "args" |
48 | 0 | #define CTRLS_RESP_STATUS_KEY "status" |
49 | 0 | #define CTRLS_RESP_RESPS_KEY "responses" |
50 | | |
51 | | typedef struct ctrls_act_obj { |
52 | | struct ctrls_act_obj *prev, *next; |
53 | | pool *pool; |
54 | | unsigned int id; |
55 | | const char *action; |
56 | | const char *desc; |
57 | | const module *module; |
58 | | volatile unsigned int flags; |
59 | | int (*action_cb)(pr_ctrls_t *, int, char **); |
60 | | } ctrls_action_t; |
61 | | |
62 | | static unsigned char ctrls_blocked = FALSE; |
63 | | |
64 | | static pool *ctrls_pool = NULL; |
65 | | static ctrls_action_t *ctrls_action_list = NULL; |
66 | | |
67 | | static pr_ctrls_t *ctrls_active_list = NULL; |
68 | | static pr_ctrls_t *ctrls_free_list = NULL; |
69 | | |
70 | | static int ctrls_use_isfifo = FALSE; |
71 | | |
72 | | static const char *trace_channel = "ctrls"; |
73 | | |
74 | | /* lookup/lookup_next indices */ |
75 | | static ctrls_action_t *action_lookup_next = NULL; |
76 | | static const char *action_lookup_action = NULL; |
77 | | static module *action_lookup_module = NULL; |
78 | | |
79 | | /* Logging */ |
80 | | static int ctrls_logfd = -1; |
81 | | |
82 | | /* necessary prototypes */ |
83 | | static ctrls_action_t *ctrls_action_new(void); |
84 | | static pr_ctrls_t *ctrls_lookup_action(module *, const char *, unsigned char); |
85 | | static pr_ctrls_t *ctrls_lookup_next_action(module *, unsigned char); |
86 | | |
87 | 0 | static pr_ctrls_t *ctrls_prepare(ctrls_action_t *act) { |
88 | 0 | pr_ctrls_t *ctrl = NULL; |
89 | |
|
90 | 0 | pr_block_ctrls(); |
91 | | |
92 | | /* Get a blank ctrl object */ |
93 | 0 | ctrl = pr_ctrls_alloc(); |
94 | | |
95 | | /* Fill in the fields from the action object. */ |
96 | 0 | ctrl->ctrls_id = act->id; |
97 | 0 | ctrl->ctrls_module = act->module; |
98 | 0 | ctrl->ctrls_action = act->action; |
99 | 0 | ctrl->ctrls_desc = act->desc; |
100 | 0 | ctrl->ctrls_cb = act->action_cb; |
101 | 0 | ctrl->ctrls_flags = act->flags; |
102 | | |
103 | | /* Add this to the "in use" list */ |
104 | 0 | ctrl->ctrls_next = ctrls_active_list; |
105 | 0 | ctrls_active_list = ctrl; |
106 | |
|
107 | 0 | pr_unblock_ctrls(); |
108 | 0 | return ctrl; |
109 | 0 | } |
110 | | |
111 | 0 | static ctrls_action_t *ctrls_action_new(void) { |
112 | 0 | ctrls_action_t *act = NULL; |
113 | 0 | pool *sub_pool = NULL; |
114 | |
|
115 | 0 | sub_pool = make_sub_pool(ctrls_pool); |
116 | 0 | pr_pool_tag(sub_pool, "ctrls action subpool"); |
117 | |
|
118 | 0 | act = pcalloc(sub_pool, sizeof(ctrls_action_t)); |
119 | 0 | act->pool = sub_pool; |
120 | |
|
121 | 0 | return act; |
122 | 0 | } |
123 | | |
124 | 0 | pr_ctrls_t *pr_ctrls_alloc(void) { |
125 | 0 | pr_ctrls_t *ctrl = NULL; |
126 | | |
127 | | /* Check for a free ctrl first */ |
128 | 0 | if (ctrls_free_list != NULL) { |
129 | | |
130 | | /* Take one from the top */ |
131 | 0 | ctrl = ctrls_free_list; |
132 | 0 | ctrls_free_list = ctrls_free_list->ctrls_next; |
133 | |
|
134 | 0 | if (ctrls_free_list != NULL) { |
135 | 0 | ctrls_free_list->ctrls_prev = NULL; |
136 | 0 | } |
137 | |
|
138 | 0 | } else { |
139 | | /* Have to allocate a new one. */ |
140 | 0 | ctrl = (pr_ctrls_t *) pcalloc(ctrls_pool, sizeof(pr_ctrls_t)); |
141 | | |
142 | | /* It's important that a new ctrl object have the retval initialized |
143 | | * to 1; this tells the Controls layer that it is "pending", not yet |
144 | | * handled. |
145 | | */ |
146 | 0 | ctrl->ctrls_cb_retval = PR_CTRLS_STATUS_PENDING; |
147 | 0 | } |
148 | |
|
149 | 0 | return ctrl; |
150 | 0 | } |
151 | | |
152 | 0 | int pr_ctrls_free(pr_ctrls_t *ctrl) { |
153 | 0 | if (ctrl == NULL) { |
154 | 0 | errno = EINVAL; |
155 | 0 | return -1; |
156 | 0 | } |
157 | | |
158 | | /* Make sure that ctrls are blocked while we're doing this */ |
159 | 0 | pr_block_ctrls(); |
160 | | |
161 | | /* Remove this object from the active list */ |
162 | 0 | if (ctrl->ctrls_prev != NULL) { |
163 | 0 | ctrl->ctrls_prev->ctrls_next = ctrl->ctrls_next; |
164 | |
|
165 | 0 | } else { |
166 | 0 | ctrls_active_list = ctrl->ctrls_next; |
167 | 0 | } |
168 | |
|
169 | 0 | if (ctrl->ctrls_next != NULL) { |
170 | 0 | ctrl->ctrls_next->ctrls_prev = ctrl->ctrls_prev; |
171 | 0 | } |
172 | | |
173 | | /* Clear its fields, and add it to the free list */ |
174 | 0 | ctrl->ctrls_next = NULL; |
175 | 0 | ctrl->ctrls_prev = NULL; |
176 | 0 | ctrl->ctrls_id = 0; |
177 | 0 | ctrl->ctrls_module = NULL; |
178 | 0 | ctrl->ctrls_action = NULL; |
179 | 0 | ctrl->ctrls_cb = NULL; |
180 | 0 | ctrl->ctrls_cb_retval = PR_CTRLS_STATUS_PENDING; |
181 | 0 | ctrl->ctrls_flags = 0; |
182 | |
|
183 | 0 | if (ctrl->ctrls_tmp_pool != NULL) { |
184 | 0 | destroy_pool(ctrl->ctrls_tmp_pool); |
185 | 0 | ctrl->ctrls_tmp_pool = NULL; |
186 | 0 | } |
187 | |
|
188 | 0 | ctrl->ctrls_cb_args = NULL; |
189 | 0 | ctrl->ctrls_cb_resps = NULL; |
190 | 0 | ctrl->ctrls_data = NULL; |
191 | |
|
192 | 0 | ctrl->ctrls_next = ctrls_free_list; |
193 | 0 | ctrls_free_list = ctrl; |
194 | |
|
195 | 0 | pr_unblock_ctrls(); |
196 | 0 | return 0; |
197 | 0 | } |
198 | | |
199 | | int pr_ctrls_register(const module *mod, const char *action, |
200 | 0 | const char *desc, int (*cb)(pr_ctrls_t *, int, char **)) { |
201 | 0 | ctrls_action_t *act = NULL, *acti = NULL; |
202 | 0 | unsigned int act_id = 0; |
203 | | |
204 | | /* sanity checks */ |
205 | 0 | if (action == NULL || |
206 | 0 | desc == NULL || |
207 | 0 | cb == NULL) { |
208 | 0 | errno = EINVAL; |
209 | 0 | return -1; |
210 | 0 | } |
211 | | |
212 | 0 | pr_trace_msg("ctrls", 3, |
213 | 0 | "module '%s' registering handler for ctrl action '%s' (at %p)", |
214 | 0 | mod ? mod->name : "(none)", action, cb); |
215 | | |
216 | | /* Block ctrls while we're doing this */ |
217 | 0 | pr_block_ctrls(); |
218 | | |
219 | | /* Get a ctrl action object */ |
220 | 0 | act = ctrls_action_new(); |
221 | | |
222 | | /* Randomly generate a unique random ID for this object */ |
223 | 0 | while (TRUE) { |
224 | 0 | int have_id = FALSE; |
225 | |
|
226 | 0 | act_id = (unsigned int) pr_random_next(1L, RAND_MAX); |
227 | | |
228 | | /* Check the list for this ID */ |
229 | 0 | for (acti = ctrls_action_list; acti; acti = acti->next) { |
230 | 0 | if (acti->id == act_id) { |
231 | 0 | have_id = TRUE; |
232 | 0 | break; |
233 | 0 | } |
234 | 0 | } |
235 | |
|
236 | 0 | if (have_id == FALSE) { |
237 | 0 | break; |
238 | 0 | } |
239 | 0 | } |
240 | |
|
241 | 0 | act->next = NULL; |
242 | 0 | act->id = act_id; |
243 | 0 | act->action = pstrdup(ctrls_pool, action); |
244 | 0 | act->desc = desc; |
245 | 0 | act->module = mod; |
246 | 0 | act->action_cb = cb; |
247 | | |
248 | | /* Add this to the list of "registered" actions */ |
249 | |
|
250 | 0 | if (ctrls_action_list != NULL) { |
251 | 0 | act->next = ctrls_action_list; |
252 | 0 | ctrls_action_list->prev = act; |
253 | 0 | } |
254 | |
|
255 | 0 | ctrls_action_list = act; |
256 | |
|
257 | 0 | pr_unblock_ctrls(); |
258 | 0 | return act_id; |
259 | 0 | } |
260 | | |
261 | 0 | int pr_ctrls_unregister(module *mod, const char *action) { |
262 | 0 | ctrls_action_t *act = NULL, *next_act = NULL; |
263 | 0 | unsigned char have_action = FALSE; |
264 | | |
265 | | /* Make sure that ctrls are blocked while we're doing this */ |
266 | 0 | pr_block_ctrls(); |
267 | |
|
268 | 0 | for (act = ctrls_action_list; act != NULL; act = next_act) { |
269 | 0 | next_act = act->next; |
270 | |
|
271 | 0 | if ((action == NULL || strcmp(act->action, action) == 0) && |
272 | 0 | (act->module == mod || mod == ANY_MODULE || mod == NULL)) { |
273 | 0 | have_action = TRUE; |
274 | | |
275 | | /* Remove this object from the list of registered actions */ |
276 | 0 | if (act->prev != NULL) { |
277 | 0 | act->prev->next = act->next; |
278 | |
|
279 | 0 | } else { |
280 | 0 | ctrls_action_list = act->next; |
281 | 0 | } |
282 | |
|
283 | 0 | if (act->next != NULL) { |
284 | 0 | act->next->prev = act->prev; |
285 | 0 | } |
286 | |
|
287 | 0 | pr_trace_msg("ctrls", 3, |
288 | 0 | "module '%s' unregistering handler for ctrl action '%s'", |
289 | 0 | mod ? mod->name : "(none)", act->action); |
290 | | |
291 | | /* Destroy this action. */ |
292 | 0 | destroy_pool(act->pool); |
293 | 0 | } |
294 | 0 | } |
295 | |
|
296 | 0 | pr_unblock_ctrls(); |
297 | |
|
298 | 0 | if (have_action == FALSE) { |
299 | 0 | errno = ENOENT; |
300 | 0 | return -1; |
301 | 0 | } |
302 | | |
303 | 0 | return 0; |
304 | 0 | } |
305 | | |
306 | 0 | int pr_ctrls_add_arg(pr_ctrls_t *ctrl, char *ctrls_arg, size_t ctrls_arglen) { |
307 | 0 | register unsigned int i; |
308 | | |
309 | | /* Sanity checks */ |
310 | 0 | if (ctrl == NULL || |
311 | 0 | ctrls_arg == NULL) { |
312 | 0 | errno = EINVAL; |
313 | 0 | return -1; |
314 | 0 | } |
315 | | |
316 | | /* Scan for non-printable characters. */ |
317 | 0 | for (i = 0; i < ctrls_arglen; i++) { |
318 | 0 | if (!PR_ISPRINT((int) ctrls_arg[i])) { |
319 | 0 | errno = EPERM; |
320 | 0 | return -1; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | /* Make sure the pr_ctrls_t has a temporary pool, from which the args will |
325 | | * be allocated. |
326 | | */ |
327 | 0 | if (ctrl->ctrls_tmp_pool == NULL) { |
328 | 0 | ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool); |
329 | 0 | pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool"); |
330 | 0 | } |
331 | |
|
332 | 0 | if (ctrl->ctrls_cb_args == NULL) { |
333 | 0 | ctrl->ctrls_cb_args = make_array(ctrl->ctrls_tmp_pool, 0, sizeof(char *)); |
334 | 0 | } |
335 | | |
336 | | /* Add the given argument */ |
337 | 0 | *((char **) push_array(ctrl->ctrls_cb_args)) = pstrndup(ctrl->ctrls_tmp_pool, |
338 | 0 | ctrls_arg, ctrls_arglen); |
339 | |
|
340 | 0 | return 0; |
341 | 0 | } |
342 | | |
343 | 0 | int pr_ctrls_copy_args(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) { |
344 | 0 | if (src_ctrl == NULL || |
345 | 0 | dst_ctrl == NULL || |
346 | 0 | src_ctrl == dst_ctrl) { |
347 | 0 | errno = EINVAL; |
348 | 0 | return -1; |
349 | 0 | } |
350 | | |
351 | | /* If source ctrl has no ctrls_cb_args member, there's nothing to be |
352 | | * done. |
353 | | */ |
354 | 0 | if (src_ctrl->ctrls_cb_args == NULL) { |
355 | 0 | return 0; |
356 | 0 | } |
357 | | |
358 | | /* Make sure the pr_ctrls_t has a temporary pool, from which the args will |
359 | | * be allocated. |
360 | | */ |
361 | 0 | if (dst_ctrl->ctrls_tmp_pool == NULL) { |
362 | 0 | dst_ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool); |
363 | 0 | pr_pool_tag(dst_ctrl->ctrls_tmp_pool, "ctrls tmp pool"); |
364 | 0 | } |
365 | | |
366 | | /* Overwrite any existing dst_ctrl->ctrls_cb_args. This is OK, as |
367 | | * the ctrl will be reset (cleared) once it has been processed. |
368 | | */ |
369 | 0 | dst_ctrl->ctrls_cb_args = copy_array(dst_ctrl->ctrls_tmp_pool, |
370 | 0 | src_ctrl->ctrls_cb_args); |
371 | |
|
372 | 0 | return 0; |
373 | 0 | } |
374 | | |
375 | 0 | int pr_ctrls_copy_resps(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) { |
376 | 0 | if (src_ctrl == NULL || |
377 | 0 | dst_ctrl == NULL || |
378 | 0 | src_ctrl == dst_ctrl) { |
379 | 0 | errno = EINVAL; |
380 | 0 | return -1; |
381 | 0 | } |
382 | | |
383 | | /* The source ctrl must have a ctrls_cb_resps member, and the destination |
384 | | * ctrl must not have a ctrls_cb_resps member. |
385 | | */ |
386 | 0 | if (src_ctrl->ctrls_cb_resps == NULL || |
387 | 0 | dst_ctrl->ctrls_cb_resps != NULL) { |
388 | 0 | errno = EPERM; |
389 | 0 | return -1; |
390 | 0 | } |
391 | | |
392 | 0 | dst_ctrl->ctrls_cb_resps = copy_array(dst_ctrl->ctrls_tmp_pool, |
393 | 0 | src_ctrl->ctrls_cb_resps); |
394 | |
|
395 | 0 | return 0; |
396 | 0 | } |
397 | | |
398 | 0 | int pr_ctrls_add_response(pr_ctrls_t *ctrl, const char *fmt, ...) { |
399 | 0 | char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; |
400 | 0 | va_list resp; |
401 | | |
402 | | /* Sanity check */ |
403 | 0 | if (ctrl == NULL || |
404 | 0 | fmt == NULL) { |
405 | 0 | errno = EINVAL; |
406 | 0 | return -1; |
407 | 0 | } |
408 | | |
409 | | /* Make sure the pr_ctrls_t has a temporary pool, from which the responses |
410 | | * will be allocated |
411 | | */ |
412 | 0 | if (ctrl->ctrls_tmp_pool == NULL) { |
413 | 0 | ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool); |
414 | 0 | pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool"); |
415 | 0 | } |
416 | |
|
417 | 0 | if (ctrl->ctrls_cb_resps == NULL) { |
418 | 0 | ctrl->ctrls_cb_resps = make_array(ctrl->ctrls_tmp_pool, 0, |
419 | 0 | sizeof(char *)); |
420 | 0 | } |
421 | | |
422 | | /* Affix the message */ |
423 | 0 | va_start(resp, fmt); |
424 | 0 | pr_vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, resp); |
425 | 0 | va_end(resp); |
426 | |
|
427 | 0 | buf[sizeof(buf) - 1] = '\0'; |
428 | | |
429 | | /* add the given response */ |
430 | 0 | *((char **) push_array(ctrl->ctrls_cb_resps)) = |
431 | 0 | pstrdup(ctrl->ctrls_tmp_pool, buf); |
432 | |
|
433 | 0 | return 0; |
434 | 0 | } |
435 | | |
436 | 0 | int pr_ctrls_flush_response(pr_ctrls_t *ctrl) { |
437 | 0 | if (ctrl == NULL) { |
438 | 0 | errno = EINVAL; |
439 | 0 | return -1; |
440 | 0 | } |
441 | | |
442 | | /* Make sure the callback(s) added responses */ |
443 | 0 | if (ctrl->ctrls_cb_resps != NULL) { |
444 | 0 | int res; |
445 | |
|
446 | 0 | if (ctrl->ctrls_cl == NULL) { |
447 | 0 | errno = EPERM; |
448 | 0 | return -1; |
449 | 0 | } |
450 | | |
451 | 0 | res = pr_ctrls_send_response(ctrl->ctrls_tmp_pool, ctrl->ctrls_cl->cl_fd, |
452 | 0 | ctrl->ctrls_cb_retval, ctrl->ctrls_cb_resps->nelts, |
453 | 0 | (char **) ctrl->ctrls_cb_resps->elts); |
454 | 0 | if (res < 0) { |
455 | 0 | return -1; |
456 | 0 | } |
457 | 0 | } |
458 | | |
459 | 0 | return 0; |
460 | 0 | } |
461 | | |
462 | 0 | static int ctrls_send_msg(pool *p, int fd, pr_json_object_t *json) { |
463 | 0 | uint32_t msglen; |
464 | 0 | int res, xerrno; |
465 | 0 | char *msg; |
466 | |
|
467 | 0 | msg = pr_json_object_to_text(p, json, ""); |
468 | 0 | if (msg == NULL) { |
469 | 0 | return -1; |
470 | 0 | } |
471 | | |
472 | 0 | msglen = strlen(msg); |
473 | | |
474 | | /* No interruptions. */ |
475 | 0 | pr_signals_block(); |
476 | |
|
477 | 0 | res = write(fd, &msglen, sizeof(uint32_t)); |
478 | 0 | xerrno = errno; |
479 | |
|
480 | 0 | if ((size_t) res != sizeof(uint32_t)) { |
481 | 0 | pr_signals_unblock(); |
482 | |
|
483 | 0 | errno = xerrno; |
484 | 0 | return -1; |
485 | 0 | } |
486 | | |
487 | 0 | while (TRUE) { |
488 | 0 | res = write(fd, msg, msglen); |
489 | 0 | xerrno = errno; |
490 | |
|
491 | 0 | if ((size_t) res != msglen) { |
492 | 0 | if (xerrno == EAGAIN) { |
493 | 0 | continue; |
494 | 0 | } |
495 | | |
496 | 0 | pr_signals_unblock(); |
497 | |
|
498 | 0 | errno = xerrno; |
499 | 0 | return -1; |
500 | 0 | } |
501 | | |
502 | 0 | break; |
503 | 0 | } |
504 | | |
505 | 0 | pr_signals_unblock(); |
506 | 0 | return 0; |
507 | 0 | } |
508 | | |
509 | | int pr_ctrls_send_request(pool *p, int fd, const char *action, |
510 | 0 | unsigned int argc, char **argv) { |
511 | 0 | register unsigned int i; |
512 | 0 | pool *tmp_pool; |
513 | 0 | int res, xerrno; |
514 | 0 | pr_json_object_t *json; |
515 | 0 | pr_json_array_t *args; |
516 | |
|
517 | 0 | if (p == NULL || |
518 | 0 | fd < 0 || |
519 | 0 | action == NULL) { |
520 | 0 | errno = EINVAL; |
521 | 0 | return -1; |
522 | 0 | } |
523 | | |
524 | 0 | if (argc > 0 && |
525 | 0 | argv == NULL) { |
526 | 0 | errno = EINVAL; |
527 | 0 | return -1; |
528 | 0 | } |
529 | | |
530 | 0 | tmp_pool = make_sub_pool(p); |
531 | 0 | pr_pool_tag(tmp_pool, "Controls API send_request pool"); |
532 | |
|
533 | 0 | json = pr_json_object_alloc(tmp_pool); |
534 | |
|
535 | 0 | res = pr_json_object_set_string(tmp_pool, json, CTRLS_REQ_ACTION_KEY, action); |
536 | 0 | xerrno = errno; |
537 | |
|
538 | 0 | if (res < 0) { |
539 | 0 | pr_json_object_free(json); |
540 | 0 | destroy_pool(tmp_pool); |
541 | |
|
542 | 0 | errno = xerrno; |
543 | 0 | return -1; |
544 | 0 | } |
545 | | |
546 | 0 | args = pr_json_array_alloc(tmp_pool); |
547 | |
|
548 | 0 | for (i = 0; i < argc; i++) { |
549 | 0 | res = pr_json_array_append_string(tmp_pool, args, argv[i]); |
550 | 0 | xerrno = errno; |
551 | |
|
552 | 0 | if (res < 0) { |
553 | 0 | pr_json_array_free(args); |
554 | 0 | pr_json_object_free(json); |
555 | 0 | destroy_pool(tmp_pool); |
556 | |
|
557 | 0 | errno = xerrno; |
558 | 0 | return -1; |
559 | 0 | } |
560 | 0 | } |
561 | | |
562 | 0 | res = pr_json_object_set_array(tmp_pool, json, CTRLS_REQ_ARGS_KEY, args); |
563 | 0 | xerrno = errno; |
564 | |
|
565 | 0 | if (res < 0) { |
566 | 0 | pr_json_array_free(args); |
567 | 0 | pr_json_object_free(json); |
568 | 0 | destroy_pool(tmp_pool); |
569 | |
|
570 | 0 | errno = xerrno; |
571 | 0 | return -1; |
572 | 0 | } |
573 | | |
574 | 0 | res = ctrls_send_msg(tmp_pool, fd, json); |
575 | 0 | xerrno = errno; |
576 | |
|
577 | 0 | pr_json_array_free(args); |
578 | 0 | pr_json_object_free(json); |
579 | 0 | destroy_pool(tmp_pool); |
580 | |
|
581 | 0 | errno = xerrno; |
582 | 0 | return res; |
583 | 0 | } |
584 | | |
585 | 0 | int pr_ctrls_recv_request(pr_ctrls_cl_t *cl) { |
586 | 0 | register int i = 0; |
587 | 0 | pr_ctrls_t *ctrl = NULL, *next_ctrl = NULL; |
588 | 0 | pool *tmp_pool = NULL; |
589 | 0 | int nread, nreqargs = 0, res, xerrno; |
590 | 0 | uint32_t msglen; |
591 | 0 | char *msg = NULL, *reqaction = NULL; |
592 | 0 | pr_json_object_t *json = NULL; |
593 | 0 | pr_json_array_t *args = NULL; |
594 | |
|
595 | 0 | if (cl == NULL || |
596 | 0 | cl->cl_ctrls == NULL) { |
597 | 0 | errno = EINVAL; |
598 | 0 | return -1; |
599 | 0 | } |
600 | | |
601 | 0 | if (cl->cl_fd < 0) { |
602 | 0 | errno = EBADF; |
603 | 0 | return -1; |
604 | 0 | } |
605 | | |
606 | | /* No interruptions */ |
607 | 0 | pr_signals_block(); |
608 | | |
609 | | /* Read in the size of the message, as JSON text. */ |
610 | |
|
611 | 0 | nread = read(cl->cl_fd, &msglen, sizeof(uint32_t)); |
612 | 0 | xerrno = errno; |
613 | |
|
614 | 0 | if (nread < 0) { |
615 | 0 | pr_trace_msg(trace_channel, 3, |
616 | 0 | "error reading %lu bytes of request message size: %s", |
617 | 0 | sizeof(msglen), strerror(xerrno)); |
618 | 0 | pr_signals_unblock(); |
619 | |
|
620 | 0 | errno = xerrno; |
621 | 0 | return -1; |
622 | 0 | } |
623 | | |
624 | | /* Watch for short reads. */ |
625 | 0 | if (nread != sizeof(uint32_t)) { |
626 | 0 | (void) pr_trace_msg(trace_channel, 3, |
627 | 0 | "short read (%d of %u bytes) of message size, unable to receive request", |
628 | 0 | nread, (unsigned int) sizeof(uint32_t)); |
629 | 0 | pr_signals_unblock(); |
630 | 0 | errno = EPERM; |
631 | 0 | return -1; |
632 | 0 | } |
633 | | |
634 | 0 | tmp_pool = make_sub_pool(cl->cl_pool); |
635 | 0 | pr_pool_tag(tmp_pool, "Controls API recv_request pool"); |
636 | | |
637 | | /* Allocate one byte for the terminating NUL. */ |
638 | 0 | msg = pcalloc(tmp_pool, msglen + 1); |
639 | |
|
640 | 0 | nread = read(cl->cl_fd, msg, msglen); |
641 | 0 | xerrno = errno; |
642 | |
|
643 | 0 | if (nread < 0) { |
644 | 0 | pr_trace_msg(trace_channel, 3, |
645 | 0 | "error reading %lu bytes of request message: %s", |
646 | 0 | (unsigned long) msglen, strerror(xerrno)); |
647 | 0 | destroy_pool(tmp_pool); |
648 | 0 | pr_signals_unblock(); |
649 | |
|
650 | 0 | errno = xerrno; |
651 | 0 | return -1; |
652 | 0 | } |
653 | | |
654 | | /* Watch for short reads. */ |
655 | 0 | if ((unsigned int) nread != msglen) { |
656 | 0 | (void) pr_trace_msg(trace_channel, 3, |
657 | 0 | "short read (%d of %u bytes) of message text, unable to receive request", |
658 | 0 | nread, (unsigned int) msglen); |
659 | 0 | destroy_pool(tmp_pool); |
660 | 0 | pr_signals_unblock(); |
661 | 0 | errno = EPERM; |
662 | 0 | return -1; |
663 | 0 | } |
664 | | |
665 | 0 | json = pr_json_object_from_text(tmp_pool, msg); |
666 | 0 | xerrno = errno; |
667 | |
|
668 | 0 | if (json == NULL) { |
669 | 0 | (void) pr_trace_msg(trace_channel, 3, |
670 | 0 | "read invalid JSON message text ('%.*s' [%lu bytes]), unable to " |
671 | 0 | "receive request: %s", (int) msglen, msg, (unsigned long) msglen, |
672 | 0 | strerror(xerrno)); |
673 | 0 | destroy_pool(tmp_pool); |
674 | 0 | pr_signals_unblock(); |
675 | |
|
676 | 0 | errno = EINVAL; |
677 | 0 | return -1; |
678 | 0 | } |
679 | | |
680 | 0 | res = pr_json_object_get_string(tmp_pool, json, CTRLS_REQ_ACTION_KEY, |
681 | 0 | &reqaction); |
682 | 0 | xerrno = errno; |
683 | |
|
684 | 0 | if (res < 0) { |
685 | 0 | (void) pr_trace_msg(trace_channel, 3, |
686 | 0 | "unable to read message action (%s), unable to receive request", |
687 | 0 | strerror(xerrno)); |
688 | 0 | pr_json_object_free(json); |
689 | 0 | destroy_pool(tmp_pool); |
690 | 0 | pr_signals_unblock(); |
691 | |
|
692 | 0 | errno = EINVAL; |
693 | 0 | return -1; |
694 | 0 | } |
695 | | |
696 | 0 | res = pr_json_object_get_array(tmp_pool, json, CTRLS_REQ_ARGS_KEY, &args); |
697 | 0 | xerrno = errno; |
698 | |
|
699 | 0 | if (res < 0) { |
700 | 0 | (void) pr_trace_msg(trace_channel, 3, |
701 | 0 | "unable to read message arguments (%s), unable to receive request", |
702 | 0 | strerror(xerrno)); |
703 | 0 | pr_json_object_free(json); |
704 | 0 | destroy_pool(tmp_pool); |
705 | 0 | pr_signals_unblock(); |
706 | |
|
707 | 0 | errno = EINVAL; |
708 | 0 | return -1; |
709 | 0 | } |
710 | | |
711 | 0 | nreqargs = pr_json_array_count(args); |
712 | 0 | pr_trace_msg(trace_channel, 19, "received request argc: %u", nreqargs); |
713 | | |
714 | | /* Find a matching action object, and use it to populate a ctrl object, |
715 | | * preparing the ctrl object for dispatching to the action handlers. |
716 | | */ |
717 | 0 | ctrl = ctrls_lookup_action(NULL, reqaction, TRUE); |
718 | 0 | if (ctrl == NULL) { |
719 | 0 | (void) pr_trace_msg(trace_channel, 3, |
720 | 0 | "unknown action requested '%s', unable to receive request", reqaction); |
721 | 0 | pr_json_array_free(args); |
722 | 0 | pr_json_object_free(json); |
723 | 0 | destroy_pool(tmp_pool); |
724 | 0 | pr_signals_unblock(); |
725 | | |
726 | | /* XXX This is where we could also add "did you mean" functionality. */ |
727 | 0 | errno = EINVAL; |
728 | 0 | return -1; |
729 | 0 | } |
730 | | |
731 | 0 | pr_trace_msg(trace_channel, 19, "known action '%s' requested", reqaction); |
732 | |
|
733 | 0 | for (i = 0; i < nreqargs; i++) { |
734 | 0 | size_t reqarglen = 0; |
735 | 0 | char *reqarg = NULL; |
736 | |
|
737 | 0 | res = pr_json_array_get_string(tmp_pool, args, i, &reqarg); |
738 | 0 | xerrno = errno; |
739 | |
|
740 | 0 | if (res < 0) { |
741 | 0 | (void) pr_trace_msg(trace_channel, 3, |
742 | 0 | "unable to read message argument #%u (%s), unable to receive request", |
743 | 0 | i+1, strerror(xerrno)); |
744 | 0 | pr_json_array_free(args); |
745 | 0 | pr_json_object_free(json); |
746 | 0 | destroy_pool(tmp_pool); |
747 | 0 | pr_signals_unblock(); |
748 | |
|
749 | 0 | errno = EINVAL; |
750 | 0 | return -1; |
751 | 0 | } |
752 | | |
753 | 0 | reqarglen = strlen(reqarg); |
754 | 0 | res = pr_ctrls_add_arg(ctrl, reqarg, reqarglen); |
755 | 0 | xerrno = errno; |
756 | |
|
757 | 0 | if (res < 0) { |
758 | 0 | pr_trace_msg(trace_channel, 3, |
759 | 0 | "error adding message argument #%u (%s): %s", i+1, reqarg, |
760 | 0 | strerror(xerrno)); |
761 | 0 | pr_json_array_free(args); |
762 | 0 | pr_json_object_free(json); |
763 | 0 | destroy_pool(tmp_pool); |
764 | 0 | pr_signals_unblock(); |
765 | |
|
766 | 0 | errno = xerrno; |
767 | 0 | return -1; |
768 | 0 | } |
769 | 0 | } |
770 | | |
771 | | /* Add this ctrls object to the client object. */ |
772 | 0 | *((pr_ctrls_t **) push_array(cl->cl_ctrls)) = ctrl; |
773 | | |
774 | | /* Set the flag that this control is ready to go */ |
775 | 0 | ctrl->ctrls_flags |= PR_CTRLS_FL_REQUESTED; |
776 | 0 | ctrl->ctrls_cl = cl; |
777 | | |
778 | | /* Copy the populated ctrl object args to ctrl objects for all other |
779 | | * matching action objects. |
780 | | */ |
781 | 0 | next_ctrl = ctrls_lookup_next_action(NULL, TRUE); |
782 | |
|
783 | 0 | while (next_ctrl != NULL) { |
784 | 0 | (void) pr_ctrls_copy_args(ctrl, next_ctrl); |
785 | | |
786 | | /* Add this ctrl object to the client object. */ |
787 | 0 | *((pr_ctrls_t **) push_array(cl->cl_ctrls)) = next_ctrl; |
788 | | |
789 | | /* Set the flag that this control is ready to go. */ |
790 | 0 | next_ctrl->ctrls_flags |= PR_CTRLS_FL_REQUESTED; |
791 | 0 | next_ctrl->ctrls_cl = cl; |
792 | |
|
793 | 0 | next_ctrl = ctrls_lookup_next_action(NULL, TRUE); |
794 | 0 | } |
795 | |
|
796 | 0 | pr_json_array_free(args); |
797 | 0 | pr_json_object_free(json); |
798 | 0 | destroy_pool(tmp_pool); |
799 | 0 | pr_signals_unblock(); |
800 | |
|
801 | 0 | return 0; |
802 | 0 | } |
803 | | |
804 | | int pr_ctrls_send_response(pool *p, int fd, int status, unsigned int argc, |
805 | 0 | char **argv) { |
806 | 0 | register unsigned int i; |
807 | 0 | pool *tmp_pool; |
808 | 0 | int res, xerrno; |
809 | 0 | pr_json_object_t *json; |
810 | 0 | pr_json_array_t *resps; |
811 | |
|
812 | 0 | if (p == NULL || |
813 | 0 | fd < 0) { |
814 | 0 | errno = EINVAL; |
815 | 0 | return -1; |
816 | 0 | } |
817 | | |
818 | 0 | if (argc > 0 && |
819 | 0 | argv == NULL) { |
820 | 0 | errno = EINVAL; |
821 | 0 | return -1; |
822 | 0 | } |
823 | | |
824 | 0 | tmp_pool = make_sub_pool(p); |
825 | 0 | pr_pool_tag(tmp_pool, "Controls API send_response pool"); |
826 | |
|
827 | 0 | json = pr_json_object_alloc(tmp_pool); |
828 | |
|
829 | 0 | res = pr_json_object_set_number(tmp_pool, json, CTRLS_RESP_STATUS_KEY, |
830 | 0 | (double) status); |
831 | 0 | xerrno = errno; |
832 | |
|
833 | 0 | if (res < 0) { |
834 | 0 | pr_json_object_free(json); |
835 | 0 | destroy_pool(tmp_pool); |
836 | |
|
837 | 0 | errno = xerrno; |
838 | 0 | return -1; |
839 | 0 | } |
840 | | |
841 | 0 | resps = pr_json_array_alloc(tmp_pool); |
842 | |
|
843 | 0 | for (i = 0; i < argc; i++) { |
844 | 0 | res = pr_json_array_append_string(tmp_pool, resps, argv[i]); |
845 | 0 | xerrno = errno; |
846 | |
|
847 | 0 | if (res < 0) { |
848 | 0 | pr_json_array_free(resps); |
849 | 0 | pr_json_object_free(json); |
850 | 0 | destroy_pool(tmp_pool); |
851 | |
|
852 | 0 | errno = xerrno; |
853 | 0 | return -1; |
854 | 0 | } |
855 | 0 | } |
856 | | |
857 | 0 | res = pr_json_object_set_array(tmp_pool, json, CTRLS_RESP_RESPS_KEY, resps); |
858 | 0 | xerrno = errno; |
859 | |
|
860 | 0 | if (res < 0) { |
861 | 0 | pr_json_array_free(resps); |
862 | 0 | pr_json_object_free(json); |
863 | 0 | destroy_pool(tmp_pool); |
864 | |
|
865 | 0 | errno = xerrno; |
866 | 0 | return -1; |
867 | 0 | } |
868 | | |
869 | 0 | res = ctrls_send_msg(tmp_pool, fd, json); |
870 | 0 | xerrno = errno; |
871 | |
|
872 | 0 | pr_json_array_free(resps); |
873 | 0 | pr_json_object_free(json); |
874 | 0 | destroy_pool(tmp_pool); |
875 | |
|
876 | 0 | errno = xerrno; |
877 | 0 | return res; |
878 | 0 | } |
879 | | |
880 | 0 | int pr_ctrls_recv_response(pool *p, int fd, int *status, char ***respargv) { |
881 | 0 | register int i = 0; |
882 | 0 | pool *tmp_pool; |
883 | 0 | int nread, res, respargc = 0, xerrno; |
884 | 0 | uint32_t msglen = 0; |
885 | 0 | char *msg = NULL; |
886 | 0 | pr_json_object_t *json = NULL; |
887 | 0 | pr_json_array_t *resps = NULL; |
888 | 0 | double dv; |
889 | 0 | array_header *resparr = NULL; |
890 | | |
891 | | /* Sanity checks */ |
892 | 0 | if (p == NULL || |
893 | 0 | fd < 0 || |
894 | 0 | status == NULL) { |
895 | 0 | errno = EINVAL; |
896 | 0 | return -1; |
897 | 0 | } |
898 | | |
899 | | /* No interruptions. */ |
900 | 0 | pr_signals_block(); |
901 | | |
902 | | /* Read in the size of the message, as JSON text. */ |
903 | |
|
904 | 0 | nread = read(fd, &msglen, sizeof(uint32_t)); |
905 | 0 | xerrno = errno; |
906 | |
|
907 | 0 | if (nread != sizeof(uint32_t)) { |
908 | 0 | pr_signals_unblock(); |
909 | |
|
910 | 0 | if (nread < 0) { |
911 | 0 | (void) pr_trace_msg(trace_channel, 3, |
912 | 0 | "error reading %u of response message size: %s", |
913 | 0 | (unsigned int) sizeof(uint32_t), strerror(xerrno)); |
914 | 0 | errno = xerrno; |
915 | 0 | return -1; |
916 | 0 | } |
917 | | |
918 | 0 | (void) pr_trace_msg(trace_channel, 3, |
919 | 0 | "short read (%d of %u bytes) of response message, unable to receive " |
920 | 0 | "response", nread, (unsigned int) sizeof(uint32_t)); |
921 | 0 | errno = EPERM; |
922 | 0 | return -1; |
923 | 0 | } |
924 | | |
925 | 0 | tmp_pool = make_sub_pool(p); |
926 | 0 | pr_pool_tag(tmp_pool, "Controls API recv_response pool"); |
927 | | |
928 | | /* Allocate one byte for the terminating NUL. */ |
929 | 0 | msg = pcalloc(tmp_pool, msglen + 1); |
930 | 0 | nread = read(fd, msg, msglen); |
931 | 0 | xerrno = errno; |
932 | |
|
933 | 0 | if (nread < 0) { |
934 | 0 | pr_trace_msg(trace_channel, 3, |
935 | 0 | "error reading %lu bytes of response message: %s", |
936 | 0 | (unsigned long) msglen, strerror(xerrno)); |
937 | 0 | destroy_pool(tmp_pool); |
938 | 0 | pr_signals_unblock(); |
939 | |
|
940 | 0 | errno = xerrno; |
941 | 0 | return -1; |
942 | 0 | } |
943 | | |
944 | | /* Watch for short reads. */ |
945 | 0 | if ((unsigned int) nread != msglen) { |
946 | 0 | (void) pr_trace_msg(trace_channel, 3, |
947 | 0 | "short read (%d of %u bytes) of message text, unable to receive response", |
948 | 0 | nread, (unsigned int) msglen); |
949 | 0 | destroy_pool(tmp_pool); |
950 | 0 | pr_signals_unblock(); |
951 | |
|
952 | 0 | errno = EPERM; |
953 | 0 | return -1; |
954 | 0 | } |
955 | | |
956 | 0 | json = pr_json_object_from_text(tmp_pool, msg); |
957 | 0 | xerrno = errno; |
958 | |
|
959 | 0 | if (json == NULL) { |
960 | 0 | (void) pr_trace_msg(trace_channel, 3, |
961 | 0 | "read invalid JSON message text ('%.*s' [%lu bytes]), unable to " |
962 | 0 | "receive response: %s", (int) msglen, msg, (unsigned long) msglen, |
963 | 0 | strerror(xerrno)); |
964 | 0 | destroy_pool(tmp_pool); |
965 | 0 | pr_signals_unblock(); |
966 | |
|
967 | 0 | errno = EINVAL; |
968 | 0 | return -1; |
969 | 0 | } |
970 | | |
971 | 0 | res = pr_json_object_get_number(tmp_pool, json, CTRLS_RESP_STATUS_KEY, &dv); |
972 | 0 | xerrno = errno; |
973 | |
|
974 | 0 | if (res < 0) { |
975 | 0 | (void) pr_trace_msg(trace_channel, 3, |
976 | 0 | "unable to read response status (%s), unable to receive response", |
977 | 0 | strerror(xerrno)); |
978 | 0 | pr_json_object_free(json); |
979 | 0 | destroy_pool(tmp_pool); |
980 | 0 | pr_signals_unblock(); |
981 | |
|
982 | 0 | errno = EINVAL; |
983 | 0 | return -1; |
984 | 0 | } |
985 | | |
986 | 0 | *status = (int) dv; |
987 | 0 | pr_trace_msg(trace_channel, 19, "received response status: %d", *status); |
988 | |
|
989 | 0 | res = pr_json_object_get_array(tmp_pool, json, CTRLS_RESP_RESPS_KEY, &resps); |
990 | 0 | xerrno = errno; |
991 | |
|
992 | 0 | if (res < 0) { |
993 | 0 | (void) pr_trace_msg(trace_channel, 3, |
994 | 0 | "unable to read message responses (%s), unable to receive response", |
995 | 0 | strerror(xerrno)); |
996 | 0 | pr_json_object_free(json); |
997 | 0 | destroy_pool(tmp_pool); |
998 | 0 | pr_signals_unblock(); |
999 | |
|
1000 | 0 | errno = EINVAL; |
1001 | 0 | return -1; |
1002 | 0 | } |
1003 | | |
1004 | 0 | respargc = pr_json_array_count(resps); |
1005 | 0 | pr_trace_msg(trace_channel, 19, "received response argc: %u", respargc); |
1006 | |
|
1007 | 0 | resparr = make_array(p, 0, sizeof(char *)); |
1008 | | |
1009 | | /* Read each response, and add it to the array */ |
1010 | 0 | for (i = 0; i < respargc; i++) { |
1011 | 0 | char *resp = NULL; |
1012 | | |
1013 | | /* TODO: Handle other response types, such as arrays or objects, for |
1014 | | * more complex responses. Think of an action that dumps the memory |
1015 | | * pools, for example. |
1016 | | */ |
1017 | 0 | res = pr_json_array_get_string(tmp_pool, resps, i, &resp); |
1018 | 0 | xerrno = errno; |
1019 | |
|
1020 | 0 | if (res < 0) { |
1021 | 0 | (void) pr_trace_msg(trace_channel, 3, |
1022 | 0 | "unable to read message response #%u (%s), unable to receive response", |
1023 | 0 | i+1, strerror(xerrno)); |
1024 | 0 | pr_json_array_free(resps); |
1025 | 0 | pr_json_object_free(json); |
1026 | 0 | destroy_pool(tmp_pool); |
1027 | 0 | pr_signals_unblock(); |
1028 | |
|
1029 | 0 | errno = EINVAL; |
1030 | 0 | return -1; |
1031 | 0 | } |
1032 | | |
1033 | 0 | *((char **) push_array(resparr)) = pstrdup(p, resp); |
1034 | 0 | } |
1035 | | |
1036 | 0 | if (respargv != NULL) { |
1037 | 0 | *respargv = ((char **) resparr->elts); |
1038 | 0 | } |
1039 | |
|
1040 | 0 | pr_json_array_free(resps); |
1041 | 0 | pr_json_object_free(json); |
1042 | 0 | destroy_pool(tmp_pool); |
1043 | 0 | pr_signals_unblock(); |
1044 | |
|
1045 | 0 | return respargc; |
1046 | 0 | } |
1047 | | |
1048 | | static pr_ctrls_t *ctrls_lookup_action(module *mod, const char *action, |
1049 | 0 | unsigned char skip_disabled) { |
1050 | | |
1051 | | /* (Re)set the current indices */ |
1052 | 0 | action_lookup_next = ctrls_action_list; |
1053 | 0 | action_lookup_action = action; |
1054 | 0 | action_lookup_module = mod; |
1055 | | |
1056 | | /* Wrapper around ctrls_lookup_next_action() */ |
1057 | 0 | return ctrls_lookup_next_action(mod, skip_disabled); |
1058 | 0 | } |
1059 | | |
1060 | | static pr_ctrls_t *ctrls_lookup_next_action(module *mod, |
1061 | 0 | unsigned char skip_disabled) { |
1062 | 0 | register ctrls_action_t *act = NULL; |
1063 | | |
1064 | | /* Sanity check */ |
1065 | 0 | if (action_lookup_action == NULL) { |
1066 | 0 | errno = EINVAL; |
1067 | 0 | return NULL; |
1068 | 0 | } |
1069 | | |
1070 | 0 | if (mod != action_lookup_module) { |
1071 | 0 | return ctrls_lookup_action(mod, action_lookup_action, skip_disabled); |
1072 | 0 | } |
1073 | | |
1074 | 0 | for (act = action_lookup_next; act; act = act->next) { |
1075 | 0 | if (skip_disabled && (act->flags & PR_CTRLS_ACT_DISABLED)) { |
1076 | 0 | continue; |
1077 | 0 | } |
1078 | | |
1079 | 0 | if (strcmp(act->action, action_lookup_action) == 0 && |
1080 | 0 | (act->module == mod || mod == ANY_MODULE || mod == NULL)) { |
1081 | 0 | action_lookup_next = act->next; |
1082 | | |
1083 | | /* Use this action object to prepare a ctrl object. */ |
1084 | 0 | return ctrls_prepare(act); |
1085 | 0 | } |
1086 | 0 | } |
1087 | | |
1088 | 0 | return NULL; |
1089 | 0 | } |
1090 | | |
1091 | 0 | int pr_get_registered_actions(pr_ctrls_t *ctrl, int flags) { |
1092 | 0 | register ctrls_action_t *act = NULL; |
1093 | 0 | int count = 0; |
1094 | |
|
1095 | 0 | if (ctrl == NULL) { |
1096 | 0 | errno = EINVAL; |
1097 | 0 | return -1; |
1098 | 0 | } |
1099 | | |
1100 | | /* Are ctrls blocked? */ |
1101 | 0 | if (ctrls_blocked == TRUE) { |
1102 | 0 | errno = EPERM; |
1103 | 0 | return -1; |
1104 | 0 | } |
1105 | | |
1106 | 0 | for (act = ctrls_action_list; act; act = act->next) { |
1107 | 0 | switch (flags) { |
1108 | 0 | case CTRLS_GET_ACTION_ALL: |
1109 | 0 | if (act->module != NULL) { |
1110 | 0 | pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action, |
1111 | 0 | act->module->name); |
1112 | |
|
1113 | 0 | } else { |
1114 | 0 | pr_ctrls_add_response(ctrl, "%s (core)", act->action); |
1115 | 0 | } |
1116 | |
|
1117 | 0 | count++; |
1118 | 0 | break; |
1119 | | |
1120 | 0 | case CTRLS_GET_ACTION_ENABLED: |
1121 | 0 | if (act->flags & PR_CTRLS_ACT_DISABLED) { |
1122 | 0 | continue; |
1123 | 0 | } |
1124 | | |
1125 | 0 | if (act->module != NULL) { |
1126 | 0 | pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action, |
1127 | 0 | act->module->name); |
1128 | |
|
1129 | 0 | } else { |
1130 | 0 | pr_ctrls_add_response(ctrl, "%s (core)", act->action); |
1131 | 0 | } |
1132 | |
|
1133 | 0 | count++; |
1134 | 0 | break; |
1135 | | |
1136 | 0 | case CTRLS_GET_DESC: |
1137 | 0 | pr_ctrls_add_response(ctrl, "%s: %s", act->action, |
1138 | 0 | act->desc); |
1139 | 0 | count++; |
1140 | 0 | break; |
1141 | 0 | } |
1142 | 0 | } |
1143 | | |
1144 | 0 | return count; |
1145 | 0 | } |
1146 | | |
1147 | | int pr_set_registered_actions(module *mod, const char *action, |
1148 | 0 | unsigned char skip_disabled, unsigned int flags) { |
1149 | 0 | register ctrls_action_t *act = NULL; |
1150 | 0 | unsigned char have_action = FALSE; |
1151 | | |
1152 | | /* Is flags a valid combination of settable flags? */ |
1153 | 0 | if (flags > 0 && |
1154 | 0 | flags != PR_CTRLS_ACT_SOLITARY && |
1155 | 0 | flags != PR_CTRLS_ACT_DISABLED && |
1156 | 0 | flags != (PR_CTRLS_ACT_SOLITARY|PR_CTRLS_ACT_DISABLED)) { |
1157 | 0 | errno = EINVAL; |
1158 | 0 | return -1; |
1159 | 0 | } |
1160 | | |
1161 | | /* Are ctrls blocked? */ |
1162 | 0 | if (ctrls_blocked == TRUE) { |
1163 | 0 | errno = EPERM; |
1164 | 0 | return -1; |
1165 | 0 | } |
1166 | | |
1167 | 0 | for (act = ctrls_action_list; act; act = act->next) { |
1168 | 0 | if (skip_disabled == TRUE && |
1169 | 0 | (act->flags & PR_CTRLS_ACT_DISABLED)) { |
1170 | 0 | continue; |
1171 | 0 | } |
1172 | | |
1173 | 0 | if ((action == NULL || |
1174 | 0 | strcmp(action, "all") == 0 || |
1175 | 0 | strcmp(act->action, action) == 0) && |
1176 | 0 | (act->module == mod || mod == ANY_MODULE || mod == NULL)) { |
1177 | 0 | have_action = TRUE; |
1178 | 0 | act->flags = flags; |
1179 | 0 | } |
1180 | 0 | } |
1181 | |
|
1182 | 0 | if (have_action == FALSE) { |
1183 | 0 | errno = ENOENT; |
1184 | 0 | return -1; |
1185 | 0 | } |
1186 | | |
1187 | 0 | return 0; |
1188 | 0 | } |
1189 | | |
1190 | | #if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1191 | | !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS) |
1192 | | static int ctrls_connect_local_creds(int fd) { |
1193 | | char buf[1] = {'\0'}; |
1194 | | int res; |
1195 | | |
1196 | | /* The backend doesn't care what we send here, but it wants |
1197 | | * exactly one character to force recvmsg() to block and wait |
1198 | | * for us. |
1199 | | */ |
1200 | | |
1201 | | res = write(fd, buf, 1); |
1202 | | while (res < 0) { |
1203 | | if (errno == EINTR) { |
1204 | | pr_signals_handle(); |
1205 | | |
1206 | | res = write(fd, buf, 1); |
1207 | | continue; |
1208 | | } |
1209 | | |
1210 | | pr_trace_msg(trace_channel, 5, |
1211 | | "error writing credentials byte for LOCAL_CREDS to fd %d: %s", fd, |
1212 | | strerror(errno)); |
1213 | | return -1; |
1214 | | } |
1215 | | |
1216 | | return res; |
1217 | | } |
1218 | | #endif /* !SCM_CREDS */ |
1219 | | |
1220 | 0 | int pr_ctrls_connect(const char *socket_file) { |
1221 | 0 | int fd = -1, len = 0; |
1222 | 0 | struct sockaddr_un cl_sock, ctrl_sock; |
1223 | |
|
1224 | 0 | if (socket_file == NULL) { |
1225 | 0 | errno = EINVAL; |
1226 | 0 | return -1; |
1227 | 0 | } |
1228 | | |
1229 | | /* No interruptions */ |
1230 | 0 | pr_signals_block(); |
1231 | | |
1232 | | /* Create a Unix domain socket */ |
1233 | 0 | fd = socket(AF_UNIX, SOCK_STREAM, 0); |
1234 | 0 | if (fd < 0) { |
1235 | 0 | int xerrno = errno; |
1236 | |
|
1237 | 0 | pr_signals_unblock(); |
1238 | |
|
1239 | 0 | errno = xerrno; |
1240 | 0 | return -1; |
1241 | 0 | } |
1242 | | |
1243 | 0 | if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { |
1244 | 0 | int xerrno = errno; |
1245 | |
|
1246 | 0 | (void) close(fd); |
1247 | 0 | pr_signals_unblock(); |
1248 | |
|
1249 | 0 | errno = xerrno; |
1250 | 0 | return -1; |
1251 | 0 | } |
1252 | | |
1253 | | /* Fill in the socket address */ |
1254 | 0 | memset(&cl_sock, 0, sizeof(cl_sock)); |
1255 | | |
1256 | | /* This first part is clever. First, this process creates a socket in |
1257 | | * the file system. It _then_ connect()s to the server. Upon accept()ing |
1258 | | * the connection, the server examines the created socket to see that it |
1259 | | * is indeed a socket, with the proper mode and time. Clever, but not |
1260 | | * ideal. |
1261 | | */ |
1262 | |
|
1263 | 0 | cl_sock.sun_family = AF_UNIX; |
1264 | 0 | pr_snprintf(cl_sock.sun_path, sizeof(cl_sock.sun_path) - 1, "%s%05u", |
1265 | 0 | "/tmp/ftp.cl", (unsigned int) getpid()); |
1266 | 0 | len = sizeof(cl_sock); |
1267 | | |
1268 | | /* Make sure the file doesn't already exist */ |
1269 | 0 | (void) unlink(cl_sock.sun_path); |
1270 | | |
1271 | | /* Make it a socket */ |
1272 | 0 | if (bind(fd, (struct sockaddr *) &cl_sock, len) < 0) { |
1273 | 0 | int xerrno = errno; |
1274 | |
|
1275 | 0 | pr_trace_msg(trace_channel, 19, "error binding local socket to '%s': %s", |
1276 | 0 | cl_sock.sun_path, strerror(xerrno)); |
1277 | 0 | (void) unlink(cl_sock.sun_path); |
1278 | 0 | (void) close(fd); |
1279 | 0 | pr_signals_unblock(); |
1280 | |
|
1281 | 0 | errno = xerrno; |
1282 | 0 | return -1; |
1283 | 0 | } |
1284 | | |
1285 | | /* Set the proper mode */ |
1286 | 0 | if (chmod(cl_sock.sun_path, PR_CTRLS_CL_MODE) < 0) { |
1287 | 0 | int xerrno = errno; |
1288 | |
|
1289 | 0 | pr_trace_msg(trace_channel, 19, "error setting local socket mode: %s", |
1290 | 0 | strerror(xerrno)); |
1291 | 0 | (void) unlink(cl_sock.sun_path); |
1292 | 0 | (void) close(fd); |
1293 | 0 | pr_signals_unblock(); |
1294 | |
|
1295 | 0 | errno = xerrno; |
1296 | 0 | return -1; |
1297 | 0 | } |
1298 | | |
1299 | | /* Now connect to the real server */ |
1300 | 0 | memset(&ctrl_sock, 0, sizeof(ctrl_sock)); |
1301 | |
|
1302 | 0 | ctrl_sock.sun_family = AF_UNIX; |
1303 | 0 | sstrncpy(ctrl_sock.sun_path, socket_file, sizeof(ctrl_sock.sun_path)); |
1304 | 0 | len = sizeof(ctrl_sock); |
1305 | |
|
1306 | 0 | if (connect(fd, (struct sockaddr *) &ctrl_sock, len) < 0) { |
1307 | 0 | int xerrno = errno; |
1308 | |
|
1309 | 0 | pr_trace_msg(trace_channel, 19, "error connecting to local socket '%s': %s", |
1310 | 0 | ctrl_sock.sun_path, strerror(xerrno)); |
1311 | 0 | (void) unlink(cl_sock.sun_path); |
1312 | 0 | (void) close(fd); |
1313 | 0 | pr_signals_unblock(); |
1314 | |
|
1315 | 0 | errno = xerrno; |
1316 | 0 | return -1; |
1317 | 0 | } |
1318 | | |
1319 | | #if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1320 | | !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS) |
1321 | | if (ctrls_connect_local_creds(fd) < 0) { |
1322 | | int xerrno = errno; |
1323 | | |
1324 | | pr_trace_msg(trace_channel, 19, "error sending creds to local socket: %s", |
1325 | | strerror(xerrno)); |
1326 | | (void) unlink(cl_sock.sun_path); |
1327 | | (void) close(fd); |
1328 | | pr_signals_unblock(); |
1329 | | |
1330 | | errno = xerrno; |
1331 | | return -1; |
1332 | | } |
1333 | | #endif /* LOCAL_CREDS */ |
1334 | | |
1335 | 0 | pr_signals_unblock(); |
1336 | 0 | return fd; |
1337 | 0 | } |
1338 | | |
1339 | 0 | int pr_ctrls_issock_unix(mode_t sock_mode) { |
1340 | |
|
1341 | 0 | if (ctrls_use_isfifo == TRUE) { |
1342 | 0 | #if defined(S_ISFIFO) |
1343 | 0 | if (S_ISFIFO(sock_mode)) { |
1344 | 0 | return 0; |
1345 | 0 | } |
1346 | 0 | #endif /* S_ISFIFO */ |
1347 | 0 | } else { |
1348 | 0 | #if defined(S_ISSOCK) |
1349 | 0 | if (S_ISSOCK(sock_mode)) { |
1350 | 0 | return 0; |
1351 | 0 | } |
1352 | 0 | #endif /* S_ISSOCK */ |
1353 | 0 | } |
1354 | | |
1355 | 0 | errno = ENOSYS; |
1356 | 0 | return -1; |
1357 | 0 | } |
1358 | | |
1359 | | #if defined(SO_PEERCRED) |
1360 | | static int ctrls_get_creds_peercred(int fd, uid_t *uid, gid_t *gid, |
1361 | 0 | pid_t *pid) { |
1362 | | # if defined(HAVE_STRUCT_SOCKPEERCRED) |
1363 | | struct sockpeercred cred; |
1364 | | # else |
1365 | 0 | struct ucred cred; |
1366 | 0 | # endif /* HAVE_STRUCT_SOCKPEERCRED */ |
1367 | 0 | socklen_t cred_len; |
1368 | |
|
1369 | 0 | cred_len = sizeof(cred); |
1370 | 0 | if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) { |
1371 | 0 | int xerrno = errno; |
1372 | |
|
1373 | 0 | pr_trace_msg(trace_channel, 2, |
1374 | 0 | "error obtaining peer credentials using SO_PEERCRED: %s", |
1375 | 0 | strerror(xerrno)); |
1376 | |
|
1377 | 0 | errno = EPERM; |
1378 | 0 | return -1; |
1379 | 0 | } |
1380 | | |
1381 | 0 | if (uid != NULL) { |
1382 | 0 | *uid = cred.uid; |
1383 | 0 | } |
1384 | |
|
1385 | 0 | if (gid != NULL) { |
1386 | 0 | *gid = cred.gid; |
1387 | 0 | } |
1388 | |
|
1389 | 0 | if (pid != NULL) { |
1390 | 0 | *pid = cred.pid; |
1391 | 0 | } |
1392 | |
|
1393 | 0 | return 0; |
1394 | 0 | } |
1395 | | #endif /* SO_PEERCRED */ |
1396 | | |
1397 | | #if !defined(SO_PEERCRED) && defined(HAVE_GETPEEREID) |
1398 | | static int ctrls_get_creds_peereid(int fd, uid_t *uid, gid_t *gid) { |
1399 | | if (getpeereid(fd, uid, gid) < 0) { |
1400 | | int xerrno = errno; |
1401 | | |
1402 | | pr_trace_msg(trace_channel, 7, "error obtaining credentials using " |
1403 | | "getpeereid(2) on fd %d: %s", fd, strerror(xerrno)); |
1404 | | |
1405 | | errno = xerrno; |
1406 | | return -1; |
1407 | | } |
1408 | | |
1409 | | return 0; |
1410 | | } |
1411 | | #endif /* !HAVE_GETPEEREID */ |
1412 | | |
1413 | | #if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1414 | | defined(HAVE_GETPEERUCRED) |
1415 | | static int ctrls_get_creds_peerucred(int fd, uid_t *uid, gid_t *gid) { |
1416 | | ucred_t *cred = NULL; |
1417 | | |
1418 | | if (getpeerucred(fd, &cred) < 0) { |
1419 | | int xerrno = errno; |
1420 | | |
1421 | | pr_trace_msg(trace_channel, 7, "error obtaining credentials using " |
1422 | | "getpeerucred(3) on fd %d: %s", fd, strerror(xerrno)); |
1423 | | |
1424 | | errno = xerrno; |
1425 | | return -1; |
1426 | | } |
1427 | | |
1428 | | if (uid != NULL) { |
1429 | | *uid = ucred_getruid(cred); |
1430 | | } |
1431 | | |
1432 | | if (gid != NULL) { |
1433 | | *gid = ucred_getrgid(cred); |
1434 | | } |
1435 | | |
1436 | | ucred_free(cred); |
1437 | | return 0; |
1438 | | } |
1439 | | #endif /* !HAVE_GETPEERUCRED */ |
1440 | | |
1441 | | #if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1442 | | !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS) |
1443 | | static int ctrls_get_creds_local(int fd, uid_t *uid, gid_t *gid, |
1444 | | pid_t *pid) { |
1445 | | int res; |
1446 | | char buf[1]; |
1447 | | struct iovec iov; |
1448 | | struct msghdr msg; |
1449 | | |
1450 | | # if defined(SOCKCREDSIZE) |
1451 | | # define MINCREDSIZE (sizeof(struct cmsghdr) + SOCKCREDSIZE(0)) |
1452 | | # else |
1453 | | # if defined(HAVE_STRUCT_CMSGCRED) |
1454 | | # define MINCREDSIZE (sizeof(struct cmsghdr) + sizeof(struct cmsgcred)) |
1455 | | # elif defined(HAVE_STRUCT_SOCKCRED) |
1456 | | # define MINCREDSIZE (sizeof(struct cmsghdr) + sizeof(struct sockcred)) |
1457 | | # endif |
1458 | | # endif /* !SOCKCREDSIZE */ |
1459 | | |
1460 | | char control[MINCREDSIZE]; |
1461 | | |
1462 | | iov.iov_base = buf; |
1463 | | iov.iov_len = 1; |
1464 | | |
1465 | | memset(&msg, 0, sizeof(msg)); |
1466 | | msg.msg_iov = &iov; |
1467 | | msg.msg_iovlen = 1; |
1468 | | msg.msg_control = control; |
1469 | | msg.msg_controllen = sizeof(control); |
1470 | | msg.msg_flags = 0; |
1471 | | |
1472 | | res = recvmsg(fd, &msg, 0); |
1473 | | while (res < 0) { |
1474 | | int xerrno = errno; |
1475 | | |
1476 | | if (xerrno == EINTR) { |
1477 | | pr_signals_handle(); |
1478 | | |
1479 | | res = recvmsg(fd, &msg, 0); |
1480 | | continue; |
1481 | | } |
1482 | | |
1483 | | pr_trace_msg(trace_channel, 6, |
1484 | | "error calling recvmsg() on fd %d: %s", fd, strerror(xerrno)); |
1485 | | |
1486 | | errno = xerrno; |
1487 | | return -1; |
1488 | | } |
1489 | | |
1490 | | if (msg.msg_controllen > 0) { |
1491 | | #if defined(HAVE_STRUCT_CMSGCRED) |
1492 | | struct cmsgcred cred; |
1493 | | #elif defined(HAVE_STRUCT_SOCKCRED) |
1494 | | struct sockcred cred; |
1495 | | #endif /* !CMSGCRED and !SOCKCRED */ |
1496 | | |
1497 | | struct cmsghdr *hdr = (struct cmsghdr *) control; |
1498 | | |
1499 | | if (hdr->cmsg_level != SOL_SOCKET) { |
1500 | | pr_trace_msg(trace_channel, 5, |
1501 | | "message received via recvmsg() on fd %d was not a SOL_SOCKET message", |
1502 | | fd); |
1503 | | |
1504 | | errno = EINVAL; |
1505 | | return -1; |
1506 | | } |
1507 | | |
1508 | | if (hdr->cmsg_len < MINCREDSIZE) { |
1509 | | pr_trace_msg(trace_channel, 5, |
1510 | | "message received via recvmsg() on fd %d was not of proper " |
1511 | | "length (%u bytes)", fd, MINCREDSIZE); |
1512 | | |
1513 | | errno = EINVAL; |
1514 | | return -1; |
1515 | | } |
1516 | | |
1517 | | if (hdr->cmsg_type != SCM_CREDS) { |
1518 | | pr_trace_msg(trace_channel, 5, |
1519 | | "message received via recvmsg() on fd %d was not of type SCM_CREDS", |
1520 | | fd); |
1521 | | |
1522 | | errno = EINVAL; |
1523 | | return -1; |
1524 | | } |
1525 | | |
1526 | | #if defined(HAVE_STRUCT_CMSGCRED) |
1527 | | memcpy(&cred, CMSG_DATA(hdr), sizeof(struct cmsgcred)); |
1528 | | |
1529 | | if (uid != NULL) { |
1530 | | *uid = cred.cmcred_uid; |
1531 | | } |
1532 | | |
1533 | | if (gid != NULL) { |
1534 | | *gid = cred.cmcred_gid; |
1535 | | } |
1536 | | |
1537 | | if (pid != NULL) { |
1538 | | *pid = cred.cmcred_pid; |
1539 | | } |
1540 | | |
1541 | | #elif defined(HAVE_STRUCT_SOCKCRED) |
1542 | | memcpy(&cred, CMSG_DATA(hdr), sizeof(struct sockcred)); |
1543 | | |
1544 | | if (uid != NULL) { |
1545 | | *uid = cred.sc_uid; |
1546 | | } |
1547 | | |
1548 | | if (gid != NULL) { |
1549 | | *gid = cred.sc_gid; |
1550 | | } |
1551 | | #endif |
1552 | | |
1553 | | return 0; |
1554 | | } |
1555 | | |
1556 | | return -1; |
1557 | | } |
1558 | | #endif /* !SCM_CREDS */ |
1559 | | |
1560 | | static int ctrls_get_creds_basic(struct sockaddr_un *sock, int cl_fd, |
1561 | 0 | unsigned int max_age, uid_t *uid, gid_t *gid, pid_t *pid) { |
1562 | 0 | pid_t cl_pid = 0; |
1563 | 0 | char *tmp = NULL; |
1564 | 0 | time_t stale_time; |
1565 | 0 | struct stat st; |
1566 | | |
1567 | | /* Check the path -- hmmm... */ |
1568 | 0 | PRIVS_ROOT |
1569 | 0 | while (stat(sock->sun_path, &st) < 0) { |
1570 | 0 | int xerrno = errno; |
1571 | |
|
1572 | 0 | if (xerrno == EINTR) { |
1573 | 0 | pr_signals_handle(); |
1574 | 0 | continue; |
1575 | 0 | } |
1576 | | |
1577 | 0 | PRIVS_RELINQUISH |
1578 | 0 | pr_trace_msg(trace_channel, 2, "error: unable to stat %s: %s", |
1579 | 0 | sock->sun_path, strerror(xerrno)); |
1580 | 0 | (void) close(cl_fd); |
1581 | |
|
1582 | 0 | errno = xerrno; |
1583 | 0 | return -1; |
1584 | 0 | } |
1585 | 0 | PRIVS_RELINQUISH |
1586 | | |
1587 | | /* Is it a socket? */ |
1588 | 0 | if (pr_ctrls_issock_unix(st.st_mode) < 0) { |
1589 | 0 | (void) close(cl_fd); |
1590 | 0 | errno = ENOTSOCK; |
1591 | 0 | return -1; |
1592 | 0 | } |
1593 | | |
1594 | | /* Are the perms _not_ rwx------? */ |
1595 | 0 | if (st.st_mode & (S_IRWXG|S_IRWXO) || |
1596 | 0 | ((st.st_mode & S_IRWXU) != PR_CTRLS_CL_MODE)) { |
1597 | 0 | pr_trace_msg(trace_channel, 3, |
1598 | 0 | "error: unable to accept connection: incorrect mode"); |
1599 | 0 | (void) close(cl_fd); |
1600 | 0 | errno = EPERM; |
1601 | 0 | return -1; |
1602 | 0 | } |
1603 | | |
1604 | | /* Is it new enough? */ |
1605 | 0 | stale_time = time(NULL) - max_age; |
1606 | |
|
1607 | 0 | if (st.st_atime < stale_time || |
1608 | 0 | st.st_ctime < stale_time || |
1609 | 0 | st.st_mtime < stale_time) { |
1610 | 0 | pool *tmp_pool; |
1611 | 0 | char *msg = "error: stale connection"; |
1612 | |
|
1613 | 0 | pr_trace_msg(trace_channel, 3, |
1614 | 0 | "unable to accept connection: stale connection"); |
1615 | | |
1616 | | /* Log the times being compared, to aid in debugging this situation. */ |
1617 | 0 | if (st.st_atime < stale_time) { |
1618 | 0 | time_t age = stale_time - st.st_atime; |
1619 | |
|
1620 | 0 | pr_trace_msg(trace_channel, 3, |
1621 | 0 | "last access time of '%s' is %lu secs old (must be less than %u secs)", |
1622 | 0 | sock->sun_path, (unsigned long) age, max_age); |
1623 | 0 | } |
1624 | |
|
1625 | 0 | if (st.st_ctime < stale_time) { |
1626 | 0 | time_t age = stale_time - st.st_ctime; |
1627 | |
|
1628 | 0 | pr_trace_msg(trace_channel, 3, |
1629 | 0 | "last change time of '%s' is %lu secs old (must be less than %u secs)", |
1630 | 0 | sock->sun_path, (unsigned long) age, max_age); |
1631 | 0 | } |
1632 | |
|
1633 | 0 | if (st.st_mtime < stale_time) { |
1634 | 0 | time_t age = stale_time - st.st_mtime; |
1635 | |
|
1636 | 0 | pr_trace_msg(trace_channel, 3, |
1637 | 0 | "last modified time of '%s' is %lu secs old (must be less than %u " |
1638 | 0 | "secs)", sock->sun_path, (unsigned long) age, max_age); |
1639 | 0 | } |
1640 | |
|
1641 | 0 | tmp_pool = make_sub_pool(permanent_pool); |
1642 | |
|
1643 | 0 | if (pr_ctrls_send_response(tmp_pool, cl_fd, -1, 1, &msg) < 0) { |
1644 | 0 | pr_trace_msg(trace_channel, 2, "error sending message: %s", |
1645 | 0 | strerror(errno)); |
1646 | 0 | } |
1647 | |
|
1648 | 0 | destroy_pool(tmp_pool); |
1649 | 0 | (void) close(cl_fd); |
1650 | |
|
1651 | 0 | errno = ETIMEDOUT; |
1652 | 0 | return -1; |
1653 | 0 | } |
1654 | | |
1655 | | /* Parse the PID out of the path */ |
1656 | 0 | tmp = sock->sun_path; |
1657 | 0 | tmp += strlen("/tmp/ftp.cl"); |
1658 | 0 | cl_pid = atol(tmp); |
1659 | | |
1660 | | /* Return the IDs of the caller */ |
1661 | 0 | if (uid != NULL) { |
1662 | 0 | *uid = st.st_uid; |
1663 | 0 | } |
1664 | |
|
1665 | 0 | if (gid != NULL) { |
1666 | 0 | *gid = st.st_gid; |
1667 | 0 | } |
1668 | |
|
1669 | 0 | if (pid != NULL) { |
1670 | 0 | *pid = cl_pid; |
1671 | 0 | } |
1672 | |
|
1673 | 0 | return 0; |
1674 | 0 | } |
1675 | | |
1676 | | int pr_ctrls_accept(int fd, uid_t *uid, gid_t *gid, pid_t *pid, |
1677 | 0 | unsigned int max_age) { |
1678 | 0 | socklen_t len = 0; |
1679 | 0 | struct sockaddr_un sock; |
1680 | 0 | int cl_fd = -1, res = -1, xerrno; |
1681 | |
|
1682 | 0 | len = sizeof(sock); |
1683 | |
|
1684 | 0 | cl_fd = accept(fd, (struct sockaddr *) &sock, &len); |
1685 | 0 | xerrno = errno; |
1686 | |
|
1687 | 0 | while (cl_fd < 0) { |
1688 | 0 | if (xerrno == EINTR) { |
1689 | 0 | pr_signals_handle(); |
1690 | |
|
1691 | 0 | cl_fd = accept(fd, (struct sockaddr *) &sock, &len); |
1692 | 0 | xerrno = errno; |
1693 | 0 | continue; |
1694 | 0 | } |
1695 | | |
1696 | 0 | pr_trace_msg(trace_channel, 3, |
1697 | 0 | "error: unable to accept on local socket: %s", strerror(xerrno)); |
1698 | |
|
1699 | 0 | errno = xerrno; |
1700 | 0 | return -1; |
1701 | 0 | } |
1702 | | |
1703 | | /* NULL terminate the name */ |
1704 | 0 | sock.sun_path[sizeof(sock.sun_path)-1] = '\0'; |
1705 | |
|
1706 | 0 | #if defined(SO_PEERCRED) |
1707 | 0 | pr_trace_msg(trace_channel, 5, |
1708 | 0 | "checking client credentials using SO_PEERCRED"); |
1709 | 0 | res = ctrls_get_creds_peercred(cl_fd, uid, gid, pid); |
1710 | |
|
1711 | | #elif !defined(SO_PEERCRED) && defined(HAVE_GETPEEREID) |
1712 | | pr_trace_msg(trace_channel, 5, |
1713 | | "checking client credentials using getpeereid(2)"); |
1714 | | res = ctrls_get_creds_peereid(cl_fd, uid, gid); |
1715 | | |
1716 | | #elif !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1717 | | defined(HAVE_GETPEERUCRED) |
1718 | | pr_trace_msg(trace_channel, 5, |
1719 | | "checking client credentials using getpeerucred(3)"); |
1720 | | res = ctrls_get_creds_peerucred(cl_fd, uid, gid); |
1721 | | |
1722 | | #elif !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \ |
1723 | | !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS) |
1724 | | pr_trace_msg(trace_channel, 5, |
1725 | | "checking client credentials using SCM_CREDS"); |
1726 | | res = ctrls_get_creds_local(cl_fd, uid, gid, pid); |
1727 | | #endif |
1728 | | |
1729 | | /* Fallback to the Stevens method of determining connection credentials, |
1730 | | * if the kernel-enforced methods did not pan out. |
1731 | | */ |
1732 | 0 | if (res < 0) { |
1733 | 0 | pr_trace_msg(trace_channel, 5, |
1734 | 0 | "checking client credentials using Stevens' method"); |
1735 | 0 | res = ctrls_get_creds_basic(&sock, cl_fd, max_age, uid, gid, pid); |
1736 | 0 | if (res < 0) { |
1737 | 0 | return res; |
1738 | 0 | } |
1739 | 0 | } |
1740 | | |
1741 | | /* Done with the path now */ |
1742 | 0 | PRIVS_ROOT |
1743 | 0 | (void) unlink(sock.sun_path); |
1744 | 0 | PRIVS_RELINQUISH |
1745 | |
|
1746 | 0 | return cl_fd; |
1747 | 0 | } |
1748 | | |
1749 | 0 | void pr_block_ctrls(void) { |
1750 | 0 | ctrls_blocked = TRUE; |
1751 | 0 | } |
1752 | | |
1753 | 0 | void pr_unblock_ctrls(void) { |
1754 | 0 | ctrls_blocked = FALSE; |
1755 | 0 | } |
1756 | | |
1757 | 0 | int pr_ctrls_check_actions(void) { |
1758 | 0 | register ctrls_action_t *act = NULL; |
1759 | |
|
1760 | 0 | for (act = ctrls_action_list; act; act = act->next) { |
1761 | 0 | if (act->flags & PR_CTRLS_ACT_SOLITARY) { |
1762 | | /* This is a territorial action -- only one instance allowed */ |
1763 | 0 | if (ctrls_lookup_action(NULL, act->action, FALSE)) { |
1764 | 0 | pr_log_pri(PR_LOG_NOTICE, |
1765 | 0 | "duplicate controls for '%s' action not allowed", |
1766 | 0 | act->action); |
1767 | 0 | errno = EEXIST; |
1768 | 0 | return -1; |
1769 | 0 | } |
1770 | 0 | } |
1771 | 0 | } |
1772 | | |
1773 | 0 | return 0; |
1774 | 0 | } |
1775 | | |
1776 | 0 | int pr_run_ctrls(module *mod, const char *action) { |
1777 | 0 | register pr_ctrls_t *ctrl = NULL; |
1778 | 0 | time_t now; |
1779 | | |
1780 | | /* Are ctrls blocked? */ |
1781 | 0 | if (ctrls_blocked == TRUE) { |
1782 | 0 | errno = EPERM; |
1783 | 0 | return -1; |
1784 | 0 | } |
1785 | | |
1786 | 0 | now = time(NULL); |
1787 | |
|
1788 | 0 | for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) { |
1789 | 0 | int res; |
1790 | |
|
1791 | 0 | if (mod != NULL && |
1792 | 0 | ctrl->ctrls_module != NULL && |
1793 | 0 | ctrl->ctrls_module != mod) { |
1794 | 0 | pr_trace_msg(trace_channel, 19, |
1795 | 0 | "skipping ctrl due to module mismatch: module = %p, ctrl module = %p", |
1796 | 0 | mod, ctrl->ctrls_module); |
1797 | 0 | continue; |
1798 | 0 | } |
1799 | | |
1800 | | /* Be watchful of the various client-side flags. Note: if |
1801 | | * ctrl->ctrls_cl is ever NULL, it means there's a bug in the code. |
1802 | | */ |
1803 | 0 | if (ctrl->ctrls_cl->cl_flags != PR_CTRLS_CL_HAVEREQ) { |
1804 | 0 | pr_trace_msg(trace_channel, 19, |
1805 | 0 | "skipping ctrl due to missing client HAVEREQ flag"); |
1806 | 0 | continue; |
1807 | 0 | } |
1808 | | |
1809 | | /* Has this control been disabled? */ |
1810 | 0 | if (ctrl->ctrls_flags & PR_CTRLS_ACT_DISABLED) { |
1811 | 0 | pr_trace_msg(trace_channel, 19, |
1812 | 0 | "skipping ctrl due to ACT_DISABLED flag"); |
1813 | 0 | continue; |
1814 | 0 | } |
1815 | | |
1816 | | /* Is it time to trigger this ctrl? */ |
1817 | 0 | if (!(ctrl->ctrls_flags & PR_CTRLS_FL_REQUESTED)) { |
1818 | 0 | pr_trace_msg(trace_channel, 19, |
1819 | 0 | "skipping ctrl due to missing CTRLS_REQUESTED flag"); |
1820 | 0 | continue; |
1821 | 0 | } |
1822 | | |
1823 | 0 | if (ctrl->ctrls_when > now) { |
1824 | 0 | pr_trace_msg(trace_channel, 19, |
1825 | 0 | "skipping ctrl because it is still pending: now = %lu, ctrl when = %lu", |
1826 | 0 | (unsigned long) now, (unsigned long) ctrl->ctrls_when); |
1827 | 0 | ctrl->ctrls_flags |= PR_CTRLS_FL_PENDING; |
1828 | 0 | pr_ctrls_add_response(ctrl, "request pending"); |
1829 | 0 | continue; |
1830 | 0 | } |
1831 | | |
1832 | 0 | if (action == NULL || |
1833 | 0 | strcmp(ctrl->ctrls_action, action) == 0) { |
1834 | 0 | pr_trace_msg(trace_channel, 7, "calling '%s' control handler", |
1835 | 0 | ctrl->ctrls_action); |
1836 | |
|
1837 | 0 | } else { |
1838 | 0 | continue; |
1839 | 0 | } |
1840 | | |
1841 | 0 | pr_unblock_ctrls(); |
1842 | 0 | res = ctrl->ctrls_cb(ctrl, |
1843 | 0 | (ctrl->ctrls_cb_args ? ctrl->ctrls_cb_args->nelts : 0), |
1844 | 0 | (ctrl->ctrls_cb_args ? (char **) ctrl->ctrls_cb_args->elts : NULL)); |
1845 | 0 | pr_block_ctrls(); |
1846 | |
|
1847 | 0 | pr_trace_msg(trace_channel, 19, |
1848 | 0 | "ran '%s' ctrl, callback value = %d", ctrl->ctrls_action, res); |
1849 | |
|
1850 | 0 | if (res >= PR_CTRLS_STATUS_PENDING) { |
1851 | 0 | pr_trace_msg(trace_channel, 1, "'%s' ctrl returned inappropriate " |
1852 | 0 | "value %d, treating as GENERIC_ERROR (%d)", ctrl->ctrls_action, res, |
1853 | 0 | PR_CTRLS_STATUS_GENERIC_ERROR); |
1854 | 0 | res = PR_CTRLS_STATUS_GENERIC_ERROR; |
1855 | 0 | } |
1856 | |
|
1857 | 0 | ctrl->ctrls_flags &= ~PR_CTRLS_FL_REQUESTED; |
1858 | 0 | ctrl->ctrls_flags &= ~PR_CTRLS_FL_PENDING; |
1859 | 0 | ctrl->ctrls_flags |= PR_CTRLS_FL_HANDLED; |
1860 | |
|
1861 | 0 | ctrl->ctrls_cb_retval = res; |
1862 | 0 | } |
1863 | |
|
1864 | 0 | return 0; |
1865 | 0 | } |
1866 | | |
1867 | 0 | int pr_ctrls_reset(void) { |
1868 | 0 | pr_ctrls_t *ctrl = NULL; |
1869 | | |
1870 | | /* NOTE: need a clean_ctrls() or somesuch that will, after sending any |
1871 | | * responses, iterate through the list and "free" any ctrls whose |
1872 | | * ctrls_cb_retval is zero. This feature is used to handle things like |
1873 | | * shutdown requests in the future -- the request is only considered |
1874 | | * "processed" when the callback returns zero. Any non-zero requests are |
1875 | | * not cleared, and are considered "pending". However, this brings up the |
1876 | | * complication of an additional request for that action being issued by the |
1877 | | * client before the request is processed. Simplest solution: remove the |
1878 | | * old request args, and replace them with the new ones. |
1879 | | * |
1880 | | * This requires that the return value of the ctrl callback be explicitly |
1881 | | * documented. |
1882 | | * |
1883 | | * How about: ctrls_cb_retval = 1 pending |
1884 | | * 0 processed, OK (reset) |
1885 | | * -1 processed, error (reset) |
1886 | | */ |
1887 | |
|
1888 | 0 | for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) { |
1889 | 0 | if (ctrl->ctrls_cb_retval < PR_CTRLS_STATUS_PENDING) { |
1890 | 0 | pr_ctrls_free(ctrl); |
1891 | 0 | } |
1892 | 0 | } |
1893 | |
|
1894 | 0 | return 0; |
1895 | 0 | } |
1896 | | |
1897 | | /* From include/mod_ctrls.h */ |
1898 | | |
1899 | | /* Returns TRUE if the given cl_gid is allowed by the group ACL, FALSE |
1900 | | * otherwise. Note that the default is to deny everyone, unless an ACL has |
1901 | | * been configured. |
1902 | | */ |
1903 | 0 | int pr_ctrls_check_group_acl(gid_t cl_gid, const ctrls_group_acl_t *group_acl) { |
1904 | 0 | int res = FALSE; |
1905 | |
|
1906 | 0 | if (group_acl == NULL) { |
1907 | 0 | errno = EINVAL; |
1908 | 0 | return -1; |
1909 | 0 | } |
1910 | | |
1911 | | /* Note: the special condition of ngids of 1 and gids of NULL signals |
1912 | | * that all groups are to be treated according to the allow member. |
1913 | | */ |
1914 | 0 | if (group_acl->gids != NULL) { |
1915 | 0 | register unsigned int i = 0; |
1916 | |
|
1917 | 0 | for (i = 0; i < group_acl->ngids; i++) { |
1918 | 0 | if ((group_acl->gids)[i] == cl_gid) { |
1919 | 0 | res = TRUE; |
1920 | 0 | } |
1921 | 0 | } |
1922 | |
|
1923 | 0 | } else if (group_acl->ngids == 1) { |
1924 | 0 | res = TRUE; |
1925 | 0 | } |
1926 | |
|
1927 | 0 | if (!group_acl->allow) { |
1928 | 0 | res = !res; |
1929 | 0 | } |
1930 | |
|
1931 | 0 | return res; |
1932 | 0 | } |
1933 | | |
1934 | | /* Returns TRUE if the given cl_uid is allowed by the user ACL, FALSE |
1935 | | * otherwise. Note that the default is to deny everyone, unless an ACL has |
1936 | | * been configured. |
1937 | | */ |
1938 | 0 | int pr_ctrls_check_user_acl(uid_t cl_uid, const ctrls_user_acl_t *user_acl) { |
1939 | 0 | int res = FALSE; |
1940 | |
|
1941 | 0 | if (user_acl == NULL) { |
1942 | 0 | errno = EINVAL; |
1943 | 0 | return -1; |
1944 | 0 | } |
1945 | | |
1946 | | /* Note: the special condition of nuids of 1 and uids of NULL signals |
1947 | | * that all users are to be treated according to the allow member. |
1948 | | */ |
1949 | 0 | if (user_acl->uids != NULL) { |
1950 | 0 | register unsigned int i = 0; |
1951 | |
|
1952 | 0 | for (i = 0; i < user_acl->nuids; i++) { |
1953 | 0 | if ((user_acl->uids)[i] == cl_uid) { |
1954 | 0 | res = TRUE; |
1955 | 0 | } |
1956 | 0 | } |
1957 | |
|
1958 | 0 | } else if (user_acl->nuids == 1) { |
1959 | 0 | res = TRUE; |
1960 | 0 | } |
1961 | |
|
1962 | 0 | if (!user_acl->allow) { |
1963 | 0 | res = !res; |
1964 | 0 | } |
1965 | |
|
1966 | 0 | return res; |
1967 | 0 | } |
1968 | | |
1969 | | /* Returns TRUE for allowed, FALSE for denied. */ |
1970 | | int pr_ctrls_check_acl(const pr_ctrls_t *ctrl, |
1971 | 0 | const ctrls_acttab_t *acttab, const char *action) { |
1972 | 0 | register unsigned int i = 0; |
1973 | |
|
1974 | 0 | if (ctrl == NULL || |
1975 | 0 | ctrl->ctrls_cl == NULL || |
1976 | 0 | acttab == NULL || |
1977 | 0 | action == NULL) { |
1978 | 0 | errno = EINVAL; |
1979 | 0 | return -1; |
1980 | 0 | } |
1981 | | |
1982 | 0 | for (i = 0; acttab[i].act_action; i++) { |
1983 | 0 | if (strcmp(acttab[i].act_action, action) == 0) { |
1984 | 0 | int user_check = FALSE, group_check = FALSE; |
1985 | |
|
1986 | 0 | if (acttab[i].act_acl != NULL) { |
1987 | 0 | user_check = pr_ctrls_check_user_acl(ctrl->ctrls_cl->cl_uid, |
1988 | 0 | &(acttab[i].act_acl->acl_users)); |
1989 | 0 | group_check = pr_ctrls_check_group_acl(ctrl->ctrls_cl->cl_gid, |
1990 | 0 | &(acttab[i].act_acl->acl_groups)); |
1991 | 0 | } |
1992 | |
|
1993 | 0 | if (user_check != TRUE && |
1994 | 0 | group_check != TRUE) { |
1995 | 0 | return FALSE; |
1996 | 0 | } |
1997 | 0 | } |
1998 | 0 | } |
1999 | | |
2000 | 0 | return TRUE; |
2001 | 0 | } |
2002 | | |
2003 | 0 | int pr_ctrls_init_acl(ctrls_acl_t *acl) { |
2004 | 0 | if (acl == NULL) { |
2005 | 0 | errno = EINVAL; |
2006 | 0 | return -1; |
2007 | 0 | } |
2008 | | |
2009 | 0 | memset(acl, 0, sizeof(ctrls_acl_t)); |
2010 | 0 | acl->acl_users.allow = acl->acl_groups.allow = TRUE; |
2011 | |
|
2012 | 0 | return 0; |
2013 | 0 | } |
2014 | | |
2015 | 0 | static char *ctrls_argsep(char **arg) { |
2016 | 0 | char *ret = NULL, *dst = NULL; |
2017 | 0 | char quote_mode = 0; |
2018 | |
|
2019 | 0 | if (arg == NULL || |
2020 | 0 | !*arg || |
2021 | 0 | !**arg) { |
2022 | 0 | errno = EINVAL; |
2023 | 0 | return NULL; |
2024 | 0 | } |
2025 | | |
2026 | 0 | while (**arg && |
2027 | 0 | PR_ISSPACE(**arg)) { |
2028 | 0 | (*arg)++; |
2029 | 0 | } |
2030 | |
|
2031 | 0 | if (!**arg) { |
2032 | 0 | return NULL; |
2033 | 0 | } |
2034 | | |
2035 | 0 | ret = dst = *arg; |
2036 | |
|
2037 | 0 | if (**arg == '\"') { |
2038 | 0 | quote_mode++; |
2039 | 0 | (*arg)++; |
2040 | 0 | } |
2041 | |
|
2042 | 0 | while (**arg && **arg != ',' && |
2043 | 0 | (quote_mode ? (**arg != '\"') : (!PR_ISSPACE(**arg)))) { |
2044 | |
|
2045 | 0 | if (**arg == '\\' && quote_mode) { |
2046 | | /* escaped char */ |
2047 | 0 | if (*((*arg) + 1)) { |
2048 | 0 | *dst = *(++(*arg)); |
2049 | 0 | } |
2050 | 0 | } |
2051 | |
|
2052 | 0 | *dst++ = **arg; |
2053 | 0 | ++(*arg); |
2054 | 0 | } |
2055 | |
|
2056 | 0 | if (**arg) { |
2057 | 0 | (*arg)++; |
2058 | 0 | } |
2059 | |
|
2060 | 0 | *dst = '\0'; |
2061 | 0 | return ret; |
2062 | 0 | } |
2063 | | |
2064 | 0 | char **pr_ctrls_parse_acl(pool *acl_pool, const char *acl_text) { |
2065 | 0 | char *name = NULL, *acl_text_dup = NULL, **acl_list = NULL; |
2066 | 0 | array_header *acl_arr = NULL; |
2067 | 0 | pool *tmp_pool = NULL; |
2068 | |
|
2069 | 0 | if (acl_pool == NULL || |
2070 | 0 | acl_text == NULL) { |
2071 | 0 | errno = EINVAL; |
2072 | 0 | return NULL; |
2073 | 0 | } |
2074 | | |
2075 | 0 | tmp_pool = make_sub_pool(acl_pool); |
2076 | 0 | acl_text_dup = pstrdup(tmp_pool, acl_text); |
2077 | | |
2078 | | /* Allocate an array */ |
2079 | 0 | acl_arr = make_array(acl_pool, 0, sizeof(char **)); |
2080 | | |
2081 | | /* Add each name to the array */ |
2082 | 0 | while ((name = ctrls_argsep(&acl_text_dup)) != NULL) { |
2083 | 0 | char *text; |
2084 | |
|
2085 | 0 | text = pstrdup(acl_pool, name); |
2086 | | |
2087 | | /* Push the name into the ACL array */ |
2088 | 0 | *((char **) push_array(acl_arr)) = text; |
2089 | 0 | } |
2090 | | |
2091 | | /* Terminate the temp array with a NULL, as is proper. */ |
2092 | 0 | *((char **) push_array(acl_arr)) = NULL; |
2093 | |
|
2094 | 0 | acl_list = (char **) acl_arr->elts; |
2095 | 0 | destroy_pool(tmp_pool); |
2096 | | |
2097 | | /* return the array of names */ |
2098 | 0 | return acl_list; |
2099 | 0 | } |
2100 | | |
2101 | | int pr_ctrls_set_group_acl(pool *group_acl_pool, ctrls_group_acl_t *group_acl, |
2102 | 0 | const char *allow, char *grouplist) { |
2103 | 0 | char *group = NULL, **groups = NULL; |
2104 | 0 | array_header *gid_list = NULL; |
2105 | 0 | gid_t gid = 0; |
2106 | 0 | pool *tmp_pool = NULL; |
2107 | |
|
2108 | 0 | if (group_acl_pool == NULL || |
2109 | 0 | group_acl == NULL || |
2110 | 0 | allow == NULL || |
2111 | 0 | grouplist == NULL) { |
2112 | 0 | errno = EINVAL; |
2113 | 0 | return -1; |
2114 | 0 | } |
2115 | | |
2116 | 0 | tmp_pool = make_sub_pool(group_acl_pool); |
2117 | |
|
2118 | 0 | if (strcasecmp(allow, "allow") == 0) { |
2119 | 0 | group_acl->allow = TRUE; |
2120 | |
|
2121 | 0 | } else { |
2122 | 0 | group_acl->allow = FALSE; |
2123 | 0 | } |
2124 | | |
2125 | | /* Parse the given expression into an array, then retrieve the GID |
2126 | | * for each given name. |
2127 | | */ |
2128 | 0 | groups = pr_ctrls_parse_acl(group_acl_pool, grouplist); |
2129 | | |
2130 | | /* Allocate an array of gid_t's */ |
2131 | 0 | gid_list = make_array(group_acl_pool, 0, sizeof(gid_t)); |
2132 | |
|
2133 | 0 | for (group = *groups; group != NULL; group = *++groups) { |
2134 | | |
2135 | | /* Handle a group name of "*" differently. */ |
2136 | 0 | if (strcmp(group, "*") == 0) { |
2137 | 0 | group_acl->ngids = 1; |
2138 | 0 | group_acl->gids = NULL; |
2139 | 0 | destroy_pool(tmp_pool); |
2140 | 0 | return 0; |
2141 | 0 | } |
2142 | | |
2143 | 0 | gid = pr_auth_name2gid(tmp_pool, group); |
2144 | 0 | if (gid == (gid_t) -1) { |
2145 | 0 | continue; |
2146 | 0 | } |
2147 | | |
2148 | 0 | *((gid_t *) push_array(gid_list)) = gid; |
2149 | 0 | } |
2150 | | |
2151 | 0 | group_acl->ngids = gid_list->nelts; |
2152 | 0 | group_acl->gids = (gid_t *) gid_list->elts; |
2153 | |
|
2154 | 0 | destroy_pool(tmp_pool); |
2155 | 0 | return 0; |
2156 | 0 | } |
2157 | | |
2158 | | int pr_ctrls_set_user_acl(pool *user_acl_pool, ctrls_user_acl_t *user_acl, |
2159 | 0 | const char *allow, char *userlist) { |
2160 | 0 | char *user = NULL, **users = NULL; |
2161 | 0 | array_header *uid_list = NULL; |
2162 | 0 | uid_t uid = 0; |
2163 | 0 | pool *tmp_pool = NULL; |
2164 | | |
2165 | | /* Sanity checks */ |
2166 | 0 | if (user_acl_pool == NULL || |
2167 | 0 | user_acl == NULL || |
2168 | 0 | allow == NULL || |
2169 | 0 | userlist == NULL) { |
2170 | 0 | errno = EINVAL; |
2171 | 0 | return -1; |
2172 | 0 | } |
2173 | | |
2174 | 0 | tmp_pool = make_sub_pool(user_acl_pool); |
2175 | |
|
2176 | 0 | if (strcasecmp(allow, "allow") == 0) { |
2177 | 0 | user_acl->allow = TRUE; |
2178 | |
|
2179 | 0 | } else { |
2180 | 0 | user_acl->allow = FALSE; |
2181 | 0 | } |
2182 | | |
2183 | | /* Parse the given expression into an array, then retrieve the UID |
2184 | | * for each given name. |
2185 | | */ |
2186 | 0 | users = pr_ctrls_parse_acl(user_acl_pool, userlist); |
2187 | | |
2188 | | /* Allocate an array of uid_t's */ |
2189 | 0 | uid_list = make_array(user_acl_pool, 0, sizeof(uid_t)); |
2190 | |
|
2191 | 0 | for (user = *users; user != NULL; user = *++users) { |
2192 | | |
2193 | | /* Handle a user name of "*" differently. */ |
2194 | 0 | if (strcmp(user, "*") == 0) { |
2195 | 0 | user_acl->nuids = 1; |
2196 | 0 | user_acl->uids = NULL; |
2197 | 0 | destroy_pool(tmp_pool); |
2198 | 0 | return 0; |
2199 | 0 | } |
2200 | | |
2201 | 0 | uid = pr_auth_name2uid(tmp_pool, user); |
2202 | 0 | if (uid == (uid_t) -1) { |
2203 | 0 | continue; |
2204 | 0 | } |
2205 | | |
2206 | 0 | *((uid_t *) push_array(uid_list)) = uid; |
2207 | 0 | } |
2208 | | |
2209 | 0 | user_acl->nuids = uid_list->nelts; |
2210 | 0 | user_acl->uids = (uid_t *) uid_list->elts; |
2211 | |
|
2212 | 0 | destroy_pool(tmp_pool); |
2213 | 0 | return 0; |
2214 | 0 | } |
2215 | | |
2216 | | int pr_ctrls_set_module_acls2(ctrls_acttab_t *acttab, pool *acl_pool, |
2217 | | char **actions, const char *allow, const char *type, char *list, |
2218 | 0 | const char **bad_action) { |
2219 | 0 | register unsigned int i = 0; |
2220 | 0 | int all_actions = FALSE; |
2221 | |
|
2222 | 0 | if (acttab == NULL || |
2223 | 0 | acl_pool == NULL || |
2224 | 0 | actions == NULL || |
2225 | 0 | type == NULL || |
2226 | 0 | bad_action == NULL) { |
2227 | 0 | errno = EINVAL; |
2228 | 0 | return -1; |
2229 | 0 | } |
2230 | | |
2231 | 0 | if (strcasecmp(type, "user") != 0 && |
2232 | 0 | strcasecmp(type, "group") != 0) { |
2233 | 0 | errno = EINVAL; |
2234 | 0 | return -1; |
2235 | 0 | } |
2236 | | |
2237 | | /* First, sanity check the given list of actions against the actions |
2238 | | * in the given table. |
2239 | | */ |
2240 | 0 | for (i = 0; actions[i]; i++) { |
2241 | 0 | register unsigned int j = 0; |
2242 | 0 | int valid_action = FALSE; |
2243 | |
|
2244 | 0 | if (strcasecmp(actions[i], "all") == 0) { |
2245 | 0 | continue; |
2246 | 0 | } |
2247 | | |
2248 | 0 | for (j = 0; acttab[j].act_action; j++) { |
2249 | 0 | if (strcmp(actions[i], acttab[j].act_action) == 0) { |
2250 | 0 | valid_action = TRUE; |
2251 | 0 | break; |
2252 | 0 | } |
2253 | 0 | } |
2254 | |
|
2255 | 0 | if (valid_action == FALSE) { |
2256 | 0 | *bad_action = actions[i]; |
2257 | 0 | errno = EPERM; |
2258 | 0 | return -1; |
2259 | 0 | } |
2260 | 0 | } |
2261 | | |
2262 | 0 | for (i = 0; actions[i]; i++) { |
2263 | 0 | register unsigned int j = 0; |
2264 | |
|
2265 | 0 | if (all_actions == FALSE && |
2266 | 0 | strcasecmp(actions[i], "all") == 0) { |
2267 | 0 | all_actions = TRUE; |
2268 | 0 | } |
2269 | |
|
2270 | 0 | for (j = 0; acttab[j].act_action; j++) { |
2271 | 0 | int res = 0; |
2272 | |
|
2273 | 0 | if (all_actions == TRUE || |
2274 | 0 | strcmp(actions[i], acttab[j].act_action) == 0) { |
2275 | | |
2276 | | /* Use the type parameter to determine whether the list is of users or |
2277 | | * of groups. |
2278 | | */ |
2279 | 0 | if (strcasecmp(type, "user") == 0) { |
2280 | 0 | res = pr_ctrls_set_user_acl(acl_pool, |
2281 | 0 | &(acttab[j].act_acl->acl_users), allow, list); |
2282 | |
|
2283 | 0 | } else if (strcasecmp(type, "group") == 0) { |
2284 | 0 | res = pr_ctrls_set_group_acl(acl_pool, |
2285 | 0 | &(acttab[j].act_acl->acl_groups), allow, list); |
2286 | 0 | } |
2287 | |
|
2288 | 0 | if (res < 0) { |
2289 | 0 | *bad_action = actions[i]; |
2290 | 0 | return -1; |
2291 | 0 | } |
2292 | 0 | } |
2293 | 0 | } |
2294 | 0 | } |
2295 | | |
2296 | 0 | return 0; |
2297 | 0 | } |
2298 | | |
2299 | | char *pr_ctrls_set_module_acls(ctrls_acttab_t *acttab, pool *acl_pool, |
2300 | 0 | char **actions, const char *allow, const char *type, char *list) { |
2301 | 0 | int res; |
2302 | 0 | char *bad_action = NULL; |
2303 | |
|
2304 | 0 | res = pr_ctrls_set_module_acls2(acttab, acl_pool, actions, allow, type, list, |
2305 | 0 | (const char **) &bad_action); |
2306 | 0 | if (res < 0) { |
2307 | 0 | return bad_action; |
2308 | 0 | } |
2309 | | |
2310 | 0 | return 0; |
2311 | 0 | } |
2312 | | |
2313 | | int pr_ctrls_unregister_module_actions2(ctrls_acttab_t *acttab, |
2314 | 0 | char **actions, module *mod, const char **bad_action) { |
2315 | 0 | register unsigned int i = 0; |
2316 | |
|
2317 | 0 | if (acttab == NULL || |
2318 | 0 | actions == NULL || |
2319 | 0 | mod == NULL || |
2320 | 0 | bad_action == NULL) { |
2321 | 0 | errno = EINVAL; |
2322 | 0 | return -1; |
2323 | 0 | } |
2324 | | |
2325 | | /* First, sanity check the given actions against the actions supported by |
2326 | | * this module. |
2327 | | */ |
2328 | 0 | for (i = 0; actions[i]; i++) { |
2329 | 0 | register unsigned int j = 0; |
2330 | 0 | int valid_action = FALSE; |
2331 | |
|
2332 | 0 | for (j = 0; acttab[j].act_action; j++) { |
2333 | 0 | if (strcmp(actions[i], acttab[j].act_action) == 0) { |
2334 | 0 | valid_action = TRUE; |
2335 | 0 | break; |
2336 | 0 | } |
2337 | 0 | } |
2338 | |
|
2339 | 0 | if (valid_action == FALSE) { |
2340 | 0 | *bad_action = actions[i]; |
2341 | 0 | errno = EPERM; |
2342 | 0 | return -1; |
2343 | 0 | } |
2344 | 0 | } |
2345 | | |
2346 | | /* Next, iterate through both lists again, looking for actions of the |
2347 | | * module _not_ in the given list. |
2348 | | */ |
2349 | 0 | for (i = 0; acttab[i].act_action; i++) { |
2350 | 0 | register unsigned int j = 0; |
2351 | 0 | int have_action = FALSE; |
2352 | |
|
2353 | 0 | for (j = 0; actions[j]; j++) { |
2354 | 0 | if (strcmp(acttab[i].act_action, actions[j]) == 0) { |
2355 | 0 | have_action = TRUE; |
2356 | 0 | break; |
2357 | 0 | } |
2358 | 0 | } |
2359 | |
|
2360 | 0 | if (have_action == TRUE) { |
2361 | 0 | pr_trace_msg(trace_channel, 4, "mod_%s.c: removing '%s' control", |
2362 | 0 | mod->name, acttab[i].act_action); |
2363 | 0 | pr_ctrls_unregister(mod, acttab[i].act_action); |
2364 | 0 | destroy_pool(acttab[i].act_acl->acl_pool); |
2365 | 0 | } |
2366 | 0 | } |
2367 | |
|
2368 | 0 | return 0; |
2369 | 0 | } |
2370 | | |
2371 | | char *pr_ctrls_unregister_module_actions(ctrls_acttab_t *acttab, |
2372 | 0 | char **actions, module *mod) { |
2373 | 0 | int res; |
2374 | 0 | char *bad_action = NULL; |
2375 | |
|
2376 | 0 | res = pr_ctrls_unregister_module_actions2(acttab, actions, mod, |
2377 | 0 | (const char **) &bad_action); |
2378 | 0 | if (res < 0) { |
2379 | 0 | return bad_action; |
2380 | 0 | } |
2381 | | |
2382 | 0 | return 0; |
2383 | 0 | } |
2384 | | |
2385 | 0 | int pr_ctrls_set_logfd(int fd) { |
2386 | | |
2387 | | /* Close any existing log fd. */ |
2388 | 0 | if (ctrls_logfd >= 0) { |
2389 | 0 | (void) close(ctrls_logfd); |
2390 | 0 | } |
2391 | |
|
2392 | 0 | ctrls_logfd = fd; |
2393 | 0 | return 0; |
2394 | 0 | } |
2395 | | |
2396 | 0 | int pr_ctrls_log(const char *module_version, const char *fmt, ...) { |
2397 | 0 | va_list msg; |
2398 | 0 | int res; |
2399 | |
|
2400 | 0 | if (ctrls_logfd < 0) { |
2401 | 0 | return 0; |
2402 | 0 | } |
2403 | | |
2404 | 0 | if (fmt == NULL) { |
2405 | 0 | return 0; |
2406 | 0 | } |
2407 | | |
2408 | 0 | va_start(msg, fmt); |
2409 | 0 | res = pr_log_vwritefile(ctrls_logfd, module_version, fmt, msg); |
2410 | 0 | va_end(msg); |
2411 | |
|
2412 | 0 | return res; |
2413 | 0 | } |
2414 | | |
2415 | 0 | static void ctrls_cleanup_cb(void *user_data) { |
2416 | 0 | ctrls_pool = NULL; |
2417 | 0 | ctrls_action_list = NULL; |
2418 | 0 | ctrls_active_list = NULL; |
2419 | 0 | ctrls_free_list = NULL; |
2420 | |
|
2421 | 0 | action_lookup_next = NULL; |
2422 | 0 | action_lookup_action = NULL; |
2423 | 0 | action_lookup_module = NULL; |
2424 | 0 | } |
2425 | | |
2426 | | /* Initialize the Controls API. */ |
2427 | 0 | int init_ctrls2(const char *socket_path) { |
2428 | 0 | struct stat st; |
2429 | 0 | int fd, xerrno; |
2430 | 0 | struct sockaddr_un sockun; |
2431 | 0 | size_t socklen; |
2432 | |
|
2433 | 0 | if (ctrls_pool != NULL) { |
2434 | 0 | destroy_pool(ctrls_pool); |
2435 | 0 | } |
2436 | |
|
2437 | 0 | ctrls_pool = make_sub_pool(permanent_pool); |
2438 | 0 | pr_pool_tag(ctrls_pool, "Controls Pool"); |
2439 | 0 | register_cleanup2(ctrls_pool, NULL, ctrls_cleanup_cb); |
2440 | | |
2441 | | /* Make sure all of the lists are zero'd out. */ |
2442 | 0 | ctrls_action_list = NULL; |
2443 | 0 | ctrls_active_list = NULL; |
2444 | 0 | ctrls_free_list = NULL; |
2445 | | |
2446 | | /* And that the lookup indices are (re)set as well... */ |
2447 | 0 | action_lookup_next = NULL; |
2448 | 0 | action_lookup_action = NULL; |
2449 | 0 | action_lookup_module = NULL; |
2450 | | |
2451 | | /* Run-time check to find out whether this platform identifies a |
2452 | | * Unix domain socket file descriptor via the S_ISFIFO macro, or |
2453 | | * the S_ISSOCK macro. |
2454 | | */ |
2455 | |
|
2456 | 0 | fd = socket(AF_UNIX, SOCK_STREAM, 0); |
2457 | 0 | xerrno = errno; |
2458 | |
|
2459 | 0 | if (fd < 0) { |
2460 | 0 | pr_log_debug(DEBUG10, "unable to create Unix domain socket: %s", |
2461 | 0 | strerror(xerrno)); |
2462 | |
|
2463 | 0 | errno = xerrno; |
2464 | 0 | return -1; |
2465 | 0 | } |
2466 | | |
2467 | 0 | memset(&sockun, 0, sizeof(sockun)); |
2468 | 0 | sockun.sun_family = AF_UNIX; |
2469 | 0 | sstrncpy(sockun.sun_path, socket_path, sizeof(sockun.sun_path)); |
2470 | 0 | socklen = sizeof(struct sockaddr_un); |
2471 | |
|
2472 | 0 | if (bind(fd, (struct sockaddr *) &sockun, socklen) < 0) { |
2473 | 0 | xerrno = errno; |
2474 | |
|
2475 | 0 | pr_log_debug(DEBUG10, "unable to bind to Unix domain socket at '%s': %s", |
2476 | 0 | socket_path, strerror(xerrno)); |
2477 | 0 | (void) close(fd); |
2478 | 0 | (void) unlink(socket_path); |
2479 | |
|
2480 | 0 | errno = xerrno; |
2481 | 0 | return -1; |
2482 | 0 | } |
2483 | | |
2484 | 0 | if (fstat(fd, &st) < 0) { |
2485 | 0 | xerrno = errno; |
2486 | |
|
2487 | 0 | pr_log_debug(DEBUG10, "unable to stat Unix domain socket at '%s': %s", |
2488 | 0 | socket_path, strerror(xerrno)); |
2489 | 0 | (void) close(fd); |
2490 | 0 | (void) unlink(socket_path); |
2491 | |
|
2492 | 0 | errno = xerrno; |
2493 | 0 | return -1; |
2494 | 0 | } |
2495 | | |
2496 | 0 | #if defined(S_ISFIFO) |
2497 | 0 | pr_trace_msg(trace_channel, 9, "testing Unix domain socket using S_ISFIFO"); |
2498 | 0 | if (S_ISFIFO(st.st_mode)) { |
2499 | 0 | ctrls_use_isfifo = TRUE; |
2500 | 0 | } |
2501 | | #else |
2502 | | pr_log_debug(DEBUG10, "cannot test Unix domain socket using S_ISFIFO: " |
2503 | | "macro undefined"); |
2504 | | #endif /* S_ISFIFO */ |
2505 | |
|
2506 | 0 | #if defined(S_ISSOCK) |
2507 | 0 | pr_trace_msg(trace_channel, 9, "testing Unix domain socket using S_ISSOCK"); |
2508 | 0 | if (S_ISSOCK(st.st_mode)) { |
2509 | 0 | ctrls_use_isfifo = FALSE; |
2510 | 0 | } |
2511 | | #else |
2512 | | pr_log_debug(DEBUG10, "cannot test Unix domain socket using S_ISSOCK: " |
2513 | | "macro undefined"); |
2514 | | #endif /* S_ISSOCK */ |
2515 | |
|
2516 | 0 | pr_trace_msg(trace_channel, 9, |
2517 | 0 | "using %s macro for Unix domain socket detection", |
2518 | 0 | ctrls_use_isfifo ? "S_ISFIFO" : "S_ISSOCK"); |
2519 | |
|
2520 | 0 | (void) close(fd); |
2521 | 0 | (void) unlink(socket_path); |
2522 | 0 | return 0; |
2523 | 0 | } |
2524 | | |
2525 | 0 | void init_ctrls(void) { |
2526 | 0 | const char *socket_path = PR_RUN_DIR "/test.sock"; |
2527 | |
|
2528 | 0 | (void) init_ctrls2(socket_path); |
2529 | 0 | } |
2530 | | #endif /* PR_USE_CTRLS */ |