|
dbda8d6…
|
drh
|
1 |
/* |
|
c19f34c…
|
drh
|
2 |
** Copyright (c) 2007 D. Richard Hipp |
|
dbda8d6…
|
drh
|
3 |
** |
|
dbda8d6…
|
drh
|
4 |
** This program is free software; you can redistribute it and/or |
|
c06edd2…
|
drh
|
5 |
** modify it under the terms of the Simplified BSD License (also |
|
c06edd2…
|
drh
|
6 |
** known as the "2-Clause License" or "FreeBSD License".) |
|
c06edd2…
|
drh
|
7 |
|
|
dbda8d6…
|
drh
|
8 |
** This program is distributed in the hope that it will be useful, |
|
c06edd2…
|
drh
|
9 |
** but without any warranty; without even the implied warranty of |
|
c06edd2…
|
drh
|
10 |
** merchantability or fitness for a particular purpose. |
|
dbda8d6…
|
drh
|
11 |
** |
|
dbda8d6…
|
drh
|
12 |
** Author contact information: |
|
dbda8d6…
|
drh
|
13 |
** [email protected] |
|
dbda8d6…
|
drh
|
14 |
** http://www.hwaci.com/drh/ |
|
dbda8d6…
|
drh
|
15 |
** |
|
dbda8d6…
|
drh
|
16 |
******************************************************************************* |
|
dbda8d6…
|
drh
|
17 |
** |
|
dbda8d6…
|
drh
|
18 |
** This file contains code for generating the login and logout screens. |
|
9c952d2…
|
drh
|
19 |
** |
|
9c952d2…
|
drh
|
20 |
** Notes: |
|
9c952d2…
|
drh
|
21 |
** |
|
a257fde…
|
drh
|
22 |
** There are four special-case user-ids: "anonymous", "nobody", |
|
a257fde…
|
drh
|
23 |
** "developer" and "reader". |
|
a257fde…
|
drh
|
24 |
** |
|
9c952d2…
|
drh
|
25 |
** The capabilities of the nobody user are available to anyone, |
|
9c952d2…
|
drh
|
26 |
** regardless of whether or not they are logged in. The capabilities |
|
9c952d2…
|
drh
|
27 |
** of anonymous are only available after logging in, but the login |
|
9c952d2…
|
drh
|
28 |
** screen displays the password for the anonymous login, so this |
|
a257fde…
|
drh
|
29 |
** should not prevent a human user from doing so. The capabilities |
|
a257fde…
|
drh
|
30 |
** of developer and reader are inherited by any user that has the |
|
a257fde…
|
drh
|
31 |
** "v" and "u" capabilities, respectively. |
|
9c952d2…
|
drh
|
32 |
** |
|
9c952d2…
|
drh
|
33 |
** The nobody user has capabilities that you want spiders to have. |
|
9c952d2…
|
drh
|
34 |
** The anonymous user has capabilities that you want people without |
|
9c952d2…
|
drh
|
35 |
** logins to have. |
|
9c952d2…
|
drh
|
36 |
** |
|
9c952d2…
|
drh
|
37 |
** Of course, a sophisticated spider could easily circumvent the |
|
9c952d2…
|
drh
|
38 |
** anonymous login requirement and walk the website. But that is |
|
9c952d2…
|
drh
|
39 |
** not really the point. The anonymous login keeps search-engine |
|
9c952d2…
|
drh
|
40 |
** crawlers and site download tools like wget from walking change |
|
9c952d2…
|
drh
|
41 |
** logs and downloading diffs of very version of the archive that |
|
9c952d2…
|
drh
|
42 |
** has ever existed, and things like that. |
|
dbda8d6…
|
drh
|
43 |
*/ |
|
dbda8d6…
|
drh
|
44 |
#include "config.h" |
|
dbda8d6…
|
drh
|
45 |
#include "login.h" |
|
45f3516…
|
jan.nijtmans
|
46 |
#if defined(_WIN32) |
|
d0305b3…
|
aku
|
47 |
# include <windows.h> /* for Sleep */ |
|
d2ba02e…
|
ron
|
48 |
# if defined(__MINGW32__) || defined(_MSC_VER) |
|
3564af0…
|
drh
|
49 |
# define sleep Sleep /* windows does not have sleep, but Sleep */ |
|
3564af0…
|
drh
|
50 |
# endif |
|
d0305b3…
|
aku
|
51 |
#endif |
|
dbda8d6…
|
drh
|
52 |
#include <time.h> |
|
45f3516…
|
jan.nijtmans
|
53 |
|
|
920ace1…
|
drh
|
54 |
/* |
|
920ace1…
|
drh
|
55 |
** Compute an appropriate Anti-CSRF token into g.zCsrfToken[]. |
|
920ace1…
|
drh
|
56 |
*/ |
|
920ace1…
|
drh
|
57 |
static void login_create_csrf_secret(const char *zSeed){ |
|
920ace1…
|
drh
|
58 |
unsigned char zResult[20]; |
|
ab05475…
|
danield
|
59 |
unsigned int i; |
|
920ace1…
|
drh
|
60 |
|
|
920ace1…
|
drh
|
61 |
sha1sum_binary(zSeed, zResult); |
|
920ace1…
|
drh
|
62 |
for(i=0; i<sizeof(g.zCsrfToken)-1; i++){ |
|
920ace1…
|
drh
|
63 |
g.zCsrfToken[i] = "abcdefghijklmnopqrstuvwxyz" |
|
920ace1…
|
drh
|
64 |
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|
920ace1…
|
drh
|
65 |
"0123456789-/"[zResult[i]%64]; |
|
920ace1…
|
drh
|
66 |
} |
|
920ace1…
|
drh
|
67 |
g.zCsrfToken[i] = 0; |
|
920ace1…
|
drh
|
68 |
} |
|
93d52a0…
|
rberteig
|
69 |
|
|
93d52a0…
|
rberteig
|
70 |
/* |
|
a257fde…
|
drh
|
71 |
** Return the login-group name. Or return 0 if this repository is |
|
a257fde…
|
drh
|
72 |
** not a member of a login-group. |
|
a257fde…
|
drh
|
73 |
*/ |
|
a257fde…
|
drh
|
74 |
const char *login_group_name(void){ |
|
a257fde…
|
drh
|
75 |
static const char *zGroup = 0; |
|
a257fde…
|
drh
|
76 |
static int once = 1; |
|
a257fde…
|
drh
|
77 |
if( once ){ |
|
a257fde…
|
drh
|
78 |
zGroup = db_get("login-group-name", 0); |
|
a257fde…
|
drh
|
79 |
once = 0; |
|
a257fde…
|
drh
|
80 |
} |
|
a257fde…
|
drh
|
81 |
return zGroup; |
|
a257fde…
|
drh
|
82 |
} |
|
a257fde…
|
drh
|
83 |
|
|
a257fde…
|
drh
|
84 |
/* |
|
a257fde…
|
drh
|
85 |
** Return a path appropriate for setting a cookie. |
|
a257fde…
|
drh
|
86 |
** |
|
a257fde…
|
drh
|
87 |
** The path is g.zTop for single-repo cookies. It is "/" for |
|
a257fde…
|
drh
|
88 |
** cookies of a login-group. |
|
a257fde…
|
drh
|
89 |
*/ |
|
f8a2aa0…
|
drh
|
90 |
const char *login_cookie_path(void){ |
|
a257fde…
|
drh
|
91 |
if( login_group_name()==0 ){ |
|
a257fde…
|
drh
|
92 |
return g.zTop; |
|
a257fde…
|
drh
|
93 |
}else{ |
|
a257fde…
|
drh
|
94 |
return "/"; |
|
a257fde…
|
drh
|
95 |
} |
|
a257fde…
|
drh
|
96 |
} |
|
a257fde…
|
drh
|
97 |
|
|
a257fde…
|
drh
|
98 |
/* |
|
a257fde…
|
drh
|
99 |
** Return the name of the login cookie. |
|
a257fde…
|
drh
|
100 |
** |
|
a257fde…
|
drh
|
101 |
** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX |
|
a257fde…
|
drh
|
102 |
** where the Xs are the first 16 characters of the login-group-code or |
|
a257fde…
|
drh
|
103 |
** of the project-code if we are not a member of any login-group. |
|
d0305b3…
|
aku
|
104 |
*/ |
|
796dcfe…
|
drh
|
105 |
char *login_cookie_name(void){ |
|
d0305b3…
|
aku
|
106 |
static char *zCookieName = 0; |
|
d0305b3…
|
aku
|
107 |
if( zCookieName==0 ){ |
|
a257fde…
|
drh
|
108 |
zCookieName = db_text(0, |
|
a257fde…
|
drh
|
109 |
"SELECT 'fossil-' || substr(value,1,16)" |
|
a257fde…
|
drh
|
110 |
" FROM config" |
|
a257fde…
|
drh
|
111 |
" WHERE name IN ('project-code','login-group-code')" |
|
fff43eb…
|
drh
|
112 |
" ORDER BY name /*sort*/" |
|
a257fde…
|
drh
|
113 |
); |
|
d0305b3…
|
aku
|
114 |
} |
|
c34003b…
|
jan.nijtmans
|
115 |
return zCookieName; |
|
d0305b3…
|
aku
|
116 |
} |
|
d0305b3…
|
aku
|
117 |
|
|
d0305b3…
|
aku
|
118 |
/* |
|
0600b27…
|
drh
|
119 |
** Redirect to the page specified by the "g" query parameter. |
|
0600b27…
|
drh
|
120 |
** Or if there is no "g" query parameter, redirect to the homepage. |
|
0600b27…
|
drh
|
121 |
*/ |
|
1958448…
|
drh
|
122 |
NORETURN void login_redirect_to_g(void){ |
|
0600b27…
|
drh
|
123 |
const char *zGoto = P("g"); |
|
0600b27…
|
drh
|
124 |
if( zGoto ){ |
|
3571c87…
|
drh
|
125 |
cgi_redirectf("%R/%s",zGoto); |
|
aa4159f…
|
drh
|
126 |
}else if( (zGoto = P("fossil-goto"))!=0 && zGoto[0]!=0 ){ |
|
aa4159f…
|
drh
|
127 |
cgi_set_cookie("fossil-goto","",0,1); |
|
aa4159f…
|
drh
|
128 |
cgi_redirect(zGoto); |
|
0600b27…
|
drh
|
129 |
}else{ |
|
0600b27…
|
drh
|
130 |
fossil_redirect_home(); |
|
0600b27…
|
drh
|
131 |
} |
|
0600b27…
|
drh
|
132 |
} |
|
0600b27…
|
drh
|
133 |
|
|
0600b27…
|
drh
|
134 |
/* |
|
a257fde…
|
drh
|
135 |
** Return an abbreviated project code. The abbreviation is the first |
|
f7ce03e…
|
drh
|
136 |
** 16 characters of the project code. |
|
a257fde…
|
drh
|
137 |
** |
|
a257fde…
|
drh
|
138 |
** Memory is obtained from malloc. |
|
86cbb69…
|
drh
|
139 |
*/ |
|
a257fde…
|
drh
|
140 |
static char *abbreviated_project_code(const char *zFullCode){ |
|
f7ce03e…
|
drh
|
141 |
return mprintf("%.16s", zFullCode); |
|
a257fde…
|
drh
|
142 |
} |
|
a257fde…
|
drh
|
143 |
|
|
86cbb69…
|
drh
|
144 |
|
|
86cbb69…
|
drh
|
145 |
/* |
|
b4a29fa…
|
drh
|
146 |
** Check to see if the anonymous login is valid. If it is valid, return |
|
b4a29fa…
|
drh
|
147 |
** the userid of the anonymous user. |
|
796dcfe…
|
drh
|
148 |
** |
|
796dcfe…
|
drh
|
149 |
** The zCS parameter is the "captcha seed" used for a specific |
|
796dcfe…
|
drh
|
150 |
** anonymous login request. |
|
b4a29fa…
|
drh
|
151 |
*/ |
|
796dcfe…
|
drh
|
152 |
int login_is_valid_anonymous( |
|
b4a29fa…
|
drh
|
153 |
const char *zUsername, /* The username. Must be "anonymous" */ |
|
796dcfe…
|
drh
|
154 |
const char *zPassword, /* The supplied password */ |
|
796dcfe…
|
drh
|
155 |
const char *zCS /* The captcha seed value */ |
|
b4a29fa…
|
drh
|
156 |
){ |
|
b4a29fa…
|
drh
|
157 |
const char *zPw; /* The correct password shown in the captcha */ |
|
b4a29fa…
|
drh
|
158 |
int uid; /* The user ID of anonymous */ |
|
8659d84…
|
drh
|
159 |
int n = 0; /* Counter of captcha-secrets */ |
|
b4a29fa…
|
drh
|
160 |
|
|
b4a29fa…
|
drh
|
161 |
if( zUsername==0 ) return 0; |
|
796dcfe…
|
drh
|
162 |
else if( zPassword==0 ) return 0; |
|
796dcfe…
|
drh
|
163 |
else if( zCS==0 ) return 0; |
|
796dcfe…
|
drh
|
164 |
else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; |
|
7d2b47a…
|
drh
|
165 |
else if( anon_cookie_lifespan()==0 ) return 0; |
|
8659d84…
|
drh
|
166 |
while( 1/*exit-by-break*/ ){ |
|
8659d84…
|
drh
|
167 |
zPw = captcha_decode((unsigned int)atoi(zCS), n); |
|
8659d84…
|
drh
|
168 |
if( zPw==0 ) return 0; |
|
8659d84…
|
drh
|
169 |
if( fossil_stricmp(zPw, zPassword)==0 ) break; |
|
8659d84…
|
drh
|
170 |
n++; |
|
8659d84…
|
drh
|
171 |
} |
|
b4a29fa…
|
drh
|
172 |
uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" |
|
604e1a6…
|
drh
|
173 |
" AND octet_length(pw)>0 AND octet_length(cap)>0"); |
|
6fdf529…
|
drh
|
174 |
return uid; |
|
c7de5f7…
|
drh
|
175 |
} |
|
c7de5f7…
|
drh
|
176 |
|
|
c7de5f7…
|
drh
|
177 |
/* |
|
c7de5f7…
|
drh
|
178 |
** Make sure the accesslog table exists. Create it if it does not |
|
c7de5f7…
|
drh
|
179 |
*/ |
|
c7de5f7…
|
drh
|
180 |
void create_accesslog_table(void){ |
|
03e21b9…
|
drh
|
181 |
if( !db_table_exists("repository","accesslog") ){ |
|
03e21b9…
|
drh
|
182 |
db_unprotect(PROTECT_READONLY); |
|
03e21b9…
|
drh
|
183 |
db_multi_exec( |
|
03e21b9…
|
drh
|
184 |
"CREATE TABLE IF NOT EXISTS repository.accesslog(" |
|
03e21b9…
|
drh
|
185 |
" uname TEXT," |
|
03e21b9…
|
drh
|
186 |
" ipaddr TEXT," |
|
03e21b9…
|
drh
|
187 |
" success BOOLEAN," |
|
03e21b9…
|
drh
|
188 |
" mtime TIMESTAMP" |
|
03e21b9…
|
drh
|
189 |
");" |
|
03e21b9…
|
drh
|
190 |
); |
|
03e21b9…
|
drh
|
191 |
db_protect_pop(); |
|
03e21b9…
|
drh
|
192 |
} |
|
6fdf529…
|
drh
|
193 |
} |
|
6fdf529…
|
drh
|
194 |
|
|
6fdf529…
|
drh
|
195 |
/* |
|
6fdf529…
|
drh
|
196 |
** Make a record of a login attempt, if login record keeping is enabled. |
|
6fdf529…
|
drh
|
197 |
*/ |
|
6fdf529…
|
drh
|
198 |
static void record_login_attempt( |
|
6fdf529…
|
drh
|
199 |
const char *zUsername, /* Name of user logging in */ |
|
6fdf529…
|
drh
|
200 |
const char *zIpAddr, /* IP address from which they logged in */ |
|
6fdf529…
|
drh
|
201 |
int bSuccess /* True if the attempt was a success */ |
|
6fdf529…
|
drh
|
202 |
){ |
|
e31c2c0…
|
drh
|
203 |
db_unprotect(PROTECT_READONLY); |
|
fc79c57…
|
drh
|
204 |
if( db_get_boolean("access-log", 1) ){ |
|
d7e10ce…
|
drh
|
205 |
create_accesslog_table(); |
|
d7e10ce…
|
drh
|
206 |
db_multi_exec( |
|
d7e10ce…
|
drh
|
207 |
"INSERT INTO accesslog(uname,ipaddr,success,mtime)" |
|
d7e10ce…
|
drh
|
208 |
"VALUES(%Q,%Q,%d,julianday('now'));", |
|
d7e10ce…
|
drh
|
209 |
zUsername, zIpAddr, bSuccess |
|
d7e10ce…
|
drh
|
210 |
); |
|
d7e10ce…
|
drh
|
211 |
} |
|
d7e10ce…
|
drh
|
212 |
if( bSuccess ){ |
|
d7e10ce…
|
drh
|
213 |
alert_user_contact(zUsername); |
|
d7e10ce…
|
drh
|
214 |
} |
|
e31c2c0…
|
drh
|
215 |
db_protect_pop(); |
|
796dcfe…
|
drh
|
216 |
} |
|
796dcfe…
|
drh
|
217 |
|
|
796dcfe…
|
drh
|
218 |
/* |
|
796dcfe…
|
drh
|
219 |
** Searches for the user ID matching the given name and password. |
|
796dcfe…
|
drh
|
220 |
** On success it returns a positive value. On error it returns 0. |
|
796dcfe…
|
drh
|
221 |
** On serious (DB-level) error it will probably exit. |
|
796dcfe…
|
drh
|
222 |
** |
|
9b4e157…
|
drh
|
223 |
** zUsername uses double indirection because we may re-point *zUsername |
|
9b4e157…
|
drh
|
224 |
** at a C string allocated with fossil_strdup() if you pass an email |
|
9b4e157…
|
drh
|
225 |
** address instead and we find that address in the user table's info |
|
9b4e157…
|
drh
|
226 |
** field, which is expected to contain a string of the form "Human Name |
|
9b4e157…
|
drh
|
227 |
** <[email protected]>". In that case, *zUsername will point to that |
|
9b4e157…
|
drh
|
228 |
** user's actual login name on return, causing a leak unless the caller |
|
9b4e157…
|
drh
|
229 |
** is diligent enough to check whether its pointer was re-pointed. |
|
9b4e157…
|
drh
|
230 |
** |
|
796dcfe…
|
drh
|
231 |
** zPassword may be either the plain-text form or the encrypted |
|
796dcfe…
|
drh
|
232 |
** form of the user's password. |
|
796dcfe…
|
drh
|
233 |
*/ |
|
9b4e157…
|
drh
|
234 |
int login_search_uid(const char **pzUsername, const char *zPasswd){ |
|
9b4e157…
|
drh
|
235 |
char *zSha1Pw = sha1_shared_secret(zPasswd, *pzUsername, 0); |
|
8c91be8…
|
drh
|
236 |
int uid = db_int(0, |
|
8c91be8…
|
drh
|
237 |
"SELECT uid FROM user" |
|
8c91be8…
|
drh
|
238 |
" WHERE login=%Q" |
|
604e1a6…
|
drh
|
239 |
" AND octet_length(cap)>0 AND octet_length(pw)>0" |
|
8c91be8…
|
drh
|
240 |
" AND login NOT IN ('anonymous','nobody','developer','reader')" |
|
8c91be8…
|
drh
|
241 |
" AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))" |
|
8c91be8…
|
drh
|
242 |
" AND (info NOT LIKE '%%expires 20%%'" |
|
8c91be8…
|
drh
|
243 |
" OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))", |
|
9b4e157…
|
drh
|
244 |
*pzUsername, zSha1Pw, zPasswd |
|
8c91be8…
|
drh
|
245 |
); |
|
8c91be8…
|
drh
|
246 |
|
|
8c91be8…
|
drh
|
247 |
/* If we did not find a login on the first attempt, and the username |
|
9b4e157…
|
drh
|
248 |
** looks like an email address, then perhaps the user entered their |
|
8c91be8…
|
drh
|
249 |
** email address instead of their login. Try again to match the user |
|
8c91be8…
|
drh
|
250 |
** against email addresses contained in the "info" field. |
|
8c91be8…
|
drh
|
251 |
*/ |
|
9b4e157…
|
drh
|
252 |
if( uid==0 && strchr(*pzUsername,'@')!=0 ){ |
|
8c91be8…
|
drh
|
253 |
Stmt q; |
|
8c91be8…
|
drh
|
254 |
db_prepare(&q, |
|
8c91be8…
|
drh
|
255 |
"SELECT login FROM user" |
|
8c91be8…
|
drh
|
256 |
" WHERE find_emailaddr(info)=%Q" |
|
8c91be8…
|
drh
|
257 |
" AND instr(login,'@')==0", |
|
9b4e157…
|
drh
|
258 |
*pzUsername |
|
8c91be8…
|
drh
|
259 |
); |
|
9b4e157…
|
drh
|
260 |
while( db_step(&q)==SQLITE_ROW ){ |
|
9b4e157…
|
drh
|
261 |
const char *zLogin = db_column_text(&q,0); |
|
9b4e157…
|
drh
|
262 |
if( (uid = login_search_uid(&zLogin, zPasswd) ) != 0 ){ |
|
9b4e157…
|
drh
|
263 |
*pzUsername = fossil_strdup(zLogin); |
|
9b4e157…
|
drh
|
264 |
break; |
|
9b4e157…
|
drh
|
265 |
} |
|
8c91be8…
|
drh
|
266 |
} |
|
8c91be8…
|
drh
|
267 |
db_finalize(&q); |
|
275da70…
|
danield
|
268 |
} |
|
796dcfe…
|
drh
|
269 |
free(zSha1Pw); |
|
796dcfe…
|
drh
|
270 |
return uid; |
|
796dcfe…
|
drh
|
271 |
} |
|
796dcfe…
|
drh
|
272 |
|
|
796dcfe…
|
drh
|
273 |
/* |
|
796dcfe…
|
drh
|
274 |
** Generates a login cookie value for a non-anonymous user. |
|
796dcfe…
|
drh
|
275 |
** |
|
796dcfe…
|
drh
|
276 |
** The zHash parameter must be a random value which must be |
|
796dcfe…
|
drh
|
277 |
** subsequently stored in user.cookie for later validation. |
|
796dcfe…
|
drh
|
278 |
** |
|
796dcfe…
|
drh
|
279 |
** The returned memory should be free()d after use. |
|
796dcfe…
|
drh
|
280 |
*/ |
|
4e18dba…
|
jan.nijtmans
|
281 |
char *login_gen_user_cookie_value(const char *zUsername, const char *zHash){ |
|
f7ce03e…
|
drh
|
282 |
char *zProjCode = db_get("project-code",NULL); |
|
f7ce03e…
|
drh
|
283 |
char *zCode = abbreviated_project_code(zProjCode); |
|
f7ce03e…
|
drh
|
284 |
free(zProjCode); |
|
796dcfe…
|
drh
|
285 |
assert((zUsername && *zUsername) && "Invalid user data."); |
|
f7ce03e…
|
drh
|
286 |
return mprintf("%s/%z/%s", zHash, zCode, zUsername); |
|
796dcfe…
|
drh
|
287 |
} |
|
796dcfe…
|
drh
|
288 |
|
|
796dcfe…
|
drh
|
289 |
/* |
|
796dcfe…
|
drh
|
290 |
** Generates a login cookie for NON-ANONYMOUS users. Note that this |
|
796dcfe…
|
drh
|
291 |
** function "could" figure out the uid by itself but it currently |
|
796dcfe…
|
drh
|
292 |
** doesn't because the code which calls this already has the uid. |
|
796dcfe…
|
drh
|
293 |
** |
|
796dcfe…
|
drh
|
294 |
** This function also updates the user.cookie, user.ipaddr, |
|
796dcfe…
|
drh
|
295 |
** and user.cexpire fields for the given user. |
|
796dcfe…
|
drh
|
296 |
** |
|
796dcfe…
|
drh
|
297 |
** If zDest is not NULL then the generated cookie is copied to |
|
e2bdc10…
|
danield
|
298 |
** *zDdest and ownership is transferred to the caller (who should |
|
796dcfe…
|
drh
|
299 |
** eventually pass it to free()). |
|
6b7b323…
|
drh
|
300 |
** |
|
6b7b323…
|
drh
|
301 |
** If bSessionCookie is true, the cookie will be a session cookie, |
|
6b7b323…
|
drh
|
302 |
** else a persistent cookie. If it's a session cookie, the |
|
6b7b323…
|
drh
|
303 |
** [user].[cexpire] and [user].[cookie] entries will be modified as if |
|
6b7b323…
|
drh
|
304 |
** it were a persistent cookie because doing so is necessary for |
|
6b7b323…
|
drh
|
305 |
** fossil's own "is this cookie still valid?" checks to work. |
|
796dcfe…
|
drh
|
306 |
*/ |
|
796dcfe…
|
drh
|
307 |
void login_set_user_cookie( |
|
4e18dba…
|
jan.nijtmans
|
308 |
const char *zUsername, /* User's name */ |
|
796dcfe…
|
drh
|
309 |
int uid, /* User's ID */ |
|
6b7b323…
|
drh
|
310 |
char **zDest, /* Optional: store generated cookie value. */ |
|
6b7b323…
|
drh
|
311 |
int bSessionCookie /* True for session-only cookie */ |
|
796dcfe…
|
drh
|
312 |
){ |
|
796dcfe…
|
drh
|
313 |
const char *zCookieName = login_cookie_name(); |
|
796dcfe…
|
drh
|
314 |
const char *zExpire = db_get("cookie-expire","8766"); |
|
6b7b323…
|
drh
|
315 |
const int expires = atoi(zExpire)*3600; |
|
6b7b323…
|
drh
|
316 |
char *zHash = 0; |
|
796dcfe…
|
drh
|
317 |
char *zCookie; |
|
4e18dba…
|
jan.nijtmans
|
318 |
const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */ |
|
73038ba…
|
drh
|
319 |
|
|
796dcfe…
|
drh
|
320 |
assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); |
|
73038ba…
|
drh
|
321 |
zHash = db_text(0, |
|
73038ba…
|
drh
|
322 |
"SELECT cookie FROM user" |
|
73038ba…
|
drh
|
323 |
" WHERE uid=%d" |
|
73038ba…
|
drh
|
324 |
" AND cexpire>julianday('now')" |
|
73038ba…
|
drh
|
325 |
" AND length(cookie)>30", |
|
7d18c40…
|
drh
|
326 |
uid); |
|
73038ba…
|
drh
|
327 |
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))"); |
|
796dcfe…
|
drh
|
328 |
zCookie = login_gen_user_cookie_value(zUsername, zHash); |
|
6b7b323…
|
drh
|
329 |
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), |
|
6b7b323…
|
drh
|
330 |
bSessionCookie ? 0 : expires); |
|
796dcfe…
|
drh
|
331 |
record_login_attempt(zUsername, zIpAddr, 1); |
|
f741baa…
|
drh
|
332 |
db_unprotect(PROTECT_USER); |
|
6b7b323…
|
drh
|
333 |
db_multi_exec("UPDATE user SET cookie=%Q," |
|
796dcfe…
|
drh
|
334 |
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
|
6b7b323…
|
drh
|
335 |
zHash, expires, uid); |
|
f741baa…
|
drh
|
336 |
db_protect_pop(); |
|
6b7b323…
|
drh
|
337 |
fossil_free(zHash); |
|
796dcfe…
|
drh
|
338 |
if( zDest ){ |
|
796dcfe…
|
drh
|
339 |
*zDest = zCookie; |
|
796dcfe…
|
drh
|
340 |
}else{ |
|
796dcfe…
|
drh
|
341 |
free(zCookie); |
|
796dcfe…
|
drh
|
342 |
} |
|
796dcfe…
|
drh
|
343 |
} |
|
796dcfe…
|
drh
|
344 |
|
|
68da478…
|
drh
|
345 |
/* |
|
7d2b47a…
|
drh
|
346 |
** SETTING: anon-cookie-lifespan width=10 default=480 |
|
7d2b47a…
|
drh
|
347 |
** The number of minutes for which an anonymous login cookie is |
|
7d2b47a…
|
drh
|
348 |
** valid. Anonymous logins are prohibited if this value is zero. |
|
7d2b47a…
|
drh
|
349 |
*/ |
|
7d2b47a…
|
drh
|
350 |
|
|
7d2b47a…
|
drh
|
351 |
|
|
7d2b47a…
|
drh
|
352 |
/* |
|
7d2b47a…
|
drh
|
353 |
** The default lifetime of an anoymous cookie, in minutes. |
|
7d2b47a…
|
drh
|
354 |
*/ |
|
7d2b47a…
|
drh
|
355 |
#define ANONYMOUS_COOKIE_LIFESPAN (8*60) |
|
7d2b47a…
|
drh
|
356 |
|
|
7d2b47a…
|
drh
|
357 |
/* |
|
7d2b47a…
|
drh
|
358 |
** Return the lifetime of an anonymous cookie, in minutes. |
|
68da478…
|
drh
|
359 |
*/ |
|
7d2b47a…
|
drh
|
360 |
int anon_cookie_lifespan(void){ |
|
7d2b47a…
|
drh
|
361 |
static int lifespan = -1; |
|
7d2b47a…
|
drh
|
362 |
if( lifespan<0 ){ |
|
7d2b47a…
|
drh
|
363 |
lifespan = db_get_int("anon-cookie-lifespan", ANONYMOUS_COOKIE_LIFESPAN); |
|
7d2b47a…
|
drh
|
364 |
if( lifespan<0 ) lifespan = 0; |
|
7d2b47a…
|
drh
|
365 |
} |
|
7d2b47a…
|
drh
|
366 |
return lifespan; |
|
7d2b47a…
|
drh
|
367 |
} |
|
68da478…
|
drh
|
368 |
|
|
796dcfe…
|
drh
|
369 |
/* Sets a cookie for an anonymous user login, which looks like this: |
|
796dcfe…
|
drh
|
370 |
** |
|
68da478…
|
drh
|
371 |
** HASH/TIME/anonymous |
|
796dcfe…
|
drh
|
372 |
** |
|
0693766…
|
drh
|
373 |
** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET |
|
0693766…
|
drh
|
374 |
** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value. |
|
796dcfe…
|
drh
|
375 |
** |
|
796dcfe…
|
drh
|
376 |
** If zCookieDest is not NULL then the generated cookie is assigned to |
|
796dcfe…
|
drh
|
377 |
** *zCookieDest and the caller must eventually free() it. |
|
6b7b323…
|
drh
|
378 |
** |
|
6b7b323…
|
drh
|
379 |
** If bSessionCookie is true, the cookie will be a session cookie. |
|
68da478…
|
drh
|
380 |
** |
|
68da478…
|
drh
|
381 |
** Search for tag-20250817a to find the code that recognizes this cookie. |
|
796dcfe…
|
drh
|
382 |
*/ |
|
1958448…
|
drh
|
383 |
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){ |
|
e2bdc10…
|
danield
|
384 |
char *zNow; /* Current time (Julian day number) */ |
|
796dcfe…
|
drh
|
385 |
char *zCookie; /* The login cookie */ |
|
0693766…
|
drh
|
386 |
const char *zUserAgent; /* The user agent */ |
|
4e18dba…
|
jan.nijtmans
|
387 |
const char *zCookieName; /* Name of the login cookie */ |
|
796dcfe…
|
drh
|
388 |
Blob b; /* Blob used during cookie construction */ |
|
7d2b47a…
|
drh
|
389 |
int expires = bSessionCookie ? 0 : anon_cookie_lifespan(); |
|
796dcfe…
|
drh
|
390 |
zCookieName = login_cookie_name(); |
|
796dcfe…
|
drh
|
391 |
zNow = db_text("0", "SELECT julianday('now')"); |
|
7d18c40…
|
drh
|
392 |
assert( zCookieName && zNow ); |
|
796dcfe…
|
drh
|
393 |
blob_init(&b, zNow, -1); |
|
0693766…
|
drh
|
394 |
zUserAgent = PD("HTTP_USER_AGENT","nil"); |
|
0693766…
|
drh
|
395 |
blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0)); |
|
796dcfe…
|
drh
|
396 |
sha1sum_blob(&b, &b); |
|
68da478…
|
drh
|
397 |
zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); |
|
796dcfe…
|
drh
|
398 |
blob_reset(&b); |
|
6b7b323…
|
drh
|
399 |
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); |
|
796dcfe…
|
drh
|
400 |
if( zCookieDest ){ |
|
796dcfe…
|
drh
|
401 |
*zCookieDest = zCookie; |
|
796dcfe…
|
drh
|
402 |
}else{ |
|
796dcfe…
|
drh
|
403 |
free(zCookie); |
|
796dcfe…
|
drh
|
404 |
} |
|
8659d84…
|
drh
|
405 |
fossil_free(zNow); |
|
796dcfe…
|
drh
|
406 |
} |
|
796dcfe…
|
drh
|
407 |
|
|
796dcfe…
|
drh
|
408 |
/* |
|
796dcfe…
|
drh
|
409 |
** "Unsets" the login cookie (insofar as cookies can be unset) and |
|
796dcfe…
|
drh
|
410 |
** clears the current user's (g.userUid) login information from the |
|
796dcfe…
|
drh
|
411 |
** user table. Sets: user.cookie, user.ipaddr, user.cexpire. |
|
796dcfe…
|
drh
|
412 |
** |
|
796dcfe…
|
drh
|
413 |
** We could/should arguably clear out g.userUid and g.perm here, but |
|
796dcfe…
|
drh
|
414 |
** we don't currently do not. |
|
796dcfe…
|
drh
|
415 |
** |
|
796dcfe…
|
drh
|
416 |
** This is a no-op if g.userUid is 0. |
|
796dcfe…
|
drh
|
417 |
*/ |
|
796dcfe…
|
drh
|
418 |
void login_clear_login_data(){ |
|
796dcfe…
|
drh
|
419 |
if(!g.userUid){ |
|
796dcfe…
|
drh
|
420 |
return; |
|
796dcfe…
|
drh
|
421 |
}else{ |
|
4e18dba…
|
jan.nijtmans
|
422 |
const char *cookie = login_cookie_name(); |
|
796dcfe…
|
drh
|
423 |
/* To logout, change the cookie value to an empty string */ |
|
796dcfe…
|
drh
|
424 |
cgi_set_cookie(cookie, "", |
|
796dcfe…
|
drh
|
425 |
login_cookie_path(), -86400); |
|
f741baa…
|
drh
|
426 |
db_unprotect(PROTECT_USER); |
|
796dcfe…
|
drh
|
427 |
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " |
|
796dcfe…
|
drh
|
428 |
" cexpire=0 WHERE uid=%d" |
|
796dcfe…
|
drh
|
429 |
" AND login NOT IN ('anonymous','nobody'," |
|
796dcfe…
|
drh
|
430 |
" 'developer','reader')", g.userUid); |
|
f741baa…
|
drh
|
431 |
db_protect_pop(); |
|
653dd40…
|
drh
|
432 |
cgi_replace_parameter(cookie, NULL); |
|
653dd40…
|
drh
|
433 |
cgi_replace_parameter("anon", NULL); |
|
653dd40…
|
drh
|
434 |
} |
|
e059e5a…
|
drh
|
435 |
} |
|
e059e5a…
|
drh
|
436 |
|
|
e059e5a…
|
drh
|
437 |
/* |
|
e059e5a…
|
drh
|
438 |
** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent |
|
8328448…
|
drh
|
439 |
** is a manually operated browser or a bot. When in doubt, assume a bot. |
|
8328448…
|
drh
|
440 |
** Return true if we believe the agent is a real person. |
|
06e0cb7…
|
drh
|
441 |
*/ |
|
06e0cb7…
|
drh
|
442 |
static int isHuman(const char *zAgent){ |
|
1167d7b…
|
drh
|
443 |
if( zAgent==0 ) return 0; /* If no UserAgent, then probably a bot */ |
|
61a8b0e…
|
drh
|
444 |
if( strstr(zAgent, "bot")!=0 ) return 0; |
|
61a8b0e…
|
drh
|
445 |
if( strstr(zAgent, "spider")!=0 ) return 0; |
|
61a8b0e…
|
drh
|
446 |
if( strstr(zAgent, "crawl")!=0 ) return 0; |
|
61a8b0e…
|
drh
|
447 |
/* If a URI appears in the User-Agent, it is probably a bot */ |
|
61a8b0e…
|
drh
|
448 |
if( strstr(zAgent, "http")!=0 ) return 0; |
|
e065d5b…
|
drh
|
449 |
if( strncmp(zAgent, "Mozilla/", 8)==0 ){ |
|
8328448…
|
drh
|
450 |
if( atoi(&zAgent[8])<4 ) return 0; /* Many bots advertise as Mozilla/3 */ |
|
61a8b0e…
|
drh
|
451 |
|
|
61a8b0e…
|
drh
|
452 |
/* Google AI Robot, maybe? */ |
|
61a8b0e…
|
drh
|
453 |
if( strstr(zAgent, "GoogleOther)")!=0 ) return 0; |
|
1e2d76e…
|
drh
|
454 |
|
|
1e2d76e…
|
drh
|
455 |
/* 2016-05-30: A pernicious spider that likes to walk Fossil timelines has |
|
1e2d76e…
|
drh
|
456 |
** been detected on the SQLite website. The spider changes its user-agent |
|
1e2d76e…
|
drh
|
457 |
** string frequently, but it always seems to include the following text: |
|
1e2d76e…
|
drh
|
458 |
*/ |
|
61a8b0e…
|
drh
|
459 |
if( strstr(zAgent, "Safari/537.36Mozilla/5.0")!=0 ) return 0; |
|
1e2d76e…
|
drh
|
460 |
|
|
2271ea4…
|
jan.nijtmans
|
461 |
if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1; |
|
2271ea4…
|
jan.nijtmans
|
462 |
if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1; |
|
2271ea4…
|
jan.nijtmans
|
463 |
if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1; |
|
cd11f92…
|
drh
|
464 |
if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ){ |
|
cd11f92…
|
drh
|
465 |
return 1; /* IE11+ */ |
|
cd11f92…
|
drh
|
466 |
} |
|
2271ea4…
|
jan.nijtmans
|
467 |
if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1; |
|
6d0be55…
|
mistachkin
|
468 |
if( sqlite3_strglob("*PaleMoon/[1-9]*", zAgent)==0 ) return 1; |
|
4fdb63d…
|
drh
|
469 |
return 0; |
|
4fdb63d…
|
drh
|
470 |
} |
|
e065d5b…
|
drh
|
471 |
if( strncmp(zAgent, "Opera/", 6)==0 ) return 1; |
|
e065d5b…
|
drh
|
472 |
if( strncmp(zAgent, "Safari/", 7)==0 ) return 1; |
|
e065d5b…
|
drh
|
473 |
if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1; |
|
e065d5b…
|
drh
|
474 |
if( strncmp(zAgent, "NetSurf/", 8)==0 ) return 1; |
|
29bab27…
|
drh
|
475 |
return 0; |
|
29bab27…
|
drh
|
476 |
} |
|
29bab27…
|
drh
|
477 |
|
|
29bab27…
|
drh
|
478 |
/* |
|
29bab27…
|
drh
|
479 |
** Make a guess at whether or not the requestor is a mobile device or |
|
29bab27…
|
drh
|
480 |
** a desktop device (narrow screen vs. wide screen) based the HTTP_USER_AGENT |
|
29bab27…
|
drh
|
481 |
** parameter. Return true for mobile and false for desktop. |
|
29bab27…
|
drh
|
482 |
** |
|
29bab27…
|
drh
|
483 |
** Caution: This is only a guess. |
|
4de677d…
|
drh
|
484 |
** |
|
4de677d…
|
drh
|
485 |
** Algorithm derived from https://developer.mozilla.org/en-US/docs/Web/ |
|
4de677d…
|
drh
|
486 |
** HTTP/Browser_detection_using_the_user_agent#mobile_device_detection on |
|
4de677d…
|
drh
|
487 |
** 2021-03-01 |
|
29bab27…
|
drh
|
488 |
*/ |
|
29bab27…
|
drh
|
489 |
int user_agent_is_likely_mobile(void){ |
|
29bab27…
|
drh
|
490 |
const char *zAgent = P("HTTP_USER_AGENT"); |
|
29bab27…
|
drh
|
491 |
if( zAgent==0 ) return 0; |
|
4de677d…
|
drh
|
492 |
if( strstr(zAgent,"Mobi")!=0 ) return 1; |
|
e059e5a…
|
drh
|
493 |
return 0; |
|
06e0cb7…
|
drh
|
494 |
} |
|
06e0cb7…
|
drh
|
495 |
|
|
06e0cb7…
|
drh
|
496 |
/* |
|
06e0cb7…
|
drh
|
497 |
** COMMAND: test-ishuman |
|
06e0cb7…
|
drh
|
498 |
** |
|
06e0cb7…
|
drh
|
499 |
** Read lines of text from standard input. Interpret each line of text |
|
06e0cb7…
|
drh
|
500 |
** as a User-Agent string from an HTTP header. Label each line as HUMAN |
|
06e0cb7…
|
drh
|
501 |
** or ROBOT. |
|
06e0cb7…
|
drh
|
502 |
*/ |
|
06e0cb7…
|
drh
|
503 |
void test_ishuman(void){ |
|
06e0cb7…
|
drh
|
504 |
char zLine[3000]; |
|
06e0cb7…
|
drh
|
505 |
while( fgets(zLine, sizeof(zLine), stdin) ){ |
|
06e0cb7…
|
drh
|
506 |
fossil_print("%s %s", isHuman(zLine) ? "HUMAN" : "ROBOT", zLine); |
|
06e0cb7…
|
drh
|
507 |
} |
|
d4a341b…
|
dmitry
|
508 |
} |
|
d4a341b…
|
dmitry
|
509 |
|
|
d4a341b…
|
dmitry
|
510 |
/* |
|
d4a341b…
|
dmitry
|
511 |
** SQL function for constant time comparison of two values. |
|
d4a341b…
|
dmitry
|
512 |
** Sets result to 0 if two values are equal. |
|
d4a341b…
|
dmitry
|
513 |
*/ |
|
d4a341b…
|
dmitry
|
514 |
static void constant_time_cmp_function( |
|
d4a341b…
|
dmitry
|
515 |
sqlite3_context *context, |
|
d4a341b…
|
dmitry
|
516 |
int argc, |
|
d4a341b…
|
dmitry
|
517 |
sqlite3_value **argv |
|
d4a341b…
|
dmitry
|
518 |
){ |
|
d4a341b…
|
dmitry
|
519 |
const unsigned char *buf1, *buf2; |
|
d4a341b…
|
dmitry
|
520 |
int len, i; |
|
d4a341b…
|
dmitry
|
521 |
unsigned char rc = 0; |
|
d4a341b…
|
dmitry
|
522 |
|
|
d4a341b…
|
dmitry
|
523 |
assert( argc==2 ); |
|
d4a341b…
|
dmitry
|
524 |
len = sqlite3_value_bytes(argv[0]); |
|
d4a341b…
|
dmitry
|
525 |
if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){ |
|
d4a341b…
|
dmitry
|
526 |
rc = 1; |
|
d4a341b…
|
dmitry
|
527 |
}else{ |
|
d4a341b…
|
dmitry
|
528 |
buf1 = sqlite3_value_text(argv[0]); |
|
d4a341b…
|
dmitry
|
529 |
buf2 = sqlite3_value_text(argv[1]); |
|
d4a341b…
|
dmitry
|
530 |
for( i=0; i<len; i++ ){ |
|
d4a341b…
|
dmitry
|
531 |
rc = rc | (buf1[i] ^ buf2[i]); |
|
d4a341b…
|
dmitry
|
532 |
} |
|
d4a341b…
|
dmitry
|
533 |
} |
|
d4a341b…
|
dmitry
|
534 |
sqlite3_result_int(context, rc); |
|
d4a341b…
|
dmitry
|
535 |
} |
|
d4a341b…
|
dmitry
|
536 |
|
|
d4a341b…
|
dmitry
|
537 |
/* |
|
653dd40…
|
drh
|
538 |
** Return true if the current page was reached by a redirect from the /login |
|
653dd40…
|
drh
|
539 |
** page. |
|
653dd40…
|
drh
|
540 |
*/ |
|
653dd40…
|
drh
|
541 |
int referred_from_login(void){ |
|
653dd40…
|
drh
|
542 |
const char *zReferer = P("HTTP_REFERER"); |
|
653dd40…
|
drh
|
543 |
char *zPattern; |
|
653dd40…
|
drh
|
544 |
int rc; |
|
653dd40…
|
drh
|
545 |
if( zReferer==0 ) return 0; |
|
653dd40…
|
drh
|
546 |
zPattern = mprintf("%s/login*", g.zBaseURL); |
|
653dd40…
|
drh
|
547 |
rc = sqlite3_strglob(zPattern, zReferer)==0; |
|
653dd40…
|
drh
|
548 |
fossil_free(zPattern); |
|
653dd40…
|
drh
|
549 |
return rc; |
|
653dd40…
|
drh
|
550 |
} |
|
653dd40…
|
drh
|
551 |
|
|
653dd40…
|
drh
|
552 |
/* |
|
07bfe3f…
|
drh
|
553 |
** Return true if users are allowed to reset their own passwords. |
|
07bfe3f…
|
drh
|
554 |
*/ |
|
07bfe3f…
|
drh
|
555 |
int login_self_password_reset_available(void){ |
|
07bfe3f…
|
drh
|
556 |
if( !db_get_boolean("self-pw-reset",0) ) return 0; |
|
07bfe3f…
|
drh
|
557 |
if( !alert_tables_exist() ) return 0; |
|
07bfe3f…
|
drh
|
558 |
return 1; |
|
07bfe3f…
|
drh
|
559 |
} |
|
07bfe3f…
|
drh
|
560 |
|
|
07bfe3f…
|
drh
|
561 |
/* |
|
99fcc43…
|
drh
|
562 |
** Return TRUE if self-registration is available. If the zNeeded |
|
99fcc43…
|
drh
|
563 |
** argument is not NULL, then only return true if self-registration is |
|
99fcc43…
|
drh
|
564 |
** available and any of the capabilities named in zNeeded are available |
|
99fcc43…
|
drh
|
565 |
** to self-registered users. |
|
99fcc43…
|
drh
|
566 |
*/ |
|
99fcc43…
|
drh
|
567 |
int login_self_register_available(const char *zNeeded){ |
|
99fcc43…
|
drh
|
568 |
CapabilityString *pCap; |
|
99fcc43…
|
drh
|
569 |
int rc; |
|
99fcc43…
|
drh
|
570 |
if( !db_get_boolean("self-register",0) ) return 0; |
|
99fcc43…
|
drh
|
571 |
if( zNeeded==0 ) return 1; |
|
c00e912…
|
drh
|
572 |
pCap = capability_add(0, db_get("default-perms", "u")); |
|
99fcc43…
|
drh
|
573 |
capability_expand(pCap); |
|
99fcc43…
|
drh
|
574 |
rc = capability_has_any(pCap, zNeeded); |
|
99fcc43…
|
drh
|
575 |
capability_free(pCap); |
|
99fcc43…
|
drh
|
576 |
return rc; |
|
99fcc43…
|
drh
|
577 |
} |
|
99fcc43…
|
drh
|
578 |
|
|
99fcc43…
|
drh
|
579 |
/* |
|
2f4a101…
|
drh
|
580 |
** There used to be a page named "my" that was designed to show information |
|
2f4a101…
|
drh
|
581 |
** about a specific user. The "my" page was linked from the "Logged in as USER" |
|
2f4a101…
|
drh
|
582 |
** line on the title bar. The "my" page was never completed so it is now |
|
2f4a101…
|
drh
|
583 |
** removed. Use this page as a placeholder in older installations. |
|
653dd40…
|
drh
|
584 |
** |
|
653dd40…
|
drh
|
585 |
** WEBPAGE: login |
|
653dd40…
|
drh
|
586 |
** WEBPAGE: logout |
|
653dd40…
|
drh
|
587 |
** WEBPAGE: my |
|
653dd40…
|
drh
|
588 |
** |
|
653dd40…
|
drh
|
589 |
** The login/logout page. Parameters: |
|
653dd40…
|
drh
|
590 |
** |
|
653dd40…
|
drh
|
591 |
** g=URL Jump back to this URL after login completes |
|
653dd40…
|
drh
|
592 |
** anon The g=URL is not accessible by "nobody" but is |
|
653dd40…
|
drh
|
593 |
** accessible by "anonymous" |
|
2f4a101…
|
drh
|
594 |
*/ |
|
2f4a101…
|
drh
|
595 |
void login_page(void){ |
|
2f4a101…
|
drh
|
596 |
const char *zUsername, *zPasswd; |
|
2f4a101…
|
drh
|
597 |
const char *zNew1, *zNew2; |
|
2f4a101…
|
drh
|
598 |
const char *zAnonPw = 0; |
|
79ef961…
|
drh
|
599 |
const char *zGoto = P("g"); |
|
653dd40…
|
drh
|
600 |
int anonFlag; /* Login as "anonymous" would be useful */ |
|
2f4a101…
|
drh
|
601 |
char *zErrMsg = ""; |
|
db0c512…
|
drh
|
602 |
int uid; /* User id logged in user */ |
|
2f4a101…
|
drh
|
603 |
char *zSha1Pw; |
|
2f4a101…
|
drh
|
604 |
const char *zIpAddr; /* IP address of requestor */ |
|
6b7b323…
|
drh
|
605 |
const int noAnon = P("noanon")!=0; |
|
6b7b323…
|
drh
|
606 |
int rememberMe; /* If true, use persistent cookie, else |
|
6b7b323…
|
drh
|
607 |
session cookie. Toggled per |
|
6b7b323…
|
drh
|
608 |
checkbox. */ |
|
4aba9ea…
|
drh
|
609 |
|
|
07bfe3f…
|
drh
|
610 |
if( P("pwreset")!=0 && login_self_password_reset_available() ){ |
|
07bfe3f…
|
drh
|
611 |
/* If the "Reset Password" button in the form was pressed, render |
|
07bfe3f…
|
drh
|
612 |
** the Request Password Reset page in place of this one. */ |
|
07bfe3f…
|
drh
|
613 |
login_reqpwreset_page(); |
|
07bfe3f…
|
drh
|
614 |
return; |
|
07bfe3f…
|
drh
|
615 |
} |
|
e58112a…
|
drh
|
616 |
|
|
e58112a…
|
drh
|
617 |
/* If the "anon" query parameter is 1 or 2, that means rework the web-page |
|
e58112a…
|
drh
|
618 |
** to make it a more user-friendly captcha. Extraneous text and boxes |
|
e58112a…
|
drh
|
619 |
** are omitted. The user has just the captcha image and an entry box |
|
e58112a…
|
drh
|
620 |
** and a "Verify" button. Underneath is the same login page for user |
|
e58112a…
|
drh
|
621 |
** "anonymous", just displayed in an easier to digest format for one-time |
|
e58112a…
|
drh
|
622 |
** visitors. |
|
e58112a…
|
drh
|
623 |
** |
|
e58112a…
|
drh
|
624 |
** anon=1 is advisory and only has effect if there is not some other login |
|
e58112a…
|
drh
|
625 |
** cookie. anon=2 means always show the captcha. |
|
e58112a…
|
drh
|
626 |
*/ |
|
7d2b47a…
|
drh
|
627 |
anonFlag = anon_cookie_lifespan()>0 ? atoi(PD("anon","0")) : 0; |
|
e58112a…
|
drh
|
628 |
if( anonFlag==2 ){ |
|
e58112a…
|
drh
|
629 |
g.zLogin = 0; |
|
e58112a…
|
drh
|
630 |
}else{ |
|
e58112a…
|
drh
|
631 |
login_check_credentials(); |
|
e58112a…
|
drh
|
632 |
if( g.zLogin!=0 ) anonFlag = 0; |
|
e58112a…
|
drh
|
633 |
} |
|
e58112a…
|
drh
|
634 |
|
|
4aba9ea…
|
drh
|
635 |
fossil_redirect_to_https_if_needed(1); |
|
2f4a101…
|
drh
|
636 |
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
|
b3e32c8…
|
jan.nijtmans
|
637 |
constant_time_cmp_function, 0, 0); |
|
2f4a101…
|
drh
|
638 |
zUsername = P("u"); |
|
2f4a101…
|
drh
|
639 |
zPasswd = P("p"); |
|
6c8c93a…
|
drh
|
640 |
|
|
653dd40…
|
drh
|
641 |
/* Handle log-out requests */ |
|
920ace1…
|
drh
|
642 |
if( P("out") && cgi_csrf_safe(2) ){ |
|
796dcfe…
|
drh
|
643 |
login_clear_login_data(); |
|
1958448…
|
drh
|
644 |
login_redirect_to_g(); |
|
99fcc43…
|
drh
|
645 |
return; |
|
99fcc43…
|
drh
|
646 |
} |
|
99fcc43…
|
drh
|
647 |
|
|
99fcc43…
|
drh
|
648 |
/* Redirect for create-new-account requests */ |
|
99fcc43…
|
drh
|
649 |
if( P("self") ){ |
|
99fcc43…
|
drh
|
650 |
cgi_redirectf("%R/register"); |
|
653dd40…
|
drh
|
651 |
return; |
|
653dd40…
|
drh
|
652 |
} |
|
653dd40…
|
drh
|
653 |
|
|
653dd40…
|
drh
|
654 |
/* Deal with password-change requests */ |
|
2f4a101…
|
drh
|
655 |
if( g.perm.Password && zPasswd |
|
2f4a101…
|
drh
|
656 |
&& (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 |
|
920ace1…
|
drh
|
657 |
&& cgi_csrf_safe(2) |
|
8b562b9…
|
mistachkin
|
658 |
){ |
|
8b562b9…
|
mistachkin
|
659 |
/* If there is not a "real" login, we cannot change any password. */ |
|
8b562b9…
|
mistachkin
|
660 |
if( g.zLogin ){ |
|
8b562b9…
|
mistachkin
|
661 |
/* The user requests a password change */ |
|
8b562b9…
|
mistachkin
|
662 |
zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); |
|
8b562b9…
|
mistachkin
|
663 |
if( db_int(1, "SELECT 0 FROM user" |
|
8b562b9…
|
mistachkin
|
664 |
" WHERE uid=%d" |
|
8b562b9…
|
mistachkin
|
665 |
" AND (constant_time_cmp(pw,%Q)=0" |
|
8b562b9…
|
mistachkin
|
666 |
" OR constant_time_cmp(pw,%Q)=0)", |
|
8b562b9…
|
mistachkin
|
667 |
g.userUid, zSha1Pw, zPasswd) ){ |
|
8b562b9…
|
mistachkin
|
668 |
sleep(1); |
|
8b562b9…
|
mistachkin
|
669 |
zErrMsg = |
|
8b562b9…
|
mistachkin
|
670 |
@ <p><span class="loginError"> |
|
8b562b9…
|
mistachkin
|
671 |
@ You entered an incorrect old password while attempting to change |
|
8b562b9…
|
mistachkin
|
672 |
@ your password. Your password is unchanged. |
|
8b562b9…
|
mistachkin
|
673 |
@ </span></p> |
|
8b562b9…
|
mistachkin
|
674 |
; |
|
8b562b9…
|
mistachkin
|
675 |
}else if( fossil_strcmp(zNew1,zNew2)!=0 ){ |
|
8b562b9…
|
mistachkin
|
676 |
zErrMsg = |
|
8b562b9…
|
mistachkin
|
677 |
@ <p><span class="loginError"> |
|
8b562b9…
|
mistachkin
|
678 |
@ The two copies of your new passwords do not match. |
|
8b562b9…
|
mistachkin
|
679 |
@ Your password is unchanged. |
|
8b562b9…
|
mistachkin
|
680 |
@ </span></p> |
|
8b562b9…
|
mistachkin
|
681 |
; |
|
8b562b9…
|
mistachkin
|
682 |
}else{ |
|
8b562b9…
|
mistachkin
|
683 |
char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); |
|
8b562b9…
|
mistachkin
|
684 |
char *zChngPw; |
|
8b562b9…
|
mistachkin
|
685 |
char *zErr; |
|
f741baa…
|
drh
|
686 |
int rc; |
|
f741baa…
|
drh
|
687 |
|
|
07bfe3f…
|
drh
|
688 |
/* vvvvvvv--- tag-20230106-1 ----vvvvvv |
|
07bfe3f…
|
drh
|
689 |
** |
|
07bfe3f…
|
drh
|
690 |
** Replicate changes made below to tag-20230106-2 |
|
07bfe3f…
|
drh
|
691 |
*/ |
|
c9c7e8c…
|
drh
|
692 |
admin_log("password change for user %s", g.zLogin); |
|
f741baa…
|
drh
|
693 |
db_unprotect(PROTECT_USER); |
|
8b562b9…
|
mistachkin
|
694 |
db_multi_exec( |
|
8b562b9…
|
mistachkin
|
695 |
"UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid |
|
8b562b9…
|
mistachkin
|
696 |
); |
|
8b562b9…
|
mistachkin
|
697 |
zChngPw = mprintf( |
|
8b562b9…
|
mistachkin
|
698 |
"UPDATE user" |
|
8b562b9…
|
mistachkin
|
699 |
" SET pw=shared_secret(%Q,%Q," |
|
8b562b9…
|
mistachkin
|
700 |
" (SELECT value FROM config WHERE name='project-code'))" |
|
8b562b9…
|
mistachkin
|
701 |
" WHERE login=%Q", |
|
8b562b9…
|
mistachkin
|
702 |
zNew1, g.zLogin, g.zLogin |
|
8b562b9…
|
mistachkin
|
703 |
); |
|
f741baa…
|
drh
|
704 |
fossil_free(zNewPw); |
|
f741baa…
|
drh
|
705 |
rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr); |
|
f741baa…
|
drh
|
706 |
db_protect_pop(); |
|
07bfe3f…
|
drh
|
707 |
/* |
|
07bfe3f…
|
drh
|
708 |
** ^^^^^^^^--- tag-20230106-1 ----^^^^^^^^^ |
|
07bfe3f…
|
drh
|
709 |
** |
|
07bfe3f…
|
drh
|
710 |
** Replicate changes above to tag-20230106-2 |
|
07bfe3f…
|
drh
|
711 |
*/ |
|
07bfe3f…
|
drh
|
712 |
|
|
f741baa…
|
drh
|
713 |
if( rc ){ |
|
8b562b9…
|
mistachkin
|
714 |
zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr); |
|
8b562b9…
|
mistachkin
|
715 |
fossil_free(zErr); |
|
8b562b9…
|
mistachkin
|
716 |
}else{ |
|
1958448…
|
drh
|
717 |
login_redirect_to_g(); |
|
8b562b9…
|
mistachkin
|
718 |
return; |
|
8b562b9…
|
mistachkin
|
719 |
} |
|
8b562b9…
|
mistachkin
|
720 |
} |
|
8b562b9…
|
mistachkin
|
721 |
}else{ |
|
8b562b9…
|
mistachkin
|
722 |
zErrMsg = |
|
8b562b9…
|
mistachkin
|
723 |
@ <p><span class="loginError"> |
|
8b562b9…
|
mistachkin
|
724 |
@ The password cannot be changed for this type of login. |
|
8b562b9…
|
mistachkin
|
725 |
@ The password is unchanged. |
|
8b562b9…
|
mistachkin
|
726 |
@ </span></p> |
|
8b562b9…
|
mistachkin
|
727 |
; |
|
2f4a101…
|
drh
|
728 |
} |
|
2f4a101…
|
drh
|
729 |
} |
|
2f4a101…
|
drh
|
730 |
zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ |
|
796dcfe…
|
drh
|
731 |
uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs")); |
|
6b7b323…
|
drh
|
732 |
if(zUsername==0){ |
|
6b7b323…
|
drh
|
733 |
/* Initial login page hit. */ |
|
6b7b323…
|
drh
|
734 |
rememberMe = 0; |
|
6b7b323…
|
drh
|
735 |
}else{ |
|
6b7b323…
|
drh
|
736 |
rememberMe = P("remember")!=0; |
|
6b7b323…
|
drh
|
737 |
} |
|
2f4a101…
|
drh
|
738 |
if( uid>0 ){ |
|
1958448…
|
drh
|
739 |
login_set_anon_cookie(NULL, rememberMe?0:1); |
|
2f4a101…
|
drh
|
740 |
record_login_attempt("anonymous", zIpAddr, 1); |
|
1958448…
|
drh
|
741 |
login_redirect_to_g(); |
|
2f4a101…
|
drh
|
742 |
} |
|
2f4a101…
|
drh
|
743 |
if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ |
|
2f4a101…
|
drh
|
744 |
/* Attempting to log in as a user other than anonymous. |
|
2f4a101…
|
drh
|
745 |
*/ |
|
9b4e157…
|
drh
|
746 |
uid = login_search_uid(&zUsername, zPasswd); |
|
2f4a101…
|
drh
|
747 |
if( uid<=0 ){ |
|
2f4a101…
|
drh
|
748 |
sleep(1); |
|
45f3516…
|
jan.nijtmans
|
749 |
zErrMsg = |
|
2f4a101…
|
drh
|
750 |
@ <p><span class="loginError"> |
|
2f4a101…
|
drh
|
751 |
@ You entered an unknown user or an incorrect password. |
|
2f4a101…
|
drh
|
752 |
@ </span></p> |
|
2f4a101…
|
drh
|
753 |
; |
|
2f4a101…
|
drh
|
754 |
record_login_attempt(zUsername, zIpAddr, 0); |
|
39d7eb0…
|
wyoung
|
755 |
cgi_set_status(401, "Unauthorized"); |
|
2f4a101…
|
drh
|
756 |
}else{ |
|
2f4a101…
|
drh
|
757 |
/* Non-anonymous login is successful. Set a cookie of the form: |
|
2f4a101…
|
drh
|
758 |
** |
|
2f4a101…
|
drh
|
759 |
** HASH/PROJECT/LOGIN |
|
2f4a101…
|
drh
|
760 |
** |
|
2f4a101…
|
drh
|
761 |
** where HASH is a random hex number, PROJECT is either project |
|
2f4a101…
|
drh
|
762 |
** code prefix, and LOGIN is the user name. |
|
2f4a101…
|
drh
|
763 |
*/ |
|
6b7b323…
|
drh
|
764 |
login_set_user_cookie(zUsername, uid, NULL, rememberMe?0:1); |
|
1958448…
|
drh
|
765 |
login_redirect_to_g(); |
|
796dcfe…
|
drh
|
766 |
} |
|
796dcfe…
|
drh
|
767 |
} |
|
112c713…
|
drh
|
768 |
style_set_current_feature("login"); |
|
2f4a101…
|
drh
|
769 |
style_header("Login/Logout"); |
|
e58112a…
|
drh
|
770 |
if( anonFlag==2 ) g.zLogin = 0; |
|
ff78d6d…
|
drh
|
771 |
style_adunit_config(ADUNIT_OFF); |
|
2f4a101…
|
drh
|
772 |
@ %s(zErrMsg) |
|
99fcc43…
|
drh
|
773 |
if( zGoto && !noAnon ){ |
|
653dd40…
|
drh
|
774 |
char *zAbbrev = fossil_strdup(zGoto); |
|
653dd40…
|
drh
|
775 |
int i; |
|
653dd40…
|
drh
|
776 |
for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){} |
|
653dd40…
|
drh
|
777 |
zAbbrev[i] = 0; |
|
653dd40…
|
drh
|
778 |
if( g.zLogin ){ |
|
73ec21e…
|
drh
|
779 |
@ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b> |
|
653dd40…
|
drh
|
780 |
@ to access <b>%h(zAbbrev)</b>. |
|
653dd40…
|
drh
|
781 |
}else if( anonFlag ){ |
|
6c8c93a…
|
drh
|
782 |
@ <p><b>Verify that you are human by typing in the 8-character text |
|
6c8c93a…
|
drh
|
783 |
@ password shown below.</b></p> |
|
653dd40…
|
drh
|
784 |
}else{ |
|
653dd40…
|
drh
|
785 |
@ <p>Login as a named user to access page <b>%h(zAbbrev)</b>. |
|
653dd40…
|
drh
|
786 |
} |
|
6b7b323…
|
drh
|
787 |
fossil_free(zAbbrev); |
|
c6785fa…
|
drh
|
788 |
} |
|
2f4a101…
|
drh
|
789 |
if( g.sslNotAvailable==0 |
|
e065d5b…
|
drh
|
790 |
&& strncmp(g.zBaseURL,"https:",6)!=0 |
|
2f4a101…
|
drh
|
791 |
&& db_get_boolean("https-login",0) |
|
2f4a101…
|
drh
|
792 |
){ |
|
c6785fa…
|
drh
|
793 |
form_begin(0, "https:%s/login", g.zBaseURL+5); |
|
c6785fa…
|
drh
|
794 |
}else{ |
|
c6785fa…
|
drh
|
795 |
form_begin(0, "%R/login"); |
|
c6785fa…
|
drh
|
796 |
} |
|
c6785fa…
|
drh
|
797 |
if( zGoto ){ |
|
f5482a0…
|
wyoung
|
798 |
@ <input type="hidden" name="g" value="%h(zGoto)"> |
|
c6785fa…
|
drh
|
799 |
} |
|
c6785fa…
|
drh
|
800 |
if( anonFlag ){ |
|
f5482a0…
|
wyoung
|
801 |
@ <input type="hidden" name="anon" value="1"> |
|
6c8c93a…
|
drh
|
802 |
@ <input type="hidden" name="u" value="anonymous"> |
|
c6785fa…
|
drh
|
803 |
} |
|
c6785fa…
|
drh
|
804 |
if( g.zLogin ){ |
|
c6785fa…
|
drh
|
805 |
@ <p>Currently logged in as <b>%h(g.zLogin)</b>. |
|
bc05e6c…
|
florian
|
806 |
@ <input type="submit" name="out" value="Logout" autofocus></p> |
|
643123d…
|
andybradford
|
807 |
@ </form> |
|
00bed59…
|
drh
|
808 |
}else{ |
|
a584491…
|
drh
|
809 |
unsigned int uSeed = captcha_seed(); |
|
7d2b47a…
|
drh
|
810 |
if( g.zLogin==0 && (anonFlag || zGoto==0) && anon_cookie_lifespan()>0 ){ |
|
a584491…
|
drh
|
811 |
zAnonPw = db_text(0, "SELECT pw FROM user" |
|
a584491…
|
drh
|
812 |
" WHERE login='anonymous'" |
|
a584491…
|
drh
|
813 |
" AND cap!=''"); |
|
a584491…
|
drh
|
814 |
}else{ |
|
a584491…
|
drh
|
815 |
zAnonPw = 0; |
|
a584491…
|
drh
|
816 |
} |
|
00bed59…
|
drh
|
817 |
@ <table class="login_out"> |
|
6c8c93a…
|
drh
|
818 |
if( P("HTTPS")==0 && !anonFlag ){ |
|
27769be…
|
drh
|
819 |
@ <tr><td class="form_label">Warning:</td> |
|
27769be…
|
drh
|
820 |
@ <td><span class='securityWarning'> |
|
275da70…
|
danield
|
821 |
@ Login information, including the password, |
|
6b7b323…
|
drh
|
822 |
@ will be sent in the clear over an unencrypted connection. |
|
6b7b323…
|
drh
|
823 |
if( !g.sslNotAvailable ){ |
|
00bed59…
|
drh
|
824 |
@ Consider logging in at |
|
00bed59…
|
drh
|
825 |
@ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead. |
|
00bed59…
|
drh
|
826 |
} |
|
27769be…
|
drh
|
827 |
@ </span></td></tr> |
|
27769be…
|
drh
|
828 |
} |
|
6c8c93a…
|
drh
|
829 |
if( !anonFlag ){ |
|
6c8c93a…
|
drh
|
830 |
@ <tr> |
|
6c8c93a…
|
drh
|
831 |
@ <td class="form_label" id="userlabel1">User ID:</td> |
|
6c8c93a…
|
drh
|
832 |
@ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \ |
|
6c8c93a…
|
drh
|
833 |
@ size="30" value="" autofocus></td> |
|
6c8c93a…
|
drh
|
834 |
@ </tr> |
|
6c8c93a…
|
drh
|
835 |
} |
|
27769be…
|
drh
|
836 |
@ <tr> |
|
6b7b323…
|
drh
|
837 |
@ <td class="form_label" id="pswdlabel">Password:</td> |
|
6b7b323…
|
drh
|
838 |
@ <td><input aria-labelledby="pswdlabel" type="password" id="p" \ |
|
b873148…
|
florian
|
839 |
@ name="p" value="" size="30"%s(anonFlag ? " autofocus" : "")> |
|
6c8c93a…
|
drh
|
840 |
if( anonFlag ){ |
|
6c8c93a…
|
drh
|
841 |
@ </td></tr> |
|
6c8c93a…
|
drh
|
842 |
@ <tr> |
|
6c8c93a…
|
drh
|
843 |
@ <td></td><td>\ |
|
6c8c93a…
|
drh
|
844 |
captcha_speakit_button(uSeed, "Read the password out loud"); |
|
6c8c93a…
|
drh
|
845 |
}else if( zAnonPw && !noAnon ){ |
|
6b7b323…
|
drh
|
846 |
captcha_speakit_button(uSeed, "Speak password for \"anonymous\""); |
|
6b7b323…
|
drh
|
847 |
} |
|
6b7b323…
|
drh
|
848 |
@ </td> |
|
27769be…
|
drh
|
849 |
@ </tr> |
|
6c8c93a…
|
drh
|
850 |
if( !anonFlag ){ |
|
6c8c93a…
|
drh
|
851 |
@ <tr> |
|
6c8c93a…
|
drh
|
852 |
@ <td></td> |
|
6c8c93a…
|
drh
|
853 |
@ <td><input type="checkbox" name="remember" value="1" \ |
|
6c8c93a…
|
drh
|
854 |
@ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")> |
|
6c8c93a…
|
drh
|
855 |
@ <label for="remember-me">Remember me?</label></td> |
|
6c8c93a…
|
drh
|
856 |
@ </tr> |
|
6c8c93a…
|
drh
|
857 |
@ <tr> |
|
6c8c93a…
|
drh
|
858 |
@ <td></td> |
|
6c8c93a…
|
drh
|
859 |
@ <td><input type="submit" name="in" value="Login"> |
|
6c8c93a…
|
drh
|
860 |
@ </tr> |
|
6c8c93a…
|
drh
|
861 |
}else{ |
|
6c8c93a…
|
drh
|
862 |
@ <tr> |
|
6c8c93a…
|
drh
|
863 |
@ <td></td> |
|
6c8c93a…
|
drh
|
864 |
@ <td><input type="submit" name="in" value="Verify that I am human"> |
|
6c8c93a…
|
drh
|
865 |
@ </tr> |
|
6c8c93a…
|
drh
|
866 |
} |
|
6c8c93a…
|
drh
|
867 |
if( !anonFlag && !noAnon && login_self_register_available(0) ){ |
|
99fcc43…
|
drh
|
868 |
@ <tr> |
|
99fcc43…
|
drh
|
869 |
@ <td></td> |
|
99fcc43…
|
drh
|
870 |
@ <td><input type="submit" name="self" value="Create A New Account"> |
|
27769be…
|
drh
|
871 |
@ </tr> |
|
27769be…
|
drh
|
872 |
} |
|
6c8c93a…
|
drh
|
873 |
if( !anonFlag && login_self_password_reset_available() ){ |
|
07bfe3f…
|
drh
|
874 |
@ <tr> |
|
07bfe3f…
|
drh
|
875 |
@ <td></td> |
|
07bfe3f…
|
drh
|
876 |
@ <td><input type="submit" name="pwreset" value="Reset My Password"> |
|
07bfe3f…
|
drh
|
877 |
@ </tr> |
|
07bfe3f…
|
drh
|
878 |
} |
|
99fcc43…
|
drh
|
879 |
@ </table> |
|
99fcc43…
|
drh
|
880 |
if( zAnonPw && !noAnon ){ |
|
8659d84…
|
drh
|
881 |
const char *zDecoded = captcha_decode(uSeed, 0); |
|
99fcc43…
|
drh
|
882 |
int bAutoCaptcha = db_get_boolean("auto-captcha", 0); |
|
99fcc43…
|
drh
|
883 |
char *zCaptcha = captcha_render(zDecoded); |
|
275da70…
|
danield
|
884 |
|
|
f5482a0…
|
wyoung
|
885 |
@ <p><input type="hidden" name="cs" value="%u(uSeed)"> |
|
6c8c93a…
|
drh
|
886 |
if( !anonFlag ){ |
|
6c8c93a…
|
drh
|
887 |
@ Visitors may enter <b>anonymous</b> as the user-ID with |
|
6c8c93a…
|
drh
|
888 |
@ the 8-character hexadecimal password shown below:</p> |
|
6c8c93a…
|
drh
|
889 |
} |
|
75c89de…
|
drh
|
890 |
@ <div class="captcha"><table class="captcha"><tr><td>\ |
|
75c89de…
|
drh
|
891 |
@ <pre class="captcha"> |
|
99fcc43…
|
drh
|
892 |
@ %h(zCaptcha) |
|
99fcc43…
|
drh
|
893 |
@ </pre></td></tr></table> |
|
6c8c93a…
|
drh
|
894 |
if( bAutoCaptcha && !anonFlag ) { |
|
99fcc43…
|
drh
|
895 |
@ <input type="button" value="Fill out captcha" id='autofillButton' \ |
|
f5482a0…
|
wyoung
|
896 |
@ data-af='%s(zDecoded)'> |
|
036a9d5…
|
drh
|
897 |
builtin_request_js("login.js"); |
|
00bed59…
|
drh
|
898 |
} |
|
00bed59…
|
drh
|
899 |
@ </div> |
|
00bed59…
|
drh
|
900 |
free(zCaptcha); |
|
00bed59…
|
drh
|
901 |
} |
|
00bed59…
|
drh
|
902 |
@ </form> |
|
00bed59…
|
drh
|
903 |
} |
|
6c8c93a…
|
drh
|
904 |
if( login_is_individual() && !anonFlag ){ |
|
6898b3e…
|
drh
|
905 |
if( g.perm.EmailAlert && alert_enabled() ){ |
|
b77f1aa…
|
drh
|
906 |
@ <hr> |
|
b77f1aa…
|
drh
|
907 |
@ <p>Configure <a href="%R/alerts">Email Alerts</a> |
|
b77f1aa…
|
drh
|
908 |
@ for user <b>%h(g.zLogin)</b></p> |
|
b77f1aa…
|
drh
|
909 |
} |
|
2d59385…
|
stephan
|
910 |
if( db_table_exists("repository","forumpost") ){ |
|
2d59385…
|
stephan
|
911 |
@ <hr><p> |
|
2d59385…
|
stephan
|
912 |
@ <a href="%R/timeline?ss=v&y=f&vfx&u=%t(g.zLogin)">Forum |
|
2d59385…
|
stephan
|
913 |
@ post timeline</a> for user <b>%h(g.zLogin)</b></p> |
|
2d59385…
|
stephan
|
914 |
} |
|
8581e37…
|
drh
|
915 |
} |
|
6c8c93a…
|
drh
|
916 |
if( !anonFlag ){ |
|
6c8c93a…
|
drh
|
917 |
@ <hr><p> |
|
6c8c93a…
|
drh
|
918 |
@ Select your preferred <a href="%R/skins">site skin</a>. |
|
6c8c93a…
|
drh
|
919 |
@ </p> |
|
6c8c93a…
|
drh
|
920 |
@ <hr><p> |
|
2a3d303…
|
drh
|
921 |
@ Manage your <a href="%R/cookies">cookies</a> or your |
|
2a3d303…
|
drh
|
922 |
@ <a href="%R/tokens">access tokens</a>.</p> |
|
6c8c93a…
|
drh
|
923 |
} |
|
8581e37…
|
drh
|
924 |
if( login_is_individual() ){ |
|
6898b3e…
|
drh
|
925 |
if( g.perm.Password ){ |
|
49f68be…
|
drh
|
926 |
char *zRPW = fossil_random_password(12); |
|
6898b3e…
|
drh
|
927 |
@ <hr> |
|
6898b3e…
|
drh
|
928 |
@ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> |
|
6898b3e…
|
drh
|
929 |
form_begin(0, "%R/login"); |
|
6898b3e…
|
drh
|
930 |
@ <table> |
|
7dd07b2…
|
drh
|
931 |
@ <tr><td class="form_label" id="oldpw">Old Password:</td> |
|
7dd07b2…
|
drh
|
932 |
@ <td><input aria-labelledby="oldpw" type="password" name="p" \ |
|
7dd07b2…
|
drh
|
933 |
@ size="30"/></td></tr> |
|
7dd07b2…
|
drh
|
934 |
@ <tr><td class="form_label" id="newpw">New Password:</td> |
|
7dd07b2…
|
drh
|
935 |
@ <td><input aria-labelledby="newpw" type="password" name="n1" \ |
|
f5482a0…
|
wyoung
|
936 |
@ size="30"> Suggestion: %z(zRPW)</td></tr> |
|
7dd07b2…
|
drh
|
937 |
@ <tr><td class="form_label" id="reppw">Repeat New Password:</td> |
|
7dd07b2…
|
drh
|
938 |
@ <td><input aria-labledby="reppw" type="password" name="n2" \ |
|
f5482a0…
|
wyoung
|
939 |
@ size="30"></td></tr> |
|
6898b3e…
|
drh
|
940 |
@ <tr><td></td> |
|
f5482a0…
|
wyoung
|
941 |
@ <td><input type="submit" value="Change Password"></td></tr> |
|
6898b3e…
|
drh
|
942 |
@ </table> |
|
6898b3e…
|
drh
|
943 |
@ </form> |
|
6898b3e…
|
drh
|
944 |
} |
|
6898b3e…
|
drh
|
945 |
} |
|
07bfe3f…
|
drh
|
946 |
style_finish_page(); |
|
07bfe3f…
|
drh
|
947 |
} |
|
07bfe3f…
|
drh
|
948 |
|
|
07bfe3f…
|
drh
|
949 |
/* |
|
07bfe3f…
|
drh
|
950 |
** Construct an appropriate URL suffix for the /resetpw page. The |
|
07bfe3f…
|
drh
|
951 |
** suffix will be of the form: |
|
07bfe3f…
|
drh
|
952 |
** |
|
07bfe3f…
|
drh
|
953 |
** UID-TIMESTAMP-HASH |
|
07bfe3f…
|
drh
|
954 |
** |
|
07bfe3f…
|
drh
|
955 |
** Where UID and TIMESTAMP are the parameters to this function, and HASH |
|
07bfe3f…
|
drh
|
956 |
** is constructed from information that is unique to the user in question |
|
07bfe3f…
|
drh
|
957 |
** and which is not publicly available. In particular, the HASH includes |
|
07bfe3f…
|
drh
|
958 |
** the existing user password. Thus, in order to construct a URL that can |
|
07bfe3f…
|
drh
|
959 |
** change a password, an attacker must know the current password, in which |
|
07bfe3f…
|
drh
|
960 |
** case the attacker does not need to construct the URL in order to take |
|
07bfe3f…
|
drh
|
961 |
** over the account. |
|
07bfe3f…
|
drh
|
962 |
** |
|
07bfe3f…
|
drh
|
963 |
** Return a pointer to the resulting string in memory obtained |
|
07bfe3f…
|
drh
|
964 |
** from fossil_malloc(). |
|
07bfe3f…
|
drh
|
965 |
*/ |
|
07bfe3f…
|
drh
|
966 |
char *login_resetpw_suffix(int uid, i64 timestamp){ |
|
07bfe3f…
|
drh
|
967 |
char *zHash; |
|
07bfe3f…
|
drh
|
968 |
char *zInnerSql; |
|
07bfe3f…
|
drh
|
969 |
char *zResult; |
|
07bfe3f…
|
drh
|
970 |
extern int sqlite3_shathree_init(sqlite3*,char**,const sqlite3_api_routines*); |
|
07bfe3f…
|
drh
|
971 |
if( timestamp<=0 ){ timestamp = time(0); } |
|
07bfe3f…
|
drh
|
972 |
sqlite3_shathree_init(g.db, 0, 0); |
|
07bfe3f…
|
drh
|
973 |
if( db_table_exists("repository","subscriber") ){ |
|
07bfe3f…
|
drh
|
974 |
zInnerSql = mprintf( |
|
07bfe3f…
|
drh
|
975 |
"SELECT %lld, login, pw, cookie, user.mtime, user.info, subscriberCode" |
|
07bfe3f…
|
drh
|
976 |
" FROM user LEFT JOIN subscriber ON suname=login" |
|
07bfe3f…
|
drh
|
977 |
" WHERE uid=%d", timestamp, uid); |
|
07bfe3f…
|
drh
|
978 |
}else{ |
|
07bfe3f…
|
drh
|
979 |
zInnerSql = mprintf( |
|
07bfe3f…
|
drh
|
980 |
"SELECT %lld, login, pw, cookie, user.mtime, user.info" |
|
07bfe3f…
|
drh
|
981 |
" FROM user WHERE uid=%d", timestamp, uid); |
|
07bfe3f…
|
drh
|
982 |
} |
|
07bfe3f…
|
drh
|
983 |
zHash = db_text(0, "SELECT lower(hex(sha3_query(%Q)))", zInnerSql); |
|
07bfe3f…
|
drh
|
984 |
fossil_free(zInnerSql); |
|
07bfe3f…
|
drh
|
985 |
zResult = mprintf("%x-%llx-%s", uid, timestamp, zHash); |
|
07bfe3f…
|
drh
|
986 |
if( strlen(zHash)<64 || strlen(zResult)<70 ){ |
|
07bfe3f…
|
drh
|
987 |
/* This should never happen, but if it does, we don't want it to lead |
|
07bfe3f…
|
drh
|
988 |
** to a security breach. */ |
|
07bfe3f…
|
drh
|
989 |
fossil_panic("insecure password reset hash generated\n"); |
|
07bfe3f…
|
drh
|
990 |
} |
|
07bfe3f…
|
drh
|
991 |
fossil_free(zHash); |
|
07bfe3f…
|
drh
|
992 |
return zResult; |
|
07bfe3f…
|
drh
|
993 |
} |
|
07bfe3f…
|
drh
|
994 |
|
|
07bfe3f…
|
drh
|
995 |
/* |
|
07bfe3f…
|
drh
|
996 |
** Check to see if the "name" query parameter is a valid resetpw suffix |
|
07bfe3f…
|
drh
|
997 |
** for a user whose password we are allowed to reset. If it is, then return |
|
07bfe3f…
|
drh
|
998 |
** the positive integer UID for that user. If the query parameter is not |
|
07bfe3f…
|
drh
|
999 |
** valid, return 0. |
|
07bfe3f…
|
drh
|
1000 |
*/ |
|
07bfe3f…
|
drh
|
1001 |
static int login_resetpw_suffix_is_valid(const char *zName){ |
|
07bfe3f…
|
drh
|
1002 |
int i, j; |
|
07bfe3f…
|
drh
|
1003 |
int uid; |
|
07bfe3f…
|
drh
|
1004 |
i64 timestamp; |
|
07bfe3f…
|
drh
|
1005 |
i64 now; |
|
07bfe3f…
|
drh
|
1006 |
char *zHash; |
|
07bfe3f…
|
drh
|
1007 |
if( zName==0 || strlen(zName)<70 ) goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1008 |
for(i=0; fossil_isxdigit(zName[i]); i++){} |
|
07bfe3f…
|
drh
|
1009 |
if( i<1 || zName[i]!='-' ) goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1010 |
for(j=i+1; fossil_isxdigit(zName[j]); j++){} |
|
07bfe3f…
|
drh
|
1011 |
if( j<=i+1 || zName[j]!='-' ) goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1012 |
uid = strtol(zName, 0, 16); |
|
07bfe3f…
|
drh
|
1013 |
if( uid<=0 ) goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1014 |
if( !db_exists("SELECT 1 FROM user WHERE uid=%d", uid) ){ |
|
07bfe3f…
|
drh
|
1015 |
goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1016 |
} |
|
07bfe3f…
|
drh
|
1017 |
timestamp = strtoll(&zName[i+1], 0, 16); |
|
07bfe3f…
|
drh
|
1018 |
now = time(0); |
|
07bfe3f…
|
drh
|
1019 |
if( timestamp+3600 <= now ) goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1020 |
zHash = login_resetpw_suffix(uid,timestamp); |
|
07bfe3f…
|
drh
|
1021 |
if( fossil_strcmp(zHash, zName)!=0 ){ |
|
07bfe3f…
|
drh
|
1022 |
fossil_free(zHash); |
|
07bfe3f…
|
drh
|
1023 |
goto not_valid_suffix; |
|
07bfe3f…
|
drh
|
1024 |
} |
|
07bfe3f…
|
drh
|
1025 |
fossil_free(zHash); |
|
07bfe3f…
|
drh
|
1026 |
return uid; |
|
07bfe3f…
|
drh
|
1027 |
|
|
07bfe3f…
|
drh
|
1028 |
not_valid_suffix: |
|
07bfe3f…
|
drh
|
1029 |
return 0; |
|
07bfe3f…
|
drh
|
1030 |
} |
|
07bfe3f…
|
drh
|
1031 |
|
|
07bfe3f…
|
drh
|
1032 |
/* |
|
07bfe3f…
|
drh
|
1033 |
** COMMAND: test-resetpw-url |
|
07bfe3f…
|
drh
|
1034 |
** Usage: fossil test-resetpw-url UID |
|
07bfe3f…
|
drh
|
1035 |
** |
|
07bfe3f…
|
drh
|
1036 |
** Generate and verify a /resetpw URL for user UID. |
|
07bfe3f…
|
drh
|
1037 |
** |
|
07bfe3f…
|
drh
|
1038 |
** This command is intended for unit testing the login_resetpw_suffix() |
|
07bfe3f…
|
drh
|
1039 |
** and login_resetpw_suffix_is_valid() functions. |
|
07bfe3f…
|
drh
|
1040 |
*/ |
|
07bfe3f…
|
drh
|
1041 |
void test_resetpw_url(void){ |
|
07bfe3f…
|
drh
|
1042 |
char *zSuffix; |
|
07bfe3f…
|
drh
|
1043 |
int uid; |
|
07bfe3f…
|
drh
|
1044 |
int xuid; |
|
07bfe3f…
|
drh
|
1045 |
char *zLogin; |
|
07bfe3f…
|
drh
|
1046 |
int i; |
|
07bfe3f…
|
drh
|
1047 |
db_find_and_open_repository(0, 0); |
|
07bfe3f…
|
drh
|
1048 |
verify_all_options(); |
|
07bfe3f…
|
drh
|
1049 |
if( g.argc<3 ){ |
|
07bfe3f…
|
drh
|
1050 |
usage("UID ..."); |
|
07bfe3f…
|
drh
|
1051 |
} |
|
07bfe3f…
|
drh
|
1052 |
for(i=2; i<g.argc; i++){ |
|
07bfe3f…
|
drh
|
1053 |
uid = atoi(g.argv[i]); |
|
07bfe3f…
|
drh
|
1054 |
zSuffix = login_resetpw_suffix(uid, 0); |
|
07bfe3f…
|
drh
|
1055 |
xuid = login_resetpw_suffix_is_valid(zSuffix); |
|
07bfe3f…
|
drh
|
1056 |
if( xuid>0 ){ |
|
07bfe3f…
|
drh
|
1057 |
zLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", xuid); |
|
07bfe3f…
|
drh
|
1058 |
}else{ |
|
07bfe3f…
|
drh
|
1059 |
zLogin = 0; |
|
07bfe3f…
|
drh
|
1060 |
} |
|
07bfe3f…
|
drh
|
1061 |
fossil_print("/resetpw/%s %d (%s)\n", |
|
07bfe3f…
|
drh
|
1062 |
zSuffix, xuid, zLogin ? zLogin : "???"); |
|
07bfe3f…
|
drh
|
1063 |
fossil_free(zSuffix); |
|
07bfe3f…
|
drh
|
1064 |
fossil_free(zLogin); |
|
07bfe3f…
|
drh
|
1065 |
} |
|
07bfe3f…
|
drh
|
1066 |
} |
|
07bfe3f…
|
drh
|
1067 |
|
|
07bfe3f…
|
drh
|
1068 |
/* |
|
07bfe3f…
|
drh
|
1069 |
** WEBPAGE: resetpw |
|
07bfe3f…
|
drh
|
1070 |
** |
|
07bfe3f…
|
drh
|
1071 |
** The URL format must be like this: |
|
07bfe3f…
|
drh
|
1072 |
** |
|
07bfe3f…
|
drh
|
1073 |
** /resetpw/UID-TIMESTAMP-HASH |
|
07bfe3f…
|
drh
|
1074 |
** |
|
07bfe3f…
|
drh
|
1075 |
** Where UID is the uid of the user whose password is to be reset, |
|
07bfe3f…
|
drh
|
1076 |
** TIMESTAMP is the unix timestamp when the request was made, and |
|
07bfe3f…
|
drh
|
1077 |
** HASH is a hash based on UID, TIMESTAMP, and other information that |
|
07bfe3f…
|
drh
|
1078 |
** is unavailable to an attacher. |
|
07bfe3f…
|
drh
|
1079 |
** |
|
07bfe3f…
|
drh
|
1080 |
** With no other arguments, a form is present which allows the user to |
|
07bfe3f…
|
drh
|
1081 |
** enter a new password. When the SUBMIT button is pressed, a POST request |
|
07bfe3f…
|
drh
|
1082 |
** back to the same URL that will change the password. |
|
07bfe3f…
|
drh
|
1083 |
*/ |
|
07bfe3f…
|
drh
|
1084 |
void login_resetpw(void){ |
|
07bfe3f…
|
drh
|
1085 |
const char *zName; |
|
07bfe3f…
|
drh
|
1086 |
int uid; |
|
07bfe3f…
|
drh
|
1087 |
char *zRPW; |
|
07bfe3f…
|
drh
|
1088 |
const char *zNew1, *zNew2; |
|
07bfe3f…
|
drh
|
1089 |
|
|
07bfe3f…
|
drh
|
1090 |
style_set_current_feature("resetpw"); |
|
07bfe3f…
|
drh
|
1091 |
style_header("Reset Password"); |
|
07bfe3f…
|
drh
|
1092 |
style_adunit_config(ADUNIT_OFF); |
|
07bfe3f…
|
drh
|
1093 |
zName = PD("name",""); |
|
07bfe3f…
|
drh
|
1094 |
uid = login_resetpw_suffix_is_valid(zName); |
|
07bfe3f…
|
drh
|
1095 |
if( uid==0 ){ |
|
07bfe3f…
|
drh
|
1096 |
@ <p><span class="loginError"> |
|
07bfe3f…
|
drh
|
1097 |
@ This password-reset URL is invalid, probably because it has expired. |
|
07bfe3f…
|
drh
|
1098 |
@ Password-reset URLs have a short lifespan. |
|
07bfe3f…
|
drh
|
1099 |
@ </span></p> |
|
07bfe3f…
|
drh
|
1100 |
style_finish_page(); |
|
275da70…
|
danield
|
1101 |
sleep(1); /* Introduce a small delay on an invalid suffix as an |
|
07bfe3f…
|
drh
|
1102 |
** extra defense against search attacks */ |
|
07bfe3f…
|
drh
|
1103 |
return; |
|
07bfe3f…
|
drh
|
1104 |
} |
|
3b1e8a0…
|
drh
|
1105 |
fossil_redirect_to_https_if_needed(1); |
|
07bfe3f…
|
drh
|
1106 |
login_set_uid(uid, 0); |
|
07bfe3f…
|
drh
|
1107 |
if( g.perm.Setup || g.perm.Admin || !g.perm.Password || g.zLogin==0 ){ |
|
07bfe3f…
|
drh
|
1108 |
@ <p><span class="loginError"> |
|
07bfe3f…
|
drh
|
1109 |
@ Cannot change the password for user <b>%h(g.zLogin)</b>. |
|
07bfe3f…
|
drh
|
1110 |
@ </span></p> |
|
07bfe3f…
|
drh
|
1111 |
style_finish_page(); |
|
07bfe3f…
|
drh
|
1112 |
return; |
|
07bfe3f…
|
drh
|
1113 |
} |
|
07bfe3f…
|
drh
|
1114 |
if( (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ |
|
07bfe3f…
|
drh
|
1115 |
if( fossil_strcmp(zNew1,zNew2)!=0 ){ |
|
07bfe3f…
|
drh
|
1116 |
@ <p><span class="loginError"> |
|
07bfe3f…
|
drh
|
1117 |
@ The two copies of your new passwords do not match. |
|
07bfe3f…
|
drh
|
1118 |
@ Try again. |
|
07bfe3f…
|
drh
|
1119 |
@ </span></p> |
|
07bfe3f…
|
drh
|
1120 |
}else{ |
|
07bfe3f…
|
drh
|
1121 |
char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); |
|
07bfe3f…
|
drh
|
1122 |
char *zChngPw; |
|
07bfe3f…
|
drh
|
1123 |
char *zErr; |
|
07bfe3f…
|
drh
|
1124 |
int rc; |
|
07bfe3f…
|
drh
|
1125 |
|
|
07bfe3f…
|
drh
|
1126 |
/* vvvvvvv--- tag-20230106-2 ----vvvvvv |
|
07bfe3f…
|
drh
|
1127 |
** |
|
07bfe3f…
|
drh
|
1128 |
** Replicate changes made below to tag-20230106-1 |
|
07bfe3f…
|
drh
|
1129 |
*/ |
|
c9c7e8c…
|
drh
|
1130 |
admin_log("password change for user %s", g.zLogin); |
|
07bfe3f…
|
drh
|
1131 |
db_unprotect(PROTECT_USER); |
|
07bfe3f…
|
drh
|
1132 |
db_multi_exec( |
|
07bfe3f…
|
drh
|
1133 |
"UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid |
|
07bfe3f…
|
drh
|
1134 |
); |
|
07bfe3f…
|
drh
|
1135 |
zChngPw = mprintf( |
|
07bfe3f…
|
drh
|
1136 |
"UPDATE user" |
|
07bfe3f…
|
drh
|
1137 |
" SET pw=shared_secret(%Q,%Q," |
|
07bfe3f…
|
drh
|
1138 |
" (SELECT value FROM config WHERE name='project-code'))" |
|
07bfe3f…
|
drh
|
1139 |
" WHERE login=%Q", |
|
07bfe3f…
|
drh
|
1140 |
zNew1, g.zLogin, g.zLogin |
|
07bfe3f…
|
drh
|
1141 |
); |
|
07bfe3f…
|
drh
|
1142 |
fossil_free(zNewPw); |
|
07bfe3f…
|
drh
|
1143 |
rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr); |
|
07bfe3f…
|
drh
|
1144 |
db_protect_pop(); |
|
07bfe3f…
|
drh
|
1145 |
/* |
|
07bfe3f…
|
drh
|
1146 |
** ^^^^^^^^--- tag-20230106-2 ----^^^^^^^^^ |
|
07bfe3f…
|
drh
|
1147 |
** |
|
07bfe3f…
|
drh
|
1148 |
** Replicate changes above to tag-20230106-1 |
|
07bfe3f…
|
drh
|
1149 |
*/ |
|
07bfe3f…
|
drh
|
1150 |
|
|
07bfe3f…
|
drh
|
1151 |
if( rc ){ |
|
07bfe3f…
|
drh
|
1152 |
@ <p><span class='loginError'> |
|
07bfe3f…
|
drh
|
1153 |
@ %s(zErr); |
|
07bfe3f…
|
drh
|
1154 |
@ </span></p> |
|
07bfe3f…
|
drh
|
1155 |
fossil_free(zErr); |
|
07bfe3f…
|
drh
|
1156 |
}else{ |
|
07bfe3f…
|
drh
|
1157 |
@ <p>Password changed successfully. Go to the |
|
07bfe3f…
|
drh
|
1158 |
@ <a href="%R/login?u=%t(g.zLogin)">Login</a> page and log in |
|
07bfe3f…
|
drh
|
1159 |
@ using the new password to continue. |
|
07bfe3f…
|
drh
|
1160 |
@ </p> |
|
07bfe3f…
|
drh
|
1161 |
style_finish_page(); |
|
07bfe3f…
|
drh
|
1162 |
return; |
|
07bfe3f…
|
drh
|
1163 |
} |
|
07bfe3f…
|
drh
|
1164 |
} |
|
07bfe3f…
|
drh
|
1165 |
} |
|
07bfe3f…
|
drh
|
1166 |
zRPW = fossil_random_password(12); |
|
07bfe3f…
|
drh
|
1167 |
@ <p>Change Password for user <b>%h(g.zLogin)</b>:</p> |
|
07bfe3f…
|
drh
|
1168 |
form_begin(0, "%R/resetpw"); |
|
07bfe3f…
|
drh
|
1169 |
@ <input type='hidden' name='name' value='%h(zName)'> |
|
07bfe3f…
|
drh
|
1170 |
@ <table> |
|
07bfe3f…
|
drh
|
1171 |
@ <tr><td class="form_label" id="newpw">New Password:</td> |
|
07bfe3f…
|
drh
|
1172 |
@ <td><input aria-labelledby="newpw" type="password" name="n1" \ |
|
f5482a0…
|
wyoung
|
1173 |
@ size="30"> Suggestion: %z(zRPW)</td></tr> |
|
07bfe3f…
|
drh
|
1174 |
@ <tr><td class="form_label" id="reppw">Repeat New Password:</td> |
|
07bfe3f…
|
drh
|
1175 |
@ <td><input aria-labledby="reppw" type="password" name="n2" \ |
|
f5482a0…
|
wyoung
|
1176 |
@ size="30"></td></tr> |
|
07bfe3f…
|
drh
|
1177 |
@ <tr><td></td> |
|
f5482a0…
|
wyoung
|
1178 |
@ <td><input type="submit" value="Change Password"></td></tr> |
|
07bfe3f…
|
drh
|
1179 |
@ </table> |
|
07bfe3f…
|
drh
|
1180 |
@ </form> |
|
112c713…
|
drh
|
1181 |
style_finish_page(); |
|
2f4a101…
|
drh
|
1182 |
} |
|
2f4a101…
|
drh
|
1183 |
|
|
2f4a101…
|
drh
|
1184 |
/* |
|
a257fde…
|
drh
|
1185 |
** Attempt to find login credentials for user zLogin on a peer repository |
|
45f3516…
|
jan.nijtmans
|
1186 |
** with project code zCode. Transfer those credentials to the local |
|
a257fde…
|
drh
|
1187 |
** repository. |
|
a257fde…
|
drh
|
1188 |
** |
|
a257fde…
|
drh
|
1189 |
** Return true if a transfer was made and false if not. |
|
a257fde…
|
drh
|
1190 |
*/ |
|
a257fde…
|
drh
|
1191 |
static int login_transfer_credentials( |
|
a257fde…
|
drh
|
1192 |
const char *zLogin, /* Login we are looking for */ |
|
a257fde…
|
drh
|
1193 |
const char *zCode, /* Project code of peer repository */ |
|
7d18c40…
|
drh
|
1194 |
const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */ |
|
a257fde…
|
drh
|
1195 |
){ |
|
a257fde…
|
drh
|
1196 |
sqlite3 *pOther = 0; /* The other repository */ |
|
a257fde…
|
drh
|
1197 |
sqlite3_stmt *pStmt; /* Query against the other repository */ |
|
a257fde…
|
drh
|
1198 |
char *zSQL; /* SQL of the query against other repo */ |
|
a257fde…
|
drh
|
1199 |
char *zOtherRepo; /* Filename of the other repository */ |
|
a257fde…
|
drh
|
1200 |
int rc; /* Result code from SQLite library functions */ |
|
a257fde…
|
drh
|
1201 |
int nXfer = 0; /* Number of credentials transferred */ |
|
a257fde…
|
drh
|
1202 |
|
|
45f3516…
|
jan.nijtmans
|
1203 |
zOtherRepo = db_text(0, |
|
a257fde…
|
drh
|
1204 |
"SELECT value FROM config WHERE name='peer-repo-%q'", |
|
a257fde…
|
drh
|
1205 |
zCode |
|
a257fde…
|
drh
|
1206 |
); |
|
a257fde…
|
drh
|
1207 |
if( zOtherRepo==0 ) return 0; /* No such peer repository */ |
|
a257fde…
|
drh
|
1208 |
|
|
19de4b5…
|
mistachkin
|
1209 |
rc = sqlite3_open_v2( |
|
19de4b5…
|
mistachkin
|
1210 |
zOtherRepo, &pOther, |
|
19de4b5…
|
mistachkin
|
1211 |
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, |
|
19de4b5…
|
mistachkin
|
1212 |
g.zVfsName |
|
19de4b5…
|
mistachkin
|
1213 |
); |
|
a257fde…
|
drh
|
1214 |
if( rc==SQLITE_OK ){ |
|
2c95802…
|
jan.nijtmans
|
1215 |
sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0); |
|
d4a341b…
|
dmitry
|
1216 |
sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
|
b3e32c8…
|
jan.nijtmans
|
1217 |
constant_time_cmp_function, 0, 0); |
|
74ecc4d…
|
drh
|
1218 |
sqlite3_busy_timeout(pOther, 5000); |
|
a257fde…
|
drh
|
1219 |
zSQL = mprintf( |
|
a257fde…
|
drh
|
1220 |
"SELECT cexpire FROM user" |
|
d4a341b…
|
dmitry
|
1221 |
" WHERE login=%Q" |
|
604e1a6…
|
drh
|
1222 |
" AND octet_length(cap)>0" |
|
604e1a6…
|
drh
|
1223 |
" AND octet_length(pw)>0" |
|
d4a341b…
|
dmitry
|
1224 |
" AND cexpire>julianday('now')" |
|
d4a341b…
|
dmitry
|
1225 |
" AND constant_time_cmp(cookie,%Q)=0", |
|
7d18c40…
|
drh
|
1226 |
zLogin, zHash |
|
a257fde…
|
drh
|
1227 |
); |
|
a257fde…
|
drh
|
1228 |
pStmt = 0; |
|
a257fde…
|
drh
|
1229 |
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); |
|
a257fde…
|
drh
|
1230 |
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ |
|
f741baa…
|
drh
|
1231 |
db_unprotect(PROTECT_USER); |
|
a257fde…
|
drh
|
1232 |
db_multi_exec( |
|
7d18c40…
|
drh
|
1233 |
"UPDATE user SET cookie=%Q, cexpire=%.17g" |
|
a257fde…
|
drh
|
1234 |
" WHERE login=%Q", |
|
275da70…
|
danield
|
1235 |
zHash, |
|
a257fde…
|
drh
|
1236 |
sqlite3_column_double(pStmt, 0), zLogin |
|
a257fde…
|
drh
|
1237 |
); |
|
f741baa…
|
drh
|
1238 |
db_protect_pop(); |
|
a257fde…
|
drh
|
1239 |
nXfer++; |
|
a257fde…
|
drh
|
1240 |
} |
|
a257fde…
|
drh
|
1241 |
sqlite3_finalize(pStmt); |
|
a257fde…
|
drh
|
1242 |
} |
|
a257fde…
|
drh
|
1243 |
sqlite3_close(pOther); |
|
a257fde…
|
drh
|
1244 |
fossil_free(zOtherRepo); |
|
a257fde…
|
drh
|
1245 |
return nXfer; |
|
a257fde…
|
drh
|
1246 |
} |
|
a257fde…
|
drh
|
1247 |
|
|
a257fde…
|
drh
|
1248 |
/* |
|
1b4b8a9…
|
drh
|
1249 |
** Return TRUE if zLogin is one of the special usernames |
|
1b4b8a9…
|
drh
|
1250 |
*/ |
|
1b4b8a9…
|
drh
|
1251 |
int login_is_special(const char *zLogin){ |
|
1b4b8a9…
|
drh
|
1252 |
if( fossil_strcmp(zLogin, "anonymous")==0 ) return 1; |
|
1b4b8a9…
|
drh
|
1253 |
if( fossil_strcmp(zLogin, "nobody")==0 ) return 1; |
|
1b4b8a9…
|
drh
|
1254 |
if( fossil_strcmp(zLogin, "developer")==0 ) return 1; |
|
1b4b8a9…
|
drh
|
1255 |
if( fossil_strcmp(zLogin, "reader")==0 ) return 1; |
|
1b4b8a9…
|
drh
|
1256 |
return 0; |
|
1b4b8a9…
|
drh
|
1257 |
} |
|
1b4b8a9…
|
drh
|
1258 |
|
|
1b4b8a9…
|
drh
|
1259 |
/* |
|
7d18c40…
|
drh
|
1260 |
** Lookup the uid for a non-built-in user with zLogin and zCookie. |
|
7d18c40…
|
drh
|
1261 |
** Return 0 if not found. |
|
796dcfe…
|
drh
|
1262 |
** |
|
796dcfe…
|
drh
|
1263 |
** Note that this only searches for logged-in entries with matching |
|
7d18c40…
|
drh
|
1264 |
** zCookie (db: user.cookie) entries. |
|
a257fde…
|
drh
|
1265 |
*/ |
|
a257fde…
|
drh
|
1266 |
static int login_find_user( |
|
a257fde…
|
drh
|
1267 |
const char *zLogin, /* User name */ |
|
7d18c40…
|
drh
|
1268 |
const char *zCookie /* Login cookie value */ |
|
a257fde…
|
drh
|
1269 |
){ |
|
a257fde…
|
drh
|
1270 |
int uid; |
|
1b4b8a9…
|
drh
|
1271 |
if( login_is_special(zLogin) ) return 0; |
|
45f3516…
|
jan.nijtmans
|
1272 |
uid = db_int(0, |
|
a257fde…
|
drh
|
1273 |
"SELECT uid FROM user" |
|
a257fde…
|
drh
|
1274 |
" WHERE login=%Q" |
|
a257fde…
|
drh
|
1275 |
" AND cexpire>julianday('now')" |
|
604e1a6…
|
drh
|
1276 |
" AND octet_length(cap)>0" |
|
604e1a6…
|
drh
|
1277 |
" AND octet_length(pw)>0" |
|
d4a341b…
|
dmitry
|
1278 |
" AND constant_time_cmp(cookie,%Q)=0", |
|
7d18c40…
|
drh
|
1279 |
zLogin, zCookie |
|
a257fde…
|
drh
|
1280 |
); |
|
2e76b99…
|
drh
|
1281 |
return uid; |
|
2e76b99…
|
drh
|
1282 |
} |
|
2e76b99…
|
drh
|
1283 |
|
|
2e76b99…
|
drh
|
1284 |
/* |
|
2e76b99…
|
drh
|
1285 |
** Attempt to use Basic Authentication to establish the user. Return the |
|
2e76b99…
|
drh
|
1286 |
** (non-zero) uid if successful. Return 0 if it does not work. |
|
2e76b99…
|
drh
|
1287 |
*/ |
|
c921545…
|
drh
|
1288 |
static int login_basic_authentication(const char *zIpAddr){ |
|
2e76b99…
|
drh
|
1289 |
const char *zAuth = PD("HTTP_AUTHORIZATION", 0); |
|
2e76b99…
|
drh
|
1290 |
int i; |
|
2e76b99…
|
drh
|
1291 |
int uid = 0; |
|
2e76b99…
|
drh
|
1292 |
int nDecode = 0; |
|
2e76b99…
|
drh
|
1293 |
char *zDecode = 0; |
|
2e76b99…
|
drh
|
1294 |
const char *zUsername = 0; |
|
2e76b99…
|
drh
|
1295 |
const char *zPasswd = 0; |
|
2e76b99…
|
drh
|
1296 |
|
|
cd11f92…
|
drh
|
1297 |
if( zAuth==0 ) return 0; /* Fail: No Authentication: header */ |
|
2e76b99…
|
drh
|
1298 |
while( fossil_isspace(zAuth[0]) ) zAuth++; /* Skip leading whitespace */ |
|
cd11f92…
|
drh
|
1299 |
if( strncmp(zAuth, "Basic ", 6)!=0 ){ |
|
cd11f92…
|
drh
|
1300 |
return 0; /* Fail: Not Basic Authentication */ |
|
cd11f92…
|
drh
|
1301 |
} |
|
2e76b99…
|
drh
|
1302 |
|
|
2e76b99…
|
drh
|
1303 |
/* Parse out the username and password, separated by a ":" */ |
|
2e76b99…
|
drh
|
1304 |
zAuth += 6; |
|
2e76b99…
|
drh
|
1305 |
while( fossil_isspace(zAuth[0]) ) zAuth++; |
|
2e76b99…
|
drh
|
1306 |
zDecode = decode64(zAuth, &nDecode); |
|
2e76b99…
|
drh
|
1307 |
|
|
2e76b99…
|
drh
|
1308 |
for(i=0; zDecode[i] && zDecode[i]!=':'; i++){} |
|
2e76b99…
|
drh
|
1309 |
if( zDecode[i] ){ |
|
2e76b99…
|
drh
|
1310 |
zDecode[i] = 0; |
|
2e76b99…
|
drh
|
1311 |
zUsername = zDecode; |
|
2e76b99…
|
drh
|
1312 |
zPasswd = &zDecode[i+1]; |
|
2e76b99…
|
drh
|
1313 |
|
|
2e76b99…
|
drh
|
1314 |
/* Attempting to log in as the user provided by HTTP |
|
2e76b99…
|
drh
|
1315 |
** basic auth |
|
2e76b99…
|
drh
|
1316 |
*/ |
|
9b4e157…
|
drh
|
1317 |
uid = login_search_uid(&zUsername, zPasswd); |
|
2e76b99…
|
drh
|
1318 |
if( uid>0 ){ |
|
2e76b99…
|
drh
|
1319 |
record_login_attempt(zUsername, zIpAddr, 1); |
|
2e76b99…
|
drh
|
1320 |
}else{ |
|
2e76b99…
|
drh
|
1321 |
record_login_attempt(zUsername, zIpAddr, 0); |
|
2e76b99…
|
drh
|
1322 |
|
|
2e76b99…
|
drh
|
1323 |
/* The user attempted to login specifically with HTTP basic |
|
2e76b99…
|
drh
|
1324 |
** auth, but provided invalid credentials. Inform them of |
|
2e76b99…
|
drh
|
1325 |
** the failed login attempt via 401. |
|
2e76b99…
|
drh
|
1326 |
*/ |
|
2e76b99…
|
drh
|
1327 |
cgi_set_status(401, "Unauthorized"); |
|
2e76b99…
|
drh
|
1328 |
cgi_reply(); |
|
2e76b99…
|
drh
|
1329 |
fossil_exit(0); |
|
2e76b99…
|
drh
|
1330 |
} |
|
2e76b99…
|
drh
|
1331 |
} |
|
2e76b99…
|
drh
|
1332 |
fossil_free(zDecode); |
|
a257fde…
|
drh
|
1333 |
return uid; |
|
a4e7b86…
|
drh
|
1334 |
} |
|
a4e7b86…
|
drh
|
1335 |
|
|
a4e7b86…
|
drh
|
1336 |
/* |
|
66b111a…
|
drh
|
1337 |
** When this routine is called, we know that the request does not |
|
66b111a…
|
drh
|
1338 |
** have a login on the present repository. This routine checks to |
|
66b111a…
|
drh
|
1339 |
** see if their login cookie might be for another member of the |
|
66b111a…
|
drh
|
1340 |
** login-group. |
|
66b111a…
|
drh
|
1341 |
** |
|
66b111a…
|
drh
|
1342 |
** If this repository is not a part of any login group, then this |
|
66b111a…
|
drh
|
1343 |
** routine always returns false. |
|
66b111a…
|
drh
|
1344 |
** |
|
66b111a…
|
drh
|
1345 |
** If this repository is part of a login group, and the login cookie |
|
66b111a…
|
drh
|
1346 |
** appears to be well-formed, then return true. That might be a |
|
66b111a…
|
drh
|
1347 |
** false-positive, as we don't actually check to see if the login |
|
66b111a…
|
drh
|
1348 |
** cookie is valid for some other repository. But false-positives |
|
66b111a…
|
drh
|
1349 |
** are ok. This routine is used for robot defense only. |
|
66b111a…
|
drh
|
1350 |
*/ |
|
66b111a…
|
drh
|
1351 |
int login_cookie_wellformed(void){ |
|
66b111a…
|
drh
|
1352 |
const char *zCookie; |
|
66b111a…
|
drh
|
1353 |
int n; |
|
66b111a…
|
drh
|
1354 |
zCookie = P(login_cookie_name()); |
|
66b111a…
|
drh
|
1355 |
if( zCookie==0 ){ |
|
66b111a…
|
drh
|
1356 |
return 0; |
|
66b111a…
|
drh
|
1357 |
} |
|
66b111a…
|
drh
|
1358 |
if( !db_exists("SELECT 1 FROM config WHERE name='login-group-code'") ){ |
|
66b111a…
|
drh
|
1359 |
return 0; |
|
66b111a…
|
drh
|
1360 |
} |
|
66b111a…
|
drh
|
1361 |
for(n=0; fossil_isXdigit(zCookie[n]); n++){} |
|
66b111a…
|
drh
|
1362 |
return n>48 && zCookie[n]=='/' && zCookie[n+1]!=0; |
|
66b111a…
|
drh
|
1363 |
} |
|
1a0b304…
|
drh
|
1364 |
|
|
1a0b304…
|
drh
|
1365 |
/* |
|
db0c512…
|
drh
|
1366 |
** This routine examines the login cookie to see if it exists and |
|
796dcfe…
|
drh
|
1367 |
** is valid. If the login cookie checks out, it then sets global |
|
e065d5b…
|
drh
|
1368 |
** variables appropriately. |
|
79ef961…
|
drh
|
1369 |
** |
|
e065d5b…
|
drh
|
1370 |
** g.userUid Database USER.UID value. Might be -1 for "nobody" |
|
e065d5b…
|
drh
|
1371 |
** g.zLogin Database USER.LOGIN value. NULL for user "nobody" |
|
e065d5b…
|
drh
|
1372 |
** g.perm Permissions granted to this user |
|
653dd40…
|
drh
|
1373 |
** g.anon Permissions that would be available to anonymous |
|
16b3309…
|
drh
|
1374 |
** g.isRobot True if the client is known to be a spider or robot |
|
9413395…
|
drh
|
1375 |
** g.perm Populated based on user account's capabilities |
|
10006db…
|
drh
|
1376 |
** g.eAuthMethod The mechanism used for authentication |
|
e065d5b…
|
drh
|
1377 |
** |
|
dbda8d6…
|
drh
|
1378 |
*/ |
|
dbda8d6…
|
drh
|
1379 |
void login_check_credentials(void){ |
|
0be5482…
|
drh
|
1380 |
int uid = 0; /* User id */ |
|
0be5482…
|
drh
|
1381 |
const char *zCookie; /* Text of the login cookie */ |
|
e720f11…
|
drh
|
1382 |
const char *zIpAddr; /* Raw IP address of the requestor */ |
|
0be5482…
|
drh
|
1383 |
const char *zCap = 0; /* Capability string */ |
|
1460b74…
|
andybradford
|
1384 |
const char *zLogin = 0; /* Login user for credentials */ |
|
dbda8d6…
|
drh
|
1385 |
|
|
dbda8d6…
|
drh
|
1386 |
/* Only run this check once. */ |
|
9c952d2…
|
drh
|
1387 |
if( g.userUid!=0 ) return; |
|
dbda8d6…
|
drh
|
1388 |
|
|
d4a341b…
|
dmitry
|
1389 |
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, |
|
b3e32c8…
|
jan.nijtmans
|
1390 |
constant_time_cmp_function, 0, 0); |
|
d4a341b…
|
dmitry
|
1391 |
|
|
dbda8d6…
|
drh
|
1392 |
/* If the HTTP connection is coming over 127.0.0.1 and if |
|
45f3516…
|
jan.nijtmans
|
1393 |
** local login is disabled and if we are using HTTP and not HTTPS, |
|
3da8a12…
|
drh
|
1394 |
** then there is no need to check user credentials. |
|
3da8a12…
|
drh
|
1395 |
** |
|
a257fde…
|
drh
|
1396 |
** This feature allows the "fossil ui" command to give the user |
|
a257fde…
|
drh
|
1397 |
** full access rights without having to log in. |
|
dbda8d6…
|
drh
|
1398 |
*/ |
|
7d18c40…
|
drh
|
1399 |
zIpAddr = PD("REMOTE_ADDR","nil"); |
|
96dcb7e…
|
drh
|
1400 |
if( ( cgi_is_loopback(zIpAddr) |
|
96dcb7e…
|
drh
|
1401 |
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
|
f7a3c6d…
|
drh
|
1402 |
&& g.useLocalauth |
|
00638d9…
|
drh
|
1403 |
&& db_get_boolean("localauth",0)==0 |
|
3da8a12…
|
drh
|
1404 |
&& P("HTTPS")==0 |
|
3da8a12…
|
drh
|
1405 |
){ |
|
920ace1…
|
drh
|
1406 |
char *zSeed; |
|
1460b74…
|
andybradford
|
1407 |
if( g.localOpen ) zLogin = db_lget("default-user",0); |
|
1460b74…
|
andybradford
|
1408 |
if( zLogin!=0 ){ |
|
1460b74…
|
andybradford
|
1409 |
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin); |
|
1460b74…
|
andybradford
|
1410 |
}else{ |
|
1460b74…
|
andybradford
|
1411 |
uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); |
|
1460b74…
|
andybradford
|
1412 |
} |
|
dbda8d6…
|
drh
|
1413 |
g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); |
|
b11359c…
|
drh
|
1414 |
zCap = "sxy"; |
|
dbda8d6…
|
drh
|
1415 |
g.noPswd = 1; |
|
16b3309…
|
drh
|
1416 |
g.isRobot = 0; |
|
10006db…
|
drh
|
1417 |
g.eAuthMethod = AUTH_LOCAL; |
|
920ace1…
|
drh
|
1418 |
zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)" |
|
920ace1…
|
drh
|
1419 |
" FROM user WHERE uid=%d", uid); |
|
920ace1…
|
drh
|
1420 |
login_create_csrf_secret(zSeed); |
|
920ace1…
|
drh
|
1421 |
fossil_free(zSeed); |
|
dbda8d6…
|
drh
|
1422 |
} |
|
dbda8d6…
|
drh
|
1423 |
|
|
dbda8d6…
|
drh
|
1424 |
/* Check the login cookie to see if it matches a known valid user. |
|
dbda8d6…
|
drh
|
1425 |
*/ |
|
9c952d2…
|
drh
|
1426 |
if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){ |
|
a257fde…
|
drh
|
1427 |
/* Parse the cookie value up into HASH/ARG/USER */ |
|
a257fde…
|
drh
|
1428 |
char *zHash = fossil_strdup(zCookie); |
|
a257fde…
|
drh
|
1429 |
char *zArg = 0; |
|
a257fde…
|
drh
|
1430 |
char *zUser = 0; |
|
a257fde…
|
drh
|
1431 |
int i, c; |
|
a257fde…
|
drh
|
1432 |
for(i=0; (c = zHash[i])!=0; i++){ |
|
a257fde…
|
drh
|
1433 |
if( c=='/' ){ |
|
a257fde…
|
drh
|
1434 |
zHash[i++] = 0; |
|
a257fde…
|
drh
|
1435 |
if( zArg==0 ){ |
|
a257fde…
|
drh
|
1436 |
zArg = &zHash[i]; |
|
a257fde…
|
drh
|
1437 |
}else{ |
|
a257fde…
|
drh
|
1438 |
zUser = &zHash[i]; |
|
a257fde…
|
drh
|
1439 |
break; |
|
a257fde…
|
drh
|
1440 |
} |
|
a257fde…
|
drh
|
1441 |
} |
|
a257fde…
|
drh
|
1442 |
} |
|
a257fde…
|
drh
|
1443 |
if( zUser==0 ){ |
|
a257fde…
|
drh
|
1444 |
/* Invalid cookie */ |
|
7d2b47a…
|
drh
|
1445 |
}else if( fossil_strcmp(zUser, "anonymous")==0 |
|
7d2b47a…
|
drh
|
1446 |
&& anon_cookie_lifespan()>0 ){ |
|
68da478…
|
drh
|
1447 |
/* Cookies of the form "HASH/TIME/anonymous". The TIME must |
|
68da478…
|
drh
|
1448 |
** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and |
|
0693766…
|
drh
|
1449 |
** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT |
|
0693766…
|
drh
|
1450 |
** is the HTTP_USER_AGENT of the client and SECRET is the |
|
68da478…
|
drh
|
1451 |
** "captcha-secret" value in the repository. See tag-20250817a |
|
68da478…
|
drh
|
1452 |
** for the code the creates this cookie. |
|
6021279…
|
drh
|
1453 |
*/ |
|
a10f931…
|
drh
|
1454 |
double rTime = atof(zArg); |
|
0693766…
|
drh
|
1455 |
const char *zUserAgent = PD("HTTP_USER_AGENT","nil"); |
|
6021279…
|
drh
|
1456 |
Blob b; |
|
8659d84…
|
drh
|
1457 |
char *zSecret; |
|
8659d84…
|
drh
|
1458 |
int n = 0; |
|
8659d84…
|
drh
|
1459 |
|
|
8659d84…
|
drh
|
1460 |
do{ |
|
8659d84…
|
drh
|
1461 |
blob_zero(&b); |
|
8659d84…
|
drh
|
1462 |
zSecret = captcha_secret(n++); |
|
8659d84…
|
drh
|
1463 |
if( zSecret==0 ) break; |
|
0693766…
|
drh
|
1464 |
blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret); |
|
8659d84…
|
drh
|
1465 |
sha1sum_blob(&b, &b); |
|
8659d84…
|
drh
|
1466 |
if( fossil_strcmp(zHash, blob_str(&b))==0 ){ |
|
8659d84…
|
drh
|
1467 |
uid = db_int(0, |
|
8659d84…
|
drh
|
1468 |
"SELECT uid FROM user WHERE login='anonymous'" |
|
8659d84…
|
drh
|
1469 |
" AND octet_length(cap)>0" |
|
8659d84…
|
drh
|
1470 |
" AND octet_length(pw)>0" |
|
68da478…
|
drh
|
1471 |
" AND %.17g>julianday('now')", |
|
7d2b47a…
|
drh
|
1472 |
rTime+anon_cookie_lifespan()/1440.0 |
|
8659d84…
|
drh
|
1473 |
); |
|
8659d84…
|
drh
|
1474 |
} |
|
8659d84…
|
drh
|
1475 |
}while( uid==0 ); |
|
6021279…
|
drh
|
1476 |
blob_reset(&b); |
|
a257fde…
|
drh
|
1477 |
}else{ |
|
a257fde…
|
drh
|
1478 |
/* Cookies of the form "HASH/CODE/USER". Search first in the |
|
a257fde…
|
drh
|
1479 |
** local user table, then the user table for project CODE if we |
|
a257fde…
|
drh
|
1480 |
** are part of a login-group. |
|
a257fde…
|
drh
|
1481 |
*/ |
|
7d18c40…
|
drh
|
1482 |
uid = login_find_user(zUser, zHash); |
|
7d18c40…
|
drh
|
1483 |
if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){ |
|
7d18c40…
|
drh
|
1484 |
uid = login_find_user(zUser, zHash); |
|
7df48cb…
|
drh
|
1485 |
if( uid ){ |
|
7df48cb…
|
drh
|
1486 |
record_login_attempt(zUser, zIpAddr, 1); |
|
7df48cb…
|
drh
|
1487 |
}else{ |
|
7df48cb…
|
drh
|
1488 |
/* The login cookie is a valid login for project CODE, but no |
|
7df48cb…
|
drh
|
1489 |
** user named USER exists on this repository. Cannot login as |
|
7df48cb…
|
drh
|
1490 |
** USER, but at least give them "anonymous" login. */ |
|
d6bbf55…
|
drh
|
1491 |
uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" |
|
d6bbf55…
|
drh
|
1492 |
" AND octet_length(cap)>0" |
|
d6bbf55…
|
drh
|
1493 |
" AND octet_length(pw)>0"); |
|
7df48cb…
|
drh
|
1494 |
} |
|
a257fde…
|
drh
|
1495 |
} |
|
6454153…
|
drh
|
1496 |
} |
|
10006db…
|
drh
|
1497 |
if( uid ) g.eAuthMethod = AUTH_COOKIE; |
|
920ace1…
|
drh
|
1498 |
login_create_csrf_secret(zHash); |
|
6454153…
|
drh
|
1499 |
} |
|
6454153…
|
drh
|
1500 |
|
|
6454153…
|
drh
|
1501 |
/* If no user found and the REMOTE_USER environment variable is set, |
|
796dcfe…
|
drh
|
1502 |
** then accept the value of REMOTE_USER as the user. |
|
6454153…
|
drh
|
1503 |
*/ |
|
6454153…
|
drh
|
1504 |
if( uid==0 ){ |
|
6454153…
|
drh
|
1505 |
const char *zRemoteUser = P("REMOTE_USER"); |
|
6454153…
|
drh
|
1506 |
if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){ |
|
6454153…
|
drh
|
1507 |
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" |
|
604e1a6…
|
drh
|
1508 |
" AND octet_length(cap)>0 AND octet_length(pw)>0", |
|
604e1a6…
|
drh
|
1509 |
zRemoteUser); |
|
10006db…
|
drh
|
1510 |
if( uid ) g.eAuthMethod = AUTH_ENV; |
|
315cf24…
|
drh
|
1511 |
} |
|
315cf24…
|
drh
|
1512 |
} |
|
315cf24…
|
drh
|
1513 |
|
|
315cf24…
|
drh
|
1514 |
/* If the request didn't provide a login cookie or the login cookie didn't |
|
315cf24…
|
drh
|
1515 |
** match a known valid user, check the HTTP "Authorization" header and |
|
315cf24…
|
drh
|
1516 |
** see if those credentials are valid for a known user. |
|
315cf24…
|
drh
|
1517 |
*/ |
|
2e76b99…
|
drh
|
1518 |
if( uid==0 && db_get_boolean("http_authentication_ok",0) ){ |
|
c921545…
|
drh
|
1519 |
uid = login_basic_authentication(zIpAddr); |
|
10006db…
|
drh
|
1520 |
if( uid ) g.eAuthMethod = AUTH_HTTP; |
|
1e81049…
|
drh
|
1521 |
} |
|
1e81049…
|
drh
|
1522 |
|
|
1e81049…
|
drh
|
1523 |
/* Check for magic query parameters "resid" (for the username) and |
|
1e81049…
|
drh
|
1524 |
** "token" for the password. Both values (if they exist) will be |
|
1e81049…
|
drh
|
1525 |
** obfuscated. |
|
1e81049…
|
drh
|
1526 |
*/ |
|
1e81049…
|
drh
|
1527 |
if( uid==0 ){ |
|
1e81049…
|
drh
|
1528 |
char *zUsr, *zPW; |
|
1e81049…
|
drh
|
1529 |
if( (zUsr = unobscure(P("resid")))!=0 |
|
1e81049…
|
drh
|
1530 |
&& (zPW = unobscure(P("token")))!=0 |
|
1e81049…
|
drh
|
1531 |
){ |
|
1e81049…
|
drh
|
1532 |
char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0); |
|
1e81049…
|
drh
|
1533 |
uid = db_int(0, "SELECT uid FROM user" |
|
1e81049…
|
drh
|
1534 |
" WHERE login=%Q" |
|
1e81049…
|
drh
|
1535 |
" AND (constant_time_cmp(pw,%Q)=0" |
|
1e81049…
|
drh
|
1536 |
" OR constant_time_cmp(pw,%Q)=0)", |
|
1e81049…
|
drh
|
1537 |
zUsr, zSha1Pw, zPW); |
|
1e81049…
|
drh
|
1538 |
fossil_free(zSha1Pw); |
|
10006db…
|
drh
|
1539 |
if( uid ) g.eAuthMethod = AUTH_PW; |
|
1e81049…
|
drh
|
1540 |
} |
|
6021279…
|
drh
|
1541 |
} |
|
6021279…
|
drh
|
1542 |
|
|
6021279…
|
drh
|
1543 |
/* If no user found yet, try to log in as "nobody" */ |
|
6021279…
|
drh
|
1544 |
if( uid==0 ){ |
|
6021279…
|
drh
|
1545 |
uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); |
|
6021279…
|
drh
|
1546 |
if( uid==0 ){ |
|
6021279…
|
drh
|
1547 |
/* If there is no user "nobody", then make one up - with no privileges */ |
|
6021279…
|
drh
|
1548 |
uid = -1; |
|
6021279…
|
drh
|
1549 |
zCap = ""; |
|
6021279…
|
drh
|
1550 |
} |
|
920ace1…
|
drh
|
1551 |
login_create_csrf_secret("none"); |
|
07bfe3f…
|
drh
|
1552 |
} |
|
07bfe3f…
|
drh
|
1553 |
|
|
07bfe3f…
|
drh
|
1554 |
login_set_uid(uid, zCap); |
|
1a0b304…
|
drh
|
1555 |
|
|
16b3309…
|
drh
|
1556 |
/* Maybe restrict access by robots */ |
|
16b3309…
|
drh
|
1557 |
if( g.zLogin==0 && robot_restrict(g.zPath) ){ |
|
16b3309…
|
drh
|
1558 |
cgi_reply(); |
|
16b3309…
|
drh
|
1559 |
fossil_exit(0); |
|
16b3309…
|
drh
|
1560 |
} |
|
07bfe3f…
|
drh
|
1561 |
} |
|
07bfe3f…
|
drh
|
1562 |
|
|
07bfe3f…
|
drh
|
1563 |
/* |
|
07bfe3f…
|
drh
|
1564 |
** Set the current logged in user to be uid. zCap is precomputed |
|
07bfe3f…
|
drh
|
1565 |
** (override) capabilities. If zCap==0, then look up the capabilities |
|
07bfe3f…
|
drh
|
1566 |
** in the USER table. |
|
07bfe3f…
|
drh
|
1567 |
*/ |
|
07bfe3f…
|
drh
|
1568 |
int login_set_uid(int uid, const char *zCap){ |
|
07bfe3f…
|
drh
|
1569 |
const char *zPublicPages = 0; /* GLOB patterns of public pages */ |
|
6021279…
|
drh
|
1570 |
|
|
6021279…
|
drh
|
1571 |
/* At this point, we know that uid!=0. Find the privileges associated |
|
6021279…
|
drh
|
1572 |
** with user uid. |
|
6021279…
|
drh
|
1573 |
*/ |
|
6021279…
|
drh
|
1574 |
assert( uid!=0 ); |
|
6021279…
|
drh
|
1575 |
if( zCap==0 ){ |
|
6021279…
|
drh
|
1576 |
Stmt s; |
|
6021279…
|
drh
|
1577 |
db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid); |
|
6021279…
|
drh
|
1578 |
if( db_step(&s)==SQLITE_ROW ){ |
|
6021279…
|
drh
|
1579 |
g.zLogin = db_column_malloc(&s, 0); |
|
6021279…
|
drh
|
1580 |
zCap = db_column_malloc(&s, 1); |
|
6021279…
|
drh
|
1581 |
} |
|
6021279…
|
drh
|
1582 |
db_finalize(&s); |
|
6021279…
|
drh
|
1583 |
if( zCap==0 ){ |
|
6021279…
|
drh
|
1584 |
zCap = ""; |
|
6021279…
|
drh
|
1585 |
} |
|
6021279…
|
drh
|
1586 |
} |
|
596f3c1…
|
drh
|
1587 |
if( g.fHttpTrace && g.zLogin ){ |
|
596f3c1…
|
drh
|
1588 |
fprintf(stderr, "# login: [%s] with capabilities [%s]\n", g.zLogin, zCap); |
|
596f3c1…
|
drh
|
1589 |
} |
|
6021279…
|
drh
|
1590 |
|
|
6021279…
|
drh
|
1591 |
/* Set the global variables recording the userid and login. The |
|
6021279…
|
drh
|
1592 |
** "nobody" user is a special case in that g.zLogin==0. |
|
6021279…
|
drh
|
1593 |
*/ |
|
6021279…
|
drh
|
1594 |
g.userUid = uid; |
|
31c52c7…
|
drh
|
1595 |
if( fossil_strcmp(g.zLogin,"nobody")==0 ){ |
|
6021279…
|
drh
|
1596 |
g.zLogin = 0; |
|
31c52c7…
|
drh
|
1597 |
} |
|
abcd5df…
|
drh
|
1598 |
if( PB("isrobot") ){ |
|
16b3309…
|
drh
|
1599 |
g.isRobot = 1; |
|
abcd5df…
|
drh
|
1600 |
}else if( g.zLogin==0 ){ |
|
16b3309…
|
drh
|
1601 |
g.isRobot = !isHuman(P("HTTP_USER_AGENT")); |
|
abcd5df…
|
drh
|
1602 |
}else{ |
|
16b3309…
|
drh
|
1603 |
g.isRobot = 0; |
|
abcd5df…
|
drh
|
1604 |
} |
|
6021279…
|
drh
|
1605 |
|
|
6021279…
|
drh
|
1606 |
/* Set the capabilities */ |
|
796dcfe…
|
drh
|
1607 |
login_replace_capabilities(zCap, 0); |
|
96f3e83…
|
drh
|
1608 |
|
|
96f3e83…
|
drh
|
1609 |
/* The auto-hyperlink setting allows hyperlinks to be displayed for users |
|
96f3e83…
|
drh
|
1610 |
** who do not have the "h" permission as long as their UserAgent string |
|
96f3e83…
|
drh
|
1611 |
** makes it appear that they are human. Check to see if auto-hyperlink is |
|
96f3e83…
|
drh
|
1612 |
** enabled for this repository and make appropriate adjustments to the |
|
a2730fe…
|
drh
|
1613 |
** permission flags if it is. This should be done before the permissions |
|
a2730fe…
|
drh
|
1614 |
** are (potentially) copied to the anonymous permission set; otherwise, |
|
a2730fe…
|
drh
|
1615 |
** those will be out-of-sync. |
|
a2730fe…
|
drh
|
1616 |
*/ |
|
16b3309…
|
drh
|
1617 |
if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){ |
|
df337eb…
|
drh
|
1618 |
int autoLink = db_get_int("auto-hyperlink",1); |
|
df337eb…
|
drh
|
1619 |
if( autoLink==1 ){ |
|
df337eb…
|
drh
|
1620 |
g.jsHref = 1; |
|
df337eb…
|
drh
|
1621 |
g.perm.Hyperlink = 1; |
|
df337eb…
|
drh
|
1622 |
}else if( autoLink==2 ){ |
|
df337eb…
|
drh
|
1623 |
g.perm.Hyperlink = 1; |
|
df337eb…
|
drh
|
1624 |
} |
|
a2730fe…
|
drh
|
1625 |
} |
|
a2730fe…
|
drh
|
1626 |
|
|
a2730fe…
|
drh
|
1627 |
/* |
|
a2730fe…
|
drh
|
1628 |
** At this point, the capabilities for the logged in user are not going |
|
a2730fe…
|
drh
|
1629 |
** to be modified anymore; therefore, we can copy them over to the ones |
|
a2730fe…
|
drh
|
1630 |
** for the anonymous user. |
|
a2730fe…
|
drh
|
1631 |
** |
|
a2730fe…
|
drh
|
1632 |
** WARNING: In the future, please do not add code after this point that |
|
a2730fe…
|
drh
|
1633 |
** modifies the capabilities for the logged in user. |
|
96f3e83…
|
drh
|
1634 |
*/ |
|
a2730fe…
|
drh
|
1635 |
login_set_anon_nobody_capabilities(); |
|
79ef961…
|
drh
|
1636 |
|
|
79ef961…
|
drh
|
1637 |
/* If the public-pages glob pattern is defined and REQUEST_URI matches |
|
79ef961…
|
drh
|
1638 |
** one of the globs in public-pages, then also add in all default-perms |
|
79ef961…
|
drh
|
1639 |
** permissions. |
|
79ef961…
|
drh
|
1640 |
*/ |
|
79ef961…
|
drh
|
1641 |
zPublicPages = db_get("public-pages",0); |
|
79ef961…
|
drh
|
1642 |
if( zPublicPages!=0 ){ |
|
d6cd147…
|
drh
|
1643 |
const char *zUri = PD("REQUEST_URI",""); |
|
d6cd147…
|
drh
|
1644 |
zUri += (int)strlen(g.zTop); |
|
dc86831…
|
drh
|
1645 |
if( glob_multi_match(zPublicPages, zUri) ){ |
|
c00e912…
|
drh
|
1646 |
login_set_capabilities(db_get("default-perms", "u"), 0); |
|
79ef961…
|
drh
|
1647 |
} |
|
e059e5a…
|
drh
|
1648 |
} |
|
07bfe3f…
|
drh
|
1649 |
return g.zLogin!=0; |
|
6021279…
|
drh
|
1650 |
} |
|
6021279…
|
drh
|
1651 |
|
|
6021279…
|
drh
|
1652 |
/* |
|
ab48825…
|
drh
|
1653 |
** Memory of settings |
|
ab48825…
|
drh
|
1654 |
*/ |
|
ab48825…
|
drh
|
1655 |
static int login_anon_once = 1; |
|
ab48825…
|
drh
|
1656 |
|
|
ab48825…
|
drh
|
1657 |
/* |
|
653dd40…
|
drh
|
1658 |
** Add to g.perm the default privileges of users "nobody" and/or "anonymous" |
|
653dd40…
|
drh
|
1659 |
** as appropriate for the user g.zLogin. |
|
653dd40…
|
drh
|
1660 |
** |
|
653dd40…
|
drh
|
1661 |
** This routine also sets up g.anon to be either a copy of g.perm for |
|
653dd40…
|
drh
|
1662 |
** all logged in uses, or the privileges that would be available to "anonymous" |
|
653dd40…
|
drh
|
1663 |
** if g.zLogin==0 (meaning that the user is "nobody"). |
|
6021279…
|
drh
|
1664 |
*/ |
|
6021279…
|
drh
|
1665 |
void login_set_anon_nobody_capabilities(void){ |
|
653dd40…
|
drh
|
1666 |
if( login_anon_once ){ |
|
6021279…
|
drh
|
1667 |
const char *zCap; |
|
653dd40…
|
drh
|
1668 |
/* All users get privileges from "nobody" */ |
|
6021279…
|
drh
|
1669 |
zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'"); |
|
3bd2de4…
|
drh
|
1670 |
login_set_capabilities(zCap, 0); |
|
653dd40…
|
drh
|
1671 |
zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); |
|
653dd40…
|
drh
|
1672 |
if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){ |
|
6021279…
|
drh
|
1673 |
/* All logged-in users inherit privileges from "anonymous" */ |
|
3bd2de4…
|
drh
|
1674 |
login_set_capabilities(zCap, 0); |
|
653dd40…
|
drh
|
1675 |
g.anon = g.perm; |
|
653dd40…
|
drh
|
1676 |
}else{ |
|
653dd40…
|
drh
|
1677 |
/* Record the privileges of anonymous in g.anon */ |
|
653dd40…
|
drh
|
1678 |
g.anon = g.perm; |
|
653dd40…
|
drh
|
1679 |
login_set_capabilities(zCap, LOGIN_ANON); |
|
ab48825…
|
drh
|
1680 |
} |
|
ab48825…
|
drh
|
1681 |
login_anon_once = 0; |
|
ab48825…
|
drh
|
1682 |
} |
|
ab48825…
|
drh
|
1683 |
} |
|
ab48825…
|
drh
|
1684 |
|
|
ab48825…
|
drh
|
1685 |
/* |
|
796dcfe…
|
drh
|
1686 |
** Flags passed into the 2nd argument of login_set/replace_capabilities(). |
|
3bd2de4…
|
drh
|
1687 |
*/ |
|
3bd2de4…
|
drh
|
1688 |
#if INTERFACE |
|
49546c5…
|
drh
|
1689 |
#define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */ |
|
653dd40…
|
drh
|
1690 |
#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */ |
|
3bd2de4…
|
drh
|
1691 |
#endif |
|
3bd2de4…
|
drh
|
1692 |
|
|
3bd2de4…
|
drh
|
1693 |
/* |
|
653dd40…
|
drh
|
1694 |
** Adds all capability flags in zCap to g.perm or g.anon. |
|
ab48825…
|
drh
|
1695 |
*/ |
|
3bd2de4…
|
drh
|
1696 |
void login_set_capabilities(const char *zCap, unsigned flags){ |
|
6021279…
|
drh
|
1697 |
int i; |
|
653dd40…
|
drh
|
1698 |
FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm; |
|
796dcfe…
|
drh
|
1699 |
if(NULL==zCap){ |
|
796dcfe…
|
drh
|
1700 |
return; |
|
796dcfe…
|
drh
|
1701 |
} |
|
6021279…
|
drh
|
1702 |
for(i=0; zCap[i]; i++){ |
|
6021279…
|
drh
|
1703 |
switch( zCap[i] ){ |
|
b241130…
|
mistachkin
|
1704 |
case 's': p->Setup = 1; /* Fall thru into Admin */ |
|
653dd40…
|
drh
|
1705 |
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip = |
|
b241130…
|
mistachkin
|
1706 |
p->RdWiki = p->WrWiki = p->NewWiki = |
|
b241130…
|
mistachkin
|
1707 |
p->ApndWiki = p->Hyperlink = p->Clone = |
|
b241130…
|
mistachkin
|
1708 |
p->NewTkt = p->Password = p->RdAddr = |
|
b241130…
|
mistachkin
|
1709 |
p->TktFmt = p->Attach = p->ApndTkt = |
|
1274054…
|
drh
|
1710 |
p->ModWiki = p->ModTkt = |
|
9a2e5f4…
|
drh
|
1711 |
p->RdForum = p->WrForum = p->ModForum = |
|
275da70…
|
danield
|
1712 |
p->WrTForum = p->AdminForum = p->Chat = |
|
a6ffdaf…
|
wyoung
|
1713 |
p->EmailAlert = p->Announce = p->Debug = 1; |
|
b241130…
|
mistachkin
|
1714 |
/* Fall thru into Read/Write */ |
|
b241130…
|
mistachkin
|
1715 |
case 'i': p->Read = p->Write = 1; break; |
|
653dd40…
|
drh
|
1716 |
case 'o': p->Read = 1; break; |
|
653dd40…
|
drh
|
1717 |
case 'z': p->Zip = 1; break; |
|
653dd40…
|
drh
|
1718 |
|
|
653dd40…
|
drh
|
1719 |
case 'h': p->Hyperlink = 1; break; |
|
653dd40…
|
drh
|
1720 |
case 'g': p->Clone = 1; break; |
|
653dd40…
|
drh
|
1721 |
case 'p': p->Password = 1; break; |
|
653dd40…
|
drh
|
1722 |
|
|
653dd40…
|
drh
|
1723 |
case 'j': p->RdWiki = 1; break; |
|
b241130…
|
mistachkin
|
1724 |
case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break; |
|
653dd40…
|
drh
|
1725 |
case 'm': p->ApndWiki = 1; break; |
|
653dd40…
|
drh
|
1726 |
case 'f': p->NewWiki = 1; break; |
|
653dd40…
|
drh
|
1727 |
case 'l': p->ModWiki = 1; break; |
|
653dd40…
|
drh
|
1728 |
|
|
653dd40…
|
drh
|
1729 |
case 'e': p->RdAddr = 1; break; |
|
653dd40…
|
drh
|
1730 |
case 'r': p->RdTkt = 1; break; |
|
653dd40…
|
drh
|
1731 |
case 'n': p->NewTkt = 1; break; |
|
653dd40…
|
drh
|
1732 |
case 'w': p->WrTkt = p->RdTkt = p->NewTkt = |
|
653dd40…
|
drh
|
1733 |
p->ApndTkt = 1; break; |
|
653dd40…
|
drh
|
1734 |
case 'c': p->ApndTkt = 1; break; |
|
653dd40…
|
drh
|
1735 |
case 'q': p->ModTkt = 1; break; |
|
653dd40…
|
drh
|
1736 |
case 't': p->TktFmt = 1; break; |
|
653dd40…
|
drh
|
1737 |
case 'b': p->Attach = 1; break; |
|
653dd40…
|
drh
|
1738 |
case 'x': p->Private = 1; break; |
|
27d743e…
|
drh
|
1739 |
case 'y': p->WrUnver = 1; break; |
|
27d743e…
|
drh
|
1740 |
|
|
9a2e5f4…
|
drh
|
1741 |
case '6': p->AdminForum = 1; |
|
9a2e5f4…
|
drh
|
1742 |
case '5': p->ModForum = 1; |
|
9a2e5f4…
|
drh
|
1743 |
case '4': p->WrTForum = 1; |
|
9a2e5f4…
|
drh
|
1744 |
case '3': p->WrForum = 1; |
|
9a2e5f4…
|
drh
|
1745 |
case '2': p->RdForum = 1; break; |
|
9a2e5f4…
|
drh
|
1746 |
|
|
25eafed…
|
drh
|
1747 |
case '7': p->EmailAlert = 1; break; |
|
65f5754…
|
drh
|
1748 |
case 'A': p->Announce = 1; break; |
|
e8ba89b…
|
drh
|
1749 |
case 'C': p->Chat = 1; break; |
|
fd31983…
|
drh
|
1750 |
case 'D': p->Debug = 1; break; |
|
25eafed…
|
drh
|
1751 |
|
|
397d23c…
|
drh
|
1752 |
/* The "u" privilege recursively |
|
6021279…
|
drh
|
1753 |
** inherits all privileges of the user named "reader" */ |
|
6021279…
|
drh
|
1754 |
case 'u': { |
|
397d23c…
|
drh
|
1755 |
if( p->XReader==0 ){ |
|
3bd2de4…
|
drh
|
1756 |
const char *zUser; |
|
397d23c…
|
drh
|
1757 |
p->XReader = 1; |
|
6021279…
|
drh
|
1758 |
zUser = db_text("", "SELECT cap FROM user WHERE login='reader'"); |
|
397d23c…
|
drh
|
1759 |
login_set_capabilities(zUser, flags); |
|
6021279…
|
drh
|
1760 |
} |
|
6021279…
|
drh
|
1761 |
break; |
|
6021279…
|
drh
|
1762 |
} |
|
6021279…
|
drh
|
1763 |
|
|
397d23c…
|
drh
|
1764 |
/* The "v" privilege recursively |
|
6021279…
|
drh
|
1765 |
** inherits all privileges of the user named "developer" */ |
|
6021279…
|
drh
|
1766 |
case 'v': { |
|
397d23c…
|
drh
|
1767 |
if( p->XDeveloper==0 ){ |
|
3bd2de4…
|
drh
|
1768 |
const char *zDev; |
|
397d23c…
|
drh
|
1769 |
p->XDeveloper = 1; |
|
6021279…
|
drh
|
1770 |
zDev = db_text("", "SELECT cap FROM user WHERE login='developer'"); |
|
397d23c…
|
drh
|
1771 |
login_set_capabilities(zDev, flags); |
|
1f1d965…
|
drh
|
1772 |
} |
|
1f1d965…
|
drh
|
1773 |
break; |
|
1f1d965…
|
drh
|
1774 |
} |
|
1f1d965…
|
drh
|
1775 |
} |
|
1f1d965…
|
drh
|
1776 |
} |
|
1f1d965…
|
drh
|
1777 |
} |
|
1f1d965…
|
drh
|
1778 |
|
|
1f1d965…
|
drh
|
1779 |
/* |
|
796dcfe…
|
drh
|
1780 |
** Zeroes out g.perm and calls login_set_capabilities(zCap,flags). |
|
796dcfe…
|
drh
|
1781 |
*/ |
|
796dcfe…
|
drh
|
1782 |
void login_replace_capabilities(const char *zCap, unsigned flags){ |
|
796dcfe…
|
drh
|
1783 |
memset(&g.perm, 0, sizeof(g.perm)); |
|
24e298e…
|
mistachkin
|
1784 |
login_set_capabilities(zCap, flags); |
|
b1ffbfa…
|
drh
|
1785 |
login_anon_once = 1; |
|
796dcfe…
|
drh
|
1786 |
} |
|
796dcfe…
|
drh
|
1787 |
|
|
796dcfe…
|
drh
|
1788 |
/* |
|
d0305b3…
|
aku
|
1789 |
** If the current login lacks any of the capabilities listed in |
|
d0305b3…
|
aku
|
1790 |
** the input, then return 0. If all capabilities are present, then |
|
d0305b3…
|
aku
|
1791 |
** return 1. |
|
e5240c9…
|
stephan
|
1792 |
** |
|
e5240c9…
|
stephan
|
1793 |
** As a special case, the 'L' pseudo-capability ID means "is logged |
|
e5240c9…
|
stephan
|
1794 |
** in" and will return true for any non-guest user. |
|
d0305b3…
|
aku
|
1795 |
*/ |
|
653dd40…
|
drh
|
1796 |
int login_has_capability(const char *zCap, int nCap, u32 flgs){ |
|
d0305b3…
|
aku
|
1797 |
int i; |
|
d0305b3…
|
aku
|
1798 |
int rc = 1; |
|
653dd40…
|
drh
|
1799 |
FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm; |
|
d0305b3…
|
aku
|
1800 |
if( nCap<0 ) nCap = strlen(zCap); |
|
d0305b3…
|
aku
|
1801 |
for(i=0; i<nCap && rc && zCap[i]; i++){ |
|
d0305b3…
|
aku
|
1802 |
switch( zCap[i] ){ |
|
653dd40…
|
drh
|
1803 |
case 'a': rc = p->Admin; break; |
|
653dd40…
|
drh
|
1804 |
case 'b': rc = p->Attach; break; |
|
653dd40…
|
drh
|
1805 |
case 'c': rc = p->ApndTkt; break; |
|
1274054…
|
drh
|
1806 |
/* d unused: see comment in capabilities.c */ |
|
653dd40…
|
drh
|
1807 |
case 'e': rc = p->RdAddr; break; |
|
653dd40…
|
drh
|
1808 |
case 'f': rc = p->NewWiki; break; |
|
653dd40…
|
drh
|
1809 |
case 'g': rc = p->Clone; break; |
|
653dd40…
|
drh
|
1810 |
case 'h': rc = p->Hyperlink; break; |
|
653dd40…
|
drh
|
1811 |
case 'i': rc = p->Write; break; |
|
653dd40…
|
drh
|
1812 |
case 'j': rc = p->RdWiki; break; |
|
653dd40…
|
drh
|
1813 |
case 'k': rc = p->WrWiki; break; |
|
653dd40…
|
drh
|
1814 |
case 'l': rc = p->ModWiki; break; |
|
653dd40…
|
drh
|
1815 |
case 'm': rc = p->ApndWiki; break; |
|
653dd40…
|
drh
|
1816 |
case 'n': rc = p->NewTkt; break; |
|
653dd40…
|
drh
|
1817 |
case 'o': rc = p->Read; break; |
|
653dd40…
|
drh
|
1818 |
case 'p': rc = p->Password; break; |
|
653dd40…
|
drh
|
1819 |
case 'q': rc = p->ModTkt; break; |
|
653dd40…
|
drh
|
1820 |
case 'r': rc = p->RdTkt; break; |
|
653dd40…
|
drh
|
1821 |
case 's': rc = p->Setup; break; |
|
653dd40…
|
drh
|
1822 |
case 't': rc = p->TktFmt; break; |
|
355ee47…
|
drh
|
1823 |
/* case 'u': READER */ |
|
355ee47…
|
drh
|
1824 |
/* case 'v': DEVELOPER */ |
|
653dd40…
|
drh
|
1825 |
case 'w': rc = p->WrTkt; break; |
|
653dd40…
|
drh
|
1826 |
case 'x': rc = p->Private; break; |
|
1f8a667…
|
mistachkin
|
1827 |
case 'y': rc = p->WrUnver; break; |
|
653dd40…
|
drh
|
1828 |
case 'z': rc = p->Zip; break; |
|
65f5754…
|
drh
|
1829 |
case '2': rc = p->RdForum; break; |
|
65f5754…
|
drh
|
1830 |
case '3': rc = p->WrForum; break; |
|
65f5754…
|
drh
|
1831 |
case '4': rc = p->WrTForum; break; |
|
65f5754…
|
drh
|
1832 |
case '5': rc = p->ModForum; break; |
|
65f5754…
|
drh
|
1833 |
case '6': rc = p->AdminForum;break; |
|
65f5754…
|
drh
|
1834 |
case '7': rc = p->EmailAlert;break; |
|
65f5754…
|
drh
|
1835 |
case 'A': rc = p->Announce; break; |
|
e8ba89b…
|
drh
|
1836 |
case 'C': rc = p->Chat; break; |
|
fd31983…
|
drh
|
1837 |
case 'D': rc = p->Debug; break; |
|
e5240c9…
|
stephan
|
1838 |
case 'L': rc = g.zLogin && *g.zLogin; break; |
|
e5240c9…
|
stephan
|
1839 |
/* Mainenance reminder: '@' should not be used because |
|
e5240c9…
|
stephan
|
1840 |
it would semantically collide with the @ in the |
|
e5240c9…
|
stephan
|
1841 |
capexpr TH1 command. */ |
|
b241130…
|
mistachkin
|
1842 |
default: rc = 0; break; |
|
d0305b3…
|
aku
|
1843 |
} |
|
d0305b3…
|
aku
|
1844 |
} |
|
d0305b3…
|
aku
|
1845 |
return rc; |
|
0be5482…
|
drh
|
1846 |
} |
|
0be5482…
|
drh
|
1847 |
|
|
0be5482…
|
drh
|
1848 |
/* |
|
ab48825…
|
drh
|
1849 |
** Change the login to zUser. |
|
ab48825…
|
drh
|
1850 |
*/ |
|
ab48825…
|
drh
|
1851 |
void login_as_user(const char *zUser){ |
|
ab48825…
|
drh
|
1852 |
char *zCap = ""; /* New capabilities */ |
|
ab48825…
|
drh
|
1853 |
|
|
ab48825…
|
drh
|
1854 |
/* Turn off all capabilities from prior logins */ |
|
b344d3c…
|
drh
|
1855 |
memset( &g.perm, 0, sizeof(g.perm) ); |
|
ab48825…
|
drh
|
1856 |
|
|
ab48825…
|
drh
|
1857 |
/* Set the global variables recording the userid and login. The |
|
ab48825…
|
drh
|
1858 |
** "nobody" user is a special case in that g.zLogin==0. |
|
ab48825…
|
drh
|
1859 |
*/ |
|
ab48825…
|
drh
|
1860 |
g.userUid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUser); |
|
ab48825…
|
drh
|
1861 |
if( g.userUid==0 ){ |
|
ab48825…
|
drh
|
1862 |
zUser = 0; |
|
ab48825…
|
drh
|
1863 |
g.userUid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); |
|
ab48825…
|
drh
|
1864 |
} |
|
ab48825…
|
drh
|
1865 |
if( g.userUid ){ |
|
ab48825…
|
drh
|
1866 |
zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", g.userUid); |
|
ab48825…
|
drh
|
1867 |
} |
|
ab48825…
|
drh
|
1868 |
if( fossil_strcmp(zUser,"nobody")==0 ) zUser = 0; |
|
ab48825…
|
drh
|
1869 |
g.zLogin = fossil_strdup(zUser); |
|
ab48825…
|
drh
|
1870 |
|
|
ab48825…
|
drh
|
1871 |
/* Set the capabilities */ |
|
3bd2de4…
|
drh
|
1872 |
login_set_capabilities(zCap, 0); |
|
ab48825…
|
drh
|
1873 |
login_anon_once = 1; |
|
ab48825…
|
drh
|
1874 |
login_set_anon_nobody_capabilities(); |
|
3bd2de4…
|
drh
|
1875 |
} |
|
3bd2de4…
|
drh
|
1876 |
|
|
3bd2de4…
|
drh
|
1877 |
/* |
|
840b762…
|
drh
|
1878 |
** Return true if the user is "nobody" |
|
840b762…
|
drh
|
1879 |
*/ |
|
840b762…
|
drh
|
1880 |
int login_is_nobody(void){ |
|
840b762…
|
drh
|
1881 |
return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0; |
|
840b762…
|
drh
|
1882 |
} |
|
840b762…
|
drh
|
1883 |
|
|
840b762…
|
drh
|
1884 |
/* |
|
b77f1aa…
|
drh
|
1885 |
** Return true if the user is a specific individual, not "nobody" or |
|
b77f1aa…
|
drh
|
1886 |
** "anonymous". |
|
b77f1aa…
|
drh
|
1887 |
*/ |
|
b77f1aa…
|
drh
|
1888 |
int login_is_individual(void){ |
|
b77f1aa…
|
drh
|
1889 |
return g.zLogin!=0 && g.zLogin[0]!=0 && fossil_strcmp(g.zLogin,"nobody")!=0 |
|
b77f1aa…
|
drh
|
1890 |
&& fossil_strcmp(g.zLogin,"anonymous")!=0; |
|
b77f1aa…
|
drh
|
1891 |
} |
|
b77f1aa…
|
drh
|
1892 |
|
|
b77f1aa…
|
drh
|
1893 |
/* |
|
840b762…
|
drh
|
1894 |
** Return the login name. If no login name is specified, return "nobody". |
|
840b762…
|
drh
|
1895 |
*/ |
|
840b762…
|
drh
|
1896 |
const char *login_name(void){ |
|
840b762…
|
drh
|
1897 |
return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; |
|
840b762…
|
drh
|
1898 |
} |
|
840b762…
|
drh
|
1899 |
|
|
840b762…
|
drh
|
1900 |
/* |
|
dbda8d6…
|
drh
|
1901 |
** Call this routine when the credential check fails. It causes |
|
dbda8d6…
|
drh
|
1902 |
** a redirect to the "login" page. |
|
dbda8d6…
|
drh
|
1903 |
*/ |
|
653dd40…
|
drh
|
1904 |
void login_needed(int anonOk){ |
|
796dcfe…
|
drh
|
1905 |
#ifdef FOSSIL_ENABLE_JSON |
|
796dcfe…
|
drh
|
1906 |
if(g.json.isJsonMode){ |
|
796dcfe…
|
drh
|
1907 |
json_err( FSL_JSON_E_DENIED, NULL, 1 ); |
|
796dcfe…
|
drh
|
1908 |
fossil_exit(0); |
|
796dcfe…
|
drh
|
1909 |
/* NOTREACHED */ |
|
796dcfe…
|
drh
|
1910 |
assert(0); |
|
796dcfe…
|
drh
|
1911 |
}else |
|
796dcfe…
|
drh
|
1912 |
#endif /* FOSSIL_ENABLE_JSON */ |
|
796dcfe…
|
drh
|
1913 |
{ |
|
653dd40…
|
drh
|
1914 |
const char *zQS = P("QUERY_STRING"); |
|
3571c87…
|
drh
|
1915 |
const char *zPathInfo = PD("PATH_INFO",""); |
|
653dd40…
|
drh
|
1916 |
Blob redir; |
|
653dd40…
|
drh
|
1917 |
blob_init(&redir, 0, 0); |
|
129ea22…
|
mistachkin
|
1918 |
if( zPathInfo[0]=='/' ) zPathInfo++; /* skip leading slash */ |
|
4aba9ea…
|
drh
|
1919 |
if( fossil_wants_https(1) ){ |
|
3571c87…
|
drh
|
1920 |
blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zPathInfo); |
|
653dd40…
|
drh
|
1921 |
}else{ |
|
3571c87…
|
drh
|
1922 |
blob_appendf(&redir, "%R/login?g=%T", zPathInfo); |
|
653dd40…
|
drh
|
1923 |
} |
|
653dd40…
|
drh
|
1924 |
if( zQS && zQS[0] ){ |
|
8d3d39f…
|
drh
|
1925 |
blob_appendf(&redir, "%%3f%T", zQS); |
|
653dd40…
|
drh
|
1926 |
} |
|
b873148…
|
florian
|
1927 |
if( anonOk ) blob_append(&redir, "&anon=1", 7); |
|
653dd40…
|
drh
|
1928 |
cgi_redirect(blob_str(&redir)); |
|
796dcfe…
|
drh
|
1929 |
/* NOTREACHED */ |
|
796dcfe…
|
drh
|
1930 |
assert(0); |
|
796dcfe…
|
drh
|
1931 |
} |
|
2b0d451…
|
drh
|
1932 |
} |
|
2b0d451…
|
drh
|
1933 |
|
|
2b0d451…
|
drh
|
1934 |
/* |
|
433cde1…
|
drh
|
1935 |
** Call this routine if the user lacks g.perm.Hyperlink permission. If |
|
e2bdc10…
|
danield
|
1936 |
** the anonymous user has Hyperlink permission, then paint a message |
|
2b0d451…
|
drh
|
1937 |
** to inform the user that much more information is available by |
|
2b0d451…
|
drh
|
1938 |
** logging in as anonymous. |
|
2b0d451…
|
drh
|
1939 |
*/ |
|
2b0d451…
|
drh
|
1940 |
void login_anonymous_available(void){ |
|
7d2b47a…
|
drh
|
1941 |
if( !g.perm.Hyperlink && g.anon.Hyperlink && anon_cookie_lifespan()>0 ){ |
|
85f87c8…
|
drh
|
1942 |
const char *zUrl = PD("PATH_INFO", ""); |
|
f5482a0…
|
wyoung
|
1943 |
@ <p>Many <span class="disabled">hyperlinks are disabled.</span><br> |
|
1fee037…
|
drh
|
1944 |
@ Use <a href="%R/login?anon=1&g=%T(zUrl)">anonymous login</a> |
|
d57de28…
|
drh
|
1945 |
@ to enable hyperlinks.</p> |
|
d57de28…
|
drh
|
1946 |
} |
|
0be5482…
|
drh
|
1947 |
} |
|
0be5482…
|
drh
|
1948 |
|
|
0be5482…
|
drh
|
1949 |
/* |
|
0be5482…
|
drh
|
1950 |
** While rendering a form, call this routine to add the Anti-CSRF token |
|
0be5482…
|
drh
|
1951 |
** as a hidden element of the form. |
|
0be5482…
|
drh
|
1952 |
*/ |
|
0be5482…
|
drh
|
1953 |
void login_insert_csrf_secret(void){ |
|
f5482a0…
|
wyoung
|
1954 |
@ <input type="hidden" name="csrf" value="%s(g.zCsrfToken)"> |
|
b13b651…
|
drh
|
1955 |
} |
|
b13b651…
|
drh
|
1956 |
|
|
b13b651…
|
drh
|
1957 |
/* |
|
b13b651…
|
drh
|
1958 |
** Check to see if the candidate username zUserID is already used. |
|
275da70…
|
danield
|
1959 |
** Return 1 if it is already in use. Return 0 if the name is |
|
e2bdc10…
|
danield
|
1960 |
** available for a self-registration. |
|
b13b651…
|
drh
|
1961 |
*/ |
|
d425d23…
|
danield
|
1962 |
static int login_self_chosen_userid_already_exists(const char *zUserID){ |
|
b13b651…
|
drh
|
1963 |
int rc = db_exists( |
|
b13b651…
|
drh
|
1964 |
"SELECT 1 FROM user WHERE login=%Q " |
|
b13b651…
|
drh
|
1965 |
"UNION ALL " |
|
b13b651…
|
drh
|
1966 |
"SELECT 1 FROM event WHERE user=%Q OR euser=%Q", |
|
b13b651…
|
drh
|
1967 |
zUserID, zUserID, zUserID |
|
b13b651…
|
drh
|
1968 |
); |
|
b13b651…
|
drh
|
1969 |
return rc; |
|
c00e912…
|
drh
|
1970 |
} |
|
07bfe3f…
|
drh
|
1971 |
|
|
07bfe3f…
|
drh
|
1972 |
/* |
|
07bfe3f…
|
drh
|
1973 |
** zEMail is an email address. (Example: "[email protected]".) This routine |
|
07bfe3f…
|
drh
|
1974 |
** searches for a user or subscriber that has that email address. If the |
|
07bfe3f…
|
drh
|
1975 |
** email address is used no-where in the system, return 0. If the email |
|
07bfe3f…
|
drh
|
1976 |
** address is assigned to a particular user return the UID for that user. |
|
07bfe3f…
|
drh
|
1977 |
** If the email address is used, but not by a particular user, return -1. |
|
07bfe3f…
|
drh
|
1978 |
*/ |
|
07bfe3f…
|
drh
|
1979 |
static int email_address_in_use(const char *zEMail){ |
|
07bfe3f…
|
drh
|
1980 |
int uid; |
|
275da70…
|
danield
|
1981 |
uid = db_int(0, |
|
07bfe3f…
|
drh
|
1982 |
"SELECT uid FROM user" |
|
07bfe3f…
|
drh
|
1983 |
" WHERE info LIKE '%%<%q>%%'", zEMail); |
|
07bfe3f…
|
drh
|
1984 |
if( uid>0 ){ |
|
07bfe3f…
|
drh
|
1985 |
if( db_exists("SELECT 1 FROM user WHERE uid=%d AND (" |
|
07bfe3f…
|
drh
|
1986 |
" cap GLOB '*[as]*' OR" |
|
07bfe3f…
|
drh
|
1987 |
" find_emailaddr(info)<>%Q COLLATE nocase)", |
|
07bfe3f…
|
drh
|
1988 |
uid, zEMail) ){ |
|
07bfe3f…
|
drh
|
1989 |
uid = -1; |
|
07bfe3f…
|
drh
|
1990 |
} |
|
07bfe3f…
|
drh
|
1991 |
} |
|
07bfe3f…
|
drh
|
1992 |
if( uid==0 && alert_tables_exist() ){ |
|
07bfe3f…
|
drh
|
1993 |
uid = db_int(0, |
|
07bfe3f…
|
drh
|
1994 |
"SELECT user.uid FROM subscriber JOIN user ON login=suname" |
|
07bfe3f…
|
drh
|
1995 |
" WHERE semail=%Q AND sverified", zEMail); |
|
07bfe3f…
|
drh
|
1996 |
if( uid ){ |
|
07bfe3f…
|
drh
|
1997 |
if( db_exists("SELECT 1 FROM user WHERE uid=%d AND " |
|
07bfe3f…
|
drh
|
1998 |
" cap GLOB '*[as]*'", |
|
07bfe3f…
|
drh
|
1999 |
uid) ){ |
|
07bfe3f…
|
drh
|
2000 |
uid = -1; |
|
07bfe3f…
|
drh
|
2001 |
} |
|
07bfe3f…
|
drh
|
2002 |
} |
|
07bfe3f…
|
drh
|
2003 |
} |
|
07bfe3f…
|
drh
|
2004 |
return uid; |
|
07bfe3f…
|
drh
|
2005 |
} |
|
07bfe3f…
|
drh
|
2006 |
|
|
07bfe3f…
|
drh
|
2007 |
/* |
|
07bfe3f…
|
drh
|
2008 |
** COMMAND: test-email-used |
|
07bfe3f…
|
drh
|
2009 |
** Usage: fossil test-email-used EMAIL ... |
|
275da70…
|
danield
|
2010 |
** |
|
07bfe3f…
|
drh
|
2011 |
** Given a list of email addresses, show the UID and LOGIN associated |
|
07bfe3f…
|
drh
|
2012 |
** with each one. |
|
07bfe3f…
|
drh
|
2013 |
*/ |
|
07bfe3f…
|
drh
|
2014 |
void test_email_used(void){ |
|
07bfe3f…
|
drh
|
2015 |
int i; |
|
07bfe3f…
|
drh
|
2016 |
db_find_and_open_repository(0, 0); |
|
07bfe3f…
|
drh
|
2017 |
verify_all_options(); |
|
07bfe3f…
|
drh
|
2018 |
if( g.argc<3 ){ |
|
07bfe3f…
|
drh
|
2019 |
usage("EMAIL ..."); |
|
07bfe3f…
|
drh
|
2020 |
} |
|
07bfe3f…
|
drh
|
2021 |
for(i=2; i<g.argc; i++){ |
|
07bfe3f…
|
drh
|
2022 |
const char *zEMail = g.argv[i]; |
|
07bfe3f…
|
drh
|
2023 |
int uid = email_address_in_use(zEMail); |
|
07bfe3f…
|
drh
|
2024 |
if( uid==0 ){ |
|
07bfe3f…
|
drh
|
2025 |
fossil_print("%s: not used\n", zEMail); |
|
07bfe3f…
|
drh
|
2026 |
}else if( uid<0 ){ |
|
07bfe3f…
|
drh
|
2027 |
fossil_print("%s: used but no password reset is available\n", zEMail); |
|
07bfe3f…
|
drh
|
2028 |
}else{ |
|
07bfe3f…
|
drh
|
2029 |
char *zLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); |
|
07bfe3f…
|
drh
|
2030 |
fossil_print("%s: UID %d (%s)\n", zEMail, uid, zLogin); |
|
07bfe3f…
|
drh
|
2031 |
fossil_free(zLogin); |
|
07bfe3f…
|
drh
|
2032 |
} |
|
07bfe3f…
|
drh
|
2033 |
} |
|
07bfe3f…
|
drh
|
2034 |
} |
|
275da70…
|
danield
|
2035 |
|
|
c00e912…
|
drh
|
2036 |
|
|
c00e912…
|
drh
|
2037 |
/* |
|
c00e912…
|
drh
|
2038 |
** Check an email address and confirm that it is valid for self-registration. |
|
c00e912…
|
drh
|
2039 |
** The email address is known already to be well-formed. Return true |
|
c00e912…
|
drh
|
2040 |
** if the email address is on the allowed list. |
|
c00e912…
|
drh
|
2041 |
** |
|
c00e912…
|
drh
|
2042 |
** The default behavior is that any valid email address is accepted. |
|
c00e912…
|
drh
|
2043 |
** But if the "auth-sub-email" setting exists and is not empty, then |
|
c00e912…
|
drh
|
2044 |
** it is a comma-separated list of GLOB patterns for email addresses |
|
c00e912…
|
drh
|
2045 |
** that are authorized to self-register. |
|
c00e912…
|
drh
|
2046 |
*/ |
|
c00e912…
|
drh
|
2047 |
int authorized_subscription_email(const char *zEAddr){ |
|
c00e912…
|
drh
|
2048 |
char *zGlob = db_get("auth-sub-email",0); |
|
c00e912…
|
drh
|
2049 |
char *zAddr; |
|
c00e912…
|
drh
|
2050 |
int rc; |
|
c00e912…
|
drh
|
2051 |
|
|
c00e912…
|
drh
|
2052 |
if( zGlob==0 || zGlob[0]==0 ) return 1; |
|
c00e912…
|
drh
|
2053 |
zGlob = fossil_strtolwr(fossil_strdup(zGlob)); |
|
dc86831…
|
drh
|
2054 |
zAddr = fossil_strtolwr(fossil_strdup(zEAddr)); |
|
dc86831…
|
drh
|
2055 |
rc = glob_multi_match(zGlob, zAddr); |
|
c00e912…
|
drh
|
2056 |
fossil_free(zGlob); |
|
c00e912…
|
drh
|
2057 |
fossil_free(zAddr); |
|
c00e912…
|
drh
|
2058 |
return rc!=0; |
|
9039a6a…
|
drh
|
2059 |
} |
|
9039a6a…
|
drh
|
2060 |
|
|
9039a6a…
|
drh
|
2061 |
/* |
|
9039a6a…
|
drh
|
2062 |
** WEBPAGE: register |
|
9039a6a…
|
drh
|
2063 |
** |
|
7ab0328…
|
drh
|
2064 |
** Page to allow users to self-register. The "self-register" setting |
|
7ab0328…
|
drh
|
2065 |
** must be enabled for this page to operate. |
|
9039a6a…
|
drh
|
2066 |
*/ |
|
9039a6a…
|
drh
|
2067 |
void register_page(void){ |
|
99fcc43…
|
drh
|
2068 |
const char *zUserID, *zPasswd, *zConfirm, *zEAddr; |
|
99fcc43…
|
drh
|
2069 |
const char *zDName; |
|
372c725…
|
drh
|
2070 |
unsigned int uSeed; |
|
4e18dba…
|
jan.nijtmans
|
2071 |
const char *zDecoded; |
|
99fcc43…
|
drh
|
2072 |
int iErrLine = -1; |
|
014bb2d…
|
mistachkin
|
2073 |
const char *zErr = 0; |
|
07bfe3f…
|
drh
|
2074 |
int uid = 0; /* User id with the same email */ |
|
c00e912…
|
drh
|
2075 |
int captchaIsCorrect = 0; /* True on a correct captcha */ |
|
c00e912…
|
drh
|
2076 |
char *zCaptcha = ""; /* Value of the captcha text */ |
|
2e30828…
|
drh
|
2077 |
char *zPerms; /* Permissions for the default user */ |
|
2e30828…
|
drh
|
2078 |
int canDoAlerts = 0; /* True if receiving email alerts is possible */ |
|
4c43f2c…
|
drh
|
2079 |
int doAlerts = 0; /* True if subscription is wanted too */ |
|
07bfe3f…
|
drh
|
2080 |
|
|
9039a6a…
|
drh
|
2081 |
if( !db_get_boolean("self-register", 0) ){ |
|
9039a6a…
|
drh
|
2082 |
style_header("Registration not possible"); |
|
9039a6a…
|
drh
|
2083 |
@ <p>This project does not allow user self-registration. Please contact the |
|
9039a6a…
|
drh
|
2084 |
@ project administrator to obtain an account.</p> |
|
112c713…
|
drh
|
2085 |
style_finish_page(); |
|
112c713…
|
drh
|
2086 |
return; |
|
112c713…
|
drh
|
2087 |
} |
|
07bfe3f…
|
drh
|
2088 |
if( P("pwreset")!=0 && login_self_password_reset_available() ){ |
|
07bfe3f…
|
drh
|
2089 |
/* The "Request Password Reset" button was pressed, so render the |
|
07bfe3f…
|
drh
|
2090 |
** "Request Password Reset" page instead of this one. */ |
|
07bfe3f…
|
drh
|
2091 |
login_reqpwreset_page(); |
|
07bfe3f…
|
drh
|
2092 |
return; |
|
07bfe3f…
|
drh
|
2093 |
} |
|
c00e912…
|
drh
|
2094 |
zPerms = db_get("default-perms", "u"); |
|
6ae9941…
|
drh
|
2095 |
login_check_credentials(); |
|
99fcc43…
|
drh
|
2096 |
|
|
2e30828…
|
drh
|
2097 |
/* Prompt the user for email alerts if this repository is configured for |
|
2e30828…
|
drh
|
2098 |
** email alerts and if the default permissions include "7" */ |
|
129ea22…
|
mistachkin
|
2099 |
canDoAlerts = alert_tables_exist() && (db_int(0, |
|
2e30828…
|
drh
|
2100 |
"SELECT fullcap(%Q) GLOB '*7*'", zPerms |
|
129ea22…
|
mistachkin
|
2101 |
) || db_get_boolean("selfreg-verify",0)); |
|
4c43f2c…
|
drh
|
2102 |
doAlerts = canDoAlerts && atoi(PD("alerts","1"))!=0; |
|
2e30828…
|
drh
|
2103 |
|
|
99fcc43…
|
drh
|
2104 |
zUserID = PDT("u",""); |
|
99fcc43…
|
drh
|
2105 |
zPasswd = PDT("p",""); |
|
99fcc43…
|
drh
|
2106 |
zConfirm = PDT("cp",""); |
|
99fcc43…
|
drh
|
2107 |
zEAddr = PDT("ea",""); |
|
99fcc43…
|
drh
|
2108 |
zDName = PDT("dn",""); |
|
99fcc43…
|
drh
|
2109 |
|
|
a4419c6…
|
drh
|
2110 |
/* Verify user imputs */ |
|
920ace1…
|
drh
|
2111 |
if( P("new")==0 || !cgi_csrf_safe(2) ){ |
|
99fcc43…
|
drh
|
2112 |
/* This is not a valid form submission. Fall through into |
|
99fcc43…
|
drh
|
2113 |
** the form display */ |
|
c00e912…
|
drh
|
2114 |
}else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ |
|
99fcc43…
|
drh
|
2115 |
iErrLine = 6; |
|
99fcc43…
|
drh
|
2116 |
zErr = "Incorrect CAPTCHA"; |
|
b13b651…
|
drh
|
2117 |
}else if( strlen(zUserID)<6 ){ |
|
99fcc43…
|
drh
|
2118 |
iErrLine = 1; |
|
b13b651…
|
drh
|
2119 |
zErr = "User ID too short. Must be at least 6 characters."; |
|
99fcc43…
|
drh
|
2120 |
}else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){ |
|
99fcc43…
|
drh
|
2121 |
iErrLine = 1; |
|
99fcc43…
|
drh
|
2122 |
zErr = "User ID may not contain spaces or special characters."; |
|
275da70…
|
danield
|
2123 |
}else if( sqlite3_strlike("anonymous%", zUserID, 0)==0 |
|
a7e9dd5…
|
drh
|
2124 |
|| sqlite3_strlike("nobody%", zUserID, 0)==0 |
|
a7e9dd5…
|
drh
|
2125 |
|| sqlite3_strlike("reader%", zUserID, 0)==0 |
|
a7e9dd5…
|
drh
|
2126 |
|| sqlite3_strlike("developer%", zUserID, 0)==0 |
|
a7e9dd5…
|
drh
|
2127 |
){ |
|
a7e9dd5…
|
drh
|
2128 |
iErrLine = 1; |
|
a7e9dd5…
|
drh
|
2129 |
zErr = "This User ID is reserved. Choose something different."; |
|
99fcc43…
|
drh
|
2130 |
}else if( zDName[0]==0 ){ |
|
99fcc43…
|
drh
|
2131 |
iErrLine = 2; |
|
99fcc43…
|
drh
|
2132 |
zErr = "Required"; |
|
99fcc43…
|
drh
|
2133 |
}else if( zEAddr[0]==0 ){ |
|
99fcc43…
|
drh
|
2134 |
iErrLine = 3; |
|
99fcc43…
|
drh
|
2135 |
zErr = "Required"; |
|
32a8d11…
|
drh
|
2136 |
}else if( email_address_is_valid(zEAddr,0)==0 ){ |
|
99fcc43…
|
drh
|
2137 |
iErrLine = 3; |
|
99fcc43…
|
drh
|
2138 |
zErr = "Not a valid email address"; |
|
c00e912…
|
drh
|
2139 |
}else if( authorized_subscription_email(zEAddr)==0 ){ |
|
c00e912…
|
drh
|
2140 |
iErrLine = 3; |
|
c00e912…
|
drh
|
2141 |
zErr = "Not an authorized email address"; |
|
99fcc43…
|
drh
|
2142 |
}else if( strlen(zPasswd)<6 ){ |
|
99fcc43…
|
drh
|
2143 |
iErrLine = 4; |
|
99fcc43…
|
drh
|
2144 |
zErr = "Password must be at least 6 characters long"; |
|
99fcc43…
|
drh
|
2145 |
}else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){ |
|
99fcc43…
|
drh
|
2146 |
iErrLine = 5; |
|
99fcc43…
|
drh
|
2147 |
zErr = "Passwords do not match"; |
|
07bfe3f…
|
drh
|
2148 |
}else if( (uid = email_address_in_use(zEAddr))!=0 ){ |
|
07bfe3f…
|
drh
|
2149 |
iErrLine = 3; |
|
07bfe3f…
|
drh
|
2150 |
zErr = "This email address is already associated with a user"; |
|
d425d23…
|
danield
|
2151 |
}else if( login_self_chosen_userid_already_exists(zUserID) ){ |
|
99fcc43…
|
drh
|
2152 |
iErrLine = 1; |
|
99fcc43…
|
drh
|
2153 |
zErr = "This User ID is already taken. Choose something different."; |
|
99fcc43…
|
drh
|
2154 |
}else{ |
|
a4419c6…
|
drh
|
2155 |
/* If all of the tests above have passed, that means that the submitted |
|
a4419c6…
|
drh
|
2156 |
** form contains valid data and we can proceed to create the new login */ |
|
99fcc43…
|
drh
|
2157 |
Blob sql; |
|
99fcc43…
|
drh
|
2158 |
int uid; |
|
99fcc43…
|
drh
|
2159 |
char *zPass = sha1_shared_secret(zPasswd, zUserID, 0); |
|
c00e912…
|
drh
|
2160 |
const char *zStartPerms = zPerms; |
|
c00e912…
|
drh
|
2161 |
if( db_get_boolean("selfreg-verify",0) ){ |
|
33d3bf3…
|
km
|
2162 |
/* If email verification is required for self-registration, initialize |
|
c00e912…
|
drh
|
2163 |
** the new user capabilities to just "7" (Sign up for email). The |
|
c00e912…
|
drh
|
2164 |
** full "default-perms" permissions will be added when they click |
|
c00e912…
|
drh
|
2165 |
** the verification link on the email they are sent. */ |
|
c00e912…
|
drh
|
2166 |
zStartPerms = "7"; |
|
c00e912…
|
drh
|
2167 |
} |
|
99fcc43…
|
drh
|
2168 |
blob_init(&sql, 0, 0); |
|
99fcc43…
|
drh
|
2169 |
blob_append_sql(&sql, |
|
99fcc43…
|
drh
|
2170 |
"INSERT INTO user(login,pw,cap,info,mtime)\n" |
|
99fcc43…
|
drh
|
2171 |
"VALUES(%Q,%Q,%Q," |
|
99fcc43…
|
drh
|
2172 |
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())", |
|
c00e912…
|
drh
|
2173 |
zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr); |
|
99fcc43…
|
drh
|
2174 |
fossil_free(zPass); |
|
f741baa…
|
drh
|
2175 |
db_unprotect(PROTECT_USER); |
|
99fcc43…
|
drh
|
2176 |
db_multi_exec("%s", blob_sql_text(&sql)); |
|
f741baa…
|
drh
|
2177 |
db_protect_pop(); |
|
99fcc43…
|
drh
|
2178 |
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID); |
|
6b7b323…
|
drh
|
2179 |
login_set_user_cookie(zUserID, uid, NULL, 0); |
|
4c43f2c…
|
drh
|
2180 |
if( doAlerts ){ |
|
2e30828…
|
drh
|
2181 |
/* Also make the new user a subscriber. */ |
|
2e30828…
|
drh
|
2182 |
Blob hdr, body; |
|
fc5c7d2…
|
drh
|
2183 |
AlertSender *pSender; |
|
2e30828…
|
drh
|
2184 |
const char *zCode; /* New subscriber code (in hex) */ |
|
2e30828…
|
drh
|
2185 |
const char *zGoto = P("g"); |
|
2e30828…
|
drh
|
2186 |
int nsub = 0; |
|
2e30828…
|
drh
|
2187 |
char ssub[20]; |
|
15e1529…
|
drh
|
2188 |
CapabilityString *pCap; |
|
15e1529…
|
drh
|
2189 |
pCap = capability_add(0, zPerms); |
|
15e1529…
|
drh
|
2190 |
capability_expand(pCap); |
|
2e30828…
|
drh
|
2191 |
ssub[nsub++] = 'a'; |
|
15e1529…
|
drh
|
2192 |
if( capability_has_any(pCap,"o") ) ssub[nsub++] = 'c'; |
|
15e1529…
|
drh
|
2193 |
if( capability_has_any(pCap,"2") ) ssub[nsub++] = 'f'; |
|
15e1529…
|
drh
|
2194 |
if( capability_has_any(pCap,"r") ) ssub[nsub++] = 't'; |
|
15e1529…
|
drh
|
2195 |
if( capability_has_any(pCap,"j") ) ssub[nsub++] = 'w'; |
|
2e30828…
|
drh
|
2196 |
ssub[nsub] = 0; |
|
15e1529…
|
drh
|
2197 |
capability_free(pCap); |
|
a4419c6…
|
drh
|
2198 |
/* Also add the user to the subscriber table. */ |
|
8a3dc1a…
|
drh
|
2199 |
zCode = db_text(0, |
|
2e30828…
|
drh
|
2200 |
"INSERT INTO subscriber(semail,suname," |
|
d7e10ce…
|
drh
|
2201 |
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)" |
|
d7e10ce…
|
drh
|
2202 |
" VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)" |
|
4c43f2c…
|
drh
|
2203 |
" ON CONFLICT(semail) DO UPDATE" |
|
8a3dc1a…
|
drh
|
2204 |
" SET suname=excluded.suname" |
|
8a3dc1a…
|
drh
|
2205 |
" RETURNING hex(subscriberCode);", |
|
2e30828…
|
drh
|
2206 |
/* semail */ zEAddr, |
|
2e30828…
|
drh
|
2207 |
/* suname */ zUserID, |
|
2e30828…
|
drh
|
2208 |
/* sverified */ 0, |
|
2e30828…
|
drh
|
2209 |
/* sdigest */ 0, |
|
2e30828…
|
drh
|
2210 |
/* ssub */ ssub, |
|
2e30828…
|
drh
|
2211 |
/* smip */ g.zIpAddr |
|
2e30828…
|
drh
|
2212 |
); |
|
4c43f2c…
|
drh
|
2213 |
if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q" |
|
4c43f2c…
|
drh
|
2214 |
" AND sverified", zEAddr) ){ |
|
4c43f2c…
|
drh
|
2215 |
/* This the case where the user was formerly a verified subscriber |
|
4c43f2c…
|
drh
|
2216 |
** and here they have also registered as a user as well. It is |
|
e2bdc10…
|
danield
|
2217 |
** not necessary to repeat the verification step */ |
|
1958448…
|
drh
|
2218 |
login_redirect_to_g(); |
|
4c43f2c…
|
drh
|
2219 |
} |
|
2e30828…
|
drh
|
2220 |
/* A verification email */ |
|
fc5c7d2…
|
drh
|
2221 |
pSender = alert_sender_new(0,0); |
|
2e30828…
|
drh
|
2222 |
blob_init(&hdr,0,0); |
|
2e30828…
|
drh
|
2223 |
blob_init(&body,0,0); |
|
2e30828…
|
drh
|
2224 |
blob_appendf(&hdr, "To: <%s>\n", zEAddr); |
|
2e30828…
|
drh
|
2225 |
blob_appendf(&hdr, "Subject: Subscription verification\n"); |
|
fc5c7d2…
|
drh
|
2226 |
alert_append_confirmation_message(&body, zCode); |
|
fc5c7d2…
|
drh
|
2227 |
alert_send(pSender, &hdr, &body, 0); |
|
2e30828…
|
drh
|
2228 |
style_header("Email Verification"); |
|
2e30828…
|
drh
|
2229 |
if( pSender->zErr ){ |
|
2e30828…
|
drh
|
2230 |
@ <h1>Internal Error</h1> |
|
2e30828…
|
drh
|
2231 |
@ <p>The following internal error was encountered while trying |
|
2e30828…
|
drh
|
2232 |
@ to send the confirmation email: |
|
2e30828…
|
drh
|
2233 |
@ <blockquote><pre> |
|
2e30828…
|
drh
|
2234 |
@ %h(pSender->zErr) |
|
2e30828…
|
drh
|
2235 |
@ </pre></blockquote> |
|
2e30828…
|
drh
|
2236 |
}else{ |
|
2e30828…
|
drh
|
2237 |
@ <p>An email has been sent to "%h(zEAddr)". That email contains a |
|
c00e912…
|
drh
|
2238 |
@ hyperlink that you must click to activate your account.</p> |
|
2e30828…
|
drh
|
2239 |
} |
|
fc5c7d2…
|
drh
|
2240 |
alert_sender_free(pSender); |
|
2e30828…
|
drh
|
2241 |
if( zGoto ){ |
|
2e30828…
|
drh
|
2242 |
@ <p><a href='%h(zGoto)'>Continue</a> |
|
2e30828…
|
drh
|
2243 |
} |
|
112c713…
|
drh
|
2244 |
style_finish_page(); |
|
2e30828…
|
drh
|
2245 |
return; |
|
2e30828…
|
drh
|
2246 |
} |
|
1958448…
|
drh
|
2247 |
login_redirect_to_g(); |
|
2e30828…
|
drh
|
2248 |
} |
|
2e30828…
|
drh
|
2249 |
|
|
2e30828…
|
drh
|
2250 |
/* Prepare the captcha. */ |
|
c00e912…
|
drh
|
2251 |
if( captchaIsCorrect ){ |
|
c00e912…
|
drh
|
2252 |
uSeed = strtoul(P("captchaseed"),0,10); |
|
c00e912…
|
drh
|
2253 |
}else{ |
|
c00e912…
|
drh
|
2254 |
uSeed = captcha_seed(); |
|
c00e912…
|
drh
|
2255 |
} |
|
8659d84…
|
drh
|
2256 |
zDecoded = captcha_decode(uSeed, 0); |
|
2e30828…
|
drh
|
2257 |
zCaptcha = captcha_render(zDecoded); |
|
2e30828…
|
drh
|
2258 |
|
|
2e30828…
|
drh
|
2259 |
style_header("Register"); |
|
2e30828…
|
drh
|
2260 |
/* Print out the registration form. */ |
|
8dd7542…
|
drh
|
2261 |
g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */ |
|
2e30828…
|
drh
|
2262 |
form_begin(0, "%R/register"); |
|
2e30828…
|
drh
|
2263 |
if( P("g") ){ |
|
f5482a0…
|
wyoung
|
2264 |
@ <input type="hidden" name="g" value="%h(P("g"))"> |
|
2e30828…
|
drh
|
2265 |
} |
|
f5482a0…
|
wyoung
|
2266 |
@ <p><input type="hidden" name="captchaseed" value="%u(uSeed)"> |
|
2e30828…
|
drh
|
2267 |
@ <table class="login_out"> |
|
2e30828…
|
drh
|
2268 |
@ <tr> |
|
7dd07b2…
|
drh
|
2269 |
@ <td class="form_label" align="right" id="uid">User ID:</td> |
|
7dd07b2…
|
drh
|
2270 |
@ <td><input aria-labelledby="uid" type="text" name="u" \ |
|
bc05e6c…
|
florian
|
2271 |
@ value="%h(zUserID)" size="30" autofocus></td> |
|
27769be…
|
drh
|
2272 |
@ |
|
2e30828…
|
drh
|
2273 |
if( iErrLine==1 ){ |
|
27769be…
|
drh
|
2274 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
2e30828…
|
drh
|
2275 |
} |
|
2e30828…
|
drh
|
2276 |
@ <tr> |
|
7dd07b2…
|
drh
|
2277 |
@ <td class="form_label" align="right" id="dpyname">Display Name:</td> |
|
7dd07b2…
|
drh
|
2278 |
@ <td><input aria-labelledby="dpyname" type="text" name="dn" \ |
|
7dd07b2…
|
drh
|
2279 |
@ value="%h(zDName)" size="30"></td> |
|
27769be…
|
drh
|
2280 |
@ </tr> |
|
2e30828…
|
drh
|
2281 |
if( iErrLine==2 ){ |
|
27769be…
|
drh
|
2282 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
2e30828…
|
drh
|
2283 |
} |
|
2e30828…
|
drh
|
2284 |
@ </tr> |
|
2e30828…
|
drh
|
2285 |
@ <tr> |
|
7dd07b2…
|
drh
|
2286 |
@ <td class="form_label" align="right" id="emaddr">Email Address:</td> |
|
7dd07b2…
|
drh
|
2287 |
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \ |
|
7dd07b2…
|
drh
|
2288 |
@ value="%h(zEAddr)" size="30"></td> |
|
27769be…
|
drh
|
2289 |
@ </tr> |
|
2e30828…
|
drh
|
2290 |
if( iErrLine==3 ){ |
|
07bfe3f…
|
drh
|
2291 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span> |
|
07bfe3f…
|
drh
|
2292 |
if( uid>0 && login_self_password_reset_available() ){ |
|
f5482a0…
|
wyoung
|
2293 |
@ <br> |
|
07bfe3f…
|
drh
|
2294 |
@ <input type="submit" name="pwreset" \ |
|
07bfe3f…
|
drh
|
2295 |
@ value="Request Password Reset For %h(zEAddr)"> |
|
07bfe3f…
|
drh
|
2296 |
} |
|
07bfe3f…
|
drh
|
2297 |
@ </td></tr> |
|
2e30828…
|
drh
|
2298 |
} |
|
2e30828…
|
drh
|
2299 |
if( canDoAlerts ){ |
|
2e30828…
|
drh
|
2300 |
int a = atoi(PD("alerts","1")); |
|
2e30828…
|
drh
|
2301 |
@ <tr> |
|
7dd07b2…
|
drh
|
2302 |
@ <td class="form_label" align="right" id="emalrt">Email Alerts?</td> |
|
7dd07b2…
|
drh
|
2303 |
@ <td><select aria-labelledby="emalrt" size='1' name='alerts'> |
|
2e30828…
|
drh
|
2304 |
@ <option value="1" %s(a?"selected":"")>Yes</option> |
|
2e30828…
|
drh
|
2305 |
@ <option value="0" %s(!a?"selected":"")>No</option> |
|
2e30828…
|
drh
|
2306 |
@ </select></td></tr> |
|
2e30828…
|
drh
|
2307 |
} |
|
2e30828…
|
drh
|
2308 |
@ <tr> |
|
7dd07b2…
|
drh
|
2309 |
@ <td class="form_label" align="right" id="pswd">Password:</td> |
|
7dd07b2…
|
drh
|
2310 |
@ <td><input aria-labelledby="pswd" type="password" name="p" \ |
|
49f68be…
|
drh
|
2311 |
@ value="%h(zPasswd)" size="30"> \ |
|
49f68be…
|
drh
|
2312 |
if( zPasswd[0]==0 ){ |
|
49f68be…
|
drh
|
2313 |
char *zRPW = fossil_random_password(12); |
|
49f68be…
|
drh
|
2314 |
@ Password suggestion: %z(zRPW)</td> |
|
49f68be…
|
drh
|
2315 |
}else{ |
|
49f68be…
|
drh
|
2316 |
@ </td> |
|
49f68be…
|
drh
|
2317 |
} |
|
27769be…
|
drh
|
2318 |
@ <tr> |
|
27769be…
|
drh
|
2319 |
if( iErrLine==4 ){ |
|
27769be…
|
drh
|
2320 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
27769be…
|
drh
|
2321 |
} |
|
2e30828…
|
drh
|
2322 |
@ <tr> |
|
7dd07b2…
|
drh
|
2323 |
@ <td class="form_label" align="right" id="pwcfrm">Confirm:</td> |
|
7dd07b2…
|
drh
|
2324 |
@ <td><input aria-labelledby="pwcfrm" type="password" name="cp" \ |
|
7dd07b2…
|
drh
|
2325 |
@ value="%h(zConfirm)" size="30"></td> |
|
27769be…
|
drh
|
2326 |
@ </tr> |
|
2e30828…
|
drh
|
2327 |
if( iErrLine==5 ){ |
|
27769be…
|
drh
|
2328 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
2e30828…
|
drh
|
2329 |
} |
|
2e30828…
|
drh
|
2330 |
@ <tr> |
|
7dd07b2…
|
drh
|
2331 |
@ <td class="form_label" align="right" id="cptcha">Captcha:</td> |
|
7dd07b2…
|
drh
|
2332 |
@ <td><input type="text" name="captcha" aria-labelledby="cptcha" \ |
|
c00e912…
|
drh
|
2333 |
@ value="%h(captchaIsCorrect?zDecoded:"")" size="30"> |
|
5a7d449…
|
drh
|
2334 |
captcha_speakit_button(uSeed, "Speak the captcha text"); |
|
5a7d449…
|
drh
|
2335 |
@ </td> |
|
27769be…
|
drh
|
2336 |
@ </tr> |
|
2e30828…
|
drh
|
2337 |
if( iErrLine==6 ){ |
|
27769be…
|
drh
|
2338 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
27769be…
|
drh
|
2339 |
} |
|
27769be…
|
drh
|
2340 |
@ <tr><td></td> |
|
f5482a0…
|
wyoung
|
2341 |
@ <td><input type="submit" name="new" value="Register"></td></tr> |
|
112c713…
|
drh
|
2342 |
@ </table> |
|
112c713…
|
drh
|
2343 |
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
|
112c713…
|
drh
|
2344 |
@ %h(zCaptcha) |
|
112c713…
|
drh
|
2345 |
@ </pre> |
|
112c713…
|
drh
|
2346 |
@ Enter this 8-letter code in the "Captcha" box above. |
|
112c713…
|
drh
|
2347 |
@ </td></tr></table></div> |
|
112c713…
|
drh
|
2348 |
@ </form> |
|
112c713…
|
drh
|
2349 |
style_finish_page(); |
|
112c713…
|
drh
|
2350 |
|
|
07bfe3f…
|
drh
|
2351 |
free(zCaptcha); |
|
07bfe3f…
|
drh
|
2352 |
} |
|
07bfe3f…
|
drh
|
2353 |
|
|
07bfe3f…
|
drh
|
2354 |
/* |
|
a90d3aa…
|
drh
|
2355 |
** WEBPAGE: reqpwreset |
|
07bfe3f…
|
drh
|
2356 |
** |
|
07bfe3f…
|
drh
|
2357 |
** A web page to request a password reset. |
|
a90d3aa…
|
drh
|
2358 |
** |
|
a90d3aa…
|
drh
|
2359 |
** A form is presented where the user can enter their email address |
|
a90d3aa…
|
drh
|
2360 |
** and a captcha. If the email address entered corresponds to a known |
|
a90d3aa…
|
drh
|
2361 |
** users, an email is sent to that address that contains a link to the |
|
a90d3aa…
|
drh
|
2362 |
** /resetpw page that allows the users to enter a new password. |
|
a90d3aa…
|
drh
|
2363 |
** |
|
a90d3aa…
|
drh
|
2364 |
** This page is only available if the self-pw-reset property is enabled |
|
a90d3aa…
|
drh
|
2365 |
** and email notifications are configured and operating. Password resets |
|
a90d3aa…
|
drh
|
2366 |
** are not available to users with Admin or Setup privilege. |
|
07bfe3f…
|
drh
|
2367 |
*/ |
|
07bfe3f…
|
drh
|
2368 |
void login_reqpwreset_page(void){ |
|
07bfe3f…
|
drh
|
2369 |
const char *zEAddr; |
|
07bfe3f…
|
drh
|
2370 |
const char *zDecoded; |
|
07bfe3f…
|
drh
|
2371 |
unsigned int uSeed; |
|
07bfe3f…
|
drh
|
2372 |
int iErrLine = -1; |
|
07bfe3f…
|
drh
|
2373 |
const char *zErr = 0; |
|
07bfe3f…
|
drh
|
2374 |
int uid = 0; /* User id with the email zEAddr */ |
|
07bfe3f…
|
drh
|
2375 |
int captchaIsCorrect = 0; /* True on a correct captcha */ |
|
07bfe3f…
|
drh
|
2376 |
char *zCaptcha = ""; /* Value of the captcha text */ |
|
07bfe3f…
|
drh
|
2377 |
|
|
07bfe3f…
|
drh
|
2378 |
if( !login_self_password_reset_available() ){ |
|
07bfe3f…
|
drh
|
2379 |
style_header("Password reset not possible"); |
|
07bfe3f…
|
drh
|
2380 |
@ <p>This project does not allow users to reset their own passwords. |
|
07bfe3f…
|
drh
|
2381 |
@ If you need a password reset, you will have to negotiate that directly |
|
07bfe3f…
|
drh
|
2382 |
@ with the project administrator. |
|
07bfe3f…
|
drh
|
2383 |
style_finish_page(); |
|
07bfe3f…
|
drh
|
2384 |
return; |
|
07bfe3f…
|
drh
|
2385 |
} |
|
07bfe3f…
|
drh
|
2386 |
zEAddr = PDT("ea",""); |
|
07bfe3f…
|
drh
|
2387 |
|
|
e2bdc10…
|
danield
|
2388 |
/* Verify user inputs */ |
|
ce8598b…
|
drh
|
2389 |
if( !cgi_csrf_safe(1) || P("reqpwreset")==0 ){ |
|
07bfe3f…
|
drh
|
2390 |
/* This is the initial display of the form. No processing or error |
|
07bfe3f…
|
drh
|
2391 |
** checking is to be done. Fall through into the form display |
|
ce8598b…
|
drh
|
2392 |
** |
|
ce8598b…
|
drh
|
2393 |
** cgi_csrf_safe(): Nothing interesting happens on this page without |
|
ce8598b…
|
drh
|
2394 |
** a valid captcha solution, so we only need to check referrer and that |
|
ce8598b…
|
drh
|
2395 |
** the request is a POST. |
|
07bfe3f…
|
drh
|
2396 |
*/ |
|
07bfe3f…
|
drh
|
2397 |
}else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ |
|
07bfe3f…
|
drh
|
2398 |
iErrLine = 2; |
|
07bfe3f…
|
drh
|
2399 |
zErr = "Incorrect CAPTCHA"; |
|
07bfe3f…
|
drh
|
2400 |
}else if( zEAddr[0]==0 ){ |
|
07bfe3f…
|
drh
|
2401 |
iErrLine = 1; |
|
07bfe3f…
|
drh
|
2402 |
zErr = "Required"; |
|
07bfe3f…
|
drh
|
2403 |
}else if( email_address_is_valid(zEAddr,0)==0 ){ |
|
07bfe3f…
|
drh
|
2404 |
iErrLine = 1; |
|
07bfe3f…
|
drh
|
2405 |
zErr = "Not a valid email address"; |
|
07bfe3f…
|
drh
|
2406 |
}else if( authorized_subscription_email(zEAddr)==0 ){ |
|
07bfe3f…
|
drh
|
2407 |
iErrLine = 1; |
|
07bfe3f…
|
drh
|
2408 |
zErr = "Not an authorized email address"; |
|
07bfe3f…
|
drh
|
2409 |
}else if( (uid = email_address_in_use(zEAddr))<=0 ){ |
|
07bfe3f…
|
drh
|
2410 |
iErrLine = 1; |
|
07bfe3f…
|
drh
|
2411 |
zErr = "This email address is not associated with a user who has " |
|
07bfe3f…
|
drh
|
2412 |
"password reset privileges."; |
|
07bfe3f…
|
drh
|
2413 |
}else if( login_set_uid(uid,0)==0 || g.perm.Admin || g.perm.Setup |
|
07bfe3f…
|
drh
|
2414 |
|| !g.perm.Password ){ |
|
07bfe3f…
|
drh
|
2415 |
iErrLine = 1; |
|
07bfe3f…
|
drh
|
2416 |
zErr = "This email address is not associated with a user who has " |
|
07bfe3f…
|
drh
|
2417 |
"password reset privileges."; |
|
07bfe3f…
|
drh
|
2418 |
}else{ |
|
07bfe3f…
|
drh
|
2419 |
|
|
07bfe3f…
|
drh
|
2420 |
/* If all of the tests above have passed, that means that the submitted |
|
07bfe3f…
|
drh
|
2421 |
** form contains valid data and we can proceed to issue the password |
|
07bfe3f…
|
drh
|
2422 |
** reset email. */ |
|
07bfe3f…
|
drh
|
2423 |
Blob hdr, body; |
|
07bfe3f…
|
drh
|
2424 |
AlertSender *pSender; |
|
07bfe3f…
|
drh
|
2425 |
char *zUrl = login_resetpw_suffix(uid, 0); |
|
07bfe3f…
|
drh
|
2426 |
pSender = alert_sender_new(0,0); |
|
07bfe3f…
|
drh
|
2427 |
blob_init(&hdr,0,0); |
|
07bfe3f…
|
drh
|
2428 |
blob_init(&body,0,0); |
|
07bfe3f…
|
drh
|
2429 |
blob_appendf(&hdr, "To: <%s>\n", zEAddr); |
|
07bfe3f…
|
drh
|
2430 |
blob_appendf(&hdr, "Subject: Password reset for %s\n", g.zBaseURL); |
|
07bfe3f…
|
drh
|
2431 |
blob_appendf(&body, |
|
07bfe3f…
|
drh
|
2432 |
"Someone has requested to reset the password for user \"%s\"\n", |
|
07bfe3f…
|
drh
|
2433 |
g.zLogin); |
|
07bfe3f…
|
drh
|
2434 |
blob_appendf(&body, "at %s.\n\n", g.zBaseURL); |
|
07bfe3f…
|
drh
|
2435 |
blob_appendf(&body, |
|
07bfe3f…
|
drh
|
2436 |
"If you did not request this password reset, ignore\n" |
|
07bfe3f…
|
drh
|
2437 |
"this email\n\n"); |
|
07bfe3f…
|
drh
|
2438 |
blob_appendf(&body, |
|
07bfe3f…
|
drh
|
2439 |
"To reset the password, visit the following link:\n\n" |
|
07bfe3f…
|
drh
|
2440 |
" %s/resetpw/%s\n\n", g.zBaseURL, zUrl); |
|
07bfe3f…
|
drh
|
2441 |
fossil_free(zUrl); |
|
07bfe3f…
|
drh
|
2442 |
alert_send(pSender, &hdr, &body, 0); |
|
07bfe3f…
|
drh
|
2443 |
style_header("Email Verification"); |
|
07bfe3f…
|
drh
|
2444 |
if( pSender->zErr ){ |
|
07bfe3f…
|
drh
|
2445 |
@ <h1>Internal Error</h1> |
|
07bfe3f…
|
drh
|
2446 |
@ <p>The following internal error was encountered while trying |
|
07bfe3f…
|
drh
|
2447 |
@ to send the confirmation email: |
|
07bfe3f…
|
drh
|
2448 |
@ <blockquote><pre> |
|
07bfe3f…
|
drh
|
2449 |
@ %h(pSender->zErr) |
|
07bfe3f…
|
drh
|
2450 |
@ </pre></blockquote> |
|
07bfe3f…
|
drh
|
2451 |
}else{ |
|
07bfe3f…
|
drh
|
2452 |
@ <p>An email containing a hyperlink that can be used to reset |
|
07bfe3f…
|
drh
|
2453 |
@ your password has been sent to "%h(zEAddr)".</p> |
|
07bfe3f…
|
drh
|
2454 |
} |
|
07bfe3f…
|
drh
|
2455 |
alert_sender_free(pSender); |
|
07bfe3f…
|
drh
|
2456 |
style_finish_page(); |
|
07bfe3f…
|
drh
|
2457 |
return; |
|
07bfe3f…
|
drh
|
2458 |
} |
|
07bfe3f…
|
drh
|
2459 |
|
|
07bfe3f…
|
drh
|
2460 |
/* Prepare the captcha. */ |
|
07bfe3f…
|
drh
|
2461 |
if( captchaIsCorrect ){ |
|
07bfe3f…
|
drh
|
2462 |
uSeed = strtoul(P("captchaseed"),0,10); |
|
07bfe3f…
|
drh
|
2463 |
}else{ |
|
07bfe3f…
|
drh
|
2464 |
uSeed = captcha_seed(); |
|
07bfe3f…
|
drh
|
2465 |
} |
|
8659d84…
|
drh
|
2466 |
zDecoded = captcha_decode(uSeed, 0); |
|
07bfe3f…
|
drh
|
2467 |
zCaptcha = captcha_render(zDecoded); |
|
07bfe3f…
|
drh
|
2468 |
|
|
07bfe3f…
|
drh
|
2469 |
style_header("Request Password Reset"); |
|
07bfe3f…
|
drh
|
2470 |
/* Print out the registration form. */ |
|
07bfe3f…
|
drh
|
2471 |
g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */ |
|
07bfe3f…
|
drh
|
2472 |
form_begin(0, "%R/reqpwreset"); |
|
f5482a0…
|
wyoung
|
2473 |
@ <p><input type="hidden" name="captchaseed" value="%u(uSeed)"> |
|
f5482a0…
|
wyoung
|
2474 |
@ <p><input type="hidden" name="reqpwreset" value="1"> |
|
07bfe3f…
|
drh
|
2475 |
@ <table class="login_out"> |
|
07bfe3f…
|
drh
|
2476 |
@ <tr> |
|
07bfe3f…
|
drh
|
2477 |
@ <td class="form_label" align="right" id="emaddr">Email Address:</td> |
|
07bfe3f…
|
drh
|
2478 |
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \ |
|
07bfe3f…
|
drh
|
2479 |
@ value="%h(zEAddr)" size="30"></td> |
|
07bfe3f…
|
drh
|
2480 |
@ </tr> |
|
07bfe3f…
|
drh
|
2481 |
if( iErrLine==1 ){ |
|
07bfe3f…
|
drh
|
2482 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
07bfe3f…
|
drh
|
2483 |
} |
|
07bfe3f…
|
drh
|
2484 |
@ <tr> |
|
07bfe3f…
|
drh
|
2485 |
@ <td class="form_label" align="right" id="cptcha">Captcha:</td> |
|
07bfe3f…
|
drh
|
2486 |
@ <td><input type="text" name="captcha" aria-labelledby="cptcha" \ |
|
07bfe3f…
|
drh
|
2487 |
@ value="%h(captchaIsCorrect?zDecoded:"")" size="30"> |
|
07bfe3f…
|
drh
|
2488 |
captcha_speakit_button(uSeed, "Speak the captcha text"); |
|
07bfe3f…
|
drh
|
2489 |
@ </td> |
|
07bfe3f…
|
drh
|
2490 |
@ </tr> |
|
07bfe3f…
|
drh
|
2491 |
if( iErrLine==2 ){ |
|
07bfe3f…
|
drh
|
2492 |
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
|
07bfe3f…
|
drh
|
2493 |
} |
|
07bfe3f…
|
drh
|
2494 |
@ <tr><td></td> |
|
07bfe3f…
|
drh
|
2495 |
@ <td><input type="submit" name="new" value="Request Password Reset"/>\ |
|
07bfe3f…
|
drh
|
2496 |
@ </td></tr> |
|
07bfe3f…
|
drh
|
2497 |
@ </table> |
|
07bfe3f…
|
drh
|
2498 |
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> |
|
07bfe3f…
|
drh
|
2499 |
@ %h(zCaptcha) |
|
07bfe3f…
|
drh
|
2500 |
@ </pre> |
|
07bfe3f…
|
drh
|
2501 |
@ Enter this 8-letter code in the "Captcha" box above. |
|
07bfe3f…
|
drh
|
2502 |
@ </td></tr></table></div> |
|
07bfe3f…
|
drh
|
2503 |
@ </form> |
|
07bfe3f…
|
drh
|
2504 |
style_finish_page(); |
|
a257fde…
|
drh
|
2505 |
free(zCaptcha); |
|
a257fde…
|
drh
|
2506 |
} |
|
a257fde…
|
drh
|
2507 |
|
|
a257fde…
|
drh
|
2508 |
/* |
|
a257fde…
|
drh
|
2509 |
** Run SQL on the repository database for every repository in our |
|
a257fde…
|
drh
|
2510 |
** login group. The SQL is run in a separate database connection. |
|
a257fde…
|
drh
|
2511 |
** |
|
a257fde…
|
drh
|
2512 |
** Any members of the login group whose repository database file |
|
a257fde…
|
drh
|
2513 |
** cannot be found is silently removed from the group. |
|
a257fde…
|
drh
|
2514 |
** |
|
a257fde…
|
drh
|
2515 |
** Error messages accumulate and are returned in *pzErrorMsg. The |
|
a257fde…
|
drh
|
2516 |
** memory used to hold these messages should be freed using |
|
a257fde…
|
drh
|
2517 |
** fossil_free() if one desired to avoid a memory leak. The |
|
a257fde…
|
drh
|
2518 |
** zPrefix and zSuffix strings surround each error message. |
|
a257fde…
|
drh
|
2519 |
** |
|
a257fde…
|
drh
|
2520 |
** Return the number of errors. |
|
a257fde…
|
drh
|
2521 |
*/ |
|
a257fde…
|
drh
|
2522 |
int login_group_sql( |
|
a257fde…
|
drh
|
2523 |
const char *zSql, /* The SQL to run */ |
|
a257fde…
|
drh
|
2524 |
const char *zPrefix, /* Prefix to each error message */ |
|
a257fde…
|
drh
|
2525 |
const char *zSuffix, /* Suffix to each error message */ |
|
a257fde…
|
drh
|
2526 |
char **pzErrorMsg /* Write error message here, if not NULL */ |
|
a257fde…
|
drh
|
2527 |
){ |
|
a257fde…
|
drh
|
2528 |
sqlite3 *pPeer; /* Connection to another database */ |
|
a257fde…
|
drh
|
2529 |
int nErr = 0; /* Number of errors seen so far */ |
|
a257fde…
|
drh
|
2530 |
int rc; /* Result code from subroutine calls */ |
|
a257fde…
|
drh
|
2531 |
char *zErr; /* SQLite error text */ |
|
a257fde…
|
drh
|
2532 |
char *zSelfCode; /* Project code for ourself */ |
|
a257fde…
|
drh
|
2533 |
Blob err; /* Accumulate errors here */ |
|
a257fde…
|
drh
|
2534 |
Stmt q; /* Query of all peer-* entries in CONFIG */ |
|
a257fde…
|
drh
|
2535 |
|
|
a257fde…
|
drh
|
2536 |
if( zPrefix==0 ) zPrefix = ""; |
|
a257fde…
|
drh
|
2537 |
if( zSuffix==0 ) zSuffix = ""; |
|
a257fde…
|
drh
|
2538 |
if( pzErrorMsg ) *pzErrorMsg = 0; |
|
f7ce03e…
|
drh
|
2539 |
zSelfCode = abbreviated_project_code(db_get("project-code", "x")); |
|
a257fde…
|
drh
|
2540 |
blob_zero(&err); |
|
45f3516…
|
jan.nijtmans
|
2541 |
db_prepare(&q, |
|
a257fde…
|
drh
|
2542 |
"SELECT name, value FROM config" |
|
a257fde…
|
drh
|
2543 |
" WHERE name GLOB 'peer-repo-*'" |
|
a257fde…
|
drh
|
2544 |
" AND name <> 'peer-repo-%q'" |
|
a257fde…
|
drh
|
2545 |
" ORDER BY +value", |
|
a257fde…
|
drh
|
2546 |
zSelfCode |
|
a257fde…
|
drh
|
2547 |
); |
|
a257fde…
|
drh
|
2548 |
while( db_step(&q)==SQLITE_ROW ){ |
|
a257fde…
|
drh
|
2549 |
const char *zRepoName = db_column_text(&q, 1); |
|
1772357…
|
drh
|
2550 |
if( file_size(zRepoName, ExtFILE)<0 ){ |
|
db0c512…
|
drh
|
2551 |
/* Silently remove non-existent repositories from the login group. */ |
|
a257fde…
|
drh
|
2552 |
const char *zLabel = db_column_text(&q, 0); |
|
f741baa…
|
drh
|
2553 |
db_unprotect(PROTECT_CONFIG); |
|
a257fde…
|
drh
|
2554 |
db_multi_exec( |
|
a257fde…
|
drh
|
2555 |
"DELETE FROM config WHERE name GLOB 'peer-*-%q'", |
|
a257fde…
|
drh
|
2556 |
&zLabel[10] |
|
a257fde…
|
drh
|
2557 |
); |
|
f741baa…
|
drh
|
2558 |
db_protect_pop(); |
|
a257fde…
|
drh
|
2559 |
continue; |
|
a257fde…
|
drh
|
2560 |
} |
|
19de4b5…
|
mistachkin
|
2561 |
rc = sqlite3_open_v2( |
|
19de4b5…
|
mistachkin
|
2562 |
zRepoName, &pPeer, |
|
19de4b5…
|
mistachkin
|
2563 |
SQLITE_OPEN_READWRITE, |
|
19de4b5…
|
mistachkin
|
2564 |
g.zVfsName |
|
19de4b5…
|
mistachkin
|
2565 |
); |
|
a257fde…
|
drh
|
2566 |
if( rc!=SQLITE_OK ){ |
|
a257fde…
|
drh
|
2567 |
blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, |
|
a257fde…
|
drh
|
2568 |
sqlite3_errmsg(pPeer), zSuffix); |
|
a257fde…
|
drh
|
2569 |
nErr++; |
|
a257fde…
|
drh
|
2570 |
sqlite3_close(pPeer); |
|
a257fde…
|
drh
|
2571 |
continue; |
|
a257fde…
|
drh
|
2572 |
} |
|
a257fde…
|
drh
|
2573 |
sqlite3_create_function(pPeer, "shared_secret", 3, SQLITE_UTF8, |
|
a257fde…
|
drh
|
2574 |
0, sha1_shared_secret_sql_function, 0, 0); |
|
2c95802…
|
jan.nijtmans
|
2575 |
sqlite3_create_function(pPeer, "now", 0,SQLITE_UTF8,0,db_now_function,0,0); |
|
74ecc4d…
|
drh
|
2576 |
sqlite3_busy_timeout(pPeer, 5000); |
|
a257fde…
|
drh
|
2577 |
zErr = 0; |
|
a257fde…
|
drh
|
2578 |
rc = sqlite3_exec(pPeer, zSql, 0, 0, &zErr); |
|
a257fde…
|
drh
|
2579 |
if( zErr ){ |
|
a257fde…
|
drh
|
2580 |
blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, zErr, zSuffix); |
|
a257fde…
|
drh
|
2581 |
sqlite3_free(zErr); |
|
a257fde…
|
drh
|
2582 |
nErr++; |
|
a257fde…
|
drh
|
2583 |
}else if( rc!=SQLITE_OK ){ |
|
a257fde…
|
drh
|
2584 |
blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, |
|
a257fde…
|
drh
|
2585 |
sqlite3_errmsg(pPeer), zSuffix); |
|
a257fde…
|
drh
|
2586 |
nErr++; |
|
a257fde…
|
drh
|
2587 |
} |
|
a257fde…
|
drh
|
2588 |
sqlite3_close(pPeer); |
|
a257fde…
|
drh
|
2589 |
} |
|
a257fde…
|
drh
|
2590 |
db_finalize(&q); |
|
a257fde…
|
drh
|
2591 |
if( pzErrorMsg && blob_size(&err)>0 ){ |
|
a257fde…
|
drh
|
2592 |
*pzErrorMsg = fossil_strdup(blob_str(&err)); |
|
a257fde…
|
drh
|
2593 |
} |
|
a257fde…
|
drh
|
2594 |
blob_reset(&err); |
|
a257fde…
|
drh
|
2595 |
fossil_free(zSelfCode); |
|
a257fde…
|
drh
|
2596 |
return nErr; |
|
a257fde…
|
drh
|
2597 |
} |
|
a257fde…
|
drh
|
2598 |
|
|
a257fde…
|
drh
|
2599 |
/* |
|
a257fde…
|
drh
|
2600 |
** Attempt to join a login-group. |
|
a257fde…
|
drh
|
2601 |
** |
|
a257fde…
|
drh
|
2602 |
** If problems arise, leave an error message in *pzErrMsg. |
|
a257fde…
|
drh
|
2603 |
*/ |
|
a257fde…
|
drh
|
2604 |
void login_group_join( |
|
a257fde…
|
drh
|
2605 |
const char *zRepo, /* Repository file in the login group */ |
|
c3ba504…
|
drh
|
2606 |
int bPwRequired, /* True if the login,password is required */ |
|
a257fde…
|
drh
|
2607 |
const char *zLogin, /* Login name for the other repo */ |
|
a257fde…
|
drh
|
2608 |
const char *zPassword, /* Password to prove we are authorized to join */ |
|
a257fde…
|
drh
|
2609 |
const char *zNewName, /* Name of new login group if making a new one */ |
|
a257fde…
|
drh
|
2610 |
char **pzErrMsg /* Leave an error message here */ |
|
a257fde…
|
drh
|
2611 |
){ |
|
a257fde…
|
drh
|
2612 |
Blob fullName; /* Blob for finding full pathnames */ |
|
a257fde…
|
drh
|
2613 |
sqlite3 *pOther; /* The other repository */ |
|
a257fde…
|
drh
|
2614 |
int rc; /* Return code from sqlite3 functions */ |
|
a257fde…
|
drh
|
2615 |
char *zOtherProjCode; /* Project code for pOther */ |
|
a257fde…
|
drh
|
2616 |
char *zSelfRepo; /* Name of our repository */ |
|
a257fde…
|
drh
|
2617 |
char *zSelfLabel; /* Project-name for our repository */ |
|
a257fde…
|
drh
|
2618 |
char *zSelfProjCode; /* Our project-code */ |
|
a257fde…
|
drh
|
2619 |
char *zSql; /* SQL to run on all peers */ |
|
a257fde…
|
drh
|
2620 |
const char *zSelf; /* The ATTACH name of our repository */ |
|
a257fde…
|
drh
|
2621 |
|
|
a257fde…
|
drh
|
2622 |
*pzErrMsg = 0; /* Default to no errors */ |
|
06aec61…
|
drh
|
2623 |
zSelf = "repository"; |
|
a257fde…
|
drh
|
2624 |
|
|
45f3516…
|
jan.nijtmans
|
2625 |
/* Get the full pathname of the other repository */ |
|
135ed93…
|
drh
|
2626 |
file_canonical_name(zRepo, &fullName, 0); |
|
49b0ff1…
|
drh
|
2627 |
zRepo = fossil_strdup(blob_str(&fullName)); |
|
a257fde…
|
drh
|
2628 |
blob_reset(&fullName); |
|
a257fde…
|
drh
|
2629 |
|
|
a257fde…
|
drh
|
2630 |
/* Get the full pathname for our repository. Also the project code |
|
a257fde…
|
drh
|
2631 |
** and project name for ourself. */ |
|
135ed93…
|
drh
|
2632 |
file_canonical_name(g.zRepositoryName, &fullName, 0); |
|
49b0ff1…
|
drh
|
2633 |
zSelfRepo = fossil_strdup(blob_str(&fullName)); |
|
a257fde…
|
drh
|
2634 |
blob_reset(&fullName); |
|
f7ce03e…
|
drh
|
2635 |
zSelfProjCode = db_get("project-code", "unknown"); |
|
a257fde…
|
drh
|
2636 |
zSelfLabel = db_get("project-name", 0); |
|
a257fde…
|
drh
|
2637 |
if( zSelfLabel==0 ){ |
|
a257fde…
|
drh
|
2638 |
zSelfLabel = zSelfProjCode; |
|
a257fde…
|
drh
|
2639 |
} |
|
a257fde…
|
drh
|
2640 |
|
|
a257fde…
|
drh
|
2641 |
/* Make sure we are not trying to join ourselves */ |
|
32ad9a1…
|
drh
|
2642 |
if( fossil_strcmp(zRepo, zSelfRepo)==0 ){ |
|
a257fde…
|
drh
|
2643 |
*pzErrMsg = mprintf("The \"other\" repository is the same as this one."); |
|
a257fde…
|
drh
|
2644 |
return; |
|
a257fde…
|
drh
|
2645 |
} |
|
a257fde…
|
drh
|
2646 |
|
|
a257fde…
|
drh
|
2647 |
/* Make sure the other repository is a valid Fossil database */ |
|
1772357…
|
drh
|
2648 |
if( file_size(zRepo, ExtFILE)<0 ){ |
|
a257fde…
|
drh
|
2649 |
*pzErrMsg = mprintf("repository file \"%s\" does not exist", zRepo); |
|
a257fde…
|
drh
|
2650 |
return; |
|
a257fde…
|
drh
|
2651 |
} |
|
19de4b5…
|
mistachkin
|
2652 |
rc = sqlite3_open_v2( |
|
19de4b5…
|
mistachkin
|
2653 |
zRepo, &pOther, |
|
19de4b5…
|
mistachkin
|
2654 |
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, |
|
19de4b5…
|
mistachkin
|
2655 |
g.zVfsName |
|
19de4b5…
|
mistachkin
|
2656 |
); |
|
a257fde…
|
drh
|
2657 |
if( rc!=SQLITE_OK ){ |
|
49b0ff1…
|
drh
|
2658 |
*pzErrMsg = fossil_strdup(sqlite3_errmsg(pOther)); |
|
a257fde…
|
drh
|
2659 |
}else{ |
|
a257fde…
|
drh
|
2660 |
rc = sqlite3_exec(pOther, "SELECT count(*) FROM user", 0, 0, pzErrMsg); |
|
a257fde…
|
drh
|
2661 |
} |
|
a257fde…
|
drh
|
2662 |
sqlite3_close(pOther); |
|
a257fde…
|
drh
|
2663 |
if( rc ) return; |
|
a257fde…
|
drh
|
2664 |
|
|
7c0f4ec…
|
jan.nijtmans
|
2665 |
/* Attach the other repository. Make sure the username/password is |
|
a257fde…
|
drh
|
2666 |
** valid and has Setup permission. |
|
a257fde…
|
drh
|
2667 |
*/ |
|
75bcb48…
|
mistachkin
|
2668 |
db_attach(zRepo, "other"); |
|
a257fde…
|
drh
|
2669 |
zOtherProjCode = db_text("x", "SELECT value FROM other.config" |
|
a257fde…
|
drh
|
2670 |
" WHERE name='project-code'"); |
|
c3ba504…
|
drh
|
2671 |
if( bPwRequired ){ |
|
c3ba504…
|
drh
|
2672 |
char *zPwHash; /* Password hash on pOther */ |
|
c3ba504…
|
drh
|
2673 |
zPwHash = sha1_shared_secret(zPassword, zLogin, zOtherProjCode); |
|
c3ba504…
|
drh
|
2674 |
if( !db_exists( |
|
c3ba504…
|
drh
|
2675 |
"SELECT 1 FROM other.user" |
|
c3ba504…
|
drh
|
2676 |
" WHERE login=%Q AND cap GLOB '*s*'" |
|
c3ba504…
|
drh
|
2677 |
" AND (pw=%Q OR pw=%Q)", |
|
c3ba504…
|
drh
|
2678 |
zLogin, zPassword, zPwHash) |
|
c3ba504…
|
drh
|
2679 |
){ |
|
c3ba504…
|
drh
|
2680 |
db_detach("other"); |
|
c3ba504…
|
drh
|
2681 |
*pzErrMsg = "The supplied username/password does not correspond to a" |
|
c3ba504…
|
drh
|
2682 |
" user Setup permission on the other repository."; |
|
c3ba504…
|
drh
|
2683 |
return; |
|
c3ba504…
|
drh
|
2684 |
} |
|
a257fde…
|
drh
|
2685 |
} |
|
a257fde…
|
drh
|
2686 |
|
|
a257fde…
|
drh
|
2687 |
/* Create all the necessary CONFIG table entries on both the |
|
a257fde…
|
drh
|
2688 |
** other repository and on our own repository. |
|
a257fde…
|
drh
|
2689 |
*/ |
|
f7ce03e…
|
drh
|
2690 |
zSelfProjCode = abbreviated_project_code(zSelfProjCode); |
|
a257fde…
|
drh
|
2691 |
zOtherProjCode = abbreviated_project_code(zOtherProjCode); |
|
e1962ef…
|
drh
|
2692 |
db_begin_transaction(); |
|
ca5a5c7…
|
stephan
|
2693 |
db_unprotect(PROTECT_CONFIG); |
|
a257fde…
|
drh
|
2694 |
db_multi_exec( |
|
49b0ff1…
|
drh
|
2695 |
"DELETE FROM \"%w\".config WHERE name GLOB 'peer-*';" |
|
49b0ff1…
|
drh
|
2696 |
"INSERT INTO \"%w\".config(name,value) VALUES('peer-repo-%q',%Q);" |
|
49b0ff1…
|
drh
|
2697 |
"INSERT INTO \"%w\".config(name,value) " |
|
a257fde…
|
drh
|
2698 |
" SELECT 'peer-name-%q', value FROM other.config" |
|
a257fde…
|
drh
|
2699 |
" WHERE name='project-name';", |
|
a257fde…
|
drh
|
2700 |
zSelf, |
|
a257fde…
|
drh
|
2701 |
zSelf, zOtherProjCode, zRepo, |
|
a257fde…
|
drh
|
2702 |
zSelf, zOtherProjCode |
|
a257fde…
|
drh
|
2703 |
); |
|
a257fde…
|
drh
|
2704 |
db_multi_exec( |
|
a257fde…
|
drh
|
2705 |
"INSERT OR IGNORE INTO other.config(name,value)" |
|
a257fde…
|
drh
|
2706 |
" VALUES('login-group-name',%Q);" |
|
a257fde…
|
drh
|
2707 |
"INSERT OR IGNORE INTO other.config(name,value)" |
|
a257fde…
|
drh
|
2708 |
" VALUES('login-group-code',lower(hex(randomblob(8))));", |
|
a257fde…
|
drh
|
2709 |
zNewName |
|
a257fde…
|
drh
|
2710 |
); |
|
a257fde…
|
drh
|
2711 |
db_multi_exec( |
|
49b0ff1…
|
drh
|
2712 |
"REPLACE INTO \"%w\".config(name,value)" |
|
a257fde…
|
drh
|
2713 |
" SELECT name, value FROM other.config" |
|
a257fde…
|
drh
|
2714 |
" WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'", |
|
a257fde…
|
drh
|
2715 |
zSelf |
|
a257fde…
|
drh
|
2716 |
); |
|
ca5a5c7…
|
stephan
|
2717 |
db_protect_pop(); |
|
a257fde…
|
drh
|
2718 |
db_end_transaction(0); |
|
a257fde…
|
drh
|
2719 |
db_multi_exec("DETACH other"); |
|
a257fde…
|
drh
|
2720 |
|
|
a257fde…
|
drh
|
2721 |
/* Propagate the changes to all other members of the login-group */ |
|
a257fde…
|
drh
|
2722 |
zSql = mprintf( |
|
a257fde…
|
drh
|
2723 |
"BEGIN;" |
|
1654456…
|
drh
|
2724 |
"REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());" |
|
1654456…
|
drh
|
2725 |
"REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());" |
|
a257fde…
|
drh
|
2726 |
"COMMIT;", |
|
a257fde…
|
drh
|
2727 |
zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo |
|
a257fde…
|
drh
|
2728 |
); |
|
f741baa…
|
drh
|
2729 |
db_unprotect(PROTECT_CONFIG); |
|
a257fde…
|
drh
|
2730 |
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
|
f741baa…
|
drh
|
2731 |
db_protect_pop(); |
|
a257fde…
|
drh
|
2732 |
fossil_free(zSql); |
|
a257fde…
|
drh
|
2733 |
} |
|
a257fde…
|
drh
|
2734 |
|
|
a257fde…
|
drh
|
2735 |
/* |
|
a257fde…
|
drh
|
2736 |
** Leave the login group that we are currently part of. |
|
a257fde…
|
drh
|
2737 |
*/ |
|
a257fde…
|
drh
|
2738 |
void login_group_leave(char **pzErrMsg){ |
|
a257fde…
|
drh
|
2739 |
char *zProjCode; |
|
a257fde…
|
drh
|
2740 |
char *zSql; |
|
a257fde…
|
drh
|
2741 |
|
|
a257fde…
|
drh
|
2742 |
*pzErrMsg = 0; |
|
f7ce03e…
|
drh
|
2743 |
zProjCode = abbreviated_project_code(db_get("project-code","x")); |
|
a257fde…
|
drh
|
2744 |
zSql = mprintf( |
|
a257fde…
|
drh
|
2745 |
"DELETE FROM config WHERE name GLOB 'peer-*-%q';" |
|
a257fde…
|
drh
|
2746 |
"DELETE FROM config" |
|
a257fde…
|
drh
|
2747 |
" WHERE name='login-group-name'" |
|
a257fde…
|
drh
|
2748 |
" AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;", |
|
a257fde…
|
drh
|
2749 |
zProjCode |
|
a257fde…
|
drh
|
2750 |
); |
|
a257fde…
|
drh
|
2751 |
fossil_free(zProjCode); |
|
f741baa…
|
drh
|
2752 |
db_unprotect(PROTECT_CONFIG); |
|
a257fde…
|
drh
|
2753 |
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
|
a257fde…
|
drh
|
2754 |
fossil_free(zSql); |
|
a257fde…
|
drh
|
2755 |
db_multi_exec( |
|
a257fde…
|
drh
|
2756 |
"DELETE FROM config " |
|
a257fde…
|
drh
|
2757 |
" WHERE name GLOB 'peer-*'" |
|
a257fde…
|
drh
|
2758 |
" OR name GLOB 'login-group-*';" |
|
a257fde…
|
drh
|
2759 |
); |
|
f741baa…
|
drh
|
2760 |
db_protect_pop(); |
|
c3ba504…
|
drh
|
2761 |
} |
|
c3ba504…
|
drh
|
2762 |
|
|
c3ba504…
|
drh
|
2763 |
/* |
|
fcec3ed…
|
drh
|
2764 |
** COMMAND: login-group* |
|
fcec3ed…
|
drh
|
2765 |
** |
|
b9107e4…
|
mgagnon
|
2766 |
** Usage: %fossil login-group ?SUBCOMMAND? ?OPTIONS? |
|
b9107e4…
|
mgagnon
|
2767 |
** |
|
b9107e4…
|
mgagnon
|
2768 |
** Run various subcommands to manage login-group related settings of the open |
|
b9107e4…
|
mgagnon
|
2769 |
** repository or of the repository identified by the -R or --repository option. |
|
b9107e4…
|
mgagnon
|
2770 |
** |
|
83bc81e…
|
mark
|
2771 |
** > fossil login-group ?-R REPO? |
|
83bc81e…
|
mark
|
2772 |
** |
|
bc36fdc…
|
danield
|
2773 |
** Show the login-group to which REPO, or if invoked from within a check-out |
|
bc36fdc…
|
danield
|
2774 |
** the repository on which the current check-out is based, belongs. |
|
bc36fdc…
|
danield
|
2775 |
** |
|
edf0355…
|
mgagnon
|
2776 |
** > fossil login-group join ?-R REPO? ?--name NAME? REPO2 |
|
bc36fdc…
|
danield
|
2777 |
** |
|
edf0355…
|
mgagnon
|
2778 |
** This command will either: (1) add the repository on which the current |
|
edf0355…
|
mgagnon
|
2779 |
** check-out is based, or the repository REPO specified with -R, to the |
|
edf0355…
|
mgagnon
|
2780 |
** login group where REPO2 is a member, in which case the optional --name |
|
edf0355…
|
mgagnon
|
2781 |
** argument is not required; or (2) create a new login group between the |
|
edf0355…
|
mgagnon
|
2782 |
** repository on which the current check-out is based, or the repository |
|
edf0355…
|
mgagnon
|
2783 |
** REPO specified with -R, and REPO2, in which case the new group NAME is |
|
edf0355…
|
mgagnon
|
2784 |
** determined by the mandatory --name option. In both cases, the specified |
|
edf0355…
|
mgagnon
|
2785 |
** repositories will first leave any group in which they are currently a |
|
edf0355…
|
mgagnon
|
2786 |
** member before joining the new login group. |
|
83bc81e…
|
mark
|
2787 |
** |
|
83bc81e…
|
mark
|
2788 |
** > fossil login-group leave ?-R REPO? |
|
83bc81e…
|
mark
|
2789 |
** |
|
bc36fdc…
|
danield
|
2790 |
** Take the repository REPO, or if invoked from within a check-out the |
|
bc36fdc…
|
danield
|
2791 |
** repository on which the current check-out is based, out of whatever |
|
83bc81e…
|
mark
|
2792 |
** login group it is a member. |
|
c3ba504…
|
drh
|
2793 |
** |
|
c3ba504…
|
drh
|
2794 |
** About Login Groups: |
|
83bc81e…
|
mark
|
2795 |
** |
|
83bc81e…
|
mark
|
2796 |
** A login-group is a set of repositories that share user credentials. |
|
c3ba504…
|
drh
|
2797 |
** If a user is logged into one member of the group, then that user can |
|
b9107e4…
|
mgagnon
|
2798 |
** access any other group member as long as they have an entry in the USER |
|
b9107e4…
|
mgagnon
|
2799 |
** table of that member. If a user changes their password using web |
|
b9107e4…
|
mgagnon
|
2800 |
** interface, their password is also automatically changed in every other |
|
b9107e4…
|
mgagnon
|
2801 |
** member of the login group. |
|
c3ba504…
|
drh
|
2802 |
*/ |
|
c3ba504…
|
drh
|
2803 |
void login_group_command(void){ |
|
c3ba504…
|
drh
|
2804 |
const char *zLGName; |
|
c3ba504…
|
drh
|
2805 |
const char *zCmd; |
|
c3ba504…
|
drh
|
2806 |
int nCmd; |
|
c3ba504…
|
drh
|
2807 |
Stmt q; |
|
b9107e4…
|
mgagnon
|
2808 |
db_find_and_open_repository(0, 0); |
|
c3ba504…
|
drh
|
2809 |
if( g.argc>2 ){ |
|
c3ba504…
|
drh
|
2810 |
zCmd = g.argv[2]; |
|
c3ba504…
|
drh
|
2811 |
nCmd = (int)strlen(zCmd); |
|
c3ba504…
|
drh
|
2812 |
if( strncmp(zCmd,"join",nCmd)==0 && nCmd>=1 ){ |
|
c3ba504…
|
drh
|
2813 |
const char *zNewName = find_option("name",0,1); |
|
edf0355…
|
mgagnon
|
2814 |
const char *zOther = 0; |
|
c3ba504…
|
drh
|
2815 |
char *zErr = 0; |
|
c3ba504…
|
drh
|
2816 |
verify_all_options(); |
|
edf0355…
|
mgagnon
|
2817 |
if( g.argc!=4 ){ |
|
769a765…
|
stephan
|
2818 |
fossil_fatal("unexpected argument count for \"login-group join\""); |
|
c3ba504…
|
drh
|
2819 |
} |
|
edf0355…
|
mgagnon
|
2820 |
zOther = g.argv[3]; |
|
535714e…
|
drh
|
2821 |
login_group_leave(&zErr); |
|
535714e…
|
drh
|
2822 |
sqlite3_free(zErr); |
|
535714e…
|
drh
|
2823 |
zErr = 0; |
|
c3ba504…
|
drh
|
2824 |
login_group_join(zOther,0,0,0,zNewName,&zErr); |
|
c3ba504…
|
drh
|
2825 |
if( zErr ){ |
|
c3ba504…
|
drh
|
2826 |
fossil_fatal("%s", zErr); |
|
c3ba504…
|
drh
|
2827 |
} |
|
c3ba504…
|
drh
|
2828 |
}else if( strncmp(zCmd,"leave",nCmd)==0 && nCmd>=1 ){ |
|
c3ba504…
|
drh
|
2829 |
verify_all_options(); |
|
c3ba504…
|
drh
|
2830 |
if( g.argc!=3 ){ |
|
c3ba504…
|
drh
|
2831 |
fossil_fatal("unknown extra arguments to \"login-group leave\""); |
|
c3ba504…
|
drh
|
2832 |
} |
|
c3ba504…
|
drh
|
2833 |
zLGName = login_group_name(); |
|
c3ba504…
|
drh
|
2834 |
if( zLGName ){ |
|
c3ba504…
|
drh
|
2835 |
char *zErr = 0; |
|
c3ba504…
|
drh
|
2836 |
fossil_print("Leaving login-group \"%s\"\n", zLGName); |
|
c3ba504…
|
drh
|
2837 |
login_group_leave(&zErr); |
|
535714e…
|
drh
|
2838 |
if( zErr ) fossil_fatal("Oops: %s", zErr); |
|
c3ba504…
|
drh
|
2839 |
return; |
|
c3ba504…
|
drh
|
2840 |
} |
|
c3ba504…
|
drh
|
2841 |
}else{ |
|
09c65d7…
|
wyoung
|
2842 |
fossil_fatal("unknown command \"%s\" - should be \"join\" or \"leave\"", |
|
c3ba504…
|
drh
|
2843 |
zCmd); |
|
c3ba504…
|
drh
|
2844 |
} |
|
c3ba504…
|
drh
|
2845 |
} |
|
c3ba504…
|
drh
|
2846 |
/* Show the current login group information */ |
|
c3ba504…
|
drh
|
2847 |
zLGName = login_group_name(); |
|
c3ba504…
|
drh
|
2848 |
if( zLGName==0 ){ |
|
c3ba504…
|
drh
|
2849 |
fossil_print("Not currently a part of any login-group\n"); |
|
c3ba504…
|
drh
|
2850 |
return; |
|
c3ba504…
|
drh
|
2851 |
} |
|
c3ba504…
|
drh
|
2852 |
fossil_print("Now part of login-group \"%s\" with:\n", zLGName); |
|
83bc81e…
|
mark
|
2853 |
db_prepare(&q, "SELECT value FROM config WHERE name LIKE 'peer-repo-%%'"); |
|
c3ba504…
|
drh
|
2854 |
while( db_step(&q)==SQLITE_ROW ){ |
|
c3ba504…
|
drh
|
2855 |
fossil_print(" %s\n", db_column_text(&q,0)); |
|
c3ba504…
|
drh
|
2856 |
} |
|
c3ba504…
|
drh
|
2857 |
db_finalize(&q); |
|
c3ba504…
|
drh
|
2858 |
|
|
dbda8d6…
|
drh
|
2859 |
} |