1*7dd7cddfSDavid du Colombier #include <u.h> 2*7dd7cddfSDavid du Colombier #include <libc.h> 3*7dd7cddfSDavid du Colombier #include <auth.h> 4*7dd7cddfSDavid du Colombier #include "httpd.h" 5*7dd7cddfSDavid du Colombier #include "can.h" 6*7dd7cddfSDavid du Colombier 7*7dd7cddfSDavid du Colombier typedef struct Endpoints Endpoints; 8*7dd7cddfSDavid du Colombier typedef struct Strings Strings; 9*7dd7cddfSDavid du Colombier 10*7dd7cddfSDavid du Colombier struct Endpoints 11*7dd7cddfSDavid du Colombier { 12*7dd7cddfSDavid du Colombier char *lsys; 13*7dd7cddfSDavid du Colombier char *lserv; 14*7dd7cddfSDavid du Colombier char *rsys; 15*7dd7cddfSDavid du Colombier char *rserv; 16*7dd7cddfSDavid du Colombier }; 17*7dd7cddfSDavid du Colombier 18*7dd7cddfSDavid du Colombier struct Strings 19*7dd7cddfSDavid du Colombier { 20*7dd7cddfSDavid du Colombier char *s1; 21*7dd7cddfSDavid du Colombier char *s2; 22*7dd7cddfSDavid du Colombier }; 23*7dd7cddfSDavid du Colombier 24*7dd7cddfSDavid du Colombier /* 25*7dd7cddfSDavid du Colombier * import from parse.c 26*7dd7cddfSDavid du Colombier */ 27*7dd7cddfSDavid du Colombier extern Can *httpcan; 28*7dd7cddfSDavid du Colombier 29*7dd7cddfSDavid du Colombier static char* abspath(char*, char*); 30*7dd7cddfSDavid du Colombier static void becomenone(char*); 31*7dd7cddfSDavid du Colombier static char *csquery(char*, char*, char*); 32*7dd7cddfSDavid du Colombier static void dolisten(char*); 33*7dd7cddfSDavid du Colombier static int getc(Connect*); 34*7dd7cddfSDavid du Colombier static char* getword(Connect*); 35*7dd7cddfSDavid du Colombier static Strings parseuri(char*); 36*7dd7cddfSDavid du Colombier static int send(Connect*); 37*7dd7cddfSDavid du Colombier static Strings stripmagic(char*); 38*7dd7cddfSDavid du Colombier static char* stripprefix(char*, char*); 39*7dd7cddfSDavid du Colombier static Strings stripsearch(char*); 40*7dd7cddfSDavid du Colombier static char* sysdom(void); 41*7dd7cddfSDavid du Colombier static Endpoints* getendpoints(char*); 42*7dd7cddfSDavid du Colombier static int parsereq(Connect *c); 43*7dd7cddfSDavid du Colombier 44*7dd7cddfSDavid du Colombier /* 45*7dd7cddfSDavid du Colombier * these should be done better; see the reponse codes in /lib/rfc/rfc2616 for 46*7dd7cddfSDavid du Colombier * more info on what should be included. 47*7dd7cddfSDavid du Colombier */ 48*7dd7cddfSDavid du Colombier #define UNAUTHED "You are not authorized to see this area.\n" 49*7dd7cddfSDavid du Colombier #define NOCONTENT "No acceptable type of data is available.\n" 50*7dd7cddfSDavid du Colombier #define NOENCODE "No acceptable encoding of the contents is available.\n" 51*7dd7cddfSDavid du Colombier #define UNMATCHED "The entity requested does not match the existing entity.\n" 52*7dd7cddfSDavid du Colombier #define BADRANGE "No bytes are avaible for the range you requested.\n" 53*7dd7cddfSDavid du Colombier 54*7dd7cddfSDavid du Colombier void 55*7dd7cddfSDavid du Colombier usage(void) 56*7dd7cddfSDavid du Colombier { 57*7dd7cddfSDavid du Colombier fprint(2, "usage: httpd [-a srvaddress] [-d domain] [-n namespace] [-w webroot]\n"); 58*7dd7cddfSDavid du Colombier exits("usage"); 59*7dd7cddfSDavid du Colombier } 60*7dd7cddfSDavid du Colombier 61*7dd7cddfSDavid du Colombier void 62*7dd7cddfSDavid du Colombier main(int argc, char **argv) 63*7dd7cddfSDavid du Colombier { 64*7dd7cddfSDavid du Colombier char *address; 65*7dd7cddfSDavid du Colombier 66*7dd7cddfSDavid du Colombier namespace = nil; 67*7dd7cddfSDavid du Colombier address = nil; 68*7dd7cddfSDavid du Colombier mydomain = nil; 69*7dd7cddfSDavid du Colombier strcpy(netdir, "/net"); 70*7dd7cddfSDavid du Colombier fmtinstall('D', dateconv); 71*7dd7cddfSDavid du Colombier fmtinstall('H', httpconv); 72*7dd7cddfSDavid du Colombier fmtinstall('U', urlconv); 73*7dd7cddfSDavid du Colombier ARGBEGIN{ 74*7dd7cddfSDavid du Colombier case 'n': 75*7dd7cddfSDavid du Colombier namespace = ARGF(); 76*7dd7cddfSDavid du Colombier break; 77*7dd7cddfSDavid du Colombier case 'a': 78*7dd7cddfSDavid du Colombier address = ARGF(); 79*7dd7cddfSDavid du Colombier break; 80*7dd7cddfSDavid du Colombier case 'd': 81*7dd7cddfSDavid du Colombier mydomain = ARGF(); 82*7dd7cddfSDavid du Colombier break; 83*7dd7cddfSDavid du Colombier case 'w': 84*7dd7cddfSDavid du Colombier webroot = ARGF(); 85*7dd7cddfSDavid du Colombier break; 86*7dd7cddfSDavid du Colombier default: 87*7dd7cddfSDavid du Colombier usage(); 88*7dd7cddfSDavid du Colombier break; 89*7dd7cddfSDavid du Colombier }ARGEND 90*7dd7cddfSDavid du Colombier 91*7dd7cddfSDavid du Colombier if(argc) 92*7dd7cddfSDavid du Colombier usage(); 93*7dd7cddfSDavid du Colombier 94*7dd7cddfSDavid du Colombier if(namespace == nil) 95*7dd7cddfSDavid du Colombier namespace = "/lib/namespace.httpd"; 96*7dd7cddfSDavid du Colombier if(address == nil) 97*7dd7cddfSDavid du Colombier address = "tcp!*!http"; 98*7dd7cddfSDavid du Colombier if(webroot == nil) 99*7dd7cddfSDavid du Colombier webroot = "/usr/web"; 100*7dd7cddfSDavid du Colombier else{ 101*7dd7cddfSDavid du Colombier cleanname(webroot); 102*7dd7cddfSDavid du Colombier if(webroot[0] != '/') 103*7dd7cddfSDavid du Colombier webroot = "/usr/web"; 104*7dd7cddfSDavid du Colombier } 105*7dd7cddfSDavid du Colombier 106*7dd7cddfSDavid du Colombier switch(rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG)) { 107*7dd7cddfSDavid du Colombier case -1: 108*7dd7cddfSDavid du Colombier sysfatal("fork"); 109*7dd7cddfSDavid du Colombier case 0: 110*7dd7cddfSDavid du Colombier break; 111*7dd7cddfSDavid du Colombier default: 112*7dd7cddfSDavid du Colombier exits(nil); 113*7dd7cddfSDavid du Colombier } 114*7dd7cddfSDavid du Colombier 115*7dd7cddfSDavid du Colombier /* 116*7dd7cddfSDavid du Colombier * open all files we might need before castrating namespace 117*7dd7cddfSDavid du Colombier */ 118*7dd7cddfSDavid du Colombier time(nil); 119*7dd7cddfSDavid du Colombier if(mydomain == nil) 120*7dd7cddfSDavid du Colombier mydomain = sysdom(); 121*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, nil); 122*7dd7cddfSDavid du Colombier logall[0] = open("/sys/log/httpd/0",OWRITE); 123*7dd7cddfSDavid du Colombier logall[1] = open("/sys/log/httpd/1",OWRITE); 124*7dd7cddfSDavid du Colombier redirectinit(); 125*7dd7cddfSDavid du Colombier contentinit(); 126*7dd7cddfSDavid du Colombier urlinit(); 127*7dd7cddfSDavid du Colombier statsinit(); 128*7dd7cddfSDavid du Colombier 129*7dd7cddfSDavid du Colombier becomenone(namespace); 130*7dd7cddfSDavid du Colombier dolisten(address); 131*7dd7cddfSDavid du Colombier exits(nil); 132*7dd7cddfSDavid du Colombier } 133*7dd7cddfSDavid du Colombier 134*7dd7cddfSDavid du Colombier static void 135*7dd7cddfSDavid du Colombier becomenone(char *namespace) 136*7dd7cddfSDavid du Colombier { 137*7dd7cddfSDavid du Colombier int fd; 138*7dd7cddfSDavid du Colombier 139*7dd7cddfSDavid du Colombier fd = open("#c/user", OWRITE); 140*7dd7cddfSDavid du Colombier if(fd < 0 || write(fd, "none", strlen("none")) < 0) 141*7dd7cddfSDavid du Colombier sysfatal("can't become none"); 142*7dd7cddfSDavid du Colombier close(fd); 143*7dd7cddfSDavid du Colombier if(newns("none", nil) < 0) 144*7dd7cddfSDavid du Colombier sysfatal("can't build normal namespace"); 145*7dd7cddfSDavid du Colombier if(addns("none", namespace) < 0) 146*7dd7cddfSDavid du Colombier sysfatal("can't build httpd namespace"); 147*7dd7cddfSDavid du Colombier } 148*7dd7cddfSDavid du Colombier 149*7dd7cddfSDavid du Colombier static Connect* 150*7dd7cddfSDavid du Colombier mkconnect(void) 151*7dd7cddfSDavid du Colombier { 152*7dd7cddfSDavid du Colombier Connect *c; 153*7dd7cddfSDavid du Colombier 154*7dd7cddfSDavid du Colombier c = ezalloc(sizeof(Connect)); 155*7dd7cddfSDavid du Colombier c->hpos = c->header; 156*7dd7cddfSDavid du Colombier c->hstop = c->header; 157*7dd7cddfSDavid du Colombier return c; 158*7dd7cddfSDavid du Colombier } 159*7dd7cddfSDavid du Colombier 160*7dd7cddfSDavid du Colombier static void 161*7dd7cddfSDavid du Colombier dolisten(char *address) 162*7dd7cddfSDavid du Colombier { 163*7dd7cddfSDavid du Colombier Connect *c; 164*7dd7cddfSDavid du Colombier Endpoints *ends; 165*7dd7cddfSDavid du Colombier char ndir[NETPATHLEN], dir[NETPATHLEN], *p; 166*7dd7cddfSDavid du Colombier int ctl, nctl, data; 167*7dd7cddfSDavid du Colombier int spotchk = 0; 168*7dd7cddfSDavid du Colombier 169*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, "httpd starting"); 170*7dd7cddfSDavid du Colombier ctl = announce(address, dir); 171*7dd7cddfSDavid du Colombier if(ctl < 0){ 172*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, "can't announce on %s: %r", address); 173*7dd7cddfSDavid du Colombier return; 174*7dd7cddfSDavid du Colombier } 175*7dd7cddfSDavid du Colombier strcpy(netdir, dir); 176*7dd7cddfSDavid du Colombier p = nil; 177*7dd7cddfSDavid du Colombier if(netdir[0] == '/'){ 178*7dd7cddfSDavid du Colombier p = strchr(netdir+1, '/'); 179*7dd7cddfSDavid du Colombier if(p != nil) 180*7dd7cddfSDavid du Colombier *p = '\0'; 181*7dd7cddfSDavid du Colombier } 182*7dd7cddfSDavid du Colombier if(p == nil) 183*7dd7cddfSDavid du Colombier strcpy(netdir, "/net"); 184*7dd7cddfSDavid du Colombier 185*7dd7cddfSDavid du Colombier for(;;){ 186*7dd7cddfSDavid du Colombier 187*7dd7cddfSDavid du Colombier /* 188*7dd7cddfSDavid du Colombier * wait for a call (or an error) 189*7dd7cddfSDavid du Colombier */ 190*7dd7cddfSDavid du Colombier nctl = listen(dir, ndir); 191*7dd7cddfSDavid du Colombier if(nctl < 0){ 192*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, "can't listen on %s: %r", address); 193*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, "ctls = %d", ctl); 194*7dd7cddfSDavid du Colombier for(;;)sleep(1000); 195*7dd7cddfSDavid du Colombier return; 196*7dd7cddfSDavid du Colombier } 197*7dd7cddfSDavid du Colombier 198*7dd7cddfSDavid du Colombier /* 199*7dd7cddfSDavid du Colombier * start a process for the service 200*7dd7cddfSDavid du Colombier */ 201*7dd7cddfSDavid du Colombier switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFNAMEG)){ 202*7dd7cddfSDavid du Colombier case -1: 203*7dd7cddfSDavid du Colombier close(nctl); 204*7dd7cddfSDavid du Colombier continue; 205*7dd7cddfSDavid du Colombier case 0: 206*7dd7cddfSDavid du Colombier /* 207*7dd7cddfSDavid du Colombier * see if we know the service requested 208*7dd7cddfSDavid du Colombier */ 209*7dd7cddfSDavid du Colombier data = accept(ctl, ndir); 210*7dd7cddfSDavid du Colombier if(data < 0){ 211*7dd7cddfSDavid du Colombier syslog(0, HTTPLOG, "can't open %s/data: %r", ndir); 212*7dd7cddfSDavid du Colombier exits(nil); 213*7dd7cddfSDavid du Colombier } 214*7dd7cddfSDavid du Colombier dup(data, 0); 215*7dd7cddfSDavid du Colombier dup(data, 1); 216*7dd7cddfSDavid du Colombier dup(data, 2); 217*7dd7cddfSDavid du Colombier close(data); 218*7dd7cddfSDavid du Colombier close(ctl); 219*7dd7cddfSDavid du Colombier close(nctl); 220*7dd7cddfSDavid du Colombier 221*7dd7cddfSDavid du Colombier ends = getendpoints(ndir); 222*7dd7cddfSDavid du Colombier c = mkconnect(); 223*7dd7cddfSDavid du Colombier c->remotesys = ends->rsys; 224*7dd7cddfSDavid du Colombier c->begin_time = time(nil); 225*7dd7cddfSDavid du Colombier 226*7dd7cddfSDavid du Colombier hinit(&c->hin, 0, Hread); 227*7dd7cddfSDavid du Colombier hinit(&c->hout, 1, Hwrite); 228*7dd7cddfSDavid du Colombier 229*7dd7cddfSDavid du Colombier parsereq(c); 230*7dd7cddfSDavid du Colombier 231*7dd7cddfSDavid du Colombier exits(nil); 232*7dd7cddfSDavid du Colombier default: 233*7dd7cddfSDavid du Colombier close(nctl); 234*7dd7cddfSDavid du Colombier break; 235*7dd7cddfSDavid du Colombier } 236*7dd7cddfSDavid du Colombier 237*7dd7cddfSDavid du Colombier if(++spotchk > 50){ 238*7dd7cddfSDavid du Colombier spotchk = 0; 239*7dd7cddfSDavid du Colombier redirectinit(); 240*7dd7cddfSDavid du Colombier contentinit(); 241*7dd7cddfSDavid du Colombier urlinit(); 242*7dd7cddfSDavid du Colombier statsinit(); 243*7dd7cddfSDavid du Colombier } 244*7dd7cddfSDavid du Colombier } 245*7dd7cddfSDavid du Colombier } 246*7dd7cddfSDavid du Colombier 247*7dd7cddfSDavid du Colombier static int 248*7dd7cddfSDavid du Colombier parsereq(Connect *c) 249*7dd7cddfSDavid du Colombier { 250*7dd7cddfSDavid du Colombier Strings ss; 251*7dd7cddfSDavid du Colombier char *vs, *v, *magic, *search, *uri, *origuri, *extra, *newpath; 252*7dd7cddfSDavid du Colombier char virtualhost[100]; 253*7dd7cddfSDavid du Colombier int t, n, ok; 254*7dd7cddfSDavid du Colombier 255*7dd7cddfSDavid du Colombier if(httpcan != nil){ 256*7dd7cddfSDavid du Colombier logit(c, "starting request with request data allocated"); 257*7dd7cddfSDavid du Colombier fail(c, Internal); 258*7dd7cddfSDavid du Colombier } 259*7dd7cddfSDavid du Colombier 260*7dd7cddfSDavid du Colombier /* 261*7dd7cddfSDavid du Colombier * serve requests until a magic request. 262*7dd7cddfSDavid du Colombier * later requests have to come quickly. 263*7dd7cddfSDavid du Colombier * only works for http/1.1 or later. 264*7dd7cddfSDavid du Colombier */ 265*7dd7cddfSDavid du Colombier for(t = 15*60*1000; ; t = 15*1000){ 266*7dd7cddfSDavid du Colombier alarm(t); 267*7dd7cddfSDavid du Colombier if(!gethead(c, 0)) 268*7dd7cddfSDavid du Colombier return 0; 269*7dd7cddfSDavid du Colombier alarm(0); 270*7dd7cddfSDavid du Colombier c->req.meth = getword(c); 271*7dd7cddfSDavid du Colombier if(c->req.meth == nil) 272*7dd7cddfSDavid du Colombier return fail(c, Syntax); 273*7dd7cddfSDavid du Colombier uri = getword(c); 274*7dd7cddfSDavid du Colombier if(uri == nil || strlen(uri) == 0) 275*7dd7cddfSDavid du Colombier return fail(c, Syntax); 276*7dd7cddfSDavid du Colombier v = getword(c); 277*7dd7cddfSDavid du Colombier if(v == nil){ 278*7dd7cddfSDavid du Colombier if(strcmp(c->req.meth, "GET") != 0) 279*7dd7cddfSDavid du Colombier return fail(c, Unimp, c->req.meth); 280*7dd7cddfSDavid du Colombier c->req.vermaj = 0; 281*7dd7cddfSDavid du Colombier c->req.vermin = 9; 282*7dd7cddfSDavid du Colombier }else{ 283*7dd7cddfSDavid du Colombier vs = v; 284*7dd7cddfSDavid du Colombier if(strncmp(vs, "HTTP/", 5) != 0) 285*7dd7cddfSDavid du Colombier return fail(c, UnkVers, vs); 286*7dd7cddfSDavid du Colombier vs += 5; 287*7dd7cddfSDavid du Colombier c->req.vermaj = strtoul(vs, &vs, 10); 288*7dd7cddfSDavid du Colombier if(*vs != '.' || c->req.vermaj != 1) 289*7dd7cddfSDavid du Colombier return fail(c, UnkVers, vs); 290*7dd7cddfSDavid du Colombier vs++; 291*7dd7cddfSDavid du Colombier c->req.vermin = strtoul(vs, &vs, 10); 292*7dd7cddfSDavid du Colombier if(*vs != '\0') 293*7dd7cddfSDavid du Colombier return fail(c, UnkVers, vs); 294*7dd7cddfSDavid du Colombier 295*7dd7cddfSDavid du Colombier extra = getword(c); 296*7dd7cddfSDavid du Colombier if(extra != nil) 297*7dd7cddfSDavid du Colombier return fail(c, Syntax); 298*7dd7cddfSDavid du Colombier } 299*7dd7cddfSDavid du Colombier 300*7dd7cddfSDavid du Colombier /* 301*7dd7cddfSDavid du Colombier * the fragment is not supposed to be sent 302*7dd7cddfSDavid du Colombier * strip it 'cause some clients send it 303*7dd7cddfSDavid du Colombier */ 304*7dd7cddfSDavid du Colombier origuri = uri; 305*7dd7cddfSDavid du Colombier uri = strchr(origuri, '#'); 306*7dd7cddfSDavid du Colombier if(uri != nil) 307*7dd7cddfSDavid du Colombier *uri = 0; 308*7dd7cddfSDavid du Colombier 309*7dd7cddfSDavid du Colombier /* 310*7dd7cddfSDavid du Colombier * http/1.1 requires the server to accept absolute 311*7dd7cddfSDavid du Colombier * or relative uri's. convert to relative with an absolute path 312*7dd7cddfSDavid du Colombier */ 313*7dd7cddfSDavid du Colombier if(http11(c)){ 314*7dd7cddfSDavid du Colombier ss = parseuri(origuri); 315*7dd7cddfSDavid du Colombier uri = ss.s1; 316*7dd7cddfSDavid du Colombier c->req.urihost = ss.s2; 317*7dd7cddfSDavid du Colombier if(uri == nil) 318*7dd7cddfSDavid du Colombier return fail(c, BadReq, uri); 319*7dd7cddfSDavid du Colombier origuri = uri; 320*7dd7cddfSDavid du Colombier } 321*7dd7cddfSDavid du Colombier 322*7dd7cddfSDavid du Colombier /* 323*7dd7cddfSDavid du Colombier * munge uri for search, protection, and magic 324*7dd7cddfSDavid du Colombier */ 325*7dd7cddfSDavid du Colombier ss = stripsearch(origuri); 326*7dd7cddfSDavid du Colombier origuri = ss.s1; 327*7dd7cddfSDavid du Colombier search = ss.s2; 328*7dd7cddfSDavid du Colombier uri = urlunesc(origuri); 329*7dd7cddfSDavid du Colombier uri = abspath(uri, "/"); 330*7dd7cddfSDavid du Colombier if(uri == nil || uri[0] == 0) 331*7dd7cddfSDavid du Colombier return fail(c, NotFound, "no object specified"); 332*7dd7cddfSDavid du Colombier ss = stripmagic(uri); 333*7dd7cddfSDavid du Colombier uri = ss.s1; 334*7dd7cddfSDavid du Colombier magic = ss.s2; 335*7dd7cddfSDavid du Colombier 336*7dd7cddfSDavid du Colombier c->req.uri = uri; 337*7dd7cddfSDavid du Colombier c->req.search = search; 338*7dd7cddfSDavid du Colombier if(magic != nil && strcmp(magic, "httpd") != 0) 339*7dd7cddfSDavid du Colombier break; 340*7dd7cddfSDavid du Colombier 341*7dd7cddfSDavid du Colombier /* 342*7dd7cddfSDavid du Colombier * normal case is just file transfer 343*7dd7cddfSDavid du Colombier */ 344*7dd7cddfSDavid du Colombier origuri = uri; 345*7dd7cddfSDavid du Colombier httpheaders(c); 346*7dd7cddfSDavid du Colombier if(!http11(c) && !c->head.persist) 347*7dd7cddfSDavid du Colombier c->head.closeit = 1; 348*7dd7cddfSDavid du Colombier 349*7dd7cddfSDavid du Colombier if(origuri[0]=='/' && origuri[1]=='~'){ 350*7dd7cddfSDavid du Colombier n = strlen(origuri) + 4 + UTFmax; 351*7dd7cddfSDavid du Colombier newpath = halloc(n); 352*7dd7cddfSDavid du Colombier snprint(newpath, n, "/who/%s", origuri+2); 353*7dd7cddfSDavid du Colombier c->req.uri = newpath; 354*7dd7cddfSDavid du Colombier uri = newpath; 355*7dd7cddfSDavid du Colombier }else if(origuri[0]=='/' && origuri[1]==0){ 356*7dd7cddfSDavid du Colombier snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host); 357*7dd7cddfSDavid du Colombier uri = redirect(virtualhost); 358*7dd7cddfSDavid du Colombier if(uri == nil) 359*7dd7cddfSDavid du Colombier uri = redirect(origuri); 360*7dd7cddfSDavid du Colombier }else 361*7dd7cddfSDavid du Colombier uri = redirect(origuri); 362*7dd7cddfSDavid du Colombier if(uri == nil) 363*7dd7cddfSDavid du Colombier ok = send(c); 364*7dd7cddfSDavid du Colombier else 365*7dd7cddfSDavid du Colombier ok = moved(c, uri); 366*7dd7cddfSDavid du Colombier 367*7dd7cddfSDavid du Colombier if(c->head.closeit || ok < 0) 368*7dd7cddfSDavid du Colombier exits(nil); 369*7dd7cddfSDavid du Colombier 370*7dd7cddfSDavid du Colombier /* 371*7dd7cddfSDavid du Colombier * clean up all of the junk from that request 372*7dd7cddfSDavid du Colombier */ 373*7dd7cddfSDavid du Colombier hxferenc(&c->hout, 0); 374*7dd7cddfSDavid du Colombier hflush(&c->hout); 375*7dd7cddfSDavid du Colombier reqcleanup(c); 376*7dd7cddfSDavid du Colombier } 377*7dd7cddfSDavid du Colombier 378*7dd7cddfSDavid du Colombier /* 379*7dd7cddfSDavid du Colombier * for magic we exec a new program and serve no more requests 380*7dd7cddfSDavid du Colombier */ 381*7dd7cddfSDavid du Colombier snprint(c->xferbuf, BufSize, "/bin/ip/httpd/%s", magic); 382*7dd7cddfSDavid du Colombier writelog(c, "Magic: %s\n", magic); 383*7dd7cddfSDavid du Colombier execl(c->xferbuf, magic, "-d", mydomain, "-w", webroot, "-r", c->remotesys, "-N", netdir, "-b", hunload(&c->hin), 384*7dd7cddfSDavid du Colombier c->req.meth, v, uri, search, nil); 385*7dd7cddfSDavid du Colombier logit(c, "no magic %s uri %s", magic, uri); 386*7dd7cddfSDavid du Colombier return fail(c, NotFound, origuri); 387*7dd7cddfSDavid du Colombier } 388*7dd7cddfSDavid du Colombier 389*7dd7cddfSDavid du Colombier static Strings 390*7dd7cddfSDavid du Colombier parseuri(char *uri) 391*7dd7cddfSDavid du Colombier { 392*7dd7cddfSDavid du Colombier Strings ss; 393*7dd7cddfSDavid du Colombier char *urihost, *p; 394*7dd7cddfSDavid du Colombier 395*7dd7cddfSDavid du Colombier urihost = nil; 396*7dd7cddfSDavid du Colombier if(uri[0] != '/'){ 397*7dd7cddfSDavid du Colombier if(cistrncmp(uri, "http://", 7) != 0){ 398*7dd7cddfSDavid du Colombier ss.s1 = nil; 399*7dd7cddfSDavid du Colombier ss.s2 = nil; 400*7dd7cddfSDavid du Colombier return ss; 401*7dd7cddfSDavid du Colombier } 402*7dd7cddfSDavid du Colombier uri += 5; /* skip http: */ 403*7dd7cddfSDavid du Colombier } 404*7dd7cddfSDavid du Colombier 405*7dd7cddfSDavid du Colombier /* 406*7dd7cddfSDavid du Colombier * anything starting with // is a host name or number 407*7dd7cddfSDavid du Colombier * hostnames constists of letters, digits, - and . 408*7dd7cddfSDavid du Colombier * for now, just ignore any port given 409*7dd7cddfSDavid du Colombier */ 410*7dd7cddfSDavid du Colombier if(uri[0] == '/' && uri[1] == '/'){ 411*7dd7cddfSDavid du Colombier urihost = uri + 2; 412*7dd7cddfSDavid du Colombier p = strchr(urihost, '/'); 413*7dd7cddfSDavid du Colombier if(p == nil) 414*7dd7cddfSDavid du Colombier uri = hstrdup("/"); 415*7dd7cddfSDavid du Colombier else{ 416*7dd7cddfSDavid du Colombier uri = hstrdup(p); 417*7dd7cddfSDavid du Colombier *p = '\0'; 418*7dd7cddfSDavid du Colombier } 419*7dd7cddfSDavid du Colombier p = strchr(urihost, ':'); 420*7dd7cddfSDavid du Colombier if(p != nil) 421*7dd7cddfSDavid du Colombier *p = '\0'; 422*7dd7cddfSDavid du Colombier } 423*7dd7cddfSDavid du Colombier 424*7dd7cddfSDavid du Colombier if(uri[0] != '/' || uri[1] == '/'){ 425*7dd7cddfSDavid du Colombier ss.s1 = nil; 426*7dd7cddfSDavid du Colombier ss.s2 = nil; 427*7dd7cddfSDavid du Colombier return ss; 428*7dd7cddfSDavid du Colombier } 429*7dd7cddfSDavid du Colombier 430*7dd7cddfSDavid du Colombier ss.s1 = uri; 431*7dd7cddfSDavid du Colombier ss.s2 = lower(urihost); 432*7dd7cddfSDavid du Colombier return ss; 433*7dd7cddfSDavid du Colombier } 434*7dd7cddfSDavid du Colombier 435*7dd7cddfSDavid du Colombier static int 436*7dd7cddfSDavid du Colombier send(Connect *c) 437*7dd7cddfSDavid du Colombier { 438*7dd7cddfSDavid du Colombier Dir dir; 439*7dd7cddfSDavid du Colombier char *w, *p; 440*7dd7cddfSDavid du Colombier int fd, fd1, n, force301, ok; 441*7dd7cddfSDavid du Colombier 442*7dd7cddfSDavid du Colombier if(c->req.search) 443*7dd7cddfSDavid du Colombier return fail(c, NoSearch, c->req.uri); 444*7dd7cddfSDavid du Colombier if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0) 445*7dd7cddfSDavid du Colombier return unallowed(c, "GET, HEAD"); 446*7dd7cddfSDavid du Colombier if(c->head.expectother || c->head.expectcont) 447*7dd7cddfSDavid du Colombier return fail(c, ExpectFail); 448*7dd7cddfSDavid du Colombier 449*7dd7cddfSDavid du Colombier ok = authcheck(c); 450*7dd7cddfSDavid du Colombier if(ok <= 0) 451*7dd7cddfSDavid du Colombier return ok; 452*7dd7cddfSDavid du Colombier 453*7dd7cddfSDavid du Colombier /* 454*7dd7cddfSDavid du Colombier * check for directory/file mismatch with trailing /, 455*7dd7cddfSDavid du Colombier * and send any redirections. 456*7dd7cddfSDavid du Colombier */ 457*7dd7cddfSDavid du Colombier n = strlen(webroot) + strlen(c->req.uri) + STRLEN("/index.html") + 1; 458*7dd7cddfSDavid du Colombier w = halloc(n); 459*7dd7cddfSDavid du Colombier strcpy(w, webroot); 460*7dd7cddfSDavid du Colombier strcat(w, c->req.uri); 461*7dd7cddfSDavid du Colombier fd = open(w, OREAD); 462*7dd7cddfSDavid du Colombier if(fd < 0) 463*7dd7cddfSDavid du Colombier return notfound(c, c->req.uri); 464*7dd7cddfSDavid du Colombier if(dirfstat(fd, &dir) < 0){ 465*7dd7cddfSDavid du Colombier close(fd); 466*7dd7cddfSDavid du Colombier return fail(c, Internal); 467*7dd7cddfSDavid du Colombier } 468*7dd7cddfSDavid du Colombier p = strchr(w, '\0'); 469*7dd7cddfSDavid du Colombier if(dir.mode & CHDIR){ 470*7dd7cddfSDavid du Colombier if(p > w && p[-1] == '/'){ 471*7dd7cddfSDavid du Colombier strcat(w, "index.html"); 472*7dd7cddfSDavid du Colombier force301 = 0; 473*7dd7cddfSDavid du Colombier }else{ 474*7dd7cddfSDavid du Colombier strcat(w, "/index.html"); 475*7dd7cddfSDavid du Colombier force301 = 1; 476*7dd7cddfSDavid du Colombier } 477*7dd7cddfSDavid du Colombier fd1 = open(w, OREAD); 478*7dd7cddfSDavid du Colombier if(fd1 < 0){ 479*7dd7cddfSDavid du Colombier close(fd); 480*7dd7cddfSDavid du Colombier return notfound(c, c->req.uri); 481*7dd7cddfSDavid du Colombier } 482*7dd7cddfSDavid du Colombier c->req.uri = w + strlen(webroot); 483*7dd7cddfSDavid du Colombier if(force301 && c->req.vermaj){ 484*7dd7cddfSDavid du Colombier close(fd); 485*7dd7cddfSDavid du Colombier close(fd1); 486*7dd7cddfSDavid du Colombier return moved(c, c->req.uri); 487*7dd7cddfSDavid du Colombier } 488*7dd7cddfSDavid du Colombier close(fd); 489*7dd7cddfSDavid du Colombier fd = fd1; 490*7dd7cddfSDavid du Colombier if(dirfstat(fd, &dir) < 0){ 491*7dd7cddfSDavid du Colombier close(fd); 492*7dd7cddfSDavid du Colombier return fail(c, Internal); 493*7dd7cddfSDavid du Colombier } 494*7dd7cddfSDavid du Colombier }else if(p > w && p[-1] == '/'){ 495*7dd7cddfSDavid du Colombier close(fd); 496*7dd7cddfSDavid du Colombier *strrchr(c->req.uri, '/') = '\0'; 497*7dd7cddfSDavid du Colombier return moved(c, c->req.uri); 498*7dd7cddfSDavid du Colombier } 499*7dd7cddfSDavid du Colombier 500*7dd7cddfSDavid du Colombier if(verbose) 501*7dd7cddfSDavid du Colombier logit(c, "%s %s %lld", c->req.meth, c->req.uri, dir.length); 502*7dd7cddfSDavid du Colombier 503*7dd7cddfSDavid du Colombier return sendfd(c, fd, &dir); 504*7dd7cddfSDavid du Colombier } 505*7dd7cddfSDavid du Colombier 506*7dd7cddfSDavid du Colombier static Strings 507*7dd7cddfSDavid du Colombier stripmagic(char *uri) 508*7dd7cddfSDavid du Colombier { 509*7dd7cddfSDavid du Colombier Strings ss; 510*7dd7cddfSDavid du Colombier char *newuri, *prog, *s; 511*7dd7cddfSDavid du Colombier 512*7dd7cddfSDavid du Colombier prog = stripprefix("/magic/", uri); 513*7dd7cddfSDavid du Colombier if(prog == nil){ 514*7dd7cddfSDavid du Colombier ss.s1 = uri; 515*7dd7cddfSDavid du Colombier ss.s2 = nil; 516*7dd7cddfSDavid du Colombier return ss; 517*7dd7cddfSDavid du Colombier } 518*7dd7cddfSDavid du Colombier 519*7dd7cddfSDavid du Colombier s = strchr(prog, '/'); 520*7dd7cddfSDavid du Colombier if(s == nil) 521*7dd7cddfSDavid du Colombier newuri = ""; 522*7dd7cddfSDavid du Colombier else{ 523*7dd7cddfSDavid du Colombier newuri = hstrdup(s); 524*7dd7cddfSDavid du Colombier *s = 0; 525*7dd7cddfSDavid du Colombier s = strrchr(s, '/'); 526*7dd7cddfSDavid du Colombier if(s != nil && s[1] == 0) 527*7dd7cddfSDavid du Colombier *s = 0; 528*7dd7cddfSDavid du Colombier } 529*7dd7cddfSDavid du Colombier ss.s1 = newuri; 530*7dd7cddfSDavid du Colombier ss.s2 = prog; 531*7dd7cddfSDavid du Colombier return ss; 532*7dd7cddfSDavid du Colombier } 533*7dd7cddfSDavid du Colombier 534*7dd7cddfSDavid du Colombier static char* 535*7dd7cddfSDavid du Colombier stripprefix(char *pre, char *str) 536*7dd7cddfSDavid du Colombier { 537*7dd7cddfSDavid du Colombier while(*pre) 538*7dd7cddfSDavid du Colombier if(*str++ != *pre++) 539*7dd7cddfSDavid du Colombier return nil; 540*7dd7cddfSDavid du Colombier return str; 541*7dd7cddfSDavid du Colombier } 542*7dd7cddfSDavid du Colombier 543*7dd7cddfSDavid du Colombier static Strings 544*7dd7cddfSDavid du Colombier stripsearch(char *uri) 545*7dd7cddfSDavid du Colombier { 546*7dd7cddfSDavid du Colombier Strings ss; 547*7dd7cddfSDavid du Colombier char *search; 548*7dd7cddfSDavid du Colombier 549*7dd7cddfSDavid du Colombier search = strchr(uri, '?'); 550*7dd7cddfSDavid du Colombier if(search != nil) 551*7dd7cddfSDavid du Colombier *search++ = 0; 552*7dd7cddfSDavid du Colombier ss.s1 = uri; 553*7dd7cddfSDavid du Colombier ss.s2 = search; 554*7dd7cddfSDavid du Colombier return ss; 555*7dd7cddfSDavid du Colombier } 556*7dd7cddfSDavid du Colombier 557*7dd7cddfSDavid du Colombier /* 558*7dd7cddfSDavid du Colombier * to circumscribe the accessible files we have to eliminate ..'s 559*7dd7cddfSDavid du Colombier * and resolve all names from the root. 560*7dd7cddfSDavid du Colombier */ 561*7dd7cddfSDavid du Colombier static char* 562*7dd7cddfSDavid du Colombier abspath(char *origpath, char *curdir) 563*7dd7cddfSDavid du Colombier { 564*7dd7cddfSDavid du Colombier char *p, *sp, *path, *work, *rpath; 565*7dd7cddfSDavid du Colombier int len, n, c; 566*7dd7cddfSDavid du Colombier 567*7dd7cddfSDavid du Colombier if(curdir == nil) 568*7dd7cddfSDavid du Colombier curdir = "/"; 569*7dd7cddfSDavid du Colombier if(origpath == nil) 570*7dd7cddfSDavid du Colombier origpath = ""; 571*7dd7cddfSDavid du Colombier work = hstrdup(origpath); 572*7dd7cddfSDavid du Colombier path = work; 573*7dd7cddfSDavid du Colombier 574*7dd7cddfSDavid du Colombier /* 575*7dd7cddfSDavid du Colombier * remove any really special characters 576*7dd7cddfSDavid du Colombier */ 577*7dd7cddfSDavid du Colombier for(sp = "`;| "; *sp; sp++){ 578*7dd7cddfSDavid du Colombier p = strchr(path, *sp); 579*7dd7cddfSDavid du Colombier if(p) 580*7dd7cddfSDavid du Colombier *p = 0; 581*7dd7cddfSDavid du Colombier } 582*7dd7cddfSDavid du Colombier 583*7dd7cddfSDavid du Colombier len = strlen(curdir) + strlen(path) + 2 + UTFmax; 584*7dd7cddfSDavid du Colombier if(len < 10) 585*7dd7cddfSDavid du Colombier len = 10; 586*7dd7cddfSDavid du Colombier rpath = halloc(len); 587*7dd7cddfSDavid du Colombier if(*path == '/') 588*7dd7cddfSDavid du Colombier rpath[0] = 0; 589*7dd7cddfSDavid du Colombier else 590*7dd7cddfSDavid du Colombier strcpy(rpath, curdir); 591*7dd7cddfSDavid du Colombier n = strlen(rpath); 592*7dd7cddfSDavid du Colombier 593*7dd7cddfSDavid du Colombier while(path){ 594*7dd7cddfSDavid du Colombier p = strchr(path, '/'); 595*7dd7cddfSDavid du Colombier if(p) 596*7dd7cddfSDavid du Colombier *p++ = 0; 597*7dd7cddfSDavid du Colombier if(strcmp(path, "..") == 0){ 598*7dd7cddfSDavid du Colombier while(n > 1){ 599*7dd7cddfSDavid du Colombier n--; 600*7dd7cddfSDavid du Colombier c = rpath[n]; 601*7dd7cddfSDavid du Colombier rpath[n] = 0; 602*7dd7cddfSDavid du Colombier if(c == '/') 603*7dd7cddfSDavid du Colombier break; 604*7dd7cddfSDavid du Colombier } 605*7dd7cddfSDavid du Colombier } else if(strcmp(path, ".") == 0) 606*7dd7cddfSDavid du Colombier ; 607*7dd7cddfSDavid du Colombier else if(n == 1) 608*7dd7cddfSDavid du Colombier n += snprint(rpath+n, len-n, "%s", path); 609*7dd7cddfSDavid du Colombier else 610*7dd7cddfSDavid du Colombier n += snprint(rpath+n, len-n, "/%s", path); 611*7dd7cddfSDavid du Colombier path = p; 612*7dd7cddfSDavid du Colombier } 613*7dd7cddfSDavid du Colombier 614*7dd7cddfSDavid du Colombier if(strncmp(rpath, "/bin/", 5) == 0) 615*7dd7cddfSDavid du Colombier strcpy(rpath, "/"); 616*7dd7cddfSDavid du Colombier return rpath; 617*7dd7cddfSDavid du Colombier } 618*7dd7cddfSDavid du Colombier 619*7dd7cddfSDavid du Colombier static char* 620*7dd7cddfSDavid du Colombier getword(Connect *c) 621*7dd7cddfSDavid du Colombier { 622*7dd7cddfSDavid du Colombier char buf[MaxWord]; 623*7dd7cddfSDavid du Colombier int ch, n; 624*7dd7cddfSDavid du Colombier 625*7dd7cddfSDavid du Colombier while((ch = getc(c)) == ' ' || ch == '\t' || ch == '\r') 626*7dd7cddfSDavid du Colombier ; 627*7dd7cddfSDavid du Colombier buf[0] = '\0'; 628*7dd7cddfSDavid du Colombier if(ch == '\n') 629*7dd7cddfSDavid du Colombier return nil; 630*7dd7cddfSDavid du Colombier n = 0; 631*7dd7cddfSDavid du Colombier for(;;){ 632*7dd7cddfSDavid du Colombier switch(ch){ 633*7dd7cddfSDavid du Colombier case ' ': 634*7dd7cddfSDavid du Colombier case '\t': 635*7dd7cddfSDavid du Colombier case '\r': 636*7dd7cddfSDavid du Colombier case '\n': 637*7dd7cddfSDavid du Colombier buf[n] = '\0'; 638*7dd7cddfSDavid du Colombier return hstrdup(buf); 639*7dd7cddfSDavid du Colombier } 640*7dd7cddfSDavid du Colombier 641*7dd7cddfSDavid du Colombier if(n < MaxWord-1) 642*7dd7cddfSDavid du Colombier buf[n++] = ch; 643*7dd7cddfSDavid du Colombier ch = getc(c); 644*7dd7cddfSDavid du Colombier } 645*7dd7cddfSDavid du Colombier return nil; 646*7dd7cddfSDavid du Colombier } 647*7dd7cddfSDavid du Colombier 648*7dd7cddfSDavid du Colombier static int 649*7dd7cddfSDavid du Colombier getc(Connect *c) 650*7dd7cddfSDavid du Colombier { 651*7dd7cddfSDavid du Colombier if(c->hpos < c->hstop) 652*7dd7cddfSDavid du Colombier return *c->hpos++; 653*7dd7cddfSDavid du Colombier return '\n'; 654*7dd7cddfSDavid du Colombier } 655*7dd7cddfSDavid du Colombier 656*7dd7cddfSDavid du Colombier static char* 657*7dd7cddfSDavid du Colombier sysdom(void) 658*7dd7cddfSDavid du Colombier { 659*7dd7cddfSDavid du Colombier char *dn; 660*7dd7cddfSDavid du Colombier 661*7dd7cddfSDavid du Colombier dn = csquery("sys" , sysname(), "dom"); 662*7dd7cddfSDavid du Colombier if(dn == nil) 663*7dd7cddfSDavid du Colombier dn = "who cares"; 664*7dd7cddfSDavid du Colombier return dn; 665*7dd7cddfSDavid du Colombier } 666*7dd7cddfSDavid du Colombier 667*7dd7cddfSDavid du Colombier /* 668*7dd7cddfSDavid du Colombier * query the connection server 669*7dd7cddfSDavid du Colombier */ 670*7dd7cddfSDavid du Colombier static char* 671*7dd7cddfSDavid du Colombier csquery(char *attr, char *val, char *rattr) 672*7dd7cddfSDavid du Colombier { 673*7dd7cddfSDavid du Colombier char token[64+4]; 674*7dd7cddfSDavid du Colombier char buf[256], *p, *sp; 675*7dd7cddfSDavid du Colombier int fd, n; 676*7dd7cddfSDavid du Colombier 677*7dd7cddfSDavid du Colombier if(val == nil || val[0] == 0) 678*7dd7cddfSDavid du Colombier return nil; 679*7dd7cddfSDavid du Colombier snprint(buf, sizeof(buf), "%s/cs", netdir); 680*7dd7cddfSDavid du Colombier fd = open(buf, ORDWR); 681*7dd7cddfSDavid du Colombier if(fd < 0) 682*7dd7cddfSDavid du Colombier return nil; 683*7dd7cddfSDavid du Colombier fprint(fd, "!%s=%s", attr, val); 684*7dd7cddfSDavid du Colombier seek(fd, 0, 0); 685*7dd7cddfSDavid du Colombier snprint(token, sizeof(token), "%s=", rattr); 686*7dd7cddfSDavid du Colombier for(;;){ 687*7dd7cddfSDavid du Colombier n = read(fd, buf, sizeof(buf)-1); 688*7dd7cddfSDavid du Colombier if(n <= 0) 689*7dd7cddfSDavid du Colombier break; 690*7dd7cddfSDavid du Colombier buf[n] = 0; 691*7dd7cddfSDavid du Colombier p = strstr(buf, token); 692*7dd7cddfSDavid du Colombier if(p != nil && (p == buf || *(p-1) == 0)){ 693*7dd7cddfSDavid du Colombier close(fd); 694*7dd7cddfSDavid du Colombier sp = strchr(p, ' '); 695*7dd7cddfSDavid du Colombier if(sp) 696*7dd7cddfSDavid du Colombier *sp = 0; 697*7dd7cddfSDavid du Colombier p = strchr(p, '='); 698*7dd7cddfSDavid du Colombier if(p == nil) 699*7dd7cddfSDavid du Colombier return nil; 700*7dd7cddfSDavid du Colombier return estrdup(p+1); 701*7dd7cddfSDavid du Colombier } 702*7dd7cddfSDavid du Colombier } 703*7dd7cddfSDavid du Colombier close(fd); 704*7dd7cddfSDavid du Colombier return nil; 705*7dd7cddfSDavid du Colombier } 706*7dd7cddfSDavid du Colombier 707*7dd7cddfSDavid du Colombier static void 708*7dd7cddfSDavid du Colombier getendpoint(char *dir, char *file, char **sysp, char **servp) 709*7dd7cddfSDavid du Colombier { 710*7dd7cddfSDavid du Colombier int fd, n; 711*7dd7cddfSDavid du Colombier char buf[128]; 712*7dd7cddfSDavid du Colombier char *sys, *serv; 713*7dd7cddfSDavid du Colombier 714*7dd7cddfSDavid du Colombier sys = serv = nil; 715*7dd7cddfSDavid du Colombier 716*7dd7cddfSDavid du Colombier snprint(buf, sizeof buf, "%s/%s", dir, file); 717*7dd7cddfSDavid du Colombier fd = open(buf, OREAD); 718*7dd7cddfSDavid du Colombier if(fd >= 0){ 719*7dd7cddfSDavid du Colombier n = read(fd, buf, sizeof(buf)-1); 720*7dd7cddfSDavid du Colombier if(n>0){ 721*7dd7cddfSDavid du Colombier buf[n-1] = 0; 722*7dd7cddfSDavid du Colombier serv = strchr(buf, '!'); 723*7dd7cddfSDavid du Colombier if(serv){ 724*7dd7cddfSDavid du Colombier *serv++ = 0; 725*7dd7cddfSDavid du Colombier serv = estrdup(serv); 726*7dd7cddfSDavid du Colombier } 727*7dd7cddfSDavid du Colombier sys = estrdup(buf); 728*7dd7cddfSDavid du Colombier } 729*7dd7cddfSDavid du Colombier close(fd); 730*7dd7cddfSDavid du Colombier } 731*7dd7cddfSDavid du Colombier if(serv == nil) 732*7dd7cddfSDavid du Colombier serv = estrdup("unknown"); 733*7dd7cddfSDavid du Colombier if(sys == nil) 734*7dd7cddfSDavid du Colombier sys = estrdup("unknown"); 735*7dd7cddfSDavid du Colombier *servp = serv; 736*7dd7cddfSDavid du Colombier *sysp = sys; 737*7dd7cddfSDavid du Colombier } 738*7dd7cddfSDavid du Colombier 739*7dd7cddfSDavid du Colombier static Endpoints* 740*7dd7cddfSDavid du Colombier getendpoints(char *dir) 741*7dd7cddfSDavid du Colombier { 742*7dd7cddfSDavid du Colombier Endpoints *ep; 743*7dd7cddfSDavid du Colombier 744*7dd7cddfSDavid du Colombier ep = ezalloc(sizeof(*ep)); 745*7dd7cddfSDavid du Colombier getendpoint(dir, "local", &ep->lsys, &ep->lserv); 746*7dd7cddfSDavid du Colombier getendpoint(dir, "remote", &ep->rsys, &ep->rserv); 747*7dd7cddfSDavid du Colombier return ep; 748*7dd7cddfSDavid du Colombier } 749