#include <sys/types.h> #include <sys/time.h> #include <sys/ioctl.h> #include <sys/file.h> #if defined(_SEQUENT_) #include <sys/procstats.h> #endif #include <fcntl.h> #if !defined(FNDELAY) && defined(O_NDELAY) #define FNDELAY O_NDELAY #endif #if !defined(FASYNC) #if defined(O_ASYNC) #define FASYNC O_ASYNC #else #define FASYNC 0 #endif #endif #define TELOPTS #include <arpa/telnet.h> #include <netdb.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <ctype.h> #include <signal.h> #include <memory.h> #include <fcntl.h> #include <setjmp.h> #ifdef _AIX #include <sys/select.h> #endif #include "config.h" #include "lint.h" #include "interpret.h" #include "comm.h" #include "object.h" #include "sent.h" #include "patchlevel.h" #ifndef EPROTO #define EPROTO EPROTOTYPE #endif int socket (int, int, int); #if !defined(sgi) && !defined(LINUX) && !defined(__NetBSD__) && !defined(__bsdi__) && !defined(_SEQUENT_) #ifdef NeXT int setsockopt (int, int, int, void *, int); #else int setsockopt (int, int, int, const char *, int); #endif int bind (int, struct sockaddr *, int); int listen (int, int); int accept (int, struct sockaddr *, int *); #endif /* sgi */ int select (int, fd_set *, fd_set *, fd_set *, struct timeval *); #if !defined(LINUX) && !defined(__NetBSD__) && !defined(__bsdi__) void bzero (char *, int); #endif void telnet_neg (char *, char *); void set_prompt (char *); char *query_ip_number (struct object *); static void add_ip_entry (unsigned long, char *); extern char *xalloc(), *string_copy(), *unshared_str_copy(); extern int d_flag; extern int current_time; extern int eval_cost; extern struct svalue *sp; char * first_cmd_in_buf (struct interactive *); void next_cmd_in_buf (struct interactive *); char * skip_eols (struct interactive *, char *); void remove_flush_entry (struct interactive *ip); void remove_interactive(), add_ref(); extern void debug_message(), fatal(), free_sentence(); struct interactive *all_players[MAX_PLAYERS]; extern int errno; void new_player(); void flush_all_player_mess(); static struct in_addr ntohl_addr(); fd_set readfds; int nfds = 0; int num_player; FILE *f_ip_demon = NULL, *f_ip_demon_wr; static struct object *first_player_for_flush=(struct object *)NULL; /* * Interprocess communication interface to the backend. */ static int s, udp_send = -1; extern int port_number; #ifdef CATCH_UDP_PORT extern int udp_port; static int udp_s; #endif #ifdef COMM_STAT int add_message_calls=0; int inet_packets=0; int inet_volume=0; #endif #ifdef SERVICE_PORT extern int service_port; static int service_s = -1, all_services[MAX_PLAYERS]; static void new_service(int sock) { int i; for (i=0 ; i<MAX_PLAYERS ; i++) if (all_services[i] < 0) break; if (i < MAX_PLAYERS) all_services[i] = sock; else { write(sock, "ERROR Too many services in use.\n", 32); close(sock); } } #endif /* SERVICE_PORT */ #if (FASYNC || defined(FIOASYNC)) && defined(SIGIO) void catch_io(int sig) { extern int volatile interupted; #if defined(SYSV) || defined(LINUX) signal(SIGIO, catch_io); #endif interupted = 1; } #endif void prepare_ipc() { struct sockaddr_in sin; struct hostent *hp; int tmp; char host_name[100]; if (gethostname(host_name, sizeof host_name) == -1) { perror("gethostname"); fatal("Error in gethostname()\n"); } hp = gethostbyname(host_name); if (hp == 0) { (void)fprintf(stderr, "gethostbyname: unknown host.\n"); exit(1); } memset((char *)&sin, '\0', sizeof sin); memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length); sin.sin_port = htons((u_short)port_number); sin.sin_family = hp->h_addrtype; sin.sin_addr.s_addr = INADDR_ANY; s = socket(hp->h_addrtype, SOCK_STREAM, 0); if (s == -1) { perror("socket"); abort(); } tmp = 1; if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)) < 0) { perror ("setsockopt"); exit (1); } if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1) { if (errno == EADDRINUSE) { fprintf(stderr, "Socket already bound!\n"); debug_message("Socket already bound!\n"); exit(errno); } else { perror("bind"); abort(); } } if (listen(s, 5) == -1) { perror("listen"); abort(); } tmp = 1; #if defined(M_UNIX) || defined(NeXT) || defined(_AIX) if (ioctl(s, FIONBIO, &tmp) == -1) { perror("ioctl socket FIONBIO"); abort(); } #if defined(FIOASYNC) && defined(SIGIO) if (ioctl(s, FIOASYNC, &tmp) == -1) { perror("ioctl socket FIOASYNC"); abort(); } #endif #else /* M_UNIX */ fcntl(s, F_SETOWN, getpid()); if (fcntl(s, F_SETFL, FNDELAY | FASYNC) == -1) { perror("fcntl socket FNDELAY | FASYNC"); abort(); } #endif /* M_UNIX */ #ifdef SERVICE_PORT if (service_port > 0) { sin.sin_port = htons((u_short)service_port); sin.sin_family = hp->h_addrtype; sin.sin_addr.s_addr = INADDR_ANY; service_s = socket(hp->h_addrtype, SOCK_STREAM, 0); if (service_s >= 0) { tmp = 1; if (setsockopt (service_s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)) < 0) { close(service_s); service_s = -1; } } if (service_s >= 0 && bind(service_s, (struct sockaddr *)&sin, sizeof sin) == -1) { close(service_s); service_s = -1; } if (service_s >= 0 && listen(service_s, 5) == -1) { close(service_s); service_s = -1; } #if defined(M_UNIX) || defined(NeXT) || defined(_AIX) tmp = 1; if (service_s >= 0 && ioctl(service_s, FIONBIO, &tmp) == -1) { close(service_s); service_s = -1; } #if defined(FIOASYNC) && defined(SIGIO) if (service_s >= 0 && ioctl(service_s, FIOASYNC, &tmp) == -1) { close(service_s); service_s = -1; } #endif #else /* M_UNIX */ if (service_s >= 0 && fcntl(service_s, F_SETOWN, getpid()) == -1) { close(service_s); service_s = -1; } if (service_s >= 0 && fcntl(service_s, F_SETFL, FNDELAY | FASYNC) == -1) { close(service_s); service_s = -1; } #endif /* M_UNIX */ } for (tmp = 0 ; tmp < MAX_PLAYERS ; tmp++) all_services[tmp] = -1; #endif /* SERVICE_PORT */ signal(SIGPIPE, SIG_IGN); #ifdef CATCH_UDP_PORT if (udp_port != -1) { sin.sin_port = htons((u_short)udp_port); #ifdef DEBUG if (d_flag & DEBUG_COMMAND) debug_message("UDP recv-socket on port: %d\n", udp_port); #endif udp_s = socket(hp->h_addrtype, SOCK_DGRAM, 0); if (udp_s == -1) { perror("udp_socket"); abort(); } tmp = 1; if (setsockopt (udp_s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)) < 0) { perror ("setsockopt"); exit (1); } if (bind(udp_s, (struct sockaddr *)&sin, sizeof sin) == -1) { if (errno == EADDRINUSE) { fprintf(stderr, "UDP Socket already bound!\n"); debug_message("UDP Socket already bound!\n"); close(udp_s); udp_port = -1; } else { perror("udp-bind"); abort(); } } } if (udp_port != -1) { tmp = 1; #if defined(M_UNIX) || defined(NeXT) || defined(_AIX) if (ioctl(udp_s, FIONBIO, &tmp) == -1) { perror("udp - ioctl socket FIONBIO"); abort(); } #if defined(FIOASYNC) && defined(SIGIO) if (ioctl(udp_s, FIOASYNC, &tmp) == -1) { perror("udp - ioctl socket FIOASYNC"); abort(); } #endif #else /* M_UNIX */ if (udp_s >= 0 && fcntl(udp_s, F_SETOWN, getpid()) == -1) { perror("udp - fcntl socket F_SETOWN"); abort(); } if (fcntl(udp_s, F_SETFL, FNDELAY | FASYNC) == -1) { perror("udp - fcntl socket FNDELAY | FASYNC"); abort(); } #endif /* M_UNIX */ } #endif /* CATCH_UDP_PORT */ } /* * This one is called when shutting down the MUD. */ void ipc_remove() { (void)printf("Shutting down ipc...\n"); if (udp_send >= 0) close(udp_send); close(s); } void write_socket(); /* * Send a message to a player. If that player is shadowed, special * care has to be taken. */ /*VARARGS1*/ void add_message2(ob,fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9) char *fmt; long a1, a2, a3, a4, a5, a6, a7, a8, a9; struct object *ob; { char buff[10000]; sprintf(buff,fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9); if (ob && !(ob->flags & O_DESTRUCTED)) { push_string(buff,STRING_MALLOC); apply("catch_tell", ob, 1, 1); } else write_socket(buff, (struct object *)0); } void add_message(fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9) char *fmt; long a1, a2, a3, a4, a5, a6, a7, a8, a9; { add_message2(command_giver,fmt,a1,a2,a3,a4,a5,a6,a7,a8,a9); } void write_socket(char *fmt, struct object *command_giver) { char buff[10000]; /* Kludgy! Hope this is enough ! */ char buff2[MAX_SOCKET_PACKET_SIZE+1]; char *c_from, *c_to; struct interactive *ip; int n, chunk, length; int from, to; int min_length; int old_message_length; if (command_giver == 0 || (command_giver->flags & O_DESTRUCTED) || command_giver->interactive == 0 || command_giver->interactive->do_close) { if ( fmt != MESSAGE_FLUSH ) fputs(fmt, stderr); fflush(stderr); return; } ip = command_giver->interactive; old_message_length = ip->message_length; if ( fmt == MESSAGE_FLUSH ) { min_length = 1; strncpy ( buff, ip->message_buf, length=old_message_length ); buff[length] = '\0'; } else { #ifdef COMM_STAT add_message_calls++; /* We want to know how many packets the old version would have sent */ #endif min_length = DESIRED_SOCKET_PACKET_SIZE; if (old_message_length + strlen(fmt) > sizeof buff) error("Too long message!\n"); (void) strcpy(buff + old_message_length, fmt); length = old_message_length + strlen(buff + old_message_length); /* * Always check that your arrays are big enough ! */ if (shadow_catch_message(command_giver, buff + old_message_length)) return; /* Insert super-snooper here */ #ifdef SUPER_SNOOP { /* This is *crude* code -Azid */ extern char super_snooped[256][16], super_snoopfile[256][32]; extern int num_super_snooped; extern struct svalue *sapply(); if (num_super_snooped) { int i, f; for (i = 0; i < num_super_snooped; i++) { if (ip->ob->living_name != NULL) { if (strcmp(ip->ob->living_name, super_snooped[i]) == 0) { f = open(super_snoopfile[i], O_WRONLY | O_APPEND | O_CREAT, 0600); if (f != NULL) { write(f, buff + old_message_length, strlen(buff + old_message_length)); close(f); } break; } } } } } /* End of super-snooper */ #endif if (ip->snoop_by) add_message2(ip->snoop_by->ob,"%%%s", buff + old_message_length); if ( length >= min_length ) strncpy ( buff, ip->message_buf, old_message_length ); else strcpy(ip->message_buf + old_message_length, buff + old_message_length ); } if (d_flag & DEBUG_OUTPUT) debug_message("[%s(%d)]: %s", command_giver->name, length, buff); /* * Insert CR after all NL. */ to = 0; if (ip->current_column > ip->screen_width) ip->current_column = ip->screen_width; for (from = 0 ; length - from >= min_length ; to = 0 ) { #ifdef WORD_WRAP if (ip->screen_width > 3) { int i; int fold_size; fold_size = ip->screen_width / 5; while(to < sizeof(buff2) - 4 && buff[from] != '\0') { if (buff[from] == '\n') { buff2[to++] = '\r'; buff2[to++] = '\n'; from++; ip->current_column = 0; continue; } if (buff[from] == '\t') { ip->current_column &= ~7; ip->current_column += 8; if (ip->current_column >= ip->screen_width) { buff2[to++] = '\r'; buff2[to++] = '\n'; ip->current_column = 0; /* Skip whitespace at the beginning of the line */ while (buff[from] == ' ' || buff[from] == '\t') from++; if (buff[from] == '\n') from++; } else buff2[to++] = '\t'; from++; continue; } for (i = 1; buff[from + i] >= '!'; i++); if (i + ip->current_column >= ip->screen_width) if ( ip->current_column > ip->screen_width - fold_size) { buff2[to++] = '\r'; buff2[to++] = '\n'; ip->current_column = 0; /* Skip whitespace at the beginning of the line */ while (buff[from] == ' ' || buff[from] == '\t') from++; if (buff[from] == '\n') from++; } else { if (to + ip->screen_width - ip->current_column + 2 >= sizeof(buff2) - 4) break; memcpy(&buff2[to], &buff[from], ip->screen_width - ip->current_column - 1); from += ip->screen_width - ip->current_column - 1; to += ip->screen_width - ip->current_column - 1; buff2[to++] = '-'; buff2[to++] = '\r'; buff2[to++] = '\n'; ip->current_column = 0; } else { if (to + i >= sizeof(buff2) - 4) break; memcpy(&buff2[to], &buff[from], i); from += i; to += i; ip->current_column += i; } } } else #endif /* WORD_WRAP */ { for ( ; to < (sizeof buff2)-1 && buff[from] != '\0';) { if (buff[from] == '\n') buff2[to++] = '\r'; buff2[to++] = buff[from++]; } } chunk = to; /* * We split up the message into something smaller than the max size. */ if ((n = write(ip->socket, buff2, chunk)) == -1) { if (errno == EMSGSIZE) return; if (errno == EINVAL) { if (old_message_length) remove_flush_entry(ip); ip->do_close = 1; return; } if (errno == ENETUNREACH) { if (old_message_length) remove_flush_entry(ip); ip->do_close = 1; return; } if (errno == EHOSTUNREACH) { if (old_message_length) remove_flush_entry(ip); ip->do_close = 1; return; } if (errno == EPIPE) { if (old_message_length) remove_flush_entry(ip); ip->do_close = 1; return; } if (errno == EWOULDBLOCK) { if (old_message_length) remove_flush_entry(ip); /* ip->do_close = 1; -- LA */ return; } fprintf(stderr, "write: unknown errno %d\n", errno); perror("write"); if (old_message_length) remove_flush_entry(ip); ip->do_close = 1; return; } #ifdef COMM_STAT inet_packets++; inet_volume += n; #endif if (n != chunk) fprintf(stderr, "write socket: wrote %d, should be %d.\n", n, chunk); continue; } length -= from; ip->message_length = length; if (from) strncpy( ip->message_buf, buff + from, length ); if ( length && !old_message_length ) { /* buffer became 'dirty' */ if ( ip->next_player_for_flush = first_player_for_flush ) { first_player_for_flush->interactive->previous_player_for_flush = command_giver; } ip->previous_player_for_flush = 0; first_player_for_flush = command_giver; } if ( !length && old_message_length ) /* buffer has become empty */ remove_flush_entry(ip); } void remove_flush_entry(struct interactive *ip) { ip->message_length = 0; if ( ip->previous_player_for_flush ) { ip->previous_player_for_flush->interactive->next_player_for_flush = ip->next_player_for_flush; } else { first_player_for_flush = ip->next_player_for_flush; } if ( ip->next_player_for_flush ) { ip->next_player_for_flush->interactive->previous_player_for_flush = ip->previous_player_for_flush; } } void flush_all_player_mess() { struct object *p,*np; for ( p = first_player_for_flush; p; p = np) { np = p->interactive->next_player_for_flush; /* beware of side-effects when calling add_message the first time! */ write_socket(MESSAGE_FLUSH,p); } } /* * Copy a string, replacing newlines with '\0'. Also add an extra * space and back space for every newline. This trick will allow * otherwise empty lines, as multiple newlines would be replaced by * multiple zeroes only. */ static INLINE int copy_chars(char *from, char *to, int n) { int i; char *start = to; for (i = 0; i < n; i++) { if (from[i] == '\r') continue; if (from[i] == '\n') { *to++ = ' '; *to++ = '\b'; *to++ = '\0'; continue; } *to++ = from[i]; } return to - start; } /* * Get a message from any player. For all players without a completed * cmd in their input buffer, read their socket. Then, return the first * cmd of the next player in sequence that has a complete cmd in their buffer. * CmdsGiven is used to allow people in ED to send more cmds (if they have * them queued up) than normal players. If we get a heartbeat, still read * all sockets; if the next cmd giver is -1, we have already cycled and * can go back to do the heart beat. */ #define StartCmdGiver (MAX_PLAYERS-1) /* the one after heartbeat */ #define IncCmdGiver NextCmdGiver = (NextCmdGiver < 0? StartCmdGiver: \ NextCmdGiver - 1) int NextCmdGiver = StartCmdGiver; int CmdsGiven = 0; /* -1 is used to poll heart beat. */ int twait = 0; /* wait time for select() */ extern comm_time_to_call_heart_beat; #define MSR_SIZE 20 int msecs_response[MSR_SIZE]; int msr_point = -1; long last_sec, last_usec; int get_msecs_response(int ix) { return (ix >= 0 && ix < MSR_SIZE) ? msecs_response[ix] : -1; } int get_message(char *buff, int size) { int i, res; struct interactive *ip = 0; char *p; /* * Stay in this loop until we have a message from a player. */ while(1) { int new_socket, cnt; struct sockaddr_in addr; int length; struct timeval timeout; #ifdef CATCH_UDP_PORT char udp_buf[1024], *st; #endif #ifdef SERVICE_PORT char service_buf[1024]; int service; #endif /* SERVICE_PORT */ /* First, try to get a new player... */ length = sizeof addr; new_socket = accept(s, (struct sockaddr *)&addr, &length); if (new_socket != -1) { #if defined(M_UNIX) || defined(NeXT) || defined(_AIX) i = 1; ioctl(new_socket, FIONBIO, &i); #else fcntl(new_socket, F_SETOWN, getpid()); fcntl(new_socket, F_SETFL, FNDELAY | FASYNC); #endif new_player(new_socket, &addr, length); } else if (new_socket == -1 && errno != EWOULDBLOCK && errno != EINTR && errno != EPROTO) { perror("accept"); abort(); } #ifdef SERVICE_PORT /* Next, try to get a new service... */ if (service_s > -1) { length = sizeof addr; new_socket = accept(service_s, (struct sockaddr *)&addr, &length); if (new_socket != -1) { #if defined(M_UNIX) || defined(NeXT) || defined(_AIX) i = 1; ioctl(new_socket, FIONBIO, &i); #else fcntl(new_socket, F_SETOWN, getpid()); fcntl(new_socket, F_SETFL, FNDELAY | FASYNC); #endif new_service(new_socket); } else if (new_socket == -1 && errno != EWOULDBLOCK && errno != EINTR && errno != EPROTO) { perror("accept"); abort(); } } #endif /* SERVICE_PORT */ #ifdef CATCH_UDP_PORT /* Then see if we got any udp messages */ if (udp_port != -1) { cnt = recvfrom(udp_s, udp_buf, sizeof(udp_buf), 0, (struct sockaddr *)&addr, &length); if (cnt != -1) { udp_buf[sizeof(udp_buf) - 1] = 0; udp_buf[cnt] = 0; st = inet_ntoa(addr.sin_addr); push_string(st, STRING_MALLOC); push_string(udp_buf, STRING_MALLOC); apply_master_ob(M_INCOMING_UDP, 2); } } #endif nfds = 0; FD_ZERO(&readfds); for (i = 0; i < MAX_PLAYERS; i++) { ip = all_players[i]; if (!ip) continue; if (ip->do_close) { ip->do_close = 0; remove_interactive(ip->ob, 1); continue; } if (!first_cmd_in_buf(ip)) { FD_SET(ip->socket, &readfds); if (ip->socket >= nfds) nfds = ip->socket+1; } } #ifdef SERVICE_PORT for (i = 0 ; i < MAX_PLAYERS ; i++) { if ((service = all_services[i]) < 0) continue; FD_SET(service, &readfds); if (service >= nfds) nfds = service + 1; } #endif /* SERVICE_PORT */ if (f_ip_demon != NULL) { FD_SET(fileno(f_ip_demon), &readfds); } /* Measure average response time */ #if defined(_SEQUENT_) get_process_stats(&timeout, PS_SELF, NULL, NULL); #else gettimeofday(&timeout, 0); #endif if (msr_point < 0) { for (i = 0; i < MSR_SIZE; i++) msecs_response[i] = -1; msr_point = 0; } else { msecs_response[msr_point++] = (timeout.tv_sec - last_sec) * 1000 + ((timeout.tv_usec > last_usec) ? (timeout.tv_usec - last_usec) / 1000 : (1000000 - (last_usec - timeout.tv_usec)) / 1000); /* Do not store the zero values */ if (msecs_response[msr_point - 1] < 5) msr_point--; msr_point %= MSR_SIZE; } timeout.tv_sec = 0; /* avoid busy waiting when no buffered cmds */ timeout.tv_usec = 0; res = select(nfds, &readfds, 0, 0, &timeout); #if defined(_SEQUENT_) get_process_stats(&timeout, PS_SELF, NULL, NULL); #else gettimeofday(&timeout, 0); #endif last_sec = timeout.tv_sec; last_usec = timeout.tv_usec; if (res == -1) { twait = 0; if (errno == EINTR) /* if we got an alarm, finish the round */ goto return_next_command; perror("select"); return 0; } if (res) { /* waiting packets */ if (f_ip_demon != NULL && FD_ISSET(fileno(f_ip_demon), &readfds)) { char buf[200], *pp, *q; unsigned long laddr; if (fgets(buf, sizeof buf, f_ip_demon)) { /*printf("hname says: %s\n", buf);*/ laddr = inet_addr(buf); if (laddr != -1) { pp = strchr(buf, ' '); if (pp) { pp++; q = strchr(buf, ','); if (q) { char *p1, *p2, *name; *q = 0; p1 = q + 1; if (p2 = strchr(p1, ',')); { *p2 = 0; p2++; if(name = strchr(p2, ':')) { *name = 0; name++; if(q = strchr(name, '\n')) { int i; struct interactive *ir; int lport, rport; *q = 0; lport = atoi(p1); rport = atoi(p2); for (i = 0; i < MAX_PLAYERS; i++) { if (!(ir = all_players[i])) continue; if (ir->addr.sin_addr.s_addr == laddr && ir->lport == lport && ir->rport == rport) { ir->rname = xalloc(strlen(name) + 1); strcpy(ir->rname, name); break; } } } } } } else q = strchr(buf, '\n'); if (q) { *q = 0; add_ip_entry(laddr, pp); } } } } } #ifdef SERVICE_PORT for (i = 0; i < MAX_PLAYERS; i++) { int l; struct svalue *ret; service = all_services[i]; if (service < 0) continue; if (FD_ISSET(service, &readfds)) { if ((l = read(service, service_buf, sizeof(service_buf)-1)) == -1) { close(service); all_services[i] = -1; continue; } service_buf[l] = '\0'; push_string(service_buf, STRING_MALLOC); ret = apply_master_ob(M_INCOMING_SERVICE, 1); if (ret == 0 || ret->type != T_STRING) { write(service, "ERROR Service calls not supported\n", 34); close(service); all_services[i] = -1; continue; } if (write(service, ret->u.string, strlen(ret->u.string)) < 0) { close(service); all_services[i] = -1; continue; } } } #endif /* SERVICE_PORT */ for (i = 0; i < MAX_PLAYERS; i++) { /* read all pending sockets */ ip = all_players[i]; if (ip == 0) continue; if (FD_ISSET(ip->socket, &readfds)) { /* read this player */ int l; /* * Dont overfill their buffer. * Use a very conservative estimate on how much we can * read. */ l = (MAX_TEXT - ip->text_end - 1) / 3; if (l < size) size = l; if ((l = read(ip->socket, buff, size)) == -1) { if (errno == ENETUNREACH) { debug_message("Net unreachable detected.\n"); remove_interactive(ip->ob, 1); continue; } if (errno == EHOSTUNREACH) { debug_message("Host unreachable detected.\n"); remove_interactive(ip->ob, 1); continue; } if (errno == ETIMEDOUT) { debug_message("Connection timed out detected.\n"); remove_interactive(ip->ob, 1); continue; } if (errno == ECONNRESET) { debug_message("Connection reset by peer detected.\n"); remove_interactive(ip->ob, 1); continue; } if (errno == EWOULDBLOCK) { debug_message("read would block socket %d!\n", ip->socket); remove_interactive(ip->ob, 1); continue; } if (errno == EMSGSIZE) { debug_message("read EMSGSIZE !\n"); continue; } perror("read"); debug_message("Unknown errno %d\n", errno); remove_interactive(ip->ob, 1); continue; } if (l == 0) { if (ip->closing) fatal("Tried to read from closing socket.\n"); remove_interactive(ip->ob, 1); return 0; } buff[l] = '\0'; /* replace newlines by nulls and catenate to buffer */ ip->text_end += copy_chars(buff, ip->text + ip->text_end, l); /* now, text->end is just after the last char read. If last */ /* char was a nl, char *before* text_end will be null. */ ip->text[ip->text_end] = '\0'; } } } /* * we have read the sockets, now find and return a command */ return_next_command: twait = 0; ip = 0; for (i = 0; i < MAX_PLAYERS + 1; i++) { if (NextCmdGiver == -1) { /* we have cycled around all players */ CmdsGiven = 0; /* check heart beat */ IncCmdGiver; return 0; } ip = all_players[NextCmdGiver]; if (ip && (p = first_cmd_in_buf(ip))) /* wont respond to partials*/ break; CmdsGiven = 0; /* new player, no cmds issued */ IncCmdGiver; } if (!ip || !p) { /* no cmds found; loop and select (on timeout) again */ NextCmdGiver = StartCmdGiver;/* do a complete poll next time */ CmdsGiven = 0; return(0); } /* * we have a player cmd - return it. If he is in ed, count his * cmds, else only allow 1 cmd. If he has only one partially * completed cmd left after * this, move it to the start of his * buffer; new stuff will be appended. */ command_giver = ip->ob; telnet_neg(buff, p); next_cmd_in_buf(ip); /* move on buffer pointers */ ip->current_column = 0; /* if he is not in ed, dont let him issue another till the poll comes again */ if (ip->ed_buffer && CmdsGiven < ALLOWED_ED_CMDS) CmdsGiven++; else { IncCmdGiver; CmdsGiven = 0; } /* manage snooping - should the snooper see type ahead? Well, he doesnt here */ if (!ip->noecho) { /* This is *crude* code -Azid */ extern char super_snooped[256][16], super_snoopfile[256][32]; extern int num_super_snooped; extern struct svalue *sapply(); #ifdef SUPER_SNOOP if (num_super_snooped) { int i, f; for (i = 0; i < num_super_snooped; i++) { if (ip->ob->living_name != NULL) { if (strcmp(ip->ob->living_name, super_snooped[i]) == 0) { f = open(super_snoopfile[i], O_WRONLY | O_APPEND | O_CREAT, 0600); if (f != NULL) { write(f, buff, strlen(buff)); write(f,"\n",1); close(f); } break; } } } } #endif } if (ip->snoop_by && !ip->noecho) add_message2(ip->snoop_by->ob,"%% %s\n", buff); command_giver = ip->ob; if (ip->noecho) { /* Must not enable echo before the user input is received. */ add_message("%c%c%c", IAC, WONT, TELOPT_ECHO); } ip->noecho = 0; ip->last_time = current_time; return 1; } } /* * find the first character of the next complete cmd in a buffer, 0 if no * completed cmd. There is a completed cmd if there is a null between * text_start and text_end. Zero length commands are discarded (as occur * between <cr> and <lf>). Update text_start if we have to skip leading * nulls. */ char * first_cmd_in_buf(struct interactive *ip) { char * p, *q; p = ip->text_start + ip->text; while ((p < (ip->text_end + ip->text)) && !*p) /* skip null input */ p++; ip->text_start = p - ip->text; if (ip->text_start >= ip->text_end) { ip->text_start = ip->text_end = 0; ip->text[0] = '\0'; return(0); } while ((p < (ip->text_end + ip->text)) && *p) /* find end of cmd */ p++; if (p < ip->text + ip->text_end) /* null terminated, was command */ return(ip->text + ip->text_start); /* have a partial command at end of buffer; move it to start, return null */ /* if it cant move down, truncate it and return it as cmd. */ p = ip->text + ip->text_start; q = ip->text; while (p < (ip->text + ip->text_end)) *(q++) = *(p++); ip->text_end -= ip->text_start; ip->text_start = 0; if (ip->text_end > MAX_TEXT - 2) { ip->text[ip->text_end-2] = '\0'; /* nulls to truncate */ ip->text[ip->text_end-1] = '\0'; /* nulls to truncate */ ip->text_end--; return(ip->text); } /* buffer not full and no newline - no cmd. */ return(0); } /* * move pointers to next cmd, or clear buf. */ void next_cmd_in_buf(struct interactive *ip) { char * p = ip->text + ip->text_start; while (*p && p < ip->text + ip->text_end) p++; /* skip past any nulls at the end */ while (!*p && p < ip->text + ip->text_end) p++; if (p < ip->text + ip->text_end) ip->text_start = p - ip->text; else { ip->text_start = ip->text_end = 0; ip->text[0] = '\0'; } } /* * Remove an interactive player immediately. */ void remove_interactive(struct object *ob, int link_dead) { struct object *save = command_giver; int i; if (!ob || !(ob->interactive)) return; for (i = 0; i < MAX_PLAYERS; i++) { if (all_players[i] != ob->interactive) continue; if (ob->interactive->closing) fatal("Double call to remove_interactive()\n"); command_giver = ob; if (ob->interactive->ed_buffer) { extern void save_ed_buffer(); /* This will call 'get_ed_buffer_save_file_name' in master_ob * If that fails(error in LPC) the closing IP will hang and * on the next read a 'fatal read on closing socket will occur' * unless we do this here before ip->closing = 1 */ add_message("Saving editor buffer.\n"); save_ed_buffer(); } if (!(ob->interactive)) return; ob->interactive->closing = 1; if (ob->interactive->snoop_by) { ob->interactive->snoop_by->snoop_on = 0; ob->interactive->snoop_by = 0; } if (ob->interactive->snoop_on) { ob->interactive->snoop_on->snoop_by = 0; ob->interactive->snoop_on = 0; } write_socket("Closing down.\n", command_giver); write_socket(MESSAGE_FLUSH, command_giver); (void)shutdown(ob->interactive->socket, 2); close(ob->interactive->socket); num_player--; if (ob->interactive->input_to) { free_vector(ob->interactive->carryover); free_sentence(ob->interactive->input_to); ob->interactive->input_to = 0; } if(ob->interactive->rname) free(ob->interactive->rname); free((char *)ob->interactive); ob->interactive = 0; all_players[i] = 0; free_object(ob, "remove_interactive"); push_object(ob); push_number(link_dead); apply_master_ob(M_REMOVE_INTERACTIVE,2); command_giver = save; return; } (void) fprintf(stderr, "Could not find and remove player %s\n", ob->name); abort(); } /* * get the I'th player object from the interactive list, i starts at 0 * and can go to num_player - 1. For users(), etc. */ struct object * get_interactive_object(i) int i; { int n; if (i >= num_player) /* love them ASSERTS() :-) */ fatal("Get interactive (%d) with only %d players!", i, num_player); for (n = 0; n < MAX_PLAYERS; n++) if (all_players[n]) if (!(i--)) return(all_players[n]->ob); fatal("Get interactive: player %d not found! (num_players = %d)", i, num_player); return 0; /* Just to satisfy some compiler warnings */ } void new_player(int new_socket, struct sockaddr_in *addr, int len) { int i; char *p; if (d_flag & DEBUG_CONNECT) debug_message("New player at socket %d.\n", new_socket); for (i = 0; i < MAX_PLAYERS; i++) { struct object *ob; struct svalue *ret; extern struct object *master_ob; if (all_players[i] != 0) continue; command_giver = master_ob; master_ob->interactive = (struct interactive *)xalloc(sizeof (struct interactive)); master_ob->interactive->default_err_message = 0; master_ob->flags |= O_ONCE_INTERACTIVE; /* This initialization is not pretty. */ master_ob->interactive->rname = 0; master_ob->interactive->ob = master_ob; master_ob->interactive->text[0] = '\0'; master_ob->interactive->input_to = 0; master_ob->interactive->closing = 0; master_ob->interactive->snoop_on = 0; master_ob->interactive->snoop_by = 0; master_ob->interactive->text_end = 0; master_ob->interactive->text_start = 0; master_ob->interactive->do_close = 0; master_ob->interactive->noecho = 0; master_ob->interactive->trace_level = 0; master_ob->interactive->trace_prefix = 0; master_ob->interactive->last_time = current_time; master_ob->interactive->ed_buffer = 0; master_ob->interactive->message_length=0; master_ob->interactive->carryover = allocate_array(0); #ifdef WORD_WRAP master_ob->interactive->current_column = 0; master_ob->interactive->screen_width = 0; #endif all_players[i] = master_ob->interactive; all_players[i]->socket = new_socket; set_prompt(NULL); #if 1 memcpy((char *)&all_players[i]->addr, (char *)addr, len); #else getpeername(new_socket, (struct sockaddr *)&all_players[i]->addr, &len); #endif all_players[i]->rport = ntohs(addr->sin_port); all_players[i]->lport = port_number; num_player++; /* * The player object has one extra reference. * It is asserted that the master_ob is loaded. */ add_ref(master_ob, "new_player"); ret = apply_master_ob(M_CONNECT, 0); if (ret == 0 || ret->type != T_OBJECT) { remove_interactive(master_ob, 0); return; } /* * There was an object returned from connect(). Use this as the * player object. */ ob = ret->u.ob; ob->interactive = master_ob->interactive; ob->interactive->ob = ob; ob->flags |= O_ONCE_INTERACTIVE; master_ob->flags &= ~O_ONCE_INTERACTIVE; write_socket(MESSAGE_FLUSH, command_giver); master_ob->interactive = 0; free_object(master_ob, "reconnect"); add_ref(ob, "new_player"); command_giver = ob; if (f_ip_demon_wr != NULL) { /*printf("sent hname %s\n:", query_ip_number(ob));*/ fprintf(f_ip_demon_wr, "%s;%d,%d\n", query_ip_number(ob), all_players[i]->lport, all_players[i]->rport); fflush(f_ip_demon_wr); } logon(ob); flush_all_player_mess(); return; } p = "Lpmud is full. Come back later.\r\n"; write(new_socket, p, strlen(p)); close(new_socket); } int call_function_interactive(struct interactive *i, char *str) { char *function; struct object *ob; int j; if (!i->input_to) return 0; /* * Special feature: input_to() has been called to setup * a call to a function. */ if (i->input_to->ob->flags & O_DESTRUCTED) { /* Sorry, the object has selfdestructed ! */ free_sentence(i->input_to); i->input_to = 0; free_vector(i->carryover); i->carryover = allocate_array(0); return 0; } function = string_copy(command_giver->interactive->input_to->function); ob = i->input_to->ob; free_sentence(i->input_to); /* * We must clear this reference before the call to apply(), because * someone might want to set up a new input_to(). */ i->input_to = 0; /* * Now we set current_object to this object, so that input_to will * work for static functions. */ push_string(str, STRING_MALLOC); current_object = ob; for (j = 0; j < i->carryover->size; j++) push_svalue(&(i->carryover->item[j])); free_vector(i->carryover); i->carryover = allocate_array(0); (void)apply(function, ob, 1 + j, 0); free(function); flush_all_player_mess(); return 1; } int set_call(struct object *ob, struct sentence *sent, int noecho) { struct object *save = command_giver; if (ob == 0 || sent == 0) return 0; if (ob->interactive == 0 || ob->interactive->input_to) return 0; ob->interactive->input_to = sent; ob->interactive->noecho = noecho; command_giver = ob; if (noecho) add_message("%c%c%c", IAC, WILL, TELOPT_ECHO); command_giver = save; return 1; } static struct in_addr ntohl_addr(struct in_addr addr) { struct in_addr ret; ret.s_addr=ntohl(addr.s_addr); return(ret); } void remove_all_players() { int i; struct svalue *ret; extern jmp_buf error_recovery_context; extern int error_recovery_context_exists; ret = apply_master_ob(M_START_SHUTDOWN, 0); if (ret && ret->type == T_POINTER) { int i; struct vector *unload_vec = ret->u.vec; unload_vec->ref++; for (i = 0; i < unload_vec->size; i++) if (setjmp(error_recovery_context)) { clear_state(); add_message("Anomaly in the fabric of world space.\n"); } else { error_recovery_context_exists = 1; push_svalue(&(unload_vec->item[i])); eval_cost = 0; apply_master_ob(M_CLEANUP_SHUTDOWN, 1); } free_vector(unload_vec); } error_recovery_context_exists = 0; apply_master_ob(M_FINAL_SHUTDOWN, 0); } void set_prompt(char *str) { if (str) command_giver->interactive->prompt = str; else command_giver->interactive->prompt = "> "; } /* * Print the prompt, but only if input_to not is disabled. */ void print_prompt() { if (command_giver == 0) fatal("command_giver == 0.\n"); if (command_giver->interactive->input_to == 0) { if (!(command_giver->flags & O_DESTRUCTED) && command_giver->interactive->prompt) add_message(command_giver->interactive->prompt); if (1) { /* add test for heart_beat later */ flush_all_player_mess(); } } } /* * 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 player 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. */ int set_snoop(struct object *me, struct object *you) { struct interactive *on = 0, *by = 0, *tmp; int i; struct svalue *ret; extern struct object *master_ob; /* Stop if people managed to quit before we got this far */ if (me->flags & O_DESTRUCTED) return 0; if (you && (you->flags & O_DESTRUCTED)) return 0; /* Find the snooper & snopee */ for(i = 0 ; i < MAX_PLAYERS && (on == 0 || by == 0); i++) { if (all_players[i] == 0) continue; if (all_players[i]->ob == me) by = all_players[i]; else if (all_players[i]->ob == you) on = all_players[i]; } /* Check for permissions with valid_snoop in master */ if (current_object != master_ob) { push_object(current_object); push_object(me); if (you == 0) push_number(0); else push_object(you); ret = apply_master_ob(M_VALID_SNOOP, 3); if (!ret || ret->type != T_NUMBER || ret->u.number == 0) return 0; } /* Stop snoop */ if (you == 0) { if (by == 0) error("Could not find snooper to stop snoop on.\n"); if (by->snoop_on == 0) return 1; by->snoop_on->snoop_by = 0; by->snoop_on = 0; return 1; } /* Strange event, but possible, so test for it */ if (on == 0 || by == 0) return 0; /* Protect against snooping loops */ for (tmp = on; tmp; tmp = tmp->snoop_on) { if (tmp == by) return 0; } /* Terminate previous snoop, if any */ if (by->snoop_on) { by->snoop_on->snoop_by = 0; by->snoop_on = 0; } if (on->snoop_by) { on->snoop_by->snoop_on = 0; on->snoop_by = 0; } on->snoop_by = by; by->snoop_on = on; return 1; } #define TS_DATA 0 #define TS_IAC 1 #define TS_WILL 2 #define TS_WONT 3 #define TS_DO 4 #define TS_DONT 5 void telnet_neg(char *to, char *from) { int state = TS_DATA; int ch; char *first = to; while(1) { ch = (*from++ & 0xff); switch(state) { case TS_DATA: switch(ch) { case IAC: state = TS_IAC; continue; case '\b': /* Backspace */ case 0x7f: /* Delete */ if (to <= first) continue; to -= 1; continue; default: if (ch & 0x80) { if (d_flag & DEBUG_TELNET) debug_message("Tel_neg: 0x%x\n", ch); continue; } *to++ = ch; if (ch == 0) return; continue; } case TS_IAC: switch(ch) { case WILL: state = TS_WILL; continue; case WONT: state = TS_WONT; continue; case DO: state = TS_DO; continue; case DONT: state = TS_DONT; continue; case DM: break; case NOP: case GA: default: break; } state = TS_DATA; continue; case TS_WILL: if (d_flag & DEBUG_TELNET) debug_message("Will %s\n", telopts[ch]); state = TS_DATA; continue; case TS_WONT: if (d_flag & DEBUG_TELNET) debug_message("Wont %s\n", telopts[ch]); state = TS_DATA; continue; case TS_DO: if (d_flag & DEBUG_TELNET) debug_message("Do %s\n", telopts[ch]); state = TS_DATA; continue; case TS_DONT: if (d_flag & DEBUG_TELNET) debug_message("Dont %s\n", telopts[ch]); state = TS_DATA; continue; default: if (d_flag & DEBUG_TELNET) debug_message("Bad state: 0x%x\n", state); state = TS_DATA; continue; } } } #define IPSIZE 200 static struct ipentry { unsigned long addr; char *name; } iptable[IPSIZE]; static int ipcur; char * query_ip_name(struct object *ob) { int i; if (ob == 0) ob = command_giver; if (!ob || ob->interactive == 0) return 0; for(i = 0; i < IPSIZE; i++) { if (iptable[i].addr == (ob->interactive->addr.sin_addr.s_addr & 0xffffffff) && iptable[i].name) return iptable[i].name; } return inet_ntoa(ob->interactive->addr.sin_addr); } static void add_ip_entry(unsigned long addr, char *name) { int i; for(i = 0; i < IPSIZE; i++) { if (iptable[i].addr == 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; } char * query_ip_number(struct object *ob) { if (ob == 0) ob = command_giver; if (!ob || ob->interactive == 0) return 0; return inet_ntoa(ob->interactive->addr.sin_addr); } #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[20]; gethostname(name, sizeof name); name[sizeof name - 1] = '\0'; /* Just to make sure */ return name; } struct object * query_snoop(struct object *ob) { if (ob->interactive->snoop_by == 0) return 0; return ob->interactive->snoop_by->ob; } int query_idle(struct object *ob) { if (!ob->interactive) error("query_idle() of non-interactive object.\n"); return current_time - ob->interactive->last_time; } void notify_no_command() { char *p,*m; extern struct object *vbfc_object; if (!command_giver->interactive) return; p = command_giver->interactive->default_err_message; if (p) { /* We want 'value by function call' */ m = process_string(p, vbfc_object != 0); if (!shadow_catch_message(command_giver, m)) add_message(m); if (m != p) free(m); free_string(p); command_giver->interactive->default_err_message = 0; } else { add_message("What ?\n"); } } void clear_notify() { if (!command_giver || !command_giver->interactive) return; if (command_giver->interactive->default_err_message) { free_string(command_giver->interactive->default_err_message); command_giver->interactive->default_err_message = 0; } } void set_notify_fail_message(char *str) { if (!command_giver || !command_giver->interactive) return; clear_notify(); if (command_giver->interactive->default_err_message) free_string(command_giver->interactive->default_err_message); command_giver->interactive->default_err_message = make_shared_string(str); } int replace_interactive(struct object *ob, struct object *obfrom, /*IGN*/char *name) { struct svalue *v; /* * Check with master that exec allowed */ push_string(name, STRING_MALLOC); if (ob) push_object(ob); else push_number(0); push_object(obfrom); v = apply_master_ob(M_VALID_EXEC, 3); if (!v || v->type != T_NUMBER || v->u.number == 0) return 0; /* fprintf(stderr,"DEBUG: %s,%s\n",ob->name,obfrom->name); */ if (ob && ob->interactive) error("Bad argument1 to exec()\n"); if (!obfrom->interactive) error("Bad argument2 to exec()\n"); if (obfrom->interactive->message_length) { write_socket(MESSAGE_FLUSH, obfrom); } if (ob) { ob->interactive = obfrom->interactive; ob->interactive->ob = ob; ob->flags |= O_ONCE_INTERACTIVE; add_ref(ob, "exec"); free_object(obfrom, "exec"); obfrom->interactive = 0; obfrom->flags &= ~O_ONCE_INTERACTIVE; } else remove_interactive(obfrom, 0); if (obfrom == command_giver) command_giver = ob; return 1; } #ifdef DEBUG /* * This is used for debugging reference counts. */ void update_ref_counts_for_players() { int i; for (i = 0; i<MAX_PLAYERS; i++) { if (all_players[i] == 0) continue; all_players[i]->ob->extra_ref++; if (all_players[i]->input_to) all_players[i]->input_to->ob->extra_ref++; } } #endif /* DEBUG */ /* * Send messages to other muds. * * The message is sent with udp. If it gets there it gets there... */ int send_udp(char *to_host, int to_port, char *msg) { int ip1, ip2, ip3, ip4; struct sockaddr_in name; struct hostent *hp, *gethostbyname(); if (udp_send < 0) { /* Create socket on which to send. */ udp_send = socket(AF_INET, SOCK_DGRAM, 0); } if (udp_send < 0) return 0; if (sscanf(to_host, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) == 4) { name.sin_addr.s_addr = inet_addr(to_host); name.sin_family = AF_INET; } else { hp = gethostbyname(to_host); if (hp == 0) return 0; memcpy(&name.sin_addr, hp->h_addr, hp->h_length); name.sin_family = AF_INET; } name.sin_port = htons(to_port); /* Send message. */ if (sendto(udp_send, msg, strlen(msg), 0, (struct sockaddr *)&name, sizeof(name)) != strlen(msg)) return 0; return 1; }