3 * UCC (University [of WA] Computer Club) Electronic Accounting System
7 * - Client/Server communication
9 * This file is licenced under the 3-clause BSD Licence. See the file
10 * COPYING for full details.
15 #include <netdb.h> // gethostbyname
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <arpa/inet.h>
19 //#include <openssl/sha.h> // SHA1
20 #include <pwd.h> // getpwuids
21 #include <unistd.h> // close/getuid
22 #include <limits.h> // INT_MIN/INT_MAX
27 char *ReadLine(int Socket);
28 int sendf(int Socket, const char *Format, ...);
30 // ---------------------
31 // --- Coke Protocol ---
32 // ---------------------
33 int OpenConnection(const char *Host, int Port)
36 struct sockaddr_in serverAddr;
39 host = gethostbyname(Host);
41 fprintf(stderr, "Unable to look up '%s'\n", Host);
45 memset(&serverAddr, 0, sizeof(serverAddr));
47 serverAddr.sin_family = AF_INET; // IPv4
48 // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
49 serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
50 serverAddr.sin_port = htons(Port);
52 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
54 fprintf(stderr, "Failed to create socket\n");
58 if( geteuid() == 0 || getuid() == 0 )
61 struct sockaddr_in localAddr;
62 memset(&localAddr, 0, sizeof(localAddr));
63 localAddr.sin_family = AF_INET; // IPv4
65 // Loop through all the top ports until one is avaliable
66 for( i = 512; i < 1024; i ++)
68 localAddr.sin_port = htons(i); // IPv4
69 // Attempt to bind to low port for autoauth
70 if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
74 printf("Warning: AUTOAUTH unavaliable\n");
77 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
78 fprintf(stderr, "Failed to connect to server\n");
82 // We're not authenticated if the connection has just opened
83 gbIsAuthenticated = 0;
88 int Authenticate_AutoAuth(int Socket, const char *Username)
94 // Attempt automatic authentication
95 sendf(Socket, "AUTOAUTH %s\n", Username);
98 buf = ReadLine(Socket);
100 responseCode = atoi(buf);
101 switch( responseCode )
103 case 200: // Autoauth succeeded, return
107 case 401: // Untrusted
108 // fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
109 ret = RV_PERMISSIONS;
111 case 404: // Bad Username
112 fprintf(stderr, "Bad Username '%s'\n", Username);
113 ret = RV_INVALID_USER;
117 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
119 ret = RV_UNKNOWN_ERROR;
127 int Authenticate_AuthIdent(int Socket)
133 // Attempt automatic authentication
134 sendf(Socket, "AUTHIDENT\n");
136 // Check if it worked
137 buf = ReadLine(Socket);
139 responseCode = atoi(buf);
140 switch( responseCode )
142 case 200: // Autoauth succeeded, return
146 case 401: // Untrusted
147 // fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
148 ret = RV_PERMISSIONS;
152 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
154 ret = RV_UNKNOWN_RESPONSE;
163 int Authenticate_Password(int Socket, const char *Username)
165 #if USE_PASSWORD_AUTH
170 regmatch_t matches[4];
172 sendf(Socket, "USER %s\n", Username);
173 printf("Using username %s\n", Username);
175 buf = ReadLine(Socket);
178 // Expected format: 100 SALT <something> ...
180 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
181 responseCode = atoi(buf);
182 if( responseCode != 100 ) {
183 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
185 return RV_UNKNOWN_ERROR; // ERROR
189 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
190 // Store it for later
191 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
192 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
196 // Give three attempts
197 for( i = 0; i < 3; i ++ )
199 int ofs = strlen(Username)+strlen(salt);
202 char *pass = getpass("Password: ");
205 // Create hash string
206 // <username><salt><hash>
207 strcpy(tmp, Username);
209 SHA1( (unsigned char*)pass, strlen(pass), h );
210 memcpy(tmp+ofs, h, 20);
213 SHA1( (unsigned char*)tmp, ofs+20, h );
214 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
215 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
216 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
220 sendf(Socket, "PASS %s\n", tmpBuf);
221 buf = ReadLine(Socket);
223 responseCode = atoi(buf);
225 if( responseCode == 200 ) break;
226 // Bad username/password
227 if( responseCode == 401 ) continue;
229 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
235 return RV_INVALID_USER; // 2 = Bad Password
239 return RV_INVALID_USER;
244 * \brief Authenticate with the server
245 * \return Boolean Failure
247 int Authenticate(int Socket)
251 if( gbIsAuthenticated ) return 0;
254 pwd = getpwuid( getuid() );
257 if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
259 else if( Authenticate_AuthIdent(Socket) == 0 )
261 else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
262 return RV_INVALID_USER;
264 // Set effective user
265 if( gsEffectiveUser ) {
268 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
270 buf = ReadLine(Socket);
271 responseCode = atoi(buf);
276 printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
280 printf("Only coke members can use `dispense -u`\n");
282 return RV_PERMISSIONS;
285 printf("Invalid user selected\n");
287 return RV_INVALID_USER;
290 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
293 return RV_UNKNOWN_ERROR;
299 gbIsAuthenticated = 1;
304 int GetUserBalance(int Socket)
306 regmatch_t matches[6];
313 if( gsEffectiveUser ) {
314 gsUserName = gsEffectiveUser;
317 pwd = getpwuid( getuid() );
318 gsUserName = strdup(pwd->pw_name);
322 sendf(Socket, "USER_INFO %s\n", gsUserName);
323 buf = ReadLine(Socket);
324 responseCode = atoi(buf);
327 case 202: break; // Ok
330 printf("Invalid user? (USER_INFO failed)\n");
332 return RV_INVALID_USER;
335 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
338 return RV_UNKNOWN_ERROR;
341 RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
343 giUserBalance = atoi( buf + matches[4].rm_so );
344 gsUserFlags = strdup( buf + matches[5].rm_so );
352 * \brief Read an item info response from the server
353 * \param Dest Destination for the read item (strings will be on the heap)
355 int ReadItemInfo(int Socket, tItem *Dest)
360 regmatch_t matches[8];
364 buf = ReadLine(Socket);
365 responseCode = atoi(buf);
372 printf("Bad item name\n");
377 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
379 return RV_UNKNOWN_ERROR;
382 RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
384 buf[ matches[3].rm_eo ] = '\0';
385 buf[ matches[5].rm_eo ] = '\0';
386 buf[ matches[7].rm_eo ] = '\0';
388 statusStr = &buf[ matches[5].rm_so ];
390 Dest->ID = atoi( buf + matches[4].rm_so );
392 if( strcmp(statusStr, "avail") == 0 )
394 else if( strcmp(statusStr, "sold") == 0 )
396 else if( strcmp(statusStr, "error") == 0 )
399 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
401 return RV_UNKNOWN_ERROR;
403 Dest->Price = atoi( buf + matches[6].rm_so );
405 // Hack a little to reduce heap fragmentation
407 char tmpType[strlen(buf + matches[3].rm_so) + 1];
408 char tmpDesc[strlen(buf + matches[7].rm_so) + 1];
409 strcpy(tmpType, buf + matches[3].rm_so);
410 strcpy(tmpDesc, buf + matches[7].rm_so);
412 Dest->Type = strdup( tmpType );
413 Dest->Desc = strdup( tmpDesc );
420 * \brief Fill the item information structure
421 * \return Boolean Failure
423 void PopulateItemList(int Socket)
430 regmatch_t matches[4];
432 // Ask server for stock list
433 send(Socket, "ENUM_ITEMS\n", 11, 0);
434 buf = ReadLine(Socket);
436 //printf("Output: %s\n", buf);
438 responseCode = atoi(buf);
439 if( responseCode != 201 ) {
440 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
441 exit(RV_UNKNOWN_ERROR);
449 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
451 arrayType = &buf[ matches[2].rm_so ]; buf[ matches[2].rm_eo ] = '\0';
452 count = atoi( &buf[ matches[3].rm_so ] );
455 if( strcmp(arrayType, "Items") != 0 ) {
457 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
459 exit(RV_UNKNOWN_ERROR);
464 gaItems = malloc( giNumItems * sizeof(tItem) );
466 // Fetch item information
467 for( i = 0; i < giNumItems; i ++ )
469 ReadItemInfo( Socket, &gaItems[i] );
473 buf = ReadLine(Socket);
474 responseCode = atoi(buf);
476 if( responseCode != 200 ) {
477 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
488 * \brief Get information on an item
489 * \return Boolean Failure
491 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
497 sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
499 ret = ReadItemInfo(Socket, &item);
502 printf("%8s:%-2i %2i.%02i %s\n",
504 item.Price/100, item.Price%100,
513 int DispenseCheckPin(int Socket, const char *Username, const char *Pin)
515 int ret, responseCode;
518 if( strlen(Pin) != 4 ) {
519 fprintf(stderr, "Pin format incorrect (not 4 characters long)\n");
523 for( int i = 0; i < 4; i ++ )
524 if( !isdigit(Pin[i]) ) {
525 fprintf(stderr, "Pin format incorrect (character %i not a digit)\n", i);
530 sendf(Socket, "CHECK_PIN %s %s\n", Username, Pin);
531 buf = ReadLine(Socket);
533 responseCode = atoi(buf);
534 switch( responseCode )
536 case 200: // Pin correct
541 printf("Pin incorrect\n");
542 ret = RV_INVALID_USER;
545 printf("Not authenticated\n");
546 ret = RV_PERMISSIONS;
549 printf("Only coke members can check accounts other than their own\n");
550 ret = RV_PERMISSIONS;
553 printf("User '%s' not found\n", Username);
554 ret = RV_INVALID_USER;
557 printf("Rate limited or client-server disagree on pin format\n");
558 ret = RV_SERVER_ERROR;
561 printf("Unknown response code %i ('%s')\n", responseCode, buf);
562 ret = RV_UNKNOWN_ERROR;
570 * \brief Dispense an item
571 * \return Boolean Failure
573 int DispenseItem(int Socket, const char *Type, int ID)
575 int ret, responseCode;
578 // Check for a dry run
580 printf("Dry Run - No action\n");
585 sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
586 buf = ReadLine(Socket);
588 responseCode = atoi(buf);
589 switch( responseCode )
592 printf("Dispense OK\n");
596 printf("Not authenticated\n");
597 ret = RV_PERMISSIONS;
600 printf("Insufficient balance\n");
604 printf("Bad item name\n");
608 printf("Item failed to dispense, is the slot empty?\n");
609 ret = RV_SERVER_ERROR;
612 printf("Dispense not possible (slot empty/permissions)\n");
613 ret = RV_SERVER_ERROR;
616 printf("Unknown response code %i ('%s')\n", responseCode, buf);
617 ret = RV_UNKNOWN_ERROR;
626 * \brief Alter a user's balance
628 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
631 int responseCode, rv = -1;
633 // Check for a dry run
635 printf("Dry Run - No action\n");
641 printf("An amount would be nice\n");
645 sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
646 buf = ReadLine(Socket);
648 responseCode = atoi(buf);
656 fprintf(stderr, "Insufficient balance\n");
659 case 403: // Not in coke
660 fprintf(stderr, "You are not in coke (sucker)\n");
663 case 404: // Unknown user
664 fprintf(stderr, "Unknown user '%s'\n", Username);
665 rv = RV_INVALID_USER;
668 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
669 rv = RV_UNKNOWN_RESPONSE;
678 * \brief Set a user's balance
679 * \note Only avaliable to dispense admins
681 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
686 // Check for a dry run
688 printf("Dry Run - No action\n");
692 sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
693 buf = ReadLine(Socket);
695 responseCode = atoi(buf);
700 case 200: return 0; // OK
701 case 403: // Not in coke
702 fprintf(stderr, "You are not an admin\n");
703 return RV_PERMISSIONS;
704 case 404: // Unknown user
705 fprintf(stderr, "Unknown user '%s'\n", Username);
706 return RV_INVALID_USER;
708 fprintf(stderr, "Unknown response code %i\n", responseCode);
709 return RV_UNKNOWN_RESPONSE;
716 * \brief Give money to another user
718 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
724 printf("Sorry, you can only give, you can't take.\n");
728 // Fast return on zero
730 printf("Are you actually going to give any?\n");
734 // Check for a dry run
736 printf("Dry Run - No action\n");
740 sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
742 buf = ReadLine(Socket);
743 responseCode = atoi(buf);
748 printf("Give succeeded\n");
749 return RV_SUCCESS; // OK
752 fprintf(stderr, "Insufficient balance\n");
755 case 404: // Unknown user
756 fprintf(stderr, "Unknown user '%s'\n", Username);
757 return RV_INVALID_USER;
760 fprintf(stderr, "Unknown response code %i\n", responseCode);
761 return RV_UNKNOWN_RESPONSE;
767 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
770 int responseCode, ret = -1;
773 if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
775 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
779 // Check username (quick)
780 if( strchr(Username, ' ') || strchr(Username, '\n') )
782 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
787 sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
789 buf = ReadLine(Socket);
790 responseCode = atoi(buf);
794 Dispense_ShowUser(Socket, Username); // Show destination account
798 fprintf(stderr, "Refund access is only avaliable to coke members\n");
799 ret = RV_PERMISSIONS;
802 fprintf(stderr, "Unknown user '%s' passed\n", Username);
803 ret = RV_INVALID_USER;
806 fprintf(stderr, "Invalid item '%s' passed\n", Item);
810 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
819 * \brief Donate money to the club
821 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
827 printf("Sorry, you can only give, you can't take.\n");
831 // Fast return on zero
833 printf("Are you actually going to give any?\n");
837 // Check for a dry run
839 printf("Dry Run - No action\n");
843 sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
844 buf = ReadLine(Socket);
846 responseCode = atoi(buf);
851 case 200: return 0; // OK
854 fprintf(stderr, "Insufficient balance\n");
858 fprintf(stderr, "Unknown response code %i\n", responseCode);
866 * \brief Enumerate users
868 int Dispense_EnumUsers(int Socket)
873 regmatch_t matches[4];
875 if( giMinimumBalance != INT_MIN ) {
876 if( giMaximumBalance != INT_MAX ) {
877 sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
880 sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
884 if( giMaximumBalance != INT_MAX ) {
885 sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
888 sendf(Socket, "ENUM_USERS\n");
891 buf = ReadLine(Socket);
892 responseCode = atoi(buf);
896 case 201: break; // Ok, length follows
899 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
904 // Get count (not actually used)
905 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
906 nUsers = atoi( buf + matches[3].rm_so );
907 printf("%i users returned\n", nUsers);
912 // Read returned users
914 buf = ReadLine(Socket);
915 responseCode = atoi(buf);
917 if( responseCode != 202 ) break;
921 } while(responseCode == 202);
923 // Check final response
924 if( responseCode != 200 ) {
925 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
935 int Dispense_ShowUser(int Socket, const char *Username)
938 int responseCode, ret;
940 sendf(Socket, "USER_INFO %s\n", Username);
941 buf = ReadLine(Socket);
943 responseCode = atoi(buf);
953 printf("Unknown user '%s'\n", Username);
958 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
968 void _PrintUserLine(const char *Line)
970 regmatch_t matches[6];
973 RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
978 int usernameLen = matches[3].rm_eo - matches[3].rm_so;
979 char username[usernameLen + 1];
980 int flagsLen = matches[5].rm_eo - matches[5].rm_so;
981 char flags[flagsLen + 1];
983 memcpy(username, Line + matches[3].rm_so, usernameLen);
984 username[usernameLen] = '\0';
985 memcpy(flags, Line + matches[5].rm_so, flagsLen);
986 flags[flagsLen] = '\0';
988 bal = atoi(Line + matches[4].rm_so);
989 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
993 int Dispense_AddUser(int Socket, const char *Username)
996 int responseCode, ret;
998 // Check for a dry run
1000 printf("Dry Run - No action\n");
1004 sendf(Socket, "USER_ADD %s\n", Username);
1006 buf = ReadLine(Socket);
1007 responseCode = atoi(buf);
1009 switch(responseCode)
1012 printf("User '%s' added\n", Username);
1017 printf("Only wheel can add users\n");
1022 printf("User '%s' already exists\n", Username);
1027 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1037 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
1040 int responseCode, ret;
1042 // Check for a dry run
1044 printf("Dry Run - No action\n");
1048 // TODO: Pre-validate the string
1050 sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
1052 buf = ReadLine(Socket);
1053 responseCode = atoi(buf);
1055 switch(responseCode)
1058 printf("User '%s' updated\n", Username);
1063 printf("Only dispense admins can modify users\n");
1064 ret = RV_PERMISSIONS;
1068 printf("User '%s' does not exist\n", Username);
1069 ret = RV_INVALID_USER;
1073 printf("Flag string is invalid\n");
1078 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1079 ret = RV_UNKNOWN_RESPONSE;
1088 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
1091 int responseCode, ret;
1093 // Check for a dry run
1095 printf("Dry Run - No action\n");
1099 sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
1101 buf = ReadLine(Socket);
1102 responseCode = atoi(buf);
1104 switch(responseCode)
1107 printf("Item %s:%i updated\n", Type, ID);
1112 printf("Only coke members can modify the slots\n");
1113 ret = RV_PERMISSIONS;
1117 printf("Invalid item passed\n");
1122 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1135 char *ReadLine(int Socket)
1137 static char buf[BUFSIZ];
1138 static int bufPos = 0;
1139 static int bufValid = 0;
1141 char *newline = NULL;
1143 char *ret = malloc(10);
1145 #if DEBUG_TRACE_SERVER
1146 printf("ReadLine: ");
1158 len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1161 return strdup("599 Client Connection Error\n");
1164 buf[bufPos+len] = '\0';
1166 newline = strchr( buf+bufPos, '\n' );
1171 retLen += strlen(buf+bufPos);
1172 ret = realloc(ret, retLen + 1);
1173 strcat( ret, buf+bufPos );
1176 int newLen = newline - (buf+bufPos) + 1;
1177 bufValid = len - newLen;
1180 if( len + bufPos == BUFSIZ - 1 ) bufPos = 0;
1184 #if DEBUG_TRACE_SERVER
1185 printf("%i '%s'\n", retLen, ret);
1191 int sendf(int Socket, const char *Format, ...)
1196 va_start(args, Format);
1197 len = vsnprintf(NULL, 0, Format, args);
1202 va_start(args, Format);
1203 vsnprintf(buf, len+1, Format, args);
1206 #if DEBUG_TRACE_SERVER
1207 printf("sendf: %s", buf);
1210 return send(Socket, buf, len, 0);