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

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