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
- branch=trunk inherited from [a28c83647d]
- sym-trunk inherited from [a28c83647d]
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++;