Implimented pin checks fully
[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
351                 printf("\nMay your pink fish bing into the distance.\n");
352
353                 return 0;
354         }
355
356         //
357         // `dispense acct`
358         // - 
359         if( strcmp(gsTextArgs[0], "acct") == 0 )
360         {
361                 // Connect to server
362                 sock = OpenConnection(gsDispenseServer, giDispensePort);
363                 if( sock < 0 )  return RV_SOCKET_ERROR;
364                 // List accounts?
365                 if( giTextArgc == 1 ) {
366                         ret = Dispense_EnumUsers(sock);
367                         close(sock);
368                         return ret;
369                 }
370                         
371                 // gsTextArgs[1]: Username
372                 
373                 // Alter account?
374                 if( giTextArgc != 2 )
375                 {
376                         if( giTextArgc != 4 ) {
377                                 fprintf(stderr, "`dispense acct` requires a reason\n");
378                                 ShowUsage();
379                                 return RV_ARGUMENTS;
380                         }
381                         
382                         // Authentication required
383                         ret = Authenticate(sock);
384                         if(ret) return ret;
385                         
386                         // gsTextArgs[1]: Username
387                         // gsTextArgs[2]: Ammount
388                         // gsTextArgs[3]: Reason
389                         
390                         if( gsTextArgs[2][0] == '=' ) {
391                                 // Set balance
392                                 if( gsTextArgs[2][1] != '0' && atoi(gsTextArgs[2]+1) == 0 ) {
393                                         fprintf(stderr, "Error: Invalid balance to be set\n");
394                                         exit(1);
395                                 }
396                                 
397                                 ret = Dispense_SetBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]+1), gsTextArgs[3]);
398                         }
399                         else {
400                                 // Alter balance
401                                 ret = Dispense_AlterBalance(sock, gsTextArgs[1], atoi(gsTextArgs[2]), 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                 ret = Dispense_Give(sock, gsTextArgs[1], atoi(gsTextArgs[2]), gsTextArgs[3]);
440
441                 close(sock);
442         
443                 return ret;
444         }
445         // 
446         // `dispense user`
447         // - User administration (Admin Only)
448         if( strcmp(gsTextArgs[0], "user") == 0 )
449         {
450                 // Check argument count
451                 if( giTextArgc == 1 ) {
452                         fprintf(stderr, "Error: `dispense user` requires arguments\n");
453                         ShowUsage();
454                         return RV_ARGUMENTS;
455                 }
456                 
457                 // Connect to server
458                 sock = OpenConnection(gsDispenseServer, giDispensePort);
459                 if( sock < 0 )  return RV_SOCKET_ERROR;
460                 
461                 // Attempt authentication
462                 ret = Authenticate(sock);
463                 if(ret) return ret;
464                 
465                 // Add new user?
466                 if( strcmp(gsTextArgs[1], "add") == 0 )
467                 {
468                         if( giTextArgc != 3 ) {
469                                 fprintf(stderr, "Error: `dispense user add` requires an argument\n");
470                                 ShowUsage();
471                                 return RV_ARGUMENTS;
472                         }
473                         
474                         ret = Dispense_AddUser(sock, gsTextArgs[2]);
475                 }
476                 // Update a user
477                 else if( strcmp(gsTextArgs[1], "type") == 0 || strcmp(gsTextArgs[1], "flags") == 0 )
478                 {
479                         if( giTextArgc < 4 || giTextArgc > 5 ) {
480                                 fprintf(stderr, "Error: `dispense user type` requires two arguments\n");
481                                 ShowUsage();
482                                 return RV_ARGUMENTS;
483                         }
484                         
485                         if( giTextArgc == 4 )
486                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], "");
487                         else
488                                 ret = Dispense_SetUserType(sock, gsTextArgs[2], gsTextArgs[3], gsTextArgs[4]);
489                 }
490                 else
491                 {
492                         fprintf(stderr, "Error: Unknown sub-command for `dispense user`\n");
493                         ShowUsage();
494                         return RV_ARGUMENTS;
495                 }
496                 close(sock);
497                 return ret;
498         }
499         // Donation!
500         else if( strcmp(gsTextArgs[0], "donate") == 0 )
501         {
502                 // Check argument count
503                 if( giTextArgc != 3 ) {
504                         fprintf(stderr, "Error: `dispense donate` requires two arguments\n");
505                         ShowUsage();
506                         return RV_ARGUMENTS;
507                 }
508                 
509                 // Connect to server
510                 sock = OpenConnection(gsDispenseServer, giDispensePort);
511                 if( sock < 0 )  return RV_SOCKET_ERROR;
512                 
513                 // Attempt authentication
514                 ret = Authenticate(sock);
515                 if(ret) return ret;
516                 
517                 // Do donation
518                 ret = Dispense_Donate(sock, atoi(gsTextArgs[1]), gsTextArgs[2]);
519                                 
520                 close(sock);
521
522                 return ret;
523         }
524         // Refund an item
525         else if( strcmp(gsTextArgs[0], "refund") == 0 )
526         {
527                  int     price = 0;
528                 // Check argument count
529                 if( giTextArgc != 3 && giTextArgc != 4 ) {
530                         fprintf(stderr, "Error: `dispense refund` takes 2 or 3 arguments\n");
531                         ShowUsage();
532                         return RV_ARGUMENTS;
533                 }
534         
535                 // Connect to server
536                 sock = OpenConnection(gsDispenseServer, giDispensePort);
537                 if(sock < 0)    return RV_SOCKET_ERROR; 
538
539                 // Attempt authentication
540                 ret = Authenticate(sock);
541                 if(ret) return ret;
542
543                 if( giTextArgc == 4 ) {
544                         price = atoi(gsTextArgs[3]);
545                         if( price <= 0 ) {
546                                 fprintf(stderr, "Error: Override price is invalid (should be > 0)\n");
547                                 return RV_ARGUMENTS;
548                         }
549                 }
550
551                 // Username, Item, cost
552                 ret = Dispense_Refund(sock, gsTextArgs[1], gsTextArgs[2], price);
553
554                 // TODO: More
555                 close(sock);
556                 return ret;
557         }
558         // Query an item price
559         else if( strcmp(gsTextArgs[0], "iteminfo") == 0 )
560         {
561                 regmatch_t matches[3];
562                 char    *type;
563                  int    id;
564                 // Check argument count
565                 if( giTextArgc != 2 ) {
566                         fprintf(stderr, "Error: `dispense iteminfo` requires an argument\n");
567                         ShowUsage();
568                         return RV_ARGUMENTS;
569                 }
570                 // Parse item ID
571                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
572                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
573                         return RV_ARGUMENTS;
574                 }
575                 type = gsTextArgs[1] + matches[1].rm_so;
576                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
577                 id = atoi( gsTextArgs[1] + matches[2].rm_so );
578
579                 sock = OpenConnection(gsDispenseServer, giDispensePort);
580                 if( sock < 0 )  return RV_SOCKET_ERROR;
581                 
582                 ret = Dispense_ItemInfo(sock, type, id);
583                 close(sock);
584                 return ret;
585         }
586         // Set slot
587         else if( strcmp(gsTextArgs[0], "slot") == 0 )
588         {
589                 regmatch_t matches[3];
590                 char    *item_type, *newname;
591                  int    item_id, price;
592                 
593                 // Check arguments
594                 if( giTextArgc != 4 ) {
595                         fprintf(stderr, "Error: `dispense slot` takes three arguments\n");
596                         ShowUsage();
597                         return RV_ARGUMENTS;
598                 }
599                 
600                 // Parse arguments
601                 if( RunRegex(&gUserItemIdentRegex, gsTextArgs[1], 3, matches, NULL) != 0 ) {
602                         fprintf(stderr, "Error: Invalid item ID passed (<type>:<id> expected)\n");
603                         return RV_ARGUMENTS;
604                 }
605                 item_type = gsTextArgs[1] + matches[1].rm_so;
606                 gsTextArgs[1][ matches[1].rm_eo ] = '\0';
607                 item_id = atoi( gsTextArgs[1] + matches[2].rm_so );
608
609                 // - Price
610                 price = atoi( gsTextArgs[2] );
611                 if( price <= 0 && gsTextArgs[2][0] != '0' ) {
612                         fprintf(stderr, "Error: Invalid price passed (must be >= 0)\n");
613                         return RV_ARGUMENTS;
614                 }
615                 
616                 // - New name
617                 newname = gsTextArgs[3];
618                 // -- Sanity
619                 {
620                         char *pos;
621                         for( pos = newname; *pos; pos ++ )
622                         {
623                                 if( !isalnum(*pos) && *pos != ' ' ) {
624                                         fprintf(stderr, "Error: You should only have letters, numbers and spaces in an item name\n");
625                                         return RV_ARGUMENTS;
626                                 }
627                         }
628                 }
629                 
630                 // Connect & Authenticate
631                 sock = OpenConnection(gsDispenseServer, giDispensePort);
632                 if( sock < 0 )  return RV_SOCKET_ERROR;
633                 ret = Authenticate(sock);
634                 if(ret) return ret;
635                 // Update the slot
636                 ret = Dispense_SetItem(sock, item_type, item_id, price, newname);
637                 
638                 close(sock);
639                 return ret;
640         }
641         // Check a user's pin
642         else if(strcmp(gsTextArgs[0], "pincheck") == 0)
643         {
644                 if( giTextArgc < 2 || giTextArgc > 3 ) {
645                         fprintf(stderr, "Error: `dispense pincheck` takes one/two arguments\n");
646                         ShowUsage();
647                         return RV_ARGUMENTS;
648                 }
649                 struct passwd   *pwd = getpwuid( getuid() );
650                 gsUserName = strdup(pwd->pw_name);
651                 
652                 const char *pin = gsTextArgs[1];
653                 const char *user = gsUserName;
654                 if( giTextArgc == 3 )
655                         user = gsTextArgs[2];
656
657                 
658                 sock = OpenConnection(gsDispenseServer, giDispensePort);
659                 if( sock < 0 )  return RV_SOCKET_ERROR;
660                 ret = Authenticate(sock);
661                 if(ret) return ret;
662                 
663                 ret = DispenseCheckPin(sock, user, pin);
664                 
665                 close(sock);
666                 return ret;
667         }
668         // Update 'your' pin
669         else if(strcmp(gsTextArgs[0], "pinset") == 0)
670         {
671                 if( giTextArgc != 2 ) {
672                         fprintf(stderr, "Error: `dispense pinset` takes one argument\n");
673                         ShowUsage();
674                         return RV_ARGUMENTS;
675                 }
676                 
677                 const char *pin = gsTextArgs[1];
678                 
679                 sock = OpenConnection(gsDispenseServer, giDispensePort);
680                 if( sock < 0 )  return RV_SOCKET_ERROR;
681                 ret = Authenticate(sock);
682                 if(ret) return ret;
683
684                 ret = DispenseSetPin(sock, pin);
685                 
686                 close(sock);
687                 return ret;
688         }
689         // Item name / pattern
690         else
691         {
692                 gsItemPattern = gsTextArgs[0];
693         }
694         
695         // Connect to server
696         sock = OpenConnection(gsDispenseServer, giDispensePort);
697         if( sock < 0 )  return RV_SOCKET_ERROR;
698
699         // Get the user's balance
700         ret = GetUserBalance(sock);
701         if(ret) return ret;
702
703         // Get items
704         PopulateItemList(sock);
705         
706         // Disconnect from server
707         close(sock);
708         
709         if( gsItemPattern && gsItemPattern[0] )
710         {
711                 regmatch_t matches[3];
712                 // Door (hard coded)
713                 if( strcmp(gsItemPattern, "door") == 0 )
714                 {
715                         // Connect, Authenticate, dispense and close
716                         sock = OpenConnection(gsDispenseServer, giDispensePort);
717                         if( sock < 0 )  return RV_SOCKET_ERROR;
718                         ret = Authenticate(sock);
719                         if(ret) return ret;
720                         ret = DispenseItem(sock, "door", 0);
721                         close(sock);
722                         return ret;
723                 }
724                 // Item id (<type>:<num>)
725                 else if( RunRegex(&gUserItemIdentRegex, gsItemPattern, 3, matches, NULL) == 0 )
726                 {
727                         char    *ident;
728                          int    id;
729                         
730                         // Get and finish ident
731                         ident = gsItemPattern + matches[1].rm_so;
732                         gsItemPattern[matches[1].rm_eo] = '\0';
733                         // Get ID
734                         id = atoi( gsItemPattern + matches[2].rm_so );
735                         
736                         // Connect, Authenticate, dispense and close
737                         sock = OpenConnection(gsDispenseServer, giDispensePort);
738                         if( sock < 0 )  return RV_SOCKET_ERROR;
739                         
740                         Dispense_ItemInfo(sock, ident, id);
741                         
742                         ret = Authenticate(sock);
743                         if(ret) return ret;
744                         ret = DispenseItem(sock, ident, id);
745                         close(sock);
746                         return ret;
747                 }
748                 // Item number (6 = coke)
749                 else if( strcmp(gsItemPattern, "0") == 0 || atoi(gsItemPattern) > 0 )
750                 {
751                         i = atoi(gsItemPattern);
752                 }
753                 // Item prefix
754                 else
755                 {
756                          int    j;
757                          int    best = -1;
758                         for( i = 0; i < giNumItems; i ++ )
759                         {
760                                 // Prefix match (with case-insensitive match)
761                                 for( j = 0; gsItemPattern[j]; j ++ )
762                                 {
763                                         if( gaItems[i].Desc[j] == gsItemPattern[j] )
764                                                 continue;
765                                         if( tolower(gaItems[i].Desc[j]) == tolower(gsItemPattern[j]) )
766                                                 continue;
767                                         break;
768                                 }
769                                 // Check if the prefix matched
770                                 if( gsItemPattern[j] != '\0' )
771                                         continue;
772                                 
773                                 // Prefect match
774                                 if( gaItems[i].Desc[j] == '\0' ) {
775                                         best = i;
776                                         break;
777                                 }
778                                 
779                                 // Only one match allowed
780                                 if( best == -1 ) {
781                                         best = i;
782                                 }
783                                 else {
784                                         // TODO: Allow ambiguous matches?
785                                         // or just print a wanrning
786                                         printf("Warning - Ambiguous pattern, stopping\n");
787                                         return RV_BAD_ITEM;
788                                 }
789                         }
790                         
791                         // Was a match found?
792                         if( best == -1 )
793                         {
794                                 fprintf(stderr, "No item matches the passed string\n");
795                                 return RV_BAD_ITEM;
796                         }
797                         
798                         i = best;
799                 }
800         }
801         else if( giUIMode != UI_MODE_BASIC )
802         {
803                 i = ShowNCursesUI();
804         }
805         else
806         {
807                 // Very basic dispense interface
808                 for( i = 0; i < giNumItems; i ++ ) {
809                         // Add a separator
810                         if( i && strcmp(gaItems[i].Type, gaItems[i-1].Type) != 0 )
811                                 printf("   ---\n");
812                         
813                         printf("%2i %s:%i\t%3i %s\n", i, gaItems[i].Type, gaItems[i].ID,
814                                 gaItems[i].Price, gaItems[i].Desc);
815                 }
816                 printf(" q Quit\n");
817                 for(;;)
818                 {
819                         char    *buf;
820                         
821                         i = -1;
822                         
823                         fgets(buffer, BUFSIZ, stdin);
824                         
825                         buf = trim(buffer);
826                         
827                         if( buf[0] == 'q' )     break;
828                         
829                         i = atoi(buf);
830                         
831                         if( i != 0 || buf[0] == '0' )
832                         {
833                                 if( i < 0 || i >= giNumItems ) {
834                                         printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
835                                         continue;
836                                 }
837                                 break;
838                         }
839                 }
840         }
841         
842         
843         // Check for a valid item ID
844         if( i >= 0 )
845         {
846                  int j;
847                 // Connect, Authenticate, dispense and close
848                 sock = OpenConnection(gsDispenseServer, giDispensePort);
849                 if( sock < 0 )  return RV_SOCKET_ERROR;
850                         
851                 ret = Dispense_ItemInfo(sock, gaItems[i].Type, gaItems[i].ID);
852                 if(ret) return ret;
853                 
854                 ret = Authenticate(sock);
855                 if(ret) return ret;
856                 
857                 for( j = 0; j < giDispenseCount; j ++ ) {
858                         ret = DispenseItem(sock, gaItems[i].Type, gaItems[i].ID);
859                         if( ret )       break;
860                 }
861                 if( j > 1 ) {
862                         printf("%i items dispensed\n", j);
863                 }
864                 Dispense_ShowUser(sock, gsUserName);
865                 close(sock);
866
867         }
868
869         return ret;
870 }
871
872 // ---------------
873 // --- Helpers ---
874 // ---------------
875 char *trim(char *string)
876 {
877          int    i;
878         
879         while( isspace(*string) )
880                 string ++;
881         
882         for( i = strlen(string); i--; )
883         {
884                 if( isspace(string[i]) )
885                         string[i] = '\0';
886                 else
887                         break;
888         }
889         
890         return string;
891 }
892
893 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
894 {
895          int    ret;
896         
897         ret = regexec(regex, string, nMatches, matches, 0);
898         if( ret && errorMessage ) {
899                 size_t  len = regerror(ret, regex, NULL, 0);
900                 char    errorStr[len];
901                 regerror(ret, regex, errorStr, len);
902                 printf("string = '%s'\n", string);
903                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
904                 exit(-1);
905         }
906         
907         return ret;
908 }
909
910 void CompileRegex(regex_t *regex, const char *pattern, int flags)
911 {
912          int    ret = regcomp(regex, pattern, flags);
913         if( ret ) {
914                 size_t  len = regerror(ret, regex, NULL, 0);
915                 char    errorStr[len];
916                 regerror(ret, regex, errorStr, len);
917                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
918                 exit(-1);
919         }
920 }

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