auc: several improvements

* include version_code in request
 * include versions of selected packages in request
 * add SHA256 verification via busybox sha256sum
 * sort attributes in policies alphabetically
 * move all API-specific string constants to precompiler macros
 * set correct MIME type for JSON post request (application/json)
 * output string error message if something goes wrong
 * auto-generate version string

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2021-03-10 16:12:17 +00:00
parent 6fa328d109
commit fb26b424e8
No known key found for this signature in database
GPG Key ID: 5A8F39C31C3217CA
2 changed files with 166 additions and 102 deletions

View File

@ -25,6 +25,7 @@ define Package/auc/description
endef
EXTRA_CFLAGS += \
-D'AUC_VERSION=\"$(PKG_VERSION)-$(PKG_RELEASE)\"' \
$(if $(CONFIG_DEBUG),-DAUC_DEBUG=ON)
define Package/auc/install

View File

@ -13,7 +13,9 @@
*/
#define _GNU_SOURCE
#define AUC_VERSION "0.1.5"
#ifndef AUC_VERSION
#define AUC_VERSION "unknown"
#endif
#include <errno.h>
#include <fcntl.h>
@ -40,13 +42,19 @@
#define REQ_TIMEOUT 15
#define API_REQUEST "api/build"
#define API_BRANCHES "branches"
#define API_INDEX "index"
#define API_JSON "json"
#define API_JSON_BRANCHES "json/branches.json"
#define API_JSON_EXT "." API_JSON
#define API_PACKAGES "packages"
#define API_REQUEST "api/build"
#define API_STATUS_QUEUED "queued"
#define API_STATUS_STARTED "started"
#define API_STORE "store"
#define API_JSON_TARGETS "targets"
#define API_TARGETS "targets"
#define PUBKEY_PATH "/etc/opkg/keys"
#define SHA256SUM "/bin/busybox sha256sum"
#ifdef AUC_DEBUG
#define DPRINTF(...) if (debug) fprintf(stderr, __VA_ARGS__)
@ -131,17 +139,17 @@ static const struct blobmsg_policy board_policy[__BOARD_MAX] = {
*/
enum {
RELEASE_DISTRIBUTION,
RELEASE_VERSION,
RELEASE_REVISION,
RELEASE_TARGET,
RELEASE_VERSION,
__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_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
[RELEASE_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
};
/*
@ -174,106 +182,94 @@ static const struct blobmsg_policy upgtest_policy[__UPGTEST_MAX] = {
[UPGTEST_STDERR] = { .name = "stderr", .type = BLOBMSG_TYPE_STRING },
};
/*
* generic policy for HTTP JSON reply
*/
enum {
REPLY_ARRAY,
REPLY_OBJECT,
__REPLY_MAX,
};
static const struct blobmsg_policy reply_policy[__REPLY_MAX] = {
[REPLY_ARRAY] = { .name = "reply", .type = BLOBMSG_TYPE_ARRAY },
[REPLY_OBJECT] = { .name = "reply", .type = BLOBMSG_TYPE_TABLE },
};
/*
* policy for branches.json
*/
enum {
BRANCH_NAME,
BRANCH_ENABLED,
BRANCH_SNAPSHOT,
BRANCH_VERSIONS,
BRANCH_GIT_BRANCH,
BRANCH_DATE_RELEASE,
BRANCH_DATE_EOL,
BRANCH_DATE_RELEASE,
BRANCH_ENABLED,
BRANCH_EXTRA_REPOS,
BRANCH_GIT_BRANCH,
BRANCH_NAME,
BRANCH_PATH,
BRANCH_PATH_PACKAGES,
BRANCH_PUBKEY,
BRANCH_UPDATES,
BRANCH_REPOS,
BRANCH_EXTRA_REPOS,
BRANCH_SNAPSHOT,
BRANCH_TARGETS,
BRANCH_UPDATES,
BRANCH_VERSIONS,
__BRANCH_MAX,
};
static const struct blobmsg_policy branches_policy[__BRANCH_MAX] = {
[BRANCH_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
[BRANCH_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_SNAPSHOT] = { .name = "snapshot", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_VERSIONS] = { .name = "versions", .type = BLOBMSG_TYPE_ARRAY },
[BRANCH_DATE_RELEASE] = { .name = "release_date", .type = BLOBMSG_TYPE_STRING },
[BRANCH_DATE_EOL] = { .name = "eol", .type = BLOBMSG_TYPE_STRING },
[BRANCH_DATE_RELEASE] = { .name = "release_date", .type = BLOBMSG_TYPE_STRING },
[BRANCH_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_EXTRA_REPOS] = { .name = "extra_repos", .type = BLOBMSG_TYPE_TABLE },
[BRANCH_GIT_BRANCH] = { .name = "git_branch", .type = BLOBMSG_TYPE_STRING },
[BRANCH_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
[BRANCH_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
[BRANCH_PATH_PACKAGES] = { .name = "path_packages", .type = BLOBMSG_TYPE_STRING },
[BRANCH_PUBKEY] = { .name = "pubkey", .type = BLOBMSG_TYPE_STRING },
[BRANCH_UPDATES] = { .name = "updates", .type = BLOBMSG_TYPE_STRING },
[BRANCH_REPOS] = { .name = "repos", .type = BLOBMSG_TYPE_ARRAY },
[BRANCH_EXTRA_REPOS] = { .name = "extra_repos", .type = BLOBMSG_TYPE_TABLE },
[BRANCH_SNAPSHOT] = { .name = "snapshot", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_TARGETS] = { .name = "targets", .type = BLOBMSG_TYPE_TABLE },
[BRANCH_UPDATES] = { .name = "updates", .type = BLOBMSG_TYPE_STRING },
[BRANCH_VERSIONS] = { .name = "versions", .type = BLOBMSG_TYPE_ARRAY },
};
/*
* shared policy for target.json and server image request reply
*/
enum {
TARGET_METADATA_VERSION,
TARGET_ARCH_PACKAGES,
TARGET_BINDIR,
TARGET_BUILD_AT,
TARGET_DEFAULT_PACKAGES,
TARGET_DEVICE_PACKAGES,
TARGET_ENQUEUED_AT,
TARGET_IMAGE_PREFIX,
TARGET_IMAGES,
TARGET_MESSAGE,
TARGET_MANIFEST,
TARGET_METADATA_VERSION,
TARGET_REQUEST_HASH,
TARGET_SOURCE_DATE_EPOCH,
TARGET_STATUS,
TARGET_STDERR,
TARGET_STDOUT,
TARGET_SUPPORTED_DEVICES,
TARGET_TARGET,
TARGET_TITLES,
TARGET_VERSION_CODE,
TARGET_VERSION_NUMBER,
TARGET_BUILD_AT,
TARGET_ENQUEUED_AT,
TARGET_MANIFEST,
TARGET_REQUEST_HASH,
TARGET_STATUS,
TARGET_STDERR,
TARGET_STDOUT,
TARGET_BINDIR,
__TARGET_MAX,
};
static const struct blobmsg_policy target_policy[__TARGET_MAX] = {
[TARGET_METADATA_VERSION] = { .name = "metadata_version", .type = BLOBMSG_TYPE_INT32 },
[TARGET_ARCH_PACKAGES] = { .name = "arch_packages", .type = BLOBMSG_TYPE_STRING },
[TARGET_BINDIR] = { .name = "bin_dir", .type = BLOBMSG_TYPE_STRING },
[TARGET_BUILD_AT] = { .name = "built_at", .type = BLOBMSG_TYPE_STRING },
[TARGET_DEFAULT_PACKAGES] = { .name = "default_packages", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_DEVICE_PACKAGES] = { .name = "device_packages", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_ENQUEUED_AT] = { .name = "enqueued_at", .type = BLOBMSG_TYPE_STRING },
[TARGET_IMAGE_PREFIX] = { .name = "image_prefix", .type = BLOBMSG_TYPE_STRING },
[TARGET_IMAGES] = { .name = "images", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_MANIFEST] = { .name = "manifest", .type = BLOBMSG_TYPE_TABLE },
[TARGET_MESSAGE] = { .name = "message", .type = BLOBMSG_TYPE_STRING },
[TARGET_METADATA_VERSION] = { .name = "metadata_version", .type = BLOBMSG_TYPE_INT32 },
[TARGET_REQUEST_HASH] = { .name = "request_hash", .type = BLOBMSG_TYPE_STRING },
[TARGET_SOURCE_DATE_EPOCH] = { .name = "source_date_epoch", .type = BLOBMSG_TYPE_STRING },
[TARGET_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDERR] = { .name = "stderr", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING },
[TARGET_SUPPORTED_DEVICES] = { .name = "supported_devices", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
[TARGET_TITLES] = { .name = "titles", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_VERSION_CODE] = { .name = "version_code", .type = BLOBMSG_TYPE_STRING },
[TARGET_VERSION_NUMBER] = { .name = "version_number", .type = BLOBMSG_TYPE_STRING },
[TARGET_BUILD_AT] = { .name = "built_at", .type = BLOBMSG_TYPE_STRING },
[TARGET_ENQUEUED_AT] = { .name = "enqueued_at", .type = BLOBMSG_TYPE_STRING },
[TARGET_MANIFEST] = { .name = "manifest", .type = BLOBMSG_TYPE_TABLE },
[TARGET_REQUEST_HASH] = { .name = "request_hash", .type = BLOBMSG_TYPE_STRING },
[TARGET_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDERR] = { .name = "stderr", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING },
[TARGET_BINDIR] = { .name = "bin_dir", .type = BLOBMSG_TYPE_STRING },
};
/*
@ -294,23 +290,33 @@ static const struct blobmsg_policy images_policy[__IMAGES_MAX] = {
[IMAGES_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
};
/*
* generic policy for HTTP JSON reply
*/
enum {
REPLY_ARRAY,
REPLY_OBJECT,
__REPLY_MAX,
};
static const struct blobmsg_policy reply_policy[__REPLY_MAX] = {
[REPLY_ARRAY] = { .name = "reply", .type = BLOBMSG_TYPE_ARRAY },
[REPLY_OBJECT] = { .name = "reply", .type = BLOBMSG_TYPE_TABLE },
};
/*
* policy for HTTP headers received from server
*/
enum {
H_RANGE,
H_LEN,
H_IBSTATUS,
H_IBQUEUEPOS,
H_RANGE,
H_UNKNOWN_PACKAGE,
__H_MAX
};
static const struct blobmsg_policy policy[__H_MAX] = {
[H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
static const struct blobmsg_policy header_policy[__H_MAX] = {
[H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING },
[H_IBSTATUS] = { .name = "x-imagebuilder-status", .type = BLOBMSG_TYPE_STRING },
[H_IBQUEUEPOS] = { .name = "x-build-queue-position", .type = BLOBMSG_TYPE_STRING },
[H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
[H_UNKNOWN_PACKAGE] = { .name = "x-unknown-package", .type = BLOBMSG_TYPE_STRING },
};
@ -431,7 +437,8 @@ static void pkglist_req_cb(struct ubus_request *req, int type, struct blob_attr
struct blob_attr *tb[__PACKAGES_MAX];
struct blob_attr *cur;
int rem;
void *array;
struct avl_pkg *pkg;
void *table;
blobmsg_parse(packages_policy, __PACKAGES_MAX, tb, blob_data(msg), blob_len(msg));
@ -440,15 +447,19 @@ static void pkglist_req_cb(struct ubus_request *req, int type, struct blob_attr
return;
}
array = blobmsg_open_array(buf, "packages");
table = blobmsg_open_table(buf, "packages_versions");
blobmsg_for_each_attr(cur, tb[PACKAGES_PACKAGES], rem) {
if (is_builtin_pkg(blobmsg_name(cur)))
continue;
blobmsg_add_string(buf, NULL, blobmsg_name(cur));
pkg = avl_find_element(&pkg_tree, blobmsg_name(cur), pkg, avl);
if (!pkg)
continue;
blobmsg_add_string(buf, blobmsg_name(cur), pkg->version);
}
blobmsg_close_array(buf, array);
blobmsg_close_table(buf, table);
};
/*
@ -600,7 +611,7 @@ static void header_done_cb(struct uclient *cl)
DPRINTF("status code: %d\n", cl->status_code);
DPRINTF("headers:\n%s\n", blobmsg_format_json_indent(cl->meta, true, 0));
blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
blobmsg_parse(header_policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
switch (cl->status_code) {
case 400:
@ -678,6 +689,9 @@ static void header_done_cb(struct uclient *cl)
request_done(cl);
}
break;
case 500:
/* server may reply JSON object */
break;
default:
DPRINTF("HTTP error %d\n", cl->status_code);
@ -811,7 +825,7 @@ static int server_request(const char *url, struct blob_buf *inbuf, struct blob_b
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");
uclient_http_set_header(ucl, "Content-Type", "application/json");
post_data = blobmsg_format_json(inbuf->head, true);
uclient_write(ucl, post_data, strlen(post_data));
}
@ -938,9 +952,9 @@ static int request_target(struct branch *br, char *url)
blobmsg_buf_init(&boardbuf);
if (server_request(url, NULL, &boardbuf)) {
if ((rc = server_request(url, NULL, &boardbuf))) {
blob_buf_free(&boardbuf);
return -EREMOTE;
return rc;
}
blobmsg_parse(reply_policy, __REPLY_MAX, tbr, blob_data(boardbuf.head), blob_len(boardbuf.head));
@ -1052,7 +1066,8 @@ static void process_branch(struct blob_attr *branch, bool only_active)
continue;
}
asprintf(&board_json_file, "%s/%s/%s/%s/%s/%s.json", serverurl, API_JSON, br->path, API_JSON_TARGETS, target, board_name);
asprintf(&board_json_file, "%s/%s/%s/%s/%s/%s%s", serverurl, API_JSON,
br->path, API_TARGETS, target, board_name, API_JSON_EXT);
tmp = board_json_file;
while ((tmp = strchr(tmp, ',')))
*tmp = '_';
@ -1077,11 +1092,12 @@ static int request_branches(bool only_active)
char url[256];
blobmsg_buf_init(&brbuf);
snprintf(url, sizeof(url), "%s/%s", serverurl, API_JSON_BRANCHES);
snprintf(url, sizeof(url), "%s/%s/%s%s", serverurl, API_JSON,
API_BRANCHES, API_JSON_EXT);
if (server_request(url, NULL, &brbuf)) {
if ((rc = server_request(url, NULL, &brbuf))) {
blob_buf_free(&brbuf);
return -EREMOTE;
return rc;
};
blobmsg_parse(reply_policy, __REPLY_MAX, tb, blob_data(brbuf.head), blob_len(brbuf.head));
@ -1180,10 +1196,11 @@ static int request_packages(struct branch *branch)
fprintf(stderr, "Requesting package lists...\n");
blobmsg_buf_init(&archpkgbuf);
snprintf(url, sizeof(url), "%s/%s/%s/targets/%s/index.json", serverurl, API_JSON, branch->path, target);
if (server_request(url, NULL, &archpkgbuf)) {
snprintf(url, sizeof(url), "%s/%s/%s/%s/%s/%s%s", serverurl, API_JSON,
branch->path, API_TARGETS, target, API_INDEX, API_JSON_EXT);
if ((rc = server_request(url, NULL, &archpkgbuf))) {
blob_buf_free(&archpkgbuf);
return -EREMOTE;
return rc;
};
ret = add_upg_packages(archpkgbuf.head, branch->arch_packages);
@ -1193,11 +1210,12 @@ static int request_packages(struct branch *branch)
return ret;
blobmsg_buf_init(&pkgbuf);
snprintf(url, sizeof(url), "%s/%s/%s/packages/%s-index.json", serverurl, API_JSON, branch->path, branch->arch_packages);
if (server_request(url, NULL, &pkgbuf)) {
snprintf(url, sizeof(url), "%s/%s/%s/%s/%s-%s%s", serverurl, API_JSON,
branch->path, API_PACKAGES, branch->arch_packages, API_INDEX, API_JSON_EXT);
if ((rc = server_request(url, NULL, &pkgbuf))) {
blob_buf_free(&archpkgbuf);
blob_buf_free(&pkgbuf);
return -EREMOTE;
return rc;
};
ret = add_upg_packages(pkgbuf.head, NULL);
@ -1267,10 +1285,42 @@ static int select_image(struct blob_attr *images, char **image_name, char **imag
return ret;
}
static bool validate_sha256(char *filename, char *sha256str)
{
char *cmd = calloc(strlen(SHA256SUM) + 1 + strlen(filename) + 1, sizeof(char));
size_t reslen = (64 + 2 + strlen(filename) + 1) * sizeof(char);
char *resstr = malloc(reslen);
FILE *f;
bool ret = false;
strcpy(cmd, SHA256SUM);
strcat(cmd, " ");
strcat(cmd, filename);
f = popen(cmd, "r");
if (!f)
goto sha256free;
if (fread(resstr, reslen, 1, f) < 1)
goto sha256close;
if (!strncmp(sha256str, resstr, 64))
ret = true;
sha256close:
fflush(f);
fclose(f);
sha256free:
free(cmd);
free(resstr);
return ret;
}
static inline bool status_delay(const char *status)
{
return !strcmp("queued", status) ||
!strcmp("started", status);
return !strcmp(API_STATUS_QUEUED, status) ||
!strcmp(API_STATUS_STARTED, status);
}
/* this main function is too big... todo: split */
@ -1291,12 +1341,6 @@ int main(int args, char *argv[]) {
unsigned char argc = 1;
bool force = false, use_get = false;
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) {
fprintf(stderr, "failed to connect to ubus.\n");
return -1;
}
snprintf(user_agent, sizeof(user_agent), "%s (%s)", argv[0], AUC_VERSION);
fprintf(stdout, "%s\n", user_agent);
@ -1327,10 +1371,6 @@ int main(int args, char *argv[]) {
argc++;
};
if (!ctx) {
fprintf(stderr, "failed to connect to ubus.\n");
return -1;
}
if (load_config()) {
rc=-EFAULT;
goto freeubus;
@ -1356,6 +1396,13 @@ int main(int args, char *argv[]) {
}
}
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) {
fprintf(stderr, "failed to connect to ubus.\n");
return -1;
}
blobmsg_buf_init(&checkbuf);
blobmsg_buf_init(&infobuf);
blobmsg_buf_init(&reqbuf);
@ -1391,10 +1438,8 @@ int main(int args, char *argv[]) {
else if (revcmp > 0)
upg_check |= PKG_DOWNGRADE;
if (request_packages(branch)) {
rc=-ECONNABORTED;
if ((rc = request_packages(branch)))
goto freebranches;
}
upg_check |= check_installed_packages(reqbuf.head);
if (upg_check & PKG_ERROR) {
@ -1421,6 +1466,7 @@ int main(int args, char *argv[]) {
goto freebranches;
blobmsg_add_string(&reqbuf, "version", branch->version);
blobmsg_add_string(&reqbuf, "version_code", branch->version_code);
blobmsg_add_string(&reqbuf, "target", target);
sanetized_board_name = strdup(board_name);
@ -1441,7 +1487,10 @@ int main(int args, char *argv[]) {
DPRINTF("requesting from %s\n%s%s", url, use_get?"":blobmsg_format_json_indent(reqbuf.head, true, 0), use_get?"":"\n");
server_request(url, use_get?NULL:&reqbuf, &imgbuf);
rc = server_request(url, use_get?NULL:&reqbuf, &imgbuf);
if (rc)
break;
blobmsg_parse(reply_policy, __REPLY_MAX, tbr, blob_data(imgbuf.head), blob_len(imgbuf.head));
if (!tbr[REPLY_OBJECT])
break;
@ -1486,15 +1535,11 @@ int main(int args, char *argv[]) {
}
} while(retry);
if (!tb[TARGET_IMAGES]) {
if (!rc)
rc=-ESTRPIPE;
goto freebranches;
}
free(sanetized_board_name);
if (!tb[TARGET_BINDIR]) {
if (!tb[TARGET_IMAGES] || !tb[TARGET_BINDIR]) {
if (!rc)
rc=-ESTRPIPE;
rc=-EBADMSG;
goto freebranches;
}
@ -1506,7 +1551,9 @@ int main(int args, char *argv[]) {
image_name);
DPRINTF("downloading image from %s\n", url);
server_request(url, NULL, NULL);
rc = server_request(url, NULL, NULL);
if (rc)
goto freebranches;
filename = uclient_get_url_filename(url, "firmware.bin");
@ -1523,6 +1570,13 @@ int main(int args, char *argv[]) {
goto freebranches;
}
if (!validate_sha256(filename, image_sha256)) {
fprintf(stderr, "sha256 mismatch\n");
unlink(filename);
rc=-EBADMSG;
goto freebranches;
}
if (strcmp(filename, "firmware.bin")) {
if (rename(filename, "firmware.bin")) {
fprintf(stderr, "can't rename to firmware.bin\n");
@ -1562,6 +1616,12 @@ freebranches:
#endif
)
fputs(blobmsg_get_string(tb[TARGET_STDERR]), stderr);
if (tb[TARGET_MESSAGE]) {
fputs(blobmsg_get_string(tb[TARGET_MESSAGE]), stderr);
fputc('\n', stderr);
}
/* ToDo */
freeboard:
free(board_name);
@ -1590,5 +1650,8 @@ freeubus:
if (ucl)
uclient_free(ucl);
if (rc)
fprintf(stderr, "%s (%d)\n", strerror(-1 * rc), -1 * rc);
return rc;
}