/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code for generating the login and logout screens.
**
** Notes:
**
** There are four special-case user-ids: "anonymous", "nobody",
** "developer" and "reader".
**
** The capabilities of the nobody user are available to anyone,
** regardless of whether or not they are logged in. The capabilities
** of anonymous are only available after logging in, but the login
** screen displays the password for the anonymous login, so this
** should not prevent a human user from doing so. The capabilities
** of developer and reader are inherited by any user that has the
** "v" and "u" capabilities, respectively.
**
** The nobody user has capabilities that you want spiders to have.
** The anonymous user has capabilities that you want people without
** logins to have.
**
** Of course, a sophisticated spider could easily circumvent the
** anonymous login requirement and walk the website. But that is
** not really the point. The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#if defined(_WIN32)
# include
@ You entered an incorrect old password while attempting to change
@ your password. Your password is unchanged.
@
@ The two copies of your new passwords do not match.
@ Your password is unchanged.
@ ", "
@ The password cannot be changed for this type of login.
@ The password is unchanged.
@
@ You entered an unknown user or an incorrect password.
@ Use a different login with greater privilege than %h(g.zLogin)
@ to access %h(zAbbrev).
}else if( anonFlag ){
@ Verify that you are human by typing in the 8-character text
@ password shown below. Login as a named user to access page %h(zAbbrev).
}
fossil_free(zAbbrev);
}
if( g.sslNotAvailable==0
&& strncmp(g.zBaseURL,"https:",6)!=0
&& db_get_boolean("https-login",0)
){
form_begin(0, "https:%s/login", g.zBaseURL+5);
}else{
form_begin(0, "%R/login");
}
if( zGoto ){
@
}
if( anonFlag ){
@
@
}
if( g.zLogin ){
@ Currently logged in as %h(g.zLogin).
@
if( !anonFlag ){
@ Visitors may enter anonymous as the user-ID with
@ the 8-character hexadecimal password shown below: Configure Email Alerts
@ for user %h(g.zLogin)
@ Forum
@ post timeline for user %h(g.zLogin)
@ Select your preferred site skin.
@
@ Manage your cookies or your
@ access tokens. Change Password for user %h(g.zLogin):
@ This password-reset URL is invalid, probably because it has expired.
@ Password-reset URLs have a short lifespan.
@
@ Cannot change the password for user %h(g.zLogin).
@
@ The two copies of your new passwords do not match.
@ Try again.
@ ", "
@ %s(zErr);
@ Password changed successfully. Go to the
@ Login page and log in
@ using the new password to continue.
@ Change Password for user %h(g.zLogin): Many hyperlinks are disabled.
if( P("HTTPS")==0 && !anonFlag ){
@
if( zAnonPw && !noAnon ){
const char *zDecoded = captcha_decode(uSeed, 0);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
@
}
if( !anonFlag ){
@ Warning:
@
@ Login information, including the password,
@ will be sent in the clear over an unencrypted connection.
if( !g.sslNotAvailable ){
@ Consider logging in at
@ %h(g.zHttpsURL) instead.
}
@
@
}
@ User ID:
@
@
@
@ Password:
@
if( anonFlag ){
@
@
if( !anonFlag ){
@ \
captcha_speakit_button(uSeed, "Read the password out loud");
}else if( zAnonPw && !noAnon ){
captcha_speakit_button(uSeed, "Speak password for \"anonymous\"");
}
@
@
@
@
@
@
@
@
}else{
@
@
@
@
}
if( !anonFlag && !noAnon && login_self_register_available(0) ){
@
@
@
@
}
if( !anonFlag && login_self_password_reset_available() ){
@
@
@
@
}
@
@
@
if( bAutoCaptcha && !anonFlag ) {
@
builtin_request_js("login.js");
}
@ \
@
@ %h(zCaptcha)
@
@
@
@
@
}
}
style_finish_page();
}
/*
** Construct an appropriate URL suffix for the /resetpw page. The
** suffix will be of the form:
**
** UID-TIMESTAMP-HASH
**
** Where UID and TIMESTAMP are the parameters to this function, and HASH
** is constructed from information that is unique to the user in question
** and which is not publicly available. In particular, the HASH includes
** the existing user password. Thus, in order to construct a URL that can
** change a password, an attacker must know the current password, in which
** case the attacker does not need to construct the URL in order to take
** over the account.
**
** Return a pointer to the resulting string in memory obtained
** from fossil_malloc().
*/
char *login_resetpw_suffix(int uid, i64 timestamp){
char *zHash;
char *zInnerSql;
char *zResult;
extern int sqlite3_shathree_init(sqlite3*,char**,const sqlite3_api_routines*);
if( timestamp<=0 ){ timestamp = time(0); }
sqlite3_shathree_init(g.db, 0, 0);
if( db_table_exists("repository","subscriber") ){
zInnerSql = mprintf(
"SELECT %lld, login, pw, cookie, user.mtime, user.info, subscriberCode"
" FROM user LEFT JOIN subscriber ON suname=login"
" WHERE uid=%d", timestamp, uid);
}else{
zInnerSql = mprintf(
"SELECT %lld, login, pw, cookie, user.mtime, user.info"
" FROM user WHERE uid=%d", timestamp, uid);
}
zHash = db_text(0, "SELECT lower(hex(sha3_query(%Q)))", zInnerSql);
fossil_free(zInnerSql);
zResult = mprintf("%x-%llx-%s", uid, timestamp, zHash);
if( strlen(zHash)<64 || strlen(zResult)<70 ){
/* This should never happen, but if it does, we don't want it to lead
** to a security breach. */
fossil_panic("insecure password reset hash generated\n");
}
fossil_free(zHash);
return zResult;
}
/*
** Check to see if the "name" query parameter is a valid resetpw suffix
** for a user whose password we are allowed to reset. If it is, then return
** the positive integer UID for that user. If the query parameter is not
** valid, return 0.
*/
static int login_resetpw_suffix_is_valid(const char *zName){
int i, j;
int uid;
i64 timestamp;
i64 now;
char *zHash;
if( zName==0 || strlen(zName)<70 ) goto not_valid_suffix;
for(i=0; fossil_isxdigit(zName[i]); i++){}
if( i<1 || zName[i]!='-' ) goto not_valid_suffix;
for(j=i+1; fossil_isxdigit(zName[j]); j++){}
if( j<=i+1 || zName[j]!='-' ) goto not_valid_suffix;
uid = strtol(zName, 0, 16);
if( uid<=0 ) goto not_valid_suffix;
if( !db_exists("SELECT 1 FROM user WHERE uid=%d", uid) ){
goto not_valid_suffix;
}
timestamp = strtoll(&zName[i+1], 0, 16);
now = time(0);
if( timestamp+3600 <= now ) goto not_valid_suffix;
zHash = login_resetpw_suffix(uid,timestamp);
if( fossil_strcmp(zHash, zName)!=0 ){
fossil_free(zHash);
goto not_valid_suffix;
}
fossil_free(zHash);
return uid;
not_valid_suffix:
return 0;
}
/*
** COMMAND: test-resetpw-url
** Usage: fossil test-resetpw-url UID
**
** Generate and verify a /resetpw URL for user UID.
**
** This command is intended for unit testing the login_resetpw_suffix()
** and login_resetpw_suffix_is_valid() functions.
*/
void test_resetpw_url(void){
char *zSuffix;
int uid;
int xuid;
char *zLogin;
int i;
db_find_and_open_repository(0, 0);
verify_all_options();
if( g.argc<3 ){
usage("UID ...");
}
for(i=2; i
@ Old Password:
@
@ New Password:
@ Suggestion: %z(zRPW)
@ Repeat New Password:
@
@
@
@
@
style_finish_page();
}
/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode. Transfer those credentials to the local
** repository.
**
** Return true if a transfer was made and false if not.
*/
static int login_transfer_credentials(
const char *zLogin, /* Login we are looking for */
const char *zCode, /* Project code of peer repository */
const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */
){
sqlite3 *pOther = 0; /* The other repository */
sqlite3_stmt *pStmt; /* Query against the other repository */
char *zSQL; /* SQL of the query against other repo */
char *zOtherRepo; /* Filename of the other repository */
int rc; /* Result code from SQLite library functions */
int nXfer = 0; /* Number of credentials transferred */
zOtherRepo = db_text(0,
"SELECT value FROM config WHERE name='peer-repo-%q'",
zCode
);
if( zOtherRepo==0 ) return 0; /* No such peer repository */
rc = sqlite3_open_v2(
zOtherRepo, &pOther,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
g.zVfsName
);
if( rc==SQLITE_OK ){
sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0);
sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
sqlite3_busy_timeout(pOther, 5000);
zSQL = mprintf(
"SELECT cexpire FROM user"
" WHERE login=%Q"
" AND octet_length(cap)>0"
" AND octet_length(pw)>0"
" AND cexpire>julianday('now')"
" AND constant_time_cmp(cookie,%Q)=0",
zLogin, zHash
);
pStmt = 0;
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
db_unprotect(PROTECT_USER);
db_multi_exec(
"UPDATE user SET cookie=%Q, cexpire=%.17g"
" WHERE login=%Q",
zHash,
sqlite3_column_double(pStmt, 0), zLogin
);
db_protect_pop();
nXfer++;
}
sqlite3_finalize(pStmt);
}
sqlite3_close(pOther);
fossil_free(zOtherRepo);
return nXfer;
}
/*
** Return TRUE if zLogin is one of the special usernames
*/
int login_is_special(const char *zLogin){
if( fossil_strcmp(zLogin, "anonymous")==0 ) return 1;
if( fossil_strcmp(zLogin, "nobody")==0 ) return 1;
if( fossil_strcmp(zLogin, "developer")==0 ) return 1;
if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
return 0;
}
/*
** Lookup the uid for a non-built-in user with zLogin and zCookie.
** Return 0 if not found.
**
** Note that this only searches for logged-in entries with matching
** zCookie (db: user.cookie) entries.
*/
static int login_find_user(
const char *zLogin, /* User name */
const char *zCookie /* Login cookie value */
){
int uid;
if( login_is_special(zLogin) ) return 0;
uid = db_int(0,
"SELECT uid FROM user"
" WHERE login=%Q"
" AND cexpire>julianday('now')"
" AND octet_length(cap)>0"
" AND octet_length(pw)>0"
" AND constant_time_cmp(cookie,%Q)=0",
zLogin, zCookie
);
return uid;
}
/*
** Attempt to use Basic Authentication to establish the user. Return the
** (non-zero) uid if successful. Return 0 if it does not work.
*/
static int login_basic_authentication(const char *zIpAddr){
const char *zAuth = PD("HTTP_AUTHORIZATION", 0);
int i;
int uid = 0;
int nDecode = 0;
char *zDecode = 0;
const char *zUsername = 0;
const char *zPasswd = 0;
if( zAuth==0 ) return 0; /* Fail: No Authentication: header */
while( fossil_isspace(zAuth[0]) ) zAuth++; /* Skip leading whitespace */
if( strncmp(zAuth, "Basic ", 6)!=0 ){
return 0; /* Fail: Not Basic Authentication */
}
/* Parse out the username and password, separated by a ":" */
zAuth += 6;
while( fossil_isspace(zAuth[0]) ) zAuth++;
zDecode = decode64(zAuth, &nDecode);
for(i=0; zDecode[i] && zDecode[i]!=':'; i++){}
if( zDecode[i] ){
zDecode[i] = 0;
zUsername = zDecode;
zPasswd = &zDecode[i+1];
/* Attempting to log in as the user provided by HTTP
** basic auth
*/
uid = login_search_uid(&zUsername, zPasswd);
if( uid>0 ){
record_login_attempt(zUsername, zIpAddr, 1);
}else{
record_login_attempt(zUsername, zIpAddr, 0);
/* The user attempted to login specifically with HTTP basic
** auth, but provided invalid credentials. Inform them of
** the failed login attempt via 401.
*/
cgi_set_status(401, "Unauthorized");
cgi_reply();
fossil_exit(0);
}
}
fossil_free(zDecode);
return uid;
}
/*
** When this routine is called, we know that the request does not
** have a login on the present repository. This routine checks to
** see if their login cookie might be for another member of the
** login-group.
**
** If this repository is not a part of any login group, then this
** routine always returns false.
**
** If this repository is part of a login group, and the login cookie
** appears to be well-formed, then return true. That might be a
** false-positive, as we don't actually check to see if the login
** cookie is valid for some other repository. But false-positives
** are ok. This routine is used for robot defense only.
*/
int login_cookie_wellformed(void){
const char *zCookie;
int n;
zCookie = P(login_cookie_name());
if( zCookie==0 ){
return 0;
}
if( !db_exists("SELECT 1 FROM config WHERE name='login-group-code'") ){
return 0;
}
for(n=0; fossil_isXdigit(zCookie[n]); n++){}
return n>48 && zCookie[n]=='/' && zCookie[n+1]!=0;
}
/*
** This routine examines the login cookie to see if it exists and
** is valid. If the login cookie checks out, it then sets global
** variables appropriately.
**
** g.userUid Database USER.UID value. Might be -1 for "nobody"
** g.zLogin Database USER.LOGIN value. NULL for user "nobody"
** g.perm Permissions granted to this user
** g.anon Permissions that would be available to anonymous
** g.isRobot True if the client is known to be a spider or robot
** g.perm Populated based on user account's capabilities
** g.eAuthMethod The mechanism used for authentication
**
*/
void login_check_credentials(void){
int uid = 0; /* User id */
const char *zCookie; /* Text of the login cookie */
const char *zIpAddr; /* Raw IP address of the requestor */
const char *zCap = 0; /* Capability string */
const char *zLogin = 0; /* Login user for credentials */
/* Only run this check once. */
if( g.userUid!=0 ) return;
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
/* If the HTTP connection is coming over 127.0.0.1 and if
** local login is disabled and if we are using HTTP and not HTTPS,
** then there is no need to check user credentials.
**
** This feature allows the "fossil ui" command to give the user
** full access rights without having to log in.
*/
zIpAddr = PD("REMOTE_ADDR","nil");
if( ( cgi_is_loopback(zIpAddr)
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
&& g.useLocalauth
&& db_get_boolean("localauth",0)==0
&& P("HTTPS")==0
){
char *zSeed;
if( g.localOpen ) zLogin = db_lget("default-user",0);
if( zLogin!=0 ){
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
}else{
uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
}
g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
zCap = "sxy";
g.noPswd = 1;
g.isRobot = 0;
g.eAuthMethod = AUTH_LOCAL;
zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)"
" FROM user WHERE uid=%d", uid);
login_create_csrf_secret(zSeed);
fossil_free(zSeed);
}
/* Check the login cookie to see if it matches a known valid user.
*/
if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
/* Parse the cookie value up into HASH/ARG/USER */
char *zHash = fossil_strdup(zCookie);
char *zArg = 0;
char *zUser = 0;
int i, c;
for(i=0; (c = zHash[i])!=0; i++){
if( c=='/' ){
zHash[i++] = 0;
if( zArg==0 ){
zArg = &zHash[i];
}else{
zUser = &zHash[i];
break;
}
}
}
if( zUser==0 ){
/* Invalid cookie */
}else if( fossil_strcmp(zUser, "anonymous")==0
&& anon_cookie_lifespan()>0 ){
/* Cookies of the form "HASH/TIME/anonymous". The TIME must
** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
** is the HTTP_USER_AGENT of the client and SECRET is the
** "captcha-secret" value in the repository. See tag-20250817a
** for the code the creates this cookie.
*/
double rTime = atof(zArg);
const char *zUserAgent = PD("HTTP_USER_AGENT","nil");
Blob b;
char *zSecret;
int n = 0;
do{
blob_zero(&b);
zSecret = captcha_secret(n++);
if( zSecret==0 ) break;
blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret);
sha1sum_blob(&b, &b);
if( fossil_strcmp(zHash, blob_str(&b))==0 ){
uid = db_int(0,
"SELECT uid FROM user WHERE login='anonymous'"
" AND octet_length(cap)>0"
" AND octet_length(pw)>0"
" AND %.17g>julianday('now')",
rTime+anon_cookie_lifespan()/1440.0
);
}
}while( uid==0 );
blob_reset(&b);
}else{
/* Cookies of the form "HASH/CODE/USER". Search first in the
** local user table, then the user table for project CODE if we
** are part of a login-group.
*/
uid = login_find_user(zUser, zHash);
if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){
uid = login_find_user(zUser, zHash);
if( uid ){
record_login_attempt(zUser, zIpAddr, 1);
}else{
/* The login cookie is a valid login for project CODE, but no
** user named USER exists on this repository. Cannot login as
** USER, but at least give them "anonymous" login. */
uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
" AND octet_length(cap)>0"
" AND octet_length(pw)>0");
}
}
}
if( uid ) g.eAuthMethod = AUTH_COOKIE;
login_create_csrf_secret(zHash);
}
/* If no user found and the REMOTE_USER environment variable is set,
** then accept the value of REMOTE_USER as the user.
*/
if( uid==0 ){
const char *zRemoteUser = P("REMOTE_USER");
if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
" AND octet_length(cap)>0 AND octet_length(pw)>0",
zRemoteUser);
if( uid ) g.eAuthMethod = AUTH_ENV;
}
}
/* If the request didn't provide a login cookie or the login cookie didn't
** match a known valid user, check the HTTP "Authorization" header and
** see if those credentials are valid for a known user.
*/
if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
uid = login_basic_authentication(zIpAddr);
if( uid ) g.eAuthMethod = AUTH_HTTP;
}
/* Check for magic query parameters "resid" (for the username) and
** "token" for the password. Both values (if they exist) will be
** obfuscated.
*/
if( uid==0 ){
char *zUsr, *zPW;
if( (zUsr = unobscure(P("resid")))!=0
&& (zPW = unobscure(P("token")))!=0
){
char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0);
uid = db_int(0, "SELECT uid FROM user"
" WHERE login=%Q"
" AND (constant_time_cmp(pw,%Q)=0"
" OR constant_time_cmp(pw,%Q)=0)",
zUsr, zSha1Pw, zPW);
fossil_free(zSha1Pw);
if( uid ) g.eAuthMethod = AUTH_PW;
}
}
/* If no user found yet, try to log in as "nobody" */
if( uid==0 ){
uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
if( uid==0 ){
/* If there is no user "nobody", then make one up - with no privileges */
uid = -1;
zCap = "";
}
login_create_csrf_secret("none");
}
login_set_uid(uid, zCap);
/* Maybe restrict access by robots */
if( g.zLogin==0 && robot_restrict(g.zPath) ){
cgi_reply();
fossil_exit(0);
}
}
/*
** Set the current logged in user to be uid. zCap is precomputed
** (override) capabilities. If zCap==0, then look up the capabilities
** in the USER table.
*/
int login_set_uid(int uid, const char *zCap){
const char *zPublicPages = 0; /* GLOB patterns of public pages */
/* At this point, we know that uid!=0. Find the privileges associated
** with user uid.
*/
assert( uid!=0 );
if( zCap==0 ){
Stmt s;
db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
if( db_step(&s)==SQLITE_ROW ){
g.zLogin = db_column_malloc(&s, 0);
zCap = db_column_malloc(&s, 1);
}
db_finalize(&s);
if( zCap==0 ){
zCap = "";
}
}
if( g.fHttpTrace && g.zLogin ){
fprintf(stderr, "# login: [%s] with capabilities [%s]\n", g.zLogin, zCap);
}
/* Set the global variables recording the userid and login. The
** "nobody" user is a special case in that g.zLogin==0.
*/
g.userUid = uid;
if( fossil_strcmp(g.zLogin,"nobody")==0 ){
g.zLogin = 0;
}
if( PB("isrobot") ){
g.isRobot = 1;
}else if( g.zLogin==0 ){
g.isRobot = !isHuman(P("HTTP_USER_AGENT"));
}else{
g.isRobot = 0;
}
/* Set the capabilities */
login_replace_capabilities(zCap, 0);
/* The auto-hyperlink setting allows hyperlinks to be displayed for users
** who do not have the "h" permission as long as their UserAgent string
** makes it appear that they are human. Check to see if auto-hyperlink is
** enabled for this repository and make appropriate adjustments to the
** permission flags if it is. This should be done before the permissions
** are (potentially) copied to the anonymous permission set; otherwise,
** those will be out-of-sync.
*/
if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){
int autoLink = db_get_int("auto-hyperlink",1);
if( autoLink==1 ){
g.jsHref = 1;
g.perm.Hyperlink = 1;
}else if( autoLink==2 ){
g.perm.Hyperlink = 1;
}
}
/*
** At this point, the capabilities for the logged in user are not going
** to be modified anymore; therefore, we can copy them over to the ones
** for the anonymous user.
**
** WARNING: In the future, please do not add code after this point that
** modifies the capabilities for the logged in user.
*/
login_set_anon_nobody_capabilities();
/* If the public-pages glob pattern is defined and REQUEST_URI matches
** one of the globs in public-pages, then also add in all default-perms
** permissions.
*/
zPublicPages = db_get("public-pages",0);
if( zPublicPages!=0 ){
const char *zUri = PD("REQUEST_URI","");
zUri += (int)strlen(g.zTop);
if( glob_multi_match(zPublicPages, zUri) ){
login_set_capabilities(db_get("default-perms", "u"), 0);
}
}
return g.zLogin!=0;
}
/*
** Memory of settings
*/
static int login_anon_once = 1;
/*
** Add to g.perm the default privileges of users "nobody" and/or "anonymous"
** as appropriate for the user g.zLogin.
**
** This routine also sets up g.anon to be either a copy of g.perm for
** all logged in uses, or the privileges that would be available to "anonymous"
** if g.zLogin==0 (meaning that the user is "nobody").
*/
void login_set_anon_nobody_capabilities(void){
if( login_anon_once ){
const char *zCap;
/* All users get privileges from "nobody" */
zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
login_set_capabilities(zCap, 0);
zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){
/* All logged-in users inherit privileges from "anonymous" */
login_set_capabilities(zCap, 0);
g.anon = g.perm;
}else{
/* Record the privileges of anonymous in g.anon */
g.anon = g.perm;
login_set_capabilities(zCap, LOGIN_ANON);
}
login_anon_once = 0;
}
}
/*
** Flags passed into the 2nd argument of login_set/replace_capabilities().
*/
#if INTERFACE
#define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */
#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */
#endif
/*
** Adds all capability flags in zCap to g.perm or g.anon.
*/
void login_set_capabilities(const char *zCap, unsigned flags){
int i;
FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
if(NULL==zCap){
return;
}
for(i=0; zCap[i]; i++){
switch( zCap[i] ){
case 's': p->Setup = 1; /* Fall thru into Admin */
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
p->RdWiki = p->WrWiki = p->NewWiki =
p->ApndWiki = p->Hyperlink = p->Clone =
p->NewTkt = p->Password = p->RdAddr =
p->TktFmt = p->Attach = p->ApndTkt =
p->ModWiki = p->ModTkt =
p->RdForum = p->WrForum = p->ModForum =
p->WrTForum = p->AdminForum = p->Chat =
p->EmailAlert = p->Announce = p->AttachForum =
p->Debug = 1;
/* Fall thru into Read/Write */
case 'i': p->Read = p->Write = 1; break;
case 'o': p->Read = 1; break;
case 'z': p->Zip = 1; break;
case 'h': p->Hyperlink = 1; break;
case 'g': p->Clone = 1; break;
case 'p': p->Password = 1; break;
case 'j': p->RdWiki = 1; break;
case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break;
case 'm': p->ApndWiki = 1; break;
case 'f': p->NewWiki = 1; break;
case 'l': p->ModWiki = 1; break;
case 'e': p->RdAddr = 1; break;
case 'r': p->RdTkt = 1; break;
case 'n': p->NewTkt = 1; break;
case 'w': p->WrTkt = p->RdTkt = p->NewTkt =
p->ApndTkt = 1; break;
case 'c': p->ApndTkt = 1; break;
case 'q': p->ModTkt = 1; break;
case 't': p->TktFmt = 1; break;
case 'b': p->Attach = 1; break;
case 'x': p->Private = 1; break;
case 'y': p->WrUnver = 1; break;
case '6': p->AdminForum = 1;
case '5': p->ModForum = 1;
case '4': p->WrTForum = 1;
case '3': p->WrForum = 1;
case '2': p->RdForum = 1; break;
case '7': p->EmailAlert = 1; break;
case 'A': p->Announce = 1; break;
case 'B': p->AttachForum = 1; break;
case 'C': p->Chat = 1; break;
case 'D': p->Debug = 1; break;
/* The "u" privilege recursively
** inherits all privileges of the user named "reader" */
case 'u': {
if( p->XReader==0 ){
const char *zUser;
p->XReader = 1;
zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
login_set_capabilities(zUser, flags);
}
break;
}
/* The "v" privilege recursively
** inherits all privileges of the user named "developer" */
case 'v': {
if( p->XDeveloper==0 ){
const char *zDev;
p->XDeveloper = 1;
zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
login_set_capabilities(zDev, flags);
}
break;
}
}
}
}
/*
** Zeroes out g.perm and calls login_set_capabilities(zCap,flags).
*/
void login_replace_capabilities(const char *zCap, unsigned flags){
memset(&g.perm, 0, sizeof(g.perm));
login_set_capabilities(zCap, flags);
login_anon_once = 1;
}
/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0. If all capabilities are present, then
** return 1.
**
** As a special case, the 'L' pseudo-capability ID means "is logged
** in" and will return true for any non-guest user.
*/
int login_has_capability(const char *zCap, int nCap, u32 flgs){
int i;
int rc = 1;
FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
if( nCap<0 ) nCap = strlen(zCap);
for(i=0; i
@ New Password:
@ Suggestion: %z(zRPW)
@ Repeat New Password:
@
@
@
@ Use anonymous login
@ to enable hyperlinks.
The following internal error was encountered while trying @ to send the confirmation email: @
@ %h(pSender->zErr)
@
}else{
@ An email has been sent to "%h(zEAddr)". That email contains a @ hyperlink that you must click to activate your account.
} alert_sender_free(pSender); if( zGoto ){ @Continue } style_finish_page(); return; } login_redirect_to_g(); } /* Prepare the captcha. */ if( captchaIsCorrect ){ uSeed = strtoul(P("captchaseed"),0,10); }else{ uSeed = captcha_seed(); } zDecoded = captcha_decode(uSeed, 0); zCaptcha = captcha_render(zDecoded); style_header("Register"); /* Print out the registration form. */ g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */ form_begin(0, "%R/register"); if( P("g") ){ @ } @
@
| User ID: | @@ if( iErrLine==1 ){ @ |
| ↑ %h(zErr) | |
| Display Name: | @@ |
| ↑ %h(zErr) | |
| Email Address: | @@ |
| ↑ %h(zErr)
if( uid>0 && login_self_password_reset_available() ){
@ @ } @ | |
| Email Alerts? | @|
| Password: | @\ if( zPasswd[0]==0 ){ char *zRPW = fossil_random_password(12); @ Password suggestion: %z(zRPW) | }else{ @ } @
| ↑ %h(zErr) | |
| Confirm: | @@ |
| ↑ %h(zErr) | |
| Captcha: | @captcha_speakit_button(uSeed, "Speak the captcha text"); @ | @
| ↑ %h(zErr) | |
| @ |
@ %h(zCaptcha) @@ Enter this 8-letter code in the "Captcha" box above. @ |
This project does not allow users to reset their own passwords. @ If you need a password reset, you will have to negotiate that directly @ with the project administrator. style_finish_page(); return; } zEAddr = PDT("ea",""); /* Verify user inputs */ if( !cgi_csrf_safe(1) || P("reqpwreset")==0 ){ /* This is the initial display of the form. No processing or error ** checking is to be done. Fall through into the form display ** ** cgi_csrf_safe(): Nothing interesting happens on this page without ** a valid captcha solution, so we only need to check referrer and that ** the request is a POST. */ }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ iErrLine = 2; zErr = "Incorrect CAPTCHA"; }else if( zEAddr[0]==0 ){ iErrLine = 1; zErr = "Required"; }else if( email_address_is_valid(zEAddr,0)==0 ){ iErrLine = 1; zErr = "Not a valid email address"; }else if( authorized_subscription_email(zEAddr)==0 ){ iErrLine = 1; zErr = "Not an authorized email address"; }else if( (uid = email_address_in_use(zEAddr))<=0 ){ iErrLine = 1; zErr = "This email address is not associated with a user who has " "password reset privileges."; }else if( login_set_uid(uid,0)==0 || g.perm.Admin || g.perm.Setup || !g.perm.Password ){ iErrLine = 1; zErr = "This email address is not associated with a user who has " "password reset privileges."; }else{ /* If all of the tests above have passed, that means that the submitted ** form contains valid data and we can proceed to issue the password ** reset email. */ Blob hdr, body; AlertSender *pSender; char *zUrl = login_resetpw_suffix(uid, 0); pSender = alert_sender_new(0,0); blob_init(&hdr,0,0); blob_init(&body,0,0); blob_appendf(&hdr, "To: <%s>\n", zEAddr); blob_appendf(&hdr, "Subject: Password reset for %s\n", g.zBaseURL); blob_appendf(&body, "Someone has requested to reset the password for user \"%s\"\n", g.zLogin); blob_appendf(&body, "at %s.\n\n", g.zBaseURL); blob_appendf(&body, "If you did not request this password reset, ignore\n" "this email\n\n"); blob_appendf(&body, "To reset the password, visit the following link:\n\n" " %s/resetpw/%s\n\n", g.zBaseURL, zUrl); fossil_free(zUrl); alert_send(pSender, &hdr, &body, 0); style_header("Email Verification"); if( pSender->zErr ){ @
The following internal error was encountered while trying @ to send the confirmation email: @
@ %h(pSender->zErr)
@
}else{
@ An email containing a hyperlink that can be used to reset @ your password has been sent to "%h(zEAddr)".
} alert_sender_free(pSender); style_finish_page(); return; } /* Prepare the captcha. */ if( captchaIsCorrect ){ uSeed = strtoul(P("captchaseed"),0,10); }else{ uSeed = captcha_seed(); } zDecoded = captcha_decode(uSeed, 0); zCaptcha = captcha_render(zDecoded); style_header("Request Password Reset"); /* Print out the registration form. */ g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */ form_begin(0, "%R/reqpwreset"); @@
@
| Email Address: | @@ |
| ↑ %h(zErr) | |
| Captcha: | @captcha_speakit_button(uSeed, "Speak the captcha text"); @ | @
| ↑ %h(zErr) | |
| @ | \ @ |
@ %h(zCaptcha) @@ Enter this 8-letter code in the "Captcha" box above. @ |