/* $Header: /cvsroot/fbmuck/fbmuck/src/interface.c,v 1.90 2003/09/13 08:33:23 revar Exp $ */ /* Copyright 1992-2001 by Fuzzball Software */ /* Consider this code protected under the GNU public license, with explicit * permission to distribute when linked against openSSL. */ #include "copyright.h" #include "config.h" #include "match.h" #include "mpi.h" #include <sys/types.h> #ifndef WIN32 #include <sys/file.h> #include <sys/ioctl.h> #include <sys/wait.h> #endif #include <fcntl.h> #if defined (HAVE_ERRNO_H) # include <errno.h> #else #if defined (HAVE_SYS_ERRNO_H) # include <sys/errno.h> #else extern int errno; #endif #endif #include <ctype.h> #ifndef WIN32 # define NEED_SOCKLEN_T # include <sys/socket.h> # include <netinet/in.h> # include <netdb.h> # include <arpa/inet.h> #else typedef int socklen_t; #endif #ifdef AIX # include <sys/select.h> #endif #ifdef USE_IPV6 # ifdef HAVE_NETINET6_IN6_H # include <netinet6/in6.h> # elif defined(HAVE_LINUX_IN6_H) # include <linux/in6.h> # elif defined(HAVE_IN6_H) # include <in6.h> # endif #endif #ifdef USE_SSL # if defined (HAVE_OPENSSL_SSL_H) # include <openssl/ssl.h> # elif defined (HAVE_SSL_SSL_H) # include <ssl/ssl.h> # elif defined (HAVE_SSL_H) # include <ssl.h> # endif #endif #include "db.h" #include "interface.h" #include "params.h" #include "tune.h" #include "props.h" #include "mcp.h" #include "mufevent.h" #include "externs.h" #include "interp.h" #ifdef __APPLE__ typedef unsigned int socklen_t; #endif int shutdown_flag = 0; int restart_flag = 0; static const char *connect_fail = "Either that player does not exist, or has a different password.\r\n"; static const char *create_fail = "Either there is already a player with that name, or that name is illegal.\r\n"; static const char *flushed_message = "<Output Flushed>\r\n"; static const char *shutdown_message = "\r\nGoing down - Bye\r\n"; int resolver_sock[2]; struct text_block { int nchars; struct text_block *nxt; char *start; char *buf; }; struct text_queue { int lines; struct text_block *head; struct text_block **tail; }; struct descriptor_data { int descriptor; int connected; int con_number; int booted; #ifdef USE_SSL SSL *ssl_session; #endif dbref player; char *output_prefix; char *output_suffix; int output_size; struct text_queue output; struct text_queue input; char *raw_input; char *raw_input_at; int inIAC; long last_time; long connected_at; const char *hostname; const char *username; int quota; struct descriptor_data *next; struct descriptor_data **prev; McpFrame mcpframe; }; struct descriptor_data *descriptor_list = NULL; #define MAX_LISTEN_SOCKS 16 static int numsocks = 0; static int listener_port[MAX_LISTEN_SOCKS]; static int sock[MAX_LISTEN_SOCKS]; #ifdef USE_SSL static int ssl_numsocks = 0; static int ssl_listener_port[MAX_LISTEN_SOCKS]; static int ssl_sock[MAX_LISTEN_SOCKS]; SSL_CTX *ssl_ctx; #endif static int ndescriptors = 0; extern void fork_and_dump(void); extern int rwhocli_setup(const char *server, const char *serverpw, const char *myname, const char *comment); extern int rwhocli_shutdown(void); extern int rwhocli_pingalive(void); extern int rwhocli_userlogin(const char *uid, const char *name, time_t tim); extern int rwhocli_userlogout(const char *uid); void process_commands(void); void shovechars(); void shutdownsock(struct descriptor_data *d); struct descriptor_data *initializesock(int s, const char *hostname); void make_nonblocking(int s); void freeqs(struct descriptor_data *d); void welcome_user(struct descriptor_data *d); void check_connect(struct descriptor_data *d, const char *msg); void close_sockets(const char *msg); int boot_off(dbref player); void boot_player_off(dbref player); #ifdef USE_IPV6 const char *addrout(int, struct in6_addr *, unsigned short); #else const char *addrout(int, long, unsigned short); #endif /* USE_IPV6 */ void dump_users(struct descriptor_data *d, char *user); struct descriptor_data *new_connection(int port, int sock); void parse_connect(const char *msg, char *command, char *user, char *pass); void set_userstring(char **userstring, const char *command); int do_command(struct descriptor_data *d, char *command); int is_interface_command(const char* cmd); char *strsave(const char *s); int make_socket(int); int queue_string(struct descriptor_data *, const char *); int queue_write(struct descriptor_data *, const char *, int); int process_output(struct descriptor_data *d); int process_input(struct descriptor_data *d); void announce_connect(int, dbref); void announce_disconnect(struct descriptor_data *); char *time_format_1(long); char *time_format_2(long); void init_descriptor_lookup(); void init_descr_count_lookup(); void remember_descriptor(struct descriptor_data *); void remember_player_descr(dbref player, int); void update_desc_count_table(); int* get_player_descrs(dbref player, int* count); void forget_player_descr(dbref player, int); void forget_descriptor(struct descriptor_data *); struct descriptor_data* descrdata_by_descr(int i); struct descriptor_data* lookup_descriptor(int); int online(dbref player); int online_init(void); dbref online_next(int *ptr); long max_open_files(void); #ifdef MUD_ID void do_setuid(char *user); #endif /* MUD_ID */ #ifdef MUD_GID void do_setgid(char *group); #endif /* MUD_GID */ #ifdef SPAWN_HOST_RESOLVER void kill_resolver(void); #endif #ifdef USE_SSL ssize_t socket_read(struct descriptor_data *d, void *buf, size_t count); ssize_t socket_write(struct descriptor_data *d, const void *buf, size_t count); #elif WIN32 #define socket_write(d, buf, count) send(d->descriptor, buf, count,0) #define socket_read(d, buf, count) recv(d->descriptor, buf, count,0) #else #define socket_write(d, buf, count) write(d->descriptor, buf, count) #define socket_read(d, buf, count) read(d->descriptor, buf, count) #endif void spawn_resolver(void); void resolve_hostnames(void); #define MALLOC(result, type, number) do { \ if (!((result) = (type *) malloc ((number) * sizeof (type)))) \ panic("Out of memory"); \ } while (0) #define FREE(x) (free((void *) x)) #ifndef BOOLEXP_DEBUGGING extern FILE *input_file; extern FILE *delta_infile; extern FILE *delta_outfile; short db_conversion_flag = 0; short db_decompression_flag = 0; short wizonly_mode = 0; pid_t global_resolver_pid=0; #ifndef DISKBASE pid_t global_dumper_pid=0; #endif short global_dumpdone=0; time_t sel_prof_start_time; long sel_prof_idle_sec; long sel_prof_idle_usec; unsigned long sel_prof_idle_use; void show_program_usage(char *prog) { fprintf(stderr, "Usage: %s [<options>] [infile [outfile [portnum [portnum ...]]]]\n", prog); fprintf(stderr, " Arguments:\n"); fprintf(stderr, " infile db file loaded at startup. optional with -dbin.\n"); fprintf(stderr, " outfile output db file to save to. optional with -dbout.\n"); fprintf(stderr, " portnum port num to listen for conns on. (16 ports max)\n"); fprintf(stderr, " Options:\n"); fprintf(stderr, " -dbin INFILE use INFILE as the database to load at startup.\n"); fprintf(stderr, " -dbout OUTFILE use OUTFILE as the output database to save to.\n"); fprintf(stderr, " -port NUMBER sets the port number to listen for connections on.\n"); #ifdef USE_SSL fprintf(stderr, " -sport NUMBER sets the port number for secure connections\n"); #else fprintf(stderr, " -sport NUMBER Ignored. SSL support isn't compiled in.\n"); #endif fprintf(stderr, " -gamedir PATH changes directory to PATH before starting up.\n"); fprintf(stderr, " -convert load the db, then save and quit.\n"); fprintf(stderr, " -decompress when saving db, save in uncompressed format.\n"); fprintf(stderr, " -nosanity don't do db sanity checks at startup time.\n"); fprintf(stderr, " -insanity load db, then enter the interactive sanity editor.\n"); fprintf(stderr, " -sanfix attempt to auto-fix a corrupt db after loading.\n"); fprintf(stderr, " -wizonly only allow wizards to login.\n"); fprintf(stderr, " -godpasswd PASS reset God(#1)'s password to PASS. Implies -convert\n"); fprintf(stderr, " -version display this server's version.\n"); fprintf(stderr, " -help display this message.\n"); exit(1); } extern int sanity_violated; int main(int argc, char **argv) { FILE *ffd; char *infile_name; char *outfile_name; char *num_one_new_passwd = NULL; int i, nomore_options; int sanity_skip; int sanity_interactive; int sanity_autofix; int val; #ifdef WIN32 WORD wVersionRequested; WSADATA wsaData; int err; #endif listener_port[0] = TINYPORT; init_descriptor_lookup(); init_descr_count_lookup(); nomore_options = 0; sanity_skip = 0; sanity_interactive = 0; sanity_autofix = 0; infile_name = NULL; outfile_name = NULL; for (i = 1; i < argc; i++) { if (!nomore_options && argv[i][0] == '-') { if (!strcmp(argv[i], "-convert")) { db_conversion_flag = 1; } else if (!strcmp(argv[i], "-decompress")) { db_decompression_flag = 1; } else if (!strcmp(argv[i], "-nosanity")) { sanity_skip = 1; } else if (!strcmp(argv[i], "-insanity")) { sanity_interactive = 1; } else if (!strcmp(argv[i], "-wizonly")) { wizonly_mode = 1; } else if (!strcmp(argv[i], "-sanfix")) { sanity_autofix = 1; } else if (!strcmp(argv[i], "-version")) { printf("%s\n", VERSION); exit(0); } else if (!strcmp(argv[i], "-dbin")) { if (i + 1 >= argc) { show_program_usage(*argv); } infile_name = argv[++i]; } else if (!strcmp(argv[i], "-dbout")) { if (i + 1 >= argc) { show_program_usage(*argv); } outfile_name = argv[++i]; } else if (!strcmp(argv[i], "-godpasswd")) { if (i + 1 >= argc) { show_program_usage(*argv); } num_one_new_passwd = argv[++i]; if (!ok_password(num_one_new_passwd)) { fprintf(stderr, "Bad -godpasswd password.\n"); exit(1); } db_conversion_flag = 1; } else if (!strcmp(argv[i], "-port")) { if (i + 1 >= argc) { show_program_usage(*argv); } #ifdef USE_SSL if ( (ssl_numsocks + numsocks) < MAX_LISTEN_SOCKS) #else if (numsocks < MAX_LISTEN_SOCKS) #endif listener_port[numsocks++] = atoi(argv[++i]); #ifdef USE_SSL } else if (!strcmp(argv[i], "-sport")) { if (i + 1 >= argc) { show_program_usage(*argv); } if ( (ssl_numsocks + numsocks) < MAX_LISTEN_SOCKS) ssl_listener_port[ssl_numsocks++] = atoi(argv[++i]); #else } else if (!strcmp(argv[i], "-sport")) { if (i + 1 >= argc) { show_program_usage(*argv); } i++; fprintf(stderr, "SSL unsupported. Ignoring -sport.\n"); #endif } else if (!strcmp(argv[i], "-gamedir")) { if (i + 1 >= argc) { show_program_usage(*argv); } if (chdir(argv[++i])) { perror("cd to gamedir"); exit(4); } } else if (!strcmp(argv[i], "--")) { nomore_options = 1; } else { show_program_usage(*argv); } } else { if (!infile_name) { infile_name = argv[i]; } else if (!outfile_name) { outfile_name = argv[i]; } else { val = atoi(argv[i]); if (val < 1 || val > 65535) { show_program_usage(*argv); } #ifdef USE_SSL if ( (ssl_numsocks + numsocks) < MAX_LISTEN_SOCKS) #else if (numsocks < MAX_LISTEN_SOCKS) #endif listener_port[numsocks++] = val; } } } if (numsocks < 1) { numsocks = 1; } if (!infile_name || !outfile_name) { show_program_usage(*argv); } #ifdef DISKBASE if (!strcmp(infile_name, outfile_name)) { fprintf(stderr, "Output file must be different from the input file."); exit(3); } #endif if (!sanity_interactive) { log_status("INIT: TinyMUCK %s starting.\n", "version"); #ifdef DETACH /* Go into the background unless requested not to */ if (!sanity_interactive && !db_conversion_flag) { fclose(stdin); fclose(stdout); fclose(stderr); if (fork() != 0) _exit(0); } #endif /* save the PID for future use */ if ((ffd = fopen(PID_FILE, "w")) != NULL) { fprintf(ffd, "%d\n", getpid()); fclose(ffd); } #ifdef DETACH if (!sanity_interactive && !db_conversion_flag) { /* Detach from the TTY, log whatever output we have... */ freopen(LOG_ERR_FILE, "a", stderr); setbuf(stderr, NULL); freopen(LOG_FILE, "a", stdout); setbuf(stdout, NULL); /* Disassociate from Process Group */ # ifdef _POSIX_SOURCE setsid(); # else # ifdef SYSV setpgrp(); /* The SysV way */ # else setpgid(0, getpid()); /* The POSIX way. */ # endif /* SYSV */ # ifdef TIOCNOTTY /* we can force this, POSIX / BSD */ { int fd; if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, (char *) 0); /* lose controll TTY */ close(fd); } } # endif /* TIOCNOTTY */ # endif /* !_POSIX_SOURCE */ } #endif /* DETACH */ #ifdef SPAWN_HOST_RESOLVER if (!db_conversion_flag) { spawn_resolver(); } #endif } /* * You have to change gid first, since setgid() relies on root permissions * if you don't call initgroups() -- and since initgroups() isn't standard, * I'd rather assume the user knows what he's doing. */ #ifdef MUD_GID if (!sanity_interactive) { do_setgid(MUD_GID); } #endif /* MUD_GID */ #ifdef MUD_ID if (!sanity_interactive) { do_setuid(MUD_ID); } #endif /* MUD_ID */ /* Initialize MCP and some packages. */ mcp_initialize(); gui_initialize(); sel_prof_start_time = time(NULL); /* Set useful starting time */ sel_prof_idle_sec = 0; sel_prof_idle_usec = 0; sel_prof_idle_use = 0; if (init_game(infile_name, outfile_name) < 0) { fprintf(stderr, "Couldn't load %s!\n", infile_name); exit(2); } if (num_one_new_passwd != NULL) { set_password(GOD, num_one_new_passwd); } if (!sanity_interactive && !db_conversion_flag) { set_signals(); if (!sanity_skip) { sanity(AMBIGUOUS); if (sanity_violated) { wizonly_mode = 1; if (sanity_autofix) { sanfix(AMBIGUOUS); } } } #ifdef WIN32 wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { perror("Unable to start socket layer"); return 1; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 ) { perror("Winsock 2.0 or later is required to run this application."); WSACleanup( ); return 1; } set_console(); /* Setup the console to handle CTRL+C */ #endif /* go do it */ shovechars(); if (restart_flag) { close_sockets("\r\nServer restarting. Try logging back on in a few minutes.\r\n"); } else { close_sockets("\r\nServer shutting down normally.\r\n"); } do_dequeue(-1, (dbref) 1, "all"); #ifdef WIN32 WSACleanup(); #endif if (tp_rwho) { rwhocli_shutdown(); } } if (sanity_interactive) { san_main(); } else { dump_database(); tune_save_parmsfile(); #ifdef SPAWN_HOST_RESOLVER kill_resolver(); #endif #ifdef MALLOC_PROFILING db_free(); free_old_macros(); purge_all_free_frames(); purge_timenode_free_pool(); purge_for_pool(); purge_for_pool(); /* have to do this a second time to purge all */ purge_try_pool(); purge_try_pool(); /* have to do this a second time to purge all */ purge_mfns(); cleanup_game(); tune_freeparms(); free_compress_dictionary(); #endif #ifdef DISKBASE fclose(input_file); #endif #ifdef DELTADUMPS fclose(delta_infile); fclose(delta_outfile); (void) unlink(DELTAFILE_NAME); #endif #ifdef MALLOC_PROFILING CrT_summarize_to_file("malloc_log", "Shutdown"); #endif if (restart_flag) { #ifndef WIN32 char **argslist; char numbuf[32]; int argcnt = numsocks + 2; int argnum = 1; #ifdef USE_SSL argcnt += ssl_numsocks; #endif argslist = (char**)calloc(argcnt, sizeof(char*)); for (i = 0; i < numsocks; i++) { snprintf(numbuf, sizeof(numbuf), "%d", listener_port[i]); argslist[argnum] = (char*)malloc(strlen(numbuf)+1); strcpy(argslist[argnum++], numbuf); } #ifdef USE_SSL for (i = 0; i < ssl_numsocks; i++) { snprintf(numbuf, sizeof(numbuf), "-sport %d", ssl_listener_port[i]); argslist[argnum] = (char*)malloc(strlen(numbuf)+1); strcpy(argslist[argnum++], numbuf); } #endif if (!fork()) { argslist[0] = "./restart"; execv(argslist[0], argslist); argslist[0] = "restart"; execv(argslist[0], argslist); fprintf(stderr, "Could not find restart script!\n"); } #else /* WIN32 */ char* argbuf[1]; argbuf[0] = NULL; execv("restart", argbuf); #endif /* WIN32 */ } } exit(0); return 0; } #endif /* BOOLEXP_DEBUGGING */ int queue_ansi(struct descriptor_data *d, const char *msg) { char buf[BUFFER_LEN + 8]; if (d->connected) { if (FLAGS(d->player) & CHOWN_OK) { strip_bad_ansi(buf, msg); } else { strip_ansi(buf, msg); } } else { strip_ansi(buf, msg); } mcp_frame_output_inband(&d->mcpframe, buf); return strlen(buf); /* return queue_string(d, buf); */ } int notify_nolisten_level = 0; int notify_nolisten(dbref player, const char *msg, int isprivate) { int retval = 0; char buf[BUFFER_LEN + 2]; char buf2[BUFFER_LEN + 2]; int firstpass = 1; char *ptr1; const char *ptr2; dbref ref; int di; int* darr; int dcount; #ifdef COMPRESS extern const char *uncompress(const char *); msg = uncompress(msg); #endif /* COMPRESS */ #if defined(ANONYMITY) msg = unmangle(player, msg); #endif ptr2 = msg; while (ptr2 && *ptr2) { ptr1 = buf; while (ptr2 && *ptr2 && *ptr2 != '\r') *(ptr1++) = *(ptr2++); *(ptr1++) = '\r'; *(ptr1++) = '\n'; *(ptr1++) = '\0'; if (*ptr2 == '\r') ptr2++; darr = get_player_descrs(player, &dcount); for (di = 0; di < dcount; di++) { queue_ansi(descrdata_by_descr(darr[di]), buf); if (firstpass) retval++; } if (tp_zombies) { if ((Typeof(player) == TYPE_THING) && (FLAGS(player) & ZOMBIE) && !(FLAGS(OWNER(player)) & ZOMBIE) && (!(FLAGS(player) & DARK) || Wizard(OWNER(player)))) { ref = getloc(player); if (Wizard(OWNER(player)) || ref == NOTHING || Typeof(ref) != TYPE_ROOM || !(FLAGS(ref) & ZOMBIE)) { if (isprivate || getloc(player) != getloc(OWNER(player))) { char pbuf[BUFFER_LEN]; const char *prefix; char ch = *match_args; *match_args = '\0'; if (notify_nolisten_level <= 0) { notify_nolisten_level++; prefix = do_parse_prop(-1, player, player, MESGPROP_PECHO, "(@Pecho)", pbuf, MPI_ISPRIVATE); notify_nolisten_level--; } else prefix = 0; *match_args = ch; if (!prefix || !*prefix) { prefix = NAME(player); snprintf(buf2, sizeof(buf2), "%s> %.*s", prefix, (int)(BUFFER_LEN - (strlen(prefix) + 3)), buf); } else { snprintf(buf2, sizeof(buf2), "%s %.*s", prefix, (int)(BUFFER_LEN - (strlen(prefix) + 2)), buf); } darr = get_player_descrs(OWNER(player), &dcount); for (di = 0; di < dcount; di++) { queue_ansi(descrdata_by_descr(darr[di]), buf2); if (firstpass) retval++; } } } } } firstpass = 0; } return retval; } int notify_filtered(dbref from, dbref player, const char *msg, int isprivate) { if ((msg == 0) || ignore_is_ignoring(player, from)) return 0; return notify_nolisten(player, msg, isprivate); } int notify_from_echo(dbref from, dbref player, const char *msg, int isprivate) { const char *ptr; #ifdef COMPRESS extern const char *uncompress(const char *); ptr = uncompress(msg); #else ptr = msg; #endif /* COMPRESS */ #ifdef ANONYMITY ptr = unmangle(player, ptr); #endif if (tp_listeners) { if (tp_listeners_obj || Typeof(player) == TYPE_ROOM) { listenqueue(-1, from, getloc(from), player, player, NOTHING, "_listen", ptr, tp_listen_mlev, 1, 0); listenqueue(-1, from, getloc(from), player, player, NOTHING, "~listen", ptr, tp_listen_mlev, 1, 1); listenqueue(-1, from, getloc(from), player, player, NOTHING, "~olisten", ptr, tp_listen_mlev, 0, 1); } } if (Typeof(player) == TYPE_THING && (FLAGS(player) & VEHICLE) && (!(FLAGS(player) & DARK) || Wizard(OWNER(player))) ) { dbref ref; ref = getloc(player); if (Wizard(OWNER(player)) || ref == NOTHING || Typeof(ref) != TYPE_ROOM || !(FLAGS(ref) & VEHICLE) ) { if (!isprivate && getloc(from) == getloc(player)) { char buf[BUFFER_LEN]; char pbuf[BUFFER_LEN]; const char *prefix; char ch = *match_args; *match_args = '\0'; prefix = do_parse_prop(-1, from, player, MESGPROP_OECHO, "(@Oecho)", pbuf, MPI_ISPRIVATE); *match_args = ch; if (!prefix || !*prefix) prefix = "Outside>"; snprintf(buf, sizeof(buf), "%s %.*s", prefix, (int)(BUFFER_LEN - (strlen(prefix) + 2)), msg); ref = DBFETCH(player)->contents; while (ref != NOTHING) { notify_filtered(from, ref, buf, isprivate); ref = DBFETCH(ref)->next; } } } } return notify_filtered(from, player, msg, isprivate); } int notify_from(dbref from, dbref player, const char *msg) { return notify_from_echo(from, player, msg, 1); } int notify(dbref player, const char *msg) { return notify_from_echo(player, player, msg, 1); } struct timeval timeval_sub(struct timeval now, struct timeval then) { now.tv_sec -= then.tv_sec; now.tv_usec -= then.tv_usec; if (now.tv_usec < 0) { now.tv_usec += 1000000; now.tv_sec--; } return now; } int msec_diff(struct timeval now, struct timeval then) { return ((now.tv_sec - then.tv_sec) * 1000 + (now.tv_usec - then.tv_usec) / 1000); } struct timeval msec_add(struct timeval t, int x) { t.tv_sec += x / 1000; t.tv_usec += (x % 1000) * 1000; if (t.tv_usec >= 1000000) { t.tv_sec += t.tv_usec / 1000000; t.tv_usec = t.tv_usec % 1000000; } return t; } struct timeval update_quotas(struct timeval last, struct timeval current) { int nslices; int cmds_per_time; struct descriptor_data *d; nslices = msec_diff(current, last) / tp_command_time_msec; if (nslices > 0) { for (d = descriptor_list; d; d = d->next) { if (d->connected) { cmds_per_time = ((FLAGS(d->player) & INTERACTIVE) ? (tp_commands_per_time * 8) : tp_commands_per_time); } else { cmds_per_time = tp_commands_per_time; } d->quota += cmds_per_time * nslices; if (d->quota > tp_command_burst_size) d->quota = tp_command_burst_size; } } return msec_add(last, nslices * tp_command_time_msec); } /* * long max_open_files() * * This returns the max number of files you may have open * as a long, and if it can use setrlimit() to increase it, * it will do so. * * Becuse there is no way to just "know" if get/setrlimit is * around, since its defs are in <sys/resource.h>, you need to * define USE_RLIMIT in config.h to attempt it. * * Otherwise it trys to use sysconf() (POSIX.1) or getdtablesize() * to get what is avalible to you. */ #ifdef HAVE_RESOURCE_H # include <sys/resource.h> #endif #if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE) # define USE_RLIMIT #endif long max_open_files(void) { #if defined(_SC_OPEN_MAX) && !defined(USE_RLIMIT) /* Use POSIX.1 method, sysconf() */ /* * POSIX.1 code. */ return sysconf(_SC_OPEN_MAX); #else /* !POSIX */ # if defined(USE_RLIMIT) && (defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)) # ifndef RLIMIT_NOFILE # define RLIMIT_NOFILE RLIMIT_OFILE /* We Be BSD! */ # endif /* !RLIMIT_NOFILE */ /* * get/setrlimit() code. */ struct rlimit file_limit; getrlimit(RLIMIT_NOFILE, &file_limit); /* Whats the limit? */ if (file_limit.rlim_cur < file_limit.rlim_max) { /* if not at max... */ file_limit.rlim_cur = file_limit.rlim_max; /* ...set to max. */ setrlimit(RLIMIT_NOFILE, &file_limit); getrlimit(RLIMIT_NOFILE, &file_limit); /* See what we got. */ } return (long) file_limit.rlim_cur; # elif WIN32 /* !RLIMIT && WIN32 */ return FD_SETSIZE; # else /* !RLIMIT && !WIN32 */ /* * Don't know what else to do, try getdtablesize(). * email other bright ideas to me. :) (whitefire) */ return (long) getdtablesize(); # endif /* !RLIMIT */ #endif /* !POSIX */ } int queue_immediate(struct descriptor_data *d, const char *msg) { char buf[BUFFER_LEN + 8]; int quote_len = 0; if (d->connected) { if (FLAGS(d->player) & CHOWN_OK) { strip_bad_ansi(buf, msg); } else { strip_ansi(buf, msg); } } else { strip_ansi(buf, msg); } if (d->mcpframe.enabled && !(strncmp(buf, MCP_MESG_PREFIX, 3) && strncmp(buf, MCP_QUOTE_PREFIX, 3))) { quote_len = strlen(MCP_QUOTE_PREFIX); socket_write(d, MCP_QUOTE_PREFIX, quote_len); } return socket_write(d, buf, strlen(buf)) + quote_len; } void goodbye_user(struct descriptor_data *d) { queue_immediate(d, "\r\n"); queue_immediate(d, tp_leave_mesg); queue_immediate(d, "\r\n\r\n"); } void idleboot_user(struct descriptor_data *d) { queue_immediate(d, "\r\n"); queue_immediate(d, tp_idle_mesg); queue_immediate(d, "\r\n\r\n"); d->booted = 1; } #ifdef USE_SSL int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) { const char *pw = (const char*)userdata; int pwlen = strlen(pw); strncpy(buf, pw, size); return ((pwlen > size)? size : pwlen); } #endif static int con_players_max = 0; /* one of Cynbe's good ideas. */ static int con_players_curr = 0; /* for playermax checks. */ extern void purge_free_frames(void); void shovechars() { fd_set input_set, output_set; time_t now; long tmptq; struct timeval last_slice, current_time; struct timeval next_slice; struct timeval timeout, slice_timeout; int maxd, cnt; struct descriptor_data *d, *dnext; struct descriptor_data *newd; struct timeval sel_in, sel_out; int avail_descriptors; int i; #ifdef USE_SSL int ssl_status_ok = 1; #endif for (i = 0; i < numsocks; i++) { sock[i] = make_socket(listener_port[i]); maxd = sock[i] + 1; } #ifdef USE_SSL SSL_load_error_strings (); OpenSSL_add_ssl_algorithms (); ssl_ctx = SSL_CTX_new (SSLv23_server_method ()); if (!SSL_CTX_use_certificate_file (ssl_ctx, SSL_CERT_FILE, SSL_FILETYPE_PEM)) { log_status("Could not load certificate file %s\n", SSL_CERT_FILE); ssl_status_ok = 0; } if (ssl_status_ok) { SSL_CTX_set_default_passwd_cb(ssl_ctx, pem_passwd_cb); SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void*)tp_ssl_keyfile_passwd); if (!SSL_CTX_use_PrivateKey_file (ssl_ctx, SSL_KEY_FILE, SSL_FILETYPE_PEM)) { log_status("Could not load private key file %s\n", SSL_KEY_FILE); ssl_status_ok = 0; } } if (ssl_status_ok) { if (!SSL_CTX_check_private_key (ssl_ctx)) { log_status("Private key does not check out and appears to be invalid.\n"); ssl_status_ok = 0; } } if (ssl_status_ok) { for (i = 0; i < ssl_numsocks; i++) { ssl_sock[i] = make_socket(ssl_listener_port[i]); maxd = ssl_sock[i] + 1; } } else { ssl_numsocks = 0; } #endif gettimeofday(&last_slice, (struct timezone *) 0); avail_descriptors = max_open_files() - 5; while (shutdown_flag == 0) { gettimeofday(¤t_time, (struct timezone *) 0); last_slice = update_quotas(last_slice, current_time); next_muckevent(); process_commands(); muf_event_process(); #ifdef WIN32 check_console(); /* Handle possible CTRL+C */ #endif for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (d->booted) { process_output(d); if (d->booted == 2) { goodbye_user(d); } d->booted = 0; process_output(d); shutdownsock(d); } } if (global_dumpdone != 0) { if (tp_dumpdone_warning) { wall_and_flush(tp_dumpdone_mesg); } global_dumpdone = 0; } purge_free_frames(); untouchprops_incremental(1); if (shutdown_flag) break; timeout.tv_sec = 10; timeout.tv_usec = 0; next_slice = msec_add(last_slice, tp_command_time_msec); slice_timeout = timeval_sub(next_slice, current_time); FD_ZERO(&input_set); FD_ZERO(&output_set); if (ndescriptors < avail_descriptors) { for (i = 0; i < numsocks; i++) { FD_SET(sock[i], &input_set); } #ifdef USE_SSL for (i = 0; i < ssl_numsocks; i++) { FD_SET(ssl_sock[i], &input_set); } #endif } for (d = descriptor_list; d; d = d->next) { if (d->input.lines > 100) timeout = slice_timeout; else FD_SET(d->descriptor, &input_set); if (d->output.head) FD_SET(d->descriptor, &output_set); #ifdef USE_SSL if (d->ssl_session) { /* SSL may want to write even if the output queue is empty */ if ( ! SSL_is_init_finished(d->ssl_session) ) { /* log_status("SSL : Init not finished.\n", "version"); */ FD_CLR(d->descriptor, &output_set); FD_SET(d->descriptor, &input_set); } if ( SSL_want_write(d->ssl_session) ) { /* log_status("SSL : Need write.\n", "version"); */ FD_SET(d->descriptor, &output_set); } } #endif } #ifdef SPAWN_HOST_RESOLVER FD_SET(resolver_sock[1], &input_set); #endif tmptq = next_muckevent_time(); if ((tmptq >= 0L) && (timeout.tv_sec > tmptq)) { timeout.tv_sec = tmptq + (tp_pause_min / 1000); timeout.tv_usec = (tp_pause_min % 1000) * 1000L; } gettimeofday(&sel_in,NULL); #ifndef WIN32 if (select(maxd, &input_set, &output_set, (fd_set *) 0, &timeout) < 0) { if (errno != EINTR) { perror("select"); return; } #else if (select(maxd, &input_set, &output_set, (fd_set *) 0, &timeout) == SOCKET_ERROR) { if (WSAGetLastError() != WSAEINTR) { perror("select"); return; } #endif } else { gettimeofday(&sel_out,NULL); if (sel_out.tv_usec < sel_in.tv_usec) { sel_out.tv_usec += 1000000; sel_out.tv_sec -= 1; } sel_out.tv_usec -= sel_in.tv_usec; sel_out.tv_sec -= sel_in.tv_sec; sel_prof_idle_sec += sel_out.tv_sec; sel_prof_idle_usec += sel_out.tv_usec; if (sel_prof_idle_usec >= 1000000) { sel_prof_idle_usec -= 1000000; sel_prof_idle_sec += 1; } sel_prof_idle_use++; (void) time(&now); for (i = 0; i < numsocks; i++) { if (FD_ISSET(sock[i], &input_set)) { if (!(newd = new_connection(listener_port[i], sock[i]))) { #ifndef WIN32 if (errno && errno != EINTR && errno != EMFILE && errno != ENFILE /* * && errno != ETIMEDOUT * && errno != ECONNRESET * && errno != ENOTCONN * && errno != EPIPE * && errno != ECONNREFUSED *#ifdef EPROTO * && errno != EPROTO *#endif */ ) { perror("new_connection"); /* return; */ } #else /* WIN32 */ if (WSAGetLastError() != WSAEINTR && WSAGetLastError() != EMFILE) { perror("new_connection"); /* return; */ } #endif /* WIN32 */ } else { if (newd->descriptor >= maxd) maxd = newd->descriptor + 1; } } } #ifdef USE_SSL for (i = 0; i < ssl_numsocks; i++) { if (FD_ISSET(ssl_sock[i], &input_set)) { if (!(newd = new_connection(ssl_listener_port[i], ssl_sock[i]))) { #ifndef WIN32 if (errno && errno != EINTR && errno != EMFILE && errno != ENFILE ) { perror("new_connection"); /* return; */ } #else if (WSAGetLastError() != WSAEINTR && WSAGetLastError() != EMFILE) { perror("new_connection"); /* return; */ } #endif } else { if (newd->descriptor >= maxd) maxd = newd->descriptor + 1; newd->ssl_session = SSL_new (ssl_ctx); SSL_set_fd(newd->ssl_session, newd->descriptor); cnt = SSL_accept(newd->ssl_session); /* log_status("SSL accept1: %i\n", cnt ); */ } } } #endif #ifdef SPAWN_HOST_RESOLVER if (FD_ISSET(resolver_sock[1], &input_set)) { resolve_hostnames(); } #endif for (cnt = 0, d = descriptor_list; d; d = dnext) { dnext = d->next; if (FD_ISSET(d->descriptor, &input_set)) { if (!process_input(d)) { d->booted = 1; } } if (FD_ISSET(d->descriptor, &output_set)) { if (!process_output(d)) { d->booted = 1; } } if (d->connected) { cnt++; if (tp_idleboot && ((now - d->last_time) > tp_maxidle) && !Wizard(d->player)) { idleboot_user(d); } } else { if ((now - d->connected_at) > 300) { d->booted = 1; } } } if (cnt > con_players_max) { add_property((dbref) 0, "_sys/max_connects", NULL, cnt); con_players_max = cnt; } con_players_curr = cnt; } } (void) time(&now); add_property((dbref) 0, "_sys/lastdumptime", NULL, (int) now); add_property((dbref) 0, "_sys/shutdowntime", NULL, (int) now); } void wall_and_flush(const char *msg) { struct descriptor_data *d, *dnext; char buf[BUFFER_LEN + 2]; #ifdef COMPRESS extern const char *uncompress(const char *); msg = uncompress(msg); #endif /* COMPRESS */ if (!msg || !*msg) return; strcpy(buf, msg); strcatn(buf, sizeof(buf), "\r\n"); for (d = descriptor_list; d; d = dnext) { dnext = d->next; queue_ansi(d, buf); /* queue_write(d, "\r\n", 2); */ if (!process_output(d)) { d->booted = 1; } } } void flush_user_output(dbref player) { int di; int* darr; int dcount; struct descriptor_data *d; darr = get_player_descrs(OWNER(player), &dcount); for (di = 0; di < dcount; di++) { d = descrdata_by_descr(darr[di]); if (d && !process_output(d)) { d->booted = 1; } } } void wall_wizards(const char *msg) { struct descriptor_data *d, *dnext; char buf[BUFFER_LEN + 2]; #ifdef COMPRESS extern const char *uncompress(const char *); msg = uncompress(msg); #endif /* COMPRESS */ strcpy(buf, msg); strcatn(buf, sizeof(buf), "\r\n"); for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (d->connected && Wizard(d->player)) { queue_ansi(d, buf); if (!process_output(d)) { d->booted = 1; } } } } struct descriptor_data * new_connection(int port, int sock) { int newsock; #ifdef USE_IPV6 struct sockaddr_in6 addr; #else struct sockaddr_in addr; #endif socklen_t addr_len; char hostname[128]; addr_len = (socklen_t)sizeof(addr); newsock = accept(sock, (struct sockaddr *) &addr, &addr_len); if (newsock < 0) { return 0; } else { #ifdef F_SETFD fcntl(newsock, F_SETFD, 1); #endif #ifdef USE_IPV6 strcpy(hostname, addrout(port, &(addr.sin6_addr), addr.sin6_port)); log_status("ACCEPT: %s(%d) on descriptor %d\n", hostname, ntohs(addr.sin6_port), newsock); #else strcpy(hostname, addrout(port, addr.sin_addr.s_addr, addr.sin_port)); log_status("ACCEPT: %s(%d) on descriptor %d\n", hostname, ntohs(addr.sin_port), newsock); #endif log_status("CONCOUNT: There are now %d open connections.\n", ++ndescriptors); return initializesock(newsock, hostname); } } #ifdef SPAWN_HOST_RESOLVER void kill_resolver(void) { int i; pid_t p; write(resolver_sock[1], "QUIT\n", 5); p = wait(&i); } static time_t resolver_spawn_time = 0; void spawn_resolver() { if (time(NULL) - resolver_spawn_time < 5) { return; } resolver_spawn_time = time(NULL); socketpair(AF_UNIX, SOCK_STREAM, 0, resolver_sock); make_nonblocking(resolver_sock[1]); if ((global_resolver_pid=fork())==0) { close(0); close(1); dup(resolver_sock[0]); dup(resolver_sock[0]); #ifdef BINDIR { char resolverpath[BUFFER_LEN]; snprintf(resolverpath, sizeof(resolverpath), "%s/fb-resolver", BINDIR); execl(resolverpath, "fb-resolver", NULL); snprintf(resolverpath, sizeof(resolverpath), "%s/resolver", BINDIR); execl(resolverpath, "resolver", NULL); } #endif execl("/usr/local/bin/fb-resolver", "resolver", NULL); execl("/usr/local/bin/resolver", "resolver", NULL); execl("/usr/bin/fb-resolver", "resolver", NULL); execl("/usr/bin/resolver", "resolver", NULL); execl("./fb-resolver", "resolver", NULL); execl("./resolver", "resolver", NULL); #if 0 execl("@bindir@/resolver", "resolver", NULL); execl("./bin/resolver", "resolver", NULL); execl("/usr/lib/fbmuck/resolver", "resolver", NULL); execl("/usr/local/fbmuck/bin/resolver", "resolver", NULL); execl("/usr/local/bin/resolver", "resolver", NULL); execl("../src/resolver", "resolver", NULL); execl("./resolver", "resolver", NULL); execl("resolver", "resolver", NULL); #endif log_status("%s","Unable to spawn host resolver!"); _exit(1); } } void resolve_hostnames() { char buf[BUFFER_LEN]; char *ptr, *ptr2, *ptr3, *hostip, *port, *hostname, *username, *tempptr; struct descriptor_data *d; int got, dc; got = read(resolver_sock[1], buf, sizeof(buf)); if (got < 0) return; if (got == sizeof(buf)) { got--; while (got > 0 && buf[got] != '\n') buf[got--] = '\0'; } ptr = buf; dc = 0; do { for (ptr2 = ptr; *ptr && *ptr != '\n' && dc < got; ptr++, dc++) ; if (*ptr) { *ptr++ = '\0'; dc++; } if (*ptr2) { #ifdef USE_IPV6 ptr3 = index(ptr2, '|'); #else ptr3 = index(ptr2, ':'); #endif if (!ptr3) return; hostip = ptr2; port = index(ptr2, '('); if (!port) return; tempptr = index(port, ')'); if (!tempptr) return; *tempptr = '\0'; hostname = ptr3; username = index(ptr3, '('); if (!username) return; tempptr = index(username, ')'); if (!tempptr) return; *tempptr = '\0'; if (*port && *hostname && *username) { *port++ = '\0'; *hostname++ = '\0'; *username++ = '\0'; for (d = descriptor_list; d; d = d->next) { if (!strcmp(d->hostname, hostip) && !strcmp(d->username, port)) { FREE(d->hostname); FREE(d->username); d->hostname = strsave(hostname); d->username = strsave(username); } } } } } while (dc < got && *ptr); } #endif /* addrout -- Translate address 'a' from int to text. */ const char * #ifdef USE_IPV6 addrout(int lport, struct in6_addr *a, unsigned short prt) #else addrout(int lport, long a, unsigned short prt) #endif { static char buf[128]; #ifdef USE_IPV6 char ip6addr[128]; struct in6_addr addr; memcpy(&addr.s6_addr, a, sizeof(struct in6_addr)); #else struct in_addr addr; addr.s_addr = a; #endif prt = ntohs(prt); #ifndef SPAWN_HOST_RESOLVER if (tp_hostnames) { /* One day the nameserver Qwest uses decided to start */ /* doing halfminute lags, locking up the entire muck */ /* that long on every connect. This is intended to */ /* prevent that, reduces average lag due to nameserver */ /* to 1 sec/call, simply by not calling nameserver if */ /* it's in a slow mood *grin*. If the nameserver lags */ /* consistently, a hostname cache ala OJ's tinymuck2.3 */ /* would make more sense: */ static secs_lost = 0; if (secs_lost) { secs_lost--; } else { time_t gethost_start = time(NULL); #ifdef USE_IPV6 struct hostent *he = gethostbyaddr(((char *) &addr), sizeof(addr), AF_INET6); #else struct hostent *he = gethostbyaddr(((char *) &addr), sizeof(addr), AF_INET); #endif time_t gethost_stop = time(NULL); time_t lag = gethost_stop - gethost_start; if (lag > 10) { secs_lost = lag; #if MIN_SECS_TO_LOG if (lag >= CFG_MIN_SECS_TO_LOG) { log_status("GETHOSTBYNAME-RAN: secs %3d\n", lag); } #endif } if (he) { snprintf(buf, sizeof(buf), "%s(%u)", he->h_name, prt); return buf; } } } #endif /* SPAWN_HOST_RESOLVER */ #ifdef USE_IPV6 inet_ntop(AF_INET6, a, ip6addr, 128); #ifdef SPAWN_HOST_RESOLVER snprintf(buf, sizeof(buf), "%s(%u)%u\n", ip6addr, prt, lport); if (tp_hostnames) { write(resolver_sock[1], buf, strlen(buf)); } #endif snprintf(buf, sizeof(buf), "%s(%u)\n", ip6addr, prt); #else a = ntohl(a); #ifdef SPAWN_HOST_RESOLVER snprintf(buf, sizeof(buf), "%ld.%ld.%ld.%ld(%u)%u\n", (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, prt, lport); if (tp_hostnames) { write(resolver_sock[1], buf, strlen(buf)); } #endif snprintf(buf, sizeof(buf), "%ld.%ld.%ld.%ld(%u)", (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, prt); #endif return buf; } void clearstrings(struct descriptor_data *d) { if (d->output_prefix) { FREE(d->output_prefix); d->output_prefix = 0; } if (d->output_suffix) { FREE(d->output_suffix); d->output_suffix = 0; } } void shutdownsock(struct descriptor_data *d) { if (d->connected) { log_status("DISCONNECT: descriptor %d player %s(%d) from %s(%s)\n", d->descriptor, NAME(d->player), d->player, d->hostname, d->username); announce_disconnect(d); } else { log_status("DISCONNECT: descriptor %d from %s(%s) never connected.\n", d->descriptor, d->hostname, d->username); } clearstrings(d); shutdown(d->descriptor, 2); close(d->descriptor); forget_descriptor(d); freeqs(d); *d->prev = d->next; if (d->next) d->next->prev = d->prev; if (d->hostname) free((void *) d->hostname); if (d->username) free((void *) d->username); mcp_frame_clear(&d->mcpframe); FREE(d); ndescriptors--; log_status("CONCOUNT: There are now %d open connections.\n", ndescriptors); } void SendText(McpFrame * mfr, const char *text) { queue_string((struct descriptor_data *) mfr->descriptor, text); } void FlushText(McpFrame * mfr) { struct descriptor_data *d = (struct descriptor_data *)mfr->descriptor; if (d && !process_output(d)) { d->booted = 1; } } int mcpframe_to_descr(McpFrame * ptr) { return ((struct descriptor_data *) ptr->descriptor)->descriptor; } int mcpframe_to_user(McpFrame * ptr) { return ((struct descriptor_data *) ptr->descriptor)->player; } struct descriptor_data * initializesock(int s, const char *hostname) { struct descriptor_data *d; char buf[128], *ptr; MALLOC(d, struct descriptor_data, 1); d->descriptor = s; #ifdef USE_SSL d->ssl_session = NULL; #endif d->connected = 0; d->booted = 0; d->player = -1; d->con_number = 0; d->connected_at = time(NULL); make_nonblocking(s); d->output_prefix = 0; d->output_suffix = 0; d->output_size = 0; d->output.lines = 0; d->output.head = 0; d->output.tail = &d->output.head; d->input.lines = 0; d->input.head = 0; d->input.tail = &d->input.head; d->raw_input = 0; d->raw_input_at = 0; d->inIAC = 0; d->quota = tp_command_burst_size; d->last_time = d->connected_at; mcp_frame_init(&d->mcpframe, d); strcpy(buf, hostname); ptr = index(buf, ')'); if (ptr) *ptr = '\0'; ptr = index(buf, '('); *ptr++ = '\0'; d->hostname = alloc_string(buf); d->username = alloc_string(ptr); if (descriptor_list) descriptor_list->prev = &d->next; d->next = descriptor_list; d->prev = &descriptor_list; descriptor_list = d; remember_descriptor(d); mcp_negotiation_start(&d->mcpframe); welcome_user(d); return d; } int make_socket(int port) { int s; int opt; #ifdef USE_IPV6 struct sockaddr_in6 server; s = socket(AF_INET6, SOCK_STREAM, 0); #else struct sockaddr_in server; s = socket(AF_INET, SOCK_STREAM, 0); #endif if (s < 0) { perror("creating stream socket"); exit(3); } opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) { perror("setsockopt"); exit(1); } #ifdef USE_IPV6 server.sin6_family = AF_INET6; memset(server.sin6_addr.s6_addr, 0, 16); server.sin6_port = htons(port); #else server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(port); #endif if (bind(s, (struct sockaddr *) &server, sizeof(server))) { perror("binding stream socket"); close(s); exit(4); } listen(s, 5); return s; } struct text_block * make_text_block(const char *s, int n) { struct text_block *p; MALLOC(p, struct text_block, 1); MALLOC(p->buf, char, n); bcopy(s, p->buf, n); p->nchars = n; p->start = p->buf; p->nxt = 0; return p; } void free_text_block(struct text_block *t) { FREE(t->buf); FREE((char *) t); } void add_to_queue(struct text_queue *q, const char *b, int n) { struct text_block *p; if (n == 0) return; p = make_text_block(b, n); p->nxt = 0; *q->tail = p; q->tail = &p->nxt; q->lines++; } int flush_queue(struct text_queue *q, int n) { struct text_block *p; int really_flushed = 0; n += strlen(flushed_message); while (n > 0 && (p = q->head)) { n -= p->nchars; really_flushed += p->nchars; q->head = p->nxt; q->lines--; free_text_block(p); } p = make_text_block(flushed_message, strlen(flushed_message)); p->nxt = q->head; q->head = p; q->lines++; if (!p->nxt) q->tail = &p->nxt; really_flushed -= p->nchars; return really_flushed; } int queue_write(struct descriptor_data *d, const char *b, int n) { int space; space = tp_max_output - d->output_size - n; if (space < 0) d->output_size -= flush_queue(&d->output, -space); add_to_queue(&d->output, b, n); d->output_size += n; return n; } int queue_string(struct descriptor_data *d, const char *s) { return queue_write(d, s, strlen(s)); } int process_output(struct descriptor_data *d) { struct text_block **qp, *cur; int cnt; /* drastic, but this may give us crash test data */ if (!d || !d->descriptor) { fprintf(stderr, "process_output: bad descriptor or connect struct!\n"); abort(); } if (d->output.lines == 0) { return 1; } for (qp = &d->output.head; (cur = *qp);) { cnt = socket_write(d, cur->start, cur->nchars); #ifndef WIN32 if (cnt < 0) { if (errno == EWOULDBLOCK) return 1; return 0; } #else if (cnt < 0 || cnt == SOCKET_ERROR) { if (WSAGetLastError() == WSAEWOULDBLOCK) return 1; return 0; } #endif d->output_size -= cnt; if (cnt == cur->nchars) { d->output.lines--; if (!cur->nxt) { d->output.tail = qp; d->output.lines = 0; } *qp = cur->nxt; free_text_block(cur); continue; /* do not adv ptr */ } cur->nchars -= cnt; cur->start += cnt; break; } return 1; } void make_nonblocking(int s) { #ifndef WIN32 # if !defined(O_NONBLOCK) || defined(ULTRIX) /* POSIX ME HARDER */ # ifdef FNDELAY /* SUN OS */ # define O_NONBLOCK FNDELAY # else # ifdef O_NDELAY /* SyseVil */ # define O_NONBLOCK O_NDELAY # endif /* O_NDELAY */ # endif /* FNDELAY */ # endif if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { perror("make_nonblocking: fcntl"); panic("O_NONBLOCK fcntl failed"); } #else /* WIN32 */ unsigned long O_NONBLOCK = 1; if (ioctlsocket(s, FIONBIO, &O_NONBLOCK) == SOCKET_ERROR) { perror("make_nonblocking: ioctlsocket"); panic("O_NONBLOCK ioctlsocket failed"); } #endif /* WIN32 */ } void freeqs(struct descriptor_data *d) { struct text_block *cur, *next; cur = d->output.head; while (cur) { next = cur->nxt; free_text_block(cur); cur = next; } d->output.lines = 0; d->output.head = 0; d->output.tail = &d->output.head; cur = d->input.head; while (cur) { next = cur->nxt; free_text_block(cur); cur = next; } d->input.lines = 0; d->input.head = 0; d->input.tail = &d->input.head; if (d->raw_input) FREE(d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } char * strsave(const char *s) { char *p; MALLOC(p, char, strlen(s) + 1); if (p) strcpy(p, s); return p; } void save_command(struct descriptor_data *d, const char *command) { add_to_queue(&d->input, command, strlen(command) + 1); } int process_input(struct descriptor_data *d) { char buf[MAX_COMMAND_LEN * 2]; int got; char *p, *pend, *q, *qend; got = socket_read(d, buf, sizeof buf); #ifndef WIN32 # ifdef USE_SSL if ( (got <= 0) && errno != EWOULDBLOCK ) # else if (got <= 0) # endif #else # ifdef USE_SSL if ( (got <= 0 || got == SOCKET_ERROR) && WSAGetLastError() != EWOULDBLOCK ) # else if (got <= 0 || got == SOCKET_ERROR) # endif #endif { /* perror("socket_read"); */ return 0; } if (!d->raw_input) { MALLOC(d->raw_input, char, MAX_COMMAND_LEN); d->raw_input_at = d->raw_input; } p = d->raw_input_at; pend = d->raw_input + MAX_COMMAND_LEN - 1; for (q = buf, qend = buf + got; q < qend; q++) { if (*q == '\n') { d->last_time = time(NULL); *p = '\0'; if (p >= d->raw_input) save_command(d, d->raw_input); p = d->raw_input; } else if (d->inIAC == 1) { switch (*q) { case '\361': /* NOP */ d->inIAC = 0; break; case '\363': /* Break */ case '\364': /* Interrupt Process */ save_command(d, BREAK_COMMAND); d->inIAC = 0; break; case '\365': /* Abort Output */ /* could be handy, but for now leave unimplemented */ d->inIAC = 0; break; case '\366': /* AYT */ { char sendbuf[] = "[Yes]\r\n"; socket_write(d, sendbuf, strlen(sendbuf)); d->inIAC = 0; break; } case '\367': /* Erase character */ if (p > d->raw_input) p--; d->inIAC = 0; break; case '\370': /* Erase line */ p = d->raw_input; d->inIAC = 0; break; case '\372': /* Go Ahead */ /* treat as a NOP (?) */ d->inIAC = 0; break; case '\373': /* WILL (option offer) */ d->inIAC = 2; break; case '\374': /* WONT (option offer) */ d->inIAC = 4; break; case '\375': /* DO (option request) */ case '\376': /* DONT (option request) */ d->inIAC = 3; break; case '\377': /* IAC a second time */ #if 0 /* If we were 8 bit clean, we'd pass this along */ *p++ = *q; #endif d->inIAC = 0; break; default: /* just ignore */ d->inIAC = 0; break; } } else if (d->inIAC == 2) { /* We don't negotiate: send back DONT option */ char sendbuf[4]; sendbuf[0] = '\377'; sendbuf[1] = '\376'; sendbuf[2] = *q; sendbuf[3] = '\0'; socket_write(d, sendbuf, 3); d->inIAC = 0; } else if (d->inIAC == 3) { /* We don't negotiate: send back WONT option */ char sendbuf[4]; sendbuf[0] = '\377'; sendbuf[1] = '\374'; sendbuf[2] = *q; sendbuf[3] = '\0'; socket_write(d, sendbuf, 3); d->inIAC = 0; } else if (d->inIAC == 4) { /* Ignore WONT option. */ d->inIAC = 0; } else if (*q == '\377') { /* Got TELNET IAC, store for next byte */ d->inIAC = 1; } else if (p < pend && isascii(*q)) { if (isprint(*q)) { *p++ = *q; } else if (*q == '\t') { *p++ = ' '; } else if (*q == 8 || *q == 127) { /* if BS or DEL, delete last character */ if (p > d->raw_input) p--; } d->inIAC = 0; } } if (p > d->raw_input) { d->raw_input_at = p; } else { FREE(d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } return 1; } void set_userstring(char **userstring, const char *command) { if (*userstring) { FREE(*userstring); *userstring = 0; } while (*command && isascii(*command) && isspace(*command)) command++; if (*command) *userstring = strsave(command); } void process_commands(void) { int nprocessed; struct descriptor_data *d, *dnext; struct text_block *t; do { nprocessed = 0; for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (d->quota > 0 && (t = d->input.head)) { if (d->connected && PLAYER_BLOCK(d->player) && !is_interface_command(t->start)) { char *tmp = t->start; if (!strncmp(tmp, "#$\"", 3)) { /* Un-escape MCP escaped lines */ tmp += 3; } /* WORK: send player's foreground/preempt programs an exclusive READ mufevent */ if (!read_event_notify(d->descriptor, d->player, tmp) && !*tmp) { /* Didn't send blank line. Eat it. */ nprocessed++; d->input.head = t->nxt; d->input.lines--; if (!d->input.head) { d->input.tail = &d->input.head; d->input.lines = 0; } free_text_block(t); } } else { if (strncmp(t->start, "#$#", 3)) { /* Not an MCP mesg, so count this against quota. */ d->quota--; } nprocessed++; if (!do_command(d, t->start)) { d->booted = 2; /* Disconnect player next pass through main event loop. */ } d->input.head = t->nxt; d->input.lines--; if (!d->input.head) { d->input.tail = &d->input.head; d->input.lines = 0; } free_text_block(t); } } } } while (nprocessed > 0); } int is_interface_command(const char* cmd) { const char* tmp = cmd; if (!strncmp(tmp, "#$\"", 3)) { /* dequote MCP quoting. */ tmp += 3; } if (!strncmp(cmd, "#$#", 3)) /* MCP mesg. */ return 1; if (!string_compare(tmp, BREAK_COMMAND)) return 1; if (!strcmp(tmp, QUIT_COMMAND)) return 1; if (!strncmp(tmp, WHO_COMMAND, strlen(WHO_COMMAND))) return 1; if (!strncmp(tmp, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) return 1; if (!strncmp(tmp, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) return 1; return 0; } int do_command(struct descriptor_data *d, char *command) { char buf[BUFFER_LEN]; char cmdbuf[BUFFER_LEN]; if (!mcp_frame_process_input(&d->mcpframe, command, cmdbuf, sizeof(cmdbuf))) { d->quota++; return 1; } command = cmdbuf; if (d->connected) ts_lastuseobject(d->player); if (!string_compare(command, BREAK_COMMAND)) { if (!d->connected) return 0; if (dequeue_prog(d->player, 2)) { if (d->output_prefix) { queue_ansi(d, d->output_prefix); queue_write(d, "\r\n", 2); } queue_ansi(d, "Foreground program aborted.\r\n"); if ((FLAGS(d->player) & INTERACTIVE)) if ((FLAGS(d->player) & READMODE)) process_command(d->descriptor, d->player, command); if (d->output_suffix) { queue_ansi(d, d->output_suffix); queue_write(d, "\r\n", 2); } } PLAYER_SET_BLOCK(d->player, 0); return 1; } else if (!strcmp(command, QUIT_COMMAND)) { return 0; } else if ((!strncmp(command, WHO_COMMAND, sizeof(WHO_COMMAND) - 1)) || (*command == OVERIDE_TOKEN && (!strncmp(command+1, WHO_COMMAND, sizeof(WHO_COMMAND) - 1)) )) { if (d->output_prefix) { queue_ansi(d, d->output_prefix); queue_write(d, "\r\n", 2); } strcpy(buf, "@"); strcatn(buf, sizeof(buf), WHO_COMMAND); strcatn(buf, sizeof(buf), " "); strcatn(buf, sizeof(buf), command + sizeof(WHO_COMMAND) - 1); if (!d->connected || (FLAGS(d->player) & INTERACTIVE)) { if (tp_secure_who) { queue_ansi(d, "Sorry, WHO is unavailable at this point.\r\n"); } else { dump_users(d, command + sizeof(WHO_COMMAND) - 1); } } else { if ((!(TrueWizard(OWNER(d->player)) && (*command == OVERIDE_TOKEN))) && can_move(d->descriptor, d->player, buf, 2)) { do_move(d->descriptor, d->player, buf, 2); } else { dump_users(d, command + sizeof(WHO_COMMAND) - ((*command == OVERIDE_TOKEN)?0:1)); } } if (d->output_suffix) { queue_ansi(d, d->output_suffix); queue_write(d, "\r\n", 2); } } else if (!strncmp(command, PREFIX_COMMAND, sizeof(PREFIX_COMMAND) - 1)) { set_userstring(&d->output_prefix, command + sizeof(PREFIX_COMMAND) - 1); } else if (!strncmp(command, SUFFIX_COMMAND, sizeof(SUFFIX_COMMAND) - 1)) { set_userstring(&d->output_suffix, command + sizeof(SUFFIX_COMMAND) - 1); } else { if (d->connected) { if (d->output_prefix) { queue_ansi(d, d->output_prefix); queue_write(d, "\r\n", 2); } process_command(d->descriptor, d->player, command); if (d->output_suffix) { queue_ansi(d, d->output_suffix); queue_write(d, "\r\n", 2); } } else { check_connect(d, command); } } return 1; } void interact_warn(dbref player) { if (FLAGS(player) & INTERACTIVE) { char buf[BUFFER_LEN]; snprintf(buf, sizeof(buf), "*** %s ***", (FLAGS(player) & READMODE) ? "You are currently using a program. Use \"@Q\" to return to a more reasonable state of control." : (PLAYER_INSERT_MODE(player) ? "You are currently inserting MUF program text. Use \".\" to return to the editor, then \"quit\" if you wish to return to your regularly scheduled Muck universe." : "You are currently using the MUF program editor.")); notify(player, buf); } } void check_connect(struct descriptor_data *d, const char *msg) { char command[MAX_COMMAND_LEN]; char user[MAX_COMMAND_LEN]; char password[MAX_COMMAND_LEN]; dbref player; parse_connect(msg, command, user, password); if (!strncmp(command, "co", 2)) { player = connect_player(user, password); if (player == NOTHING) { queue_ansi(d, connect_fail); log_status("FAILED CONNECT %s on descriptor %d\n", user, d->descriptor); } else { if ((wizonly_mode || (tp_playermax && con_players_curr >= tp_playermax_limit)) && !TrueWizard(player)) { if (wizonly_mode) { queue_ansi(d, "Sorry, but the game is in maintenance mode currently, and only wizards are allowed to connect. Try again later."); } else { queue_ansi(d, tp_playermax_bootmesg); } queue_string(d, "\r\n"); d->booted = 1; } else { log_status("CONNECTED: %s(%d) on descriptor %d\n", NAME(player), player, d->descriptor); d->connected = 1; d->connected_at = time(NULL); d->player = player; update_desc_count_table(); remember_player_descr(player, d->descriptor); /* cks: someone has to initialize this somewhere. */ PLAYER_SET_BLOCK(d->player, 0); spit_file(player, MOTD_FILE); announce_connect(d->descriptor, player); interact_warn(player); if (sanity_violated && Wizard(player)) { notify(player, "#########################################################################"); notify(player, "## WARNING! The DB appears to be corrupt! Please repair immediately! ##"); notify(player, "#########################################################################"); } con_players_curr++; } } } else if (!strncmp(command, "cr", 2)) { if (!tp_registration) { if (wizonly_mode || (tp_playermax && con_players_curr >= tp_playermax_limit)) { if (wizonly_mode) { queue_ansi(d, "Sorry, but the game is in maintenance mode currently, and only wizards are allowed to connect. Try again later."); } else { queue_ansi(d, tp_playermax_bootmesg); } queue_string(d, "\r\n"); d->booted = 1; } else { player = create_player(user, password); if (player == NOTHING) { queue_ansi(d, create_fail); log_status("FAILED CREATE %s on descriptor %d\n", user, d->descriptor); } else { log_status("CREATED %s(%d) on descriptor %d\n", NAME(player), player, d->descriptor); d->connected = 1; d->connected_at = time(NULL); d->player = player; update_desc_count_table(); remember_player_descr(player, d->descriptor); /* cks: someone has to initialize this somewhere. */ PLAYER_SET_BLOCK(d->player, 0); spit_file(player, MOTD_FILE); announce_connect(d->descriptor, player); con_players_curr++; } } } else { queue_ansi(d, tp_register_mesg); queue_string(d, "\r\n"); log_status("FAILED CREATE %s on descriptor %d\n", user, d->descriptor); } } else if (!*command) { /* do nothing */ } else { welcome_user(d); } } void parse_connect(const char *msg, char *command, char *user, char *pass) { char *p; while (*msg && isascii(*msg) && isspace(*msg)) msg++; p = command; while (*msg && isascii(*msg) && !isspace(*msg)) *p++ = *msg++; *p = '\0'; while (*msg && isascii(*msg) && isspace(*msg)) msg++; p = user; while (*msg && isascii(*msg) && !isspace(*msg)) *p++ = *msg++; *p = '\0'; while (*msg && isascii(*msg) && isspace(*msg)) msg++; p = pass; while (*msg && isascii(*msg) && !isspace(*msg)) *p++ = *msg++; *p = '\0'; } int boot_off(dbref player) { int* darr; int dcount; struct descriptor_data *last = NULL; darr = get_player_descrs(player, &dcount); if (darr) { last = descrdata_by_descr(darr[0]); } if (last) { process_output(last); last->booted = 1; /* shutdownsock(last); */ return 1; } return 0; } void boot_player_off(dbref player) { int di; int* darr; int dcount; struct descriptor_data *d; darr = get_player_descrs(player, &dcount); for (di = 0; di < dcount; di++) { d = descrdata_by_descr(darr[di]); if (d) { d->booted = 1; } } } void close_sockets(const char *msg) { struct descriptor_data *d, *dnext; int i; for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (d->connected) { forget_player_descr(d->player, d->descriptor); } socket_write(d, msg, strlen(msg)); socket_write(d, shutdown_message, strlen(shutdown_message)); clearstrings(d); if (shutdown(d->descriptor, 2) < 0) perror("shutdown"); close(d->descriptor); freeqs(d); *d->prev = d->next; if (d->next) d->next->prev = d->prev; if (d->hostname) free((void *) d->hostname); if (d->username) free((void *) d->username); mcp_frame_clear(&d->mcpframe); FREE(d); ndescriptors--; } update_desc_count_table(); for (i = 0; i < numsocks; i++) { close(sock[i]); } #ifdef USE_SSL for (i = 0; i < ssl_numsocks; i++) { close(ssl_sock[i]); } #endif } void do_armageddon(dbref player, const char *msg) { char buf[BUFFER_LEN]; if (!Wizard(player)) { notify(player, "Sorry, but you don't look like the god of War to me."); log_status("ILLEGAL ARMAGEDDON: tried by %s\n", unparse_object(player, player)); return; } snprintf(buf, sizeof(buf), "\r\nImmediate shutdown initiated by %s.\r\n", NAME(player)); if (msg || *msg) strcatn(buf, sizeof(buf), msg); log_status("ARMAGEDDON initiated by %s(%d): %s\n", NAME(player), player, msg); fprintf(stderr, "ARMAGEDDON initiated by %s(%d): %s\n", NAME(player), player, msg); close_sockets(buf); #ifdef SPAWN_HOST_RESOLVER kill_resolver(); #endif exit(1); } void emergency_shutdown(void) { close_sockets("\r\nEmergency shutdown due to server crash."); #ifdef SPAWN_HOST_RESOLVER kill_resolver(); #endif } void dump_users(struct descriptor_data *e, char *user) { struct descriptor_data *d; int wizard, players; time_t now; char buf[2048]; char pbuf[64]; #ifdef COMPRESS extern const char *uncompress(const char *); #endif /* #ifdef GOD_PRIV */ /* -- Wizard should always override tp_who_doing JES if (tp_who_doing) { wizard = e->connected && God(e->player); } else { wizard = e->connected && Wizard(e->player); } */ /* #else */ wizard = e->connected && Wizard(e->player) && !tp_who_doing; /* #endif */ while (*user && (isspace(*user) || *user == '*')) { if (tp_who_doing && *user == '*' && e->connected && Wizard(e->player)) wizard = 1; user++; } if (wizard) /* S/he is connected and not quelled. Okay; log it. */ log_command("WIZ: %s(%d) in %s(%d): %s\n", NAME(e->player), (int) e->player, NAME(DBFETCH(e->player)->location), (int) DBFETCH(e->player)->location, "WHO"); if (!*user) user = NULL; (void) time(&now); if (wizard) { queue_ansi(e, "Player Name Location On For Idle Host\r\n"); } else { if (tp_who_doing) { queue_ansi(e, "Player Name On For Idle Doing...\r\n"); } else { queue_ansi(e, "Player Name On For Idle\r\n"); } } d = descriptor_list; players = 0; while (d) { if (d->connected && (!tp_who_hides_dark || (wizard || !(FLAGS(d->player) & DARK))) && ++players && (!user || string_prefix(NAME(d->player), user)) ) { if (wizard) { /* don't print flags, to save space */ snprintf(pbuf, sizeof(pbuf), "%.*s(#%d)", PLAYER_NAME_LIMIT + 1, NAME(d->player), (int) d->player); #ifdef GOD_PRIV if (!God(e->player)) #ifdef USE_SSL snprintf(buf, sizeof(buf), "%-*s [%6d] %10s %4s%c%c %s\r\n", #else snprintf(buf, sizeof(buf), "%-*s [%6d] %10s%4s%c %s\r\n", #endif PLAYER_NAME_LIMIT + 10, pbuf, (int) DBFETCH(d->player)->location, time_format_1(now - d->connected_at), time_format_2(now - d->last_time), ((FLAGS(d->player) & INTERACTIVE) ? '*' : ' '), #ifdef USE_SSL (d->ssl_session ? '@' : ' '), #endif d->hostname); else #endif #ifdef USE_SSL snprintf(buf, sizeof(buf), "%-*s [%6d] %10s %4s%c%c %s(%s)\r\n", #else snprintf(buf, sizeof(buf), "%-*s [%6d] %10s %4s%c %s(%s)\r\n", #endif PLAYER_NAME_LIMIT + 10, pbuf, (int) DBFETCH(d->player)->location, time_format_1(now - d->connected_at), time_format_2(now - d->last_time), ((FLAGS(d->player) & INTERACTIVE) ? '*' : ' '), #ifdef USE_SSL (d->ssl_session ? '@' : ' '), #endif d->hostname, d->username); } else { if (tp_who_doing) { /* Modified to take into account PLAYER_NAME_LIMIT changes */ #ifdef USE_SSL snprintf(buf, sizeof(buf), "%-*s %10s %4s%c%c %*s\r\n", #else snprintf(buf, sizeof(buf), "%-*s %10s %4s%c %*s\r\n", #endif PLAYER_NAME_LIMIT + 1, NAME(d->player), time_format_1(now - d->connected_at), time_format_2(now - d->last_time), ((FLAGS(d->player) & INTERACTIVE) ? '*' : ' '), #ifdef USE_SSL (d->ssl_session ? '@' : ' '), #endif (int) (44 - (PLAYER_NAME_LIMIT - 16)), GETDOING(d->player) ? #ifdef COMPRESS uncompress(GETDOING(d->player)) #else GETDOING(d->player) #endif : ""); } else { #ifdef USE_SSL snprintf(buf, sizeof(buf), "%-*s %10s %4s%c%c\r\n", #else snprintf(buf, sizeof(buf), "%-*s %10s %4s%c\r\n", #endif (int)(PLAYER_NAME_LIMIT + 1), NAME(d->player), time_format_1(now - d->connected_at), time_format_2(now - d->last_time), ((FLAGS(d->player) & INTERACTIVE) ? '*' : ' ') #ifdef USE_SSL ,(d->ssl_session ? '@' : ' ') #endif ); } } queue_ansi(e, buf); } d = d->next; } if (players > con_players_max) con_players_max = players; snprintf(buf, sizeof(buf), "%d player%s %s connected. (Max was %d)\r\n", players, (players == 1) ? "" : "s", (players == 1) ? "is" : "are", con_players_max); queue_ansi(e, buf); } char * time_format_1(long dt) { register struct tm *delta; static char buf[64]; delta = gmtime((time_t *) &dt); if (delta->tm_yday > 0) snprintf(buf, sizeof(buf), "%dd %02d:%02d", delta->tm_yday, delta->tm_hour, delta->tm_min); else snprintf(buf, sizeof(buf), "%02d:%02d", delta->tm_hour, delta->tm_min); return buf; } char * time_format_2(long dt) { register struct tm *delta; static char buf[64]; delta = gmtime((time_t *) &dt); if (delta->tm_yday > 0) snprintf(buf, sizeof(buf), "%dd", delta->tm_yday); else if (delta->tm_hour > 0) snprintf(buf, sizeof(buf), "%dh", delta->tm_hour); else if (delta->tm_min > 0) snprintf(buf, sizeof(buf), "%dm", delta->tm_min); else snprintf(buf, sizeof(buf), "%ds", delta->tm_sec); return buf; } void announce_puppets(dbref player, const char *msg, const char *prop) { dbref what, where; const char *ptr, *msg2; char buf[BUFFER_LEN]; for (what = 0; what < db_top; what++) { if (Typeof(what) == TYPE_THING && (FLAGS(what) & ZOMBIE)) { if (OWNER(what) == player) { where = getloc(what); if ((!Dark(where)) && (!Dark(player)) && (!Dark(what))) { msg2 = msg; if ((ptr = (char *) get_property_class(what, prop)) && *ptr) msg2 = ptr; snprintf(buf, sizeof(buf), "%.512s %.3000s", PNAME(what), msg2); notify_except(DBFETCH(where)->contents, what, buf, what); } } } } } void announce_connect(int descr, dbref player) { dbref loc; char buf[BUFFER_LEN]; struct match_data md; dbref exit; time_t tt; if ((loc = getloc(player)) == NOTHING) return; if (tp_rwho) { time(&tt); snprintf(buf, sizeof(buf), "%d@%s", player, tp_muckname); rwhocli_userlogin(buf, NAME(player), tt); } if ((!Dark(player)) && (!Dark(loc))) { snprintf(buf, sizeof(buf), "%s has connected.", PNAME(player)); notify_except(DBFETCH(loc)->contents, player, buf, player); } exit = NOTHING; if (online(player) == 1) { init_match(descr, player, "connect", TYPE_EXIT, &md); /* match for connect */ md.match_level = 1; match_all_exits(&md); exit = match_result(&md); if (exit == AMBIGUOUS) exit = NOTHING; } if (exit == NOTHING || !(FLAGS(exit) & STICKY)) { if (can_move(descr, player, tp_autolook_cmd, 1)) { do_move(descr, player, tp_autolook_cmd, 1); } else { do_look_around(descr, player); } } /* * See if there's a connect action. If so, and the player is the first to * connect, send the player through it. If the connect action is set * sticky, then suppress the normal look-around. */ if (exit != NOTHING) do_move(descr, player, "connect", 1); if (online(player) == 1) { announce_puppets(player, "wakes up.", "_/pcon"); } /* queue up all _connect programs referred to by properties */ envpropqueue(descr, player, getloc(player), NOTHING, player, NOTHING, "_connect", "Connect", 1, 1); envpropqueue(descr, player, getloc(player), NOTHING, player, NOTHING, "_oconnect", "Oconnect", 1, 0); ts_useobject(player); return; } void announce_disconnect(struct descriptor_data *d) { dbref player = d->player; dbref loc; char buf[BUFFER_LEN]; int dcount; if ((loc = getloc(player)) == NOTHING) return; if (tp_rwho) { snprintf(buf, sizeof(buf), "%d@%s", player, tp_muckname); rwhocli_userlogout(buf); } get_player_descrs(d->player, &dcount); if (dcount < 2 && dequeue_prog(player, 2)) notify(player, "Foreground program aborted."); if ((!Dark(player)) && (!Dark(loc))) { snprintf(buf, sizeof(buf), "%s has disconnected.", PNAME(player)); notify_except(DBFETCH(loc)->contents, player, buf, player); } /* trigger local disconnect action */ if (online(player) == 1) { if (can_move(d->descriptor, player, "disconnect", 1)) { do_move(d->descriptor, player, "disconnect", 1); } announce_puppets(player, "falls asleep.", "_/pdcon"); } gui_dlog_closeall_descr(d->descriptor); d->connected = 0; d->player = NOTHING; forget_player_descr(player, d->descriptor); update_desc_count_table(); /* queue up all _connect programs referred to by properties */ envpropqueue(d->descriptor, player, getloc(player), NOTHING, player, NOTHING, "_disconnect", "Disconnect", 1, 1); envpropqueue(d->descriptor, player, getloc(player), NOTHING, player, NOTHING, "_odisconnect", "Odisconnect", 1, 0); ts_lastuseobject(player); DBDIRTY(player); } #ifdef MUD_ID #include <pwd.h> void do_setuid(char *name) { struct passwd *pw; if ((pw = getpwnam(name)) == NULL) { log_status("can't get pwent for %s\n", name); exit(1); } if (setuid(pw->pw_uid) == -1) { log_status("can't setuid(%d): ", pw->pw_uid); perror("setuid"); exit(1); } } #endif /* MUD_ID */ #ifdef MUD_GID #include <grp.h> void do_setgid(char *name) { struct group *gr; if ((gr = getgrnam(name)) == NULL) { log_status("can't get grent for group %s\n", name); exit(1); } if (setgid(gr->gr_gid) == -1) { log_status("can't setgid(%d): ",gr->gr_gid); perror("setgid"); exit(1); } } #endif /* MUD_GID */ /***** O(1) Connection Optimizations *****/ struct descriptor_data *descr_count_table[FD_SETSIZE]; int current_descr_count = 0; void init_descr_count_lookup() { int i; for (i = 0; i < FD_SETSIZE; i++) { descr_count_table[i] = NULL; } } void update_desc_count_table() { int c; struct descriptor_data *d; current_descr_count = 0; for (c = 0, d = descriptor_list; d; d = d->next) { if (d->connected) { d->con_number = c + 1; descr_count_table[c++] = d; current_descr_count++; } } } struct descriptor_data * descrdata_by_count(int c) { c--; if (c >= current_descr_count || c < 0) { return NULL; } return descr_count_table[c]; } struct descriptor_data *descr_lookup_table[FD_SETSIZE]; #ifdef WIN32 int descr_hash_table[FD_SETSIZE]; int sethash_descr(int d) { for (int i = 0; i < FD_SETSIZE; i++) { if (descr_hash_table[i] == -1) { descr_hash_table[i] = d; return i; } } fprintf(stderr,"descr hash table full!", NULL); /* Should NEVER happen */ return -1; } int gethash_descr(int d) { for (int i = 0; i < FD_SETSIZE; i++) { if (descr_hash_table[i] == d) return i; } fprintf(stderr,"descr hash value missing!", NULL); /* Should NEVER happen */ return -1; } void unsethash_descr(int d) { for (int i = 0; i < FD_SETSIZE; i++) { if (descr_hash_table[i] == d) { descr_hash_table[i] = -1; return; } } fprintf(stderr,"descr hash value missing!", NULL); /* Should NEVER happen */ } #endif void init_descriptor_lookup() { int i; #ifdef WIN32 for (i = 0; i < FD_SETSIZE; i++) { descr_hash_table[i] = -1; } #endif for (i = 0; i < FD_SETSIZE; i++) { descr_lookup_table[i] = NULL; } } int index_descr(int index) { if((index < 0) || (index >= FD_SETSIZE)) return -1; if(descr_lookup_table[index] == NULL) return -1; return descr_lookup_table[index]->descriptor; } int* get_player_descrs(dbref player, int* count) { int* darr; if (Typeof(player) == TYPE_PLAYER) { *count = PLAYER_DESCRCOUNT(player); darr = PLAYER_DESCRS(player); if (!darr) { *count = 0; } return darr; } else { *count = 0; return NULL; } } void remember_player_descr(dbref player, int descr) { int count = 0; int* arr = NULL; if (Typeof(player) != TYPE_PLAYER) return; count = PLAYER_DESCRCOUNT(player); arr = PLAYER_DESCRS(player); if (!arr) { arr = (int*)malloc(sizeof(int)); arr[0] = descr; count = 1; } else { arr = (int*)realloc(arr,sizeof(int) * (count+1)); arr[count] = descr; count++; } PLAYER_SET_DESCRCOUNT(player, count); PLAYER_SET_DESCRS(player, arr); } void forget_player_descr(dbref player, int descr) { int count = 0; int* arr = NULL; if (Typeof(player) != TYPE_PLAYER) return; count = PLAYER_DESCRCOUNT(player); arr = PLAYER_DESCRS(player); if (!arr) { count = 0; } else if (count > 1) { int src, dest; for (src = dest = 0; src < count; src++) { if (arr[src] != descr) { if (src != dest) { arr[dest] = arr[src]; } dest++; } } if (dest != count) { count = dest; arr = (int*)realloc(arr,sizeof(int) * count); } } else { free((void*)arr); arr = NULL; count = 0; } PLAYER_SET_DESCRCOUNT(player, count); PLAYER_SET_DESCRS(player, arr); } void remember_descriptor(struct descriptor_data *d) { if (d) { #ifdef WIN32 descr_lookup_table[sethash_descr(d->descriptor)] = d; #else descr_lookup_table[d->descriptor] = d; #endif } } void forget_descriptor(struct descriptor_data *d) { if (d) { #ifdef WIN32 unsethash_descr(d->descriptor); #else descr_lookup_table[d->descriptor] = NULL; #endif } } struct descriptor_data * lookup_descriptor(int c) { #ifdef WIN32 if ( c < 0 ) return NULL; return descr_lookup_table[gethash_descr(c)]; #else if (c >= FD_SETSIZE || c < 0) { return NULL; } return descr_lookup_table[c]; #endif } struct descriptor_data * descrdata_by_descr(int i) { return lookup_descriptor(i); } /*** JME ***/ int online(dbref player) { return PLAYER_DESCRCOUNT(player); } int pcount(void) { return current_descr_count; } int pidle(int c) { struct descriptor_data *d; time_t now; d = descrdata_by_count(c); (void) time(&now); if (d) { return (now - d->last_time); } return -1; } int pdescridle(int c) { struct descriptor_data *d; time_t now; d = descrdata_by_descr(c); (void) time(&now); if (d) { return (now - d->last_time); } return -1; } dbref pdbref(int c) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { return (d->player); } return NOTHING; } dbref pdescrdbref(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return (d->player); } return NOTHING; } int pontime(int c) { struct descriptor_data *d; time_t now; d = descrdata_by_count(c); (void) time(&now); if (d) { return (now - d->connected_at); } return -1; } int pdescrontime(int c) { struct descriptor_data *d; time_t now; d = descrdata_by_descr(c); (void) time(&now); if (d) { return (now - d->connected_at); } return -1; } char * phost(int c) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { return ((char *) d->hostname); } return (char *) NULL; } char * pdescrhost(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return ((char *) d->hostname); } return (char *) NULL; } char * puser(int c) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { return ((char *) d->username); } return (char *) NULL; } char * pdescruser(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return ((char *) d->username); } return (char *) NULL; } /*** Foxen ***/ int least_idle_player_descr(dbref who) { struct descriptor_data *d; struct descriptor_data *best_d = NULL; int dcount, di; int* darr; long best_time = 0; darr = get_player_descrs(who, &dcount); for (di = 0; di < dcount; di++) { d = descrdata_by_descr(darr[di]); if (d && (!best_time || d->last_time > best_time)) { best_d = d; best_time = d->last_time; } } if (best_d) { return best_d->con_number; } return 0; } int most_idle_player_descr(dbref who) { struct descriptor_data *d; struct descriptor_data *best_d = NULL; int dcount, di; int* darr; long best_time = 0; darr = get_player_descrs(who, &dcount); for (di = 0; di < dcount; di++) { d = descrdata_by_descr(darr[di]); if (d && (!best_time || d->last_time < best_time)) { best_d = d; best_time = d->last_time; } } if (best_d) { return best_d->con_number; } return 0; } void pboot(int c) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { process_output(d); d->booted = 1; /* shutdownsock(d); */ } } int pdescrboot(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { process_output(d); d->booted = 1; /* shutdownsock(d); */ return 1; } return 0; } void pnotify(int c, char *outstr) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { queue_ansi(d, outstr); queue_write(d, "\r\n", 2); } } int pdescrnotify(int c, char *outstr) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { queue_ansi(d, outstr); queue_write(d, "\r\n", 2); return 1; } return 0; } int pdescr(int c) { struct descriptor_data *d; d = descrdata_by_count(c); if (d) { return (d->descriptor); } return -1; } int pdescrcount(void) { return current_descr_count; } int pfirstdescr(void) { struct descriptor_data *d; d = descrdata_by_count(1); if (d) { return d->descriptor; } return 0; } int plastdescr(void) { struct descriptor_data *d; d = descrdata_by_count(current_descr_count); if (d) { return d->descriptor; } return 0; } int pnextdescr(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { d = d->next; } while (d && (!d->connected)) d = d->next; if (d) { return (d->descriptor); } return (0); } int pdescrcon(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return d->con_number; } else { return 0; } } int pset_user(int c, dbref who) { struct descriptor_data *d; static int setuser_depth = 0; if (++setuser_depth > 8) { /* Prevent infinite loops */ setuser_depth--; return 0; } d = descrdata_by_descr(c); if (d && d->connected) { announce_disconnect(d); if (who != NOTHING) { d->player = who; d->connected = 1; update_desc_count_table(); remember_player_descr(who, d->descriptor); announce_connect(d->descriptor, who); } setuser_depth--; return 1; } setuser_depth--; return 0; } int dbref_first_descr(dbref c) { int dcount; int* darr; darr = get_player_descrs(c, &dcount); if (dcount > 0) { return darr[dcount - 1]; } else { return -1; } } McpFrame * descr_mcpframe(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return &d->mcpframe; } return NULL; } int pdescrflush(int c) { struct descriptor_data *d; int i = 0; if (c != -1) { d = descrdata_by_descr(c); if (d) { if (!process_output(d)) { d->booted = 1; } i++; } } else { for (d = descriptor_list; d; d = d->next) { if (!process_output(d)) { d->booted = 1; } i++; } } return i; } int pdescrsecure(int c) { #ifdef USE_SSL struct descriptor_data *d; d = descrdata_by_descr(c); if (d && d->ssl_session) return 1; else return 0; #else return 0; #endif } int pdescrbufsize(int c) { struct descriptor_data *d; d = descrdata_by_descr(c); if (d) { return (tp_max_output - d->output_size); } return -1; } dbref partial_pmatch(const char *name) { struct descriptor_data *d; dbref last = NOTHING; d = descriptor_list; while (d) { if (d->connected && (last != d->player) && string_prefix(NAME(d->player), name)) { if (last != NOTHING) { last = AMBIGUOUS; break; } last = d->player; } d = d->next; } return (last); } void update_rwho(void) { struct descriptor_data *d; char buf[BUFFER_LEN]; rwhocli_pingalive(); d = descriptor_list; while (d) { if (d->connected) { snprintf(buf, sizeof(buf), "%d@%s", d->player, tp_muckname); rwhocli_userlogin(buf, NAME(d->player), d->connected_at); } d = d->next; } } void welcome_user(struct descriptor_data *d) { FILE *f; char *ptr; char buf[BUFFER_LEN]; if ((f = fopen(WELC_FILE, "r")) == NULL) { queue_ansi(d, DEFAULT_WELCOME_MESSAGE); perror("spit_file: welcome.txt"); } else { while (fgets(buf, sizeof(buf) - 3, f)) { ptr = index(buf, '\n'); if (ptr && ptr > buf && *(ptr - 1) != '\r') { *ptr++ = '\r'; *ptr++ = '\n'; *ptr++ = '\0'; } queue_ansi(d, buf); } fclose(f); } if (wizonly_mode) { queue_ansi(d, "## The game is currently in maintenance mode, and only wizards will be able to connect.\r\n"); } else if (tp_playermax && con_players_curr >= tp_playermax_limit) { if (tp_playermax_warnmesg && *tp_playermax_warnmesg) { queue_ansi(d, tp_playermax_warnmesg); queue_string(d, "\r\n"); } } } void dump_status(void) { struct descriptor_data *d; time_t now; char buf[BUFFER_LEN]; (void) time(&now); log_status("STATUS REPORT:\n"); for (d = descriptor_list; d; d = d->next) { if (d->connected) { snprintf(buf, sizeof(buf), "PLAYING descriptor %d player %s(%d) from host %s(%s), %s.\n", d->descriptor, NAME(d->player), d->player, d->hostname, d->username, (d->last_time) ? "idle %d seconds" : "never used"); } else { snprintf(buf, sizeof(buf), "CONNECTING descriptor %d from host %s(%s), %s.\n", d->descriptor, d->hostname, d->username, (d->last_time) ? "idle %d seconds" : "never used"); } log_status(buf, now - d->last_time); } } #ifdef USE_SSL ssize_t socket_read(struct descriptor_data *d, void *buf, size_t count) { int i; if (! d->ssl_session) { return read(d->descriptor, buf, count); } else { i = SSL_read(d->ssl_session, buf, count); if ( i < 0 ) { i = SSL_get_error(d->ssl_session, i); if ( (i == SSL_ERROR_WANT_READ) || (i == SSL_ERROR_WANT_WRITE) ) { /* log_status("SSL read: Return wouldblock.\n", "version"); */ #ifndef WIN32 errno = EWOULDBLOCK; #else WSASetLastError(WSAEWOULDBLOCK); #endif return -1; } else { /* log_status("SSL read: Return EBADF.\n", "version"); */ #ifndef WIN32 errno = EBADF; #endif return -1; } } return i; } } ssize_t socket_write(struct descriptor_data *d, const void *buf, size_t count) { int i; if (! d->ssl_session) { return write(d->descriptor, buf, count); } else { i = SSL_write(d->ssl_session, buf, count); if ( i < 0 ) { i = SSL_get_error(d->ssl_session, i); if ( (i == SSL_ERROR_WANT_READ) || (i == SSL_ERROR_WANT_WRITE) ) { /* log_status("SSL write: Return wouldblock.\n", "version"); */ #ifndef WIN32 errno = EWOULDBLOCK; #else WSASetLastError(WSAEWOULDBLOCK); #endif return -1; } else { /* log_status("SSL write: Return EBADF.\n", "version"); */ #ifndef WIN32 errno = EBADF; #endif return -1; } } return i; } } #endif /* Ignore support -- Could do with moving into its own file */ static int ignore_is_ignoring_sub(dbref Player, dbref Who) { int Top, Bottom; dbref* List; if (!tp_ignore_support) return 0; if ((Player < 0) || (Player >= db_top) || (Typeof(Player) == TYPE_GARBAGE)) return 0; if ((Who < 0) || (Who >= db_top) || (Typeof(Who) == TYPE_GARBAGE)) return 0; Player = OWNER(Player); Who = OWNER(Who); /* You can't ignore yourself, or an unquelled wizard, */ /* and unquelled wizards can ignore no one. */ if ((Player == Who) || (Wizard(Player)) || (Wizard(Who))) return 0; if (PLAYER_IGNORE_LAST(Player) == AMBIGUOUS) return 0; /* Ignore the last player ignored without bothering to look them up */ if (PLAYER_IGNORE_LAST(Player) == Who) return 1; if ((PLAYER_IGNORE_CACHE(Player) == NULL) && !ignore_prime_cache(Player)) return 0; Top = 0; Bottom = PLAYER_IGNORE_COUNT(Player); List = PLAYER_IGNORE_CACHE(Player); while(Top < Bottom) { int Middle = Top + (Bottom - Top) / 2; if (List[Middle] == Who) break; if (List[Middle] < Who) Top = Middle + 1; else Bottom = Middle; } if (Top >= Bottom) return 0; PLAYER_SET_IGNORE_LAST(Player, Who); return 1; } int ignore_is_ignoring(dbref Player, dbref Who) { return ignore_is_ignoring_sub(Player, Who) || (tp_ignore_bidirectional && ignore_is_ignoring_sub(Who, Player)); } static int ignore_dbref_compare(const void* Lhs, const void* Rhs) { return *(dbref*)Lhs - *(dbref*)Rhs; } int ignore_prime_cache(dbref Player) { const char* Txt = 0; const char* Ptr = 0; dbref* List = 0; int Count = 0; int i; if (!tp_ignore_support) return 0; if ((Player < 0) || (Player >= db_top) || (Typeof(Player) != TYPE_PLAYER)) return 0; if ((Txt = get_uncompress(get_property_class(Player, IGNORE_PROP))) == NULL) { PLAYER_SET_IGNORE_LAST(Player, AMBIGUOUS); return 0; } while(*Txt && isspace(*Txt)) Txt++; if (*Txt == '\0') { PLAYER_SET_IGNORE_LAST(Player, AMBIGUOUS); return 0; } for(Ptr = Txt; *Ptr; ) { Count++; if (*Ptr == '#') Ptr++; while(*Ptr && !isspace(*Ptr)) Ptr++; while(*Ptr && isspace(*Ptr)) Ptr++; } if ((List = (dbref*)malloc(sizeof(dbref) * Count)) == 0) return 0; for(Ptr = Txt, i = 0; *Ptr; ) { if (*Ptr == '#') Ptr++; if (isdigit(*Ptr)) List[i++] = atoi(Ptr); else List[i++] = NOTHING; while(*Ptr && !isspace(*Ptr)) Ptr++; while(*Ptr && isspace(*Ptr)) Ptr++; } qsort(List, Count, sizeof(dbref), ignore_dbref_compare); PLAYER_SET_IGNORE_CACHE(Player, List); PLAYER_SET_IGNORE_COUNT(Player, Count); return 1; } void ignore_flush_cache(dbref Player) { if ((Player < 0) || (Player >= db_top) || (Typeof(Player) != TYPE_PLAYER)) return; if (PLAYER_IGNORE_CACHE(Player)) { free(PLAYER_IGNORE_CACHE(Player)); PLAYER_SET_IGNORE_CACHE(Player, NULL); PLAYER_SET_IGNORE_COUNT(Player, 0); } PLAYER_SET_IGNORE_LAST(Player, NOTHING); } void ignore_flush_all_cache() { int i; /* Don't touch the database if it's not been loaded yet... */ if (db == 0) return; for(i = 0; i < db_top; i++) { if (Typeof(i) == TYPE_PLAYER) { if (PLAYER_IGNORE_CACHE(i)) { free(PLAYER_IGNORE_CACHE(i)); PLAYER_SET_IGNORE_CACHE(i, NULL); PLAYER_SET_IGNORE_COUNT(i, 0); } PLAYER_SET_IGNORE_LAST(i, NOTHING); } } } void ignore_add_player(dbref Player, dbref Who) { if (!tp_ignore_support) return; if ((Player < 0) || (Player >= db_top) || (Typeof(Player) == TYPE_GARBAGE)) return; if ((Who < 0) || (Who >= db_top) || (Typeof(Who) == TYPE_GARBAGE)) return; reflist_add(OWNER(Player), IGNORE_PROP, OWNER(Who)); ignore_flush_cache(OWNER(Player)); } void ignore_remove_player(dbref Player, dbref Who) { if (!tp_ignore_support) return; if ((Player < 0) || (Player >= db_top) || (Typeof(Player) == TYPE_GARBAGE)) return; if ((Who < 0) || (Who >= db_top) || (Typeof(Who) == TYPE_GARBAGE)) return; reflist_del(OWNER(Player), IGNORE_PROP, OWNER(Who)); ignore_flush_cache(OWNER(Player)); } void ignore_remove_from_all_players(dbref Player) { int i; if (!tp_ignore_support) return; for(i = 0; i < db_top; i++) if (Typeof(i) == TYPE_PLAYER) reflist_del(i, IGNORE_PROP, Player); ignore_flush_all_cache(); }