]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpd/webls.c
add ip/tftpfs
[plan9front.git] / sys / src / cmd / ip / httpd / webls.c
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
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
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 *
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
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
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
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 *
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
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
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
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 }