1 /* 2 * rpcd - UBUS RPC server 3 * 4 * Copyright (C) 2013-2014 Jo-Philipp Wich <jow@openwrt.org> 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <stdbool.h> 20 #include <libubus.h> 21 #include <sys/mman.h> 22 23 #include <rpcd/exec.h> 24 #include <rpcd/plugin.h> 25 #include <rpcd/session.h> 26 #include <sys/reboot.h> 27 28 static const struct rpc_daemon_ops *ops; 29 30 enum { 31 RPC_P_USER, 32 RPC_P_PASSWORD, 33 __RPC_P_MAX 34 }; 35 36 static const struct blobmsg_policy rpc_password_policy[__RPC_P_MAX] = { 37 [RPC_P_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING }, 38 [RPC_P_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING }, 39 }; 40 41 enum { 42 RPC_UPGRADE_KEEP, 43 __RPC_UPGRADE_MAX 44 }; 45 46 static const struct blobmsg_policy rpc_upgrade_policy[__RPC_UPGRADE_MAX] = { 47 [RPC_UPGRADE_KEEP] = { .name = "keep", .type = BLOBMSG_TYPE_BOOL }, 48 }; 49 50 enum { 51 RPC_PACKAGELIST_ALL, 52 __RPC_PACKAGELIST_MAX 53 }; 54 55 static const struct blobmsg_policy rpc_packagelist_policy[__RPC_PACKAGELIST_MAX] = { 56 [RPC_PACKAGELIST_ALL] = { .name = "all", .type = BLOBMSG_TYPE_BOOL }, 57 }; 58 59 static int 60 rpc_errno_status(void) 61 { 62 switch (errno) 63 { 64 case EACCES: 65 return UBUS_STATUS_PERMISSION_DENIED; 66 67 case ENOTDIR: 68 return UBUS_STATUS_INVALID_ARGUMENT; 69 70 case ENOENT: 71 return UBUS_STATUS_NOT_FOUND; 72 73 case EINVAL: 74 return UBUS_STATUS_INVALID_ARGUMENT; 75 76 default: 77 return UBUS_STATUS_UNKNOWN_ERROR; 78 } 79 } 80 81 static int 82 rpc_cgi_password_set(struct ubus_context *ctx, struct ubus_object *obj, 83 struct ubus_request_data *req, const char *method, 84 struct blob_attr *msg) 85 { 86 pid_t pid; 87 int fd, fds[2]; 88 struct stat s; 89 struct blob_attr *tb[__RPC_P_MAX]; 90 ssize_t n; 91 int ret; 92 const char *const passwd = "/bin/passwd"; 93 const struct timespec ts = {0, 100 * 1000 * 1000}; 94 95 blobmsg_parse(rpc_password_policy, __RPC_P_MAX, tb, 96 blob_data(msg), blob_len(msg)); 97 98 if (!tb[RPC_P_USER] || !tb[RPC_P_PASSWORD]) 99 return UBUS_STATUS_INVALID_ARGUMENT; 100 101 if (stat(passwd, &s)) 102 return UBUS_STATUS_NOT_FOUND; 103 104 if (!(s.st_mode & S_IXUSR)) 105 return UBUS_STATUS_PERMISSION_DENIED; 106 107 if (pipe(fds)) 108 return rpc_errno_status(); 109 110 switch ((pid = fork())) 111 { 112 case -1: 113 close(fds[0]); 114 close(fds[1]); 115 return rpc_errno_status(); 116 117 case 0: 118 uloop_done(); 119 120 dup2(fds[0], 0); 121 close(fds[0]); 122 close(fds[1]); 123 124 if ((fd = open("/dev/null", O_RDWR)) > -1) 125 { 126 dup2(fd, 1); 127 dup2(fd, 2); 128 close(fd); 129 } 130 131 ret = chdir("/"); 132 if (ret < 0) 133 return rpc_errno_status(); 134 135 if (execl(passwd, passwd, 136 blobmsg_data(tb[RPC_P_USER]), NULL)) 137 return rpc_errno_status(); 138 139 default: 140 close(fds[0]); 141 142 n = write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), 143 blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); 144 if (n < 0) 145 return rpc_errno_status(); 146 147 n = write(fds[1], "\n", 1); 148 if (n < 0) 149 return rpc_errno_status(); 150 151 nanosleep(&ts, NULL); 152 153 n = write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), 154 blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); 155 if (n < 0) 156 return rpc_errno_status(); 157 n = write(fds[1], "\n", 1); 158 if (n < 0) 159 return rpc_errno_status(); 160 161 close(fds[1]); 162 163 waitpid(pid, NULL, 0); 164 165 return 0; 166 } 167 } 168 169 static bool 170 is_all_or_world(const char *pkg, const char **world) 171 { 172 /* compares null-terminated pkg with non-null-terminated world[i] */ 173 /* e.g., "my_pkg\0" == "my_pkg==1.2.3\n" => true */ 174 175 if (!world) return true; /* handles 'all' case */ 176 177 const char *terminators = "\n@~<>="; /* man 5 apk-world */ 178 179 size_t i, c; 180 const char *item; 181 for (i = 0; *world[i]; i++) { 182 item = world[i]; 183 for (c = 0; pkg[c] == item[c]; c++); 184 if (pkg[c] == '\0' && strchr(terminators, item[c])) 185 return true; 186 } 187 188 return false; 189 } 190 191 static bool 192 is_blank(const char *line) 193 { 194 for (; *line; line++) 195 if (!isspace(*line)) 196 return false; 197 return true; 198 } 199 200 static int 201 rpc_sys_packagelist(struct ubus_context *ctx, struct ubus_object *obj, 202 struct ubus_request_data *req, const char *method, 203 struct blob_attr *msg) 204 { 205 struct blob_attr *tb[__RPC_PACKAGELIST_MAX]; 206 bool all = false; 207 struct blob_buf buf = { 0 }; 208 /* line len = prefix(2) + content(512) + newline + null = 516 */ 209 char line[516], abi[128], pkg[128], ver[128]; 210 void *tbl; 211 struct stat statbuf; 212 const char **world = NULL; 213 char *world_mmap = NULL; 214 size_t world_mmap_size = 0; 215 char *token = NULL; 216 217 /* 218 * Status file fields, /usr/lib/opkg/status vs /lib/apk/db/installed 219 * opkg apk 220 * PACKAGE_ABIVERSION "ABIVersion" "g:openwrt:abiversion=" 221 * PACKAGE_AUTOINSTALLED "Auto-Installed" package listed in 'world', not a db field 222 * PACKAGE_NAME "Package" "P" 223 * PACKAGE_STATUS "Status" package listed in db, not a status value 224 * PACKAGE_VERSION "Version" "V" 225 */ 226 227 blobmsg_parse(rpc_packagelist_policy, __RPC_PACKAGELIST_MAX, tb, 228 blob_data(msg), blob_len(msg)); 229 230 if (tb[RPC_PACKAGELIST_ALL] && blobmsg_get_bool(tb[RPC_PACKAGELIST_ALL])) 231 all = true; 232 233 FILE *f = fopen("/lib/apk/db/installed", "r"); 234 if (!f) 235 return UBUS_STATUS_NOT_FOUND; 236 237 if (!all) { 238 /* We return only those items appearing in 'world' file. */ 239 int world_fd = open("/etc/apk/world", O_RDONLY); 240 if (world_fd == -1) 241 return rpc_errno_status(); 242 243 if (fstat(world_fd, &statbuf) == -1) { 244 close(world_fd); 245 return rpc_errno_status(); 246 } 247 248 world_mmap_size = statbuf.st_size + 1; 249 if (world_mmap_size == 1) { 250 /* 'world' file is malformed: empty */ 251 close(world_fd); 252 return UBUS_STATUS_UNKNOWN_ERROR; 253 } 254 255 world_mmap = (char *)mmap(NULL, world_mmap_size, PROT_READ, MAP_PRIVATE, world_fd, 0); 256 close(world_fd); 257 if (world_mmap == MAP_FAILED) { 258 return rpc_errno_status(); 259 } 260 261 if (world_mmap[world_mmap_size-2] != '\n') { 262 /* 'world' file is malformed: missing final newline */ 263 munmap(world_mmap, world_mmap_size); 264 return UBUS_STATUS_UNKNOWN_ERROR; 265 } 266 267 /* resulting 'world' pointer map looks like this: 268 * nstrs = 2 == count of newlines in mmap 269 * mmap = "pkg1\npkg2=1.2\n\0" 270 * | | | 271 * world[0]-+ | | 272 * world[1]-------+ | 273 * world[2]-----------------+ 274 */ 275 276 size_t istr, nstrs; 277 char *s; 278 for (nstrs = 0, s = world_mmap; s[nstrs]; s[nstrs] == '\n' ? nstrs++ : *s++); 279 280 if (nstrs) { 281 /* extra one in world for NULL sentinel */ 282 world = (const char **)calloc(nstrs+1, sizeof(char *)); 283 world[0] = world_mmap; 284 for (istr = 1, s = world_mmap; *s; s++) { 285 if (*s == '\n') { 286 world[istr] = s + 1; 287 istr++; 288 } 289 } 290 } 291 } 292 293 blob_buf_init(&buf, 0); 294 tbl = blobmsg_open_table(&buf, "packages"); 295 abi[0] = pkg[0] = ver[0] = '\0'; 296 297 while (fgets(line, sizeof(line), f)) { 298 switch (line[0]) { 299 case 'P': 300 if (sscanf(line, "P: %127s", pkg) != 1) 301 pkg[0] = '\0'; 302 break; 303 case 'V': 304 if (sscanf(line, "V: %127s", ver) != 1) 305 ver[0] = '\0'; 306 break; 307 case 'g': 308 /* this is a custom tag, defined in include/package-pack.mk */ 309 token = strtok(line+2, " =\n"); 310 while (token) { 311 if (strcmp(token, "openwrt:abiversion") == 0) { 312 token = strtok(NULL, " =\n"); 313 if (token) 314 strlcpy(abi, token, sizeof(abi)); 315 break; 316 } 317 token = strtok(NULL, " =\n"); 318 } 319 break; 320 default: 321 if (is_blank(line)) { 322 if (pkg[0] && ver[0]) { 323 /* Need to check both ABI-versioned and non-versioned pkg */ 324 bool keep = is_all_or_world(pkg, world); 325 if (abi[0]) { 326 pkg[strlen(pkg)-strlen(abi)] = '\0'; 327 if (!keep) 328 keep = is_all_or_world(pkg, world); 329 } 330 if (keep) 331 blobmsg_add_string(&buf, pkg, ver); 332 } 333 abi[0] = pkg[0] = ver[0] = '\0'; 334 } 335 break; 336 } 337 } 338 339 if (world) 340 free(world); 341 if (world_mmap) 342 munmap(world_mmap, world_mmap_size); 343 344 blobmsg_close_table(&buf, tbl); 345 ubus_send_reply(ctx, req, buf.head); 346 blob_buf_free(&buf); 347 fclose(f); 348 349 return 0; 350 } 351 352 static int 353 rpc_sys_upgrade_test(struct ubus_context *ctx, struct ubus_object *obj, 354 struct ubus_request_data *req, const char *method, 355 struct blob_attr *msg) 356 { 357 const char *cmd[4] = { "sysupgrade", "--test", "/tmp/firmware.bin", NULL }; 358 return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); 359 } 360 361 static int 362 rpc_sys_upgrade_start(struct ubus_context *ctx, struct ubus_object *obj, 363 struct ubus_request_data *req, const char *method, 364 struct blob_attr *msg) 365 { 366 struct blob_attr *tb[__RPC_UPGRADE_MAX]; 367 char * const cmd[4] = { "/sbin/sysupgrade", "-n", "/tmp/firmware.bin", NULL }; 368 char * const cmd_keep[3] = { "/sbin/sysupgrade", "/tmp/firmware.bin", NULL }; 369 char * const * c = cmd; 370 371 blobmsg_parse(rpc_upgrade_policy, __RPC_UPGRADE_MAX, tb, 372 blob_data(msg), blob_len(msg)); 373 374 if (tb[RPC_UPGRADE_KEEP] && blobmsg_get_bool(tb[RPC_UPGRADE_KEEP])) 375 c = cmd_keep; 376 377 if (!fork()) { 378 /* wait for the RPC call to complete */ 379 sleep(2); 380 return execv(c[0], c); 381 } 382 383 return 0; 384 } 385 386 static int 387 rpc_sys_upgrade_clean(struct ubus_context *ctx, struct ubus_object *obj, 388 struct ubus_request_data *req, const char *method, 389 struct blob_attr *msg) 390 { 391 if (unlink("/tmp/firmware.bin")) 392 return rpc_errno_status(); 393 394 return 0; 395 } 396 397 static int 398 rpc_sys_factory(struct ubus_context *ctx, struct ubus_object *obj, 399 struct ubus_request_data *req, const char *method, 400 struct blob_attr *msg) 401 { 402 char * const cmd[4] = { "/sbin/jffs2reset", "-y", "-r", NULL }; 403 404 if (!fork()) { 405 /* wait for the RPC call to complete */ 406 sleep(2); 407 return execv(cmd[0], cmd); 408 } 409 410 return 0; 411 } 412 413 static int 414 rpc_sys_reboot(struct ubus_context *ctx, struct ubus_object *obj, 415 struct ubus_request_data *req, const char *method, 416 struct blob_attr *msg) 417 { 418 if (!fork()) { 419 sync(); 420 sleep(2); 421 reboot(RB_AUTOBOOT); 422 while (1) 423 ; 424 } 425 426 return 0; 427 } 428 429 static int 430 rpc_sys_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx) 431 { 432 static const struct ubus_method sys_methods[] = { 433 UBUS_METHOD("packagelist", rpc_sys_packagelist, rpc_packagelist_policy), 434 UBUS_METHOD("password_set", rpc_cgi_password_set, rpc_password_policy), 435 UBUS_METHOD_NOARG("upgrade_test", rpc_sys_upgrade_test), 436 UBUS_METHOD("upgrade_start", rpc_sys_upgrade_start, 437 rpc_upgrade_policy), 438 UBUS_METHOD_NOARG("upgrade_clean", rpc_sys_upgrade_clean), 439 UBUS_METHOD_NOARG("factory", rpc_sys_factory), 440 UBUS_METHOD_NOARG("reboot", rpc_sys_reboot), 441 }; 442 443 static struct ubus_object_type sys_type = 444 UBUS_OBJECT_TYPE("rpcd-plugin-sys", sys_methods); 445 446 static struct ubus_object obj = { 447 .name = "rpc-sys", 448 .type = &sys_type, 449 .methods = sys_methods, 450 .n_methods = ARRAY_SIZE(sys_methods), 451 }; 452 453 ops = o; 454 455 return ubus_add_object(ctx, &obj); 456 } 457 458 struct rpc_plugin rpc_plugin = { 459 .init = rpc_sys_api_init 460 }; 461
This page was automatically generated by LXR 0.3.1. • OpenWrt