Fixes to bugs pointed by [BOB]
[tpg/opendispense2.git] / src / cokebank_sqlite / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  *
5  * SQLite Coke Bank (Accounts Database)
6  *
7  * This file is licenced under the 3-clause BSD Licence. See the file
8  * COPYING for full details.
9  */
10 #include <inttypes.h>
11 #include <stdlib.h>
12 #include <limits.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include "../cokebank.h"
16 #include <sqlite3.h>
17
18 const char * const csBank_DatabaseSetup = 
19 "CREATE TABLE IF NOT EXISTS accounts ("
20 "       acct_id INTEGER PRIMARY KEY NOT NULL,"
21 "       acct_balance INTEGER NOT NULL DEFAULT 0,"
22 "       acct_last_seen DATETIME NOT NULL DEFAULT (datetime('now')),"
23 "       acct_name STRING UNIQUE,"
24 "       acct_uid INTEGER UNIQUE DEFAULT NULL,"
25 "       acct_pin INTEGER DEFAULT NULL," //"     acct_pin INTEGER CHECK (acct_pin > 0 AND acct_pin < 10000) DEFAULT NULL,"
26 "       acct_is_disabled BOOLEAN NOT NULL DEFAULT false,"
27 "       acct_is_coke BOOLEAN NOT NULL DEFAULT false,"
28 "       acct_is_admin BOOLEAN NOT NULL DEFAULT false,"
29 "       acct_is_door BOOLEAN NOT NULL DEFAULT false,"
30 "       acct_is_internal BOOLEAN NOT NULL DEFAULT false"
31 ");"
32 "CREATE TABLE IF NOT EXISTS cards ("
33 "       acct_id INTEGER NOT NULL,"
34 "       card_name STRING NOT NULL UNIQUE,"
35 "       FOREIGN KEY (acct_id) REFERENCES accounts (acct_id) ON DELETE CASCADE"
36 //                       Deletion of the account frees the card  ^ ^ ^
37 ");"
38 "INSERT INTO accounts (acct_name,acct_is_admin,acct_uid) VALUES ('root',1,0);"
39 "INSERT INTO accounts (acct_name,acct_is_internal,acct_uid) VALUES ('"COKEBANK_SALES_ACCT"',1,-1);"
40 "INSERT INTO accounts (acct_name,acct_is_internal,acct_uid) VALUES ('"COKEBANK_DEBT_ACCT"',1,-2);"
41 "INSERT INTO accounts (acct_name,acct_is_internal,acct_uid) VALUES ('"COKEBANK_FREE_ACCT"',1,-3);"
42 ;
43
44 // === TYPES ===
45 struct sAcctIterator    // Unused really, just used as a void type
46 {
47 };
48
49 // === PROTOYPES ===
50  int    Bank_Initialise(const char *Argument);
51  int    Bank_Transfer(int SourceAcct, int DestAcct, int Ammount, const char *Reason);
52  int    Bank_GetFlags(int AcctID);
53  int    Bank_SetFlags(int AcctID, int Mask, int Value);
54  int    Bank_GetBalance(int AcctID);
55 char    *Bank_GetAcctName(int AcctID);
56 sqlite3_stmt    *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query);
57 sqlite3_stmt    *Bank_int_QuerySingle(sqlite3 *Database, const char *Query);
58  int    Bank_int_IsValidName(const char *Name);
59
60 // === GLOBALS ===
61 sqlite3 *gBank_Database;
62
63 // === CODE ===
64 int Bank_Initialise(const char *Argument)
65 {
66          int    rv;
67         char    *errmsg;
68         // Open database
69         rv = sqlite3_open(Argument, &gBank_Database);
70         if(rv != 0)
71         {
72                 fprintf(stderr, "CokeBank: Unable to open database '%s'\n", Argument);
73                 fprintf(stderr, "Reason: %s\n", sqlite3_errmsg(gBank_Database));
74                 sqlite3_close(gBank_Database);
75                 return 1;
76         }
77
78         // Check structure
79         rv = sqlite3_exec(gBank_Database, "SELECT acct_id FROM accounts LIMIT 1", NULL, NULL, &errmsg);
80         if( rv == SQLITE_OK )
81         {
82                 // NOP
83         }
84         else if( rv == SQLITE_NOTFOUND || rv == SQLITE_ERROR )
85         {
86                 sqlite3_free(errmsg);
87                 // Create tables
88                 rv = sqlite3_exec(gBank_Database, csBank_DatabaseSetup, NULL, NULL, &errmsg);
89                 if( rv != SQLITE_OK ) {
90                         fprintf(stderr, "Bank_Initialise - SQLite Error: %s\n", errmsg);
91                         sqlite3_free(errmsg);
92                         return 1;
93                 }
94                 
95                 Log_Info("SQLite database rebuilt");
96         }
97         else
98         {
99                 // Unknown error
100                 fprintf(stderr, "Bank_Initialise - SQLite Error: %s (rv = %i)\n", errmsg, rv);
101                 sqlite3_free(errmsg);
102                 return 1;
103         }
104
105         return 0;
106 }
107
108 /*
109  * Move Money
110  */
111 int Bank_Transfer(int SourceUser, int DestUser, int Ammount, const char *Reason)
112 {
113         char    *query;
114          int    rv;
115         char    *errmsg;
116         
117         Reason = "";    // Shut GCC up
118         
119         // Begin SQL Transaction
120         sqlite3_exec(gBank_Database, "BEGIN TRANSACTION", NULL, NULL, NULL);
121
122         // Take from the source
123         query = mkstr("UPDATE accounts SET acct_balance=acct_balance-%i,acct_last_seen=datetime('now') WHERE acct_id=%i", Ammount, SourceUser);
124         printf("query = \"%s\"\n", query);
125         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
126         free(query);
127         if( rv != SQLITE_OK )
128         {
129                 fprintf(stderr, "Bank_Transfer - SQLite Error: %s\n", errmsg);
130                 sqlite3_free(errmsg);
131                 sqlite3_exec(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
132                 return 1;
133         }
134
135         // Give to the destination
136         query = mkstr("UPDATE accounts SET acct_balance=acct_balance+%i,acct_last_seen=datetime('now') WHERE acct_id=%i", Ammount, DestUser);
137         printf("query = \"%s\"\n", query);
138         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
139         free(query);
140         if( rv != SQLITE_OK )
141         {
142                 fprintf(stderr, "Bank_Transfer - SQLite Error: %s\n", errmsg);
143                 sqlite3_free(errmsg);
144                 sqlite3_exec(gBank_Database, "ROLLBACK", NULL, NULL, NULL);
145                 return 1;
146         }
147
148         // Commit transaction
149         sqlite3_exec(gBank_Database, "COMMIT", NULL, NULL, NULL);
150
151         return 0;
152 }
153
154 /*
155  * Get user flags
156  */
157 int Bank_GetFlags(int UserID)
158 {
159         sqlite3_stmt    *statement;
160         char    *query;
161          int    ret;
162
163         // Build Query
164         query = mkstr(
165                 "SELECT acct_is_disabled,acct_is_coke,acct_is_admin,acct_is_door,acct_is_internal"
166                 " FROM accounts WHERE acct_id=%i LIMIT 1",
167                 UserID
168                 );
169         statement = Bank_int_QuerySingle(gBank_Database, query);
170         free(query);
171         if( !statement )        return -1;
172
173         // Get Flags
174         ret = 0;
175         // - Disabled
176         if( sqlite3_column_int(statement, 0) )  ret |= USER_FLAG_DISABLED;
177         // - Coke
178         if( sqlite3_column_int(statement, 1) )  ret |= USER_FLAG_COKE;
179         // - Wheel
180         if( sqlite3_column_int(statement, 2) )  ret |= USER_FLAG_ADMIN;
181         // - Door
182         if( sqlite3_column_int(statement, 3) )  ret |= USER_FLAG_DOORGROUP;
183         // - Internal
184         if( sqlite3_column_int(statement, 4) )  ret |= USER_FLAG_INTERNAL;
185         
186         // Destroy and return
187         sqlite3_finalize(statement);
188         
189         return ret;
190 }
191
192 /*
193  * Set user flags
194  */
195 int Bank_SetFlags(int UserID, int Mask, int Value)
196 {
197         char    *query;
198          int    rv;
199         char    *errmsg;
200
201         #define MAP_FLAG(name, flag)    (Mask&(flag)?(Value&(flag)?","name"=1":","name"=0"):"")
202         query = mkstr(
203                 "UPDATE accounts SET acct_id=acct_id%s%s%s%s%s WHERE acct_id=%i",// LIMIT 1",
204                 MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
205                 MAP_FLAG("acct_is_admin", USER_FLAG_ADMIN),
206                 MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
207                 MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
208                 MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED),
209                 UserID
210                 );
211         #undef MAP_FLAG
212
213         // Execute Query
214         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
215         if( rv != SQLITE_OK )
216         {
217                 fprintf(stderr, "Bank_SetUserFlags - SQLite Error: %s\n", errmsg);
218                 fprintf(stderr, "query = '%s'\n", query);
219                 free(query);
220                 sqlite3_free(errmsg);
221                 return -1;
222         }
223         free(query);
224         
225         return 0;
226 }
227
228 /*
229  * Get user balance
230  */
231 int Bank_GetBalance(int AcctID)
232 {
233         sqlite3_stmt    *statement;
234         char    *query;
235          int    ret;
236         
237         query = mkstr("SELECT acct_balance FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
238         statement = Bank_int_QuerySingle(gBank_Database, query);
239         free(query);
240         if( !statement )        return INT_MIN;
241         
242         // Read return value
243         ret = sqlite3_column_int(statement, 0);
244         
245         // Clean up and return
246         sqlite3_finalize(statement);
247         return ret;
248 }
249
250 /*
251  * Get the name of an account
252  */
253 char *Bank_GetAcctName(int AcctID)
254 {
255         sqlite3_stmt    *statement;
256         char    *query;
257         char    *ret;
258         
259         query = mkstr("SELECT acct_name FROM accounts WHERE acct_id=%i LIMIT 1", AcctID);
260         statement = Bank_int_QuerySingle(gBank_Database, query);
261         free(query);
262         if( !statement )        return NULL;
263         
264         // Read return value
265         ret = strdup( (const char*)sqlite3_column_text(statement, 0) );
266         
267         // Clean up and return
268         sqlite3_finalize(statement);
269         return ret;
270 }
271
272 /*
273  * Get an account ID from a name
274  */
275 int Bank_GetAcctByName(const char *Name)
276 {
277         char    *query;
278         sqlite3_stmt    *statement;
279          int    ret;
280         
281         if( !Bank_int_IsValidName(Name) )       return -1;
282         
283         query = mkstr("SELECT acct_id FROM accounts WHERE acct_name='%s' LIMIT 1", Name);
284         statement = Bank_int_QuerySingle(gBank_Database, query);
285         free(query);
286         if( !statement )        return -1;
287         
288         ret = sqlite3_column_int(statement, 0);
289         if( ret == 0 )  return -1;
290         
291         sqlite3_finalize(statement);
292         return ret;
293 }
294
295 /*
296  * Create a new named account
297  */
298 int Bank_CreateAcct(const char *Name)
299 {
300         char    *query;
301         char    *errmsg;
302          int    rv;
303         
304         if( Name )
305         {
306                 if( !Bank_int_IsValidName(Name) )       return -1;
307                 query = mkstr("INSERT INTO accounts (acct_name) VALUES ('%s')", Name);
308         }
309         else
310         {
311                 query = strdup("INSERT INTO accounts (acct_name) VALUES (NULL)");
312         }
313                 
314         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
315         if( rv != SQLITE_OK )
316         {
317                 fprintf(stderr, "Bank_CreateAcct - SQLite Error: '%s'\n", errmsg);
318                 fprintf(stderr, "Query = '%s'\n", query);
319                 sqlite3_free(errmsg);
320                 free(query);
321                 return -1;
322         }
323         
324         free(query);
325         
326         return sqlite3_last_insert_rowid(gBank_Database);
327 }
328
329 /*
330  * Create an iterator for user accounts
331  */
332 tAcctIterator *Bank_Iterator(int FlagMask, int FlagValues, int Flags, int MinMaxBalance, time_t LastSeen)
333 {
334         char    *query;
335         const char      *balanceClause;
336         const char      *lastSeenClause;
337         const char      *orderClause;
338         const char      *revSort;
339         sqlite3_stmt    *ret;
340         
341         // Balance condtion
342         if( Flags & BANK_ITFLAG_MINBALANCE )
343                 balanceClause = " AND acct_balance>=";
344         else if( Flags & BANK_ITFLAG_MAXBALANCE )
345                 balanceClause = " AND acct_balance<=";
346         else {
347                 balanceClause = " AND 1!=";
348                 MinMaxBalance = 0;
349         }
350         
351         // Last seen condition
352         if( Flags & BANK_ITFLAG_SEENAFTER )
353                 lastSeenClause = " AND acct_last_seen>=";
354         else if( Flags & BANK_ITFLAG_SEENBEFORE )
355                 lastSeenClause = " AND acct_last_seen<=";
356         else {
357                 lastSeenClause = " AND datetime(-1,'unixepoch')!=";
358         }
359         
360         // Sorting clause
361         switch( Flags & BANK_ITFLAG_SORTMASK )
362         {
363         case BANK_ITFLAG_SORT_NONE:
364                 orderClause = "";
365                 revSort = "";
366                 break;
367         case BANK_ITFLAG_SORT_NAME:
368                 orderClause = "ORDER BY acct_name";
369                 revSort = " DESC";
370                 break;
371         case BANK_ITFLAG_SORT_BAL:
372                 orderClause = "ORDER BY acct_balance";
373                 revSort = " DESC";
374                 break;
375         case BANK_ITFLAG_SORT_LASTSEEN:
376                 orderClause = "ORDER BY acct_balance";
377                 revSort = " DESC";
378                 break;
379         default:
380                 fprintf(stderr, "BUG: Unknown sort (%x) in SQLite CokeBank\n", Flags & BANK_ITFLAG_SORTMASK);
381                 return NULL;
382         }
383         if( !(Flags & BANK_ITFLAG_REVSORT) )
384                 revSort = "";
385         
386         #define MAP_FLAG(name, flag)    (FlagMask&(flag)?(FlagValues&(flag)?" AND "name"=1":" AND "name"=0"):"")
387         query = mkstr("SELECT acct_id FROM accounts WHERE 1=1"
388                 "%s%s%s%s%s"    // Flags
389                 "%s%i"  // Balance
390                 "%sdatetime(%"PRIu64",'unixepoch')"     // Last seen
391                 "%s%s"  // Sort and direction
392                 ,
393                 MAP_FLAG("acct_is_coke", USER_FLAG_COKE),
394                 MAP_FLAG("acct_is_admin", USER_FLAG_ADMIN),
395                 MAP_FLAG("acct_is_door", USER_FLAG_DOORGROUP),
396                 MAP_FLAG("acct_is_internal", USER_FLAG_INTERNAL),
397                 MAP_FLAG("acct_is_disabled", USER_FLAG_DISABLED),
398                 balanceClause, MinMaxBalance,
399                 lastSeenClause, (uint64_t)LastSeen,
400                 orderClause, revSort
401                 );
402         //printf("query = \"%s\"\n", query);
403         #undef MAP_FLAG
404         
405         ret = Bank_int_MakeStatemnt(gBank_Database, query);
406         if( !ret )      return NULL;
407         
408         free(query);
409         
410         return (void*)ret;
411 }
412
413 /*
414  * Get the next account in an iterator
415  */
416 int Bank_IteratorNext(tAcctIterator *It)
417 {
418          int    rv;
419         rv = sqlite3_step( (sqlite3_stmt*)It );
420         
421         if( rv == SQLITE_DONE ) return -1;
422         if( rv != SQLITE_ROW ) {
423                 fprintf(stderr, "Bank_IteratorNext - SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
424                 return -1;
425         }
426         
427         return sqlite3_column_int( (sqlite3_stmt*)It, 0 );
428 }
429
430 /*
431  * Free an interator
432  */
433 void Bank_DelIterator(tAcctIterator *It)
434 {
435         sqlite3_finalize( (sqlite3_stmt*)It );
436 }
437
438 /*
439  * Check user authentication token
440  */
441 int Bank_GetUserAuth(const char *Salt, const char *Username, const char *Password)
442 {
443         Salt = Password = Username;     // Shut up GCC
444         // DEBUG HACKS!
445         #if 1
446         return Bank_GetAcctByName(Username);
447         #else
448         return -1;
449         #endif
450 }
451
452 /*
453  * Get an account number given a card ID
454  * NOTE: Actually ends up just being an alternate authentication token,
455  *       as no checking is done on the ID's validity, save for SQL sanity.
456  */
457 int Bank_GetAcctByCard(const char *CardID)
458 {
459         char    *query;
460         sqlite3_stmt    *statement;
461          int    ret;
462         
463         if( !Bank_int_IsValidName(CardID) )
464                 return -1;
465         
466         query = mkstr("SELECT acct_id FROM cards WHERE card_name='%s' LIMIT 1", CardID);
467         statement = Bank_int_QuerySingle(gBank_Database, query);
468         free(query);
469         if( !statement )        return -1;
470         
471         ret = sqlite3_column_int(statement, 0);
472         
473         sqlite3_finalize(statement);
474         
475         return ret;
476 }
477
478 /*
479  * Add a card to an account
480  */
481 int Bank_AddAcctCard(int AcctID, const char *CardID)
482 {
483         char    *query;
484          int    rv;
485         char    *errmsg;
486         
487         if( !Bank_int_IsValidName(CardID) )
488                 return -1;
489         
490         // TODO: Check the AcctID too
491         
492         // Insert card
493         query = mkstr("INSERT INTO cards (acct_id,card_name) VALUES (%i,'%s')",
494                 AcctID, CardID);
495         rv = sqlite3_exec(gBank_Database, query, NULL, NULL, &errmsg);
496         if( rv == SQLITE_CONSTRAINT )
497         {
498                 sqlite3_free(errmsg);
499                 free(query);
500                 return 2;       // Card in use
501         }
502         if( rv != SQLITE_OK )
503         {
504                 fprintf(stderr, "Bank_AddAcctCard - SQLite Error: '%s'\n", errmsg);
505                 fprintf(stderr, "Query = '%s'\n", query);
506                 sqlite3_free(errmsg);
507                 free(query);
508                 return -1;
509         }
510         free(query);
511         
512         return 0;
513 }
514
515 /*
516  * Create a SQLite Statement
517  */
518 sqlite3_stmt *Bank_int_MakeStatemnt(sqlite3 *Database, const char *Query)
519 {
520          int    rv;
521         sqlite3_stmt    *ret;
522         rv = sqlite3_prepare_v2(Database, Query, strlen(Query)+1, &ret, NULL);
523         if( rv != SQLITE_OK ) {
524                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(Database));
525                 fprintf(stderr, "query = \"%s\"\n", Query);
526                 return NULL;
527         }
528         
529         return ret;
530 }
531
532 /*
533  * Create a SQLite statement and query it for the first row
534  * Returns NULL if the the set is empty
535  */
536 sqlite3_stmt *Bank_int_QuerySingle(sqlite3 *Database, const char *Query)
537 {
538         sqlite3_stmt    *ret;
539          int    rv;
540         
541         // Prepare query
542         ret = Bank_int_MakeStatemnt(Database, Query);
543         if( !ret )      return NULL;
544         
545         // Get row
546         rv = sqlite3_step(ret);
547         // - Empty result set
548         if( rv == SQLITE_DONE ) return NULL;
549         // - Other error
550         if( rv != SQLITE_ROW ) {
551                 fprintf(stderr, "SQLite Error: %s\n", sqlite3_errmsg(gBank_Database));
552                 fprintf(stderr, "query = \"%s\"\n", Query);
553                 return NULL;
554         }
555         
556         return ret;
557 }
558
559 /**
560  * \brief Checks if the passed account name is valid
561  */
562 int Bank_int_IsValidName(const char *Name)
563 {
564         while(*Name)
565         {
566                 if( *Name == '\'' )     return 0;
567                 Name ++;
568         }
569         return 1;
570 }

UCC git Repository :: git.ucc.asn.au