#include <sys/file.h> #include "kernel.h" #include <errno.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/ioctl.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include "jmp.h" #ifdef VARGS #include <stdarg.h> #endif #include "mud.h" #include "log.h" #include "bootstrap.h" #include "sendsys.h" #include "mobile.h" #include "commands.h" #include "timing.h" #include "s_socket.h" static int xmain(int fd); static int go_background(int pid_fd); static int check_pid(void); static void get_options(int argc,char **argv); static void usage(void); static void main_loop(int m_socket); static void rm_pid_file(void); static void end_connection(void); static void new_connection(int fd); static void handle_packet(int fd); static void sig_handler(int sig); /* Some local variables */ fd_set sockets_fds; fd_set buffer_fds; int mud_port = PORT; int main_socket; int width; int fildes[2]; Boolean kill_other_mud = False; Boolean clear_syslog_file = False; Boolean stay_foreground = False; Boolean sig_term_happened = False; Boolean sig_timer_happened = False; static int inp_buf_c = 0; static struct timeval zerotime = { 0, 0 }; void main(int argc, char **argv, char **ep) { int fd, x; envp = ep; progname = argv[0]; get_options(argc,argv); /* Parse command line */ printf( "\ndata_dir = \"%s\".\nmax_players = %d.\nport = %d.\n", (data_dir == NULL ? "<null>" : data_dir), max_players, mud_port); printf( "%s " LOG_FILE " file.\n", clear_syslog_file ? "Clear" : "Do not clear"); if (kill_other_mud) { printf( "Kill other mud.\n"); } else { printf( "Ask if other mud should be killed.\n"); } if (data_dir != NULL) chdir(data_dir); /* Do chdir if specified */ /* Check if PID_FILE is there...and what it contains */ fd = check_pid(); /* We arrive here only if we are to continue and now we are alone. */ /* Also, the PID_FILE is opened */ x = xmain(fd); unlink(PID_FILE); if (x < 0) { mudlog("Abnormal termination of mud"); exit(1); } mudlog("Normal termination of mud"); } static void connect_ok(char *h, int port) { char b[80]; sprintf(b, "Connected to port %d on %s.\n", port, h); if (stay_foreground) { printf( "%s", b ); } else { write(fildes[1], b, sizeof(b)); #ifdef TIOCNOTTY /* Waiting for parent to die */ while (getppid() > 1); #endif close(fildes[1]); } } static int xmain(int fd) { int s; int k; /* We arrive here only if we are to continue and now we are alone. */ /* Also, the PID_FILE is opened */ if (open_logfile(LOG_FILE,clear_syslog_file) < 0) { close(fd); return -1; } if (bootstrap() < 0) { /* Initialize data structures */ close(fd); return -1; } /* Now we go background */ if (go_background(fd) < 0) { return -1; } k = 10; while ( (s = main_socket = make_service(mud_port,my_hostname, sizeof(my_hostname), &my_hostent, &s_in)) == -4 && errno == EADDRINUSE && --k >= 0) { sleep(2); } if (s < 0) { mudlog( "Error code %d from make_service", s); progerror("make_service"); return -1; } if (s > 0) { /* We want the main socket at fd 0 */ dup2(s, 0); close(s); s = main_socket = 0; } width = 1; connect_ok(my_hostname, mud_port); FD_ZERO(&sockets_fds); FD_ZERO(&buffer_fds); FD_SET(s,&sockets_fds); /* Main program loop */ main_loop(s); mudlog( "Closing listening socket"); close(s); return 0; } #ifndef TIOCNOTTY /* System V style daemonizing */ static int go_background(int fd) { char b[80]; int tty, x, y, z; char *s, *t; /* Go background */ signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGTTOU, SIG_DFL); signal(SIGTTIN, SIG_DFL); if (!stay_foreground) { if (pipe(fildes) < 0) { progerror("go_background/pipe"); exit(1); } setpgrp(); switch (fork()) { case -1: progerror("fork"); close(fd); return -1; case 0: break; default: close(fd); close(fildes[1]); read(fildes[0],b,sizeof(b)); printf( "PID = %s\n", b ); read(fildes[0],b,sizeof(b)); printf( "%s", b ); exit(0); /* Let our parent process die */ } close(fildes[0]); fclose(stdin); fclose(stdout); setpgrp(); /* Detach from terminal. */ switch ( pid = fork() ) { case -1: progerror("second fork"); close(fd); close(fildes[1]); return -1; case 0: break; default: close(fd); close(fildes[1]); exit(0); /* Child process dies, grand-child lives on. */ } } pid = getpid(); /* Get our process id */ sprintf(b,"%d", pid); write(fd, b, strlen(b)); close(fd); if (!stay_foreground) { write(fildes[1],b,sizeof(b)); /* Send our PID to grand parent. */ signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } else { printf( "PID = %d\n", pid ); signal(SIGHUP, SIG_DFL); signal(SIGINT, sig_handler); signal(SIGQUIT, SIG_DFL); } signal(SIGTERM, sig_handler); signal(SIGTSTP, SIG_DFL); signal(SIGCONT, SIG_DFL); signal(SIGTTOU, SIG_DFL); signal(SIGTTIN, SIG_DFL); signal(SIGSEGV, sig_handler); /* Segmentation fault */ signal(SIGBUS, sig_handler); /* Bus error */ signal(SIGSYS, sig_handler); /* Bad argument to system call */ signal(SIGPIPE, SIG_IGN); /* Broken pipe */ } #else /* BSD style daemonizing */ static int go_background(int fd) { char b[80]; int tty, x, y, z; char *s, *t; /* Go background */ signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGTSTP, SIG_DFL); signal(SIGTTOU, SIG_DFL); signal(SIGTTIN, SIG_DFL); if (!stay_foreground) { if (pipe(fildes) < 0) { progerror("go_background/pipe"); exit(1); } switch (pid = fork()) { case -1: progerror("fork"); close(fd); return -1; case 0: break; default: printf( "PID = %d\n", pid); close(fd); close(fildes[1]); fflush(stdout); /* Flush stdout */ read(fildes[0], b, sizeof(b) ); printf( "%s", b ); exit(0); /* Let our parent process die */ } fclose(stdin); fclose(stdout); close(fildes[0]); } pid = getpid(); /* Get our process id */ sprintf(b,"%d\n", pid); write(fd, b, strlen(b)); close(fd); if (!stay_foreground) { if ((tty = open("/dev/tty",O_RDWR,S_IRUSR|S_IWUSR)) >= 0) { if (ioctl(tty,TIOCNOTTY,0) < 0) { progerror("ioctl,TIOCNOTTY"); return -1; } close(tty); setpgrp(pid,pid); /* Make our own process group */ } else if (errno != ENXIO) { progerror("open,tty"); return -1; } signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } else { signal(SIGHUP, SIG_DFL); signal(SIGINT, sig_handler); signal(SIGQUIT, SIG_DFL); } signal(SIGTERM, sig_handler); signal(SIGTSTP, SIG_DFL); signal(SIGCONT, SIG_DFL); signal(SIGTTOU, SIG_DFL); signal(SIGTTIN, SIG_DFL); signal(SIGSEGV, sig_handler); /* Segmentation fault */ signal(SIGBUS, sig_handler); /* Bus error */ signal(SIGSYS, sig_handler); /* Bad argument to system call */ signal(SIGPIPE, SIG_IGN); /* Broken pipe */ } #endif static int check_pid(void) { int fd; int pid = -1; int c; FILE *f; char b[80]; if ((fd = open(PID_FILE,O_WRONLY|O_CREAT|O_EXCL,S_IRUSR|S_IWUSR)) < 0) { /* The file exist already */ if (errno == EEXIST) { if ((f = fopen(PID_FILE,"r")) != NULL) { fgets(b,sizeof b,f); pid = atoi(b); fclose(f); } else { perror("fopen," PID_FILE); exit(1); } if (pid > 0) { if (!kill_other_mud) { printf( "There is another mud running, want me to kill it? "); while ((c = getchar()) != 'N' && c != 'n' && c != 'Y' && c != 'y' && c != '\n' && c != '\r'); if (c == 'Y' || c == 'y') kill_other_mud = True; } if (!kill_other_mud) { printf( "Ok, then I'll die.\n" ); exit(0); } printf( "Ok, Will kill other mud (PID = %d)\n", pid); if (kill(pid,SIGTERM) < 0) { if (errno != ESRCH) { perror("kill"); exit(1); } else if (unlink(PID_FILE) < 0) { /* PID_FILE without process */ perror("unlink"); exit(1); } } } else { if (unlink(PID_FILE) < 0) { /* PID_FILE without process */ perror("unlink"); exit(1); } } c = 6; while (True) { sleep(1); if ((fd = open(PID_FILE,O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) >= 0) break; if (--c < 0) { printf( "Timeout, kill it yourself, I give up.\n" ); exit(0); } } } else { perror("open " PID_FILE); exit(1); } } return fd; } static void usage(void); static void get_options(int argc,char **argv) { char *s; char *v; int x; if (argc == 1) { /* data_dir = DATA_DIR; */ stay_foreground = False; clear_syslog_file = False; kill_other_mud = False; mud_port = PORT; max_players = 32; return; } else { /* *since we make odd defaults with no args, we have to add * an option to say 'use real defaults', I select option a for that. * mudd -a is what usually should be just 'mudd'. -Alf */ } while (--argc > 0) { s = *++argv; if (*s++ != '-') { usage(); exit(1); } x = *s++; switch(x) { case 'a': break; case 'p': if (*s != '\0' || --argc > 0 && *(s = *++argv) != '\0') { if ((mud_port = atoi(s)) < 1000 || mud_port > 65535) { mud_port = PORT + 5; } } break; case 'f': stay_foreground = True; break; case 'k': kill_other_mud = True; break; case 'c': clear_syslog_file = True; break; case 'v': printf("%s\n", VERSION); exit(0); break; case 'n': if (*s != '\0' || --argc > 0 && *(s = *++argv) != '\0') { if ((max_players = atoi(s)) < 1 || max_players > 1000) { max_players = 32; } } break; case 'd': if (*s != '\0' || --argc > 0 && *(s = *++argv) != '\0') { data_dir = s; } break; default: usage(); exit(1); } } if (argc > 0) { usage(); exit(1); } } static void usage(void) { fprintf(stderr, "usage: %s [-p port] [-d directory]\n", progname); exit(1); } static void new_connection(int fd); static void main_loop(int m_socket) { int w; int v, i; int fd; int plx = 0; struct timeval *tv; fd_set r_fds, e_fds; time(&global_clock); /* Reset everything: */ for (i = 0; i < numzon; i++) { reset_zone(i, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } last_reset = global_clock; cur_player = NULL; quit_list = -1; signal(SIGALRM, sig_handler); set_timer(); while(!sig_term_happened) { switch (v = setjmp(to_main_loop)) { case JMP_QUITTING: bflush(); end_connection(); setup_globals(-1); ++fd; break; } if (sig_timer_happened) { sig_timer_happened = False; signal(SIGALRM, sig_handler); setup_globals(-1); /* bflush() and disable mynum */ set_timer(); on_timer(); } else { while (quit_list >= 0) end_connection(); r_fds = e_fds = sockets_fds; if (inp_buf_c > 0) tv = &zerotime; else tv = NULL; #ifdef SYS_HP_UX if ((v = select(width, (int *)&r_fds, NULL, (int *)&e_fds, tv)) < 0) { #else if ((v = select(width, &r_fds, NULL, &e_fds, tv)) < 0) { #endif if (errno == EINTR) continue; progerror("select"); exit(1); } for (fd = 0, w = width; fd < w; fd++) { if (FD_ISSET(fd,&e_fds)) { mudlog("Exception pending with fd = %d", fd); --v; } if (FD_ISSET(fd,&r_fds) || FD_ISSET(fd,&buffer_fds)) { --v; if (fd == m_socket) { new_connection(fd); } else { handle_packet(fd); } bflush(); /* while (quit_list >= 0) end_connection(); */ } while (quit_list >= 0) end_connection(); } } } mudlog("SIGNAL: SIGTERM Handled"); for (plx = 0; plx < max_players; plx++) { if (!EMPTY(pname(plx))) { setup_globals(plx); crapup("\tSomething very unpleasant seems to have happened...", CRAP_UNALIAS|CRAP_SAVE|CRAP_RETURN); } } rm_pid_file(); } static void rm_pid_file(void) { if (unlink(PID_FILE) < 0) { progerror("rm_pid_file"); } } static void end_connection(void) { INP_HANDLER *i, *j; int fd; int me = real_mynum; int x; if ( (x = quit_list) < 0) return; if (x == real_mynum) me = -1; setup_globals(x); quit_list = cur_player->quit_next; cur_player->quit_next = -2; fd = cur_player->fil_des; bflush(); i = cur_player->inp_handler; while (i != NULL) { j = i; i = i->next; free(j); } cur_player->inp_handler = NULL; if(FD_ISSET(fd,&buffer_fds)) /* Erkk! Hopefully the fix */ inp_buf_c--; /* for the processor eating bug */ /* (from Vita* 1/1/93 -Nck) */ FD_CLR(fd,&sockets_fds); FD_CLR(fd,&buffer_fds); close(fd); fclose(cur_player->stream); cur_player->stream = NULL; cur_player->inp_buf_p = cur_player->inp_buf_end = cur_player->inp_buffer; #if 0 cur_player->sock_buf_p = cur_player->sock_buf_end = cur_player->sock_buffer; #endif setup_globals(me); } #ifdef SYS_INET_NTOA_BUG /* If the system include file for inet_ntoa contains erroneous * prototypes. */ static char *my_inet_ntoa(struct in_addr *in) { static char addr[20]; sprintf(addr, "%d.%d.%d.%d", (int)((in->s_addr >> 24) & 0xff), (int)((in->s_addr >> 16) & 0xff), (int)((in->s_addr >> 8) & 0xff), (int)(in->s_addr & 0xff)); return addr; } #endif static void new_connection(int m_socket) { PLAYER_REC *pl; FILE *f; struct hostent *h; int plx; int fd; int sin_len; Boolean host_banned = False, host_b2 = False; struct sockaddr_in sin; char *host, *s; char hostnum[MAXHOSTNAMELEN]; sin_len = sizeof(struct sockaddr_in); if ((fd = accept(m_socket,&sin,&sin_len)) < 0) { progerror("accept"); } else if ((f = fdopen(fd,"w")) == NULL) { progerror("fdopen"); exit(1); } else { #ifdef SYS_INET_NTOA_BUG strcpy(hostnum, my_inet_ntoa(& sin.sin_addr)); #else strcpy(hostnum, inet_ntoa(& sin.sin_addr)); #endif host_b2 = is_host_banned(hostnum); host = hostnum; if ((h = gethostbyaddr((char *)&sin.sin_addr,sizeof(sin.sin_addr), AF_INET)) == NULL) { /* mudlog( "gethostbyaddr: Couldn't find hostentry for %s", hostnum)*/; } else { host_banned = is_host_banned(h->h_name); host = h->h_name; } if ((plx = find_free_player_slot()) < 0) { fprintf(f, "\nSorry, but this mud is full, please come back later.\n"); fflush(f); fclose(f); return; } setup_globals(plx); pl = cur_player; #if 0 pl->sock_buf_p = pl->sock_buf_end = pl->sock_buffer; #endif pl->sin_len = sin_len; pl->sin = sin; pl->fil_des = fd; pl->stream = f; s = pl->hostname; if (host_banned || host_b2) *s++ = '*'; strcpy(s,host); /* Include this socket as a socket to listen to */ if (fd >= width) width = fd + 1; FD_SET(fd,&sockets_fds); new_player(); } } void handle_packet(int fd) { int plx = find_pl_index(fd); int x , y, g; char z; char *b, *p, *c /*, *k */ ; int k; if (plx < 0) return; setup_globals(plx); if (cur_player->stream == NULL) return; if (cur_player->inp_buf_p >= cur_player->inp_buf_end) { if ((x = read(fd,cur_player->inp_buffer,MAX_COM_LEN - 5)) < 0) { if (errno == ECONNRESET || /* Connection reset by peer */ errno == EHOSTUNREACH || /* No route to host */ errno == ETIMEDOUT || /* Connection timed out */ errno == ENETUNREACH || /* Network is unreachable */ errno == ENETRESET || /* Network dropped connection on reset */ errno == ENETDOWN ) { /* Network is down */ crapup(NULL,CRAP_SAVE|CRAP_UNALIAS|CRAP_RETURN); errno = 0; quit_player(); return; } mudlog("ERROR: when reading data from %s\n", pname(mynum)); progerror(pname(mynum)); return; } if (x == 0) { crapup(NULL, CRAP_SAVE|CRAP_UNALIAS|CRAP_RETURN /*|CRAP_LINKLOSS*/ ); errno = 0; quit_player(); return; } inp_buf_c++; FD_SET(fd,&buffer_fds); b = cur_player->inp_buf_p = cur_player->inp_buffer; cur_player->inp_buf_end = b + x; } else { b = cur_player->inp_buf_p; x = cur_player->inp_buf_end - b; } #if 0 /* Telnet code corrected for bug in PC (and some other) telnet by ErIC */ if(*b == '\377') { b += 3; x -= 3; if(x <= 0) { inp_buf_c--; FD_CLR(fd,&buffer_fds); cur_player->inp_buf_end = 0; return; } } c = cur_player->sock_buf_p; for (y = 0; (y < x) && (c < (cur_player->sock_buffer + MAX_COM_LEN - 6)) && b[y] != '\n' && b[y] != '\r' && b[y] != '\0'; ++y) { if ((b[y] == '\010') || (b[y] == '\177')) { if (c > cur_player->sock_buffer) c--; } else *c++ = b[y]; } cur_player->sock_buf_p = c; if ((y < x) && (c < (cur_player->sock_buffer + MAX_COM_LEN - 6))) { while (y < x && ( b[y] == '\n' || b[y] == '\r' || !b[y]) ) y++; *c = 0; k = c; c = cur_player->sock_buffer; for (p = c; *p != '\0'; p++) { if (iscntrl(*p)) *p = ' '; } if (cur_player->snooped > 0) { *(k++) = '\n'; *(k--) = 0; print_buf(c, True); *k = 0; } g = 1; } cur_player->sock_buf_p = c; cur_player->inp_buf_p = b + y; if (y >= x) { FD_CLR(fd, &buffer_fds); --inp_buf_c; } if (g) { cur_player->inp_handler->inp_handler(c); cur_player->sock_buf_p=cur_player->sock_buffer; } #else for (y = 0; y < x && b[y] != '\n' && b[y] != '\r'; ++y); b[k = y] = 0; while (++y < x && ( b[y] == '\n' || b[y] == '\r' ) ) ; cur_player->inp_buf_p = b + y; if (y >= x) { FD_CLR(fd,&buffer_fds); --inp_buf_c; } /* Let us check if it is any response from TELNET */ if ((*b & 0377) == 0377) { /* Yes it is */ /* If it is ECHO option (001) and the response is IAC DON'T ECHO * 0377 0376 0001 and we have no_echo it means that we have requested * echo off and that telnet rejects it. * All other responses are either 'ok' or may be ignored. */ if (b[2] == 0001 && (b[1] & 0377) == 0376 && cur_player->no_echo) { bprintf( "(WARNING: Password will show): "); } } else { /* Remove unwanted control-characters like \001 etc. : */ for (p = b; *p != '\0'; p++) { if (iscntrl(*p)) *p = ' '; } if (cur_player->snooped > 0) { z = b[++k]; b[k] = 0; b[--k] = '\n'; print_buf(b,True); b[k] = 0; b[k+1] = z; } cur_player->inp_handler->inp_handler(b); } #endif } void sig_handler(int sig) { int plx; FILE *f; switch (sig) { case SIGTERM: mudlog( "SIGNAL: SIGTERM" ); sig_term_happened = True; return; case SIGALRM: sig_timer_happened = True; return; case SIGSEGV: mudlog( "SIGNAL: SIGSEGV[%d]", sig); break; case SIGBUS: mudlog( "SIGNAL: SIGBUS[%d]", sig); break; case SIGINT: break; default: mudlog( "SIGNAL: %d", sig); break; } rm_pid_file(); exit(1); }