1/* 2 some simple CGI helper routines 3 Copyright (C) Andrew Tridgell 1997-1998 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17*/ 18 19 20#include "includes.h" 21#include "web/swat_proto.h" 22 23#define MAX_VARIABLES 10000 24 25/* set the expiry on fixed pages */ 26#define EXPIRY_TIME (60*60*24*7) 27 28#ifdef DEBUG_COMMENTS 29extern void print_title(char *fmt, ...); 30#endif 31 32struct cgi_var { 33 char *name; 34 char *value; 35}; 36 37static struct cgi_var variables[MAX_VARIABLES]; 38static int num_variables; 39static int content_length; 40static int request_post; 41static char *query_string; 42static const char *baseurl; 43static char *pathinfo; 44static char *C_user; 45static bool inetd_server; 46static bool got_request; 47 48static char *grab_line(FILE *f, int *cl) 49{ 50 char *ret = NULL; 51 int i = 0; 52 int len = 0; 53 54 while ((*cl)) { 55 int c; 56 57 if (i == len) { 58 char *ret2; 59 if (len == 0) len = 1024; 60 else len *= 2; 61 ret2 = (char *)SMB_REALLOC_KEEP_OLD_ON_ERROR(ret, len); 62 if (!ret2) return ret; 63 ret = ret2; 64 } 65 66 c = fgetc(f); 67 (*cl)--; 68 69 if (c == EOF) { 70 (*cl) = 0; 71 break; 72 } 73 74 if (c == '\r') continue; 75 76 if (strchr_m("\n&", c)) break; 77 78 ret[i++] = c; 79 80 } 81 82 if (ret) { 83 ret[i] = 0; 84 } 85 return ret; 86} 87 88/** 89 URL encoded strings can have a '+', which should be replaced with a space 90 91 (This was in rfc1738_unescape(), but that broke the squid helper) 92**/ 93 94static void plus_to_space_unescape(char *buf) 95{ 96 char *p=buf; 97 98 while ((p=strchr_m(p,'+'))) 99 *p = ' '; 100} 101 102/*************************************************************************** 103 load all the variables passed to the CGI program. May have multiple variables 104 with the same name and the same or different values. Takes a file parameter 105 for simulating CGI invocation eg loading saved preferences. 106 ***************************************************************************/ 107void cgi_load_variables(void) 108{ 109 static char *line; 110 char *p, *s, *tok; 111 int len, i; 112 FILE *f = stdin; 113 114#ifdef DEBUG_COMMENTS 115 char dummy[100]=""; 116 print_title(dummy); 117 d_printf("<!== Start dump in cgi_load_variables() %s ==>\n",__FILE__); 118#endif 119 120 if (!content_length) { 121 p = getenv("CONTENT_LENGTH"); 122 len = p?atoi(p):0; 123 } else { 124 len = content_length; 125 } 126 127 128 if (len > 0 && 129 (request_post || 130 ((s=getenv("REQUEST_METHOD")) && 131 strequal(s,"POST")))) { 132 while (len && (line=grab_line(f, &len))) { 133 p = strchr_m(line,'='); 134 if (!p) continue; 135 136 *p = 0; 137 138 variables[num_variables].name = SMB_STRDUP(line); 139 variables[num_variables].value = SMB_STRDUP(p+1); 140 141 SAFE_FREE(line); 142 143 if (!variables[num_variables].name || 144 !variables[num_variables].value) 145 continue; 146 147 plus_to_space_unescape(variables[num_variables].value); 148 rfc1738_unescape(variables[num_variables].value); 149 plus_to_space_unescape(variables[num_variables].name); 150 rfc1738_unescape(variables[num_variables].name); 151 152#ifdef DEBUG_COMMENTS 153 printf("<!== POST var %s has value \"%s\" ==>\n", 154 variables[num_variables].name, 155 variables[num_variables].value); 156#endif 157 158 num_variables++; 159 if (num_variables == MAX_VARIABLES) break; 160 } 161 } 162 163 fclose(stdin); 164 open("/dev/null", O_RDWR); 165 166 if ((s=query_string) || (s=getenv("QUERY_STRING"))) { 167 char *saveptr; 168 for (tok=strtok_r(s, "&;", &saveptr); tok; 169 tok=strtok_r(NULL, "&;", &saveptr)) { 170 p = strchr_m(tok,'='); 171 if (!p) continue; 172 173 *p = 0; 174 175 variables[num_variables].name = SMB_STRDUP(tok); 176 variables[num_variables].value = SMB_STRDUP(p+1); 177 178 if (!variables[num_variables].name || 179 !variables[num_variables].value) 180 continue; 181 182 plus_to_space_unescape(variables[num_variables].value); 183 rfc1738_unescape(variables[num_variables].value); 184 plus_to_space_unescape(variables[num_variables].name); 185 rfc1738_unescape(variables[num_variables].name); 186 187#ifdef DEBUG_COMMENTS 188 printf("<!== Commandline var %s has value \"%s\" ==>\n", 189 variables[num_variables].name, 190 variables[num_variables].value); 191#endif 192 num_variables++; 193 if (num_variables == MAX_VARIABLES) break; 194 } 195 196 } 197#ifdef DEBUG_COMMENTS 198 printf("<!== End dump in cgi_load_variables() ==>\n"); 199#endif 200 201 /* variables from the client are in UTF-8 - convert them 202 to our internal unix charset before use */ 203 for (i=0;i<num_variables;i++) { 204 TALLOC_CTX *frame = talloc_stackframe(); 205 char *dest = NULL; 206 size_t dest_len; 207 208 convert_string_talloc(frame, CH_UTF8, CH_UNIX, 209 variables[i].name, strlen(variables[i].name), 210 &dest, &dest_len, True); 211 SAFE_FREE(variables[i].name); 212 variables[i].name = SMB_STRDUP(dest ? dest : ""); 213 214 dest = NULL; 215 convert_string_talloc(frame, CH_UTF8, CH_UNIX, 216 variables[i].value, strlen(variables[i].value), 217 &dest, &dest_len, True); 218 SAFE_FREE(variables[i].value); 219 variables[i].value = SMB_STRDUP(dest ? dest : ""); 220 TALLOC_FREE(frame); 221 } 222} 223 224 225/*************************************************************************** 226 find a variable passed via CGI 227 Doesn't quite do what you think in the case of POST text variables, because 228 if they exist they might have a value of "" or even " ", depending on the 229 browser. Also doesn't allow for variables[] containing multiple variables 230 with the same name and the same or different values. 231 ***************************************************************************/ 232 233const char *cgi_variable(const char *name) 234{ 235 int i; 236 237 for (i=0;i<num_variables;i++) 238 if (strcmp(variables[i].name, name) == 0) 239 return variables[i].value; 240 return NULL; 241} 242 243/*************************************************************************** 244 Version of the above that can't return a NULL pointer. 245***************************************************************************/ 246 247const char *cgi_variable_nonull(const char *name) 248{ 249 const char *var = cgi_variable(name); 250 if (var) { 251 return var; 252 } else { 253 return ""; 254 } 255} 256 257/*************************************************************************** 258tell a browser about a fatal error in the http processing 259 ***************************************************************************/ 260static void cgi_setup_error(const char *err, const char *header, const char *info) 261{ 262 if (!got_request) { 263 /* damn browsers don't like getting cut off before they give a request */ 264 char line[1024]; 265 while (fgets(line, sizeof(line)-1, stdin)) { 266 if (strnequal(line,"GET ", 4) || 267 strnequal(line,"POST ", 5) || 268 strnequal(line,"PUT ", 4)) { 269 break; 270 } 271 } 272 } 273 274 d_printf("HTTP/1.0 %s\r\n%sConnection: close\r\nContent-Type: text/html\r\n\r\n<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY><H1>%s</H1>%s<p></BODY></HTML>\r\n\r\n", err, header, err, err, info); 275 fclose(stdin); 276 fclose(stdout); 277 exit(0); 278} 279 280 281/*************************************************************************** 282tell a browser about a fatal authentication error 283 ***************************************************************************/ 284static void cgi_auth_error(void) 285{ 286 if (inetd_server) { 287 cgi_setup_error("401 Authorization Required", 288 "WWW-Authenticate: Basic realm=\"SWAT\"\r\n", 289 "You must be authenticated to use this service"); 290 } else { 291 printf("Content-Type: text/html\r\n"); 292 293 printf("\r\n<HTML><HEAD><TITLE>SWAT</TITLE></HEAD>\n"); 294 printf("<BODY><H1>Installation Error</H1>\n"); 295 printf("SWAT must be installed via inetd. It cannot be run as a CGI script<p>\n"); 296 printf("</BODY></HTML>\r\n"); 297 } 298 exit(0); 299} 300 301/*************************************************************************** 302authenticate when we are running as a CGI 303 ***************************************************************************/ 304static void cgi_web_auth(void) 305{ 306 const char *user = getenv("REMOTE_USER"); 307 struct passwd *pwd; 308 const char *head = "Content-Type: text/html\r\n\r\n<HTML><BODY><H1>SWAT installation Error</H1>\n"; 309 const char *tail = "</BODY></HTML>\r\n"; 310 311 if (!user) { 312 printf("%sREMOTE_USER not set. Not authenticated by web server.<br>%s\n", 313 head, tail); 314 exit(0); 315 } 316 317 pwd = Get_Pwnam_alloc(talloc_autofree_context(), user); 318 if (!pwd) { 319 printf("%sCannot find user %s<br>%s\n", head, user, tail); 320 exit(0); 321 } 322 323 setuid(0); 324 setuid(pwd->pw_uid); 325 if (geteuid() != pwd->pw_uid || getuid() != pwd->pw_uid) { 326 printf("%sFailed to become user %s - uid=%d/%d<br>%s\n", 327 head, user, (int)geteuid(), (int)getuid(), tail); 328 exit(0); 329 } 330 TALLOC_FREE(pwd); 331} 332 333 334/*************************************************************************** 335handle a http authentication line 336 ***************************************************************************/ 337static bool cgi_handle_authorization(char *line) 338{ 339 char *p; 340 fstring user, user_pass; 341 struct passwd *pass = NULL; 342 343 if (!strnequal(line,"Basic ", 6)) { 344 goto err; 345 } 346 line += 6; 347 while (line[0] == ' ') line++; 348 base64_decode_inplace(line); 349 if (!(p=strchr_m(line,':'))) { 350 /* 351 * Always give the same error so a cracker 352 * cannot tell why we fail. 353 */ 354 goto err; 355 } 356 *p = 0; 357 358 convert_string(CH_UTF8, CH_UNIX, 359 line, -1, 360 user, sizeof(user), True); 361 362 convert_string(CH_UTF8, CH_UNIX, 363 p+1, -1, 364 user_pass, sizeof(user_pass), True); 365 366 /* 367 * Try and get the user from the UNIX password file. 368 */ 369 370 pass = Get_Pwnam_alloc(talloc_autofree_context(), user); 371 372 /* 373 * Validate the password they have given. 374 */ 375 376 if NT_STATUS_IS_OK(pass_check(pass, user, user_pass, 377 strlen(user_pass), NULL, False)) { 378 379 if (pass) { 380 /* 381 * Password was ok. 382 */ 383 384 if ( initgroups(pass->pw_name, pass->pw_gid) != 0 ) 385 goto err; 386 387 become_user_permanently(pass->pw_uid, pass->pw_gid); 388 389 /* Save the users name */ 390 C_user = SMB_STRDUP(user); 391 TALLOC_FREE(pass); 392 return True; 393 } 394 } 395 396err: 397 cgi_setup_error("401 Bad Authorization", 398 "WWW-Authenticate: Basic realm=\"SWAT\"\r\n", 399 "username or password incorrect"); 400 401 TALLOC_FREE(pass); 402 return False; 403} 404 405/*************************************************************************** 406is this root? 407 ***************************************************************************/ 408bool am_root(void) 409{ 410 if (geteuid() == 0) { 411 return( True); 412 } else { 413 return( False); 414 } 415} 416 417/*************************************************************************** 418return a ptr to the users name 419 ***************************************************************************/ 420char *cgi_user_name(void) 421{ 422 return(C_user); 423} 424 425 426/*************************************************************************** 427handle a file download 428 ***************************************************************************/ 429static void cgi_download(char *file) 430{ 431 SMB_STRUCT_STAT st; 432 char buf[1024]; 433 int fd, l, i; 434 char *p; 435 char *lang; 436 437 /* sanitise the filename */ 438 for (i=0;file[i];i++) { 439 if (!isalnum((int)file[i]) && !strchr_m("/.-_", file[i])) { 440 cgi_setup_error("404 File Not Found","", 441 "Illegal character in filename"); 442 } 443 } 444 445 if (sys_stat(file, &st, false) != 0) { 446 cgi_setup_error("404 File Not Found","", 447 "The requested file was not found"); 448 } 449 450 if (S_ISDIR(st.st_ex_mode)) 451 { 452 snprintf(buf, sizeof(buf), "%s/index.html", file); 453 if (!file_exist_stat(buf, &st, false) 454 || !S_ISREG(st.st_ex_mode)) 455 { 456 cgi_setup_error("404 File Not Found","", 457 "The requested file was not found"); 458 } 459 } 460 else if (S_ISREG(st.st_ex_mode)) 461 { 462 snprintf(buf, sizeof(buf), "%s", file); 463 } 464 else 465 { 466 cgi_setup_error("404 File Not Found","", 467 "The requested file was not found"); 468 } 469 470 fd = web_open(buf,O_RDONLY,0); 471 if (fd == -1) { 472 cgi_setup_error("404 File Not Found","", 473 "The requested file was not found"); 474 } 475 printf("HTTP/1.0 200 OK\r\n"); 476 if ((p=strrchr_m(buf, '.'))) { 477 if (strcmp(p,".gif")==0) { 478 printf("Content-Type: image/gif\r\n"); 479 } else if (strcmp(p,".jpg")==0) { 480 printf("Content-Type: image/jpeg\r\n"); 481 } else if (strcmp(p,".png")==0) { 482 printf("Content-Type: image/png\r\n"); 483 } else if (strcmp(p,".css")==0) { 484 printf("Content-Type: text/css\r\n"); 485 } else if (strcmp(p,".txt")==0) { 486 printf("Content-Type: text/plain\r\n"); 487 } else { 488 printf("Content-Type: text/html\r\n"); 489 } 490 } 491 printf("Expires: %s\r\n", 492 http_timestring(talloc_tos(), time(NULL)+EXPIRY_TIME)); 493 494 lang = lang_tdb_current(); 495 if (lang) { 496 printf("Content-Language: %s\r\n", lang); 497 } 498 499 printf("Content-Length: %d\r\n\r\n", (int)st.st_ex_size); 500 while ((l=read(fd,buf,sizeof(buf)))>0) { 501 if (fwrite(buf, 1, l, stdout) != l) { 502 break; 503 } 504 } 505 close(fd); 506 exit(0); 507} 508 509 510 511 512/** 513 * @brief Setup the CGI framework. 514 * 515 * Setup the cgi framework, handling the possibility that this program 516 * is either run as a true CGI program with a gateway to a web server, or 517 * is itself a mini web server. 518 **/ 519void cgi_setup(const char *rootdir, int auth_required) 520{ 521 bool authenticated = False; 522 char line[1024]; 523 char *url=NULL; 524 char *p; 525 char *lang; 526 527 if (chdir(rootdir)) { 528 cgi_setup_error("500 Server Error", "", 529 "chdir failed - the server is not configured correctly"); 530 } 531 532 /* Handle the possibility we might be running as non-root */ 533 sec_init(); 534 535 if ((lang=getenv("HTTP_ACCEPT_LANGUAGE"))) { 536 /* if running as a cgi program */ 537 web_set_lang(lang); 538 } 539 540 /* maybe we are running under a web server */ 541 if (getenv("CONTENT_LENGTH") || getenv("REQUEST_METHOD")) { 542 if (auth_required) { 543 cgi_web_auth(); 544 } 545 return; 546 } 547 548 inetd_server = True; 549 550 if (!check_access(1, lp_hostsallow(-1), lp_hostsdeny(-1))) { 551 cgi_setup_error("403 Forbidden", "", 552 "Samba is configured to deny access from this client\n<br>Check your \"hosts allow\" and \"hosts deny\" options in smb.conf "); 553 } 554 555 /* we are a mini-web server. We need to read the request from stdin 556 and handle authentication etc */ 557 while (fgets(line, sizeof(line)-1, stdin)) { 558 if (line[0] == '\r' || line[0] == '\n') break; 559 if (strnequal(line,"GET ", 4)) { 560 got_request = True; 561 url = SMB_STRDUP(&line[4]); 562 } else if (strnequal(line,"POST ", 5)) { 563 got_request = True; 564 request_post = 1; 565 url = SMB_STRDUP(&line[5]); 566 } else if (strnequal(line,"PUT ", 4)) { 567 got_request = True; 568 cgi_setup_error("400 Bad Request", "", 569 "This server does not accept PUT requests"); 570 } else if (strnequal(line,"Authorization: ", 15)) { 571 authenticated = cgi_handle_authorization(&line[15]); 572 } else if (strnequal(line,"Content-Length: ", 16)) { 573 content_length = atoi(&line[16]); 574 } else if (strnequal(line,"Accept-Language: ", 17)) { 575 web_set_lang(&line[17]); 576 } 577 /* ignore all other requests! */ 578 } 579 580 if (auth_required && !authenticated) { 581 cgi_auth_error(); 582 } 583 584 if (!url) { 585 cgi_setup_error("400 Bad Request", "", 586 "You must specify a GET or POST request"); 587 } 588 589 /* trim the URL */ 590 if ((p = strchr_m(url,' ')) || (p=strchr_m(url,'\t'))) { 591 *p = 0; 592 } 593 while (*url && strchr_m("\r\n",url[strlen(url)-1])) { 594 url[strlen(url)-1] = 0; 595 } 596 597 /* anything following a ? in the URL is part of the query string */ 598 if ((p=strchr_m(url,'?'))) { 599 query_string = p+1; 600 *p = 0; 601 } 602 603 string_sub(url, "/swat/", "", 0); 604 605 if (url[0] != '/' && strstr(url,"..")==0) { 606 cgi_download(url); 607 } 608 609 printf("HTTP/1.0 200 OK\r\nConnection: close\r\n"); 610 printf("Date: %s\r\n", http_timestring(talloc_tos(), time(NULL))); 611 baseurl = ""; 612 pathinfo = url+1; 613} 614 615 616/*************************************************************************** 617return the current pages URL 618 ***************************************************************************/ 619const char *cgi_baseurl(void) 620{ 621 if (inetd_server) { 622 return baseurl; 623 } 624 return getenv("SCRIPT_NAME"); 625} 626 627/*************************************************************************** 628return the current pages path info 629 ***************************************************************************/ 630const char *cgi_pathinfo(void) 631{ 632 char *r; 633 if (inetd_server) { 634 return pathinfo; 635 } 636 r = getenv("PATH_INFO"); 637 if (!r) return ""; 638 if (*r == '/') r++; 639 return r; 640} 641 642/*************************************************************************** 643return the hostname of the client 644 ***************************************************************************/ 645const char *cgi_remote_host(void) 646{ 647 if (inetd_server) { 648 return get_peer_name(1,False); 649 } 650 return getenv("REMOTE_HOST"); 651} 652 653/*************************************************************************** 654return the hostname of the client 655 ***************************************************************************/ 656const char *cgi_remote_addr(void) 657{ 658 if (inetd_server) { 659 char addr[INET6_ADDRSTRLEN]; 660 get_peer_addr(1,addr,sizeof(addr)); 661 return talloc_strdup(talloc_tos(), addr); 662 } 663 return getenv("REMOTE_ADDR"); 664} 665 666 667/*************************************************************************** 668return True if the request was a POST 669 ***************************************************************************/ 670bool cgi_waspost(void) 671{ 672 if (inetd_server) { 673 return request_post; 674 } 675 return strequal(getenv("REQUEST_METHOD"), "POST"); 676} 677