diff options
-rw-r--r-- | testing/aaudit/APKBUILD | 63 | ||||
-rw-r--r-- | testing/aaudit/aaudit | 9 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-create | 20 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-emaildiff | 61 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-import-tar | 194 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-refresh | 16 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-repo-create | 51 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-repo-update | 25 | ||||
-rw-r--r-- | testing/aaudit/aaudit-server.conf | 29 | ||||
-rwxr-xr-x | testing/aaudit/aaudit-shell | 14 | ||||
-rw-r--r-- | testing/aaudit/aaudit.conf | 27 | ||||
-rw-r--r-- | testing/aaudit/aaudit.lua | 267 |
12 files changed, 433 insertions, 343 deletions
diff --git a/testing/aaudit/APKBUILD b/testing/aaudit/APKBUILD index 471f4bb34b..f471ed92fa 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 0000000000..489fd30164 --- /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 450dec9e6e..0000000000 --- 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 56d7541032..0000000000 --- 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 47de805fe5..0000000000 --- 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 c2ff334b59..0000000000 --- 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 0000000000..7b3ce91220 --- /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 0000000000..3aa4cc5854 --- /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 0000000000..34ad9b2cec --- /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 0000000000..73ebd2e7eb --- /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 5786225a40..0000000000 --- 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 0000000000..915c177e71 --- /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 |