Diff
Not logged in

Differences From:

File src/login.c part of check-in [355ee47555] - Add the new "reader" role, analogous to "developer". by drh on 2009-03-31 16:47:41. [view]

To:

File src/login.c part of check-in [7a2c37063a] - merge trunk into creole branch by bob on 2009-09-22 07:49:39. Also file src/login.c part of check-in [bbb8ae7ebf] - Make it harder to misconfigure the user accounts in a way that might give people greater access than intended. by drh on 2009-09-15 18:44:51. [view]

@@ -80,19 +80,50 @@
   }
 }
 
 /*
-** WEBPAGE: /login
-** WEBPAGE: /logout
+** Check to see if the anonymous login is valid.  If it is valid, return
+** the userid of the anonymous user.
+*/
+static int isValidAnonymousLogin(
+  const char *zUsername,  /* The username.  Must be "anonymous" */
+  const char *zPassword   /* The supplied password */
+){
+  const char *zCS;        /* The captcha seed value */
+  const char *zPw;        /* The correct password shown in the captcha */
+  int uid;                /* The user ID of anonymous */
+
+  if( zUsername==0 ) return 0;
+  if( zPassword==0 ) return 0;
+  if( strcmp(zUsername,"anonymous")!=0 ) return 0;
+  zCS = P("cs");   /* The "cs" parameter is the "captcha seed" */
+  if( zCS==0 ) return 0;
+  zPw = captcha_decode((unsigned int)atoi(zCS));
+  if( strcasecmp(zPw, zPassword)!=0 ) return 0;
+  uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
+                  " AND length(pw)>0 AND length(cap)>0");
+  return uid;
+}
+
+/*
+** WEBPAGE: login
+** WEBPAGE: logout
+** WEBPAGE: my
+**
+** Generate the login page.
 **
-** Generate the login page
+** There used to be a page named "my" that was designed to show information
+** about a specific user.  The "my" page was linked from the "Logged in as USER"
+** line on the title bar.  The "my" page was never completed so it is now
+** removed.  Use this page as a placeholder in older installations.
 */
 void login_page(void){
   const char *zUsername, *zPasswd;
   const char *zNew1, *zNew2;
   const char *zAnonPw = 0;
   int anonFlag;
   char *zErrMsg = "";
+  int uid;                     /* User id loged in user */
 
   login_check_credentials();
   zUsername = P("u");
   zPasswd = P("p");
@@ -126,13 +157,37 @@
       redirect_to_g();
       return;
     }
   }
+  uid = isValidAnonymousLogin(zUsername, zPasswd);
+  if( uid>0 ){
+    char *zNow;                  /* Current time (julian day number) */
+    const char *zIpAddr;         /* IP address of requestor */
+    char *zCookie;               /* The login cookie */
+    const char *zCookieName;     /* Name of the login cookie */
+    Blob b;                      /* Blob used during cookie construction */
+
+    zIpAddr = PD("REMOTE_ADDR","nil");
+    zCookieName = login_cookie_name();
+    zNow = db_text("0", "SELECT julianday('now')");
+    blob_init(&b, zNow, -1);
+    blob_appendf(&b, "/%s/%s", zIpAddr, db_get("captcha-secret",""));
+    sha1sum_blob(&b, &b);
+    zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
+    blob_reset(&b);
+    free(zNow);
+    cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
+    redirect_to_g();
+  }
   if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
-    int uid = db_int(0,
+    uid = db_int(0,
         "SELECT uid FROM user"
-        " WHERE login=%Q AND pw=%Q", zUsername, zPasswd);
-    if( uid<=0 || strcmp(zUsername,"nobody")==0 ){
+        " WHERE login=%Q"
+        "   AND login NOT IN ('anonymous','nobody','developer','reader')"
+        "   AND pw=%Q",
+        zUsername, zPasswd
+    );
+    if( uid<=0 ){
       sleep(1);
       zErrMsg =
          @ <p><font color="red">
          @ You entered an unknown user or an incorrect password.
@@ -144,19 +199,15 @@
       const char *zExpire = db_get("cookie-expire","8766");
       int expires = atoi(zExpire)*3600;
       const char *zIpAddr = PD("REMOTE_ADDR","nil");
 
-      if( strcmp(zUsername, "anonymous")==0 ){
-        cgi_set_cookie(zCookieName, "anonymous", 0, expires);
-      }else{
-        zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
-        cgi_set_cookie(zCookieName, zCookie, 0, expires);
-        db_multi_exec(
-          "UPDATE user SET cookie=%Q, ipaddr=%Q, "
-          "  cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
-          zCookie, zIpAddr, expires, uid
-        );
-      }
+      zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
+      cgi_set_cookie(zCookieName, zCookie, 0, expires);
+      db_multi_exec(
+        "UPDATE user SET cookie=%Q, ipaddr=%Q, "
+        "  cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
+        zCookie, zIpAddr, expires, uid
+      );
       redirect_to_g();
     }
   }
   style_header("Login/Logout");
@@ -181,36 +232,35 @@
   if( g.zLogin==0 ){
     zAnonPw = db_text(0, "SELECT pw FROM user"
                          " WHERE login='anonymous'"
                          "   AND cap!=''");
-    if( zAnonPw && anonFlag ){
-      @ <tr><td></td>
-      @ <td>The anonymous password is "<b>%h(zAnonPw)</b>".</td>
-      @ </tr>
-    }
   }
   @ <tr>
   @   <td></td>
   @   <td><input type="submit" name="in" value="Login"></td>
   @ </tr>
   @ </table>
   if( g.zLogin==0 ){
-    @ <p>To login
+    @ <p>Enter
   }else{
     @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
-    @ <p>To change your login to a different user
-  }
-  @ enter the user-id and password at the left and press the
+    @ <p>To change your login to a different user, enter
+  }
+  @ your user-id and password at the left and press the
   @ "Login" button.  Your user name will be stored in a browser cookie.
   @ You must configure your web browser to accept cookies in order for
   @ the login to take.</p>
-  if( g.zLogin==0 ){
-    if( zAnonPw && !anonFlag ){
-      @ <p>The password for user "anonymous" is "<b>%h(zAnonPw)</b>".</p>
-      @ <p>&nbsp;</p>
-    }else{
-      @ <p>&nbsp;</p><p>&nbsp;</p>
-    }
+  if( zAnonPw ){
+    unsigned int uSeed = captcha_seed();
+    char *zCaptcha = captcha_render(captcha_decode(uSeed));
+
+    @ <input type="hidden" name="cs" value="%u(uSeed)">
+    @ <p>Visitors may enter <b>anonymous</b> as the user-ID with
+    @ the 8-character hexadecimal password shown below:</p>
+    @ <center><table border="1" cellpadding="10"><tr><td><pre>
+    @ %s(zCaptcha)
+    @ </pre></td></tr></table></center>
+    free(zCaptcha);
   }
   if( g.zLogin ){
     @ <br clear="both"><hr>
     @ <p>To log off the system (and delete your login cookie)
@@ -251,10 +301,8 @@
   int uid = 0;                  /* User id */
   const char *zCookie;          /* Text of the login cookie */
   const char *zRemoteAddr;      /* IP address of the requestor */
   const char *zCap = 0;         /* Capability string */
-  const char *zNcap;            /* Capabilities of user "nobody" */
-  const char *zAcap;            /* Capabllities of user "anonymous" */
 
   /* Only run this check once.  */
   if( g.userUid!=0 ) return;
 
@@ -279,8 +327,10 @@
   /* Check the login cookie to see if it matches a known valid user.
   */
   if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
     if( isdigit(zCookie[0]) ){
+      /* Cookies of the form "uid/randomness".  There must be a
+      ** corresponding entry in the user table. */
       uid = db_int(0,
             "SELECT uid FROM user"
             " WHERE uid=%d"
             "   AND cookie=%Q"
@@ -287,51 +337,95 @@
             "   AND ipaddr=%Q"
             "   AND cexpire>julianday('now')",
             atoi(zCookie), zCookie, zRemoteAddr
          );
-    }else if( zCookie[0]=='a' ){
-      uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'");
+    }else if( memcmp(zCookie,"anon/",5)==0 ){
+      /* Cookies of the form "anon/TIME/HASH".  The TIME must not be
+      ** too old and the sha1 hash of TIME+IPADDR+SECRET must match HASH.
+      ** SECRET is the "captcha-secret" value in the repository.
+      */
+      double rTime;
+      int i;
+      Blob b;
+      rTime = atof(&zCookie[5]);
+      for(i=5; zCookie[i] && zCookie[i]!='/'; i++){}
+      blob_init(&b, &zCookie[5], i-5);
+      if( zCookie[i]=='/' ){ i++; }
+      blob_append(&b, "/", 1);
+      blob_appendf(&b, "%s/%s", zRemoteAddr, db_get("captcha-secret",""));
+      sha1sum_blob(&b, &b);
+      uid = db_int(0,
+          "SELECT uid FROM user WHERE login='anonymous'"
+          " AND length(cap)>0"
+          " AND length(pw)>0"
+          " AND %f+0.25>julianday('now')"
+          " AND %Q=%Q",
+          rTime, &zCookie[i], blob_buffer(&b)
+      );
+      blob_reset(&b);
     }
     sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie);
   }
 
+  /* If no user found yet, try to log in as "nobody" */
   if( uid==0 ){
     uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
     if( uid==0 ){
+      /* If there is no user "nobody", then make one up - with no privileges */
       uid = -1;
       zCap = "";
     }
     strcpy(g.zCsrfToken, "none");
   }
+
+  /* At this point, we know that uid!=0.  Find the privileges associated
+  ** with user uid.
+  */
+  assert( uid!=0 );
   if( zCap==0 ){
-    if( uid ){
-      Stmt s;
-      db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
-      if( db_step(&s)==SQLITE_ROW ){
-        g.zLogin = db_column_malloc(&s, 0);
-        zCap = db_column_malloc(&s, 1);
-      }
-      db_finalize(&s);
+    Stmt s;
+    db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
+    if( db_step(&s)==SQLITE_ROW ){
+      g.zLogin = db_column_malloc(&s, 0);
+      zCap = db_column_malloc(&s, 1);
     }
+    db_finalize(&s);
     if( zCap==0 ){
       zCap = "";
     }
   }
+
+  /* Set the global variables recording the userid and login.  The
+  ** "nobody" user is a special case in that g.zLogin==0.
+  */
   g.userUid = uid;
   if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){
     g.zLogin = 0;
   }
-  if( uid && g.zLogin ){
+
+  /* Set the capabilities */
+  login_set_capabilities(zCap);
+  login_set_anon_nobody_capabilities();
+}
+
+/*
+** Add the default privileges of users "nobody" and "anonymous" as appropriate
+** for the user g.zLogin.
+*/
+void login_set_anon_nobody_capabilities(void){
+  static int once = 1;
+  if( g.zLogin && once ){
+    const char *zCap;
     /* All logged-in users inherit privileges from "nobody" */
-    zNcap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
-    login_set_capabilities(zNcap);
+    zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
+    login_set_capabilities(zCap);
     if( strcmp(g.zLogin, "anonymous")!=0 ){
       /* All logged-in users inherit privileges from "anonymous" */
-      zAcap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
-      login_set_capabilities(zAcap);
+      zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
+      login_set_capabilities(zCap);
     }
-  }
-  login_set_capabilities(zCap);
+    once = 0;
+  }
 }
 
 /*
 ** Set the global capability flags based on a capability string.
@@ -475,10 +569,10 @@
 
 /*
 ** Before using the results of a form, first call this routine to verify
 ** that ths Anti-CSRF token is present and is valid.  If the Anti-CSRF token
-** is missing or is incorrect, then this emits and error message and never
-** returns.
+** is missing or is incorrect, that indicates a cross-site scripting attach
+** so emits an error message and abort.
 */
 void login_verify_csrf_secret(void){
   const char *zCsrf;            /* The CSRF secret */
   if( g.okCsrf ) return;