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

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