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

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