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

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