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