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

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