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

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