3 * UCC (University [of WA] Computer Club) Electronic Accounting System
6 * main.c - Core and Initialisation
8 * This file is licenced under the 3-clause BSD Licence. See the file
9 * COPYING for full details.
14 #include <ctype.h> // isspace
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 #include <openssl/sha.h> // SHA1
27 #define USE_NCURSES_INTERFACE 0
30 typedef struct sItem {
38 int ShowNCursesUI(void);
39 void ShowItemAt(int Row, int Col, int Width, int Index);
40 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
41 // --- Coke Server Communication ---
42 int OpenConnection(const char *Host, int Port);
43 int Authenticate(int Socket);
44 void PopulateItemList(int Socket);
45 int DispenseItem(int Socket, int ItemID);
47 int sendf(int Socket, const char *Format, ...);
48 char *trim(char *string);
49 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
50 void CompileRegex(regex_t *regex, const char *pattern, int flags);
53 char *gsDispenseServer = "localhost";
54 int giDispensePort = 11020;
57 regex_t gArrayRegex, gItemRegex, gSaltRegex;
59 char *gsOverrideUser; //!< '-u' argument (dispense as another user)
60 int gbUseNCurses = 0; //!< '-G' Use the NCurses GUI?
63 int main(int argc, char *argv[])
69 // -- Create regular expressions
70 // > Code Type Count ...
71 CompileRegex(&gArrayRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([0-9]+)", REG_EXTENDED); //
72 // > Code Type Ident Price Desc
73 CompileRegex(&gItemRegex, "^([0-9]{3})\\s+([A-Za-z]+)\\s+([A-Za-z0-9:]+?)\\s+([0-9]+)\\s+(.+)$", REG_EXTENDED);
75 CompileRegex(&gSaltRegex, "^([0-9]{3})\\s+(.+)\\s+(.+)$", REG_EXTENDED);
78 sock = OpenConnection(gsDispenseServer, giDispensePort);
79 if( sock < 0 ) return -1;
85 for( i = 1; i < argc; i ++ )
93 case 'u': // Override User
94 gsOverrideUser = argv[++i];
105 if( strcmp(argv[1], "acct") == 0 ) {
111 // Item name / pattern
116 PopulateItemList(sock);
124 for( i = 0; i < giNumItems; i ++ ) {
125 printf("%2i %s\t%3i %s\n", i, gaItems[i].Ident, gaItems[i].Price, gaItems[i].Desc);
134 fgets(buffer, BUFSIZ, stdin);
138 if( buf[0] == 'q' ) break;
142 if( i != 0 || buf[0] == '0' )
144 if( i < 0 || i >= giNumItems ) {
145 printf("Bad item %i (should be between 0 and %i)\n", i, giNumItems);
153 // Check for a valid item ID
155 DispenseItem(sock, i);
162 // -------------------
163 // --- NCurses GUI ---
164 // -------------------
166 * \brief Render the NCurses UI
168 int ShowNCursesUI(void)
170 // TODO: ncurses interface (with separation between item classes)
171 // - Hmm... that would require standardising the item ID to be <class>:<index>
176 const int displayMinWidth = 40;
177 const int displayMinItems = 8;
178 char *titleString = "Dispense";
179 int itemCount = displayMinItems;
182 int ret = -2; // -2: Used for marking "no return yet"
191 // - 6: randomly chosen (Need at least 3)
192 itemCount = LINES - 6;
193 if( itemCount > giNumItems )
194 itemCount = giNumItems;
197 height = itemCount + 3;
198 width = displayMinWidth;
201 xBase = COLS/2 - width/2;
202 yBase = LINES/2 - height/2;
207 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
210 for( i = 0; i < itemCount; i ++ )
212 move( yBase + 1 + i, xBase );
214 if( currentItem == itemBase + i ) {
222 // - Oh god, magic numbers!
223 if( i == 0 && itemBase > 0 ) {
225 times = width-1 - 8 - 3;
226 while(times--) addch(' ');
228 else if( i == itemCount - 1 && itemBase < giNumItems - itemCount ) {
230 times = width-1 - 8 - 3;
231 while(times--) addch(' ');
235 ShowItemAt( yBase + 1 + i, xBase + 5, width - 7, itemBase + i);
239 // Scrollbar (if needed)
240 if( giNumItems > itemCount ) {
244 else if( i == itemCount - 1 ) {
248 int percentage = itemBase * 100 / (giNumItems-itemCount);
249 if( i-1 == percentage*(itemCount-3)/100 ) {
263 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
276 //if( itemBase < giNumItems - (itemCount) )
278 if( currentItem < giNumItems - 1 )
280 if( itemBase + itemCount - 1 <= currentItem && itemBase + itemCount < giNumItems )
286 if( currentItem > 0 )
288 if( itemBase + 1 > currentItem && itemBase > 0 )
304 ret = -1; // -1: Return with no dispense
308 // Check if the return value was changed
309 if( ret != -2 ) break;
321 * \brief Show item \a Index at (\a Col, \a Row)
322 * \note Part of the NCurses UI
324 void ShowItemAt(int Row, int Col, int Width, int Index)
332 if( Index < 0 || Index >= giNumItems ) {
337 name = gaItems[Index].Desc;
338 price = gaItems[Index].Price;
341 printw("%02i %s", Index, name);
343 getyx(stdscr, _y, _x);
344 // Assumes max 4 digit prices
345 times = Width - 4 - (_x - Col); // TODO: Better handling for large prices
346 while(times--) addch(' ');
347 printw("%4i", price);
351 * \brief Print a three-part string at the specified position (formatted)
352 * \note NCurses UI Helper
354 * Prints \a Left on the left of the area, \a Right on the righthand side
355 * and \a Mid in the middle of the area. These are padded with \a Pad1
356 * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
358 * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
359 * and the arguments to these are read in that order.
361 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
362 const char *Mid, char Pad2, const char *Right, ...)
364 int lLen, mLen, rLen;
369 // Get the length of the strings
370 va_start(args, Right);
371 lLen = vsnprintf(NULL, 0, Left, args);
372 mLen = vsnprintf(NULL, 0, Mid, args);
373 rLen = vsnprintf(NULL, 0, Right, args);
377 if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
378 return ; // TODO: What to do?
384 va_start(args, Right);
388 vsnprintf(tmp, lLen+1, Left, args);
392 times = Width/2 - mLen/2 - lLen;
393 while(times--) addch(Pad1);
397 vsnprintf(tmp, mLen+1, Mid, args);
401 times = Width/2 - mLen/2 - rLen;
402 while(times--) addch(Pad2);
406 vsnprintf(tmp, rLen+1, Right, args);
411 // ---------------------
412 // --- Coke Protocol ---
413 // ---------------------
414 int OpenConnection(const char *Host, int Port)
416 struct hostent *host;
417 struct sockaddr_in serverAddr;
420 host = gethostbyname(Host);
422 fprintf(stderr, "Unable to look up '%s'\n", Host);
426 memset(&serverAddr, 0, sizeof(serverAddr));
428 serverAddr.sin_family = AF_INET; // IPv4
429 // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
430 serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
431 serverAddr.sin_port = htons(Port);
433 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
435 fprintf(stderr, "Failed to create socket\n");
441 struct sockaddr_in localAddr;
442 memset(&localAddr, 0, sizeof(localAddr));
443 localAddr.sin_family = AF_INET; // IPv4
444 localAddr.sin_port = 1023; // IPv4
445 // Attempt to bind to low port for autoauth
446 bind(sock, &localAddr, sizeof(localAddr));
450 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
451 fprintf(stderr, "Failed to connect to server\n");
459 * \brief Authenticate with the server
460 * \return Boolean Failure
462 int Authenticate(int Socket)
469 regmatch_t matches[4];
472 pwd = getpwuid( getuid() );
474 // Attempt automatic authentication
475 sendf(Socket, "AUTOAUTH %s\n", pwd->pw_name);
477 // Check if it worked
478 recv(Socket, buf, 511, 0);
481 responseCode = atoi(buf);
482 switch( responseCode )
484 case 200: // Authenticated, return :)
486 case 401: // Untrusted, attempt password authentication
487 sendf(Socket, "USER %s\n", pwd->pw_name);
488 printf("Using username %s\n", pwd->pw_name);
490 recv(Socket, buf, 511, 0);
493 // Expected format: 100 SALT <something> ...
495 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
496 responseCode = atoi(buf);
497 if( responseCode != 100 ) {
498 fprintf(stderr, "Unknown repsonse code %i from server\n", responseCode);
503 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
504 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
505 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
511 // Give three attempts
512 for( i = 0; i < 3; i ++ )
514 int ofs = strlen(pwd->pw_name)+strlen(salt);
516 char *pass = getpass("Password: ");
519 // Create hash string
520 // <username><salt><hash>
521 strcpy(tmp, pwd->pw_name);
523 SHA1( (unsigned char*)pass, strlen(pass), h );
524 memcpy(tmp+ofs, h, 20);
527 SHA1( (unsigned char*)tmp, ofs+20, h );
528 sprintf(buf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
529 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
530 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
532 fflush(stdout); // Debug
535 sendf(Socket, "PASS %s\n", buf);
536 recv(Socket, buf, 511, 0);
538 responseCode = atoi(buf);
540 if( responseCode == 200 ) break;
541 // Bad username/password
542 if( responseCode == 401 ) continue;
544 fprintf(stderr, "Unknown repsonse code %i from server\n", responseCode);
547 return 2; // 2 = Bad Password
549 case 404: // Bad Username
550 fprintf(stderr, "Bad Username '%s'\n", pwd->pw_name);
554 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
560 return 0; // Seems OK
563 void PopulateItemList(int Socket)
569 char *itemType, *itemStart;
571 regmatch_t matches[4];
573 // Ask server for stock list
574 send(Socket, "ENUM_ITEMS\n", 11, 0);
575 len = recv(Socket, buffer, BUFSIZ-1, 0);
580 //printf("Output: %s\n", buffer);
582 responseCode = atoi(buffer);
583 if( responseCode != 201 ) {
584 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
590 // Expected format: 201 Items <count> <item1> <item2> ...
591 RunRegex(&gArrayRegex, buffer, 4, matches, "Malformed server response");
593 itemType = &buffer[ matches[2].rm_so ]; buffer[ matches[2].rm_eo ] = '\0';
594 count = atoi( &buffer[ matches[3].rm_so ] );
597 if( strcmp(itemType, "Items") != 0 ) {
599 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
604 itemStart = &buffer[ matches[3].rm_eo ];
606 gaItems = malloc( count * sizeof(tItem) );
608 for( giNumItems = 0; giNumItems < count && itemStart; giNumItems ++ )
610 char *next = strchr( ++itemStart, ' ' );
611 if( next ) *next = '\0';
612 gaItems[giNumItems].Ident = strdup(itemStart);
616 // Fetch item information
617 for( i = 0; i < giNumItems; i ++ )
619 regmatch_t matches[6];
622 sendf(Socket, "ITEM_INFO %s\n", gaItems[i].Ident);
623 len = recv(Socket, buffer, BUFSIZ-1, 0);
627 responseCode = atoi(buffer);
628 if( responseCode != 202 ) {
629 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
633 RunRegex(&gItemRegex, buffer, 6, matches, "Malformed server response");
635 buffer[ matches[3].rm_eo ] = '\0';
637 gaItems[i].Price = atoi( buffer + matches[4].rm_so );
638 gaItems[i].Desc = strdup( buffer + matches[5].rm_so );
642 int DispenseItem(int Socket, int ItemID)
644 int len, responseCode;
647 if( ItemID < 0 || ItemID > giNumItems ) return -1;
650 sendf(Socket, "DISPENSE %s\n", gaItems[ItemID].Ident);
651 len = recv(Socket, buffer, BUFSIZ-1, 0);
655 responseCode = atoi(buffer);
656 switch( responseCode )
659 printf("Dispense OK\n");
662 printf("Not authenticated\n");
665 printf("Insufficient balance\n");
668 printf("Bad item name, bug report\n");
671 printf("Item failed to dispense, is the slot empty?\n");
674 printf("Dispense not possible (slot empty/permissions)\n");
677 printf("Unknown response code %i ('%s')\n", responseCode, buffer);
685 int sendf(int Socket, const char *Format, ...)
690 va_start(args, Format);
691 len = vsnprintf(NULL, 0, Format, args);
696 va_start(args, Format);
697 vsnprintf(buf, len+1, Format, args);
700 return send(Socket, buf, len, 0);
704 char *trim(char *string)
708 while( isspace(*string) )
711 for( i = strlen(string); i--; )
713 if( isspace(string[i]) )
722 int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
726 ret = regexec(regex, string, nMatches, matches, 0);
728 size_t len = regerror(ret, regex, NULL, 0);
730 regerror(ret, regex, errorStr, len);
731 printf("string = '%s'\n", string);
732 fprintf(stderr, "%s\n%s", errorMessage, errorStr);
739 void CompileRegex(regex_t *regex, const char *pattern, int flags)
741 int ret = regcomp(regex, pattern, flags);
743 size_t len = regerror(ret, regex, NULL, 0);
745 regerror(ret, regex, errorStr, len);
746 fprintf(stderr, "Regex compilation failed - %s\n", errorStr);