autoupdater: new implementation

This new version of the autoupdater is implemented in C instead of Lua,
allowing us to interface with libuclient (HTTP downloads) and libecdsautil
(signature checks) directly instead of spawning external processes,
saving RAM and making error handling more robust.

[Matthias Schiffer: add commit message]
This commit is contained in:
Jan-Philipp Litza 2017-02-27 01:22:48 +01:00 committed by Matthias Schiffer
parent 57c67964e2
commit 49cb4b3fdb
No known key found for this signature in database
GPG Key ID: 16EF3F64CB201D9C
19 changed files with 1531 additions and 591 deletions

View File

@ -1,31 +1,37 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=autoupdater
PKG_VERSION:=2
PKG_VERSION:=3
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/autoupdater
SECTION:=admin
CATEGORY:=Administration
DEPENDS:=+lua-platform-info +libuci-lua +luci-lib-nixio +ecdsautils +!BUSYBOX_CONFIG_SHA256SUM:coreutils-sha256sum
DEPENDS:=+libuclient +libecdsautil +libplatforminfo +libuci
TITLE:=Automatically update firmware
endef
define Package/autoupdater/conffiles
/etc/config/autoupdater
endef
CMAKE_OPTIONS += \
-DCMAKE_BUILD_TYPE:String="MINSIZEREL"
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
endef
define Build/Configure
endef
define Build/Compile
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/autoupdater/install
$(CP) ./files/* $(1)/
$(INSTALL_DIR) $(1)/usr/sbin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/autoupdater $(1)/usr/sbin/
endef
$(eval $(call BuildPackage,autoupdater))

View File

@ -1,133 +0,0 @@
local nixio = require 'nixio'
local fs = require 'nixio.fs'
local util = require 'nixio.util'
module('autoupdater.util', package.seeall)
-- Executes a command in the background, without parsing the command through a shell (in contrast to os.execute)
function exec(timeout, ...)
local pid, errno, error = nixio.fork()
if pid == 0 then
nixio.execp(...)
os.exit(127)
elseif pid > 0 then
if timeout then
local starttime = os.time()
while true do
if os.difftime(os.time(), starttime) > timeout then
nixio.kill(pid, nixio.const.SIGTERM)
end
local wpid, status, code = nixio.waitpid(pid, 'nohang')
if wpid then
return wpid and status == 'exited' and code
end
nixio.nanosleep(1)
end
else
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code
end
else
return pid, errno, error
end
end
-- Executes a command in the background, returning its PID and a pipe connected to the command's standard input
function popen(write, ...)
local inr, inw = nixio.pipe()
local pid = nixio.fork()
if pid > 0 then
if write then
inr:close()
return pid, inw
else
inw:close()
return pid, inr
end
elseif pid == 0 then
if write then
nixio.dup(inr, nixio.stdin)
else
nixio.dup(inw, nixio.stdout)
end
inr:close()
inw:close()
nixio.execp(...)
os.exit(127)
end
end
-- Executes all executable files in a directory
function run_dir(dir)
local function is_ok(entry)
if entry:sub(1, 1) == '.' then
return false
end
local file = dir .. '/' .. entry
if fs.stat(file, 'type') ~= 'reg' then
return false
end
if not fs.access(file, 'x') then
return false
end
return true
end
local files = util.consume(fs.dir(dir))
if not files then
return
end
table.sort(files)
for _, entry in ipairs(files) do
if is_ok(entry) then
exec(nil, dir .. '/' .. entry)
end
end
end
-- Seeds Lua's random generator from /dev/urandom
function randomseed()
local f = io.open('/dev/urandom', 'r')
local b1, b2, b3, b4 = f:read(4):byte(1, 4)
f:close()
-- The and is necessary as Lua on OpenWrt doesn't like integers over 2^31-1
math.randomseed(nixio.bit.band(b1*0x1000000 + b2*0x10000 + b3*0x100 + b4, 0x7fffffff))
end
-- Takes a date and time in RFC3339 format and returns a Unix timestamp
function parse_date(date)
local year, month, day, hour, minute, second, tzs, tzh, tzm = date:match('^(%d%d%d%d)%-(%d%d)%-(%d%d) (%d%d):(%d%d):(%d%d)([%+%-])(%d%d):(%d%d)$')
if not year then
return nil
end
local a = math.floor((14 - month)/12)
local y = year - a
local m = month + 12*a - 3
-- Based on a well-known formula for Julian dates
local days = day + math.floor((153*m + 2)/5) + 365*y + math.floor(y/4) - math.floor(y/100) + math.floor(y/400) - 719469
local time = hour*3600 + minute*60 + second
local tz = tzh*3600 + tzm*60
if tzs == '-' then
tz = -tz
end
return days * 86400 + time - tz
end

View File

@ -1,79 +0,0 @@
module 'autoupdater.version'
-- version comparison is based on dpkg code
local function isdigit(s, i)
local c = s:sub(i, i)
return c and c:match('^%d$')
end
local function char_value(s, i)
return s:byte(i, i) or 0
end
local function char_order(s, i)
local c = s:sub(i, i)
if c == '' or c:match('^%d$') then
return 0
elseif c:match('^%a$') then
return c:byte()
elseif c == '~' then
return -1
else
return c:byte() + 256
end
end
-- returns true when a is a higher version number than b
function newer_than(a, b)
local apos = 1
local bpos = 1
while apos <= a:len() or bpos <= b:len() do
local first_diff = 0
while (apos <= a:len() and not isdigit(a, apos)) or (bpos <= b:len() and not isdigit(b, bpos)) do
local ac = char_order(a, apos)
local bc = char_order(b, bpos)
if ac ~= bc then
return ac > bc
end
apos = apos + 1
bpos = bpos + 1
end
while a:sub(apos, apos) == '0' do
apos = apos + 1
end
while b:sub(bpos, bpos) == '0' do
bpos = bpos + 1
end
while isdigit(a, apos) and isdigit(b, bpos) do
if first_diff == 0 then
first_diff = char_value(a, apos) - char_value(b, bpos)
end
apos = apos + 1
bpos = bpos + 1
end
if isdigit(a, apos) then
return true
end
if isdigit(b, bpos) then
return false
end
if first_diff ~= 0 then
return first_diff > 0
end
end
return false
end

View File

@ -1,369 +0,0 @@
#!/usr/bin/lua
local nixio = require('nixio')
local fs = require('nixio.fs')
local platform_info = require('platform_info')
local uci = require('uci').cursor()
local autoupdater_util = require('autoupdater.util')
local autoupdater_version = require('autoupdater.version')
if not platform_info.get_image_name() then
io.stderr:write("The autoupdater doesn't support this hardware model.\n")
os.exit(1)
end
autoupdater_util.randomseed()
local settings = uci:get_all('autoupdater', 'settings')
local branch_name = settings.branch
local version_file = io.open(settings.version_file)
local old_version = version_file and version_file:read('*l') or ''
version_file:close()
-- If force is true the updater will perform an upgrade regardless of
-- the priority and even when it is disabled in uci
local force = false
-- If fallback is true the updater will perform an update only if the
-- timespan given by the priority and another 24h have passed
local fallback = false
local mirrors = {}
local function parse_args()
local i = 1
while arg[i] do
if arg[i] == '-f' then
force = true
elseif arg[i] == '--fallback' then
fallback = true
elseif arg[i] == '-b' then
i = i+1
if not arg[i] then
io.stderr:write("Error parsing command line: expected branch name\n")
os.exit(1)
end
branch_name = arg[i]
elseif arg[i]:sub(0, 1) == '-' then
io.stderr:write("Error parsing command line: unexpected argument '" .. arg[i] .. "'\n")
os.exit(1)
else
table.insert(mirrors, arg[i])
end
i = i+1
end
end
parse_args()
local branch = uci:get_all('autoupdater', branch_name)
if not branch then
io.stderr:write("Can't find configuration for branch '" .. branch_name .. "'\n")
os.exit(1)
end
if settings.enabled ~= '1' and not force then
io.stderr:write('autoupdater is disabled.\n')
os.exit(0)
end
-- Verifies a file given as a list of lines with a list of signatures using ecdsaverify
local function verify_lines(lines, sigs)
local command = {'ecdsaverify', '-n', tostring(branch.good_signatures)}
-- Build command line from sigs and branch.pubkey
for _, sig in ipairs(sigs) do
if sig:match('^' .. string.rep('%x', 128) .. '$') then
table.insert(command, '-s')
table.insert(command, sig)
end
end
for _, key in ipairs(branch.pubkey) do
if key:match('^' .. string.rep('%x', 64) .. '$') then
table.insert(command, '-p')
table.insert(command, key)
end
end
-- Call ecdsautils
local pid, f = autoupdater_util.popen(true, unpack(command))
for _, line in ipairs(lines) do
f:write(line)
f:write('\n')
end
f:close()
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code == 0
end
-- Downloads, parses and verifies the update manifest from a mirror
-- Returns a table with the fields version, checksum and filename if everything is ok, nil otherwise
local function read_manifest(mirror)
local sep = false
local lines = {}
local sigs = {}
local branch_ok = false
local ret = {}
-- Remove potential trailing slash
mirror = mirror:gsub('/$', '')
local starttime = os.time()
local pid, manifest_loader = autoupdater_util.popen(false, 'wget', '-T', '120', '-O-', string.format('%s/%s.manifest', mirror, branch.name))
local data = ''
-- Read all lines from the manifest
-- The upper part is saved to lines, the lower part to sigs
while true do
-- If the manifest download takes more than 5 minutes, we don't really
-- have a chance to download a whole image
local timeout = starttime+300 - os.time()
if timeout < 0 or not nixio.poll({{fd = manifest_loader, events = nixio.poll_flags('in')}}, timeout * 1000) then
io.stderr:write("Timeout while reading manifest.\n")
nixio.kill(pid, nixio.const.SIGTERM)
manifest_loader:close()
return nil
end
local r = manifest_loader:read(1024)
if not r or r == '' then
break
end
data = data .. r
while data:match('\n') do
local line, rest = data:match('^([^\n]*)\n(.*)$')
data = rest
if not sep then
if line == '---' then
sep = true
else
table.insert(lines, line)
if line == ('BRANCH=' .. branch.name) then
branch_ok = true
end
local date = line:match('^DATE=(.+)$')
local priority = line:match('^PRIORITY=([%d%.]+)$')
local model, version, checksum, filename = line:match('^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$')
if date then
ret.date = autoupdater_util.parse_date(date)
elseif priority then
ret.priority = tonumber(priority)
elseif model == platform_info.get_image_name() and #checksum == 64 then
ret.version = version
ret.checksum = checksum
ret.filename = filename
end
end
else
table.insert(sigs, line)
end
end
end
manifest_loader:close()
-- Do some very basic checks before checking the signatures
-- (as the signature verification is computationally expensive)
if not sep then
io.stderr:write('There seems to have gone something wrong downloading the manifest from ' .. mirror .. '\n')
return nil
end
if not ret.date or not ret.priority then
io.stderr:write('The manifest downloaded from ' .. mirror .. ' is invalid (DATE or PRIORITY missing)\n')
return nil
end
if not branch_ok then
io.stderr:write('Wrong branch. We are on ', branch.name, '.\n')
return nil
end
if not ret.version then
io.stderr:write('No matching firmware found (model ' .. platform_info.get_image_name() .. ')\n')
return nil
end
if not verify_lines(lines, sigs) then
io.stderr:write('Not enough valid signatures!\n')
return nil
end
return ret
end
-- Downloads the firmware image from a mirror to a given output file
local function fetch_firmware(mirror, filename, output)
-- Let's give the image download 30 minutes, hopefully more than enough
if autoupdater_util.exec(1800, 'wget', '-T', '120', '-O', output, mirror .. '/' .. filename) ~= 0 then
io.stderr:write('Error downloading the image from ' .. mirror .. '\n')
return false
end
return true
end
-- Returns the computed update probability
local function get_probability(date, priority)
local seconds = priority * 86400
local diff = os.difftime(os.time(), date)
if diff < 0 then
-- When the difference is negative, there are two possibilities: The manifest contains a wrong date, or our own clock is wrong.
-- As there isn't anything useful to do for an incorrect manifest, we'll assume the latter case and update anyways as we
-- can't do anything better
io.stderr:write('Warning: clock seems to be incorrect.\n')
if tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) ')) < 600 then
-- If the uptime is very low, it's possible we just didn't get the time over NTP yet, so we'll just wait until the next time the updater runs
return 0
else
-- Will give 1 when priority == 0, and lower probabilities the higher the priority value is
-- (similar to the old static probability system)
return 0.75^priority
end
elseif fallback then
if diff >= seconds + 86400 then
return 1
else
return 0
end
elseif diff >= seconds then
return 1
else
local x = diff/seconds
-- This is the most simple polynomial with value 0 at 0, 1 at 1, and whose first derivative is 0 at both 0 and 1
-- (we all love continuously differentiable functions, right?)
return (-2)*x^3 + 3*x^2
end
end
-- Tries to perform an update from a given mirror
local function autoupdate(mirror)
local download_d_dir = '/usr/lib/autoupdater/download.d'
local abort_d_dir = '/usr/lib/autoupdater/abort.d'
local upgrade_d_dir = '/usr/lib/autoupdater/upgrade.d'
local manifest = read_manifest(mirror)
if not manifest then
return false
end
if not autoupdater_version.newer_than(manifest.version, old_version) then
io.stderr:write('No new firmware available.\n')
return true
end
io.stderr:write('New version available.\n')
if not force and math.random() >= get_probability(manifest.date, manifest.priority) then
io.stderr:write('No autoupdate this time. Use -f to override.\n')
return true
end
autoupdater_util.run_dir(download_d_dir)
collectgarbage()
local image = os.tmpname()
if not fetch_firmware(mirror, manifest.filename, image) then
autoupdater_util.run_dir(abort_d_dir)
return false
end
local popen = io.popen(string.format("exec sha256sum '%s'", image))
local checksum = popen:read('*l'):match('^%x+')
popen:close()
if checksum ~= manifest.checksum then
io.stderr:write('Invalid image checksum!\n')
os.remove(image)
autoupdater_util.run_dir(abort_d_dir)
return false
end
autoupdater_util.run_dir(upgrade_d_dir)
io.stderr:write('Upgrading firmware...\n')
local null = nixio.open('/dev/null', 'w+')
if null then
nixio.dup(null, nixio.stdin)
nixio.dup(null, nixio.stderr)
if null:fileno() > 2 then
null:close()
end
end
nixio.exec('/sbin/sysupgrade', image)
-- This should never be reached as nixio.exec replaces the autoupdater process unless /sbin/sysupgrade can't be executed
-- We output the error message through stdout as stderr isn't available anymore
io.write('Failed to call sysupgrade?\n')
os.remove(image)
autoupdater_util.run_dir(abort_d_dir)
os.exit(1)
end
local lockfile = '/var/lock/autoupdater.lock'
local lockfd = nixio.open(lockfile, 'w', 'rw-------')
if not lockfd:lock('tlock') then
io.stderr:write(string.format(
"Unable to lock file %s. Make sure there is no other instance of the autoupdater running.\n",
lockfile, err
))
os.exit(1)
end
if #mirrors == 0 then
while #branch.mirror > 0 do
table.insert(mirrors, table.remove(branch.mirror, math.random(#branch.mirror)))
end
end
for k, mirror in ipairs(mirrors) do
if autoupdate(mirror) then
os.exit(0)
end
end
io.stderr:write('No usable mirror found.\n')
os.exit(1)

View File

@ -2,8 +2,8 @@ BRANCH=stable
DATE=1970-01-01 00:00:00+00:00
PRIORITY=7
# model ver sha512sum filename
tp-link-tl-wdr4300-v1 0.4 c300c2b80a8863506cf3b19359873c596d87af3183c4826462dfb5aa69bec7ce65e3db23a9f6f779fd0f3cc50db5d57070c2b62942abf4fb0e08ae4cb48191a0 gluon-0.4-tp-link-tl-wdr4300-v1-sysupgrade.bin
# model ver sha256sum size filename
tp-link-tl-wdr4300-v1 0.4 0ce0fb6a79802ba98c933ac3ae7757fdf2f62b32641fb6c5efc09211b9082c46 3735556 gluon-ffhl-0.4-tp-link-tl-wdr4300-v1-sysupgrade.bin
# after three dashes follow the ecdsa signatures of everything above the dashes
---

View File

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 2.8.8)
project(AUTOUPDATER C)
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE)
find_path(UBOX_INCLUDE_DIR NAMES libubox/uloop.h)
find_library(UBOX_LIBRARY NAMES ubox)
find_library(UCLIENT_LIBRARY NAMES uclient)
find_library(UCI_LIBRARY NAMES uci)
find_library(PLATFORMINFO_LIBRARY NAMES platforminfo)
find_package(PkgConfig REQUIRED QUIET)
pkg_check_modules(ECDSAUTIL REQUIRED ecdsautil)
include_directories(${UBOX_INCLUDE_DIR} ${ECDSAUTIL_INCLUDE_DIRS})
add_executable(autoupdater
autoupdater.c
hexutil.c
manifest.c
settings.c
uclient.c
util.c
version.c
)
set_property(TARGET autoupdater PROPERTY COMPILE_FLAGS "-std=c99 -Wall")
#set_property(TARGET autoupdater PROPERTY LINK_FLAGS "")
target_link_libraries(autoupdater
m
${PLATFORMINFO_LIBRARY}
${UCI_LIBRARY}
${UBOX_LIBRARY}
${UCLIENT_LIBRARY}
${ECDSAUTIL_LIBRARIES}
)
install(TARGETS autoupdater RUNTIME DESTINATION sbin)

View File

@ -0,0 +1,476 @@
/*
Copyright (c) 2017, Matthias Schiffer <mschiffer@universe-factory.net>
Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "manifest.h"
#include "settings.h"
#include "uclient.h"
#include "util.h"
#include "version.h"
#include <libplatforminfo.h>
#include <libubox/uloop.h>
#include <ecdsautil/ecdsa.h>
#include <ecdsautil/sha256.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#define MAX_LINE_LENGTH 512
#define STRINGIFY(str) #str
static const char *const download_d_dir = "/usr/lib/autoupdater/download.d";
static const char *const abort_d_dir = "/usr/lib/autoupdater/abort.d";
static const char *const upgrade_d_dir = "/usr/lib/autoupdater/upgrade.d";
static const char *const lockfile = "/var/lock/autoupdater.lock";
static const char *const firmware_path = "/tmp/firmware.bin";
static const char *const sysupgrade_path = "/sbin/sysupgrade";
struct recv_manifest_ctx {
struct settings *s;
struct manifest m;
char buf[MAX_LINE_LENGTH + 1];
char *ptr;
};
struct recv_image_ctx {
int fd;
ecdsa_sha256_context_t hash_ctx;
};
static void usage(void) {
fputs("\n"
"Usage: autoupdater [options] [<mirror> ...]\n\n"
"Possible options are:\n"
" -b, --branch BRANCH Override the branch given in the configuration.\n\n"
" -f, --force Always upgrade to a new version, ignoring its priority\n"
" and whether the autoupdater even is enabled.\n\n"
" -h, --help Show this help.\n\n"
" -n, --no-action Download and validate the manifest as usual, but do not\n"
" really flash a new firmware if one is available.\n\n"
" --fallback Upgrade if and only if the upgrade timespan of the new\n"
" version has passed for at least 24 hours.\n\n"
" <mirror> ... Override the mirror URLs given in the configuration. If\n"
" specified, these are not shuffled.\n\n",
stderr
);
}
static void parse_args(int argc, char *argv[], struct settings *settings) {
enum option_values {
OPTION_BRANCH = 'b',
OPTION_FORCE = 'f',
OPTION_HELP = 'h',
OPTION_NO_ACTION = 'n',
OPTION_FALLBACK = 256,
};
const struct option options[] = {
{"branch", required_argument, NULL, OPTION_BRANCH},
{"force", no_argument, NULL, OPTION_FORCE},
{"fallback", no_argument, NULL, OPTION_FALLBACK},
{"no-action", no_argument, NULL, OPTION_NO_ACTION},
{"help", no_argument, NULL, OPTION_HELP},
};
while (true) {
int c = getopt_long(argc, argv, "b:fhn", options, NULL);
if (c < 0)
break;
switch (c) {
case OPTION_BRANCH:
settings->branch = optarg;
break;
case OPTION_FORCE:
settings->force = true;
break;
case OPTION_FALLBACK:
settings->fallback = true;
break;
case OPTION_HELP:
usage();
exit(0);
case OPTION_NO_ACTION:
settings->no_action = true;
break;
default:
usage();
exit(1);
}
}
if (optind < argc) {
settings->n_mirrors = argc - optind;
settings->mirrors = malloc(settings->n_mirrors * sizeof(char *));
for (int i = optind; i < argc; i++) {
settings->mirrors[i - optind] = argv[i];
}
}
}
static float get_probability(time_t date, float priority, bool fallback) {
float seconds = priority * 86400;
time_t diff = time(NULL) - date;
if (diff < 0) {
/*
When the difference is negative, there are two possibilities: the
manifest contains an incorrect date, or our own clock is wrong. As there
isn't anything sensible to do for an incorrect manifest, we'll assume
the latter is the case and update anyways as we can't do anything better
*/
fputs("autoupdater: warning: clock seems to be incorrect.\n", stderr);
if (get_uptime() < 600)
/*
If the uptime is very low, it's possible we just didn't get the
time over NTP yet, so we'll just wait until the next time the
updater runs
*/
return 0;
else
/*
Will give 1 when priority == 0, and lower probabilities the higher
the priority value is (similar to the old static probability system)
*/
return powf(0.75f, priority);
}
else if (fallback) {
if (diff >= seconds + 86400)
return 1;
else
return 0;
}
else if (diff >= seconds) {
return 1;
}
else {
float x = diff/seconds;
/*
This is the simplest polynomial with value 0 at 0, 1 at 1, and which has a
first derivative of 0 at both 0 and 1 (we all love continuously differentiable
functions, right?)
*/
return 3*x*x - 2*x*x*x;
}
}
/** Receives data from uclient, chops it to lines and hands it to \ref parse_line */
static void recv_manifest_cb(struct uclient *cl) {
struct recv_manifest_ctx *ctx = uclient_get_custom(cl);
char *newline;
int len;
while (true) {
if (ctx->ptr - ctx->buf == MAX_LINE_LENGTH) {
fputs("autoupdater: error: encountered manifest line exceeding limit of " STRINGIFY(MAX_LINE_LENGTH) " characters\n", stderr);
break;
}
len = uclient_read_account(cl, ctx->ptr, MAX_LINE_LENGTH - (ctx->ptr - ctx->buf));
if (len <= 0)
break;
ctx->ptr[len] = '\0';
char *line = ctx->buf;
while (true) {
newline = strchr(line, '\n');
if (newline == NULL)
break;
*newline = '\0';
parse_line(line, &ctx->m, ctx->s->branch, platforminfo_get_image_name());
line = newline + 1;
}
// Move the beginning of the next line to the beginning of the
// buffer. We cannot use strcpy here because the memory areas
// might overlap!
int n = strlen(line);
memmove(ctx->buf, line, n);
ctx->ptr = ctx->buf + n;
}
}
/** Receives data from uclient and writes it to file */
static void recv_image_cb(struct uclient *cl) {
struct recv_image_ctx *ctx = uclient_get_custom(cl);
char buf[1024];
int len;
while (true) {
len = uclient_read_account(cl, buf, sizeof(buf));
if (len <= 0)
return;
printf(
"\rDownloading image: % 5zi / %zi KiB",
uclient_data(cl)->downloaded / 1024,
uclient_data(cl)->length / 1024
);
fflush(stdout);
if (write(ctx->fd, buf, len) < len) {
fputs("autoupdater: error: downloading firmware image failed: ", stderr);
perror(NULL);
return;
}
ecdsa_sha256_update(&ctx->hash_ctx, buf, len);
}
}
static bool autoupdate(const char *mirror, struct settings *s, int lock_fd) {
bool ret = false;
struct recv_manifest_ctx manifest_ctx = { .s = s };
manifest_ctx.ptr = manifest_ctx.buf;
struct manifest *m = &manifest_ctx.m;
/**** Get and check manifest *****************************************/
/* Construct manifest URL */
char manifest_url[strlen(mirror) + strlen(s->branch) + 11];
sprintf(manifest_url, "%s/%s.manifest", mirror, s->branch);
printf("Retrieving manifest from %s ...\n", manifest_url);
/* Download manifest */
ecdsa_sha256_init(&m->hash_ctx);
int err_code = get_url(manifest_url, recv_manifest_cb, &manifest_ctx, -1);
if (err_code != 0) {
fprintf(stderr, "autoupdater: warning: error downloading manifest: %s\n", uclient_get_errmsg(err_code));
goto out;
}
/* Check manifest signatures */
{
ecc_int256_t hash;
ecdsa_sha256_final(&m->hash_ctx, hash.p);
ecdsa_verify_context_t ctxs[m->n_signatures];
for (size_t i = 0; i < m->n_signatures; i++)
ecdsa_verify_prepare_legacy(&ctxs[i], &hash, m->signatures[i]);
long unsigned int good_signatures = ecdsa_verify_list_legacy(ctxs, m->n_signatures, s->pubkeys, s->n_pubkeys);
if (good_signatures < s->good_signatures) {
fprintf(stderr, "autoupdater: warning: manifest %s only carried %lu valid signatures, %lu are required\n", manifest_url, good_signatures, s->good_signatures);
goto out;
}
}
/* Check manifest */
if (!m->date_ok || !m->priority_ok) {
fprintf(stderr, "autoupdater: warning: manifest is missing mandatory fields\n");
goto out;
}
if (!m->branch_ok) {
fprintf(stderr, "autoupdater: warning: manifest %s is not for branch %s\n", manifest_url, s->branch);
goto out;
}
if (!m->model_ok) {
fprintf(stderr, "autoupdater: warning: no matching firmware found (model %s)\n", platforminfo_get_image_name());
goto out;
}
/* Check version and update probability */
if (!newer_than(m->version, s->old_version)) {
puts("No new firmware available.");
ret = true;
goto out;
}
if (!s->force && random() >= RAND_MAX * get_probability(m->date, m->priority, s->fallback)) {
fputs("autoupdater: info: no autoupdate this time. Use -f to override.\n", stderr);
ret = true;
goto out;
}
/**** Download and verify image file *********************************/
/* Begin download of the image */
run_dir(download_d_dir);
struct recv_image_ctx image_ctx = { };
image_ctx.fd = open(firmware_path, O_WRONLY|O_CREAT, 0600);
if (image_ctx.fd < 0) {
fprintf(stderr, "autoupdater: error: failed opening firmware file %s\n", firmware_path);
goto fail_after_download;
}
/* Download image and calculate SHA256 checksum */
{
char image_url[strlen(mirror) + strlen(m->image_filename) + 2];
sprintf(image_url, "%s/%s", mirror, m->image_filename);
ecdsa_sha256_init(&image_ctx.hash_ctx);
int err_code = get_url(image_url, &recv_image_cb, &image_ctx, m->imagesize);
puts("");
if (err_code != 0) {
fprintf(stderr, "autoupdater: warning: error downloading image: %s\n", uclient_get_errmsg(err_code));
close(image_ctx.fd);
goto fail_after_download;
}
}
close(image_ctx.fd);
/* Verify image checksum */
{
ecc_int256_t hash;
ecdsa_sha256_final(&image_ctx.hash_ctx, hash.p);
if (memcmp(hash.p, m->image_hash, ECDSA_SHA256_HASH_SIZE)) {
fputs("autoupdater: warning: invalid image checksum!\n", stderr);
goto fail_after_download;
}
}
clear_manifest(m);
/**** Call sysupgrade ************************************************/
if (s->no_action) {
printf(
"autoupdater: info: Aborting successful upgrade because simulation was requested.\n"
"autoupdater: info: You can find the firmware file in %s\n",
firmware_path
);
run_dir(abort_d_dir);
ret = true;
goto out;
}
/* Begin upgrade */
run_dir(upgrade_d_dir);
/* Unset FD_CLOEXEC so the lockfile stays locked during sysupgrade */
fcntl(lock_fd, F_SETFD, 0);
execl(sysupgrade_path, sysupgrade_path, firmware_path, NULL);
/* execl() shouldn't return */
fputs("autoupdater: error: failed to call sysupgrade\n", stderr);
fcntl(lock_fd, F_SETFD, FD_CLOEXEC);
fail_after_download:
unlink(firmware_path);
run_dir(abort_d_dir);
out:
clear_manifest(m);
return ret;
}
static int lock_autoupdater(void) {
int fd = open(lockfile, O_CREAT|O_RDONLY|O_CLOEXEC, 0600);
if (fd < 0) {
fprintf(stderr, "autoupdater: error: unable to open lock file: %m\n");
return -1;
}
if (flock(fd, LOCK_EX|LOCK_NB)) {
fputs("autoupdater: error: another instance is currently running\n", stderr);
close(fd);
return -1;
}
return fd;
}
int main(int argc, char *argv[]) {
struct settings s = { };
parse_args(argc, argv, &s);
if (!platforminfo_get_image_name()) {
fputs("autoupdater: error: unsupported hardware model\n", stderr);
return EXIT_FAILURE;
}
bool external_mirrors = s.n_mirrors > 0;
load_settings(&s);
randomize();
int lock_fd = lock_autoupdater();
if (lock_fd < 0)
return EXIT_FAILURE;
uloop_init();
size_t mirrors_left = s.n_mirrors;
while (mirrors_left) {
const char **mirror = s.mirrors;
size_t i = external_mirrors ? 0 : random() % mirrors_left;
/* Move forward by i non-NULL entries */
while (true) {
while (!*mirror)
mirror++;
if (!i)
break;
mirror++;
i--;
}
if (autoupdate(*mirror, &s, lock_fd)) {
// update the mtime of the lockfile to indicate a successful run
futimens(lock_fd, NULL);
return EXIT_SUCCESS;
}
/* When the update has failed, remove the mirror from the list */
*mirror = NULL;
mirrors_left--;
}
uloop_done();
fputs("autoupdater: error: no usable mirror found\n", stderr);
return EXIT_FAILURE;
}

View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2012, Nils Schneider <nils@nilsschneider.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "hexutil.h"
#include <stdio.h>
#include <string.h>
bool parsehex(void *output, const char *input, size_t len) {
unsigned char *buffer = output;
// number of digits must be 2 * len
if ((strspn(input, "0123456789abcdefABCDEF") != 2*len) || input[2*len])
return false;
for (size_t i = 0; i < len; i++)
sscanf(&input[2*i], "%02hhx", &buffer[i]);
return true;
}

View File

@ -0,0 +1,35 @@
/*
Copyright (c) 2012, Nils Schneider <nils@nilsschneider.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
/* Converts a string of hexadecimal digits and stores it in a given buffer.
* In order for this function to return successfully the decoded string
* must fit exactly into the buffer.
*/
bool parsehex(void *buffer, const char *string, size_t len);

View File

@ -0,0 +1,153 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "hexutil.h"
#include "manifest.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// only frees the data inside the manifest struct, not the struct itself!
void clear_manifest(struct manifest *m) {
free(m->image_filename);
free(m->version);
for (size_t i = 0; i < m->n_signatures; i++)
free(m->signatures[i]);
free(m->signatures);
memset(m, 0, sizeof(*m));
}
static bool parse_rfc3339(const char *input, time_t *date) {
char tzs;
unsigned year, month, day, hour, minute, second, tzh, tzm;
if (sscanf(input, "%04u-%02u-%02u %02u:%02u:%02u%c%02u:%02u",
&year, &month, &day, &hour, &minute, &second,
&tzs, &tzh, &tzm) != 9)
return false;
time_t a = (14 - month)/12;
time_t y = year - a;
time_t m = month + 12*a - 3;
/* Based on a well-known formula for Julian dates */
time_t days = day + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 719469;
time_t tim = hour*3600 + minute*60 + second;
time_t tz = 3600 * tzh + 60 * tzm;
if (tzs == '-')
tz = -tz;
else if (tzs != '+')
return false;
*date = 86400*days + tim - tz;
return true;
}
void parse_line(char *line, struct manifest *m, const char *branch, const char *image_name) {
if (m->sep_found) {
ecdsa_signature_t *sig = malloc(sizeof(ecdsa_signature_t));
if (!parsehex(sig, line, sizeof(*sig))) {
free(sig);
fprintf(stderr, "autoupdater: warning: garbage in signature area: %s\n", line);
return;
}
m->n_signatures++;
m->signatures = realloc(m->signatures, m->n_signatures * sizeof(ecdsa_signature_t *));
m->signatures[m->n_signatures - 1] = sig;
} else if (strcmp(line, "---") == 0) {
m->sep_found = true;
} else {
ecdsa_sha256_update(&m->hash_ctx, line, strlen(line));
ecdsa_sha256_update(&m->hash_ctx, "\n", 1);
if (!strncmp(line, "BRANCH=", 7) && !strcmp(&line[7], branch)) {
m->branch_ok = true;
}
else if (!strncmp(line, "DATE=", 5)) {
if (m->date_ok)
return;
m->date_ok = parse_rfc3339(&line[5], &m->date);
}
else if (!strncmp(line, "PRIORITY=", 9)) {
if (m->priority_ok)
return;
m->priority = strtof(&line[9], NULL);
m->priority_ok = true;
}
else {
if (m->model_ok)
return;
char *model = strtok(line, " ");
char *version = strtok(NULL, " ");
char *checksum = strtok(NULL, " ");
char *imagesize = strtok(NULL, " ");
char *filename = strtok(NULL, " ");
if (!filename || strtok(NULL, " "))
return;
if (strcmp(model, image_name) != 0)
return;
if (!parsehex(m->image_hash, checksum, ECDSA_SHA256_HASH_SIZE))
return;
{
char *endptr;
errno = 0;
unsigned long long val = strtoull(imagesize, &endptr, 10);
if (errno || *endptr || val > SSIZE_MAX)
return;
m->imagesize = val;
}
m->version = strdup(version);
m->image_filename = strdup(filename);
m->model_ok = true;
}
}
}

View File

@ -0,0 +1,57 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <ecdsautil/ecdsa.h>
#include <ecdsautil/sha256.h>
#include <sys/types.h>
#include <stdbool.h>
#include <time.h>
struct manifest {
bool sep_found:1;
bool branch_ok:1;
bool date_ok:1;
bool priority_ok:1;
bool model_ok:1;
char *image_filename;
unsigned char *image_hash[ECDSA_SHA256_HASH_SIZE];
char *version;
time_t date;
float priority;
ssize_t imagesize;
size_t n_signatures;
ecdsa_signature_t **signatures;
ecdsa_sha256_context_t hash_ctx;
};
void clear_manifest(struct manifest *m);
void parse_line(char *line, struct manifest *m, const char *branch, const char *image_name);

View File

@ -0,0 +1,178 @@
/*
Copyright (c) 2017, Matthias Schiffer <mschiffer@universe-factory.net>
Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "settings.h"
#include "hexutil.h"
#include <uci.h>
#include <stdlib.h>
#include <string.h>
static char * read_one_line(const char *filename) {
FILE *f = fopen(filename, "r");
if (!f)
return NULL;
char *line = NULL;
size_t len = 0;
ssize_t r = getline(&line, &len, f);
fclose(f);
if (r >= 0) {
len = strlen(line);
if (len && line[len-1] == '\n')
line[len-1] = 0;
}
else {
free(line);
line = NULL;
}
return line;
}
static unsigned long load_positive_number(struct uci_context *ctx, struct uci_section *s, const char *option) {
const char *str = uci_lookup_option_string(ctx, s, option);
if (!str) {
fprintf(stderr, "autoupdater: error: unable to load option '%s'\n", option);
exit(1);
}
char *end;
unsigned long ret = strtoul(str, &end, 0);
if (*end || !ret) {
fprintf(stderr, "autoupdater: error: invalid value for option '%s'\n", option);
exit(1);
}
return ret;
}
static const char ** load_string_list(struct uci_context *ctx, struct uci_section *s, const char *option, size_t *len) {
struct uci_option *o = uci_lookup_option(ctx, s, option);
if (!o) {
fprintf(stderr, "autoupdater: error: unable to load option '%s'\n", option);
exit(1);
}
if (o->type != UCI_TYPE_LIST) {
fprintf(stderr, "autoupdater: error: invalid value for option '%s'\n", option);
exit(1);
}
size_t i = 0;
struct uci_element *e;
uci_foreach_element(&o->v.list, e)
i++;
*len = i;
const char **ret = malloc(i * sizeof(char *));
i = 0;
uci_foreach_element(&o->v.list, e)
ret[i++] = e->name;
return ret;
}
void load_settings(struct settings *settings) {
struct uci_context *ctx = uci_alloc_context();
ctx->flags &= ~UCI_FLAG_STRICT;
struct uci_package *p;
struct uci_section *s;
if (uci_load(ctx, "autoupdater", &p) != UCI_OK) {
fputs("autoupdater: error: unable to load UCI package\n", stderr);
exit(1);
}
s = uci_lookup_section(ctx, p, "settings");
if (!s || strcmp(s->type, "autoupdater")) {
fputs("autoupdater: error: unable to load UCI settings\n", stderr);
exit(1);
}
const char *enabled = uci_lookup_option_string(ctx, s, "enabled");
if ((!enabled || strcmp(enabled, "1")) && !settings->force) {
fputs("autoupdater is disabled\n", stderr);
exit(0);
}
const char *version_file = uci_lookup_option_string(ctx, s, "version_file");
if (version_file)
settings->old_version = read_one_line(version_file);
if (!settings->branch)
settings->branch = uci_lookup_option_string(ctx, s, "branch");
if (!settings->branch) {
fputs("autoupdater: error: no branch given in settings or command line\n", stderr);
exit(1);
}
struct uci_section *branch = uci_lookup_section(ctx, p, settings->branch);
if (!branch || strcmp(branch->type, "branch")) {
fprintf(stderr, "autoupdater: error: unable to load branch configuration for branch '%s'\n", settings->branch);
exit(1);
}
settings->good_signatures = load_positive_number(ctx, branch, "good_signatures");
if (settings->n_mirrors == 0)
settings->mirrors = load_string_list(ctx, branch, "mirror", &settings->n_mirrors);
const char **pubkeys_str = load_string_list(ctx, branch, "pubkey", &settings->n_pubkeys);
settings->pubkeys = malloc(settings->n_pubkeys * sizeof(ecc_25519_work_t));
size_t ignored_keys = 0;
for (size_t i = 0; i < settings->n_pubkeys; i++) {
ecc_int256_t pubkey_packed;
if (!pubkeys_str[i])
goto pubkey_fail;
if (!parsehex(pubkey_packed.p, pubkeys_str[i], 32))
goto pubkey_fail;
if (!ecc_25519_load_packed_legacy(&settings->pubkeys[i-ignored_keys], &pubkey_packed))
goto pubkey_fail;
if (!ecdsa_is_valid_pubkey(&settings->pubkeys[i-ignored_keys]))
goto pubkey_fail;
continue;
pubkey_fail:
fprintf(stderr, "autoupdater: warning: ignoring invalid public key %s\n", pubkeys_str[i]);
ignored_keys++;
}
settings->n_pubkeys -= ignored_keys;
/* Don't free UCI context, we still reference values from it */
}

View File

@ -0,0 +1,48 @@
/*
Copyright (c) 2017, Matthias Schiffer <mschiffer@universe-factory.net>
Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <ecdsautil/ecdsa.h>
struct settings {
bool force;
bool fallback;
bool no_action;
const char *branch;
unsigned long good_signatures;
char *old_version;
size_t n_mirrors;
const char **mirrors;
size_t n_pubkeys;
ecc_25519_work_t *pubkeys;
};
void load_settings(struct settings *settings);

View File

@ -0,0 +1,178 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "uclient.h"
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <limits.h>
#include <stdio.h>
#define TIMEOUT_MSEC 300000
static const char *const user_agent = "Gluon Autoupdater (using libuclient)";
enum uclient_own_error_code {
UCLIENT_ERROR_REDIRECT_FAILED = 32,
UCLIENT_ERROR_TOO_MANY_REDIRECTS,
UCLIENT_ERROR_CONNECTION_RESET_PREMATURELY,
UCLIENT_ERROR_SIZE_MISMATCH,
UCLIENT_ERROR_STATUS_CODE = 1024,
};
const char *uclient_get_errmsg(int code) {
static char http_code_errmsg[16];
if (code & UCLIENT_ERROR_STATUS_CODE) {
snprintf(http_code_errmsg, 16, "HTTP error %d",
code & (~UCLIENT_ERROR_STATUS_CODE));
return http_code_errmsg;
}
switch(code) {
case UCLIENT_ERROR_CONNECT:
return "Connection failed";
case UCLIENT_ERROR_TIMEDOUT:
return "Connection timed out";
case UCLIENT_ERROR_REDIRECT_FAILED:
return "Failed to redirect";
case UCLIENT_ERROR_TOO_MANY_REDIRECTS:
return "Too many redirects";
case UCLIENT_ERROR_CONNECTION_RESET_PREMATURELY:
return "Connection reset prematurely";
case UCLIENT_ERROR_SIZE_MISMATCH:
return "Incorrect file size";
default:
return "Unknown error";
}
}
static void request_done(struct uclient *cl, int err_code) {
uclient_data(cl)->err_code = err_code;
uclient_disconnect(cl);
uloop_end();
}
static void header_done_cb(struct uclient *cl) {
const struct blobmsg_policy policy = {
.name = "content-length",
.type = BLOBMSG_TYPE_STRING,
};
struct blob_attr *tb_len;
if (uclient_data(cl)->retries < 10) {
int ret = uclient_http_redirect(cl);
if (ret < 0) {
request_done(cl, UCLIENT_ERROR_REDIRECT_FAILED);
return;
}
if (ret > 0) {
uclient_data(cl)->retries++;
return;
}
}
switch (cl->status_code) {
case 200:
break;
case 301:
case 302:
case 307:
request_done(cl, UCLIENT_ERROR_TOO_MANY_REDIRECTS);
return;
default:
request_done(cl, UCLIENT_ERROR_STATUS_CODE | cl->status_code);
return;
}
blobmsg_parse(&policy, 1, &tb_len, blob_data(cl->meta), blob_len(cl->meta));
if (tb_len) {
char *endptr;
errno = 0;
unsigned long long val = strtoull(blobmsg_get_string(tb_len), &endptr, 10);
if (!errno && !*endptr && val <= SSIZE_MAX) {
if (uclient_data(cl)->length >= 0 && uclient_data(cl)->length != (ssize_t)val) {
request_done(cl, UCLIENT_ERROR_SIZE_MISMATCH);
return;
}
uclient_data(cl)->length = val;
}
}
}
static void eof_cb(struct uclient *cl) {
request_done(cl, cl->data_eof ? 0 : UCLIENT_ERROR_CONNECTION_RESET_PREMATURELY);
}
ssize_t uclient_read_account(struct uclient *cl, char *buf, int len) {
struct uclient_data *d = uclient_data(cl);
int r = uclient_read(cl, buf, len);
if (r >= 0) {
d->downloaded += r;
if (d->length >= 0 && d->downloaded > d->length) {
request_done(cl, UCLIENT_ERROR_SIZE_MISMATCH);
return -1;
}
}
return r;
}
int get_url(const char *url, void (*read_cb)(struct uclient *cl), void *cb_data, ssize_t len) {
struct uclient_data d = { .custom = cb_data, .length = len };
struct uclient_cb cb = {
.header_done = header_done_cb,
.data_read = read_cb,
.data_eof = eof_cb,
.error = request_done,
};
struct uclient *cl = uclient_new(url, NULL, &cb);
cl->priv = &d;
uclient_set_timeout(cl, TIMEOUT_MSEC);
uclient_connect(cl);
uclient_http_set_request_type(cl, "GET");
uclient_http_reset_headers(cl);
uclient_http_set_header(cl, "User-Agent", user_agent);
uclient_request(cl);
uloop_run();
uclient_free(cl);
if (!d.err_code && d.length >= 0 && d.downloaded != d.length)
return UCLIENT_ERROR_SIZE_MISMATCH;
return d.err_code;
}

View File

@ -0,0 +1,54 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <libubox/uclient.h>
#include <sys/types.h>
struct uclient_data {
/* data that can be passed in by caller and used in custom callbacks */
void *custom;
/* data used by uclient callbacks */
int retries;
int err_code;
ssize_t downloaded;
ssize_t length;
};
inline struct uclient_data * uclient_data(struct uclient *cl) {
return (struct uclient_data *)cl->priv;
}
inline void * uclient_get_custom(struct uclient *cl) {
return uclient_data(cl)->custom;
}
ssize_t uclient_read_account(struct uclient *cl, char *buf, int len);
int get_url(const char *url, void (*read_cb)(struct uclient *cl), void *cb_data, ssize_t len);
const char *uclient_get_errmsg(int code);

View File

@ -0,0 +1,102 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "util.h"
#include <fcntl.h>
#include <glob.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
void run_dir(const char *dir) {
char pat[strlen(dir) + 3];
sprintf(pat, "%s/*", dir);
glob_t globbuf;
if (glob(pat, 0, NULL, &globbuf))
return;
for (size_t i = 0; i < globbuf.gl_pathc; i++) {
char *path = globbuf.gl_pathv[i];
if (access(path, X_OK) < 0)
continue;
pid_t pid = fork();
if (pid < 0) {
fputs("autoupdater: warning: failed to fork: %m", stderr);
continue;
}
if (pid == 0) {
execl(path, path, (char *)NULL);
exit(EXIT_FAILURE);
}
int wstatus;
if (waitpid(pid, &wstatus, 0) != pid) {
fprintf(stderr, "autoupdater: warning: failed waiting for child %d corresponding to %s: ", pid, path);
perror(NULL);
} else if (!WIFEXITED(wstatus)) {
fprintf(stderr, "autoupdater: warning: execution of %s exited abnormally\n", path);
} else if (WEXITSTATUS(wstatus)) {
fprintf(stderr, "autoupdater: warning: execution of %s exited with status code %d\n", path, WEXITSTATUS(wstatus));
}
}
globfree(&globbuf);
}
void randomize(void) {
struct timespec tv;
if (clock_gettime(CLOCK_MONOTONIC, &tv)) {
perror("autoupdater: error: clock_gettime");
exit(1);
}
srandom(tv.tv_nsec);
}
float get_uptime(void) {
FILE *f = fopen("/proc/uptime", "r");
if (f) {
float uptime;
int match = fscanf(f, "%f", &uptime);
fclose(f);
if (match == 1)
return uptime;
}
fputs("autoupdater: error: unable to determine uptime\n", stderr);
exit(1);
}

View File

@ -0,0 +1,30 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
void run_dir(const char *dir);
void randomize(void);
float get_uptime(void);

View File

@ -0,0 +1,92 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "version.h"
#include <ctype.h>
#include <string.h>
static int char_order(char c) {
if (isdigit(c))
return 0;
else if (isalpha(c))
return c;
else if (c == '~')
return -1;
else
return c + 256;
}
bool newer_than(const char *a, const char *b) {
if (a == NULL)
return false;
if (b == NULL)
return true;
while (*a != '\0' && *b != '\0') {
int first_diff = 0;
// compare non-digits character by character
while ((*a != '\0' && !isdigit(*a)) || (*b != '\0' && !isdigit(*b))) {
int ac = char_order(*a);
int bc = char_order(*b);
if (ac != bc)
return ac > bc;
a++;
b++;
}
// ignore leading zeroes
while (*a == '0')
a++;
while (*b == '0')
b++;
// compare numbers digit by digit, but don't return yet in case
// one number is longer (and thus larger) than the other
while (isdigit(*a) && isdigit(*b)) {
if (first_diff == 0)
first_diff = *a - *b;
a++;
b++;
}
// check if one number is larger
if (isdigit(*a))
return true;
if (isdigit(*b))
return false;
if (first_diff != 0)
return first_diff > 0;
}
return false;
}

View File

@ -0,0 +1,30 @@
/*
Copyright (c) 2017, Jan-Philipp Litza <janphilipp@litza.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <stdbool.h>
bool newer_than(const char *a, const char *b);