...
 
Commits (24)
FROM debian:stable
FROM debian:unstable
ENV HOME=/root
......@@ -7,9 +7,10 @@ RUN \
apt-get -y upgrade && \
apt-get -y install --no-install-recommends \
busybox ca-certificates curl git \
make pkg-config gcc \
make cmake pkg-config gcc \
check cppcheck lua-check valgrind \
libcurl4-openssl-dev libevent-dev libssl-dev \
libcurl4-openssl-dev libevent-dev libssl-dev liburiparser-dev \
libb64-dev uthash-dev \
lua5.1 liblua5.1-0-dev \
asciidoc lcov markdown libcommon-sense-perl \
wget procps && \
......
......@@ -11,7 +11,7 @@ __pycache__
# The desired results
*.html
*.so
/src/opkg-trans/opkg-trans
/src/pkgtransaction/pkgtransaction
/src/pkgupdate/pkgupdate
/src/migrator/pkgmigrate
# Some stuff for debugging
......
......@@ -4,3 +4,6 @@
[submodule "tests/lunit-launch/lunit"]
path = tests/lunit-launch/lunit
url = http://repo.or.cz/lunit.git
[submodule "tests/usign"]
path = tests/usign
url = git://git.openwrt.org/project/usign.git
UPDATER_VERSION := $(shell (git describe --match 'v*' --dirty || echo 'unknown') | sed -e 's/^v//')
LUA_NAME := $(shell for lua in lua5.1 lua-5.1 lua51 lua ; do if pkg-config $$lua ; then echo $$lua ; break ; fi ; done)
VALGRIND:=IN_VALGRIND=1 valgrind --leak-check=full --show-leak-kinds=all --track-fds=yes --trace-children=no --child-silent-after-fork=yes --error-exitcode=1 --track-origins=yes
VALGRIND:=IN_VALGRIND=1 valgrind --leak-check=full --show-leak-kinds=definite,indirect,possible --track-fds=yes --trace-children=no --child-silent-after-fork=yes --error-exitcode=1 --track-origins=yes
# For picosat, it otherwise needs some headers not available on musl for a feature we don't need. And we need trace enabled.
EXTRA_DEFINES := NGETRUSAGE TRACE UPDATER_VERSION='"$(UPDATER_VERSION)"'
ifdef BUSYBOX_EXEC
......
......@@ -7,10 +7,12 @@ configuration scripts.
Dependencies
------------
Binary dependencies:
* C compiler (gcc preferred)
* C compiler (gcc preferred) with C11 support
* Lua 5.1
* libcurl
* libevent2
* libb64
* uthash
Runtime dependencies:
* usign (for signatures validation)
......
......@@ -14,13 +14,13 @@ supported version of Lua on OpenWRT is 5.1, but there should be very
little difference in what we use). Just the set of functions available
is limited to the functions listed here.
Note that using conditions, loops and variables is fully supported
TIP: Using conditions, loops and variables is fully supported
and sometimes desirable.
Security levels
---------------
There are different security levels of the scripts used. The security
There are different security levels of the scripts used. The security
level may further limit the set of commands and the abilities of given
commands. This is to ensure the server may never send malicious
commands covertly (it still can send version of package that contains
......@@ -37,12 +37,10 @@ Full::
(including compiled `.so` modules). This is what the internals of the
updater would be built in.
Local::
It is possible to reference local files and directories as further
configuration scripts. It is possible to read UCI configuration and
execute arbitrary shell commands.
It is possible to read UCI configuration and execute arbitrary
shell commands.
Remote::
The functions may reference only other remote resources, not local
ones. Reading UCI config is not possible.
Reading UCI config is not possible.
Restricted::
It is possible to further restrict the entities referenced to a
string match (eg. ensure that it comes from a given server). Access
......@@ -71,28 +69,6 @@ It is possible to hook some functions in between (after, before)
installation of packages, or even between installation and
configuration.
Script names
------------
Each script has a name. The names form a tree structure and are used
to namespace various kinds of information, most importantly flag
storage.
Having two scripts of the same full name is an error. The name of the
script may be the same if it is referenced from different scripts.
The names are separated by a slash. The top-level built in script has
an empty name, but it doesn't store any information, only references
other scripts.
A script may reference other scripts by the names. An empty string
means itself. Names starting with slash are absolute ‒ they start from
the top-level script. Other names are relative and refer to
sub-scripts.
This is similar to filesystem paths. However, the `.` and `..` names
are not supported here.
URIs
----
......@@ -101,9 +77,9 @@ live in the local filesystem or be on an external server.
These are the types of URIs supported:
* `file://`
* `http://`
* `https://`
* `file://`
* `data:`
The remote ones (`http` and `https`) may need verification of the
......@@ -114,71 +90,61 @@ The `data:` is slightly limited compared to what the standard (RFC
2397) allows. The media type and charset are irrelevant to the
updater and are therefore not supported.
Scripts with access level of `remote` or lower are not allowed to use
the `file://` and `internal:` schemes.
NOTE: In previous versions there was an `internal:` URI but that one is no longer
available and can't be used.
Verification
------------
It is desirable to verify that the scripts and repository indices
weren't tampered with. It isn't needed to verify the packages (unless
they are stand-alone without repository), because the repository index
contains hashes of the packages.
There are two things we may verify. The server certificate (with the
`https` schema) and the file signature.
Each command that takes an URI as a parameter can have following extra
options:
verification::
This specifies how the resource is verified. Possible values are
(case insensitive):
none;;
Doesn't do any verification. This is the default for `file://`,
`data://` and `internal://` URIs.
cert;;
Verify the server's SSL certificate.
sig;;
Verify file signature. This is the default for `http://` URIs.
both;;
Do both `cert` and `sig` verification. This is the default for
`https://` URIs.
sig::
URI where the signature of the resource lives. This one is not
verified. If it isn't specified, it is constructed by adding `.sig`
to the end of the verified URI. The option has effect only with
`sig` and `both` verification.
pubkey::
An URI or table of URIs with trusted public signature keys. These
are not verified (therefore it is recommended to come from a already
verified source ‒ like `data:` URI or `file://` URI). If it is not
specified (`nil`), it is inherited from the verification of the script
running the command. While it has no direct effect if the option is
specified on another verification than `sig` or `both`, it
influences the inheritance. Default value is `{}`.
ca::
An URI or table of URIs with trusted SSL certificate authorities, in
PEM format. Similar notes as with `pubkey` apply. But instead of table
or URI you can also specify special value `system_cas`, which results
into system authorities to be used. `system_cas` is also default value.
crl::
An URI or table of URIs with CRLs relevant to the server. If set into
`no_crl`, CRL is not checked. Note that the `crl` field is also
inherited, therefore you may want to set it manually to `no_crl`.
Default value is `no_crl`.
ocsp::
`true` of `false` if you want or don't want to use OCSP (Online Certificate
Status Protocol). Default value is `true`. Inheritance is same as with
`pubkey`.
The file signature is verified using the `usign` utility.
~~~~~~~~~~~~
Note that while a `remote` or `restricted` script may not specify
local (`file://` and `internal:`) URIs, it may inherit them.
To make remote access secure we need to verify downloaded content. This
is relevant to `http://` and `https://` URIs.
Options relevant to `https://` URI::
ca;;
An URI or table of URIs with trusted SSL certificate authorities, in PEM format.
These URIs are not verified. `true` can be specified and in such case all system
certificates are considered (generally all files in `/etc/ssl/certs`).
If `false` or empty table is specified then CA verification is skipped. If not
specified (or set to `nil`) it is inherited from the verification of script
running the command. In default it is set to `true` so it verifies server
against all installed CA certificates.
crl;;
An URI or table of URIs with CRLs relevant to the server. Can be set to `false`
or empty table and in such case CRL is not checked. If set to `nil` then its
value is inherited from the verification of script running the command. In
default it is set to `{}`.
ocsp;;
`true` of `false` if you want or don't want to use OCSP (Online Certificate
Status Protocol). If set to `nil` then value is inherited from verification of
the script. Default value is `true`.
Options relevant to `http://` and `https://` URIs::
pubkey;;
An URI or table of URIs with trusted public signature keys. These are not
verified (therefore it is recommended to come from a already verified source ‒
like `data:` or `file://` URI). If it is not specified (`nil`), it is inherited
from the verification of the script running the command. If empty table is
specified then no signature checking is done. Default value is `{}`.
sig;;
URI where the signature of the resource lives. This one is not verified. If it
is set to `nil`, it is constructed by adding `.sig` to the end of the verified
URI. The option has effect only if `pubkey` is set so signature checking is
done. In default it's set to `nil`.
URIs specified in these verification options are not verified (default values, not
inherited ones, are used). Because of that it is suggested to used only
trusted/secure URIs for that purpose. Suggested are `file://` and `data://`.
NOTE: Another option `verification` exist. It was originally used for verification
level specification but that is now replaced with `pubkey` and `ca` option
specific values. For backward compatibility it is silently ignored.
NOTE: For `ca` option there is also constant `system_cas` and for `crl` option
there is constant `no_crl`. These are obsoleted but are still defined.
`system_cas` is defined as `true` and `no_crl` is defined as `false`.
`system_cas`
Dependency description
----------------------
......@@ -221,12 +187,10 @@ Most of the commands has following format:
Command("string", "string", {param = 1})
They start with a capital letter, since they act as constructors.
Script
~~~~~~
script = Script("uri", { extra })
Script("uri", { extra })
This command runs another script.
......@@ -242,89 +206,72 @@ security::
raise the level, such attempt is reported as an error. If not
specified, the level is deduced from the URI. If the URI is remote,
it doesn't go above `remote`, otherwise it doesn't go above `local`.
ignore::
Ignore certain errors. If they happen, don't process such script,
but continue with the rest. This is a lua table with strings, each
one specifying a category of erorrs to ignore.
missing;;
If the script can't be found.
integrity;;
Some signatures don't match.
verification::
optional::
Set this to `true` to not fail if retrieval of this script fails.
sig::
pubkey::
ca::
crl::
ocsp::
Options to verify the script integrity.
Note that following format is now marked as obsolete and should not be used:
WARNING: Following format is now marked as obsolete and should not be used:
`Script("script-name", "uri", { extra })`
script = Script("script-name", "uri", { extra })
NOTE: There is also obsoleted extra option `ignore`. This should not be used and
any value set to it is effectively considered to be same as setting `optional` to
`true`.
Repository
~~~~~~~~~~
repository = Repository("repository-name", "uri", { extra })
Repository("repository-name", "uri", { extra })
This command introduces another repository of packages. The name may
This command introduces another repository of packages. The can
be used as a reference from other commands and is used in error
messages. However, every place where the name may be used, the result
of the command may be used instead. It is legal to have multiple
repositories with the same name, but referencing it by name may
produce any of them. Referencing by the result is reliable.
messages. Be aware that collision names are considered error and
such repositories are not considered.
The URI is expected to contain an OpenWRT repository in the format
produced by the buildroot.
Extra parameters are:
subdirs::
If the URI contains multiple subdirectories, each one being a valid
repository, you may list the subdirectories here (as a lua table of
strings). The repository will unify all the subdirectory contents
together to form one huge repository. In case of collision of
packages between the subdirectories, the first one containing a
given package wins (in the order listed here). If this option is not
listed, the repository acts in normal way (the URI directly
containing the packages).
index::
Overrides the URI at which the repository index lives and uses the
main URI as the place where packages are downloaded from. Both
gzipped and plain versions may be in the given URI. If the option is
not listed, it is expected to be in `Packages.gz`. Overriding the
URI is not compatible with the subdirs option.
ignore::
Ignore certain errors. This is a lua table with strings, each
specifying a category of errors to ignore. If there's an error
ignored, the repository acts as being empty. Otherwise, such error
would cause the updater to stop.
missing;;
This error happens if the repository is not found. This can mean,
for example, that the `https` URI where the index
(`https://example.org/repository/Packages.gz`) returns 404.
However, a missing package from the repository is not this kind of
error (and cannot be ignored, because it is discovered late after
planning what to install).
integrity;;
This is when the integrity verification/signature check fails.
This may be caused by manipulation with the content, or by missing
a key on our side.
syntax;;
It happens when the repository index can not be parsed because of
syntax errors.
not listed, it is expected to be in `Packages.gz`.
priority::
In case of a package being available in multiple directories, the
package is taken from the repository with highest priority. In case
of equality, the one introduced first wins. The default when the
option is not specified is 50. The number must be an integer between
0 and 100.
verification::
optional::
Set this to `true` to not fail if it is not possible to receive repository for
any reason or to parse it. This can be due to missing resource or invalid
verification but in both cases this is not fatal for updater execution and it
continues without this repository.
sig::
pubkey::
ca::
crl::
ocsp::
Options to verify the index integrity.
NOTE: There is also obsoleted `subdirs` extra parameter. It was intended to be
used as a simple way to add multiple repositories at once. It had small trick
under its sleeve that it combined all those repositories under one name but that
effectively changes nothing. In new versions of Updater-ng this option is only
emulated and repository with name generated with following script is added
instead: `NAME-SUBDIR` where `NAME` is name of repository and `SUBDIR` is specific
`subdirs` values.
NOTE: There is also obsoleted extra option `ignore`. This should not be used and
any value set to it is effectively considered to be same as setting `optional` to
`true`.
Uninstall
~~~~~~~~~
......@@ -333,8 +280,8 @@ Uninstall
This command takes multiple package names. It ensures none of the
packages is installed.
Note that this is not needed most of the time, since unneeded packages
are removed automatically.
TIP: This is not needed most cases, since unneeded packages are removed
automatically.
Extra options modify the packages preceding them, but only up to the
previous extra options block. Therefore, the first two packages in the
......@@ -389,26 +336,25 @@ critical::
consistent state or be able to at least fix it without access to
network. Other packages may stop working if the update is
interrupted at the wrong time (for example by a power outage), but
would be fixed by another finished updater run.
ignore::
Ignore certain errors regarding the installation request. Note that
errors related to the package itself are modified by the `Package`
command. This takes an array of strings, each string represents one
category of errors to ignore.
missing;;
Don't fail on the package not being available. The package
wouldn't be installed if not available, but the run of the updater
wouldn't be aborted.
Note that a package may be required to be installed or uninstalled
multiple times (for example by multiple scripts). All such
requirements are tried to be met (eg. by unifying the version options,
etc).
would be fixed by another finished updater run. For critical packages
`priority` field is ignored.
optional::
Set this to `true` to not fail if packages is not available from any configured
repository. Be aware that this has implications if form of possible removed
packages from system.
IMPORTANT: Package may be required to be installed or uninstalled multiple times
(for example by multiple scripts). All such requirements are tried to be met (eg.
by unifying the version options, etc).
NOTE: There is also obsoleted but still working option `ignore` which if set to
any boolean true value it is considered as if `optional` extra option would be
set to `true`.
Package
~~~~~~~
package = Package("name", { extra })
Package("name", { extra })
This command allows amending a package from a repository. It allows
for adding dependencies (even negative or alternative dependencies).
......@@ -419,13 +365,6 @@ doesn't really exist, but can participates in the dependency computation.
A package may be amended multiple times. Each time the options are
merged into the package options.
The result may be used instead of a package name in the dependencies
of other packages and in `Install` and `Uninstall` commands.
Also, the name parameter is optional. If it is omitted (either
specified as nil or just left out), an unique name is generated. This
is useful only for virtual packages.
The options are:
virtual::
......@@ -494,19 +433,10 @@ abi_change_deep::
package that changed its ABI. That means if some package is reinstalled because
of change of ABI, all packages that depends on it are also reinstalled and so
on.
ignore::
Ignore listed categories of errors. This takes an array of strings,
each string meaning one category to ignore.
deps;;
Don't error on missing dependencies. Simply install the package
without satisfying the dependency.
validation;;
Install the package despite it failing validation (eg. when having
different checksum).
installation;;
Don't report errors of installation in this package as an error
and don't abort the rest of the installation process even if it is
in an early stage.
NOTE: Originally there was also option `ignore` that allowed pass for different
problems but most of those were not working and usage of them was questionable.
This options is now considered as obsolete and is ignored.
Export and Unexport
~~~~~~~~~~~~~~~~~~~
......@@ -598,34 +528,6 @@ root_dir
Root directory specified from command line or `/` if no such option
was specified. Use this if you are accessing some files.
serial
~~~~~~
The variable contains the serial number of the device. It may be `nil`
in case it is not supported on the given device.
architectures
~~~~~~~~~~~~~
Allowed package architectures (in a table).
model
~~~~~
Content of `/tmp/sysinfo/model`. On non-OpenWRT systems it has to be supplied by
`--model` argument.
board_name
~~~~~~~~~~
Content of `/tmp/sysinfo/board_name`. On non-OpenWRT systems it has to be supplied
by `--board` argument.
turris_version
~~~~~~~~~~~~~~
Content of `/etc/turris-version`. Might be nil on non-Turris systems.
self_version
~~~~~~~~~~~~
......@@ -656,6 +558,12 @@ abi_change_deep::
replan_string::
Updater expects replan to be a string (if this feature isn't set than it's
expected to be only boolean).
relative_uri::
Updater supports relative URI where URI is deduced relative to script in which
it was defined in.
no_returns::
Functions such as `Repository` and `Package` no longer return handler that can
be used in other calls.
installed
~~~~~~~~~
......@@ -683,6 +591,32 @@ The top-level table is instantiated (not generated through
meta-tables), therefore it is possible to get the list of installed
packages.
os_release
~~~~~~~~~~
This is table with parsed content of os-release file. Path to this file is
`etc/os-release` but relative to target root. This means that if you are running
updater on root file system that is not current root then values in this table are
for target not for host system.
This is normal table and you can iterate trough it using `pairs` or you can
directly access specific value by indexing it. List of standard options can be
found https://www.freedesktop.org/software/systemd/man/os-release.html[here].
The most interesting value is `os_release.VERSION` as this contains current system
release version.
This table can be empty if there was no `os-release` file.
host_os_release
~~~~~~~~~~~~~~~
This is table with parsed content of os-release file for host system. Source file
for this is always `/etc/os-release`. See variable os_release for example usage
and expected content.
Table can be empty if there was no `/etc/os-release`.
Export variables to Script
--------------------------
......
......@@ -25,11 +25,18 @@ libupdater_MODULES := \
embed_types \
events \
subprocess \
download \
uri \
uri_lua \
journal \
locks \
picosat \
util \
logging
syscnf \
multiwrite \
logging \
pkgsorter
ifdef COV
libupdater_MODULES += lcoverage.embed
endif
......@@ -39,9 +46,9 @@ endif
libupdater_MODULES_3RDPARTY := picosat-965/picosat
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent libcurl libcrypto
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent libcurl libcrypto liburiparser
# Workaround, lua.pc doesn't containd -ldl, even when it uses dlopen
libupdater_SO_LIBS += dl
libupdater_SO_LIBS += dl b64
LIB_DOCS := \
journal \
......
......@@ -77,10 +77,6 @@ static const char *opt_help[COT_LAST] = {
"--exclude=<name> Exclude this from output.\n",
[COT_USIGN] =
"--usign=<path> Path to usign tool used to verify packages signature. In default /usr/bin/usign.\n",
[COT_MODEL] =
"--model=<model> Set/override target system model (e.g. Turris Omnia)\n",
[COT_BOARD] =
"--board=<board> Set/override target system board (e.g. rtrom01)\n",
[COT_NO_REPLAN] =
"--no-replan Don't replan. Install everyting at once. Use this if updater you are running isn't from packages it installs.\n",
[COT_NO_IMMEDIATE_REBOOT] =
......@@ -102,8 +98,6 @@ enum option_val {
OPT_TASK_LOG_VAL,
OPT_EXCLUDE,
OPT_USIGN,
OPT_MODEL,
OPT_BOARD,
OPT_NO_REPLAN,
OPT_NO_IMMEDIATE_REBOOT,
OPT_OUT_OF_ROOT,
......@@ -127,8 +121,6 @@ static const struct option opt_long[] = {
{ .name = "task-log", .has_arg = required_argument, .val = OPT_TASK_LOG_VAL },
{ .name = "exclude", .has_arg = required_argument, .val = OPT_EXCLUDE },
{ .name = "usign", .has_arg = required_argument, .val = OPT_USIGN },
{ .name = "model", .has_arg = required_argument, .val = OPT_MODEL },
{ .name = "board", .has_arg = required_argument, .val = OPT_BOARD },
{ .name = "no-replan", .has_arg = no_argument, .val = OPT_NO_REPLAN },
{ .name = "no-immediate-reboot", .has_arg = no_argument, .val = OPT_NO_IMMEDIATE_REBOOT },
{ .name = "out-of-root", .has_arg = no_argument, .val = OPT_OUT_OF_ROOT },
......@@ -154,8 +146,6 @@ static const struct simple_opt {
[OPT_TASK_LOG_VAL] = { COT_TASK_LOG, true, true },
[OPT_EXCLUDE] = { COT_EXCLUDE, true, true },
[OPT_USIGN] = { COT_USIGN, true, true },
[OPT_MODEL] = { COT_MODEL, true, true },
[OPT_BOARD] = { COT_BOARD, true, true },
[OPT_NO_REPLAN] = { COT_NO_REPLAN, false, true },
[OPT_NO_IMMEDIATE_REBOOT] = { COT_NO_IMMEDIATE_REBOOT, false, true },
[OPT_OUT_OF_ROOT] = { COT_OUT_OF_ROOT, false, false },
......@@ -276,8 +266,6 @@ struct cmd_op *cmd_args_parse(int argc, char *argv[], const enum cmd_op_type acc
case COT_APPROVE:
case COT_EXCLUDE:
case COT_USIGN:
case COT_MODEL:
case COT_BOARD:
case COT_NO_REPLAN:
case COT_TASK_LOG: {
struct cmd_op tmp = result[i];
......
......@@ -68,10 +68,6 @@ enum cmd_op_type {
COT_EXCLUDE,
// Path to usign tool
COT_USIGN,
// Target model specification
COT_MODEL,
// Target board specification
COT_BOARD,
// Don't replan (do whole install at once)
COT_NO_REPLAN,
// Don't immediatelly reboot system
......
......@@ -35,10 +35,11 @@ local mkdir = mkdir
local stat = stat
local events_wait = events_wait
local run_util = run_util
local uri = require "uri"
module "utils"
-- luacheck: globals lines2set map set2arr arr2set cleanup_dirs dir_ensure mkdirp read_file write_file clone shallow_copy table_merge arr_append exception multi_index private filter_best strip table_overlay randstr arr_prune arr_inv file_exists
-- luacheck: globals lines2set map set2arr arr2set cleanup_dirs dir_ensure mkdirp read_file write_file clone shallow_copy table_merge arr_append exception multi_index private filter_best strip table_overlay table_wrap randstr arr_prune arr_inv file_exists uri_syste_cas uri_no_crl uri_config uri_content
--[[
Convert provided text into set of lines. Doesn't care about the order.
......@@ -357,6 +358,18 @@ function table_overlay(table)
})
end
--[[
This function returns always table. If input is not table then it is placed to
table. If input is table then it is returned as is.
]]
function table_wrap(table)
if type(table) == "table" then
return table
else
return {table}
end
end
--[[
Check whether file exists
]]
......@@ -370,4 +383,51 @@ function file_exists(name)
end
end
--[[
This function applies given table of configuration to given uri object.
This is here because we need bridge between old approach of using lua tables and
approach of inherited settings in uri object.
For full support of all fields see language documentation, section Verification.
Any field that is not set in table is ignored (configuration is not changed).
]]
function uri_config(uriobj, config)
-- TODO and how about veri?
if config.ca ~= nil then
uriobj:set_ssl_verify(config.ca)
uriobj:add_ca(nil)
for ca in pairs(table_wrap(config.ca)) do
uriobj:add_ca(ca)
end
end
if config.crl ~= nil then
uriobj:add_crl(nil)
for crl in pairs(table_wrap(config.crl)) do
uriobj:add_crl(crl)
end
end
if config.ocsp ~= nil then
uriobj:set_ocsp(config.ocsp)
end
if config.pubkey ~= nil then
uriobj:add_pubkey(nil)
for pubkey in pairs(table_wrap(config.pubkey)) do
uriobj:add_pubkey(pubkey)
end
end
if config.sig ~= nil then
uriobj:set_sig(config.sig)
end
end
-- Get content of given URI
-- It returns downloaded content as first argument and uri object as second (which
-- can be used as a parent to other uris)
function uri_content(struri, parent, config)
local master = uri.new()
local u = master:to_buffer(struri, parent)
uri_config(u, config)
-- TODO finish error and others?
return u:finish(), u
end
return _M
--[[
Copyright 2018, CZ.NIC z.s.p.o. (http://www.nic.cz/)
This file is part of the turris updater.
Updater is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Updater is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Updater. If not, see <http://www.gnu.org/licenses/>.
]]--
local os = os
local utils = require "utils"
local getcwd = getcwd
local DIE = DIE
module "syscnf"
-- Variables accessed from outside of this module
-- luacheck: globals root_dir status_file info_dir pkg_download_dir pkg_unpacked_dir dir_opkg_collided target_model target_board
-- Functions that we want to access from outside of this module
-- luacheck: globals set_root_dir set_target
local status_file_suffix = "usr/lib/opkg/status"
local info_dir_suffix = "usr/lib/opkg/info/"
local pkg_unpacked_dir_suffix = "usr/share/updater/unpacked/"
local pkg_download_dir_suffix = "usr/share/updater/download/"
local dir_opkg_collided_suffix = "usr/share/updater/collided/"
--[[
Canonizes path to absolute path. It does no change in case path is already an
absolute but it if not then it prepends current working directory. There is also
special handling in case path starts with tilde (~) in that case that character is
replaced with content of HOME environment variable.
]]
local function path2abspath(path)
if path:match("^/") then
return path
elseif path:match("^~/") then
return os.getenv('HOME') .. "/" .. path
else
return getcwd() .. "/" .. path
end
end
--[[
Set all the configurable directories to be inside the provided dir
Effectively sets that the whole system is mounted under some
prefix.
]]
function set_root_dir(dir)
if dir then
dir = (path2abspath(dir) .. "/"):gsub("/+", "/")
else
dir = "/"
end
-- A root directory
root_dir = dir
-- The file with status of installed packages
status_file = dir .. status_file_suffix
-- The directory where unpacked control files of the packages live
info_dir = dir .. info_dir_suffix
-- A directory to which we download packages
pkg_download_dir = dir .. pkg_download_dir_suffix
-- A directory where unpacked packages live
pkg_unpacked_dir = dir .. pkg_unpacked_dir_suffix
-- Directory where we move files and directories that weren't part of any package.
dir_opkg_collided = dir .. dir_opkg_collided_suffix
end
--[[
Set variables taget_model and target_board.
You can explicitly specify model or board or both. If not specified then detection
is performed. That is files from /tmp/sysinfo directory are used.
If no model or board is specified (passed as nil) and detection failed than this
function causes error and execution termination.
]]
function set_target(model, board)
-- Name of the target model (ex: Turris Omnia)
target_model = model or utils.strip(utils.read_file('/tmp/sysinfo/model'))
-- Name of the target board (ex: rtrom01)
target_board = board or utils.strip(utils.read_file('/tmp/sysinfo/board_name'))
if not target_model or not target_board then
DIE("Auto detection of target model or board failed.You can specify them " ..
"explicitly using --model and --board arguments.")
end
end
......@@ -763,13 +763,13 @@ function steal_configs(current_status, installed_confs, configs)
end
--[[
Move anything on given path to dir_opkg_collided. This backups and removes original files.
Move anything on given path to opkg_collided_dir. This backups and removes original files.
When keep is set to true, file is copied instead of moved.
]]
function user_path_move(path, keep)
-- At first create same parent directory relative to dir_opkg_collided
-- At first create same parent directory relative to opkg_collided_dir
local fpath = ""
for dir in (syscnf.dir_opkg_collided .. path):gsub("[^/]*/?$", ""):gmatch("[^/]+") do
for dir in (syscnf.opkg_collided_dir .. path):gsub("[^/]*/?$", ""):gmatch("[^/]+") do
local randex = ""
while not utils.dir_ensure(fpath .. "/" .. dir .. randex) do
-- If there is file with same name, then append some random extension
......
......@@ -24,21 +24,25 @@ the configuration scripts to be run in.
local pairs = pairs
local ipairs = ipairs
local next = next
local type = type
local pcall = pcall
local string = string
local error = error
local require = require
local tostring = tostring
local assert = assert
local table = table
local unpack = unpack
local utils = require "utils"
local uri = require "uri"
local DBG = DBG
local WARN = WARN
local ERROR = ERROR
module "requests"
-- luacheck: globals known_packages package_wrap known_repositories known_repositories_all repo_serial repository repository_get content_requests install uninstall script package
-- luacheck: globals known_packages known_repositories repositories_uri_master repo_serial repository content_requests install uninstall script package
-- Verifications fields are same for script, repository and package. Lets define them here once and then just append.
local allowed_extras_verification = {
......@@ -95,19 +99,6 @@ local function extra_check_package_type(pkg, field)
end
end
-- Common check for accepted values in table
local function extra_check_table(field, what, table, accepted)
local acc = utils.arr2set(accepted)
for _, v in pairs(table) do
if type(v) ~= "string" then
extra_field_invalid_type(v, field, what)
end
if not acc[v] then
WARN("Unknown value " .. v .. " in table of extra option " .. field .. " for a " .. what)
end
end
end
-- Common check for verification field
local function extra_check_verification(what, extra)
if extra.verification == nil then return end -- we don't care if there is no setting
......@@ -140,7 +131,7 @@ local allowed_package_extras = {
["abi_change"] = utils.arr2set({"table", "boolean"}),
["abi_change_deep"] = utils.arr2set({"table", "boolean"}),
["priority"] = utils.arr2set({"number"}),
["ignore"] = utils.arr2set({"table"})
["ignore"] = utils.arr2set({"table"}), -- obsoleted
}
utils.table_merge(allowed_package_extras, allowed_extras_verification)
......@@ -165,8 +156,7 @@ local function extra_check_deps(what, field, deps)
end
else
invalid(deps)
end
end
end end
--[[
We simply store all package promises, so they can be taken
......@@ -176,6 +166,16 @@ We just store them in an array for future processing.
]]
known_packages = {}
local function new_package(pkg_name, extra)
local pkg = {
tp = "package",
name = pkg_name,
}
utils.table_merge(pkg, extra)
table.insert(known_packages, pkg)
return pkg
end
--[[
This package is just a promise of a real package in the future. It holds the
name and possibly some additional info for the package. Once we go through
......@@ -187,7 +187,7 @@ has been run).
The package has no methods, it's just a stupid structure.
]]
function package(_, pkg, extra)
function package(_, pkg_name, extra)
-- Minimal typo verification. Further verification is done when actually using the package.
extra = allowed_extras_check_type(allowed_package_extras, "package", extra or {})
extra_check_verification("package", extra)
......@@ -218,53 +218,34 @@ function package(_, pkg, extra)
extra_check_package_type(v, name)
end
end
elseif name == "ignore" then
extra_check_table("package", name, value, {"deps", "validation", "installation"})
end
end
local result = {}
utils.table_merge(result, extra)
result.name = pkg
result.tp = "package"
table.insert(known_packages, result)
return result
end
--[[
Either create a new package of that name (if string is passed) or
pass the provided package.
]]
function package_wrap(context, pkg)
if type(pkg) == "table" and pkg.tp == "package" then
-- It is already a package object
return pkg
else
return package(context, pkg)
if extra["ignore"] then -- obsolete
WARN('Package extra option "ignore" is obsolete and is ignored.')
extra["ignore"] = nil
end
new_package(pkg_name, extra)
end
-- List of allowed extra options for a Repository command
local allowed_repository_extras = {
["subdirs"] = utils.arr2set({"table"}),
["index"] = utils.arr2set({"string"}),
["ignore"] = utils.arr2set({"table"}),
["priority"] = utils.arr2set({"number"}),
["optional"] = utils.arr2set({"boolean"}),
["subdirs"] = utils.arr2set({"table"}), -- obsolete
["ignore"] = utils.arr2set({"table"}), -- obsolete
}
utils.table_merge(allowed_repository_extras, allowed_extras_verification)
--[[
The repositories we already created. If there are multiple repos of the
same name, we are allowed to provide any of them. Therefore, this is
indexed by their names.
]]
-- All added known repositories
known_repositories = {}
-- One with all the repositories, even if there are name collisions
known_repositories_all = {}
-- Order of the repositories as they are parsed
-- Order of the repositories as they are introduced
-- We need this to decide in corner case of same repository priority
repo_serial = 1
repositories_uri_master = uri.new()
--[[
Promise of a future repository. The repository shall be downloaded after
all the configuration scripts are run, parsed and used as a source of
......@@ -275,99 +256,78 @@ function repository(context, name, repo_uri, extra)
-- Catch possible typos
extra = allowed_extras_check_type(allowed_repository_extras, 'repository', extra or {})
extra_check_verification("repository", extra)
for name, value in pairs(extra) do
if name == "subdirs" or name == "ignore" then
for _, v in pairs(value) do
if type(v) ~= "string" then
extra_field_invalid_type(v, name, "repository")
end
end
elseif name == "ignore" then
extra_check_table("repository", name, value, {"missing", "integrity", "syntax"})
if extra.ignore then
WARN('Repository extra option "ignore" is obsolete and should not be used. Use "optional" instead.')
if extra.optional == nil then
extra.optional = next(extra.ignore) ~= nil -- if any ignore was specified then set it as optional
end
extra.ignore = nil
end
local result = {}
utils.table_merge(result, extra)
result.repo_uri = repo_uri
utils.private(result).context = context
--[[
Start the download. This way any potential access violation is reported
right away. It also allows for some parallel downloading while we process
the configs.
Pass result as the validation parameter, as all validation info would be
part of the extra.
We do some mangling with the sig URI, since they are not at Package.gz.sig, but at
Package.sig only.
]]
if extra.subdirs then
utils.private(result).index_uri = {}
for _, sub in pairs(extra.subdirs) do
sub = "/" .. sub
local u = repo_uri .. sub .. '/Packages.gz'
local params = utils.table_overlay(result)
params.sig = repo_uri .. sub .. '/Packages.sig'
utils.private(result).index_uri[sub] = uri(context, u, params)
local function register_repo(u, repo_name)
if known_repositories[repo_name] then
ERROR("Repository of name '" .. repo_name "' was already added. Repetition is ignored.")
return
end
else
local u = result.index or repo_uri .. '/Packages.gz'
local params = utils.table_overlay(result)
params.sig = params.sig or u:gsub('%.gz$', '') .. '.sig'
utils.private(result).index_uri = {[""] = uri(context, u, params)}
local iuri = repositories_uri_master:to_buffer(u, context.paret_script_uri)
utils.uri_config(iuri, {unpack(extra), ["sig"] = extra.sig or u:gsub('%.gz$', '') .. '.sig'})
local repo = {
tp = "repository",
index_uri = iuri,
repo_uri = repo_uri,
name = repo_name,
serial = repo_serial,
}
utils.table_merge(repo, extra)
repo.priority = extra.priority or 50
known_repositories[repo_name] = repo
repo_serial = repo_serial + 1
end
result.priority = result.priority or 50
result.serial = repo_serial
repo_serial = repo_serial + 1
result.name = name
result.tp = "repository"
known_repositories[name] = result
table.insert(known_repositories_all, result)
return result
end
-- Either return the repo, if it is one already, or look it up. Nil if it doesn't exist.
function repository_get(repo)
if type(repo) == "table" and (repo.tp == "repository" or repo.tp == "parsed-repository") then
return repo
if extra.subdirs then
WARN('Repository extra option "subdirs" is obsolete and should not be used anymore.')
for _, sub in pairs(extra.subdirs) do
register_repo(repo_uri .. '/' .. sub .. '/' .. (extra.index or 'Packages.gz'), name .. '-' .. sub)
end
else
return known_repositories[repo]
register_repo(repo_uri .. '/' .. (extra.index or 'Packages.gz'), name)
end
end
local allowed_install_extras = {
["priority"] = utils.arr2set({"number"}),
["version"] = utils.arr2set({"string"}),
["repository"] = utils.arr2set({"string", "table"}),
["reinstall"] = utils.arr2set({"boolean"}),
["critical"] = utils.arr2set({"boolean"}),
["ignore"] = utils.arr2set({"table"})
}
-- This is list of all requests to be fulfilled
content_requests = {}
local function content_request(context, cmd, allowed, ...)
local function content_request(cmd, allowed, ...)
local batch = {}
local function submit(extras)
for _, pkg in ipairs(batch) do
pkg = package_wrap(context, pkg)
DBG("Request " .. cmd .. " of " .. (pkg.name or pkg))
local request = {
package = pkg,
tp = cmd
}
extras = allowed_extras_check_type(allowed, cmd, extras)
for name, value in pairs(extras) do
if name == "repository" and type(value) == "table" then
for _, v in pairs(value) do
if type(v) ~= "string" then
extra_field_invalid_type(v, name, cmd)
end
extras = allowed_extras_check_type(allowed, cmd, extras)
if extras.repository then
if type(extras.repository) == "table" then
for _, v in pairs(extras.repository) do
if type(v) ~= "string" then
extra_field_invalid_type(v, "repository", cmd)
end
elseif name == "ignore" then -- note: we don't check what cmd we have as allowed_extras_check_type filters out ignore parameters for uninstall
extra_check_table("cmd", name, value, {"missing"})
end
end
end
if extras.ignore then
-- Note: this is applicable only to Install
WARN('Install extra option "ignore" is obsolete and should not be used. Use "optional" instead.')
if extras.optional == nil then
extras.optional = next(extras.ignore) ~= nil -- if any ignore was specified then set it as optional
end
extras.ignore = nil
end
for _, pkg_name in ipairs(batch) do
DBG("Request " .. cmd .. " of " .. pkg_name)
local request = {
package = new_package(pkg_name, {}),
tp = cmd
}
utils.table_merge(request, extras)
request.priority = request.priority or 50
table.insert(content_requests, request)
......@@ -375,7 +335,7 @@ local function content_request(context, cmd, allowed, ...)
batch = {}
end
for _, val in ipairs({...}) do
if type(val) == "table" and val.tp ~= "package" then
if type(val) == "table" then
submit(val)
else
table.insert(batch, val)
......@@ -384,35 +344,36 @@ local function content_request(context, cmd, allowed, ...)
submit({})
end
function install(context, ...)
return content_request(context, "install", allowed_install_extras, ...)
local allowed_install_extras = {
["priority"] = utils.arr2set({"number"}),
["version"] = utils.arr2set({"string"}),
["repository"] = utils.arr2set({"string", "table"}),
["reinstall"] = utils.arr2set({"boolean"}),
["critical"] = utils.arr2set({"boolean"}),
["optional"] = utils.arr2set({"boolean"}),
["ignore"] = utils.arr2set({"table"}), -- obsolete
}
function install(_, ...)
return content_request("install", allowed_install_extras, ...)
end
local allowed_uninstall_extras = {
["priority"] = utils.arr2set({"number"})
}
function uninstall(context, ...)
return content_request(context, "uninstall", allowed_uninstall_extras, ...)
function uninstall(_, ...)
return content_request("uninstall", allowed_uninstall_extras, ...)
end
local allowed_script_extras = {
["security"] = utils.arr2set({"string"}),
["restrict"] = utils.arr2set({"string"}), -- This is now obsoleted (not used)
["ignore"] = utils.arr2set({"table"})
["optional"] = utils.arr2set({"boolean"}),
["restrict"] = utils.arr2set({"string"}), -- obsolete
["ignore"] = utils.arr2set({"table"}), -- obsolete
}
utils.table_merge(allowed_script_extras, allowed_extras_verification)
--[[
We want to insert these options into the new context, if they exist.
]]
local script_insert_options = {
pubkey = true,
ca = true,
crl = true,
ocsp = true
}
--[[
Note that we have filler field just for backward compatibility so when we have
just one argument or two arguments where second one is table we move all arguments
......@@ -427,40 +388,33 @@ function script(context, filler, script_uri, extra)
else
WARN("Syntax \"Script('script-name', 'uri', { extra })\" is deprecated and will be removed.")
end
DBG("Running script " .. script_uri)
extra = allowed_extras_check_type(allowed_script_extras, 'script', extra or {})
extra_check_verification("script", extra)
for name, value in pairs(extra) do
if name == "ignore" then
extra_check_table("script", script_uri, value, {"missing", "integrity"})
if extra.ignore then
WARN('Script extra option "ignore" is obsolete and should not be used. Use "optional" instead.')
if extra.optional == nil then
extra.optional = next(extra.ignore) ~= nil
end
end
local result = {}
local u = uri(context, script_uri, extra)
local ok, content = u:get()
local ok, content, u = pcall(utils.uri_content, script_uri, context.paret_script_uri, extra)
if not ok then
if utils.arr2set(extra.ignore or {})["missing"] then
WARN("Script " .. script_uri .. " not found, but ignoring its absence as requested")
result.tp = "script"
result.name = script_uri
result.ignored = true
return result
if extra.optional then
WARN("Script " .. script_uri .. " wasn't executed: " .. content)
return
end
-- If couldn't get the script, propagate the error
error(content)
end
DBG("Running script " .. script_uri)
-- Resolve circular dependency between this module and sandbox
local sandbox = require "sandbox"
if extra.security and not context:level_check(extra.security) then
error(utils.exception("access violation", "Attempt to raise security level from " .. tostring(context.sec_level) .. " to " .. extra.security))
end
-- Insert the data related to validation, so scripts inside can reuse the info
local merge = {}
for name in pairs(script_insert_options) do
if extra[name] ~= nil then
merge[name] = utils.clone(extra[name])
end
end
local merge = {
-- Note: this uri does not contain any data (it was finished) so we use it only as paret for meta data
["parent_script_uri"] = u
}
local err = sandbox.run_sandboxed(content, script_uri, extra.security, context, merge)
if err and err.tp == 'error' then
if not err.origin then
......@@ -468,10 +422,6 @@ function script(context, filler, script_uri, extra)
end
error(err)
end
-- Return a dummy handle, just as a formality
result.tp = "script"
result.uri = script_uri
return result
end
return _M
......@@ -33,14 +33,11 @@ local tostring = tostring
local error = error
local WARN = WARN
local ERROR = ERROR
local run_command = run_command
local events_wait = events_wait
local get_updater_version = get_updater_version
local utils = require "utils"
local backend = require "backend"
local requests = require "requests"
local syscnf = require "syscnf"
local uri = require "uri"
local uci_ok, uci = pcall(require, "uci")
module "sandbox"
......@@ -54,7 +51,9 @@ local updater_features = utils.arr2set({
'conflicts',
'abi_change',
'abi_change_deep',
'replan_string'
'replan_string',
'relative_uri',
'no_returns'
})
-- Available functions and "constants" from global environment
......@@ -93,8 +92,8 @@ local local_available_funcs = {
local rest_additional_funcs = {
{"version_match", backend.version_match},
{"version_cmp", backend.version_cmp},
{"system_cas", uri.system_cas},
{"no_crl", uri.no_crl}
{"system_cas", true},
{"no_crl", false}
}
state_vars = nil
......@@ -120,12 +119,11 @@ function load_state_vars()
]]
state_vars = {
root_dir = syscnf.root_dir,
model = syscnf.target_model,
board_name = syscnf.target_board,
turris_version = utils.strip(utils.read_file('/etc/turris-version')),
self_version = get_updater_version(),
language_version = 1,
features = updater_features,
os_release = syscnf.os_release(),
host_os_release = syscnf.host_os_release(),
--[[
In case we fail to read that file (it is not there), we match against
an empty string, which produces nil ‒ the element won't be in there.
......@@ -147,11 +145,6 @@ function load_state_vars()
end
end)
}
events_wait(run_command(function (ecode, _, stdout, _)
if ecode == 0 then
state_vars.serial = utils.strip(stdout)
end
end, nil, nil, -1, -1, '/usr/bin/atsha204cmd', 'serial-number'))
end
......@@ -404,8 +397,7 @@ function run_sandboxed(chunk, name, sec_level, parent, context_merge, context_mo
end
local context = new(sec_level, parent)
utils.table_merge(context, context_merge or {})
context_mod = context_mod or function () end
context_mod(context)
if context_mod then context_mod(context) end
local func = setfenv(chunk, context.env)
local ok, err = pcall(func)
if ok then
......
--[[
Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
This file is part of the turris updater.
Updater is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Updater is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Updater. If not, see <http://www.gnu.org/licenses/>.
]]--
--[[
This module prepares and manipulates contexts and environments for
the configuration scripts to be run in.
]]
local error = error
local ipairs = ipairs
local pairs = pairs
local tonumber = tonumber
local tostring = tostring
local pcall = pcall
local type = type
local require = require
local unpack = unpack
local setmetatable = setmetatable
local table = table
local os = os
local io = io
local string = string
local events_wait = events_wait
local download = download
local run_command = run_command
local run_util = run_util
local utils = require "utils"
local TRACE = TRACE
local sha256 = sha256
module "uri"
-- luacheck: globals wait signature_check parse new system_cas no_crl usign_exec_set
-- Constants used for inheritance breakage
system_cas = "uri_system_cas"
no_crl = "uri_no_crl"
local function percent_decode(text)
return text:gsub('%%(..)', function (encoded)
local cnum = tonumber(encoded, 16)
if not cnum then
error(utils.exception("bad value", encoded .. " is not a hex number"))
end
return string.char(cnum)
end)
end
--[[
The following function is borrowed from http://lua-users.org/wiki/BaseSixtyFour
-- Lua 5.1+ base64 v3.0 (c) 2009 by Alex Kloss <alexthkloss@web.de>
-- licensed under the terms of the LGPL2
]]
-- character table string
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-- decoding
local function base64_decode(data)
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
-- End of borrowed function.
local function handler_data(uri, err_cback, done_cback)
local params, data = uri:match('^data:([^,]*),(.*)')
if not data then
return err_cback(utils.exception("malformed URI", "It doesn't look like data URI"))
end
local ok, result = pcall(percent_decode, data)
if ok then
data = result
else
return err_cback(utils.exception("malformed URI", "Bad URL encoding"))
end
params = utils.lines2set(params, ';')
if params['base64'] then
local ok, result = pcall(base64_decode, data)
if ok then
data = result
else
return err_cback(utils.exception("malformed URI", "Bad base64 data"))
end
end
-- Once decoded, this is complete ‒ nothing asynchronous about this URI
done_cback(data)
end
local function handler_file(uri, err_cback, done_cback)
local fname = uri:match('^file://(.*)')
if not fname then
return err_cback(utils.exception("malformed URI", "Not a file:// URI"))
end
local ok
ok, fname = pcall(percent_decode, fname)
if not ok then
return err_cback(utils.exception("malformed URI", "Bad URL encoding"))
end
local ok, content, err = pcall(utils.read_file, fname)
if (not ok) or (not content) then
return err_cback(utils.exception("unreachable", tostring(content or err)))
end
done_cback(content)
end
-- Actually, both for http and https
local function handler_http(uri, err_cback, done_cback, ca, crl, ocsp, use_ssl)
return download(function (status, answer)