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

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