xref: /plan9-contrib/sys/src/cmd/ip/httpd/9down.c (revision 26efc675cd2a637181faaecfb9746792ff0dfdd8)
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