5f5f1c85a2e33ce099e7e86a9f2b1b8bf4763e64
[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
19 #include <unistd.h>     // close
20 #include <netdb.h>      // gethostbyname
21 #include <pwd.h>        // getpwuids
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25
26 // === TYPES ===
27 typedef struct sItem {
28         char    *Ident;
29         char    *Desc;
30          int    Price;
31 }       tItem;
32
33 // === PROTOTYPES ===
34  int    ShowNCursesUI(void);
35
36  int    sendf(int Socket, const char *Format, ...);
37  int    OpenConnection(const char *Host, int Port);
38 void    Authenticate(int Socket);
39 char    *trim(char *string);
40  int    RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
41 void    CompileRegex(regex_t *regex, const char *pattern, int flags);
42
43 // === GLOBALS ===
44 char    *gsDispenseServer = "localhost";
45  int    giDispensePort = 11020;
46 tItem   *gaItems;
47  int    giNumItems;
48 regex_t gArrayRegex;
49 regex_t gItemRegex;
50
51 // === CODE ===
52 int main(int argc, char *argv[])
53 {
54          int    sock;
55          int    i, responseCode, len;
56         char    buffer[BUFSIZ];
57         
58         // -- Create regular expressions
59         // > Code Type Count ...
60         CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED);     //
61         // > Code Type Ident Price Desc
62         CompileRegex(&gItemRegex, "^([0-9]{3})\\s+(.+?)\\s+(.+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
63         
64         // Connect to server
65         sock = OpenConnection(gsDispenseServer, giDispensePort);
66         if( sock < 0 )  return -1;
67
68         // Determine what to do
69         if( argc > 1 )
70         {
71                 if( strcmp(argv[1], "acct") == 0 )
72                 {
73                         // Alter account
74                         // List accounts
75                         return 0;
76                 }
77         }
78
79         // Ask server for stock list
80         send(sock, "ENUM_ITEMS\n", 11, 0);
81         len = recv(sock, buffer, BUFSIZ-1, 0);
82         buffer[len] = '\0';
83         
84         trim(buffer);
85         
86         printf("Output: %s\n", buffer);
87         
88         responseCode = atoi(buffer);
89         if( responseCode != 201 )
90         {
91                 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
92                 return -1;
93         }
94         
95         // Get item list
96         {
97                 char    *itemType, *itemStart;
98                  int    count;
99                 regmatch_t      matches[4];
100                 
101                 // Expected format: 201 Items <count> <item1> <item2> ...
102                 RunRegex(&gArrayRegex, buffer, 4, matches, "Malformed server response");
103                 
104                 itemType = &buffer[ matches[2].rm_so ]; buffer[ matches[2].rm_eo ] = '\0';
105                 count = atoi( &buffer[ matches[3].rm_so ] );
106                 
107                 // Check array type
108                 if( strcmp(itemType, "Items") != 0 ) {
109                         // What the?!
110                         fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
111                                 itemType);
112                         return -1;
113                 }
114                 
115                 itemStart = &buffer[ matches[3].rm_eo ];
116                 
117                 gaItems = malloc( count * sizeof(tItem) );
118                 
119                 for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
120                 {
121                         char    *next = strchr( ++itemStart, ' ' );
122                         if( next )      *next = '\0';
123                         gaItems[giNumItems].Ident = strdup(itemStart);
124                         itemStart = next;
125                 }
126         }
127         
128         // Get item information
129         for( i = 0; i < giNumItems; i ++ )
130         {
131                 regmatch_t      matches[6];
132                 
133                 // Print item Ident
134                 printf("%2i %s\t", i, gaItems[i].Ident);
135                 
136                 // Get item info
137                 sendf(sock, "ITEM_INFO %s\n", gaItems[i].Ident);
138                 len = recv(sock, buffer, BUFSIZ-1, 0);
139                 buffer[len] = '\0';
140                 trim(buffer);
141                 
142                 responseCode = atoi(buffer);
143                 if( responseCode != 202 ) {
144                         fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
145                         return -1;
146                 }
147                 
148                 RunRegex(&gItemRegex, buffer, 6, matches, "Malformed server response");
149                 
150                 buffer[ matches[3].rm_eo ] = '\0';
151                 
152                 gaItems[i].Price = atoi( buffer + matches[4].rm_so );
153                 gaItems[i].Desc = strdup( buffer + matches[5].rm_so );
154                 
155                 printf("%3i %s\n", gaItems[i].Price, gaItems[i].Desc);
156         }
157         
158         Authenticate(sock);
159         
160         // and choose what to dispense
161         // TODO: ncurses interface (with separation between item classes)
162         // - Hmm... that would require standardising the item ID to be <class>:<index>
163         // Oh, why not :)
164         
165         #if 1
166         i = ShowNCursesUI();
167         #else
168         
169         for(;;)
170         {
171                 char    *buf;
172                 
173                 fgets(buffer, BUFSIZ, stdin);
174                 
175                 buf = trim(buffer);
176                 
177                 if( buf[0] == 'q' )     break;
178                 
179                 i = atoi(buf);
180                 
181                 printf("buf = '%s', atoi(buf) = %i\n", buf, i);
182                 
183                 if( i != 0 || buf[0] == '0' )
184                 {
185                         printf("i = %i\n", i);
186                         
187                         if( i < 0 || i >= giNumItems ) {
188                                 printf("Bad item (should be between 0 and %i)\n", giNumItems);
189                                 continue;
190                         }
191                         break;
192                 }
193         }
194         #endif
195         
196         if( i >= 0 )
197         {       
198                 // Dispense!
199                 sendf(sock, "DISPENSE %s\n", gaItems[i].Ident);
200                 
201                 len = recv(sock, buffer, BUFSIZ-1, 0);
202                 buffer[len] = '\0';
203                 trim(buffer);
204                 
205                 responseCode = atoi(buffer);
206                 switch( responseCode )
207                 {
208                 case 200:
209                         printf("Dispense OK\n");
210                         break;
211                 case 401:
212                         printf("Not authenticated\n");
213                         break;
214                 case 402:
215                         printf("Insufficient balance\n");
216                         break;
217                 case 406:
218                         printf("Bad item name, bug report\n");
219                         break;
220                 case 500:
221                         printf("Item failed to dispense, is the slot empty?\n");
222                         break;
223                 default:
224                         printf("Unknown response code %i\n", responseCode);
225                         break;
226                 }
227         }
228
229         close(sock);
230
231         return 0;
232 }
233
234 /**
235  */
236 int ShowNCursesUI(void)
237 {
238          int    ch;
239          int    i, times;
240          int    xBase, yBase;
241         const int       displayMinWidth = 34;
242         const int       displayMinItems = 8;
243         char    *titleString = "Dispense";
244          int    titleStringLen = strlen(titleString);
245          int    itemCount = displayMinItems;
246          int    itemBase = 0;
247          
248          int    height = itemCount + 3;
249          int    width = displayMinWidth;
250          
251         // Enter curses mode
252         initscr();
253         raw(); noecho();
254         
255         xBase = COLS/2 - width/2;
256         yBase = LINES/2 - height/2;
257         
258         for( ;; )
259         {
260                 // Header
261                 move( yBase, xBase );
262                 addch('/');
263                 times = width/2 - titleStringLen/2 - 2;
264                 while(times --) addch('-');
265                 addch(' ');
266                 addstr(titleString);
267                 addch(' ');
268                 times = width/2 - titleStringLen/2 - 2;
269                 while(times --) addch('-');
270                 addch('\\');
271                 
272                 // Items
273                 for( i = 0; i < itemCount; i ++ )
274                 {
275                          int    _x, _y;
276                         move( yBase + 1 + i, xBase );
277                         addch('|');
278                         addch(' ');
279                         
280                         // Check for ... row
281                         if( i == 0 && itemBase > 0 ) {
282                                 printw("   ...");
283                                 times = width - 1 - 8;
284                                 while(times--)  addch(' ');
285                         }
286                         else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
287                                 printw("   ...");
288                                 times = width - 1 - 8;
289                                 while(times--)  addch(' ');
290                         }
291                         // Show an item
292                         else {
293                                 if( itemBase + i < 0 || itemBase + i >= giNumItems ) {
294                                         printw("%02i %i OOR", itemBase + i, i);
295                                         continue ;
296                                 }
297                                 printw("%02i %s", itemBase + i, gaItems[itemBase + i].Desc);
298                                 
299                                 getyx(stdscr, _y, _x);
300                                 times = width - 6 - (_x - xBase);       // TODO: Better handling for large prices
301                                 while(times--)  addch(' ');
302                                 printw("%4i ", gaItems[itemBase + i].Price);
303                         }
304                         
305                         // Scrollbar (if needed)
306                         if( giNumItems > itemCount ) {
307                                 if( i == 0 ) {
308                                         addch('A');
309                                 }
310                                 else if( i == itemCount - 1 ) {
311                                         addch('V');
312                                 }
313                                 else {
314                                          int    percentage = itemBase * 100 / (giNumItems-itemCount);
315                                         if( i-1 == percentage*(itemCount-3)/100 ) {
316                                                 addch('#');
317                                         }
318                                         else {
319                                                 addch('|');
320                                         }
321                                 }
322                         }
323                         else {
324                                 addch('|');
325                         }
326                 }
327                 
328                 // Footer
329                 move( yBase + 1 + itemCount, xBase );
330                 addch('\\');
331                 times = width/2 - titleStringLen/2 - 2;
332                 while(times --) addch('-');
333                 addch(' ');
334                 addstr(titleString);
335                 addch(' ');
336                 times = width/2 - titleStringLen/2 - 2;
337                 while(times --) addch('-');
338                 addch('/');
339                 
340                 move( yBase + 1 + itemCount + 1, xBase );
341                 {
342                          int    count = itemCount-2;
343                          int    ofs = itemBase;
344                         if( itemBase == 0 )     count ++;
345                         else    ofs ++;
346                         if( itemBase == giNumItems-itemCount) {
347                                 count ++;
348                                 ofs ++;
349                         }
350                         printw("%i - %i / %i items", itemBase, itemBase+count, giNumItems);
351                 }
352                 
353                 ch = getch();
354                 
355                 if( ch == '\x1B' ) {
356                         ch = getch();
357                         if( ch == '[' ) {
358                                 ch = getch();
359                                 
360                                 switch(ch)
361                                 {
362                                 case 'B':
363                                         if( itemBase < giNumItems - (itemCount) )
364                                                 itemBase ++;
365                                         break;
366                                 case 'A':
367                                         if( itemBase > 0 )
368                                                 itemBase --;
369                                         break;
370                                 }
371                         }
372                         else {
373                                 
374                         }
375                 }
376                 else {
377                         break;
378                 }
379                 
380         }
381         
382         
383         // Leave
384         endwin();
385         return -1;
386 }
387
388 // === HELPERS ===
389 int sendf(int Socket, const char *Format, ...)
390 {
391         va_list args;
392          int    len;
393         
394         va_start(args, Format);
395         len = vsnprintf(NULL, 0, Format, args);
396         va_end(args);
397         
398         {
399                 char    buf[len+1];
400                 va_start(args, Format);
401                 vsnprintf(buf, len+1, Format, args);
402                 va_end(args);
403                 
404                 return send(Socket, buf, len, 0);
405         }
406 }
407
408 int OpenConnection(const char *Host, int Port)
409 {
410         struct hostent  *host;
411         struct sockaddr_in      serverAddr;
412          int    sock;
413         
414         host = gethostbyname(Host);
415         if( !host ) {
416                 fprintf(stderr, "Unable to look up '%s'\n", Host);
417                 return -1;
418         }
419         
420         memset(&serverAddr, 0, sizeof(serverAddr));
421         
422         serverAddr.sin_family = AF_INET;        // IPv4
423         // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
424         serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
425         serverAddr.sin_port = htons(Port);
426         
427         sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
428         if( sock < 0 ) {
429                 fprintf(stderr, "Failed to create socket\n");
430                 return -1;
431         }
432         
433         #if USE_AUTOAUTH
434         {
435                 struct sockaddr_in      localAddr;
436                 memset(&localAddr, 0, sizeof(localAddr));
437                 localAddr.sin_family = AF_INET; // IPv4
438                 localAddr.sin_port = 1023;      // IPv4
439                 // Attempt to bind to low port for autoauth
440                 bind(sock, &localAddr, sizeof(localAddr));
441         }
442         #endif
443         
444         if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
445                 fprintf(stderr, "Failed to connect to server\n");
446                 return -1;
447         }
448         
449         return sock;
450 }
451
452 void Authenticate(int Socket)
453 {
454         struct passwd   *pwd;
455         char    buf[512];
456          int    responseCode;
457         
458         // Get user name
459         pwd = getpwuid( getuid() );
460         
461         // Attempt automatic authentication
462         sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
463         
464         // Check if it worked
465         recv(Socket, buf, 511, 0);
466         trim(buf);
467         
468         responseCode = atoi(buf);
469         switch( responseCode )
470         {
471         case 200:       // Authenticated, return :)
472                 return ;
473         case 401:       // Untrusted, attempt password authentication
474                 break;
475         case 404:       // Bad Username
476                 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
477                 exit(-1);
478         default:
479                 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
480                 printf("%s\n", buf);
481                 exit(-1);
482         }
483         
484         printf("%s\n", buf);
485 }
486
487 char *trim(char *string)
488 {
489          int    i;
490         
491         while( isspace(*string) )
492                 string ++;
493         
494         for( i = strlen(string); i--; )
495         {
496                 if( isspace(string[i]) )
497                         string[i] = '\0';
498                 else
499                         break;
500         }
501         
502         return string;
503 }
504
505 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
506 {
507          int    ret;
508         
509         ret = regexec(regex, string, nMatches, matches, 0);
510         if( ret ) {
511                 size_t  len = regerror(ret, regex, NULL, 0);
512                 char    errorStr[len];
513                 regerror(ret, regex, errorStr, len);
514                 printf("string = '%s'\n", string);
515                 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
516                 exit(-1);
517         }
518         
519         return ret;
520 }
521
522 void CompileRegex(regex_t *regex, const char *pattern, int flags)
523 {
524          int    ret = regcomp(regex, pattern, flags);
525         if( ret ) {
526                 size_t  len = regerror(ret, regex, NULL, 0);
527                 char    errorStr[len];
528                 regerror(ret, regex, errorStr, len);
529                 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);
530                 exit(-1);
531         }
532 }

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