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 } 410
This page was automatically generated by LXR 0.3.1. • OpenWrt