Check-in [6b0b57a924]
Not logged in
Overview

SHA1 Hash:6b0b57a924b66439b52629bda6f6306f12fcdd96
Date: 2008-10-25 17:51:37
User: drh
Comment:Add logic to do a configuration push. Add logic to synchronize the CONCEALED table containing hidden email addresses (assuming appropriate permissions). Additional testng is needed; this check-in is to transfer the work to another machine.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/clone.c from [ac6a2f8b2b] to [58d2271cc8].

@@ -72,11 +72,11 @@
       "REPLACE INTO config(name,value)"
       " VALUES('server-code', lower(hex(randomblob(20))));"
     );
     url_enable_proxy(0);
     g.xlinkClusterOnly = 1;
-    client_sync(0,0,1,CONFIGSET_ALL);
+    client_sync(0,0,1,CONFIGSET_ALL,0);
     g.xlinkClusterOnly = 0;
     verify_cancel();
     db_end_transaction(0);
     db_close();
     db_open_repository(g.argv[3]);

Modified src/configure.c from [2cb2adc7b4] to [2a0972b75d].

@@ -37,10 +37,11 @@
 #define CONFIGSET_SKIN   0x000001     /* WWW interface appearance */
 #define CONFIGSET_TKT    0x000002     /* Ticket configuration */
 #define CONFIGSET_PROJ   0x000004     /* Project name */
 #define CONFIGSET_SHUN   0x000008     /* Shun settings */
 #define CONFIGSET_USER   0x000010     /* The USER table */
+#define CONFIGSET_ADDR   0x000020     /* The CONCEALED table */
 
 #define CONFIGSET_ALL    0xffffff     /* Everything */
 
 #endif /* INTERFACE */
 
@@ -50,16 +51,17 @@
 static struct {
   const char *zName;   /* Name of the configuration set */
   int groupMask;       /* Mask for that configuration set */
   const char *zHelp;   /* What it does */
 } aGroupName[] = {
-  { "skin",         CONFIGSET_SKIN,  "Web interface apparance settings" },
-  { "ticket",       CONFIGSET_TKT,   "Ticket setup",                    },
-  { "project",      CONFIGSET_PROJ,  "Project name and description"     },
-  { "shun",         CONFIGSET_SHUN,  "List of shunned artifacts"        },
-  { "user",         CONFIGSET_USER,  "Users and privilege settings"     },
-  { "all",          CONFIGSET_ALL,   "All of the above"                 },
+  { "email",        CONFIGSET_ADDR,  "Concealed email addresses in tickets" },
+  { "project",      CONFIGSET_PROJ,  "Project name and description"         },
+  { "skin",         CONFIGSET_SKIN,  "Web interface apparance settings"     },
+  { "shun",         CONFIGSET_SHUN,  "List of shunned artifacts"            },
+  { "ticket",       CONFIGSET_TKT,   "Ticket setup",                        },
+  { "user",         CONFIGSET_USER,  "Users and privilege settings"         },
+  { "all",          CONFIGSET_ALL,   "All of the above"                     },
 };
 
 
 /*
 ** The following is a list of settings that we are willing to
@@ -90,10 +92,11 @@
   { "ticket-key-template",    CONFIGSET_TKT  },
   { "ticket-title-expr",      CONFIGSET_TKT  },
   { "ticket-closed-expr",     CONFIGSET_TKT  },
   { "@reportfmt",             CONFIGSET_TKT  },
   { "@user",                  CONFIGSET_USER },
+  { "@concealed",             CONFIGSET_ADDR },
   { "@shun",                  CONFIGSET_SHUN },
 };
 static int iConfig = 0;
 
 /*
@@ -124,10 +127,13 @@
     if( strcmp(zName, aConfig[i].zName)==0 ){
       int m = aConfig[i].groupMask;
       if( !g.okAdmin ){
         m &= ~CONFIGSET_USER;
       }
+      if( !g.okRdAddr ){
+        m &= ~CONFIGSET_ADDR;
+      }
       return m;
     }
   }
   return 0;
 }
@@ -149,11 +155,11 @@
     }
     db_finalize(&q);
   }else if( strcmp(zName, "@reportfmt")==0 ){
     db_prepare(&q, "SELECT title, cols, sqlcode FROM reportfmt");
     while( db_step(&q)==SQLITE_ROW ){
-      blob_appendf(pOut, "INSERT INTO _xfer_reportfmt(title,cols,sqlcode) "
+      blob_appendf(pOut, "INSERT INTO _xfer_reportfmt(title,cols,sqlcode)"
                          " VALUES(%Q,%Q,%Q);\n",
         db_column_text(&q, 0),
         db_column_text(&q, 1),
         db_column_text(&q, 2)
       );
@@ -160,16 +166,26 @@
     }
     db_finalize(&q);
   }else if( strcmp(zName, "@user")==0 ){
     db_prepare(&q, "SELECT login, cap, info, quote(photo) FROM user");
     while( db_step(&q)==SQLITE_ROW ){
-      blob_appendf(pOut, "INSERT INTO _xfer_user(login,cap,info,photo) "
+      blob_appendf(pOut, "INSERT INTO _xfer_user(login,cap,info,photo)"
                          " VALUES(%Q,%Q,%Q,%s);\n",
         db_column_text(&q, 0),
         db_column_text(&q, 1),
         db_column_text(&q, 2),
         db_column_text(&q, 3)
+      );
+    }
+    db_finalize(&q);
+  }else if( strcmp(zName, "@concealed")==0 ){
+    db_prepare(&q, "SELECT hash, content FROM concealed");
+    while( db_step(&q)==SQLITE_ROW ){
+      blob_appendf(pOut, "INSERT OR IGNORE INTO concealed(hash,content)"
+                         " VALUES(%Q,%Q);\n",
+        db_column_text(&q, 0),
+        db_column_text(&q, 1)
       );
     }
     db_finalize(&q);
   }
 }
@@ -353,11 +369,11 @@
 /*
 ** COMMAND: configuration
 **
 ** Usage: %fossil configure METHOD ...
 **
-** Where METHOD is one of: export import merge pull reset.  All methods
+** Where METHOD is one of: export import merge pull push reset.  All methods
 ** accept the -R or --repository option to specific a repository.
 **
 **    %fossil configuration export AREA FILENAME
 **
 **         Write to FILENAME exported configuraton information for AREA.
@@ -377,10 +393,16 @@
 **    %fossil configuration pull AREA ?URL?
 **
 **         Pull and install the configuration from a different server
 **         identified by URL.  If no URL is specified, then the default
 **         server is used.
+**
+**    %fossil configuration push AREA ?URL?
+**
+**         Push the local configuration into the remote server identified
+**         by URL.  Admin privilege is required on the remote server for
+**         this to work.
 **
 **    %fossil configuration reset AREA
 **
 **         Restore the configuration to the default.  AREA as above.
 **
@@ -414,11 +436,11 @@
     configure_prepare_to_receive(zMethod[0]=='i');
     db_multi_exec("%s", blob_str(&in));
     configure_finalize_receive();
     db_end_transaction(0);
   }else
-  if( strncmp(zMethod, "pull", n)==0 ){
+  if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){
     int mask;
     const char *zServer;
     url_proxy_options();
     if( g.argc!=4 && g.argc!=5 ){
       usage("pull AREA ?URL?");
@@ -435,11 +457,15 @@
     url_parse(zServer);
     if( g.urlIsFile ){
       fossil_fatal("network sync only");
     }
     user_select();
-    client_sync(0,0,0,mask);
+    if( strncmp(zMethod, "push", n)==0 ){
+      client_sync(0,0,0,0,mask);
+    }else{
+      client_sync(0,0,0,mask,0);
+    }
   }else
   if( strncmp(zMethod, "reset", n)==0 ){
     int mask, i;
     char *zBackup;
     if( g.argc!=4 ) usage("reset AREA");
@@ -454,10 +480,14 @@
       if( zName[0]!='@' ){
         db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
       }else if( strcmp(zName,"@user")==0 ){
         db_multi_exec("DELETE FROM user");
         db_create_default_users();
+      }else if( strcmp(zName,"@concealed")==0 ){
+        db_multi_exec("DELETE FROM concealed");
+      }else if( strcmp(zName,"@shun")==0 ){
+        db_multi_exec("DELETE FROM shun");
       }else if( strcmp(zName,"@reportfmt")==0 ){
         db_multi_exec("DELETE FROM reportfmt");
       }
     }
     db_end_transaction(0);
@@ -464,8 +494,9 @@
     printf("Configuration reset to factory defaults.\n");
     printf("To recover, use:  %s %s import %s\n",
             g.argv[0], g.argv[1], zBackup);
   }else
   {
-    fossil_fatal("METHOD should be one of:  export import merge pull reset");
+    fossil_fatal("METHOD should be one of:"
+                 " export import merge pull push reset");
   }
 }

Modified src/db.c from [5a3c0d4818] to [d9d1c45fb2].

@@ -911,30 +911,43 @@
 ** CONCEALED table so that the hash can be undo using the db_reveal()
 ** function at some later time.
 **
 ** The value returned is stored in static space and will be overwritten
 ** on subsequent calls.
+**
+** If zContent is already a well-formed SHA1 hash, then return a copy
+** of that hash, not a hash of the hash.
+**
+** The CONCEALED table is meant to obscure email addresses.  Every valid
+** email address will contain a "@" character and "@" is not valid within
+** an SHA1 hash so there is no chance that a valid email address will go
+** unconcealed.
 */
 char *db_conceal(const char *zContent, int n){
   static char zHash[42];
   Blob out;
-  sha1sum_step_text(zContent, n);
-  sha1sum_finish(&out);
-  strcpy(zHash, blob_str(&out));
-  blob_reset(&out);
-  db_multi_exec(
-     "INSERT OR IGNORE INTO concealed VALUES(%Q,%#Q)",
-     zHash, n, zContent
-  );
+  if( n==40 && validate16(zContent, n) ){
+    memcpy(zHash, zContent, n);
+    zHash[n] = 0;
+  }else{
+    sha1sum_step_text(zContent, n);
+    sha1sum_finish(&out);
+    strcpy(zHash, blob_str(&out));
+    blob_reset(&out);
+    db_multi_exec(
+       "INSERT OR IGNORE INTO concealed VALUES(%Q,%#Q)",
+       zHash, n, zContent
+    );
+  }
   return zHash;
 }
 
 /*
 ** Attempt to look up the input in the CONCEALED table.  If found,
 ** and if the okRdAddr permission is enabled then return the
 ** original value for which the input is a hash.  If okRdAddr is
-** false or if the lookup fails, return the original input.
+** false or if the lookup fails, return the original string content.
 **
 ** In either case, the string returned is stored in space obtained
 ** from malloc and should be freed by the calling function.
 */
 char *db_reveal(const char *zKey){
@@ -949,66 +962,19 @@
   }
   return zOut;
 }
 
 /*
-** The conceal() SQL function.  Compute an SHA1 hash of the argument
-** and return that hash as a 40-character lower-case hex number.
-*/
-static void sha1_function(
-  sqlite3_context *context,
-  int argc,
-  sqlite3_value **argv
-){
-  const char *zIn;
-  int nIn;
-  char *zOut;
-  assert(argc==1);
-
-  zIn = (const char*)sqlite3_value_text(argv[0]);
-  if( zIn ){
-    nIn = sqlite3_value_bytes(argv[0]);
-    zOut = db_conceal(zIn, nIn);
-    sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT);
-  }
-}
-
-/*
-** The reveal() SQL function invokes the db_reveal() function.
-*/
-static void reveal_function(
-  sqlite3_context *context,
-  int argc,
-  sqlite3_value **argv
-){
-  const char *zIn;
-  char *zOut;
-  assert(argc==1);
-
-  zIn = (const char*)sqlite3_value_text(argv[0]);
-  if( zIn ){
-    zOut = db_reveal(zIn);
-    sqlite3_result_text(context, zOut, -1, free);
-  }
-}
-
-/*
 ** This function registers auxiliary functions when the SQLite
 ** database connection is first established.
 */
 LOCAL void db_connection_init(void){
   static int once = 1;
   if( once ){
     sqlite3_create_function(g.db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
     sqlite3_create_function(
       g.db, "file_is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
-    );
-    sqlite3_create_function(
-      g.db, "conceal", 1, SQLITE_UTF8, 0, sha1_function,0,0
-    );
-    sqlite3_create_function(
-      g.db, "reveal", 1, SQLITE_UTF8, 0, reveal_function,0,0
     );
     if( g.fSqlTrace ){
       sqlite3_trace(g.db, db_sql_trace, 0);
     }
     once = 0;

Modified src/sync.c from [c4538ea75a] to [9adfc72509].

@@ -61,11 +61,11 @@
     printf("Autosync:  http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
   }else{
     printf("Autosync:  http://%s%s\n", g.urlName, g.urlPath);
   }
   url_enable_proxy("via proxy: ");
-  client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0);
+  client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, 0);
 }
 
 /*
 ** This routine processes the command-line argument for push, pull,
 ** and sync.  If a command-line argument is given, that is the URL
@@ -124,11 +124,11 @@
 ** specifies the TCP port of the server.  The default port is
 ** 80.
 */
 void pull_cmd(void){
   process_sync_args();
-  client_sync(0,1,0,0);
+  client_sync(0,1,0,0,0);
 }
 
 /*
 ** COMMAND: push
 **
@@ -137,11 +137,11 @@
 ** Push changes in the local repository over into a remote repository.
 ** See the "pull" command for additional information.
 */
 void push_cmd(void){
   process_sync_args();
-  client_sync(1,0,0,0);
+  client_sync(1,0,0,0,0);
 }
 
 
 /*
 ** COMMAND: sync
@@ -152,7 +152,7 @@
 ** the equivalent of running both "push" and "pull" at the same time.
 ** See the "pull" command for additional information.
 */
 void sync_cmd(void){
   process_sync_args();
-  client_sync(1,1,0,0);
+  client_sync(1,1,0,0,0);
 }

Modified src/xfer.c from [35dc0c6452] to [38cf45242e].

@@ -481,10 +481,32 @@
   }
   db_finalize(&q);
 }
 
 /*
+** Send a single config card for configuration item zName
+*/
+static void send_config_card(Xfer *pXfer, const char *zName){
+  if( zName[0]!='@' ){
+    char *zValue = db_get(zName, 0);
+    if( zValue ){
+      blob_appendf(pXfer->pOut, "config %s %d\n%s\n",
+                   zName, strlen(zValue), zValue);
+      free(zValue);
+    }
+  }else{
+    Blob content;
+    blob_zero(&content);
+    configure_render_special_name(zName, &content);
+    blob_appendf(pXfer->pOut, "config %s %d\n%s\n", zName,
+                 blob_size(&content), blob_str(&content));
+    blob_reset(&content);
+  }
+}
+
+
+/*
 ** If this variable is set, disable login checks.  Used for debugging
 ** only.
 */
 static int disableLogin = 0;
 
@@ -507,10 +529,12 @@
   int nErr = 0;
   Xfer xfer;
   int deltaFlag = 0;
   int isClone = 0;
   int nGimme = 0;
+  int size;
+  int recvConfig = 0;
 
   memset(&xfer, 0, sizeof(xfer));
   blobarray_zero(xfer.aToken, count(xfer.aToken));
   cgi_set_content_type(g.zContentType);
   blob_zero(&xfer.err);
@@ -678,28 +702,54 @@
      && xfer.nToken==2
     ){
       if( g.okRead ){
         char *zName = blob_str(&xfer.aToken[1]);
         if( configure_is_exportable(zName) ){
-          if( zName[0]!='@' ){
-            char *zValue = db_get(zName, 0);
-            if( zValue ){
-              blob_appendf(xfer.pOut, "config %s %d\n%s\n", zName,
-                           strlen(zValue), zValue);
-              free(zValue);
-            }
-          }else{
-            Blob content;
-            blob_zero(&content);
-            configure_render_special_name(zName, &content);
-            blob_appendf(xfer.pOut, "config %s %d\n%s\n", zName,
-               blob_size(&content), blob_str(&content));
-            blob_reset(&content);
-          }
+          send_config_card(&xfer, zName);
+        }
+      }
+    }else
+
+    /*   config NAME SIZE \n CONTENT
+    **
+    ** Receive a configuration value from the client.  This is only
+    ** permitted for high-privilege users.
+    */
+    if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
+        && blob_is_int(&xfer.aToken[2], &size) ){
+      const char *zName = blob_str(&xfer.aToken[1]);
+      Blob content;
+      blob_zero(&content);
+      blob_extract(xfer.pIn, size, &content);
+      if( !g.okAdmin ){
+        cgi_reset_content();
+        @ error not\sauthorized\sto\spush\sconfiguration\data
+        nErr++;
+        break;
+      }
+      if( zName[0]!='@' ){
+        if( !recvConfig ){
+          configure_prepare_to_receive(0);
+          recvConfig = 1;
         }
+        db_multi_exec(
+            "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
+            zName, blob_str(&content)
+        );
+      }else{
+        /* Notice that we are evaluating arbitrary SQL received from the
+        ** client.  But this can only happen if the client has authenticated
+        ** as an administrator, so presumably we trust the client at this
+        ** point.
+        */
+        db_multi_exec("%s", blob_str(&content));
       }
+      blob_reset(&content);
+      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
     }else
+
+
 
     /*    cookie TEXT
     **
     ** A cookie contains a arbitrary-length argument that is server-defined.
     ** The argument must be encoded so as not to contain any whitespace.
@@ -741,10 +791,13 @@
     */
     send_all(&xfer);
   }else if( isPull ){
     create_cluster();
     send_unclustered(&xfer);
+  }
+  if( recvConfig ){
+    configure_finalize_receive();
   }
   db_end_transaction(0);
 }
 
 /*
@@ -793,19 +846,25 @@
 **
 ** Records are pushed to the server if pushFlag is true.  Records
 ** are pulled if pullFlag is true.  A full sync occurs if both are
 ** true.
 */
-void client_sync(int pushFlag, int pullFlag, int cloneFlag, int configMask){
+void client_sync(
+  int pushFlag,          /* True to do a push (or a sync) */
+  int pullFlag,          /* True to do a pull (or a sync) */
+  int cloneFlag,         /* True if this is a clone */
+  int configRcvMask,     /* Receive these configuration items */
+  int configSendMask     /* Send these configuration items */
+){
   int go = 1;        /* Loop until zero */
   const char *zSCode = db_get("server-code", "x");
   const char *zPCode = db_get("project-code", 0);
   int nCard = 0;         /* Number of cards sent or received */
   int nCycle = 0;        /* Number of round trips to the server */
   int size;               /* Size of a config value */
   int nFileSend = 0;
-  int origConfigMask;     /* Original value of configMask */
+  int origConfigRcvMask;  /* Original value of configRcvMask */
   int nFileRecv;          /* Number of files received */
   int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
   const char *zCookie;    /* Server cookie */
   Blob send;        /* Text we are sending to the server */
   Blob recv;        /* Reply we got back from the server */
@@ -814,11 +873,11 @@
   memset(&xfer, 0, sizeof(xfer));
   xfer.pIn = &recv;
   xfer.pOut = &send;
   xfer.mxSend = db_get_int("max-upload", 250000);
 
-  assert( pushFlag || pullFlag || cloneFlag || configMask );
+  assert( pushFlag | pullFlag | cloneFlag | configRcvMask | configSendMask );
   assert( !g.urlIsFile );          /* This only works for networking */
 
   db_begin_transaction();
   db_record_repository_filename(0);
   db_multi_exec(
@@ -827,11 +886,11 @@
   blobarray_zero(xfer.aToken, count(xfer.aToken));
   blob_zero(&send);
   blob_zero(&recv);
   blob_zero(&xfer.err);
   blob_zero(&xfer.line);
-  origConfigMask = configMask;
+  origConfigRcvMask = configRcvMask;
 
   /*
   ** Always begin with a clone, pull, or push message
   */
   if( cloneFlag ){
@@ -872,22 +931,34 @@
       send_unsent(&xfer);
       nCard += send_unclustered(&xfer);
     }
 
     /* Send configuration parameter requests */
-    if( configMask ){
+    if( configRcvMask ){
       const char *zName;
-      zName = configure_first_name(configMask);
+      zName = configure_first_name(configRcvMask);
       while( zName ){
         blob_appendf(&send, "reqconfig %s\n", zName);
-        zName = configure_next_name(configMask);
+        zName = configure_next_name(configRcvMask);
         nCard++;
       }
-      if( configMask & (CONFIGSET_USER|CONFIGSET_TKT) ){
+      if( configRcvMask & (CONFIGSET_USER|CONFIGSET_TKT) ){
         configure_prepare_to_receive(0);
       }
-      configMask = 0;
+      configRcvMask = 0;
+    }
+
+    /* Send configuration parameters being pushed */
+    if( configSendMask ){
+      const char *zName;
+      zName = configure_first_name(configSendMask);
+      while( zName ){
+        send_config_card(&xfer, zName);
+        zName = configure_next_name(configSendMask);
+        nCard++;
+      }
+      configSendMask = 0;
     }
 
     /* Append randomness to the end of the message */
 #if 0   /* Enable this after all servers have upgraded */
     zRandomness = db_text(0, "SELECT hex(randomblob(20))");
@@ -1006,17 +1077,22 @@
           && blob_is_int(&xfer.aToken[2], &size) ){
         const char *zName = blob_str(&xfer.aToken[1]);
         Blob content;
         blob_zero(&content);
         blob_extract(xfer.pIn, size, &content);
-        if( configure_is_exportable(zName) & origConfigMask ){
+        if( configure_is_exportable(zName) & origConfigRcvMask ){
           if( zName[0]!='@' ){
             db_multi_exec(
                 "REPLACE INTO config(name,value) VALUES(%Q,%Q)",
                 zName, blob_str(&content)
             );
           }else{
+            /* Notice that we are evaluating arbitrary SQL received from the
+            ** server.  But this can only happen if we have specifically
+            ** requested configuration information from the server, so
+            ** presumably the operator trusts the server.
+            */
             db_multi_exec("%s", blob_str(&content));
           }
         }
         nCard++;
         blob_reset(&content);
@@ -1066,14 +1142,14 @@
         fossil_fatal("%b", &xfer.err);
       }
       blobarray_reset(xfer.aToken, xfer.nToken);
       blob_reset(&xfer.line);
     }
-    if( origConfigMask & (CONFIGSET_TKT|CONFIGSET_USER) ){
+    if( origConfigRcvMask & (CONFIGSET_TKT|CONFIGSET_USER) ){
       configure_finalize_receive();
     }
-    origConfigMask = 0;
+    origConfigRcvMask = 0;
     printf(zValueFormat, "Received:",
             blob_size(&recv), nCard,
             xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
     blob_reset(&recv);
     nCycle++;