/* * comm.c -- communications functions and more. * Dwayne Fontenot (Jacques@TMI) */ #include "std.h" #include "comm.h" #include "main.h" #include "socket_efuns.h" #include "backend.h" #include "socket_ctrl.h" #include "debug.h" #include "ed.h" #include "file.h" #include "master.h" #include "add_action.h" #include "eval.h" #include "console.h" #ifndef ENOSR #define ENOSR 63 #endif #ifndef ANSI_SUBSTITUTE #define ANSI_SUBSTITUTE 0x20 #endif #ifndef ADDRFAIL_NOTIFY #define ADDRFAIL_NOTIFY 0 #endif #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif #define TELOPT_MSSP 70 #define TELOPT_COMPRESS 85 #define TELOPT_COMPRESS2 86 #define TELOPT_MXP 91 // mud extension protocol #define TELOPT_ZMP 93 // zenith mud protocol #define TELOPT_GMCP 201 // something mud communication protocol, how many do we need? #define MSSP_VAR 1 #define MSSP_VAL 2 #ifndef MAX #define MAX(x,y) (((x)>(y))?(x):(y)) #endif #ifndef ENV_FILLER #define ENV_FILLER 0x1e #endif #define TELOPT_NEW_ENVIRON 39 #define NEW_ENV_IS 0 #define NEW_ENV_SEND 1 #define NEW_ENV_INFO 2 #define NEW_ENV_VAR 0 #define NEW_ENV_VALUE 1 #define NEW_ENV_ESC 2 #define NEW_ENV_USERVAR 3 static unsigned char telnet_break_response[] = { 28, IAC, WILL, TELOPT_TM }; static unsigned char telnet_ip_response[] = { 127, IAC, WILL, TELOPT_TM }; static unsigned char telnet_abort_response[] = { IAC, DM }; static unsigned char telnet_do_tm_response[] = { IAC, WILL, TELOPT_TM }; static unsigned char telnet_do_naws[] = { IAC, DO, TELOPT_NAWS }; static unsigned char telnet_dont_naws[] = { IAC, DONT, TELOPT_NAWS }; static unsigned char telnet_do_ttype[] = { IAC, DO, TELOPT_TTYPE }; static unsigned char telnet_term_query[] = { IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE }; static unsigned char telnet_no_echo[] = { IAC, WONT, TELOPT_ECHO }; static unsigned char telnet_no_single[] = { IAC, WONT, TELOPT_SGA }; static unsigned char telnet_yes_echo[] = { IAC, WILL, TELOPT_ECHO }; static unsigned char telnet_yes_single[] = { IAC, WILL, TELOPT_SGA }; static unsigned char telnet_ga[] = { IAC, GA }; static unsigned char telnet_ayt_response[] = { '\n', '[', '-', 'Y', 'e', 's', '-', ']', ' ', '\n' }; static unsigned char telnet_line_mode[] = { IAC, DO, TELOPT_LINEMODE }; static unsigned char telnet_lm_mode[] = { IAC, SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT | MODE_TRAPSIG, IAC, SE }; static unsigned char telnet_char_mode[] = { IAC, DONT, TELOPT_LINEMODE }; static unsigned char slc_default_flags[] = { SLC_NOSUPPORT, SLC_CANTCHANGE, SLC_CANTCHANGE, SLC_CANTCHANGE, SLC_CANTCHANGE, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_CANTCHANGE, SLC_CANTCHANGE, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT, SLC_NOSUPPORT }; static unsigned char slc_default_chars[] = { 0x00, BREAK, IP, AO, AYT, 0x00, 0x00, 0x00, SUSP, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #ifdef HAVE_ZLIB static unsigned char telnet_compress_send_request_v2[] = { IAC, WILL, TELOPT_COMPRESS2 }; static unsigned char telnet_compress_send_request_v1[] = { IAC, WILL, TELOPT_COMPRESS }; static unsigned char telnet_compress_v1_response[] = { IAC, SB, TELOPT_COMPRESS, WILL, SE }; static unsigned char telnet_compress_v2_response[] = { IAC, SB, TELOPT_COMPRESS2, IAC, SE }; #endif static unsigned char telnet_do_mxp[] = { IAC, DO, TELOPT_MXP }; static unsigned char telnet_will_mxp[] = { IAC, SB, TELOPT_MXP, IAC, SE }; static unsigned char telnet_will_mssp[] = { IAC, WILL, TELOPT_MSSP }; static unsigned char telnet_start_mssp[] = { IAC, SB, TELOPT_MSSP }; static unsigned char telnet_mssp_value[] = {MSSP_VAR, '%', 's', MSSP_VAL, '%', 's', 0}; static unsigned char telnet_end_sub[] = {IAC, SE}; static unsigned char telnet_will_zmp[] = { IAC, WILL, TELOPT_ZMP}; static unsigned char telnet_start_zmp[] = { IAC, SB, TELOPT_ZMP}; static unsigned char telnet_do_newenv[] = { IAC, DO, TELOPT_NEW_ENVIRON }; static unsigned char telnet_send_uservar[] = { IAC, SB, TELOPT_NEW_ENVIRON, NEW_ENV_SEND, IAC, SE }; static unsigned char telnet_do_gmcp[] = {IAC, DO, TELOPT_GMCP}; static unsigned char telnet_start_gmcp[] = {IAC, SB, TELOPT_GMCP}; /* * local function prototypes. */ #ifdef SIGNAL_FUNC_TAKES_INT static void sigpipe_handler (int); #else static void sigpipe_handler (void); #endif static void hname_handler (void); static void get_user_data (interactive_t *); static char *get_user_command (void); static char *first_cmd_in_buf (interactive_t *); static int cmd_in_buf (interactive_t *); static int call_function_interactive (interactive_t *, char *); static void print_prompt (interactive_t *); static void query_addr_name (object_t *); static void got_addr_number (char *, char *); #ifdef IPV6 static void add_ip_entry (struct in6_addr, char *); #else static void add_ip_entry (long, char *); #endif static void new_user_handler (int); static void end_compression (interactive_t *); static void start_compression (interactive_t *); static int send_compressed (interactive_t *ip, unsigned char* data, int length); #ifdef NO_SNOOP # define handle_snoop(str, len, who) #else # define handle_snoop(str, len, who) if ((who)->snooped_by) receive_snoop(str, len, who->snooped_by) static void receive_snoop (const char *, int, object_t * ob); #endif /* * public local variables. */ fd_set readmask, writemask; int num_user; #ifdef F_SET_HIDE int num_hidden_users = 0; /* for the O_HIDDEN flag. This counter must * be kept up to date at all times! If you * modify the O_HIDDEN flag in an object, * make sure that you update this counter if * the object is interactive. */ #endif int add_message_calls = 0; #ifdef F_NETWORK_STATS int inet_out_packets = 0; int inet_out_volume = 0; int inet_in_packets = 0; int inet_in_volume = 0; #ifdef PACKAGE_SOCKETS int inet_socket_in_packets = 0; int inet_socket_in_volume = 0; int inet_socket_out_packets = 0; int inet_socket_out_volume = 0; #endif #endif int inet_packets = 0; int inet_volume = 0; interactive_t **all_users = 0; int max_users = 0; #ifdef HAS_CONSOLE int has_console = -1; #endif /* * private local variables. */ static int addr_server_fd = -1; static void set_linemode (interactive_t * ip) { if (ip->iflags & USING_LINEMODE) { add_binary_message(ip->ob, telnet_line_mode, sizeof(telnet_line_mode)); add_binary_message(ip->ob, telnet_lm_mode, sizeof(telnet_lm_mode)); } else { add_binary_message(ip->ob, telnet_no_single, sizeof(telnet_no_single)); } } static void set_charmode (interactive_t * ip) { if (ip->iflags & USING_LINEMODE) { add_binary_message(ip->ob, telnet_char_mode, sizeof(telnet_char_mode)); } else { add_binary_message(ip->ob, telnet_yes_single, sizeof(telnet_yes_single)); } } #ifndef NO_SNOOP static void receive_snoop (const char * buf, int len, object_t * snooper) { /* command giver no longer set to snooper */ #ifdef RECEIVE_SNOOP char *str; str = new_string(len, "receive_snoop"); memcpy(str, buf, len); str[len] = 0; push_malloced_string(str); apply(APPLY_RECEIVE_SNOOP, snooper, 1, ORIGIN_DRIVER); #else /* snoop output is now % in all cases */ add_message(snooper, "%", 1); add_message(snooper, buf, len); #endif } #endif /* * Initialize new user connection socket. */ void init_user_conn() { #ifdef IPV6 struct sockaddr_in6 sin; #else struct sockaddr_in sin; #endif memset(&sin, 0, sizeof(sin)); socklen_t sin_len; int optval; int i; int have_fd6; int fd6_which = -1; /* Check for fd #6 open as a valid socket */ optval = 1; have_fd6 = (setsockopt(6, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval)) == 0); for (i=0; i < 5; i++) { #ifdef F_NETWORK_STATS external_port[i].in_packets = 0; external_port[i].in_volume = 0; external_port[i].out_packets = 0; external_port[i].out_volume = 0; #endif if (!external_port[i].port) { #if defined(FD6_KIND) && defined(FD6_PORT) if (!have_fd6) continue; fd6_which = i; have_fd6 = 0; if (FD6_KIND == PORT_UNDEFINED || FD6_PORT < 1) { debug_message("Socket passed to fd 6 ignored (support is disabled).\n"); continue; } debug_message("Accepting connections on fd 6 (port %d).\n", FD6_PORT); external_port[i].kind = FD6_KIND; external_port[i].port = FD6_PORT; external_port[i].fd = 6; #else continue; #endif } else { /* * create socket of proper type. */ int sockflags = SOCK_STREAM; #ifdef IPV6 if ((external_port[i].fd = socket(PF_INET6, sockflags, 0)) == -1) { #else if ((external_port[i].fd = socket(PF_INET, sockflags, 0)) == -1) { #endif debug_perror("init_user_conn: socket", 0); exit(1); } /* * enable local address reuse. */ optval = 1; if (setsockopt(external_port[i].fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, sizeof(optval)) == -1) { socket_perror("init_user_conn: setsockopt", 0); exit(2); } #ifdef FD_CLOEXEC fcntl(external_port[i].fd, F_SETFD, FD_CLOEXEC); #endif /* * fill in socket address information. */ #ifdef IPV6 sin.sin6_family = AF_INET6; if(MUD_IP[0]) inet_pton(AF_INET6, MUD_IP, &(sin.sin6_addr)); else sin.sin6_addr = in6addr_any; sin.sin6_port = htons((u_short) external_port[i].port); #else sin.sin_family = AF_INET; if (MUD_IP[0]) sin.sin_addr.s_addr = inet_addr(MUD_IP); else sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons((u_short) external_port[i].port); #endif /* * bind name to socket. */ if (bind(external_port[i].fd, (struct sockaddr *) & sin, sizeof(sin)) == -1) { socket_perror("init_user_conn: bind", 0); exit(3); } } /* * get socket name. */ sin_len = sizeof(sin); if (getsockname(external_port[i].fd, (struct sockaddr *) & sin, &sin_len) == -1) { socket_perror("init_user_conn: getsockname", 0); if (i != fd6_which) { exit(4); } } /* * set socket non-blocking, */ if (set_socket_nonblocking(external_port[i].fd, 1) == -1) { socket_perror("init_user_conn: set_socket_nonblocking 1", 0); if (i != fd6_which) { exit(8); } } /* * listen on socket for connections. */ if (listen(external_port[i].fd, 128) == -1) { socket_perror("init_user_conn: listen", 0); if (i != fd6_which) { exit(10); } } } if (have_fd6) { debug_message("No more ports available; fd #6 ignored.\n"); } /* * register signal handler for SIGPIPE. */ #if defined(SIGPIPE) && defined(SIGNAL_ERROR) #ifdef SIG_IGN if (signal(SIGPIPE, SIG_IGN) == SIGNAL_ERROR) { debug_perror("init_user_conn: signal SIGPIPE",0); exit(5); } #else if (signal(SIGPIPE, sigpipe_handler) == SIGNAL_ERROR) { debug_perror("init_user_conn: signal SIGPIPE",0); exit(5); } #endif #endif } /* * Shut down new user accept file descriptor. */ void ipc_remove() { int i; for (i = 0; i < 5; i++) { if (!external_port[i].port) continue; if (OS_socket_close(external_port[i].fd) == -1) { socket_perror("ipc_remove: close", 0); } } debug_message("closed external ports\n"); } void init_addr_server (char * hostname, int addr_server_port) { #ifdef WIN32 WORD wVersionRequested = MAKEWORD(1,1); WSADATA wsaData; WSAStartup(wVersionRequested, &wsaData); #endif #ifdef IPV6 struct sockaddr_in6 server; #else struct sockaddr_in server; #endif #ifndef IPV6 struct hostent *hp; int addr; #endif int server_fd; int optval; if (addr_server_fd >= 0) return; if (!hostname) return; #ifdef IPV6 /* * get network host data for hostname. */ struct addrinfo hints, *res; hints.ai_family = AF_INET6; hints.ai_socktype = 0; hints.ai_protocol = 0; #ifndef AI_V4MAPPED hints.ai_flags = AI_CANONNAME; #else hints.ai_flags = AI_CANONNAME| AI_V4MAPPED; #endif if(getaddrinfo(hostname, "1234", &hints, &res)){ //failed socket_perror("init_addr_server: getaddrinfo", 0); return; } memcpy(&server, res->ai_addr, sizeof(server)); freeaddrinfo(res); server.sin6_port = htons((u_short) addr_server_port); //inet_pton(AF_INET6, hostname, &(server.sin6_addr)); //memcpy((char *) &server.sin6_addr, (char *) hp->h_addr, hp->h_length); /* * create socket of proper type. */ server_fd = socket(AF_INET6, SOCK_STREAM, 0); #else /* * get network host data for hostname. */ if (hostname[0] >= '0' && hostname[0] <= '9' && (addr = inet_addr(hostname)) != -1) { hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET); } else { hp = gethostbyname(hostname); } if (hp == NULL) { socket_perror("init_addr_server: gethostbyname", 0); return; } /* * set up address information for server. */ server.sin_family = AF_INET; server.sin_port = htons((u_short) addr_server_port); server.sin_addr.s_addr = inet_addr(hostname); memcpy((char *) &server.sin_addr, (char *) hp->h_addr, hp->h_length); /* * create socket of proper type. */ server_fd = socket(AF_INET, SOCK_STREAM, 0); #endif if (server_fd == INVALID_SOCKET) { /* problem opening socket */ socket_perror("init_addr_server: socket", 0); return; } /* * enable local address reuse. */ optval = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optval, sizeof(optval)) == -1) { socket_perror("init_addr_server: setsockopt", 0); return; } /* * connect socket to server address. */ if (connect(server_fd, (struct sockaddr *) & server, sizeof(server)) == -1) { if(ADDRFAIL_NOTIFY){ if (socket_errno == ECONNREFUSED && ADDRFAIL_NOTIFY) debug_message("Connection to address server (%s %d) refused.\n", hostname, addr_server_port); else socket_perror("init_addr_server: connect", 0); } OS_socket_close(server_fd); return; } addr_server_fd = server_fd; debug_message("Connected to address server on %s port %d\n", hostname, addr_server_port); /* * set socket non-blocking. */ if (set_socket_nonblocking(server_fd, 1) == -1) { socket_perror("init_addr_server: set_socket_nonblocking 1", 0); return; } #ifdef WIN32 WSACleanup(); #endif } /* * If there is a shadow for this object, then the message should be * sent to it. But only if catch_tell() is defined. Beware that one of the * shadows may be the originator of the message, which means that we must * not send the message to that shadow, or any shadows in the linked list * before that shadow. * * Also note that we don't need to do this in the case of * INTERACTIVE_CATCH_TELL, since catch_tell() was already called * _instead of_ add_message(), and shadows got their chance then. */ #if !defined(INTERACTIVE_CATCH_TELL) && !defined(NO_SHADOWS) #define SHADOW_CATCH_MESSAGE #endif #ifdef SHADOW_CATCH_MESSAGE static int shadow_catch_message (object_t * ob, const char * str) { if (!ob->shadowed) return 0; while (ob->shadowed != 0 && ob->shadowed != current_object) ob = ob->shadowed; while (ob->shadowing) { copy_and_push_string(str); if (apply(APPLY_CATCH_TELL, ob, 1, ORIGIN_DRIVER)) /* this will work, since we know the */ /* function is defined */ return 1; ob = ob->shadowing; } return 0; } #endif /* * Send a message to an interactive object. If that object is shadowed, * special handling is done. */ void add_message (object_t * who, const char * data, int len) { interactive_t *ip; const char *cp; const char *end; char *trans; int translen; /* * if who->interactive is not valid, write message on stderr. * (maybe) */ if (!who || (who->flags & O_DESTRUCTED) || !who->interactive || (who->interactive->iflags & (NET_DEAD | CLOSING))) { #ifdef NONINTERACTIVE_STDERR_WRITE putc(']', stderr); fwrite(data, len, 1, stderr); #endif return; } ip = who->interactive; trans = translate(ip->trans->outgoing, data, len, &translen); #ifdef SHADOW_CATCH_MESSAGE /* * shadow handling. */ if (shadow_catch_message(who, data)) { #ifdef SNOOP_SHADOWED handle_snoop(data, len, ip); #endif return; } #endif /* NO_SHADOWS */ /* * write message into ip->message_buf. */ end = trans + translen; for (cp = trans; cp < end; cp++) { if (ip->message_length == MESSAGE_BUF_SIZE) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.")); return; } if (ip->message_length == MESSAGE_BUF_SIZE) break; } if ((*cp == '\n' || *cp == -1) #ifndef NO_BUFFER_TYPE && ip->connection_type != PORT_BINARY #endif ) { if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.")); return; } if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) break; } ip->message_buf[ip->message_producer] = (*cp == '\n')?'\r':-1; ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE; ip->message_length++; } ip->message_buf[ip->message_producer] = *cp; ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE; ip->message_length++; } handle_snoop(data, len, ip); #ifdef FLUSH_OUTPUT_IMMEDIATELY flush_message(ip); #endif add_message_calls++; } /* add_message() */ /* WARNING: this can only handle results < LARGEST_PRINTABLE_STRING in size */ void add_vmessage (object_t *who, const char *format, ...) { int len; interactive_t *ip; char *cp, new_string_data[LARGEST_PRINTABLE_STRING + 1]; va_list args; V_START(args, format); V_VAR(object_t *, who, args); V_VAR(char *, format, args); /* * if who->interactive is not valid, write message on stderr. * (maybe) */ if (!who || (who->flags & O_DESTRUCTED) || !who->interactive || (who->interactive->iflags & (NET_DEAD | CLOSING))) { #ifdef NONINTERACTIVE_STDERR_WRITE putc(']', stderr); vfprintf(stderr, format, args); #endif va_end(args); return; } ip = who->interactive; new_string_data[0] = '\0'; vsnprintf(new_string_data, LARGEST_PRINTABLE_STRING, format, args); va_end(args); len = strlen(new_string_data); #ifdef SHADOW_CATCH_MESSAGE /* * shadow handling. */ if (shadow_catch_message(who, new_string_data)) { #ifdef SNOOP_SHADOWED handle_snoop(new_string_data, len, ip); #endif return; } #endif /* NO_SHADOWS */ /* * write message into ip->message_buf. */ for (cp = new_string_data; *cp != '\0'; cp++) { if (ip->message_length == MESSAGE_BUF_SIZE) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.")); return; } if (ip->message_length == MESSAGE_BUF_SIZE) break; } if (*cp == '\n') { if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.\n")); return; } if (ip->message_length == (MESSAGE_BUF_SIZE - 1)) break; } ip->message_buf[ip->message_producer] = '\r'; ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE; ip->message_length++; } ip->message_buf[ip->message_producer] = *cp; ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE; ip->message_length++; } if (ip->message_length != 0) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.\n")); return; } } handle_snoop(new_string_data, len, ip); #ifdef FLUSH_OUTPUT_IMMEDIATELY flush_message(ip); #endif add_message_calls++; } /* add_message() */ void add_binary_message (object_t * who, const unsigned char * data, int len) { interactive_t *ip; const unsigned char *cp, *end; /* * if who->interactive is not valid, bail */ if (!who || (who->flags & O_DESTRUCTED) || !who->interactive || (who->interactive->iflags & (NET_DEAD | CLOSING))) { return; } ip = who->interactive; /* * write message into ip->message_buf. */ end = data + len; for (cp = data; cp < end; cp++) { if (ip->message_length == MESSAGE_BUF_SIZE) { if (!flush_message(ip)) { debug(connections, ("Broken connection during add_message.")); return; } if (ip->message_length == MESSAGE_BUF_SIZE) break; } ip->message_buf[ip->message_producer] = *cp; ip->message_producer = (ip->message_producer + 1) % MESSAGE_BUF_SIZE; ip->message_length++; } flush_message(ip); add_message_calls++; } /* * Flush outgoing message buffer of current interactive object. */ int flush_message (interactive_t * ip) { int length, num_bytes; /* * if ip is not valid, do nothing. */ if (!ip || !ip->ob || !IP_VALID(ip, ip->ob) || (ip->ob->flags & O_DESTRUCTED) || (ip->iflags & (NET_DEAD | CLOSING))){ //debug(connections, ("flush_message: invalid target!\n")); return 0; } /* * write ip->message_buf[] to socket. */ while (ip->message_length != 0) { if (ip->message_consumer < ip->message_producer) { length = ip->message_producer - ip->message_consumer; } else { length = MESSAGE_BUF_SIZE - ip->message_consumer; } /* Need to use send to get out of band data num_bytes = write(ip->fd,ip->message_buf + ip->message_consumer,length); */ #ifdef HAVE_ZLIB if (ip->compressed_stream) { num_bytes = send_compressed(ip, (unsigned char *)ip->message_buf + ip->message_consumer, length); } else { #endif num_bytes = send(ip->fd, ip->message_buf + ip->message_consumer, length, ip->out_of_band | MSG_NOSIGNAL); #ifdef HAVE_ZLIB } #endif if (!num_bytes) { ip->iflags |= NET_DEAD; return 0; } if (num_bytes == -1) { #ifdef EWOULDBLOCK if (socket_errno == EWOULDBLOCK) { //debug(connections, ("flush_message: write: Operation would block\n")); return 1; #else if (0) { ; #endif } else if (socket_errno == EINTR) { //debug(connections, ("flush_message: write: Interrupted system call")); return 1; } else { //socket_perror("flush_message: write", 0); ip->iflags |= NET_DEAD; return 0; } } ip->message_consumer = (ip->message_consumer + num_bytes) % MESSAGE_BUF_SIZE; ip->message_length -= num_bytes; ip->out_of_band = 0; inet_packets++; inet_volume += num_bytes; #ifdef F_NETWORK_STATS inet_out_packets++; inet_out_volume += num_bytes; external_port[ip->external_port].out_packets++; external_port[ip->external_port].out_volume += num_bytes; #endif } return 1; } /* flush_message() */ static int send_mssp_val(mapping_t *map, mapping_node_t *el, void *obp){ object_t *ob = (object_t *)obp; if(el->values[0].type == T_STRING && el->values[1].type == T_STRING){ char buf[1024]; int len = sprintf(buf, (char *)telnet_mssp_value, el->values[0].u.string, el->values[1].u.string); add_binary_message(ob, (unsigned char *)buf, len); } else if (el->values[0].type == T_STRING && el->values[1].type == T_ARRAY && el->values[1].u.arr->size > 0 && el->values[1].u.arr->item[0].type == T_STRING){ char buf[10240]; int len = sprintf(buf, (char *)telnet_mssp_value, el->values[0].u.string, el->values[1].u.arr->item[0].u.string); add_binary_message(ob, (unsigned char *)buf, len); array_t *ar = el->values[1].u.arr; int i; unsigned char val = MSSP_VAL; for(i=1; i < ar->size; i++){ if(ar->item[i].type == T_STRING){ add_binary_message(ob, &val, 1); add_binary_message(ob, (const unsigned char *)ar->item[i].u.string, strlen(ar->item[i].u.string)); } } } return 0; } static void copy_chars (interactive_t * ip, char * from, int num_bytes) { int i, start, x; unsigned char dont_response[3] = { IAC, DONT, 0 }; unsigned char wont_response[3] = { IAC, WONT, 0 }; start = ip->text_end; for (i = 0; i < num_bytes; i++) { switch (ip->state) { case TS_DATA: switch ((unsigned char)from[i]) { case IAC: ip->state = TS_IAC; break; #if defined(NO_ANSI) && defined(STRIP_BEFORE_PROCESS_INPUT) case 0x1b: ip->text[ip->text_end++] = ANSI_SUBSTITUTE; break; #endif case 0x08: case 0x7f: if (ip->iflags & SINGLE_CHAR) ip->text[ip->text_end++] = from[i]; else { if (ip->text_end > 0) ip->text_end--; } break; default: ip->text[ip->text_end++] = from[i]; break; } break; case TS_IAC: switch ((unsigned char)from[i]) { case IAC: ip->state = TS_DATA; ip->text[ip->text_end++] = from[i]; break; case WILL: ip->state = TS_WILL; break; case WONT: ip->state = TS_WONT; break; case DO: ip->state = TS_DO; break; case DONT: ip->state = TS_DONT; break; case SB: ip->state = TS_SB; ip->sb_pos = 0; break; case BREAK: add_binary_message(ip->ob, telnet_break_response, sizeof(telnet_break_response)); break; case IP: /* interrupt process */ add_binary_message(ip->ob, telnet_ip_response, sizeof(telnet_ip_response)); break; case AYT: /* are you there? you bet */ add_binary_message(ip->ob, telnet_ayt_response, sizeof(telnet_ayt_response)); break; case AO: /* abort output */ flush_message(ip); ip->out_of_band = MSG_OOB; add_binary_message(ip->ob, telnet_abort_response, sizeof(telnet_abort_response)); break; default: ip->state = TS_DATA; break; } break; case TS_WILL: ip->iflags |= USING_TELNET; switch ((unsigned char)from[i]) { case TELOPT_TTYPE: add_binary_message(ip->ob, telnet_term_query, sizeof(telnet_term_query)); break; case TELOPT_LINEMODE: /* Do linemode and set the mode: EDIT + TRAPSIG */ ip->iflags |= USING_LINEMODE; set_linemode(ip); break; case TELOPT_ECHO: case TELOPT_NAWS: /* do nothing, but don't send a dont response */ break; case TELOPT_MXP : /* Mxp is enabled, tell the mudlib about it. */ apply(APPLY_MXP_ENABLE, ip->ob, 0, ORIGIN_DRIVER); ip->iflags |= USING_MXP; break; case TELOPT_GMCP: apply(APPLY_GMCP_ENABLE, ip->ob, 0, ORIGIN_DRIVER); ip->iflags |= USING_GMCP; break; case TELOPT_NEW_ENVIRON : add_binary_message(ip->ob, telnet_send_uservar, sizeof(telnet_send_uservar)); break; default: dont_response[2] = from[i]; add_binary_message(ip->ob, dont_response, sizeof(dont_response)); break; } ip->state = TS_DATA; break; case TS_WONT: ip->iflags |= USING_TELNET; switch ((unsigned char)from[i]) { case TELOPT_LINEMODE: /* If we're in single char mode, we just requested for * linemode to be disabled, so don't remove our flag. */ if (!(ip->iflags & SINGLE_CHAR)) ip->iflags &= ~USING_LINEMODE; break; } ip->state = TS_DATA; break; case TS_DO: switch ((unsigned char)from[i]) { case TELOPT_TM: add_binary_message(ip->ob, telnet_do_tm_response, sizeof(telnet_do_tm_response)); break; case TELOPT_SGA: if (ip->iflags & USING_LINEMODE) { ip->iflags |= SUPPRESS_GA; add_binary_message(ip->ob, telnet_yes_single, sizeof(telnet_yes_single)); } else { if (ip->iflags & SINGLE_CHAR) add_binary_message(ip->ob, telnet_yes_single, sizeof(telnet_yes_single)); else add_binary_message(ip->ob, telnet_no_single, sizeof(telnet_no_single)); } break; case TELOPT_ECHO: /* do nothing, but don't send a wont response */ break; case TELOPT_MSSP: { add_binary_message(ip->ob, telnet_start_mssp, sizeof(telnet_start_mssp)); svalue_t *res = apply_master_ob(APPLY_GET_MUD_STATS, 0); mapping_t *map; if(res <= 0 || res->type != T_MAPPING) { map = allocate_mapping(0); free_svalue(&apply_ret_value, "telnet neg"); apply_ret_value.type = T_MAPPING; apply_ret_value.u.map = map; } else map = res->u.map; //ok, so we have a mapping, first make sure we send the required values char *tmp = findstring("NAME"); if(tmp){ svalue_t *name = find_string_in_mapping(map, tmp); if(!name || name->type != T_STRING) tmp = 0; } if(!tmp){ char buf[1024]; int len = sprintf(buf, (char *)telnet_mssp_value, "NAME", MUD_NAME); add_binary_message(ip->ob, (unsigned char *)buf, len); } tmp = findstring("PLAYERS"); if(tmp){ svalue_t *players = find_string_in_mapping(map, tmp); if(!players || players->type != T_STRING) tmp = 0; } if(!tmp){ char buf[1024]; char num[5]; sprintf(num, "%d", num_user); int len = sprintf(buf, (char *)telnet_mssp_value, "PLAYERS", num); add_binary_message(ip->ob, (unsigned char *)buf, len); } tmp = findstring("UPTIME"); if(tmp){ svalue_t *upt = find_string_in_mapping(map, tmp); if(!upt || upt->type != T_STRING) tmp = 0; } if(!tmp){ char buf[1024]; char num[20]; sprintf(num, "%ld", boot_time); int len = sprintf(buf, (char *)telnet_mssp_value, "UPTIME", num); add_binary_message(ip->ob, (unsigned char *)buf, len); } //now send the rest mapTraverse(map, send_mssp_val, ip->ob); add_binary_message(ip->ob, telnet_end_sub, sizeof(telnet_end_sub)); } #ifdef HAVE_ZLIB case TELOPT_COMPRESS : if(!ip->compressed_stream){ add_binary_message(ip->ob, telnet_compress_v1_response, sizeof(telnet_compress_v1_response)); start_compression(ip); } break; case TELOPT_COMPRESS2 : if(!ip->compressed_stream){ add_binary_message(ip->ob, telnet_compress_v2_response, sizeof(telnet_compress_v2_response)); start_compression(ip); } break; #endif case TELOPT_ZMP : ip->iflags |= USING_ZMP; break; default: wont_response[2] = from[i]; add_binary_message(ip->ob, wont_response, sizeof(wont_response)); break; } ip->state = TS_DATA; break; case TS_DONT: switch ((unsigned char)from[i]) { case TELOPT_SGA: if (ip->iflags & USING_LINEMODE) { ip->iflags &= ~SUPPRESS_GA; add_binary_message(ip->ob, telnet_no_single, sizeof(telnet_no_single)); } break; #ifdef HAVE_ZLIB case TELOPT_COMPRESS2: // If we are told not to use v2, then try v1. add_binary_message(ip->ob, telnet_compress_send_request_v1, sizeof(telnet_compress_send_request_v1)); break; #endif } ip->state = TS_DATA; break; case TS_SB: if ((unsigned char)from[i] == IAC) { ip->state = TS_SB_IAC; break; } if (ip->sb_pos < ip->sb_size - 1) ip->sb_buf[ip->sb_pos++] = from[i]; else if(ip->sb_size < MAX_STRING_LENGTH){ ip->sb_size*=2; if(ip->sb_size > MAX_STRING_LENGTH) ip->sb_size = MAX_STRING_LENGTH; ip->sb_buf = (char *)REALLOC(ip->sb_buf, ip->sb_size); if (ip->sb_pos < ip->sb_size - 1) ip->sb_buf[ip->sb_pos++] = from[i]; } break; case TS_SB_IAC: switch ((unsigned char)from[i]) { case IAC: if (ip->sb_pos < SB_SIZE - 1) { ip->sb_buf[ip->sb_pos++] = from[i]; ip->state = TS_SB; } break; case SE: ip->state = TS_DATA; ip->sb_buf[ip->sb_pos] = 0; switch ((unsigned char)ip->sb_buf[0]) { case TELOPT_NEW_ENVIRON : { int j, k; char env_buf[BUF_SIZE]; j = 0; k = 1; while(ip->sb_buf[k] > -1 && k < (ip->sb_pos - 1)){ k++; if(!(ip->sb_buf[k])) env_buf[j] = ENV_FILLER; if(ip->sb_buf[k] == 1) env_buf[j] = 1; if((ip->sb_buf[k] > 31)){ env_buf[j] = ip->sb_buf[k]; } if(env_buf[j]) j++; } env_buf[j] = 0; copy_and_push_string(env_buf); apply(APPLY_RECEIVE_ENVIRON, ip->ob, 1, ORIGIN_DRIVER); break; } case TELOPT_LINEMODE: switch ((unsigned char)ip->sb_buf[1]) { case LM_MODE: /* Don't do anything with an ACK */ if (!(ip->sb_buf[2] & MODE_ACK)) { unsigned char sb_ack[] = { IAC, SB, TELOPT_LINEMODE, LM_MODE, MODE_EDIT | MODE_TRAPSIG | MODE_ACK, IAC, SE }; /* Accept only EDIT and TRAPSIG && force them too */ add_binary_message(ip->ob, sb_ack, sizeof(sb_ack)); } break; case LM_SLC: { int slc_length = 4; unsigned char slc_response[SB_SIZE + 6] = { IAC, SB, TELOPT_LINEMODE, LM_SLC }; for (x = 2; x < ip->sb_pos; x += 3) { /* no response for an ack */ if (ip->sb_buf[x + 1] & SLC_ACK) continue; /* If we get { 0, SLC_DEFAULT, 0 } or { 0, SLC_VARIABLE, 0 } return a list of values */ /* If it's SLC_DEFAULT, reset to defaults first */ if (!ip->sb_buf[x] && !ip->sb_buf[x + 2]) { if (ip->sb_buf[x + 1] == SLC_DEFAULT || ip->sb_buf[x + 1] == SLC_VARIABLE) { int n; for (n = 0; n < NSLC; n++) { slc_response[slc_length++] = n + 1; if (ip->sb_buf[x + 1] == SLC_DEFAULT) { ip->slc[n][0] = slc_default_flags[n]; ip->slc[n][1] = slc_default_chars[n]; slc_response[slc_length++] = SLC_DEFAULT; } else { slc_response[slc_length++] = ip->slc[n][0]; } slc_response[slc_length++] = ip->slc[n][1]; } break; } } slc_response[slc_length++] = ip->sb_buf[x]--; /* If the first octet is out of range, we don't support it */ /* If the default flag is not supported, we don't support it */ if (ip->sb_buf[x] >= NSLC || slc_default_flags[ip->sb_buf[x]] == SLC_NOSUPPORT) { slc_response[slc_length++] = SLC_NOSUPPORT; slc_response[slc_length++] = ip->sb_buf[x + 2]; if ((unsigned char)ip->sb_buf[x + 2] == IAC) slc_response[slc_length++] = IAC; continue; } switch ((ip->sb_buf[x + 1] & SLC_LEVELBITS)) { case SLC_NOSUPPORT: if (slc_default_flags[ip->sb_buf[x]] == SLC_CANTCHANGE) { slc_response[slc_length++] = SLC_CANTCHANGE; slc_response[slc_length++] = ip->slc[ip->sb_buf[x]][1]; break; } slc_response[slc_length++] = SLC_ACK | SLC_NOSUPPORT; slc_response[slc_length++] = ip->sb_buf[x + 2]; ip->slc[ip->sb_buf[x]][0] = SLC_NOSUPPORT; ip->slc[ip->sb_buf[x]][1] = 0; break; case SLC_VARIABLE: if (slc_default_flags[ip->sb_buf[x]] == SLC_CANTCHANGE) { slc_response[slc_length++] = SLC_CANTCHANGE; slc_response[slc_length++] = ip->slc[ip->sb_buf[x]][1]; break; } slc_response[slc_length++] = SLC_ACK | SLC_VARIABLE; slc_response[slc_length++] = ip->sb_buf[x + 2]; ip->slc[ip->sb_buf[x]][0] = ip->sb_buf[x + 1]; ip->slc[ip->sb_buf[x]][1] = ip->sb_buf[x + 2]; break; case SLC_CANTCHANGE: slc_response[slc_length++] = SLC_ACK | SLC_CANTCHANGE; slc_response[slc_length++] = ip->sb_buf[x + 2]; ip->slc[ip->sb_buf[x]][0] = ip->sb_buf[x + 1]; ip->slc[ip->sb_buf[x]][1] = ip->sb_buf[x + 2]; break; case SLC_DEFAULT: slc_response[slc_length++] = slc_default_flags[ip->sb_buf[x]]; slc_response[slc_length++] = slc_default_flags[ip->sb_buf[x]]; ip->slc[ip->sb_buf[x]][0] = slc_default_flags[ip->sb_buf[x]]; ip->slc[ip->sb_buf[x]][1] = slc_default_chars[ip->sb_buf[x]]; break; default: slc_response[slc_length++] = SLC_NOSUPPORT; slc_response[slc_length++] = ip->sb_buf[x + 2]; if ((unsigned char)slc_response[slc_length - 1] == IAC) slc_response[slc_length++] = IAC; break; } } if (slc_length > 4) { /* send our response */ slc_response[slc_length++] = IAC; slc_response[slc_length++] = SE; add_binary_message(ip->ob, slc_response, slc_length); } } break; case DO: { unsigned char sb_wont[] = { IAC, SB, TELOPT_LINEMODE, WONT, 0, IAC, SE }; /* send back IAC SB TELOPT_LINEMODE WONT x IAC SE */ sb_wont[4] = ip->sb_buf[2]; add_binary_message(ip->ob, sb_wont, sizeof(sb_wont)); } break; case WILL: { unsigned char sb_dont[] = { IAC, SB, TELOPT_LINEMODE, DONT, 0, IAC, SE }; /* send back IAC SB TELOPT_LINEMODE DONT x IAC SE */ sb_dont[4] = ip->sb_buf[2]; add_binary_message(ip->ob, sb_dont, sizeof(sb_dont)); } break; } break; case TELOPT_NAWS: if (ip->sb_pos >= 5) { push_number(((unsigned char)ip->sb_buf[1] << 8) | (unsigned char)ip->sb_buf[2]); push_number(((unsigned char)ip->sb_buf[3] << 8) | (unsigned char)ip->sb_buf[4]); apply(APPLY_WINDOW_SIZE, ip->ob, 2, ORIGIN_DRIVER); } break; case TELOPT_TTYPE: if (!ip->sb_buf[1]) { copy_and_push_string(ip->sb_buf + 2); apply(APPLY_TERMINAL_TYPE, ip->ob, 1, ORIGIN_DRIVER); } break; case TELOPT_ZMP: { array_t *arr = allocate_array(max_array_size); ip->sb_buf = (char *)REALLOC(ip->sb_buf, MAX(ip->sb_pos + 2, SB_SIZE)); ip->sb_size = MAX(ip->sb_pos + 2, SB_SIZE); ip->sb_buf[ip->sb_pos] = 0; copy_and_push_string(ip->sb_buf+1); int off=0; int aro = 0; while(1){ off += strlen(ip->sb_buf+1+off)+2; if(off >= ip->sb_pos-1) break; arr->item[aro].u.string = string_copy(&ip->sb_buf[off], "ZMP"); arr->item[aro].type = T_STRING; arr->item[aro++].subtype = STRING_MALLOC; } arr = resize_array(arr, aro); push_refed_array(arr); apply(APPLY_ZMP, ip->ob, 2, ORIGIN_DRIVER); } break; case TELOPT_GMCP: ip->sb_buf[ip->sb_pos] = 0; copy_and_push_string(&ip->sb_buf[1]); apply(APPLY_GMCP, ip->ob, 1, ORIGIN_DRIVER); break; default: for (x = 0; x < ip->sb_pos; x++) ip->sb_buf[x] = (ip->sb_buf[x] ? ip->sb_buf[x] : 'I'); copy_and_push_string(ip->sb_buf); apply(APPLY_TELNET_SUBOPTION, ip->ob, 1, ORIGIN_DRIVER); break; } break; default: /* * Apparently, old versions of MudOS would revert to TS_DATA here. * Later versions handle the IAC, and then go back to TS_DATA mode. * I don't think either is proper, but the related RFC documents * aren't clear on what to do (854, 855). It is my feeling, that * the safest thing to do here is to ignore the option. * -- Marius, 6-Jun-2000 */ ip->state = TS_SB; break; } break; } } if (ip->text_end > start) { /* handle snooping - snooper does not see type-ahead due to telnet being in linemode */ if (!(ip->iflags & NOECHO)) handle_snoop(ip->text + start, ip->text_end - start, ip); } } /* * Read pending data for a user into user->interactive->text. * This also does telnet negotiation. */ static void get_user_data (interactive_t * ip) { int num_bytes, text_space; char buf[MAX_TEXT]; /* compute how much data we can read right now */ switch (ip->connection_type) { case PORT_TELNET: text_space = MAX_TEXT - ip->text_end; /* check if we need more space */ if (text_space < MAX_TEXT / 16) { if (ip->text_start > 0) { memmove(ip->text, ip->text + ip->text_start, ip->text_end - ip->text_start); text_space += ip->text_start; ip->text_end -= ip->text_start; ip->text_start = 0; } if (text_space < MAX_TEXT / 16) { /* the user is sending too much data. flush it */ ip->iflags |= SKIP_COMMAND; ip->text_start = ip->text_end = 0; text_space = MAX_TEXT; } } break; case PORT_MUD: if (ip->text_end < 4) text_space = 4 - ip->text_end; else text_space = *(volatile int *)ip->text - ip->text_end + 4; break; default: text_space = sizeof(buf); break; } /* read the data from the socket */ //debug(connections, ("get_user_data: read on fd %d\n", ip->fd)); num_bytes = OS_socket_read(ip->fd, buf, text_space); if (!num_bytes) { //if (ip->iflags & CLOSING) // debug_message("get_user_data: tried to read from closing fd.\n"); ip->iflags |= NET_DEAD; remove_interactive(ip->ob, 0); return; } if (num_bytes == -1) { #ifdef EWOULDBLOCK if (socket_errno == EWOULDBLOCK) { // debug(connections, ("get_user_data: read on fd %d: Operation would block.\n", ip->fd)); return; } #endif // debug_message("get_user_data: read on fd %d\n", ip->fd); // socket_perror("get_user_data: read", 0); ip->iflags |= NET_DEAD; remove_interactive(ip->ob, 0); return; } #ifdef F_NETWORK_STATS inet_in_packets++; inet_in_volume += num_bytes; external_port[ip->external_port].in_packets++; external_port[ip->external_port].in_volume += num_bytes; #endif /* process the data that we've just read */ switch (ip->connection_type) { case PORT_TELNET: copy_chars(ip, buf, num_bytes); if (cmd_in_buf(ip)) ip->iflags |= CMD_IN_BUF; break; case PORT_MUD: memcpy(ip->text + ip->text_end, buf, num_bytes); ip->text_end += num_bytes; if (num_bytes == text_space) { if (ip->text_end == 4) { *(volatile int *)ip->text = ntohl(*(int *)ip->text); if (*(volatile int *)ip->text > MAX_TEXT - 5) remove_interactive(ip->ob, 0); } else { svalue_t value; ip->text[ip->text_end] = 0; if (restore_svalue(ip->text + 4, &value) == 0) { STACK_INC; *sp = value; } else { push_undefined(); } ip->text_end = 0; apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER); } } break; case PORT_ASCII: { char *nl, *p; memcpy(ip->text + ip->text_end, buf, num_bytes); ip->text_end += num_bytes; p = ip->text + ip->text_start; while ((nl = ( char *)memchr(p, '\n', ip->text_end - ip->text_start))) { ip->text_start = (nl + 1) - ip->text; *nl = 0; if (*(nl - 1) == '\r') *--nl = 0; if (!(ip->ob->flags & O_DESTRUCTED)) { char *str; str = new_string(nl - p, "PORT_ASCII"); memcpy(str, p, nl - p + 1); push_malloced_string(str); apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER); } if (ip->text_start == ip->text_end) { ip->text_start = ip->text_end = 0; break; } p = nl + 1; } } break; #ifndef NO_BUFFER_TYPE case PORT_BINARY: { buffer_t *buffer; buffer = allocate_buffer(num_bytes); memcpy(buffer->item, buf, num_bytes); push_refed_buffer(buffer); apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER); } break; #endif } } static int clean_buf (interactive_t * ip) { /* skip null input */ while (ip->text_start < ip->text_end && !*(ip->text + ip->text_start)) ip->text_start++; /* if we've advanced beyond the end of the buffer, reset it */ if (ip->text_start >= ip->text_end) { ip->text_start = ip->text_end = 0; } /* if we're skipping the current command, check to see if it has been completed yet. if it has, flush it and clear the skip bit */ if (ip->iflags & SKIP_COMMAND) { char *p; for (p = ip->text + ip->text_start; p < ip->text + ip->text_end; p++) { if (*p == '\r' || *p == '\n') { ip->text_start += p - (ip->text + ip->text_start) + 1; ip->iflags &= ~SKIP_COMMAND; return clean_buf(ip); } } } return (ip->text_end > ip->text_start); } static int cmd_in_buf (interactive_t * ip) { char *p; /* do standard input buffer cleanup */ if (!clean_buf(ip)) return 0; /* if we're in single character mode, we've got input */ if (ip->iflags & SINGLE_CHAR) return 1; /* search for a newline. if found, we have a command */ for (p = ip->text + ip->text_start; p < ip->text + ip->text_end; p++) { if (*p == '\r' || *p == '\n') return 1; } /* duh, no command */ return 0; } static char *first_cmd_in_buf (interactive_t * ip) { char *p; #ifdef GET_CHAR_IS_BUFFERED static char tmp[2]; #endif /* do standard input buffer cleanup */ if (!clean_buf(ip)) return 0; p = ip->text + ip->text_start; /* if we're in single character mode, we've got input */ if (ip->iflags & SINGLE_CHAR) { if (*p == 8 || *p == 127) *p = 0; #ifndef GET_CHAR_IS_BUFFERED ip->text_start++; if (!clean_buf(ip)) ip->iflags &= ~CMD_IN_BUF; return p; #else tmp[0] = *p; ip->text[ip->text_start++] = 0; if (!clean_buf(ip)) ip->iflags &= ~CMD_IN_BUF; return tmp; #endif } /* search for the newline */ while (ip->text[ip->text_start] != '\n' && ip->text[ip->text_start] != '\r') ip->text_start++; /* check for "\r\n" or "\n\r" */ if (ip->text_start + 1 < ip->text_end && ((ip->text[ip->text_start] == '\r' && ip->text[ip->text_start + 1] == '\n') || (ip->text[ip->text_start] == '\n' && ip->text[ip->text_start + 1] == '\r'))) { ip->text[ip->text_start++] = 0; } ip->text[ip->text_start++] = 0; if (!cmd_in_buf(ip)) ip->iflags &= ~CMD_IN_BUF; return p; } /* * SIGPIPE handler -- does very little for now. */ #ifndef SIG_IGN #ifdef SIGNAL_FUNC_TAKES_INT void sigpipe_handler (int sig) #else void sigpipe_handler() #endif { debug(connections, ("SIGPIPE received.")); //don't comment the next line out, i'm pretty sure we'd crash on the next SIGPIPE, they're not worth it signal(SIGPIPE, sigpipe_handler); } /* sigpipe_handler() */ #endif /* * SIGALRM handler. */ #ifdef SIGNAL_FUNC_TAKES_INT void sigalrm_handler (int sig) #else void sigalrm_handler() #endif { outoftime = 1; } /* sigalrm_handler() */ int max_fd; INLINE void make_selectmasks() { int i; max_fd = addr_server_fd; /* * generate readmask and writemask for select() call. */ FD_ZERO(&readmask); FD_ZERO(&writemask); #ifdef HAS_CONSOLE /* set up a console */ if(has_console > 0) FD_SET(STDIN_FILENO, &readmask); #endif /* * set new user accept fd in readmask. */ for (i = 0; i < 5; i++) { if (!external_port[i].port) continue; FD_SET(external_port[i].fd, &readmask); if(external_port[i].fd > max_fd) max_fd = external_port[i].fd; } /* * set user fds in readmask. */ for (i = 0; i < max_users; i++) { if (!all_users[i] || (all_users[i]->iflags & (CLOSING | CMD_IN_BUF))) continue; /* * if this user needs more input to make a complete command, set his * fd so we can get it. */ FD_SET(all_users[i]->fd, &readmask); if(all_users[i]->fd > max_fd) max_fd = all_users[i]->fd; if (all_users[i]->message_length != 0) FD_SET(all_users[i]->fd, &writemask); } /* * if addr_server_fd is set, set its fd in readmask. */ if (addr_server_fd >= 0) { FD_SET(addr_server_fd, &readmask); } #if defined(PACKAGE_SOCKETS) || defined(PACKAGE_EXTERNAL) /* * set fd's for efun sockets. */ for (i = 0; i < max_lpc_socks; i++) { if (lpc_socks[i].state != STATE_CLOSED) { if (lpc_socks[i].state != STATE_FLUSHING && (lpc_socks[i].flags & S_WACCEPT) == 0){ FD_SET(lpc_socks[i].fd, &readmask); if(lpc_socks[i].fd > max_fd) max_fd = lpc_socks[i].fd; } if (lpc_socks[i].flags & S_BLOCKED){ FD_SET(lpc_socks[i].fd, &writemask); if(lpc_socks[i].fd > max_fd) max_fd = lpc_socks[i].fd; } } } #endif } /* make_selectmasks() */ /* * Process I/O. */ INLINE void process_io() { int i; /* * check for new user connection. */ for (i = 0; i < 5; i++) { if (!external_port[i].port) continue; if (FD_ISSET(external_port[i].fd, &readmask)) { debug(connections, ("process_io: NEW_USER\n")); new_user_handler(i); } } /* * check for data pending on user connections. */ for (i = 0; i < max_users; i++) { if (!all_users[i] || (all_users[i]->iflags & (CLOSING | CMD_IN_BUF))) continue; if (all_users[i]->iflags & NET_DEAD) { remove_interactive(all_users[i]->ob, 0); continue; } if (FD_ISSET(all_users[i]->fd, &readmask)) { debug(connections, ("process_io: USER %d\n", i)); get_user_data(all_users[i]); if (!all_users[i]) continue; } if (FD_ISSET(all_users[i]->fd, &writemask)) flush_message(all_users[i]); } #if defined(PACKAGE_SOCKETS) || defined(PACKAGE_EXTERNAL) /* * check for data pending on efun socket connections. */ for (i = 0; i < max_lpc_socks; i++) { if (lpc_socks[i].state != STATE_CLOSED) if (FD_ISSET(lpc_socks[i].fd, &readmask)) socket_read_select_handler(i); if (lpc_socks[i].state != STATE_CLOSED) if (FD_ISSET(lpc_socks[i].fd, &writemask)) socket_write_select_handler(i); } #endif /* * check for data pending from address server. */ if (addr_server_fd >= 0) { if (FD_ISSET(addr_server_fd, &readmask)) { debug(connections, ("process_io: IP_DAEMON\n")); hname_handler(); } } #ifdef HAS_CONSOLE /* Process console input */ /* Note: need the has_console on the next line because linux (at least) recycles fds, even STDIN_FILENO */ if((has_console > 0) && FD_ISSET(STDIN_FILENO, &readmask)) { char s[1024]; int sz; if((sz = read(STDIN_FILENO, s, 1023)) > 0) { s[sz-1] = '\0'; console_command(s); } else if(sz == 0) { printf("Console exiting. The MUD remains.\n"); has_console = 0; } else { printf("Console read error: %d %d. Closing console.\n", sz, errno); has_console = 0; restore_sigttin(); } } #endif } /* * This is the new user connection handler. This function is called by the * event handler when data is pending on the listening socket (new_user_fd). * If space is available, an interactive data structure is initialized and * the user is connected. */ static void new_user_handler (int which) { int new_socket_fd; #ifdef IPV6 struct sockaddr_in6 addr; #else struct sockaddr_in addr; #endif socklen_t length; int i, x; object_t *master, *ob; svalue_t *ret; length = sizeof(addr); debug(connections, ("new_user_handler: accept on fd %d\n", external_port[which].fd)); new_socket_fd = accept(external_port[which].fd, (struct sockaddr *) & addr, &length); if (new_socket_fd < 0) { #ifdef EWOULDBLOCK if (socket_errno == EWOULDBLOCK) { debug(connections, ("new_user_handler: accept: Operation would block\n")); } else { #else if (1) { #endif socket_perror("new_user_handler: accept", 0); } return; } /* * according to Amylaar, 'accepted' sockets in Linux 0.99p6 don't * properly inherit the nonblocking property from the listening socket. * Marius, 19-Jun-2000: this happens on other platforms as well, so just * do it for everyone */ if (set_socket_nonblocking(new_socket_fd, 1) == -1) { socket_perror("new_user_handler: set_socket_nonblocking 1", 0); OS_socket_close(new_socket_fd); return; } #if defined(SO_NOSIGPIPE) i = 1; if (setsockopt(new_socket_fd, 1, SO_NOSIGPIPE, &i, sizeof(i)) == -1) { socket_perror("new_user_handler: setsockopt SO_NOSIGPIPE", 0); /* it's ok if this fails */ } #endif /* find the first available slot */ for (i = 0; i < max_users; i++) if (!all_users[i]) break; if (i == max_users) { if (all_users) { all_users = RESIZE(all_users, max_users + 10, interactive_t *, TAG_USERS, "new_user_handler"); } else { all_users = CALLOCATE(10, interactive_t *, TAG_USERS, "new_user_handler"); } while (max_users < i + 10) all_users[max_users++] = 0; } set_command_giver(master_ob); master_ob->interactive = (interactive_t *) DXALLOC(sizeof(interactive_t), TAG_INTERACTIVE, "new_user_handler"); #ifndef NO_ADD_ACTION master_ob->interactive->default_err_message.s = 0; #endif master_ob->interactive->connection_type = external_port[which].kind; master_ob->interactive->sb_buf = (char *)MALLOC(SB_SIZE); master_ob->interactive->sb_size = SB_SIZE; master_ob->flags |= O_ONCE_INTERACTIVE; /* * initialize new user interactive data structure. */ master_ob->interactive->ob = master_ob; #if defined(F_INPUT_TO) || defined(F_GET_CHAR) master_ob->interactive->input_to = 0; #endif master_ob->interactive->iflags = 0; master_ob->interactive->text[0] = '\0'; master_ob->interactive->text_end = 0; master_ob->interactive->text_start = 0; #if defined(F_INPUT_TO) || defined(F_GET_CHAR) master_ob->interactive->carryover = NULL; master_ob->interactive->num_carry = 0; #endif #ifndef NO_SNOOP master_ob->interactive->snooped_by = 0; #endif master_ob->interactive->last_time = current_time; #ifdef TRACE master_ob->interactive->trace_level = 0; master_ob->interactive->trace_prefix = 0; #endif #ifdef OLD_ED master_ob->interactive->ed_buffer = 0; #endif #ifdef HAVE_ZLIB master_ob->interactive->compressed_stream = NULL; #endif master_ob->interactive->message_producer = 0; master_ob->interactive->message_consumer = 0; master_ob->interactive->message_length = 0; master_ob->interactive->state = TS_DATA; master_ob->interactive->out_of_band = 0; #ifdef USE_ICONV master_ob->interactive->trans = get_translator("UTF-8"); #else master_ob->interactive->trans = (struct translation *) master_ob; //never actually used, but avoids multiple ifdefs later on! #endif for (x = 0; x < NSLC; x++) { master_ob->interactive->slc[x][0] = slc_default_flags[x]; master_ob->interactive->slc[x][1] = slc_default_chars[x]; } all_users[i] = master_ob->interactive; all_users[i]->fd = new_socket_fd; #ifdef F_QUERY_IP_PORT all_users[i]->local_port = external_port[which].port; #endif #ifdef F_NETWORK_STATS all_users[i]->external_port = which; #endif set_prompt("> "); memcpy((char *) &all_users[i]->addr, (char *) &addr, length); #ifdef IPV6 char tmp[INET6_ADDRSTRLEN]; debug(connections, ("New connection from %s.\n", inet_ntop(AF_INET6, &addr.sin6_addr, &tmp, INET6_ADDRSTRLEN))); #else debug(connections, ("New connection from %s.\n", inet_ntoa(addr.sin_addr))); #endif num_user++; /* * The user object has one extra reference. It is asserted that the * master_ob is loaded. Save a pointer to the master ob incase it * changes during APPLY_CONNECT. We want to free the reference on * the right copy of the object. */ master = master_ob; add_ref(master_ob, "new_user"); push_number(external_port[which].port); ret = apply_master_ob(APPLY_CONNECT, 1); /* master_ob->interactive can be zero if the master object self destructed in the above (don't ask) */ set_command_giver(0); if (ret == 0 || ret == (svalue_t *)-1 || ret->type != T_OBJECT || !master_ob->interactive) { if (master_ob->interactive) remove_interactive(master_ob, 0); else free_object(&master, "new_user"); #ifdef IPV6 debug_message("Connection from %s aborted.\n", inet_ntop(AF_INET6, &addr.sin6_addr, tmp, INET6_ADDRSTRLEN)); #else debug_message("Connection from %s aborted.\n", inet_ntoa(addr.sin_addr)); #endif return; } /* * There was an object returned from connect(). Use this as the user * object. */ ob = ret->u.ob; #ifdef F_SET_HIDE if (ob->flags & O_HIDDEN) num_hidden_users++; #endif ob->interactive = master_ob->interactive; ob->interactive->ob = ob; ob->flags |= O_ONCE_INTERACTIVE; /* * assume the existance of write_prompt and process_input in user.c * until proven wrong (after trying to call them). */ ob->interactive->iflags |= (HAS_WRITE_PROMPT | HAS_PROCESS_INPUT); free_object(&master, "new_user"); master_ob->flags &= ~O_ONCE_INTERACTIVE; master_ob->interactive = 0; add_ref(ob, "new_user"); set_command_giver(ob); if (addr_server_fd >= 0) { query_addr_name(ob); } if (external_port[which].kind == PORT_TELNET) { /* Ask permission to ask them for their terminal type */ add_binary_message(ob, telnet_do_ttype, sizeof(telnet_do_ttype)); /* Ask them for their window size */ add_binary_message(ob, telnet_do_naws, sizeof(telnet_do_naws)); #ifdef HAVE_ZLIB add_binary_message(ob, telnet_compress_send_request_v2, sizeof(telnet_compress_send_request_v2)); #endif // Ask them if they support mxp. add_binary_message(ob, telnet_do_mxp, sizeof(telnet_do_mxp)); // And mssp add_binary_message(ob, telnet_will_mssp, sizeof(telnet_will_mssp)); // May as well ask for zmp while we're there! add_binary_message(ob, telnet_will_zmp, sizeof(telnet_will_zmp)); // Also newenv add_binary_message(ob, telnet_do_newenv, sizeof(telnet_do_newenv)); // gmcp *yawn* add_binary_message(ob, telnet_do_gmcp, sizeof(telnet_do_gmcp)); } logon(ob); debug(connections, ("new_user_handler: end\n")); set_command_giver(0); } /* new_user_handler() */ /* * Return the first command of the next user in sequence that has a complete * command in their buffer. A command is defined to be a single character * when SINGLE_CHAR is set, or a newline terminated string otherwise. */ static char *get_user_command() { static int NextCmdGiver = 0; int i; interactive_t *ip; char *user_command = 0; /* find and return a user command */ for (i = 0; i < max_users; i++) { ip = all_users[NextCmdGiver++]; NextCmdGiver %= max_users; if (!ip || !ip->ob || ip->ob->flags & O_DESTRUCTED) continue; /* if we've got text to send, try to flush it, could lose the link here */ if (ip->message_length) { object_t *ob = ip->ob; flush_message(ip); if (!IP_VALID(ip, ob) || (ip->iflags & NET_DEAD)) continue; } /* if there's a command in the buffer, pull it out! */ if (ip->iflags & CMD_IN_BUF) { NextCmdGiver++; NextCmdGiver %= max_users; user_command = first_cmd_in_buf(ip); break; } } /* no command found - return 0 */ if (!user_command) return 0; /* got a command - return it and set command_giver */ debug(connections, ("get_user_command: user_command = (%s)\n", user_command)); save_command_giver(ip->ob); #ifndef GET_CHAR_IS_BUFFERED if (ip->iflags & NOECHO) { #else if ((ip->iflags & NOECHO) && !(ip->iflags & SINGLE_CHAR)) { #endif /* must not enable echo before the user input is received */ add_binary_message(command_giver, telnet_no_echo, sizeof(telnet_no_echo)); ip->iflags &= ~NOECHO; } ip->last_time = current_time; return user_command; } /* get_user_command() */ static int escape_command (interactive_t * ip, char * user_command) { if (user_command[0] != '!') return 0; #ifdef OLD_ED if (ip->ed_buffer) return 1; #endif #if defined(F_INPUT_TO) || defined(F_GET_CHAR) if (ip->input_to && ( !(ip->iflags & NOESC) && !(ip->iflags & I_SINGLE_CHAR) ) ) return 1; #endif return 0; } static void process_input (interactive_t * ip, char * user_command) { svalue_t *ret; if (!(ip->iflags & HAS_PROCESS_INPUT)) { parse_command(user_command, command_giver); return; } /* * send a copy of user input back to user object to provide * support for things like command history and mud shell * programming languages. */ copy_and_push_string(user_command); ret = apply(APPLY_PROCESS_INPUT, command_giver, 1, ORIGIN_DRIVER); if (!IP_VALID(ip, command_giver)) return; if (!ret) { ip->iflags &= ~HAS_PROCESS_INPUT; parse_command(user_command, command_giver); return; } #ifndef NO_ADD_ACTION if (ret->type == T_STRING) { static char buf[MAX_TEXT]; strncpy(buf, ret->u.string, MAX_TEXT - 1); parse_command(buf, command_giver); } else { if (ret->type != T_NUMBER || !ret->u.number) parse_command(user_command, command_giver); } #endif } /* * This is the user command handler. This function is called when * a user command needs to be processed. * This function calls get_user_command() to get a user command. * One user command is processed per execution of this function. */ int process_user_command() { char *user_command; interactive_t *ip=NULL;//for if(ip) below /* * WARNING: get_user_command() sets command_giver via * save_command_giver(), but only when the return is non-zero! */ if (!(user_command = get_user_command())) return 0; if(command_giver) ip = command_giver->interactive; current_interactive = command_giver; /* this is yuck phooey, sigh */ if(ip) clear_notify(ip->ob); update_load_av(); debug(connections, ("process_user_command: command_giver = /%s\n", command_giver->obname)); if(!ip) goto exit; user_command = translate_easy(ip->trans->incoming, user_command); if(ip->iflags & USING_MXP && user_command[0] == ' ' && user_command[1] == '[' && user_command[3] == 'z' ){ svalue_t *ret; copy_and_push_string(user_command); ret=apply(APPLY_MXP_TAG, ip->ob, 1, ORIGIN_DRIVER); if(ret && ret->type==T_NUMBER && ret->u.number){ goto exit; } } if (escape_command(ip, user_command)) { if (ip->iflags & SINGLE_CHAR) { /* only 1 char ... switch to line buffer mode */ ip->iflags |= WAS_SINGLE_CHAR; ip->iflags &= ~SINGLE_CHAR; #ifdef GET_CHAR_IS_BUFFERED ip->text_start = ip->text_end = *ip->text = 0; #endif set_linemode(ip); } else { if (ip->iflags & WAS_SINGLE_CHAR) { /* we now have a string ... switch back to char mode */ ip->iflags &= ~WAS_SINGLE_CHAR; ip->iflags |= SINGLE_CHAR; set_charmode(ip); if (!IP_VALID(ip, command_giver)) { goto exit; } } process_input(ip, user_command + 1); } goto exit; } #ifdef OLD_ED if (ip->ed_buffer) { ed_cmd(user_command); goto exit; } #endif #if defined(F_INPUT_TO) || defined(F_GET_CHAR) if (call_function_interactive(ip, user_command)) { goto exit; } #endif process_input(ip, user_command); exit: /* * Print a prompt if user is still here. */ if (IP_VALID(ip, command_giver)) print_prompt(ip); current_interactive = 0; restore_command_giver(); return 1; } #define HNAME_BUF_SIZE 200 /* * This is the hname input data handler. This function is called by the * master handler when data is pending on the hname socket (addr_server_fd). */ static void hname_handler() { static char hname_buf[HNAME_BUF_SIZE]; static int hname_buf_pos; int num_bytes; num_bytes = HNAME_BUF_SIZE - hname_buf_pos - 1; /* room for nul */ num_bytes = OS_socket_read(addr_server_fd, hname_buf + hname_buf_pos, num_bytes); if (num_bytes <= 0) { if (num_bytes == -1) { #ifdef EWOULDBLOCK if (socket_errno == EWOULDBLOCK) { debug(connections, ("hname_handler: read on fd %d: Operation would block.\n", addr_server_fd)); return; } #endif debug_message("hname_handler: read on fd %d\n", addr_server_fd); socket_perror("hname_handler: read", 0); } else { debug_message("hname_handler: closing address server connection.\n"); } OS_socket_close(addr_server_fd); addr_server_fd = -1; return; } hname_buf_pos += num_bytes; hname_buf[hname_buf_pos] = 0; debug(connections, ("hname_handler: address server replies: %s", hname_buf)); while (hname_buf_pos) { char *nl, *pp; /* if there's no newline, there's more data to come */ if (!(nl = strchr(hname_buf, '\n'))) break; *nl++ = 0; if ((pp = strchr(hname_buf, ' ')) != 0) { *pp++ = 0; got_addr_number(pp, hname_buf); if (isdigit(hname_buf[0]) || hname_buf[0] == ':') { #ifdef IPV6 struct in6_addr addr; int ret; if(1 ==(ret = inet_pton(AF_INET6, hname_buf, &addr))) { if (strcmp(pp, "0") != 0) add_ip_entry(addr, pp); } #else unsigned long laddr; if ((laddr = inet_addr(hname_buf)) != INADDR_NONE) { if (strcmp(pp, "0") != 0) add_ip_entry(laddr, pp); } #endif } } hname_buf_pos -= (nl - hname_buf); if (hname_buf_pos) memmove(hname_buf, nl, hname_buf_pos + 1); /* be sure to get the nul */ } } /* * Remove an interactive user immediately. */ void remove_interactive (object_t * ob, int dested) { int idx; /* don't have to worry about this dangling, since this is the routine * that causes this to dangle elsewhere, and we are protected from * getting called recursively by CLOSING. safe_apply() should be * used here, since once we start this process we can't back out, * so jumping out with an error would be bad. */ interactive_t *ip = ob->interactive; if (!ip) return; if (ip->iflags & CLOSING) { if (!dested) debug_message("Double call to remove_interactive()\n"); return; } debug(connections, ("Closing connection from %s.\n", inet_ntoa(ip->addr.sin_addr))); flush_message(ip); ip->iflags |= CLOSING; #ifdef OLD_ED if (ip->ed_buffer) { save_ed_buffer(ob); } #else if (ob->flags & O_IN_EDIT) { object_save_ed_buffer(ob); ob->flags &= ~O_IN_EDIT; } #endif if (!dested) { /* * auto-notification of net death */ save_command_giver(ob); safe_apply(APPLY_NET_DEAD, ob, 0, ORIGIN_DRIVER); restore_command_giver(); } #ifndef NO_SNOOP if (ip->snooped_by) { ip->snooped_by->flags &= ~O_SNOOP; ip->snooped_by = 0; } #endif #ifdef HAVE_ZLIB if (ip->compressed_stream) { end_compression(ip); } #endif debug(connections, ("remove_interactive: closing fd %d\n", ip->fd)); if (OS_socket_close(ip->fd) == -1) { socket_perror("remove_interactive: close", 0); } #ifdef F_SET_HIDE if (ob->flags & O_HIDDEN) num_hidden_users--; #endif num_user--; clear_notify(ip->ob); #if defined(F_INPUT_TO) || defined(F_GET_CHAR) if (ip->input_to) { free_object(&ip->input_to->ob, "remove_interactive"); free_sentence(ip->input_to); if (ip->num_carry > 0) free_some_svalues(ip->carryover, ip->num_carry); ip->carryover = NULL; ip->num_carry = 0; ip->input_to = 0; } #endif for (idx = 0; idx < max_users; idx++) if (all_users[idx] == ip) break; DEBUG_CHECK(idx == max_users, "remove_interactive: could not find and remove user!\n"); FREE(ip->sb_buf); FREE(ip); ob->interactive = 0; all_users[idx] = 0; free_object(&ob, "remove_interactive"); return; } /* remove_interactive() */ #if defined(F_INPUT_TO) || defined(F_GET_CHAR) static int call_function_interactive (interactive_t * i, char * str) { object_t *ob; funptr_t *funp; char *function; svalue_t *args; sentence_t *sent; int num_arg; #ifdef GET_CHAR_IS_BUFFERED int was_single = 0; int was_noecho = 0; #endif i->iflags &= ~NOESC; if (!(sent = i->input_to)) return (0); /* * Special feature: input_to() has been called to setup a call to a * function. */ if (sent->ob->flags & O_DESTRUCTED) { /* Sorry, the object has selfdestructed ! */ free_object(&sent->ob, "call_function_interactive"); free_sentence(sent); i->input_to = 0; if (i->num_carry) free_some_svalues(i->carryover, i->num_carry); i->carryover = NULL; i->num_carry = 0; i->input_to = 0; if (i->iflags & SINGLE_CHAR) { /* * clear single character mode */ i->iflags &= ~SINGLE_CHAR; #ifndef GET_CHAR_IS_BUFFERED set_linemode(i); #else was_single = 1; if (i->iflags & NOECHO) { was_noecho = 1; i->iflags &= ~NOECHO; } #endif } return (0); } /* * We must all references to input_to fields before the call to apply(), * because someone might want to set up a new input_to(). */ /* we put the function on the stack in case of an error */ STACK_INC; if (sent->flags & V_FUNCTION) { function = 0; sp->type = T_FUNCTION; sp->u.fp = funp = sent->function.f; funp->hdr.ref++; } else { sp->type = T_STRING; sp->subtype = STRING_SHARED; sp->u.string = function = sent->function.s; ref_string(function); } ob = sent->ob; free_object(&sent->ob, "call_function_interactive"); free_sentence(sent); /* * If we have args, we have to copy them, so the svalues on the * interactive struct can be FREEd */ num_arg = i->num_carry; if (num_arg) { args = i->carryover; i->num_carry = 0; i->carryover = NULL; } else args = NULL; i->input_to = 0; if (i->iflags & SINGLE_CHAR) { /* * clear single character mode */ i->iflags &= ~SINGLE_CHAR; #ifndef GET_CHAR_IS_BUFFERED set_linemode(i); #else was_single = 1; if (i->iflags & NOECHO) { was_noecho = 1; i->iflags &= ~NOECHO; } #endif } copy_and_push_string(str); /* * If we have args, we have to push them onto the stack in the order they * were in when we got them. They will be popped off by the called * function. */ if (args) { transfer_push_some_svalues(args, num_arg); FREE(args); } /* current_object no longer set */ if (function) { if (function[0] == APPLY___INIT_SPECIAL_CHAR) error("Illegal function name.\n"); (void) apply(function, ob, num_arg + 1, ORIGIN_INTERNAL); } else call_function_pointer(funp, num_arg + 1); pop_stack(); /* remove `function' from stack */ #ifdef GET_CHAR_IS_BUFFERED if (IP_VALID(i, ob)) { if (was_single && !(i->iflags & SINGLE_CHAR)) { i->text_start = i->text_end = 0; i->text[0] = '\0'; i->iflags &= ~CMD_IN_BUF; set_linemode(i); } if (was_noecho && !(i->iflags & NOECHO)) add_binary_message(i->ob, telnet_no_echo, sizeof(telnet_no_echo)); } #endif return (1); } /* call_function_interactive() */ int set_call (object_t * ob, sentence_t * sent, int flags) { if (ob == 0 || sent == 0) return (0); if (ob->interactive == 0 || ob->interactive->input_to) return (0); ob->interactive->input_to = sent; ob->interactive->iflags |= (flags & (I_NOECHO | I_NOESC | I_SINGLE_CHAR)); if (flags & I_NOECHO) add_binary_message(ob, telnet_yes_echo, sizeof(telnet_yes_echo)); if (flags & I_SINGLE_CHAR) set_charmode(ob->interactive); return (1); } /* set_call() */ #endif void set_prompt (const char * str) { if (command_giver && command_giver->interactive) { command_giver->interactive->prompt = str; } } /* set_prompt() */ /* * Print the prompt, but only if input_to not is disabled. */ static void print_prompt (interactive_t* ip) { object_t *ob = ip->ob; #if defined(F_INPUT_TO) || defined(F_GET_CHAR) if (ip->input_to == 0) { #endif /* give user object a chance to write its own prompt */ if (!(ip->iflags & HAS_WRITE_PROMPT)) tell_object(ip->ob, ip->prompt, strlen(ip->prompt)); #ifdef OLD_ED else if (ip->ed_buffer) tell_object(ip->ob, ip->prompt, strlen(ip->prompt)); #endif else if (!apply(APPLY_WRITE_PROMPT, ip->ob, 0, ORIGIN_DRIVER)) { if (!IP_VALID(ip, ob)) return; ip->iflags &= ~HAS_WRITE_PROMPT; tell_object(ip->ob, ip->prompt, strlen(ip->prompt)); } #if defined(F_INPUT_TO) || defined(F_GET_CHAR) } #endif if (!IP_VALID(ip, ob)) return; /* * Put the IAC GA thing in here... Moved from before writing the prompt; * vt src says it's a terminator. Should it be inside the no-input_to * case? We'll see, I guess. */ if ((ip->iflags & USING_TELNET) && !(ip->iflags & SUPPRESS_GA)) add_binary_message(command_giver, telnet_ga, sizeof(telnet_ga)); if (!IP_VALID(ip, ob)) return; } /* print_prompt() */ /* * Let object 'me' snoop object 'you'. If 'you' is 0, then turn off * snooping. * * This routine is almost identical to the old set_snoop. The main * difference is that the routine writes nothing to user directly, * all such communication is taken care of by the mudlib. It communicates * with master.c in order to find out if the operation is permissble or * not. The old routine let everyone snoop anyone. This routine also returns * 0 or 1 depending on success. */ #ifndef NO_SNOOP int new_set_snoop (object_t * by, object_t * victim) { interactive_t *ip; object_t *tmp; if (by->flags & O_DESTRUCTED) return 0; if (victim && (victim->flags & O_DESTRUCTED)) return 0; if (victim) { if (!victim->interactive) error("Second argument of snoop() is not interactive!\n"); ip = victim->interactive; } else { /* * Stop snoop. */ if (by->flags & O_SNOOP) { int i; for (i = 0; i < max_users; i++) { if (all_users[i] && all_users[i]->snooped_by == by) all_users[i]->snooped_by = 0; } by->flags &= ~O_SNOOP; } return 1; } /* * Protect against snooping loops. */ tmp = by; while (tmp) { if (tmp == victim) return 0; /* the person snooping us, if any */ tmp = (tmp->interactive ? tmp->interactive->snooped_by : 0); } /* * Terminate previous snoop, if any. */ if (by->flags & O_SNOOP) { int i; for (i = 0; i < max_users; i++) { if (all_users[i] && all_users[i]->snooped_by == by) all_users[i]->snooped_by = 0; } } if (ip->snooped_by) ip->snooped_by->flags &= ~O_SNOOP; by->flags |= O_SNOOP; ip->snooped_by = by; return 1; } /* set_new_snoop() */ #endif static void query_addr_name (object_t * ob) { static char buf[100]; static char *dbuf = &buf[sizeof(int) + sizeof(int) + sizeof(int)]; int msglen; int msgtype; sprintf(dbuf, "%s", query_ip_number(ob)); msglen = sizeof(int) + strlen(dbuf) +1; msgtype = DATALEN; memcpy(buf, (char *) &msgtype, sizeof(msgtype)); memcpy(&buf[sizeof(int)], (char *) &msglen, sizeof(msglen)); msgtype = NAMEBYIP; memcpy(&buf[sizeof(int) + sizeof(int)], (char *) &msgtype, sizeof(msgtype)); debug(connections, ("query_addr_name: sent address server %s\n", dbuf)); if (OS_socket_write(addr_server_fd, buf, msglen + sizeof(int) + sizeof(int)) == -1) { switch (socket_errno) { case EBADF: debug_message("Address server has closed connection.\n"); addr_server_fd = -1; break; default: socket_perror("query_addr_name: write", 0); break; } } } /* query_addr_name() */ #define IPSIZE 200 typedef struct { char *name; svalue_t call_back; object_t *ob_to_call; } ipnumberentry_t; static ipnumberentry_t ipnumbertable[IPSIZE]; /* * Does a call back on the current_object with the function call_back. */ int query_addr_number (const char * name, svalue_t * call_back) { static char buf[100]; static char *dbuf = &buf[sizeof(int) + sizeof(int) + sizeof(int)]; int msglen; int msgtype; int i; if ((addr_server_fd < 0) || (strlen(name) >= 100 - (sizeof(msgtype) + sizeof(msglen) + sizeof(int)))) { share_and_push_string(name); push_undefined(); if (call_back->type == T_STRING) apply(call_back->u.string, current_object, 2, ORIGIN_INTERNAL); else call_function_pointer(call_back->u.fp, 2); return 0; } strcpy(dbuf, name); msglen = sizeof(int) + strlen(name) +1; msgtype = DATALEN; memcpy(buf, (char *) &msgtype, sizeof(msgtype)); memcpy(&buf[sizeof(int)], (char *) &msglen, sizeof(msglen)); msgtype = NAMEBYIP; for (i = 0; i < strlen(name); i++){ if (isalpha(name[i])) { msgtype = IPBYNAME; break; } } memcpy(&buf[sizeof(int) + sizeof(int)], (char *) &msgtype, sizeof(msgtype)); debug(connections, ("query_addr_number: sent address server %s\n", dbuf)); if (addr_server_fd && OS_socket_write(addr_server_fd, buf, msglen + sizeof(int) + sizeof(int)) == -1) { switch (socket_errno) { case EBADF: debug_message("Address server has closed connection.\n"); addr_server_fd = -1; break; default: socket_perror("query_addr_name: write", 0); break; } share_and_push_string(name); push_undefined(); if (call_back->type == T_STRING) apply(call_back->u.string, current_object, 2, ORIGIN_INTERNAL); else call_function_pointer(call_back->u.fp, 2); return 0; } else { int i; /* We put ourselves into the pending name lookup entry table */ /* Find the first free entry */ for (i = 0; i < IPSIZE && ipnumbertable[i].name; i++) ; if (i == IPSIZE) { /* We need to error... */ share_and_push_string(name); push_undefined(); if (call_back->type == T_STRING) apply(call_back->u.string, current_object, 2, ORIGIN_INTERNAL); else call_function_pointer(call_back->u.fp, 2); return 0; } /* Create our entry... */ ipnumbertable[i].name = make_shared_string(name); assign_svalue_no_free(&ipnumbertable[i].call_back, call_back); ipnumbertable[i].ob_to_call = current_object; add_ref(current_object, "query_addr_number: "); return i + 1; } } /* query_addr_number() */ static void got_addr_number (char * number, char * name) { int i; char *theName, *theNumber; /* First remove all the dested ones... */ for (i = 0; i < IPSIZE; i++) if (ipnumbertable[i].name && ipnumbertable[i].ob_to_call->flags & O_DESTRUCTED) { free_svalue(&ipnumbertable[i].call_back, "got_addr_number"); free_string(ipnumbertable[i].name); free_object(&ipnumbertable[i].ob_to_call, "got_addr_number: "); ipnumbertable[i].name = NULL; } for (i = 0; i < IPSIZE; i++) { if (ipnumbertable[i].name && strcmp(name, ipnumbertable[i].name)== 0) { /* Found one, do the call back... */ theName = ipnumbertable[i].name; theNumber = number; if (uisdigit(theName[0])) { char *tmp; tmp = theName; theName = theNumber; theNumber = tmp; } if (strcmp(theName, "0")) { share_and_push_string(theName); } else { push_undefined(); } if (strcmp(theNumber, "0")) { share_and_push_string(theNumber); } else { push_undefined(); } push_number(i + 1); if (ipnumbertable[i].call_back.type == T_STRING) safe_apply(ipnumbertable[i].call_back.u.string, ipnumbertable[i].ob_to_call, 3, ORIGIN_INTERNAL); else safe_call_function_pointer(ipnumbertable[i].call_back.u.fp, 3); free_svalue(&ipnumbertable[i].call_back, "got_addr_number"); free_string(ipnumbertable[i].name); free_object(&ipnumbertable[i].ob_to_call, "got_addr_number: "); ipnumbertable[i].name = NULL; } } } /* got_addr_number() */ #undef IPSIZE #define IPSIZE 200 typedef struct { #ifdef IPV6 struct in6_addr addr; #else long addr; #endif char *name; } ipentry_t; static ipentry_t iptable[IPSIZE]; static int ipcur; #ifdef DEBUGMALLOC_EXTENSIONS void mark_iptable() { int i; for (i=0; i < IPSIZE; i++) if (iptable[i].name) EXTRA_REF(BLOCK(iptable[i].name))++; } #endif #ifdef IPV6 char ipv6addr[INET6_ADDRSTRLEN]; #endif char *query_ip_name (object_t * ob) { int i; if (ob == 0) ob = command_giver; if (!ob || ob->interactive == 0) return NULL; #ifdef IPV6 for (i = 0; i < IPSIZE; i++) { if (!memcmp(&iptable[i].addr, &ob->interactive->addr.sin6_addr, sizeof(ob->interactive->addr.sin6_addr)) && iptable[i].name) return (iptable[i].name); } inet_ntop(AF_INET6, &ob->interactive->addr.sin6_addr, ipv6addr, INET6_ADDRSTRLEN); return ipv6addr; #else for (i = 0; i < IPSIZE; i++) { if (iptable[i].addr == ob->interactive->addr.sin_addr.s_addr && iptable[i].name) return (iptable[i].name); } return (inet_ntoa(ob->interactive->addr.sin_addr)); #endif } #ifdef IPV6 static void add_ip_entry (struct in6_addr addr, char * name) #else static void add_ip_entry (long addr, char * name) #endif { int i; for (i = 0; i < IPSIZE; i++) { if (!memcmp(&iptable[i].addr, &addr, sizeof(addr))) return; } iptable[ipcur].addr = addr; if (iptable[ipcur].name) free_string(iptable[ipcur].name); iptable[ipcur].name = make_shared_string(name); ipcur = (ipcur + 1) % IPSIZE; } const char *query_ip_number (object_t * ob) { if (ob == 0) ob = command_giver; if (!ob || ob->interactive == 0) return 0; #ifdef IPV6 inet_ntop(AF_INET6, &ob->interactive->addr.sin6_addr, ipv6addr, INET6_ADDRSTRLEN); return &ipv6addr[0]; #else return (inet_ntoa(ob->interactive->addr.sin_addr)); #endif } #ifndef INET_NTOA_OK /* * Note: if the address string is "a.b.c.d" the address number is * a * 256^3 + b * 256^2 + c * 256 + d */ char *inet_ntoa (struct in_addr ad) { u_long s_ad; int a, b, c, d; static char addr[20]; /* 16 + 1 should be enough */ s_ad = ad.s_addr; d = s_ad % 256; s_ad /= 256; c = s_ad % 256; s_ad /= 256; b = s_ad % 256; a = s_ad / 256; sprintf(addr, "%d.%d.%d.%d", a, b, c, d); return (addr); } #endif /* INET_NTOA_OK */ char *query_host_name() { static char name[400]; gethostname(name, sizeof(name)); name[sizeof(name) - 1] = '\0'; /* Just to make sure */ return (name); } /* query_host_name() */ #ifndef NO_SNOOP object_t *query_snoop (object_t * ob) { if (!ob->interactive) return 0; return ob->interactive->snooped_by; } /* query_snoop() */ object_t *query_snooping (object_t * ob) { int i; if (!(ob->flags & O_SNOOP)) return 0; for (i = 0; i < max_users; i++) { if (all_users[i] && all_users[i]->snooped_by == ob) return all_users[i]->ob; } fatal("couldn't find snoop target.\n"); return 0; } /* query_snooping() */ #endif int query_idle (object_t * ob) { if (!ob->interactive) error("query_idle() of non-interactive object.\n"); return (current_time - ob->interactive->last_time); } /* query_idle() */ #ifdef F_EXEC int replace_interactive (object_t * ob, object_t * obfrom) { if (ob->interactive) { error("Bad argument 1 to exec()\n"); } if (!obfrom->interactive) { error("Bad argument 2 to exec()\n"); } #ifdef F_SET_HIDE if ((ob->flags & O_HIDDEN) != (obfrom->flags & O_HIDDEN)) { if (ob->flags & O_HIDDEN) { num_hidden_users++; } else { num_hidden_users--; } } #endif ob->interactive = obfrom->interactive; /* * assume the existance of write_prompt and process_input in user.c until * proven wrong (after trying to call them). */ ob->interactive->iflags |= (HAS_WRITE_PROMPT | HAS_PROCESS_INPUT); obfrom->interactive = 0; ob->interactive->ob = ob; ob->flags |= O_ONCE_INTERACTIVE; obfrom->flags &= ~O_ONCE_INTERACTIVE; add_ref(ob, "exec"); if (obfrom == command_giver) { set_command_giver(ob); } free_object(&obfrom, "exec"); return (1); } /* replace_interactive() */ #endif void outbuf_zero (outbuffer_t * outbuf) { outbuf->real_size = 0; outbuf->buffer = 0; } int outbuf_extend (outbuffer_t * outbuf, int l) { int limit; DEBUG_CHECK(l < 0, "Negative length passed to outbuf_extend.\n"); l = (l > MAX_STRING_LENGTH ? MAX_STRING_LENGTH : l); if (outbuf->buffer) { limit = MSTR_SIZE(outbuf->buffer); if (outbuf->real_size + l > limit) { if (outbuf->real_size == MAX_STRING_LENGTH) return 0; /* TRUNCATED */ /* assume it's going to grow some more */ limit = (outbuf->real_size + l) * 2; if (limit > MAX_STRING_LENGTH) { limit = MAX_STRING_LENGTH; outbuf->buffer = extend_string(outbuf->buffer, limit); return limit - outbuf->real_size; } outbuf->buffer = extend_string(outbuf->buffer, limit); } } else { outbuf->buffer = new_string(l, "outbuf_extend"); outbuf->real_size = 0; } return l; } void outbuf_add (outbuffer_t * outbuf, const char * str) { int l, limit; if (!outbuf) return; l = strlen(str); if ((limit = outbuf_extend(outbuf, l)) > 0) { strncpy(outbuf->buffer + outbuf->real_size, str, limit); outbuf->real_size += (l > limit ? limit : l); *(outbuf->buffer + outbuf->real_size) = 0; } } void outbuf_addchar (outbuffer_t * outbuf, char c) { if(outbuf && (outbuf_extend(outbuf, 1) > 0)) { *(outbuf->buffer + outbuf->real_size++) = c; *(outbuf->buffer + outbuf->real_size) = 0; } } void outbuf_addv (outbuffer_t *outbuf, const char *format, ...) { char buf[LARGEST_PRINTABLE_STRING + 1]; va_list args; V_START(args, format); V_VAR(outbuffer_t *, outbuf, args); V_VAR(char *, format, args); vsnprintf(buf, LARGEST_PRINTABLE_STRING, format, args); va_end(args); if (!outbuf) return; outbuf_add(outbuf, buf); } void outbuf_fix (outbuffer_t * outbuf) { if (outbuf && outbuf->buffer) outbuf->buffer = extend_string(outbuf->buffer, outbuf->real_size); } void outbuf_push (outbuffer_t * outbuf) { STACK_INC; sp->type = T_STRING; if (outbuf && outbuf->buffer) { outbuf->buffer = extend_string(outbuf->buffer, outbuf->real_size); sp->subtype = STRING_MALLOC; sp->u.string = outbuf->buffer; } else { sp->subtype = STRING_CONSTANT; sp->u.string = ""; } } #ifdef HAVE_ZLIB void* zlib_alloc(void* opaque, unsigned int items, unsigned int size) { return CALLOC(items, size); } void zlib_free(void* opaque, void* address) { FREE(address); } static void end_compression (interactive_t *ip) { unsigned char dummy[1]; if (!ip->compressed_stream) { return ; } ip->compressed_stream->avail_in = 0; ip->compressed_stream->next_in = dummy; if (deflate(ip->compressed_stream, Z_FINISH) != Z_STREAM_END) { } deflateEnd(ip->compressed_stream); FREE(ip->compressed_stream); ip->compressed_stream = NULL; } static void start_compression (interactive_t *ip) { z_stream* zcompress; if (ip->compressed_stream) { return ; } zcompress = (z_stream *) DXALLOC(sizeof(z_stream), TAG_INTERACTIVE, "start_compression"); zcompress->next_in = NULL; zcompress->avail_in = 0; zcompress->next_out = ip->compress_buf; zcompress->avail_out = COMPRESS_BUF_SIZE; zcompress->zalloc = zlib_alloc; zcompress->zfree = zlib_free; zcompress->opaque = NULL; if (deflateInit(zcompress, 9) != Z_OK) { FREE(zcompress); fprintf(stderr, "Compression failed.\n"); return ; } // Ok, compressing. ip->compressed_stream = zcompress; } static int flush_compressed_output (interactive_t *ip) { int iStart, nBlock, nWrite, len; z_stream* zcompress; int ret = 1; if (!ip->compressed_stream) { return ret; } zcompress = ip->compressed_stream; /* Try to write out some data.. */ len = zcompress->next_out - ip->compress_buf; if (len > 0) { /* we have some data to write */ nWrite = 0; for (iStart = 0; iStart < len; iStart += nWrite) { if (len - iStart < 4096) { nBlock =len - iStart; } else { nBlock = 4096; } nWrite = send(ip->fd, &ip->compress_buf[iStart], nBlock, ip->out_of_band); if (nWrite < 0) { fprintf(stderr, "Error sending compressed data (%d)\n", errno); if (errno == EAGAIN #ifndef WIN32 || errno == ENOSR #endif ) { ret = 2; break; } return FALSE; /* write error */ } if (nWrite <= 0) { break; } } if (iStart) { /* We wrote "iStart" bytes */ if (iStart < len) { memmove(ip->compress_buf, ip->compress_buf+iStart, len - iStart); } zcompress->next_out = ip->compress_buf + len - iStart; } } return ret; } static int send_compressed (interactive_t *ip, unsigned char* data, int length) { z_stream* zcompress; int wr = 1; int first = 1; zcompress = ip->compressed_stream; zcompress->next_in = data; zcompress->avail_in = length; while (zcompress->avail_in && (wr == 1 || first)) { if(wr == 2) first = 0; zcompress->avail_out = COMPRESS_BUF_SIZE - (zcompress->next_out - ip->compress_buf); if (zcompress->avail_out) { deflate(zcompress, Z_SYNC_FLUSH); } if(!( wr = flush_compressed_output(ip))) return 0; } return length; } #endif #ifdef F_ACT_MXP void f_act_mxp(){ add_binary_message(current_object, telnet_will_mxp, sizeof(telnet_will_mxp)); } #endif #ifdef F_SEND_ZMP void f_send_zmp(){ add_binary_message(current_object, telnet_start_zmp, sizeof(telnet_start_zmp)); add_binary_message(current_object, (const unsigned char *)(sp-1)->u.string, strlen((sp-1)->u.string)); int i; unsigned char zero = 0; for(i=0; iu.arr->size; i++){ if(sp->u.arr->item[i].type == T_STRING){ add_binary_message(current_object, &zero, 1); add_binary_message(current_object, (const unsigned char *)sp->u.arr->item[i].u.string, strlen(sp->u.arr->item[i].u.string)); } } add_binary_message(current_object, &zero, 1); add_binary_message(current_object, telnet_end_sub, sizeof(telnet_end_sub)); pop_2_elems(); } #endif #ifdef F_SEND_GMCP void f_send_gmcp(){ add_binary_message(current_object, telnet_start_gmcp, sizeof(telnet_start_gmcp)); add_binary_message(current_object, (const unsigned char *)(sp->u.string), strlen(sp->u.string)); add_binary_message(current_object, telnet_end_sub, sizeof(telnet_end_sub)); } #endif #ifdef F_REQUEST_TERM_TYPE void f_request_term_type(){ add_binary_message(command_giver, telnet_term_query, sizeof(telnet_term_query)); } #endif #ifdef F_START_REQUEST_TERM_TYPE void f_start_request_term_type(){ add_binary_message(command_giver, telnet_do_ttype, sizeof(telnet_do_ttype)); } #endif #ifdef F_REQUEST_TERM_SIZE void f_request_term_size(){ if((st_num_arg == 1) && (sp->u.number == 0)) add_binary_message(command_giver, telnet_dont_naws, sizeof(telnet_dont_naws)); else add_binary_message(command_giver, telnet_do_naws, sizeof(telnet_do_naws)); if(st_num_arg == 1) sp--; } #endif