local M = {} local posix = require 'posix' local json = require 'cjson' local zlib = require 'zlib' local aac = require 'aaudit.common' local smtp = require 'socket.smtp' local HOME = os.getenv("HOME") M.serverconfig = aac.readconfig(("%s/aaudit-server.json"):format(HOME)) or {} local function merge_bool(a, b) return a or b end local function merge_array(a, b) a = a or {} if b then for i=1,#b do a[#a+1] = b[i] end end return a 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 rfc822_address(id) local identities = M.serverconfig.identities if id == nil then id = "_default" end if identities and identities[id] then id = identities[id] end local name, email = id:match("^(.-) *(<.*>)$") if not email then return ("<%s>"):format(id) end return ("%s %s"):format(name, email) end local function rfc822_email(rfc822) return rfc822:match("(<.*>)$") end function M.sendemail(mail) local to = {} local tocheck = {} local m = { headers = { ["Content-Type"] = 'text/plain; charset=utf8', ["X-RT-Command"] = mail.rtheader, from = rfc822_address(mail.from), subject = mail.subject, }, body = mail.message, } local rcpt = {} for _, addr in ipairs(mail.to) do if tocheck[addr] == nil then tocheck[addr] = true local rfc822 = rfc822_address(addr) table.insert(to, rfc822) table.insert(rcpt, rfc822_email(rfc822)) end end m.headers.to = table.concat(to, ", ") return smtp.send{ from = rfc822_email(m.headers.from), rcpt = rcpt, source = smtp.message(m) } end local rt_keywords = { fix = true, fixes = true, close = true, closes = true, ref = false, refs = false, rt = false, } local function sendcommitdiff(body, req, R, G) if not body then return true end if not G.notify_emails then return true end if #G.notify_emails == 0 then return true end local subject = ("config change - %s (%s)"):format(R.description, R.address) local mail = { from = req.committer, to = G.notify_emails, subject = subject, message = subject .. "\n\n" .. table.concat(body, '\n') } -- Set Request Tracker headers if relevant local rtqueue = M.serverconfig.rtqueue if rtqueue then for k,no in req.message:gmatch("(%a+) #(%d+)") do local action = rt_keywords[k:lower()] if action ~= nil then mail.subject = ("[%s #%s] %s"):format(rtqueue, no, mail.subject) if action == true then mail.rtheader = "Status: resolved" end break end end end -- Send email return M.sendemail(mail) end local function import_tar(TAR, GIT, req, G) local branch_ref = "refs/heads/import" local from_ref = "refs/heads/master" local blocksize = 512 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 not block:match("[^%z]") 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.name == "etc/aaudit/aaudit.json" then local success, res = pcall(json.decode, file_data) if success and res.contact then local contact = res.contact G.notify_emails = merge_array(G.notify_emails, {contact}) if req.local_change then req.author = rfc822_address(res.contact) end end end if header.typeflag:match("^[0-46]$") and not match_file(header.name, G.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 G.track_filemode then GIT:write('blob\n', 'mark :', nextmark, '\n', 'data <