Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -9,10 +9,11 @@ with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} internal-sqlite=1 => {Don't use the internal sqlite, use the system one} static=0 => {Link a static executable} lineedit=1 => {Disable line editing} fossil-debug=0 => {Build with fossil debugging enabled} + ipv6=1 => {Disable IPv6 support} json=0 => {Build with fossil JSON API enabled} } # sqlite wants these types if possible cc-with {-includes {stdint.h inttypes.h}} { @@ -72,10 +73,19 @@ if {[opt-bool static]} { # XXX: This will not work on all systems. define-append EXTRA_LDFLAGS -static } + +if {[opt-bool ipv6]} { + define-append EXTRA_CFLAGS -DWITH_IPV6 + msg-result "IPv6 support enabled" + if {[cc-check-functions getaddrinfo]} { + define-append EXTRA_CFLAGS -DHAVE_GETADDRINFO + msg-result "getaddrinfo() enabled" + } +} # Check for zlib, using the given location if specified set zlibpath [opt-val with-zlib] if {$zlibpath ne ""} { Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -34,10 +34,11 @@ # include <arpa/inet.h> # include <sys/times.h> # include <sys/time.h> # include <sys/wait.h> # include <sys/select.h> +# include <netdb.h> /* for NI_NUMERICHOST */ #endif #ifdef __EMX__ typedef int socklen_t; #endif #include <time.h> @@ -1116,12 +1117,12 @@ ** and subsequent code handles the actual generation of the webpage. */ void cgi_handle_http_request(const char *zIpAddr){ char *z, *zToken; int i; - struct sockaddr_in remoteName; - socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_storage remoteName; + socklen_t size = sizeof(remoteName); char zLine[2000]; /* A single line of input. */ g.fullHttpReply = 1; if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request(); } @@ -1142,20 +1143,10 @@ cgi_setenv("REQUEST_URI", zToken); for(i=0; zToken[i] && zToken[i]!='?'; i++){} if( zToken[i] ) zToken[i++] = 0; cgi_setenv("PATH_INFO", zToken); cgi_setenv("QUERY_STRING", &zToken[i]); - if( zIpAddr==0 && - getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, - &size)>=0 - ){ - zIpAddr = inet_ntoa(remoteName.sin_addr); - } - if( zIpAddr ){ - cgi_setenv("REMOTE_ADDR", zIpAddr); - g.zIpAddr = mprintf("%s", zIpAddr); - } /* Get all the optional fields that follow the first line. */ while( fgets(zLine,sizeof(zLine),g.httpIn) ){ char *zFieldName; @@ -1182,18 +1173,53 @@ cgi_setenv("HTTP_HOST", zVal); }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){ cgi_setenv("HTTP_IF_NONE_MATCH", zVal); }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){ cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); + }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ + char* p = zVal; + /* + ** x-forwarded-for header is a list of comma-separated addresses, + ** with leftmost address corresponding to the client + */ + while(*p && *p != ',') p++; + *p = '\0'; + zIpAddr = mprintf( "%s", zVal ); #if 0 }else if( fossil_strcmp(zFieldName,"referer:")==0 ){ cgi_setenv("HTTP_REFERER", zVal); #endif }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); } } + + if( zIpAddr==0 && + getsockname(fileno(g.httpIn), (struct sockaddr*)&remoteName, + &size)>=0 + ){ + sa_family_t family; + int v4mapped=0; + if( remoteName.ss_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6*)&remoteName)->sin6_addr)) ){ + v4mapped = 1; + } + if(!getnameinfo((struct sockaddr*)&remoteName, size, zLine, sizeof(zLine), + NULL, 0, NI_NUMERICHOST)){ + zIpAddr = zLine; + } else { + zIpAddr = NULL; + } + if(zIpAddr && v4mapped) { + /* ::ffff:172.16.0.2 */ + zIpAddr += 7; + } + } + if( zIpAddr ){ + cgi_setenv("REMOTE_ADDR", zIpAddr); + g.zIpAddr = mprintf("%s", zIpAddr); + } cgi_init(); } #if INTERFACE @@ -1230,37 +1256,116 @@ fd_set readfds; /* Set of file descriptors for select() */ socklen_t lenaddr; /* Length of the inaddr structure */ int child; /* PID of the child process */ int nchildren = 0; /* Number of child processes */ struct timeval delay; /* How long to wait inside select() */ +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; + struct addrinfo* res; + struct addrinfo* i; + struct sockaddr_storage inaddr; /* The socket address */ + char* sPort; + int iRet; +#else +#ifdef WITH_IPV6 + struct sockaddr_in6 inaddr; /* The socket address */ +#else struct sockaddr_in inaddr; /* The socket address */ +#endif +#endif int opt = 1; /* setsockopt flag */ int iPort = mnPort; while( iPort<=mxPort ){ - memset(&inaddr, 0, sizeof(inaddr)); - inaddr.sin_family = AF_INET; - if( flags & HTTP_SERVER_LOCALHOST ){ - inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - }else{ - inaddr.sin_addr.s_addr = htonl(INADDR_ANY); +#ifdef HAVE_GETADDRINFO + memset(&hints, 0, sizeof(struct addrinfo)); +#ifdef WITH_IPV6 + hints.ai_family = PF_UNSPEC; +#else + hints.ai_family = PF_INET; +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + if(!(flags & HTTP_SERVER_LOCALHOST)) hints.ai_flags |= AI_PASSIVE; + + sPort = mprintf("%d", iPort); + + if(iRet = getaddrinfo(NULL, sPort, &hints, &res)) { + fossil_fatal("Unable to obtain address: %s", gai_strerror(iRet)); + } + + for(i = res; i; i = i->ai_next) { + listener = socket(i->ai_family, i->ai_socktype, i->ai_protocol); + if(listener < 0) { + fossil_fatal("Unable to create socket"); + } + opt=1; + setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); + if(i->ai_family == AF_INET6) { + opt=0; + setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + } + if( bind(listener, i->ai_addr, i->ai_addrlen)<0 ){ + close(listener); + listener = -1; + } + break; } - inaddr.sin_port = htons(iPort); - listener = socket(AF_INET, SOCK_STREAM, 0); - if( listener<0 ){ + + free(sPort); + freeaddrinfo(res); + + if(listener == -1) { iPort++; continue; } +#else + memset(&inaddr, 0, sizeof(inaddr)); + +#ifdef WITH_IPV6 + inaddr.sin6_family = AF_INET6; +#else + inaddr.sin_family = AF_INET; +#endif + if( flags & HTTP_SERVER_LOCALHOST ){ +#ifdef WITH_IPV6 + memcpy(&inaddr.sin6_addr, &in6addr_loopback, sizeof(inaddr.sin6_addr)); +#else + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); +#endif + }else{ +#ifdef WITH_IPV6 + memcpy(&inaddr.sin6_addr, &in6addr_any, sizeof(inaddr.sin6_addr)); +#else + inaddr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + } +#ifdef WITH_IPV6 + inaddr.sin6_port = htons(iPort); + listener = socket(AF_INET6, SOCK_STREAM, 0); +#else + inaddr.sin_port = htons(iPort); + listener = socket(AF_INET, SOCK_STREAM, 0); +#endif + if( listener<0 ){ + fossil_fatal("Unable to create socket"); + } /* if we can't terminate nicely, at least allow the socket to be reused */ setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); + +#ifdef WITH_IPV6 + opt=0; + setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); +#endif if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){ close(listener); iPort++; continue; } +#endif break; } if( iPort>mxPort ){ if( mnPort==mxPort ){ fossil_fatal("unable to open listening socket on ports %d", mnPort); Index: src/http_socket.c ================================================================== --- src/http_socket.c +++ src/http_socket.c @@ -135,10 +135,58 @@ ** g.urlPort TCP/IP port to use. Ex: 80 ** ** Return the number of errors. */ int socket_open(void){ + int error = 0; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; + struct addrinfo* res; + struct addrinfo* i; + char ip[INET6_ADDRSTRLEN]; + void* addr; + char* sPort; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_ADDRCONFIG; +#ifdef WITH_IPV6 + hints.ai_family = PF_UNSPEC; +#else + hints.ai_family = PF_INET; +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + sPort = mprintf("%d", g.urlPort); + + if(getaddrinfo(g.urlName, sPort, &hints, &res)) { + socket_set_errmsg("can't resolve host name: %s", g.urlName); + free(sPort); + return 1; + } + for(i = res; i; i = i->ai_next) { + iSocket = socket(i->ai_family, i->ai_socktype, i->ai_protocol); + if(iSocket < 0) { + continue; + } + if(connect(iSocket, i->ai_addr, i->ai_addrlen) < 0) { + close(iSocket); + iSocket = -1; + continue; + } + if(!getnameinfo(i->ai_addr, i->ai_addrlen, ip, sizeof(ip), + NULL, 0, NI_NUMERICHOST)) + g.zIpAddr = mprintf("%s", ip); + break; + } + if(iSocket == -1) { + socket_set_errmsg("cannot connect to host %s:%s", g.urlName, sPort); + error = 1; + } + free(sPort); + freeaddrinfo(res); +#else static struct sockaddr_in addr; /* The server address */ static int addrIsInit = 0; /* True once addr is initialized */ socket_global_init(); if( !addrIsInit ){ @@ -172,16 +220,18 @@ return 1; } if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){ socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort); socket_close(); - return 1; + error = 1; } +#endif #if !defined(_WIN32) - signal(SIGPIPE, SIG_IGN); + if(!error) + signal(SIGPIPE, SIG_IGN); #endif - return 0; + return error; } /* ** Send content out over the open socket connection. */ Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -775,11 +775,12 @@ ** ** This feature allows the "fossil ui" command to give the user ** full access rights without having to log in. */ zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil")); - if( fossil_strcmp(zIpAddr, "127.0.0.1")==0 + if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 || + fossil_strcmp(zIpAddr, "::1")==0 ) && g.useLocalauth && db_get_int("localauth",0)==0 && P("HTTPS")==0 ){ uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); @@ -1114,11 +1115,11 @@ db_exists("SELECT 1 FROM user" " WHERE login='anonymous'" " AND cap LIKE '%%h%%'") ){ const char *zUrl = PD("REQUEST_URI", "index"); @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> - @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> + @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(g.zRoot)%T(zUrl)">anonymous login</a> @ to enable hyperlinks.</p> } } /* Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -118,10 +118,12 @@ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fNoSync; /* Do not do an autosync even. --nosync */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ + char *zRedirectBaseURL; /* Full text of the URL being served to be used in redirect */ + char *zRoot; /* Repository web root */ char *zTop; /* Parent directory of zPath */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ int sslNotAvailable; /* SSL is not available. Do not redirect to https: */ @@ -1073,20 +1075,21 @@ const char *zHost; const char *zMode; const char *zCur; if( g.zBaseURL!=0 ) return; + if( g.zRoot==0 ) g.zRoot=""; zHost = PD("HTTP_HOST",""); zMode = PD("HTTPS","off"); zCur = PD("SCRIPT_NAME","/"); i = strlen(zCur); while( i>0 && zCur[i-1]=='/' ) i--; if( fossil_stricmp(zMode,"on")==0 ){ - g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur); + g.zBaseURL = mprintf("https://%s%s%.*s", zHost, g.zRoot, i, zCur); g.zTop = &g.zBaseURL[8+strlen(zHost)]; }else{ - g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur); + g.zBaseURL = mprintf("http://%s%s%.*s", zHost, g.zRoot, i, zCur); g.zTop = &g.zBaseURL[7+strlen(zHost)]; } } /* @@ -1661,10 +1664,14 @@ ** TCP port 8080, or on any other TCP port defined by the -P or ** --port option. The optional argument is the name of the repository. ** The repository argument may be omitted if the working directory is ** within an open checkout. ** +** If HTTP requests are being reverse proxied to the fossil server, +** and in proxy server fossil is mapped at a virtual directory, then +** virtual directory can be specified using optional -R or --root option. +** ** The "ui" command automatically starts a web browser after initializing ** the web server. The "ui" command also binds to 127.0.0.1 and so will ** only process HTTP traffic from the local machine. ** ** In the "server" command, the REPOSITORY can be a directory (aka folder) @@ -1675,14 +1682,15 @@ ** By default, the "ui" command provides full administrative access without ** having to log in. This can be disabled by setting turning off the ** "localauth" setting. Automatic login for the "server" command is available ** if the --localauth option is present and the "localauth" setting is off ** and the connection is from localhost. -** ** Options: ** --localauth enable automatic login for requests from localhost ** -P|--port TCPPORT listen to request on port TCPPORT +** -R|--root ROOT map fossil at virtual directory ROOT in reverse +** proxying ** --th-trace trace TH1 execution (for debugging purposes) ** ** See also: cgi, http, winsrv */ void cmd_webserver(void){ @@ -1699,10 +1707,11 @@ zStopperFile = find_option("stopper", 0, 1); #endif g.thTrace = find_option("th-trace", 0, 0)!=0; g.useLocalauth = find_option("localauth", 0, 0)!=0; + g.zRoot = find_option("root", "R", 1); if( g.thTrace ){ blob_zero(&g.thLog); } zPort = find_option("port", "P", 1); zNotFound = find_option("notfound", 0, 1); @@ -1736,11 +1745,11 @@ } } #else zBrowser = db_get("web-browser", "open"); #endif - zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser); + zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", zBrowser, (g.zRoot==0?"":g.zRoot)); } db_close(1); if( cgi_http_server(iPort, mxPort, zBrowserCmd, flags) ){ fossil_fatal("unable to listen on TCP socket %d", iPort); }