• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/rpcd/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         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         /* uloop no longer tracks the process after uloop_process_delete(), so
113          * reap the killed child here to avoid leaving a zombie behind. */
114         waitpid(c->process.pid, NULL, 0);
115 
116         rc_list_readdir(c);
117 }
118 
119 /**
120  * rc_exec - execute a file and call callback on complete
121  */
122 static int rc_list_exec(struct rc_list_context *c, const char *action, uloop_process_handler cb)
123 {
124         pid_t pid;
125         int err;
126         int fd;
127 
128         pid = fork();
129         switch (pid) {
130         case -1:
131                 return -errno;
132         case 0:
133                 if (c->skip_running_check)
134                         exit(-EFAULT);
135 
136                 if (!c->entry.use_procd)
137                         exit(-EOPNOTSUPP);
138 
139                 /* Set stdin, stdout & stderr to /dev/null */
140                 fd = open("/dev/null", O_RDWR);
141                 if (fd >= 0) {
142                         dup2(fd, 0);
143                         dup2(fd, 1);
144                         dup2(fd, 2);
145                         if (fd > 2)
146                                 close(fd);
147                 }
148 
149                 uloop_end();
150 
151                 execl(c->entry.path, c->entry.path, action, NULL);
152                 exit(errno);
153         default:
154                 c->process.pid = pid;
155                 c->process.cb = cb;
156 
157                 err = uloop_process_add(&c->process);
158                 if (err)
159                         return err;
160 
161                 c->timeout.cb = rpc_list_exec_timeout_cb;
162                 err = uloop_timeout_set(&c->timeout, RC_LIST_EXEC_TIMEOUT_MS);
163                 if (err) {
164                         uloop_process_delete(&c->process);
165                         return err;
166                 }
167 
168                 return 0;
169         }
170 }
171 
172 static void rc_list_exec_running_cb(struct uloop_process *p, int stat)
173 {
174         struct rc_list_context *c = container_of(p, struct rc_list_context, process);
175 
176         uloop_timeout_cancel(&c->timeout);
177 
178         c->entry.running = !stat;
179         rc_list_add_table(c);
180 
181         rc_list_readdir(c);
182 }
183 
184 static void rc_list_readdir(struct rc_list_context *c)
185 {
186         struct dirent *e;
187         FILE *fp;
188 
189         e = readdir(c->dir);
190         /* 
191          * If scanning for a specific script and entry.d_name is set
192          * we can assume we found a matching one in the previous
193          * iteration since entry.d_name is set only if a match is found.
194          */
195         if (!e || (c->req_name && c->entry.d_name)) {
196                 closedir(c->dir);
197                 ubus_send_reply(c->ctx, &c->req, c->buf.head);
198                 ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK);
199                 blob_buf_free(&c->buf);
200                 free(c->req_name);
201                 free(c);
202                 return;
203         }
204 
205         if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
206                 goto next;
207 
208         if (c->req_name && strcmp(e->d_name, c->req_name))
209                 goto next;
210 
211         memset(&c->entry, 0, sizeof(c->entry));
212         c->entry.start = -1;
213         c->entry.stop = -1;
214 
215         snprintf(c->entry.path, sizeof(c->entry.path), "/etc/init.d/%s", e->d_name);
216         if (rc_check_script(c->entry.path))
217                 goto next;
218 
219         c->entry.d_name = e->d_name;
220 
221         fp = fopen(c->entry.path, "r");
222         if (fp) {
223                 struct stat s;
224                 char path[PATH_MAX];
225                 char line[255];
226                 bool beginning;
227                 int count = 0;
228 
229                 beginning = true;
230                 while ((c->entry.start < 0 || c->entry.stop < 0 ||
231                        (!c->skip_running_check && !c->entry.use_procd)) &&
232                        count <= 10 && fgets(line, sizeof(line), fp)) {
233                         if (beginning) {
234                                 if (!strncmp(line, "START=", 6)) {
235                                         c->entry.start = strtoul(line + 6, NULL, 0);
236                                 } else if (!strncmp(line, "STOP=", 5)) {
237                                         c->entry.stop = strtoul(line + 5, NULL, 0);
238                                 } else if (!c->skip_running_check && !strncmp(line, "USE_PROCD=", 10)) {
239                                         c->entry.use_procd = !!strtoul(line + 10, NULL, 0);
240                                 }
241                                 count++;
242                         }
243 
244                         beginning = !!strchr(line, '\n');
245                 }
246                 fclose(fp);
247 
248                 if (c->entry.start >= 0) {
249                         snprintf(path, sizeof(path), "/etc/rc.d/S%02d%s", c->entry.start, c->entry.d_name);
250                         if (!stat(path, &s) && (s.st_mode & S_IXUSR))
251                                 c->entry.enabled = true;
252                 }
253         }
254 
255         if (rc_list_exec(c, "running", rc_list_exec_running_cb))
256                 goto next;
257 
258         return;
259 next:
260         rc_list_readdir(c);
261 }
262 
263 /**
264  * rc_list - allocate listing context and start reading directory
265  */
266 static int rc_list(struct ubus_context *ctx, struct ubus_object *obj,
267                    struct ubus_request_data *req, const char *method,
268                    struct blob_attr *msg)
269 {
270         struct blob_attr *tb[__RC_LIST_MAX];
271         struct rc_list_context *c;
272 
273         blobmsg_parse(rc_list_policy, __RC_LIST_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
274 
275         c = calloc(1, sizeof(*c));
276         if (!c)
277                 return UBUS_STATUS_UNKNOWN_ERROR;
278 
279         c->ctx = ctx;
280         c->dir = opendir("/etc/init.d");
281         if (!c->dir) {
282                 free(c);
283                 return UBUS_STATUS_UNKNOWN_ERROR;
284         }
285 
286         blob_buf_init(&c->buf, 0);
287 
288         if (tb[RC_LIST_SKIP_RUNNING_CHECK])
289                 c->skip_running_check = blobmsg_get_bool(tb[RC_LIST_SKIP_RUNNING_CHECK]);
290         /* Copy the requested name: msg (and the ctx receive buffer it points
291          * into) is only valid during this call, but req_name is dereferenced
292          * later from the deferred, asynchronous directory walk. */
293         if (tb[RC_LIST_NAME])
294                 c->req_name = strdup(blobmsg_get_string(tb[RC_LIST_NAME]));
295 
296         ubus_defer_request(ctx, req, &c->req);
297 
298         rc_list_readdir(c);
299 
300         return 0; /* Deferred */
301 }
302 
303 struct rc_init_context {
304         struct uloop_process process;
305         struct ubus_context *ctx;
306         struct ubus_request_data req;
307 };
308 
309 static void rc_init_cb(struct uloop_process *p, int stat)
310 {
311         struct rc_init_context *c = container_of(p, struct rc_init_context, process);
312 
313         ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK);
314 
315         free(c);
316 }
317 
318 static int rc_init(struct ubus_context *ctx, struct ubus_object *obj,
319                    struct ubus_request_data *req, const char *method,
320                    struct blob_attr *msg)
321 {
322         struct blob_attr *tb[__RC_INIT_MAX];
323         struct rc_init_context *c;
324         char path[PATH_MAX];
325         const char *action;
326         const char *name;
327         const char *chr;
328         pid_t pid;
329         int err;
330         int fd;
331 
332         blobmsg_parse(rc_init_policy, __RC_INIT_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
333 
334         if (!tb[RC_INIT_NAME] || !tb[RC_INIT_ACTION])
335                 return UBUS_STATUS_INVALID_ARGUMENT;
336 
337         name = blobmsg_get_string(tb[RC_INIT_NAME]);
338 
339         /* Validate script name */
340         for (chr = name; (chr = strchr(chr, '.')); chr++) {
341                 if (*(chr + 1) == '.')
342                         return UBUS_STATUS_INVALID_ARGUMENT;
343         }
344         if (strchr(name, '/'))
345                 return UBUS_STATUS_INVALID_ARGUMENT;
346 
347         snprintf(path, sizeof(path), "/etc/init.d/%s", name);
348 
349         /* Validate script privileges */
350         err = rc_check_script(path);
351         if (err)
352                 return err;
353 
354         action = blobmsg_get_string(tb[RC_INIT_ACTION]);
355         if (strcmp(action, "disable") &&
356             strcmp(action, "enable") &&
357             strcmp(action, "stop") &&
358             strcmp(action, "start") &&
359             strcmp(action, "restart") &&
360             strcmp(action, "reload"))
361                 return UBUS_STATUS_INVALID_ARGUMENT;
362 
363         c = calloc(1, sizeof(*c));
364         if (!c)
365                 return UBUS_STATUS_UNKNOWN_ERROR;
366 
367         pid = fork();
368         switch (pid) {
369         case -1:
370                 free(c);
371                 return UBUS_STATUS_UNKNOWN_ERROR;
372         case 0:
373                 /* Set stdin, stdout & stderr to /dev/null */
374                 fd = open("/dev/null", O_RDWR);
375                 if (fd >= 0) {
376                         dup2(fd, 0);
377                         dup2(fd, 1);
378                         dup2(fd, 2);
379                         if (fd > 2)
380                                 close(fd);
381                 }
382 
383                 uloop_end();
384 
385                 execl(path, path, action, NULL);
386                 exit(errno);
387         default:
388                 c->ctx = ctx;
389                 c->process.pid = pid;
390                 c->process.cb = rc_init_cb;
391                 uloop_process_add(&c->process);
392 
393                 ubus_defer_request(ctx, req, &c->req);
394 
395                 return 0; /* Deferred */
396         }
397 }
398 
399 int rpc_rc_api_init(struct ubus_context *ctx)
400 {
401         static const struct ubus_method rc_methods[] = {
402                 UBUS_METHOD("list", rc_list, rc_list_policy),
403                 UBUS_METHOD("init", rc_init, rc_init_policy),
404         };
405 
406         static struct ubus_object_type rc_type =
407                 UBUS_OBJECT_TYPE("rc", rc_methods);
408 
409         static struct ubus_object obj = {
410                 .name = "rc",
411                 .type = &rc_type,
412                 .methods = rc_methods,
413                 .n_methods = ARRAY_SIZE(rc_methods),
414         };
415 
416         return ubus_add_object(ctx, &obj);
417 }
418 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt