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

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