Fossil

Artifact [0fda31b6]
Login

Artifact 0fda31b6683c206ac2cc527270d3228d8d9995b4cf18c438751b1bf3f6865ad8:


     1  /*
     2  ** Copyright (c) 2007 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  **   drh@hwaci.com
    14  **   http://www.hwaci.com/drh/
    15  **
    16  *******************************************************************************
    17  **
    18  ** This file contains code for generating the login and logout screens.
    19  **
    20  ** Notes:
    21  **
    22  ** There are four special-case user-ids:  "anonymous", "nobody",
    23  ** "developer" and "reader".
    24  **
    25  ** The capabilities of the nobody user are available to anyone,
    26  ** regardless of whether or not they are logged in.  The capabilities
    27  ** of anonymous are only available after logging in, but the login
    28  ** screen displays the password for the anonymous login, so this
    29  ** should not prevent a human user from doing so.  The capabilities
    30  ** of developer and reader are inherited by any user that has the
    31  ** "v" and "u" capabilities, respectively.
    32  **
    33  ** The nobody user has capabilities that you want spiders to have.
    34  ** The anonymous user has capabilities that you want people without
    35  ** logins to have.
    36  **
    37  ** Of course, a sophisticated spider could easily circumvent the
    38  ** anonymous login requirement and walk the website.  But that is
    39  ** not really the point.  The anonymous login keeps search-engine
    40  ** crawlers and site download tools like wget from walking change
    41  ** logs and downloading diffs of very version of the archive that
    42  ** has ever existed, and things like that.
    43  */
    44  #include "config.h"
    45  #include "login.h"
    46  #if defined(_WIN32)
    47  #  include <windows.h>           /* for Sleep */
    48  #  if defined(__MINGW32__) || defined(_MSC_VER)
    49  #    define sleep Sleep            /* windows does not have sleep, but Sleep */
    50  #  endif
    51  #endif
    52  #include <time.h>
    53  
    54  
    55  /*
    56  ** Return the login-group name.  Or return 0 if this repository is
    57  ** not a member of a login-group.
    58  */
    59  const char *login_group_name(void){
    60    static const char *zGroup = 0;
    61    static int once = 1;
    62    if( once ){
    63      zGroup = db_get("login-group-name", 0);
    64      once = 0;
    65    }
    66    return zGroup;
    67  }
    68  
    69  /*
    70  ** Return a path appropriate for setting a cookie.
    71  **
    72  ** The path is g.zTop for single-repo cookies.  It is "/" for
    73  ** cookies of a login-group.
    74  */
    75  const char *login_cookie_path(void){
    76    if( login_group_name()==0 ){
    77      return g.zTop;
    78    }else{
    79      return "/";
    80    }
    81  }
    82  
    83  /*
    84  ** Return the name of the login cookie.
    85  **
    86  ** The login cookie name is always of the form:  fossil-XXXXXXXXXXXXXXXX
    87  ** where the Xs are the first 16 characters of the login-group-code or
    88  ** of the project-code if we are not a member of any login-group.
    89  */
    90  char *login_cookie_name(void){
    91    static char *zCookieName = 0;
    92    if( zCookieName==0 ){
    93      zCookieName = db_text(0,
    94         "SELECT 'fossil-' || substr(value,1,16)"
    95         "  FROM config"
    96         " WHERE name IN ('project-code','login-group-code')"
    97         " ORDER BY name /*sort*/"
    98      );
    99    }
   100    return zCookieName;
   101  }
   102  
   103  /*
   104  ** Redirect to the page specified by the "g" query parameter.
   105  ** Or if there is no "g" query parameter, redirect to the homepage.
   106  */
   107  static void redirect_to_g(void){
   108    const char *zGoto = P("g");
   109    if( zGoto ){
   110      cgi_redirect(zGoto);
   111    }else{
   112      fossil_redirect_home();
   113    }
   114  }
   115  
   116  /*
   117  ** The IP address of the client is stored as part of login cookies.
   118  ** But some clients are behind firewalls that shift the IP address
   119  ** with each HTTP request.  To allow such (broken) clients to log in,
   120  ** extract just a prefix of the IP address.
   121  */
   122  static char *ipPrefix(const char *zIP){
   123    int i, j;
   124    static int ip_prefix_terms = -1;
   125    if( ip_prefix_terms<0 ){
   126      ip_prefix_terms = db_get_int("ip-prefix-terms",2);
   127    }
   128    if( ip_prefix_terms==0 ) return mprintf("0");
   129    for(i=j=0; zIP[i]; i++){
   130      if( zIP[i]=='.' ){
   131        j++;
   132        if( j==ip_prefix_terms ) break;
   133      }
   134    }
   135    return mprintf("%.*s", i, zIP);
   136  }
   137  
   138  /*
   139  ** Return an abbreviated project code.  The abbreviation is the first
   140  ** 16 characters of the project code.
   141  **
   142  ** Memory is obtained from malloc.
   143  */
   144  static char *abbreviated_project_code(const char *zFullCode){
   145    return mprintf("%.16s", zFullCode);
   146  }
   147  
   148  
   149  /*
   150  ** Check to see if the anonymous login is valid.  If it is valid, return
   151  ** the userid of the anonymous user.
   152  **
   153  ** The zCS parameter is the "captcha seed" used for a specific
   154  ** anonymous login request.
   155  */
   156  int login_is_valid_anonymous(
   157    const char *zUsername,  /* The username.  Must be "anonymous" */
   158    const char *zPassword,  /* The supplied password */
   159    const char *zCS         /* The captcha seed value */
   160  ){
   161    const char *zPw;        /* The correct password shown in the captcha */
   162    int uid;                /* The user ID of anonymous */
   163  
   164    if( zUsername==0 ) return 0;
   165    else if( zPassword==0 ) return 0;
   166    else if( zCS==0 ) return 0;
   167    else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
   168    zPw = captcha_decode((unsigned int)atoi(zCS));
   169    if( fossil_stricmp(zPw, zPassword)!=0 ) return 0;
   170    uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
   171                    " AND length(pw)>0 AND length(cap)>0");
   172    return uid;
   173  }
   174  
   175  /*
   176  ** Make sure the accesslog table exists.  Create it if it does not
   177  */
   178  void create_accesslog_table(void){
   179    db_multi_exec(
   180      "CREATE TABLE IF NOT EXISTS repository.accesslog("
   181      "  uname TEXT,"
   182      "  ipaddr TEXT,"
   183      "  success BOOLEAN,"
   184      "  mtime TIMESTAMP"
   185      ");"
   186    );
   187  }
   188  
   189  /*
   190  ** Make a record of a login attempt, if login record keeping is enabled.
   191  */
   192  static void record_login_attempt(
   193    const char *zUsername,     /* Name of user logging in */
   194    const char *zIpAddr,       /* IP address from which they logged in */
   195    int bSuccess               /* True if the attempt was a success */
   196  ){
   197    if( !db_get_boolean("access-log", 0) ) return;
   198    create_accesslog_table();
   199    db_multi_exec(
   200      "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
   201      "VALUES(%Q,%Q,%d,julianday('now'));",
   202      zUsername, zIpAddr, bSuccess
   203    );
   204  }
   205  
   206  /*
   207  ** Searches for the user ID matching the given name and password.
   208  ** On success it returns a positive value. On error it returns 0.
   209  ** On serious (DB-level) error it will probably exit.
   210  **
   211  ** zUsername uses double indirection because we may re-point *zUsername
   212  ** at a C string allocated with fossil_strdup() if you pass an email
   213  ** address instead and we find that address in the user table's info
   214  ** field, which is expected to contain a string of the form "Human Name
   215  ** <human@example.com>".  In that case, *zUsername will point to that
   216  ** user's actual login name on return, causing a leak unless the caller
   217  ** is diligent enough to check whether its pointer was re-pointed.
   218  **
   219  ** zPassword may be either the plain-text form or the encrypted
   220  ** form of the user's password.
   221  */
   222  int login_search_uid(const char **pzUsername, const char *zPasswd){
   223    char *zSha1Pw = sha1_shared_secret(zPasswd, *pzUsername, 0);
   224    int uid = db_int(0,
   225      "SELECT uid FROM user"
   226      " WHERE login=%Q"
   227      "   AND length(cap)>0 AND length(pw)>0"
   228      "   AND login NOT IN ('anonymous','nobody','developer','reader')"
   229      "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))"
   230      "   AND (info NOT LIKE '%%expires 20%%'"
   231      "      OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))",
   232      *pzUsername, zSha1Pw, zPasswd
   233    );
   234  
   235    /* If we did not find a login on the first attempt, and the username
   236    ** looks like an email address, then perhaps the user entered their
   237    ** email address instead of their login.  Try again to match the user
   238    ** against email addresses contained in the "info" field.
   239    */
   240    if( uid==0 && strchr(*pzUsername,'@')!=0 ){
   241      Stmt q;
   242      db_prepare(&q,
   243        "SELECT login FROM user"
   244        " WHERE find_emailaddr(info)=%Q"
   245        "   AND instr(login,'@')==0",
   246        *pzUsername
   247      );
   248      while( db_step(&q)==SQLITE_ROW ){
   249        const char *zLogin = db_column_text(&q,0);
   250        if( (uid = login_search_uid(&zLogin, zPasswd) ) != 0 ){
   251          *pzUsername = fossil_strdup(zLogin);
   252          break;
   253        }
   254      }
   255      db_finalize(&q);
   256    }    
   257    free(zSha1Pw);
   258    return uid;
   259  }
   260  
   261  /*
   262  ** Generates a login cookie value for a non-anonymous user.
   263  **
   264  ** The zHash parameter must be a random value which must be
   265  ** subsequently stored in user.cookie for later validation.
   266  **
   267  ** The returned memory should be free()d after use.
   268  */
   269  char *login_gen_user_cookie_value(const char *zUsername, const char *zHash){
   270    char *zProjCode = db_get("project-code",NULL);
   271    char *zCode = abbreviated_project_code(zProjCode);
   272    free(zProjCode);
   273    assert((zUsername && *zUsername) && "Invalid user data.");
   274    return mprintf("%s/%z/%s", zHash, zCode, zUsername);
   275  }
   276  
   277  /*
   278  ** Generates a login cookie for NON-ANONYMOUS users.  Note that this
   279  ** function "could" figure out the uid by itself but it currently
   280  ** doesn't because the code which calls this already has the uid.
   281  **
   282  ** This function also updates the user.cookie, user.ipaddr,
   283  ** and user.cexpire fields for the given user.
   284  **
   285  ** If zDest is not NULL then the generated cookie is copied to
   286  ** *zDdest and ownership is transfered to the caller (who should
   287  ** eventually pass it to free()).
   288  */
   289  void login_set_user_cookie(
   290    const char *zUsername,  /* User's name */
   291    int uid,                /* User's ID */
   292    char **zDest            /* Optional: store generated cookie value. */
   293  ){
   294    const char *zCookieName = login_cookie_name();
   295    const char *zExpire = db_get("cookie-expire","8766");
   296    int expires = atoi(zExpire)*3600;
   297    char *zHash;
   298    char *zCookie;
   299    const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
   300    char *zRemoteAddr = ipPrefix(zIpAddr);         /* Abbreviated IP address */
   301  
   302    assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
   303    zHash = db_text(0,
   304        "SELECT cookie FROM user"
   305        " WHERE uid=%d"
   306        "   AND ipaddr=%Q"
   307        "   AND cexpire>julianday('now')"
   308        "   AND length(cookie)>30",
   309        uid, zRemoteAddr);
   310    if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
   311    zCookie = login_gen_user_cookie_value(zUsername, zHash);
   312    cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
   313    record_login_attempt(zUsername, zIpAddr, 1);
   314    db_multi_exec(
   315                  "UPDATE user SET cookie=%Q, ipaddr=%Q, "
   316                  "  cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
   317                  zHash, zRemoteAddr, expires, uid
   318                  );
   319    free(zRemoteAddr);
   320    free(zHash);
   321    if( zDest ){
   322      *zDest = zCookie;
   323    }else{
   324      free(zCookie);
   325    }
   326  }
   327  
   328  /* Sets a cookie for an anonymous user login, which looks like this:
   329  **
   330  **    HASH/TIME/anonymous
   331  **
   332  ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
   333  ** is the abbreviated IP address and SECRET is captcha-secret.
   334  **
   335  ** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
   336  ** is used.
   337  **
   338  ** If zCookieDest is not NULL then the generated cookie is assigned to
   339  ** *zCookieDest and the caller must eventually free() it.
   340  */
   341  void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){
   342    const char *zNow;            /* Current time (julian day number) */
   343    char *zCookie;               /* The login cookie */
   344    const char *zCookieName;     /* Name of the login cookie */
   345    Blob b;                      /* Blob used during cookie construction */
   346    char *zRemoteAddr;           /* Abbreviated IP address */
   347    if(!zIpAddr){
   348      zIpAddr = PD("REMOTE_ADDR","nil");
   349    }
   350    zRemoteAddr = ipPrefix(zIpAddr);
   351    zCookieName = login_cookie_name();
   352    zNow = db_text("0", "SELECT julianday('now')");
   353    assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
   354    blob_init(&b, zNow, -1);
   355    blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
   356    sha1sum_blob(&b, &b);
   357    zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
   358    blob_reset(&b);
   359    cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
   360    if( zCookieDest ){
   361      *zCookieDest = zCookie;
   362    }else{
   363      free(zCookie);
   364    }
   365  
   366  }
   367  
   368  /*
   369  ** "Unsets" the login cookie (insofar as cookies can be unset) and
   370  ** clears the current user's (g.userUid) login information from the
   371  ** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
   372  **
   373  ** We could/should arguably clear out g.userUid and g.perm here, but
   374  ** we don't currently do not.
   375  **
   376  ** This is a no-op if g.userUid is 0.
   377  */
   378  void login_clear_login_data(){
   379    if(!g.userUid){
   380      return;
   381    }else{
   382      const char *cookie = login_cookie_name();
   383      /* To logout, change the cookie value to an empty string */
   384      cgi_set_cookie(cookie, "",
   385                     login_cookie_path(), -86400);
   386      db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
   387                    "  cexpire=0 WHERE uid=%d"
   388                    "  AND login NOT IN ('anonymous','nobody',"
   389                    "  'developer','reader')", g.userUid);
   390      cgi_replace_parameter(cookie, NULL);
   391      cgi_replace_parameter("anon", NULL);
   392    }
   393  }
   394  
   395  /*
   396  ** Return true if the prefix of zStr matches zPattern.  Return false if
   397  ** they are different.
   398  **
   399  ** A lowercase character in zPattern will match either upper or lower
   400  ** case in zStr.  But an uppercase in zPattern will only match an
   401  ** uppercase in zStr.
   402  */
   403  static int prefix_match(const char *zPattern, const char *zStr){
   404    int i;
   405    char c;
   406    for(i=0; (c = zPattern[i])!=0; i++){
   407      if( zStr[i]!=c && fossil_tolower(zStr[i])!=c ) return 0;
   408    }
   409    return 1;
   410  }
   411  
   412  /*
   413  ** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent
   414  ** is a manually operated browser or a bot.  When in doubt, assume a bot.
   415  ** Return true if we believe the agent is a real person.
   416  */
   417  static int isHuman(const char *zAgent){
   418    int i;
   419    if( zAgent==0 ) return 0;  /* If no UserAgent, then probably a bot */
   420    for(i=0; zAgent[i]; i++){
   421      if( prefix_match("bot", zAgent+i) ) return 0;
   422      if( prefix_match("spider", zAgent+i) ) return 0;
   423      if( prefix_match("crawl", zAgent+i) ) return 0;
   424      /* If a URI appears in the User-Agent, it is probably a bot */
   425      if( strncmp("http", zAgent+i,4)==0 ) return 0;
   426    }
   427    if( strncmp(zAgent, "Mozilla/", 8)==0 ){
   428      if( atoi(&zAgent[8])<4 ) return 0;  /* Many bots advertise as Mozilla/3 */
   429  
   430      /* 2016-05-30:  A pernicious spider that likes to walk Fossil timelines has
   431      ** been detected on the SQLite website.  The spider changes its user-agent
   432      ** string frequently, but it always seems to include the following text:
   433      */
   434      if( sqlite3_strglob("*Safari/537.36Mozilla/5.0*", zAgent)==0 ) return 0;
   435  
   436      if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1;
   437      if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1;
   438      if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1;
   439      if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ) return 1; /* IE11+ */
   440      if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1;
   441      return 0;
   442    }
   443    if( strncmp(zAgent, "Opera/", 6)==0 ) return 1;
   444    if( strncmp(zAgent, "Safari/", 7)==0 ) return 1;
   445    if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1;
   446    if( strncmp(zAgent, "NetSurf/", 8)==0 ) return 1;
   447    return 0;
   448  }
   449  
   450  /*
   451  ** COMMAND: test-ishuman
   452  **
   453  ** Read lines of text from standard input.  Interpret each line of text
   454  ** as a User-Agent string from an HTTP header.  Label each line as HUMAN
   455  ** or ROBOT.
   456  */
   457  void test_ishuman(void){
   458    char zLine[3000];
   459    while( fgets(zLine, sizeof(zLine), stdin) ){
   460      fossil_print("%s %s", isHuman(zLine) ? "HUMAN" : "ROBOT", zLine);
   461    }
   462  }
   463  
   464  /*
   465  ** SQL function for constant time comparison of two values.
   466  ** Sets result to 0 if two values are equal.
   467  */
   468  static void constant_time_cmp_function(
   469   sqlite3_context *context,
   470   int argc,
   471   sqlite3_value **argv
   472  ){
   473    const unsigned char *buf1, *buf2;
   474    int len, i;
   475    unsigned char rc = 0;
   476  
   477    assert( argc==2 );
   478    len = sqlite3_value_bytes(argv[0]);
   479    if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
   480      rc = 1;
   481    }else{
   482      buf1 = sqlite3_value_text(argv[0]);
   483      buf2 = sqlite3_value_text(argv[1]);
   484      for( i=0; i<len; i++ ){
   485        rc = rc | (buf1[i] ^ buf2[i]);
   486      }
   487    }
   488    sqlite3_result_int(context, rc);
   489  }
   490  
   491  /*
   492  ** Return true if the current page was reached by a redirect from the /login
   493  ** page.
   494  */
   495  int referred_from_login(void){
   496    const char *zReferer = P("HTTP_REFERER");
   497    char *zPattern;
   498    int rc;
   499    if( zReferer==0 ) return 0;
   500    zPattern = mprintf("%s/login*", g.zBaseURL);
   501    rc = sqlite3_strglob(zPattern, zReferer)==0;
   502    fossil_free(zPattern);
   503    return rc;
   504  }
   505  
   506  /*
   507  ** Return TRUE if self-registration is available.  If the zNeeded
   508  ** argument is not NULL, then only return true if self-registration is
   509  ** available and any of the capabilities named in zNeeded are available
   510  ** to self-registered users.
   511  */
   512  int login_self_register_available(const char *zNeeded){
   513    CapabilityString *pCap;
   514    int rc;
   515    if( !db_get_boolean("self-register",0) ) return 0;
   516    if( zNeeded==0 ) return 1;
   517    pCap = capability_add(0, db_get("default-perms",""));
   518    capability_expand(pCap);
   519    rc = capability_has_any(pCap, zNeeded);
   520    capability_free(pCap);
   521    return rc;
   522  }
   523  
   524  /*
   525  ** There used to be a page named "my" that was designed to show information
   526  ** about a specific user.  The "my" page was linked from the "Logged in as USER"
   527  ** line on the title bar.  The "my" page was never completed so it is now
   528  ** removed.  Use this page as a placeholder in older installations.
   529  **
   530  ** WEBPAGE: login
   531  ** WEBPAGE: logout
   532  ** WEBPAGE: my
   533  **
   534  ** The login/logout page.  Parameters:
   535  **
   536  **    g=URL             Jump back to this URL after login completes
   537  **    anon              The g=URL is not accessible by "nobody" but is
   538  **                      accessible by "anonymous"
   539  */
   540  void login_page(void){
   541    const char *zUsername, *zPasswd;
   542    const char *zNew1, *zNew2;
   543    const char *zAnonPw = 0;
   544    const char *zGoto = P("g");
   545    int anonFlag;                /* Login as "anonymous" would be useful */
   546    char *zErrMsg = "";
   547    int uid;                     /* User id logged in user */
   548    char *zSha1Pw;
   549    const char *zIpAddr;         /* IP address of requestor */
   550    const char *zReferer;
   551    int noAnon = P("noanon")!=0;
   552  
   553    login_check_credentials();
   554    if( login_wants_https_redirect() ){
   555      const char *zQS = P("QUERY_STRING");
   556      if( P("redir")!=0 ){
   557        style_header("Insecure Connection");
   558        @ <h1>Unable To Establish An Encrypted Connection</h1>
   559        @ <p>This website requires that login credentials be sent over
   560        @ an encrypted connection.  The current connection is not encrypted
   561        @ across the entire route between your browser and the server.
   562        @ An attempt was made to redirect to %h(g.zHttpsURL) but
   563        @ the connection is still insecure even after the redirect.</p>
   564        @ <p>This is probably some kind of configuration problem.  Please
   565        @ contact your sysadmin.</p>
   566        @ <p>Sorry it did not work out.</p>
   567        style_footer();
   568        return;
   569      }
   570      if( zQS==0 ){
   571        zQS = "?redir=1";
   572      }else if( zQS[0]!=0 ){
   573        zQS = mprintf("?%s&redir=1", zQS);
   574      }
   575      cgi_redirectf("%s%T%s", g.zHttpsURL, P("PATH_INFO"), zQS);
   576      return;
   577    }
   578    sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
   579                    constant_time_cmp_function, 0, 0);
   580    zUsername = P("u");
   581    zPasswd = P("p");
   582    anonFlag = g.zLogin==0 && PB("anon");
   583  
   584    /* Handle log-out requests */
   585    if( P("out") ){
   586      login_clear_login_data();
   587      redirect_to_g();
   588      return;
   589    }
   590  
   591    /* Redirect for create-new-account requests */
   592    if( P("self") ){
   593      cgi_redirectf("%R/register");
   594      return;
   595    }
   596  
   597    /* Deal with password-change requests */
   598    if( g.perm.Password && zPasswd
   599     && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
   600    ){
   601      /* If there is not a "real" login, we cannot change any password. */
   602      if( g.zLogin ){
   603        /* The user requests a password change */
   604        zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
   605        if( db_int(1, "SELECT 0 FROM user"
   606                      " WHERE uid=%d"
   607                      " AND (constant_time_cmp(pw,%Q)=0"
   608                      "      OR constant_time_cmp(pw,%Q)=0)",
   609                      g.userUid, zSha1Pw, zPasswd) ){
   610          sleep(1);
   611          zErrMsg =
   612             @ <p><span class="loginError">
   613             @ You entered an incorrect old password while attempting to change
   614             @ your password.  Your password is unchanged.
   615             @ </span></p>
   616          ;
   617        }else if( fossil_strcmp(zNew1,zNew2)!=0 ){
   618          zErrMsg =
   619             @ <p><span class="loginError">
   620             @ The two copies of your new passwords do not match.
   621             @ Your password is unchanged.
   622             @ </span></p>
   623          ;
   624        }else{
   625          char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
   626          char *zChngPw;
   627          char *zErr;
   628          db_multi_exec(
   629             "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
   630          );
   631          fossil_free(zNewPw);
   632          zChngPw = mprintf(
   633             "UPDATE user"
   634             "   SET pw=shared_secret(%Q,%Q,"
   635             "        (SELECT value FROM config WHERE name='project-code'))"
   636             " WHERE login=%Q",
   637             zNew1, g.zLogin, g.zLogin
   638          );
   639          if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){
   640            zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
   641            fossil_free(zErr);
   642          }else{
   643            redirect_to_g();
   644            return;
   645          }
   646        }
   647      }else{
   648        zErrMsg =
   649           @ <p><span class="loginError">
   650           @ The password cannot be changed for this type of login.
   651           @ The password is unchanged.
   652           @ </span></p>
   653        ;
   654      }
   655    }
   656    zIpAddr = PD("REMOTE_ADDR","nil");   /* Complete IP address for logging */
   657    zReferer = P("HTTP_REFERER");
   658    uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
   659    if( uid>0 ){
   660      login_set_anon_cookie(zIpAddr, NULL);
   661      record_login_attempt("anonymous", zIpAddr, 1);
   662      redirect_to_g();
   663    }
   664    if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
   665      /* Attempting to log in as a user other than anonymous.
   666      */
   667      uid = login_search_uid(&zUsername, zPasswd);
   668      if( uid<=0 ){
   669        sleep(1);
   670        zErrMsg =
   671           @ <p><span class="loginError">
   672           @ You entered an unknown user or an incorrect password.
   673           @ </span></p>
   674        ;
   675        record_login_attempt(zUsername, zIpAddr, 0);
   676      }else{
   677        /* Non-anonymous login is successful.  Set a cookie of the form:
   678        **
   679        **    HASH/PROJECT/LOGIN
   680        **
   681        ** where HASH is a random hex number, PROJECT is either project
   682        ** code prefix, and LOGIN is the user name.
   683        */
   684        login_set_user_cookie(zUsername, uid, NULL);
   685        redirect_to_g();
   686      }
   687    }
   688    style_header("Login/Logout");
   689    style_adunit_config(ADUNIT_OFF);
   690    @ %s(zErrMsg)
   691    if( zGoto && !noAnon ){
   692      char *zAbbrev = fossil_strdup(zGoto);
   693      int i;
   694      for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
   695      zAbbrev[i] = 0;
   696      if( g.zLogin ){
   697        @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
   698        @ to access <b>%h(zAbbrev)</b>.
   699      }else if( anonFlag ){
   700        @ <p>Login as <b>anonymous</b> or any named user
   701        @ to access page <b>%h(zAbbrev)</b>.
   702      }else{
   703        @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
   704      }
   705    }
   706    if( g.sslNotAvailable==0
   707     && strncmp(g.zBaseURL,"https:",6)!=0
   708     && db_get_boolean("https-login",0)
   709    ){
   710      form_begin(0, "https:%s/login", g.zBaseURL+5);
   711    }else{
   712      form_begin(0, "%R/login");
   713    }
   714    if( zGoto ){
   715      @ <input type="hidden" name="g" value="%h(zGoto)" />
   716    }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
   717      @ <input type="hidden" name="g" value="%h(zReferer)" />
   718    }
   719    if( anonFlag ){
   720      @ <input type="hidden" name="anon" value="1" />
   721    }
   722    if( g.zLogin ){
   723      @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
   724      @ <input type="submit" name="out" value="Logout"></p>
   725    }else{
   726      @ <table class="login_out">
   727      @ <tr>
   728      @   <td class="form_label">User ID:</td>
   729      if( anonFlag ){
   730        @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
   731      }else{
   732        @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
   733      }
   734      @ </tr>
   735      @ <tr>
   736      @  <td class="form_label">Password:</td>
   737      @  <td><input type="password" id="p" name="p" value="" size="30" /></td>
   738      @ </tr>
   739      if( P("HTTPS")==0 ){
   740        @ <tr><td class="form_label">Warning:</td>
   741        @ <td><span class='securityWarning'>
   742        @ Your password will be sent in the clear over an
   743        @ unencrypted connection.
   744        if( g.sslNotAvailable ){
   745          @ No encrypted connection is available on this server.
   746        }else{
   747          @ Consider logging in at
   748          @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
   749        }
   750        @ </span></td></tr>
   751      }
   752      if( g.zLogin==0 && (anonFlag || zGoto==0) ){
   753        zAnonPw = db_text(0, "SELECT pw FROM user"
   754                             " WHERE login='anonymous'"
   755                             "   AND cap!=''");
   756      }
   757      @ <tr>
   758      @   <td></td>
   759      @   <td><input type="submit" name="in" value="Login"></td>
   760      @ </tr>
   761      if( !noAnon && login_self_register_available(0) ){
   762        @ <tr>
   763        @   <td></td>
   764        @   <td><input type="submit" name="self" value="Create A New Account">
   765        @ </tr>
   766      }
   767      @ </table>
   768      if( zAnonPw && !noAnon ){
   769        unsigned int uSeed = captcha_seed();
   770        const char *zDecoded = captcha_decode(uSeed);
   771        int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
   772        char *zCaptcha = captcha_render(zDecoded);
   773    
   774        @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
   775        @ Visitors may enter <b>anonymous</b> as the user-ID with
   776        @ the 8-character hexadecimal password shown below:</p>
   777        @ <div class="captcha"><table class="captcha"><tr><td>\
   778        @ <pre class="captcha">
   779        @ %h(zCaptcha)
   780        @ </pre></td></tr></table>
   781        if( bAutoCaptcha ) {
   782           @ <input type="button" value="Fill out captcha" id='autofillButton' \
   783           @ data-af='%s(zDecoded)' />
   784           style_load_one_js_file("login.js");
   785        }
   786        @ </div>
   787        free(zCaptcha);
   788      }
   789      @ </form>
   790    }
   791    if( login_is_individual() ){
   792      if( g.perm.EmailAlert && alert_enabled() ){
   793        @ <hr>
   794        @ <p>Configure <a href="%R/alerts">Email Alerts</a>
   795        @ for user <b>%h(g.zLogin)</b></p>
   796      }
   797      if( g.perm.Password ){
   798        @ <hr>
   799        @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
   800        form_begin(0, "%R/login");
   801        @ <table>
   802        @ <tr><td class="form_label">Old Password:</td>
   803        @ <td><input type="password" name="p" size="30" /></td></tr>
   804        @ <tr><td class="form_label">New Password:</td>
   805        @ <td><input type="password" name="n1" size="30" /></td></tr>
   806        @ <tr><td class="form_label">Repeat New Password:</td>
   807        @ <td><input type="password" name="n2" size="30" /></td></tr>
   808        @ <tr><td></td>
   809        @ <td><input type="submit" value="Change Password" /></td></tr>
   810        @ </table>
   811        @ </form>
   812      }
   813    }
   814    style_footer();
   815  }
   816  
   817  /*
   818  ** Attempt to find login credentials for user zLogin on a peer repository
   819  ** with project code zCode.  Transfer those credentials to the local
   820  ** repository.
   821  **
   822  ** Return true if a transfer was made and false if not.
   823  */
   824  static int login_transfer_credentials(
   825    const char *zLogin,          /* Login we are looking for */
   826    const char *zCode,           /* Project code of peer repository */
   827    const char *zHash,           /* HASH from login cookie HASH/CODE/LOGIN */
   828    const char *zRemoteAddr      /* Request comes from here */
   829  ){
   830    sqlite3 *pOther = 0;         /* The other repository */
   831    sqlite3_stmt *pStmt;         /* Query against the other repository */
   832    char *zSQL;                  /* SQL of the query against other repo */
   833    char *zOtherRepo;            /* Filename of the other repository */
   834    int rc;                      /* Result code from SQLite library functions */
   835    int nXfer = 0;               /* Number of credentials transferred */
   836  
   837    zOtherRepo = db_text(0,
   838         "SELECT value FROM config WHERE name='peer-repo-%q'",
   839         zCode
   840    );
   841    if( zOtherRepo==0 ) return 0;  /* No such peer repository */
   842  
   843    rc = sqlite3_open_v2(
   844         zOtherRepo, &pOther,
   845         SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
   846         g.zVfsName
   847    );
   848    if( rc==SQLITE_OK ){
   849      sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0);
   850      sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
   851                    constant_time_cmp_function, 0, 0);
   852      sqlite3_busy_timeout(pOther, 5000);
   853      zSQL = mprintf(
   854        "SELECT cexpire FROM user"
   855        " WHERE login=%Q"
   856        "   AND ipaddr=%Q"
   857        "   AND length(cap)>0"
   858        "   AND length(pw)>0"
   859        "   AND cexpire>julianday('now')"
   860        "   AND constant_time_cmp(cookie,%Q)=0",
   861        zLogin, zRemoteAddr, zHash
   862      );
   863      pStmt = 0;
   864      rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
   865      if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
   866        db_multi_exec(
   867          "UPDATE user SET cookie=%Q, ipaddr=%Q, cexpire=%.17g"
   868          " WHERE login=%Q",
   869          zHash, zRemoteAddr,
   870          sqlite3_column_double(pStmt, 0), zLogin
   871        );
   872        nXfer++;
   873      }
   874      sqlite3_finalize(pStmt);
   875    }
   876    sqlite3_close(pOther);
   877    fossil_free(zOtherRepo);
   878    return nXfer;
   879  }
   880  
   881  /*
   882  ** Return TRUE if zLogin is one of the special usernames
   883  */
   884  int login_is_special(const char *zLogin){
   885    if( fossil_strcmp(zLogin, "anonymous")==0 ) return 1;
   886    if( fossil_strcmp(zLogin, "nobody")==0 ) return 1;
   887    if( fossil_strcmp(zLogin, "developer")==0 ) return 1;
   888    if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
   889    return 0;
   890  }
   891  
   892  /*
   893  ** Lookup the uid for a non-built-in user with zLogin and zCookie and
   894  ** zRemoteAddr.  Return 0 if not found.
   895  **
   896  ** Note that this only searches for logged-in entries with matching
   897  ** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
   898  ** entries.
   899  */
   900  static int login_find_user(
   901    const char *zLogin,            /* User name */
   902    const char *zCookie,           /* Login cookie value */
   903    const char *zRemoteAddr        /* Abbreviated IP address for valid login */
   904  ){
   905    int uid;
   906    if( login_is_special(zLogin) ) return 0;
   907    uid = db_int(0,
   908      "SELECT uid FROM user"
   909      " WHERE login=%Q"
   910      "   AND ipaddr=%Q"
   911      "   AND cexpire>julianday('now')"
   912      "   AND length(cap)>0"
   913      "   AND length(pw)>0"
   914      "   AND constant_time_cmp(cookie,%Q)=0",
   915      zLogin, zRemoteAddr, zCookie
   916    );
   917    return uid;
   918  }
   919  
   920  /*
   921  ** Return true if it is appropriate to redirect login requests to HTTPS.
   922  **
   923  ** Redirect to https is appropriate if all of the above are true:
   924  **    (1) The redirect-to-https flag is set
   925  **    (2) The current connection is http, not https or ssh
   926  **    (3) The sslNotAvailable flag is clear
   927  */
   928  int login_wants_https_redirect(void){
   929    if( g.sslNotAvailable ) return 0;
   930    if( db_get_boolean("redirect-to-https",0)==0 ) return 0;
   931    if( P("HTTPS")!=0 ) return 0;
   932    return 1;
   933  }
   934  
   935  
   936  /*
   937  ** Attempt to use Basic Authentication to establish the user.  Return the
   938  ** (non-zero) uid if successful.  Return 0 if it does not work.
   939  */
   940  static int logic_basic_authentication(const char *zIpAddr){
   941    const char *zAuth = PD("HTTP_AUTHORIZATION", 0);
   942    int i;
   943    int uid = 0;
   944    int nDecode = 0;
   945    char *zDecode = 0;
   946    const char *zUsername = 0;
   947    const char *zPasswd = 0;
   948  
   949    if( zAuth==0 ) return 0;                    /* Fail: No Authentication: header */
   950    while( fossil_isspace(zAuth[0]) ) zAuth++;  /* Skip leading whitespace */
   951    if( strncmp(zAuth, "Basic ", 6)!=0 ) return 0;  /* Fail: Not Basic Authentication */
   952  
   953    /* Parse out the username and password, separated by a ":" */
   954    zAuth += 6;
   955    while( fossil_isspace(zAuth[0]) ) zAuth++;
   956    zDecode = decode64(zAuth, &nDecode);
   957  
   958    for(i=0; zDecode[i] && zDecode[i]!=':'; i++){}
   959    if( zDecode[i] ){
   960      zDecode[i] = 0;
   961      zUsername = zDecode;
   962      zPasswd = &zDecode[i+1];
   963  
   964      /* Attempting to log in as the user provided by HTTP
   965      ** basic auth
   966      */
   967      uid = login_search_uid(&zUsername, zPasswd);
   968      if( uid>0 ){
   969        record_login_attempt(zUsername, zIpAddr, 1);
   970      }else{
   971        record_login_attempt(zUsername, zIpAddr, 0);
   972  
   973        /* The user attempted to login specifically with HTTP basic
   974        ** auth, but provided invalid credentials. Inform them of
   975        ** the failed login attempt via 401.
   976        */
   977        cgi_set_status(401, "Unauthorized");
   978        cgi_reply();
   979        fossil_exit(0);
   980      }
   981    }
   982    fossil_free(zDecode);
   983    return uid;
   984  }
   985  
   986  /*
   987  ** This routine examines the login cookie to see if it exists and
   988  ** is valid.  If the login cookie checks out, it then sets global
   989  ** variables appropriately.
   990  **
   991  **    g.userUid      Database USER.UID value.  Might be -1 for "nobody"
   992  **    g.zLogin       Database USER.LOGIN value.  NULL for user "nobody"
   993  **    g.perm         Permissions granted to this user
   994  **    g.anon         Permissions that would be available to anonymous
   995  **    g.isHuman      True if the user is human, not a spider or robot
   996  **
   997  */
   998  void login_check_credentials(void){
   999    int uid = 0;                  /* User id */
  1000    const char *zCookie;          /* Text of the login cookie */
  1001    const char *zIpAddr;          /* Raw IP address of the requestor */
  1002    char *zRemoteAddr;            /* Abbreviated IP address of the requestor */
  1003    const char *zCap = 0;         /* Capability string */
  1004    const char *zPublicPages = 0; /* GLOB patterns of public pages */
  1005    const char *zLogin = 0;       /* Login user for credentials */
  1006  
  1007    /* Only run this check once.  */
  1008    if( g.userUid!=0 ) return;
  1009  
  1010    sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
  1011                    constant_time_cmp_function, 0, 0);
  1012  
  1013    /* If the HTTP connection is coming over 127.0.0.1 and if
  1014    ** local login is disabled and if we are using HTTP and not HTTPS,
  1015    ** then there is no need to check user credentials.
  1016    **
  1017    ** This feature allows the "fossil ui" command to give the user
  1018    ** full access rights without having to log in.
  1019    */
  1020    zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
  1021    if( ( cgi_is_loopback(zIpAddr)
  1022         || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
  1023     && g.useLocalauth
  1024     && db_get_int("localauth",0)==0
  1025     && P("HTTPS")==0
  1026    ){
  1027      if( g.localOpen ) zLogin = db_lget("default-user",0);
  1028      if( zLogin!=0 ){
  1029        uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
  1030      }else{
  1031        uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
  1032      }
  1033      g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
  1034      zCap = "sx";
  1035      g.noPswd = 1;
  1036      g.isHuman = 1;
  1037      sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost");
  1038    }
  1039  
  1040    /* Check the login cookie to see if it matches a known valid user.
  1041    */
  1042    if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
  1043      /* Parse the cookie value up into HASH/ARG/USER */
  1044      char *zHash = fossil_strdup(zCookie);
  1045      char *zArg = 0;
  1046      char *zUser = 0;
  1047      int i, c;
  1048      for(i=0; (c = zHash[i])!=0; i++){
  1049        if( c=='/' ){
  1050          zHash[i++] = 0;
  1051          if( zArg==0 ){
  1052            zArg = &zHash[i];
  1053          }else{
  1054            zUser = &zHash[i];
  1055            break;
  1056          }
  1057        }
  1058      }
  1059      if( zUser==0 ){
  1060        /* Invalid cookie */
  1061      }else if( fossil_strcmp(zUser, "anonymous")==0 ){
  1062        /* Cookies of the form "HASH/TIME/anonymous".  The TIME must not be
  1063        ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
  1064        ** SECRET is the "captcha-secret" value in the repository.
  1065        */
  1066        double rTime = atof(zArg);
  1067        Blob b;
  1068        blob_zero(&b);
  1069        blob_appendf(&b, "%s/%s/%s",
  1070                     zArg, zRemoteAddr, db_get("captcha-secret",""));
  1071        sha1sum_blob(&b, &b);
  1072        if( fossil_strcmp(zHash, blob_str(&b))==0 ){
  1073          uid = db_int(0,
  1074              "SELECT uid FROM user WHERE login='anonymous'"
  1075              " AND length(cap)>0"
  1076              " AND length(pw)>0"
  1077              " AND %.17g+0.25>julianday('now')",
  1078              rTime
  1079          );
  1080        }
  1081        blob_reset(&b);
  1082      }else{
  1083        /* Cookies of the form "HASH/CODE/USER".  Search first in the
  1084        ** local user table, then the user table for project CODE if we
  1085        ** are part of a login-group.
  1086        */
  1087        uid = login_find_user(zUser, zHash, zRemoteAddr);
  1088        if( uid==0 && login_transfer_credentials(zUser,zArg,zHash,zRemoteAddr) ){
  1089          uid = login_find_user(zUser, zHash, zRemoteAddr);
  1090          if( uid ) record_login_attempt(zUser, zIpAddr, 1);
  1091        }
  1092      }
  1093      sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
  1094    }
  1095  
  1096    /* If no user found and the REMOTE_USER environment variable is set,
  1097    ** then accept the value of REMOTE_USER as the user.
  1098    */
  1099    if( uid==0 ){
  1100      const char *zRemoteUser = P("REMOTE_USER");
  1101      if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
  1102        uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
  1103                        " AND length(cap)>0 AND length(pw)>0", zRemoteUser);
  1104      }
  1105    }
  1106  
  1107    /* If the request didn't provide a login cookie or the login cookie didn't
  1108    ** match a known valid user, check the HTTP "Authorization" header and
  1109    ** see if those credentials are valid for a known user.
  1110    */
  1111    if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
  1112      uid = logic_basic_authentication(zIpAddr);
  1113    }
  1114  
  1115    /* If no user found yet, try to log in as "nobody" */
  1116    if( uid==0 ){
  1117      uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
  1118      if( uid==0 ){
  1119        /* If there is no user "nobody", then make one up - with no privileges */
  1120        uid = -1;
  1121        zCap = "";
  1122      }
  1123      sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "none");
  1124    }
  1125  
  1126    /* At this point, we know that uid!=0.  Find the privileges associated
  1127    ** with user uid.
  1128    */
  1129    assert( uid!=0 );
  1130    if( zCap==0 ){
  1131      Stmt s;
  1132      db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
  1133      if( db_step(&s)==SQLITE_ROW ){
  1134        g.zLogin = db_column_malloc(&s, 0);
  1135        zCap = db_column_malloc(&s, 1);
  1136      }
  1137      db_finalize(&s);
  1138      if( zCap==0 ){
  1139        zCap = "";
  1140      }
  1141    }
  1142    if( g.fHttpTrace && g.zLogin ){
  1143      fprintf(stderr, "# login: [%s] with capabilities [%s]\n", g.zLogin, zCap);
  1144    }
  1145  
  1146    /* Set the global variables recording the userid and login.  The
  1147    ** "nobody" user is a special case in that g.zLogin==0.
  1148    */
  1149    g.userUid = uid;
  1150    if( fossil_strcmp(g.zLogin,"nobody")==0 ){
  1151      g.zLogin = 0;
  1152    }
  1153    if( PB("isrobot") ){
  1154      g.isHuman = 0;
  1155    }else if( g.zLogin==0 ){
  1156      g.isHuman = isHuman(P("HTTP_USER_AGENT"));
  1157    }else{
  1158      g.isHuman = 1;
  1159    }
  1160  
  1161    /* Set the capabilities */
  1162    login_replace_capabilities(zCap, 0);
  1163  
  1164    /* The auto-hyperlink setting allows hyperlinks to be displayed for users
  1165    ** who do not have the "h" permission as long as their UserAgent string
  1166    ** makes it appear that they are human.  Check to see if auto-hyperlink is
  1167    ** enabled for this repository and make appropriate adjustments to the
  1168    ** permission flags if it is.  This should be done before the permissions
  1169    ** are (potentially) copied to the anonymous permission set; otherwise,
  1170    ** those will be out-of-sync.
  1171    */
  1172    if( zCap[0]
  1173     && !g.perm.Hyperlink
  1174     && g.isHuman
  1175     && db_get_boolean("auto-hyperlink",1)
  1176    ){
  1177      g.perm.Hyperlink = 1;
  1178      g.javascriptHyperlink = 1;
  1179    }
  1180  
  1181    /*
  1182    ** At this point, the capabilities for the logged in user are not going
  1183    ** to be modified anymore; therefore, we can copy them over to the ones
  1184    ** for the anonymous user.
  1185    **
  1186    ** WARNING: In the future, please do not add code after this point that
  1187    **          modifies the capabilities for the logged in user.
  1188    */
  1189    login_set_anon_nobody_capabilities();
  1190  
  1191    /* If the public-pages glob pattern is defined and REQUEST_URI matches
  1192    ** one of the globs in public-pages, then also add in all default-perms
  1193    ** permissions.
  1194    */
  1195    zPublicPages = db_get("public-pages",0);
  1196    if( zPublicPages!=0 ){
  1197      Glob *pGlob = glob_create(zPublicPages);
  1198      const char *zUri = PD("REQUEST_URI","");
  1199      zUri += (int)strlen(g.zTop);
  1200      if( glob_match(pGlob, zUri) ){
  1201        login_set_capabilities(db_get("default-perms","u"), 0);
  1202      }
  1203      glob_free(pGlob);
  1204    }
  1205  }
  1206  
  1207  /*
  1208  ** Memory of settings
  1209  */
  1210  static int login_anon_once = 1;
  1211  
  1212  /*
  1213  ** Add to g.perm the default privileges of users "nobody" and/or "anonymous"
  1214  ** as appropriate for the user g.zLogin.
  1215  **
  1216  ** This routine also sets up g.anon to be either a copy of g.perm for
  1217  ** all logged in uses, or the privileges that would be available to "anonymous"
  1218  ** if g.zLogin==0 (meaning that the user is "nobody").
  1219  */
  1220  void login_set_anon_nobody_capabilities(void){
  1221    if( login_anon_once ){
  1222      const char *zCap;
  1223      /* All users get privileges from "nobody" */
  1224      zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
  1225      login_set_capabilities(zCap, 0);
  1226      zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
  1227      if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){
  1228        /* All logged-in users inherit privileges from "anonymous" */
  1229        login_set_capabilities(zCap, 0);
  1230        g.anon = g.perm;
  1231      }else{
  1232        /* Record the privileges of anonymous in g.anon */
  1233        g.anon = g.perm;
  1234        login_set_capabilities(zCap, LOGIN_ANON);
  1235      }
  1236      login_anon_once = 0;
  1237    }
  1238  }
  1239  
  1240  /*
  1241  ** Flags passed into the 2nd argument of login_set/replace_capabilities().
  1242  */
  1243  #if INTERFACE
  1244  #define LOGIN_IGNORE_UV  0x01         /* Ignore "u" and "v" */
  1245  #define LOGIN_ANON       0x02         /* Use g.anon instead of g.perm */
  1246  #endif
  1247  
  1248  /*
  1249  ** Adds all capability flags in zCap to g.perm or g.anon.
  1250  */
  1251  void login_set_capabilities(const char *zCap, unsigned flags){
  1252    int i;
  1253    FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
  1254    if(NULL==zCap){
  1255      return;
  1256    }
  1257    for(i=0; zCap[i]; i++){
  1258      switch( zCap[i] ){
1259 case 's': p->Setup = 1; /* Fall thru into Admin */ 1260 case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
1261 p->RdWiki = p->WrWiki = p->NewWiki = 1262 p->ApndWiki = p->Hyperlink = p->Clone = 1263 p->NewTkt = p->Password = p->RdAddr = 1264 p->TktFmt = p->Attach = p->ApndTkt = 1265 p->ModWiki = p->ModTkt = p->Delete = 1266 p->RdForum = p->WrForum = p->ModForum = 1267 p->WrTForum = p->AdminForum = 1268 p->EmailAlert = p->Announce = p->Debug = 1269 p->WrUnver = p->Private = 1; 1270 /* Fall thru into Read/Write */ 1271 case 'i': p->Read = p->Write = 1; break; 1272 case 'o': p->Read = 1; break; 1273 case 'z': p->Zip = 1; break; 1274 1275 case 'd': p->Delete = 1; break; 1276 case 'h': p->Hyperlink = 1; break; 1277 case 'g': p->Clone = 1; break; 1278 case 'p': p->Password = 1; break; 1279 1280 case 'j': p->RdWiki = 1; break; 1281 case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break; 1282 case 'm': p->ApndWiki = 1; break; 1283 case 'f': p->NewWiki = 1; break; 1284 case 'l': p->ModWiki = 1; break; 1285 1286 case 'e': p->RdAddr = 1; break; 1287 case 'r': p->RdTkt = 1; break; 1288 case 'n': p->NewTkt = 1; break; 1289 case 'w': p->WrTkt = p->RdTkt = p->NewTkt = 1290 p->ApndTkt = 1; break; 1291 case 'c': p->ApndTkt = 1; break; 1292 case 'q': p->ModTkt = 1; break; 1293 case 't': p->TktFmt = 1; break; 1294 case 'b': p->Attach = 1; break; 1295 case 'x': p->Private = 1; break; 1296 case 'y': p->WrUnver = 1; break; 1297 1298 case '6': p->AdminForum = 1; 1299 case '5': p->ModForum = 1; 1300 case '4': p->WrTForum = 1; 1301 case '3': p->WrForum = 1; 1302 case '2': p->RdForum = 1; break; 1303 1304 case '7': p->EmailAlert = 1; break; 1305 case 'A': p->Announce = 1; break; 1306 case 'D': p->Debug = 1; break; 1307 1308 /* The "u" privilege recursively 1309 ** inherits all privileges of the user named "reader" */ 1310 case 'u': { 1311 if( p->XReader==0 ){ 1312 const char *zUser; 1313 p->XReader = 1; 1314 zUser = db_text("", "SELECT cap FROM user WHERE login='reader'"); 1315 login_set_capabilities(zUser, flags); 1316 } 1317 break; 1318 } 1319 1320 /* The "v" privilege recursively 1321 ** inherits all privileges of the user named "developer" */ 1322 case 'v': { 1323 if( p->XDeveloper==0 ){ 1324 const char *zDev; 1325 p->XDeveloper = 1; 1326 zDev = db_text("", "SELECT cap FROM user WHERE login='developer'"); 1327 login_set_capabilities(zDev, flags); 1328 } 1329 break; 1330 } 1331 } 1332 } 1333 } 1334 1335 /* 1336 ** Zeroes out g.perm and calls login_set_capabilities(zCap,flags). 1337 */ 1338 void login_replace_capabilities(const char *zCap, unsigned flags){ 1339 memset(&g.perm, 0, sizeof(g.perm)); 1340 login_set_capabilities(zCap, flags); 1341 login_anon_once = 1; 1342 } 1343 1344 /* 1345 ** If the current login lacks any of the capabilities listed in 1346 ** the input, then return 0. If all capabilities are present, then 1347 ** return 1. 1348 */ 1349 int login_has_capability(const char *zCap, int nCap, u32 flgs){ 1350 int i; 1351 int rc = 1; 1352 FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm; 1353 if( nCap<0 ) nCap = strlen(zCap); 1354 for(i=0; i<nCap && rc && zCap[i]; i++){ 1355 switch( zCap[i] ){ 1356 case 'a': rc = p->Admin; break; 1357 case 'b': rc = p->Attach; break; 1358 case 'c': rc = p->ApndTkt; break; 1359 case 'd': rc = p->Delete; break; 1360 case 'e': rc = p->RdAddr; break; 1361 case 'f': rc = p->NewWiki; break; 1362 case 'g': rc = p->Clone; break; 1363 case 'h': rc = p->Hyperlink; break; 1364 case 'i': rc = p->Write; break; 1365 case 'j': rc = p->RdWiki; break; 1366 case 'k': rc = p->WrWiki; break; 1367 case 'l': rc = p->ModWiki; break; 1368 case 'm': rc = p->ApndWiki; break; 1369 case 'n': rc = p->NewTkt; break; 1370 case 'o': rc = p->Read; break; 1371 case 'p': rc = p->Password; break; 1372 case 'q': rc = p->ModTkt; break; 1373 case 'r': rc = p->RdTkt; break; 1374 case 's': rc = p->Setup; break; 1375 case 't': rc = p->TktFmt; break; 1376 /* case 'u': READER */ 1377 /* case 'v': DEVELOPER */ 1378 case 'w': rc = p->WrTkt; break; 1379 case 'x': rc = p->Private; break; 1380 case 'y': rc = p->WrUnver; break; 1381 case 'z': rc = p->Zip; break; 1382 case '2': rc = p->RdForum; break; 1383 case '3': rc = p->WrForum; break; 1384 case '4': rc = p->WrTForum; break; 1385 case '5': rc = p->ModForum; break; 1386 case '6': rc = p->AdminForum;break; 1387 case '7': rc = p->EmailAlert;break; 1388 case 'A': rc = p->Announce; break; 1389 case 'D': rc = p->Debug; break; 1390 default: rc = 0; break; 1391 } 1392 } 1393 return rc; 1394 } 1395 1396 /* 1397 ** Change the login to zUser. 1398 */ 1399 void login_as_user(const char *zUser){ 1400 char *zCap = ""; /* New capabilities */ 1401 1402 /* Turn off all capabilities from prior logins */ 1403 memset( &g.perm, 0, sizeof(g.perm) ); 1404 1405 /* Set the global variables recording the userid and login. The 1406 ** "nobody" user is a special case in that g.zLogin==0. 1407 */ 1408 g.userUid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUser); 1409 if( g.userUid==0 ){ 1410 zUser = 0; 1411 g.userUid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); 1412 } 1413 if( g.userUid ){ 1414 zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", g.userUid); 1415 } 1416 if( fossil_strcmp(zUser,"nobody")==0 ) zUser = 0; 1417 g.zLogin = fossil_strdup(zUser); 1418 1419 /* Set the capabilities */ 1420 login_set_capabilities(zCap, 0); 1421 login_anon_once = 1; 1422 login_set_anon_nobody_capabilities(); 1423 } 1424 1425 /* 1426 ** Return true if the user is "nobody" 1427 */ 1428 int login_is_nobody(void){ 1429 return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0; 1430 } 1431 1432 /* 1433 ** Return true if the user is a specific individual, not "nobody" or 1434 ** "anonymous". 1435 */ 1436 int login_is_individual(void){ 1437 return g.zLogin!=0 && g.zLogin[0]!=0 && fossil_strcmp(g.zLogin,"nobody")!=0 1438 && fossil_strcmp(g.zLogin,"anonymous")!=0; 1439 } 1440 1441 /* 1442 ** Return the login name. If no login name is specified, return "nobody". 1443 */ 1444 const char *login_name(void){ 1445 return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; 1446 } 1447 1448 /* 1449 ** Call this routine when the credential check fails. It causes 1450 ** a redirect to the "login" page. 1451 */ 1452 void login_needed(int anonOk){ 1453 #ifdef FOSSIL_ENABLE_JSON 1454 if(g.json.isJsonMode){ 1455 json_err( FSL_JSON_E_DENIED, NULL, 1 ); 1456 fossil_exit(0); 1457 /* NOTREACHED */ 1458 assert(0); 1459 }else 1460 #endif /* FOSSIL_ENABLE_JSON */ 1461 { 1462 const char *zUrl = PD("REQUEST_URI", "index"); 1463 const char *zQS = P("QUERY_STRING"); 1464 Blob redir; 1465 blob_init(&redir, 0, 0); 1466 if( login_wants_https_redirect() && !g.sslNotAvailable ){ 1467 blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrl); 1468 }else{ 1469 blob_appendf(&redir, "%R/login?g=%T", zUrl); 1470 } 1471 if( anonOk ) blob_append(&redir, "&anon", 5); 1472 if( zQS && zQS[0] ){ 1473 blob_appendf(&redir, "&%s", zQS); 1474 } 1475 cgi_redirect(blob_str(&redir)); 1476 /* NOTREACHED */ 1477 assert(0); 1478 } 1479 } 1480 1481 /* 1482 ** Call this routine if the user lacks g.perm.Hyperlink permission. If 1483 ** the anonymous user has Hyperlink permission, then paint a mesage 1484 ** to inform the user that much more information is available by 1485 ** logging in as anonymous. 1486 */ 1487 void login_anonymous_available(void){ 1488 if( !g.perm.Hyperlink && g.anon.Hyperlink ){ 1489 const char *zUrl = PD("REQUEST_URI", "index"); 1490 @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> 1491 @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a> 1492 @ to enable hyperlinks.</p> 1493 } 1494 } 1495 1496 /* 1497 ** While rendering a form, call this routine to add the Anti-CSRF token 1498 ** as a hidden element of the form. 1499 */ 1500 void login_insert_csrf_secret(void){ 1501 @ <input type="hidden" name="csrf" value="%s(g.zCsrfToken)" /> 1502 } 1503 1504 /* 1505 ** Before using the results of a form, first call this routine to verify 1506 ** that this Anti-CSRF token is present and is valid. If the Anti-CSRF token 1507 ** is missing or is incorrect, that indicates a cross-site scripting attack. 1508 ** If the event of an attack is detected, an error message is generated and 1509 ** all further processing is aborted. 1510 */ 1511 void login_verify_csrf_secret(void){ 1512 if( g.okCsrf ) return; 1513 if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){ 1514 g.okCsrf = 1; 1515 return; 1516 } 1517 fossil_fatal("Cross-site request forgery attempt"); 1518 } 1519 1520 /* 1521 ** WEBPAGE: register 1522 ** 1523 ** Page to allow users to self-register. The "self-register" setting 1524 ** must be enabled for this page to operate. 1525 */ 1526 void register_page(void){ 1527 const char *zUserID, *zPasswd, *zConfirm, *zEAddr; 1528 const char *zDName; 1529 unsigned int uSeed; 1530 const char *zDecoded; 1531 char *zCaptcha; 1532 int iErrLine = -1; 1533 const char *zErr = 0; 1534 char *zPerms; /* Permissions for the default user */ 1535 int canDoAlerts = 0; /* True if receiving email alerts is possible */ 1536 int doAlerts = 0; /* True if subscription is wanted too */ 1537 if( !db_get_boolean("self-register", 0) ){ 1538 style_header("Registration not possible"); 1539 @ <p>This project does not allow user self-registration. Please contact the 1540 @ project administrator to obtain an account.</p> 1541 style_footer(); 1542 return; 1543 } 1544 zPerms = db_get("default-perms","u"); 1545 1546 /* Prompt the user for email alerts if this repository is configured for 1547 ** email alerts and if the default permissions include "7" */ 1548 canDoAlerts = alert_tables_exist() && db_int(0, 1549 "SELECT fullcap(%Q) GLOB '*7*'", zPerms 1550 ); 1551 doAlerts = canDoAlerts && atoi(PD("alerts","1"))!=0; 1552 1553 zUserID = PDT("u",""); 1554 zPasswd = PDT("p",""); 1555 zConfirm = PDT("cp",""); 1556 zEAddr = PDT("ea",""); 1557 zDName = PDT("dn",""); 1558 1559 /* Verify user imputs */ 1560 if( P("new")==0 || !cgi_csrf_safe(1) ){ 1561 /* This is not a valid form submission. Fall through into 1562 ** the form display */ 1563 }else if( !captcha_is_correct(1) ){ 1564 iErrLine = 6; 1565 zErr = "Incorrect CAPTCHA"; 1566 }else if( strlen(zUserID)<3 ){ 1567 iErrLine = 1; 1568 zErr = "User ID too short. Must be at least 3 characters."; 1569 }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){ 1570 iErrLine = 1; 1571 zErr = "User ID may not contain spaces or special characters."; 1572 }else if( zDName[0]==0 ){ 1573 iErrLine = 2; 1574 zErr = "Required"; 1575 }else if( zEAddr[0]==0 ){ 1576 iErrLine = 3; 1577 zErr = "Required"; 1578 }else if( email_copy_addr(zEAddr,0)==0 ){ 1579 iErrLine = 3; 1580 zErr = "Not a valid email address"; 1581 }else if( strlen(zPasswd)<6 ){ 1582 iErrLine = 4; 1583 zErr = "Password must be at least 6 characters long"; 1584 }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){ 1585 iErrLine = 5; 1586 zErr = "Passwords do not match"; 1587 }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zUserID) ){ 1588 iErrLine = 1; 1589 zErr = "This User ID is already taken. Choose something different."; 1590 }else if( 1591 /* If the email is found anywhere in USER.INFO... */ 1592 db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr) 1593 || 1594 /* Or if the email is a verify subscriber email with an associated 1595 ** user... */ 1596 db_exists( 1597 "SELECT 1 FROM subscriber WHERE semail=%Q AND suname IS NOT NULL" 1598 " AND sverified",zEAddr) 1599 ){ 1600 iErrLine = 3; 1601 zErr = "This email address is already claimed by another user"; 1602 }else{ 1603 /* If all of the tests above have passed, that means that the submitted 1604 ** form contains valid data and we can proceed to create the new login */ 1605 Blob sql; 1606 int uid; 1607 char *zPass = sha1_shared_secret(zPasswd, zUserID, 0); 1608 blob_init(&sql, 0, 0); 1609 blob_append_sql(&sql, 1610 "INSERT INTO user(login,pw,cap,info,mtime)\n" 1611 "VALUES(%Q,%Q,%Q," 1612 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())", 1613 zUserID, zPass, zPerms, zDName, zEAddr, g.zIpAddr); 1614 fossil_free(zPass); 1615 db_multi_exec("%s", blob_sql_text(&sql)); 1616 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID); 1617 login_set_user_cookie(zUserID, uid, NULL); 1618 if( doAlerts ){ 1619 /* Also make the new user a subscriber. */ 1620 Blob hdr, body; 1621 AlertSender *pSender; 1622 sqlite3_int64 id; /* New subscriber Id */ 1623 const char *zCode; /* New subscriber code (in hex) */ 1624 const char *zGoto = P("g"); 1625 int nsub = 0; 1626 char ssub[20]; 1627 ssub[nsub++] = 'a'; 1628 if( g.perm.Read ) ssub[nsub++] = 'c'; 1629 if( g.perm.RdForum ) ssub[nsub++] = 'f'; 1630 if( g.perm.RdTkt ) ssub[nsub++] = 't'; 1631 if( g.perm.RdWiki ) ssub[nsub++] = 'w'; 1632 ssub[nsub] = 0; 1633 /* Also add the user to the subscriber table. */ 1634 db_multi_exec( 1635 "INSERT INTO subscriber(semail,suname," 1636 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)" 1637 " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)" 1638 " ON CONFLICT(semail) DO UPDATE" 1639 " SET suname=excluded.suname", 1640 /* semail */ zEAddr, 1641 /* suname */ zUserID, 1642 /* sverified */ 0, 1643 /* sdigest */ 0, 1644 /* ssub */ ssub, 1645 /* smip */ g.zIpAddr 1646 ); 1647 id = db_last_insert_rowid(); 1648 if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q" 1649 " AND sverified", zEAddr) ){ 1650 /* This the case where the user was formerly a verified subscriber 1651 ** and here they have also registered as a user as well. It is 1652 ** not necessary to repeat the verfication step */ 1653 redirect_to_g(); 1654 } 1655 zCode = db_text(0, 1656 "SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld", 1657 id); 1658 /* A verification email */ 1659 pSender = alert_sender_new(0,0); 1660 blob_init(&hdr,0,0); 1661 blob_init(&body,0,0); 1662 blob_appendf(&hdr, "To: <%s>\n", zEAddr); 1663 blob_appendf(&hdr, "Subject: Subscription verification\n"); 1664 alert_append_confirmation_message(&body, zCode); 1665 alert_send(pSender, &hdr, &body, 0); 1666 style_header("Email Verification"); 1667 if( pSender->zErr ){ 1668 @ <h1>Internal Error</h1> 1669 @ <p>The following internal error was encountered while trying 1670 @ to send the confirmation email: 1671 @ <blockquote><pre> 1672 @ %h(pSender->zErr) 1673 @ </pre></blockquote> 1674 }else{ 1675 @ <p>An email has been sent to "%h(zEAddr)". That email contains a 1676 @ hyperlink that you must click on in order to activate your 1677 @ subscription.</p> 1678 } 1679 alert_sender_free(pSender); 1680 if( zGoto ){ 1681 @ <p><a href='%h(zGoto)'>Continue</a> 1682 } 1683 style_footer(); 1684 return; 1685 } 1686 redirect_to_g(); 1687 } 1688 1689 /* Prepare the captcha. */ 1690 uSeed = captcha_seed(); 1691 zDecoded = captcha_decode(uSeed); 1692 zCaptcha = captcha_render(zDecoded); 1693 1694 style_header("Register"); 1695 /* Print out the registration form. */ 1696 form_begin(0, "%R/register"); 1697 if( P("g") ){ 1698 @ <input type="hidden" name="g" value="%h(P("g"))" /> 1699 } 1700 @ <p><input type="hidden" name="captchaseed" value="%u(uSeed)" /> 1701 @ <table class="login_out"> 1702 @ <tr> 1703 @ <td class="form_label" align="right">User ID:</td> 1704 @ <td><input type="text" name="u" value="%h(zUserID)" size="30"></td> 1705 @ 1706 if( iErrLine==1 ){ 1707 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1708 } 1709 @ <tr> 1710 @ <td class="form_label" align="right">Display Name:</td> 1711 @ <td><input type="text" name="dn" value="%h(zDName)" size="30"></td> 1712 @ </tr> 1713 if( iErrLine==2 ){ 1714 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1715 } 1716 @ </tr> 1717 @ <tr> 1718 @ <td class="form_label" align="right">Email Address:</td> 1719 @ <td><input type="text" name="ea" value="%h(zEAddr)" size="30"></td> 1720 @ </tr> 1721 if( iErrLine==3 ){ 1722 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1723 } 1724 if( canDoAlerts ){ 1725 int a = atoi(PD("alerts","1")); 1726 @ <tr> 1727 @ <td class="form_label" align="right">Email&nbsp;Alerts?</td> 1728 @ <td><select size='1' name='alerts'> 1729 @ <option value="1" %s(a?"selected":"")>Yes</option> 1730 @ <option value="0" %s(!a?"selected":"")>No</option> 1731 @ </select></td></tr> 1732 } 1733 @ <tr> 1734 @ <td class="form_label" align="right">Password:</td> 1735 @ <td><input type="password" name="p" value="%h(zPasswd)" size="30"></td> 1736 @ <tr> 1737 if( iErrLine==4 ){ 1738 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1739 } 1740 @ <tr> 1741 @ <td class="form_label" align="right">Confirm:</td> 1742 @ <td><input type="password" name="cp" value="%h(zConfirm)" size="30"></td> 1743 @ </tr> 1744 if( iErrLine==5 ){ 1745 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1746 } 1747 @ <tr> 1748 @ <td class="form_label" align="right">Captcha:</td> 1749 @ <td><input type="text" name="captcha" value="" size="30"></td> 1750 @ </tr> 1751 if( iErrLine==6 ){ 1752 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr> 1753 } 1754 @ <tr><td></td> 1755 @ <td><input type="submit" name="new" value="Register" /></td></tr> 1756 @ </table> 1757 @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> 1758 @ %h(zCaptcha) 1759 @ </pre> 1760 @ Enter this 8-letter code in the "Captcha" box above. 1761 @ </td></tr></table></div> 1762 @ </form> 1763 style_footer(); 1764 1765 free(zCaptcha); 1766 } 1767 1768 /* 1769 ** Run SQL on the repository database for every repository in our 1770 ** login group. The SQL is run in a separate database connection. 1771 ** 1772 ** Any members of the login group whose repository database file 1773 ** cannot be found is silently removed from the group. 1774 ** 1775 ** Error messages accumulate and are returned in *pzErrorMsg. The 1776 ** memory used to hold these messages should be freed using 1777 ** fossil_free() if one desired to avoid a memory leak. The 1778 ** zPrefix and zSuffix strings surround each error message. 1779 ** 1780 ** Return the number of errors. 1781 */ 1782 int login_group_sql( 1783 const char *zSql, /* The SQL to run */ 1784 const char *zPrefix, /* Prefix to each error message */ 1785 const char *zSuffix, /* Suffix to each error message */ 1786 char **pzErrorMsg /* Write error message here, if not NULL */ 1787 ){ 1788 sqlite3 *pPeer; /* Connection to another database */ 1789 int nErr = 0; /* Number of errors seen so far */ 1790 int rc; /* Result code from subroutine calls */ 1791 char *zErr; /* SQLite error text */ 1792 char *zSelfCode; /* Project code for ourself */ 1793 Blob err; /* Accumulate errors here */ 1794 Stmt q; /* Query of all peer-* entries in CONFIG */ 1795 1796 if( zPrefix==0 ) zPrefix = ""; 1797 if( zSuffix==0 ) zSuffix = ""; 1798 if( pzErrorMsg ) *pzErrorMsg = 0; 1799 zSelfCode = abbreviated_project_code(db_get("project-code", "x")); 1800 blob_zero(&err); 1801 db_prepare(&q, 1802 "SELECT name, value FROM config" 1803 " WHERE name GLOB 'peer-repo-*'" 1804 " AND name <> 'peer-repo-%q'" 1805 " ORDER BY +value", 1806 zSelfCode 1807 ); 1808 while( db_step(&q)==SQLITE_ROW ){ 1809 const char *zRepoName = db_column_text(&q, 1); 1810 if( file_size(zRepoName, ExtFILE)<0 ){ 1811 /* Silently remove non-existent repositories from the login group. */ 1812 const char *zLabel = db_column_text(&q, 0); 1813 db_multi_exec( 1814 "DELETE FROM config WHERE name GLOB 'peer-*-%q'", 1815 &zLabel[10] 1816 ); 1817 continue; 1818 } 1819 rc = sqlite3_open_v2( 1820 zRepoName, &pPeer, 1821 SQLITE_OPEN_READWRITE, 1822 g.zVfsName 1823 ); 1824 if( rc!=SQLITE_OK ){ 1825 blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, 1826 sqlite3_errmsg(pPeer), zSuffix); 1827 nErr++; 1828 sqlite3_close(pPeer); 1829 continue; 1830 } 1831 sqlite3_create_function(pPeer, "shared_secret", 3, SQLITE_UTF8, 1832 0, sha1_shared_secret_sql_function, 0, 0); 1833 sqlite3_create_function(pPeer, "now", 0,SQLITE_UTF8,0,db_now_function,0,0); 1834 sqlite3_busy_timeout(pPeer, 5000); 1835 zErr = 0; 1836 rc = sqlite3_exec(pPeer, zSql, 0, 0, &zErr); 1837 if( zErr ){ 1838 blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, zErr, zSuffix); 1839 sqlite3_free(zErr); 1840 nErr++; 1841 }else if( rc!=SQLITE_OK ){ 1842 blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, 1843 sqlite3_errmsg(pPeer), zSuffix); 1844 nErr++; 1845 } 1846 sqlite3_close(pPeer); 1847 } 1848 db_finalize(&q); 1849 if( pzErrorMsg && blob_size(&err)>0 ){ 1850 *pzErrorMsg = fossil_strdup(blob_str(&err)); 1851 } 1852 blob_reset(&err); 1853 fossil_free(zSelfCode); 1854 return nErr; 1855 } 1856 1857 /* 1858 ** Attempt to join a login-group. 1859 ** 1860 ** If problems arise, leave an error message in *pzErrMsg. 1861 */ 1862 void login_group_join( 1863 const char *zRepo, /* Repository file in the login group */ 1864 const char *zLogin, /* Login name for the other repo */ 1865 const char *zPassword, /* Password to prove we are authorized to join */ 1866 const char *zNewName, /* Name of new login group if making a new one */ 1867 char **pzErrMsg /* Leave an error message here */ 1868 ){ 1869 Blob fullName; /* Blob for finding full pathnames */ 1870 sqlite3 *pOther; /* The other repository */ 1871 int rc; /* Return code from sqlite3 functions */ 1872 char *zOtherProjCode; /* Project code for pOther */ 1873 char *zPwHash; /* Password hash on pOther */ 1874 char *zSelfRepo; /* Name of our repository */ 1875 char *zSelfLabel; /* Project-name for our repository */ 1876 char *zSelfProjCode; /* Our project-code */ 1877 char *zSql; /* SQL to run on all peers */ 1878 const char *zSelf; /* The ATTACH name of our repository */ 1879 1880 *pzErrMsg = 0; /* Default to no errors */ 1881 zSelf = "repository"; 1882 1883 /* Get the full pathname of the other repository */ 1884 file_canonical_name(zRepo, &fullName, 0); 1885 zRepo = fossil_strdup(blob_str(&fullName)); 1886 blob_reset(&fullName); 1887 1888 /* Get the full pathname for our repository. Also the project code 1889 ** and project name for ourself. */ 1890 file_canonical_name(g.zRepositoryName, &fullName, 0); 1891 zSelfRepo = fossil_strdup(blob_str(&fullName)); 1892 blob_reset(&fullName); 1893 zSelfProjCode = db_get("project-code", "unknown"); 1894 zSelfLabel = db_get("project-name", 0); 1895 if( zSelfLabel==0 ){ 1896 zSelfLabel = zSelfProjCode; 1897 } 1898 1899 /* Make sure we are not trying to join ourselves */ 1900 if( fossil_strcmp(zRepo, zSelfRepo)==0 ){ 1901 *pzErrMsg = mprintf("The \"other\" repository is the same as this one."); 1902 return; 1903 } 1904 1905 /* Make sure the other repository is a valid Fossil database */ 1906 if( file_size(zRepo, ExtFILE)<0 ){ 1907 *pzErrMsg = mprintf("repository file \"%s\" does not exist", zRepo); 1908 return; 1909 } 1910 rc = sqlite3_open_v2( 1911 zRepo, &pOther, 1912 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 1913 g.zVfsName 1914 ); 1915 if( rc!=SQLITE_OK ){ 1916 *pzErrMsg = fossil_strdup(sqlite3_errmsg(pOther)); 1917 }else{ 1918 rc = sqlite3_exec(pOther, "SELECT count(*) FROM user", 0, 0, pzErrMsg); 1919 } 1920 sqlite3_close(pOther); 1921 if( rc ) return; 1922 1923 /* Attach the other repository. Make sure the username/password is 1924 ** valid and has Setup permission. 1925 */ 1926 db_attach(zRepo, "other"); 1927 zOtherProjCode = db_text("x", "SELECT value FROM other.config" 1928 " WHERE name='project-code'"); 1929 zPwHash = sha1_shared_secret(zPassword, zLogin, zOtherProjCode); 1930 if( !db_exists( 1931 "SELECT 1 FROM other.user" 1932 " WHERE login=%Q AND cap GLOB '*s*'" 1933 " AND (pw=%Q OR pw=%Q)", 1934 zLogin, zPassword, zPwHash) 1935 ){ 1936 db_detach("other"); 1937 *pzErrMsg = "The supplied username/password does not correspond to a" 1938 " user Setup permission on the other repository."; 1939 return; 1940 } 1941 1942 /* Create all the necessary CONFIG table entries on both the 1943 ** other repository and on our own repository. 1944 */ 1945 zSelfProjCode = abbreviated_project_code(zSelfProjCode); 1946 zOtherProjCode = abbreviated_project_code(zOtherProjCode); 1947 db_begin_transaction(); 1948 db_multi_exec( 1949 "DELETE FROM \"%w\".config WHERE name GLOB 'peer-*';" 1950 "INSERT INTO \"%w\".config(name,value) VALUES('peer-repo-%q',%Q);" 1951 "INSERT INTO \"%w\".config(name,value) " 1952 " SELECT 'peer-name-%q', value FROM other.config" 1953 " WHERE name='project-name';", 1954 zSelf, 1955 zSelf, zOtherProjCode, zRepo, 1956 zSelf, zOtherProjCode 1957 ); 1958 db_multi_exec( 1959 "INSERT OR IGNORE INTO other.config(name,value)" 1960 " VALUES('login-group-name',%Q);" 1961 "INSERT OR IGNORE INTO other.config(name,value)" 1962 " VALUES('login-group-code',lower(hex(randomblob(8))));", 1963 zNewName 1964 ); 1965 db_multi_exec( 1966 "REPLACE INTO \"%w\".config(name,value)" 1967 " SELECT name, value FROM other.config" 1968 " WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'", 1969 zSelf 1970 ); 1971 db_end_transaction(0); 1972 db_multi_exec("DETACH other"); 1973 1974 /* Propagate the changes to all other members of the login-group */ 1975 zSql = mprintf( 1976 "BEGIN;" 1977 "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());" 1978 "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());" 1979 "COMMIT;", 1980 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo 1981 ); 1982 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); 1983 fossil_free(zSql); 1984 } 1985 1986 /* 1987 ** Leave the login group that we are currently part of. 1988 */ 1989 void login_group_leave(char **pzErrMsg){ 1990 char *zProjCode; 1991 char *zSql; 1992 1993 *pzErrMsg = 0; 1994 zProjCode = abbreviated_project_code(db_get("project-code","x")); 1995 zSql = mprintf( 1996 "DELETE FROM config WHERE name GLOB 'peer-*-%q';" 1997 "DELETE FROM config" 1998 " WHERE name='login-group-name'" 1999 " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;", 2000 zProjCode 2001 ); 2002 fossil_free(zProjCode); 2003 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); 2004 fossil_free(zSql); 2005 db_multi_exec( 2006 "DELETE FROM config " 2007 " WHERE name GLOB 'peer-*'" 2008 " OR name GLOB 'login-group-*';" 2009 ); 2010 }