/* ************************************************************************ * File: comm.c Part of CircleMUD * * Usage: Communication, socket handling, main(), central game loop * * * * All rights reserved. See license.doc for complete information. * * * * Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University * * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * ************************************************************************ */ #define __COMM_C__ #include "conf.h" #include "sysdep.h" #if CIRCLE_GNU_LIBC_MEMORY_TRACK # include #endif #ifdef CIRCLE_MACINTOSH /* Includes for the Macintosh */ # define SIGPIPE 13 # define SIGALRM 14 /* GUSI headers */ # include /* Codewarrior dependant */ # include # include #endif #ifdef CIRCLE_WINDOWS /* Includes for Win32 */ # ifdef __BORLANDC__ # include # else /* MSVC */ # include # endif # include #endif /* CIRCLE_WINDOWS */ #ifdef CIRCLE_AMIGA /* Includes for the Amiga */ # include # include #endif /* CIRCLE_AMIGA */ #ifdef CIRCLE_ACORN /* Includes for the Acorn (RiscOS) */ # include # include # include #endif /* * Note, most includes for all platforms are in sysdep.h. The list of * files that is included is controlled by conf.h for that platform. */ #include "structs.h" #include "utils.h" #include "comm.h" #include "interpreter.h" #include "handler.h" #include "db.h" #include "house.h" #ifdef HAVE_ARPA_TELNET_H #include #else #include "telnet.h" #endif #ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) #endif /* externs */ extern struct ban_list_element *ban_list; extern int num_invalid; extern char *GREETINGS; extern const char *circlemud_version; extern int circle_restrict; extern int mini_mud; extern int no_rent_check; extern FILE *player_fl; extern ush_int DFLT_PORT; extern const char *DFLT_DIR; extern const char *DFLT_IP; extern const char *LOGNAME; extern int max_playing; extern int nameserver_is_slow; /* see config.c */ extern int auto_save; /* see config.c */ extern int autosave_time; /* see config.c */ extern int *cmd_sort_info; extern struct time_info_data time_info; /* In db.c */ extern char *help; /* local globals */ struct descriptor_data *descriptor_list = NULL; /* master desc list */ struct txt_block *bufpool = 0; /* pool of large output buffers */ int buf_largecount = 0; /* # of large buffers which exist */ int buf_overflows = 0; /* # of overflows of output */ int buf_switches = 0; /* # of switches from small to large buf */ int circle_shutdown = 0; /* clean shutdown */ int circle_reboot = 0; /* reboot the game after a shutdown */ int no_specials = 0; /* Suppress ass. of special routines */ int max_players = 0; /* max descriptors available */ int tics = 0; /* for extern checkpointing */ int scheck = 0; /* for syntax checking mode */ struct timeval null_time; /* zero-valued time structure */ byte reread_wizlist; /* signal: SIGUSR1 */ byte emergency_unban; /* signal: SIGUSR2 */ FILE *logfile = NULL; /* Where to send the log messages. */ const char *text_overflow = "**OVERFLOW**\r\n"; /* functions in this file */ RETSIGTYPE reread_wizlists(int sig); RETSIGTYPE unrestrict_game(int sig); RETSIGTYPE reap(int sig); RETSIGTYPE checkpointing(int sig); RETSIGTYPE hupsig(int sig); ssize_t perform_socket_read(socket_t desc, char *read_point,size_t space_left); ssize_t perform_socket_write(socket_t desc, const char *txt,size_t length); void echo_off(struct descriptor_data *d); void echo_on(struct descriptor_data *d); void circle_sleep(struct timeval *timeout); int get_from_q(struct txt_q *queue, char *dest, int *aliased); void init_game(ush_int port); void signal_setup(void); void game_loop(socket_t mother_desc); socket_t init_socket(ush_int port); int new_descriptor(socket_t s); int get_max_players(void); int process_output(struct descriptor_data *t); int process_input(struct descriptor_data *t); void timediff(struct timeval *diff, struct timeval *a, struct timeval *b); void timeadd(struct timeval *sum, struct timeval *a, struct timeval *b); void flush_queues(struct descriptor_data *d); void nonblock(socket_t s); int perform_subst(struct descriptor_data *t, char *orig, char *subst); void record_usage(void); char *make_prompt(struct descriptor_data *point); void check_idle_passwords(void); void heartbeat(int pulse); struct in_addr *get_bind_addr(void); int parse_ip(const char *addr, struct in_addr *inaddr); int set_sendbuf(socket_t s); void setup_log(const char *filename, int fd); int open_logfile(const char *filename, FILE *stderr_fp); #if defined(POSIX) sigfunc *my_signal(int signo, sigfunc *func); #endif /* extern fcnts */ void reboot_wizlists(void); void boot_world(void); void affect_update(void); /* In magic.c */ void mobile_activity(void); void perform_violence(void); void show_string(struct descriptor_data *d, char *input); int isbanned(char *hostname); void weather_and_time(int mode); int perform_alias(struct descriptor_data *d, char *orig, size_t maxlen); void clear_free_list(void); void free_messages(void); void Board_clear_all(void); void free_social_messages(void); void Free_Invalid_List(void); #ifdef __CXREF__ #undef FD_ZERO #undef FD_SET #undef FD_ISSET #undef FD_CLR #define FD_ZERO(x) #define FD_SET(x, y) 0 #define FD_ISSET(x, y) 0 #define FD_CLR(x, y) #endif /*********************************************************************** * main game loop and related stuff * ***********************************************************************/ #if defined(CIRCLE_WINDOWS) || defined(CIRCLE_MACINTOSH) /* * Windows doesn't have gettimeofday, so we'll simulate it. * The Mac doesn't have gettimeofday either. * Borland C++ warns: "Undefined structure 'timezone'" */ void gettimeofday(struct timeval *t, struct timezone *dummy) { #if defined(CIRCLE_WINDOWS) DWORD millisec = GetTickCount(); #elif defined(CIRCLE_MACINTOSH) unsigned long int millisec; millisec = (int)((float)TickCount() * 1000.0 / 60.0); #endif t->tv_sec = (int) (millisec / 1000); t->tv_usec = (millisec % 1000) * 1000; } #endif /* CIRCLE_WINDOWS || CIRCLE_MACINTOSH */ int main(int argc, char **argv) { ush_int port; int pos = 1; const char *dir; #if CIRCLE_GNU_LIBC_MEMORY_TRACK mtrace(); /* This must come before any use of malloc(). */ #endif #ifdef CIRCLE_MACINTOSH /* * ccommand() calls the command line/io redirection dialog box from * Codewarriors's SIOUX library */ argc = ccommand(&argv); /* Initialize the GUSI library calls. */ GUSIDefaultSetup(); #endif port = DFLT_PORT; dir = DFLT_DIR; while ((pos < argc) && (*(argv[pos]) == '-')) { switch (*(argv[pos] + 1)) { case 'o': if (*(argv[pos] + 2)) LOGNAME = argv[pos] + 2; else if (++pos < argc) LOGNAME = argv[pos]; else { puts("SYSERR: File name to log to expected after option -o."); exit(1); } break; case 'd': if (*(argv[pos] + 2)) dir = argv[pos] + 2; else if (++pos < argc) dir = argv[pos]; else { puts("SYSERR: Directory arg expected after option -d."); exit(1); } break; case 'm': mini_mud = 1; no_rent_check = 1; puts("Running in minimized mode & with no rent check."); break; case 'c': scheck = 1; puts("Syntax check mode enabled."); break; case 'q': no_rent_check = 1; puts("Quick boot mode -- rent check supressed."); break; case 'r': circle_restrict = 1; puts("Restricting game -- no new players allowed."); break; case 's': no_specials = 1; puts("Suppressing assignment of special routines."); break; case 'h': /* From: Anil Mahajan */ printf("Usage: %s [-c] [-m] [-q] [-r] [-s] [-d pathname] [port #]\n" " -c Enable syntax check mode.\n" " -d Specify library directory (defaults to 'lib').\n" " -h Print this command line argument help.\n" " -m Start in mini-MUD mode.\n" " -o Write log to instead of stderr.\n" " -q Quick boot (doesn't scan rent for object limits)\n" " -r Restrict MUD -- no new players allowed.\n" " -s Suppress special procedure assignments.\n", argv[0] ); exit(0); default: printf("SYSERR: Unknown option -%c in argument string.\n", *(argv[pos] + 1)); break; } pos++; } if (pos < argc) { if (!isdigit(*argv[pos])) { printf("Usage: %s [-c] [-m] [-q] [-r] [-s] [-d pathname] [port #]\n", argv[0]); exit(1); } else if ((port = atoi(argv[pos])) <= 1024) { printf("SYSERR: Illegal port number %d.\n", port); exit(1); } } /* All arguments have been parsed, try to open log file. */ setup_log(LOGNAME, STDERR_FILENO); /* * Moved here to distinguish command line options and to show up * in the log if stderr is redirected to a file. */ log("%s", circlemud_version); if (chdir(dir) < 0) { perror("SYSERR: Fatal error changing to data directory"); exit(1); } log("Using %s as data directory.", dir); if (scheck) boot_world(); else { log("Running game on port %d.", port); init_game(port); } log("Clearing game world."); destroy_db(); if (!scheck) { log("Clearing other memory."); free_player_index(); /* db.c */ free_messages(); /* fight.c */ clear_free_list(); /* mail.c */ free_text_files(); /* db.c */ Board_clear_all(); /* boards.c */ free(cmd_sort_info); /* act.informative.c */ free_social_messages(); /* act.social.c */ free_help(); /* db.c */ Free_Invalid_List(); /* ban.c */ } log("Done."); return (0); } /* Init sockets, run game, and cleanup sockets */ void init_game(ush_int port) { socket_t mother_desc; /* We don't want to restart if we crash before we get up. */ touch(KILLSCRIPT_FILE); circle_srandom(time(0)); log("Finding player limit."); max_players = get_max_players(); log("Opening mother connection."); mother_desc = init_socket(port); boot_db(); #if defined(CIRCLE_UNIX) || defined(CIRCLE_MACINTOSH) log("Signal trapping."); signal_setup(); #endif /* If we made it this far, we will be able to restart without problem. */ remove(KILLSCRIPT_FILE); log("Entering game loop."); game_loop(mother_desc); Crash_save_all(); log("Closing all sockets."); while (descriptor_list) close_socket(descriptor_list); CLOSE_SOCKET(mother_desc); fclose(player_fl); log("Saving current MUD time."); save_mud_time(&time_info); if (circle_reboot) { log("Rebooting."); exit(52); /* what's so great about HHGTTG, anyhow? */ } log("Normal termination of game."); } /* * init_socket sets up the mother descriptor - creates the socket, sets * its options up, binds it, and listens. */ socket_t init_socket(ush_int port) { socket_t s; struct sockaddr_in sa; int opt; #ifdef CIRCLE_WINDOWS { WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); if (WSAStartup(wVersionRequested, &wsaData) != 0) { log("SYSERR: WinSock not available!"); exit(1); } /* * 4 = stdin, stdout, stderr, mother_desc. Windows might * keep sockets and files separate, in which case this isn't * necessary, but we will err on the side of caution. */ if ((wsaData.iMaxSockets - 4) < max_players) { max_players = wsaData.iMaxSockets - 4; } log("Max players set to %d", max_players); if ((s = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { log("SYSERR: Error opening network connection: Winsock error #%d", WSAGetLastError()); exit(1); } } #else /* * Should the first argument to socket() be AF_INET or PF_INET? I don't * know, take your pick. PF_INET seems to be more widely adopted, and * Comer (_Internetworking with TCP/IP_) even makes a point to say that * people erroneously use AF_INET with socket() when they should be using * PF_INET. However, the man pages of some systems indicate that AF_INET * is correct; some such as ConvexOS even say that you can use either one. * All implementations I've seen define AF_INET and PF_INET to be the same * number anyway, so the point is (hopefully) moot. */ if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("SYSERR: Error creating socket"); exit(1); } #endif /* CIRCLE_WINDOWS */ #if defined(SO_REUSEADDR) && !defined(CIRCLE_MACINTOSH) opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0){ perror("SYSERR: setsockopt REUSEADDR"); exit(1); } #endif set_sendbuf(s); /* * The GUSI sockets library is derived from BSD, so it defines * SO_LINGER, even though setsockopt() is unimplimented. * (from Dean Takemori ) */ #if defined(SO_LINGER) && !defined(CIRCLE_MACINTOSH) { struct linger ld; ld.l_onoff = 0; ld.l_linger = 0; if (setsockopt(s, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)) < 0) perror("SYSERR: setsockopt SO_LINGER"); /* Not fatal I suppose. */ } #endif /* Clear the structure */ memset((char *)&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr = *(get_bind_addr()); if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) { perror("SYSERR: bind"); CLOSE_SOCKET(s); exit(1); } nonblock(s); listen(s, 5); return (s); } int get_max_players(void) { #ifndef CIRCLE_UNIX return (max_playing); #else int max_descs = 0; const char *method; /* * First, we'll try using getrlimit/setrlimit. This will probably work * on most systems. HAS_RLIMIT is defined in sysdep.h. */ #ifdef HAS_RLIMIT { struct rlimit limit; /* find the limit of file descs */ method = "rlimit"; if (getrlimit(RLIMIT_NOFILE, &limit) < 0) { perror("SYSERR: calling getrlimit"); exit(1); } /* set the current to the maximum */ limit.rlim_cur = limit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &limit) < 0) { perror("SYSERR: calling setrlimit"); exit(1); } #ifdef RLIM_INFINITY if (limit.rlim_max == RLIM_INFINITY) max_descs = max_playing + NUM_RESERVED_DESCS; else max_descs = MIN(max_playing + NUM_RESERVED_DESCS, limit.rlim_max); #else max_descs = MIN(max_playing + NUM_RESERVED_DESCS, limit.rlim_max); #endif } #elif defined (OPEN_MAX) || defined(FOPEN_MAX) #if !defined(OPEN_MAX) #define OPEN_MAX FOPEN_MAX #endif method = "OPEN_MAX"; max_descs = OPEN_MAX; /* Uh oh.. rlimit didn't work, but we have * OPEN_MAX */ #elif defined (_SC_OPEN_MAX) /* * Okay, you don't have getrlimit() and you don't have OPEN_MAX. Time to * try the POSIX sysconf() function. (See Stevens' _Advanced Programming * in the UNIX Environment_). */ method = "POSIX sysconf"; errno = 0; if ((max_descs = sysconf(_SC_OPEN_MAX)) < 0) { if (errno == 0) max_descs = max_playing + NUM_RESERVED_DESCS; else { perror("SYSERR: Error calling sysconf"); exit(1); } } #else /* if everything has failed, we'll just take a guess */ method = "random guess"; max_descs = max_playing + NUM_RESERVED_DESCS; #endif /* now calculate max _players_ based on max descs */ max_descs = MIN(max_playing, max_descs - NUM_RESERVED_DESCS); if (max_descs <= 0) { log("SYSERR: Non-positive max player limit! (Set at %d using %s).", max_descs, method); exit(1); } log(" Setting player limit to %d using %s.", max_descs, method); return (max_descs); #endif /* CIRCLE_UNIX */ } /* * game_loop contains the main loop which drives the entire MUD. It * cycles once every 0.10 seconds and is responsible for accepting new * new connections, polling existing connections for input, dequeueing * output and sending it out to players, and calling "heartbeat" functions * such as mobile_activity(). */ void game_loop(socket_t mother_desc) { fd_set input_set, output_set, exc_set, null_set; struct timeval last_time, opt_time, process_time, temp_time; struct timeval before_sleep, now, timeout; char comm[MAX_INPUT_LENGTH]; struct descriptor_data *d, *next_d; int pulse = 0, missed_pulses, maxdesc, aliased; /* initialize various time values */ null_time.tv_sec = 0; null_time.tv_usec = 0; opt_time.tv_usec = OPT_USEC; opt_time.tv_sec = 0; FD_ZERO(&null_set); gettimeofday(&last_time, (struct timezone *) 0); /* The Main Loop. The Big Cheese. The Top Dog. The Head Honcho. The.. */ while (!circle_shutdown) { /* Sleep if we don't have any connections */ if (descriptor_list == NULL) { log("No connections. Going to sleep."); FD_ZERO(&input_set); FD_SET(mother_desc, &input_set); if (select(mother_desc + 1, &input_set, (fd_set *) 0, (fd_set *) 0, NULL) < 0) { if (errno == EINTR) log("Waking up to process signal."); else perror("SYSERR: Select coma"); } else log("New connection. Waking up."); gettimeofday(&last_time, (struct timezone *) 0); } /* Set up the input, output, and exception sets for select(). */ FD_ZERO(&input_set); FD_ZERO(&output_set); FD_ZERO(&exc_set); FD_SET(mother_desc, &input_set); maxdesc = mother_desc; for (d = descriptor_list; d; d = d->next) { #ifndef CIRCLE_WINDOWS if (d->descriptor > maxdesc) maxdesc = d->descriptor; #endif FD_SET(d->descriptor, &input_set); FD_SET(d->descriptor, &output_set); FD_SET(d->descriptor, &exc_set); } /* * At this point, we have completed all input, output and heartbeat * activity from the previous iteration, so we have to put ourselves * to sleep until the next 0.1 second tick. The first step is to * calculate how long we took processing the previous iteration. */ gettimeofday(&before_sleep, (struct timezone *) 0); /* current time */ timediff(&process_time, &before_sleep, &last_time); /* * If we were asleep for more than one pass, count missed pulses and sleep * until we're resynchronized with the next upcoming pulse. */ if (process_time.tv_sec == 0 && process_time.tv_usec < OPT_USEC) { missed_pulses = 0; } else { missed_pulses = process_time.tv_sec * PASSES_PER_SEC; missed_pulses += process_time.tv_usec / OPT_USEC; process_time.tv_sec = 0; process_time.tv_usec = process_time.tv_usec % OPT_USEC; } /* Calculate the time we should wake up */ timediff(&temp_time, &opt_time, &process_time); timeadd(&last_time, &before_sleep, &temp_time); /* Now keep sleeping until that time has come */ gettimeofday(&now, (struct timezone *) 0); timediff(&timeout, &last_time, &now); /* Go to sleep */ do { circle_sleep(&timeout); gettimeofday(&now, (struct timezone *) 0); timediff(&timeout, &last_time, &now); } while (timeout.tv_usec || timeout.tv_sec); /* Poll (without blocking) for new input, output, and exceptions */ if (select(maxdesc + 1, &input_set, &output_set, &exc_set, &null_time) < 0) { perror("SYSERR: Select poll"); return; } /* If there are new connections waiting, accept them. */ if (FD_ISSET(mother_desc, &input_set)) new_descriptor(mother_desc); /* Kick out the freaky folks in the exception set and marked for close */ for (d = descriptor_list; d; d = next_d) { next_d = d->next; if (FD_ISSET(d->descriptor, &exc_set)) { FD_CLR(d->descriptor, &input_set); FD_CLR(d->descriptor, &output_set); close_socket(d); } } /* Process descriptors with input pending */ for (d = descriptor_list; d; d = next_d) { next_d = d->next; if (FD_ISSET(d->descriptor, &input_set)) if (process_input(d) < 0) close_socket(d); } /* Process commands we just read from process_input */ for (d = descriptor_list; d; d = next_d) { next_d = d->next; /* * Not combined to retain --(d->wait) behavior. -gg 2/20/98 * If no wait state, no subtraction. If there is a wait * state then 1 is subtracted. Therefore we don't go less * than 0 ever and don't require an 'if' bracket. -gg 2/27/99 */ if (d->character) { GET_WAIT_STATE(d->character) -= (GET_WAIT_STATE(d->character) > 0); if (GET_WAIT_STATE(d->character)) continue; } if (!get_from_q(&d->input, comm, &aliased)) continue; if (d->character) { /* Reset the idle timer & pull char back from void if necessary */ d->character->char_specials.timer = 0; if (STATE(d) == CON_PLAYING && GET_WAS_IN(d->character) != NOWHERE) { if (IN_ROOM(d->character) != NOWHERE) char_from_room(d->character); char_to_room(d->character, GET_WAS_IN(d->character)); GET_WAS_IN(d->character) = NOWHERE; act("$n has returned.", TRUE, d->character, 0, 0, TO_ROOM); } GET_WAIT_STATE(d->character) = 1; } d->has_prompt = FALSE; if (d->str) /* Writing boards, mail, etc. */ string_add(d, comm); else if (d->showstr_count) /* Reading something w/ pager */ show_string(d, comm); else if (STATE(d) != CON_PLAYING) /* In menus, etc. */ nanny(d, comm); else { /* else: we're playing normally. */ if (aliased) /* To prevent recursive aliases. */ d->has_prompt = TRUE; /* To get newline before next cmd output. */ else if (perform_alias(d, comm, sizeof(comm))) /* Run it through aliasing system */ get_from_q(&d->input, comm, &aliased); command_interpreter(d->character, comm); /* Send it to interpreter */ } } /* Send queued output out to the operating system (ultimately to user). */ for (d = descriptor_list; d; d = next_d) { next_d = d->next; if (*(d->output) && FD_ISSET(d->descriptor, &output_set)) { /* Output for this player is ready. */ process_output(d); if (d->bufptr == 0) /* All output sent. */ d->has_prompt = TRUE; } } /* Print prompts for other descriptors who had no other output */ for (d = descriptor_list; d; d = d->next) { if (!d->has_prompt && d->bufptr == 0) { write_to_descriptor(d->descriptor, make_prompt(d)); d->has_prompt = TRUE; } } /* Kick out folks in the CON_CLOSE or CON_DISCONNECT state */ for (d = descriptor_list; d; d = next_d) { next_d = d->next; if (STATE(d) == CON_CLOSE || STATE(d) == CON_DISCONNECT) close_socket(d); } /* * Now, we execute as many pulses as necessary--just one if we haven't * missed any pulses, or make up for lost time if we missed a few * pulses by sleeping for too long. */ missed_pulses++; if (missed_pulses <= 0) { log("SYSERR: **BAD** MISSED_PULSES NONPOSITIVE (%d), TIME GOING BACKWARDS!!", missed_pulses); missed_pulses = 1; } /* If we missed more than 30 seconds worth of pulses, just do 30 secs */ if (missed_pulses > 30 RL_SEC) { log("SYSERR: Missed %d seconds worth of pulses.", missed_pulses / PASSES_PER_SEC); missed_pulses = 30 RL_SEC; } /* Now execute the heartbeat functions */ while (missed_pulses--) heartbeat(++pulse); /* Check for any signals we may have received. */ if (reread_wizlist) { reread_wizlist = FALSE; mudlog(CMP, LVL_IMMORT, TRUE, "Signal received - rereading wizlists."); reboot_wizlists(); } if (emergency_unban) { emergency_unban = FALSE; mudlog(BRF, LVL_IMMORT, TRUE, "Received SIGUSR2 - completely unrestricting game (emergent)"); ban_list = NULL; circle_restrict = 0; num_invalid = 0; } /* Roll pulse over after 10 hours */ if (pulse >= (10 * 60 * 60 * PASSES_PER_SEC)) pulse = 0; #ifdef CIRCLE_UNIX /* Update tics for deadlock protection (UNIX only) */ tics++; #endif } } void heartbeat(int pulse) { static int mins_since_crashsave = 0; if (!(pulse % PULSE_ZONE)) zone_update(); if (!(pulse % PULSE_IDLEPWD)) /* 15 seconds */ check_idle_passwords(); if (!(pulse % PULSE_MOBILE)) mobile_activity(); if (!(pulse % PULSE_VIOLENCE)) perform_violence(); if (!(pulse % (SECS_PER_MUD_HOUR * PASSES_PER_SEC))) { weather_and_time(1); affect_update(); point_update(); fflush(player_fl); } if (auto_save && !(pulse % PULSE_AUTOSAVE)) { /* 1 minute */ if (++mins_since_crashsave >= autosave_time) { mins_since_crashsave = 0; Crash_save_all(); House_save_all(); } } if (!(pulse % PULSE_USAGE)) record_usage(); if (!(pulse % PULSE_TIMESAVE)) save_mud_time(&time_info); /* Every pulse! Don't want them to stink the place up... */ extract_pending_chars(); } /* ****************************************************************** * general utility stuff (for local use) * ****************************************************************** */ /* * new code to calculate time differences, which works on systems * for which tv_usec is unsigned (and thus comparisons for something * being < 0 fail). Based on code submitted by ss@sirocco.cup.hp.com. */ /* * code to return the time difference between a and b (a-b). * always returns a nonnegative value (floors at 0). */ void timediff(struct timeval *rslt, struct timeval *a, struct timeval *b) { if (a->tv_sec < b->tv_sec) *rslt = null_time; else if (a->tv_sec == b->tv_sec) { if (a->tv_usec < b->tv_usec) *rslt = null_time; else { rslt->tv_sec = 0; rslt->tv_usec = a->tv_usec - b->tv_usec; } } else { /* a->tv_sec > b->tv_sec */ rslt->tv_sec = a->tv_sec - b->tv_sec; if (a->tv_usec < b->tv_usec) { rslt->tv_usec = a->tv_usec + 1000000 - b->tv_usec; rslt->tv_sec--; } else rslt->tv_usec = a->tv_usec - b->tv_usec; } } /* * Add 2 time values. * * Patch sent by "d. hall" to fix 'static' usage. */ void timeadd(struct timeval *rslt, struct timeval *a, struct timeval *b) { rslt->tv_sec = a->tv_sec + b->tv_sec; rslt->tv_usec = a->tv_usec + b->tv_usec; while (rslt->tv_usec >= 1000000) { rslt->tv_usec -= 1000000; rslt->tv_sec++; } } void record_usage(void) { int sockets_connected = 0, sockets_playing = 0; struct descriptor_data *d; for (d = descriptor_list; d; d = d->next) { sockets_connected++; if (STATE(d) == CON_PLAYING) sockets_playing++; } log("nusage: %-3d sockets connected, %-3d sockets playing", sockets_connected, sockets_playing); #ifdef RUSAGE /* Not RUSAGE_SELF because it doesn't guarantee prototype. */ { struct rusage ru; getrusage(RUSAGE_SELF, &ru); log("rusage: user time: %ld sec, system time: %ld sec, max res size: %ld", ru.ru_utime.tv_sec, ru.ru_stime.tv_sec, ru.ru_maxrss); } #endif } /* * Turn off echoing (specific to telnet client) */ void echo_off(struct descriptor_data *d) { char off_string[] = { (char) IAC, (char) WILL, (char) TELOPT_ECHO, (char) 0, }; write_to_output(d, "%s", off_string); } /* * Turn on echoing (specific to telnet client) */ void echo_on(struct descriptor_data *d) { char on_string[] = { (char) IAC, (char) WONT, (char) TELOPT_ECHO, (char) 0 }; write_to_output(d, "%s", on_string); } char *make_prompt(struct descriptor_data *d) { static char prompt[MAX_PROMPT_LENGTH]; /* Note, prompt is truncated at MAX_PROMPT_LENGTH chars (structs.h) */ if (d->str) strcpy(prompt, "] "); /* strcpy: OK (for 'MAX_PROMPT_LENGTH >= 3') */ else if (d->showstr_count) { snprintf(prompt, sizeof(prompt), "\r\n[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d/%d) ]", d->showstr_page, d->showstr_count); } else if (STATE(d) == CON_PLAYING && !IS_NPC(d->character)) { int count; size_t len = 0; *prompt = '\0'; if (GET_INVIS_LEV(d->character) && len < sizeof(prompt)) { count = snprintf(prompt + len, sizeof(prompt) - len, "i%d ", GET_INVIS_LEV(d->character)); if (count >= 0) len += count; } if (PRF_FLAGGED(d->character, PRF_DISPHP) && len < sizeof(prompt)) { count = snprintf(prompt + len, sizeof(prompt) - len, "%dH ", GET_HIT(d->character)); if (count >= 0) len += count; } if (PRF_FLAGGED(d->character, PRF_DISPMANA) && len < sizeof(prompt)) { count = snprintf(prompt + len, sizeof(prompt) - len, "%dM ", GET_MANA(d->character)); if (count >= 0) len += count; } if (PRF_FLAGGED(d->character, PRF_DISPMOVE) && len < sizeof(prompt)) { count = snprintf(prompt + len, sizeof(prompt) - len, "%dV ", GET_MOVE(d->character)); if (count >= 0) len += count; } if (len < sizeof(prompt)) strncat(prompt, "> ", sizeof(prompt) - len - 1); /* strncat: OK */ } else if (STATE(d) == CON_PLAYING && IS_NPC(d->character)) snprintf(prompt, sizeof(prompt), "%s> ", GET_NAME(d->character)); else *prompt = '\0'; return (prompt); } /* * NOTE: 'txt' must be at most MAX_INPUT_LENGTH big. */ void write_to_q(const char *txt, struct txt_q *queue, int aliased) { struct txt_block *newt; CREATE(newt, struct txt_block, 1); newt->text = strdup(txt); newt->aliased = aliased; /* queue empty? */ if (!queue->head) { newt->next = NULL; queue->head = queue->tail = newt; } else { queue->tail->next = newt; queue->tail = newt; newt->next = NULL; } } /* * NOTE: 'dest' must be at least MAX_INPUT_LENGTH big. */ int get_from_q(struct txt_q *queue, char *dest, int *aliased) { struct txt_block *tmp; /* queue empty? */ if (!queue->head) return (0); strcpy(dest, queue->head->text); /* strcpy: OK (mutual MAX_INPUT_LENGTH) */ *aliased = queue->head->aliased; tmp = queue->head; queue->head = queue->head->next; free(tmp->text); free(tmp); return (1); } /* Empty the queues before closing connection */ void flush_queues(struct descriptor_data *d) { if (d->large_outbuf) { d->large_outbuf->next = bufpool; bufpool = d->large_outbuf; } while (d->input.head) { struct txt_block *tmp = d->input.head; d->input.head = d->input.head->next; free(tmp->text); free(tmp); } } /* Add a new string to a player's output queue. For outside use. */ size_t write_to_output(struct descriptor_data *t, const char *txt, ...) { va_list args; size_t left; va_start(args, txt); left = vwrite_to_output(t, txt, args); va_end(args); return left; } /* Add a new string to a player's output queue. */ size_t vwrite_to_output(struct descriptor_data *t, const char *format, va_list args) { static char txt[MAX_STRING_LENGTH]; size_t wantsize; int size; /* if we're in the overflow state already, ignore this new output */ if (t->bufspace == 0) return (0); wantsize = size = vsnprintf(txt, sizeof(txt), format, args); /* If exceeding the size of the buffer, truncate it for the overflow message */ if (size < 0 || wantsize >= sizeof(txt)) { size = sizeof(txt) - 1; strcpy(txt + size - strlen(text_overflow), text_overflow); /* strcpy: OK */ } /* * If the text is too big to fit into even a large buffer, truncate * the new text to make it fit. (This will switch to the overflow * state automatically because t->bufspace will end up 0.) */ if (size + t->bufptr + 1 > LARGE_BUFSIZE) { size = LARGE_BUFSIZE - t->bufptr - 1; txt[size] = '\0'; buf_overflows++; } /* * If we have enough space, just write to buffer and that's it! If the * text just barely fits, then it's switched to a large buffer instead. */ if (t->bufspace > size) { strcpy(t->output + t->bufptr, txt); /* strcpy: OK (size checked above) */ t->bufspace -= size; t->bufptr += size; return (t->bufspace); } buf_switches++; /* if the pool has a buffer in it, grab it */ if (bufpool != NULL) { t->large_outbuf = bufpool; bufpool = bufpool->next; } else { /* else create a new one */ CREATE(t->large_outbuf, struct txt_block, 1); CREATE(t->large_outbuf->text, char, LARGE_BUFSIZE); buf_largecount++; } strcpy(t->large_outbuf->text, t->output); /* strcpy: OK (size checked previously) */ t->output = t->large_outbuf->text; /* make big buffer primary */ strcat(t->output, txt); /* strcat: OK (size checked) */ /* set the pointer for the next write */ t->bufptr = strlen(t->output); /* calculate how much space is left in the buffer */ t->bufspace = LARGE_BUFSIZE - 1 - t->bufptr; return (t->bufspace); } /* ****************************************************************** * socket handling * ****************************************************************** */ /* * get_bind_addr: Return a struct in_addr that should be used in our * call to bind(). If the user has specified a desired binding * address, we try to bind to it; otherwise, we bind to INADDR_ANY. * Note that inet_aton() is preferred over inet_addr() so we use it if * we can. If neither is available, we always bind to INADDR_ANY. */ struct in_addr *get_bind_addr() { static struct in_addr bind_addr; /* Clear the structure */ memset((char *) &bind_addr, 0, sizeof(bind_addr)); /* If DLFT_IP is unspecified, use INADDR_ANY */ if (DFLT_IP == NULL) { bind_addr.s_addr = htonl(INADDR_ANY); } else { /* If the parsing fails, use INADDR_ANY */ if (!parse_ip(DFLT_IP, &bind_addr)) { log("SYSERR: DFLT_IP of %s appears to be an invalid IP address",DFLT_IP); bind_addr.s_addr = htonl(INADDR_ANY); } } /* Put the address that we've finally decided on into the logs */ if (bind_addr.s_addr == htonl(INADDR_ANY)) log("Binding to all IP interfaces on this host."); else log("Binding only to IP address %s", inet_ntoa(bind_addr)); return (&bind_addr); } #ifdef HAVE_INET_ATON /* * inet_aton's interface is the same as parse_ip's: 0 on failure, non-0 if * successful */ int parse_ip(const char *addr, struct in_addr *inaddr) { return (inet_aton(addr, inaddr)); } #elif HAVE_INET_ADDR /* inet_addr has a different interface, so we emulate inet_aton's */ int parse_ip(const char *addr, struct in_addr *inaddr) { long ip; if ((ip = inet_addr(addr)) == -1) { return (0); } else { inaddr->s_addr = (unsigned long) ip; return (1); } } #else /* If you have neither function - sorry, you can't do specific binding. */ int parse_ip(const char *addr, struct in_addr *inaddr) { log("SYSERR: warning: you're trying to set DFLT_IP but your system has no " "functions to parse IP addresses (how bizarre!)"); return (0); } #endif /* INET_ATON and INET_ADDR */ /* Sets the kernel's send buffer size for the descriptor */ int set_sendbuf(socket_t s) { #if defined(SO_SNDBUF) && !defined(CIRCLE_MACINTOSH) int opt = MAX_SOCK_BUF; if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *) &opt, sizeof(opt)) < 0) { perror("SYSERR: setsockopt SNDBUF"); return (-1); } #if 0 if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *) &opt, sizeof(opt)) < 0) { perror("SYSERR: setsockopt RCVBUF"); return (-1); } #endif #endif return (0); } int new_descriptor(socket_t s) { socket_t desc; int sockets_connected = 0; socklen_t i; static int last_desc = 0; /* last descriptor number */ struct descriptor_data *newd; struct sockaddr_in peer; struct hostent *from; /* accept the new connection */ i = sizeof(peer); if ((desc = accept(s, (struct sockaddr *) &peer, &i)) == INVALID_SOCKET) { perror("SYSERR: accept"); return (-1); } /* keep it from blocking */ nonblock(desc); /* set the send buffer size */ if (set_sendbuf(desc) < 0) { CLOSE_SOCKET(desc); return (0); } /* make sure we have room for it */ for (newd = descriptor_list; newd; newd = newd->next) sockets_connected++; if (sockets_connected >= max_players) { write_to_descriptor(desc, "Sorry, CircleMUD is full right now... please try again later!\r\n"); CLOSE_SOCKET(desc); return (0); } /* create a new descriptor */ CREATE(newd, struct descriptor_data, 1); /* find the sitename */ if (nameserver_is_slow || !(from = gethostbyaddr((char *) &peer.sin_addr, sizeof(peer.sin_addr), AF_INET))) { /* resolution failed */ if (!nameserver_is_slow) perror("SYSERR: gethostbyaddr"); /* find the numeric site address */ strncpy(newd->host, (char *)inet_ntoa(peer.sin_addr), HOST_LENGTH); /* strncpy: OK (n->host:HOST_LENGTH+1) */ *(newd->host + HOST_LENGTH) = '\0'; } else { strncpy(newd->host, from->h_name, HOST_LENGTH); /* strncpy: OK (n->host:HOST_LENGTH+1) */ *(newd->host + HOST_LENGTH) = '\0'; } /* determine if the site is banned */ if (isbanned(newd->host) == BAN_ALL) { CLOSE_SOCKET(desc); mudlog(CMP, LVL_GOD, TRUE, "Connection attempt denied from [%s]", newd->host); free(newd); return (0); } #if 0 /* * Log new connections - probably unnecessary, but you may want it. * Note that your immortals may wonder if they see a connection from * your site, but you are wizinvis upon login. */ mudlog(CMP, LVL_GOD, FALSE, "New connection from [%s]", newd->host); #endif /* initialize descriptor data */ newd->descriptor = desc; newd->idle_tics = 0; newd->output = newd->small_outbuf; newd->bufspace = SMALL_BUFSIZE - 1; newd->login_time = time(0); *newd->output = '\0'; newd->bufptr = 0; newd->has_prompt = 1; /* prompt is part of greetings */ STATE(newd) = CON_GET_NAME; /* * This isn't exactly optimal but allows us to make a design choice. * Do we embed the history in descriptor_data or keep it dynamically * allocated and allow a user defined history size? */ CREATE(newd->history, char *, HISTORY_SIZE); if (++last_desc == 1000) last_desc = 1; newd->desc_num = last_desc; /* prepend to list */ newd->next = descriptor_list; descriptor_list = newd; write_to_output(newd, "%s", GREETINGS); return (0); } /* * Send all of the output that we've accumulated for a player out to * the player's descriptor. * * 32 byte GARBAGE_SPACE in MAX_SOCK_BUF used for: * 2 bytes: prepended \r\n * 14 bytes: overflow message * 2 bytes: extra \r\n for non-comapct * 14 bytes: unused */ int process_output(struct descriptor_data *t) { char i[MAX_SOCK_BUF], *osb = i + 2; int result; /* we may need this \r\n for later -- see below */ strcpy(i, "\r\n"); /* strcpy: OK (for 'MAX_SOCK_BUF >= 3') */ /* now, append the 'real' output */ strcpy(osb, t->output); /* strcpy: OK (t->output:LARGE_BUFSIZE < osb:MAX_SOCK_BUF-2) */ /* if we're in the overflow state, notify the user */ if (t->bufspace == 0) strcat(osb, "**OVERFLOW**\r\n"); /* strcpy: OK (osb:MAX_SOCK_BUF-2 reserves space) */ /* add the extra CRLF if the person isn't in compact mode */ if (STATE(t) == CON_PLAYING && t->character && !IS_NPC(t->character) && !PRF_FLAGGED(t->character, PRF_COMPACT)) strcat(osb, "\r\n"); /* strcpy: OK (osb:MAX_SOCK_BUF-2 reserves space) */ /* add a prompt */ strcat(i, make_prompt(t)); /* strcpy: OK (i:MAX_SOCK_BUF reserves space) */ /* * now, send the output. If this is an 'interruption', use the prepended * CRLF, otherwise send the straight output sans CRLF. */ if (t->has_prompt) { t->has_prompt = FALSE; result = write_to_descriptor(t->descriptor, i); if (result >= 2) result -= 2; } else result = write_to_descriptor(t->descriptor, osb); if (result < 0) { /* Oops, fatal error. Bye! */ close_socket(t); return (-1); } else if (result == 0) /* Socket buffer full. Try later. */ return (0); /* Handle snooping: prepend "% " and send to snooper. */ if (t->snoop_by) write_to_output(t->snoop_by, "%% %*s%%%%", result, t->output); /* The common case: all saved output was handed off to the kernel buffer. */ if (result >= t->bufptr) { /* * if we were using a large buffer, put the large buffer on the buffer pool * and switch back to the small one */ if (t->large_outbuf) { t->large_outbuf->next = bufpool; bufpool = t->large_outbuf; t->large_outbuf = NULL; t->output = t->small_outbuf; } /* reset total bufspace back to that of a small buffer */ t->bufspace = SMALL_BUFSIZE - 1; t->bufptr = 0; *(t->output) = '\0'; /* * If the overflow message or prompt were partially written, try to save * them. There will be enough space for them if this is true. 'result' * is effectively unsigned here anyway. */ if ((unsigned int)result < strlen(osb)) { size_t savetextlen = strlen(osb + result); strcat(t->output, osb + result); t->bufptr -= savetextlen; t->bufspace += savetextlen; } } else { /* Not all data in buffer sent. result < output buffersize. */ strcpy(t->output, t->output + result); /* strcpy: OK (overlap) */ t->bufptr -= result; t->bufspace += result; } return (result); } /* * perform_socket_write: takes a descriptor, a pointer to text, and a * text length, and tries once to send that text to the OS. This is * where we stuff all the platform-dependent stuff that used to be * ugly #ifdef's in write_to_descriptor(). * * This function must return: * * -1 If a fatal error was encountered in writing to the descriptor. * 0 If a transient failure was encountered (e.g. socket buffer full). * >0 To indicate the number of bytes successfully written, possibly * fewer than the number the caller requested be written. * * Right now there are two versions of this function: one for Windows, * and one for all other platforms. */ #if defined(CIRCLE_WINDOWS) ssize_t perform_socket_write(socket_t desc, const char *txt, size_t length) { ssize_t result; result = send(desc, txt, length, 0); if (result > 0) { /* Write was sucessful */ return (result); } if (result == 0) { /* This should never happen! */ log("SYSERR: Huh?? write() returned 0??? Please report this!"); return (-1); } /* result < 0: An error was encountered. */ /* Transient error? */ if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR) return (0); /* Must be a fatal error. */ return (-1); } #else #if defined(CIRCLE_ACORN) #define write socketwrite #endif /* perform_socket_write for all Non-Windows platforms */ ssize_t perform_socket_write(socket_t desc, const char *txt, size_t length) { ssize_t result; result = write(desc, txt, length); if (result > 0) { /* Write was successful. */ return (result); } if (result == 0) { /* This should never happen! */ log("SYSERR: Huh?? write() returned 0??? Please report this!"); return (-1); } /* * result < 0, so an error was encountered - is it transient? * Unfortunately, different systems use different constants to * indicate this. */ #ifdef EAGAIN /* POSIX */ if (errno == EAGAIN) return (0); #endif #ifdef EWOULDBLOCK /* BSD */ if (errno == EWOULDBLOCK) return (0); #endif #ifdef EDEADLK /* Macintosh */ if (errno == EDEADLK) return (0); #endif /* Looks like the error was fatal. Too bad. */ return (-1); } #endif /* CIRCLE_WINDOWS */ /* * write_to_descriptor takes a descriptor, and text to write to the * descriptor. It keeps calling the system-level write() until all * the text has been delivered to the OS, or until an error is * encountered. * * Returns: * >=0 If all is well and good. * -1 If an error was encountered, so that the player should be cut off. */ int write_to_descriptor(socket_t desc, const char *txt) { ssize_t bytes_written; size_t total = strlen(txt), write_total = 0; while (total > 0) { bytes_written = perform_socket_write(desc, txt, total); if (bytes_written < 0) { /* Fatal error. Disconnect the player. */ perror("SYSERR: Write to socket"); return (-1); } else if (bytes_written == 0) { /* Temporary failure -- socket buffer full. */ return (write_total); } else { txt += bytes_written; total -= bytes_written; write_total += bytes_written; } } return (write_total); } /* * Same information about perform_socket_write applies here. I like * standards, there are so many of them. -gg 6/30/98 */ ssize_t perform_socket_read(socket_t desc, char *read_point, size_t space_left) { ssize_t ret; #if defined(CIRCLE_ACORN) ret = recv(desc, read_point, space_left, MSG_DONTWAIT); #elif defined(CIRCLE_WINDOWS) ret = recv(desc, read_point, space_left, 0); #else ret = read(desc, read_point, space_left); #endif /* Read was successful. */ if (ret > 0) return (ret); /* read() returned 0, meaning we got an EOF. */ if (ret == 0) { log("WARNING: EOF on socket read (connection broken by peer)"); return (-1); } /* * read returned a value < 0: there was an error */ #if defined(CIRCLE_WINDOWS) /* Windows */ if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR) return (0); #else #ifdef EINTR /* Interrupted system call - various platforms */ if (errno == EINTR) return (0); #endif #ifdef EAGAIN /* POSIX */ if (errno == EAGAIN) return (0); #endif #ifdef EWOULDBLOCK /* BSD */ if (errno == EWOULDBLOCK) return (0); #endif /* EWOULDBLOCK */ #ifdef EDEADLK /* Macintosh */ if (errno == EDEADLK) return (0); #endif #ifdef ECONNRESET if (errno == ECONNRESET) return (-1); #endif #endif /* CIRCLE_WINDOWS */ /* * We don't know what happened, cut them off. This qualifies for * a SYSERR because we have no idea what happened at this point. */ perror("SYSERR: perform_socket_read: about to lose connection"); return (-1); } /* * ASSUMPTION: There will be no newlines in the raw input buffer when this * function is called. We must maintain that before returning. * * Ever wonder why 'tmp' had '+8' on it? The crusty old code could write * MAX_INPUT_LENGTH+1 bytes to 'tmp' if there was a '$' as the final * character in the input buffer. This would also cause 'space_left' to * drop to -1, which wasn't very happy in an unsigned variable. Argh. * So to fix the above, 'tmp' lost the '+8' since it doesn't need it * and the code has been changed to reserve space by accepting one less * character. (Do you really need 256 characters on a line?) * -gg 1/21/2000 */ int process_input(struct descriptor_data *t) { int buf_length, failed_subst; ssize_t bytes_read; size_t space_left; char *ptr, *read_point, *write_point, *nl_pos = NULL; char tmp[MAX_INPUT_LENGTH]; /* first, find the point where we left off reading data */ buf_length = strlen(t->inbuf); read_point = t->inbuf + buf_length; space_left = MAX_RAW_INPUT_LENGTH - buf_length - 1; do { if (space_left <= 0) { log("WARNING: process_input: about to close connection: input overflow"); return (-1); } bytes_read = perform_socket_read(t->descriptor, read_point, space_left); if (bytes_read < 0) /* Error, disconnect them. */ return (-1); else if (bytes_read == 0) /* Just blocking, no problems. */ return (0); /* at this point, we know we got some data from the read */ *(read_point + bytes_read) = '\0'; /* terminate the string */ /* search for a newline in the data we just read */ for (ptr = read_point; *ptr && !nl_pos; ptr++) if (ISNEWL(*ptr)) nl_pos = ptr; read_point += bytes_read; space_left -= bytes_read; /* * on some systems such as AIX, POSIX-standard nonblocking I/O is broken, * causing the MUD to hang when it encounters input not terminated by a * newline. This was causing hangs at the Password: prompt, for example. * I attempt to compensate by always returning after the _first_ read, instead * of looping forever until a read returns -1. This simulates non-blocking * I/O because the result is we never call read unless we know from select() * that data is ready (process_input is only called if select indicates that * this descriptor is in the read set). JE 2/23/95. */ #if !defined(POSIX_NONBLOCK_BROKEN) } while (nl_pos == NULL); #else } while (0); if (nl_pos == NULL) return (0); #endif /* POSIX_NONBLOCK_BROKEN */ /* * okay, at this point we have at least one newline in the string; now we * can copy the formatted data to a new array for further processing. */ read_point = t->inbuf; while (nl_pos != NULL) { write_point = tmp; space_left = MAX_INPUT_LENGTH - 1; /* The '> 1' reserves room for a '$ => $$' expansion. */ for (ptr = read_point; (space_left > 1) && (ptr < nl_pos); ptr++) { if (*ptr == '\b' || *ptr == 127) { /* handle backspacing or delete key */ if (write_point > tmp) { if (*(--write_point) == '$') { write_point--; space_left += 2; } else space_left++; } } else if (isascii(*ptr) && isprint(*ptr)) { if ((*(write_point++) = *ptr) == '$') { /* copy one character */ *(write_point++) = '$'; /* if it's a $, double it */ space_left -= 2; } else space_left--; } } *write_point = '\0'; if ((space_left <= 0) && (ptr < nl_pos)) { char buffer[MAX_INPUT_LENGTH + 64]; snprintf(buffer, sizeof(buffer), "Line too long. Truncated to:\r\n%s\r\n", tmp); if (write_to_descriptor(t->descriptor, buffer) < 0) return (-1); } if (t->snoop_by) write_to_output(t->snoop_by, "%% %s\r\n", tmp); failed_subst = 0; if (*tmp == '!' && !(*(tmp + 1))) /* Redo last command. */ strcpy(tmp, t->last_input); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ else if (*tmp == '!' && *(tmp + 1)) { char *commandln = (tmp + 1); int starting_pos = t->history_pos, cnt = (t->history_pos == 0 ? HISTORY_SIZE - 1 : t->history_pos - 1); skip_spaces(&commandln); for (; cnt != starting_pos; cnt--) { if (t->history[cnt] && is_abbrev(commandln, t->history[cnt])) { strcpy(tmp, t->history[cnt]); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ strcpy(t->last_input, tmp); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ write_to_output(t, "%s\r\n", tmp); break; } if (cnt == 0) /* At top, loop to bottom. */ cnt = HISTORY_SIZE; } } else if (*tmp == '^') { if (!(failed_subst = perform_subst(t, t->last_input, tmp))) strcpy(t->last_input, tmp); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ } else { strcpy(t->last_input, tmp); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ if (t->history[t->history_pos]) free(t->history[t->history_pos]); /* Clear the old line. */ t->history[t->history_pos] = strdup(tmp); /* Save the new. */ if (++t->history_pos >= HISTORY_SIZE) /* Wrap to top. */ t->history_pos = 0; } if (!failed_subst) write_to_q(tmp, &t->input, 0); /* find the end of this line */ while (ISNEWL(*nl_pos)) nl_pos++; /* see if there's another newline in the input buffer */ read_point = ptr = nl_pos; for (nl_pos = NULL; *ptr && !nl_pos; ptr++) if (ISNEWL(*ptr)) nl_pos = ptr; } /* now move the rest of the buffer up to the beginning for the next pass */ write_point = t->inbuf; while (*read_point) *(write_point++) = *(read_point++); *write_point = '\0'; return (1); } /* perform substitution for the '^..^' csh-esque syntax orig is the * orig string, i.e. the one being modified. subst contains the * substition string, i.e. "^telm^tell" */ int perform_subst(struct descriptor_data *t, char *orig, char *subst) { char newsub[MAX_INPUT_LENGTH + 5]; char *first, *second, *strpos; /* * first is the position of the beginning of the first string (the one * to be replaced */ first = subst + 1; /* now find the second '^' */ if (!(second = strchr(first, '^'))) { write_to_output(t, "Invalid substitution.\r\n"); return (1); } /* terminate "first" at the position of the '^' and make 'second' point * to the beginning of the second string */ *(second++) = '\0'; /* now, see if the contents of the first string appear in the original */ if (!(strpos = strstr(orig, first))) { write_to_output(t, "Invalid substitution.\r\n"); return (1); } /* now, we construct the new string for output. */ /* first, everything in the original, up to the string to be replaced */ strncpy(newsub, orig, strpos - orig); /* strncpy: OK (newsub:MAX_INPUT_LENGTH+5 > orig:MAX_INPUT_LENGTH) */ newsub[strpos - orig] = '\0'; /* now, the replacement string */ strncat(newsub, second, MAX_INPUT_LENGTH - strlen(newsub) - 1); /* strncpy: OK */ /* now, if there's anything left in the original after the string to * replaced, copy that too. */ if (((strpos - orig) + strlen(first)) < strlen(orig)) strncat(newsub, strpos + strlen(first), MAX_INPUT_LENGTH - strlen(newsub) - 1); /* strncpy: OK */ /* terminate the string in case of an overflow from strncat */ newsub[MAX_INPUT_LENGTH - 1] = '\0'; strcpy(subst, newsub); /* strcpy: OK (by mutual MAX_INPUT_LENGTH) */ return (0); } void close_socket(struct descriptor_data *d) { struct descriptor_data *temp; REMOVE_FROM_LIST(d, descriptor_list, next); CLOSE_SOCKET(d->descriptor); flush_queues(d); /* Forget snooping */ if (d->snooping) d->snooping->snoop_by = NULL; if (d->snoop_by) { write_to_output(d->snoop_by, "Your victim is no longer among us.\r\n"); d->snoop_by->snooping = NULL; } if (d->character) { /* If we're switched, this resets the mobile taken. */ d->character->desc = NULL; /* Plug memory leak, from Eric Green. */ if (!IS_NPC(d->character) && PLR_FLAGGED(d->character, PLR_MAILING) && d->str) { if (*(d->str)) free(*(d->str)); free(d->str); } if (STATE(d) == CON_PLAYING || STATE(d) == CON_DISCONNECT) { struct char_data *link_challenged = d->original ? d->original : d->character; /* We are guaranteed to have a person. */ act("$n has lost $s link.", TRUE, link_challenged, 0, 0, TO_ROOM); save_char(link_challenged); mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(link_challenged)), TRUE, "Closing link to: %s.", GET_NAME(link_challenged)); } else { mudlog(CMP, LVL_IMMORT, TRUE, "Losing player: %s.", GET_NAME(d->character) ? GET_NAME(d->character) : ""); free_char(d->character); } } else mudlog(CMP, LVL_IMMORT, TRUE, "Losing descriptor without char."); /* JE 2/22/95 -- part of my unending quest to make switch stable */ if (d->original && d->original->desc) d->original->desc = NULL; /* Clear the command history. */ if (d->history) { int cnt; for (cnt = 0; cnt < HISTORY_SIZE; cnt++) if (d->history[cnt]) free(d->history[cnt]); free(d->history); } if (d->showstr_head) free(d->showstr_head); if (d->showstr_count) free(d->showstr_vector); free(d); } void check_idle_passwords(void) { struct descriptor_data *d, *next_d; for (d = descriptor_list; d; d = next_d) { next_d = d->next; if (STATE(d) != CON_PASSWORD && STATE(d) != CON_GET_NAME) continue; if (!d->idle_tics) { d->idle_tics++; continue; } else { echo_on(d); write_to_output(d, "\r\nTimed out... goodbye.\r\n"); STATE(d) = CON_CLOSE; } } } /* * I tried to universally convert Circle over to POSIX compliance, but * alas, some systems are still straggling behind and don't have all the * appropriate defines. In particular, NeXT 2.x defines O_NDELAY but not * O_NONBLOCK. Krusty old NeXT machines! (Thanks to Michael Jones for * this and various other NeXT fixes.) */ #if defined(CIRCLE_WINDOWS) void nonblock(socket_t s) { unsigned long val = 1; ioctlsocket(s, FIONBIO, &val); } #elif defined(CIRCLE_AMIGA) void nonblock(socket_t s) { long val = 1; IoctlSocket(s, FIONBIO, &val); } #elif defined(CIRCLE_ACORN) void nonblock(socket_t s) { int val = 1; socket_ioctl(s, FIONBIO, &val); } #elif defined(CIRCLE_VMS) void nonblock(socket_t s) { int val = 1; if (ioctl(s, FIONBIO, &val) < 0) { perror("SYSERR: Fatal error executing nonblock (comm.c)"); exit(1); } } #elif defined(CIRCLE_UNIX) || defined(CIRCLE_OS2) || defined(CIRCLE_MACINTOSH) #ifndef O_NONBLOCK #define O_NONBLOCK O_NDELAY #endif void nonblock(socket_t s) { int flags; flags = fcntl(s, F_GETFL, 0); flags |= O_NONBLOCK; if (fcntl(s, F_SETFL, flags) < 0) { perror("SYSERR: Fatal error executing nonblock (comm.c)"); exit(1); } } #endif /* CIRCLE_UNIX || CIRCLE_OS2 || CIRCLE_MACINTOSH */ /* ****************************************************************** * signal-handling functions (formerly signals.c). UNIX only. * ****************************************************************** */ #if defined(CIRCLE_UNIX) || defined(CIRCLE_MACINTOSH) RETSIGTYPE reread_wizlists(int sig) { reread_wizlist = TRUE; } RETSIGTYPE unrestrict_game(int sig) { emergency_unban = TRUE; } #ifdef CIRCLE_UNIX /* clean up our zombie kids to avoid defunct processes */ RETSIGTYPE reap(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0); my_signal(SIGCHLD, reap); } /* Dying anyway... */ RETSIGTYPE checkpointing(int sig) { if (!tics) { log("SYSERR: CHECKPOINT shutdown: tics not updated. (Infinite loop suspected)"); abort(); } else tics = 0; } /* Dying anyway... */ RETSIGTYPE hupsig(int sig) { log("SYSERR: Received SIGHUP, SIGINT, or SIGTERM. Shutting down..."); exit(1); /* perhaps something more elegant should * substituted */ } #endif /* CIRCLE_UNIX */ /* * This is an implementation of signal() using sigaction() for portability. * (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced * Programming in the UNIX Environment_. We are specifying that all system * calls _not_ be automatically restarted for uniformity, because BSD systems * do not restart select(), even if SA_RESTART is used. * * Note that NeXT 2.x is not POSIX and does not have sigaction; therefore, * I just define it to be the old signal. If your system doesn't have * sigaction either, you can use the same fix. * * SunOS Release 4.0.2 (sun386) needs this too, according to Tim Aldric. */ #ifndef POSIX #define my_signal(signo, func) signal(signo, func) #else sigfunc *my_signal(int signo, sigfunc *func) { struct sigaction sact, oact; sact.sa_handler = func; sigemptyset(&sact.sa_mask); sact.sa_flags = 0; #ifdef SA_INTERRUPT sact.sa_flags |= SA_INTERRUPT; /* SunOS */ #endif if (sigaction(signo, &sact, &oact) < 0) return (SIG_ERR); return (oact.sa_handler); } #endif /* POSIX */ void signal_setup(void) { #ifndef CIRCLE_MACINTOSH struct itimerval itime; struct timeval interval; /* user signal 1: reread wizlists. Used by autowiz system. */ my_signal(SIGUSR1, reread_wizlists); /* * user signal 2: unrestrict game. Used for emergencies if you lock * yourself out of the MUD somehow. (Duh...) */ my_signal(SIGUSR2, unrestrict_game); /* * set up the deadlock-protection so that the MUD aborts itself if it gets * caught in an infinite loop for more than 3 minutes. */ interval.tv_sec = 180; interval.tv_usec = 0; itime.it_interval = interval; itime.it_value = interval; setitimer(ITIMER_VIRTUAL, &itime, NULL); my_signal(SIGVTALRM, checkpointing); /* just to be on the safe side: */ my_signal(SIGHUP, hupsig); my_signal(SIGCHLD, reap); #endif /* CIRCLE_MACINTOSH */ my_signal(SIGINT, hupsig); my_signal(SIGTERM, hupsig); my_signal(SIGPIPE, SIG_IGN); my_signal(SIGALRM, SIG_IGN); } #endif /* CIRCLE_UNIX || CIRCLE_MACINTOSH */ /* **************************************************************** * Public routines for system-to-player-communication * **************************************************************** */ size_t send_to_char(struct char_data *ch, const char *messg, ...) { if (ch->desc && messg && *messg) { size_t left; va_list args; va_start(args, messg); left = vwrite_to_output(ch->desc, messg, args); va_end(args); return left; } return 0; } void send_to_all(const char *messg, ...) { struct descriptor_data *i; va_list args; if (messg == NULL) return; for (i = descriptor_list; i; i = i->next) { if (STATE(i) != CON_PLAYING) continue; va_start(args, messg); vwrite_to_output(i, messg, args); va_end(args); } } void send_to_outdoor(const char *messg, ...) { struct descriptor_data *i; if (!messg || !*messg) return; for (i = descriptor_list; i; i = i->next) { va_list args; if (STATE(i) != CON_PLAYING || i->character == NULL) continue; if (!AWAKE(i->character) || !OUTSIDE(i->character)) continue; va_start(args, messg); vwrite_to_output(i, messg, args); va_end(args); } } void send_to_room(room_rnum room, const char *messg, ...) { struct char_data *i; va_list args; if (messg == NULL) return; for (i = world[room].people; i; i = i->next_in_room) { if (!i->desc) continue; va_start(args, messg); vwrite_to_output(i->desc, messg, args); va_end(args); } } const char *ACTNULL = ""; #define CHECK_NULL(pointer, expression) \ if ((pointer) == NULL) i = ACTNULL; else i = (expression); /* higher-level communication: the act() function */ void perform_act(const char *orig, struct char_data *ch, struct obj_data *obj, const void *vict_obj, const struct char_data *to) { const char *i = NULL; char lbuf[MAX_STRING_LENGTH], *buf, *j; bool uppercasenext = FALSE; buf = lbuf; for (;;) { if (*orig == '$') { switch (*(++orig)) { case 'n': i = PERS(ch, to); break; case 'N': CHECK_NULL(vict_obj, PERS((const struct char_data *) vict_obj, to)); break; case 'm': i = HMHR(ch); break; case 'M': CHECK_NULL(vict_obj, HMHR((const struct char_data *) vict_obj)); break; case 's': i = HSHR(ch); break; case 'S': CHECK_NULL(vict_obj, HSHR((const struct char_data *) vict_obj)); break; case 'e': i = HSSH(ch); break; case 'E': CHECK_NULL(vict_obj, HSSH((const struct char_data *) vict_obj)); break; case 'o': CHECK_NULL(obj, OBJN(obj, to)); break; case 'O': CHECK_NULL(vict_obj, OBJN((const struct obj_data *) vict_obj, to)); break; case 'p': CHECK_NULL(obj, OBJS(obj, to)); break; case 'P': CHECK_NULL(vict_obj, OBJS((const struct obj_data *) vict_obj, to)); break; case 'a': CHECK_NULL(obj, SANA(obj)); break; case 'A': CHECK_NULL(vict_obj, SANA((const struct obj_data *) vict_obj)); break; case 'T': CHECK_NULL(vict_obj, (const char *) vict_obj); break; case 'F': CHECK_NULL(vict_obj, fname((const char *) vict_obj)); break; /* uppercase previous word */ case 'u': for (j=buf; j > lbuf && !isspace((int) *(j-1)); j--); if (j != buf) *j = UPPER(*j); i = ""; break; /* uppercase next word */ case 'U': uppercasenext = TRUE; i = ""; break; case '$': i = "$"; break; default: log("SYSERR: Illegal $-code to act(): %c", *orig); log("SYSERR: %s", orig); i = ""; break; } while ((*buf = *(i++))) { if (uppercasenext && !isspace((int) *buf)) { *buf = UPPER(*buf); uppercasenext = FALSE; } buf++; } orig++; } else if (!(*(buf++) = *(orig++))) { break; } else if (uppercasenext && !isspace((int) *(buf-1))) { *(buf-1) = UPPER(*(buf-1)); uppercasenext = FALSE; } } *(--buf) = '\r'; *(++buf) = '\n'; *(++buf) = '\0'; write_to_output(to->desc, "%s", CAP(lbuf)); } #define SENDOK(ch) ((ch)->desc && (to_sleeping || AWAKE(ch)) && \ (IS_NPC(ch) || !PLR_FLAGGED((ch), PLR_WRITING))) void act(const char *str, int hide_invisible, struct char_data *ch, struct obj_data *obj, const void *vict_obj, int type) { const struct char_data *to; int to_sleeping; if (!str || !*str) return; /* * Warning: the following TO_SLEEP code is a hack. * * I wanted to be able to tell act to deliver a message regardless of sleep * without adding an additional argument. TO_SLEEP is 128 (a single bit * high up). It's ONLY legal to combine TO_SLEEP with one other TO_x * command. It's not legal to combine TO_x's with each other otherwise. * TO_SLEEP only works because its value "happens to be" a single bit; * do not change it to something else. In short, it is a hack. */ /* check if TO_SLEEP is there, and remove it if it is. */ if ((to_sleeping = (type & TO_SLEEP))) type &= ~TO_SLEEP; if (type == TO_CHAR) { if (ch && SENDOK(ch)) perform_act(str, ch, obj, vict_obj, ch); return; } if (type == TO_VICT) { if ((to = (const struct char_data *) vict_obj) != NULL && SENDOK(to)) perform_act(str, ch, obj, vict_obj, to); return; } /* ASSUMPTION: at this point we know type must be TO_NOTVICT or TO_ROOM */ if (ch && IN_ROOM(ch) != NOWHERE) to = world[IN_ROOM(ch)].people; else if (obj && IN_ROOM(obj) != NOWHERE) to = world[IN_ROOM(obj)].people; else { log("SYSERR: no valid target to act()!"); return; } for (; to; to = to->next_in_room) { if (!SENDOK(to) || (to == ch)) continue; if (hide_invisible && ch && !CAN_SEE(to, ch)) continue; if (type != TO_ROOM && to == vict_obj) continue; perform_act(str, ch, obj, vict_obj, to); } } /* Prefer the file over the descriptor. */ void setup_log(const char *filename, int fd) { FILE *s_fp; #if defined(__MWERKS__) || defined(__GNUC__) s_fp = stderr; #else if ((s_fp = fdopen(STDERR_FILENO, "w")) == NULL) { puts("SYSERR: Error opening stderr, trying stdout."); if ((s_fp = fdopen(STDOUT_FILENO, "w")) == NULL) { puts("SYSERR: Error opening stdout, trying a file."); /* If we don't have a file, try a default. */ if (filename == NULL || *filename == '\0') filename = "log/syslog"; } } #endif if (filename == NULL || *filename == '\0') { /* No filename, set us up with the descriptor we just opened. */ logfile = s_fp; puts("Using file descriptor for logging."); return; } /* We honor the default filename first. */ if (open_logfile(filename, s_fp)) return; /* Well, that failed but we want it logged to a file so try a default. */ if (open_logfile("log/syslog", s_fp)) return; /* Ok, one last shot at a file. */ if (open_logfile("syslog", s_fp)) return; /* Erp, that didn't work either, just die. */ puts("SYSERR: Couldn't open anything to log to, giving up."); exit(1); } int open_logfile(const char *filename, FILE *stderr_fp) { if (stderr_fp) /* freopen() the descriptor. */ logfile = freopen(filename, "w", stderr_fp); else logfile = fopen(filename, "w"); if (logfile) { printf("Using log file '%s'%s.\n", filename, stderr_fp ? " with redirection" : ""); return (TRUE); } printf("SYSERR: Error opening file '%s': %s\n", filename, strerror(errno)); return (FALSE); } /* * This may not be pretty but it keeps game_loop() neater than if it was inline. */ #if defined(CIRCLE_WINDOWS) void circle_sleep(struct timeval *timeout) { Sleep(timeout->tv_sec * 1000 + timeout->tv_usec / 1000); } #else void circle_sleep(struct timeval *timeout) { if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, timeout) < 0) { if (errno != EINTR) { perror("SYSERR: Select sleep"); exit(1); } } } #endif /* CIRCLE_WINDOWS */