summaryrefslogtreecommitdiffstatshomepage
path: root/shared.c
Commit message (Expand)AuthorAge
* Fix gcc 8.1.1 compiler warningsJason A. Donenfeld2018-07-04
* extra-head-content: introduce another option for meta tagsJason A. Donenfeld2018-07-03
* git: update to v2.18.0Christian Hesse2018-06-27
* snapshot: strip bit from struct cgit_snapshot_formatChristian Hesse2018-06-27
* snapshot: support special value 'all' to enable all formatsChristian Hesse2018-06-27
* git: update to v2.16.0Christian Hesse2018-01-19
* git: update to v2.14Jeff Smith2017-08-10
* git: update to v2.13.4Christian Hesse2017-08-10
* shared: remove unused function strlpart()Christian Hesse2016-10-12
* shared: remove unused function strrpart()Christian Hesse2016-10-12
* shared: make cgit_free_taginfo() publicJohn Keeping2016-10-01
* shared: remove return value from cgit_free_commitinfo()John Keeping2016-10-01
* git: update to v2.10.0Christian Hesse2016-09-04
* ui-shared: add homepage to tabsJason A. Donenfeld2016-02-22
* ui-plain: add enable-html-serving flagJason A. Donenfeld2016-01-14
* git: update to v2.7.0Christian Hesse2016-01-13
* Avoid use of non-reentrant functionsJason A. Donenfeld2015-10-09
* mime: rewrite detection functionJason A. Donenfeld2015-08-17
* refactor get_mimetype_from_file() to get_mimetype_for_filename()Christian Hesse2015-08-17
* move get_mimetype_from_file() to sharedChristian Hesse2015-08-17
* Remove redundant includesJohn Keeping2015-08-13
* shared: make cgit_diff_tree_cb publicJohn Keeping2015-08-12
* git: update to v2.5.0Christian Hesse2015-08-12
* shared: make some variables 'static'John Keeping2015-03-09
* Add repo.hide and repo.ignoreLukas Fleischer2015-01-29
* repolist: add owner-filterChris Burroughs2014-12-23
* diffstat: do not rely on uninitialized dataJason A. Donenfeld2014-02-20
* filter: add support for email filterJason A. Donenfeld2014-01-14
* filter: split filter functions into their own fileJason A. Donenfeld2014-01-10
* filter: make exit status localJason A. Donenfeld2014-01-10
* Refactor cgit_parse_snapshots_mask()Lukas Fleischer2014-01-10
* Disallow use of undocumented snapshot delimitersLukas Fleischer2014-01-10
* Update copyright informationLukas Fleischer2014-01-08
* shared.c: use die_errno() where appropriateJohn Keeping2013-05-22
* Add branch-sort and repo.branch-sort options.Jason A. Donenfeld2013-04-10
* shared.c: add strbuf_ensure_endJohn Keeping2013-04-08
* Fix out-of-bounds memory accesses with virtual_root=""John Keeping2013-04-08
* Free reflists after usageLukas Fleischer2013-03-04
* Mark several functions/variables staticLukas Fleischer2013-03-04
* White space around control verbs.Jason A. Donenfeld2013-03-04
* Fix several whitespace errorsLukas Fleischer2013-03-04
* Update git to v1.7.5.4John Keeping2013-03-02
* ui-log: Add "commit-sort" option for controlling commit orderingTobias Bieniek2012-10-17
* scan-tree: Unify gitweb.* and cgit.* settings into one config option.Jason A. Donenfeld2012-10-17
* Merge branch 'jp/defbranch'Lars Hjemli2012-03-18
|\
| * Only guess default branch when a repo page is requestedLars Hjemli2011-06-20
* | Merge branch 'lh/module-links'Lars Hjemli2012-03-18
|\ \
| * | ui-tree.c: add support for path-selected submodule linksLars Hjemli2011-06-15
| |/
* | shared.c: Only setenv() if value is non-nullLukas Fleischer2012-01-03
* | shared.c: Remove unused "linux/limits.h" includeLukas Fleischer2012-01-03
|/
800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
--
-- Requirements:
-- 	luacrypto >= 0.3
-- 	<http://mkottman.github.io/luacrypto/>
-- 	lualdap >= 1.2
-- 	<http://git.zx2c4.com/lualdap/about/>
--


--
--
-- Configure these variables for your settings.
--
--

-- A list of password protected repositories, with which gentooAccess
-- group is allowed to access each one.
local protected_repos = {
	glouglou = "infra",
	portage = "dev"
}


-- All cookies will be authenticated based on this secret. Make it something
-- totally random and impossible to guess. It should be large.
local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM"



--
--
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
--
--

-- Sets HTTP cookie headers based on post and sets up redirection.
function authenticate_post()
	local redirect = validate_value("redirect", post["redirect"])

	if redirect == nil then
		not_found()
		return 0
	end

	redirect_to(redirect)
	
	local groups = gentoo_ldap_user_groups(post["username"], post["password"])
	if groups == nil then
		set_cookie("cgitauth", "")
	else
		-- One week expiration time
		set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
	end

	html("\n")
	return 0
end


-- Returns 1 if the cookie is valid and 0 if it is not.
function authenticate_cookie()
	local required_group = protected_repos[cgit["repo"]]
	if required_group == nil then
		-- We return as valid if the repo is not protected.
		return 1
	end
	
	local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
	if user_groups == nil or user_groups == "" then
		return 0
	end
	for group in string.gmatch(user_groups, "[^,]+") do
		if group == required_group then
			return 1
		end
	end
	return 0
end

-- Prints the html for the login form.
function body()
	html("<h2>Gentoo LDAP Authentication Required</h2>")
	html("<form method='post' action='")
	html_attr(cgit["login"])
	html("'>")
	html("<input type='hidden' name='redirect' value='")
	html_attr(secure_value("redirect", cgit["url"], 0))
	html("' />")
	html("<table>")
	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
	html("</table></form>")

	return 0
end

--
--
-- Gentoo LDAP support.
--
--

local lualdap = require("lualdap")

function gentoo_ldap_user_groups(username, password)
	-- Ensure the user is alphanumeric
	if username:match("%W") then
		return nil
	end

	local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"

	local ldap, err = lualdap.open_simple {
		uri = "ldap://ldap1.gentoo.org",
		who = who,
		password = password,
		starttls = true,
		certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
		keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
		cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
	}
	if ldap == nil then
		return nil
	end

	local group_suffix = ".group"
	local group_suffix_len = group_suffix:len()
	local groups = {}
	for dn, attribs in ldap:search { base = who, scope = "subtree" } do
		local access = attribs["gentooAccess"]
		if dn == who and access ~= nil then
			for i, v in ipairs(access) do
				local vlen = v:len()
				if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
					table.insert(groups, v:sub(1, vlen - group_suffix_len))
				end
			end
		end
	end

	ldap:close()

	return groups
end

--
--
-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
--
--

local actions = {}
actions["authenticate-post"] = authenticate_post
actions["authenticate-cookie"] = authenticate_cookie
actions["body"] = body

function filter_open(...)
	action = actions[select(1, ...)]

	http = {}
	http["cookie"] = select(2, ...)
	http["method"] = select(3, ...)
	http["query"] = select(4, ...)
	http["referer"] = select(5, ...)
	http["path"] = select(6, ...)
	http["host"] = select(7, ...)
	http["https"] = select(8, ...)

	cgit = {}
	cgit["repo"] = select(9, ...)
	cgit["page"] = select(10, ...)
	cgit["url"] = select(11, ...)
	cgit["login"] = select(12, ...)

end

function filter_close()
	return action()
end

function filter_write(str)
	post = parse_qs(str)
end


--
--
-- Utility functions based on keplerproject/wsapi.
--
--

function url_decode(str)
	if not str then
		return ""
	end
	str = string.gsub(str, "+", " ")
	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
	str = string.gsub(str, "\r\n", "\n")
	return str
end

function url_encode(str)
	if not str then
		return ""
	end
	str = string.gsub(str, "\n", "\r\n")
	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
	str = string.gsub(str, " ", "+")
	return str
end

function parse_qs(qs)
	local tab = {}
	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
		tab[url_decode(key)] = url_decode(val)
	end
	return tab
end

function get_cookie(cookies, name)
	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
	return string.match(cookies, ";" .. name .. "=(.-);")
end


--
--
-- Cookie construction and validation helpers.
--
--

local crypto = require("crypto")

-- Returns value of cookie if cookie is valid. Otherwise returns nil.
function validate_value(expected_field, cookie)
	local i = 0
	local value = ""
	local field = ""
	local expiration = 0
	local salt = ""
	local hmac = ""

	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
		return nil
	end

	for component in string.gmatch(cookie, "[^|]+") do
		if i == 0 then
			field = component
		elseif i == 1 then
			value = component
		elseif i == 2 then
			expiration = tonumber(component)
			if expiration == nil then
				expiration = -1
			end
		elseif i == 3 then
			salt = component
		elseif i == 4 then
			hmac = component
		else
			break
		end
		i = i + 1
	end

	if hmac == nil or hmac:len() == 0 then
		return nil
	end

	-- Lua hashes strings, so these comparisons are time invariant.
	if hmac ~= crypto.hmac.digest("sha1", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, secret) then
		return nil
	end

	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
		return nil
	end

	if url_decode(field) ~= expected_field then
		return nil
	end

	return url_decode(value)
end

function secure_value(field, value, expiration)
	if value == nil or value:len() <= 0 then
		return ""
	end

	local authstr = ""
	local salt = crypto.hex(crypto.rand.bytes(16))
	value = url_encode(value)
	field = url_encode(field)
	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
	authstr = authstr .. "|" .. crypto.hmac.digest("sha1", authstr, secret)
	return authstr
end

function set_cookie(cookie, value)
	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
		html("; secure")
	end
	html("\n")
end

function redirect_to(url)
	html("Status: 302 Redirect\n")
	html("Cache-Control: no-cache, no-store\n")
	html("Location: " .. url .. "\n")
end

function not_found()
	html("Status: 404 Not Found\n")
	html("Cache-Control: no-cache, no-store\n\n")
end