/* * This file contains the socket code, used for accepting * new connections as well as reading and writing to * sockets, and closing down unused sockets. */ #include <errno.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/time.h> #include <event2/event.h> #include <event2/buffer.h> #include <event2/bufferevent.h> /* including main header file */ #include "mud.h" /* global variables */ struct event_base *io_base; evutil_socket_t io_sock; struct event *io_sock_event; D_SOCKET *dsock_free = NULL; /* the socket free list */ D_SOCKET *dsock_list = NULL; /* the linked list of active sockets */ D_MOBILE *dmobile_free = NULL; /* the mobile free list */ D_MOBILE *dmobile_list = NULL; /* the mobile list of active mobiles */ /* mccp support */ #ifndef NOMCCP const unsigned char compress_will [] = { IAC, WILL, TELOPT_COMPRESS, '\0' }; const unsigned char compress_will2 [] = { IAC, WILL, TELOPT_COMPRESS2, '\0' }; #endif const unsigned char do_echo [] = { IAC, WONT, TELOPT_ECHO, '\0' }; const unsigned char dont_echo [] = { IAC, WILL, TELOPT_ECHO, '\0' }; #ifdef CYGWIN32 pthread_mutex_t lookup_mutex = PTHREAD_MUTEX_INITIALIZER; #endif /* * This is where it all starts, nothing special. */ int main(int argc, char **argv) { bool copyover = FALSE; /* note startup */ log_string("Program starting."); if (argc > 2 && atoi(argv[2]) > 0) { copyover = TRUE; io_sock = atoi(argv[2]); } /* init */ init_io(copyover); /* load */ load_muddata(copyover); /* run */ event_base_dispatch(io_base); /* kill */ kill_io(); /* note shutdown */ log_string("Program terminated without errors."); return 0; } void prompt(D_SOCKET *dsock) { /* bust a prompt */ if (dsock->state == STATE_PLAYING && dsock->bust_prompt) { text_to_buffer(dsock, "EventMud:> "); dsock->bust_prompt = FALSE; } } /* * Set of callback routines to handle socket and timer events */ void cb_recycle(evutil_socket_t sockfd, short flags, void *arg) { struct timeval tv; recycle_sockets(); /* reschedule the recycle timer */ tv.tv_sec = 10; tv.tv_usec = 0; event_base_once(io_base, -1, EV_TIMEOUT, cb_recycle, NULL, &tv); } void cb_update(evutil_socket_t sockfd, short flags, void *arg) { struct timeval tv; update_handler(); /* reschedule the update timer */ tv.tv_sec = 0; tv.tv_usec = 1000000 / PULSES_PER_SECOND; event_base_once(io_base, -1, EV_TIMEOUT, cb_update, NULL, &tv); } void cb_command(evutil_socket_t sockfd, short flags, void *arg) { D_SOCKET *dsock = arg; if (dsock->state == STATE_CLOSED) { return; } /* check for pending command */ if (dsock->next_command[0] != '\0') { struct timeval tv; process_cmd(dsock); retrieve_cmd(dsock); /* reschedule command timer */ tv.tv_sec = 0; tv.tv_usec = 1000000 / PULSES_PER_SECOND; event_base_once(io_base, -1, EV_TIMEOUT, cb_command, (void*) dsock, &tv); } /* bust a prompt */ prompt(dsock); } void cb_read(struct bufferevent *event, void *arg) { D_SOCKET *dsock = arg; /* transfer to input buffer - could be optimized not to */ struct evbuffer *buffer = bufferevent_get_input(event); int size = strlen(dsock->inbuf); int left = sizeof(dsock->inbuf) - size - 1; int read = evbuffer_remove(buffer, (void*) (dsock->inbuf + size), left); dsock->inbuf[size + read] = '\0'; /* discard the remainder */ evbuffer_drain(buffer, evbuffer_get_length(buffer)); /* check for pending command */ if (dsock->next_command[0] == '\0') { retrieve_cmd(dsock); cb_command(-1, 0, arg); } } void cb_write(struct bufferevent *event, void *arg) { D_SOCKET *dsock = arg; /* bust a prompt */ prompt(dsock); } void cb_error(struct bufferevent *event, short flags, void *arg) { D_SOCKET *dsock = arg; /* disconnect */ close_socket(dsock, FALSE); } void cb_accept(evutil_socket_t sockfd, short flags, void *arg) { struct sockaddr_storage ss; socklen_t slen = sizeof(ss); int fd; /* connect */ fd = accept(sockfd, (struct sockaddr*) &ss, &slen); evutil_make_socket_nonblocking(fd); new_socket(fd); } /* * io startup and shutdown functions */ void init_io(bool copyover) { struct timeval tv; /* initialize io */ io_base = event_base_new(); /* open game socket */ if (copyover == FALSE) { struct sockaddr_in sin; io_sock = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_nonblocking(io_sock); evutil_make_listen_socket_reuseable(io_sock); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(MUDPORT); bind(io_sock, (struct sockaddr*) &sin, sizeof(struct sockaddr)); listen(io_sock, 3); } /* register accept handler */ io_sock_event = event_new(io_base, io_sock, (EV_READ | EV_PERSIST), cb_accept, NULL); event_add(io_sock_event, NULL); /* schedule recycle timer */ tv.tv_sec = 10; tv.tv_usec = 0; event_base_once(io_base, -1, EV_TIMEOUT, cb_recycle, NULL, &tv); /* schedule update timer */ tv.tv_sec = 0; tv.tv_usec = 1000000 / PULSES_PER_SECOND; event_base_once(io_base, -1, EV_TIMEOUT, cb_update, NULL, &tv); } void kill_io() { D_SOCKET *dsock, *dsock_next; /* close user sockets */ for (dsock = dsock_list; dsock; dsock = dsock_next) { dsock_next = dsock->next; if (dsock->lookup_status == STATE_CLOSED) continue; close_socket(dsock, FALSE); } /* deregister accept handler */ event_del(io_sock_event); event_free(io_sock_event); /* close game socket */ close(io_sock); /* release io */ event_base_free(io_base); } /* * New_socket() * * Initializes a new socket, get's the hostname * and puts it in the active socket_list. */ void new_socket(int fd) { D_SOCKET * dsock; struct sockaddr_in sock_addr; pthread_t thread_lookup; LOOKUP_DATA * lData; socklen_t size; pthread_attr_t attr; /* initialize threads */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); /* * allocate some memory for a new socket if * there is no free socket in the free_list */ if (dsock_free == NULL) { if ((dsock = (D_SOCKET *) malloc(sizeof(D_SOCKET))) == NULL) { bug("New_socket: Cannot allocate memory for socket."); abort(); } } else { dsock = dsock_free; dsock_free = dsock_free->next; } /* reset the socket */ reset_socket(dsock, fd); /* update the linked list of sockets */ dsock->next = dsock_list; dsock_list = dsock; /* do a host lookup */ size = sizeof(sock_addr); if (getpeername(fd, (struct sockaddr *) &sock_addr, &size) < 0) { perror("New_socket: getpeername"); dsock->hostname = strdup("unknown"); } else { /* set the IP number as the temporary hostname */ dsock->hostname = strdup(inet_ntoa(sock_addr.sin_addr)); if (!compares(dsock->hostname, "127.0.0.1")) { /* allocate some memory for the lookup data */ if ((lData = malloc(sizeof(*lData))) == NULL) { bug("New_socket: Cannot allocate memory for lookup data."); abort(); } /* Set the lookup_data for use in lookup_address() */ lData->buf = strdup((char *) &sock_addr.sin_addr); lData->dsock = dsock; /* dispatch the lookup thread */ pthread_create(&thread_lookup, &attr, &lookup_address, (void*) lData); } else dsock->lookup_status++; } /* negotiate compression */ #ifndef NOMCCP text_to_buffer(dsock, (char *) compress_will2); text_to_buffer(dsock, (char *) compress_will); #endif /* send the greeting */ text_to_buffer(dsock, greeting); text_to_buffer(dsock, "What is your name? "); } /* * Close_socket() * * Will close one socket directly, freeing all * resources and making the socket availably on * the socket free_list. */ void close_socket(D_SOCKET *dsock, bool reconnect) { if (dsock->lookup_status > TSTATE_DONE) return; dsock->lookup_status += 2; /* remove the socket */ if (dsock->state == STATE_PLAYING) { if (reconnect) text_to_socket(dsock, "This connection has been taken over.\n\r"); else if (dsock->player) { dsock->player->socket = NULL; log_string("Closing link to %s", dsock->player->name); } } else if (dsock->player) free_mobile(dsock->player); /* set the closed state */ close(dsock->control); dsock->state = STATE_CLOSED; /* release user context */ bufferevent_free(dsock->context); dsock->context = NULL; } void reset_socket(D_SOCKET *sock_new, int fd) { struct bufferevent *event; /* reset the structure */ bzero(sock_new, sizeof(*sock_new)); sock_new->control = fd; sock_new->state = STATE_NEW_NAME; sock_new->lookup_status = TSTATE_LOOKUP; /* allocate user context */ event = (void*) bufferevent_socket_new(io_base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(event, cb_read, cb_write, cb_error, (void*) sock_new); bufferevent_setwatermark(event, EV_READ, 0, 4096); bufferevent_enable(event, (EV_READ | EV_WRITE)); sock_new->context = event; } /* * Text_to_buffer() * * Stores outbound text in a buffer, where it will * stay untill it is flushed in the gameloop. * * Will also parse ANSI colors and other tags. */ void text_to_buffer(D_SOCKET *dsock, const char *txt) { static char output[8 * MAX_BUFFER]; bool underline = FALSE, bold = FALSE; int iPtr = 0, last = -1, j, k; int length = strlen(txt); /* the color struct */ struct sAnsiColor { const char cTag; const char * cString; int aFlag; }; /* the color table... */ const struct sAnsiColor ansiTable[] = { { 'd', "30", eTHIN }, { 'D', "30", eBOLD }, { 'r', "31", eTHIN }, { 'R', "31", eBOLD }, { 'g', "32", eTHIN }, { 'G', "32", eBOLD }, { 'y', "33", eTHIN }, { 'Y', "33", eBOLD }, { 'b', "34", eTHIN }, { 'B', "34", eBOLD }, { 'p', "35", eTHIN }, { 'P', "35", eBOLD }, { 'c', "36", eTHIN }, { 'C', "36", eBOLD }, { 'w', "37", eTHIN }, { 'W', "37", eBOLD }, /* the end tag */ { '\0', "", eTHIN } }; if (length >= MAX_BUFFER) { log_string("text_to_buffer: buffer overflow."); return; } /* always start with a leading space */ if (dsock->top_output == 0) { dsock->outbuf[0] = '\n'; dsock->outbuf[1] = '\r'; dsock->top_output = 2; } while (*txt != '\0') { /* simple bound checking */ if (iPtr > (8 * MAX_BUFFER - 15)) break; switch(*txt) { default: output[iPtr++] = *txt++; break; case '#': txt++; /* toggle underline on/off with #u */ if (*txt == 'u') { txt++; if (underline) { underline = FALSE; output[iPtr++] = 27; output[iPtr++] = '['; output[iPtr++] = '0'; if (bold) { output[iPtr++] = ';'; output[iPtr++] = '1'; } if (last != -1) { output[iPtr++] = ';'; for (j = 0; ansiTable[last].cString[j] != '\0'; j++) { output[iPtr++] = ansiTable[last].cString[j]; } } output[iPtr++] = 'm'; } else { underline = TRUE; output[iPtr++] = 27; output[iPtr++] = '['; output[iPtr++] = '4'; output[iPtr++] = 'm'; } } /* parse ## to # */ else if (*txt == '#') { txt++; output[iPtr++] = '#'; } /* #n should clear all tags */ else if (*txt == 'n') { txt++; if (last != -1 || underline || bold) { underline = FALSE; bold = FALSE; output[iPtr++] = 27; output[iPtr++] = '['; output[iPtr++] = '0'; output[iPtr++] = 'm'; } last = -1; } /* check for valid color tag and parse */ else { bool validTag = FALSE; for (j = 0; ansiTable[j].cString[0] != '\0'; j++) { if (*txt == ansiTable[j].cTag) { validTag = TRUE; /* we only add the color sequence if it's needed */ if (last != j) { bool cSequence = FALSE; /* escape sequence */ output[iPtr++] = 27; output[iPtr++] = '['; /* remember if a color change is needed */ if (last == -1 || last / 2 != j / 2) cSequence = TRUE; /* handle font boldness */ if (bold && ansiTable[j].aFlag == eTHIN) { output[iPtr++] = '0'; bold = FALSE; if (underline) { output[iPtr++] = ';'; output[iPtr++] = '4'; } /* changing to eTHIN wipes the old color */ output[iPtr++] = ';'; cSequence = TRUE; } else if (!bold && ansiTable[j].aFlag == eBOLD) { output[iPtr++] = '1'; bold = TRUE; if (cSequence) output[iPtr++] = ';'; } /* add color sequence if needed */ if (cSequence) { for (k = 0; ansiTable[j].cString[k] != '\0'; k++) { output[iPtr++] = ansiTable[j].cString[k]; } } output[iPtr++] = 'm'; } /* remember the last color */ last = j; } } /* it wasn't a valid color tag */ if (!validTag) output[iPtr++] = '#'; else txt++; } break; } } /* and terminate it with the standard color */ if (last != -1 || underline || bold) { output[iPtr++] = 27; output[iPtr++] = '['; output[iPtr++] = '0'; output[iPtr++] = 'm'; } output[iPtr] = '\0'; /* check to see if the socket can accept that much data */ if (dsock->top_output + iPtr >= MAX_OUTPUT) { bug("Text_to_buffer: ouput overflow on %s.", dsock->hostname); return; } /* add data to buffer */ strcpy(dsock->outbuf + dsock->top_output, output); dsock->top_output += iPtr; /* reset the top pointer */ dsock->top_output = 0; /* queue onto socket */ text_to_socket(dsock, dsock->outbuf); } /* * Text_to_socket() * * Sends text directly to the socket, * will compress the data if needed. */ bool text_to_socket(D_SOCKET *dsock, const char *txt) { int iBlck, iPtr, iWrt = 0, length; length = strlen(txt); /* write compressed */ #ifndef NOMCCP if (dsock && dsock->out_compress) { dsock->out_compress->next_in = (unsigned char *) txt; dsock->out_compress->avail_in = length; while (dsock->out_compress->avail_in) { dsock->out_compress->avail_out = COMPRESS_BUF_SIZE - (dsock->out_compress->next_out - dsock->out_compress_buf); if (dsock->out_compress->avail_out) { int status = deflate(dsock->out_compress, Z_SYNC_FLUSH); if (status != Z_OK) return FALSE; } length = dsock->out_compress->next_out - dsock->out_compress_buf; if (length > 0) { for (iPtr = 0; iPtr < length; iPtr += iWrt) { iBlck = UMIN(length - iPtr, 4096); if (bufferevent_write((struct bufferevent*) dsock->context, (void*) (dsock->out_compress_buf + iPtr), iBlck) < 0) { bug("Text_to_socket (compressed): bufferevent_write() failed"); return FALSE; } iWrt = iBlck; } if (iWrt <= 0) break; if (iPtr > 0) { if (iPtr < length) memmove(dsock->out_compress_buf, dsock->out_compress_buf + iPtr, length - iPtr); dsock->out_compress->next_out = dsock->out_compress_buf + length - iPtr; } } } return TRUE; } #endif /* write uncompressed */ for (iPtr = 0; iPtr < length; iPtr += iWrt) { iBlck = UMIN(length - iPtr, 4096); if (bufferevent_write((struct bufferevent*) dsock->context, (void*) (txt + iPtr), iBlck) < 0) { bug("Text_to_socket: bufferevent_write() failed"); return FALSE; } iWrt = iBlck; } return TRUE; } /* * Text_to_mobile() * * If the mobile has a socket, then the data will * be send to text_to_buffer(). */ void text_to_mobile(D_MOBILE *dMob, const char *txt) { if (dMob->socket) { text_to_buffer(dMob->socket, txt); dMob->socket->bust_prompt = TRUE; } } void retrieve_cmd(D_SOCKET *dsock) { int size = 0, i = 0, j = 0; #ifndef NOMCCP int telopt = 0; #endif /* if theres already a command ready, we return */ if (dsock->next_command[0] != '\0') return; /* if there is nothing pending, then return */ if (dsock->inbuf[0] == '\0') return; /* check how long the next command is */ while (dsock->inbuf[size] != '\0' && dsock->inbuf[size] != '\n' && dsock->inbuf[size] != '\r') size++; /* we only deal with real commands - but treat a full buffer as a command */ if (size < (sizeof(dsock->inbuf) - 1) && dsock->inbuf[size] == '\0') return; /* copy the next command into next_command */ for ( ; i < size; i++) { #ifndef NOMCCP if (dsock->inbuf[i] == (signed char) IAC) { telopt = 1; } else if (telopt == 1 && (dsock->inbuf[i] == (signed char) DO || dsock->inbuf[i] == (signed char) DONT)) { telopt = 2; } else if (telopt == 2) { telopt = 0; if (dsock->inbuf[i] == (signed char) TELOPT_COMPRESS) /* check for version 1 */ { if (dsock->inbuf[i-1] == (signed char) DO) /* start compressing */ compressStart(dsock, TELOPT_COMPRESS); else if (dsock->inbuf[i-1] == (signed char) DONT) /* stop compressing */ compressEnd(dsock, TELOPT_COMPRESS, FALSE); } else if (dsock->inbuf[i] == (signed char) TELOPT_COMPRESS2) /* check for version 2 */ { if (dsock->inbuf[i-1] == (signed char) DO) /* start compressing */ compressStart(dsock, TELOPT_COMPRESS2); else if (dsock->inbuf[i-1] == (signed char) DONT) /* stop compressing */ compressEnd(dsock, TELOPT_COMPRESS2, FALSE); } } else #endif if (isprint((int) dsock->inbuf[i]) && isascii((int) dsock->inbuf[i])) { dsock->next_command[j++] = dsock->inbuf[i]; } } dsock->next_command[j] = '\0'; /* skip forward to the next line */ while (dsock->inbuf[size] == '\n' || dsock->inbuf[size] == '\r') { dsock->bust_prompt = TRUE; /* seems like a good place to check */ size++; } /* use i as a static pointer */ i = size; /* move the context of inbuf down */ while (dsock->inbuf[size] != '\0') { dsock->inbuf[size - i] = dsock->inbuf[size]; size++; } dsock->inbuf[size - i] = '\0'; } void process_cmd(D_SOCKET *dsock) { /* figure out how to deal with the incoming command */ switch(dsock->state) { default: break; case STATE_NEW_NAME: case STATE_NEW_PASSWORD: case STATE_VERIFY_PASSWORD: case STATE_ASK_PASSWORD: handle_new_connections(dsock, dsock->next_command); break; case STATE_PLAYING: handle_cmd_input(dsock, dsock->next_command); break; } dsock->next_command[0] = '\0'; } void handle_new_connections(D_SOCKET *dsock, char *arg) { D_MOBILE *p_new; int i; switch(dsock->state) { default: bug("Handle_new_connections: Bad state."); break; case STATE_NEW_NAME: if (dsock->lookup_status != TSTATE_DONE) { text_to_buffer(dsock, "Making a dns lookup, please have patience.\n\rWhat is your name? "); return; } if (!check_name(arg)) /* check for a legal name */ { text_to_buffer(dsock, "Sorry, that's not a legal name, please pick another.\n\rWhat is your name? "); break; } arg[0] = toupper((int) arg[0]); log_string("%s is trying to connect.", arg); /* Check for a new Player */ if ((p_new = load_profile(arg)) == NULL) { if (dmobile_free == NULL) { if ((p_new = malloc(sizeof(*p_new))) == NULL) { bug("Handle_new_connection: Cannot allocate memory."); abort(); } } else { p_new = dmobile_free; dmobile_free = dmobile_free->next; } clear_mobile(p_new); /* give the player it's name */ p_new->name = strdup(arg); /* prepare for next step */ text_to_buffer(dsock, "Please enter a new password: "); dsock->state = STATE_NEW_PASSWORD; } else /* old player */ { /* prepare for next step */ text_to_buffer(dsock, "What is your password? "); dsock->state = STATE_ASK_PASSWORD; } /* socket <-> player */ p_new->socket = dsock; dsock->player = p_new; break; case STATE_NEW_PASSWORD: if (strlen(arg) < 5 || strlen(arg) > 12) { text_to_buffer(dsock, "Between 5 and 12 chars please!\n\rPlease enter a new password: "); return; } dsock->player->password = strdup(crypt(arg, dsock->player->name)); for (i = 0; dsock->player->password[i] != '\0'; i++) { if (dsock->player->password[i] == '~') { text_to_buffer(dsock, "Illegal password!\n\rPlease enter a new password: "); return; } } text_to_buffer(dsock, "Please verify the password: "); dsock->state = STATE_VERIFY_PASSWORD; break; case STATE_VERIFY_PASSWORD: if (compares(crypt(arg, dsock->player->name), dsock->player->password)) { /* put him in the list */ dsock->player->next = dmobile_list; dmobile_list = dsock->player; log_string("New player: %s has entered the game.", dsock->player->name); /* and into the game */ dsock->state = STATE_PLAYING; text_to_buffer(dsock, motd); } else { free(dsock->player->password); text_to_buffer(dsock, "Password mismatch!\n\rPlease enter a new password: "); dsock->state = STATE_NEW_PASSWORD; } break; case STATE_ASK_PASSWORD: if (compares(crypt(arg, dsock->player->name), dsock->player->password)) { if ((p_new = check_reconnect(dsock->player->name)) != NULL) { /* attach the new player */ ex_free_mob(dsock->player); dsock->player = p_new; p_new->socket = dsock; log_string("%s has reconnected.", dsock->player->name); /* and let him enter the game */ dsock->state = STATE_PLAYING; text_to_buffer(dsock, "You take over a body already in use.\n\r"); } else if ((p_new = load_player(dsock->player->name)) == NULL) { text_to_socket(dsock, "ERROR: Your pfile is missing!\n\r"); ex_free_mob(dsock->player); dsock->player = NULL; close_socket(dsock, FALSE); return; } else { /* attach the new player */ ex_free_mob(dsock->player); dsock->player = p_new; p_new->socket = dsock; /* put him in the active list */ p_new->next = dmobile_list; dmobile_list = p_new; log_string("%s has entered the game.", dsock->player->name); /* and let him enter the game */ dsock->state = STATE_PLAYING; text_to_buffer(dsock, motd); } } else { text_to_socket(dsock, "Bad password!\n\r"); ex_free_mob(dsock->player); dsock->player = NULL; close_socket(dsock, FALSE); } break; } } /* does the lookup, changes the hostname, and dies */ void *lookup_address(void *arg) { LOOKUP_DATA *lData = (LOOKUP_DATA *) arg; struct hostent *from = 0; #ifndef CYGWIN32 struct hostent ent; char buf[16384]; int err; #endif #ifdef CYGWIN32 pthread_mutex_lock(&lookup_mutex); from = gethostbyaddr(lData->buf, sizeof(lData->buf), AF_INET); if (from && from->h_name) { free(lData->dsock->hostname); lData->dsock->hostname = strdup(from->h_name); } pthread_mutex_unlock(&lookup_mutex); #else /* do the lookup and store the result at &from */ gethostbyaddr_r(lData->buf, sizeof(lData->buf), AF_INET, &ent, buf, 16384, &from, &err); /* did we get anything ? */ if (from && from->h_name) { free(lData->dsock->hostname); lData->dsock->hostname = strdup(from->h_name); } #endif /* set it ready to be closed or used */ lData->dsock->lookup_status++; /* free the lookup data */ free(lData->buf); free(lData); /* and kill the thread */ pthread_exit(0); #ifdef CYGWIN32 return NULL; #endif } void recycle_sockets() { D_SOCKET *dsock, *dsock_next; for (dsock = dsock_list; dsock; dsock = dsock_next) { dsock_next = dsock->next; if (dsock->lookup_status != TSTATE_CLOSED) continue; /* remove the socket from the socket list */ if (dsock == dsock_list) dsock_list = dsock->next; else { D_SOCKET *prev; for (prev = dsock_list; prev && prev->next != dsock; prev = prev->next) ; if (prev) prev->next = dsock->next; else bug("Recycle_sockets: Closed socket not in list"); } /* free the memory */ free(dsock->hostname); /* stop compression */ #ifndef NOMCCP compressEnd(dsock, dsock->compressing, TRUE); #endif /* put the socket in the free_list */ dsock->next = dsock_free; dsock_free = dsock; } }