| | @@ -567,10 +567,54 @@ |
| 567 | 567 | ** Send an HTTP redirect back to the designated Index Page. |
| 568 | 568 | */ |
| 569 | 569 | void fossil_redirect_home(void){ |
| 570 | 570 | cgi_redirectf("%s%s", g.zBaseURL, db_get("index-page", "/index")); |
| 571 | 571 | } |
| 572 | + |
| 573 | +/* |
| 574 | +** If running as root, chroot to the directory containing the |
| 575 | +** repository zRepo and then drop root privileges. Return the |
| 576 | +** new repository name. |
| 577 | +** |
| 578 | +** zRepo might be a directory itself. In that case chroot into |
| 579 | +** the directory zRepo. |
| 580 | +** |
| 581 | +** Assume the user-id and group-id of the repository, or if zRepo |
| 582 | +** is a directory, of that directory. |
| 583 | +*/ |
| 584 | +static char *enter_chroot_jail(char *zRepo){ |
| 585 | +#if !defined(__MINGW32__) |
| 586 | + if( getuid()==0 ){ |
| 587 | + int i; |
| 588 | + struct stat sStat; |
| 589 | + Blob dir; |
| 590 | + char *zDir; |
| 591 | + |
| 592 | + file_canonical_name(zRepo, &dir); |
| 593 | + zDir = blob_str(&dir); |
| 594 | + if( file_isdir(zDir)==1 ){ |
| 595 | + chdir(zDir); |
| 596 | + chroot(zDir); |
| 597 | + zRepo = "/"; |
| 598 | + }else{ |
| 599 | + for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} |
| 600 | + if( zDir[i]!='/' ) fossil_panic("bad repository name: %s", zRepo); |
| 601 | + zDir[i] = 0; |
| 602 | + chdir(zDir); |
| 603 | + chroot(zDir); |
| 604 | + zDir[i] = '/'; |
| 605 | + zRepo = &zDir[i]; |
| 606 | + } |
| 607 | + if( stat(zRepo, &sStat)!=0 ){ |
| 608 | + fossil_fatal("cannot stat() repository: %s", zRepo); |
| 609 | + } |
| 610 | + setgid(sStat.st_gid); |
| 611 | + setuid(sStat.st_uid); |
| 612 | + } |
| 613 | +#endif |
| 614 | + return zRepo; |
| 615 | +} |
| 572 | 616 | |
| 573 | 617 | /* |
| 574 | 618 | ** Preconditions: |
| 575 | 619 | ** |
| 576 | 620 | ** * Environment variables are set up according to the CGI standard. |
| | @@ -580,11 +624,11 @@ |
| 580 | 624 | ** and the actual repository is taken from the first element of PATH_INFO. |
| 581 | 625 | ** |
| 582 | 626 | ** Process the webpage specified by the PATH_INFO or REQUEST_URI |
| 583 | 627 | ** environment variable. |
| 584 | 628 | */ |
| 585 | | -static void process_one_web_page(void){ |
| 629 | +static void process_one_web_page(const char *zNotFound){ |
| 586 | 630 | const char *zPathInfo; |
| 587 | 631 | char *zPath = NULL; |
| 588 | 632 | int idx; |
| 589 | 633 | int i; |
| 590 | 634 | |
| | @@ -608,13 +652,17 @@ |
| 608 | 652 | for(j=strlen(g.zRepositoryName)+1, k=0; k<i-1; j++, k++){ |
| 609 | 653 | if( !isalnum(zRepo[j]) && zRepo[j]!='-' ) zRepo[j] = '_'; |
| 610 | 654 | } |
| 611 | 655 | |
| 612 | 656 | if( file_size(zRepo)<1024 ){ |
| 613 | | - @ <h1>Not Found</h1> |
| 614 | | - cgi_set_status(404, "not found"); |
| 615 | | - cgi_reply(); |
| 657 | + if( zNotFound ){ |
| 658 | + cgi_redirect(zNotFound); |
| 659 | + }else{ |
| 660 | + @ <h1>Not Found</h1> |
| 661 | + cgi_set_status(404, "not found"); |
| 662 | + cgi_reply(); |
| 663 | + } |
| 616 | 664 | return; |
| 617 | 665 | } |
| 618 | 666 | zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo); |
| 619 | 667 | cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]); |
| 620 | 668 | zPathInfo += i; |
| | @@ -703,10 +751,11 @@ |
| 703 | 751 | ** the repository, fossil will generate a webpage on stdout based on |
| 704 | 752 | ** the values of standard CGI environment variables. |
| 705 | 753 | */ |
| 706 | 754 | void cmd_cgi(void){ |
| 707 | 755 | const char *zFile; |
| 756 | + const char *zNotFound = 0; |
| 708 | 757 | Blob config, line, key, value; |
| 709 | 758 | if( g.argc==3 && strcmp(g.argv[1],"cgi")==0 ){ |
| 710 | 759 | zFile = g.argv[2]; |
| 711 | 760 | }else{ |
| 712 | 761 | zFile = g.argv[1]; |
| | @@ -740,19 +789,30 @@ |
| 740 | 789 | continue; |
| 741 | 790 | } |
| 742 | 791 | if( blob_eq(&key, "repository:") && blob_token(&line, &value) ){ |
| 743 | 792 | db_open_repository(blob_str(&value)); |
| 744 | 793 | blob_reset(&value); |
| 745 | | - blob_reset(&config); |
| 746 | | - break; |
| 794 | + continue; |
| 795 | + } |
| 796 | + if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){ |
| 797 | + db_close(); |
| 798 | + g.zRepositoryName = mprintf("%s", blob_str(&value)); |
| 799 | + blob_reset(&value); |
| 800 | + continue; |
| 801 | + } |
| 802 | + if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){ |
| 803 | + zNotFound = mprintf("%s", blob_str(&value)); |
| 804 | + blob_reset(&value); |
| 805 | + continue; |
| 747 | 806 | } |
| 748 | 807 | } |
| 749 | | - if( g.db==0 ){ |
| 808 | + blob_reset(&config); |
| 809 | + if( g.db==0 && g.zRepositoryName==0 ){ |
| 750 | 810 | cgi_panic("Unable to find or open the project repository"); |
| 751 | 811 | } |
| 752 | 812 | cgi_init(); |
| 753 | | - process_one_web_page(); |
| 813 | + process_one_web_page(zNotFound); |
| 754 | 814 | } |
| 755 | 815 | |
| 756 | 816 | /* |
| 757 | 817 | ** If g.argv[2] exists then it is either the name of a repository |
| 758 | 818 | ** that will be used by a server, or else it is a directory that |
| | @@ -789,45 +849,30 @@ |
| 789 | 849 | ** |
| 790 | 850 | ** The argv==6 form is used by the win32 server only. |
| 791 | 851 | ** |
| 792 | 852 | ** COMMAND: http |
| 793 | 853 | ** |
| 794 | | -** Usage: %fossil http REPOSITORY |
| 854 | +** Usage: %fossil http REPOSITORY [--notfound URL] |
| 795 | 855 | ** |
| 796 | 856 | ** Handle a single HTTP request appearing on stdin. The resulting webpage |
| 797 | 857 | ** is delivered on stdout. This method is used to launch an HTTP request |
| 798 | 858 | ** handler from inetd, for example. The argument is the name of the |
| 799 | 859 | ** repository. |
| 800 | 860 | ** |
| 801 | 861 | ** If REPOSITORY is a directory that contains one or more respositories |
| 802 | 862 | ** with names of the form "*.fossil" then the first element of the URL |
| 803 | | -** pathname selects among the various repositories. |
| 863 | +** pathname selects among the various repositories. If the pathname does |
| 864 | +** not select a valid repository and the --notfound option is available, |
| 865 | +** then the server redirects (HTTP code 302) to the URL of --notfound. |
| 804 | 866 | */ |
| 805 | 867 | void cmd_http(void){ |
| 806 | 868 | const char *zIpAddr; |
| 869 | + const char *zNotFound; |
| 870 | + zNotFound = find_option("notfound", 0, 1); |
| 807 | 871 | if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){ |
| 808 | 872 | cgi_panic("no repository specified"); |
| 809 | 873 | } |
| 810 | | -#if !defined(__MINGW32__) |
| 811 | | - if( g.argc==3 && getuid()==0 ){ |
| 812 | | - int i; |
| 813 | | - char *zRepo = g.argv[2]; |
| 814 | | - struct stat sStat; |
| 815 | | - for(i=strlen(zRepo)-1; i>0 && zRepo[i]!='/'; i--){} |
| 816 | | - if( zRepo[i]=='/' ){ |
| 817 | | - zRepo[i] = 0; |
| 818 | | - chdir(g.argv[2]); |
| 819 | | - chroot(g.argv[2]); |
| 820 | | - g.argv[2] = &zRepo[i+1]; |
| 821 | | - } |
| 822 | | - if( stat(g.argv[2], &sStat)!=0 ){ |
| 823 | | - fossil_fatal("cannot stat() repository: %s", g.argv[2]); |
| 824 | | - } |
| 825 | | - setgid(sStat.st_gid); |
| 826 | | - setuid(sStat.st_uid); |
| 827 | | - } |
| 828 | | -#endif |
| 829 | 874 | g.cgiPanic = 1; |
| 830 | 875 | g.fullHttpReply = 1; |
| 831 | 876 | if( g.argc==6 ){ |
| 832 | 877 | g.httpIn = fopen(g.argv[3], "rb"); |
| 833 | 878 | g.httpOut = fopen(g.argv[4], "wb"); |
| | @@ -836,12 +881,13 @@ |
| 836 | 881 | g.httpIn = stdin; |
| 837 | 882 | g.httpOut = stdout; |
| 838 | 883 | zIpAddr = 0; |
| 839 | 884 | } |
| 840 | 885 | find_server_repository(0); |
| 886 | + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); |
| 841 | 887 | cgi_handle_http_request(zIpAddr); |
| 842 | | - process_one_web_page(); |
| 888 | + process_one_web_page(zNotFound); |
| 843 | 889 | } |
| 844 | 890 | |
| 845 | 891 | /* |
| 846 | 892 | ** COMMAND: test-http |
| 847 | 893 | ** Works like the http command but gives setup permission to all users. |
| | @@ -896,15 +942,16 @@ |
| 896 | 942 | ** that contains one or more respositories with names ending in ".fossil". |
| 897 | 943 | ** In that case, the first element of the URL is used to select among the |
| 898 | 944 | ** various repositories. |
| 899 | 945 | */ |
| 900 | 946 | void cmd_webserver(void){ |
| 901 | | - int iPort, mxPort; |
| 902 | | - const char *zPort; |
| 903 | | - char *zBrowser; |
| 904 | | - char *zBrowserCmd = 0; |
| 947 | + int iPort, mxPort; /* Range of TCP ports allowed */ |
| 948 | + const char *zPort; /* Value of the --port option */ |
| 949 | + char *zBrowser; /* Name of web browser program */ |
| 950 | + char *zBrowserCmd = 0; /* Command to launch the web browser */ |
| 905 | 951 | int isUiCmd; /* True if command is "ui", not "server' */ |
| 952 | + const char *zNotFound; /* The --notfound option or NULL */ |
| 906 | 953 | |
| 907 | 954 | #ifdef __MINGW32__ |
| 908 | 955 | const char *zStopperFile; /* Name of file used to terminate server */ |
| 909 | 956 | zStopperFile = find_option("stopper", 0, 1); |
| 910 | 957 | #endif |
| | @@ -912,10 +959,11 @@ |
| 912 | 959 | g.thTrace = find_option("th-trace", 0, 0)!=0; |
| 913 | 960 | if( g.thTrace ){ |
| 914 | 961 | blob_zero(&g.thLog); |
| 915 | 962 | } |
| 916 | 963 | zPort = find_option("port", "P", 1); |
| 964 | + zNotFound = find_option("notfound", 0, 1); |
| 917 | 965 | if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); |
| 918 | 966 | isUiCmd = g.argv[1][0]=='u'; |
| 919 | 967 | find_server_repository(isUiCmd); |
| 920 | 968 | if( zPort ){ |
| 921 | 969 | iPort = mxPort = atoi(zPort); |
| | @@ -953,17 +1001,18 @@ |
| 953 | 1001 | if( g.fHttpTrace || g.fSqlTrace ){ |
| 954 | 1002 | fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); |
| 955 | 1003 | } |
| 956 | 1004 | g.cgiPanic = 1; |
| 957 | 1005 | find_server_repository(isUiCmd); |
| 1006 | + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); |
| 958 | 1007 | cgi_handle_http_request(0); |
| 959 | | - process_one_web_page(); |
| 1008 | + process_one_web_page(zNotFound); |
| 960 | 1009 | #else |
| 961 | 1010 | /* Win32 implementation */ |
| 962 | 1011 | if( isUiCmd ){ |
| 963 | 1012 | zBrowser = db_get("web-browser", "start"); |
| 964 | 1013 | zBrowserCmd = mprintf("%s http://127.0.0.1:%%d/", zBrowser); |
| 965 | 1014 | } |
| 966 | 1015 | db_close(); |
| 967 | | - win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile); |
| 1016 | + win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zNotFound); |
| 968 | 1017 | #endif |
| 969 | 1018 | } |
| 970 | 1019 | |