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.
12 //#define DEBUG_TRACE_SERVER 2
17 #include <netdb.h> // gethostbyname
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20 #include <arpa/inet.h>
21 //#include <openssl/sha.h> // SHA1
22 #include <pwd.h> // getpwuids
23 #include <unistd.h> // close/getuid
24 #include <limits.h> // INT_MIN/INT_MAX
26 #include <ctype.h> // isdigit
30 char *ReadLine(int Socket);
31 int sendf(int Socket, const char *Format, ...);
33 // ---------------------
34 // --- Coke Protocol ---
35 // ---------------------
36 int OpenConnection(const char *Host, int Port)
39 struct sockaddr_in serverAddr;
42 host = gethostbyname(Host);
44 fprintf(stderr, "Unable to look up '%s'\n", Host);
48 memset(&serverAddr, 0, sizeof(serverAddr));
50 serverAddr.sin_family = AF_INET; // IPv4
51 // NOTE: I have a suspicion that IPv6 will play sillybuggers with this :)
52 serverAddr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]);
53 serverAddr.sin_port = htons(Port);
55 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
57 fprintf(stderr, "Failed to create socket\n");
61 if( geteuid() == 0 || getuid() == 0 )
64 struct sockaddr_in localAddr;
65 memset(&localAddr, 0, sizeof(localAddr));
66 localAddr.sin_family = AF_INET; // IPv4
68 // Loop through all the top ports until one is avaliable
69 for( i = 512; i < 1024; i ++)
71 localAddr.sin_port = htons(i); // IPv4
72 // Attempt to bind to low port for autoauth
73 if( bind(sock, (struct sockaddr*)&localAddr, sizeof(localAddr)) == 0 )
77 printf("Warning: AUTOAUTH unavaliable\n");
80 if( connect(sock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0 ) {
81 fprintf(stderr, "Failed to connect to server\n");
85 // We're not authenticated if the connection has just opened
86 gbIsAuthenticated = 0;
91 int Authenticate_AutoAuth(int Socket, const char *Username)
97 // Attempt automatic authentication
98 sendf(Socket, "AUTOAUTH %s\n", Username);
100 // Check if it worked
101 buf = ReadLine(Socket);
103 responseCode = atoi(buf);
104 switch( responseCode )
106 case 200: // Autoauth succeeded, return
110 case 401: // Untrusted
111 // fprintf(stderr, "Untrusted host, AUTOAUTH unavaliable\n");
112 ret = RV_PERMISSIONS;
114 case 404: // Bad Username
115 fprintf(stderr, "Bad Username '%s'\n", Username);
116 ret = RV_INVALID_USER;
120 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
122 ret = RV_UNKNOWN_ERROR;
130 int Authenticate_AuthIdent(int Socket)
136 // Attempt automatic authentication
137 sendf(Socket, "AUTHIDENT\n");
139 // Check if it worked
140 buf = ReadLine(Socket);
142 responseCode = atoi(buf);
143 switch( responseCode )
145 case 200: // Autoauth succeeded, return
149 case 401: // Untrusted
150 // fprintf(stderr, "Untrusted host, AUTHIDENT unavaliable\n");
151 ret = RV_PERMISSIONS;
155 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
157 ret = RV_UNKNOWN_RESPONSE;
166 int Authenticate_Password(int Socket, const char *Username)
168 #if USE_PASSWORD_AUTH
173 regmatch_t matches[4];
175 sendf(Socket, "USER %s\n", Username);
176 printf("Using username %s\n", Username);
178 buf = ReadLine(Socket);
181 // Expected format: 100 SALT <something> ...
183 RunRegex(&gSaltRegex, buf, 4, matches, "Malformed server response");
184 responseCode = atoi(buf);
185 if( responseCode != 100 ) {
186 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
188 return RV_UNKNOWN_ERROR; // ERROR
192 if( memcmp( buf+matches[2].rm_so, "SALT", matches[2].rm_eo - matches[2].rm_so) == 0) {
193 // Store it for later
194 memcpy( salt, buf + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so );
195 salt[ matches[3].rm_eo - matches[3].rm_so ] = 0;
199 // Give three attempts
200 for( i = 0; i < 3; i ++ )
202 int ofs = strlen(Username)+strlen(salt);
205 char *pass = getpass("Password: ");
208 // Create hash string
209 // <username><salt><hash>
210 strcpy(tmp, Username);
212 SHA1( (unsigned char*)pass, strlen(pass), h );
213 memcpy(tmp+ofs, h, 20);
216 SHA1( (unsigned char*)tmp, ofs+20, h );
217 sprintf(tmpBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
218 h[ 0], h[ 1], h[ 2], h[ 3], h[ 4], h[ 5], h[ 6], h[ 7], h[ 8], h[ 9],
219 h[10], h[11], h[12], h[13], h[14], h[15], h[16], h[17], h[18], h[19]
223 sendf(Socket, "PASS %s\n", tmpBuf);
224 buf = ReadLine(Socket);
226 responseCode = atoi(buf);
228 if( responseCode == 200 ) break;
229 // Bad username/password
230 if( responseCode == 401 ) continue;
232 fprintf(stderr, "Unknown repsonse code %i from server\n%s\n", responseCode, buf);
238 return RV_INVALID_USER; // 2 = Bad Password
242 return RV_INVALID_USER;
247 * \brief Authenticate with the server
248 * \return Boolean Failure
250 int Authenticate(int Socket)
254 if( gbIsAuthenticated ) return 0;
257 pwd = getpwuid( getuid() );
260 if( Authenticate_AutoAuth(Socket, pwd->pw_name) == 0 )
262 else if( Authenticate_AuthIdent(Socket) == 0 )
264 else if( Authenticate_Password(Socket, pwd->pw_name) == 0 )
265 return RV_INVALID_USER;
267 // Set effective user
268 if( gsEffectiveUser ) {
271 sendf(Socket, "SETEUSER %s\n", gsEffectiveUser);
273 buf = ReadLine(Socket);
274 responseCode = atoi(buf);
279 printf("Running as '%s' by '%s'\n", gsEffectiveUser, pwd->pw_name);
283 printf("Only coke members can use `dispense -u`\n");
285 return RV_PERMISSIONS;
288 printf("Invalid user selected\n");
290 return RV_INVALID_USER;
293 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
296 return RV_UNKNOWN_ERROR;
302 gbIsAuthenticated = 1;
307 int GetUserBalance(int Socket)
309 regmatch_t matches[6];
316 if( gsEffectiveUser ) {
317 gsUserName = gsEffectiveUser;
320 pwd = getpwuid( getuid() );
321 gsUserName = strdup(pwd->pw_name);
325 sendf(Socket, "USER_INFO %s\n", gsUserName);
326 buf = ReadLine(Socket);
327 responseCode = atoi(buf);
330 case 202: break; // Ok
333 printf("Invalid user? (USER_INFO failed)\n");
335 return RV_INVALID_USER;
338 fprintf(stderr, "Unkown response code %i from server\n", responseCode);
341 return RV_UNKNOWN_ERROR;
344 RunRegex(&gUserInfoRegex, buf, 6, matches, "Malformed server response");
346 giUserBalance = atoi( buf + matches[4].rm_so );
347 gsUserFlags = strdup( buf + matches[5].rm_so );
355 * \brief Read an item info response from the server
356 * \param Dest Destination for the read item (strings will be on the heap)
358 int ReadItemInfo(int Socket, tItem *Dest)
363 regmatch_t matches[8];
367 buf = ReadLine(Socket);
368 responseCode = atoi(buf);
375 printf("Bad item name\n");
380 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n%s", responseCode, buf);
382 return RV_UNKNOWN_ERROR;
385 RunRegex(&gItemRegex, buf, 8, matches, "Malformed server response");
387 buf[ matches[3].rm_eo ] = '\0';
388 buf[ matches[5].rm_eo ] = '\0';
389 buf[ matches[7].rm_eo ] = '\0';
391 statusStr = &buf[ matches[5].rm_so ];
393 Dest->ID = atoi( buf + matches[4].rm_so );
395 if( strcmp(statusStr, "avail") == 0 )
397 else if( strcmp(statusStr, "sold") == 0 )
399 else if( strcmp(statusStr, "error") == 0 )
402 fprintf(stderr, "Unknown response from dispense server (status '%s')\n",
404 return RV_UNKNOWN_ERROR;
406 Dest->Price = atoi( buf + matches[6].rm_so );
408 // Hack a little to reduce heap fragmentation
410 char tmpType[strlen(buf + matches[3].rm_so) + 1];
411 char tmpDesc[strlen(buf + matches[7].rm_so) + 1];
412 strcpy(tmpType, buf + matches[3].rm_so);
413 strcpy(tmpDesc, buf + matches[7].rm_so);
415 Dest->Type = strdup( tmpType );
416 Dest->Desc = strdup( tmpDesc );
423 * \brief Fill the item information structure
424 * \return Boolean Failure
426 void PopulateItemList(int Socket)
433 regmatch_t matches[4];
435 // Ask server for stock list
436 send(Socket, "ENUM_ITEMS\n", 11, 0);
437 buf = ReadLine(Socket);
439 //printf("Output: %s\n", buf);
441 responseCode = atoi(buf);
442 if( responseCode != 201 ) {
443 fprintf(stderr, "Unknown response from dispense server (Response Code %i)\n", responseCode);
444 exit(RV_UNKNOWN_ERROR);
452 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
454 arrayType = &buf[ matches[2].rm_so ]; buf[ matches[2].rm_eo ] = '\0';
455 count = atoi( &buf[ matches[3].rm_so ] );
458 if( strcmp(arrayType, "Items") != 0 ) {
460 fprintf(stderr, "Unexpected array type, expected 'Items', got '%s'\n",
462 exit(RV_UNKNOWN_ERROR);
467 gaItems = malloc( giNumItems * sizeof(tItem) );
469 // Fetch item information
470 for( i = 0; i < giNumItems; i ++ )
472 ReadItemInfo( Socket, &gaItems[i] );
476 buf = ReadLine(Socket);
477 responseCode = atoi(buf);
479 if( responseCode != 200 ) {
480 fprintf(stderr, "Unknown response from dispense server %i\n'%s'",
491 * \brief Get information on an item
492 * \return Boolean Failure
494 int Dispense_ItemInfo(int Socket, const char *Type, int ID)
500 sendf(Socket, "ITEM_INFO %s:%i\n", Type, ID);
502 ret = ReadItemInfo(Socket, &item);
505 printf("%8s:%-2i %2i.%02i %s\n",
507 item.Price/100, item.Price%100,
516 int DispenseCheckPin(int Socket, const char *Username, const char *Pin)
518 int ret, responseCode;
521 if( strlen(Pin) != 4 ) {
522 fprintf(stderr, "Pin format incorrect (not 4 characters long)\n");
526 for( int i = 0; i < 4; i ++ ) {
527 if( !isdigit(Pin[i]) ) {
528 fprintf(stderr, "Pin format incorrect (character %i not a digit)\n", i);
533 sendf(Socket, "PIN_CHECK %s %s\n", Username, Pin);
534 buf = ReadLine(Socket);
536 responseCode = atoi(buf);
537 switch( responseCode )
539 case 200: // Pin correct
544 printf("Pin incorrect\n");
545 ret = RV_INVALID_USER;
548 printf("Not authenticated\n");
549 ret = RV_PERMISSIONS;
552 printf("Only coke members can check accounts other than their own\n");
553 ret = RV_PERMISSIONS;
556 printf("User '%s' not found\n", Username);
557 ret = RV_INVALID_USER;
560 printf("Rate limited or client-server disagree on pin format\n");
561 ret = RV_SERVER_ERROR;
564 printf("Unknown response code %i ('%s')\n", responseCode, buf);
565 ret = RV_UNKNOWN_ERROR;
572 int DispenseSetPin(int Socket, const char *Pin)
574 int ret, responseCode;
577 if( strlen(Pin) != 4 ) {
578 fprintf(stderr, "Pin format incorrect (not 4 characters long)\n");
582 for( int i = 0; i < 4; i ++ ) {
583 if( !isdigit(Pin[i]) ) {
584 fprintf(stderr, "Pin format incorrect (character %i not a digit)\n", i);
589 sendf(Socket, "PIN_SET %s\n", Pin);
590 buf = ReadLine(Socket);
592 responseCode = atoi(buf);
596 printf("Pin Updated\n");
600 printf("Not authenticated\n");
601 ret = RV_PERMISSIONS;
604 printf("Client/server disagreement on pin format\n");
605 ret = RV_SERVER_ERROR;
608 printf("Unknown response code %i ('%s')\n", responseCode, buf);
609 ret = RV_UNKNOWN_ERROR;
616 * \brief Dispense an item
617 * \return Boolean Failure
619 int DispenseItem(int Socket, const char *Type, int ID)
621 int ret, responseCode;
624 // Check for a dry run
626 printf("Dry Run - No action\n");
631 sendf(Socket, "DISPENSE %s:%i\n", Type, ID);
632 buf = ReadLine(Socket);
634 responseCode = atoi(buf);
635 switch( responseCode )
638 printf("Dispense OK\n");
642 printf("Not authenticated\n");
643 ret = RV_PERMISSIONS;
646 printf("Insufficient balance\n");
650 printf("Bad item name\n");
654 printf("Item failed to dispense, is the slot empty?\n");
655 ret = RV_SERVER_ERROR;
658 printf("Dispense not possible (slot empty/permissions)\n");
659 ret = RV_SERVER_ERROR;
662 printf("Unknown response code %i ('%s')\n", responseCode, buf);
663 ret = RV_UNKNOWN_ERROR;
672 * \brief Alter a user's balance
674 int Dispense_AlterBalance(int Socket, const char *Username, int Ammount, const char *Reason)
677 int responseCode, rv = -1;
679 // Check for a dry run
681 printf("Dry Run - No action\n");
687 printf("An amount would be nice\n");
691 sendf(Socket, "ADD %s %i %s\n", Username, Ammount, Reason);
692 buf = ReadLine(Socket);
694 responseCode = atoi(buf);
702 fprintf(stderr, "Insufficient balance\n");
705 case 403: // Not in coke
706 fprintf(stderr, "Permissions error: %s\n", buf+4);
709 case 404: // Unknown user
710 fprintf(stderr, "Unknown user '%s'\n", Username);
711 rv = RV_INVALID_USER;
714 fprintf(stderr, "Unknown response code %i\n'%s'\n", responseCode, buf);
715 rv = RV_UNKNOWN_RESPONSE;
724 * \brief Set a user's balance
725 * \note Only avaliable to dispense admins
727 int Dispense_SetBalance(int Socket, const char *Username, int Balance, const char *Reason)
732 // Check for a dry run
734 printf("Dry Run - No action\n");
738 sendf(Socket, "SET %s %i %s\n", Username, Balance, Reason);
739 buf = ReadLine(Socket);
741 responseCode = atoi(buf);
746 case 200: return 0; // OK
747 case 403: // Not an administrator
748 fprintf(stderr, "You are not an admin\n");
749 return RV_PERMISSIONS;
750 case 404: // Unknown user
751 fprintf(stderr, "Unknown user '%s'\n", Username);
752 return RV_INVALID_USER;
754 fprintf(stderr, "Unknown response code %i\n", responseCode);
755 return RV_UNKNOWN_RESPONSE;
762 * \brief Give money to another user
764 int Dispense_Give(int Socket, const char *Username, int Ammount, const char *Reason)
770 printf("Sorry, you can only give, you can't take.\n");
774 // Fast return on zero
776 printf("Are you actually going to give any?\n");
780 // Check for a dry run
782 printf("Dry Run - No action\n");
786 sendf(Socket, "GIVE %s %i %s\n", Username, Ammount, Reason);
788 buf = ReadLine(Socket);
789 responseCode = atoi(buf);
794 printf("Give succeeded\n");
795 return RV_SUCCESS; // OK
798 fprintf(stderr, "Insufficient balance\n");
801 case 404: // Unknown user
802 fprintf(stderr, "Unknown user '%s'\n", Username);
803 return RV_INVALID_USER;
806 fprintf(stderr, "Unknown response code %i\n", responseCode);
807 return RV_UNKNOWN_RESPONSE;
813 int Dispense_Refund(int Socket, const char *Username, const char *Item, int PriceOverride)
816 int responseCode, ret = -1;
819 if( RunRegex(&gUserItemIdentRegex, Item, 0, NULL, NULL) != 0 )
821 fprintf(stderr, "Error: Invalid item ID passed (should be <type>:<num>)\n");
825 // Check username (quick)
826 if( strchr(Username, ' ') || strchr(Username, '\n') )
828 fprintf(stderr, "Error: Username is invalid (no spaces or newlines please)\n");
833 sendf(Socket, "REFUND %s %s %i\n", Username, Item, PriceOverride);
835 buf = ReadLine(Socket);
836 responseCode = atoi(buf);
840 Dispense_ShowUser(Socket, Username); // Show destination account
844 fprintf(stderr, "Refund access is only avaliable to coke members\n");
845 ret = RV_PERMISSIONS;
848 fprintf(stderr, "Unknown user '%s' passed\n", Username);
849 ret = RV_INVALID_USER;
852 fprintf(stderr, "Invalid item '%s' passed\n", Item);
856 fprintf(stderr, "Unknown response from server %i\n%s\n", responseCode, buf);
865 * \brief Donate money to the club
867 int Dispense_Donate(int Socket, int Ammount, const char *Reason)
873 printf("Sorry, you can only give, you can't take.\n");
877 // Fast return on zero
879 printf("Are you actually going to give any?\n");
883 // Check for a dry run
885 printf("Dry Run - No action\n");
889 sendf(Socket, "DONATE %i %s\n", Ammount, Reason);
890 buf = ReadLine(Socket);
892 responseCode = atoi(buf);
897 case 200: return 0; // OK
900 fprintf(stderr, "Insufficient balance\n");
904 fprintf(stderr, "Unknown response code %i\n", responseCode);
912 * \brief Enumerate users
914 int Dispense_EnumUsers(int Socket)
919 regmatch_t matches[4];
921 if( giMinimumBalance != INT_MIN ) {
922 if( giMaximumBalance != INT_MAX ) {
923 sendf(Socket, "ENUM_USERS min_balance:%i max_balance:%i\n", giMinimumBalance, giMaximumBalance);
926 sendf(Socket, "ENUM_USERS min_balance:%i\n", giMinimumBalance);
930 if( giMaximumBalance != INT_MAX ) {
931 sendf(Socket, "ENUM_USERS max_balance:%i\n", giMaximumBalance);
934 sendf(Socket, "ENUM_USERS\n");
937 buf = ReadLine(Socket);
938 responseCode = atoi(buf);
942 case 201: break; // Ok, length follows
945 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
950 // Get count (not actually used)
951 RunRegex(&gArrayRegex, buf, 4, matches, "Malformed server response");
952 nUsers = atoi( buf + matches[3].rm_so );
953 printf("%i users returned\n", nUsers);
958 // Read returned users
960 buf = ReadLine(Socket);
961 responseCode = atoi(buf);
963 if( responseCode != 202 ) break;
967 } while(responseCode == 202);
969 // Check final response
970 if( responseCode != 200 ) {
971 fprintf(stderr, "Unknown response code %i\n%s\n", responseCode, buf);
981 int Dispense_ShowUser(int Socket, const char *Username)
984 int responseCode, ret;
986 sendf(Socket, "USER_INFO %s\n", Username);
987 buf = ReadLine(Socket);
989 responseCode = atoi(buf);
999 printf("Unknown user '%s'\n", Username);
1004 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1014 void _PrintUserLine(const char *Line)
1016 regmatch_t matches[6];
1019 RunRegex(&gUserInfoRegex, Line, 6, matches, "Malformed server response");
1024 int usernameLen = matches[3].rm_eo - matches[3].rm_so;
1025 char username[usernameLen + 1];
1026 int flagsLen = matches[5].rm_eo - matches[5].rm_so;
1027 char flags[flagsLen + 1];
1029 memcpy(username, Line + matches[3].rm_so, usernameLen);
1030 username[usernameLen] = '\0';
1031 memcpy(flags, Line + matches[5].rm_so, flagsLen);
1032 flags[flagsLen] = '\0';
1034 bal = atoi(Line + matches[4].rm_so);
1035 printf("%-15s: $%8.02f (%s)\n", username, ((float)bal)/100, flags);
1039 int Dispense_AddUser(int Socket, const char *Username)
1042 int responseCode, ret;
1044 // Check for a dry run
1046 printf("Dry Run - No action\n");
1050 sendf(Socket, "USER_ADD %s\n", Username);
1052 buf = ReadLine(Socket);
1053 responseCode = atoi(buf);
1055 switch(responseCode)
1058 printf("User '%s' added\n", Username);
1063 printf("Only wheel can add users\n");
1068 printf("User '%s' already exists\n", Username);
1073 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1083 int Dispense_SetUserType(int Socket, const char *Username, const char *TypeString, const char *Reason)
1086 int responseCode, ret;
1088 // Check for a dry run
1090 printf("Dry Run - No action\n");
1094 // TODO: Pre-validate the string
1096 sendf(Socket, "USER_FLAGS %s %s %s\n", Username, TypeString, Reason);
1098 buf = ReadLine(Socket);
1099 responseCode = atoi(buf);
1101 switch(responseCode)
1104 printf("User '%s' updated\n", Username);
1109 printf("Only dispense admins can modify users\n");
1110 ret = RV_PERMISSIONS;
1114 printf("User '%s' does not exist\n", Username);
1115 ret = RV_INVALID_USER;
1119 printf("Flag string is invalid\n");
1124 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1125 ret = RV_UNKNOWN_RESPONSE;
1134 int Dispense_SetItem(int Socket, const char *Type, int ID, int NewPrice, const char *NewName)
1137 int responseCode, ret;
1139 // Check for a dry run
1141 printf("Dry Run - No action\n");
1145 sendf(Socket, "UPDATE_ITEM %s:%i %i %s\n", Type, ID, NewPrice, NewName);
1147 buf = ReadLine(Socket);
1148 responseCode = atoi(buf);
1150 switch(responseCode)
1153 printf("Item %s:%i updated\n", Type, ID);
1158 printf("Only coke members can modify the slots\n");
1159 ret = RV_PERMISSIONS;
1163 printf("Invalid item passed\n");
1168 fprintf(stderr, "Unknown response code %i '%s'\n", responseCode, buf);
1181 /// Read from the input socket until a newline is seen
1182 char *ReadLine(int Socket)
1184 static char buf[BUFSIZ];
1185 static int bufValid = 0;
1187 char *newline = NULL;
1189 char *ret = malloc(32);
1191 #if DEBUG_TRACE_SERVER
1192 printf("ReadLine: ");
1198 // While a newline hasn't been seen
1201 assert(bufValid < BUFSIZ);
1202 // If there is data left over from a previous call, use the data from that for the first pass
1208 // Otherwise read some data
1209 len = recv(Socket, buf, BUFSIZ, 0);
1212 return strdup("599 Client Connection Error\n");
1215 assert(len < BUFSIZ);
1218 // Search for newline in buffer
1219 newline = strchr( buf, '\n' );
1224 // Increment return length by amount of data up to newline (or end of read)
1225 retLen += strlen(buf);
1226 ret = realloc(ret, retLen + 1);
1227 assert(ret); // evil NULL check
1228 strcat( ret, buf ); // append buffer data
1231 #if DEBUG_TRACE_SERVER
1232 printf("%i '%s'\n", retLen, ret);
1235 // If the newline wasn't the last character in the buffer. (I.e. there's extra data for the next call)
1236 assert(newline - buf + 1 <= len);
1237 if( newline - buf + 1 < len ) {
1238 int extra_bytes = len - (newline - buf + 1);
1239 // Copy `extra_bytes` from end of buffer down to start and set `bufValid` to `extra_bytes`?
1240 memmove(&buf[0], newline + 1, extra_bytes);
1241 bufValid = extra_bytes;
1243 #if DEBUG_TRACE_SERVER > 1
1244 printf("- Caching %i bytes '%.*s'\n", bufValid, bufValid, buf);
1251 int sendf(int Socket, const char *Format, ...)
1256 va_start(args, Format);
1257 len = vsnprintf(NULL, 0, Format, args);
1262 va_start(args, Format);
1263 vsnprintf(buf, len+1, Format, args);
1266 #if DEBUG_TRACE_SERVER
1267 printf("sendf: %s", buf);
1270 return send(Socket, buf, len, 0);