auc: add new package
authorDaniel Golle <redacted>
Sun, 12 Nov 2017 22:29:35 +0000 (23:29 +0100)
committerDaniel Golle <redacted>
Sun, 12 Nov 2017 22:33:15 +0000 (23:33 +0100)
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 <redacted>
utils/auc/Makefile [new file with mode: 0644]
utils/auc/src/CMakeLists.txt [new file with mode: 0644]
utils/auc/src/auc.c [new file with mode: 0644]

diff --git a/utils/auc/Makefile b/utils/auc/Makefile
new file mode 100644 (file)
index 0000000..731b34b
--- /dev/null
@@ -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 (file)
index 0000000..ce291a4
--- /dev/null
@@ -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 (file)
index 0000000..e7a3c8a
--- /dev/null
@@ -0,0 +1,856 @@
+/*
+ * auc - attendedsysUpgrade CLI
+ * Copyright (C) 2017 Daniel Golle <daniel@makrotopia.org>
+ *
+ * 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 <fcntl.h>
+#include <dlfcn.h>
+#include <glob.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <uci.h>
+#include <uci_blob.h>
+#include <json-c/json.h>
+#include <libubox/ulog.h>
+#include <libubox/list.h>
+#include <libubox/vlist.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/uclient.h>
+#include <libubox/uclient-utils.h>
+#include <libubus.h>
+
+#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;
+}
git clone https://git.99rst.org/PROJECT