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

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