user.c (4124B)
1 #include <assert.h> 2 3 #define const 4 5 #include <sys/types.h> 6 #include <stdarg.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <time.h> 12 #include <kcgi.h> 13 #include <kcgihtml.h> 14 15 #include <err.h> 16 #include <pwd.h> 17 18 #include <sqlbox.h> 19 20 #include "page.h" 21 #include "stmt.h" 22 #include "user.h" 23 #include "util.h" 24 25 #include "config.h" 26 27 char *user_errors[] = { 28 [LoginValid] = "Login valid", 29 [LoginNotFound] = "User not found", 30 [LoginInvalid] = "Invalid user credentials", 31 [LoginSystem] = "System error", 32 [LoginCorrupt] = "User database is corrupt" 33 }; 34 35 int 36 adduser(struct pagedata *pd, char *name, char *key) 37 { 38 char *salt, *hash; 39 40 salt = bcrypt_gensalt(12); 41 hash = bcrypt(key, salt); 42 if (!hash) 43 return 1; 44 45 struct sqlbox_parm p[] = { 46 { .sparm = hash, .type = SQLBOX_PARM_STRING }, 47 { .sparm = name, .type = SQLBOX_PARM_STRING } 48 }; 49 50 if (sqlbox_exec(pd->db, pd->dbid, StmtAddUser, Len(p), p, 0) != SQLBOX_CODE_OK) 51 return 1; 52 return 0; 53 } 54 55 int 56 deleteuser(struct pagedata *pd, char *hash) 57 { 58 struct sqlbox_parm p = { 59 .sparm = hash, .type = SQLBOX_PARM_STRING 60 }; 61 62 return sqlbox_exec(pd->db, 0, StmtDeleteUser, 1, &p, 0) != SQLBOX_CODE_OK; 63 } 64 65 void 66 freeuser(struct user *user) 67 { 68 if (!user) 69 return; 70 free(user->hash); 71 free(user->name); 72 free(user->auth); 73 } 74 75 enum user_error 76 getuser(struct user **ruser, struct pagedata *pd, char *field, char *value) 77 { 78 size_t stmtid; 79 enum StmtID gets; 80 struct user *user; 81 struct sqlbox_parmset *res; 82 struct sqlbox_parm p[] = { 83 { .sparm = value, .type = SQLBOX_PARM_STRING } 84 }; 85 86 if (strcmp(field, "name") == 0) 87 gets = StmtGetUserByName; 88 else if (strcmp(field, "hash") == 0) 89 gets = StmtGetUserByHash; 90 else if (strcmp(field, "token") == 0) 91 gets = StmtGetUserByToken; 92 else 93 gets = -1; 94 95 assert(gets >= 0); 96 97 if (!(stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, gets, Len(p), p, 0))) 98 return LoginSystem; 99 if (!(res = sqlbox_step(pd->db, stmtid))) 100 return LoginSystem; 101 102 if (!res->psz && res->code == SQLBOX_CODE_OK) { 103 sqlbox_finalise(pd->db, stmtid); 104 return LoginNotFound; 105 } 106 107 user = calloc(1, sizeof(*user)); 108 if (!user) 109 return LoginSystem; 110 111 if (res->psz != 2) { 112 freeuser(user); 113 return LoginCorrupt; 114 } 115 116 if (!(user->hash = strdup(res->ps[0].sparm)) || 117 !(user->name = strdup(res->ps[1].sparm))) { 118 freeuser(user); 119 return LoginSystem; 120 } 121 122 if (!sqlbox_finalise(pd->db, stmtid)) { 123 freeuser(user); 124 return LoginSystem; 125 } 126 127 assert(ruser); 128 *ruser = user; 129 130 return LoginValid; 131 } 132 133 enum sqlbox_code 134 addtoken(struct pagedata *pd, char *hash, char *token, time_t expires) 135 { 136 struct sqlbox_parm params[] = { 137 { .sparm = hash, .type = SQLBOX_PARM_STRING }, 138 { .sparm = token, .type = SQLBOX_PARM_STRING }, 139 { .iparm = expires, .type = SQLBOX_PARM_INT } 140 }; 141 142 return sqlbox_exec(pd->db, pd->dbid, StmtAddAuthToken, 143 Len(params), params, 0); 144 } 145 146 int 147 revoketokens(struct pagedata *pd, char *hash) 148 { 149 struct sqlbox_parm param = 150 { .sparm = hash, .type = SQLBOX_PARM_STRING }; 151 152 return sqlbox_exec(pd->db, pd->dbid, StmtRevokeAuthTokens, 1, ¶m, 0) != 153 SQLBOX_CODE_OK; 154 } 155 156 char * 157 newtoken(struct pagedata *pd, char *hash) 158 { 159 time_t expires; 160 char *salt, *token; 161 162 salt = bcrypt_gensalt(12); 163 if (!salt) { 164 kutil_warn(NULL, NULL, "Unable to generate salt for token"); 165 return NULL; 166 } 167 168 token = bcrypt(hash, salt); 169 if (!token || !(token = strdup(token))) 170 return NULL; 171 172 expires = time(NULL) + TokenTTL; 173 if (addtoken(pd, hash, token, expires) != SQLBOX_CODE_OK) { 174 free(token); 175 return NULL; 176 } 177 return token; 178 } 179 180 enum user_error 181 loginuser(struct user **ruser, struct pagedata *pd, char *name, char *key) 182 { 183 struct user *user; 184 char *testhash; 185 enum user_error status; 186 187 if ((status = getuser(&user, pd, "name", name))) 188 return status; 189 190 testhash = bcrypt(key, user->hash); 191 if (!testhash) { 192 freeuser(user); 193 return LoginSystem; 194 } 195 196 if (strcmp(user->hash, testhash) != 0) { 197 freeuser(user); 198 return LoginInvalid; 199 } 200 201 user->auth = newtoken(pd, user->hash); 202 if (!user->auth) { 203 freeuser(user); 204 return LoginSystem; 205 } 206 207 if (ruser) 208 *ruser = user; 209 else 210 freeuser(user); 211 212 return LoginValid; 213 }