Fossil SCM

fossil-scm / src / user.c
Blame History Raw 836 lines
1
/*
2
** Copyright (c) 2006 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** Commands and procedures used for creating, processing, editing, and
19
** querying information about users.
20
*/
21
#include "config.h"
22
#include "user.h"
23
24
/*
25
** Strip leading and trailing space from a string and add the string
26
** onto the end of a blob.
27
*/
28
static void strip_string(Blob *pBlob, char *z){
29
int i;
30
blob_reset(pBlob);
31
while( fossil_isspace(*z) ){ z++; }
32
for(i=0; z[i]; i++){
33
if( z[i]=='\r' || z[i]=='\n' ){
34
while( i>0 && fossil_isspace(z[i-1]) ){ i--; }
35
z[i] = 0;
36
break;
37
}
38
if( z[i]>0 && z[i]<' ' ) z[i] = ' ';
39
}
40
blob_append(pBlob, z, -1);
41
}
42
43
#if defined(_WIN32) || (defined(__BIONIC__) && !defined(FOSSIL_HAVE_GETPASS))
44
#ifdef _WIN32
45
#include <conio.h>
46
#endif
47
48
/*
49
** getpass() for Windows and Android.
50
*/
51
static char *zPwdBuffer = 0;
52
static size_t nPwdBuffer = 0;
53
54
static char *getpass(const char *prompt){
55
char *zPwd;
56
size_t nPwd;
57
size_t i;
58
#if defined(_WIN32)
59
int useGetch = _isatty(_fileno(stderr));
60
#endif
61
62
if( zPwdBuffer==0 ){
63
zPwdBuffer = fossil_secure_alloc_page(&nPwdBuffer);
64
assert( zPwdBuffer );
65
}else{
66
fossil_secure_zero(zPwdBuffer, nPwdBuffer);
67
}
68
zPwd = zPwdBuffer;
69
nPwd = nPwdBuffer;
70
fputs(prompt,stderr);
71
fflush(stderr);
72
assert( zPwd!=0 );
73
assert( nPwd>0 );
74
for(i=0; i<nPwd-1; ++i){
75
#if defined(_WIN32)
76
zPwd[i] = useGetch ? _getch() : getc(stdin);
77
#else
78
zPwd[i] = getc(stdin);
79
#endif
80
if(zPwd[i]=='\r' || zPwd[i]=='\n'){
81
break;
82
}
83
/* BS or DEL */
84
else if(i>0 && (zPwd[i]==8 || zPwd[i]==127)){
85
i -= 2;
86
continue;
87
}
88
/* CTRL-C */
89
else if(zPwd[i]==3) {
90
i=0;
91
break;
92
}
93
/* ESC */
94
else if(zPwd[i]==27){
95
i=0;
96
break;
97
}
98
else{
99
#if defined(_WIN32)
100
if( useGetch )
101
#endif
102
fputc('*',stderr);
103
}
104
}
105
zPwd[i]='\0';
106
fputs("\n", stderr);
107
assert( zPwd==zPwdBuffer );
108
return zPwd;
109
}
110
void freepass(){
111
if( !zPwdBuffer ) return;
112
assert( nPwdBuffer>0 );
113
fossil_secure_free_page(zPwdBuffer, nPwdBuffer);
114
zPwdBuffer = 0;
115
nPwdBuffer = 0;
116
}
117
#endif
118
119
/*
120
** Scramble substitution matrix:
121
*/
122
static char aSubst[256];
123
124
/*
125
** Descramble the password
126
*/
127
static void userDescramble(char *z){
128
int i;
129
for(i=0; z[i]; i++) z[i] = aSubst[(unsigned char)z[i]];
130
}
131
132
/* Print a string in 5-letter groups */
133
static void printFive(const unsigned char *z){
134
int i;
135
for(i=0; z[i]; i++){
136
if( i>0 && (i%5)==0 ) putchar(' ');
137
putchar(z[i]);
138
}
139
putchar('\n');
140
}
141
142
/* Return a pseudo-random integer between 0 and N-1 */
143
static int randint(int N){
144
unsigned char x;
145
assert( N<256 );
146
sqlite3_randomness(1, &x);
147
return x % N;
148
}
149
150
/*
151
** Generate and print a random scrambling of letters a through z (omitting x)
152
** and set up the aSubst[] matrix to descramble.
153
*/
154
static void userGenerateScrambleCode(void){
155
unsigned char zOrig[30];
156
unsigned char zA[30];
157
unsigned char zB[30];
158
int nA = 25;
159
int nB = 0;
160
int i;
161
memcpy(zOrig, "abcdefghijklmnopqrstuvwyz", nA+1);
162
memcpy(zA, zOrig, nA+1);
163
assert( nA==(int)strlen((char*)zA) );
164
for(i=0; i<(int)sizeof(aSubst); i++) aSubst[i] = i;
165
printFive(zA);
166
while( nA>0 ){
167
int x = randint(nA);
168
zB[nB++] = zA[x];
169
zA[x] = zA[--nA];
170
}
171
assert( nB==25 );
172
zB[nB] = 0;
173
printFive(zB);
174
for(i=0; i<nB; i++) aSubst[zB[i]] = zOrig[i];
175
}
176
177
/*
178
** Return the value of the FOSSIL_SECURITY_LEVEL environment variable.
179
** Or return 0 if that variable does not exist.
180
*/
181
int fossil_security_level(void){
182
const char *zLevel = fossil_getenv("FOSSIL_SECURITY_LEVEL");
183
if( zLevel==0 ) return 0;
184
return atoi(zLevel);
185
}
186
187
188
/*
189
** Do a single prompt for a passphrase. Store the results in the blob.
190
**
191
**
192
** The return value is a pointer to a static buffer that is overwritten
193
** on subsequent calls to this same routine.
194
*/
195
static void prompt_for_passphrase(const char *zPrompt, Blob *pPassphrase){
196
char *z;
197
#if 0
198
*/
199
** If the FOSSIL_PWREADER environment variable is set, then it will
200
** be the name of a program that prompts the user for their password/
201
** passphrase in a secure manner. The program should take one or more
202
** arguments which are the prompts and should output the acquired
203
** passphrase as a single line on stdout. This function will read the
204
** output using popen().
205
**
206
** If FOSSIL_PWREADER is not set, or if it is not the name of an
207
** executable, then use the C-library getpass() routine.
208
*/
209
const char *zProg = fossil_getenv("FOSSIL_PWREADER");
210
if( zProg && zProg[0] ){
211
static char zPass[100];
212
Blob cmd;
213
FILE *in;
214
blob_zero(&cmd);
215
blob_appendf(&cmd, "%s \"Fossil Passphrase\" \"%s\"", zProg, zPrompt);
216
zPass[0] = 0;
217
in = popen(blob_str(&cmd), "r");
218
fgets(zPass, sizeof(zPass), in);
219
pclose(in);
220
blob_reset(&cmd);
221
z = zPass;
222
}else
223
#endif
224
if( fossil_security_level()>=2 ){
225
userGenerateScrambleCode();
226
z = getpass(zPrompt);
227
if( z ) userDescramble(z);
228
printf("\033[3A\033[J"); /* Erase previous three lines */
229
fflush(stdout);
230
}else{
231
z = getpass(zPrompt);
232
}
233
strip_string(pPassphrase, z);
234
}
235
236
/*
237
** Prompt the user for a password. Store the result in the pPassphrase
238
** blob.
239
**
240
** Behavior is controlled by the verify parameter:
241
**
242
** 0 Just ask once.
243
**
244
** 1 If the first answer is a non-empty string, ask for
245
** verification. Repeat if the two strings do not match.
246
**
247
** 2 Ask twice, repeat if the strings do not match.
248
*/
249
void prompt_for_password(
250
const char *zPrompt,
251
Blob *pPassphrase,
252
int verify
253
){
254
Blob secondTry;
255
blob_zero(pPassphrase);
256
blob_zero(&secondTry);
257
while(1){
258
prompt_for_passphrase(zPrompt, pPassphrase);
259
if( verify==0 ) break;
260
if( verify==1 && blob_size(pPassphrase)==0 ) break;
261
prompt_for_passphrase("Retype new password: ", &secondTry);
262
if( blob_compare(pPassphrase, &secondTry) ){
263
fossil_print("Passphrases do not match. Try again...\n");
264
}else{
265
break;
266
}
267
}
268
blob_reset(&secondTry);
269
}
270
271
/*
272
** Prompt to save Fossil user password
273
*/
274
int save_password_prompt(const char *passwd){
275
Blob x;
276
char c;
277
if( fossil_security_level()>=1 ) return 0;
278
prompt_user("remember password (Y/n)? ", &x);
279
c = blob_str(&x)[0];
280
blob_reset(&x);
281
return ( c!='n' && c!='N' );
282
}
283
284
/*
285
** Prompt for Fossil user password
286
*/
287
char *prompt_for_user_password(const char *zUser){
288
char *zPrompt = mprintf("\rpassword for %s: ", zUser);
289
char *zPw;
290
Blob x;
291
fossil_force_newline();
292
prompt_for_password(zPrompt, &x, 0);
293
fossil_free(zPrompt);
294
zPw = blob_str(&x)/*transfer ownership*/;
295
return zPw;
296
}
297
298
/*
299
** Prompt the user to enter a single line of text.
300
*/
301
void prompt_user(const char *zPrompt, Blob *pIn){
302
char *z;
303
char zLine[1000];
304
blob_init(pIn, 0, 0);
305
fossil_force_newline();
306
fossil_print("%s", zPrompt);
307
fflush(stdout);
308
z = fgets(zLine, sizeof(zLine), stdin);
309
if( z ){
310
int n = (int)strlen(z);
311
if( n>0 && z[n-1]=='\n' ) fossil_new_line_started();
312
strip_string(pIn, z);
313
}
314
}
315
316
/*
317
** COMMAND: user*
318
**
319
** Usage: %fossil user SUBCOMMAND ... ?-R|--repository REPO?
320
**
321
** Run various subcommands on users of the open repository or of
322
** the repository identified by the -R or --repository option.
323
**
324
** > fossil user capabilities USERNAME ?STRING?
325
**
326
** Query or set the capabilities for user USERNAME
327
**
328
** > fossil user contact USERNAME ?CONTACT-INFO?
329
**
330
** Query or set contact information for user USERNAME
331
**
332
** > fossil user default ?OPTIONS? ?USERNAME?
333
**
334
** Query or set the default user. The default user is the
335
** user for command-line interaction. If USERNAME is an
336
** empty string, then the default user is unset from the
337
** repository and will subsequently be determined by the -U
338
** command-line option or by environment variables
339
** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
340
** OPTIONS:
341
**
342
** -v|--verbose Show how the default user is computed
343
**
344
** > fossil user list | ls
345
**
346
** List all users known to the repository
347
**
348
** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD?
349
**
350
** Create a new user in the repository. Users can never be
351
** deleted. They can be denied all access but they must continue
352
** to exist in the database.
353
**
354
** > fossil user password USERNAME ?PASSWORD?
355
**
356
** Change the web access password for a user.
357
*/
358
void user_cmd(void){
359
int n;
360
db_find_and_open_repository(0, 0);
361
if( g.argc<3 ){
362
usage("capabilities|contact|default|list|new|password ...");
363
}
364
n = strlen(g.argv[2]);
365
if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
366
Blob passwd, login, caps, contact;
367
char *zPw;
368
blob_init(&caps, db_get("default-perms", 0), -1);
369
370
if( g.argc>=4 ){
371
blob_init(&login, g.argv[3], -1);
372
}else{
373
prompt_user("login: ", &login);
374
}
375
if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
376
fossil_fatal("user %b already exists", &login);
377
}
378
if( g.argc>=5 ){
379
blob_init(&contact, g.argv[4], -1);
380
}else{
381
prompt_user("contact-info: ", &contact);
382
}
383
if( g.argc>=6 ){
384
blob_init(&passwd, g.argv[5], -1);
385
}else{
386
prompt_for_password("password: ", &passwd, 1);
387
}
388
zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0);
389
db_unprotect(PROTECT_USER);
390
db_multi_exec(
391
"INSERT INTO user(login,pw,cap,info,mtime)"
392
"VALUES(%B,%Q,%B,%B,now())",
393
&login, zPw, &caps, &contact
394
);
395
db_protect_pop();
396
free(zPw);
397
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
398
int eVerbose = find_option("verbose","v",0)!=0;
399
verify_all_options();
400
if( g.argc>3 ){
401
const char *zUser = g.argv[3];
402
if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
403
db_begin_transaction();
404
if( g.localOpen ){
405
db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
406
}
407
db_unset("default-user",0);
408
db_commit_transaction();
409
}else{
410
if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
411
fossil_fatal("no such user: %s", g.argv[3]);
412
}
413
if( g.localOpen ){
414
db_lset("default-user", g.argv[3]);
415
}else{
416
db_set("default-user", g.argv[3], 0);
417
}
418
}
419
}
420
if( g.argc==3 || eVerbose ){
421
int eHow = user_select();
422
const char *zHow = "???";
423
switch( eHow ){
424
case 1: zHow = "-U option"; break;
425
case 2: zHow = "previously set"; break;
426
case 3: zHow = "local check-out"; break;
427
case 4: zHow = "repository"; break;
428
case 5: zHow = "FOSSIL_USER"; break;
429
case 6: zHow = "USER"; break;
430
case 7: zHow = "LOGNAME"; break;
431
case 8: zHow = "USERNAME"; break;
432
case 9: zHow = "URL"; break;
433
}
434
if( eVerbose ){
435
fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
436
}else{
437
fossil_print("%s\n", g.zLogin);
438
}
439
}
440
}else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
441
( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
442
Stmt q;
443
db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
444
while( db_step(&q)==SQLITE_ROW ){
445
fossil_print("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1));
446
}
447
db_finalize(&q);
448
}else if( n>=2 && strncmp(g.argv[2],"password",2)==0 ){
449
char *zPrompt;
450
int uid;
451
Blob pw;
452
if( g.argc!=4 && g.argc!=5 ) usage("password USERNAME ?NEW-PASSWORD?");
453
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
454
if( uid==0 ){
455
fossil_fatal("no such user: %s", g.argv[3]);
456
}
457
if( g.argc==5 ){
458
blob_init(&pw, g.argv[4], -1);
459
}else{
460
zPrompt = mprintf("New password for %s: ", g.argv[3]);
461
prompt_for_password(zPrompt, &pw, 1);
462
}
463
if( blob_size(&pw)==0 ){
464
fossil_print("password unchanged\n");
465
}else{
466
char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
467
db_unprotect(PROTECT_USER);
468
db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
469
zSecret, uid);
470
db_protect_pop();
471
fossil_free(zSecret);
472
}
473
}else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
474
int uid;
475
if( g.argc!=4 && g.argc!=5 ){
476
usage("capabilities USERNAME ?PERMISSIONS?");
477
}
478
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
479
if( uid==0 ){
480
fossil_fatal("no such user: %s", g.argv[3]);
481
}
482
if( g.argc==5 ){
483
db_unprotect(PROTECT_USER);
484
db_multi_exec(
485
"UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
486
g.argv[4], uid
487
);
488
db_protect_pop();
489
}
490
fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
491
}else if( n>=2 && strncmp(g.argv[2], "contact", 2)==0 ){
492
int uid;
493
if( g.argc!=4 && g.argc!=5 ){
494
usage("contact USERNAME ?CONTACT-INFO?");
495
}
496
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
497
if( uid==0 ){
498
fossil_fatal("no such user: %s", g.argv[3]);
499
}
500
if( g.argc==5 ){
501
db_unprotect(PROTECT_USER);
502
db_multi_exec(
503
"UPDATE user SET info=%Q, mtime=now() WHERE uid=%d",
504
g.argv[4], uid
505
);
506
db_protect_pop();
507
}
508
fossil_print("%s\n", db_text(0, "SELECT info FROM user WHERE uid=%d", uid));
509
}else{
510
fossil_fatal("user subcommand should be one of: "
511
"capabilities contact default list new password");
512
}
513
}
514
515
/*
516
** Attempt to set the user to zLogin
517
*/
518
static int attempt_user(const char *zLogin){
519
int uid;
520
521
if( zLogin==0 ){
522
return 0;
523
}
524
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
525
if( uid ){
526
g.userUid = uid;
527
g.zLogin = fossil_strdup(zLogin);
528
return 1;
529
}
530
return 0;
531
}
532
533
/*
534
** Figure out what user is at the controls.
535
**
536
** (1) Use the --user and -U command-line options.
537
**
538
** (2) The name used for login (if there was a login).
539
**
540
** (3) If the local database is open, check in VVAR.
541
**
542
** (4) Check the default-user in the repository
543
**
544
** (5) Try the FOSSIL_USER environment variable.
545
**
546
** (6) Try the USER environment variable.
547
**
548
** (7) Try the LOGNAME environment variable.
549
**
550
** (8) Try the USERNAME environment variable.
551
**
552
** (9) Check if the user can be extracted from the remote URL.
553
**
554
** The user name is stored in g.zLogin. The uid is in g.userUid.
555
*/
556
int user_select(void){
557
UrlData url;
558
if( g.userUid ) return 1;
559
if( g.zLogin ){
560
if( attempt_user(g.zLogin)==0 ){
561
fossil_fatal("no such user: %s", g.zLogin);
562
}else{
563
return 2;
564
}
565
}
566
567
if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
568
569
if( attempt_user(db_get("default-user", 0)) ) return 4;
570
571
if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
572
573
if( attempt_user(fossil_getenv("USER")) ) return 6;
574
575
if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
576
577
if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
578
579
memset(&url, 0, sizeof(url));
580
url_parse_local(0, URL_USE_CONFIG, &url);
581
if( url.user && attempt_user(url.user) ) return 9;
582
583
fossil_print(
584
"Cannot figure out who you are! Consider using the --user\n"
585
"command line option, setting your USER environment variable,\n"
586
"or setting a default user with \"fossil user default USER\".\n"
587
);
588
fossil_fatal("cannot determine user");
589
}
590
591
/*
592
** COMMAND: test-usernames
593
**
594
** Usage: %fossil test-usernames
595
**
596
** Print details about sources of fossil usernames.
597
*/
598
void test_usernames_cmd(void){
599
db_find_and_open_repository(0, 0);
600
601
fossil_print("Initial g.zLogin: %s\n", g.zLogin);
602
fossil_print("Initial g.userUid: %d\n", g.userUid);
603
fossil_print("check-out default-user: %s\n", g.localOpen ?
604
db_lget("default-user","") : "<<no open check-out>>");
605
fossil_print("default-user: %s\n", db_get("default-user",""));
606
fossil_print("FOSSIL_USER: %s\n", fossil_getenv("FOSSIL_USER"));
607
fossil_print("USER: %s\n", fossil_getenv("USER"));
608
fossil_print("LOGNAME: %s\n", fossil_getenv("LOGNAME"));
609
fossil_print("USERNAME: %s\n", fossil_getenv("USERNAME"));
610
url_parse(0, URL_USE_CONFIG);
611
fossil_print("URL user: %s\n", g.url.user);
612
user_select();
613
fossil_print("Final g.zLogin: %s\n", g.zLogin);
614
fossil_print("Final g.userUid: %d\n", g.userUid);
615
}
616
617
618
/*
619
** Make sure the USER table is up-to-date. It should contain
620
** the "JX" column (as of version 2.21). If it does not, add it.
621
**
622
** The "JX" column is intended to hold a JSON object containing optional
623
** key-value pairs.
624
*/
625
void user_update_user_table(void){
626
if( db_table_has_column("repository","user","jx")==0 ){
627
db_multi_exec("ALTER TABLE repository.user"
628
" ADD COLUMN jx TEXT DEFAULT '{}';");
629
}
630
}
631
632
/*
633
** COMMAND: test-hash-passwords
634
**
635
** Usage: %fossil test-hash-passwords REPOSITORY
636
**
637
** Convert all local password storage to use a SHA1 hash of the password
638
** rather than cleartext. Passwords that are already stored as the SHA1
639
** has are unchanged.
640
*/
641
void user_hash_passwords_cmd(void){
642
if( g.argc!=3 ) usage("REPOSITORY");
643
db_open_repository(g.argv[2]);
644
sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
645
sha1_shared_secret_sql_function, 0, 0);
646
db_unprotect(PROTECT_ALL);
647
db_multi_exec(
648
"UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
649
" WHERE length(pw)>0 AND length(pw)!=40"
650
);
651
}
652
653
/*
654
** Ensure that the password for a user is hashed.
655
*/
656
void hash_user_password(const char *zUser){
657
sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
658
sha1_shared_secret_sql_function, 0, 0);
659
db_unprotect(PROTECT_USER);
660
db_multi_exec(
661
"UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
662
" WHERE login=%Q AND length(pw)>0 AND length(pw)!=40", zUser
663
);
664
db_protect_pop();
665
}
666
667
/*
668
** COMMAND: test-prompt-user
669
**
670
** Usage: %fossil test-prompt-user PROMPT
671
**
672
** Prompts the user for input and then prints it verbatim (i.e. without
673
** a trailing line terminator).
674
*/
675
void test_prompt_user_cmd(void){
676
Blob answer;
677
if( g.argc!=3 ) usage("PROMPT");
678
prompt_user(g.argv[2], &answer);
679
fossil_print("%s\n", blob_str(&answer));
680
}
681
682
/*
683
** COMMAND: test-prompt-password
684
**
685
** Usage: %fossil test-prompt-password PROMPT VERIFY
686
**
687
** Prompts the user for a password and then prints it verbatim.
688
**
689
** Behavior is controlled by the VERIFY parameter:
690
**
691
** 0 Just ask once.
692
**
693
** 1 If the first answer is a non-empty string, ask for
694
** verification. Repeat if the two strings do not match.
695
**
696
** 2 Ask twice, repeat if the strings do not match.
697
698
*/
699
void test_prompt_password_cmd(void){
700
Blob answer;
701
int iVerify = 0;
702
if( g.argc!=4 ) usage("PROMPT VERIFY");
703
iVerify = atoi(g.argv[3]);
704
prompt_for_password(g.argv[2], &answer, iVerify);
705
fossil_print("[%s]\n", blob_str(&answer));
706
}
707
708
/*
709
** WEBPAGE: access_log
710
** WEBPAGE: user_log
711
**
712
** Show login attempts, including timestamp and IP address.
713
** Requires Admin privileges.
714
**
715
** Query parameters:
716
**
717
** y=N 1: success only. 2: failure only. 3: both (default: 3)
718
** n=N Number of entries to show (default: 200)
719
** o=N Skip this many entries (default: 0)
720
*/
721
void user_log_page(void){
722
int y = atoi(PD("y","3"));
723
int n = atoi(PD("n","200"));
724
int skip = atoi(PD("o","0"));
725
const char *zUser = P("u");
726
Blob sql;
727
Stmt q;
728
int cnt = 0;
729
int rc;
730
int fLogEnabled;
731
732
login_check_credentials();
733
if( !g.perm.Admin ){ login_needed(0); return; }
734
create_accesslog_table();
735
736
737
if( P("delall") && P("delallbtn") ){
738
db_multi_exec("DELETE FROM accesslog");
739
cgi_redirectf("%R/user_log?y=%d&n=%d&o=%o", y, n, skip);
740
return;
741
}
742
if( P("delanon") && P("delanonbtn") ){
743
db_multi_exec("DELETE FROM accesslog WHERE uname='anonymous'");
744
cgi_redirectf("%R/user_log?y=%d&n=%d&o=%o", y, n, skip);
745
return;
746
}
747
if( P("delfail") && P("delfailbtn") ){
748
db_multi_exec("DELETE FROM accesslog WHERE NOT success");
749
cgi_redirectf("%R/user_log?y=%d&n=%d&o=%o", y, n, skip);
750
return;
751
}
752
if( P("delold") && P("deloldbtn") ){
753
db_multi_exec("DELETE FROM accesslog WHERE rowid in"
754
"(SELECT rowid FROM accesslog ORDER BY rowid DESC"
755
" LIMIT -1 OFFSET 200)");
756
cgi_redirectf("%R/user_log?y=%d&n=%d", y, n);
757
return;
758
}
759
style_header("User Log");
760
style_submenu_element("Log-Menu", "setup-logmenu");
761
762
blob_zero(&sql);
763
blob_append_sql(&sql,
764
"SELECT uname, ipaddr, datetime(mtime,toLocal()), success"
765
" FROM accesslog"
766
);
767
if( zUser ){
768
blob_append_sql(&sql, " WHERE uname=%Q", zUser);
769
n = 1000000000;
770
skip = 0;
771
}else if( y==1 ){
772
blob_append(&sql, " WHERE success", -1);
773
}else if( y==2 ){
774
blob_append(&sql, " WHERE NOT success", -1);
775
}
776
blob_append_sql(&sql," ORDER BY rowid DESC LIMIT %d OFFSET %d", n+1, skip);
777
if( skip ){
778
style_submenu_element("Newer", "%R/user_log?o=%d&n=%d&y=%d",
779
skip>=n ? skip-n : 0, n, y);
780
}
781
rc = db_prepare_ignore_error(&q, "%s", blob_sql_text(&sql));
782
fLogEnabled = db_get_boolean("access-log", 1);
783
@ <div align="center">User logging is %s(fLogEnabled?"on":"off").
784
@ (Change this on the <a href="setup_settings">settings</a> page.)</div>
785
@ <table border="1" cellpadding="5" class="sortable" align="center" \
786
@ data-column-types='Ttt' data-init-sort='1'>
787
@ <thead><tr><th width="33%%">Date</th><th width="34%%">User</th>
788
@ <th width="33%%">IP Address</th></tr></thead><tbody>
789
while( rc==SQLITE_OK && db_step(&q)==SQLITE_ROW ){
790
const char *zName = db_column_text(&q, 0);
791
const char *zIP = db_column_text(&q, 1);
792
const char *zDate = db_column_text(&q, 2);
793
int bSuccess = db_column_int(&q, 3);
794
cnt++;
795
if( cnt>n ){
796
style_submenu_element("Older", "%R/user_log?o=%d&n=%d&y=%d",
797
skip+n, n, y);
798
break;
799
}
800
if( bSuccess ){
801
@ <tr>
802
}else{
803
@ <tr bgcolor="#ffacc0">
804
}
805
@ <td>%s(zDate)</td><td>%h(zName)</td><td>%h(zIP)</td></tr>
806
}
807
if( skip>0 || cnt>n ){
808
style_submenu_element("All", "%R/user_log?n=10000000");
809
}
810
@ </tbody></table>
811
db_finalize(&q);
812
@ <hr>
813
@ <form method="post" action="%R/user_log">
814
@ <label><input type="checkbox" name="delold">
815
@ Delete all but the most recent 200 entries</input></label>
816
@ <input type="submit" name="deloldbtn" value="Delete"></input>
817
@ </form>
818
@ <form method="post" action="%R/user_log">
819
@ <label><input type="checkbox" name="delanon">
820
@ Delete all entries for user "anonymous"</input></label>
821
@ <input type="submit" name="delanonbtn" value="Delete"></input>
822
@ </form>
823
@ <form method="post" action="%R/user_log">
824
@ <label><input type="checkbox" name="delfail">
825
@ Delete all failed login attempts</input></label>
826
@ <input type="submit" name="delfailbtn" value="Delete"></input>
827
@ </form>
828
@ <form method="post" action="%R/user_log">
829
@ <label><input type="checkbox" name="delall">
830
@ Delete all entries</input></label>
831
@ <input type="submit" name="delallbtn" value="Delete"></input>
832
@ </form>
833
style_table_sorter();
834
style_finish_page();
835
}
836

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button