xref: /plan9/sys/src/cmd/ip/httpd/webls.c (revision b39189fd423aed869c5cf5189bc504918cff969b)
1f3e9c68aSDavid du Colombier #include <u.h>
2f3e9c68aSDavid du Colombier #include <libc.h>
3f3e9c68aSDavid du Colombier #include <ctype.h>
4f3e9c68aSDavid du Colombier #include <bio.h>
5f3e9c68aSDavid du Colombier #include <regexp.h>
6f3e9c68aSDavid du Colombier #include <fcall.h>
7f3e9c68aSDavid du Colombier #include "httpd.h"
8f3e9c68aSDavid du Colombier #include "httpsrv.h"
9f3e9c68aSDavid du Colombier 
10f3e9c68aSDavid du Colombier static	Hio		*hout;
11f3e9c68aSDavid du Colombier static	Hio		houtb;
12f3e9c68aSDavid du Colombier static	HConnect	*connect;
13f3e9c68aSDavid du Colombier static	int		vermaj, gidwidth, uidwidth, lenwidth, devwidth;
14f3e9c68aSDavid du Colombier static	Biobuf		*aio, *dio;
15f3e9c68aSDavid du Colombier 
16f3e9c68aSDavid du Colombier static void
doctype(void)17f3e9c68aSDavid du Colombier doctype(void)
18f3e9c68aSDavid du Colombier {
19f3e9c68aSDavid du Colombier 	hprint(hout, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n");
20f3e9c68aSDavid du Colombier 	hprint(hout, "    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
21f3e9c68aSDavid du Colombier }
22f3e9c68aSDavid du Colombier 
23f3e9c68aSDavid du Colombier void
error(char * title,char * fmt,...)24f3e9c68aSDavid du Colombier error(char *title, char *fmt, ...)
25f3e9c68aSDavid du Colombier {
26f3e9c68aSDavid du Colombier 	va_list arg;
27f3e9c68aSDavid du Colombier 	char buf[1024], *out;
28f3e9c68aSDavid du Colombier 
29f3e9c68aSDavid du Colombier 	va_start(arg, fmt);
30f3e9c68aSDavid du Colombier 	out = vseprint(buf, buf+sizeof(buf), fmt, arg);
31f3e9c68aSDavid du Colombier 	va_end(arg);
32f3e9c68aSDavid du Colombier 	*out = 0;
33f3e9c68aSDavid du Colombier 
34f3e9c68aSDavid du Colombier 	hprint(hout, "%s 404 %s\r\n", hversion, title);
35f3e9c68aSDavid du Colombier 	hprint(hout, "Date: %D\r\n", time(nil));
36f3e9c68aSDavid du Colombier 	hprint(hout, "Server: Plan9\r\n");
37f3e9c68aSDavid du Colombier 	hprint(hout, "Content-type: text/html\r\n");
38f3e9c68aSDavid du Colombier 	hprint(hout, "\r\n");
39f3e9c68aSDavid du Colombier 	doctype();
40f3e9c68aSDavid du Colombier 	hprint(hout, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
41f3e9c68aSDavid du Colombier 	hprint(hout, "<head><title>%s</title></head>\n", title);
42f3e9c68aSDavid du Colombier 	hprint(hout, "<body>\n");
43f3e9c68aSDavid du Colombier 	hprint(hout, "<h1>%s</h1>\n", title);
44f3e9c68aSDavid du Colombier 	hprint(hout, "%s\n", buf);
45f3e9c68aSDavid du Colombier 	hprint(hout, "</body>\n");
46f3e9c68aSDavid du Colombier 	hprint(hout, "</html>\n");
47f3e9c68aSDavid du Colombier 	hflush(hout);
48f3e9c68aSDavid du Colombier 	writelog(connect, "Reply: 404\nReason: %s\n", title);
49f3e9c68aSDavid du Colombier 	exits(nil);
50f3e9c68aSDavid du Colombier }
51f3e9c68aSDavid du Colombier 
52f3e9c68aSDavid du Colombier /*
53f3e9c68aSDavid du Colombier  * Are we actually allowed to look in here?
54f3e9c68aSDavid du Colombier  *
55f3e9c68aSDavid du Colombier  * Rules:
56f3e9c68aSDavid du Colombier  *	1) If neither allowed nor denied files exist, access is granted.
57f3e9c68aSDavid du Colombier  *	2) If allowed exists and denied does not, dir *must* be in allowed
58f3e9c68aSDavid du Colombier  *	   for access to be granted, otherwise, access is denied.
59f3e9c68aSDavid du Colombier  *	3) If denied exists and allowed does not, dir *must not* be in
60f3e9c68aSDavid du Colombier  *	   denied for access to be granted, otherwise, access is enied.
61f3e9c68aSDavid du Colombier  *	4) If both exist, okay if either (a) file is not in denied, or
62f3e9c68aSDavid du Colombier  *	   (b) in denied and in allowed.  Otherwise, access is denied.
63f3e9c68aSDavid du Colombier  */
64f3e9c68aSDavid du Colombier static Reprog *
getre(Biobuf * buf)65f3e9c68aSDavid du Colombier getre(Biobuf *buf)
66f3e9c68aSDavid du Colombier {
67f3e9c68aSDavid du Colombier 	Reprog	*re;
68f3e9c68aSDavid du Colombier 	char	*p, *t;
69b832af4eSDavid du Colombier 	char	*bbuf;
70b832af4eSDavid du Colombier 	int	n;
71f3e9c68aSDavid du Colombier 
72f3e9c68aSDavid du Colombier 	if (buf == nil)
73f3e9c68aSDavid du Colombier 		return(nil);
74f3e9c68aSDavid du Colombier 	for ( ; ; free(p)) {
75f3e9c68aSDavid du Colombier 		p = Brdstr(buf, '\n', 0);
76f3e9c68aSDavid du Colombier 		if (p == nil)
77f3e9c68aSDavid du Colombier 			return(nil);
78f3e9c68aSDavid du Colombier 		t = strchr(p, '#');
79f3e9c68aSDavid du Colombier 		if (t != nil)
80f3e9c68aSDavid du Colombier 			*t = '\0';
81f3e9c68aSDavid du Colombier 		t = p + strlen(p);
82f3e9c68aSDavid du Colombier 		while (--t > p && isspace(*t))
83f3e9c68aSDavid du Colombier 			*t = '\0';
84b832af4eSDavid du Colombier 		n = strlen(p);
85b832af4eSDavid du Colombier 		if (n == 0)
86f3e9c68aSDavid du Colombier 			continue;
87b832af4eSDavid du Colombier 
88b832af4eSDavid du Colombier 		/* root the regular expresssion */
89b832af4eSDavid du Colombier 		bbuf = malloc(n+2);
90b832af4eSDavid du Colombier 		if(bbuf == nil)
91b832af4eSDavid du Colombier 			sysfatal("out of memory");
92b832af4eSDavid du Colombier 		bbuf[0] = '^';
93b832af4eSDavid du Colombier 		strcpy(bbuf+1, p);
94b832af4eSDavid du Colombier 		re = regcomp(bbuf);
95b832af4eSDavid du Colombier 		free(bbuf);
96b832af4eSDavid du Colombier 
97f3e9c68aSDavid du Colombier 		if (re == nil)
98f3e9c68aSDavid du Colombier 			continue;
99f3e9c68aSDavid du Colombier 		free(p);
100f3e9c68aSDavid du Colombier 		return(re);
101f3e9c68aSDavid du Colombier 	}
102f3e9c68aSDavid du Colombier }
103f3e9c68aSDavid du Colombier 
104f3e9c68aSDavid du Colombier static int
allowed(char * dir)105f3e9c68aSDavid du Colombier allowed(char *dir)
106f3e9c68aSDavid du Colombier {
107f3e9c68aSDavid du Colombier 	Reprog	*re;
108f3e9c68aSDavid du Colombier 	int	okay;
109f3e9c68aSDavid du Colombier 	Resub	match;
110f3e9c68aSDavid du Colombier 
11140ef9009SDavid du Colombier 	if (strcmp(dir, "..") == 0 || strncmp(dir, "../", 3) == 0)
112f3e9c68aSDavid du Colombier 		return(0);
113b832af4eSDavid du Colombier 	if (aio == nil)
114b832af4eSDavid du Colombier 		return(0);
115b832af4eSDavid du Colombier 
116f3e9c68aSDavid du Colombier 	if (aio != nil)
117f3e9c68aSDavid du Colombier 		Bseek(aio, 0, 0);
118f3e9c68aSDavid du Colombier 	if (dio != nil)
119f3e9c68aSDavid du Colombier 		Bseek(dio, 0, 0);
120f3e9c68aSDavid du Colombier 
121b832af4eSDavid du Colombier 	/* if no deny list, assume everything is denied */
122f3e9c68aSDavid du Colombier 	okay = (dio != nil);
123b832af4eSDavid du Colombier 
124b832af4eSDavid du Colombier 	/* go through denials till we find a match */
125f3e9c68aSDavid du Colombier 	while (okay && (re = getre(dio)) != nil) {
126f3e9c68aSDavid du Colombier 		memset(&match, 0, sizeof(match));
127f3e9c68aSDavid du Colombier 		okay = (regexec(re, dir, &match, 1) != 1);
128f3e9c68aSDavid du Colombier 		free(re);
129f3e9c68aSDavid du Colombier 	}
130b832af4eSDavid du Colombier 
131b832af4eSDavid du Colombier 	/* go through accepts till we have a match */
132f3e9c68aSDavid du Colombier 	if (aio == nil)
133f3e9c68aSDavid du Colombier 		return(okay);
134f3e9c68aSDavid du Colombier 	while (!okay && (re = getre(aio)) != nil) {
135f3e9c68aSDavid du Colombier 		memset(&match, 0, sizeof(match));
136f3e9c68aSDavid du Colombier 		okay = (regexec(re, dir, &match, 1) == 1);
137f3e9c68aSDavid du Colombier 		free(re);
138f3e9c68aSDavid du Colombier 	}
139f3e9c68aSDavid du Colombier 	return(okay);
140f3e9c68aSDavid du Colombier }
141f3e9c68aSDavid du Colombier 
142f3e9c68aSDavid du Colombier /*
143f3e9c68aSDavid du Colombier  * Comparison routine for sorting the directory.
144f3e9c68aSDavid du Colombier  */
145f3e9c68aSDavid du Colombier static int
compar(Dir * a,Dir * b)146f3e9c68aSDavid du Colombier compar(Dir *a, Dir *b)
147f3e9c68aSDavid du Colombier {
148f3e9c68aSDavid du Colombier 	return(strcmp(a->name, b->name));
149f3e9c68aSDavid du Colombier }
150f3e9c68aSDavid du Colombier 
151f3e9c68aSDavid du Colombier /*
152f3e9c68aSDavid du Colombier  * These is for formating; how wide are variable-length
153f3e9c68aSDavid du Colombier  * fields?
154f3e9c68aSDavid du Colombier  */
155f3e9c68aSDavid du Colombier static void
maxwidths(Dir * dp,long n)156f3e9c68aSDavid du Colombier maxwidths(Dir *dp, long n)
157f3e9c68aSDavid du Colombier {
158f3e9c68aSDavid du Colombier 	long	i;
159f3e9c68aSDavid du Colombier 	char	scratch[64];
160f3e9c68aSDavid du Colombier 
161f3e9c68aSDavid du Colombier 	for (i = 0; i < n; i++) {
162f3e9c68aSDavid du Colombier 		if (snprint(scratch, sizeof scratch, "%ud", dp[i].dev) > devwidth)
163f3e9c68aSDavid du Colombier 			devwidth = strlen(scratch);
164f3e9c68aSDavid du Colombier 		if (strlen(dp[i].uid) > uidwidth)
165f3e9c68aSDavid du Colombier 			uidwidth = strlen(dp[i].uid);
166f3e9c68aSDavid du Colombier 		if (strlen(dp[i].gid) > gidwidth)
167f3e9c68aSDavid du Colombier 			gidwidth = strlen(dp[i].gid);
168f3e9c68aSDavid du Colombier 		if (snprint(scratch, sizeof scratch, "%lld", dp[i].length) > lenwidth)
169f3e9c68aSDavid du Colombier 			lenwidth = strlen(scratch);
170f3e9c68aSDavid du Colombier 	}
171f3e9c68aSDavid du Colombier }
172f3e9c68aSDavid du Colombier 
173f3e9c68aSDavid du Colombier /*
174f3e9c68aSDavid du Colombier  * Do an actual directory listing.
175f3e9c68aSDavid du Colombier  * asciitime is lifted directly out of ls.
176f3e9c68aSDavid du Colombier  */
177f3e9c68aSDavid du Colombier char *
asciitime(long l)178f3e9c68aSDavid du Colombier asciitime(long l)
179f3e9c68aSDavid du Colombier {
180f3e9c68aSDavid du Colombier 	ulong clk;
181f3e9c68aSDavid du Colombier 	static char buf[32];
182f3e9c68aSDavid du Colombier 	char *t;
183f3e9c68aSDavid du Colombier 
184f3e9c68aSDavid du Colombier 	clk = time(nil);
185f3e9c68aSDavid du Colombier 	t = ctime(l);
186f3e9c68aSDavid du Colombier 	/* 6 months in the past or a day in the future */
187f3e9c68aSDavid du Colombier 	if(l<clk-180L*24*60*60 || clk+24L*60*60<l){
188f3e9c68aSDavid du Colombier 		memmove(buf, t+4, 7);		/* month and day */
189f3e9c68aSDavid du Colombier 		memmove(buf+7, t+23, 5);		/* year */
190f3e9c68aSDavid du Colombier 	}else
191f3e9c68aSDavid du Colombier 		memmove(buf, t+4, 12);		/* skip day of week */
192f3e9c68aSDavid du Colombier 	buf[12] = 0;
193f3e9c68aSDavid du Colombier 	return buf;
194f3e9c68aSDavid du Colombier }
195f3e9c68aSDavid du Colombier 
196f3e9c68aSDavid du Colombier static void
dols(char * dir)197f3e9c68aSDavid du Colombier dols(char *dir)
198f3e9c68aSDavid du Colombier {
199f3e9c68aSDavid du Colombier 	Dir	*d;
200e5b1f7ddSDavid du Colombier 	char	*f, *p,*nm;
201f3e9c68aSDavid du Colombier 	long	i, n;
202f3e9c68aSDavid du Colombier 	int	fd;
203f3e9c68aSDavid du Colombier 
204e5b1f7ddSDavid du Colombier 	cleanname(dir); //  expands "" to "."; ``dir+1'' access below depends on that
205f3e9c68aSDavid du Colombier 	if (!allowed(dir)) {
206f3e9c68aSDavid du Colombier 		error("Permission denied", "<p>Cannot list directory %s: Access prohibited</p>", dir);
207f3e9c68aSDavid du Colombier 		return;
208f3e9c68aSDavid du Colombier 	}
209f3e9c68aSDavid du Colombier 	fd = open(dir, OREAD);
210f3e9c68aSDavid du Colombier 	if (fd < 0) {
211f3e9c68aSDavid du Colombier 		error("Cannot read directory", "<p>Cannot read directory %s: %r</p>", dir);
212f3e9c68aSDavid du Colombier 		return;
213f3e9c68aSDavid du Colombier 	}
214f3e9c68aSDavid du Colombier 	if (vermaj) {
215f3e9c68aSDavid du Colombier 		hokheaders(connect);
216f3e9c68aSDavid du Colombier 		hprint(hout, "Content-type: text/html\r\n");
217f3e9c68aSDavid du Colombier 		hprint(hout, "\r\n");
218f3e9c68aSDavid du Colombier 	}
219f3e9c68aSDavid du Colombier 	doctype();
220f3e9c68aSDavid du Colombier 	hprint(hout, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
221f3e9c68aSDavid du Colombier 	hprint(hout, "<head><title>Index of %s</title></head>\n", dir);
222f3e9c68aSDavid du Colombier 	hprint(hout, "<body>\n");
223e5b1f7ddSDavid du Colombier 	hprint(hout, "<h1>Index of ");
224e5b1f7ddSDavid du Colombier 	nm = dir;
225e5b1f7ddSDavid du Colombier 	while((p = strchr(nm, '/')) != nil){
226e5b1f7ddSDavid du Colombier 		*p = '\0';
227e5b1f7ddSDavid du Colombier 		f = (*dir == '\0') ? "/" : dir;
228e5b1f7ddSDavid du Colombier 		if (!(*dir == '\0' && *(dir+1) == '\0') && allowed(f))
229e5b1f7ddSDavid du Colombier 			hprint(hout, "<a href=\"/magic/webls?dir=%H\">%s/</a>", f, nm);
230e5b1f7ddSDavid du Colombier 		else
231e5b1f7ddSDavid du Colombier 			hprint(hout, "%s/", nm);
232e5b1f7ddSDavid du Colombier 		*p = '/';
233e5b1f7ddSDavid du Colombier 		nm = p+1;
234e5b1f7ddSDavid du Colombier 	}
235e5b1f7ddSDavid du Colombier 	hprint(hout, "%s</h1>\n", nm);
236f3e9c68aSDavid du Colombier 	n = dirreadall(fd, &d);
237f3e9c68aSDavid du Colombier 	close(fd);
238f3e9c68aSDavid du Colombier 	maxwidths(d, n);
239f3e9c68aSDavid du Colombier 	qsort(d, n, sizeof(Dir), (int (*)(void *, void *))compar);
240f3e9c68aSDavid du Colombier 	hprint(hout, "<pre>\n");
241f3e9c68aSDavid du Colombier 	for (i = 0; i < n; i++) {
242f3e9c68aSDavid du Colombier 		f = smprint("%s/%s", dir, d[i].name);
243f3e9c68aSDavid du Colombier 		cleanname(f);
244f3e9c68aSDavid du Colombier 		if (d[i].mode & DMDIR) {
245f3e9c68aSDavid du Colombier 			p = smprint("/magic/webls?dir=%H", f);
246f3e9c68aSDavid du Colombier 			free(f);
247f3e9c68aSDavid du Colombier 			f = p;
248f3e9c68aSDavid du Colombier 		}
249f3e9c68aSDavid du Colombier 		hprint(hout, "%M %C %*ud %-*s %-*s %*lld %s <a href=\"%s\">%s</a>\n",
250f3e9c68aSDavid du Colombier 		    d[i].mode, d[i].type,
251f3e9c68aSDavid du Colombier 		    devwidth, d[i].dev,
252f3e9c68aSDavid du Colombier 		    uidwidth, d[i].uid,
253f3e9c68aSDavid du Colombier 		    gidwidth, d[i].gid,
254f3e9c68aSDavid du Colombier 		    lenwidth, d[i].length,
255f3e9c68aSDavid du Colombier 		    asciitime(d[i].mtime), f, d[i].name);
256f3e9c68aSDavid du Colombier 		free(f);
257f3e9c68aSDavid du Colombier 	}
258f3e9c68aSDavid du Colombier 	f = smprint("%s/..", dir);
259f3e9c68aSDavid du Colombier 	cleanname(f);
260e5b1f7ddSDavid du Colombier 	if (strcmp(f, dir) != 0 && allowed(f))
261f3e9c68aSDavid du Colombier 		hprint(hout, "\nGo to <a href=\"/magic/webls?dir=%H\">parent</a> directory\n", f);
262f3e9c68aSDavid du Colombier 	else
263f3e9c68aSDavid du Colombier 		hprint(hout, "\nEnd of directory listing\n");
264f3e9c68aSDavid du Colombier 	free(f);
265f3e9c68aSDavid du Colombier 	hprint(hout, "</pre>\n</body>\n</html>\n");
266f3e9c68aSDavid du Colombier 	hflush(hout);
267f3e9c68aSDavid du Colombier 	free(d);
268f3e9c68aSDavid du Colombier }
269f3e9c68aSDavid du Colombier 
270f3e9c68aSDavid du Colombier /*
271f3e9c68aSDavid du Colombier  * Handle unpacking the request in the URI and
272f3e9c68aSDavid du Colombier  * invoking the actual handler.
273f3e9c68aSDavid du Colombier  */
274f3e9c68aSDavid du Colombier static void
dosearch(char * search)275f3e9c68aSDavid du Colombier dosearch(char *search)
276f3e9c68aSDavid du Colombier {
277f3e9c68aSDavid du Colombier 	if (strncmp(search, "dir=", 4) == 0){
278f3e9c68aSDavid du Colombier 		search = hurlunesc(connect, search+4);
279f3e9c68aSDavid du Colombier 		dols(search);
280f3e9c68aSDavid du Colombier 		return;
281f3e9c68aSDavid du Colombier 	}
282f3e9c68aSDavid du Colombier 
283f3e9c68aSDavid du Colombier 	/*
284f3e9c68aSDavid du Colombier 	 * Otherwise, we've gotten an illegal request.
285f3e9c68aSDavid du Colombier 	 * spit out a non-apologetic error.
286f3e9c68aSDavid du Colombier 	 */
287f3e9c68aSDavid du Colombier 	search = hurlunesc(connect, search);
288f3e9c68aSDavid du Colombier 	error("Bad directory listing request",
289f3e9c68aSDavid du Colombier 	    "<p>Illegal formatted directory listing request:</p>\n"
290f3e9c68aSDavid du Colombier 	    "<p>%H</p>", search);
291f3e9c68aSDavid du Colombier }
292f3e9c68aSDavid du Colombier 
293f3e9c68aSDavid du Colombier void
main(int argc,char ** argv)294f3e9c68aSDavid du Colombier main(int argc, char **argv)
295f3e9c68aSDavid du Colombier {
296f3e9c68aSDavid du Colombier 	fmtinstall('H', httpfmt);
297f3e9c68aSDavid du Colombier 	fmtinstall('U', hurlfmt);
298f3e9c68aSDavid du Colombier 	fmtinstall('M', dirmodefmt);
299f3e9c68aSDavid du Colombier 
300f3e9c68aSDavid du Colombier 	aio = Bopen("/sys/lib/webls.allowed", OREAD);
301f3e9c68aSDavid du Colombier 	dio = Bopen("/sys/lib/webls.denied", OREAD);
302f3e9c68aSDavid du Colombier 
303f3e9c68aSDavid du Colombier 	if(argc == 2){
304f3e9c68aSDavid du Colombier 		hinit(&houtb, 1, Hwrite);
305f3e9c68aSDavid du Colombier 		hout = &houtb;
306f3e9c68aSDavid du Colombier 		dols(argv[1]);
307f3e9c68aSDavid du Colombier 		exits(nil);
308f3e9c68aSDavid du Colombier 	}
309f3e9c68aSDavid du Colombier 	close(2);
310f3e9c68aSDavid du Colombier 
311f3e9c68aSDavid du Colombier 	connect = init(argc, argv);
312f3e9c68aSDavid du Colombier 	hout = &connect->hout;
313f3e9c68aSDavid du Colombier 	vermaj = connect->req.vermaj;
314f3e9c68aSDavid du Colombier 	if(hparseheaders(connect, HSTIMEOUT) < 0)
315f3e9c68aSDavid du Colombier 		exits("failed");
316f3e9c68aSDavid du Colombier 
317f3e9c68aSDavid du Colombier 	if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0){
318f3e9c68aSDavid du Colombier 		hunallowed(connect, "GET, HEAD");
319f3e9c68aSDavid du Colombier 		exits("not allowed");
320f3e9c68aSDavid du Colombier 	}
321f3e9c68aSDavid du Colombier 	if(connect->head.expectother || connect->head.expectcont){
322f3e9c68aSDavid du Colombier 		hfail(connect, HExpectFail, nil);
323f3e9c68aSDavid du Colombier 		exits("failed");
324f3e9c68aSDavid du Colombier 	}
325f3e9c68aSDavid du Colombier 
326*b39189fdSDavid du Colombier 	bind(webroot, "/", MREPL);
327f3e9c68aSDavid du Colombier 
328f3e9c68aSDavid du Colombier 	if(connect->req.search != nil)
329f3e9c68aSDavid du Colombier 		dosearch(connect->req.search);
330f3e9c68aSDavid du Colombier 	else
331f3e9c68aSDavid du Colombier 		error("Bad argument", "<p>Need a search argument</p>");
332f3e9c68aSDavid du Colombier 	hflush(hout);
333f3e9c68aSDavid du Colombier 	writelog(connect, "200 webls %ld %ld\n", hout->seek, hout->seek);
334f3e9c68aSDavid du Colombier 	exits(nil);
335f3e9c68aSDavid du Colombier }
336