Fossil

Artifact Content
Login

Artifact b16221ffb736caa21beb8f9b9e09f775251a2846a203f7d30996b1b13eb54ad8:


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