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

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