Fossil

Check-in [cebf9919]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Implemented anonymous user login over JSON. Requires 2 requests (captcha-fetch and then login).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | json
Files: files | file ages | folders
SHA1: cebf9919f87c6678313cada3bdcc29f689a0e2ce
User & Date: stephan 2011-09-18 08:11:39
Context
2011-09-18
10:25
Added userName to /json/stat output for the nobody user (it was previously not set in that case). Renamed captcha to password in /json/anonymousPassword. Added NYI (not yet implemented) placeholders for several planned request types. check-in: 13cc3b82 user: stephan tags: json
08:11
Implemented anonymous user login over JSON. Requires 2 requests (captcha-fetch and then login). check-in: cebf9919 user: stephan tags: json
05:51
merged trunk [b54b8e751a]. check-in: 76c4ae5e user: stephan tags: json
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/json.c.

62
63
64
65
66
67
68

69
70
71
72
73
74
75
...
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
...
711
712
713
714
715
716
717













718
719
720
721
722
723
724
725
726

727
728
729
730
731
732
733
...
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
...
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
...
910
911
912
913
914
915
916



917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943


944






























945

946
947
948
949
950
951
952
953

954


955

956
957
958
959

960
961



962


963

964
965
966
967
968

969
970
971
972
973
974
975
...
989
990
991
992
993
994
995


















996
997
998
999
1000
1001
1002
....
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
....
1114
1115
1116
1117
1118
1119
1120

1121
1122
1123
1124
1125
1126
1127
....
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
....
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
    C(RESOURCE_NOT_FOUND,"Resource not found");
    C(TIMEOUT,"Timeout reached");
    C(ASSERT,"Assertion failed");
    C(ALLOC,"Resource allocation failed");
    C(NYI,"Not yet implemented.");
    C(AUTH,"Authentication error");
    C(LOGIN_FAILED,"Login failed");

    C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
    C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
    C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
    C(MISSING_AUTH,"Authentication info missing from request");
    C(DENIED,"Access denied");
    C(WRONG_MODE,"Request not allowed (wrong operation mode)");

................................................................................
** resultText property.
**
** If resultCode is non-zero and payload is not NULL then this
** function calls cson_value_free(payload) and does not insert the
** payload into the response.
**
*/
cson_value * json_response_skeleton( int resultCode,
                                     cson_value * payload,
                                     char const * pMsg ){
  cson_value * v = NULL;
  cson_value * tmp = NULL;
  cson_object * o = NULL;
  int rc;
  resultCode = json_dumbdown_rc(resultCode);
................................................................................
  }
  if( pMsg && *pMsg ){
    tmp = cson_value_new_string(pMsg,strlen(pMsg));
    SET("resultText");
  }
  tmp = json_getenv("requestId");
  if( tmp ) cson_object_set( o, "requestId", tmp );













  if( NULL != payload ){
    if( resultCode ){
      cson_value_free(payload);
      payload = NULL;
    }else{
      tmp = payload;
      SET("payload");
    }
  }

#undef SET

  if(0){/*Only for debuggering, add some info to the response.*/
    tmp = cson_value_new_integer( g.json.cmd.offset );
    cson_object_set( o, "cmd.offset", tmp );
    cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
  }
................................................................................
                          ? g.json.resultCode
                          : FSL_JSON_E_UNKNOWN);
  cson_value * resp = NULL;
  rc = json_dumbdown_rc(rc);
  if( rc && !msg ){
    msg = json_err_str(rc);
  }
  resp = json_response_skeleton(rc, NULL, msg);
  if( g.isCGI ){
    Blob buf = empty_blob;
    cgi_reset_content();
    cson_output_Blob( resp, &buf, &g.json.outOpt );
    cgi_set_content(&buf);
    if( alsoOutput ){
      cgi_reply();
................................................................................
**
*/
cson_value * json_page_login(void){
  static char preciseErrors =
#if 0
    g.json.errorDetailParanoia ? 0 : 1
#else
    0
#endif
    ;
  /*
    FIXME: we want to check the GET/POST args in this order:

    - GET: name, n, password, p
    - POST: name, password
................................................................................
    then ONLY a GET param of "name" will match ("n"
    is not recognized). If we reverse the order of the
    checks then both forms work. Strangely enough, the
    "p"/"password" check is not affected by this.
   */
  char const * name = cson_value_get_cstr(json_payload_property("name"));
  char const * pw = NULL;



  if( !name ){
    name = PD("n",NULL);
    if( !name ){
      name = PD("name",NULL);
      if( !name ){
        g.json.resultCode = preciseErrors
          ? FSL_JSON_E_LOGIN_FAILED_NONAME
          : FSL_JSON_E_LOGIN_FAILED;
        return NULL;
      }
    }
  }

  pw = cson_value_get_cstr(json_payload_property("password"));
  if( !pw ){
    pw = PD("p",NULL);
    if( !pw ){
      pw = PD("password",NULL);
    }
  }
  if(!pw){
    g.json.resultCode = preciseErrors
      ? FSL_JSON_E_LOGIN_FAILED_NOPW
      : FSL_JSON_E_LOGIN_FAILED;
    return NULL;
  }else{
    cson_value * payload = NULL;


    int uid = 0;






























#if 0

    /* only for debugging the PD()-incorrect-result problem */
    cson_object * o = NULL;
    uid = login_search_uid( name, pw );
    payload = cson_value_new_object();
    o = cson_value_get_object(payload);
    cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
    cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
    return payload;

#else


    uid = login_search_uid( name, pw );

    if( !uid ){
      g.json.resultCode = preciseErrors
        ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
        : FSL_JSON_E_LOGIN_FAILED;

    }else{
      char * cookie = NULL;



      login_set_user_cookie(name, uid, &cookie);


      payload = cson_value_new_string( cookie, strlen(cookie) );

      free(cookie);
    }
    return payload;
#endif
  }

}

/*
** Impl of /json/logout.
**
*/
cson_value * json_page_logout(void){
................................................................................
    g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
  }else{
    login_clear_login_data();
    g.json.authToken = NULL /* memory is owned elsewhere.*/;
  }
  return NULL;
}



















/*
** Implementation of the /json/stat page/command.
**
*/
cson_value * json_page_stat(void){
  i64 t, fsize;
................................................................................
  const char *zDb;
  enum { BufLen = 1000 };
  char zBuf[BufLen];
  cson_value * jv = NULL;
  cson_object * jo = NULL;
  cson_value * jv2 = NULL;
  cson_object * jo2 = NULL;
  login_check_credentials();
  if( !g.perm.Read ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }
#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));

  jv = cson_value_new_object();
................................................................................

/*
** Mapping of names to JSON pages/commands.  Each name is a subpath of
** /json (in CGI mode) or a subcommand of the json command in CLI mode
*/
static const JsonPageDef JsonPageDefs[] = {
/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */

{"cap", json_page_cap, 0},
{"HAI",json_page_version,0},
{"login",json_page_login,1/*should be >0. Only 0 for dev/testing purposes.*/},
{"logout",json_page_logout,1/*should be >0. Only 0 for dev/testing purposes.*/},
{"stat",json_page_stat,0},
{"version",json_page_version,0},
{"wiki",json_page_wiki,0},
................................................................................
    rc = 0;
    payload = (*pageDef->func)();
  }
  if( g.json.resultCode ){
    json_err(g.json.resultCode, NULL, 0);
  }else{
    blob_zero(&buf);
    root = json_response_skeleton(rc, payload, NULL);
    cson_output_Blob( root, &buf, NULL );
    cson_value_free(root);
    cgi_set_content(&buf)/*takes ownership of the buf memory*/;
  }
}

/*
................................................................................
  }else{
    rc = 0;
    payload = (pageDef->func)();
  }
  if( g.json.resultCode ){
    json_err(g.json.resultCode, NULL, 1);
  }else{
    payload = json_response_skeleton(rc, payload, NULL);
    cson_output_FILE( payload, stdout, &g.json.outOpt );
    cson_value_free( payload );
    if((0 != rc) && !g.isCGI){
      /* FIXME: we need a way of passing this error back
         up to the routine which called this callback.
         e.g. add g.errCode.
      */







>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>









>







 







|







 







|







 







>
>
>












|












<
<
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>








>

>
>
|
>
|
|
|
|
>
|
|
>
>
>

>
>
|
>
|
<

<

>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<







 







>







 







|







 







|







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
...
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
...
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
...
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
...
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
...
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959


960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024

1025

1026
1027
1028
1029
1030
1031
1032
1033
1034
....
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
....
1081
1082
1083
1084
1085
1086
1087

1088
1089
1090
1091
1092
1093
1094
....
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
....
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
....
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
    C(RESOURCE_NOT_FOUND,"Resource not found");
    C(TIMEOUT,"Timeout reached");
    C(ASSERT,"Assertion failed");
    C(ALLOC,"Resource allocation failed");
    C(NYI,"Not yet implemented.");
    C(AUTH,"Authentication error");
    C(LOGIN_FAILED,"Login failed");
    C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed.");
    C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
    C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
    C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
    C(MISSING_AUTH,"Authentication info missing from request");
    C(DENIED,"Access denied");
    C(WRONG_MODE,"Request not allowed (wrong operation mode)");

................................................................................
** resultText property.
**
** If resultCode is non-zero and payload is not NULL then this
** function calls cson_value_free(payload) and does not insert the
** payload into the response.
**
*/
cson_value * json_create_response( int resultCode,
                                     cson_value * payload,
                                     char const * pMsg ){
  cson_value * v = NULL;
  cson_value * tmp = NULL;
  cson_object * o = NULL;
  int rc;
  resultCode = json_dumbdown_rc(resultCode);
................................................................................
  }
  if( pMsg && *pMsg ){
    tmp = cson_value_new_string(pMsg,strlen(pMsg));
    SET("resultText");
  }
  tmp = json_getenv("requestId");
  if( tmp ) cson_object_set( o, "requestId", tmp );

  if(0){
    if(g.json.cmd.v){/* this is only intended for my own testing...*/
      tmp = g.json.cmd.v;
      SET("$commandPath");
    }
    if(g.json.param.v){/* this is only intended for my own testing...*/
      tmp = g.json.param.v;
      SET("$params");
    }
  }

  /* Only add the payload to SUCCESS responses. Else delete it. */
  if( NULL != payload ){
    if( resultCode ){
      cson_value_free(payload);
      payload = NULL;
    }else{
      tmp = payload;
      SET("payload");
    }
  }

#undef SET

  if(0){/*Only for debuggering, add some info to the response.*/
    tmp = cson_value_new_integer( g.json.cmd.offset );
    cson_object_set( o, "cmd.offset", tmp );
    cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
  }
................................................................................
                          ? g.json.resultCode
                          : FSL_JSON_E_UNKNOWN);
  cson_value * resp = NULL;
  rc = json_dumbdown_rc(rc);
  if( rc && !msg ){
    msg = json_err_str(rc);
  }
  resp = json_create_response(rc, NULL, msg);
  if( g.isCGI ){
    Blob buf = empty_blob;
    cgi_reset_content();
    cson_output_Blob( resp, &buf, &g.json.outOpt );
    cgi_set_content(&buf);
    if( alsoOutput ){
      cgi_reply();
................................................................................
**
*/
cson_value * json_page_login(void){
  static char preciseErrors =
#if 0
    g.json.errorDetailParanoia ? 0 : 1
#else
    1
#endif
    ;
  /*
    FIXME: we want to check the GET/POST args in this order:

    - GET: name, n, password, p
    - POST: name, password
................................................................................
    then ONLY a GET param of "name" will match ("n"
    is not recognized). If we reverse the order of the
    checks then both forms work. Strangely enough, the
    "p"/"password" check is not affected by this.
   */
  char const * name = cson_value_get_cstr(json_payload_property("name"));
  char const * pw = NULL;
  char const * anonSeed = NULL;
  cson_value * payload = NULL;
  int uid = 0;
  if( !name ){
    name = PD("n",NULL);
    if( !name ){
      name = PD("name",NULL);
      if( !name ){
        g.json.resultCode = preciseErrors
          ? FSL_JSON_E_LOGIN_FAILED_NONAME
          : FSL_JSON_E_LOGIN_FAILED;
        return NULL;
      }
    }
  }
  
  pw = cson_value_get_cstr(json_payload_property("password"));
  if( !pw ){
    pw = PD("p",NULL);
    if( !pw ){
      pw = PD("password",NULL);
    }
  }
  if(!pw){
    g.json.resultCode = preciseErrors
      ? FSL_JSON_E_LOGIN_FAILED_NOPW
      : FSL_JSON_E_LOGIN_FAILED;
    return NULL;


  }

  if(0 == strcmp("anonymous",name)){
    /* check captcha/seed values... */
    enum { SeedBufLen = 100 /* in some JSON tests i once actually got an
                           80-digit number.
                        */
    };
    static char seedBuffer[SeedBufLen];
    seedBuffer[0] = 0;
    cson_value const * jseed = json_getenv("anonymousSeed");
    if( !jseed ){
      jseed = json_payload_property("anonymousSeed");
      if( !jseed ){
        jseed = json_getenv("cs") /* name used by HTML interface */;
      }
    }
    if(jseed){
      if( cson_value_is_number(jseed) ){
        sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed));
        anonSeed = seedBuffer;
      }else if( cson_value_is_string(jseed) ){
        anonSeed = cson_string_cstr(cson_value_get_string(jseed));
      }
    }
    if(!anonSeed){
      g.json.resultCode = preciseErrors
        ? FSL_JSON_E_LOGIN_FAILED_NOSEED
        : FSL_JSON_E_LOGIN_FAILED;
      return NULL;
    }
  }

#if 0
  {
    /* only for debugging the PD()-incorrect-result problem */
    cson_object * o = NULL;
    uid = login_search_uid( name, pw );
    payload = cson_value_new_object();
    o = cson_value_get_object(payload);
    cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
    cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
    return payload;
  }
#else
  uid = anonSeed
    ? login_is_valid_anonymous(name, pw, anonSeed)
    : login_search_uid(name, pw)
    ;
  if( !uid ){
    g.json.resultCode = preciseErrors
      ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
      : FSL_JSON_E_LOGIN_FAILED;
    return NULL;
  }else{
    char * cookie = NULL;
    if(anonSeed){
      login_set_anon_cookie(NULL, &cookie);
    }else{
      login_set_user_cookie(name, uid, &cookie);
    }
    payload = cookie
      ? cson_value_new_string( cookie, strlen(cookie) )
      : cson_value_null();
    free(cookie);

    return payload;

  }
#endif
}

/*
** Impl of /json/logout.
**
*/
cson_value * json_page_logout(void){
................................................................................
    g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
  }else{
    login_clear_login_data();
    g.json.authToken = NULL /* memory is owned elsewhere.*/;
  }
  return NULL;
}

/*
** Implementation of the /json/anonymousPassword page.
*/
cson_value * json_page_anon_password(void){
  cson_value * v = cson_value_new_object();
  cson_object * o = cson_value_get_object(v);
  unsigned const int seed = captcha_seed();
  char const * zCaptcha = captcha_decode(seed);
  cson_object_set(o, "seed",
                  cson_value_new_integer( (cson_int_t)seed )
                  );
  cson_object_set(o, "captcha",
                  cson_value_new_string( zCaptcha, strlen(zCaptcha) )
                  );
  return v;
}


/*
** Implementation of the /json/stat page/command.
**
*/
cson_value * json_page_stat(void){
  i64 t, fsize;
................................................................................
  const char *zDb;
  enum { BufLen = 1000 };
  char zBuf[BufLen];
  cson_value * jv = NULL;
  cson_object * jo = NULL;
  cson_value * jv2 = NULL;
  cson_object * jo2 = NULL;

  if( !g.perm.Read ){
    g.json.resultCode = FSL_JSON_E_DENIED;
    return NULL;
  }
#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));

  jv = cson_value_new_object();
................................................................................

/*
** Mapping of names to JSON pages/commands.  Each name is a subpath of
** /json (in CGI mode) or a subcommand of the json command in CLI mode
*/
static const JsonPageDef JsonPageDefs[] = {
/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */
{"anonymousPassword", json_page_anon_password, 1},
{"cap", json_page_cap, 0},
{"HAI",json_page_version,0},
{"login",json_page_login,1/*should be >0. Only 0 for dev/testing purposes.*/},
{"logout",json_page_logout,1/*should be >0. Only 0 for dev/testing purposes.*/},
{"stat",json_page_stat,0},
{"version",json_page_version,0},
{"wiki",json_page_wiki,0},
................................................................................
    rc = 0;
    payload = (*pageDef->func)();
  }
  if( g.json.resultCode ){
    json_err(g.json.resultCode, NULL, 0);
  }else{
    blob_zero(&buf);
    root = json_create_response(rc, payload, NULL);
    cson_output_Blob( root, &buf, NULL );
    cson_value_free(root);
    cgi_set_content(&buf)/*takes ownership of the buf memory*/;
  }
}

/*
................................................................................
  }else{
    rc = 0;
    payload = (pageDef->func)();
  }
  if( g.json.resultCode ){
    json_err(g.json.resultCode, NULL, 1);
  }else{
    payload = json_create_response(rc, payload, NULL);
    cson_output_FILE( payload, stdout, &g.json.outOpt );
    cson_value_free( payload );
    if((0 != rc) && !g.isCGI){
      /* FIXME: we need a way of passing this error back
         up to the routine which called this callback.
         e.g. add g.errCode.
      */

Changes to src/json_detail.h.

31
32
33
34
35
36
37

38
39
40
41
42
43
44
45
46
47

FSL_JSON_E_AUTH = 2000,
FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 1,
FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 2,
FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 3,

FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100,

FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 1,
FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 2,
FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 3,

FSL_JSON_E_USAGE = 3000,
FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1,
FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2,

FSL_JSON_E_DB = 4000,
FSL_JSON_E_STMT_PREP = FSL_JSON_E_DB + 1,







>
|
|
|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

FSL_JSON_E_AUTH = 2000,
FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 1,
FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 2,
FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 3,

FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100,
FSL_JSON_E_LOGIN_FAILED_NOSEED = FSL_JSON_E_LOGIN_FAILED + 1,
FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 2,
FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 3,
FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 4,

FSL_JSON_E_USAGE = 3000,
FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1,
FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2,

FSL_JSON_E_DB = 4000,
FSL_JSON_E_STMT_PREP = FSL_JSON_E_DB + 1,

Changes to src/login.c.

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
...
272
273
274
275
276
277
278








































279
280
281
282
283
284
285
...
292
293
294
295
296
297
298
299


300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
...
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

413
414
415
416
417
418
419
...
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
...
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
...
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
...
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
...
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820



821
822
823
824
825
826
827
...
871
872
873
874
875
876
877








878
879
880
881
882
883
884
/*
** Check to see if the anonymous login is valid.  If it is valid, return
** the userid of the anonymous user.
**
** The zCS parameter is the "captcha seed" used for a specific
** anonymous login request.
*/
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 */

................................................................................
  free(zHash);
  if( zDest ){
    *zDest = zCookie;
  }else{
    free(zCookie);
  }
}









































/*
** "Unsets" the login cookie (insofar as cookies can be unset) and
** clears the current user's (g.userUid) login information from the
** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
**
** We could/should arguably clear out g.userUid and g.perm here, but
................................................................................
    return;
  }else{
    char const * cookie = login_cookie_name(); 
    /* To logout, change the cookie value to an empty string */
    cgi_set_cookie(cookie, "",
                   login_cookie_path(), -86400);
    db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
                  "  cexpire=0 WHERE uid=%d", g.userUid);


    cgi_replace_parameter(cookie, NULL)
      /* At the time of this writing, cgi_replace_parameter() was
      ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any
      ** downstream problems here. We could alternately use "" here.
      */
      ;
    /* Potential improvement: do we want/need to skip this step for
    ** the guest user?
    */
  }
}

/*
** WEBPAGE: login
** WEBPAGE: logout
** WEBPAGE: my
................................................................................
  const char *zNew1, *zNew2;
  const char *zAnonPw = 0;
  int anonFlag;
  char *zErrMsg = "";
  int uid;                     /* User id loged in user */
  char *zSha1Pw;
  const char *zIpAddr;         /* IP address of requestor */
  char *zRemoteAddr;           /* Abbreviated IP address of requestor */

  login_check_credentials();
  zUsername = P("u");
  zPasswd = P("p");
  anonFlag = P("anon")!=0;
  if( P("out")!=0 ){
    login_clear_login_data();
................................................................................
      }else{
        redirect_to_g();
        return;
      }
    }
  }
  zIpAddr = PD("REMOTE_ADDR","nil");   /* Complete IP address for logging */
  zRemoteAddr = ipPrefix(zIpAddr);     /* Abbreviated IP address */
  uid = isValidAnonymousLogin(zUsername, zPasswd, P("cs"));
  if( uid>0 ){
    /* Successful login as anonymous.  Set a cookie that looks like
    ** this:
    **
    **    HASH/TIME/anonymous
    **
    ** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
    ** is the abbreviated IP address and SECRET is captcha-secret.
    */
    char *zNow;                  /* Current time (julian day number) */
    char *zCookie;               /* The login cookie */
    const char *zCookieName;     /* Name of the login cookie */
    Blob b;                      /* Blob used during cookie construction */

    zCookieName = login_cookie_name();
    zNow = db_text("0", "SELECT julianday('now')");
    blob_init(&b, zNow, -1);
    blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
    sha1sum_blob(&b, &b);
    zCookie = sqlite3_mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
    blob_reset(&b);
    free(zNow);
    cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);

    record_login_attempt("anonymous", zIpAddr, 1);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
................................................................................
  }
  sqlite3_close(pOther);
  fossil_free(zOtherRepo);
  return nXfer;
}

/*
** Lookup the uid for a user with zLogin and zCookie and zRemoteAddr.
** Return 0 if not found.
**
** Note that this only searches for logged-in entries with
** matching zCookie (user.cookie) and zRemoteAddr (user.ipaddr)
** entries.
*/
static int login_find_user(
  const char *zLogin,            /* User name */
  const char *zCookie,           /* Login cookie value */
  const char *zRemoteAddr        /* Abbreviated IP address for valid login */
){
................................................................................
    "   AND length(pw)>0",
    zLogin, zCookie, zRemoteAddr
  );
  return uid;
}

/*
** This routine examines the login cookie to see if it exists and
** and is valid.  If the login cookie checks out, it then sets 
** global variables appropriately.  Global variables set include
** g.userUid and g.zLogin and of the g.perm.Read family of permission
** booleans.
**
*/
void login_check_credentials(void){
  int uid = 0;                  /* User id */
  const char *zCookie;          /* Text of the login cookie */
  const char *zIpAddr;          /* Raw IP address of the requestor */
  char *zRemoteAddr;            /* Abbreviated IP address of the requestor */
  const char *zCap = 0;         /* Capability string */
................................................................................
        if( uid ) record_login_attempt(zUser, zIpAddr, 1);
      }
    }
    sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
  }

  /* If no user found and the REMOTE_USER environment variable is set,
  ** the accept the value of REMOTE_USER as the user.
  */
  if( uid==0 ){
    const char *zRemoteUser = P("REMOTE_USER");
    if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
                      " AND length(cap)>0 AND length(pw)>0", zRemoteUser);
    }
................................................................................
  */
  g.userUid = uid;
  if( fossil_strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;
  }

  /* Set the capabilities */
  login_set_capabilities(zCap, 0);
  login_set_anon_nobody_capabilities();
}

/*
** Memory of settings
*/
static int login_anon_once = 1;
................................................................................
      login_set_capabilities(zCap, 0);
    }
    login_anon_once = 0;
  }
}

/*
** Flags passed into the 2nd argument of login_set_capabilities().
*/
#if INTERFACE
#define LOGIN_IGNORE_U   0x01         /* Ignore "u" */
#define LOGIN_IGNORE_V   0x01         /* Ignore "v" */
#endif

/*
** Set the global capability flags based on a capability string.
*/
void login_set_capabilities(const char *zCap, unsigned flags){
  int i;



  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
      case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
                              g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
                              g.perm.ApndWiki = g.perm.History = g.perm.Clone = 
                              g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
................................................................................
          login_set_capabilities(zDev, flags | LOGIN_IGNORE_V);
        }
        break;
      }
    }
  }
}









/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0.  If all capabilities are present, then
** return 1.
*/
int login_has_capability(const char *zCap, int nCap){







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>






<
<
<







 







<







 







<
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
>







 







|
|

|
|







 







|
|
|
|
<
<







 







|







 







|







 







|







|



>
>
>







 







>
>
>
>
>
>
>
>







143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
...
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347



348
349
350
351
352
353
354
...
365
366
367
368
369
370
371

372
373
374
375
376
377
378
...
419
420
421
422
423
424
425

426
427






















428
429
430
431
432
433
434
435
...
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
...
645
646
647
648
649
650
651
652
653
654
655


656
657
658
659
660
661
662
...
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
...
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
...
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
...
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
/*
** Check to see if the anonymous login is valid.  If it is valid, return
** the userid of the anonymous user.
**
** The zCS parameter is the "captcha seed" used for a specific
** anonymous login request.
*/
int login_is_valid_anonymous(
  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 */

................................................................................
  free(zHash);
  if( zDest ){
    *zDest = zCookie;
  }else{
    free(zCookie);
  }
}

/* Sets a cookie for an anonymous user login, which looks like this:
**
**    HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/IPADDR/SECRET, in which IPADDR
** is the abbreviated IP address and SECRET is captcha-secret.
**
** If either zIpAddr or zRemoteAddr are NULL then REMOTE_ADDR
** is used.
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
*/
void login_set_anon_cookie(char const * zIpAddr, char ** zCookieDest ){
  char const *zNow;            /* Current time (julian day number) */
  char *zCookie;               /* The login cookie */
  char const *zCookieName;     /* Name of the login cookie */
  Blob b;                      /* Blob used during cookie construction */
  char * zRemoteAddr;     /* Abbreviated IP address */
  if(!zIpAddr){
    zIpAddr = PD("REMOTE_ADDR","nil");
  }
  zRemoteAddr = ipPrefix(zIpAddr);
  zCookieName = login_cookie_name();
  zNow = db_text("0", "SELECT julianday('now')");
  assert( zCookieName && zRemoteAddr && zIpAddr && zNow );
  blob_init(&b, zNow, -1);
  blob_appendf(&b, "/%s/%s", zRemoteAddr, db_get("captcha-secret",""));
  sha1sum_blob(&b, &b);
  zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
  blob_reset(&b);
  cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
  if( zCookieDest ){
    *zCookieDest = zCookie;
  }else{
    free(zCookie);
  }

}

/*
** "Unsets" the login cookie (insofar as cookies can be unset) and
** clears the current user's (g.userUid) login information from the
** user table. Sets: user.cookie, user.ipaddr, user.cexpire.
**
** We could/should arguably clear out g.userUid and g.perm here, but
................................................................................
    return;
  }else{
    char const * cookie = login_cookie_name(); 
    /* To logout, change the cookie value to an empty string */
    cgi_set_cookie(cookie, "",
                   login_cookie_path(), -86400);
    db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
                  "  cexpire=0 WHERE uid=%d"
                  "  AND login NOT IN ('anonymous','guest',"
                  "  'developer','reader')", g.userUid);
    cgi_replace_parameter(cookie, NULL)
      /* At the time of this writing, cgi_replace_parameter() was
      ** "NULL-value-safe", and i'm hoping the NULL doesn't cause any
      ** downstream problems here. We could alternately use "" here.
      */
      ;



  }
}

/*
** WEBPAGE: login
** WEBPAGE: logout
** WEBPAGE: my
................................................................................
  const char *zNew1, *zNew2;
  const char *zAnonPw = 0;
  int anonFlag;
  char *zErrMsg = "";
  int uid;                     /* User id loged in user */
  char *zSha1Pw;
  const char *zIpAddr;         /* IP address of requestor */


  login_check_credentials();
  zUsername = P("u");
  zPasswd = P("p");
  anonFlag = P("anon")!=0;
  if( P("out")!=0 ){
    login_clear_login_data();
................................................................................
      }else{
        redirect_to_g();
        return;
      }
    }
  }
  zIpAddr = PD("REMOTE_ADDR","nil");   /* Complete IP address for logging */

  uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
  if( uid>0 ){






















    login_set_anon_cookie(zIpAddr, NULL);
    record_login_attempt("anonymous", zIpAddr, 1);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
................................................................................
  }
  sqlite3_close(pOther);
  fossil_free(zOtherRepo);
  return nXfer;
}

/*
** Lookup the uid for a non-built-in user with zLogin and zCookie and
** zRemoteAddr.  Return 0 if not found.
**
** Note that this only searches for logged-in entries with matching
** zCookie (db: user.cookie) and zRemoteAddr (db: user.ipaddr)
** entries.
*/
static int login_find_user(
  const char *zLogin,            /* User name */
  const char *zCookie,           /* Login cookie value */
  const char *zRemoteAddr        /* Abbreviated IP address for valid login */
){
................................................................................
    "   AND length(pw)>0",
    zLogin, zCookie, zRemoteAddr
  );
  return uid;
}

/*
** This routine examines the login cookie to see if it exists and and
** is valid.  If the login cookie checks out, it then sets global
** variables appropriately.  Global variables set include g.userUid
** and g.zLogin and the g.perm family of permission booleans.


*/
void login_check_credentials(void){
  int uid = 0;                  /* User id */
  const char *zCookie;          /* Text of the login cookie */
  const char *zIpAddr;          /* Raw IP address of the requestor */
  char *zRemoteAddr;            /* Abbreviated IP address of the requestor */
  const char *zCap = 0;         /* Capability string */
................................................................................
        if( uid ) record_login_attempt(zUser, zIpAddr, 1);
      }
    }
    sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
  }

  /* If no user found and the REMOTE_USER environment variable is set,
  ** then accept the value of REMOTE_USER as the user.
  */
  if( uid==0 ){
    const char *zRemoteUser = P("REMOTE_USER");
    if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
                      " AND length(cap)>0 AND length(pw)>0", zRemoteUser);
    }
................................................................................
  */
  g.userUid = uid;
  if( fossil_strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;
  }

  /* Set the capabilities */
  login_replace_capabilities(zCap, 0);
  login_set_anon_nobody_capabilities();
}

/*
** Memory of settings
*/
static int login_anon_once = 1;
................................................................................
      login_set_capabilities(zCap, 0);
    }
    login_anon_once = 0;
  }
}

/*
** Flags passed into the 2nd argument of login_set/replace_capabilities().
*/
#if INTERFACE
#define LOGIN_IGNORE_U   0x01         /* Ignore "u" */
#define LOGIN_IGNORE_V   0x01         /* Ignore "v" */
#endif

/*
** Adds all capability flags in zCap to g.perm.
*/
void login_set_capabilities(const char *zCap, unsigned flags){
  int i;
  if(NULL==zCap){
    return;
  }
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
      case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
                              g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
                              g.perm.ApndWiki = g.perm.History = g.perm.Clone = 
                              g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
................................................................................
          login_set_capabilities(zDev, flags | LOGIN_IGNORE_V);
        }
        break;
      }
    }
  }
}

/*
** Zeroes out g.perm and calls login_set_capabilities(zCap,flags).
*/
void login_replace_capabilities(const char *zCap, unsigned flags){
  memset(&g.perm, 0, sizeof(g.perm));
  return login_set_capabilities(zCap, flags);
}

/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0.  If all capabilities are present, then
** return 1.
*/
int login_has_capability(const char *zCap, int nCap){