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 // printf("geteuid() = %i, getuid() = %i\n", geteuid(), getuid());
60 if( geteuid() == 0 || getuid() == 0 )
63 struct sockaddr_in localAddr;
64 memset(&localAddr, 0, sizeof(localAddr));
65 localAddr.sin_family = AF_INET; // IPv4
67 // Loop through all the top ports until one is avaliable
68 for( i = 512; i < 1024; i ++)
70 localAddr.sin_port = htons(i); // IPv4
71 // Attempt to bind to low port for autoauth
72 if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
76 printf("Warning: AUTOAUTH unavaliable\n");
78 // printf("Bound to 0.0.0.0:%i\n", i);
81 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
82 fprintf(stderr, "Failed to connect to server\n");
86 // We're not authenticated if the connection has just opened
87 gbIsAuthenticated = 0;
92 int Authenticate_AutoAuth(int Socket, const char *Username)
98 // Attempt automatic authentication
99 sendf(Socket, "AUTOAUTH %s\n", Username);
101 // Check if it worked
102 buf = ReadLine(Socket);
104 responseCode = atoi(buf);
105 switch( responseCode )
107 case 200: // Autoauth succeeded, return
111 case 401: // Untrusted
112 // fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
113 ret = RV_PERMISSIONS;
115 case 404: // Bad Username
116 fprintf(stderr, "Bad Username '%s'\n", Username);
117 ret = RV_INVALID_USER;
121 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
123 ret = RV_UNKNOWN_ERROR;
131 int Authenticate_AuthIdent(int Socket)
137 // Attempt automatic authentication
138 sendf(Socket, "AUTHIDENT\n");
140 // Check if it worked
141 buf = ReadLine(Socket);
143 responseCode = atoi(buf);
144 switch( responseCode )
146 case 200: // Autoauth succeeded, return
150 case 401: // Untrusted
151 // fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
152 ret = RV_PERMISSIONS;
156 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
158 ret = RV_UNKNOWN_RESPONSE;
167 int Authenticate_Password(int Socket, const char *Username)
169 #if USE_PASSWORD_AUTH
174 regmatch_t matches[4];
176 sendf(Socket, "USER %s\n", Username);
177 printf("Using username %s\n", Username);
179 buf = ReadLine(Socket);
182 // Expected format: 100 SALT <something> ...
184 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
185 responseCode = atoi(buf);
186 if( responseCode != 100 ) {
187 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
189 return RV_UNKNOWN_ERROR; // ERROR
193 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
194 // Store it for later
195 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
196 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
200 // Give three attempts
201 for( i = 0; i < 3; i ++ )
203 int ofs = strlen(Username)+strlen(salt);
206 char *pass = getpass("Password: ");
209 // Create hash string
210 // <username><salt><hash>
211 strcpy(tmp, Username);
213 SHA1( (unsigned char*)pass, strlen(pass), h );
214 memcpy(tmp+ofs, h, 20);
217 SHA1( (unsigned char*)tmp, ofs+20, h );
218 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
219 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
220 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
224 sendf(Socket, "PASS %s\n", tmpBuf);
225 buf = ReadLine(Socket);
227 responseCode = atoi(buf);
229 if( responseCode == 200 ) break;
230 // Bad username/password
231 if( responseCode == 401 ) continue;
233 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
239 return RV_INVALID_USER; // 2 = Bad Password
243 return RV_INVALID_USER;
248 * \brief Authenticate with the server
249 * \return Boolean Failure
251 int Authenticate(int Socket)
255 if( gbIsAuthenticated ) return 0;
258 pwd = getpwuid( getuid() );
261 if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
263 else if( Authenticate_AuthIdent(Socket) == 0 )
265 else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
266 return RV_INVALID_USER;
268 // Set effective user
269 if( gsEffectiveUser ) {
272 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
274 buf = ReadLine(Socket);
275 responseCode = atoi(buf);
280 printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
284 printf("Only coke members can use `dispense -u`\n");
286 return RV_PERMISSIONS;
289 printf("Invalid user selected\n");
291 return RV_INVALID_USER;
294 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
297 return RV_UNKNOWN_ERROR;
303 gbIsAuthenticated = 1;
308 int GetUserBalance(int Socket)
310 regmatch_t matches[6];
317 if( gsEffectiveUser ) {
318 gsUserName = gsEffectiveUser;
321 pwd = getpwuid( getuid() );
322 gsUserName = strdup(pwd->pw_name);
326 sendf(Socket, "USER_INFO %s\n", gsUserName);
327 buf = ReadLine(Socket);
328 responseCode = atoi(buf);
331 case 202: break; // Ok
334 printf("Invalid user? (USER_INFO failed)\n");
336 return RV_INVALID_USER;
339 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
342 return RV_UNKNOWN_ERROR;
345 RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
347 giUserBalance = atoi( buf + matches[4].rm_so );
348 gsUserFlags = strdup( buf + matches[5].rm_so );
356 * \brief Read an item info response from the server
357 * \param Dest Destination for the read item (strings will be on the heap)
359 int ReadItemInfo(int Socket, tItem *Dest)
364 regmatch_t matches[8];
368 buf = ReadLine(Socket);
369 responseCode = atoi(buf);
376 printf("Bad item name\n");
381 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
383 return RV_UNKNOWN_ERROR;
386 RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
388 buf[ matches[3].rm_eo ] = '\0';
389 buf[ matches[5].rm_eo ] = '\0';
390 buf[ matches[7].rm_eo ] = '\0';
392 statusStr = &buf[ matches[5].rm_so ];
394 Dest->ID = atoi( buf + matches[4].rm_so );
396 if( strcmp(statusStr, "avail") == 0 )
398 else if( strcmp(statusStr, "sold") == 0 )
400 else if( strcmp(statusStr, "error") == 0 )
403 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
405 return RV_UNKNOWN_ERROR;
407 Dest->Price = atoi( buf + matches[6].rm_so );
409 // Hack a little to reduce heap fragmentation
411 char tmpType[strlen(buf + matches[3].rm_so) + 1];
412 char tmpDesc[strlen(buf + matches[7].rm_so) + 1];
413 strcpy(tmpType, buf + matches[3].rm_so);
414 strcpy(tmpDesc, buf + matches[7].rm_so);
416 Dest->Type = strdup( tmpType );
417 Dest->Desc = strdup( tmpDesc );
424 * \brief Fill the item information structure
425 * \return Boolean Failure
427 void PopulateItemList(int Socket)
434 regmatch_t matches[4];
436 // Ask server for stock list
437 send(Socket, "ENUM_ITEMS\n", 11, 0);
438 buf = ReadLine(Socket);
440 //printf("Output: %s\n", buf);
442 responseCode = atoi(buf);
443 if( responseCode != 201 ) {
444 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
445 exit(RV_UNKNOWN_ERROR);
453 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
455 arrayType = &buf[ matches[2].rm_so ]; buf[ matches[2].rm_eo ] = '\0';
456 count = atoi( &buf[ matches[3].rm_so ] );
459 if( strcmp(arrayType, "Items") != 0 ) {
461 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
463 exit(RV_UNKNOWN_ERROR);
468 gaItems = malloc( giNumItems * sizeof(tItem) );
470 // Fetch item information
471 for( i = 0; i < giNumItems; i ++ )
473 ReadItemInfo( Socket, &gaItems[i] );
477 buf = ReadLine(Socket);
478 responseCode = atoi(buf);
480 if( responseCode != 200 ) {
481 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
492 * \brief Get information on an item
493 * \return Boolean Failure
495 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
501 sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
503 ret = ReadItemInfo(Socket, &item);
506 printf("%8s:%-2i %2i.%02i %s\n",
508 item.Price/100, item.Price%100,
518 * \brief Dispense an item
519 * \return Boolean Failure
521 int DispenseItem(int Socket, const char *Type, int ID)
523 int ret, responseCode;
526 // Check for a dry run
528 printf("Dry Run - No action\n");
533 sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
534 buf = ReadLine(Socket);
536 responseCode = atoi(buf);
537 switch( responseCode )
540 printf("Dispense OK\n");
544 printf("Not authenticated\n");
545 ret = RV_PERMISSIONS;
548 printf("Insufficient balance\n");
552 printf("Bad item name\n");
556 printf("Item failed to dispense, is the slot empty?\n");
557 ret = RV_SERVER_ERROR;
560 printf("Dispense not possible (slot empty/permissions)\n");
561 ret = RV_SERVER_ERROR;
564 printf("Unknown response code %i ('%s')\n", responseCode, buf);
565 ret = RV_UNKNOWN_ERROR;
574 * \brief Alter a user's balance
576 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
579 int responseCode, rv = -1;
581 // Check for a dry run
583 printf("Dry Run - No action\n");
589 printf("An amount would be nice\n");
593 sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
594 buf = ReadLine(Socket);
596 responseCode = atoi(buf);
604 fprintf(stderr, "Insufficient balance\n");
607 case 403: // Not in coke
608 fprintf(stderr, "You are not in coke (sucker)\n");
611 case 404: // Unknown user
612 fprintf(stderr, "Unknown user '%s'\n", Username);
613 rv = RV_INVALID_USER;
616 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
617 rv = RV_UNKNOWN_RESPONSE;
626 * \brief Set a user's balance
627 * \note Only avaliable to dispense admins
629 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
634 // Check for a dry run
636 printf("Dry Run - No action\n");
640 sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
641 buf = ReadLine(Socket);
643 responseCode = atoi(buf);
648 case 200: return 0; // OK
649 case 403: // Not in coke
650 fprintf(stderr, "You are not an admin\n");
651 return RV_PERMISSIONS;
652 case 404: // Unknown user
653 fprintf(stderr, "Unknown user '%s'\n", Username);
654 return RV_INVALID_USER;
656 fprintf(stderr, "Unknown response code %i\n", responseCode);
657 return RV_UNKNOWN_RESPONSE;
664 * \brief Give money to another user
666 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
672 printf("Sorry, you can only give, you can't take.\n");
676 // Fast return on zero
678 printf("Are you actually going to give any?\n");
682 // Check for a dry run
684 printf("Dry Run - No action\n");
688 sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
690 buf = ReadLine(Socket);
691 responseCode = atoi(buf);
696 printf("Give succeeded\n");
697 return RV_SUCCESS; // OK
700 fprintf(stderr, "Insufficient balance\n");
703 case 404: // Unknown user
704 fprintf(stderr, "Unknown user '%s'\n", Username);
705 return RV_INVALID_USER;
708 fprintf(stderr, "Unknown response code %i\n", responseCode);
709 return RV_UNKNOWN_RESPONSE;
715 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
718 int responseCode, ret = -1;
721 if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
723 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
727 // Check username (quick)
728 if( strchr(Username, ' ') || strchr(Username, '\n') )
730 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
735 sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
737 buf = ReadLine(Socket);
738 responseCode = atoi(buf);
742 Dispense_ShowUser(Socket, Username); // Show destination account
746 fprintf(stderr, "Refund access is only avaliable to coke members\n");
747 ret = RV_PERMISSIONS;
750 fprintf(stderr, "Unknown user '%s' passed\n", Username);
751 ret = RV_INVALID_USER;
754 fprintf(stderr, "Invalid item '%s' passed\n", Item);
758 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
767 * \brief Donate money to the club
769 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
775 printf("Sorry, you can only give, you can't take.\n");
779 // Fast return on zero
781 printf("Are you actually going to give any?\n");
785 // Check for a dry run
787 printf("Dry Run - No action\n");
791 sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
792 buf = ReadLine(Socket);
794 responseCode = atoi(buf);
799 case 200: return 0; // OK
802 fprintf(stderr, "Insufficient balance\n");
806 fprintf(stderr, "Unknown response code %i\n", responseCode);
814 * \brief Enumerate users
816 int Dispense_EnumUsers(int Socket)
821 regmatch_t matches[4];
823 if( giMinimumBalance != INT_MIN ) {
824 if( giMaximumBalance != INT_MAX ) {
825 sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
828 sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
832 if( giMaximumBalance != INT_MAX ) {
833 sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
836 sendf(Socket, "ENUM_USERS\n");
839 buf = ReadLine(Socket);
840 responseCode = atoi(buf);
844 case 201: break; // Ok, length follows
847 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
852 // Get count (not actually used)
853 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
854 nUsers = atoi( buf + matches[3].rm_so );
855 printf("%i users returned\n", nUsers);
860 // Read returned users
862 buf = ReadLine(Socket);
863 responseCode = atoi(buf);
865 if( responseCode != 202 ) break;
869 } while(responseCode == 202);
871 // Check final response
872 if( responseCode != 200 ) {
873 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
883 int Dispense_ShowUser(int Socket, const char *Username)
886 int responseCode, ret;
888 sendf(Socket, "USER_INFO %s\n", Username);
889 buf = ReadLine(Socket);
891 responseCode = atoi(buf);
901 printf("Unknown user '%s'\n", Username);
906 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
916 void _PrintUserLine(const char *Line)
918 regmatch_t matches[6];
921 RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
926 int usernameLen = matches[3].rm_eo - matches[3].rm_so;
927 char username[usernameLen + 1];
928 int flagsLen = matches[5].rm_eo - matches[5].rm_so;
929 char flags[flagsLen + 1];
931 memcpy(username, Line + matches[3].rm_so, usernameLen);
932 username[usernameLen] = '\0';
933 memcpy(flags, Line + matches[5].rm_so, flagsLen);
934 flags[flagsLen] = '\0';
936 bal = atoi(Line + matches[4].rm_so);
937 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
941 int Dispense_AddUser(int Socket, const char *Username)
944 int responseCode, ret;
946 // Check for a dry run
948 printf("Dry Run - No action\n");
952 sendf(Socket, "USER_ADD %s\n", Username);
954 buf = ReadLine(Socket);
955 responseCode = atoi(buf);
960 printf("User '%s' added\n", Username);
965 printf("Only wheel can add users\n");
970 printf("User '%s' already exists\n", Username);
975 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
985 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
988 int responseCode, ret;
990 // Check for a dry run
992 printf("Dry Run - No action\n");
996 // TODO: Pre-validate the string
998 sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
1000 buf = ReadLine(Socket);
1001 responseCode = atoi(buf);
1003 switch(responseCode)
1006 printf("User '%s' updated\n", Username);
1011 printf("Only dispense admins can modify users\n");
1012 ret = RV_PERMISSIONS;
1016 printf("User '%s' does not exist\n", Username);
1017 ret = RV_INVALID_USER;
1021 printf("Flag string is invalid\n");
1026 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1027 ret = RV_UNKNOWN_RESPONSE;
1036 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
1039 int responseCode, ret;
1041 // Check for a dry run
1043 printf("Dry Run - No action\n");
1047 sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
1049 buf = ReadLine(Socket);
1050 responseCode = atoi(buf);
1052 switch(responseCode)
1055 printf("Item %s:%i updated\n", Type, ID);
1060 printf("Only coke members can modify the slots\n");
1061 ret = RV_PERMISSIONS;
1065 printf("Invalid item passed\n");
1070 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1083 char *ReadLine(int Socket)
1085 static char buf[BUFSIZ];
1086 static int bufPos = 0;
1087 static int bufValid = 0;
1089 char *newline = NULL;
1091 char *ret = malloc(10);
1093 #if DEBUG_TRACE_SERVER
1094 printf("ReadLine: ");
1106 len = recv(Socket, buf+bufPos, BUFSIZ-1-bufPos, 0);
1109 return strdup("599 Client Connection Error\n");
1112 buf[bufPos+len] = '\0';
1114 newline = strchr( buf+bufPos, '\n' );
1119 retLen += strlen(buf+bufPos);
1120 ret = realloc(ret, retLen + 1);
1121 strcat( ret, buf+bufPos );
1124 int newLen = newline - (buf+bufPos) + 1;
1125 bufValid = len - newLen;
1128 if( len + bufPos == BUFSIZ - 1 ) bufPos = 0;
1132 #if DEBUG_TRACE_SERVER
1133 printf("%i '%s'\n", retLen, ret);
1139 int sendf(int Socket, const char *Format, ...)
1144 va_start(args, Format);
1145 len = vsnprintf(NULL, 0, Format, args);
1150 va_start(args, Format);
1151 vsnprintf(buf, len+1, Format, args);
1154 #if DEBUG_TRACE_SERVER
1155 printf("sendf: %s", buf);
1158 return send(Socket, buf, len, 0);