Client - Fixed scrolling code
[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 #define MAX_TXT_ARGS    5       // Maximum number of textual arguments (including command)
33 #define DISPENSE_MULTIPLE_MAX   20      // Maximum argument to -c
34
35 enum eUI_Modes
36 {
37         UI_MODE_BASIC,  // Non-NCurses
38         UI_MODE_STANDARD,
39         UI_MODE_DRINKSONLY,
40         UI_MODE_ALL,
41         NUM_UI_MODES
42 };
43
44 enum eReturnValues
45 {
46         RV_SUCCESS,
47         RV_BAD_ITEM,
48         RV_INVALID_USER,
49         RV_PERMISSIONS,
50         RV_ARGUMENTS,
51         RV_BALANCE,
52         RV_SERVER_ERROR,        // Generic for 5xx codes
53         RV_UNKNOWN_ERROR = -1,
54         RV_SOCKET_ERROR = -2,
55         RV_UNKNOWN_RESPONSE = -3,
56 };
57
58 // === TYPES ===
59 typedef struct sItem {
60         char    *Type;
61          int    ID;
62          int    Status; // 0: Availiable, 1: Sold out, -1: Error
63         char    *Desc;
64          int    Price;
65 }       tItem;
66
67 // === PROTOTYPES ===
68 void    ShowUsage(void);
69  int    main(int argc, char *argv[]);
70 // --- GUI ---
71  int    ShowNCursesUI(void);
72  int    ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
73 void    PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
74 // --- Coke Server Communication ---
75  int    OpenConnection(const char *Host, int Port);
76  int    Authenticate(int Socket);
77  int    GetUserBalance(int Socket);
78 void    PopulateItemList(int Socket);
79  int    Dispense_ItemInfo(int Socket, const char *Type, int ID);
80  int    DispenseItem(int Socket, const char *Type, int ID);
81  int    Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason);
82  int    Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason);
83  int    Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason);
84  int    Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride);
85  int    Dispense_Donate(int Socket, int Ammount, const char *Reason);
86  int    Dispense_EnumUsers(int Socket);
87  int    Dispense_ShowUser(int Socket, const char *Username);
88 void    _PrintUserLine(const char *Line);
89  int    Dispense_AddUser(int Socket, const char *Username);
90  int    Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason);
91  int    Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName);
92 // --- Helpers ---
93 char    *ReadLine(int Socket);
94  int    sendf(int Socket, const char *Format, ...);
95 char    *trim(char *string);
96  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
97 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
98
99 // === GLOBALS ===
100 char    *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
101  int    giDispensePort = 11020;
102
103 tItem   *gaItems;
104  int    giNumItems;
105 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
106  int    gbIsAuthenticated = 0;
107
108 char    *gsItemPattern; //!< Item pattern
109 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
110
111  int    giUIMode = UI_MODE_STANDARD;
112  int    gbDryRun = 0;   //!< '-n' Read-only
113  int    gbDisallowSelectWithoutBalance = 1;     //!< Don't allow items to be hilighted if not affordable
114
115  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
116  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
117
118  char   *gsUserName;    //!< User that dispense will happen as
119 char    *gsUserFlags;   //!< User's flag set
120  int    giUserBalance = -1;     //!< User balance (set by Authenticate)
121  int    giDispenseCount = 1;    //!< Number of dispenses to do
122
123 char    *gsTextArgs[MAX_TXT_ARGS];
124  int    giTextArgc;
125
126 // === CODE ===
127 void ShowUsage(void)
128 {
129         printf( "Usage:\n" );
130         if( giTextArgc == 0 )
131                 printf(
132                         "  == Everyone ==\n"
133                         "    dispense\n"
134                         "        Show interactive list\n"
135                         "    dispense <name>|<index>|<itemid>\n"
136                         "        Dispense named item (<name> matches if it is a unique prefix)\n"
137                         );
138         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
139                 printf(
140                         "    dispense give <user> <ammount> \"<reason>\"\n"
141                         "        Give money to another user\n"
142                         );
143         
144         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
145                 printf(
146                         "    dispense donate <ammount> \"<reason>\"\n"
147                         "        Donate to the club\n"
148                         );
149         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
150                 printf(
151                         "    dispense iteminfo <itemid>\n"
152                         "        Get the name and price for an item\n"
153                         );
154 //      if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
155 //              printf(
156 //                      "    dispense enumitems\n"
157 //                      "        List avaliable items\n"
158 //                      );
159         if( giTextArgc == 0 )
160                 printf("  == Coke members == \n");
161         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
162                 printf(
163                         "    dispense acct [<user>]\n"
164                         "        Show user balances\n"
165                         "    dispense acct <user> [+-]<ammount> \"<reason>\"\n"
166                         "        Alter a account value\n"
167                         "    dispense acct <user> =<ammount> \"<reason>\"\n"
168                         "        Set an account balance\n"
169                         );
170         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
171                 printf(
172                         "    dispense refund <user> <itemid> [<price>]\n"
173                         "        Refund an item to a user (with optional price override)\n"
174                         "        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
175                         "        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
176                         );
177         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
178                 printf(
179                         "    dispense slot <itemid> <price> <name>\n"
180                         "        Rename/Re-price a slot\n"
181                         );
182         if( giTextArgc == 0 )
183                 printf("  == Dispense administrators ==\n");
184         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
185                 printf(
186                         "    dispense user add <user>\n"
187                         "        Create new account\n"
188                         "    dispense user type <user> <flags> <reason>\n"
189                         "        Alter a user's flags\n"
190                         "        <flags> is a comma-separated list of user, coke, admin, internal or disabled\n"
191                         "        Flags are removed by preceding the name with '-' or '!'\n"
192                         );
193         if( giTextArgc == 0 )
194                 printf( "\n"
195                         "General Options:\n"
196                         "    -c <count>\n"
197                         "        Dispense multiple times\n"
198                         "    -u <username>\n"
199                         "        Set a different user (Coke members only)\n"
200                         "    -h / -?\n"
201                         "        Show help text\n"
202                         "    -G\n"
203                         "        Use simple textual interface (instead of ncurses)\n"
204                         "    -n\n"
205                         "        Dry run - Do not actually do dispenses\n"
206                         "    -m <min balance>\n"
207                         "    -M <max balance>\n"
208                         "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
209                         "Definitions:\n"
210                         "    <itemid>\n"
211                         "        Item ID of the form <type>:<num> where <type> is a non-empty string of alpha-numeric characters, and <num> is a non-negative integer\n"
212 //                      "    <user>\n"
213 //                      "        Account name\n"
214                         );
215 }
216
217 int main(int argc, char *argv[])
218 {
219          int    sock;
220          int    i, ret = 0;
221         char    buffer[BUFSIZ];
222         
223         gsTextArgs[0] = "";
224
225         // -- Create regular expressions
226         // > Code Type Count ...
227         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
228         // > Code Type Ident Status Price Desc
229         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z]+):([0-9]+)\\s+(avail|sold|error)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
230         // > Code 'SALT' salt
231         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
232         // > Code 'User' Username Balance Flags
233         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
234         // > Item Ident
235         CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED);
236
237         // Parse Arguments
238         for( i = 1; i < argc; i ++ )
239         {
240                 char    *arg = argv[i];
241                 
242                 if( arg[0] == '-' )
243                 {                       
244                         switch(arg[1])
245                         {
246                         case 'h':
247                         case '?':
248                                 ShowUsage();
249                                 return 0;
250                                         
251                         case 'c':
252                                 if( i + 1 >= argc ) {
253                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
254                                         ShowUsage();
255                                         return -1;
256                                 }
257                                 giDispenseCount = atoi(argv[++i]);
258                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
259                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
260                                         return -1;
261                                 }
262                                 
263                                 break ;
264         
265                         case 'm':       // Minimum balance
266                                 if( i + 1 >= argc ) {
267                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
268                                         ShowUsage();
269                                         return RV_ARGUMENTS;
270                                 }
271                                 giMinimumBalance = atoi(argv[++i]);
272                                 break;
273                         case 'M':       // Maximum balance
274                                 if( i + 1 >= argc ) {
275                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
276                                         ShowUsage();
277                                         return RV_ARGUMENTS;
278                                 }
279                                 giMaximumBalance = atoi(argv[++i]);
280                                 break;
281                         
282                         case 'u':       // Override User
283                                 if( i + 1 >= argc ) {
284                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
285                                         ShowUsage();
286                                         return RV_ARGUMENTS;
287                                 }
288                                 gsEffectiveUser = argv[++i];
289                                 break;
290                         
291                         case 'H':       // Override remote host
292                                 if( i + 1 >= argc ) {
293                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
294                                         ShowUsage();
295                                         return RV_ARGUMENTS;
296                                 }
297                                 gsDispenseServer = argv[++i];
298                                 break;
299                         case 'P':       // Override remote port
300                                 if( i + 1 >= argc ) {
301                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
302                                         ShowUsage();
303                                         return RV_ARGUMENTS;
304                                 }
305                                 giDispensePort = atoi(argv[++i]);
306                                 break;
307                         
308                         // Set slot name/price
309                         case 's':
310                                 if( giTextArgc != 0 ) {
311                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
312                                         ShowUsage();
313                                         return RV_ARGUMENTS;
314                                 }
315                                 gsTextArgs[0] = "slot"; // HACK!!
316                                 giTextArgc ++;
317                                 break;
318                         
319                         case 'G':       // Don't use GUI
320                                 giUIMode = UI_MODE_BASIC;
321                                 break;
322                         case 'D':       // Drinks only
323                                 giUIMode = UI_MODE_DRINKSONLY;
324                                 break;
325                         case 'n':       // Dry Run / read-only
326                                 gbDryRun = 1;
327                                 break;
328                         case '-':
329                                 if( strcmp(argv[i], "--help") == 0 ) {
330                                         ShowUsage();
331                                         return 0;
332                                 }
333                                 else if( strcmp(argv[i], "--dry-run") == 0 ) {
334                                         gbDryRun = 1;
335                                 }
336                                 else if( strcmp(argv[i], "--drinks-only") == 0 ) {
337                                         giUIMode = UI_MODE_DRINKSONLY;
338                                 }
339                                 else if( strcmp(argv[i], "--can-select-all") == 0 ) {
340                                         gbDisallowSelectWithoutBalance = 0;
341                                 }
342                                 else {
343                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
344                                         ShowUsage();
345                                         return RV_ARGUMENTS;
346                                 }
347                                 break;
348                         default:
349                                 // The first argument is not allowed to begin with 'i'
350                                 // (catches most bad flags)
351                                 if( giTextArgc == 0 ) {
352                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
353                                         ShowUsage();
354                                         return RV_ARGUMENTS;
355                                 }
356                                 if( giTextArgc == MAX_TXT_ARGS )
357                                 {
358                                         fprintf(stderr, "ERROR: Too many arguments\n");
359                                         return RV_ARGUMENTS;
360                                 }
361                                 gsTextArgs[giTextArgc++] = argv[i];
362                                 break;
363                         }
364
365                         continue;
366                 }
367
368                 if( giTextArgc == MAX_TXT_ARGS )
369                 {
370                         fprintf(stderr, "ERROR: Too many arguments\n");
371                         return RV_ARGUMENTS;
372                 }
373         
374                 gsTextArgs[giTextArgc++] = argv[i];
375         
376         }
377
378         //
379         // `dispense acct`
380         // - 
381         if( strcmp(gsTextArgs[0], "acct") == 0 )
382         {
383                 // Connect to server
384                 sock = OpenConnection(gsDispenseServer, giDispensePort);
385                 if( sock < 0 )  return RV_SOCKET_ERROR;
386                 // List accounts?
387                 if( giTextArgc == 1 ) {
388                         ret = Dispense_EnumUsers(sock);
389                         close(sock);
390                         return ret;
391                 }
392                         
393                 // gsTextArgs[1]: Username
394                 
395                 // Alter account?
396                 if( giTextArgc != 2 )
397                 {
398                         if( giTextArgc != 4 ) {
399                                 fprintf(stderr, "`dispense acct` requires a reason\n");
400                                 ShowUsage();
401                                 return RV_ARGUMENTS;
402                         }
403                         
404                         // Authentication required
405                         ret = Authenticate(sock);
406                         if(ret) return ret;
407                         
408                         // gsTextArgs[1]: Username
409                         // gsTextArgs[2]: Ammount
410                         // gsTextArgs[3]: Reason
411                         
412                         if( gsTextArgs[2][0] == '=' ) {
413                                 // Set balance
414                                 if( gsTextArgs[2][1] != '0' && atoi(gsTextArgs[2]+1) == 0 ) {
415                                         fprintf(stderr, "Error: Invalid balance to be set\n");
416                                         exit(1);
417                                 }
418                                 
419                                 ret = Dispense_SetBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]+1), gsTextArgs[3]);
420                         }
421                         else {
422                                 // Alter balance
423                                 ret = Dispense_AlterBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
424                         }
425                 }
426                 // On error, quit
427                 if( ret ) {
428                         close(sock);
429                         return ret;
430                 }
431                 
432                 // Show user information
433                 ret = Dispense_ShowUser(sock, gsTextArgs[1]);
434                 
435                 close(sock);
436                 return ret;
437         }
438         //
439         // `dispense give`
440         // - "Here, have some money."
441         else if( strcmp(gsTextArgs[0], "give") == 0 )
442         {
443                 if( giTextArgc != 4 ) {
444                         fprintf(stderr, "`dispense give` takes three arguments\n");
445                         ShowUsage();
446                         return RV_ARGUMENTS;
447                 }
448                 
449                 // gsTextArgs[1]: Destination
450                 // gsTextArgs[2]: Ammount
451                 // gsTextArgs[3]: Reason
452                 
453                 // Connect to server
454                 sock = OpenConnection(gsDispenseServer, giDispensePort);
455                 if( sock < 0 )  return RV_SOCKET_ERROR;
456                 
457                 // Authenticate
458                 ret = Authenticate(sock);
459                 if(ret) return ret;
460                 
461                 ret = Dispense_Give(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
462
463                 close(sock);
464         
465                 return ret;
466         }
467         // 
468         // `dispense user`
469         // - User administration (Admin Only)
470         if( strcmp(gsTextArgs[0], "user") == 0 )
471         {
472                 // Check argument count
473                 if( giTextArgc == 1 ) {
474                         fprintf(stderr, "Error: `dispense user` requires arguments\n");
475                         ShowUsage();
476                         return RV_ARGUMENTS;
477                 }
478                 
479                 // Connect to server
480                 sock = OpenConnection(gsDispenseServer, giDispensePort);
481                 if( sock < 0 )  return RV_SOCKET_ERROR;
482                 
483                 // Attempt authentication
484                 ret = Authenticate(sock);
485                 if(ret) return ret;
486                 
487                 // Add new user?
488                 if( strcmp(gsTextArgs[1], "add") == 0 )
489                 {
490                         if( giTextArgc != 3 ) {
491                                 fprintf(stderr, "Error: `dispense user add` requires an argument\n");
492                                 ShowUsage();
493                                 return RV_ARGUMENTS;
494                         }
495                         
496                         ret = Dispense_AddUser(sock, gsTextArgs[2]);
497                 }
498                 // Update a user
499                 else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 )
500                 {
501                         if( giTextArgc < 4 || giTextArgc > 5 ) {
502                                 fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
503                                 ShowUsage();
504                                 return RV_ARGUMENTS;
505                         }
506                         
507                         if( giTextArgc == 4 )
508                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], "");
509                         else
510                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]);
511                 }
512                 else
513                 {
514                         fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
515                         ShowUsage();
516                         return RV_ARGUMENTS;
517                 }
518                 close(sock);
519                 return ret;
520         }
521         // Donation!
522         else if( strcmp(gsTextArgs[0], "donate") == 0 )
523         {
524                 // Check argument count
525                 if( giTextArgc != 3 ) {
526                         fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
527                         ShowUsage();
528                         return RV_ARGUMENTS;
529                 }
530                 
531                 // Connect to server
532                 sock = OpenConnection(gsDispenseServer, giDispensePort);
533                 if( sock < 0 )  return RV_SOCKET_ERROR;
534                 
535                 // Attempt authentication
536                 ret = Authenticate(sock);
537                 if(ret) return ret;
538                 
539                 // Do donation
540                 ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]);
541                                 
542                 close(sock);
543
544                 return ret;
545         }
546         // Refund an item
547         else if( strcmp(gsTextArgs[0], "refund") == 0 )
548         {
549                  int     price = 0;
550                 // Check argument count
551                 if( giTextArgc != 3 && giTextArgc != 4 ) {
552                         fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
553                         ShowUsage();
554                         return RV_ARGUMENTS;
555                 }
556         
557                 // Connect to server
558                 sock = OpenConnection(gsDispenseServer, giDispensePort);
559                 if(sock < 0)    return RV_SOCKET_ERROR; 
560
561                 // Attempt authentication
562                 ret = Authenticate(sock);
563                 if(ret) return ret;
564
565                 if( giTextArgc == 4 ) {
566                         price = atoi(gsTextArgs[3]);
567                         if( price <= 0 ) {
568                                 fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
569                                 return RV_ARGUMENTS;
570                         }
571                 }
572
573                 // Username, Item, cost
574                 ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price);
575
576                 // TODO: More
577                 close(sock);
578                 return ret;
579         }
580         // Query an item price
581         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 )
582         {
583                 regmatch_t matches[3];
584                 char    *type;
585                  int    id;
586                 // Check argument count
587                 if( giTextArgc != 2 ) {
588                         fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n");
589                         ShowUsage();
590                         return RV_ARGUMENTS;
591                 }
592                 // Parse item ID
593                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
594                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
595                         return RV_ARGUMENTS;
596                 }
597                 type = gsTextArgs[1] + matches[1].rm_so;
598                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
599                 id = atoi( gsTextArgs[1] + matches[2].rm_so );
600
601                 sock = OpenConnection(gsDispenseServer, giDispensePort);
602                 if( sock < 0 )  return RV_SOCKET_ERROR;
603                 
604                 ret = Dispense_ItemInfo(sock, type, id);
605                 close(sock);
606                 return ret;
607         }
608         // Set slot
609         else if( strcmp(gsTextArgs[0], "slot") == 0 )
610         {
611                 regmatch_t matches[3];
612                 char    *item_type, *newname;
613                  int    item_id, price;
614                 
615                 // Check arguments
616                 if( giTextArgc != 4 ) {
617                         fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
618                         ShowUsage();
619                         return RV_ARGUMENTS;
620                 }
621                 
622                 // Parse arguments
623                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
624                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
625                         return RV_ARGUMENTS;
626                 }
627                 item_type = gsTextArgs[1] + matches[1].rm_so;
628                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
629                 item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
630
631                 // - Price
632                 price = atoi( gsTextArgs[2] );
633                 if( price <= 0 && gsTextArgs[2][0] != '0' ) {
634                         fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
635                         return RV_ARGUMENTS;
636                 }
637                 
638                 // - New name
639                 newname = gsTextArgs[3];
640                 // -- Sanity
641                 {
642                         char *pos;
643                         for( pos = newname; *pos; pos ++ )
644                         {
645                                 if( !isalnum(*pos) && *pos != ' ' ) {
646                                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
647                                         return RV_ARGUMENTS;
648                                 }
649                         }
650                 }
651                 
652                 // Connect & Authenticate
653                 sock = OpenConnection(gsDispenseServer, giDispensePort);
654                 if( sock < 0 )  return RV_SOCKET_ERROR;
655                 ret = Authenticate(sock);
656                 if(ret) return ret;
657                 // Update the slot
658                 ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
659                 
660                 close(sock);
661                 return ret;
662         }
663         // Item name / pattern
664         else
665         {
666                 gsItemPattern = gsTextArgs[0];
667         }
668         
669         // Connect to server
670         sock = OpenConnection(gsDispenseServer, giDispensePort);
671         if( sock < 0 )  return RV_SOCKET_ERROR;
672
673         // Get the user's balance
674         ret = GetUserBalance(sock);
675         if(ret) return ret;
676
677         // Get items
678         PopulateItemList(sock);
679         
680         // Disconnect from server
681         close(sock);
682         
683         if( gsItemPattern && gsItemPattern[0] )
684         {
685                 regmatch_t matches[3];
686                 // Door (hard coded)
687                 if( strcmp(gsItemPattern, "door") == 0 )
688                 {
689                         // Connect, Authenticate, dispense and close
690                         sock = OpenConnection(gsDispenseServer, giDispensePort);
691                         if( sock < 0 )  return RV_SOCKET_ERROR;
692                         ret = Authenticate(sock);
693                         if(ret) return ret;
694                         ret = DispenseItem(sock, "door", 0);
695                         close(sock);
696                         return ret;
697                 }
698                 // Item id (<type>:<num>)
699                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
700                 {
701                         char    *ident;
702                          int    id;
703                         
704                         // Get and finish ident
705                         ident = gsItemPattern + matches[1].rm_so;
706                         gsItemPattern[matches[1].rm_eo] = '\0';
707                         // Get ID
708                         id = atoi( gsItemPattern + matches[2].rm_so );
709                         
710                         // Connect, Authenticate, dispense and close
711                         sock = OpenConnection(gsDispenseServer, giDispensePort);
712                         if( sock < 0 )  return RV_SOCKET_ERROR;
713                         
714                         Dispense_ItemInfo(sock, ident, id);
715                         
716                         ret = Authenticate(sock);
717                         if(ret) return ret;
718                         ret = DispenseItem(sock, ident, id);
719                         close(sock);
720                         return ret;
721                 }
722                 // Item number (6 = coke)
723                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
724                 {
725                         i = atoi(gsItemPattern);
726                 }
727                 // Item prefix
728                 else
729                 {
730                          int    j;
731                          int    best = -1;
732                         for( i = 0; i < giNumItems; i ++ )
733                         {
734                                 // Prefix match (with case-insensitive match)
735                                 for( j = 0; gsItemPattern[j]; j ++ )
736                                 {
737                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
738                                                 continue;
739                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
740                                                 continue;
741                                         break;
742                                 }
743                                 // Check if the prefix matched
744                                 if( gsItemPattern[j] != '\0' )
745                                         continue;
746                                 
747                                 // Prefect match
748                                 if( gaItems[i].Desc[j] == '\0' ) {
749                                         best = i;
750                                         break;
751                                 }
752                                 
753                                 // Only one match allowed
754                                 if( best == -1 ) {
755                                         best = i;
756                                 }
757                                 else {
758                                         // TODO: Allow ambiguous matches?
759                                         // or just print a wanrning
760                                         printf("Warning - Ambiguous pattern, stopping\n");
761                                         return RV_BAD_ITEM;
762                                 }
763                         }
764                         
765                         // Was a match found?
766                         if( best == -1 )
767                         {
768                                 fprintf(stderr, "No item matches the passed string\n");
769                                 return RV_BAD_ITEM;
770                         }
771                         
772                         i = best;
773                 }
774         }
775         else if( giUIMode != UI_MODE_BASIC )
776         {
777                 i = ShowNCursesUI();
778         }
779         else
780         {
781                 // Very basic dispense interface
782                 for( i = 0; i < giNumItems; i ++ ) {
783                         // Add a separator
784                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
785                                 printf("   ---\n");
786                         
787                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
788                                 gaItems[i].Price, gaItems[i].Desc);
789                 }
790                 printf(" q Quit\n");
791                 for(;;)
792                 {
793                         char    *buf;
794                         
795                         i = -1;
796                         
797                         fgets(buffer, BUFSIZ, stdin);
798                         
799                         buf = trim(buffer);
800                         
801                         if( buf[0] == 'q' )     break;
802                         
803                         i = atoi(buf);
804                         
805                         if( i != 0 || buf[0] == '0' )
806                         {
807                                 if( i < 0 || i >= giNumItems ) {
808                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
809                                         continue;
810                                 }
811                                 break;
812                         }
813                 }
814         }
815         
816         
817         // Check for a valid item ID
818         if( i >= 0 )
819         {
820                  int j;
821                 // Connect, Authenticate, dispense and close
822                 sock = OpenConnection(gsDispenseServer, giDispensePort);
823                 if( sock < 0 )  return RV_SOCKET_ERROR;
824                         
825                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
826                 if(ret) return ret;
827                 
828                 ret = Authenticate(sock);
829                 if(ret) return ret;
830                 
831                 for( j = 0; j < giDispenseCount; j ++ ) {
832                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
833                         if( ret )       break;
834                 }
835                 if( j > 1 ) {
836                         printf("%i items dispensed\n", j);
837                 }
838                 Dispense_ShowUser(sock, gsUserName);
839                 close(sock);
840
841         }
842
843         return ret;
844 }
845
846 // -------------------
847 // --- NCurses GUI ---
848 // -------------------
849 /**
850  * \brief Render the NCurses UI
851  */
852 int ShowNCursesUI(void)
853 {
854          int    ch;
855          int    i, times;
856          int    xBase, yBase;
857         const int       displayMinWidth = 40;
858         char    *titleString = "Dispense";
859          int    items_in_view;
860          int    maxItemIndex;
861          int    itemBase = 0;
862          int    currentItem;
863          int    ret = -2;       // -2: Used for marking "no return yet"
864         
865         char    balance_str[5+1+2+1];   // If $9999.99 is too little, something's wrong
866         char    *username;
867         struct passwd *pwd;
868          
869          int    height, width;
870          
871         // Get Username
872         if( gsEffectiveUser )
873                 username = gsEffectiveUser;
874         else {
875                 pwd = getpwuid( getuid() );
876                 username = pwd->pw_name;
877         }
878         // Get balance
879         snprintf(balance_str, sizeof(balance_str), "$%i.%02i", giUserBalance/100, abs(giUserBalance)%100);
880         
881         // Enter curses mode
882         initscr();
883         cbreak(); noecho();
884         
885         // Get max index
886         maxItemIndex = ShowItemAt(0, 0, 0, -1, 0);
887         // Get item count per screen
888         // - 6: randomly chosen (Need at least 3)
889         items_in_view = LINES - 6;
890         if( items_in_view > maxItemIndex )
891                 items_in_view = maxItemIndex;
892         // Get first index
893         currentItem = 0;
894         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
895                 currentItem ++;
896         
897         
898         // Get dimensions
899         height = items_in_view + 3;
900         width = displayMinWidth;
901         
902         // Get positions
903         xBase = COLS/2 - width/2;
904         yBase = LINES/2 - height/2;
905         
906         for( ;; )
907         {
908                 // Header
909                 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
910                 
911                 // Items
912                 for( i = 0; i < items_in_view; i ++ )
913                 {
914                          int    pos = 0;
915                         
916                         move( yBase + 1 + i, xBase );
917                         printw("| ");
918                         
919                         pos += 2;
920                         
921                         // Check for the '...' row
922                         // - Oh god, magic numbers!
923                         if( (i == 0 && itemBase > 0)
924                          || (i == items_in_view - 1 && itemBase < maxItemIndex - items_in_view) )
925                         {
926                                 printw("     ...");     pos += 8;
927                                 times = (width - pos) - 1;
928                                 while(times--)  addch(' ');
929                         }
930                         // Show an item
931                         else {
932                                 ShowItemAt(
933                                         yBase + 1 + i, xBase + pos,     // Position
934                                         (width - pos) - 3,      // Width
935                                         itemBase + i,   // Index
936                                         !!(currentItem == itemBase + i) // Hilighted
937                                         );
938                                 printw("  ");
939                         }
940                         
941                         // Scrollbar (if needed)
942                         if( maxItemIndex > items_in_view ) {
943                                 if( i == 0 ) {
944                                         addch('A');
945                                 }
946                                 else if( i == items_in_view - 1 ) {
947                                         addch('V');
948                                 }
949                                 else {
950                                          int    percentage = itemBase * 100 / (maxItemIndex-items_in_view);
951                                         if( i-1 == percentage*(items_in_view-3)/100 ) {
952                                                 addch('#');
953                                         }
954                                         else {
955                                                 addch('|');
956                                         }
957                                 }
958                         }
959                         else {
960                                 addch('|');
961                         }
962                 }
963                 
964                 // Footer
965                 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
966                 
967                 // User line
968                 // - Username, balance, flags
969                 PrintAlign(yBase+height-1, xBase+1, width-2,
970                         username, ' ', balance_str, ' ', gsUserFlags);
971                 
972                 
973                 // Get input
974                 ch = getch();
975                 
976                 if( ch == '\x1B' ) {
977                         ch = getch();
978                         if( ch == '[' ) {
979                                 ch = getch();
980                                 
981                                 switch(ch)
982                                 {
983                                 case 'B':
984                                         currentItem ++;
985                                         // Skip over spacers
986                                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
987                                                 currentItem ++;
988                                         
989                                         if( currentItem >= maxItemIndex ) {
990                                                 currentItem = 0;
991                                                 // Skip over spacers
992                                                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
993                                                         currentItem ++;
994                                         }
995                                         break;
996                                 case 'A':
997                                         currentItem --;
998                                         // Skip over spacers
999                                         while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
1000                                                 currentItem --;
1001                                         
1002                                         if( currentItem < 0 ) {
1003                                                 currentItem = maxItemIndex - 1;
1004                                                 // Skip over spacers
1005                                                 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
1006                                                         currentItem --;
1007                                         }
1008                                         break;
1009                                 }
1010                         }
1011                         else {
1012                                 
1013                         }
1014                 
1015                         // Scroll only if needed
1016                         if( items_in_view < maxItemIndex )
1017                         {
1018                                 // - If the current item is above the second item shown, and we're not at the top
1019                                 if( currentItem < itemBase + 2 && itemBase > 0 ) {
1020                                         itemBase = currentItem - 2;
1021                                         if(itemBase < 0)        itemBase = 0;
1022                                 }
1023                                 // - If the current item is below the second item show, and we're not at the bottom
1024                                 if( currentItem > itemBase + items_in_view - 2 && itemBase + items_in_view < maxItemIndex ) {
1025                                         itemBase = currentItem - items_in_view + 2;
1026                                         if( itemBase > maxItemIndex - items_in_view )
1027                                                 itemBase = maxItemIndex - items_in_view;
1028                                 }
1029                         }
1030                 }
1031                 else {
1032                         switch(ch)
1033                         {
1034                         case '\n':
1035                                 ret = ShowItemAt(0, 0, 0, currentItem, 0);
1036                                 break;
1037                         case 0x1b:      // Escape
1038                         case 'q':
1039                                 ret = -1;       // -1: Return with no dispense
1040                                 break;
1041                         }
1042                         
1043                         // Check if the return value was changed
1044                         if( ret != -2 ) break;
1045                 }
1046                 
1047         }
1048         
1049         
1050         // Leave
1051         endwin();
1052         return ret;
1053 }
1054
1055 /**
1056  * \brief Show item \a Index at (\a Col, \a Row)
1057  * \return Dispense index of item
1058  * \note Part of the NCurses UI
1059  */
1060 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted)
1061 {
1062         char    *name = NULL;
1063          int    price = 0;
1064          int    status = -1;
1065         
1066         switch(giUIMode)
1067         {
1068         // Standard UI
1069         // - This assumes that 
1070         case UI_MODE_STANDARD:
1071                 // Bounds check
1072                 // Index = -1, request limit
1073                 if( Index < 0 || Index >= giNumItems+2 )
1074                         return giNumItems+2;
1075                 // Drink label
1076                 if( Index == 0 )
1077                 {
1078                         price = 0;
1079                         name = "Coke Machine";
1080                         Index = -1;     // -1 indicates a label
1081                         break;
1082                 }
1083                 Index --;
1084                 // Drinks 0 - 6
1085                 if( Index <= 6 )
1086                 {
1087                         name = gaItems[Index].Desc;
1088                         price = gaItems[Index].Price;
1089                         status = gaItems[Index].Status;
1090                         break;
1091                 }
1092                 Index -= 7;
1093                 // EPS label
1094                 if( Index == 0 )
1095                 {
1096                         price = 0;
1097                         name = "Electronic Payment System";
1098                         Index = -1;     // -1 indicates a label
1099                         break;
1100                 }
1101                 Index --;
1102                 Index += 7;
1103                 name = gaItems[Index].Desc;
1104                 price = gaItems[Index].Price;
1105                 status = gaItems[Index].Status;
1106                 break;
1107         default:
1108                 return -1;
1109         }
1110         
1111         // Width = 0, don't print
1112         if( Width > 0 )
1113         {
1114                 // 4 preceding, 5 price
1115                 int nameWidth = Width - 4 - 5;
1116                 move( Row, Col );
1117                 
1118                 if( Index >= 0 )
1119                 {
1120                         // Show hilight and status
1121                         switch( status )
1122                         {
1123                         case 0:
1124                                 if( bHilighted )
1125                                         printw("->  ");
1126                                 else
1127                                         printw("    ");
1128                                 break;
1129                         case 1:
1130                                 printw("SLD ");
1131                                 break;
1132                         
1133                         default:
1134                         case -1:
1135                                 printw("ERR ");
1136                                 break;
1137                         }
1138                         
1139                         if( price > 100*100 ) {
1140                                 nameWidth --;
1141                         }
1142                         if( price > 1000*100 ) {
1143                                 nameWidth --;
1144                         }
1145                         
1146                         printw("%-*.*s", nameWidth, nameWidth, name);
1147                 
1148                         // 99.99 should be enough
1149                         printw(" %4i", price);
1150                 }
1151                 else
1152                 {
1153                         printw("-- %-*.*s ", Width-4, Width-4, name);
1154                 }
1155         }
1156         
1157         // If the item isn't availiable for sale, return -1 (so it's skipped)
1158         if( status || (price > giUserBalance && gbDisallowSelectWithoutBalance) )
1159                 Index = -1;
1160         
1161         return Index;
1162 }
1163
1164 /**
1165  * \brief Print a three-part string at the specified position (formatted)
1166  * \note NCurses UI Helper
1167  * 
1168  * Prints \a Left on the left of the area, \a Right on the righthand side
1169  * and \a Mid in the middle of the area. These are padded with \a Pad1
1170  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
1171  * 
1172  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
1173  * and the arguments to these are read in that order.
1174  */
1175 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
1176         const char *Mid, char Pad2, const char *Right, ...)
1177 {
1178          int    lLen, mLen, rLen;
1179          int    times;
1180         
1181         va_list args;
1182         
1183         // Get the length of the strings
1184         va_start(args, Right);
1185         lLen = vsnprintf(NULL, 0, Left, args);
1186         mLen = vsnprintf(NULL, 0, Mid, args);
1187         rLen = vsnprintf(NULL, 0, Right, args);
1188         va_end(args);
1189         
1190         // Sanity check
1191         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
1192                 return ;        // TODO: What to do?
1193         }
1194         
1195         move(Row, Col);
1196         
1197         // Render strings
1198         va_start(args, Right);
1199         // - Left
1200         {
1201                 char    tmp[lLen+1];
1202                 vsnprintf(tmp, lLen+1, Left, args);
1203                 addstr(tmp);
1204         }
1205         // - Left padding
1206         times = (Width - mLen)/2 - lLen;
1207         while(times--)  addch(Pad1);
1208         // - Middle
1209         {
1210                 char    tmp[mLen+1];
1211                 vsnprintf(tmp, mLen+1, Mid, args);
1212                 addstr(tmp);
1213         }
1214         // - Right Padding
1215         times = (Width - mLen)/2 - rLen;
1216         if( (Width - mLen) % 2 )        times ++;
1217         while(times--)  addch(Pad2);
1218         // - Right
1219         {
1220                 char    tmp[rLen+1];
1221                 vsnprintf(tmp, rLen+1, Right, args);
1222                 addstr(tmp);
1223         }
1224 }
1225
1226 // ---------------------
1227 // --- Coke Protocol ---
1228 // ---------------------
1229 int OpenConnection(const char *Host, int Port)
1230 {
1231         struct hostent  *host;
1232         struct sockaddr_in      serverAddr;
1233          int    sock;
1234         
1235         host = gethostbyname(Host);
1236         if( !host ) {
1237                 fprintf(stderr, "Unable to look up '%s'\n", Host);
1238                 return -1;
1239         }
1240         
1241         memset(&serverAddr, 0, sizeof(serverAddr));
1242         
1243         serverAddr.sin_family = AF_INET;        // IPv4
1244         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
1245         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
1246         serverAddr.sin_port = htons(Port);
1247         
1248         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
1249         if( sock < 0 ) {
1250                 fprintf(stderr, "Failed to create socket\n");
1251                 return -1;
1252         }
1253
1254 //      printf("geteuid() = %i, getuid() = %i\n", geteuid(), getuid());
1255         
1256         if( geteuid() == 0 || getuid() == 0 )
1257         {
1258                  int    i;
1259                 struct sockaddr_in      localAddr;
1260                 memset(&localAddr, 0, sizeof(localAddr));
1261                 localAddr.sin_family = AF_INET; // IPv4
1262                 
1263                 // Loop through all the top ports until one is avaliable
1264                 for( i = 512; i < 1024; i ++)
1265                 {
1266                         localAddr.sin_port = htons(i);  // IPv4
1267                         // Attempt to bind to low port for autoauth
1268                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
1269                                 break;
1270                 }
1271                 if( i == 1024 )
1272                         printf("Warning: AUTOAUTH unavaliable\n");
1273 //              else
1274 //                      printf("Bound to 0.0.0.0:%i\n", i);
1275         }
1276         
1277         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
1278                 fprintf(stderr, "Failed to connect to server\n");
1279                 return -1;
1280         }
1281
1282         // We're not authenticated if the connection has just opened
1283         gbIsAuthenticated = 0;
1284         
1285         return sock;
1286 }
1287
1288 /**
1289  * \brief Authenticate with the server
1290  * \return Boolean Failure
1291  */
1292 int Authenticate(int Socket)
1293 {
1294         struct passwd   *pwd;
1295         char    *buf;
1296          int    responseCode;
1297         #if ATTEMPT_PASSWORD_AUTH
1298         char    salt[32];
1299          int    i;
1300         regmatch_t      matches[4];
1301         #endif
1302         
1303         if( gbIsAuthenticated ) return 0;
1304         
1305         // Get user name
1306         pwd = getpwuid( getuid() );
1307         
1308         // Attempt automatic authentication
1309         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
1310         
1311         // Check if it worked
1312         buf = ReadLine(Socket);
1313         
1314         responseCode = atoi(buf);
1315         switch( responseCode )
1316         {
1317         case 200:       // Autoauth succeeded, return
1318                 free(buf);
1319                 break;
1320         
1321         case 401:       // Untrusted, attempt password authentication
1322                 free(buf);
1323
1324                 #if ATTEMPT_PASSWORD_AUTH       
1325                 sendf(Socket, "USER %s\n", pwd->pw_name);
1326                 printf("Using username %s\n", pwd->pw_name);
1327                 
1328                 buf = ReadLine(Socket);
1329                 
1330                 // TODO: Get Salt
1331                 // Expected format: 100 SALT <something> ...
1332                 // OR             : 100 User Set
1333                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
1334                 responseCode = atoi(buf);
1335                 if( responseCode != 100 ) {
1336                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1337                         free(buf);
1338                         return RV_UNKNOWN_ERROR;        // ERROR
1339                 }
1340                 
1341                 // Check for salt
1342                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
1343                         // Store it for later
1344                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
1345                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
1346                 }
1347                 free(buf);
1348                 
1349                 // Give three attempts
1350                 for( i = 0; i < 3; i ++ )
1351                 {
1352                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
1353                         char    tmpBuf[42];
1354                         char    tmp[ofs+20];
1355                         char    *pass = getpass("Password: ");
1356                         uint8_t h[20];
1357                         
1358                         // Create hash string
1359                         // <username><salt><hash>
1360                         strcpy(tmp, pwd->pw_name);
1361                         strcat(tmp, salt);
1362                         SHA1( (unsigned char*)pass, strlen(pass), h );
1363                         memcpy(tmp+ofs, h, 20);
1364                         
1365                         // Hash all that
1366                         SHA1( (unsigned char*)tmp, ofs+20, h );
1367                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1368                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
1369                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
1370                                 );
1371                 
1372                         // Send password
1373                         sendf(Socket, "PASS %s\n", tmpBuf);
1374                         buf = ReadLine(Socket);
1375                 
1376                         responseCode = atoi(buf);
1377                         // Auth OK?
1378                         if( responseCode == 200 )       break;
1379                         // Bad username/password
1380                         if( responseCode == 401 )       continue;
1381                         
1382                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1383                         free(buf);
1384                         return RV_UNKNOWN_ERROR;
1385                 }
1386                 free(buf);
1387                 if( i == 3 )
1388                         return RV_INVALID_USER; // 2 = Bad Password
1389                 
1390                 #else
1391                 fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
1392                 return RV_INVALID_USER;
1393                 #endif
1394                 break;
1395         
1396         case 404:       // Bad Username
1397                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
1398                 free(buf);
1399                 return RV_INVALID_USER;
1400         
1401         default:
1402                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1403                 printf("%s\n", buf);
1404                 free(buf);
1405                 return RV_UNKNOWN_ERROR;
1406         }
1407         
1408         // Set effective user
1409         if( gsEffectiveUser ) {
1410                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
1411                 
1412                 buf = ReadLine(Socket);
1413                 responseCode = atoi(buf);
1414                 
1415                 switch(responseCode)
1416                 {
1417                 case 200:
1418                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
1419                         break;
1420                 
1421                 case 403:
1422                         printf("Only coke members can use `dispense -u`\n");
1423                         free(buf);
1424                         return RV_PERMISSIONS;
1425                 
1426                 case 404:
1427                         printf("Invalid user selected\n");
1428                         free(buf);
1429                         return RV_INVALID_USER;
1430                 
1431                 default:
1432                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1433                         printf("%s\n", buf);
1434                         free(buf);
1435                         return RV_UNKNOWN_ERROR;
1436                 }
1437                 
1438                 free(buf);
1439         }
1440         
1441         gbIsAuthenticated = 1;
1442         
1443         return 0;
1444 }
1445
1446 int GetUserBalance(int Socket)
1447 {
1448         regmatch_t      matches[6];
1449         struct passwd   *pwd;
1450         char    *buf;
1451          int    responseCode;
1452         
1453         if( !gsUserName )
1454         {
1455                 if( gsEffectiveUser ) {
1456                         gsUserName = gsEffectiveUser;
1457                 }
1458                 else {
1459                         pwd = getpwuid( getuid() );
1460                         gsUserName = strdup(pwd->pw_name);
1461                 }
1462         }
1463         
1464         sendf(Socket, "USER_INFO %s\n", gsUserName);
1465         buf = ReadLine(Socket);
1466         responseCode = atoi(buf);
1467         switch(responseCode)
1468         {
1469         case 202:       break;  // Ok
1470         
1471         case 404:
1472                 printf("Invalid user? (USER_INFO failed)\n");
1473                 free(buf);
1474                 return RV_INVALID_USER;
1475         
1476         default:
1477                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1478                 printf("%s\n", buf);
1479                 free(buf);
1480                 return RV_UNKNOWN_ERROR;
1481         }
1482
1483         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
1484         
1485         giUserBalance = atoi( buf + matches[4].rm_so );
1486         gsUserFlags = strdup( buf + matches[5].rm_so );
1487         
1488         free(buf);
1489         
1490         return 0;
1491 }
1492
1493 /**
1494  * \brief Read an item info response from the server
1495  * \param Dest  Destination for the read item (strings will be on the heap)
1496  */
1497 int ReadItemInfo(int Socket, tItem *Dest)
1498 {
1499         char    *buf;
1500          int    responseCode;
1501         
1502         regmatch_t      matches[8];
1503         char    *statusStr;
1504         
1505         // Get item info
1506         buf = ReadLine(Socket);
1507         responseCode = atoi(buf);
1508         
1509         switch(responseCode)
1510         {
1511         case 202:       break;
1512         
1513         case 406:
1514                 printf("Bad item name\n");
1515                 free(buf);
1516                 return RV_BAD_ITEM;
1517         
1518         default:
1519                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
1520                 free(buf);
1521                 return RV_UNKNOWN_ERROR;
1522         }
1523         
1524         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
1525         
1526         buf[ matches[3].rm_eo ] = '\0';
1527         buf[ matches[5].rm_eo ] = '\0';
1528         buf[ matches[7].rm_eo ] = '\0';
1529         
1530         statusStr = &buf[ matches[5].rm_so ];
1531         
1532         Dest->ID = atoi( buf + matches[4].rm_so );
1533         
1534         if( strcmp(statusStr, "avail") == 0 )
1535                 Dest->Status = 0;
1536         else if( strcmp(statusStr, "sold") == 0 )
1537                 Dest->Status = 1;
1538         else if( strcmp(statusStr, "error") == 0 )
1539                 Dest->Status = -1;
1540         else {
1541                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
1542                         statusStr);
1543                 return RV_UNKNOWN_ERROR;
1544         }
1545         Dest->Price = atoi( buf + matches[6].rm_so );
1546         
1547         // Hack a little to reduce heap fragmentation
1548         {
1549                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
1550                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
1551                 strcpy(tmpType, buf + matches[3].rm_so);
1552                 strcpy(tmpDesc, buf + matches[7].rm_so);
1553                 free(buf);
1554                 Dest->Type = strdup( tmpType );
1555                 Dest->Desc = strdup( tmpDesc );
1556         }
1557         
1558         return 0;
1559 }
1560
1561 /**
1562  * \brief Fill the item information structure
1563  * \return Boolean Failure
1564  */
1565 void PopulateItemList(int Socket)
1566 {
1567         char    *buf;
1568          int    responseCode;
1569         
1570         char    *arrayType;
1571          int    count, i;
1572         regmatch_t      matches[4];
1573         
1574         // Ask server for stock list
1575         send(Socket, "ENUM_ITEMS\n", 11, 0);
1576         buf = ReadLine(Socket);
1577         
1578         //printf("Output: %s\n", buf);
1579         
1580         responseCode = atoi(buf);
1581         if( responseCode != 201 ) {
1582                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
1583                 exit(RV_UNKNOWN_ERROR);
1584         }
1585         
1586         // - Get item list -
1587         
1588         // Expected format:
1589         //  201 Items <count>
1590         //  202 Item <count>
1591         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1592                 
1593         arrayType = &buf[ matches[2].rm_so ];   buf[ matches[2].rm_eo ] = '\0';
1594         count = atoi( &buf[ matches[3].rm_so ] );
1595                 
1596         // Check array type
1597         if( strcmp(arrayType, "Items") != 0 ) {
1598                 // What the?!
1599                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
1600                         arrayType);
1601                 exit(RV_UNKNOWN_ERROR);
1602         }
1603         free(buf);
1604         
1605         giNumItems = count;
1606         gaItems = malloc( giNumItems * sizeof(tItem) );
1607         
1608         // Fetch item information
1609         for( i = 0; i < giNumItems; i ++ )
1610         {
1611                 ReadItemInfo( Socket, &gaItems[i] );
1612         }
1613         
1614         // Read end of list
1615         buf = ReadLine(Socket);
1616         responseCode = atoi(buf);
1617                 
1618         if( responseCode != 200 ) {
1619                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
1620                         responseCode, buf
1621                         );
1622                 exit(-1);
1623         }
1624         
1625         free(buf);
1626 }
1627
1628
1629 /**
1630  * \brief Get information on an item
1631  * \return Boolean Failure
1632  */
1633 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
1634 {
1635         tItem   item;
1636          int    ret;
1637         
1638         // Query
1639         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
1640         
1641         ret = ReadItemInfo(Socket, &item);
1642         if(ret) return ret;
1643         
1644         printf("%8s:%-2i %2i.%02i %s\n",
1645                 item.Type, item.ID,
1646                 item.Price/100, item.Price%100,
1647                 item.Desc);
1648         
1649         free(item.Type);
1650         free(item.Desc);
1651         
1652         return 0;
1653 }
1654
1655 /**
1656  * \brief Dispense an item
1657  * \return Boolean Failure
1658  */
1659 int DispenseItem(int Socket, const char *Type, int ID)
1660 {
1661          int    ret, responseCode;
1662         char    *buf;
1663         
1664         // Check for a dry run
1665         if( gbDryRun ) {
1666                 printf("Dry Run - No action\n");
1667                 return 0;
1668         }
1669         
1670         // Dispense!
1671         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
1672         buf = ReadLine(Socket);
1673         
1674         responseCode = atoi(buf);
1675         switch( responseCode )
1676         {
1677         case 200:
1678                 printf("Dispense OK\n");
1679                 ret = 0;
1680                 break;
1681         case 401:
1682                 printf("Not authenticated\n");
1683                 ret = RV_PERMISSIONS;
1684                 break;
1685         case 402:
1686                 printf("Insufficient balance\n");
1687                 ret = RV_BALANCE;
1688                 break;
1689         case 406:
1690                 printf("Bad item name\n");
1691                 ret = RV_BAD_ITEM;
1692                 break;
1693         case 500:
1694                 printf("Item failed to dispense, is the slot empty?\n");
1695                 ret = RV_SERVER_ERROR;
1696                 break;
1697         case 501:
1698                 printf("Dispense not possible (slot empty/permissions)\n");
1699                 ret = RV_SERVER_ERROR;
1700                 break;
1701         default:
1702                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
1703                 ret = RV_UNKNOWN_ERROR;
1704                 break;
1705         }
1706         
1707         free(buf);
1708         return ret;
1709 }
1710
1711 /**
1712  * \brief Alter a user's balance
1713  */
1714 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
1715 {
1716         char    *buf;
1717          int    responseCode, rv = -1;
1718         
1719         // Check for a dry run
1720         if( gbDryRun ) {
1721                 printf("Dry Run - No action\n");
1722                 return 0;
1723         }
1724
1725         // Sanity
1726         if( Ammount == 0 ) {
1727                 printf("An ammount would be nice\n");
1728                 return RV_ARGUMENTS;
1729         }
1730         
1731         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
1732         buf = ReadLine(Socket);
1733         
1734         responseCode = atoi(buf);
1735         
1736         switch(responseCode)
1737         {
1738         case 200:
1739                 rv = 0; // OK
1740                 break;
1741         case 402:
1742                 fprintf(stderr, "Insufficient balance\n");
1743                 rv = RV_BAD_ITEM;
1744                 break;
1745         case 403:       // Not in coke
1746                 fprintf(stderr, "You are not in coke (sucker)\n");
1747                 rv = RV_PERMISSIONS;
1748                 break;
1749         case 404:       // Unknown user
1750                 fprintf(stderr, "Unknown user '%s'\n", Username);
1751                 rv = RV_INVALID_USER;
1752                 break;
1753         default:
1754                 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
1755                 rv = RV_UNKNOWN_RESPONSE;
1756                 break;
1757         }
1758         free(buf);
1759         
1760         return rv;
1761 }
1762
1763 /**
1764  * \brief Set a user's balance
1765  * \note Only avaliable to dispense admins
1766  */
1767 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1768 {
1769         char    *buf;
1770          int    responseCode;
1771         
1772         // Check for a dry run
1773         if( gbDryRun ) {
1774                 printf("Dry Run - No action\n");
1775                 return 0;
1776         }
1777         
1778         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1779         buf = ReadLine(Socket);
1780         
1781         responseCode = atoi(buf);
1782         free(buf);
1783         
1784         switch(responseCode)
1785         {
1786         case 200:       return 0;       // OK
1787         case 403:       // Not in coke
1788                 fprintf(stderr, "You are not an admin\n");
1789                 return RV_PERMISSIONS;
1790         case 404:       // Unknown user
1791                 fprintf(stderr, "Unknown user '%s'\n", Username);
1792                 return RV_INVALID_USER;
1793         default:
1794                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1795                 return RV_UNKNOWN_RESPONSE;
1796         }
1797         
1798         return -1;
1799 }
1800
1801 /**
1802  * \brief Give money to another user
1803  */
1804 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1805 {
1806         char    *buf;
1807          int    responseCode;
1808         
1809         if( Ammount < 0 ) {
1810                 printf("Sorry, you can only give, you can't take.\n");
1811                 return RV_ARGUMENTS;
1812         }
1813         
1814         // Fast return on zero
1815         if( Ammount == 0 ) {
1816                 printf("Are you actually going to give any?\n");
1817                 return RV_ARGUMENTS;
1818         }
1819         
1820         // Check for a dry run
1821         if( gbDryRun ) {
1822                 printf("Dry Run - No action\n");
1823                 return 0;
1824         }
1825         
1826         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1827
1828         buf = ReadLine(Socket);
1829         responseCode = atoi(buf);
1830         free(buf);      
1831         switch(responseCode)
1832         {
1833         case 200:
1834                 printf("Give succeeded\n");
1835                 return RV_SUCCESS;      // OK
1836         
1837         case 402:       
1838                 fprintf(stderr, "Insufficient balance\n");
1839                 return RV_BALANCE;
1840         
1841         case 404:       // Unknown user
1842                 fprintf(stderr, "Unknown user '%s'\n", Username);
1843                 return RV_INVALID_USER;
1844         
1845         default:
1846                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1847                 return RV_UNKNOWN_RESPONSE;
1848         }
1849         
1850         return -1;
1851 }
1852
1853 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
1854 {
1855         char    *buf;
1856          int    responseCode, ret = -1;
1857         
1858         // Check item id
1859         if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
1860         {
1861                 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
1862                 return RV_ARGUMENTS;
1863         }
1864
1865         // Check username (quick)
1866         if( strchr(Username, ' ') || strchr(Username, '\n') )
1867         {
1868                 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
1869                 return RV_ARGUMENTS;
1870         }
1871
1872         // Send the query
1873         sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
1874
1875         buf = ReadLine(Socket);
1876         responseCode = atoi(buf);
1877         switch(responseCode)
1878         {
1879         case 200:
1880                 Dispense_ShowUser(Socket, Username);    // Show destination account
1881                 ret = 0;
1882                 break;
1883         case 403:
1884                 fprintf(stderr, "Refund access is only avaliable to coke members\n");
1885                 ret = RV_PERMISSIONS;
1886                 break;
1887         case 404:
1888                 fprintf(stderr, "Unknown user '%s' passed\n", Username);
1889                 ret = RV_INVALID_USER;
1890                 break;
1891         case 406:
1892                 fprintf(stderr, "Invalid item '%s' passed\n", Item);
1893                 ret = RV_BAD_ITEM;
1894                 break;
1895         default:
1896                 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
1897                 ret = -1;
1898                 break;
1899         }
1900         free(buf);
1901         return ret;
1902 }
1903
1904 /**
1905  * \brief Donate money to the club
1906  */
1907 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
1908 {
1909         char    *buf;
1910          int    responseCode;
1911         
1912         if( Ammount < 0 ) {
1913                 printf("Sorry, you can only give, you can't take.\n");
1914                 return -1;
1915         }
1916         
1917         // Fast return on zero
1918         if( Ammount == 0 ) {
1919                 printf("Are you actually going to give any?\n");
1920                 return 1;
1921         }
1922         
1923         // Check for a dry run
1924         if( gbDryRun ) {
1925                 printf("Dry Run - No action\n");
1926                 return 0;
1927         }
1928         
1929         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
1930         buf = ReadLine(Socket);
1931         
1932         responseCode = atoi(buf);
1933         free(buf);
1934         
1935         switch(responseCode)
1936         {
1937         case 200:       return 0;       // OK
1938         
1939         case 402:       
1940                 fprintf(stderr, "Insufficient balance\n");
1941                 return 1;
1942         
1943         default:
1944                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1945                 return -1;
1946         }
1947         
1948         return -1;
1949 }
1950
1951 /**
1952  * \brief Enumerate users
1953  */
1954 int Dispense_EnumUsers(int Socket)
1955 {
1956         char    *buf;
1957          int    responseCode;
1958          int    nUsers;
1959         regmatch_t      matches[4];
1960         
1961         if( giMinimumBalance != INT_MIN ) {
1962                 if( giMaximumBalance != INT_MAX ) {
1963                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
1964                 }
1965                 else {
1966                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
1967                 }
1968         }
1969         else {
1970                 if( giMaximumBalance != INT_MAX ) {
1971                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
1972                 }
1973                 else {
1974                         sendf(Socket, "ENUM_USERS\n");
1975                 }
1976         }
1977         buf = ReadLine(Socket);
1978         responseCode = atoi(buf);
1979         
1980         switch(responseCode)
1981         {
1982         case 201:       break;  // Ok, length follows
1983         
1984         default:
1985                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1986                 free(buf);
1987                 return -1;
1988         }
1989         
1990         // Get count (not actually used)
1991         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1992         nUsers = atoi( buf + matches[3].rm_so );
1993         printf("%i users returned\n", nUsers);
1994         
1995         // Free string
1996         free(buf);
1997         
1998         // Read returned users
1999         do {
2000                 buf = ReadLine(Socket);
2001                 responseCode = atoi(buf);
2002                 
2003                 if( responseCode != 202 )       break;
2004                 
2005                 _PrintUserLine(buf);
2006                 free(buf);
2007         } while(responseCode == 202);
2008         
2009         // Check final response
2010         if( responseCode != 200 ) {
2011                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
2012                 free(buf);
2013                 return -1;
2014         }
2015         
2016         free(buf);
2017         
2018         return 0;
2019 }
2020
2021 int Dispense_ShowUser(int Socket, const char *Username)
2022 {
2023         char    *buf;
2024          int    responseCode, ret;
2025         
2026         sendf(Socket, "USER_INFO %s\n", Username);
2027         buf = ReadLine(Socket);
2028         
2029         responseCode = atoi(buf);
2030         
2031         switch(responseCode)
2032         {
2033         case 202:
2034                 _PrintUserLine(buf);
2035                 ret = 0;
2036                 break;
2037         
2038         case 404:
2039                 printf("Unknown user '%s'\n", Username);
2040                 ret = 1;
2041                 break;
2042         
2043         default:
2044                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2045                 ret = -1;
2046                 break;
2047         }
2048         
2049         free(buf);
2050         
2051         return ret;
2052 }
2053
2054 void _PrintUserLine(const char *Line)
2055 {
2056         regmatch_t      matches[6];
2057          int    bal;
2058         
2059         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
2060         // 3: Username
2061         // 4: Balance
2062         // 5: Flags
2063         {
2064                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
2065                 char    username[usernameLen + 1];
2066                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
2067                 char    flags[flagsLen + 1];
2068                 
2069                 memcpy(username, Line + matches[3].rm_so, usernameLen);
2070                 username[usernameLen] = '\0';
2071                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
2072                 flags[flagsLen] = '\0';
2073                 
2074                 bal = atoi(Line + matches[4].rm_so);
2075                 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
2076         }
2077 }
2078
2079 int Dispense_AddUser(int Socket, const char *Username)
2080 {
2081         char    *buf;
2082          int    responseCode, ret;
2083         
2084         // Check for a dry run
2085         if( gbDryRun ) {
2086                 printf("Dry Run - No action\n");
2087                 return 0;
2088         }
2089         
2090         sendf(Socket, "USER_ADD %s\n", Username);
2091         
2092         buf = ReadLine(Socket);
2093         responseCode = atoi(buf);
2094         
2095         switch(responseCode)
2096         {
2097         case 200:
2098                 printf("User '%s' added\n", Username);
2099                 ret = 0;
2100                 break;
2101                 
2102         case 403:
2103                 printf("Only wheel can add users\n");
2104                 ret = 1;
2105                 break;
2106                 
2107         case 404:
2108                 printf("User '%s' already exists\n", Username);
2109                 ret = 0;
2110                 break;
2111         
2112         default:
2113                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2114                 ret = -1;
2115                 break;
2116         }
2117         
2118         free(buf);
2119         
2120         return ret;
2121 }
2122
2123 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
2124 {
2125         char    *buf;
2126          int    responseCode, ret;
2127         
2128         // Check for a dry run
2129         if( gbDryRun ) {
2130                 printf("Dry Run - No action\n");
2131                 return 0;
2132         }
2133         
2134         // TODO: Pre-validate the string
2135         
2136         sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
2137         
2138         buf = ReadLine(Socket);
2139         responseCode = atoi(buf);
2140         
2141         switch(responseCode)
2142         {
2143         case 200:
2144                 printf("User '%s' updated\n", Username);
2145                 ret = 0;
2146                 break;
2147                 
2148         case 403:
2149                 printf("Only dispense admins can modify users\n");
2150                 ret = RV_PERMISSIONS;
2151                 break;
2152         
2153         case 404:
2154                 printf("User '%s' does not exist\n", Username);
2155                 ret = RV_INVALID_USER;
2156                 break;
2157         
2158         case 407:
2159                 printf("Flag string is invalid\n");
2160                 ret = RV_ARGUMENTS;
2161                 break;
2162         
2163         default:
2164                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2165                 ret = RV_UNKNOWN_RESPONSE;
2166                 break;
2167         }
2168         
2169         free(buf);
2170         
2171         return ret;
2172 }
2173
2174 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
2175 {
2176         char    *buf;
2177          int    responseCode, ret;
2178         
2179         // Check for a dry run
2180         if( gbDryRun ) {
2181                 printf("Dry Run - No action\n");
2182                 return 0;
2183         }
2184         
2185         sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
2186         
2187         buf = ReadLine(Socket);
2188         responseCode = atoi(buf);
2189         
2190         switch(responseCode)
2191         {
2192         case 200:
2193                 printf("Item %s:%i updated\n", Type, ID);
2194                 ret = 0;
2195                 break;
2196                 
2197         case 403:
2198                 printf("Only coke members can modify the slots\n");
2199                 ret = RV_PERMISSIONS;
2200                 break;
2201         
2202         case 406:
2203                 printf("Invalid item passed\n");
2204                 ret = RV_BAD_ITEM;
2205                 break;
2206         
2207         default:
2208                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2209                 ret = -1;
2210                 break;
2211         }
2212         
2213         free(buf);
2214         
2215         return ret;
2216 }
2217
2218 // ---------------
2219 // --- Helpers ---
2220 // ---------------
2221 char *ReadLine(int Socket)
2222 {
2223         static char     buf[BUFSIZ];
2224         static int      bufPos = 0;
2225         static int      bufValid = 0;
2226          int    len;
2227         char    *newline = NULL;
2228          int    retLen = 0;
2229         char    *ret = malloc(10);
2230         
2231         #if DEBUG_TRACE_SERVER
2232         printf("ReadLine: ");
2233         fflush(stdout);
2234         #endif
2235         
2236         ret[0] = '\0';
2237         
2238         while( !newline )
2239         {
2240                 if( bufValid ) {
2241                         len = bufValid;
2242                 }
2243                 else {
2244                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
2245                         if( len <= 0 ) {
2246                                 free(ret);
2247                                 return strdup("599 Client Connection Error\n");
2248                         }
2249                 }
2250                 buf[bufPos+len] = '\0';
2251                 
2252                 newline = strchr( buf+bufPos, '\n' );
2253                 if( newline ) {
2254                         *newline = '\0';
2255                 }
2256                 
2257                 retLen += strlen(buf+bufPos);
2258                 ret = realloc(ret, retLen + 1);
2259                 strcat( ret, buf+bufPos );
2260                 
2261                 if( newline ) {
2262                          int    newLen = newline - (buf+bufPos) + 1;
2263                         bufValid = len - newLen;
2264                         bufPos += newLen;
2265                 }
2266                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
2267         }
2268         
2269         #if DEBUG_TRACE_SERVER
2270         printf("%i '%s'\n", retLen, ret);
2271         #endif
2272         
2273         return ret;
2274 }
2275
2276 int sendf(int Socket, const char *Format, ...)
2277 {
2278         va_list args;
2279          int    len;
2280         
2281         va_start(args, Format);
2282         len = vsnprintf(NULL, 0, Format, args);
2283         va_end(args);
2284         
2285         {
2286                 char    buf[len+1];
2287                 va_start(args, Format);
2288                 vsnprintf(buf, len+1, Format, args);
2289                 va_end(args);
2290                 
2291                 #if DEBUG_TRACE_SERVER
2292                 printf("sendf: %s", buf);
2293                 #endif
2294                 
2295                 return send(Socket, buf, len, 0);
2296         }
2297 }
2298
2299 char *trim(char *string)
2300 {
2301          int    i;
2302         
2303         while( isspace(*string) )
2304                 string ++;
2305         
2306         for( i = strlen(string); i--; )
2307         {
2308                 if( isspace(string[i]) )
2309                         string[i] = '\0';
2310                 else
2311                         break;
2312         }
2313         
2314         return string;
2315 }
2316
2317 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
2318 {
2319          int    ret;
2320         
2321         ret = regexec(regex, string, nMatches, matches, 0);
2322         if( ret && errorMessage ) {
2323                 size_t  len = regerror(ret, regex, NULL, 0);
2324                 char    errorStr[len];
2325                 regerror(ret, regex, errorStr, len);
2326                 printf("string = '%s'\n", string);
2327                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
2328                 exit(-1);
2329         }
2330         
2331         return ret;
2332 }
2333
2334 void CompileRegex(regex_t *regex, const char *pattern, int flags)
2335 {
2336          int    ret = regcomp(regex, pattern, flags);
2337         if( ret ) {
2338                 size_t  len = regerror(ret, regex, NULL, 0);
2339                 char    errorStr[len];
2340                 regerror(ret, regex, errorStr, len);
2341                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
2342                 exit(-1);
2343         }
2344 }

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