| | @@ -178,11 +178,11 @@ |
| 178 | 178 | const char *zCookieName = login_cookie_name(); |
| 179 | 179 | cgi_set_cookie(zCookieName, "", 0, -86400); |
| 180 | 180 | redirect_to_g(); |
| 181 | 181 | } |
| 182 | 182 | if( g.okPassword && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ |
| 183 | | - zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin); |
| 183 | + zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); |
| 184 | 184 | if( db_int(1, "SELECT 0 FROM user" |
| 185 | 185 | " WHERE uid=%d AND (pw=%Q OR pw=%Q)", |
| 186 | 186 | g.userUid, zPasswd, zSha1Pw) ){ |
| 187 | 187 | sleep(1); |
| 188 | 188 | zErrMsg = |
| | @@ -197,16 +197,31 @@ |
| 197 | 197 | @ The two copies of your new passwords do not match. |
| 198 | 198 | @ Your password is unchanged. |
| 199 | 199 | @ </span></p> |
| 200 | 200 | ; |
| 201 | 201 | }else{ |
| 202 | | - char *zNewPw = sha1_shared_secret(zNew1, g.zLogin); |
| 202 | + char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); |
| 203 | + char *zChngPw; |
| 204 | + char *zErr; |
| 203 | 205 | db_multi_exec( |
| 204 | 206 | "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid |
| 205 | 207 | ); |
| 206 | | - redirect_to_g(); |
| 207 | | - return; |
| 208 | + fossil_free(zNewPw); |
| 209 | + zChngPw = mprintf( |
| 210 | + "UPDATE user" |
| 211 | + " SET pw=shared_secret(%Q,%Q," |
| 212 | + " (SELECT value FROM config WHERE name='project-code'))" |
| 213 | + " WHERE login=%Q", |
| 214 | + zNew1, g.zLogin, g.zLogin |
| 215 | + ); |
| 216 | + if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){ |
| 217 | + zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr); |
| 218 | + fossil_free(zErr); |
| 219 | + }else{ |
| 220 | + redirect_to_g(); |
| 221 | + return; |
| 222 | + } |
| 208 | 223 | } |
| 209 | 224 | } |
| 210 | 225 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 211 | 226 | uid = isValidAnonymousLogin(zUsername, zPasswd); |
| 212 | 227 | if( uid>0 ){ |
| | @@ -226,11 +241,11 @@ |
| 226 | 241 | cgi_set_cookie(zCookieName, zCookie, 0, 6*3600); |
| 227 | 242 | record_login_attempt("anonymous", zIpAddr, 1); |
| 228 | 243 | redirect_to_g(); |
| 229 | 244 | } |
| 230 | 245 | if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ |
| 231 | | - zSha1Pw = sha1_shared_secret(zPasswd, zUsername); |
| 246 | + zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0); |
| 232 | 247 | uid = db_int(0, |
| 233 | 248 | "SELECT uid FROM user" |
| 234 | 249 | " WHERE login=%Q" |
| 235 | 250 | " AND login NOT IN ('anonymous','nobody','developer','reader')" |
| 236 | 251 | " AND (pw=%Q OR pw=%Q)", |
| | @@ -795,11 +810,11 @@ |
| 795 | 810 | * this %s(zUsername), or at least I don't know how to force it to.*/ |
| 796 | 811 | @ <p><span class="loginError"> |
| 797 | 812 | @ %s(zUsername) already exists. |
| 798 | 813 | @ </span></p> |
| 799 | 814 | }else{ |
| 800 | | - char *zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login)); |
| 815 | + char *zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0); |
| 801 | 816 | int uid; |
| 802 | 817 | char *zCookie; |
| 803 | 818 | const char *zCookieName; |
| 804 | 819 | const char *zExpire; |
| 805 | 820 | int expires; |
| | @@ -873,5 +888,243 @@ |
| 873 | 888 | @ </form> |
| 874 | 889 | style_footer(); |
| 875 | 890 | |
| 876 | 891 | free(zCaptcha); |
| 877 | 892 | } |
| 893 | + |
| 894 | +/* |
| 895 | +** Return an abbreviated project code. |
| 896 | +** |
| 897 | +** Memory is obtained from malloc. |
| 898 | +*/ |
| 899 | +static char *abbreviated_project_code(const char *zFullCode){ |
| 900 | + return mprintf("%.16s", zFullCode); |
| 901 | +} |
| 902 | + |
| 903 | +/* |
| 904 | +** Run SQL on the repository database for every repository in our |
| 905 | +** login group. The SQL is run in a separate database connection. |
| 906 | +** |
| 907 | +** Any members of the login group whose repository database file |
| 908 | +** cannot be found is silently removed from the group. |
| 909 | +** |
| 910 | +** Error messages accumulate and are returned in *pzErrorMsg. The |
| 911 | +** memory used to hold these messages should be freed using |
| 912 | +** fossil_free() if one desired to avoid a memory leak. The |
| 913 | +** zPrefix and zSuffix strings surround each error message. |
| 914 | +** |
| 915 | +** Return the number of errors. |
| 916 | +*/ |
| 917 | +int login_group_sql( |
| 918 | + const char *zSql, /* The SQL to run */ |
| 919 | + const char *zPrefix, /* Prefix to each error message */ |
| 920 | + const char *zSuffix, /* Suffix to each error message */ |
| 921 | + char **pzErrorMsg /* Write error message here, if not NULL */ |
| 922 | +){ |
| 923 | + sqlite3 *pPeer; /* Connection to another database */ |
| 924 | + int nErr = 0; /* Number of errors seen so far */ |
| 925 | + int rc; /* Result code from subroutine calls */ |
| 926 | + char *zErr; /* SQLite error text */ |
| 927 | + char *zSelfCode; /* Project code for ourself */ |
| 928 | + Blob err; /* Accumulate errors here */ |
| 929 | + Stmt q; /* Query of all peer-* entries in CONFIG */ |
| 930 | + |
| 931 | + if( zPrefix==0 ) zPrefix = ""; |
| 932 | + if( zSuffix==0 ) zSuffix = ""; |
| 933 | + if( pzErrorMsg ) *pzErrorMsg = 0; |
| 934 | + zSelfCode = abbreviated_project_code(db_get("project-code", "x")); |
| 935 | + blob_zero(&err); |
| 936 | + db_prepare(&q, |
| 937 | + "SELECT name, value FROM config" |
| 938 | + " WHERE name GLOB 'peer-repo-*'" |
| 939 | + " AND name <> 'peer-repo-%q'" |
| 940 | + " ORDER BY +value", |
| 941 | + zSelfCode |
| 942 | + ); |
| 943 | + while( db_step(&q)==SQLITE_ROW ){ |
| 944 | + const char *zRepoName = db_column_text(&q, 1); |
| 945 | + if( file_size(zRepoName)<0 ){ |
| 946 | + /* Silently remove non-existant repositories from the login group. */ |
| 947 | + const char *zLabel = db_column_text(&q, 0); |
| 948 | + db_multi_exec( |
| 949 | + "DELETE FROM config WHERE name GLOB 'peer-*-%q'", |
| 950 | + &zLabel[10] |
| 951 | + ); |
| 952 | + continue; |
| 953 | + } |
| 954 | + rc = sqlite3_open_v2(zRepoName, &pPeer, SQLITE_OPEN_READWRITE, 0); |
| 955 | + if( rc!=SQLITE_OK ){ |
| 956 | + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, |
| 957 | + sqlite3_errmsg(pPeer), zSuffix); |
| 958 | + nErr++; |
| 959 | + sqlite3_close(pPeer); |
| 960 | + continue; |
| 961 | + } |
| 962 | + sqlite3_create_function(pPeer, "shared_secret", 3, SQLITE_UTF8, |
| 963 | + 0, sha1_shared_secret_sql_function, 0, 0); |
| 964 | + zErr = 0; |
| 965 | + rc = sqlite3_exec(pPeer, zSql, 0, 0, &zErr); |
| 966 | + if( zErr ){ |
| 967 | + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, zErr, zSuffix); |
| 968 | + sqlite3_free(zErr); |
| 969 | + nErr++; |
| 970 | + }else if( rc!=SQLITE_OK ){ |
| 971 | + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, |
| 972 | + sqlite3_errmsg(pPeer), zSuffix); |
| 973 | + nErr++; |
| 974 | + } |
| 975 | + sqlite3_close(pPeer); |
| 976 | + } |
| 977 | + db_finalize(&q); |
| 978 | + if( pzErrorMsg && blob_size(&err)>0 ){ |
| 979 | + *pzErrorMsg = fossil_strdup(blob_str(&err)); |
| 980 | + } |
| 981 | + blob_reset(&err); |
| 982 | + fossil_free(zSelfCode); |
| 983 | + return nErr; |
| 984 | +} |
| 985 | + |
| 986 | +/* |
| 987 | +** Attempt to join a login-group. |
| 988 | +** |
| 989 | +** If problems arise, leave an error message in *pzErrMsg. |
| 990 | +*/ |
| 991 | +void login_group_join( |
| 992 | + const char *zRepo, /* Repository file in the login group */ |
| 993 | + const char *zLogin, /* Login name for the other repo */ |
| 994 | + const char *zPassword, /* Password to prove we are authorized to join */ |
| 995 | + const char *zNewName, /* Name of new login group if making a new one */ |
| 996 | + char **pzErrMsg /* Leave an error message here */ |
| 997 | +){ |
| 998 | + Blob fullName; /* Blob for finding full pathnames */ |
| 999 | + sqlite3 *pOther; /* The other repository */ |
| 1000 | + int rc; /* Return code from sqlite3 functions */ |
| 1001 | + char *zOtherProjCode; /* Project code for pOther */ |
| 1002 | + char *zPwHash; /* Password hash on pOther */ |
| 1003 | + char *zSelfRepo; /* Name of our repository */ |
| 1004 | + char *zSelfLabel; /* Project-name for our repository */ |
| 1005 | + char *zSelfProjCode; /* Our project-code */ |
| 1006 | + char *zSql; /* SQL to run on all peers */ |
| 1007 | + const char *zSelf; /* The ATTACH name of our repository */ |
| 1008 | + |
| 1009 | + *pzErrMsg = 0; /* Default to no errors */ |
| 1010 | + zSelf = db_name("repository"); |
| 1011 | + |
| 1012 | + /* Get the full pathname of the other repository */ |
| 1013 | + file_canonical_name(zRepo, &fullName); |
| 1014 | + zRepo = mprintf(blob_str(&fullName)); |
| 1015 | + blob_reset(&fullName); |
| 1016 | + |
| 1017 | + /* Get the full pathname for our repository. Also the project code |
| 1018 | + ** and project name for ourself. */ |
| 1019 | + file_canonical_name(g.zRepositoryName, &fullName); |
| 1020 | + zSelfRepo = mprintf(blob_str(&fullName)); |
| 1021 | + blob_reset(&fullName); |
| 1022 | + zSelfProjCode = db_get("project-code", "unknown"); |
| 1023 | + zSelfLabel = db_get("project-name", 0); |
| 1024 | + if( zSelfLabel==0 ){ |
| 1025 | + zSelfLabel = zSelfProjCode; |
| 1026 | + } |
| 1027 | + |
| 1028 | + /* Make sure we are not trying to join ourselves */ |
| 1029 | + if( strcmp(zRepo, zSelfRepo)==0 ){ |
| 1030 | + *pzErrMsg = mprintf("The \"other\" repository is the same as this one."); |
| 1031 | + return; |
| 1032 | + } |
| 1033 | + |
| 1034 | + /* Make sure the other repository is a valid Fossil database */ |
| 1035 | + if( file_size(zRepo)<0 ){ |
| 1036 | + *pzErrMsg = mprintf("repository file \"%s\" does not exist", zRepo); |
| 1037 | + return; |
| 1038 | + } |
| 1039 | + rc = sqlite3_open(zRepo, &pOther); |
| 1040 | + if( rc!=SQLITE_OK ){ |
| 1041 | + *pzErrMsg = mprintf(sqlite3_errmsg(pOther)); |
| 1042 | + }else{ |
| 1043 | + rc = sqlite3_exec(pOther, "SELECT count(*) FROM user", 0, 0, pzErrMsg); |
| 1044 | + } |
| 1045 | + sqlite3_close(pOther); |
| 1046 | + if( rc ) return; |
| 1047 | + |
| 1048 | + /* Attach the other respository. Make sure the username/password is |
| 1049 | + ** valid and has Setup permission. |
| 1050 | + */ |
| 1051 | + db_multi_exec("ATTACH %Q AS other", zRepo); |
| 1052 | + zOtherProjCode = db_text("x", "SELECT value FROM other.config" |
| 1053 | + " WHERE name='project-code'"); |
| 1054 | + zPwHash = sha1_shared_secret(zPassword, zLogin, zOtherProjCode); |
| 1055 | + if( !db_exists( |
| 1056 | + "SELECT 1 FROM other.user" |
| 1057 | + " WHERE login=%Q AND cap GLOB '*s*'" |
| 1058 | + " AND (pw=%Q OR pw=%Q)", |
| 1059 | + zLogin, zPassword, zPwHash) |
| 1060 | + ){ |
| 1061 | + db_multi_exec("DETACH other"); |
| 1062 | + *pzErrMsg = "The supplied username/password does not correspond to a" |
| 1063 | + " user Setup permission on the other repository."; |
| 1064 | + return; |
| 1065 | + } |
| 1066 | + |
| 1067 | + /* Create all the necessary CONFIG table entries on both the |
| 1068 | + ** other repository and on our own repository. |
| 1069 | + */ |
| 1070 | + zSelfProjCode = abbreviated_project_code(zSelfProjCode); |
| 1071 | + db_begin_transaction(); |
| 1072 | + db_multi_exec( |
| 1073 | + "DELETE FROM %s.config WHERE name GLOB 'peer-*';" |
| 1074 | + "INSERT INTO %s.config(name,value) VALUES('peer-repo-%s',%Q);" |
| 1075 | + "INSERT INTO %s.config(name,value) " |
| 1076 | + " SELECT 'peer-name-%q', value FROM other.config" |
| 1077 | + " WHERE name='project-name';", |
| 1078 | + zSelf, |
| 1079 | + zSelf, zOtherProjCode, zRepo, |
| 1080 | + zSelf, zOtherProjCode |
| 1081 | + ); |
| 1082 | + db_multi_exec( |
| 1083 | + "INSERT OR IGNORE INTO other.config(name,value)" |
| 1084 | + " VALUES('login-group-name',%Q);", |
| 1085 | + zNewName |
| 1086 | + ); |
| 1087 | + db_multi_exec( |
| 1088 | + "REPLACE INTO %s.config(name,value)" |
| 1089 | + " SELECT name, value FROM other.config" |
| 1090 | + " WHERE name GLOB 'peer-*' OR name='login-group-name'", |
| 1091 | + zSelf |
| 1092 | + ); |
| 1093 | + db_end_transaction(0); |
| 1094 | + db_multi_exec("DETACH other"); |
| 1095 | + |
| 1096 | + /* Propagate the changes to all other members of the login-group */ |
| 1097 | + zSql = mprintf( |
| 1098 | + "BEGIN;" |
| 1099 | + "REPLACE INTO config(name, value) VALUES('peer-name-%q', %Q);" |
| 1100 | + "REPLACE INTO config(name, value) VALUES('peer-repo-%q', %Q);" |
| 1101 | + "COMMIT;", |
| 1102 | + zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo |
| 1103 | + ); |
| 1104 | + login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
| 1105 | + fossil_free(zSql); |
| 1106 | +} |
| 1107 | + |
| 1108 | +/* |
| 1109 | +** Leave the login group that we are currently part of. |
| 1110 | +*/ |
| 1111 | +void login_group_leave(char **pzErrMsg){ |
| 1112 | + char *zProjCode; |
| 1113 | + char *zSql; |
| 1114 | + |
| 1115 | + *pzErrMsg = 0; |
| 1116 | + zProjCode = abbreviated_project_code(db_get("project-code","x")); |
| 1117 | + zSql = mprintf( |
| 1118 | + "DELETE FROM config WHERE name GLOB 'peer-*-%q';" |
| 1119 | + "DELETE FROM config" |
| 1120 | + " WHERE name='login-group-name'" |
| 1121 | + " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;", |
| 1122 | + zProjCode |
| 1123 | + ); |
| 1124 | + fossil_free(zProjCode); |
| 1125 | + login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
| 1126 | + fossil_free(zSql); |
| 1127 | + db_multi_exec( |
| 1128 | + "DELETE FROM config WHERE name GLOB 'peer-*' OR name='login-group-name';" |
| 1129 | + ); |
| 1130 | +} |
| 878 | 1131 | |