From 49cb4b3fdb8b9db0abb81783b9b3b386a8ed6a92 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Litza Date: Mon, 27 Feb 2017 01:22:48 +0100 Subject: [PATCH] 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] --- admin/autoupdater/Makefile | 22 +- .../files/usr/lib/lua/autoupdater/util.lua | 133 ----- .../files/usr/lib/lua/autoupdater/version.lua | 79 --- admin/autoupdater/files/usr/sbin/autoupdater | 369 -------------- admin/autoupdater/manifest.sample | 4 +- admin/autoupdater/src/CMakeLists.txt | 39 ++ admin/autoupdater/src/autoupdater.c | 476 ++++++++++++++++++ admin/autoupdater/src/hexutil.c | 43 ++ admin/autoupdater/src/hexutil.h | 35 ++ admin/autoupdater/src/manifest.c | 153 ++++++ admin/autoupdater/src/manifest.h | 57 +++ admin/autoupdater/src/settings.c | 178 +++++++ admin/autoupdater/src/settings.h | 48 ++ admin/autoupdater/src/uclient.c | 178 +++++++ admin/autoupdater/src/uclient.h | 54 ++ admin/autoupdater/src/util.c | 102 ++++ admin/autoupdater/src/util.h | 30 ++ admin/autoupdater/src/version.c | 92 ++++ admin/autoupdater/src/version.h | 30 ++ 19 files changed, 1531 insertions(+), 591 deletions(-) delete mode 100644 admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua delete mode 100644 admin/autoupdater/files/usr/lib/lua/autoupdater/version.lua delete mode 100755 admin/autoupdater/files/usr/sbin/autoupdater create mode 100644 admin/autoupdater/src/CMakeLists.txt create mode 100644 admin/autoupdater/src/autoupdater.c create mode 100644 admin/autoupdater/src/hexutil.c create mode 100644 admin/autoupdater/src/hexutil.h create mode 100644 admin/autoupdater/src/manifest.c create mode 100644 admin/autoupdater/src/manifest.h create mode 100644 admin/autoupdater/src/settings.c create mode 100644 admin/autoupdater/src/settings.h create mode 100644 admin/autoupdater/src/uclient.c create mode 100644 admin/autoupdater/src/uclient.h create mode 100644 admin/autoupdater/src/util.c create mode 100644 admin/autoupdater/src/util.h create mode 100644 admin/autoupdater/src/version.c create mode 100644 admin/autoupdater/src/version.h diff --git a/admin/autoupdater/Makefile b/admin/autoupdater/Makefile index c5b3b37..8d3ace5 100644 --- a/admin/autoupdater/Makefile +++ b/admin/autoupdater/Makefile @@ -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)) diff --git a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua deleted file mode 100644 index b6075ea..0000000 --- a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua +++ /dev/null @@ -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 diff --git a/admin/autoupdater/files/usr/lib/lua/autoupdater/version.lua b/admin/autoupdater/files/usr/lib/lua/autoupdater/version.lua deleted file mode 100644 index 797dc51..0000000 --- a/admin/autoupdater/files/usr/lib/lua/autoupdater/version.lua +++ /dev/null @@ -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 diff --git a/admin/autoupdater/files/usr/sbin/autoupdater b/admin/autoupdater/files/usr/sbin/autoupdater deleted file mode 100755 index f810faf..0000000 --- a/admin/autoupdater/files/usr/sbin/autoupdater +++ /dev/null @@ -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) diff --git a/admin/autoupdater/manifest.sample b/admin/autoupdater/manifest.sample index 508e3fd..2b8759e 100644 --- a/admin/autoupdater/manifest.sample +++ b/admin/autoupdater/manifest.sample @@ -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 --- diff --git a/admin/autoupdater/src/CMakeLists.txt b/admin/autoupdater/src/CMakeLists.txt new file mode 100644 index 0000000..44992e9 --- /dev/null +++ b/admin/autoupdater/src/CMakeLists.txt @@ -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) diff --git a/admin/autoupdater/src/autoupdater.c b/admin/autoupdater/src/autoupdater.c new file mode 100644 index 0000000..0c08f3a --- /dev/null +++ b/admin/autoupdater/src/autoupdater.c @@ -0,0 +1,476 @@ +/* + Copyright (c) 2017, Matthias Schiffer + Jan-Philipp Litza + 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#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] [ ...]\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" + " ... 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; +} diff --git a/admin/autoupdater/src/hexutil.c b/admin/autoupdater/src/hexutil.c new file mode 100644 index 0000000..6927b67 --- /dev/null +++ b/admin/autoupdater/src/hexutil.c @@ -0,0 +1,43 @@ +/* + Copyright (c) 2012, Nils Schneider + 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 +#include + + +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; +} diff --git a/admin/autoupdater/src/hexutil.h b/admin/autoupdater/src/hexutil.h new file mode 100644 index 0000000..85aa1d3 --- /dev/null +++ b/admin/autoupdater/src/hexutil.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2012, Nils Schneider + 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 +#include + + +/* 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); diff --git a/admin/autoupdater/src/manifest.c b/admin/autoupdater/src/manifest.c new file mode 100644 index 0000000..0c51c24 --- /dev/null +++ b/admin/autoupdater/src/manifest.c @@ -0,0 +1,153 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include +#include +#include +#include + + +// 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; + } + } +} diff --git a/admin/autoupdater/src/manifest.h b/admin/autoupdater/src/manifest.h new file mode 100644 index 0000000..f121c6d --- /dev/null +++ b/admin/autoupdater/src/manifest.h @@ -0,0 +1,57 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include + +#include +#include +#include + + +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); diff --git a/admin/autoupdater/src/settings.c b/admin/autoupdater/src/settings.c new file mode 100644 index 0000000..cc9d17a --- /dev/null +++ b/admin/autoupdater/src/settings.c @@ -0,0 +1,178 @@ +/* + Copyright (c) 2017, Matthias Schiffer + Jan-Philipp Litza + 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 + +#include +#include + + +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 */ +} diff --git a/admin/autoupdater/src/settings.h b/admin/autoupdater/src/settings.h new file mode 100644 index 0000000..54b98f6 --- /dev/null +++ b/admin/autoupdater/src/settings.h @@ -0,0 +1,48 @@ +/* + Copyright (c) 2017, Matthias Schiffer + Jan-Philipp Litza + 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 + + +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); diff --git a/admin/autoupdater/src/uclient.c b/admin/autoupdater/src/uclient.c new file mode 100644 index 0000000..f9ee31e --- /dev/null +++ b/admin/autoupdater/src/uclient.c @@ -0,0 +1,178 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include + +#include +#include + + +#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; +} diff --git a/admin/autoupdater/src/uclient.h b/admin/autoupdater/src/uclient.h new file mode 100644 index 0000000..4f82df1 --- /dev/null +++ b/admin/autoupdater/src/uclient.h @@ -0,0 +1,54 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include + + +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); diff --git a/admin/autoupdater/src/util.c b/admin/autoupdater/src/util.c new file mode 100644 index 0000000..881523f --- /dev/null +++ b/admin/autoupdater/src/util.c @@ -0,0 +1,102 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include +#include +#include +#include +#include +#include + +#include + + +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); +} diff --git a/admin/autoupdater/src/util.h b/admin/autoupdater/src/util.h new file mode 100644 index 0000000..5c23d79 --- /dev/null +++ b/admin/autoupdater/src/util.h @@ -0,0 +1,30 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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); diff --git a/admin/autoupdater/src/version.c b/admin/autoupdater/src/version.c new file mode 100644 index 0000000..dd4d3ff --- /dev/null +++ b/admin/autoupdater/src/version.c @@ -0,0 +1,92 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 +#include + +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; +} diff --git a/admin/autoupdater/src/version.h b/admin/autoupdater/src/version.h new file mode 100644 index 0000000..6c23fa8 --- /dev/null +++ b/admin/autoupdater/src/version.h @@ -0,0 +1,30 @@ +/* + Copyright (c) 2017, Jan-Philipp Litza + 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 + +bool newer_than(const char *a, const char *b);