f3ac712d30b7cf91eeda72f2d80fe6eb27210bd6
[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          int    _x, _y, times;
1038         char    *name = NULL;
1039          int    price = 0;
1040          int    status = -1;
1041         
1042         switch(giUIMode)
1043         {
1044         // Standard UI
1045         // - This assumes that 
1046         case UI_MODE_STANDARD:
1047                 // Bounds check
1048                 // Index = -1, request limit
1049                 if( Index < 0 || Index >= giNumItems+2 )
1050                         return giNumItems+2;
1051                 // Drink label
1052                 if( Index == 0 )
1053                 {
1054                         price = 0;
1055                         name = "Coke Machine";
1056                         Index = -1;     // -1 indicates a label
1057                         break;
1058                 }
1059                 Index --;
1060                 // Drinks 0 - 6
1061                 if( Index <= 6 )
1062                 {
1063                         name = gaItems[Index].Desc;
1064                         price = gaItems[Index].Price;
1065                         status = gaItems[Index].Status;
1066                         break;
1067                 }
1068                 Index -= 7;
1069                 // EPS label
1070                 if( Index == 0 )
1071                 {
1072                         price = 0;
1073                         name = "Electronic Payment System";
1074                         Index = -1;     // -1 indicates a label
1075                         break;
1076                 }
1077                 Index --;
1078                 Index += 7;
1079                 name = gaItems[Index].Desc;
1080                 price = gaItems[Index].Price;
1081                 status = gaItems[Index].Status;
1082                 break;
1083         default:
1084                 return -1;
1085         }
1086         
1087         // Width = 0, don't print
1088         if( Width > 0 )
1089         {
1090                 // 4 preceding, 5 price
1091                 int nameWidth = Width - 4 - 5;
1092                 move( Row, Col );
1093                 
1094                 if( Index >= 0 )
1095                 {
1096                         // Show hilight and status
1097                         switch( status )
1098                         {
1099                         case 0:
1100                                 if( bHilighted )
1101                                         printw("->  ");
1102                                 else
1103                                         printw("    ");
1104                                 break;
1105                         case 1:
1106                                 printw("SLD ");
1107                                 break;
1108                         
1109                         default:
1110                         case -1:
1111                                 printw("ERR ");
1112                                 break;
1113                         }
1114                         
1115                         printw("%-*.*s", nameWidth, nameWidth, name);
1116                 
1117 //                      getyx(stdscr, _y, _x);
1118                         // Assumes max 4 digit prices
1119 //                      times = Width - 5 - (_x - Col); // TODO: Better handling for large prices
1120 //                      while(times--)  addch(' ');
1121                         
1122                         printw(" %4i", price);
1123                 }
1124                 else
1125                 {
1126                         printw("-- %s", name);
1127                         getyx(stdscr, _y, _x);
1128                         times = Width - 4 - (_x - Col);
1129                         while(times--)  addch(' ');
1130                         printw("    ");
1131                 }
1132         }
1133         
1134         // If the item isn't availiable for sale, return -1 (so it's skipped)
1135         if( status || price > giUserBalance )
1136                 Index = -1;
1137         
1138         return Index;
1139 }
1140
1141 /**
1142  * \brief Print a three-part string at the specified position (formatted)
1143  * \note NCurses UI Helper
1144  * 
1145  * Prints \a Left on the left of the area, \a Right on the righthand side
1146  * and \a Mid in the middle of the area. These are padded with \a Pad1
1147  * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
1148  * 
1149  * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
1150  * and the arguments to these are read in that order.
1151  */
1152 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
1153         const char *Mid, char Pad2, const char *Right, ...)
1154 {
1155          int    lLen, mLen, rLen;
1156          int    times;
1157         
1158         va_list args;
1159         
1160         // Get the length of the strings
1161         va_start(args, Right);
1162         lLen = vsnprintf(NULL, 0, Left, args);
1163         mLen = vsnprintf(NULL, 0, Mid, args);
1164         rLen = vsnprintf(NULL, 0, Right, args);
1165         va_end(args);
1166         
1167         // Sanity check
1168         if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
1169                 return ;        // TODO: What to do?
1170         }
1171         
1172         move(Row, Col);
1173         
1174         // Render strings
1175         va_start(args, Right);
1176         // - Left
1177         {
1178                 char    tmp[lLen+1];
1179                 vsnprintf(tmp, lLen+1, Left, args);
1180                 addstr(tmp);
1181         }
1182         // - Left padding
1183         times = (Width - mLen)/2 - lLen;
1184         while(times--)  addch(Pad1);
1185         // - Middle
1186         {
1187                 char    tmp[mLen+1];
1188                 vsnprintf(tmp, mLen+1, Mid, args);
1189                 addstr(tmp);
1190         }
1191         // - Right Padding
1192         times = (Width - mLen)/2 - rLen;
1193         if( (Width - mLen) % 2 )        times ++;
1194         while(times--)  addch(Pad2);
1195         // - Right
1196         {
1197                 char    tmp[rLen+1];
1198                 vsnprintf(tmp, rLen+1, Right, args);
1199                 addstr(tmp);
1200         }
1201 }
1202
1203 // ---------------------
1204 // --- Coke Protocol ---
1205 // ---------------------
1206 int OpenConnection(const char *Host, int Port)
1207 {
1208         struct hostent  *host;
1209         struct sockaddr_in      serverAddr;
1210          int    sock;
1211         
1212         host = gethostbyname(Host);
1213         if( !host ) {
1214                 fprintf(stderr, "Unable to look up '%s'\n", Host);
1215                 return -1;
1216         }
1217         
1218         memset(&serverAddr, 0, sizeof(serverAddr));
1219         
1220         serverAddr.sin_family = AF_INET;        // IPv4
1221         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
1222         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
1223         serverAddr.sin_port = htons(Port);
1224         
1225         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
1226         if( sock < 0 ) {
1227                 fprintf(stderr, "Failed to create socket\n");
1228                 return -1;
1229         }
1230
1231 //      printf("geteuid() = %i, getuid() = %i\n", geteuid(), getuid());
1232         
1233         if( geteuid() == 0 || getuid() == 0 )
1234         {
1235                  int    i;
1236                 struct sockaddr_in      localAddr;
1237                 memset(&localAddr, 0, sizeof(localAddr));
1238                 localAddr.sin_family = AF_INET; // IPv4
1239                 
1240                 // Loop through all the top ports until one is avaliable
1241                 for( i = 512; i < 1024; i ++)
1242                 {
1243                         localAddr.sin_port = htons(i);  // IPv4
1244                         // Attempt to bind to low port for autoauth
1245                         if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
1246                                 break;
1247                 }
1248                 if( i == 1024 )
1249                         printf("Warning: AUTOAUTH unavaliable\n");
1250 //              else
1251 //                      printf("Bound to 0.0.0.0:%i\n", i);
1252         }
1253         
1254         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
1255                 fprintf(stderr, "Failed to connect to server\n");
1256                 return -1;
1257         }
1258
1259         // We're not authenticated if the connection has just opened
1260         gbIsAuthenticated = 0;
1261         
1262         return sock;
1263 }
1264
1265 /**
1266  * \brief Authenticate with the server
1267  * \return Boolean Failure
1268  */
1269 int Authenticate(int Socket)
1270 {
1271         struct passwd   *pwd;
1272         char    *buf;
1273          int    responseCode;
1274         #if ATTEMPT_PASSWORD_AUTH
1275         char    salt[32];
1276          int    i;
1277         regmatch_t      matches[4];
1278         #endif
1279         
1280         if( gbIsAuthenticated ) return 0;
1281         
1282         // Get user name
1283         pwd = getpwuid( getuid() );
1284         
1285         // Attempt automatic authentication
1286         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
1287         
1288         // Check if it worked
1289         buf = ReadLine(Socket);
1290         
1291         responseCode = atoi(buf);
1292         switch( responseCode )
1293         {
1294         case 200:       // Autoauth succeeded, return
1295                 free(buf);
1296                 break;
1297         
1298         case 401:       // Untrusted, attempt password authentication
1299                 free(buf);
1300
1301                 #if ATTEMPT_PASSWORD_AUTH       
1302                 sendf(Socket, "USER %s\n", pwd->pw_name);
1303                 printf("Using username %s\n", pwd->pw_name);
1304                 
1305                 buf = ReadLine(Socket);
1306                 
1307                 // TODO: Get Salt
1308                 // Expected format: 100 SALT <something> ...
1309                 // OR             : 100 User Set
1310                 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
1311                 responseCode = atoi(buf);
1312                 if( responseCode != 100 ) {
1313                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1314                         free(buf);
1315                         return RV_UNKNOWN_ERROR;        // ERROR
1316                 }
1317                 
1318                 // Check for salt
1319                 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
1320                         // Store it for later
1321                         memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
1322                         salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
1323                 }
1324                 free(buf);
1325                 
1326                 // Give three attempts
1327                 for( i = 0; i < 3; i ++ )
1328                 {
1329                          int    ofs = strlen(pwd->pw_name)+strlen(salt);
1330                         char    tmpBuf[42];
1331                         char    tmp[ofs+20];
1332                         char    *pass = getpass("Password: ");
1333                         uint8_t h[20];
1334                         
1335                         // Create hash string
1336                         // <username><salt><hash>
1337                         strcpy(tmp, pwd->pw_name);
1338                         strcat(tmp, salt);
1339                         SHA1( (unsigned char*)pass, strlen(pass), h );
1340                         memcpy(tmp+ofs, h, 20);
1341                         
1342                         // Hash all that
1343                         SHA1( (unsigned char*)tmp, ofs+20, h );
1344                         sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
1345                                 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
1346                                 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
1347                                 );
1348                 
1349                         // Send password
1350                         sendf(Socket, "PASS %s\n", tmpBuf);
1351                         buf = ReadLine(Socket);
1352                 
1353                         responseCode = atoi(buf);
1354                         // Auth OK?
1355                         if( responseCode == 200 )       break;
1356                         // Bad username/password
1357                         if( responseCode == 401 )       continue;
1358                         
1359                         fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
1360                         free(buf);
1361                         return RV_UNKNOWN_ERROR;
1362                 }
1363                 free(buf);
1364                 if( i == 3 )
1365                         return RV_INVALID_USER; // 2 = Bad Password
1366                 
1367                 #else
1368                 fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
1369                 return RV_INVALID_USER;
1370                 #endif
1371                 break;
1372         
1373         case 404:       // Bad Username
1374                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
1375                 free(buf);
1376                 return RV_INVALID_USER;
1377         
1378         default:
1379                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1380                 printf("%s\n", buf);
1381                 free(buf);
1382                 return RV_UNKNOWN_ERROR;
1383         }
1384         
1385         // Set effective user
1386         if( gsEffectiveUser ) {
1387                 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
1388                 
1389                 buf = ReadLine(Socket);
1390                 responseCode = atoi(buf);
1391                 
1392                 switch(responseCode)
1393                 {
1394                 case 200:
1395                         printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
1396                         break;
1397                 
1398                 case 403:
1399                         printf("Only coke members can use `dispense -u`\n");
1400                         free(buf);
1401                         return RV_PERMISSIONS;
1402                 
1403                 case 404:
1404                         printf("Invalid user selected\n");
1405                         free(buf);
1406                         return RV_INVALID_USER;
1407                 
1408                 default:
1409                         fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1410                         printf("%s\n", buf);
1411                         free(buf);
1412                         return RV_UNKNOWN_ERROR;
1413                 }
1414                 
1415                 free(buf);
1416         }
1417         
1418         gbIsAuthenticated = 1;
1419         
1420         return 0;
1421 }
1422
1423 int GetUserBalance(int Socket)
1424 {
1425         regmatch_t      matches[6];
1426         struct passwd   *pwd;
1427         char    *buf;
1428          int    responseCode;
1429         
1430         if( !gsUserName )
1431         {
1432                 if( gsEffectiveUser ) {
1433                         gsUserName = gsEffectiveUser;
1434                 }
1435                 else {
1436                         pwd = getpwuid( getuid() );
1437                         gsUserName = strdup(pwd->pw_name);
1438                 }
1439         }
1440         
1441         sendf(Socket, "USER_INFO %s\n", gsUserName);
1442         buf = ReadLine(Socket);
1443         responseCode = atoi(buf);
1444         switch(responseCode)
1445         {
1446         case 202:       break;  // Ok
1447         
1448         case 404:
1449                 printf("Invalid user? (USER_INFO failed)\n");
1450                 free(buf);
1451                 return RV_INVALID_USER;
1452         
1453         default:
1454                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
1455                 printf("%s\n", buf);
1456                 free(buf);
1457                 return RV_UNKNOWN_ERROR;
1458         }
1459
1460         RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
1461         
1462         giUserBalance = atoi( buf + matches[4].rm_so );
1463         gsUserFlags = strdup( buf + matches[5].rm_so );
1464         
1465         free(buf);
1466         
1467         return 0;
1468 }
1469
1470 /**
1471  * \brief Read an item info response from the server
1472  * \param Dest  Destination for the read item (strings will be on the heap)
1473  */
1474 int ReadItemInfo(int Socket, tItem *Dest)
1475 {
1476         char    *buf;
1477          int    responseCode;
1478         
1479         regmatch_t      matches[8];
1480         char    *statusStr;
1481         
1482         // Get item info
1483         buf = ReadLine(Socket);
1484         responseCode = atoi(buf);
1485         
1486         switch(responseCode)
1487         {
1488         case 202:       break;
1489         
1490         case 406:
1491                 printf("Bad item name\n");
1492                 free(buf);
1493                 return RV_BAD_ITEM;
1494         
1495         default:
1496                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
1497                 free(buf);
1498                 return RV_UNKNOWN_ERROR;
1499         }
1500         
1501         RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
1502         
1503         buf[ matches[3].rm_eo ] = '\0';
1504         buf[ matches[5].rm_eo ] = '\0';
1505         buf[ matches[7].rm_eo ] = '\0';
1506         
1507         statusStr = &buf[ matches[5].rm_so ];
1508         
1509         Dest->ID = atoi( buf + matches[4].rm_so );
1510         
1511         if( strcmp(statusStr, "avail") == 0 )
1512                 Dest->Status = 0;
1513         else if( strcmp(statusStr, "sold") == 0 )
1514                 Dest->Status = 1;
1515         else if( strcmp(statusStr, "error") == 0 )
1516                 Dest->Status = -1;
1517         else {
1518                 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
1519                         statusStr);
1520                 return RV_UNKNOWN_ERROR;
1521         }
1522         Dest->Price = atoi( buf + matches[6].rm_so );
1523         
1524         // Hack a little to reduce heap fragmentation
1525         {
1526                 char    tmpType[strlen(buf + matches[3].rm_so) + 1];
1527                 char    tmpDesc[strlen(buf + matches[7].rm_so) + 1];
1528                 strcpy(tmpType, buf + matches[3].rm_so);
1529                 strcpy(tmpDesc, buf + matches[7].rm_so);
1530                 free(buf);
1531                 Dest->Type = strdup( tmpType );
1532                 Dest->Desc = strdup( tmpDesc );
1533         }
1534         
1535         return 0;
1536 }
1537
1538 /**
1539  * \brief Fill the item information structure
1540  * \return Boolean Failure
1541  */
1542 void PopulateItemList(int Socket)
1543 {
1544         char    *buf;
1545          int    responseCode;
1546         
1547         char    *arrayType;
1548          int    count, i;
1549         regmatch_t      matches[4];
1550         
1551         // Ask server for stock list
1552         send(Socket, "ENUM_ITEMS\n", 11, 0);
1553         buf = ReadLine(Socket);
1554         
1555         //printf("Output: %s\n", buf);
1556         
1557         responseCode = atoi(buf);
1558         if( responseCode != 201 ) {
1559                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
1560                 exit(RV_UNKNOWN_ERROR);
1561         }
1562         
1563         // - Get item list -
1564         
1565         // Expected format:
1566         //  201 Items <count>
1567         //  202 Item <count>
1568         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1569                 
1570         arrayType = &buf[ matches[2].rm_so ];   buf[ matches[2].rm_eo ] = '\0';
1571         count = atoi( &buf[ matches[3].rm_so ] );
1572                 
1573         // Check array type
1574         if( strcmp(arrayType, "Items") != 0 ) {
1575                 // What the?!
1576                 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
1577                         arrayType);
1578                 exit(RV_UNKNOWN_ERROR);
1579         }
1580         free(buf);
1581         
1582         giNumItems = count;
1583         gaItems = malloc( giNumItems * sizeof(tItem) );
1584         
1585         // Fetch item information
1586         for( i = 0; i < giNumItems; i ++ )
1587         {
1588                 ReadItemInfo( Socket, &gaItems[i] );
1589         }
1590         
1591         // Read end of list
1592         buf = ReadLine(Socket);
1593         responseCode = atoi(buf);
1594                 
1595         if( responseCode != 200 ) {
1596                 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
1597                         responseCode, buf
1598                         );
1599                 exit(-1);
1600         }
1601         
1602         free(buf);
1603 }
1604
1605
1606 /**
1607  * \brief Get information on an item
1608  * \return Boolean Failure
1609  */
1610 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
1611 {
1612         tItem   item;
1613          int    ret;
1614         
1615         // Query
1616         sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
1617         
1618         ret = ReadItemInfo(Socket, &item);
1619         if(ret) return ret;
1620         
1621         printf("%8s:%-2i %2i.%02i %s\n",
1622                 item.Type, item.ID,
1623                 item.Price/100, item.Price%100,
1624                 item.Desc);
1625         
1626         free(item.Type);
1627         free(item.Desc);
1628         
1629         return 0;
1630 }
1631
1632 /**
1633  * \brief Dispense an item
1634  * \return Boolean Failure
1635  */
1636 int DispenseItem(int Socket, const char *Type, int ID)
1637 {
1638          int    ret, responseCode;
1639         char    *buf;
1640         
1641         // Check for a dry run
1642         if( gbDryRun ) {
1643                 printf("Dry Run - No action\n");
1644                 return 0;
1645         }
1646         
1647         // Dispense!
1648         sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
1649         buf = ReadLine(Socket);
1650         
1651         responseCode = atoi(buf);
1652         switch( responseCode )
1653         {
1654         case 200:
1655                 printf("Dispense OK\n");
1656                 ret = 0;
1657                 break;
1658         case 401:
1659                 printf("Not authenticated\n");
1660                 ret = RV_PERMISSIONS;
1661                 break;
1662         case 402:
1663                 printf("Insufficient balance\n");
1664                 ret = RV_BALANCE;
1665                 break;
1666         case 406:
1667                 printf("Bad item name\n");
1668                 ret = RV_BAD_ITEM;
1669                 break;
1670         case 500:
1671                 printf("Item failed to dispense, is the slot empty?\n");
1672                 ret = RV_SERVER_ERROR;
1673                 break;
1674         case 501:
1675                 printf("Dispense not possible (slot empty/permissions)\n");
1676                 ret = RV_SERVER_ERROR;
1677                 break;
1678         default:
1679                 printf("Unknown response code %i ('%s')\n", responseCode, buf);
1680                 ret = RV_UNKNOWN_ERROR;
1681                 break;
1682         }
1683         
1684         free(buf);
1685         return ret;
1686 }
1687
1688 /**
1689  * \brief Alter a user's balance
1690  */
1691 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
1692 {
1693         char    *buf;
1694          int    responseCode, rv = -1;
1695         
1696         // Check for a dry run
1697         if( gbDryRun ) {
1698                 printf("Dry Run - No action\n");
1699                 return 0;
1700         }
1701
1702         // Sanity
1703         if( Ammount == 0 ) {
1704                 printf("An ammount would be nice\n");
1705                 return RV_ARGUMENTS;
1706         }
1707         
1708         sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
1709         buf = ReadLine(Socket);
1710         
1711         responseCode = atoi(buf);
1712         
1713         switch(responseCode)
1714         {
1715         case 200:
1716                 rv = 0; // OK
1717                 break;
1718         case 402:
1719                 fprintf(stderr, "Insufficient balance\n");
1720                 rv = RV_BAD_ITEM;
1721                 break;
1722         case 403:       // Not in coke
1723                 fprintf(stderr, "You are not in coke (sucker)\n");
1724                 rv = RV_PERMISSIONS;
1725                 break;
1726         case 404:       // Unknown user
1727                 fprintf(stderr, "Unknown user '%s'\n", Username);
1728                 rv = RV_INVALID_USER;
1729                 break;
1730         default:
1731                 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
1732                 rv = RV_UNKNOWN_RESPONSE;
1733                 break;
1734         }
1735         free(buf);
1736         
1737         return rv;
1738 }
1739
1740 /**
1741  * \brief Set a user's balance
1742  * \note Only avaliable to dispense admins
1743  */
1744 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
1745 {
1746         char    *buf;
1747          int    responseCode;
1748         
1749         // Check for a dry run
1750         if( gbDryRun ) {
1751                 printf("Dry Run - No action\n");
1752                 return 0;
1753         }
1754         
1755         sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
1756         buf = ReadLine(Socket);
1757         
1758         responseCode = atoi(buf);
1759         free(buf);
1760         
1761         switch(responseCode)
1762         {
1763         case 200:       return 0;       // OK
1764         case 403:       // Not in coke
1765                 fprintf(stderr, "You are not an admin\n");
1766                 return RV_PERMISSIONS;
1767         case 404:       // Unknown user
1768                 fprintf(stderr, "Unknown user '%s'\n", Username);
1769                 return RV_INVALID_USER;
1770         default:
1771                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1772                 return RV_UNKNOWN_RESPONSE;
1773         }
1774         
1775         return -1;
1776 }
1777
1778 /**
1779  * \brief Give money to another user
1780  */
1781 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
1782 {
1783         char    *buf;
1784          int    responseCode;
1785         
1786         if( Ammount < 0 ) {
1787                 printf("Sorry, you can only give, you can't take.\n");
1788                 return RV_ARGUMENTS;
1789         }
1790         
1791         // Fast return on zero
1792         if( Ammount == 0 ) {
1793                 printf("Are you actually going to give any?\n");
1794                 return RV_ARGUMENTS;
1795         }
1796         
1797         // Check for a dry run
1798         if( gbDryRun ) {
1799                 printf("Dry Run - No action\n");
1800                 return 0;
1801         }
1802         
1803         sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
1804
1805         buf = ReadLine(Socket);
1806         responseCode = atoi(buf);
1807         free(buf);      
1808         switch(responseCode)
1809         {
1810         case 200:
1811                 printf("Give succeeded\n");
1812                 return RV_SUCCESS;      // OK
1813         
1814         case 402:       
1815                 fprintf(stderr, "Insufficient balance\n");
1816                 return RV_BALANCE;
1817         
1818         case 404:       // Unknown user
1819                 fprintf(stderr, "Unknown user '%s'\n", Username);
1820                 return RV_INVALID_USER;
1821         
1822         default:
1823                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1824                 return RV_UNKNOWN_RESPONSE;
1825         }
1826         
1827         return -1;
1828 }
1829
1830 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
1831 {
1832         char    *buf;
1833          int    responseCode, ret = -1;
1834         
1835         // Check item id
1836         if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
1837         {
1838                 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
1839                 return RV_ARGUMENTS;
1840         }
1841
1842         // Check username (quick)
1843         if( strchr(Username, ' ') || strchr(Username, '\n') )
1844         {
1845                 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
1846                 return RV_ARGUMENTS;
1847         }
1848
1849         // Send the query
1850         sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
1851
1852         buf = ReadLine(Socket);
1853         responseCode = atoi(buf);
1854         switch(responseCode)
1855         {
1856         case 200:
1857                 Dispense_ShowUser(Socket, Username);    // Show destination account
1858                 ret = 0;
1859                 break;
1860         case 403:
1861                 fprintf(stderr, "Refund access is only avaliable to coke members\n");
1862                 ret = RV_PERMISSIONS;
1863                 break;
1864         case 404:
1865                 fprintf(stderr, "Unknown user '%s' passed\n", Username);
1866                 ret = RV_INVALID_USER;
1867                 break;
1868         case 406:
1869                 fprintf(stderr, "Invalid item '%s' passed\n", Item);
1870                 ret = RV_BAD_ITEM;
1871                 break;
1872         default:
1873                 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
1874                 ret = -1;
1875                 break;
1876         }
1877         free(buf);
1878         return ret;
1879 }
1880
1881 /**
1882  * \brief Donate money to the club
1883  */
1884 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
1885 {
1886         char    *buf;
1887          int    responseCode;
1888         
1889         if( Ammount < 0 ) {
1890                 printf("Sorry, you can only give, you can't take.\n");
1891                 return -1;
1892         }
1893         
1894         // Fast return on zero
1895         if( Ammount == 0 ) {
1896                 printf("Are you actually going to give any?\n");
1897                 return 1;
1898         }
1899         
1900         // Check for a dry run
1901         if( gbDryRun ) {
1902                 printf("Dry Run - No action\n");
1903                 return 0;
1904         }
1905         
1906         sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
1907         buf = ReadLine(Socket);
1908         
1909         responseCode = atoi(buf);
1910         free(buf);
1911         
1912         switch(responseCode)
1913         {
1914         case 200:       return 0;       // OK
1915         
1916         case 402:       
1917                 fprintf(stderr, "Insufficient balance\n");
1918                 return 1;
1919         
1920         default:
1921                 fprintf(stderr, "Unknown response code %i\n", responseCode);
1922                 return -1;
1923         }
1924         
1925         return -1;
1926 }
1927
1928 /**
1929  * \brief Enumerate users
1930  */
1931 int Dispense_EnumUsers(int Socket)
1932 {
1933         char    *buf;
1934          int    responseCode;
1935          int    nUsers;
1936         regmatch_t      matches[4];
1937         
1938         if( giMinimumBalance != INT_MIN ) {
1939                 if( giMaximumBalance != INT_MAX ) {
1940                         sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
1941                 }
1942                 else {
1943                         sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
1944                 }
1945         }
1946         else {
1947                 if( giMaximumBalance != INT_MAX ) {
1948                         sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
1949                 }
1950                 else {
1951                         sendf(Socket, "ENUM_USERS\n");
1952                 }
1953         }
1954         buf = ReadLine(Socket);
1955         responseCode = atoi(buf);
1956         
1957         switch(responseCode)
1958         {
1959         case 201:       break;  // Ok, length follows
1960         
1961         default:
1962                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1963                 free(buf);
1964                 return -1;
1965         }
1966         
1967         // Get count (not actually used)
1968         RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
1969         nUsers = atoi( buf + matches[3].rm_so );
1970         printf("%i users returned\n", nUsers);
1971         
1972         // Free string
1973         free(buf);
1974         
1975         // Read returned users
1976         do {
1977                 buf = ReadLine(Socket);
1978                 responseCode = atoi(buf);
1979                 
1980                 if( responseCode != 202 )       break;
1981                 
1982                 _PrintUserLine(buf);
1983                 free(buf);
1984         } while(responseCode == 202);
1985         
1986         // Check final response
1987         if( responseCode != 200 ) {
1988                 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
1989                 free(buf);
1990                 return -1;
1991         }
1992         
1993         free(buf);
1994         
1995         return 0;
1996 }
1997
1998 int Dispense_ShowUser(int Socket, const char *Username)
1999 {
2000         char    *buf;
2001          int    responseCode, ret;
2002         
2003         sendf(Socket, "USER_INFO %s\n", Username);
2004         buf = ReadLine(Socket);
2005         
2006         responseCode = atoi(buf);
2007         
2008         switch(responseCode)
2009         {
2010         case 202:
2011                 _PrintUserLine(buf);
2012                 ret = 0;
2013                 break;
2014         
2015         case 404:
2016                 printf("Unknown user '%s'\n", Username);
2017                 ret = 1;
2018                 break;
2019         
2020         default:
2021                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2022                 ret = -1;
2023                 break;
2024         }
2025         
2026         free(buf);
2027         
2028         return ret;
2029 }
2030
2031 void _PrintUserLine(const char *Line)
2032 {
2033         regmatch_t      matches[6];
2034          int    bal;
2035         
2036         RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
2037         // 3: Username
2038         // 4: Balance
2039         // 5: Flags
2040         {
2041                  int    usernameLen = matches[3].rm_eo - matches[3].rm_so;
2042                 char    username[usernameLen + 1];
2043                  int    flagsLen = matches[5].rm_eo - matches[5].rm_so;
2044                 char    flags[flagsLen + 1];
2045                 
2046                 memcpy(username, Line + matches[3].rm_so, usernameLen);
2047                 username[usernameLen] = '\0';
2048                 memcpy(flags, Line + matches[5].rm_so, flagsLen);
2049                 flags[flagsLen] = '\0';
2050                 
2051                 bal = atoi(Line + matches[4].rm_so);
2052                 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
2053         }
2054 }
2055
2056 int Dispense_AddUser(int Socket, const char *Username)
2057 {
2058         char    *buf;
2059          int    responseCode, ret;
2060         
2061         // Check for a dry run
2062         if( gbDryRun ) {
2063                 printf("Dry Run - No action\n");
2064                 return 0;
2065         }
2066         
2067         sendf(Socket, "USER_ADD %s\n", Username);
2068         
2069         buf = ReadLine(Socket);
2070         responseCode = atoi(buf);
2071         
2072         switch(responseCode)
2073         {
2074         case 200:
2075                 printf("User '%s' added\n", Username);
2076                 ret = 0;
2077                 break;
2078                 
2079         case 403:
2080                 printf("Only wheel can add users\n");
2081                 ret = 1;
2082                 break;
2083                 
2084         case 404:
2085                 printf("User '%s' already exists\n", Username);
2086                 ret = 0;
2087                 break;
2088         
2089         default:
2090                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2091                 ret = -1;
2092                 break;
2093         }
2094         
2095         free(buf);
2096         
2097         return ret;
2098 }
2099
2100 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
2101 {
2102         char    *buf;
2103          int    responseCode, ret;
2104         
2105         // Check for a dry run
2106         if( gbDryRun ) {
2107                 printf("Dry Run - No action\n");
2108                 return 0;
2109         }
2110         
2111         // TODO: Pre-validate the string
2112         
2113         sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
2114         
2115         buf = ReadLine(Socket);
2116         responseCode = atoi(buf);
2117         
2118         switch(responseCode)
2119         {
2120         case 200:
2121                 printf("User '%s' updated\n", Username);
2122                 ret = 0;
2123                 break;
2124                 
2125         case 403:
2126                 printf("Only dispense admins can modify users\n");
2127                 ret = RV_PERMISSIONS;
2128                 break;
2129         
2130         case 404:
2131                 printf("User '%s' does not exist\n", Username);
2132                 ret = RV_INVALID_USER;
2133                 break;
2134         
2135         case 407:
2136                 printf("Flag string is invalid\n");
2137                 ret = RV_ARGUMENTS;
2138                 break;
2139         
2140         default:
2141                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2142                 ret = RV_UNKNOWN_RESPONSE;
2143                 break;
2144         }
2145         
2146         free(buf);
2147         
2148         return ret;
2149 }
2150
2151 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
2152 {
2153         char    *buf;
2154          int    responseCode, ret;
2155         
2156         // Check for a dry run
2157         if( gbDryRun ) {
2158                 printf("Dry Run - No action\n");
2159                 return 0;
2160         }
2161         
2162         sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
2163         
2164         buf = ReadLine(Socket);
2165         responseCode = atoi(buf);
2166         
2167         switch(responseCode)
2168         {
2169         case 200:
2170                 printf("Item %s:%i updated\n", Type, ID);
2171                 ret = 0;
2172                 break;
2173                 
2174         case 403:
2175                 printf("Only coke members can modify the slots\n");
2176                 ret = RV_PERMISSIONS;
2177                 break;
2178         
2179         case 406:
2180                 printf("Invalid item passed\n");
2181                 ret = RV_BAD_ITEM;
2182                 break;
2183         
2184         default:
2185                 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
2186                 ret = -1;
2187                 break;
2188         }
2189         
2190         free(buf);
2191         
2192         return ret;
2193 }
2194
2195 // ---------------
2196 // --- Helpers ---
2197 // ---------------
2198 char *ReadLine(int Socket)
2199 {
2200         static char     buf[BUFSIZ];
2201         static int      bufPos = 0;
2202         static int      bufValid = 0;
2203          int    len;
2204         char    *newline = NULL;
2205          int    retLen = 0;
2206         char    *ret = malloc(10);
2207         
2208         #if DEBUG_TRACE_SERVER
2209         printf("ReadLine: ");
2210         fflush(stdout);
2211         #endif
2212         
2213         ret[0] = '\0';
2214         
2215         while( !newline )
2216         {
2217                 if( bufValid ) {
2218                         len = bufValid;
2219                 }
2220                 else {
2221                         len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
2222                         if( len < 0 ) {
2223                                 free(ret);
2224                                 return strdup("599 Client Connection Error\n");
2225                         }
2226                 }
2227                 buf[bufPos+len] = '\0';
2228                 
2229                 newline = strchr( buf+bufPos, '\n' );
2230                 if( newline ) {
2231                         *newline = '\0';
2232                 }
2233                 
2234                 retLen += strlen(buf+bufPos);
2235                 ret = realloc(ret, retLen + 1);
2236                 strcat( ret, buf+bufPos );
2237                 
2238                 if( newline ) {
2239                          int    newLen = newline - (buf+bufPos) + 1;
2240                         bufValid = len - newLen;
2241                         bufPos += newLen;
2242                 }
2243                 if( len + bufPos == BUFSIZ - 1 )        bufPos = 0;
2244         }
2245         
2246         #if DEBUG_TRACE_SERVER
2247         printf("%i '%s'\n", retLen, ret);
2248         #endif
2249         
2250         return ret;
2251 }
2252
2253 int sendf(int Socket, const char *Format, ...)
2254 {
2255         va_list args;
2256          int    len;
2257         
2258         va_start(args, Format);
2259         len = vsnprintf(NULL, 0, Format, args);
2260         va_end(args);
2261         
2262         {
2263                 char    buf[len+1];
2264                 va_start(args, Format);
2265                 vsnprintf(buf, len+1, Format, args);
2266                 va_end(args);
2267                 
2268                 #if DEBUG_TRACE_SERVER
2269                 printf("sendf: %s", buf);
2270                 #endif
2271                 
2272                 return send(Socket, buf, len, 0);
2273         }
2274 }
2275
2276 char *trim(char *string)
2277 {
2278          int    i;
2279         
2280         while( isspace(*string) )
2281                 string ++;
2282         
2283         for( i = strlen(string); i--; )
2284         {
2285                 if( isspace(string[i]) )
2286                         string[i] = '\0';
2287                 else
2288                         break;
2289         }
2290         
2291         return string;
2292 }
2293
2294 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
2295 {
2296          int    ret;
2297         
2298         ret = regexec(regex, string, nMatches, matches, 0);
2299         if( ret && errorMessage ) {
2300                 size_t  len = regerror(ret, regex, NULL, 0);
2301                 char    errorStr[len];
2302                 regerror(ret, regex, errorStr, len);
2303                 printf("string = '%s'\n", string);
2304                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
2305                 exit(-1);
2306         }
2307         
2308         return ret;
2309 }
2310
2311 void CompileRegex(regex_t *regex, const char *pattern, int flags)
2312 {
2313          int    ret = regcomp(regex, pattern, flags);
2314         if( ret ) {
2315                 size_t  len = regerror(ret, regex, NULL, 0);
2316                 char    errorStr[len];
2317                 regerror(ret, regex, errorStr, len);
2318                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
2319                 exit(-1);
2320         }
2321 }

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