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