Implemented `dispense acct <name> =`
[tpg/opendispense2.git] / src / client / main.c
1 /*
2  * OpenDispense 2 
3  * UCC (University [of WA] Computer Club) Electronic Accounting System
4  * - Dispense Client
5  *
6  * main.c - Core and Initialisation
7  *
8  * This file is licenced under the 3-clause BSD Licence. See the file
9  * COPYING for full details.
10  */
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <ctype.h>      // isspace
15 #include <stdarg.h>
16 #include <regex.h>
17 #include <ncurses.h>
18 #include <limits.h>
19
20 #include <unistd.h>     // close
21 #include <netdb.h>      // gethostbyname
22 #include <pwd.h>        // getpwuids
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 #include <openssl/sha.h>        // SHA1
27
28 #define USE_NCURSES_INTERFACE   0
29 #define DEBUG_TRACE_SERVER      0
30
31 // === TYPES ===
32 typedef struct sItem {
33         char    *Type;
34          int    ID;
35         char    *Desc;
36          int    Price;
37 }       tItem;
38
39 // === PROTOTYPES ===
40  int    main(int argc, char *argv[]);
41 void    ShowUsage(void);
42 // --- GUI ---
43  int    ShowNCursesUI(void);
44 void    ShowItemAt(int Row, int Col, int Width, int Index);
45 void    PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
46 // --- Coke Server Communication ---
47  int    OpenConnection(const char *Host, int Port);
48  int    Authenticate(int Socket);
49 void    PopulateItemList(int Socket);
50  int    DispenseItem(int Socket, int ItemID);
51  int    Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
52  int    Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason);
53  int    Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason);
54  int    Dispense_Donate(int Socket, int Ammount, const char *Reason);
55  int    Dispense_EnumUsers(int Socket);
56  int    Dispense_ShowUser(int Socket, const char *Username);
57 void    _PrintUserLine(const char *Line);
58  int    Dispense_AddUser(int Socket, const char *Username);
59  int    Dispense_SetUserType(int Socket, const char *Username, const char *TypeString);
60 // --- Helpers ---
61 char    *ReadLine(int Socket);
62  int    sendf(int Socket, const char *Format, ...);
63 char    *trim(char *string);
64  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
65 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
66
67 // === GLOBALS ===
68 char    *gsDispenseServer = "localhost";
69  int    giDispensePort = 11020;
70
71 tItem   *gaItems;
72  int    giNumItems;
73 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex;
74  int    gbIsAuthenticated = 0;
75
76 char    *gsItemPattern; //!< Item pattern
77 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
78  int    gbUseNCurses = 0;       //!< '-G' Use the NCurses GUI?
79  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
80  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
81
82 // === CODE ===
83 int main(int argc, char *argv[])
84 {
85          int    sock;
86          int    i;
87         char    buffer[BUFSIZ];
88         
89         // -- Create regular expressions
90         // > Code Type Count ...
91         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
92         // > Code Type Ident Price Desc
93         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z]+):([0-9]+)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
94         // > Code 'SALT' salt
95         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
96         // > Code 'User' Username Balance Flags
97         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
98
99         // Parse Arguments
100         for( i = 1; i < argc; i ++ )
101         {
102                 char    *arg = argv[i];
103                 
104                 if( arg[0] == '-' )
105                 {                       
106                         switch(arg[1])
107                         {
108                         case 'h':
109                         case '?':
110                                 ShowUsage();
111                                 return 0;
112                         
113                         case 'm':       // Minimum balance
114                                 giMinimumBalance = atoi(argv[++i]);
115                                 break;
116                         case 'M':       // Maximum balance
117                                 giMaximumBalance = atoi(argv[++i]);
118                                 break;
119                         
120                         case 'u':       // Override User
121                                 gsEffectiveUser = argv[++i];
122                                 break;
123                         
124                         case 'G':       // Use GUI
125                                 gbUseNCurses = 1;
126                                 break;
127                         }
128
129                         continue;
130                 }
131                 
132                 //
133                 // `dispense acct`
134                 // - 
135                 if( strcmp(arg, "acct") == 0 )
136                 {
137                         // Connect to server
138                         sock = OpenConnection(gsDispenseServer, giDispensePort);
139                         if( sock < 0 )  return -1;
140
141                         // List accounts?
142                         if( i + 1 == argc ) {
143                                 Dispense_EnumUsers(sock);
144                                 return 0;
145                         }
146                         
147                         // argv[i+1]: Username
148                         
149                         // Alter account?
150                         if( i + 2 < argc )
151                         {
152                                 if( i + 3 >= argc ) {
153                                         fprintf(stderr, "Error: `dispense acct' needs a reason\n");
154                                         exit(1);
155                                 }
156                                 
157                                 // Authentication required
158                                 if( Authenticate(sock) )
159                                         return -1;
160                                 
161                                 // argv[i+1]: Username
162                                 // argv[i+2]: Ammount
163                                 // argv[i+3]: Reason
164                                 
165                                 if( argv[i+2][0] == '=' ) {
166                                         // Set balance
167                                         if( argv[i+2][1] != '0' && atoi(&argv[i+2][1]) == 0 ) {
168                                                 fprintf(stderr, "Error: Invalid balance to be set\n");
169                                                 exit(1);
170                                         }
171                                         
172                                         Dispense_SetBalance(sock, argv[i+1], atoi(argv[i+2]+1), argv[i+3]);
173                                 }
174                                 else {
175                                         // Alter balance
176                                         Dispense_AlterBalance(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]);
177                                 }
178                         }
179                         
180                         // Show user information
181                         Dispense_ShowUser(sock, argv[i+1]);
182                         
183                         close(sock);
184                         return 0;
185                 }
186                 //
187                 // `dispense give`
188                 // - "Here, have some money."
189                 if( strcmp(arg, "give") == 0 )
190                 {
191                         if( i + 3 >= argc ) {
192                                 fprintf(stderr, "`dispense give` takes three arguments\n");
193                                 ShowUsage();
194                                 return -1;
195                         }
196                         // TODO: `dispense give`
197                         
198                         // argv[i+1]: Destination
199                         // argv[i+2]: Ammount
200                         // argv[i+3]: Reason
201                         
202                         // Connect to server
203                         sock = OpenConnection(gsDispenseServer, giDispensePort);
204                         if( sock < 0 )  return -1;
205                         
206                         // Authenticate
207                         if( Authenticate(sock) )
208                                 return -1;
209                         
210                         Dispense_Give(sock, argv[i+1], atoi(argv[i+2]), argv[i+3]);
211                         return 0;
212                 }
213                 // 
214                 // `dispense user`
215                 // - User administration (Admin Only)
216                 if( strcmp(arg, "user") == 0 )
217                 {
218                         // Check argument count
219                         if( i + 1 >= argc ) {
220                                 fprintf(stderr, "Error: `dispense user` requires arguments\n");
221                                 ShowUsage();
222                                 exit(1);
223                         }
224                         
225                         // Connect to server
226                         sock = OpenConnection(gsDispenseServer, giDispensePort);
227                         if( sock < 0 )  return -1;
228                         
229                         // Attempt authentication
230                         if( Authenticate(sock) )
231                                 return -1;
232                         
233                         // Add new user?
234                         if( strcmp(argv[i+1], "add") == 0 )
235                         {
236                                 if( i + 2 >= argc ) {
237                                         fprintf(stderr, "Error: `dispense user add` requires an argument\n");
238                                         ShowUsage();
239                                         exit(1);
240                                 }
241                                 
242                                 Dispense_AddUser(sock, argv[i+2]);
243                         }
244                         // Update a user
245                         else if( strcmp(argv[i+1], "type") == 0 )
246                         {
247                                 if( i + 3 >= argc ) {
248                                         fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
249                                         ShowUsage();
250                                         exit(1);
251                                 }
252                                 
253                                 Dispense_SetUserType(sock, argv[i+2], argv[i+3]);
254                         }
255                         else
256                         {
257                                 fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
258                                 ShowUsage();
259                                 exit(1);
260                         }
261                         return 0;
262                 }
263                 
264                 // Donation!
265                 if( strcmp(arg, "donate") == 0 )
266                 {
267                         // Check argument count
268                         if( i + 2 >= argc ) {
269                                 fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
270                                 ShowUsage();
271                                 exit(1);
272                         }
273                         
274                         // Connect to server
275                         sock = OpenConnection(gsDispenseServer, giDispensePort);
276                         if( sock < 0 )  return -1;
277                         
278                         // Attempt authentication
279                         if( Authenticate(sock) )
280                                 return -1;
281                         
282                         // Do donation
283                         Dispense_Donate(sock, atoi(argv[i+1]), argv[i+1]);
284                         
285                         return 0;
286                 }
287                 
288                 else {
289                         // Item name / pattern
290                         gsItemPattern = arg;
291                         break;
292                 }
293         }
294         
295         // Connect to server
296         sock = OpenConnection(gsDispenseServer, giDispensePort);
297         if( sock < 0 )  return -1;
298         
299         // Authenticate
300         Authenticate(sock);
301
302         // Get items
303         PopulateItemList(sock);
304         
305         if( gsItemPattern )
306         {
307                 // TODO: Implement `dispense <name>`
308                 printf("TODO: Implement `dispense <name>`\n");
309                 i = -1;
310         }
311         else if( gbUseNCurses )
312         {
313                 i = ShowNCursesUI();
314         }
315         else
316         {
317                 // Very basic dispense interface
318                 for( i = 0; i < giNumItems; i ++ ) {            
319                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
320                                 gaItems[i].Price, gaItems[i].Desc);
321                 }
322                 printf(" q Quit\n");
323                 for(;;)
324                 {
325                         char    *buf;
326                         
327                         i = -1;
328                         
329                         fgets(buffer, BUFSIZ, stdin);
330                         
331                         buf = trim(buffer);
332                         
333                         if( buf[0] == 'q' )     break;
334                         
335                         i = atoi(buf);
336                         
337                         if( i != 0 || buf[0] == '0' )
338                         {
339                                 if( i < 0 || i >= giNumItems ) {
340                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
341                                         continue;
342                                 }
343                                 break;
344                         }
345                 }
346         }
347         
348         // Check for a valid item ID
349         if( i >= 0 )
350                 DispenseItem(sock, i);
351
352         close(sock);
353
354         return 0;
355 }
356
357 void ShowUsage(void)
358 {
359         printf(
360                 "Usage:\n"
361                 "  == Everyone ==\n"
362                 "    dispense\n"
363                 "        Show interactive list\n"
364                 "    dispense <item>\n"
365                 "        Dispense named item\n"
366                 "    dispense give <user> <ammount> \"<reason>\"\n"
367                 "        Give money to another user\n"
368                 "    dispense donate <ammount> \"<reason>\"\n"
369                 "        Donate to the club\n"
370                 "  == Coke members == \n"
371                 "    dispense acct [<user>]\n"
372                 "        Show user balances\n"
373                 "    dispense acct <user> [+-]<ammount> \"<reason>\"\n"
374                 "        Alter a account value\n"
375                 "  == Dispense administrators ==\n"
376                 "    dispense acct <user> =<ammount> \"<reason>\"\n"
377                 "        Set an account balance\n"
378                 "    dispense user add <user>\n"
379                 "        Create new coke account (Admins only)\n"
380                 "    dispense user type <user> <flags>\n"
381                 "        Alter a user's flags\n"
382                 "        <flags> is a comma-separated list of user, coke, admin or disabled\n"
383                 "        Flags are removed by preceding the name with '-' or '!'\n"
384                 "\n"
385                 "General Options:\n"
386                 "    -u <username>\n"
387                 "        Set a different user (Coke members only)\n"
388                 "    -h / -?\n"
389                 "        Show help text\n"
390                 "    -G\n"
391                 "        Use alternate GUI\n"
392                 "    -m <min balance>\n"
393                 "    -M <max balance>\n"
394                 "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
395                 );
396 }
397
398 // -------------------
399 // --- NCurses GUI ---
400 // -------------------
401 /**
402  * \brief Render the NCurses UI
403  */
404 int ShowNCursesUI(void)
405 {
406         // TODO: ncurses interface (with separation between item classes)
407         // - Hmm... that would require standardising the item ID to be <class>:<index>
408         // Oh, why not :)
409          int    ch;
410          int    i, times;
411          int    xBase, yBase;
412         const int       displayMinWidth = 40;
413         const int       displayMinItems = 8;
414         char    *titleString = "Dispense";
415          int    itemCount = displayMinItems;
416          int    itemBase = 0;
417          int    currentItem = 0;
418          int    ret = -2;       // -2: Used for marking "no return yet"
419          
420          int    height, width;
421          
422         // Enter curses mode
423         initscr();
424         raw(); noecho();
425         
426         // Get item count
427         // - 6: randomly chosen (Need at least 3)
428         itemCount = LINES - 6;
429         if( itemCount > giNumItems )
430                 itemCount = giNumItems;
431         
432         // Get dimensions
433         height = itemCount + 3;
434         width = displayMinWidth;
435         
436         // Get positions
437         xBase = COLS/2 - width/2;
438         yBase = LINES/2 - height/2;
439         
440         for( ;; )
441         {
442                 // Header
443                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
444                 
445                 // Items
446                 for( i = 0; i < itemCount; i ++ )
447                 {
448                         move( yBase + 1 + i, xBase );
449                         
450                         if( currentItem == itemBase + i ) {
451                                 printw("| -> ");
452                         }
453                         else {
454                                 printw("|    ");
455                         }
456                         
457                         // Check for ... row
458                         // - Oh god, magic numbers!
459                         if( i == 0 && itemBase > 0 ) {
460                                 printw("   ...");
461                                 times = width-1 - 8 - 3;
462                                 while(times--)  addch(' ');
463                         }
464                         else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
465                                 printw("   ...");
466                                 times = width-1 - 8 - 3;
467                                 while(times--)  addch(' ');
468                         }
469                         // Show an item
470                         else {
471                                 ShowItemAt( yBase + 1 + i, xBase + 5, width - 7, itemBase + i);
472                                 addch(' ');
473                         }
474                         
475                         // Scrollbar (if needed)
476                         if( giNumItems > itemCount ) {
477                                 if( i == 0 ) {
478                                         addch('A');
479                                 }
480                                 else if( i == itemCount - 1 ) {
481                                         addch('V');
482                                 }
483                                 else {
484                                          int    percentage = itemBase * 100 / (giNumItems-itemCount);
485                                         if( i-1 == percentage*(itemCount-3)/100 ) {
486                                                 addch('#');
487                                         }
488                                         else {
489                                                 addch('|');
490                                         }
491                                 }
492                         }
493                         else {
494                                 addch('|');
495                         }
496                 }
497                 
498                 // Footer
499                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
500                 
501                 // Get input
502                 ch = getch();
503                 
504                 if( ch == '\x1B' ) {
505                         ch = getch();
506                         if( ch == '[' ) {
507                                 ch = getch();
508                                 
509                                 switch(ch)
510                                 {
511                                 case 'B':
512                                         //if( itemBase < giNumItems - (itemCount) )
513                                         //      itemBase ++;
514                                         if( currentItem < giNumItems - 1 )
515                                                 currentItem ++;
516                                         if( itemBase + itemCount - 1 <= currentItem && itemBase + itemCount < giNumItems )
517                                                 itemBase ++;
518                                         break;
519                                 case 'A':
520                                         //if( itemBase > 0 )
521                                         //      itemBase --;
522                                         if( currentItem > 0 )
523                                                 currentItem --;
524                                         if( itemBase + 1 > currentItem && itemBase > 0 )
525                                                 itemBase --;
526                                         break;
527                                 }
528                         }
529                         else {
530                                 
531                         }
532                 }
533                 else {
534                         switch(ch)
535                         {
536                         case '\n':
537                                 ret = currentItem;
538                                 break;
539                         case 'q':
540                                 ret = -1;       // -1: Return with no dispense
541                                 break;
542                         }
543                         
544                         // Check if the return value was changed
545                         if( ret != -2 ) break;
546                 }
547                 
548         }
549         
550         
551         // Leave
552         endwin();
553         return ret;
554 }
555
556 /**
557  * \brief Show item \a Index at (\a Col, \a Row)
558  * \note Part of the NCurses UI
559  */
560 void ShowItemAt(int Row, int Col, int Width, int Index)
561 {
562          int    _x, _y, times;
563         char    *name;
564          int    price;
565         
566         move( Row, Col );
567         
568         if( Index < 0 || Index >= giNumItems ) {
569                 name = "OOR";
570                 price = 0;
571         }
572         else {
573                 name = gaItems[Index].Desc;
574                 price = gaItems[Index].Price;
575         }
576
577         printw("%02i %s", Index, name);
578         
579         getyx(stdscr, _y, _x);
580         // Assumes max 4 digit prices
581         times = Width - 4 - (_x - Col); // TODO: Better handling for large prices
582         while(times--)  addch(' ');
583         printw("%4i", price);
584 }
585
586 /**
587  * \brief Print a three-part string at the specified position (formatted)
588  * \note NCurses UI Helper
589  * 
590  * Prints \a Left on the left of the area, \a Right on the righthand side
591  * and \a Mid in the middle of the area. These are padded with \a Pad1
592  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
593  * 
594  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
595  * and the arguments to these are read in that order.
596  */
597 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
598         const char *Mid, char Pad2, const char *Right, ...)
599 {
600          int    lLen, mLen, rLen;
601          int    times;
602         
603         va_list args;
604         
605         // Get the length of the strings
606         va_start(args, Right);
607         lLen = vsnprintf(NULL, 0, Left, args);
608         mLen = vsnprintf(NULL, 0, Mid, args);
609         rLen = vsnprintf(NULL, 0, Right, args);
610         va_end(args);
611         
612         // Sanity check
613         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
614                 return ;        // TODO: What to do?
615         }
616         
617         move(Row, Col);
618         
619         // Render strings
620         va_start(args, Right);
621         // - Left
622         {
623                 char    tmp[lLen+1];
624                 vsnprintf(tmp, lLen+1, Left, args);
625                 addstr(tmp);
626         }
627         // - Left padding
628         times = Width/2 - mLen/2 - lLen;
629         while(times--)  addch(Pad1);
630         // - Middle
631         {
632                 char    tmp[mLen+1];
633                 vsnprintf(tmp, mLen+1, Mid, args);
634                 addstr(tmp);
635         }
636         // - Right Padding
637         times = Width/2 - mLen/2 - rLen;
638         while(times--)  addch(Pad2);
639         // - Right
640         {
641                 char    tmp[rLen+1];
642                 vsnprintf(tmp, rLen+1, Right, args);
643                 addstr(tmp);
644         }
645 }
646
647 // ---------------------
648 // --- Coke Protocol ---
649 // ---------------------
650 int OpenConnection(const char *Host, int Port)
651 {
652         struct hostent  *host;
653         struct sockaddr_in      serverAddr;
654          int    sock;
655         
656         host = gethostbyname(Host);
657         if( !host ) {
658                 fprintf(stderr, "Unable to look up '%s'\n", Host);
659                 return -1;
660         }
661         
662         memset(&serverAddr, 0, sizeof(serverAddr));
663         
664         serverAddr.sin_family = AF_INET;        // IPv4
665         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
666         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
667         serverAddr.sin_port = htons(Port);
668         
669         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
670         if( sock < 0 ) {
671                 fprintf(stderr, "Failed to create socket\n");
672                 return -1;
673         }
674         
675         #if USE_AUTOAUTH
676         {
677                 struct sockaddr_in      localAddr;
678                 memset(&localAddr, 0, sizeof(localAddr));
679                 localAddr.sin_family = AF_INET; // IPv4
680                 localAddr.sin_port = 1023;      // IPv4
681                 // Attempt to bind to low port for autoauth
682                 bind(sock, &localAddr, sizeof(localAddr));
683         }
684         #endif
685         
686         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
687                 fprintf(stderr, "Failed to connect to server\n");
688                 return -1;
689         }
690         
691         return sock;
692 }
693
694 /**
695  * \brief Authenticate with the server
696  * \return Boolean Failure
697  */
698 int Authenticate(int Socket)
699 {
700         struct passwd   *pwd;
701         char    *buf;
702          int    responseCode;
703         char    salt[32];
704          int    i;
705         regmatch_t      matches[4];
706         
707         if( gbIsAuthenticated ) return 0;
708         
709         // Get user name
710         pwd = getpwuid( getuid() );
711         
712         // Attempt automatic authentication
713         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
714         
715         // Check if it worked
716         buf = ReadLine(Socket);
717         
718         responseCode = atoi(buf);
719         switch( responseCode )
720         {
721         case 200:       // Autoauth succeeded, return
722                 free(buf);
723                 break;
724         
725         case 401:       // Untrusted, attempt password authentication
726                 free(buf);
727                 
728                 sendf(Socket, "USER %s\n", pwd->pw_name);
729                 printf("Using username %s\n", pwd->pw_name);
730                 
731                 buf = ReadLine(Socket);
732                 
733                 // TODO: Get Salt
734                 // Expected format: 100 SALT <something> ...
735                 // OR             : 100 User Set
736                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
737                 responseCode = atoi(buf);
738                 if( responseCode != 100 ) {
739                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
740                         free(buf);
741                         return -1;      // ERROR
742                 }
743                 
744                 // Check for salt
745                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
746                         // Store it for later
747                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
748                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
749                 }
750                 free(buf);
751                 
752                 // Give three attempts
753                 for( i = 0; i < 3; i ++ )
754                 {
755                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
756                         char    tmpBuf[42];
757                         char    tmp[ofs+20];
758                         char    *pass = getpass("Password: ");
759                         uint8_t h[20];
760                         
761                         // Create hash string
762                         // <username><salt><hash>
763                         strcpy(tmp, pwd->pw_name);
764                         strcat(tmp, salt);
765                         SHA1( (unsigned char*)pass, strlen(pass), h );
766                         memcpy(tmp+ofs, h, 20);
767                         
768                         // Hash all that
769                         SHA1( (unsigned char*)tmp, ofs+20, h );
770                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
771                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
772                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
773                                 );
774                 
775                         // Send password
776                         sendf(Socket, "PASS %s\n", tmpBuf);
777                         buf = ReadLine(Socket);
778                 
779                         responseCode = atoi(buf);
780                         // Auth OK?
781                         if( responseCode == 200 )       break;
782                         // Bad username/password
783                         if( responseCode == 401 )       continue;
784                         
785                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
786                         free(buf);
787                         return -1;
788                 }
789                 free(buf);
790                 if( i == 3 )
791                         return 2;       // 2 = Bad Password
792                 break;
793         
794         case 404:       // Bad Username
795                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
796                 free(buf);
797                 return 1;
798         
799         default:
800                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
801                 printf("%s\n", buf);
802                 free(buf);
803                 return -1;
804         }
805         
806         // Set effective user
807         if( gsEffectiveUser ) {
808                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
809                 
810                 buf = ReadLine(Socket);
811                 responseCode = atoi(buf);
812                 
813                 switch(responseCode)
814                 {
815                 case 200:
816                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
817                         break;
818                 
819                 case 403:
820                         printf("Only coke members can use `dispense -u`\n");
821                         free(buf);
822                         return -1;
823                 
824                 case 404:
825                         printf("Invalid user selected\n");
826                         free(buf);
827                         return -1;
828                 
829                 default:
830                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
831                         printf("%s\n", buf);
832                         free(buf);
833                         exit(-1);
834                 }
835                 
836                 free(buf);
837         }
838         
839         gbIsAuthenticated = 1;
840         
841         return 0;
842 }
843
844
845 /**
846  * \brief Fill the item information structure
847  * \return Boolean Failure
848  */
849 void PopulateItemList(int Socket)
850 {
851         char    *buf;
852          int    responseCode;
853         
854         char    *itemType, *itemStart;
855          int    count, i;
856         regmatch_t      matches[4];
857         
858         // Ask server for stock list
859         send(Socket, "ENUM_ITEMS\n", 11, 0);
860         buf = ReadLine(Socket);
861         
862         //printf("Output: %s\n", buf);
863         
864         responseCode = atoi(buf);
865         if( responseCode != 201 ) {
866                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
867                 exit(-1);
868         }
869         
870         // - Get item list -
871         
872         // Expected format:
873         //  201 Items <count>
874         //  202 Item <count>
875         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
876                 
877         itemType = &buf[ matches[2].rm_so ];    buf[ matches[2].rm_eo ] = '\0';
878         count = atoi( &buf[ matches[3].rm_so ] );
879                 
880         // Check array type
881         if( strcmp(itemType, "Items") != 0 ) {
882                 // What the?!
883                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
884                         itemType);
885                 exit(-1);
886         }
887                 
888         itemStart = &buf[ matches[3].rm_eo ];
889         
890         free(buf);
891         
892         giNumItems = count;
893         gaItems = malloc( giNumItems * sizeof(tItem) );
894         
895         // Fetch item information
896         for( i = 0; i < giNumItems; i ++ )
897         {
898                 regmatch_t      matches[7];
899                 
900                 // Get item info
901                 buf = ReadLine(Socket);
902                 responseCode = atoi(buf);
903                 
904                 if( responseCode != 202 ) {
905                         fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
906                         exit(-1);
907                 }
908                 
909                 RunRegex(&gItemRegex, buf, 7, matches, "Malformed server response");
910                 
911                 buf[ matches[3].rm_eo ] = '\0';
912                 
913                 gaItems[i].Type = strdup( buf + matches[3].rm_so );
914                 gaItems[i].ID = atoi( buf + matches[4].rm_so );
915                 gaItems[i].Price = atoi( buf + matches[5].rm_so );
916                 gaItems[i].Desc = strdup( buf + matches[6].rm_so );
917                 
918                 free(buf);
919         }
920         
921         // Read end of list
922         buf = ReadLine(Socket);
923         responseCode = atoi(buf);
924                 
925         if( responseCode != 200 ) {
926                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
927                         responseCode, buf
928                         );
929                 exit(-1);
930         }
931         
932         free(buf);
933 }
934
935 /**
936  * \brief Dispense an item
937  * \return Boolean Failure
938  */
939 int DispenseItem(int Socket, int ItemID)
940 {
941          int    ret, responseCode;
942         char    *buf;
943         
944         if( ItemID < 0 || ItemID > giNumItems ) return -1;
945         
946         // Dispense!
947         sendf(Socket, "DISPENSE %s:%i\n", gaItems[ItemID].Type, gaItems[ItemID].ID);
948         buf = ReadLine(Socket);
949         
950         responseCode = atoi(buf);
951         switch( responseCode )
952         {
953         case 200:
954                 printf("Dispense OK\n");
955                 ret = 0;
956                 break;
957         case 401:
958                 printf("Not authenticated\n");
959                 ret = 1;
960                 break;
961         case 402:
962                 printf("Insufficient balance\n");
963                 ret = 1;
964                 break;
965         case 406:
966                 printf("Bad item name, bug report\n");
967                 ret = 1;
968                 break;
969         case 500:
970                 printf("Item failed to dispense, is the slot empty?\n");
971                 ret = 1;
972                 break;
973         case 501:
974                 printf("Dispense not possible (slot empty/permissions)\n");
975                 ret = 1;
976                 break;
977         default:
978                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
979                 ret = -2;
980                 break;
981         }
982         
983         free(buf);
984         return ret;
985 }
986
987 /**
988  * \brief Alter a user's balance
989  */
990 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
991 {
992         char    *buf;
993          int    responseCode;
994         
995         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
996         buf = ReadLine(Socket);
997         
998         responseCode = atoi(buf);
999         free(buf);
1000         
1001         switch(responseCode)
1002         {
1003         case 200:       return 0;       // OK
1004         case 402:
1005                 fprintf(stderr, "Insufficient balance\n");
1006                 return 1;
1007         case 403:       // Not in coke
1008                 fprintf(stderr, "You are not in coke (sucker)\n");
1009                 return 1;
1010         case 404:       // Unknown user
1011                 fprintf(stderr, "Unknown user '%s'\n", Username);
1012                 return 2;
1013         default:
1014                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1015                 return -1;
1016         }
1017         
1018         return -1;
1019 }
1020
1021 /**
1022  * \brief Set a user's balance
1023  * \note Only avaliable to dispense admins
1024  */
1025 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1026 {
1027         char    *buf;
1028          int    responseCode;
1029         
1030         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1031         buf = ReadLine(Socket);
1032         
1033         responseCode = atoi(buf);
1034         free(buf);
1035         
1036         switch(responseCode)
1037         {
1038         case 200:       return 0;       // OK
1039         case 403:       // Not in coke
1040                 fprintf(stderr, "You are not an admin\n");
1041                 return 1;
1042         case 404:       // Unknown user
1043                 fprintf(stderr, "Unknown user '%s'\n", Username);
1044                 return 2;
1045         default:
1046                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1047                 return -1;
1048         }
1049         
1050         return -1;
1051 }
1052
1053 /**
1054  * \brief Give money to another user
1055  */
1056 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1057 {
1058         char    *buf;
1059          int    responseCode;
1060         
1061         if( Ammount < 0 ) {
1062                 printf("Sorry, you can only give, you can't take.\n");
1063                 return -1;
1064         }
1065         
1066         // Fast return on zero
1067         if( Ammount == 0 ) {
1068                 printf("Are you actually going to give any?\n");
1069                 return 0;
1070         }
1071         
1072         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1073         buf = ReadLine(Socket);
1074         
1075         responseCode = atoi(buf);
1076         free(buf);
1077         
1078         switch(responseCode)
1079         {
1080         case 200:       return 0;       // OK
1081         
1082         case 402:       
1083                 fprintf(stderr, "Insufficient balance\n");
1084                 return 1;
1085         
1086         case 404:       // Unknown user
1087                 fprintf(stderr, "Unknown user '%s'\n", Username);
1088                 return 2;
1089         
1090         default:
1091                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1092                 return -1;
1093         }
1094         
1095         return -1;
1096 }
1097
1098
1099 /**
1100  * \brief Donate money to the club
1101  */
1102 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
1103 {
1104         char    *buf;
1105          int    responseCode;
1106         
1107         if( Ammount < 0 ) {
1108                 printf("Sorry, you can only give, you can't take.\n");
1109                 return -1;
1110         }
1111         
1112         // Fast return on zero
1113         if( Ammount == 0 ) {
1114                 printf("Are you actually going to give any?\n");
1115                 return 0;
1116         }
1117         
1118         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
1119         buf = ReadLine(Socket);
1120         
1121         responseCode = atoi(buf);
1122         free(buf);
1123         
1124         switch(responseCode)
1125         {
1126         case 200:       return 0;       // OK
1127         
1128         case 402:       
1129                 fprintf(stderr, "Insufficient balance\n");
1130                 return 1;
1131         
1132         default:
1133                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1134                 return -1;
1135         }
1136         
1137         return -1;
1138 }
1139
1140 /**
1141  * \brief Enumerate users
1142  */
1143 int Dispense_EnumUsers(int Socket)
1144 {
1145         char    *buf;
1146          int    responseCode;
1147          int    nUsers;
1148         regmatch_t      matches[4];
1149         
1150         if( giMinimumBalance != INT_MIN ) {
1151                 if( giMaximumBalance != INT_MAX ) {
1152                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
1153                 }
1154                 else {
1155                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
1156                 }
1157         }
1158         else {
1159                 if( giMaximumBalance != INT_MAX ) {
1160                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
1161                 }
1162                 else {
1163                         sendf(Socket, "ENUM_USERS\n");
1164                 }
1165         }
1166         buf = ReadLine(Socket);
1167         responseCode = atoi(buf);
1168         
1169         switch(responseCode)
1170         {
1171         case 201:       break;  // Ok, length follows
1172         
1173         default:
1174                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1175                 free(buf);
1176                 return -1;
1177         }
1178         
1179         // Get count (not actually used)
1180         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1181         nUsers = atoi( buf + matches[3].rm_so );
1182         printf("%i users returned\n", nUsers);
1183         
1184         // Free string
1185         free(buf);
1186         
1187         // Read returned users
1188         do {
1189                 buf = ReadLine(Socket);
1190                 responseCode = atoi(buf);
1191                 
1192                 if( responseCode != 202 )       break;
1193                 
1194                 _PrintUserLine(buf);
1195                 free(buf);
1196         } while(responseCode == 202);
1197         
1198         // Check final response
1199         if( responseCode != 200 ) {
1200                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1201                 free(buf);
1202                 return -1;
1203         }
1204         
1205         free(buf);
1206         
1207         return 0;
1208 }
1209
1210 int Dispense_ShowUser(int Socket, const char *Username)
1211 {
1212         char    *buf;
1213          int    responseCode, ret;
1214         
1215         sendf(Socket, "USER_INFO %s\n", Username);
1216         buf = ReadLine(Socket);
1217         
1218         responseCode = atoi(buf);
1219         
1220         switch(responseCode)
1221         {
1222         case 202:
1223                 _PrintUserLine(buf);
1224                 ret = 0;
1225                 break;
1226         
1227         case 404:
1228                 printf("Unknown user '%s'\n", Username);
1229                 ret = 1;
1230                 break;
1231         
1232         default:
1233                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1234                 ret = -1;
1235                 break;
1236         }
1237         
1238         free(buf);
1239         
1240         return ret;
1241 }
1242
1243 void _PrintUserLine(const char *Line)
1244 {
1245         regmatch_t      matches[6];
1246          int    bal;
1247         
1248         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
1249         // 3: Username
1250         // 4: Balance
1251         // 5: Flags
1252         {
1253                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
1254                 char    username[usernameLen + 1];
1255                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
1256                 char    flags[flagsLen + 1];
1257                 
1258                 memcpy(username, Line + matches[3].rm_so, usernameLen);
1259                 username[usernameLen] = '\0';
1260                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
1261                 flags[flagsLen] = '\0';
1262                 
1263                 bal = atoi(Line + matches[4].rm_so);
1264                 printf("%-15s: $%4i.%02i (%s)\n", username, bal/100, abs(bal)%100, flags);
1265         }
1266 }
1267
1268 int Dispense_AddUser(int Socket, const char *Username)
1269 {
1270         char    *buf;
1271          int    responseCode, ret;
1272         
1273         sendf(Socket, "USER_ADD %s\n", Username);
1274         
1275         buf = ReadLine(Socket);
1276         responseCode = atoi(buf);
1277         
1278         switch(responseCode)
1279         {
1280         case 200:
1281                 printf("User '%s' added\n", Username);
1282                 ret = 0;
1283                 break;
1284                 
1285         case 403:
1286                 printf("Only wheel can add users\n");
1287                 ret = 1;
1288                 break;
1289                 
1290         case 404:
1291                 printf("User '%s' already exists\n", Username);
1292                 ret = 0;
1293                 break;
1294         
1295         default:
1296                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1297                 ret = -1;
1298                 break;
1299         }
1300         
1301         free(buf);
1302         
1303         return ret;
1304 }
1305
1306 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString)
1307 {
1308         char    *buf;
1309          int    responseCode, ret;
1310         
1311         // TODO: Pre-validate the string
1312         
1313         sendf(Socket, "USER_FLAGS %s %s\n", Username, TypeString);
1314         
1315         buf = ReadLine(Socket);
1316         responseCode = atoi(buf);
1317         
1318         switch(responseCode)
1319         {
1320         case 200:
1321                 printf("User '%s' updated\n", Username);
1322                 ret = 0;
1323                 break;
1324                 
1325         case 403:
1326                 printf("Only wheel can modify users\n");
1327                 ret = 1;
1328                 break;
1329         
1330         case 404:
1331                 printf("User '%s' does not exist\n", Username);
1332                 ret = 0;
1333                 break;
1334         
1335         case 407:
1336                 printf("Flag string is invalid\n");
1337                 ret = 0;
1338                 break;
1339         
1340         default:
1341                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1342                 ret = -1;
1343                 break;
1344         }
1345         
1346         free(buf);
1347         
1348         return ret;
1349 }
1350
1351 // ---------------
1352 // --- Helpers ---
1353 // ---------------
1354 char *ReadLine(int Socket)
1355 {
1356         static char     buf[BUFSIZ];
1357         static int      bufPos = 0;
1358         static int      bufValid = 0;
1359          int    len;
1360         char    *newline = NULL;
1361          int    retLen = 0;
1362         char    *ret = malloc(10);
1363         
1364         #if DEBUG_TRACE_SERVER
1365         printf("ReadLine: ");
1366         #endif
1367         fflush(stdout);
1368         
1369         ret[0] = '\0';
1370         
1371         while( !newline )
1372         {
1373                 if( bufValid ) {
1374                         len = bufValid;
1375                 }
1376                 else {
1377                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1378                         buf[bufPos+len] = '\0';
1379                 }
1380                 
1381                 newline = strchr( buf+bufPos, '\n' );
1382                 if( newline ) {
1383                         *newline = '\0';
1384                 }
1385                 
1386                 retLen += strlen(buf+bufPos);
1387                 ret = realloc(ret, retLen + 1);
1388                 strcat( ret, buf+bufPos );
1389                 
1390                 if( newline ) {
1391                          int    newLen = newline - (buf+bufPos) + 1;
1392                         bufValid = len - newLen;
1393                         bufPos += newLen;
1394                 }
1395                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
1396         }
1397         
1398         #if DEBUG_TRACE_SERVER
1399         printf("%i '%s'\n", retLen, ret);
1400         #endif
1401         
1402         return ret;
1403 }
1404
1405 int sendf(int Socket, const char *Format, ...)
1406 {
1407         va_list args;
1408          int    len;
1409         
1410         va_start(args, Format);
1411         len = vsnprintf(NULL, 0, Format, args);
1412         va_end(args);
1413         
1414         {
1415                 char    buf[len+1];
1416                 va_start(args, Format);
1417                 vsnprintf(buf, len+1, Format, args);
1418                 va_end(args);
1419                 
1420                 #if DEBUG_TRACE_SERVER
1421                 printf("sendf: %s", buf);
1422                 #endif
1423                 
1424                 return send(Socket, buf, len, 0);
1425         }
1426 }
1427
1428 char *trim(char *string)
1429 {
1430          int    i;
1431         
1432         while( isspace(*string) )
1433                 string ++;
1434         
1435         for( i = strlen(string); i--; )
1436         {
1437                 if( isspace(string[i]) )
1438                         string[i] = '\0';
1439                 else
1440                         break;
1441         }
1442         
1443         return string;
1444 }
1445
1446 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
1447 {
1448          int    ret;
1449         
1450         ret = regexec(regex, string, nMatches, matches, 0);
1451         if( ret ) {
1452                 size_t  len = regerror(ret, regex, NULL, 0);
1453                 char    errorStr[len];
1454                 regerror(ret, regex, errorStr, len);
1455                 printf("string = '%s'\n", string);
1456                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
1457                 exit(-1);
1458         }
1459         
1460         return ret;
1461 }
1462
1463 void CompileRegex(regex_t *regex, const char *pattern, int flags)
1464 {
1465          int    ret = regcomp(regex, pattern, flags);
1466         if( ret ) {
1467                 size_t  len = regerror(ret, regex, NULL, 0);
1468                 char    errorStr[len];
1469                 regerror(ret, regex, errorStr, len);
1470                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
1471                 exit(-1);
1472         }
1473 }

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