source: branches/samba-3.0/source/web/cgi.c@ 347

Last change on this file since 347 was 119, checked in by Paul Smedley, 18 years ago

No need to change user on OS2 when running as Apache cgi

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