Fixed up some error reporting
[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 <pwd.h>        // getpwuids
16 #include <unistd.h>     // close/getuid
17 #include <limits.h>     // INT_MIN/INT_MAX
18 #include "common.h"
19
20 #define USE_NCURSES_INTERFACE   0
21 #define DEBUG_TRACE_SERVER      0
22 #define USE_AUTOAUTH    1
23
24 #define MAX_TXT_ARGS    5       // Maximum number of textual arguments (including command)
25 #define DISPENSE_MULTIPLE_MAX   20      // Maximum argument to -c
26
27 // === TYPES ===
28
29 // === PROTOTYPES ===
30 void    ShowUsage(void);
31  int    main(int argc, char *argv[]);
32 // --- Coke Server Communication ---
33 // --- Helpers ---
34 char    *trim(char *string);
35  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
36 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
37
38 // === GLOBALS ===
39 char    *gsDispenseServer = "merlo.ucc.gu.uwa.edu.au";
40  int    giDispensePort = 11020;
41
42 tItem   *gaItems;
43  int    giNumItems;
44 regex_t gArrayRegex, gItemRegex, gSaltRegex, gUserInfoRegex, gUserItemIdentRegex;
45  int    gbIsAuthenticated = 0;
46
47 char    *gsItemPattern; //!< Item pattern
48 char    *gsEffectiveUser;       //!< '-u' Dispense as another user
49
50 enum eUI_Modes  giUIMode = UI_MODE_STANDARD;
51  int    gbDryRun = 0;   //!< '-n' Read-only
52  int    gbDisallowSelectWithoutBalance = 1;     //!< Don't allow items to be hilighted if not affordable
53
54  int    giMinimumBalance = INT_MIN;     //!< '-m' Minumum balance for `dispense acct`
55  int    giMaximumBalance = INT_MAX;     //!< '-M' Maximum balance for `dispense acct`
56
57  char   *gsUserName;    //!< User that dispense will happen as
58 char    *gsUserFlags;   //!< User's flag set
59  int    giUserBalance = -1;     //!< User balance (set by Authenticate)
60  int    giDispenseCount = 1;    //!< Number of dispenses to do
61
62 char    *gsTextArgs[MAX_TXT_ARGS];
63  int    giTextArgc;
64
65 // === CODE ===
66 void ShowUsage(void)
67 {
68         printf( "Usage:\n" );
69         if( giTextArgc == 0 )
70                 printf(
71                         "  == Everyone ==\n"
72                         "    dispense\n"
73                         "        Show interactive list\n"
74                         "    dispense <name>|<index>|<itemid>\n"
75                         "        Dispense named item (<name> matches if it is a unique prefix)\n"
76                         "    dispense finger\n"
77                         "        Show the finger output\n"
78                         );
79         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "give") == 0 )
80                 printf(
81                         "    dispense give <user> <amount> \"<reason>\"\n"
82                         "        Give money to another user\n"
83                         );
84         
85         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "donate") == 0 )
86                 printf(
87                         "    dispense donate <amount> \"<reason>\"\n"
88                         "        Donate to the club\n"
89                         );
90         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "iteminfo") == 0 )
91                 printf(
92                         "    dispense iteminfo <itemid>\n"
93                         "        Get the name and price for an item\n"
94                         );
95 //      if( giTextArgc == 0 || strcmp(gsTextArgs[0], "enumitems") == 0 )
96 //              printf(
97 //                      "    dispense enumitems\n"
98 //                      "        List avaliable items\n"
99 //                      );
100         if( giTextArgc == 0 )
101                 printf("  == Coke members == \n");
102         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "acct") == 0 )
103                 printf(
104                         "    dispense acct [<user>]\n"
105                         "        Show user balances\n"
106                         "    dispense acct <user> [+-]<amount> \"<reason>\"\n"
107                         "        Alter a account value\n"
108                         "    dispense acct <user> =<amount> \"<reason>\"\n"
109                         "        Set an account balance\n"
110                         );
111         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "refund") == 0 )
112                 printf(
113                         "    dispense refund <user> <itemid> [<price>]\n"
114                         "        Refund an item to a user (with optional price override)\n"
115                         "        Item IDs can be seen in the cokelog (in the brackets after the item name)\n"
116                         "        e.g. coke:6 for a coke, snack:33 for slot 33 of the snack machine\n"
117                         );
118         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "slot") == 0 )
119                 printf(
120                         "    dispense slot <itemid> <price> <name>\n"
121                         "        Rename/Re-price a slot\n"
122                         );
123         if( giTextArgc == 0 )
124                 printf("  == Dispense administrators ==\n");
125         if( giTextArgc == 0 || strcmp(gsTextArgs[0], "user") == 0 )
126                 printf(
127                         "    dispense user add <user>\n"
128                         "        Create new account\n"
129                         "    dispense user type <user> <flags> <reason>\n"
130                         "        Alter a user's flags\n"
131                         "        <flags> is a comma-separated list of user, coke, admin, internal or disabled\n"
132                         "        Flags are removed by preceding the name with '-' or '!'\n"
133                         );
134         if( giTextArgc == 0 )
135                 printf( "\n"
136                         "General Options:\n"
137                         "    -c <count>\n"
138                         "        Dispense multiple times\n"
139                         "    -u <username>\n"
140                         "        Set a different user (Coke members only)\n"
141                         "    -h / -?\n"
142                         "        Show help text\n"
143                         "    -G\n"
144                         "        Use simple textual interface (instead of ncurses)\n"
145                         "    -n\n"
146                         "        Dry run - Do not actually do dispenses\n"
147                         "    -m <min balance>\n"
148                         "    -M <max balance>\n"
149                         "        Set the Maximum/Minimum balances shown in `dispense acct`\n"
150                         "Definitions:\n"
151                         "    <itemid>\n"
152                         "        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"
153 //                      "    <user>\n"
154 //                      "        Account name\n"
155                         );
156 }
157
158 int main(int argc, char *argv[])
159 {
160          int    sock;
161          int    i, ret = 0;
162         char    buffer[BUFSIZ];
163         
164         gsTextArgs[0] = "";
165
166         // -- Create regular expressions
167         // > Code Type Count ...
168         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
169         // > Code Type Ident Status Price Desc
170         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);
171         // > Code 'SALT' salt
172         CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+(.+)$", REG_EXTENDED);
173         // > Code 'User' Username Balance Flags
174         CompileRegex(&gUserInfoRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([^ ]+)\\s+(-?[0-9]+)\\s+(.+)$", REG_EXTENDED);
175         // > Item Ident
176         CompileRegex(&gUserItemIdentRegex, "^([A-Za-z]+):([0-9]+)$", REG_EXTENDED);
177
178         // Parse Arguments
179         for( i = 1; i < argc; i ++ )
180         {
181                 char    *arg = argv[i];
182                 
183                 if( arg[0] == '-' )
184                 {                       
185                         switch(arg[1])
186                         {
187                         case 'h':
188                         case '?':
189                                 ShowUsage();
190                                 return 0;
191                                         
192                         case 'c':
193                                 if( i > 2 && strcmp(argv[i-1], "type") == 0 )
194                                         goto _default;
195                                 if( i + 1 >= argc ) {
196                                         fprintf(stderr, "%s: -c takes an argument\n", argv[0]);
197                                         ShowUsage();
198                                         return -1;
199                                 }
200                                 giDispenseCount = atoi(argv[++i]);
201                                 if( giDispenseCount < 1 || giDispenseCount > DISPENSE_MULTIPLE_MAX ) {
202                                         fprintf(stderr, "Sorry, only 1-20 can be passed to -c (safety)\n");
203                                         return -1;
204                                 }
205                                 
206                                 break ;
207         
208                         case 'm':       // Minimum balance
209                                 if( i + 1 >= argc ) {
210                                         fprintf(stderr, "%s: -m takes an argument\n", argv[0]);
211                                         ShowUsage();
212                                         return RV_ARGUMENTS;
213                                 }
214                                 giMinimumBalance = atoi(argv[++i]);
215                                 break;
216                         case 'M':       // Maximum balance
217                                 if( i + 1 >= argc ) {
218                                         fprintf(stderr, "%s: -M takes an argument\n", argv[0]);
219                                         ShowUsage();
220                                         return RV_ARGUMENTS;
221                                 }
222                                 giMaximumBalance = atoi(argv[++i]);
223                                 break;
224                         
225                         case 'u':       // Override User
226                                 if( i + 1 >= argc ) {
227                                         fprintf(stderr, "%s: -u takes an argument\n", argv[0]);
228                                         ShowUsage();
229                                         return RV_ARGUMENTS;
230                                 }
231                                 gsEffectiveUser = argv[++i];
232                                 break;
233                         
234                         case 'H':       // Override remote host
235                                 if( i + 1 >= argc ) {
236                                         fprintf(stderr, "%s: -H takes an argument\n", argv[0]);
237                                         ShowUsage();
238                                         return RV_ARGUMENTS;
239                                 }
240                                 gsDispenseServer = argv[++i];
241                                 break;
242                         case 'P':       // Override remote port
243                                 if( i + 1 >= argc ) {
244                                         fprintf(stderr, "%s: -P takes an argument\n", argv[0]);
245                                         ShowUsage();
246                                         return RV_ARGUMENTS;
247                                 }
248                                 giDispensePort = atoi(argv[++i]);
249                                 break;
250                         
251                         // Set slot name/price
252                         case 's':
253                                 if( giTextArgc != 0 ) {
254                                         fprintf(stderr, "%s: -s must appear before other arguments\n", argv[0]);
255                                         ShowUsage();
256                                         return RV_ARGUMENTS;
257                                 }
258                                 gsTextArgs[0] = "slot"; // HACK!!
259                                 giTextArgc ++;
260                                 break;
261                         
262                         case 'G':       // Don't use GUI
263                                 giUIMode = UI_MODE_BASIC;
264                                 break;
265                         case 'D':       // Drinks only
266                                 giUIMode = UI_MODE_DRINKSONLY;
267                                 break;
268                         case 'n':       // Dry Run / read-only
269                                 gbDryRun = 1;
270                                 break;
271                         case '-':
272                                 if( strcmp(argv[i], "--help") == 0 ) {
273                                         ShowUsage();
274                                         return 0;
275                                 }
276                                 else if( strcmp(argv[i], "--dry-run") == 0 ) {
277                                         gbDryRun = 1;
278                                 }
279                                 else if( strcmp(argv[i], "--drinks-only") == 0 ) {
280                                         giUIMode = UI_MODE_DRINKSONLY;
281                                 }
282                                 else if( strcmp(argv[i], "--can-select-all") == 0 ) {
283                                         gbDisallowSelectWithoutBalance = 0;
284                                 }
285                                 else {
286                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
287                                         ShowUsage();
288                                         return RV_ARGUMENTS;
289                                 }
290                                 break;
291                         default: _default:
292                                 // The first argument is not allowed to begin with 'i'
293                                 // (catches most bad flags)
294                                 if( giTextArgc == 0 ) {
295                                         fprintf(stderr, "%s: Unknown switch '%s'\n", argv[0], argv[i]);
296                                         ShowUsage();
297                                         return RV_ARGUMENTS;
298                                 }
299                                 if( giTextArgc == MAX_TXT_ARGS )
300                                 {
301                                         fprintf(stderr, "ERROR: Too many arguments\n");
302                                         return RV_ARGUMENTS;
303                                 }
304                                 gsTextArgs[giTextArgc++] = argv[i];
305                                 break;
306                         }
307
308                         continue;
309                 }
310
311                 if( giTextArgc == MAX_TXT_ARGS )
312                 {
313                         fprintf(stderr, "ERROR: Too many arguments\n");
314                         return RV_ARGUMENTS;
315                 }
316         
317                 gsTextArgs[giTextArgc++] = argv[i];
318         
319         }
320
321         //
322         // `dispense finger`
323         // -
324         if( strcmp(gsTextArgs[0], "finger") == 0 )
325         {
326                 // Connect to server
327                 sock = OpenConnection(gsDispenseServer, giDispensePort);
328                 if( sock < 0 )  return RV_SOCKET_ERROR;
329
330                 // Get items
331                 PopulateItemList(sock);
332
333                 printf("The UCC Coke machine.\n\n");
334
335                 // Only get coke slot statuses
336                 for( i = 0; i <= 6; i ++ )
337                 {
338                         const char *status;
339                         switch(gaItems[i].Status)
340                         {
341                         case 0: status = "Avail";       break;
342                         case 1: status = "Sold ";       break;
343                         default:
344                                 status = "Error";
345                                 break;
346                         }
347                         printf("%i - %s %3i %s\n", gaItems[i].ID, status, gaItems[i].Price, gaItems[i].Desc);
348                 }
349
350                 printf("\nMay your pink fish bing into the distance.\n");
351
352                 return 0;
353         }
354
355         //
356         // `dispense acct`
357         // - 
358         if( strcmp(gsTextArgs[0], "acct") == 0 )
359         {
360                 // Connect to server
361                 sock = OpenConnection(gsDispenseServer, giDispensePort);
362                 if( sock < 0 )  return RV_SOCKET_ERROR;
363                 // List accounts?
364                 if( giTextArgc == 1 ) {
365                         ret = Dispense_EnumUsers(sock);
366                         close(sock);
367                         return ret;
368                 }
369                         
370                 // gsTextArgs[1]: Username
371                 
372                 // Alter account?
373                 if( giTextArgc != 2 )
374                 {
375                         if( giTextArgc != 4 ) {
376                                 fprintf(stderr, "`dispense acct` requires a reason\n");
377                                 ShowUsage();
378                                 return RV_ARGUMENTS;
379                         }
380                         
381                         // Authentication required
382                         ret = Authenticate(sock);
383                         if(ret) return ret;
384                         
385                         // gsTextArgs[1]: Username
386                         // gsTextArgs[2]: Ammount
387                         // gsTextArgs[3]: Reason
388                          char   *tmp = NULL;
389                         long int balance = strtol(gsTextArgs[2]+(gsTextArgs[2][0] == '='), &tmp, 10);
390                         if(!tmp || *tmp != '\0') {
391                                 fprintf(stderr, "dispense acct: Value must be a decimal number of cents\n");
392                                 return RV_ARGUMENTS;
393                         }
394                         
395                         if( gsTextArgs[2][0] == '=' ) {
396                                 // Set balance
397                                 ret = Dispense_SetBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]);
398                         }
399                         else {
400                                 // Alter balance
401                                 ret = Dispense_AlterBalance(sock, gsTextArgs[1], balance, gsTextArgs[3]);
402                         }
403                 }
404                 // On error, quit
405                 if( ret ) {
406                         close(sock);
407                         return ret;
408                 }
409                 
410                 // Show user information
411                 ret = Dispense_ShowUser(sock, gsTextArgs[1]);
412                 
413                 close(sock);
414                 return ret;
415         }
416         //
417         // `dispense give`
418         // - "Here, have some money."
419         else if( strcmp(gsTextArgs[0], "give") == 0 )
420         {
421                 if( giTextArgc != 4 ) {
422                         fprintf(stderr, "`dispense give` takes three arguments\n");
423                         ShowUsage();
424                         return RV_ARGUMENTS;
425                 }
426                 
427                 // gsTextArgs[1]: Destination
428                 // gsTextArgs[2]: Ammount
429                 // gsTextArgs[3]: Reason
430                 
431                 // Connect to server
432                 sock = OpenConnection(gsDispenseServer, giDispensePort);
433                 if( sock < 0 )  return RV_SOCKET_ERROR;
434                 
435                 // Authenticate
436                 ret = Authenticate(sock);
437                 if(ret) return ret;
438
439                 char    *tmp = NULL;
440                 int amt = strtol(gsTextArgs[2], &tmp, 10);
441                 if( !tmp || *tmp != '\0' ) {
442                         fprintf(stderr, "dispense give: Balance is invalid, must be decimal number of cents");
443                         return RV_ARGUMENTS;
444                 }
445                 ret = Dispense_Give(sock, gsTextArgs[1], amt, gsTextArgs[3]);
446
447                 close(sock);
448         
449                 return ret;
450         }
451         // 
452         // `dispense user`
453         // - User administration (Admin Only)
454         if( strcmp(gsTextArgs[0], "user") == 0 )
455         {
456                 // Check argument count
457                 if( giTextArgc == 1 ) {
458                         fprintf(stderr, "Error: `dispense user` requires 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                 // Add new user?
472                 if( strcmp(gsTextArgs[1], "add") == 0 )
473                 {
474                         if( giTextArgc != 3 ) {
475                                 fprintf(stderr, "Error: `dispense user add` requires an argument\n");
476                                 ShowUsage();
477                                 return RV_ARGUMENTS;
478                         }
479                         
480                         ret = Dispense_AddUser(sock, gsTextArgs[2]);
481                 }
482                 // Update a user
483                 else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 )
484                 {
485                         if( giTextArgc < 4 || giTextArgc > 5 ) {
486                                 fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
487                                 ShowUsage();
488                                 return RV_ARGUMENTS;
489                         }
490                         
491                         if( giTextArgc == 4 )
492                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], "");
493                         else
494                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]);
495                 }
496                 else
497                 {
498                         fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
499                         ShowUsage();
500                         return RV_ARGUMENTS;
501                 }
502                 close(sock);
503                 return ret;
504         }
505         // Donation!
506         else if( strcmp(gsTextArgs[0], "donate") == 0 )
507         {
508                 // Check argument count
509                 if( giTextArgc != 3 ) {
510                         fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
511                         ShowUsage();
512                         return RV_ARGUMENTS;
513                 }
514                 
515                 // Connect to server
516                 sock = OpenConnection(gsDispenseServer, giDispensePort);
517                 if( sock < 0 )  return RV_SOCKET_ERROR;
518                 
519                 // Attempt authentication
520                 ret = Authenticate(sock);
521                 if(ret) return ret;
522                 
523                 // Do donation
524                 ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]);
525                                 
526                 close(sock);
527
528                 return ret;
529         }
530         // Refund an item
531         else if( strcmp(gsTextArgs[0], "refund") == 0 )
532         {
533                  int     price = 0;
534                 // Check argument count
535                 if( giTextArgc != 3 && giTextArgc != 4 ) {
536                         fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
537                         ShowUsage();
538                         return RV_ARGUMENTS;
539                 }
540         
541                 // Connect to server
542                 sock = OpenConnection(gsDispenseServer, giDispensePort);
543                 if(sock < 0)    return RV_SOCKET_ERROR; 
544
545                 // Attempt authentication
546                 ret = Authenticate(sock);
547                 if(ret) return ret;
548
549                 if( giTextArgc == 4 ) {
550                         price = atoi(gsTextArgs[3]);
551                         if( price <= 0 ) {
552                                 fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
553                                 return RV_ARGUMENTS;
554                         }
555                 }
556
557                 // Username, Item, cost
558                 ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price);
559
560                 // TODO: More
561                 close(sock);
562                 return ret;
563         }
564         // Query an item price
565         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 )
566         {
567                 regmatch_t matches[3];
568                 char    *type;
569                  int    id;
570                 // Check argument count
571                 if( giTextArgc != 2 ) {
572                         fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n");
573                         ShowUsage();
574                         return RV_ARGUMENTS;
575                 }
576                 // Parse item ID
577                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
578                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
579                         return RV_ARGUMENTS;
580                 }
581                 type = gsTextArgs[1] + matches[1].rm_so;
582                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
583                 id = atoi( gsTextArgs[1] + matches[2].rm_so );
584
585                 sock = OpenConnection(gsDispenseServer, giDispensePort);
586                 if( sock < 0 )  return RV_SOCKET_ERROR;
587                 
588                 ret = Dispense_ItemInfo(sock, type, id);
589                 close(sock);
590                 return ret;
591         }
592         // Set slot
593         else if( strcmp(gsTextArgs[0], "slot") == 0 )
594         {
595                 regmatch_t matches[3];
596                 char    *item_type, *newname;
597                  int    item_id, price;
598                 
599                 // Check arguments
600                 if( giTextArgc != 4 ) {
601                         fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
602                         ShowUsage();
603                         return RV_ARGUMENTS;
604                 }
605                 
606                 // Parse arguments
607                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
608                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
609                         return RV_ARGUMENTS;
610                 }
611                 item_type = gsTextArgs[1] + matches[1].rm_so;
612                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
613                 item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
614
615                 // - Price
616                 price = atoi( gsTextArgs[2] );
617                 if( price <= 0 && gsTextArgs[2][0] != '0' ) {
618                         fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
619                         return RV_ARGUMENTS;
620                 }
621                 
622                 // - New name
623                 newname = gsTextArgs[3];
624                 // -- Sanity
625                 {
626                         char *pos;
627                         for( pos = newname; *pos; pos ++ )
628                         {
629                                 if( !isalnum(*pos) && *pos != ' ' ) {
630                                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
631                                         return RV_ARGUMENTS;
632                                 }
633                         }
634                 }
635                 
636                 // Connect & Authenticate
637                 sock = OpenConnection(gsDispenseServer, giDispensePort);
638                 if( sock < 0 )  return RV_SOCKET_ERROR;
639                 ret = Authenticate(sock);
640                 if(ret) return ret;
641                 // Update the slot
642                 ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
643                 
644                 close(sock);
645                 return ret;
646         }
647         // Check a user's pin
648         else if(strcmp(gsTextArgs[0], "pincheck") == 0)
649         {
650                 if( giTextArgc < 2 || giTextArgc > 3 ) {
651                         fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n");
652                         ShowUsage();
653                         return RV_ARGUMENTS;
654                 }
655                 struct passwd   *pwd = getpwuid( getuid() );
656                 gsUserName = strdup(pwd->pw_name);
657                 
658                 const char *pin = gsTextArgs[1];
659                 const char *user = gsUserName;
660                 if( giTextArgc == 3 )
661                         user = gsTextArgs[2];
662
663                 
664                 sock = OpenConnection(gsDispenseServer, giDispensePort);
665                 if( sock < 0 )  return RV_SOCKET_ERROR;
666                 ret = Authenticate(sock);
667                 if(ret) return ret;
668                 
669                 ret = DispenseCheckPin(sock, user, pin);
670                 
671                 close(sock);
672                 return ret;
673         }
674         // Update 'your' pin
675         else if(strcmp(gsTextArgs[0], "pinset") == 0)
676         {
677                 if( giTextArgc != 2 ) {
678                         fprintf(stderr, "Error: `dispense pinset` takes one argument\n");
679                         ShowUsage();
680                         return RV_ARGUMENTS;
681                 }
682                 
683                 const char *pin = gsTextArgs[1];
684                 
685                 sock = OpenConnection(gsDispenseServer, giDispensePort);
686                 if( sock < 0 )  return RV_SOCKET_ERROR;
687                 ret = Authenticate(sock);
688                 if(ret) return ret;
689
690                 ret = DispenseSetPin(sock, pin);
691                 
692                 close(sock);
693                 return ret;
694         }
695         // Item name / pattern
696         else
697         {
698                 gsItemPattern = gsTextArgs[0];
699         }
700         
701         // Connect to server
702         sock = OpenConnection(gsDispenseServer, giDispensePort);
703         if( sock < 0 )  return RV_SOCKET_ERROR;
704
705         // Get the user's balance
706         ret = GetUserBalance(sock);
707         if(ret) return ret;
708
709         // Get items
710         PopulateItemList(sock);
711         
712         // Disconnect from server
713         close(sock);
714         
715         if( gsItemPattern && gsItemPattern[0] )
716         {
717                 regmatch_t matches[3];
718                 // Door (hard coded)
719                 if( strcmp(gsItemPattern, "door") == 0 )
720                 {
721                         // Connect, Authenticate, dispense and close
722                         sock = OpenConnection(gsDispenseServer, giDispensePort);
723                         if( sock < 0 )  return RV_SOCKET_ERROR;
724                         ret = Authenticate(sock);
725                         if(ret) return ret;
726                         ret = DispenseItem(sock, "door", 0);
727                         close(sock);
728                         return ret;
729                 }
730                 // Item id (<type>:<num>)
731                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
732                 {
733                         char    *ident;
734                          int    id;
735                         
736                         // Get and finish ident
737                         ident = gsItemPattern + matches[1].rm_so;
738                         gsItemPattern[matches[1].rm_eo] = '\0';
739                         // Get ID
740                         id = atoi( gsItemPattern + matches[2].rm_so );
741                         
742                         // Connect, Authenticate, dispense and close
743                         sock = OpenConnection(gsDispenseServer, giDispensePort);
744                         if( sock < 0 )  return RV_SOCKET_ERROR;
745                         
746                         Dispense_ItemInfo(sock, ident, id);
747                         
748                         ret = Authenticate(sock);
749                         if(ret) return ret;
750                         ret = DispenseItem(sock, ident, id);
751                         close(sock);
752                         return ret;
753                 }
754                 // Item number (6 = coke)
755                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
756                 {
757                         i = atoi(gsItemPattern);
758                 }
759                 // Item prefix
760                 else
761                 {
762                          int    j;
763                          int    best = -1;
764                         for( i = 0; i < giNumItems; i ++ )
765                         {
766                                 // Prefix match (with case-insensitive match)
767                                 for( j = 0; gsItemPattern[j]; j ++ )
768                                 {
769                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
770                                                 continue;
771                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
772                                                 continue;
773                                         break;
774                                 }
775                                 // Check if the prefix matched
776                                 if( gsItemPattern[j] != '\0' )
777                                         continue;
778                                 
779                                 // Prefect match
780                                 if( gaItems[i].Desc[j] == '\0' ) {
781                                         best = i;
782                                         break;
783                                 }
784                                 
785                                 // Only one match allowed
786                                 if( best == -1 ) {
787                                         best = i;
788                                 }
789                                 else {
790                                         // TODO: Allow ambiguous matches?
791                                         // or just print a wanrning
792                                         printf("Warning - Ambiguous pattern, stopping\n");
793                                         return RV_BAD_ITEM;
794                                 }
795                         }
796                         
797                         // Was a match found?
798                         if( best == -1 )
799                         {
800                                 fprintf(stderr, "No item matches the passed string\n");
801                                 return RV_BAD_ITEM;
802                         }
803                         
804                         i = best;
805                 }
806         }
807         else if( giUIMode != UI_MODE_BASIC )
808         {
809                 i = ShowNCursesUI();
810         }
811         else
812         {
813                 // Very basic dispense interface
814                 for( i = 0; i < giNumItems; i ++ ) {
815                         // Add a separator
816                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
817                                 printf("   ---\n");
818                         
819                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
820                                 gaItems[i].Price, gaItems[i].Desc);
821                 }
822                 printf(" q Quit\n");
823                 for(;;)
824                 {
825                         char    *buf;
826                         
827                         i = -1;
828                         
829                         fgets(buffer, BUFSIZ, stdin);
830                         
831                         buf = trim(buffer);
832                         
833                         if( buf[0] == 'q' )     break;
834                         
835                         i = atoi(buf);
836                         
837                         if( i != 0 || buf[0] == '0' )
838                         {
839                                 if( i < 0 || i >= giNumItems ) {
840                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
841                                         continue;
842                                 }
843                                 break;
844                         }
845                 }
846         }
847         
848         
849         // Check for a valid item ID
850         if( i >= 0 )
851         {
852                  int j;
853                 // Connect, Authenticate, dispense and close
854                 sock = OpenConnection(gsDispenseServer, giDispensePort);
855                 if( sock < 0 )  return RV_SOCKET_ERROR;
856                         
857                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
858                 if(ret) return ret;
859                 
860                 ret = Authenticate(sock);
861                 if(ret) return ret;
862                 
863                 for( j = 0; j < giDispenseCount; j ++ ) {
864                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
865                         if( ret )       break;
866                 }
867                 if( j > 1 ) {
868                         printf("%i items dispensed\n", j);
869                 }
870                 Dispense_ShowUser(sock, gsUserName);
871                 close(sock);
872
873         }
874
875         return ret;
876 }
877
878 // ---------------
879 // --- Helpers ---
880 // ---------------
881 char *trim(char *string)
882 {
883          int    i;
884         
885         while( isspace(*string) )
886                 string ++;
887         
888         for( i = strlen(string); i--; )
889         {
890                 if( isspace(string[i]) )
891                         string[i] = '\0';
892                 else
893                         break;
894         }
895         
896         return string;
897 }
898
899 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
900 {
901          int    ret;
902         
903         ret = regexec(regex, string, nMatches, matches, 0);
904         if( ret && errorMessage ) {
905                 size_t  len = regerror(ret, regex, NULL, 0);
906                 char    errorStr[len];
907                 regerror(ret, regex, errorStr, len);
908                 printf("string = '%s'\n", string);
909                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
910                 exit(-1);
911         }
912         
913         return ret;
914 }
915
916 void CompileRegex(regex_t *regex, const char *pattern, int flags)
917 {
918          int    ret = regcomp(regex, pattern, flags);
919         if( ret ) {
920                 size_t  len = regerror(ret, regex, NULL, 0);
921                 char    errorStr[len];
922                 regerror(ret, regex, errorStr, len);
923                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
924                 exit(-1);
925         }
926 }

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