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

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