summaryrefslogtreecommitdiffstats
path: root/testing
diff options
context:
space:
mode:
authorTimo Teräs <timo.teras@iki.fi>2014-05-08 17:06:40 +0300
committerTimo Teräs <timo.teras@iki.fi>2014-05-08 17:07:07 +0300
commitf0df41eacf24e7e7f2acf680eb0ad45c215430b7 (patch)
tree75bb1e3d67e08aaf424a6183521f97c67e638281 /testing
parent7e41e855da70522b776e11a230448c80340e72f3 (diff)
downloadaports-f0df41eacf24e7e7f2acf680eb0ad45c215430b7.tar.bz2
aports-f0df41eacf24e7e7f2acf680eb0ad45c215430b7.tar.xz
testing/aaudit: convert everything to lua, start serverside ssh integration
Diffstat (limited to 'testing')
-rw-r--r--testing/aaudit/APKBUILD63
-rw-r--r--testing/aaudit/aaudit9
-rwxr-xr-xtesting/aaudit/aaudit-create20
-rwxr-xr-xtesting/aaudit/aaudit-emaildiff61
-rwxr-xr-xtesting/aaudit/aaudit-import-tar194
-rwxr-xr-xtesting/aaudit/aaudit-refresh16
-rwxr-xr-xtesting/aaudit/aaudit-repo-create51
-rwxr-xr-xtesting/aaudit/aaudit-repo-update25
-rw-r--r--testing/aaudit/aaudit-server.conf29
-rwxr-xr-xtesting/aaudit/aaudit-shell14
-rw-r--r--testing/aaudit/aaudit.conf27
-rw-r--r--testing/aaudit/aaudit.lua267
12 files changed, 433 insertions, 343 deletions
diff --git a/testing/aaudit/APKBUILD b/testing/aaudit/APKBUILD
index 471f4bb34..f471ed92f 100644
--- a/testing/aaudit/APKBUILD
+++ b/testing/aaudit/APKBUILD
@@ -1,44 +1,57 @@
# Contributor: Timo Teräs <timo.teras@iki.fi>
# Maintainer: Timo Teräs <timo.teras@iki.fi>
pkgname=aaudit
-pkgver=0.2
+pkgver=0.3
pkgrel=0
pkgdesc="Alpine Auditor"
url="http://alpinelinux.org"
arch="noarch"
license="GPL"
-depends="lua5.2 lua-posix git"
+depends=""
makedepends=""
install=""
-subpackages=""
+subpackages="$pkgname-server"
replaces=""
-source_libexec="aaudit-emaildiff aaudit-import-tar"
-source_bin="aaudit-create aaudit-refresh"
-source="$source_libexec $source_bin aaudit.conf"
+client_bin="aaudit"
+server_bin="aaudit-repo-create aaudit-repo-update aaudit-shell"
+server_lua="aaudit.lua"
+source="$client_bin $server_lua $server_bin aaudit-server.conf"
build() {
return 0
}
package() {
- mkdir -p "$pkgdir"/etc/aaudit "$pkgdir"/usr/bin "$pkgdir"/usr/libexec/aaudit
- cp aaudit.conf "$pkgdir"/etc/aaudit
- cp $source_bin "$pkgdir"/usr/bin
- cp $source_libexec "$pkgdir"/usr/libexec/aaudit
+ mkdir -p "$pkgdir"/usr/bin
+ cp $client_bin "$pkgdir"/usr/bin
}
-md5sums="6ab0ebec3419a4c495a1935a07d4825c aaudit-emaildiff
-a85e99fa4ad3845a78104763444b21bb aaudit-import-tar
-5dafe6078c114ac0a445dcf0633371cd aaudit-create
-9f43a5cd22d8176fab45903642be878a aaudit-refresh
-5a6da6c58f46ecede9553c3d183cf384 aaudit.conf"
-sha256sums="56ec6e2c13a5e857ae604264a424fd8c6dc04bf37122d88197ddbbb92e42b560 aaudit-emaildiff
-7edad95c6dda08dfa9595a22b796828e3425d1eb27ae585196db85bd2d467b87 aaudit-import-tar
-6643a7c1353253a417a319b0ac8558a348248cb97dcca2c724940350edef47b2 aaudit-create
-cec7b57721006b4e2601db1fa7f02009ebd73c1543c59d0f01ef07fb75644349 aaudit-refresh
-93a841ed9d0079d40649df53240bbfe75d3ee8b9d5eb1f03e455eac0a94869a5 aaudit.conf"
-sha512sums="114d931491faf8f2df71a050a87d2d895a73f48b3948da424f3c1def9da9ec9dec2db96b2a1fefafd25477dc285a010ea95ce0372174b139d081899e09be01d3 aaudit-emaildiff
-b1b96ba344f407bf09fe1ae480b4e5041ae5558bea57364e7998c98107eb19ee98b48c7e896805a550b2adb66f2978543b2106a70786c09af4dbd207b448558f aaudit-import-tar
-85911c1b5e548cfaf417b310abff0d42d0a5a77a49f40584275d55feef30a2c68413c1db70d946709a3bf794dc31ec70bc61c3e50f2a8e1d91e57e13dc6470b1 aaudit-create
-115ef61434dde446abc1b9c67d81338acce133ca595669774392db4b3206ae44659841c65f25e0f70a0dcea6086fd065cc11273e4fa7ff64066298e522a70c90 aaudit-refresh
-ff66efccb6f7a304ad515dad31c8ce4fe20b5adb16856968576b2ff08f03620ec9c23113ae95ff25755fea677f8431d95fd46ade0e3d95291274f4379d0850f3 aaudit.conf"
+server() {
+ depends="lua5.2 lua5.2-posix git"
+
+ mkdir -p "$subpkgdir"/etc/aaudit \
+ "$subpkgdir"/usr/libexec/aaudit \
+ "$subpkgdir"/usr/share/lua/5.2/
+ cp aaudit-server.conf "$subpkgdir"/etc/aaudit
+ cp $server_lua "$subpkgdir"/usr/share/lua/5.2/
+ cp $server_bin "$subpkgdir"/usr/libexec/aaudit
+}
+
+md5sums="e8ea430114aab3f07704060605670e0b aaudit
+c7733c44b464e6e8efe73826d075af17 aaudit.lua
+b11fe0d8285a00a135f8ac9af0206449 aaudit-repo-create
+b900f83afedc4fb1dae2f74c9380fb72 aaudit-repo-update
+0958044c64d1b5c475939687a5620a41 aaudit-shell
+274e2126de7f30170ad6d6acc1bb9ef1 aaudit-server.conf"
+sha256sums="093ded6192adc7ee81ec1e435bac4652355950c30c573cbd0d2f9ab1307f1ade aaudit
+c4e64cd76a23a6e10f944f88904ec7bc511be90af0659526745086fb732530f7 aaudit.lua
+f01ecd5b99cadbc591d8472f6010d34ad3136085aa35c93d7da56b29a251f6c1 aaudit-repo-create
+2c108a129411373be55a4e4add7ca5c005e05f1ca48be813e9903f7ef84f1e7e aaudit-repo-update
+8a24abf3ff360f74afbf408d38ad5336a17f59bf0ec9ff553cae4fe0a4bfc376 aaudit-shell
+23e75c1c935d2cd516c489c0c6835178e864595daef45975da54897296aebcf6 aaudit-server.conf"
+sha512sums="b52acc614c4437ed54f348daeb887aca965b62b4e45bbd1f95b731f5e03b360277476b513254e05306387cdea1f196a86b4d9cf5bbc76916707164b45364521d aaudit
+9d64ba1904639aca31f34aa384cdfce7ddefd17959dfb08904811015343e36959904707ff667879e0fa5587f199ff4dac0213a42d484f983801914dc61ae2899 aaudit.lua
+a8c875eb726e267d6fb56f41cb5c39c45e6f8af8a7a55059bcaff8a0fe8498dac2c90bb21c88e37c34658c574bf2afa8f6ef24e725f602ee1153ba04d9cc84d5 aaudit-repo-create
+e59320cbc6bd7a07687a261399b7df4ef00e349240bee64539a9dfd925b05fb6c679f0f8efb42d1429a7c1d6b918d429a6acb0bc3d4d7f6ef059f9562b748abf aaudit-repo-update
+492f342115dfe1b622601d11edeb2e5bc87512412645c9f242ce5fe870e6c6a5ee333aa8e3dcb7f9b7f72ebce8b8cb88d6446af39255485c0bd3786ab2c81982 aaudit-shell
+b370c408c242cb4d4c349ca2208e69cdc44c750990c8aacb62e2d8b018cdb87e25c5955fe144352f0fd5c41ff0329ed1118fb3a977aa40e13ddb6b115bf4dd2a aaudit-server.conf"
diff --git a/testing/aaudit/aaudit b/testing/aaudit/aaudit
new file mode 100644
index 000000000..489fd3016
--- /dev/null
+++ b/testing/aaudit/aaudit
@@ -0,0 +1,9 @@
+#!/bin/sh
+CONF="/etc/aaudit/aaudit.conf"
+[ -r "$CONF" ] && . "$CONF"
+AAUDIT_USER=${AAUDIT_USER:-aaudit}
+if [ -z "$AAUDIT_SERVER" ]; then
+ echo "Initialize $CONF with AAUDIT_SERVER=<hostname> first!"
+ exit 0
+fi
+exec ssh $AAUDIT_USER@$AAUDIT_SERVER "$@"
diff --git a/testing/aaudit/aaudit-create b/testing/aaudit/aaudit-create
deleted file mode 100755
index 450dec9e6..000000000
--- a/testing/aaudit/aaudit-create
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh -e
-
-IP="$1"
-DESC="$2"
-
-if [ -z "$IP" -o -z "$DESC" ]; then
- echo "usage: $0 <IP> <Description>"
- exit 1
-fi
-
-git init --quiet --bare
-echo "$IP" > aaudit-hostname
-echo "$DESC ($IP)" > description
-
-if ! ssh root@$IP 'lbu package -' | gunzip | /usr/libexec/aaudit/aaudit-import-tar --initial-commit; then
- git branch --quiet -D import
- exit 1
-fi
-git branch --quiet --force master import
-git branch --quiet -D import
diff --git a/testing/aaudit/aaudit-emaildiff b/testing/aaudit/aaudit-emaildiff
deleted file mode 100755
index 56d754103..000000000
--- a/testing/aaudit/aaudit-emaildiff
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/lua5.2
-
-local posix = require 'posix'
-local config_file = "/etc/aaudit/aaudit.conf"
-
-local function load_config(filename)
- local F = io.open(filename, "r")
- local cfg = "return {" .. F:read("*all").. "}"
- F:close()
- return loadstring(cfg, "config:"..filename)()
-end
-
-local function match_file(fn, match_list)
- if not match_list then return false end
- local i, m
- for i, pattern in ipairs(match_list) do
- if posix.fnmatch(pattern, fn) then return true end
- end
- return false
-end
-
-local CONF = load_config(config_file)
-if CONF.notify_email == nil or CONF.smtp_server == nil then return end
-
-local visible, has_data = false, false
-local diff = {}
-for l in io.lines() do
- local fn = l:match("^diff [^ \t]* a/([^ \t]*)")
- if fn then
- visible = not match_file(fn, CONF.no_notify_files)
- if visible then
- has_data = true
- visible = not match_file(fn, CONF.private_files)
- if not visible then
- table.insert(diff, "Private file "..fn.." changed")
- end
- end
- end
- if visible then table.insert(diff, l) end
-end
-
-if has_data then
- local EMAIL = io.popen(string.format("sendmail -t -S %s", CONF.smtp_server), "w")
- EMAIL:write(string.format([[
-From: %s <%s>
-To: %s
-Subject: Configuration change on %s
-Date: %s
-
-This is automatically generated e-mail about the following configuration change:
-
-%s
-]],
- CONF.author_name or "Alpine Auditor", CONF.author_email or "auditor@alpine.local",
- table.concat(CONF.notify_email, ", "),
- arg[1],
- os.date("%a, %d %b %Y %H:%M:%S"),
- table.concat(diff, '\n')
- ))
- EMAIL:close()
-end
diff --git a/testing/aaudit/aaudit-import-tar b/testing/aaudit/aaudit-import-tar
deleted file mode 100755
index 47de805fe..000000000
--- a/testing/aaudit/aaudit-import-tar
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/usr/bin/lua5.2
-
-local posix = require 'posix'
-local branch_ref = "refs/heads/import"
-local from_ref = "refs/heads/master"
-local config_file = "/etc/aaudit/aaudit.conf"
-
-local function load_config(filename)
- local F = io.open(filename, "r")
- local cfg = "return {" .. F:read("*all").. "}"
- F:close()
- return loadstring(cfg, "config:"..filename)()
-end
-
-local function match_file(fn, match_list)
- if not match_list then return false end
- local i, m
- for i, pattern in ipairs(match_list) do
- if posix.fnmatch(pattern, fn) then return true end
- end
- return false
-end
-
-function sortedpairs(t)
- local i, keys, k = 0, {}
- for k in pairs(t) do keys[#keys+1] = k end
- table.sort(keys)
- return function()
- i = i + 1
- if keys[i] then return keys[i], t[keys[i]] end
- end
-end
-
-local function checksum_header(block)
- local sum = 256
- for i = 1,148 do sum = sum + block:byte(i) end
- for i = 157,500 do sum = sum + block:byte(i) end
- return sum
-end
-
-local function nullterm(s) return s:match("^[^%z]*") end
-local function octal_to_number(str) return tonumber(nullterm(str), 8) end
-
-local function read_header_block(block)
- local header = {
- name = nullterm(block:sub(1,100)),
- mode = octal_to_number(block:sub(101,108)),
- uid = octal_to_number(block:sub(109,116)),
- gid = octal_to_number(block:sub(117,124)),
- size = octal_to_number(block:sub(125,136)),
- mtime = octal_to_number(block:sub(137,148)),
- chksum = octal_to_number(block:sub(149,156)),
- typeflag = block:sub(157,157),
- linkname = nullterm(block:sub(158,257)),
- magic = block:sub(258,263),
- version = block:sub(264,265),
- uname = nullterm(block:sub(266,297)),
- gname = nullterm(block:sub(298,329)),
- devmajor = octal_to_number(block:sub(330,337)),
- devminor = octal_to_number(block:sub(338,345)),
- prefix = nullterm(block:sub(346,500)),
- }
- if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
- return false, "Invalid header magic "..header.magic
- end
- if header.version ~= "00" and header.version ~= " \0" then
- return false, "Unknown version "..header.version
- end
- if not checksum_header(block) == header.chksum then
- return false, "Failed header checksum"
- end
- return header
-end
-
-function import_tar(CONF, TAR, GIT, initial_commit)
- local blocksize = 512
- local zeroblock = string.rep("\0", blocksize)
- local nextmark = 1
- local author_time = 0
- local all_files = {}
- local long_name, long_link_name
- local symlinkmode = tonumber('0120000', 8)
- local rwmode = tonumber('0755', 8)
- local romode = tonumber('0644', 8)
- local wandmode = tonumber('0111', 8)
-
- local author_name = CONF.author_name or "Alpine Auditor"
- local author_email = CONF.author_email or "auditor@alpine.local"
-
- while true do
- local block = TAR:read(blocksize)
- if not block then
- return false, "Premature end of archive"
- end
- if block == zeroblock then break end
-
- local header, err = read_header_block(block)
- if not header then return false, err end
-
- local file_data = TAR:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
- if header.typeflag == "L" then
- long_name = nullterm(file_data)
- elseif header.typeflag == "K" then
- long_link_name = nullterm(file_data)
- else
- if long_name then
- header.name = long_name
- long_name = nil
- end
- if long_link_name then
- header.linkname = long_link_name
- long_link_name = nil
- end
- end
-
- if header.typeflag:match("^[0-46]$") and
- not match_file(header.name, CONF.no_track_files) then
- GIT:write('blob\nmark :'..nextmark..'\n')
- if header.typeflag == "2" then
- GIT:write('data '..#header.linkname..'\n'..header.linkname)
- header.mode = symlinkmode
- else
- GIT:write('data '..header.size..'\n')
- GIT:write(file_data)
- end
- GIT:write('\n')
-
- local fn = header.prefix..header.name
- all_files[fn] = { mark=nextmark, mode=header.mode, uname=header.uname, gname=header.gname }
- nextmark = nextmark + 1
- if header.mtime > author_time then author_time = header.mtime end
- end
- end
- if CONF.track_filemode then
- GIT:write("blob\nmark :"..nextmark.."\n")
- GIT:write("data <<END_OF_PERMISSONS\n")
- for path, v in sortedpairs(all_files) do
- GIT:write(string.format("%o %s:%s %s\n", v.mode, v.uname, v.gname, path))
- end
- GIT:write("END_OF_PERMISSONS\n")
- end
-
- GIT:write(string.format([[
-commit %s
-author %s <%s> %d +0000
-committer %s <%s> %d +0000
-data <<END_OF_COMMIT_MESSAGE
-%s
-END_OF_COMMIT_MESSAGE
-
-]],
- branch_ref,
- author_name, author_email, author_time,
- author_name, author_email, os.time(),
- CONF.commit_message or "Changes"
- ))
-
- if not initial_commit then GIT:write(string.format("from %s^0\n", from_ref)) end
- GIT:write("deleteall\n")
- if CONF.track_filemode then
- GIT:write(string.format("M %o :%i %s\n", romode, nextmark, '.permissions.txt'))
- end
- local path, v
- for path, v in pairs(all_files) do
- local mode = v.mode
- if mode ~= symlinkmode then
- if bit32.band(mode, wandmode) then
- mode = rwmode
- else
- mode = romode
- end
- end
- GIT:write(string.format("M %o :%i %s\n", mode, v.mark, path))
- end
- GIT:write("\n")
-
- return true
-end
-
-local initial_commit = false
-local a
-for _, a in ipairs(arg) do
- if a == '--initial-commit' then
- initial_commit = true
- else
- os.exit(1)
- end
-end
-
-local GI = io.popen("git fast-import --quiet", "w")
-local rc = import_tar(load_config(config_file), io.stdin, GI, initial_commit)
-GI:close()
-
-if not rc then os.exit(1) end
diff --git a/testing/aaudit/aaudit-refresh b/testing/aaudit/aaudit-refresh
deleted file mode 100755
index c2ff334b5..000000000
--- a/testing/aaudit/aaudit-refresh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-if [ ! -f aaudit-hostname -o ! -f config -o ! -f description -o ! -d objects ]; then
- echo "run in the created git repository"
- exit 1
-fi
-IP="$(cat aaudit-hostname)"
-if ! ssh root@"$IP" 'lbu package -' | gunzip | /usr/libexec/aaudit/aaudit-import-tar; then
- git branch --quiet -D import
- exit 1
-fi
-if ! git diff --quiet --exit-code master..import; then
- git diff --patch-with-stat master..import | /usr/libexec/aaudit/aaudit-emaildiff "$(cat description)"
- git branch --quiet --force master import
-fi
-git branch --quiet -D import
-git gc --quiet --prune=now
diff --git a/testing/aaudit/aaudit-repo-create b/testing/aaudit/aaudit-repo-create
new file mode 100755
index 000000000..7b3ce9122
--- /dev/null
+++ b/testing/aaudit/aaudit-repo-create
@@ -0,0 +1,51 @@
+#!/usr/bin/lua5.2
+
+local posix = require 'posix'
+local aaudit = require 'aaudit'
+
+local function usage()
+ print("usage: aaudit-repo-create [-a ADDRESS] -d DESCRIPTION [-i COMMIT_IDENTITY] [-m COMMIT_MESSAGE] [-g GROUPS]")
+ os.exit(1)
+end
+
+local C = { initial=true }
+local groups = {}
+local address, description
+
+for ret, optval in posix.getopt(arg, 'a:d:g:i:m:') do
+ if ret == 'a' then
+ address = optval
+ elseif ret == 'd' then
+ description = optval
+ elseif ret == 'g' then
+ groups['"'..optval..'"'] = true
+ elseif ret == 'i' then
+ C.identity = optval
+ elseif ret == 'm' then
+ C.message = optval
+ else
+ usage()
+ end
+end
+
+if not address or not description then usage() end
+
+-- For now default to use address as the repository name
+local repo, repohome = address, aaudit.repohome(address)
+
+-- Create repository + write config
+os.execute(([[
+mkdir -p %s; cd %s
+git init --quiet --bare
+]]):format(repohome, repohome))
+
+aaudit.write_file(("%s/aaudit.conf"):format(repohome), ([[
+address = "%s";
+description = "%s";
+groups = { %s };
+]]):format(address, description, table.concat(groups, ', ')))
+
+aaudit.write_file(("%s/description"):format(repohome), ("%s (%s)"):format(description, address))
+
+-- Initial import of configuration
+aaudit.import_commit(repohome, C)
diff --git a/testing/aaudit/aaudit-repo-update b/testing/aaudit/aaudit-repo-update
new file mode 100755
index 000000000..3aa4cc585
--- /dev/null
+++ b/testing/aaudit/aaudit-repo-update
@@ -0,0 +1,25 @@
+#!/usr/bin/lua5.2
+
+local posix = require 'posix'
+local aaudit = require 'aaudit'
+
+local function usage()
+ print("usage: aaudit-repo-update [-i COMMIT_IDENTITY] [-m COMMIT_MESSAGE]")
+ os.exit(1)
+end
+
+local C = { }
+local address
+for ret, optval in posix.getopt(arg, 'a:i:m:') do
+ if ret == 'a' then
+ address = optval
+ elseif ret == 'i' then
+ C.identity = optval
+ elseif ret == 'm' then
+ C.message = optval
+ else
+ usage()
+ end
+end
+
+aaudit.import_commit(aaudit.repohome(address), C)
diff --git a/testing/aaudit/aaudit-server.conf b/testing/aaudit/aaudit-server.conf
new file mode 100644
index 000000000..34ad9b2ce
--- /dev/null
+++ b/testing/aaudit/aaudit-server.conf
@@ -0,0 +1,29 @@
+-- smtp_server = "<server>";
+
+identities = {
+};
+
+groups = {
+ all = {
+ notify_email = { "engineers@alpine.local" };
+ track_filemode = true;
+
+ no_track = {
+ "*/.git/*",
+ "*.apk-new",
+ "*~",
+ "etc/unbound/root.hints",
+ "etc/chrony/chrony.drift",
+ "etc/ld.so.cache",
+ };
+ no_notify = {
+ "etc/acf/password",
+ };
+ no_diff = {
+ "etc/shadow*",
+ "*.crt",
+ "*.pem",
+ "*.pfx",
+ };
+ };
+};
diff --git a/testing/aaudit/aaudit-shell b/testing/aaudit/aaudit-shell
new file mode 100755
index 000000000..73ebd2e7e
--- /dev/null
+++ b/testing/aaudit/aaudit-shell
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+local ip="${SSH_CLIENT/ */}"
+local identity="$1"
+[ -z "$ip" -o -z "$identity" ] && exit 1
+
+set -- $SSH_ORIGINAL_COMMAND
+cmd="$1"
+shift
+
+case "$cmd" in
+create) /usr/libexec/aaudit/aaudit-repo-create -a "$ip" "$@" -i "$identity" ;;
+commit) /usr/libexec/aaudit/aaudit-repo-update -a "$ip" "$@" -i "$identity" ;;
+esac
diff --git a/testing/aaudit/aaudit.conf b/testing/aaudit/aaudit.conf
deleted file mode 100644
index 5786225a4..000000000
--- a/testing/aaudit/aaudit.conf
+++ /dev/null
@@ -1,27 +0,0 @@
-author_name = "Alpine Auditor";
-author_email = "audit@alpine.local";
-notify_email = { "engineers@alpine.local" };
--- smtp_server = "<server>";
-commit_message = "Changes";
-
-track_filemode = true;
-
-no_track_files = {
- "*/.git/*",
- "*.apk-new",
- "*~",
- "etc/unbound/root.hints",
- "etc/chrony/chrony.drift",
- "etc/ld.so.cache",
-};
-
-no_notify_files = {
- "etc/acf/password",
-};
-
-private_files = {
- "etc/shadow*",
- "*.crt",
- "*.pem",
- "*.pfx",
-};
diff --git a/testing/aaudit/aaudit.lua b/testing/aaudit/aaudit.lua
new file mode 100644
index 000000000..915c177e7
--- /dev/null
+++ b/testing/aaudit/aaudit.lua
@@ -0,0 +1,267 @@
+local M = {}
+
+local posix = require 'posix'
+
+function M.repohome(repo)
+ return ("%s/%s.git"):format(os.getenv("HOME"), repo)
+end
+
+function M.write_file(filename, content)
+ assert(io.open(filename, "w")):write(content):close()
+end
+
+local function load_config(filename)
+ local F = assert(io.open(filename, "r"))
+ local cfg = "return {" .. F:read("*all").. "}"
+ F:close()
+ return loadstring(cfg, "config:"..filename)()
+end
+
+local function merge_bool(a, b) return a or b end
+local function merge_dict(a, b) for k, v in pairs(b) do a[k] = v end return a end
+local function merge_array(a, b) for i=1,#b do a[#a+1] = b[i] end return a end
+
+local function load_repo_configs(repohome)
+ local G = load_config(("%s/aaudit.conf"):format(os.getenv("HOME")))
+ local R = load_config(("%s/aaudit.conf"):format(repohome))
+ -- merge global and per-repository group configs
+ local RG = (G.groups or {}).all
+ for g in pairs(R.groups or {}) do
+ RG.notify_emails = merge_dict(RG.notify_emails, g.notify_emails)
+ RG.track_filemode = merge_bool(RG.track_filemode, g.track_filemode)
+ RG.no_track = merge_array(RG.no_track, g.no_track)
+ RG.no_notify = merge_array(RG.no_notify, g.no_notify)
+ RG.no_diff = merge_array(RG.no_diff, g.no_diff)
+ end
+ return G, R, RG
+end
+
+local function match_file(fn, match_list)
+ if not match_list then return false end
+ local i, m
+ for i, pattern in ipairs(match_list) do
+ if posix.fnmatch(pattern, fn) then return true end
+ end
+ return false
+end
+
+local function sortedpairs(t)
+ local i, keys, k = 0, {}
+ for k in pairs(t) do keys[#keys+1] = k end
+ table.sort(keys)
+ return function()
+ i = i + 1
+ if keys[i] then return keys[i], t[keys[i]] end
+ end
+end
+
+local function checksum_header(block)
+ local sum = 256
+ for i = 1,148 do sum = sum + block:byte(i) end
+ for i = 157,500 do sum = sum + block:byte(i) end
+ return sum
+end
+
+local function nullterm(s) return s:match("^[^%z]*") end
+local function octal_to_number(str) return tonumber(nullterm(str), 8) end
+
+local function read_header_block(block)
+ local header = {
+ name = nullterm(block:sub(1,100)),
+ mode = octal_to_number(block:sub(101,108)),
+ uid = octal_to_number(block:sub(109,116)),
+ gid = octal_to_number(block:sub(117,124)),
+ size = octal_to_number(block:sub(125,136)),
+ mtime = octal_to_number(block:sub(137,148)),
+ chksum = octal_to_number(block:sub(149,156)),
+ typeflag = block:sub(157,157),
+ linkname = nullterm(block:sub(158,257)),
+ magic = block:sub(258,263),
+ version = block:sub(264,265),
+ uname = nullterm(block:sub(266,297)),
+ gname = nullterm(block:sub(298,329)),
+ devmajor = octal_to_number(block:sub(330,337)),
+ devminor = octal_to_number(block:sub(338,345)),
+ prefix = nullterm(block:sub(346,500)),
+ }
+ if header.magic ~= "ustar " and header.magic ~= "ustar\0" then
+ return false, "Invalid header magic "..header.magic
+ end
+ if header.version ~= "00" and header.version ~= " \0" then
+ return false, "Unknown version "..header.version
+ end
+ if not checksum_header(block) == header.chksum then
+ return false, "Failed header checksum"
+ end
+ return header
+end
+
+local function import_tar(TAR, GIT, CI, RG)
+ local branch_ref = "refs/heads/import"
+ local from_ref = "refs/heads/master"
+ local blocksize = 512
+ local zeroblock = string.rep("\0", blocksize)
+ local nextmark = 1
+ local author_time = 0
+ local all_files = {}
+ local long_name, long_link_name
+ local symlinkmode = tonumber('0120000', 8)
+ local rwmode = tonumber('0755', 8)
+ local romode = tonumber('0644', 8)
+ local wandmode = tonumber('0111', 8)
+
+ while true do
+ local block = TAR:read(blocksize)
+ if not block then
+ return false, "Premature end of archive"
+ end
+ if block == zeroblock then break end
+
+ local header, err = read_header_block(block)
+ if not header then return false, err end
+
+ local file_data = TAR:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
+ if header.typeflag == "L" then
+ long_name = nullterm(file_data)
+ elseif header.typeflag == "K" then
+ long_link_name = nullterm(file_data)
+ else
+ if long_name then
+ header.name = long_name
+ long_name = nil
+ end
+ if long_link_name then
+ header.linkname = long_link_name
+ long_link_name = nil
+ end
+ end
+
+ if header.typeflag:match("^[0-46]$") and
+ not match_file(header.name, RG.no_track) then
+ GIT:write('blob\n', 'mark :', nextmark, '\n')
+ if header.typeflag == "2" then
+ GIT:write('data ', tostring(#header.linkname), '\n', header.linkname, '\n')
+ header.mode = symlinkmode
+ else
+ GIT:write('data ', tostring(header.size), '\n', file_data, '\n')
+ end
+ local fn = header.prefix..header.name
+ all_files[fn] = { mark=nextmark, mode=header.mode, uname=header.uname, gname=header.gname }
+ nextmark = nextmark + 1
+ if header.mtime > author_time then author_time = header.mtime end
+ end
+ end
+ if RG.track_filemode then
+ GIT:write('blob\n', 'mark :', nextmark, '\n',
+ 'data <<END_OF_PERMISSONS\n')
+ for path, v in sortedpairs(all_files) do
+ GIT:write(("%o %s:%s %s\n"):format(v.mode, v.uname, v.gname, path))
+ end
+ GIT:write('END_OF_PERMISSONS\n')
+ end
+
+ GIT:write(([[
+commit %s
+author %s <%s> %d +0000
+committer %s <%s> %d +0000
+data <<END_OF_COMMIT_MESSAGE
+%s
+END_OF_COMMIT_MESSAGE
+
+]]):format(branch_ref,
+ CI.identity_name, CI.identity_email, author_time,
+ CI.identity_name, CI.identity_email, os.time(),
+ CI.message or "Changes"))
+
+ if not CI.initial then GIT:write(("from %s^0\n"):format(from_ref)) end
+ GIT:write("deleteall\n")
+ if RG.track_filemode then
+ GIT:write(("M %o :%i %s\n"):format(romode, nextmark, '.permissions.txt'))
+ end
+ local path, v
+ for path, v in pairs(all_files) do
+ local mode = v.mode
+ if mode ~= symlinkmode then
+ if bit32.band(mode, wandmode) then
+ mode = rwmode
+ else
+ mode = romode
+ end
+ end
+ GIT:write(("M %o :%i %s\n"):format(mode, v.mark, path))
+ end
+ GIT:write("\n")
+
+ return true
+end
+
+local function generate_diff(repohome, commit, RG)
+ local DIFF = io.popen(("cd %s; git show %s --"):format(repohome, commit), "r")
+ local visible = true
+ local has_changes, has_visible_changes = false, false
+ local text = {}
+ for l in DIFF:lines() do
+ local fn = l:match("^diff [^ \t]* a/([^ \t]*)")
+ if fn then
+ has_changes = true
+ visible = not match_file(fn, RG.no_notify)
+ if visible then
+ has_visible_changes = true
+ visible = not match_file(fn, RG.no_diff)
+ if not visible then
+ table.insert(text, "Private file "..fn.." changed")
+ end
+ end
+ end
+ if visible then table.insert(text, l) end
+ end
+ DIFF:close()
+ if not has_visible_changes then text = nil end
+ return has_changes, text
+end
+
+local function send_email(addresses, body, CI, G, R, RG)
+ if not body then return end
+ if not RG.notify_emails then return end
+
+ local EMAIL = io.popen(("sendmail -t -S %s"):format(G.smtp_server), "w")
+ EMAIL:write(([[
+From: %s <%s>
+To: %s
+Subject: apkovl changed - %s (%s)
+Date: %s
+
+]]):format( CI.identity_name, CI.identity_email,
+ table.concat(RG.notify_emails, ", "),
+ R.description, R.address,
+ os.date("%a, %d %b %Y %H:%M:%S")))
+
+ for _, l in ipairs(body) do EMAIL:write(l,'\n') end
+ EMAIL:close()
+end
+
+function M.import_commit(repohome, CI)
+ local G, R, RG = load_repo_configs(repohome)
+
+ CI.identity_name, CI.identity_email = table.unpack(G.identities[CI.identity])
+ CI.identity_name = CI.identity_name or "Alpine Auditor"
+ CI.identity_email = CI.identity_email or "auditor@alpine.local"
+
+ local TAR = io.popen(("ssh root@%s 'lbu package -' | gunzip"):format(R.address), "r")
+ local GIT = io.popen(("cd %s; git fast-import --quiet"):format(repohome), "w")
+ local rc, err = import_tar(TAR, GIT, CI, RG)
+ GIT:close()
+ TAR:close()
+ if not rc then return rc, err end
+
+ local has_changes, email_body = generate_diff(repohome, "import", RG)
+ if has_changes then
+ if not CI.initial then send_email(CONF, email_body, CI, G, R, RG) end
+ os.execute(("cd %s; git branch --quiet --force master import; git branch --quiet -D import"):format(repohome))
+ else
+ os.execute(("cd %s; git branch --quiet -D import; git gc --quiet --prune=now"):format(repohome))
+ end
+
+end
+
+return M