sys: use "Auto-Installed" field for packagelist
[project/rpcd.git] / rc.c
1 // SPDX-License-Identifier: ISC OR MIT
2 /*
3 * rpcd - UBUS RPC server
4 *
5 * Copyright (C) 2020 Rafał Miłecki <rafal@milecki.pl>
6 */
7
8 #include <dirent.h>
9 #include <fcntl.h>
10 #include <linux/limits.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13
14 #include <libubox/blobmsg.h>
15 #include <libubox/ulog.h>
16 #include <libubox/uloop.h>
17 #include <libubus.h>
18
19 #include <rpcd/rc.h>
20
21 #define RC_LIST_EXEC_TIMEOUT_MS 3000
22
23 enum {
24 RC_LIST_NAME,
25 RC_LIST_SKIP_RUNNING_CHECK,
26 __RC_LIST_MAX
27 };
28
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 },
32 };
33
34 enum {
35 RC_INIT_NAME,
36 RC_INIT_ACTION,
37 __RC_INIT_MAX
38 };
39
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 },
43 };
44
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;
50 struct blob_buf *buf;
51 DIR *dir;
52 bool skip_running_check;
53 const char *req_name;
54
55 /* Info about currently processed init.d entry */
56 struct {
57 char path[PATH_MAX];
58 const char *d_name;
59 int start;
60 int stop;
61 bool enabled;
62 bool running;
63 bool use_procd;
64 } entry;
65 };
66
67 static void rc_list_readdir(struct rc_list_context *c);
68
69 /**
70 * rc_check_script - check if script is safe to execute as root
71 *
72 * Check if it's owned by root and if only root can modify it.
73 */
74 static int rc_check_script(const char *path)
75 {
76 struct stat s;
77
78 if (stat(path, &s))
79 return UBUS_STATUS_NOT_FOUND;
80
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;
83
84 return UBUS_STATUS_OK;
85 }
86
87 static void rc_list_add_table(struct rc_list_context *c)
88 {
89 void *e;
90
91 e = blobmsg_open_table(c->buf, c->entry.d_name);
92
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);
100
101 blobmsg_close_table(c->buf, e);
102 }
103
104 static void rpc_list_exec_timeout_cb(struct uloop_timeout *t)
105 {
106 struct rc_list_context *c = container_of(t, struct rc_list_context, timeout);
107
108 ULOG_WARN("Timeout waiting for %s\n", c->entry.path);
109
110 uloop_process_delete(&c->process);
111 kill(c->process.pid, SIGKILL);
112
113 rc_list_readdir(c);
114 }
115
116 /**
117 * rc_exec - execute a file and call callback on complete
118 */
119 static int rc_list_exec(struct rc_list_context *c, const char *action, uloop_process_handler cb)
120 {
121 pid_t pid;
122 int err;
123 int fd;
124
125 pid = fork();
126 switch (pid) {
127 case -1:
128 return -errno;
129 case 0:
130 if (c->skip_running_check)
131 exit(-EFAULT);
132
133 if (!c->entry.use_procd)
134 exit(-EOPNOTSUPP);
135
136 /* Set stdin, stdout & stderr to /dev/null */
137 fd = open("/dev/null", O_RDWR);
138 if (fd >= 0) {
139 dup2(fd, 0);
140 dup2(fd, 1);
141 dup2(fd, 2);
142 if (fd > 2)
143 close(fd);
144 }
145
146 uloop_end();
147
148 execl(c->entry.path, c->entry.path, action, NULL);
149 exit(errno);
150 default:
151 c->process.pid = pid;
152 c->process.cb = cb;
153
154 err = uloop_process_add(&c->process);
155 if (err)
156 return err;
157
158 c->timeout.cb = rpc_list_exec_timeout_cb;
159 err = uloop_timeout_set(&c->timeout, RC_LIST_EXEC_TIMEOUT_MS);
160 if (err) {
161 uloop_process_delete(&c->process);
162 return err;
163 }
164
165 return 0;
166 }
167 }
168
169 static void rc_list_exec_running_cb(struct uloop_process *p, int stat)
170 {
171 struct rc_list_context *c = container_of(p, struct rc_list_context, process);
172
173 uloop_timeout_cancel(&c->timeout);
174
175 c->entry.running = !stat;
176 rc_list_add_table(c);
177
178 rc_list_readdir(c);
179 }
180
181 static void rc_list_readdir(struct rc_list_context *c)
182 {
183 struct dirent *e;
184 FILE *fp;
185
186 e = readdir(c->dir);
187 /*
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.
191 */
192 if (!e || (c->req_name && c->entry.d_name)) {
193 closedir(c->dir);
194 ubus_send_reply(c->ctx, &c->req, c->buf->head);
195 ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK);
196 return;
197 }
198
199 if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
200 goto next;
201
202 if (c->req_name && strcmp(e->d_name, c->req_name))
203 goto next;
204
205 memset(&c->entry, 0, sizeof(c->entry));
206 c->entry.start = -1;
207 c->entry.stop = -1;
208
209 snprintf(c->entry.path, sizeof(c->entry.path), "/etc/init.d/%s", e->d_name);
210 if (rc_check_script(c->entry.path))
211 goto next;
212
213 c->entry.d_name = e->d_name;
214
215 fp = fopen(c->entry.path, "r");
216 if (fp) {
217 struct stat s;
218 char path[PATH_MAX];
219 char line[255];
220 bool beginning;
221 int count = 0;
222
223 beginning = true;
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)) {
227 if (beginning) {
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);
234 }
235 count++;
236 }
237
238 beginning = !!strchr(line, '\n');
239 }
240 fclose(fp);
241
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;
246 }
247 }
248
249 if (rc_list_exec(c, "running", rc_list_exec_running_cb))
250 goto next;
251
252 return;
253 next:
254 rc_list_readdir(c);
255 }
256
257 /**
258 * rc_list - allocate listing context and start reading directory
259 */
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)
263 {
264 struct blob_attr *tb[__RC_LIST_MAX];
265 static struct blob_buf buf;
266 struct rc_list_context *c;
267
268 blobmsg_parse(rc_list_policy, __RC_LIST_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
269
270 blob_buf_init(&buf, 0);
271
272 c = calloc(1, sizeof(*c));
273 if (!c)
274 return UBUS_STATUS_UNKNOWN_ERROR;
275
276 c->ctx = ctx;
277 c->buf = &buf;
278 c->dir = opendir("/etc/init.d");
279 if (!c->dir) {
280 free(c);
281 return UBUS_STATUS_UNKNOWN_ERROR;
282 }
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]);
287
288 ubus_defer_request(ctx, req, &c->req);
289
290 rc_list_readdir(c);
291
292 return 0; /* Deferred */
293 }
294
295 struct rc_init_context {
296 struct uloop_process process;
297 struct ubus_context *ctx;
298 struct ubus_request_data req;
299 };
300
301 static void rc_init_cb(struct uloop_process *p, int stat)
302 {
303 struct rc_init_context *c = container_of(p, struct rc_init_context, process);
304
305 ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK);
306
307 free(c);
308 }
309
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)
313 {
314 struct blob_attr *tb[__RC_INIT_MAX];
315 struct rc_init_context *c;
316 char path[PATH_MAX];
317 const char *action;
318 const char *name;
319 const char *chr;
320 pid_t pid;
321 int err;
322 int fd;
323
324 blobmsg_parse(rc_init_policy, __RC_INIT_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
325
326 if (!tb[RC_INIT_NAME] || !tb[RC_INIT_ACTION])
327 return UBUS_STATUS_INVALID_ARGUMENT;
328
329 name = blobmsg_get_string(tb[RC_INIT_NAME]);
330
331 /* Validate script name */
332 for (chr = name; (chr = strchr(chr, '.')); chr++) {
333 if (*(chr + 1) == '.')
334 return UBUS_STATUS_INVALID_ARGUMENT;
335 }
336 if (strchr(name, '/'))
337 return UBUS_STATUS_INVALID_ARGUMENT;
338
339 snprintf(path, sizeof(path), "/etc/init.d/%s", name);
340
341 /* Validate script privileges */
342 err = rc_check_script(path);
343 if (err)
344 return err;
345
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;
354
355 c = calloc(1, sizeof(*c));
356 if (!c)
357 return UBUS_STATUS_UNKNOWN_ERROR;
358
359 pid = fork();
360 switch (pid) {
361 case -1:
362 free(c);
363 return UBUS_STATUS_UNKNOWN_ERROR;
364 case 0:
365 /* Set stdin, stdout & stderr to /dev/null */
366 fd = open("/dev/null", O_RDWR);
367 if (fd >= 0) {
368 dup2(fd, 0);
369 dup2(fd, 1);
370 dup2(fd, 2);
371 if (fd > 2)
372 close(fd);
373 }
374
375 uloop_end();
376
377 execl(path, path, action, NULL);
378 exit(errno);
379 default:
380 c->ctx = ctx;
381 c->process.pid = pid;
382 c->process.cb = rc_init_cb;
383 uloop_process_add(&c->process);
384
385 ubus_defer_request(ctx, req, &c->req);
386
387 return 0; /* Deferred */
388 }
389 }
390
391 int rpc_rc_api_init(struct ubus_context *ctx)
392 {
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),
396 };
397
398 static struct ubus_object_type rc_type =
399 UBUS_OBJECT_TYPE("rc", rc_methods);
400
401 static struct ubus_object obj = {
402 .name = "rc",
403 .type = &rc_type,
404 .methods = rc_methods,
405 .n_methods = ARRAY_SIZE(rc_methods),
406 };
407
408 return ubus_add_object(ctx, &obj);
409 }