Finally found the time to work a little on implementing the "backend"
command that I proposed a few weeks ago. Please find a patch attached.
I've chosen a rather minimal approach. Also, it currently only does the
http
version.

fossil backend [-P port|pipe] [-F front-server-ip] [repository]

Fossil "backend" mainly operates like "fossil server", with a little more
added in:
- Auto login is disabled
- Only if the request is comming from the front server IP:
  . X-Forwarded-For is honoured, single ip only (i.e. no forwarding
chains)
  . If X-Forwared-For is present, X-Fossil-BaseUrl is honoured; this is
the base path to use (similar to cgi's "script_name")
- Requests coming from all other IP's are treated as if the command had
been "fossil server"; this way local, in-company traffic works as before
and public access can be proxied out to the web, all from one fossil
server.

If there is interest in this patch for the main trunk I'll send the
copyright release etc. I'll also add scgi support to the command, using
the idea of looking at the first character when parsing the request
header. If
there are security concerns with this approach, please advise.

All input & constructive criticism welcome!

Thanks,

Paul

PS: I don't quite understand why the notfound path is appended to the
client ip in the win http server code. It seems to me this is unused, but
perhaps I'm missing something.

====

On Tue, 08 Jun 2010 08:41:57 +0200, Paul Ruizendaal <p...@planet.nl> wrote:
> Have thought about it some more, but no security epiphany. My view
remains
> that the command should be something like this:
> 
> fossil backend http|scgi [-P port|pipe] [-F front-server-ip] [-R
> repository]
> 
> Semantics could be:
> - all requests coming from a client other than the front-server-ip (fip)
> are denied with a "403 forbidden" response.
> - all requests that specify a real client ip of 127.0.0.1 are denied
with
> the same response
> - For http, X-Forwarded-For only looks at the first (ultimate client)
ip;
> if ommitted the fip is used
> - For http, it sets the base url to X-Fossil-BaseUrl; if ommitted the
base
> url is root
> - For scgi, the client ip is set to the client ip header which must be
> present (at penalty of "400 bad request")
> - For both, REMOTE_USER is honoured
> 
> Variations could be to add "any" to http|scgi to serve both at the same
> time, or to make acceptance of remote user a feature that must be
enabled
> with an additonal flag. I think & hope that the above can be done in a
> small patch, a I remain opposed to adding bloat to Fossil.
> 
> Will that work for security and convience? Input welcome.

Index: src/cgi.c
===================================================================
--- src/cgi.c
+++ src/cgi.c
@@ -656,24 +656,41 @@
 
 /*
 ** Initialize the query parameter database.  Information is pulled from
 ** the QUERY_STRING environment variable (if it exists), from standard
 ** input if there is POST data, and from HTTP_COOKIE.
+** If we are running as a backend server and this request comes from the
+** front server, then check and use the HTTP_X_FORWARDED_FOR header, otherwise
+** stick to the REMOTE_ADDR as the client IP address. If the Forwarded-For
+** header is present, also check for the X-Fossil-BaseURL header, which
+** becomes the base location for generated html links, similar to the script
+** path for regular cgi usage of Fossil.
 */
 void cgi_init(void){
-  char *z;
+  char *z, *zz;
   const char *zType;
   int len;
   cgi_destination(CGI_BODY);
   z = (char*)P("QUERY_STRING");
   if( z ){
     z = mprintf("%s",z);
     add_param_list(z, '&');
   }
 
-  z = (char*)P("REMOTE_ADDR");
-  if( z ) g.zIpAddr = mprintf("%s", z);
+  z  = (char*)P("REMOTE_ADDR");
+  zz = (char*)P("HTTP_X_FORWARDED_FOR");
+  if( zz && g.zFrontIP && z && inet_addr(z)==inet_addr(g.zFrontIP) ){
+    /* The next statements cannot be reached for regular cgi usage, but
+     * only when acting in "backend" server mode. */
+    g.zIpAddr = mprintf("%s", zz);
+    zz = strchr(g.zIpAddr, ',');
+    if( zz ) *zz = 0;
+    zz = (char*)P("HTTP_X_FOSSIL_BASEURL");
+    if( zz ) cgi_replace_parameter("SCRIPT_NAME", zz);
+  }else{
+    if( z ) g.zIpAddr = mprintf("%s", z);
+  }
 
   len = atoi(PD("CONTENT_LENGTH", "0"));
   g.zContentType = zType = P("CONTENT_TYPE");
   if( len>0 && zType ){
     blob_zero(&g.cgiIn);
@@ -1138,10 +1155,14 @@
       cgi_setenv("HTTP_COOKIE", zVal);
     }else if( strcmp(zFieldName,"if-none-match:")==0 ){
       cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
     }else if( strcmp(zFieldName,"if-modified-since:")==0 ){
       cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
+    }else if( strcmp(zFieldName,"x-forwarded-for:")==0 ){
+      cgi_setenv("HTTP_X_FORWARDED_FOR", zVal);
+    }else if( strcmp(zFieldName,"x-fossil-baseurl:")==0 ){
+      cgi_setenv("HTTP_X_FOSSIL_BASEURL", zVal);
     }
   }
 
   cgi_init();
 }

Index: src/http_transport.c
===================================================================
--- src/http_transport.c
+++ src/http_transport.c
@@ -173,11 +173,11 @@
 */
 void transport_flip(void){
   if( g.urlIsFile ){
     char *zCmd;
     fclose(transport.pFile);
-    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1",
+    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 -",
        g.argv[0], g.urlName, transport.zOutFile, transport.zInFile
     );
     portable_system(zCmd);
     free(zCmd);
     transport.pFile = fopen(transport.zInFile, "rb");

Index: src/login.c
===================================================================
--- src/login.c
+++ src/login.c
@@ -331,17 +331,19 @@
 
   /* Only run this check once.  */
   if( g.userUid!=0 ) return;
 
 
-  /* If the HTTP connection is coming over 127.0.0.1 and if
-  ** local login is disabled and if we are using HTTP and not HTTPS,
-  ** then there is no need to check user credentials.
+  /* If the HTTP connection is coming over 127.0.0.1, and if we're not
+  ** running in "backend" mode and if local login is disabled and if
+  ** we are using HTTP and not HTTPS, then there is no need to check
+  ** user credentials.
   **
   */
   zRemoteAddr = PD("REMOTE_ADDR","nil");
   if( strcmp(zRemoteAddr, "127.0.0.1")==0
+   && g.zFrontIP==NULL
    && db_get_int("localauth",0)==0
    && P("HTTPS")==0
   ){
     uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
     g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);

Index: src/main.c
===================================================================
--- src/main.c
+++ src/main.c
@@ -83,10 +83,11 @@
   FILE *httpOut;          /* Send HTTP output here */
   int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
   int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected 
*/
   int *aCommitFile;       /* Array of files to be committed */
   int markPrivate;        /* All new artifacts are private if true */
+  char *zFrontIP;         /* IP address of front server or NULL if none */
 
   int urlIsFile;          /* True if a "file:" url */
   int urlIsHttps;         /* True if a "https:" url */
   char *urlName;          /* Hostname for http: or filename for file: */
   char *urlHostname;      /* The HOST: parameter on http headers */
@@ -888,13 +889,15 @@
 }
 
 /*
 ** undocumented format:
 **
-**        fossil http REPOSITORY INFILE OUTFILE IPADDR
+**        fossil http REPOSITORY INFILE OUTFILE CLIENTIP FRONTIP
 **
-** The argv==6 form is used by the win32 server only.
+** The argv==7 form is used by the win32 server only. FRONTIP is
+** either the IP of the front server specified with the "backend"
+** command, or a dash for the "server" and "ui" commands.
 **
 ** COMMAND: http
 **
 ** Usage: %fossil http REPOSITORY [--notfound URL]
 **
@@ -911,19 +914,23 @@
 */
 void cmd_http(void){
   const char *zIpAddr;
   const char *zNotFound;
   zNotFound = find_option("notfound", 0, 1);
-  if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
+  if( g.argc!=2 && g.argc!=3 && g.argc!=7 ){
     cgi_panic("no repository specified");
+    return;
   }
   g.cgiOutput = 1;
   g.fullHttpReply = 1;
-  if( g.argc==6 ){
+  if( g.argc==7 ){
     g.httpIn = fopen(g.argv[3], "rb");
     g.httpOut = fopen(g.argv[4], "wb");
     zIpAddr = g.argv[5];
+    if( g.argv[6][0]!='-' ){
+      g.zFrontIP = g.argv[6];
+    }
   }else{
     g.httpIn = stdin;
     g.httpOut = stdout;
     zIpAddr = 0;
   }
@@ -968,13 +975,15 @@
 #endif
 
 /*
 ** COMMAND: server
 ** COMMAND: ui
+** COMMAND: backend
 **
 ** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
 **    Or: %fossil ui ?-P|--port TCPPORT? ?REPOSITORY?
+**    Or: %fossil backend -F|--front IPADDRESS ?-P|--port TCPPORT? ?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
@@ -985,17 +994,27 @@
 **
 ** In the "server" command, the REPOSITORY can be a directory (aka folder)
 ** that contains one or more respositories with names ending in ".fossil".
 ** In that case, the first element of the URL is used to select among the
 ** various repositories.
+**
+** The "backend" command is intended when fossil is used as a backend server
+** for a larger web site that contains a fossil subsection. It offers a
+** superset of the plain "server" functionality when the connection is coming
+** from the front server. The extra functions are:
+** - accepting X-Forwarded-For headers (as long as it is not 127.0.0.1)
+** - accepting X-FossilBaseUrl headers, which rebase fossil web links to
+**   their location in the embedding web site
+** Specifying the front server IP address is obligatory with "backend".
 */
 void cmd_webserver(void){
   int iPort, mxPort;        /* Range of TCP ports allowed */
   const char *zPort;        /* Value of the --port option */
   char *zBrowser;           /* Name of web browser program */
   char *zBrowserCmd = 0;    /* Command to launch the web browser */
-  int isUiCmd;              /* True if command is "ui", not "server' */
+  int isUiCmd;              /* True if command is "ui", not "server" or 
"backend" */
+  int isBackCmd;            /* True if command is "backend", not "server" or 
"ui" */
   const char *zNotFound;    /* The --notfound option or NULL */
 
 #ifdef __MINGW32__
   const char *zStopperFile;    /* Name of file used to terminate server */
   zStopperFile = find_option("stopper", 0, 1);
@@ -1005,10 +1024,15 @@
   if( g.thTrace ){
     blob_zero(&g.thLog);
   }
   zPort = find_option("port", "P", 1);
   zNotFound = find_option("notfound", 0, 1);
+  isBackCmd = g.argv[1][0]=='b';
+  g.zFrontIP = find_option("front", "F", 1);
+  if( isBackCmd ) {
+    if( !g.zFrontIP || inet_addr(g.zFrontIP)==INADDR_NONE ) usage("-F|--front 
IPADDRESS");
+  }
   if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
   isUiCmd = g.argv[1][0]=='u';
   find_server_repository(isUiCmd);
   if( zPort ){
     iPort = mxPort = atoi(zPort);

Index: src/winhttp.c
===================================================================
--- src/winhttp.c
+++ src/winhttp.c
@@ -104,13 +104,14 @@
     }
     wanted -= got;
   }
   fclose(out);
   out = 0;
-  sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s%s",
+  z = g.zFrontIP ? g.zFrontIP : "-";
+  sprintf(zCmd, "\"%s\" http \"%s\" %s %s %s%s %s",
     g.argv[0], g.zRepositoryName, zRequestFName, zReplyFName,
-    inet_ntoa(p->addr.sin_addr), p->zNotFound
+    inet_ntoa(p->addr.sin_addr), p->zNotFound, z
   );
   portable_system(zCmd);
   in = fopen(zReplyFName, "rb");
   if( in ){
     while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){

_______________________________________________
fossil-users mailing list
fossil-users@lists.fossil-scm.org
http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users

Reply via email to