1 // SPDX-License-Identifier: ISC OR MIT
3 * rpcd - UBUS RPC server
5 * Copyright (C) 2020 Rafał Miłecki <rafal@milecki.pl>
10 #include <linux/limits.h>
14 #include <libubox/blobmsg.h>
15 #include <libubox/ulog.h>
16 #include <libubox/uloop.h>
21 #define RC_LIST_EXEC_TIMEOUT_MS 3000
25 RC_LIST_SKIP_RUNNING_CHECK
,
29 static const struct blobmsg_policy rc_list_policy
[] = {
30 [RC_LIST_NAME
] = { "name", BLOBMSG_TYPE_STRING
},
31 [RC_LIST_SKIP_RUNNING_CHECK
] = { "skip_running_check", BLOBMSG_TYPE_BOOL
},
40 static const struct blobmsg_policy rc_init_policy
[] = {
41 [RC_INIT_NAME
] = { "name", BLOBMSG_TYPE_STRING
},
42 [RC_INIT_ACTION
] = { "action", BLOBMSG_TYPE_STRING
},
45 struct rc_list_context
{
46 struct uloop_process process
;
47 struct uloop_timeout timeout
;
48 struct ubus_context
*ctx
;
49 struct ubus_request_data req
;
52 bool skip_running_check
;
55 /* Info about currently processed init.d entry */
67 static void rc_list_readdir(struct rc_list_context
*c
);
70 * rc_check_script - check if script is safe to execute as root
72 * Check if it's owned by root and if only root can modify it.
74 static int rc_check_script(const char *path
)
79 return UBUS_STATUS_NOT_FOUND
;
81 if (s
.st_uid
!= 0 || s
.st_gid
!= 0 || !(s
.st_mode
& S_IXUSR
) || (s
.st_mode
& S_IWOTH
))
82 return UBUS_STATUS_PERMISSION_DENIED
;
84 return UBUS_STATUS_OK
;
87 static void rc_list_add_table(struct rc_list_context
*c
)
91 e
= blobmsg_open_table(c
->buf
, c
->entry
.d_name
);
93 if (c
->entry
.start
>= 0)
94 blobmsg_add_u16(c
->buf
, "start", c
->entry
.start
);
95 if (c
->entry
.stop
>= 0)
96 blobmsg_add_u16(c
->buf
, "stop", c
->entry
.stop
);
97 blobmsg_add_u8(c
->buf
, "enabled", c
->entry
.enabled
);
98 if (!c
->skip_running_check
&& c
->entry
.use_procd
)
99 blobmsg_add_u8(c
->buf
, "running", c
->entry
.running
);
101 blobmsg_close_table(c
->buf
, e
);
104 static void rpc_list_exec_timeout_cb(struct uloop_timeout
*t
)
106 struct rc_list_context
*c
= container_of(t
, struct rc_list_context
, timeout
);
108 ULOG_WARN("Timeout waiting for %s\n", c
->entry
.path
);
110 uloop_process_delete(&c
->process
);
111 kill(c
->process
.pid
, SIGKILL
);
117 * rc_exec - execute a file and call callback on complete
119 static int rc_list_exec(struct rc_list_context
*c
, const char *action
, uloop_process_handler cb
)
130 if (c
->skip_running_check
)
133 if (!c
->entry
.use_procd
)
136 /* Set stdin, stdout & stderr to /dev/null */
137 fd
= open("/dev/null", O_RDWR
);
148 execl(c
->entry
.path
, c
->entry
.path
, action
, NULL
);
151 c
->process
.pid
= pid
;
154 err
= uloop_process_add(&c
->process
);
158 c
->timeout
.cb
= rpc_list_exec_timeout_cb
;
159 err
= uloop_timeout_set(&c
->timeout
, RC_LIST_EXEC_TIMEOUT_MS
);
161 uloop_process_delete(&c
->process
);
169 static void rc_list_exec_running_cb(struct uloop_process
*p
, int stat
)
171 struct rc_list_context
*c
= container_of(p
, struct rc_list_context
, process
);
173 uloop_timeout_cancel(&c
->timeout
);
175 c
->entry
.running
= !stat
;
176 rc_list_add_table(c
);
181 static void rc_list_readdir(struct rc_list_context
*c
)
188 * If scanning for a specific script and entry.d_name is set
189 * we can assume we found a matching one in the previous
190 * iteration since entry.d_name is set only if a match is found.
192 if (!e
|| (c
->req_name
&& c
->entry
.d_name
)) {
194 ubus_send_reply(c
->ctx
, &c
->req
, c
->buf
->head
);
195 ubus_complete_deferred_request(c
->ctx
, &c
->req
, UBUS_STATUS_OK
);
199 if (!strcmp(e
->d_name
, ".") || !strcmp(e
->d_name
, ".."))
202 if (c
->req_name
&& strcmp(e
->d_name
, c
->req_name
))
205 memset(&c
->entry
, 0, sizeof(c
->entry
));
209 snprintf(c
->entry
.path
, sizeof(c
->entry
.path
), "/etc/init.d/%s", e
->d_name
);
210 if (rc_check_script(c
->entry
.path
))
213 c
->entry
.d_name
= e
->d_name
;
215 fp
= fopen(c
->entry
.path
, "r");
224 while ((c
->entry
.start
< 0 || c
->entry
.stop
< 0 ||
225 (!c
->skip_running_check
&& !c
->entry
.use_procd
)) &&
226 count
<= 10 && fgets(line
, sizeof(line
), fp
)) {
228 if (!strncmp(line
, "START=", 6)) {
229 c
->entry
.start
= strtoul(line
+ 6, NULL
, 0);
230 } else if (!strncmp(line
, "STOP=", 5)) {
231 c
->entry
.stop
= strtoul(line
+ 5, NULL
, 0);
232 } else if (!c
->skip_running_check
&& !strncmp(line
, "USE_PROCD=", 10)) {
233 c
->entry
.use_procd
= !!strtoul(line
+ 10, NULL
, 0);
238 beginning
= !!strchr(line
, '\n');
242 if (c
->entry
.start
>= 0) {
243 snprintf(path
, sizeof(path
), "/etc/rc.d/S%02d%s", c
->entry
.start
, c
->entry
.d_name
);
244 if (!stat(path
, &s
) && (s
.st_mode
& S_IXUSR
))
245 c
->entry
.enabled
= true;
249 if (rc_list_exec(c
, "running", rc_list_exec_running_cb
))
258 * rc_list - allocate listing context and start reading directory
260 static int rc_list(struct ubus_context
*ctx
, struct ubus_object
*obj
,
261 struct ubus_request_data
*req
, const char *method
,
262 struct blob_attr
*msg
)
264 struct blob_attr
*tb
[__RC_LIST_MAX
];
265 static struct blob_buf buf
;
266 struct rc_list_context
*c
;
268 blobmsg_parse(rc_list_policy
, __RC_LIST_MAX
, tb
, blobmsg_data(msg
), blobmsg_data_len(msg
));
270 blob_buf_init(&buf
, 0);
272 c
= calloc(1, sizeof(*c
));
274 return UBUS_STATUS_UNKNOWN_ERROR
;
278 c
->dir
= opendir("/etc/init.d");
281 return UBUS_STATUS_UNKNOWN_ERROR
;
283 if (tb
[RC_LIST_SKIP_RUNNING_CHECK
])
284 c
->skip_running_check
= blobmsg_get_bool(tb
[RC_LIST_SKIP_RUNNING_CHECK
]);
285 if (tb
[RC_LIST_NAME
])
286 c
->req_name
= blobmsg_get_string(tb
[RC_LIST_NAME
]);
288 ubus_defer_request(ctx
, req
, &c
->req
);
292 return 0; /* Deferred */
295 struct rc_init_context
{
296 struct uloop_process process
;
297 struct ubus_context
*ctx
;
298 struct ubus_request_data req
;
301 static void rc_init_cb(struct uloop_process
*p
, int stat
)
303 struct rc_init_context
*c
= container_of(p
, struct rc_init_context
, process
);
305 ubus_complete_deferred_request(c
->ctx
, &c
->req
, UBUS_STATUS_OK
);
310 static int rc_init(struct ubus_context
*ctx
, struct ubus_object
*obj
,
311 struct ubus_request_data
*req
, const char *method
,
312 struct blob_attr
*msg
)
314 struct blob_attr
*tb
[__RC_INIT_MAX
];
315 struct rc_init_context
*c
;
324 blobmsg_parse(rc_init_policy
, __RC_INIT_MAX
, tb
, blobmsg_data(msg
), blobmsg_data_len(msg
));
326 if (!tb
[RC_INIT_NAME
] || !tb
[RC_INIT_ACTION
])
327 return UBUS_STATUS_INVALID_ARGUMENT
;
329 name
= blobmsg_get_string(tb
[RC_INIT_NAME
]);
331 /* Validate script name */
332 for (chr
= name
; (chr
= strchr(chr
, '.')); chr
++) {
333 if (*(chr
+ 1) == '.')
334 return UBUS_STATUS_INVALID_ARGUMENT
;
336 if (strchr(name
, '/'))
337 return UBUS_STATUS_INVALID_ARGUMENT
;
339 snprintf(path
, sizeof(path
), "/etc/init.d/%s", name
);
341 /* Validate script privileges */
342 err
= rc_check_script(path
);
346 action
= blobmsg_get_string(tb
[RC_INIT_ACTION
]);
347 if (strcmp(action
, "disable") &&
348 strcmp(action
, "enable") &&
349 strcmp(action
, "stop") &&
350 strcmp(action
, "start") &&
351 strcmp(action
, "restart") &&
352 strcmp(action
, "reload"))
353 return UBUS_STATUS_INVALID_ARGUMENT
;
355 c
= calloc(1, sizeof(*c
));
357 return UBUS_STATUS_UNKNOWN_ERROR
;
363 return UBUS_STATUS_UNKNOWN_ERROR
;
365 /* Set stdin, stdout & stderr to /dev/null */
366 fd
= open("/dev/null", O_RDWR
);
377 execl(path
, path
, action
, NULL
);
381 c
->process
.pid
= pid
;
382 c
->process
.cb
= rc_init_cb
;
383 uloop_process_add(&c
->process
);
385 ubus_defer_request(ctx
, req
, &c
->req
);
387 return 0; /* Deferred */
391 int rpc_rc_api_init(struct ubus_context
*ctx
)
393 static const struct ubus_method rc_methods
[] = {
394 UBUS_METHOD("list", rc_list
, rc_list_policy
),
395 UBUS_METHOD("init", rc_init
, rc_init_policy
),
398 static struct ubus_object_type rc_type
=
399 UBUS_OBJECT_TYPE("rc", rc_methods
);
401 static struct ubus_object obj
= {
404 .methods
= rc_methods
,
405 .n_methods
= ARRAY_SIZE(rc_methods
),
408 return ubus_add_object(ctx
, &obj
);