]> begriffs open source - libpw/blob - pw.c
Informative status codes for all funcs
[libpw] / pw.c
1 #include "pw.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 #include <pwd.h>
7 #include <unistd.h>
8
9 // Using bcrypt for now, but alternatives are emerging
10 // https://isopenbsdsecu.re/mitigations/password_hashing/
11 #define CRYPT_FLAVOR "bcrypt,a"
12
13 struct pw_context
14 {
15         sqlite3 *conn;
16         const char *dummy_hash;
17         sqlite3_stmt *upsert_stmt;
18         sqlite3_stmt *find_stmt;
19 };
20
21 pw_status
22 pw_init_context(sqlite3 *db, pw_context **out)
23 {
24         int rc, fk_enabled;
25         static char shared_dummy_hash[_PASSWORD_LEN];
26
27         pw_context *ctx = malloc(sizeof *ctx);
28         if(!ctx)
29                 return PW_NO_MEM;
30         *out = ctx;
31
32         rc = sqlite3_db_config(db,
33                 SQLITE_DBCONFIG_ENABLE_FKEY, 1, &fk_enabled
34         );
35         if (rc != SQLITE_OK || !fk_enabled)
36                 return PW_SQLITE_ERROR;
37
38         ctx->conn = db;
39
40         if (shared_dummy_hash[0] == '\0')
41         {
42                 rc = crypt_newhash(
43                         "bogus", CRYPT_FLAVOR,
44                         shared_dummy_hash, _PASSWORD_LEN);
45                 if (rc < 0)
46                         return PW_CRYPT_ERROR;
47         }
48         ctx->dummy_hash = shared_dummy_hash;
49
50         rc = sqlite3_prepare_v2(db,
51                 "INSERT INTO login (user, pass) VALUES (?1, ?2) "
52                 "ON CONFLICT (user) DO UPDATE SET pass = ?2",
53                 -1, &ctx->upsert_stmt, NULL);
54         if (rc != SQLITE_OK)
55                 return PW_SQLITE_ERROR;
56         rc = sqlite3_prepare_v2(db,
57                 "SELECT pass FROM login WHERE user = ?",
58                 -1, &ctx->find_stmt, NULL);
59         if (rc != SQLITE_OK)
60                 return PW_SQLITE_ERROR;
61
62         return PW_OK;
63 }
64
65 pw_status
66 pw_set(pw_context *ctx, char *user, char *pass)
67 {
68         int rc;
69         rc = sqlite3_bind_text(ctx->upsert_stmt, 1, user, -1, SQLITE_TRANSIENT);
70         if (rc != SQLITE_OK)
71                 return PW_SQLITE_ERROR;
72
73         char hash[_PASSWORD_LEN];
74         if (crypt_newhash(pass, CRYPT_FLAVOR, hash, sizeof hash) < 0)
75                 return PW_CRYPT_ERROR;
76
77         rc = sqlite3_bind_text(ctx->upsert_stmt, 2, hash, -1, SQLITE_TRANSIENT);
78         if (rc != SQLITE_OK)
79                 return PW_SQLITE_ERROR;
80
81         rc = sqlite3_step(ctx->upsert_stmt);
82         sqlite3_reset(ctx->upsert_stmt);
83         return (rc == SQLITE_DONE && sqlite3_changes(ctx->conn) > 0)
84                 ? PW_OK : PW_SQLITE_ERROR;
85 }
86
87 pw_status
88 pw_check(pw_context *ctx, char *user, char *pass)
89 {
90         int rc;
91         rc = sqlite3_bind_text(ctx->find_stmt, 1, user, -1, SQLITE_TRANSIENT);
92         if (rc != SQLITE_OK)
93                 return PW_SQLITE_ERROR;
94
95         bool user_exists = sqlite3_step(ctx->find_stmt) == SQLITE_ROW;
96
97         // Unconditionally run crypt_checkpass() so pw_check() takes a
98         // similar amount of time to complete whether or not the user is
99         // in the db
100         const char *pass_hash = user_exists
101                 // bcrypt uses characters in portable range of char
102                 ? (const char *)sqlite3_column_text(ctx->find_stmt, 0)
103                 // magic hash doesn't matter, just make crypt_checkpass sweat
104                 : ctx->dummy_hash;
105
106         rc = crypt_checkpass(pass, pass_hash);
107         sqlite3_reset(ctx->find_stmt);
108
109         if (!user_exists)
110                 return PW_NO_USER;
111         return (rc == 0) ? PW_OK : PW_BAD_PASS;
112 }