Hi everyone, I've written attached diff sometime ago to add:
1. Improved reverse proxy support. I'm not sure if there is any explicit support for reverse proxy, but the way in which I was trying to use it, i.e. exposing fossil repository at a _location_ in nginx, it wasn't working. So I decided to fix issues I encountered and added explicit support for it in the code which now works as I expected: --8<---------------cut here---------------start------------->8--- chateau.d.if!abbe fossils % fossil server -R /code -P 8080 . --8<---------------cut here---------------end--------------->8--- And following is the corresponding nginx configuration to proxy traffic at /code/ to fossil server listening at port 8080: --8<---------------cut here---------------start------------->8--- location /code/ { proxy_pass http://localhost:8080/ ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect http://localhost:8080/code/ http://$http_host/code/; } --8<---------------cut here---------------end--------------->8--- If this is achievable using current version (hosted at: fossil-scm.org), please let me know. 2. IPv6 support. Fossil uses IPv4 sockets by default. I was not sure if there was any technical reason to not add support for IPv6, so I modified it all relevant places (I was able to find) to use IPv6 sockets, perform hostname lookups using getaddrinfo(3), accept push/pulls to/from IPv6 address, and persist this information in the repository database. I'm using it with the Fossil snapshot of September, 2011 with FreeBSD 8.2-R (amd64) without any issues. Please let me know if you find any problems with code. I'll be glad if these changes makes to the trunk. Thanks -- Ashish SHUKLA “The wonderful thing about science is that it doesn't ask for your faith, it just ask for your eyes.” (xkcd #154)
Index: auto.def =================================================================== --- auto.def +++ auto.def @@ -8,10 +8,11 @@ with-zlib:path => {Look for zlib in the given 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} } # sqlite wants these types if possible cc-with {-includes {stdint.h inttypes.h}} { cc-check-types uint32_t uint16_t int16_t uint8_t @@ -65,10 +66,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] Index: src/cgi.c =================================================================== --- src/cgi.c +++ src/cgi.c @@ -992,12 +992,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); + char remoteName[256]; + 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(); @@ -1019,20 +1019,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; @@ -1059,18 +1049,57 @@ 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); }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); } #endif + } + + if( zIpAddr==0 && + getsockname(fileno(g.httpIn), (struct sockaddr*)&remoteName, + &size)>=0 + ){ + sa_family_t family; + void* addr = NULL; + int v4mapped=0; + switch(family = ((struct sockaddr_in*)&remoteName)->sin_family) + { + case AF_INET: + addr = &((struct sockaddr_in*)&remoteName)->sin_addr; + break; + case AF_INET6: + addr = &((struct sockaddr_in6*)&remoteName)->sin6_addr; + if(IN6_IS_ADDR_V4MAPPED(((struct in6_addr*)addr))) v4mapped=1; + break; + } + if(addr) { + zIpAddr = inet_ntop(family, addr, zLine, sizeof(zLine)); + if(zIpAddr && v4mapped) { + /* ::ffff:172.16.0.2 */ + zIpAddr += 7; + } + } + } + if( zIpAddr ){ + cgi_setenv("REMOTE_ADDR", zIpAddr); + g.zIpAddr = mprintf("%s", zIpAddr); } cgi_init(); } @@ -1108,31 +1137,57 @@ 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 WITH_IPV6 + struct sockaddr_in6 inaddr; /* The socket address */ +#else struct sockaddr_in inaddr; /* The socket address */ +#endif int opt = 1; /* setsockopt flag */ int iPort = mnPort; while( iPort<=mxPort ){ 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 ){ iPort++; continue; } /* 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; Index: src/http_socket.c =================================================================== --- src/http_socket.c +++ src/http_socket.c @@ -135,10 +135,61 @@ ** 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; + + 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; + if(getaddrinfo(g.urlName, NULL, &hints, &res)) { + socket_set_errmsg("can't resolve host name: %s", g.urlName); + 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(i->ai_family == AF_INET) { + ((struct sockaddr_in*)i->ai_addr)->sin_port = htons(g.urlPort); + addr = &((struct sockaddr_in*)i->ai_addr)->sin_addr; + } +#ifdef WITH_IPV6 + else if(i->ai_family == AF_INET6) { + ((struct sockaddr_in6*)i->ai_addr)->sin6_port = htons(g.urlPort); + addr = &((struct sockaddr_in6*)i->ai_addr)->sin6_addr; + } +#endif + if(connect(iSocket, i->ai_addr, i->ai_addrlen) < 0) { + close(iSocket); + iSocket = -1; + continue; + } + inet_ntop(i->ai_family, addr, &ip[0], INET6_ADDRSTRLEN); + g.zIpAddr = mprintf("%s", ip); + break; + } + if(iSocket == -1) { + socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort); + error = 1; + } + 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 +223,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 @@ -563,11 +563,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%%'"); @@ -877,11 +878,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 @@ -98,10 +98,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: */ @@ -823,20 +825,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)]; } } /* @@ -1364,18 +1367,22 @@ /* ** COMMAND: server ** COMMAND: ui ** -** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY? -** Or: %fossil ui ?-P|--port TCPPORT? ?REPOSITORY? +** Usage: %fossil server ?-P|--port TCPPORT? -?-R|--root ROOT? ?REPOSITORY? +** Or: %fossil ui ?-P|--port TCPPORT? -?-R|--root ROOT? ?REPOSITORY? ** ** Open a socket and begin listening and responding to HTTP requests on ** 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. ** @@ -1404,10 +1411,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); @@ -1441,11 +1449,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); }
pgpwlwFE7gR6x.pgp
Description: PGP signature
_______________________________________________ fossil-users mailing list fossil-users@lists.fossil-scm.org http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users