about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README13
-rw-r--r--cgit.c7
-rw-r--r--cgit.h1
-rw-r--r--cgit.mk27
-rw-r--r--cgitrc.5.txt34
-rw-r--r--filter.c255
-rw-r--r--filters/email-gravatar.lua35
-rwxr-xr-xfilters/email-gravatar.py3
-rw-r--r--filters/email-libravatar.lua36
-rw-r--r--filters/file-authentication.lua359
-rw-r--r--filters/gentoo-ldap-authentication.lua360
-rw-r--r--filters/owner-example.lua17
-rw-r--r--filters/simple-authentication.lua314
-rw-r--r--tests/filters/dump.lua17
-rwxr-xr-xtests/setup.sh19
-rwxr-xr-xtests/t0111-filter.sh3
16 files changed, 0 insertions, 1500 deletions
diff --git a/README b/README index 7a6b4a4..371cf21 100644 --- a/README +++ b/README
@@ -32,18 +32,6 @@ This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You
32can configure this location (and a few other things) by providing a `cgit.conf` 32can configure this location (and a few other things) by providing a `cgit.conf`
33file (see the Makefile for details). 33file (see the Makefile for details).
34 34
35If you'd like to compile without Lua support, you may use:
36
37 $ make NO_LUA=1
38
39And if you'd like to specify a Lua implementation, you may use:
40
41 $ make LUA_PKGCONFIG=lua5.1
42
43If this is not specified, the Lua implementation will be auto-detected,
44preferring LuaJIT if many are present. Acceptable values are generally "lua",
45"luajit", "lua5.1", and "lua5.2".
46
47 35
48Dependencies 36Dependencies
49------------ 37------------
@@ -51,7 +39,6 @@ Dependencies
51* libzip 39* libzip
52* libcrypto (OpenSSL) 40* libcrypto (OpenSSL)
53* libssl (OpenSSL) 41* libssl (OpenSSL)
54* optional: luajit or lua, most reliably used when pkg-config is available
55 42
56Apache configuration 43Apache configuration
57-------------------- 44--------------------
diff --git a/cgit.c b/cgit.c index 40a266b..f3fe4c7 100644 --- a/cgit.c +++ b/cgit.c
@@ -964,12 +964,6 @@ static void cgit_parse_args(int argc, const char **argv)
964 for (i = 1; i < argc; i++) { 964 for (i = 1; i < argc; i++) {
965 if (!strcmp(argv[i], "--version")) { 965 if (!strcmp(argv[i], "--version")) {
966 printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION); 966 printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION);
967#ifdef NO_LUA
968 printf("[-] ");
969#else
970 printf("[+] ");
971#endif
972 printf("Lua scripting\n");
973#ifndef HAVE_LINUX_SENDFILE 967#ifndef HAVE_LINUX_SENDFILE
974 printf("[-] "); 968 printf("[-] ");
975#else 969#else
@@ -1051,7 +1045,6 @@ int cmd_main(int argc, const char **argv)
1051 const char *path; 1045 const char *path;
1052 int err, ttl; 1046 int err, ttl;
1053 1047
1054 cgit_init_filters();
1055 atexit(cgit_cleanup_filters); 1048 atexit(cgit_cleanup_filters);
1056 1049
1057 prepare_context(); 1050 prepare_context();
diff --git a/cgit.h b/cgit.h index 69b5c13..72fcd84 100644 --- a/cgit.h +++ b/cgit.h
@@ -385,7 +385,6 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char
385extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv); 385extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
386extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype); 386extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
387extern void cgit_cleanup_filters(void); 387extern void cgit_cleanup_filters(void);
388extern void cgit_init_filters(void);
389 388
390extern void cgit_prepare_repo_env(struct cgit_repo * repo); 389extern void cgit_prepare_repo_env(struct cgit_repo * repo);
391 390
diff --git a/cgit.mk b/cgit.mk index 3fcc1ca..5b9ed5b 100644 --- a/cgit.mk +++ b/cgit.mk
@@ -27,32 +27,6 @@ ifdef NO_C99_FORMAT
27 CFLAGS += -DNO_C99_FORMAT 27 CFLAGS += -DNO_C99_FORMAT
28endif 28endif
29 29
30ifdef NO_LUA
31 LUA_MESSAGE := linking without specified Lua support
32 CGIT_CFLAGS += -DNO_LUA
33else
34ifeq ($(LUA_PKGCONFIG),)
35 LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
36 $(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
37 done)
38 LUA_MODE := autodetected
39else
40 LUA_MODE := specified
41endif
42ifneq ($(LUA_PKGCONFIG),)
43 LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
44 LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
45 LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
46 CGIT_LIBS += $(LUA_LIBS)
47 CGIT_CFLAGS += $(LUA_CFLAGS)
48else
49 LUA_MESSAGE := linking without autodetected Lua support
50 NO_LUA := YesPlease
51 CGIT_CFLAGS += -DNO_LUA
52endif
53
54endif
55
56# Add -ldl to linker flags on systems that commonly use GNU libc. 30# Add -ldl to linker flags on systems that commonly use GNU libc.
57ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) 31ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD))
58 CGIT_LIBS += -ldl 32 CGIT_LIBS += -ldl
@@ -130,7 +104,6 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
130 $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< 104 $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
131 105
132$(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) 106$(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
133 @echo 1>&1 " * $(LUA_MESSAGE)"
134 $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) 107 $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
135 108
136CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS)) 109CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 45db6c7..7dd644a 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt
@@ -632,37 +632,6 @@ specification with the relevant string; available values are:
632'exec:':: 632'exec:'::
633 The default "one process per filter" mode. 633 The default "one process per filter" mode.
634 634
635'lua:'::
636 Executes the script using a built-in Lua interpreter. The script is
637 loaded once per execution of cgit, and may be called multiple times
638 during cgit's lifetime, making it a good choice for repeated filters
639 such as the 'email filter'. It responds to three functions:
640
641 'filter_open(argument1, argument2, argument3, ...)'::
642 This is called upon activation of the filter for a particular
643 set of data.
644 'filter_write(buffer)'::
645 This is called whenever cgit writes data to the webpage.
646 'filter_close()'::
647 This is called when the current filtering operation is
648 completed. It must return an integer value. Usually 0
649 indicates success.
650
651 Additionally, cgit exposes to the Lua the following built-in functions:
652
653 'html(str)'::
654 Writes 'str' to the webpage.
655 'html_txt(str)'::
656 HTML escapes and writes 'str' to the webpage.
657 'html_attr(str)'::
658 HTML escapes for an attribute and writes "str' to the webpage.
659 'html_url_path(str)'::
660 URL escapes for a path and writes 'str' to the webpage.
661 'html_url_arg(str)'::
662 URL escapes for an argument and writes 'str' to the webpage.
663 'html_include(file)'::
664 Includes 'file' in webpage.
665
666 635
667Parameters are provided to filters as follows. 636Parameters are provided to filters as follows.
668 637
@@ -696,9 +665,6 @@ auth filter::
696 with a 302 redirect, and write to output one or more "Set-Cookie" 665 with a 302 redirect, and write to output one or more "Set-Cookie"
697 HTTP headers, each followed by a newline. 666 HTTP headers, each followed by a newline.
698 667
699 Please see `filters/simple-authentication.lua` for a clear example
700 script that may be modified.
701
702commit filter:: 668commit filter::
703 This filter is given no arguments. The commit message text that is to 669 This filter is given no arguments. The commit message text that is to
704 be filtered is available on standard input and the filtered text is 670 be filtered is available on standard input and the filtered text is
diff --git a/filter.c b/filter.c index fba26aa..2b6c838 100644 --- a/filter.c +++ b/filter.c
@@ -8,12 +8,6 @@
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#ifndef NO_LUA
12#include <dlfcn.h>
13#include <lua.h>
14#include <lualib.h>
15#include <lauxlib.h>
16#endif
17 11
18static inline void reap_filter(struct cgit_filter *filter) 12static inline void reap_filter(struct cgit_filter *filter)
19{ 13{
@@ -138,252 +132,6 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar
138 filter->base.argument_count = 0; 132 filter->base.argument_count = 0;
139} 133}
140 134
141#ifdef NO_LUA
142void cgit_init_filters(void)
143{
144}
145#endif
146
147#ifndef NO_LUA
148static size_t (*libc_fwrite)(const void *buf, size_t size, size_t n, FILE *);
149static ssize_t (*libc_write)(int fd, const void *buf, size_t size);
150static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
151static struct cgit_filter *current_write_filter = NULL;
152
153void cgit_init_filters(void)
154{
155 /*
156 * we need to wrap both functions since the Lua filter may
157 * have code which calls write(2) directly, bypassing fwrite(3)
158 */
159 libc_fwrite = dlsym(RTLD_NEXT, "fwrite");
160 if (!libc_fwrite)
161 die("Could not locate libc's write function");
162 libc_write = dlsym(RTLD_NEXT, "write");
163 if (!libc_write)
164 die("Could not locate libc's write function");
165}
166
167size_t fwrite(const void *buf, size_t size, size_t n, FILE *f)
168{
169 if (f != stdout || !filter_write)
170 return libc_fwrite(buf, size, n, f);
171 return filter_write(current_write_filter, buf, size * n);
172}
173
174ssize_t write(int fd, const void *buf, size_t count)
175{
176 if (fd != STDOUT_FILENO || !filter_write)
177 return libc_write(fd, buf, count);
178 return filter_write(current_write_filter, buf, count);
179}
180
181static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
182{
183 /* We want to avoid buggy nested patterns. */
184 assert(filter_write == NULL);
185 assert(current_write_filter == NULL);
186 current_write_filter = filter;
187 filter_write = new_write;
188}
189
190static inline void unhook_write(void)
191{
192 assert(filter_write != NULL);
193 assert(current_write_filter != NULL);
194 filter_write = NULL;
195 current_write_filter = NULL;
196}
197
198struct lua_filter {
199 struct cgit_filter base;
200 char *script_file;
201 lua_State *lua_state;
202};
203
204static void error_lua_filter(struct lua_filter *filter)
205{
206 die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
207 lua_pop(filter->lua_state, 1);
208}
209
210static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
211{
212 struct lua_filter *filter = (struct lua_filter *)base;
213
214 lua_getglobal(filter->lua_state, "filter_write");
215 lua_pushlstring(filter->lua_state, buf, count);
216 if (lua_pcall(filter->lua_state, 1, 0, 0)) {
217 error_lua_filter(filter);
218 errno = EIO;
219 return -1;
220 }
221 return count;
222}
223
224static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
225{
226 const char *str;
227 ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
228 struct cgit_filter *save_filter;
229
230 str = lua_tostring(lua_state, 1);
231 if (!str)
232 return 0;
233
234 save_filter_write = filter_write;
235 save_filter = current_write_filter;
236 unhook_write();
237 fn(str);
238 hook_write(save_filter, save_filter_write);
239
240 return 0;
241}
242
243static int html_lua_filter(lua_State *lua_state)
244{
245 return hook_lua_filter(lua_state, html);
246}
247
248static int html_txt_lua_filter(lua_State *lua_state)
249{
250 return hook_lua_filter(lua_state, html_txt);
251}
252
253static int html_attr_lua_filter(lua_State *lua_state)
254{
255 return hook_lua_filter(lua_state, html_attr);
256}
257
258static int html_url_path_lua_filter(lua_State *lua_state)
259{
260 return hook_lua_filter(lua_state, html_url_path);
261}
262
263static int html_url_arg_lua_filter(lua_State *lua_state)
264{
265 return hook_lua_filter(lua_state, html_url_arg);
266}
267
268static int html_include_lua_filter(lua_State *lua_state)
269{
270 return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
271}
272
273static void cleanup_lua_filter(struct cgit_filter *base)
274{
275 struct lua_filter *filter = (struct lua_filter *)base;
276
277 if (!filter->lua_state)
278 return;
279
280 lua_close(filter->lua_state);
281 filter->lua_state = NULL;
282 if (filter->script_file) {
283 free(filter->script_file);
284 filter->script_file = NULL;
285 }
286}
287
288static int init_lua_filter(struct lua_filter *filter)
289{
290 if (filter->lua_state)
291 return 0;
292
293 if (!(filter->lua_state = luaL_newstate()))
294 return 1;
295
296 luaL_openlibs(filter->lua_state);
297
298 lua_pushcfunction(filter->lua_state, html_lua_filter);
299 lua_setglobal(filter->lua_state, "html");
300 lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
301 lua_setglobal(filter->lua_state, "html_txt");
302 lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
303 lua_setglobal(filter->lua_state, "html_attr");
304 lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
305 lua_setglobal(filter->lua_state, "html_url_path");
306 lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
307 lua_setglobal(filter->lua_state, "html_url_arg");
308 lua_pushcfunction(filter->lua_state, html_include_lua_filter);
309 lua_setglobal(filter->lua_state, "html_include");
310
311 if (luaL_dofile(filter->lua_state, filter->script_file)) {
312 error_lua_filter(filter);
313 lua_close(filter->lua_state);
314 filter->lua_state = NULL;
315 return 1;
316 }
317 return 0;
318}
319
320static int open_lua_filter(struct cgit_filter *base, va_list ap)
321{
322 struct lua_filter *filter = (struct lua_filter *)base;
323 int i;
324
325 if (fflush(stdout))
326 return 1;
327
328 if (init_lua_filter(filter))
329 return 1;
330
331 hook_write(base, write_lua_filter);
332
333 lua_getglobal(filter->lua_state, "filter_open");
334 for (i = 0; i < filter->base.argument_count; ++i)
335 lua_pushstring(filter->lua_state, va_arg(ap, char *));
336 if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
337 error_lua_filter(filter);
338 return 1;
339 }
340 return 0;
341}
342
343static int close_lua_filter(struct cgit_filter *base)
344{
345 struct lua_filter *filter = (struct lua_filter *)base;
346 int ret = 0;
347
348 lua_getglobal(filter->lua_state, "filter_close");
349 if (lua_pcall(filter->lua_state, 0, 1, 0)) {
350 error_lua_filter(filter);
351 ret = -1;
352 } else {
353 ret = lua_tonumber(filter->lua_state, -1);
354 lua_pop(filter->lua_state, 1);
355 }
356
357 unhook_write();
358 return ret;
359}
360
361static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
362{
363 struct lua_filter *filter = (struct lua_filter *)base;
364 fprintf(f, "%slua:%s\n", prefix, filter->script_file);
365}
366
367
368static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
369{
370 struct lua_filter *filter;
371
372 filter = xmalloc(sizeof(*filter));
373 memset(filter, 0, sizeof(*filter));
374 filter->base.open = open_lua_filter;
375 filter->base.close = close_lua_filter;
376 filter->base.fprintf = fprintf_lua_filter;
377 filter->base.cleanup = cleanup_lua_filter;
378 filter->base.argument_count = argument_count;
379 filter->script_file = xstrdup(cmd);
380
381 return &filter->base;
382}
383
384#endif
385
386
387int cgit_open_filter(struct cgit_filter *filter, ...) 135int cgit_open_filter(struct cgit_filter *filter, ...)
388{ 136{
389 int result; 137 int result;
@@ -415,9 +163,6 @@ static const struct {
415 struct cgit_filter *(*ctor)(const char *cmd, int argument_count); 163 struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
416} filter_specs[] = { 164} filter_specs[] = {
417 { "exec", new_exec_filter }, 165 { "exec", new_exec_filter },
418#ifndef NO_LUA
419 { "lua", new_lua_filter },
420#endif
421}; 166};
422 167
423struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) 168struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
diff --git a/filters/email-gravatar.lua b/filters/email-gravatar.lua deleted file mode 100644 index c39b490..0000000 --- a/filters/email-gravatar.lua +++ /dev/null
@@ -1,35 +0,0 @@
1-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
2-- It adds gravatar icons to author names. It is designed to be used with the lua:
3-- prefix in filters. It is much faster than the corresponding python script.
4--
5-- Requirements:
6-- luaossl
7-- <http://25thandclement.com/~william/projects/luaossl.html>
8--
9
10local digest = require("openssl.digest")
11
12function md5_hex(input)
13 local b = digest.new("md5"):final(input)
14 local x = ""
15 for i = 1, #b do
16 x = x .. string.format("%.2x", string.byte(b, i))
17 end
18 return x
19end
20
21function filter_open(email, page)
22 buffer = ""
23 md5 = md5_hex(email:sub(2, -2):lower())
24end
25
26function filter_close()
27 html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer)
28 return 0
29end
30
31function filter_write(str)
32 buffer = buffer .. str
33end
34
35
diff --git a/filters/email-gravatar.py b/filters/email-gravatar.py index d70440e..012113c 100755 --- a/filters/email-gravatar.py +++ b/filters/email-gravatar.py
@@ -1,8 +1,5 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2 2
3# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
4# script is very slow, in comparison.
5#
6# This script may be used with the email-filter or repo.email-filter settings in cgitrc. 3# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
7# 4#
8# The following environment variables can be used to retrieve the configuration 5# The following environment variables can be used to retrieve the configuration
diff --git a/filters/email-libravatar.lua b/filters/email-libravatar.lua deleted file mode 100644 index 7336baf..0000000 --- a/filters/email-libravatar.lua +++ /dev/null
@@ -1,36 +0,0 @@
1-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
2-- It adds libravatar icons to author names. It is designed to be used with the lua:
3-- prefix in filters.
4--
5-- Requirements:
6-- luaossl
7-- <http://25thandclement.com/~william/projects/luaossl.html>
8--
9
10local digest = require("openssl.digest")
11
12function md5_hex(input)
13 local b = digest.new("md5"):final(input)
14 local x = ""
15 for i = 1, #b do
16 x = x .. string.format("%.2x", string.byte(b, i))
17 end
18 return x
19end
20
21function filter_open(email, page)
22 buffer = ""
23 md5 = md5_hex(email:sub(2, -2):lower())
24end
25
26function filter_close()
27 baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/"
28 html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer)
29 return 0
30end
31
32function filter_write(str)
33 buffer = buffer .. str
34end
35
36
diff --git a/filters/file-authentication.lua b/filters/file-authentication.lua deleted file mode 100644 index 0248804..0000000 --- a/filters/file-authentication.lua +++ /dev/null
@@ -1,359 +0,0 @@
1-- This script may be used with the auth-filter.
2--
3-- Requirements:
4-- luaossl
5-- <http://25thandclement.com/~william/projects/luaossl.html>
6-- luaposix
7-- <https://github.com/luaposix/luaposix>
8--
9local sysstat = require("posix.sys.stat")
10local unistd = require("posix.unistd")
11local rand = require("openssl.rand")
12local hmac = require("openssl.hmac")
13
14-- This file should contain a series of lines in the form of:
15-- username1:hash1
16-- username2:hash2
17-- username3:hash3
18-- ...
19-- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`.
20-- This file should not be world-readable.
21local users_filename = "/etc/cgit-auth/users"
22
23-- This file should contain a series of lines in the form of:
24-- groupname1:username1,username2,username3,...
25-- ...
26local groups_filename = "/etc/cgit-auth/groups"
27
28-- This file should contain a series of lines in the form of:
29-- reponame1:groupname1,groupname2,groupname3,...
30-- ...
31local repos_filename = "/etc/cgit-auth/repos"
32
33-- Set this to a path this script can write to for storing a persistent
34-- cookie secret, which should not be world-readable.
35local secret_filename = "/var/cache/cgit/auth-secret"
36
37--
38--
39-- Authentication functions follow below. Swap these out if you want different authentication semantics.
40--
41--
42
43-- Looks up a hash for a given user.
44function lookup_hash(user)
45 local line
46 for line in io.lines(users_filename) do
47 local u, h = string.match(line, "(.-):(.+)")
48 if u:lower() == user:lower() then
49 return h
50 end
51 end
52 return nil
53end
54
55-- Looks up users for a given repo.
56function lookup_users(repo)
57 local users = nil
58 local groups = nil
59 local line, group, user
60 for line in io.lines(repos_filename) do
61 local r, g = string.match(line, "(.-):(.+)")
62 if r == repo then
63 groups = { }
64 for group in string.gmatch(g, "([^,]+)") do
65 groups[group:lower()] = true
66 end
67 break
68 end
69 end
70 if groups == nil then
71 return nil
72 end
73 for line in io.lines(groups_filename) do
74 local g, u = string.match(line, "(.-):(.+)")
75 if groups[g:lower()] then
76 if users == nil then
77 users = { }
78 end
79 for user in string.gmatch(u, "([^,]+)") do
80 users[user:lower()] = true
81 end
82 end
83 end
84 return users
85end
86
87
88-- Sets HTTP cookie headers based on post and sets up redirection.
89function authenticate_post()
90 local hash = lookup_hash(post["username"])
91 local redirect = validate_value("redirect", post["redirect"])
92
93 if redirect == nil then
94 not_found()
95 return 0
96 end
97
98 redirect_to(redirect)
99
100 if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
101 set_cookie("cgitauth", "")
102 else
103 -- One week expiration time
104 local username = secure_value("username", post["username"], os.time() + 604800)
105 set_cookie("cgitauth", username)
106 end
107
108 html("\n")
109 return 0
110end
111
112
113-- Returns 1 if the cookie is valid and 0 if it is not.
114function authenticate_cookie()
115 accepted_users = lookup_users(cgit["repo"])
116 if accepted_users == nil then
117 -- We return as valid if the repo is not protected.
118 return 1
119 end
120
121 local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
122 if username == nil or not accepted_users[username:lower()] then
123 return 0
124 else
125 return 1
126 end
127end
128
129-- Prints the html for the login form.
130function body()
131 html("<h2>Authentication Required</h2>")
132 html("<form method='post' action='")
133 html_attr(cgit["login"])
134 html("'>")
135 html("<input type='hidden' name='redirect' value='")
136 html_attr(secure_value("redirect", cgit["url"], 0))
137 html("' />")
138 html("<table>")
139 html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
140 html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
141 html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
142 html("</table></form>")
143
144 return 0
145end
146
147
148
149--
150--
151-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
152--
153--
154
155local actions = {}
156actions["authenticate-post"] = authenticate_post
157actions["authenticate-cookie"] = authenticate_cookie
158actions["body"] = body
159
160function filter_open(...)
161 action = actions[select(1, ...)]
162
163 http = {}
164 http["cookie"] = select(2, ...)
165 http["method"] = select(3, ...)
166 http["query"] = select(4, ...)
167 http["referer"] = select(5, ...)
168 http["path"] = select(6, ...)
169 http["host"] = select(7, ...)
170 http["https"] = select(8, ...)
171
172 cgit = {}
173 cgit["repo"] = select(9, ...)
174 cgit["page"] = select(10, ...)
175 cgit["url"] = select(11, ...)
176 cgit["login"] = select(12, ...)
177
178end
179
180function filter_close()
181 return action()
182end
183
184function filter_write(str)
185 post = parse_qs(str)
186end
187
188
189--
190--
191-- Utility functions based on keplerproject/wsapi.
192--
193--
194
195function url_decode(str)
196 if not str then
197 return ""
198 end
199 str = string.gsub(str, "+", " ")
200 str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
201 str = string.gsub(str, "\r\n", "\n")
202 return str
203end
204
205function url_encode(str)
206 if not str then
207 return ""
208 end
209 str = string.gsub(str, "\n", "\r\n")
210 str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
211 str = string.gsub(str, " ", "+")
212 return str
213end
214
215function parse_qs(qs)
216 local tab = {}
217 for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
218 tab[url_decode(key)] = url_decode(val)
219 end
220 return tab
221end
222
223function get_cookie(cookies, name)
224 cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
225 return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
226end
227
228function tohex(b)
229 local x = ""
230 for i = 1, #b do
231 x = x .. string.format("%.2x", string.byte(b, i))
232 end
233 return x
234end
235
236--
237--
238-- Cookie construction and validation helpers.
239--
240--
241
242local secret = nil
243
244-- Loads a secret from a file, creates a secret, or returns one from memory.
245function get_secret()
246 if secret ~= nil then
247 return secret
248 end
249 local secret_file = io.open(secret_filename, "r")
250 if secret_file == nil then
251 local old_umask = sysstat.umask(63)
252 local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
253 local temporary_file = io.open(temporary_filename, "w")
254 if temporary_file == nil then
255 os.exit(177)
256 end
257 temporary_file:write(tohex(rand.bytes(32)))
258 temporary_file:close()
259 unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
260 unistd.unlink(temporary_filename)
261 sysstat.umask(old_umask)
262 secret_file = io.open(secret_filename, "r")
263 end
264 if secret_file == nil then
265 os.exit(177)
266 end
267 secret = secret_file:read()
268 secret_file:close()
269 if secret:len() ~= 64 then
270 os.exit(177)
271 end
272 return secret
273end
274
275-- Returns value of cookie if cookie is valid. Otherwise returns nil.
276function validate_value(expected_field, cookie)
277 local i = 0
278 local value = ""
279 local field = ""
280 local expiration = 0
281 local salt = ""
282 local chmac = ""
283
284 if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
285 return nil
286 end
287
288 for component in string.gmatch(cookie, "[^|]+") do
289 if i == 0 then
290 field = component
291 elseif i == 1 then
292 value = component
293 elseif i == 2 then
294 expiration = tonumber(component)
295 if expiration == nil then
296 expiration = -1
297 end
298 elseif i == 3 then
299 salt = component
300 elseif i == 4 then
301 chmac = component
302 else
303 break
304 end
305 i = i + 1
306 end
307
308 if chmac == nil or chmac:len() == 0 then
309 return nil
310 end
311
312 -- Lua hashes strings, so these comparisons are time invariant.
313 if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
314 return nil
315 end
316
317 if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
318 return nil
319 end
320
321 if url_decode(field) ~= expected_field then
322 return nil
323 end
324
325 return url_decode(value)
326end
327
328function secure_value(field, value, expiration)
329 if value == nil or value:len() <= 0 then
330 return ""
331 end
332
333 local authstr = ""
334 local salt = tohex(rand.bytes(16))
335 value = url_encode(value)
336 field = url_encode(field)
337 authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
338 authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
339 return authstr
340end
341
342function set_cookie(cookie, value)
343 html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
344 if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
345 html("; secure")
346 end
347 html("\n")
348end
349
350function redirect_to(url)
351 html("Status: 302 Redirect\n")
352 html("Cache-Control: no-cache, no-store\n")
353 html("Location: " .. url .. "\n")
354end
355
356function not_found()
357 html("Status: 404 Not Found\n")
358 html("Cache-Control: no-cache, no-store\n\n")
359end
diff --git a/filters/gentoo-ldap-authentication.lua b/filters/gentoo-ldap-authentication.lua deleted file mode 100644 index 673c88d..0000000 --- a/filters/gentoo-ldap-authentication.lua +++ /dev/null
@@ -1,360 +0,0 @@
1-- This script may be used with the auth-filter. Be sure to configure it as you wish.
2--
3-- Requirements:
4-- luaossl
5-- <http://25thandclement.com/~william/projects/luaossl.html>
6-- lualdap >= 1.2
7-- <https://git.zx2c4.com/lualdap/about/>
8-- luaposix
9-- <https://github.com/luaposix/luaposix>
10--
11local sysstat = require("posix.sys.stat")
12local unistd = require("posix.unistd")
13local lualdap = require("lualdap")
14local rand = require("openssl.rand")
15local hmac = require("openssl.hmac")
16
17--
18--
19-- Configure these variables for your settings.
20--
21--
22
23-- A list of password protected repositories, with which gentooAccess
24-- group is allowed to access each one.
25local protected_repos = {
26 glouglou = "infra",
27 portage = "dev"
28}
29
30-- Set this to a path this script can write to for storing a persistent
31-- cookie secret, which should be guarded.
32local secret_filename = "/var/cache/cgit/auth-secret"
33
34
35--
36--
37-- Authentication functions follow below. Swap these out if you want different authentication semantics.
38--
39--
40
41-- Sets HTTP cookie headers based on post and sets up redirection.
42function authenticate_post()
43 local redirect = validate_value("redirect", post["redirect"])
44
45 if redirect == nil then
46 not_found()
47 return 0
48 end
49
50 redirect_to(redirect)
51
52 local groups = gentoo_ldap_user_groups(post["username"], post["password"])
53 if groups == nil then
54 set_cookie("cgitauth", "")
55 else
56 -- One week expiration time
57 set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
58 end
59
60 html("\n")
61 return 0
62end
63
64
65-- Returns 1 if the cookie is valid and 0 if it is not.
66function authenticate_cookie()
67 local required_group = protected_repos[cgit["repo"]]
68 if required_group == nil then
69 -- We return as valid if the repo is not protected.
70 return 1
71 end
72
73 local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
74 if user_groups == nil or user_groups == "" then
75 return 0
76 end
77 for group in string.gmatch(user_groups, "[^,]+") do
78 if group == required_group then
79 return 1
80 end
81 end
82 return 0
83end
84
85-- Prints the html for the login form.
86function body()
87 html("<h2>Gentoo LDAP Authentication Required</h2>")
88 html("<form method='post' action='")
89 html_attr(cgit["login"])
90 html("'>")
91 html("<input type='hidden' name='redirect' value='")
92 html_attr(secure_value("redirect", cgit["url"], 0))
93 html("' />")
94 html("<table>")
95 html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
96 html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
97 html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
98 html("</table></form>")
99
100 return 0
101end
102
103--
104--
105-- Gentoo LDAP support.
106--
107--
108
109function gentoo_ldap_user_groups(username, password)
110 -- Ensure the user is alphanumeric
111 if username == nil or username:match("%W") then
112 return nil
113 end
114
115 local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"
116
117 local ldap, err = lualdap.open_simple {
118 uri = "ldap://ldap1.gentoo.org",
119 who = who,
120 password = password,
121 starttls = true,
122 certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
123 keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
124 cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
125 }
126 if ldap == nil then
127 return nil
128 end
129
130 local group_suffix = ".group"
131 local group_suffix_len = group_suffix:len()
132 local groups = {}
133 for dn, attribs in ldap:search { base = who, scope = "subtree" } do
134 local access = attribs["gentooAccess"]
135 if dn == who and access ~= nil then
136 for i, v in ipairs(access) do
137 local vlen = v:len()
138 if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
139 table.insert(groups, v:sub(1, vlen - group_suffix_len))
140 end
141 end
142 end
143 end
144
145 ldap:close()
146
147 return groups
148end
149
150--
151--
152-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
153--
154--
155
156local actions = {}
157actions["authenticate-post"] = authenticate_post
158actions["authenticate-cookie"] = authenticate_cookie
159actions["body"] = body
160
161function filter_open(...)
162 action = actions[select(1, ...)]
163
164 http = {}
165 http["cookie"] = select(2, ...)
166 http["method"] = select(3, ...)
167 http["query"] = select(4, ...)
168 http["referer"] = select(5, ...)
169 http["path"] = select(6, ...)
170 http["host"] = select(7, ...)
171 http["https"] = select(8, ...)
172
173 cgit = {}
174 cgit["repo"] = select(9, ...)
175 cgit["page"] = select(10, ...)
176 cgit["url"] = select(11, ...)
177 cgit["login"] = select(12, ...)
178
179end
180
181function filter_close()
182 return action()
183end
184
185function filter_write(str)
186 post = parse_qs(str)
187end
188
189
190--
191--
192-- Utility functions based on keplerproject/wsapi.
193--
194--
195
196function url_decode(str)
197 if not str then
198 return ""
199 end
200 str = string.gsub(str, "+", " ")
201 str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
202 str = string.gsub(str, "\r\n", "\n")
203 return str
204end
205
206function url_encode(str)
207 if not str then
208 return ""
209 end
210 str = string.gsub(str, "\n", "\r\n")
211 str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
212 str = string.gsub(str, " ", "+")
213 return str
214end
215
216function parse_qs(qs)
217 local tab = {}
218 for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
219 tab[url_decode(key)] = url_decode(val)
220 end
221 return tab
222end
223
224function get_cookie(cookies, name)
225 cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
226 return string.match(cookies, ";" .. name .. "=(.-);")
227end
228
229function tohex(b)
230 local x = ""
231 for i = 1, #b do
232 x = x .. string.format("%.2x", string.byte(b, i))
233 end
234 return x
235end
236
237--
238--
239-- Cookie construction and validation helpers.
240--
241--
242
243local secret = nil
244
245-- Loads a secret from a file, creates a secret, or returns one from memory.
246function get_secret()
247 if secret ~= nil then
248 return secret
249 end
250 local secret_file = io.open(secret_filename, "r")
251 if secret_file == nil then
252 local old_umask = sysstat.umask(63)
253 local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
254 local temporary_file = io.open(temporary_filename, "w")
255 if temporary_file == nil then
256 os.exit(177)
257 end
258 temporary_file:write(tohex(rand.bytes(32)))
259 temporary_file:close()
260 unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
261 unistd.unlink(temporary_filename)
262 sysstat.umask(old_umask)
263 secret_file = io.open(secret_filename, "r")
264 end
265 if secret_file == nil then
266 os.exit(177)
267 end
268 secret = secret_file:read()
269 secret_file:close()
270 if secret:len() ~= 64 then
271 os.exit(177)
272 end
273 return secret
274end
275
276-- Returns value of cookie if cookie is valid. Otherwise returns nil.
277function validate_value(expected_field, cookie)
278 local i = 0
279 local value = ""
280 local field = ""
281 local expiration = 0
282 local salt = ""
283 local chmac = ""
284
285 if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
286 return nil
287 end
288
289 for component in string.gmatch(cookie, "[^|]+") do
290 if i == 0 then
291 field = component
292 elseif i == 1 then
293 value = component
294 elseif i == 2 then
295 expiration = tonumber(component)
296 if expiration == nil then
297 expiration = -1
298 end
299 elseif i == 3 then
300 salt = component
301 elseif i == 4 then
302 chmac = component
303 else
304 break
305 end
306 i = i + 1
307 end
308
309 if chmac == nil or chmac:len() == 0 then
310 return nil
311 end
312
313 -- Lua hashes strings, so these comparisons are time invariant.
314 if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
315 return nil
316 end
317
318 if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
319 return nil
320 end
321
322 if url_decode(field) ~= expected_field then
323 return nil
324 end
325
326 return url_decode(value)
327end
328
329function secure_value(field, value, expiration)
330 if value == nil or value:len() <= 0 then
331 return ""
332 end
333
334 local authstr = ""
335 local salt = tohex(rand.bytes(16))
336 value = url_encode(value)
337 field = url_encode(field)
338 authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
339 authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
340 return authstr
341end
342
343function set_cookie(cookie, value)
344 html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
345 if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
346 html("; secure")
347 end
348 html("\n")
349end
350
351function redirect_to(url)
352 html("Status: 302 Redirect\n")
353 html("Cache-Control: no-cache, no-store\n")
354 html("Location: " .. url .. "\n")
355end
356
357function not_found()
358 html("Status: 404 Not Found\n")
359 html("Cache-Control: no-cache, no-store\n\n")
360end
diff --git a/filters/owner-example.lua b/filters/owner-example.lua deleted file mode 100644 index 50fc25a..0000000 --- a/filters/owner-example.lua +++ /dev/null
@@ -1,17 +0,0 @@
1-- This script is an example of an owner-filter. It replaces the
2-- usual query link with one to a fictional homepage. This script may
3-- be used with the owner-filter or repo.owner-filter settings in
4-- cgitrc with the `lua:` prefix.
5
6function filter_open()
7 buffer = ""
8end
9
10function filter_close()
11 html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer))
12 return 0
13end
14
15function filter_write(str)
16 buffer = buffer .. str
17end
diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua deleted file mode 100644 index 23d3457..0000000 --- a/filters/simple-authentication.lua +++ /dev/null
@@ -1,314 +0,0 @@
1-- This script may be used with the auth-filter. Be sure to configure it as you wish.
2--
3-- Requirements:
4-- luaossl
5-- <http://25thandclement.com/~william/projects/luaossl.html>
6-- luaposix
7-- <https://github.com/luaposix/luaposix>
8--
9local sysstat = require("posix.sys.stat")
10local unistd = require("posix.unistd")
11local rand = require("openssl.rand")
12local hmac = require("openssl.hmac")
13
14--
15--
16-- Configure these variables for your settings.
17--
18--
19
20-- A list of password protected repositories along with the users who can access them.
21local protected_repos = {
22 glouglou = { laurent = true, jason = true },
23 qt = { jason = true, bob = true }
24}
25
26-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
27local users = {
28 jason = "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1",
29 laurent = "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.",
30 bob = "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC"
31}
32
33-- Set this to a path this script can write to for storing a persistent
34-- cookie secret, which should be guarded.
35local secret_filename = "/var/cache/cgit/auth-secret"
36
37--
38--
39-- Authentication functions follow below. Swap these out if you want different authentication semantics.
40--
41--
42
43-- Sets HTTP cookie headers based on post and sets up redirection.
44function authenticate_post()
45 local hash = users[post["username"]]
46 local redirect = validate_value("redirect", post["redirect"])
47
48 if redirect == nil then
49 not_found()
50 return 0
51 end
52
53 redirect_to(redirect)
54
55 if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
56 set_cookie("cgitauth", "")
57 else
58 -- One week expiration time
59 local username = secure_value("username", post["username"], os.time() + 604800)
60 set_cookie("cgitauth", username)
61 end
62
63 html("\n")
64 return 0
65end
66
67
68-- Returns 1 if the cookie is valid and 0 if it is not.
69function authenticate_cookie()
70 accepted_users = protected_repos[cgit["repo"]]
71 if accepted_users == nil then
72 -- We return as valid if the repo is not protected.
73 return 1
74 end
75
76 local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
77 if username == nil or not accepted_users[username:lower()] then
78 return 0
79 else
80 return 1
81 end
82end
83
84-- Prints the html for the login form.
85function body()
86 html("<h2>Authentication Required</h2>")
87 html("<form method='post' action='")
88 html_attr(cgit["login"])
89 html("'>")
90 html("<input type='hidden' name='redirect' value='")
91 html_attr(secure_value("redirect", cgit["url"], 0))
92 html("' />")
93 html("<table>")
94 html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
95 html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
96 html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
97 html("</table></form>")
98
99 return 0
100end
101
102
103
104--
105--
106-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
107--
108--
109
110local actions = {}
111actions["authenticate-post"] = authenticate_post
112actions["authenticate-cookie"] = authenticate_cookie
113actions["body"] = body
114
115function filter_open(...)
116 action = actions[select(1, ...)]
117
118 http = {}
119 http["cookie"] = select(2, ...)
120 http["method"] = select(3, ...)
121 http["query"] = select(4, ...)
122 http["referer"] = select(5, ...)
123 http["path"] = select(6, ...)
124 http["host"] = select(7, ...)
125 http["https"] = select(8, ...)
126
127 cgit = {}
128 cgit["repo"] = select(9, ...)
129 cgit["page"] = select(10, ...)
130 cgit["url"] = select(11, ...)
131 cgit["login"] = select(12, ...)
132
133end
134
135function filter_close()
136 return action()
137end
138
139function filter_write(str)
140 post = parse_qs(str)
141end
142
143
144--
145--
146-- Utility functions based on keplerproject/wsapi.
147--
148--
149
150function url_decode(str)
151 if not str then
152 return ""
153 end
154 str = string.gsub(str, "+", " ")
155 str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
156 str = string.gsub(str, "\r\n", "\n")
157 return str
158end
159
160function url_encode(str)
161 if not str then
162 return ""
163 end
164 str = string.gsub(str, "\n", "\r\n")
165 str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
166 str = string.gsub(str, " ", "+")
167 return str
168end
169
170function parse_qs(qs)
171 local tab = {}
172 for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
173 tab[url_decode(key)] = url_decode(val)
174 end
175 return tab
176end
177
178function get_cookie(cookies, name)
179 cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
180 return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
181end
182
183function tohex(b)
184 local x = ""
185 for i = 1, #b do
186 x = x .. string.format("%.2x", string.byte(b, i))
187 end
188 return x
189end
190
191--
192--
193-- Cookie construction and validation helpers.
194--
195--
196
197local secret = nil
198
199-- Loads a secret from a file, creates a secret, or returns one from memory.
200function get_secret()
201 if secret ~= nil then
202 return secret
203 end
204 local secret_file = io.open(secret_filename, "r")
205 if secret_file == nil then
206 local old_umask = sysstat.umask(63)
207 local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
208 local temporary_file = io.open(temporary_filename, "w")
209 if temporary_file == nil then
210 os.exit(177)
211 end
212 temporary_file:write(tohex(rand.bytes(32)))
213 temporary_file:close()
214 unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
215 unistd.unlink(temporary_filename)
216 sysstat.umask(old_umask)
217 secret_file = io.open(secret_filename, "r")
218 end
219 if secret_file == nil then
220 os.exit(177)
221 end
222 secret = secret_file:read()
223 secret_file:close()
224 if secret:len() ~= 64 then
225 os.exit(177)
226 end
227 return secret
228end
229
230-- Returns value of cookie if cookie is valid. Otherwise returns nil.
231function validate_value(expected_field, cookie)
232 local i = 0
233 local value = ""
234 local field = ""
235 local expiration = 0
236 local salt = ""
237 local chmac = ""
238
239 if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
240 return nil
241 end
242
243 for component in string.gmatch(cookie, "[^|]+") do
244 if i == 0 then
245 field = component
246 elseif i == 1 then
247 value = component
248 elseif i == 2 then
249 expiration = tonumber(component)
250 if expiration == nil then
251 expiration = -1
252 end
253 elseif i == 3 then
254 salt = component
255 elseif i == 4 then
256 chmac = component
257 else
258 break
259 end
260 i = i + 1
261 end
262
263 if chmac == nil or chmac:len() == 0 then
264 return nil
265 end
266
267 -- Lua hashes strings, so these comparisons are time invariant.
268 if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
269 return nil
270 end
271
272 if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
273 return nil
274 end
275
276 if url_decode(field) ~= expected_field then
277 return nil
278 end
279
280 return url_decode(value)
281end
282
283function secure_value(field, value, expiration)
284 if value == nil or value:len() <= 0 then
285 return ""
286 end
287
288 local authstr = ""
289 local salt = tohex(rand.bytes(16))
290 value = url_encode(value)
291 field = url_encode(field)
292 authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
293 authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
294 return authstr
295end
296
297function set_cookie(cookie, value)
298 html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
299 if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
300 html("; secure")
301 end
302 html("\n")
303end
304
305function redirect_to(url)
306 html("Status: 302 Redirect\n")
307 html("Cache-Control: no-cache, no-store\n")
308 html("Location: " .. url .. "\n")
309end
310
311function not_found()
312 html("Status: 404 Not Found\n")
313 html("Cache-Control: no-cache, no-store\n\n")
314end
diff --git a/tests/filters/dump.lua b/tests/filters/dump.lua deleted file mode 100644 index 1f15c93..0000000 --- a/tests/filters/dump.lua +++ /dev/null
@@ -1,17 +0,0 @@
1function filter_open(...)
2 buffer = ""
3 for i = 1, select("#", ...) do
4 buffer = buffer .. select(i, ...) .. " "
5 end
6end
7
8function filter_close()
9 html(buffer)
10 return 0
11end
12
13function filter_write(str)
14 buffer = buffer .. string.upper(str)
15end
16
17
diff --git a/tests/setup.sh b/tests/setup.sh index 8db810f..31e7d5b 100755 --- a/tests/setup.sh +++ b/tests/setup.sh
@@ -60,12 +60,6 @@ fi
60 60
61FILTER_DIRECTORY=$(cd ../filters && pwd) 61FILTER_DIRECTORY=$(cd ../filters && pwd)
62 62
63if cgit --version | grep -F -q "[+] Lua scripting"; then
64 export CGIT_HAS_LUA=1
65else
66 export CGIT_HAS_LUA=0
67fi
68
69mkrepo() { 63mkrepo() {
70 name=$1 64 name=$1
71 count=$2 65 count=$2
@@ -144,19 +138,6 @@ repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh
144repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh 138repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh
145repo.readme=master:a+b 139repo.readme=master:a+b
146EOF 140EOF
147
148 if [ $CGIT_HAS_LUA -eq 1 ]; then
149 cat >>cgitrc <<EOF
150repo.url=filter-lua
151repo.path=$PWD/repos/filter/.git
152repo.desc=filtered repo
153repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua
154repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua
155repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua
156repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua
157repo.readme=master:a+b
158EOF
159 fi
160} 141}
161 142
162cgit_query() 143cgit_query()
diff --git a/tests/t0111-filter.sh b/tests/t0111-filter.sh index 2fdc366..e5d3575 100755 --- a/tests/t0111-filter.sh +++ b/tests/t0111-filter.sh
@@ -4,9 +4,6 @@ test_description='Check filtered content'
4. ./setup.sh 4. ./setup.sh
5 5
6prefixes="exec" 6prefixes="exec"
7if [ $CGIT_HAS_LUA -eq 1 ]; then
8 prefixes="$prefixes lua"
9fi
10 7
11for prefix in $prefixes 8for prefix in $prefixes
12do 9do