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:
parent
57c67964e2
commit
49cb4b3fdb
|
@ -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))
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
---
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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 */
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
Loading…
Reference in New Issue