1 /* $NetBSD: cgi-bozo.c,v 1.6 2007/11/04 15:20:12 rtr Exp $ */ 2 3 /* $eterna: cgi-bozo.c,v 1.13 2006/05/17 08:19:10 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2006 Matthew R. Green 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer and 16 * dedication in the documentation and/or other materials provided 17 * with the distribution. 18 * 3. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 */ 34 35 /* this code implements CGI/1.2 for bozohttpd */ 36 37 #ifndef NO_CGIBIN_SUPPORT 38 39 #include <sys/param.h> 40 #include <sys/socket.h> 41 42 #include <ctype.h> 43 #include <errno.h> 44 #include <paths.h> 45 #include <signal.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include <netinet/in.h> 51 52 #include "bozohttpd.h" 53 54 #define CGIBIN_PREFIX "cgi-bin/" 55 #define CGIBIN_PREFIX_LEN (sizeof(CGIBIN_PREFIX)-1) 56 57 static char *cgibin; /* cgi-bin directory */ 58 static int Cflag; /* added a cgi handler, always process_cgi() */ 59 60 static const char * content_cgihandler(http_req *, const char *); 61 static void finish_cgi_output(http_req *request, int, int); 62 static int parse_header(const char *, ssize_t, char **, char **); 63 64 void 65 set_cgibin(char *path) 66 { 67 cgibin = path; 68 debug((DEBUG_OBESE, "cgibin (cgi-bin directory) is %s", cgibin)); 69 } 70 71 /* help build up the environ pointer */ 72 void 73 spsetenv(const char *env, const char *val, char **envp) 74 { 75 char *s1 = bozomalloc(strlen(env) + strlen(val) + 2); 76 77 strcpy(s1, env); 78 strcat(s1, "="); 79 strcat(s1, val); 80 debug((DEBUG_OBESE, "spsetenv: %s", s1)); 81 *envp = s1; 82 } 83 84 /* 85 * Checks if the request has asked for a cgi-bin. Should only be called if 86 * cgibin is set. If it starts CGIBIN_PREFIX or has a ncontent handler, 87 * process the cgi, otherwise just return. 88 */ 89 void 90 process_cgi(http_req *request) 91 { 92 char buf[WRSZ]; 93 struct headers *headp; 94 const char *type, *clen, *info, *cgihandler; 95 char *query, *s, *t, *path, *env, *command, *url; 96 char **envp, **curenvp, *argv[4]; 97 size_t len; 98 ssize_t rbytes; 99 pid_t pid; 100 int envpsize, ix, nph; 101 int sv[2]; 102 103 if (!cgibin && !Cflag) 104 return; 105 106 debug((DEBUG_NORMAL, "process_cgi: url `%s'", request->hr_url)); 107 108 url = bozostrdup(request->hr_url); 109 if ((s = strchr(url, '?')) != NULL) { 110 *s++ = '\0'; 111 query = s; 112 } else 113 query = NULL; 114 path = NULL; 115 envp = NULL; 116 cgihandler = NULL; 117 command = NULL; 118 info = NULL; 119 120 len = strlen(url); 121 if (len == 0 || url[len - 1] == '/') { /* append index.html */ 122 debug((DEBUG_FAT, "appending index.html")); 123 url = bozorealloc(url, len + strlen(index_html) + 1); 124 strcat(url, index_html); 125 debug((DEBUG_NORMAL, "process_cgi: url adjusted to `%s'", url)); 126 } 127 128 auth_check(request, url + 1); 129 130 if (!cgibin || strncmp(url + 1, CGIBIN_PREFIX, CGIBIN_PREFIX_LEN) != 0) { 131 cgihandler = content_cgihandler(request, url + 1); 132 if (cgihandler == NULL) { 133 free(url); 134 return; 135 } 136 debug((DEBUG_NORMAL, "process_cgi: cgihandler `%s'", 137 cgihandler)); 138 } 139 140 ix = 0; 141 if (cgihandler) { 142 command = url + 1; 143 path = bozostrdup(cgihandler); 144 argv[ix++] = path; 145 /* argv[] = [ path, command, query, NULL ] */ 146 } else { 147 command = url + CGIBIN_PREFIX_LEN + 1; 148 if ((s = strchr(command, '/')) != NULL) { 149 info = bozostrdup(s); 150 *s = '\0'; 151 } 152 path = bozomalloc(strlen(cgibin) + 1 + strlen(command) + 1); 153 strcpy(path, cgibin); 154 strcat(path, "/"); 155 strcat(path, command); 156 /* argv[] = [ command, query, NULL ] */ 157 } 158 argv[ix++] = command; 159 argv[ix++] = query; 160 argv[ix++] = NULL; 161 162 nph = strncmp(command, "nph-", 4) == 0; 163 164 debug((DEBUG_FAT, 165 "process_cgi: path `%s' cmd `%s' info `%s' query `%s' nph `%d'", 166 path, command, strornull(info), strornull(query), nph)); 167 168 type = request->hr_content_type; 169 clen = request->hr_content_length; 170 171 envpsize = 13 + request->hr_nheaders + 172 (info && *info ? 1 : 0) + 173 (query && *query ? 1 : 0) + 174 (type && *type ? 1 : 0) + 175 (clen && *clen ? 1 : 0) + 176 (request->hr_remotehost && *request->hr_remotehost ? 1 : 0) + 177 (request->hr_remoteaddr && *request->hr_remoteaddr ? 1 : 0) + 178 auth_cgi_count(request) + 179 (request->hr_serverport && *request->hr_serverport ? 1 : 0); 180 181 envp = bozomalloc(sizeof(*envp) * envpsize); 182 for (ix = 0; ix < envpsize; ix++) 183 envp[ix] = NULL; 184 curenvp = envp; 185 186 SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) { 187 const char *s2; 188 env = bozomalloc(6 + strlen(headp->h_header) + 1 + 189 strlen(headp->h_value)); 190 191 t = env; 192 strcpy(t, "HTTP_"); 193 t += strlen(t); 194 for (s2 = headp->h_header; *s2; t++, s2++) 195 if (islower((u_int)*s2)) 196 *t = toupper((u_int)*s2); 197 else if (*s2 == '-') 198 *t = '_'; 199 else 200 *t = *s2; 201 *t = '\0'; 202 debug((DEBUG_OBESE, "setting header %s as %s = %s", 203 headp->h_header, env, headp->h_value)); 204 spsetenv(env, headp->h_value, curenvp++); 205 free(env); 206 } 207 208 #ifndef _PATH_DEFPATH 209 #define _PATH_DEFPATH "/usr/bin:/bin" 210 #endif 211 212 spsetenv("PATH", _PATH_DEFPATH, curenvp++); 213 spsetenv("IFS", " \t\n", curenvp++); 214 spsetenv("SERVER_NAME", myname, curenvp++); 215 spsetenv("GATEWAY_INTERFACE", "CGI/1.1", curenvp++); 216 spsetenv("SERVER_PROTOCOL", request->hr_proto, curenvp++); 217 spsetenv("REQUEST_METHOD", request->hr_methodstr, curenvp++); 218 spsetenv("SCRIPT_NAME", url, curenvp++); 219 spsetenv("SCRIPT_FILENAME", url + 1, curenvp++); 220 spsetenv("SERVER_SOFTWARE", server_software, curenvp++); 221 spsetenv("REQUEST_URI", request->hr_url, curenvp++); 222 spsetenv("DATE_GMT", http_date(), curenvp++); 223 if (query && *query) 224 spsetenv("QUERY_STRING", query, curenvp++); 225 if (info && *info) 226 spsetenv("PATH_INFO", info, curenvp++); 227 if (type && *type) 228 spsetenv("CONTENT_TYPE", type, curenvp++); 229 if (clen && *clen) 230 spsetenv("CONTENT_LENGTH", clen, curenvp++); 231 if (request->hr_serverport && *request->hr_serverport) 232 spsetenv("SERVER_PORT", request->hr_serverport, curenvp++); 233 if (request->hr_remotehost && *request->hr_remotehost) 234 spsetenv("REMOTE_HOST", request->hr_remotehost, curenvp++); 235 if (request->hr_remoteaddr && *request->hr_remoteaddr) 236 spsetenv("REMOTE_ADDR", request->hr_remoteaddr, curenvp++); 237 auth_cgi_setenv(request, &curenvp); 238 239 debug((DEBUG_FAT, "process_cgi: going exec %s, %s %s %s", 240 path, argv[0], strornull(argv[1]), strornull(argv[2]))); 241 242 if (-1 == socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv)) 243 error(1, "child socketpair failed: %s", strerror(errno)); 244 245 /* 246 * We create 2 procs: one to become the CGI, one read from 247 * the CGI and output to the network, and this parent will 248 * continue reading from the network and writing to the 249 * CGI procsss. 250 */ 251 switch (fork()) { 252 case -1: /* eep, failure */ 253 error(1, "child fork failed: %s", strerror(errno)); 254 case 0: 255 close(sv[0]); 256 dup2(sv[1], STDIN_FILENO); 257 dup2(sv[1], STDOUT_FILENO); 258 259 if (-1 == execve(path, argv, envp)) 260 error(1, "child exec failed: %s", path); 261 /* NOT REACHED */ 262 error(1, "child execve returned?!"); 263 } 264 265 close(sv[1]); 266 267 /* parent: read from stdin (bozoread()) write to sv[0] */ 268 /* child: read from sv[0] (bozowrite()) write to stdout */ 269 pid = fork(); 270 if (pid == -1) 271 error(1, "io child fork failed: %s", strerror(errno)); 272 else if (pid == 0) { 273 /* child reader/writer */ 274 close(STDIN_FILENO); 275 finish_cgi_output(request, sv[0], nph); 276 /* if we're done output, our parent is useless... */ 277 kill(getppid(), SIGKILL); 278 debug((DEBUG_FAT, "done processing cgi output")); 279 _exit(0); 280 } 281 close(STDOUT_FILENO); 282 283 /* XXX we should have some goo that times us out 284 */ 285 while ((rbytes = bozoread(STDIN_FILENO, buf, sizeof buf)) > 0) { 286 ssize_t wbytes; 287 char *bp = buf; 288 289 while (rbytes) { 290 wbytes = write(sv[0], buf , rbytes); 291 if (wbytes > 0) { 292 rbytes -= wbytes; 293 bp += wbytes; 294 } else 295 error(1, "write failed: %s", strerror(errno)); 296 } 297 } 298 debug((DEBUG_FAT, "done processing cgi input")); 299 exit(0); 300 } 301 302 /* 303 * handle parsing a CGI header output, transposing a Status: header 304 * into the HTTP reply (ie, instead of "200 OK"). 305 */ 306 static void 307 finish_cgi_output(http_req *request, int in, int nph) 308 { 309 char buf[WRSZ]; 310 char *str; 311 size_t len; 312 ssize_t rbytes; 313 SIMPLEQ_HEAD(, headers) headers; 314 struct headers *hdr; 315 int write_header, nheaders = 0; 316 317 /* much of this code is like read_request()'s header loop. hmmm... */ 318 SIMPLEQ_INIT(&headers); 319 write_header = nph == 0; 320 while (nph == 0 && (str = dgetln(in, (ssize_t *)&len, read)) != NULL) { 321 char * hdr_name, * hdr_value; 322 323 if (parse_header(str, (ssize_t)len, &hdr_name, &hdr_value)) 324 break; 325 326 /* 327 * The CGI 1.{1,2} spec both say that if the cgi program 328 * returns a `Status:' header field then the server MUST 329 * return it in the response. If the cgi program does 330 * not return any `Status:' header then the server should 331 * respond with 200 OK. 332 * XXX The CGI 1.1 and 1.2 specification differ slightly on 333 * this in that v1.2 says that the script MUST NOT return a 334 * `Status:' header if it is returning a `Location:' header. 335 * For compatibility we are going with the CGI 1.1 behavior. 336 */ 337 if (strcasecmp(hdr_name, "status") == 0) { 338 debug((DEBUG_OBESE, "process_cgi: writing HTTP header " 339 "from status %s ..", hdr_value)); 340 bozoprintf("%s %s\r\n", request->hr_proto, hdr_value); 341 bozoflush(stdout); 342 write_header = 0; 343 free(hdr_name); 344 break; 345 } 346 347 hdr = bozomalloc(sizeof *hdr); 348 hdr->h_header = hdr_name; 349 hdr->h_value = hdr_value; 350 SIMPLEQ_INSERT_TAIL(&headers, hdr, h_next); 351 nheaders++; 352 } 353 354 if (write_header) { 355 debug((DEBUG_OBESE, "process_cgi: writing HTTP header ..")); 356 bozoprintf("%s 200 OK\r\n", request->hr_proto); 357 bozoflush(stdout); 358 } 359 360 if (nheaders) { 361 debug((DEBUG_OBESE, "process_cgi: writing delayed HTTP " 362 "headers ..")); 363 SIMPLEQ_FOREACH(hdr, &headers, h_next) { 364 bozoprintf("%s: %s\r\n", hdr->h_header, hdr->h_value); 365 free(hdr->h_header); 366 free(hdr); 367 } 368 bozoflush(stdout); 369 } 370 371 /* XXX we should have some goo that times us out 372 */ 373 while ((rbytes = read(in, buf, sizeof buf)) > 0) { 374 ssize_t wbytes; 375 char *bp = buf; 376 377 while (rbytes) { 378 wbytes = bozowrite(STDOUT_FILENO, buf, rbytes); 379 if (wbytes > 0) { 380 rbytes -= wbytes; 381 bp += wbytes; 382 } else 383 error(1, "cgi output write failed: %s", 384 strerror(errno)); 385 } 386 } 387 } 388 389 static int 390 parse_header(const char * str, ssize_t len, char ** hdr_str, char ** hdr_val) 391 { 392 char * name, * value; 393 394 /* if the string passed is zero-length bail out */ 395 if (*str == '\0') 396 return -1; 397 398 name = value = bozostrdup(str); 399 400 /* locate the ':' separator in the header/value */ 401 name = strnsep(&value, ":", &len); 402 403 if (NULL == name || -1 == len) { 404 free(name); 405 return -1; 406 } 407 408 /* skip leading space/tab */ 409 while (*value == ' ' || *value == '\t') 410 len--, value++; 411 412 *hdr_str = name; 413 *hdr_val = value; 414 415 return 0; 416 } 417 418 /* 419 * given the file name, return a CGI interpreter 420 */ 421 static const char * 422 content_cgihandler(http_req *request, const char *file) 423 { 424 struct content_map *map; 425 426 map = match_content_map(file, 0); 427 if (map) 428 return (map->cgihandler); 429 return (NULL); 430 } 431 432 #ifndef NO_DYNAMIC_CONTENT 433 /* cgi maps are simple ".postfix /path/to/prog" */ 434 void 435 add_content_map_cgi(char *arg, char *cgihandler) 436 { 437 struct content_map *map; 438 439 debug((DEBUG_NORMAL, "add_content_map_cgi: name %s cgi %s", arg, cgihandler)); 440 441 Cflag = 1; 442 443 map = get_content_map(arg); 444 map->name = arg; 445 map->type = map->encoding = map->encoding11 = NULL; 446 map->cgihandler = cgihandler; 447 } 448 #endif /* NO_DYNAMIC_CONTENT */ 449 450 #endif /* NO_CGIBIN_SUPPORT */ 451