From 95d96a360f0240fcfd7d956f41e354ddd99452c9 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sun, 12 Nov 2017 23:29:35 +0100 Subject: [PATCH] auc: add new package The Attended sysUpgrade CLI is a full-featured client for the attended-sysupgrade service which works directly on the target device. It requires libustream-ssl as well as at least the CA certificate needed to contact the sysupgrade server. It has only been tested briefly and is by no means ready for production! Signed-off-by: Daniel Golle --- utils/auc/Makefile | 32 ++ utils/auc/src/CMakeLists.txt | 12 + utils/auc/src/auc.c | 856 +++++++++++++++++++++++++++++++++++ 3 files changed, 900 insertions(+) create mode 100644 utils/auc/Makefile create mode 100644 utils/auc/src/CMakeLists.txt create mode 100644 utils/auc/src/auc.c diff --git a/utils/auc/Makefile b/utils/auc/Makefile new file mode 100644 index 0000000000..731b34be8b --- /dev/null +++ b/utils/auc/Makefile @@ -0,0 +1,32 @@ +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=auc +PKG_VERSION:=0.0.1 +PKG_RELEASE=1 +PKG_LICENSE:=GPL-3.0 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/auc + SECTION:=base + CATEGORY:=Base system + TITLE:=attended sysupgrade (CLI version) + DEPENDS:=+attendedsysupgrade-common +libblobmsg-json +libubox +libubus \ + +libuci +libuclient +rpcd-mod-rpcsys +endef + +define Package/auc/description + CLI client for attended-sysupgrade +endef + +define Package/auc/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/auc $(1)/usr/sbin/ +endef + +$(eval $(call BuildPackage,auc)) diff --git a/utils/auc/src/CMakeLists.txt b/utils/auc/src/CMakeLists.txt new file mode 100644 index 0000000000..ce291a4e34 --- /dev/null +++ b/utils/auc/src/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(auc C) +ADD_DEFINITIONS(-Os -ggdb -Wall --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +find_library(json NAMES json-c json) + +ADD_EXECUTABLE(auc auc.c) +TARGET_LINK_LIBRARIES(auc uci ubox ubus uclient blobmsg_json ${json}) +INSTALL(TARGETS auc RUNTIME DESTINATION sbin) diff --git a/utils/auc/src/auc.c b/utils/auc/src/auc.c new file mode 100644 index 0000000000..e7a3c8a7d1 --- /dev/null +++ b/utils/auc/src/auc.c @@ -0,0 +1,856 @@ +/* + * auc - attendedsysUpgrade CLI + * Copyright (C) 2017 Daniel Golle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REQ_TIMEOUT 15 +#define APIOBJ_CHECK "api/upgrade-check" +#define APIOBJ_REQUEST "api/upgrade-request" + +static const char *user_agent = "auc"; +static char *serverurl; +static struct ustream_ssl_ctx *ssl_ctx; +static const struct ustream_ssl_ops *ssl_ops; +static off_t out_bytes; +static off_t out_len; +static off_t out_offset; +static bool cur_resume; +static int output_fd = -1; +static int retry, imagebuilder, building; +static char *board_name = NULL; +static char *target = NULL, *subtarget = NULL; +static char *distribution = NULL, *version = NULL, *revision = NULL; +static int uptodate; +static char *filename = NULL; + +/* + * policy for ubus call system board + * see procd/system.c + */ +enum { + BOARD_KERNEL, + BOARD_HOSTNAME, + BOARD_SYSTEM, + BOARD_MODEL, + BOARD_BOARD_NAME, + BOARD_RELEASE, + __BOARD_MAX, +}; + +static const struct blobmsg_policy board_policy[__BOARD_MAX] = { + [BOARD_KERNEL] = { .name = "kernel", .type = BLOBMSG_TYPE_STRING }, + [BOARD_HOSTNAME] = { .name = "hostname", .type = BLOBMSG_TYPE_STRING }, + [BOARD_SYSTEM] = { .name = "system", .type = BLOBMSG_TYPE_STRING }, + [BOARD_MODEL] = { .name = "model", .type = BLOBMSG_TYPE_STRING }, + [BOARD_BOARD_NAME] = { .name = "board_name", .type = BLOBMSG_TYPE_STRING }, + [BOARD_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_TABLE }, +}; + +/* + * policy for release information in system board reply + * see procd/system.c + */ +enum { + RELEASE_DISTRIBUTION, + RELEASE_VERSION, + RELEASE_REVISION, + RELEASE_CODENAME, + RELEASE_TARGET, + RELEASE_DESCRIPTION, + __RELEASE_MAX, +}; + +static const struct blobmsg_policy release_policy[__RELEASE_MAX] = { + [RELEASE_DISTRIBUTION] = { .name = "distribution", .type = BLOBMSG_TYPE_STRING }, + [RELEASE_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING }, + [RELEASE_REVISION] = { .name = "revision", .type = BLOBMSG_TYPE_STRING }, + [RELEASE_CODENAME] = { .name = "codename", .type = BLOBMSG_TYPE_STRING }, + [RELEASE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, + [RELEASE_DESCRIPTION] = { .name = "description", .type = BLOBMSG_TYPE_STRING }, +}; + +/* + * policy for packagelist + * see rpcd/sys.c + */ +enum { + PACKAGELIST_PACKAGES, + __PACKAGELIST_MAX, +}; + +static const struct blobmsg_policy packagelist_policy[__PACKAGELIST_MAX] = { + [PACKAGELIST_PACKAGES] = { .name = "packages", .type = BLOBMSG_TYPE_TABLE }, +}; + +/* + * policy for upgrade_test + * see rpcd/sys.c + */ +enum { + UPGTEST_CODE, + UPGTEST_STDOUT, + __UPGTEST_MAX, +}; + +static const struct blobmsg_policy upgtest_policy[__UPGTEST_MAX] = { + [UPGTEST_CODE] = { .name = "code", .type = BLOBMSG_TYPE_INT32 }, + [UPGTEST_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING }, +}; + + +/* + * policy to extract version from upgrade-check response + */ +enum { + CHECK_VERSION, + __CHECK_MAX, +}; + +static const struct blobmsg_policy check_policy[__CHECK_MAX] = { + [CHECK_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING }, +}; + +/* + * policy for upgrade-request response + * it can be either only a queue position or the download information + * for the ready image. + */ +enum { + IMAGE_QUEUE, + IMAGE_FILESIZE, + IMAGE_URL, + IMAGE_CHECKSUM, + IMAGE_FILES, + IMAGE_SYSUPGRADE, + __IMAGE_MAX, +}; + +static const struct blobmsg_policy image_policy[__IMAGE_MAX] = { + [IMAGE_QUEUE] = { .name = "queue", .type = BLOBMSG_TYPE_INT32 }, + [IMAGE_FILESIZE] = { .name = "filesize", .type = BLOBMSG_TYPE_INT32 }, + [IMAGE_URL] = { .name = "url", .type = BLOBMSG_TYPE_STRING }, + [IMAGE_CHECKSUM] = { .name = "checksum", .type = BLOBMSG_TYPE_STRING }, + [IMAGE_FILES] = { .name = "files", .type = BLOBMSG_TYPE_STRING }, + [IMAGE_SYSUPGRADE] = { .name = "sysupgrade", .type = BLOBMSG_TYPE_STRING }, +}; + + +/* + * load serverurl from UCI + */ +static int load_config() { + static struct uci_context *uci_ctx; + static struct uci_package *uci_attendedsysupgrade; + struct uci_section *uci_server; + + uci_ctx = uci_alloc_context(); + if (!uci_ctx) + return -1; + + uci_ctx->flags &= ~UCI_FLAG_STRICT; + + if (uci_load(uci_ctx, "attendedsysupgrade", &uci_attendedsysupgrade) || + !uci_attendedsysupgrade) { + fprintf(stderr, "Failed to load attendedsysupgrade config\n"); + return -1; + } + uci_server = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "server"); + if (!uci_server) { + fprintf(stderr, "Failed to read server url from config\n"); + return -1; + } + serverurl = strdup(uci_lookup_option_string(uci_ctx, uci_server, "url")); + uci_free_context(uci_ctx); + + return 0; +} + + +/** + * UBUS response callbacks + */ + +/* + * rpc-sys packagelist + * append packagelist response to blobbuf given in req->priv + */ +static void pkglist_cb(struct ubus_request *req, int type, struct blob_attr *msg) { + struct blob_buf *buf = (struct blob_buf *)req->priv; + struct blob_attr *tb[__PACKAGELIST_MAX]; + + blobmsg_parse(packagelist_policy, __PACKAGELIST_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[PACKAGELIST_PACKAGES]) { + fprintf(stderr, "No packagelist received\n"); + return; + } + + blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "packages", blobmsg_data(tb[PACKAGELIST_PACKAGES]), blobmsg_data_len(tb[PACKAGELIST_PACKAGES])); +}; + +/* + * system board + * append append board information to blobbuf given in req->priv + * populate board and release global strings + */ +static void board_cb(struct ubus_request *req, int type, struct blob_attr *msg) { + struct blob_buf *buf = (struct blob_buf *)req->priv; + struct blob_attr *tb[__BOARD_MAX]; + struct blob_attr *rel[__RELEASE_MAX]; + + blobmsg_parse(board_policy, __BOARD_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[BOARD_BOARD_NAME]) { + fprintf(stderr, "No board name received\n"); + return; + } + board_name = strdup(blobmsg_get_string(tb[BOARD_BOARD_NAME])); + + if (!tb[BOARD_RELEASE]) { + fprintf(stderr, "No release received\n"); + return; + } + + blobmsg_parse(release_policy, __RELEASE_MAX, rel, + blobmsg_data(tb[BOARD_RELEASE]), blobmsg_data_len(tb[BOARD_RELEASE])); + + if (!rel[RELEASE_TARGET]) { + fprintf(stderr, "No target received\n"); + return; + } + + target = strdup(blobmsg_get_string(rel[RELEASE_TARGET])); + subtarget = strchr(target, '/'); + *subtarget++ = '\0'; + + distribution = strdup(blobmsg_get_string(rel[RELEASE_DISTRIBUTION])); + version = strdup(blobmsg_get_string(rel[RELEASE_VERSION])); + revision = strdup(blobmsg_get_string(rel[RELEASE_REVISION])); + + blobmsg_add_string(buf, "distro", distribution); + blobmsg_add_string(buf, "target", target); + blobmsg_add_string(buf, "subtarget", subtarget); + blobmsg_add_string(buf, "version", version); +} + +/* + * rpc-sys upgrade_test + * check if downloaded file is accepted by sysupgrade + */ +static void upgtest_cb(struct ubus_request *req, int type, struct blob_attr *msg) { + int *valid = (int *)req->priv; + struct blob_attr *tb[__UPGTEST_MAX]; + + blobmsg_parse(upgtest_policy, __UPGTEST_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[UPGTEST_CODE]) { + fprintf(stderr, "No sysupgrade test return code received\n"); + return; + } + + *valid = (blobmsg_get_u32(tb[UPGTEST_CODE]) == 0)?1:0; + if (*valid == 0) + fprintf(stderr, "%s", blobmsg_get_string(tb[UPGTEST_STDOUT])); +}; + +/** + * uclient stuff + */ +static int open_output_file(const char *path, uint64_t resume_offset) +{ + char *filename = NULL; + int flags; + int ret; + + if (cur_resume) + flags = O_RDWR; + else + flags = O_WRONLY | O_EXCL; + + flags |= O_CREAT; + + filename = uclient_get_url_filename(path, "firmware.bin"); + + fprintf(stderr, "Writing to '%s'\n", filename); + ret = open(filename, flags, 0644); + if (ret < 0) + goto free; + + if (resume_offset && + lseek(ret, resume_offset, SEEK_SET) < 0) { + fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset); + close(ret); + ret = -1; + goto free; + } + + out_offset = resume_offset; + out_bytes += resume_offset; + +free: + free(filename); + return ret; +} + +static void request_done(struct uclient *cl) +{ + uclient_disconnect(cl); + uloop_end(); +} + +static void header_done_cb(struct uclient *cl) +{ + enum { + H_RANGE, + H_LEN, + __H_MAX + }; + static const struct blobmsg_policy policy[__H_MAX] = { + [H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING }, + [H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[__H_MAX]; + uint64_t resume_offset = 0, resume_end, resume_size; + static int retries; + + if (retries < 10 && uclient_http_redirect(cl)) { + fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host); + + retries++; + return; + } + + if (cl->status_code == 204 && cur_resume) { + /* Resume attempt failed, try normal download */ + cur_resume = false; + //init_request(cl); + return; + } + + blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta)); + + switch (cl->status_code) { + case 400: + request_done(cl); + break; + case 416: + fprintf(stderr, "File download already fully retrieved; nothing to do.\n"); + request_done(cl); + break; + case 422: + fprintf(stderr, "unknown package requested.\n"); + request_done(cl); + break; + case 201: + if (!imagebuilder) { + fprintf(stderr, "server is dispatching build job\n"); + imagebuilder=1; + } + retry=1; + break; + case 204: + fprintf(stderr, "system is up to date.\n"); + uptodate=1; + break; + case 206: + if (!cur_resume) { + if (!building) { + fprintf(stderr, "server is now building image...\n"); + building=1; + } + retry=1; + request_done(cl); + break; + } + + if (!tb[H_RANGE]) { + fprintf(stderr, "Content-Range header is missing\n"); + break; + } + + if (sscanf(blobmsg_get_string(tb[H_RANGE]), + "bytes %"PRIu64"-%"PRIu64"/%"PRIu64, + &resume_offset, &resume_end, &resume_size) != 3) { + fprintf(stderr, "Content-Range header is invalid\n"); + break; + } + case 200: + if (cl->priv) + break; + + if (tb[H_LEN]) + out_len = strtoul(blobmsg_get_string(tb[H_LEN]), NULL, 10); + + output_fd = open_output_file(cl->url->location, resume_offset); + if (output_fd < 0) { + perror("Cannot open output file"); + request_done(cl); + } + break; + + default: + fprintf(stderr, "HTTP error %d\n", cl->status_code); + request_done(cl); + break; + } +} + +static void read_data_cb(struct uclient *cl) +{ + char buf[256]; + int len; + json_tokener *tok; + json_object *jsobj; + + struct blob_buf *outbuf = (struct blob_buf *)cl->priv; + + if (!outbuf) { + while (1) { + len = uclient_read(cl, buf, sizeof(buf)); + if (!len) + return; + + out_bytes += len; + write(output_fd, buf, len); + } + return; + } + + tok = json_tokener_new(); + + while (1) { + len = uclient_read(cl, buf, sizeof(buf)); + if (!len) + break; + + out_bytes += len; + + jsobj = json_tokener_parse_ex(tok, buf, len); + + if (json_tokener_get_error(tok) == json_tokener_continue) + continue; + + if (json_tokener_get_error(tok) != json_tokener_success) + break; + + if (jsobj) + { + if (json_object_get_type(jsobj) == json_type_object) + blobmsg_add_object(outbuf, jsobj); + + json_object_put(jsobj); + break; + } + } + + json_tokener_free(tok); +} + +static void eof_cb(struct uclient *cl) +{ + if (!cl->data_eof && !uptodate) { + fprintf(stderr, "Connection reset prematurely\n"); + } + request_done(cl); +} + +static void handle_uclient_error(struct uclient *cl, int code) +{ + const char *type = "Unknown error"; + + switch(code) { + case UCLIENT_ERROR_CONNECT: + type = "Connection failed"; + break; + case UCLIENT_ERROR_TIMEDOUT: + type = "Connection timed out"; + break; + case UCLIENT_ERROR_SSL_INVALID_CERT: + type = "Invalid SSL certificate"; + break; + case UCLIENT_ERROR_SSL_CN_MISMATCH: + type = "Server hostname does not match SSL certificate"; + break; + default: + break; + } + + fprintf(stderr, "Connection error: %s\n", type); + + request_done(cl); +} + +static const struct uclient_cb check_cb = { + .header_done = header_done_cb, + .data_read = read_data_cb, + .data_eof = eof_cb, + .error = handle_uclient_error, +}; + +static int server_request(const char *url, struct blob_buf *inbuf, struct blob_buf *outbuf) { + struct uclient *ucl; + int rc = -1; + char *post_data; + out_offset = 0; + out_bytes = 0; + out_len = 0; + + uloop_init(); + + ucl = uclient_new(url, NULL, &check_cb); + uclient_http_set_ssl_ctx(ucl, ssl_ops, ssl_ctx, 1); + ucl->timeout_msecs = REQ_TIMEOUT * 1000; + ucl->priv = outbuf; + rc = uclient_connect(ucl); + if (rc) + return rc; + + rc = uclient_http_set_request_type(ucl, inbuf?"POST":"GET"); + if (rc) + return rc; + + uclient_http_reset_headers(ucl); + uclient_http_set_header(ucl, "User-Agent", user_agent); + if (inbuf) { + uclient_http_set_header(ucl, "Content-Type", "text/json"); + post_data = blobmsg_format_json(inbuf->head, true); + uclient_write(ucl, post_data, strlen(post_data)); + } + rc = uclient_request(ucl); + if (rc) + return rc; + + uloop_run(); + uloop_done(); + uclient_free(ucl); + + return 0; +} + +/** + * ustream-ssl + */ +static int init_ustream_ssl(void) { + void *dlh; + glob_t gl; + int i; + + dlh = dlopen("libustream-ssl.so", RTLD_LAZY | RTLD_LOCAL); + if (!dlh) + return -1; + + ssl_ops = dlsym(dlh, "ustream_ssl_ops"); + if (!ssl_ops) + return -1; + + ssl_ctx = ssl_ops->context_new(false); + + glob("/etc/ssl/certs/*.crt", 0, NULL, &gl); + if (!gl.gl_pathc) + return -2; + + for (i = 0; i < gl.gl_pathc; i++) + ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]); + + return 0; +} + +/** + * use busybox md5sum (from jow's luci-ng) + */ +static char *md5sum(const char *file) { + pid_t pid; + int fds[2]; + static char md5[33]; + + if (pipe(fds)) + return NULL; + + switch ((pid = fork())) + { + case -1: + return NULL; + + case 0: + uloop_done(); + + dup2(fds[1], 1); + + close(0); + close(2); + close(fds[0]); + close(fds[1]); + + if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL)); + return NULL; + + break; + + default: + memset(md5, 0, sizeof(md5)); + read(fds[0], md5, 32); + waitpid(pid, NULL, 0); + close(fds[0]); + close(fds[1]); + } + + return md5; +} + +static int ask_user(void) +{ + fprintf(stderr, "Are you sure to proceed? [N/y]\n"); + if (getchar() != 'y') + return -1; + return 0; +} + +/* this main function is too big... todo: split */ +int main(int args, char *argv[]) { + static struct blob_buf checkbuf, reqbuf, imgbuf, upgbuf; + struct ubus_context *ctx = ubus_connect(NULL); + uint32_t id; + int rc; + int queuepos, valid; + char url[256]; + char *newversion = NULL; + struct blob_attr *tb[__IMAGE_MAX]; + struct blob_attr *tbc[__CHECK_MAX]; + unsigned int filesize; + char *checksum = NULL; + struct stat imgstat; + + if (!ctx) { + fprintf(stderr, "failed to connect to ubus.\n"); + return -1; + } + if (load_config()) { + rc=-1; + goto freeubus; + } + + if (chdir("/tmp")) { + rc=-1; + goto freeconfig; + } + + rc = init_ustream_ssl(); + if (rc == -2) { + fprintf(stderr, "No CA certificates loaded, please install ca-certificates\n"); + rc=-1; + goto freessl; + } + + if (rc || !ssl_ctx) { + fprintf(stderr, "SSL support not available, please install ustream-ssl\n"); + rc=-1; + goto freessl; + } + + blobmsg_buf_init(&checkbuf); + blobmsg_buf_init(&reqbuf); + blobmsg_buf_init(&imgbuf); + blobmsg_buf_init(&upgbuf); + + if (!ubus_lookup_id(ctx, "system", &id)) { + ubus_invoke(ctx, id, "board", NULL, board_cb, &checkbuf, 3000); + } else { + fprintf(stderr, "cannot request board info from procd\n"); + rc=-1; + goto freebufs; + } + + if (!ubus_lookup_id(ctx, "rpc-sys", &id)) { + ubus_invoke(ctx, id, "packagelist", NULL, pkglist_cb, &checkbuf, 3000); + } else { + fprintf(stderr, "cannot request packagelist from rpcd\n"); + rc=-1; + goto freeboard; + } + + blobmsg_add_string(&reqbuf, "distro", distribution); + blobmsg_add_string(&reqbuf, "target", target); + blobmsg_add_string(&reqbuf, "subtarget", subtarget); + blobmsg_add_string(&reqbuf, "board", board_name); + + snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_CHECK); + uptodate=0; + server_request(url, &checkbuf, &reqbuf); + blobmsg_parse(check_policy, __CHECK_MAX, tbc, blob_data(reqbuf.head), blob_len(reqbuf.head)); + if (!tbc[CHECK_VERSION]) { + if (!uptodate) { + fprintf(stderr, "server reply invalid.\n"); + rc=-1; + goto freeboard; + } + rc=0; + goto freeboard; + } + newversion = blobmsg_get_string(tbc[CHECK_VERSION]); + fprintf(stderr, "new release %s found.\n", newversion); + + rc = ask_user(); + if (rc) + goto freeboard; + + snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_REQUEST); + + imagebuilder = 0; + building = 0; + + do { + queuepos = 0; + retry = 0; + server_request(url, &reqbuf, &imgbuf); + blobmsg_parse(image_policy, __IMAGE_MAX, tb, blob_data(imgbuf.head), blob_len(imgbuf.head)); + + if (tb[IMAGE_QUEUE]) { + queuepos = blobmsg_get_u32(tb[IMAGE_QUEUE]); + fprintf(stderr, "build is in queue position %u.\n", queuepos); + } + + if (retry || queuepos) { + if (imgbuf.buf) + free(imgbuf.buf); + + memset(&imgbuf, '\0', sizeof(imgbuf)); + blobmsg_buf_init(&imgbuf); + sleep(3); + } + } while(retry || queuepos); + + if (!tb[IMAGE_SYSUPGRADE]) { + fprintf(stderr, "no sysupgrade image returned\n"); + rc=-1; + goto freeboard; + } + strncpy(url, blobmsg_get_string(tb[IMAGE_SYSUPGRADE]), sizeof(url)); + + if (!tb[IMAGE_FILESIZE]) { + fprintf(stderr, "no image size returned\n"); + rc=-1; + goto freeboard; + } + filesize = blobmsg_get_u32(tb[IMAGE_FILESIZE]); + + if (!tb[IMAGE_CHECKSUM]) { + fprintf(stderr, "no image checksum returned\n"); + rc=-1; + goto freeboard; + } + checksum = blobmsg_get_string(tb[IMAGE_CHECKSUM]); + server_request(url, NULL, NULL); +/* usign signature is not yet implemented! */ +// strncat(url, ".sig", sizeof(url)); +// server_request(url, NULL, NULL); + filename = uclient_get_url_filename(url, "firmware.bin"); + + if (stat(filename, &imgstat)) { + fprintf(stderr, "image download failed\n"); + rc=-1; + goto freeboard; + } + + if ((intmax_t)imgstat.st_size != filesize) { + fprintf(stderr, "file size mismatch\n"); + unlink(filename); + rc=-1; + goto freeboard; + } + + if (strncmp(checksum, md5sum(filename), 33)) { + fprintf(stderr, "image checksum mismatch\n"); + unlink(filename); + rc=-1; + goto freeboard; + }; + + if (strcmp(filename, "firmware.bin")) { + if (rename(filename, "firmware.bin")) { + fprintf(stderr, "can't rename to firmware.bin\n"); + unlink(filename); + rc=-1; + goto freeboard; + } + } + + if (!ubus_lookup_id(ctx, "rpc-sys", &id)) { + valid = 0; + ubus_invoke(ctx, id, "upgrade_test", NULL, upgtest_cb, &valid, 3000); + if (!valid) { + rc=-1; + goto freeboard; + } + + blobmsg_add_u8(&upgbuf, "keep", 1); + fprintf(stderr, "invoking sysupgrade\n"); + ubus_invoke(ctx, id, "upgrade_start", upgbuf.head, NULL, NULL, 3000); + } else { + rc=-1; + } + +freeboard: + free(board_name); + free(target); + /* subtarget is a pointer within target, don't free */ + free(distribution); + free(version); + free(revision); + + +freebufs: + if (checkbuf.buf) + free(checkbuf.buf); + + if (reqbuf.buf) + free(reqbuf.buf); + + if (imgbuf.buf) + free(imgbuf.buf); + + if (upgbuf.buf) + free(upgbuf.buf); + +freessl: + if (ssl_ctx) + ssl_ops->context_free(ssl_ctx); + +freeconfig: + free(serverurl); + +freeubus: + ubus_free(ctx); + + return rc; +}