1 /*
2 * Serve files from the Plan 9 distribution.
3 * Check that we're not giving out files to bad countries
4 * and then just handle the request normally.
5 * Beware: behaviour changes based on argv[0].
6 * Modified to serve /sys/src, not /n/sources.
7 */
8 #include <u.h>
9 #include <libc.h>
10 #include <bio.h>
11 #include <ndb.h>
12 #include <ip.h>
13 #include <libsec.h>
14 #include <auth.h>
15 #include "httpd.h"
16 #include "httpsrv.h"
17
18 #define LOG "9down"
19
20 #ifndef OUTSIDE
21 #define OUTSIDE 0
22 #endif
23
24 static Hio houtb;
25 static Hio *hout;
26 static HConnect*connect;
27
28 enum
29 {
30 MNAMELEN = 64,
31 };
32
33 void cat(char*, int);
34 void filter(char*, char*, char*, char*, char*);
35 HRange* myfixrange(HConnect*, Dir*, int);
36 void error(char*, ...);
37 void interr(char*, ...);
38 void system(char*, ...);
39
40 int sources;
41
42 void
main(int argc,char ** argv)43 main(int argc, char **argv)
44 {
45 int trailingslash;
46 HSPriv *hp;
47 char *file, *dir;
48 char ok[256];
49
50 quotefmtinstall();
51 fmtinstall('H', httpfmt);
52 fmtinstall('U', hurlfmt);
53
54 connect = init(argc, argv);
55 hout = &connect->hout;
56 if(hparseheaders(connect, HSTIMEOUT) < 0)
57 exits("failed");
58
59 if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0){
60 hunallowed(connect, "GET, HEAD");
61 exits("unallowed");
62 }
63 if(connect->head.expectother || connect->head.expectcont){
64 hfail(connect, HExpectFail, nil);
65 exits("failed");
66 }
67
68 hp = connect->private;
69 /* rename 9down to sources to display sources tree! */
70 if(strstr(argv[0], "sources"))
71 sources = 1;
72 syslog(0, LOG, "%s %s ver %d.%d uri %s search %s",
73 sources ? "sources" : "9down",
74 hp->remotesys,
75 connect->req.vermaj, connect->req.vermin,
76 connect->req.uri, connect->req.search);
77
78 file = strdup(connect->req.uri);
79
80 dir = "/usr/web";
81 if(sources)
82 dir = "/usr/web/sources";
83 if(chdir(dir) < 0)
84 interr("cd %s: %r", dir);
85
86 trailingslash = (file[0] && file[strlen(file)-1] == '/');
87 if(file[0] == 0)
88 file = strdup("/");
89 cleanname(file);
90 if(file[0] == '/'){
91 if(file[1])
92 file++;
93 else
94 file = ".";
95 }
96 /* now file is not rooted, no dot dots */
97
98 if(file[0] == '#')
99 interr("bad file %s", file);
100 if(access(file, AEXIST) < 0)
101 error("404 %s not found", file);
102 if (OUTSIDE) {
103 if(access("/mnt/ipok/ok", AEXIST) < 0){
104 system("mount /srv/ipok /mnt/ipok >[2]/dev/null");
105 if(access("/mnt/ipok/ok", AEXIST) < 0)
106 interr("no /mnt/ipok/ok");
107 }
108 snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", hp->remotesys);
109 if(access(ok, AEXIST) < 0){
110 syslog(0, LOG, "reject %s %s",
111 hp->remotesys, connect->req.uri);
112 file = "/sys/lib/dist/web/err/prohibited.html";
113 }
114 }
115 cat(file, trailingslash);
116 hflush(hout);
117 exits(nil);
118 }
119
120 static void
doerr(char * msg)121 doerr(char *msg)
122 {
123 hprint(hout, "%s %s\r\n", hversion, msg);
124 writelog(connect, "Reply: %s\n", msg);
125 hflush(hout);
126 exits(nil);
127 }
128
129 /* must pass an http error code then a message, e.g., 404 not found */
130 void
error(char * fmt,...)131 error(char *fmt, ...)
132 {
133 char *buf;
134 va_list arg;
135
136 va_start(arg, fmt);
137 buf = vsmprint(fmt, arg);
138 va_end(arg);
139
140 syslog(0, LOG, "error: %s", buf);
141 doerr(buf);
142 }
143
144 /* must not pass an http error code, just a message */
145 void
interr(char * fmt,...)146 interr(char *fmt, ...)
147 {
148 char *buf;
149 va_list arg;
150
151 va_start(arg, fmt);
152 buf = vsmprint(fmt, arg);
153 va_end(arg);
154
155 syslog(0, LOG, "internal error: %s", buf);
156 doerr("500 Internal Error");
157 }
158
159 void
system(char * fmt,...)160 system(char *fmt, ...)
161 {
162 char buf[512];
163 va_list arg;
164
165 va_start(arg, fmt);
166 vsnprint(buf, sizeof buf, fmt, arg);
167 va_end(arg);
168
169 if(fork() == 0){
170 execl("/bin/rc", "rc", "-c", buf, nil);
171 _exits("execl failed");
172 }
173 waitpid();
174 }
175
176 void
cat(char * file,int trailingslash)177 cat(char *file, int trailingslash)
178 {
179 int fd, m;
180 char *reply;
181 long rem;
182 HRange rr;
183 Dir *d;
184 HRange *r;
185 HConnect *c;
186 HSPriv *hp;
187 char buf[8192];
188
189 c = connect;
190 if((fd = open(file, OREAD)) < 0){
191 notfound:
192 hprint(hout, "%s 404 Not Found\r\n", hversion);
193 writelog(c, "Reply: 404 Not Found\n");
194 return;
195 }
196
197 d = dirfstat(fd);
198 if(d == nil){
199 close(fd);
200 goto notfound;
201 }
202
203 if(sources){
204 if((d->mode&DMDIR) && !trailingslash){
205 hprint(hout, "%s 301 Moved Permanently\r\n", hversion);
206 hprint(hout, "Date: %D\r\n", time(nil));
207 hprint(hout, "Connection: close\r\n");
208 if(strcmp(file, ".") == 0)
209 hprint(hout, "Location: /sources/\r\n");
210 else
211 hprint(hout, "Location: /sources/%s/\r\n", file);
212 hprint(hout, "\r\n");
213 hflush(hout);
214 exits(0);
215 }
216 hprint(hout, "%s 200 OK\r\n", hversion);
217 hprint(hout, "Server: Plan9\r\n");
218 hprint(hout, "Date: %D\r\n", time(nil));
219 hprint(hout, "Connection: close\r\n");
220 hprint(hout, "Last-Modified: %D\r\n", d->mtime);
221 hflush(hout);
222 dup(hout->fd, 1);
223 dup(hout->fd, 2);
224 system("/sys/lib/dist/sources2web %q", file);
225 exits(0);
226 }
227
228 if(d->mode&DMDIR)
229 goto notfound;
230
231 r = myfixrange(connect, d, 0);
232 hflush(hout);
233 if(r){
234 reply = "206";
235 if(seek(fd, r->start, 0) < 0)
236 syslog(0, LOG, "seek: %r");
237 rem = r->stop-r->start+1;
238 } else {
239 reply = "200";
240 rr.start = 0;
241 rr.stop = d->length-1;
242 r = &rr;
243 rem = d->length;
244 }
245 for(; rem > 0; rem -= m){
246 m = read(fd, buf, sizeof(buf));
247 if(m <= 0)
248 break;
249 if(m > rem)
250 m = rem;
251 if(hwrite(hout, buf, m) != m)
252 break;
253 }
254 hp = c->private;
255 syslog(0, LOG, "%s: rs %s %lud-%lud/%lud-%lud %s", file, hp->remotesys,
256 r->start, r->stop+1-rem,
257 r->start, r->stop+1,
258 c->head.client);
259 writelog(c, "Reply: %s\n", reply);
260 close(fd);
261 return;
262 }
263
264 HRange*
myfixrange(HConnect * c,Dir * d,int align)265 myfixrange(HConnect *c, Dir *d, int align)
266 {
267 HRange *r, *rv;
268
269 if(!c->req.vermaj)
270 return nil;
271
272 rv = nil;
273 r = c->head.range;
274
275 if(r == nil)
276 goto out;
277
278 if(c->head.ifrangeetag != nil)
279 goto out;
280
281 if(c->head.ifrangedate != 0 && c->head.ifrangedate != d->mtime)
282 goto out;
283
284 if(d->length == 0)
285 goto out;
286
287 /* we only support a single range */
288 if(r->next != nil)
289 goto out;
290
291 if(r->suffix){
292 r->start = d->length - r->stop;
293 if(r->start >= d->length)
294 r->start = 0;
295 r->stop = d->length - 1;
296 r->suffix = 0;
297 }
298 if(r->stop >= d->length)
299 r->stop = d->length - 1;
300 if(r->start > r->stop)
301 goto out;
302 if(align && (r->start%512) != 0)
303 goto out;
304
305 rv = r;
306
307 out:
308 if(rv != nil)
309 hprint(hout, "%s 206 Partial Content\r\n", hversion);
310 else
311 hprint(hout, "%s 200 OK\r\n", hversion);
312 hprint(hout, "Server: Plan9\r\n");
313 hprint(hout, "Date: %D\r\n", time(nil));
314 hprint(hout, "Connection: close\r\n");
315 hprint(hout, "Content-type: application/octet-stream\r\n");
316 if(rv != nil){
317 hprint(hout, "Content-Range: bytes %uld-%uld/%lld\r\n", r->start, r->stop,
318 d->length);
319 hprint(hout, "Content-Length: %uld\r\n", r->stop-r->start+1);
320 }else
321 hprint(hout, "Content-Length: %lld\r\n", d->length);
322 hprint(hout, "Last-Modified: %D\r\n", d->mtime);
323 hprint(hout, "\r\n");
324 return rv;
325 }
326
327
328