Fossil SCM

Users unconditionally inherit capabilities of "anonymous". New capability "v" means to inherit capabilities of user "developer". Login is prohibited if the password is empty.

drh 2008-08-03 16:47 trunk
Commit 1f1d96529c64ef5e9afda16af7d405aade92cb3d
+1 -1
--- src/admin.c
+++ src/admin.c
@@ -108,10 +108,10 @@
108108
}
109109
admin_prepare_submenu();
110110
style_header("Admin");
111111
@ <h2>Links:</h2>
112112
@ <ul>
113
- @ <li><a href='%s(g.zBaseURL)/admin/setup'>Fossil WWW Setup</a></li>
113
+ @ <li><a href='%s(g.zBaseURL)/setup'>Fossil WWW Setup</a></li>
114114
@ <li><a href='%s(g.zBaseURL)/admin/sql'>Run SQL queries</a></li>
115115
@ </ul>
116116
style_footer();
117117
}
118118
--- src/admin.c
+++ src/admin.c
@@ -108,10 +108,10 @@
108 }
109 admin_prepare_submenu();
110 style_header("Admin");
111 @ <h2>Links:</h2>
112 @ <ul>
113 @ <li><a href='%s(g.zBaseURL)/admin/setup'>Fossil WWW Setup</a></li>
114 @ <li><a href='%s(g.zBaseURL)/admin/sql'>Run SQL queries</a></li>
115 @ </ul>
116 style_footer();
117 }
118
--- src/admin.c
+++ src/admin.c
@@ -108,10 +108,10 @@
108 }
109 admin_prepare_submenu();
110 style_header("Admin");
111 @ <h2>Links:</h2>
112 @ <ul>
113 @ <li><a href='%s(g.zBaseURL)/setup'>Fossil WWW Setup</a></li>
114 @ <li><a href='%s(g.zBaseURL)/admin/sql'>Run SQL queries</a></li>
115 @ </ul>
116 style_footer();
117 }
118
+3 -1
--- src/db.c
+++ src/db.c
@@ -789,13 +789,15 @@
789789
"INSERT INTO user(login, pw, cap, info)"
790790
"VALUES(%Q,'','s','')", zUser
791791
);
792792
db_multi_exec(
793793
"INSERT INTO user(login,pw,cap,info)"
794
- " VALUES('anonymous','anonymous','hjkorw','Anon');"
794
+ " VALUES('anonymous','anonymous','aghknw','Anon');"
795795
"INSERT INTO user(login,pw,cap,info)"
796796
" VALUES('nobody','','jor','Nobody');"
797
+ "INSERT INTO user(login,pw,cap,info)"
798
+ " VALUES('developer','','deipt','Dev');"
797799
);
798800
user_select();
799801
800802
if (makeInitialVersion){
801803
blob_zero(&manifest);
802804
--- src/db.c
+++ src/db.c
@@ -789,13 +789,15 @@
789 "INSERT INTO user(login, pw, cap, info)"
790 "VALUES(%Q,'','s','')", zUser
791 );
792 db_multi_exec(
793 "INSERT INTO user(login,pw,cap,info)"
794 " VALUES('anonymous','anonymous','hjkorw','Anon');"
795 "INSERT INTO user(login,pw,cap,info)"
796 " VALUES('nobody','','jor','Nobody');"
 
 
797 );
798 user_select();
799
800 if (makeInitialVersion){
801 blob_zero(&manifest);
802
--- src/db.c
+++ src/db.c
@@ -789,13 +789,15 @@
789 "INSERT INTO user(login, pw, cap, info)"
790 "VALUES(%Q,'','s','')", zUser
791 );
792 db_multi_exec(
793 "INSERT INTO user(login,pw,cap,info)"
794 " VALUES('anonymous','anonymous','aghknw','Anon');"
795 "INSERT INTO user(login,pw,cap,info)"
796 " VALUES('nobody','','jor','Nobody');"
797 "INSERT INTO user(login,pw,cap,info)"
798 " VALUES('developer','','deipt','Dev');"
799 );
800 user_select();
801
802 if (makeInitialVersion){
803 blob_zero(&manifest);
804
+23 -2
--- src/login.c
+++ src/login.c
@@ -113,11 +113,11 @@
113113
);
114114
cgi_redirect(zGoto);
115115
return;
116116
}
117117
}
118
- if( zUsername!=0 && zPasswd!=0 ){
118
+ if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
119119
int uid = db_int(0,
120120
"SELECT uid FROM user"
121121
" WHERE login=%Q AND pw=%Q", zUsername, zPasswd);
122122
if( uid<=0 || strcmp(zUsername,"nobody")==0 ){
123123
sleep(1);
@@ -300,13 +300,15 @@
300300
g.userUid = uid;
301301
if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
302302
g.zLogin = 0;
303303
}
304304
if( uid && g.zLogin ){
305
+ /* All logged-in users inherit privileges from "nobody" */
305306
zNcap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
306307
login_set_capabilities(zNcap);
307
- if( db_get_int("inherit-anon",0) ){
308
+ if( strcmp(g.zLogin, "anonymous")!=0 ){
309
+ /* All logged-in users inherit privileges from "anonymous" */
308310
zAcap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
309311
login_set_capabilities(zAcap);
310312
}
311313
}
312314
login_set_capabilities(zCap);
@@ -314,10 +316,11 @@
314316
315317
/*
316318
** Set the global capability flags based on a capability string.
317319
*/
318320
void login_set_capabilities(const char *zCap){
321
+ static char *zDev = 0;
319322
int i;
320323
for(i=0; zCap[i]; i++){
321324
switch( zCap[i] ){
322325
case 's': g.okSetup = 1;
323326
case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt =
@@ -343,10 +346,20 @@
343346
case 'n': g.okNewTkt = 1; break;
344347
case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt =
345348
g.okApndTkt = 1; break;
346349
case 'c': g.okApndTkt = 1; break;
347350
case 't': g.okTktFmt = 1; break;
351
+
352
+ /* The "v" privileges is a little different. It recursively
353
+ ** inherits all privileges of the user named "developer" */
354
+ case 'v': {
355
+ if( zDev==0 ){
356
+ zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
357
+ login_set_capabilities(zDev);
358
+ }
359
+ break;
360
+ }
348361
}
349362
}
350363
}
351364
352365
/*
@@ -359,27 +372,35 @@
359372
int rc = 1;
360373
if( nCap<0 ) nCap = strlen(zCap);
361374
for(i=0; i<nCap && rc && zCap[i]; i++){
362375
switch( zCap[i] ){
363376
case 'a': rc = g.okAdmin; break;
377
+ /* case 'b': */
364378
case 'c': rc = g.okApndTkt; break;
365379
case 'd': rc = g.okDelete; break;
366380
case 'e': rc = g.okRdAddr; break;
367381
case 'f': rc = g.okNewWiki; break;
368382
case 'g': rc = g.okClone; break;
369383
case 'h': rc = g.okHistory; break;
370384
case 'i': rc = g.okWrite; break;
371385
case 'j': rc = g.okRdWiki; break;
372386
case 'k': rc = g.okWrWiki; break;
387
+ /* case 'l': */
373388
case 'm': rc = g.okApndWiki; break;
374389
case 'n': rc = g.okNewTkt; break;
375390
case 'o': rc = g.okRead; break;
376391
case 'p': rc = g.okPassword; break;
392
+ /* case 'q': */
377393
case 'r': rc = g.okRdTkt; break;
378394
case 's': rc = g.okSetup; break;
379395
case 't': rc = g.okTktFmt; break;
396
+ /* case 'u': */
397
+ /* case 'v': */
380398
case 'w': rc = g.okWrTkt; break;
399
+ /* case 'x': */
400
+ /* case 'y': */
401
+ /* case 'z': */
381402
default: rc = 0; break;
382403
}
383404
}
384405
return rc;
385406
}
386407
--- src/login.c
+++ src/login.c
@@ -113,11 +113,11 @@
113 );
114 cgi_redirect(zGoto);
115 return;
116 }
117 }
118 if( zUsername!=0 && zPasswd!=0 ){
119 int uid = db_int(0,
120 "SELECT uid FROM user"
121 " WHERE login=%Q AND pw=%Q", zUsername, zPasswd);
122 if( uid<=0 || strcmp(zUsername,"nobody")==0 ){
123 sleep(1);
@@ -300,13 +300,15 @@
300 g.userUid = uid;
301 if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
302 g.zLogin = 0;
303 }
304 if( uid && g.zLogin ){
 
305 zNcap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
306 login_set_capabilities(zNcap);
307 if( db_get_int("inherit-anon",0) ){
 
308 zAcap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
309 login_set_capabilities(zAcap);
310 }
311 }
312 login_set_capabilities(zCap);
@@ -314,10 +316,11 @@
314
315 /*
316 ** Set the global capability flags based on a capability string.
317 */
318 void login_set_capabilities(const char *zCap){
 
319 int i;
320 for(i=0; zCap[i]; i++){
321 switch( zCap[i] ){
322 case 's': g.okSetup = 1;
323 case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt =
@@ -343,10 +346,20 @@
343 case 'n': g.okNewTkt = 1; break;
344 case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt =
345 g.okApndTkt = 1; break;
346 case 'c': g.okApndTkt = 1; break;
347 case 't': g.okTktFmt = 1; break;
 
 
 
 
 
 
 
 
 
 
348 }
349 }
350 }
351
352 /*
@@ -359,27 +372,35 @@
359 int rc = 1;
360 if( nCap<0 ) nCap = strlen(zCap);
361 for(i=0; i<nCap && rc && zCap[i]; i++){
362 switch( zCap[i] ){
363 case 'a': rc = g.okAdmin; break;
 
364 case 'c': rc = g.okApndTkt; break;
365 case 'd': rc = g.okDelete; break;
366 case 'e': rc = g.okRdAddr; break;
367 case 'f': rc = g.okNewWiki; break;
368 case 'g': rc = g.okClone; break;
369 case 'h': rc = g.okHistory; break;
370 case 'i': rc = g.okWrite; break;
371 case 'j': rc = g.okRdWiki; break;
372 case 'k': rc = g.okWrWiki; break;
 
373 case 'm': rc = g.okApndWiki; break;
374 case 'n': rc = g.okNewTkt; break;
375 case 'o': rc = g.okRead; break;
376 case 'p': rc = g.okPassword; break;
 
377 case 'r': rc = g.okRdTkt; break;
378 case 's': rc = g.okSetup; break;
379 case 't': rc = g.okTktFmt; break;
 
 
380 case 'w': rc = g.okWrTkt; break;
 
 
 
381 default: rc = 0; break;
382 }
383 }
384 return rc;
385 }
386
--- src/login.c
+++ src/login.c
@@ -113,11 +113,11 @@
113 );
114 cgi_redirect(zGoto);
115 return;
116 }
117 }
118 if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
119 int uid = db_int(0,
120 "SELECT uid FROM user"
121 " WHERE login=%Q AND pw=%Q", zUsername, zPasswd);
122 if( uid<=0 || strcmp(zUsername,"nobody")==0 ){
123 sleep(1);
@@ -300,13 +300,15 @@
300 g.userUid = uid;
301 if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
302 g.zLogin = 0;
303 }
304 if( uid && g.zLogin ){
305 /* All logged-in users inherit privileges from "nobody" */
306 zNcap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
307 login_set_capabilities(zNcap);
308 if( strcmp(g.zLogin, "anonymous")!=0 ){
309 /* All logged-in users inherit privileges from "anonymous" */
310 zAcap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
311 login_set_capabilities(zAcap);
312 }
313 }
314 login_set_capabilities(zCap);
@@ -314,10 +316,11 @@
316
317 /*
318 ** Set the global capability flags based on a capability string.
319 */
320 void login_set_capabilities(const char *zCap){
321 static char *zDev = 0;
322 int i;
323 for(i=0; zCap[i]; i++){
324 switch( zCap[i] ){
325 case 's': g.okSetup = 1;
326 case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt =
@@ -343,10 +346,20 @@
346 case 'n': g.okNewTkt = 1; break;
347 case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt =
348 g.okApndTkt = 1; break;
349 case 'c': g.okApndTkt = 1; break;
350 case 't': g.okTktFmt = 1; break;
351
352 /* The "v" privileges is a little different. It recursively
353 ** inherits all privileges of the user named "developer" */
354 case 'v': {
355 if( zDev==0 ){
356 zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
357 login_set_capabilities(zDev);
358 }
359 break;
360 }
361 }
362 }
363 }
364
365 /*
@@ -359,27 +372,35 @@
372 int rc = 1;
373 if( nCap<0 ) nCap = strlen(zCap);
374 for(i=0; i<nCap && rc && zCap[i]; i++){
375 switch( zCap[i] ){
376 case 'a': rc = g.okAdmin; break;
377 /* case 'b': */
378 case 'c': rc = g.okApndTkt; break;
379 case 'd': rc = g.okDelete; break;
380 case 'e': rc = g.okRdAddr; break;
381 case 'f': rc = g.okNewWiki; break;
382 case 'g': rc = g.okClone; break;
383 case 'h': rc = g.okHistory; break;
384 case 'i': rc = g.okWrite; break;
385 case 'j': rc = g.okRdWiki; break;
386 case 'k': rc = g.okWrWiki; break;
387 /* case 'l': */
388 case 'm': rc = g.okApndWiki; break;
389 case 'n': rc = g.okNewTkt; break;
390 case 'o': rc = g.okRead; break;
391 case 'p': rc = g.okPassword; break;
392 /* case 'q': */
393 case 'r': rc = g.okRdTkt; break;
394 case 's': rc = g.okSetup; break;
395 case 't': rc = g.okTktFmt; break;
396 /* case 'u': */
397 /* case 'v': */
398 case 'w': rc = g.okWrTkt; break;
399 /* case 'x': */
400 /* case 'y': */
401 /* case 'z': */
402 default: rc = 0; break;
403 }
404 }
405 return rc;
406 }
407
+6 -6
--- src/my_page.c
+++ src/my_page.c
@@ -27,12 +27,12 @@
2727
*/
2828
#include <assert.h>
2929
#include "config.h"
3030
#include "my_page.h"
3131
32
-/**
33
-Renders a logout button.
32
+/*
33
+** Renders a logout button.
3434
*/
3535
static void mypage_logout_button()
3636
{
3737
if( g.zLogin ){
3838
@ <br clear="both"/><hr/>
@@ -43,12 +43,12 @@
4343
@ <input type="submit" name="out" value="Logout"/></p>
4444
@ </form>
4545
}
4646
}
4747
48
-/**
49
-Renders a password changer.
48
+/*
49
+** Renders a password changer.
5050
*/
5151
static void mypage_password_changer()
5252
{
5353
if( g.okPassword ){
5454
@ <br clear="both"/><hr/>
@@ -71,12 +71,12 @@
7171
@ </form>
7272
}
7373
7474
}
7575
76
-/**
77
-Default page rendered for /my.
76
+/*
77
+** Default page rendered for /my.
7878
*/
7979
static void mypage_page_default()
8080
{
8181
int uid = g.userUid;
8282
char * sql = mprintf( "SELECT login,cap,info FROM user WHERE uid=%d",
8383
--- src/my_page.c
+++ src/my_page.c
@@ -27,12 +27,12 @@
27 */
28 #include <assert.h>
29 #include "config.h"
30 #include "my_page.h"
31
32 /**
33 Renders a logout button.
34 */
35 static void mypage_logout_button()
36 {
37 if( g.zLogin ){
38 @ <br clear="both"/><hr/>
@@ -43,12 +43,12 @@
43 @ <input type="submit" name="out" value="Logout"/></p>
44 @ </form>
45 }
46 }
47
48 /**
49 Renders a password changer.
50 */
51 static void mypage_password_changer()
52 {
53 if( g.okPassword ){
54 @ <br clear="both"/><hr/>
@@ -71,12 +71,12 @@
71 @ </form>
72 }
73
74 }
75
76 /**
77 Default page rendered for /my.
78 */
79 static void mypage_page_default()
80 {
81 int uid = g.userUid;
82 char * sql = mprintf( "SELECT login,cap,info FROM user WHERE uid=%d",
83
--- src/my_page.c
+++ src/my_page.c
@@ -27,12 +27,12 @@
27 */
28 #include <assert.h>
29 #include "config.h"
30 #include "my_page.h"
31
32 /*
33 ** Renders a logout button.
34 */
35 static void mypage_logout_button()
36 {
37 if( g.zLogin ){
38 @ <br clear="both"/><hr/>
@@ -43,12 +43,12 @@
43 @ <input type="submit" name="out" value="Logout"/></p>
44 @ </form>
45 }
46 }
47
48 /*
49 ** Renders a password changer.
50 */
51 static void mypage_password_changer()
52 {
53 if( g.okPassword ){
54 @ <br clear="both"/><hr/>
@@ -71,12 +71,12 @@
71 @ </form>
72 }
73
74 }
75
76 /*
77 ** Default page rendered for /my.
78 */
79 static void mypage_page_default()
80 {
81 int uid = g.userUid;
82 char * sql = mprintf( "SELECT login,cap,info FROM user WHERE uid=%d",
83
+72 -19
--- src/setup.c
+++ src/setup.c
@@ -152,34 +152,50 @@
152152
@ <li value="15"><b>Check-Out</b>: Check out versions</li>
153153
@ <li value="16"><b>Password</b>: Change your own password</li>
154154
@ <li value="18"><b>Read-Tkt</b>: View tickets</li>
155155
@ <li value="19"><b>Setup:</b> Setup and configure this website</li>
156156
@ <li value="20"><b>Tkt-Report:</b> Create new bug summary reports</li>
157
+ @ <li value="22"><b>Developer:</b> Inherit privileges of user "developer"</li>
157158
@ <li value="23"><b>Write-Tkt</b>: Edit tickets</li>
158159
@ </ol>
159160
@ </p></li>
160161
@
161162
@ <li><p>
162
- @ Every user, logged in or not, has the privileges of <b>nobody</b>.
163
+ @ Every user, logged in or not, inherits the privileges of <b>nobody</b>.
163164
@ Any human can login as <b>anonymous</b> since the password is
164165
@ clearly displayed on the login page for them to type. The purpose
165166
@ of requiring anonymous to log in is to prevent access by spiders.
167
+ @ Every logged-in user inherits the privileges of <b>anonymous</b>.
166168
@ </p></li>
167169
@
168170
@ </ol>
169171
@ </td></tr></table>
170172
style_footer();
171173
}
174
+
175
+/*
176
+** Return true if zPw is a valid password string. A valid
177
+** password string is:
178
+**
179
+** (1) A zero-length string, or
180
+** (2) a string that contains a character other than '*'.
181
+*/
182
+static int isValidPwString(const char *zPw){
183
+ if( zPw==0 ) return 0;
184
+ if( zPw[0]==0 ) return 1;
185
+ while( zPw[0]=='*' ){ zPw++; }
186
+ return zPw[0]!=0;
187
+}
172188
173189
/*
174190
** WEBPAGE: /setup_uedit
175191
*/
176192
void user_edit(void){
177
- const char *zId, *zLogin, *zInfo, *zCap;
193
+ const char *zId, *zLogin, *zInfo, *zCap, *zPw;
178194
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
179195
char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
180
- char *oat;
196
+ char *oat, *oav;
181197
int doWrite;
182198
int uid;
183199
int higherUser = 0; /* True if user being edited is SETUP and the */
184200
/* user doing the editing is ADMIN. Disallow editing */
185201
@@ -208,12 +224,10 @@
208224
** modified user record. After writing the user record, redirect
209225
** to the page that displays a list of users.
210226
*/
211227
doWrite = cgi_all("login","info","pw") && !higherUser;
212228
if( doWrite ){
213
- const char *zPw;
214
- const char *zLogin;
215229
char zCap[50];
216230
int i = 0;
217231
int aa = P("aa")!=0;
218232
int ad = P("ad")!=0;
219233
int ae = P("ae")!=0;
@@ -230,10 +244,11 @@
230244
int af = P("af")!=0;
231245
int am = P("am")!=0;
232246
int ah = P("ah")!=0;
233247
int ag = P("ag")!=0;
234248
int at = P("at")!=0;
249
+ int av = P("av")!=0;
235250
if( aa ){ zCap[i++] = 'a'; }
236251
if( ac ){ zCap[i++] = 'c'; }
237252
if( ad ){ zCap[i++] = 'd'; }
238253
if( ae ){ zCap[i++] = 'e'; }
239254
if( af ){ zCap[i++] = 'f'; }
@@ -247,15 +262,16 @@
247262
if( ao ){ zCap[i++] = 'o'; }
248263
if( ap ){ zCap[i++] = 'p'; }
249264
if( ar ){ zCap[i++] = 'r'; }
250265
if( as ){ zCap[i++] = 's'; }
251266
if( at ){ zCap[i++] = 't'; }
267
+ if( av ){ zCap[i++] = 'v'; }
252268
if( aw ){ zCap[i++] = 'w'; }
253269
254270
zCap[i] = 0;
255271
zPw = P("pw");
256
- if( zPw==0 || zPw[0]==0 ){
272
+ if( !isValidPwString(zPw) ){
257273
zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
258274
}
259275
zLogin = P("login");
260276
if( uid>0 &&
261277
db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
@@ -280,16 +296,18 @@
280296
/* Load the existing information about the user, if any
281297
*/
282298
zLogin = "";
283299
zInfo = "";
284300
zCap = "";
301
+ zPw = "";
285302
oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
286
- oan = oao = oap = oar = oas = oat = oaw = "";
303
+ oan = oao = oap = oar = oas = oat = oav = oaw = "";
287304
if( uid ){
288305
zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
289306
zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
290307
zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
308
+ zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
291309
if( strchr(zCap, 'a') ) oaa = " checked";
292310
if( strchr(zCap, 'c') ) oac = " checked";
293311
if( strchr(zCap, 'd') ) oad = " checked";
294312
if( strchr(zCap, 'e') ) oae = " checked";
295313
if( strchr(zCap, 'f') ) oaf = " checked";
@@ -303,10 +321,11 @@
303321
if( strchr(zCap, 'o') ) oao = " checked";
304322
if( strchr(zCap, 'p') ) oap = " checked";
305323
if( strchr(zCap, 'r') ) oar = " checked";
306324
if( strchr(zCap, 's') ) oas = " checked";
307325
if( strchr(zCap, 't') ) oat = " checked";
326
+ if( strchr(zCap, 'v') ) oav = " checked";
308327
if( strchr(zCap, 'w') ) oaw = " checked";
309328
}
310329
311330
/* Begin generating the page
312331
*/
@@ -346,10 +365,11 @@
346365
@ <input type="checkbox" name="ae"%s(oad)>Email</input><br>
347366
@ <input type="checkbox" name="ap"%s(oap)>Password</input><br>
348367
@ <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
349368
@ <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
350369
@ <input type="checkbox" name="ah"%s(oah)>History</input><br>
370
+ @ <input type="checkbox" name="av"%s(oav)>Developer</input><br>
351371
@ <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
352372
@ <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
353373
@ <input type="checkbox" name="af"%s(oaf)>New Wiki</input><br>
354374
@ <input type="checkbox" name="am"%s(oam)>Append Wiki</input><br>
355375
@ <input type="checkbox" name="ak"%s(oak)>Write Wiki</input><br>
@@ -360,21 +380,30 @@
360380
@ <input type="checkbox" name="at"%s(oat)>Tkt Report</input>
361381
@ </td>
362382
@ </tr>
363383
@ <tr>
364384
@ <td align="right">Password:</td>
365
- @ <td><input type="password" name="pw" value=""></td>
385
+ if( strcmp(zLogin, "anonymous")==0 ){
386
+ /* User the password for "anonymous" as cleartext */
387
+ @ <td><input type="text" name="pw" value="%h(zPw)"></td>
388
+ }else if( zPw[0] ){
389
+ /* Obscure the password for all other users */
390
+ @ <td><input type="password" name="pw" value="**********"></td>
391
+ }else{
392
+ /* Show an empty password as an empty input field */
393
+ @ <td><input type="password" name="pw" value=""></td>
394
+ }
366395
@ </tr>
367396
if( !higherUser ){
368397
@ <tr>
369398
@ <td>&nbsp</td>
370399
@ <td><input type="submit" name="submit" value="Apply Changes">
371400
@ </tr>
372401
}
373402
@ </table></td></tr></table>
374
- @ <p><b>Notes:</b></p>
375
- @ <ol>
403
+ @ <h2>Privileges And Capabilities:</h2>
404
+ @ <ul>
376405
if( higherUser ){
377406
@ <li><p><font color="blue"><b>
378407
@ User %h(zLogin) has Setup privileges and you only have Admin privileges
379408
@ so you are not permitted to make changes to %h(zLogin).
380409
@ </b></font></p></li>
@@ -401,10 +430,15 @@
401430
@ This is recommended ON for most logged-in users but OFF for
402431
@ user "nobody" to avoid problems with spiders trying to walk every
403432
@ historical version of every baseline and file.
404433
@ </p></li>
405434
@
435
+ @ <li><p>
436
+ @ The <b>Developer</b> privilege causes all privileges of the user
437
+ @ named "developer" to be inherited by this user.
438
+ @ </p></li>
439
+ @
406440
@ <li><p>
407441
@ The <b>Check-in</b> privilege allows remote users to "push".
408442
@ The <b>Check-out</b> privilege allows remote users to "pull".
409443
@ The <b>Clone</b> privilege allows remote users to "clone".
410444
@ </li><p>
@@ -418,39 +452,58 @@
418452
@ ticket report formats.
419453
@ </p></li>
420454
@
421455
@ <li><p>
422456
@ Users with the <b>Password</b> privilege are allowed to change their
423
- @ own password. Recommended ON for most users but OFF for "anonynmous"
424
- @ and "nobody".
457
+ @ own password. Recommended ON for most users but OFF for special
458
+ @ users "developer, "anonynmous", and "nobody".
425459
@ </p></li>
426460
@
427461
@ <li><p>
428462
@ The <b>EMail</b> privilege allows the display of sensitive information
429463
@ such as the email address of users and contact information on tickets.
430464
@ Recommended OFF for "anonymous" and for "nobody".
431465
@ </p></li>
432466
@
467
+ @ <li><p>
468
+ @ Login is prohibited if the password is an empty string.
469
+ @ </p></li>
470
+ @ </ul>
471
+ @
472
+ @ <h2>Special Logins</h2>
473
+ @
474
+ @ <ul>
433475
@ <li><p>
434476
@ No login is required for user "<b>nobody</b>". The capabilities
435
- @ of this user are available to anyone without supplying a username or
436
- @ password. To disable nobody access, make sure there is no user
437
- @ with an ID of <b>nobody</b> or that the nobody user has no
438
- @ capabilities enabled. The password for nobody is ignore. To
439
- @ avoid problems with spiders overloading the server, it is suggested
440
- @ that the 'h' (History) capability be turned off for user nobody.
477
+ @ of the <b>nobody</b> user are inherited by all users, regardless of
478
+ @ whether or not they are logged in. To disable universal access
479
+ @ to the repository, make sure no user named "<b>nobody</b>" exists or
480
+ @ that the <b>nobody</b> user has no capabilities enabled.
481
+ @ The password for <b>nobody</b> is ignore. To avoid problems with
482
+ @ spiders overloading the server, it is recommended
483
+ @ that the 'h' (History) capability be turned off for the <b>nobody</b>
484
+ @ user.
441485
@ </p></li>
442486
@
443487
@ <li><p>
444488
@ Login is required for user "<b>anonymous</b>" but the password
445489
@ is displayed on the login screen beside the password entry box
446490
@ so anybody who can read should be able to login as anonymous.
447491
@ On the other hand, spiders and web-crawlers will typically not
448492
@ be able to login. Set the capabilities of the anonymous user
449493
@ to things that you want any human to be able to do, but not any
450
- @ spider.
494
+ @ spider. Every other logged-in user inherits the privileges of
495
+ @ <b>anonymous</b>.
451496
@ </p></li>
497
+ @
498
+ @ <li><p>
499
+ @ The "<b>developer</b>" user is intended as a template for trusted users
500
+ @ with check-in privileges. When adding new trusted users, simply
501
+ @ select the <b>Developer</b> privilege to cause the new user to inherit
502
+ @ all privileges of the "developer" user.
503
+ @ </li></p>
504
+ @ </ul>
452505
@ </form>
453506
style_footer();
454507
}
455508
456509
457510
--- src/setup.c
+++ src/setup.c
@@ -152,34 +152,50 @@
152 @ <li value="15"><b>Check-Out</b>: Check out versions</li>
153 @ <li value="16"><b>Password</b>: Change your own password</li>
154 @ <li value="18"><b>Read-Tkt</b>: View tickets</li>
155 @ <li value="19"><b>Setup:</b> Setup and configure this website</li>
156 @ <li value="20"><b>Tkt-Report:</b> Create new bug summary reports</li>
 
157 @ <li value="23"><b>Write-Tkt</b>: Edit tickets</li>
158 @ </ol>
159 @ </p></li>
160 @
161 @ <li><p>
162 @ Every user, logged in or not, has the privileges of <b>nobody</b>.
163 @ Any human can login as <b>anonymous</b> since the password is
164 @ clearly displayed on the login page for them to type. The purpose
165 @ of requiring anonymous to log in is to prevent access by spiders.
 
166 @ </p></li>
167 @
168 @ </ol>
169 @ </td></tr></table>
170 style_footer();
171 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
173 /*
174 ** WEBPAGE: /setup_uedit
175 */
176 void user_edit(void){
177 const char *zId, *zLogin, *zInfo, *zCap;
178 char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
179 char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
180 char *oat;
181 int doWrite;
182 int uid;
183 int higherUser = 0; /* True if user being edited is SETUP and the */
184 /* user doing the editing is ADMIN. Disallow editing */
185
@@ -208,12 +224,10 @@
208 ** modified user record. After writing the user record, redirect
209 ** to the page that displays a list of users.
210 */
211 doWrite = cgi_all("login","info","pw") && !higherUser;
212 if( doWrite ){
213 const char *zPw;
214 const char *zLogin;
215 char zCap[50];
216 int i = 0;
217 int aa = P("aa")!=0;
218 int ad = P("ad")!=0;
219 int ae = P("ae")!=0;
@@ -230,10 +244,11 @@
230 int af = P("af")!=0;
231 int am = P("am")!=0;
232 int ah = P("ah")!=0;
233 int ag = P("ag")!=0;
234 int at = P("at")!=0;
 
235 if( aa ){ zCap[i++] = 'a'; }
236 if( ac ){ zCap[i++] = 'c'; }
237 if( ad ){ zCap[i++] = 'd'; }
238 if( ae ){ zCap[i++] = 'e'; }
239 if( af ){ zCap[i++] = 'f'; }
@@ -247,15 +262,16 @@
247 if( ao ){ zCap[i++] = 'o'; }
248 if( ap ){ zCap[i++] = 'p'; }
249 if( ar ){ zCap[i++] = 'r'; }
250 if( as ){ zCap[i++] = 's'; }
251 if( at ){ zCap[i++] = 't'; }
 
252 if( aw ){ zCap[i++] = 'w'; }
253
254 zCap[i] = 0;
255 zPw = P("pw");
256 if( zPw==0 || zPw[0]==0 ){
257 zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
258 }
259 zLogin = P("login");
260 if( uid>0 &&
261 db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
@@ -280,16 +296,18 @@
280 /* Load the existing information about the user, if any
281 */
282 zLogin = "";
283 zInfo = "";
284 zCap = "";
 
285 oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
286 oan = oao = oap = oar = oas = oat = oaw = "";
287 if( uid ){
288 zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
289 zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
290 zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
 
291 if( strchr(zCap, 'a') ) oaa = " checked";
292 if( strchr(zCap, 'c') ) oac = " checked";
293 if( strchr(zCap, 'd') ) oad = " checked";
294 if( strchr(zCap, 'e') ) oae = " checked";
295 if( strchr(zCap, 'f') ) oaf = " checked";
@@ -303,10 +321,11 @@
303 if( strchr(zCap, 'o') ) oao = " checked";
304 if( strchr(zCap, 'p') ) oap = " checked";
305 if( strchr(zCap, 'r') ) oar = " checked";
306 if( strchr(zCap, 's') ) oas = " checked";
307 if( strchr(zCap, 't') ) oat = " checked";
 
308 if( strchr(zCap, 'w') ) oaw = " checked";
309 }
310
311 /* Begin generating the page
312 */
@@ -346,10 +365,11 @@
346 @ <input type="checkbox" name="ae"%s(oad)>Email</input><br>
347 @ <input type="checkbox" name="ap"%s(oap)>Password</input><br>
348 @ <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
349 @ <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
350 @ <input type="checkbox" name="ah"%s(oah)>History</input><br>
 
351 @ <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
352 @ <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
353 @ <input type="checkbox" name="af"%s(oaf)>New Wiki</input><br>
354 @ <input type="checkbox" name="am"%s(oam)>Append Wiki</input><br>
355 @ <input type="checkbox" name="ak"%s(oak)>Write Wiki</input><br>
@@ -360,21 +380,30 @@
360 @ <input type="checkbox" name="at"%s(oat)>Tkt Report</input>
361 @ </td>
362 @ </tr>
363 @ <tr>
364 @ <td align="right">Password:</td>
365 @ <td><input type="password" name="pw" value=""></td>
 
 
 
 
 
 
 
 
 
366 @ </tr>
367 if( !higherUser ){
368 @ <tr>
369 @ <td>&nbsp</td>
370 @ <td><input type="submit" name="submit" value="Apply Changes">
371 @ </tr>
372 }
373 @ </table></td></tr></table>
374 @ <p><b>Notes:</b></p>
375 @ <ol>
376 if( higherUser ){
377 @ <li><p><font color="blue"><b>
378 @ User %h(zLogin) has Setup privileges and you only have Admin privileges
379 @ so you are not permitted to make changes to %h(zLogin).
380 @ </b></font></p></li>
@@ -401,10 +430,15 @@
401 @ This is recommended ON for most logged-in users but OFF for
402 @ user "nobody" to avoid problems with spiders trying to walk every
403 @ historical version of every baseline and file.
404 @ </p></li>
405 @
 
 
 
 
 
406 @ <li><p>
407 @ The <b>Check-in</b> privilege allows remote users to "push".
408 @ The <b>Check-out</b> privilege allows remote users to "pull".
409 @ The <b>Clone</b> privilege allows remote users to "clone".
410 @ </li><p>
@@ -418,39 +452,58 @@
418 @ ticket report formats.
419 @ </p></li>
420 @
421 @ <li><p>
422 @ Users with the <b>Password</b> privilege are allowed to change their
423 @ own password. Recommended ON for most users but OFF for "anonynmous"
424 @ and "nobody".
425 @ </p></li>
426 @
427 @ <li><p>
428 @ The <b>EMail</b> privilege allows the display of sensitive information
429 @ such as the email address of users and contact information on tickets.
430 @ Recommended OFF for "anonymous" and for "nobody".
431 @ </p></li>
432 @
 
 
 
 
 
 
 
 
433 @ <li><p>
434 @ No login is required for user "<b>nobody</b>". The capabilities
435 @ of this user are available to anyone without supplying a username or
436 @ password. To disable nobody access, make sure there is no user
437 @ with an ID of <b>nobody</b> or that the nobody user has no
438 @ capabilities enabled. The password for nobody is ignore. To
439 @ avoid problems with spiders overloading the server, it is suggested
440 @ that the 'h' (History) capability be turned off for user nobody.
 
 
441 @ </p></li>
442 @
443 @ <li><p>
444 @ Login is required for user "<b>anonymous</b>" but the password
445 @ is displayed on the login screen beside the password entry box
446 @ so anybody who can read should be able to login as anonymous.
447 @ On the other hand, spiders and web-crawlers will typically not
448 @ be able to login. Set the capabilities of the anonymous user
449 @ to things that you want any human to be able to do, but not any
450 @ spider.
 
451 @ </p></li>
 
 
 
 
 
 
 
 
452 @ </form>
453 style_footer();
454 }
455
456
457
--- src/setup.c
+++ src/setup.c
@@ -152,34 +152,50 @@
152 @ <li value="15"><b>Check-Out</b>: Check out versions</li>
153 @ <li value="16"><b>Password</b>: Change your own password</li>
154 @ <li value="18"><b>Read-Tkt</b>: View tickets</li>
155 @ <li value="19"><b>Setup:</b> Setup and configure this website</li>
156 @ <li value="20"><b>Tkt-Report:</b> Create new bug summary reports</li>
157 @ <li value="22"><b>Developer:</b> Inherit privileges of user "developer"</li>
158 @ <li value="23"><b>Write-Tkt</b>: Edit tickets</li>
159 @ </ol>
160 @ </p></li>
161 @
162 @ <li><p>
163 @ Every user, logged in or not, inherits the privileges of <b>nobody</b>.
164 @ Any human can login as <b>anonymous</b> since the password is
165 @ clearly displayed on the login page for them to type. The purpose
166 @ of requiring anonymous to log in is to prevent access by spiders.
167 @ Every logged-in user inherits the privileges of <b>anonymous</b>.
168 @ </p></li>
169 @
170 @ </ol>
171 @ </td></tr></table>
172 style_footer();
173 }
174
175 /*
176 ** Return true if zPw is a valid password string. A valid
177 ** password string is:
178 **
179 ** (1) A zero-length string, or
180 ** (2) a string that contains a character other than '*'.
181 */
182 static int isValidPwString(const char *zPw){
183 if( zPw==0 ) return 0;
184 if( zPw[0]==0 ) return 1;
185 while( zPw[0]=='*' ){ zPw++; }
186 return zPw[0]!=0;
187 }
188
189 /*
190 ** WEBPAGE: /setup_uedit
191 */
192 void user_edit(void){
193 const char *zId, *zLogin, *zInfo, *zCap, *zPw;
194 char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
195 char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
196 char *oat, *oav;
197 int doWrite;
198 int uid;
199 int higherUser = 0; /* True if user being edited is SETUP and the */
200 /* user doing the editing is ADMIN. Disallow editing */
201
@@ -208,12 +224,10 @@
224 ** modified user record. After writing the user record, redirect
225 ** to the page that displays a list of users.
226 */
227 doWrite = cgi_all("login","info","pw") && !higherUser;
228 if( doWrite ){
 
 
229 char zCap[50];
230 int i = 0;
231 int aa = P("aa")!=0;
232 int ad = P("ad")!=0;
233 int ae = P("ae")!=0;
@@ -230,10 +244,11 @@
244 int af = P("af")!=0;
245 int am = P("am")!=0;
246 int ah = P("ah")!=0;
247 int ag = P("ag")!=0;
248 int at = P("at")!=0;
249 int av = P("av")!=0;
250 if( aa ){ zCap[i++] = 'a'; }
251 if( ac ){ zCap[i++] = 'c'; }
252 if( ad ){ zCap[i++] = 'd'; }
253 if( ae ){ zCap[i++] = 'e'; }
254 if( af ){ zCap[i++] = 'f'; }
@@ -247,15 +262,16 @@
262 if( ao ){ zCap[i++] = 'o'; }
263 if( ap ){ zCap[i++] = 'p'; }
264 if( ar ){ zCap[i++] = 'r'; }
265 if( as ){ zCap[i++] = 's'; }
266 if( at ){ zCap[i++] = 't'; }
267 if( av ){ zCap[i++] = 'v'; }
268 if( aw ){ zCap[i++] = 'w'; }
269
270 zCap[i] = 0;
271 zPw = P("pw");
272 if( !isValidPwString(zPw) ){
273 zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
274 }
275 zLogin = P("login");
276 if( uid>0 &&
277 db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
@@ -280,16 +296,18 @@
296 /* Load the existing information about the user, if any
297 */
298 zLogin = "";
299 zInfo = "";
300 zCap = "";
301 zPw = "";
302 oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
303 oan = oao = oap = oar = oas = oat = oav = oaw = "";
304 if( uid ){
305 zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
306 zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
307 zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
308 zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
309 if( strchr(zCap, 'a') ) oaa = " checked";
310 if( strchr(zCap, 'c') ) oac = " checked";
311 if( strchr(zCap, 'd') ) oad = " checked";
312 if( strchr(zCap, 'e') ) oae = " checked";
313 if( strchr(zCap, 'f') ) oaf = " checked";
@@ -303,10 +321,11 @@
321 if( strchr(zCap, 'o') ) oao = " checked";
322 if( strchr(zCap, 'p') ) oap = " checked";
323 if( strchr(zCap, 'r') ) oar = " checked";
324 if( strchr(zCap, 's') ) oas = " checked";
325 if( strchr(zCap, 't') ) oat = " checked";
326 if( strchr(zCap, 'v') ) oav = " checked";
327 if( strchr(zCap, 'w') ) oaw = " checked";
328 }
329
330 /* Begin generating the page
331 */
@@ -346,10 +365,11 @@
365 @ <input type="checkbox" name="ae"%s(oad)>Email</input><br>
366 @ <input type="checkbox" name="ap"%s(oap)>Password</input><br>
367 @ <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
368 @ <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
369 @ <input type="checkbox" name="ah"%s(oah)>History</input><br>
370 @ <input type="checkbox" name="av"%s(oav)>Developer</input><br>
371 @ <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
372 @ <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
373 @ <input type="checkbox" name="af"%s(oaf)>New Wiki</input><br>
374 @ <input type="checkbox" name="am"%s(oam)>Append Wiki</input><br>
375 @ <input type="checkbox" name="ak"%s(oak)>Write Wiki</input><br>
@@ -360,21 +380,30 @@
380 @ <input type="checkbox" name="at"%s(oat)>Tkt Report</input>
381 @ </td>
382 @ </tr>
383 @ <tr>
384 @ <td align="right">Password:</td>
385 if( strcmp(zLogin, "anonymous")==0 ){
386 /* User the password for "anonymous" as cleartext */
387 @ <td><input type="text" name="pw" value="%h(zPw)"></td>
388 }else if( zPw[0] ){
389 /* Obscure the password for all other users */
390 @ <td><input type="password" name="pw" value="**********"></td>
391 }else{
392 /* Show an empty password as an empty input field */
393 @ <td><input type="password" name="pw" value=""></td>
394 }
395 @ </tr>
396 if( !higherUser ){
397 @ <tr>
398 @ <td>&nbsp</td>
399 @ <td><input type="submit" name="submit" value="Apply Changes">
400 @ </tr>
401 }
402 @ </table></td></tr></table>
403 @ <h2>Privileges And Capabilities:</h2>
404 @ <ul>
405 if( higherUser ){
406 @ <li><p><font color="blue"><b>
407 @ User %h(zLogin) has Setup privileges and you only have Admin privileges
408 @ so you are not permitted to make changes to %h(zLogin).
409 @ </b></font></p></li>
@@ -401,10 +430,15 @@
430 @ This is recommended ON for most logged-in users but OFF for
431 @ user "nobody" to avoid problems with spiders trying to walk every
432 @ historical version of every baseline and file.
433 @ </p></li>
434 @
435 @ <li><p>
436 @ The <b>Developer</b> privilege causes all privileges of the user
437 @ named "developer" to be inherited by this user.
438 @ </p></li>
439 @
440 @ <li><p>
441 @ The <b>Check-in</b> privilege allows remote users to "push".
442 @ The <b>Check-out</b> privilege allows remote users to "pull".
443 @ The <b>Clone</b> privilege allows remote users to "clone".
444 @ </li><p>
@@ -418,39 +452,58 @@
452 @ ticket report formats.
453 @ </p></li>
454 @
455 @ <li><p>
456 @ Users with the <b>Password</b> privilege are allowed to change their
457 @ own password. Recommended ON for most users but OFF for special
458 @ users "developer, "anonynmous", and "nobody".
459 @ </p></li>
460 @
461 @ <li><p>
462 @ The <b>EMail</b> privilege allows the display of sensitive information
463 @ such as the email address of users and contact information on tickets.
464 @ Recommended OFF for "anonymous" and for "nobody".
465 @ </p></li>
466 @
467 @ <li><p>
468 @ Login is prohibited if the password is an empty string.
469 @ </p></li>
470 @ </ul>
471 @
472 @ <h2>Special Logins</h2>
473 @
474 @ <ul>
475 @ <li><p>
476 @ No login is required for user "<b>nobody</b>". The capabilities
477 @ of the <b>nobody</b> user are inherited by all users, regardless of
478 @ whether or not they are logged in. To disable universal access
479 @ to the repository, make sure no user named "<b>nobody</b>" exists or
480 @ that the <b>nobody</b> user has no capabilities enabled.
481 @ The password for <b>nobody</b> is ignore. To avoid problems with
482 @ spiders overloading the server, it is recommended
483 @ that the 'h' (History) capability be turned off for the <b>nobody</b>
484 @ user.
485 @ </p></li>
486 @
487 @ <li><p>
488 @ Login is required for user "<b>anonymous</b>" but the password
489 @ is displayed on the login screen beside the password entry box
490 @ so anybody who can read should be able to login as anonymous.
491 @ On the other hand, spiders and web-crawlers will typically not
492 @ be able to login. Set the capabilities of the anonymous user
493 @ to things that you want any human to be able to do, but not any
494 @ spider. Every other logged-in user inherits the privileges of
495 @ <b>anonymous</b>.
496 @ </p></li>
497 @
498 @ <li><p>
499 @ The "<b>developer</b>" user is intended as a template for trusted users
500 @ with check-in privileges. When adding new trusted users, simply
501 @ select the <b>Developer</b> privilege to cause the new user to inherit
502 @ all privileges of the "developer" user.
503 @ </li></p>
504 @ </ul>
505 @ </form>
506 style_footer();
507 }
508
509
510

Keyboard Shortcuts

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