/* bsd.c - BSD-style network and signal routines */ /* $Id: bsd.c,v 1.72 2004/10/09 23:17:03 tyrspace Exp $ */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include <sys/file.h> #include <sys/stat.h> #include <signal.h> #include "alloc.h" /* required by mudconf */ #include "flags.h" /* required by mudconf */ #include "htab.h" /* required by mudconf */ #include "mudconf.h" /* required by code */ #include "db.h" /* required by externs */ #include "externs.h" /* required by interface */ #include "interface.h" /* required by code */ #include "file_c.h" /* required by code */ #include "command.h" /* required by code */ #include "attrs.h" /* required by code */ #ifndef NSIG extern const int _sys_nsig; #define NSIG _sys_nsig #endif #ifdef CONCENTRATE extern void FDECL(send_killconcid, (DESC *)); extern long NDECL(make_concid); #endif extern void NDECL(dispatch); int sock; int ndescriptors = 0; int maxd = 0; DESC *descriptor_list = NULL; volatile pid_t slave_pid = 0; volatile int slave_socket = -1; DESC *FDECL(initializesock, (int, struct sockaddr_in *)); DESC *FDECL(new_connection, (int)); int FDECL(process_output, (DESC *)); int FDECL(process_input, (DESC *)); /* Some systems are lame, and inet_addr() returns -1 on failure, despite * the fact that it returns an unsigned long. */ #ifndef INADDR_NONE #define INADDR_NONE -1 #endif /* * get a result from the slave */ #define GSR_SKIP_WHITESPACE(x) \ while (isspace(*(x))) \ ++(x) #define GSR_REQUIRE_CHAR(x,c) \ if (*(x) != (c)) { \ goto gsr_end; \ } \ ++(x) #define GSR_STRCHR_INC(x,y,c) \ (x) = strchr((y), (c)); \ if (!(x)) { \ goto gsr_end; \ } \ ++(x) static int get_slave_result() { char *buf, *host1, *hostname, *host2, *p, *userid; int remote_port, len; unsigned long addr; DESC *d; buf = alloc_lbuf("slave_buf"); len = read(slave_socket, buf, LBUF_SIZE - 1); if (len < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { free_lbuf(buf); return (-1); } close(slave_socket); slave_socket = -1; free_lbuf(buf); return (-1); } else if (len == 0) { free_lbuf(buf); return (-1); } buf[len] = '\0'; host1 = buf; GSR_STRCHR_INC(hostname, host1, ' '); hostname[-1] = '\0'; GSR_STRCHR_INC(host2, hostname, '\n'); host2[-1] = '\0'; if (mudconf.use_hostname) { for (d = descriptor_list; d; d = d->next) { if (strcmp(d->addr, host1)) continue; if (d->player != 0) { if (d->username[0]) { atr_add_raw(d->player, A_LASTSITE, tprintf("%s@%s", d->username, hostname)); } else { atr_add_raw(d->player, A_LASTSITE, hostname); } } StringCopyTrunc(d->addr, hostname, 50); d->addr[50] = '\0'; } } GSR_STRCHR_INC(p, host2, ' '); p[-1] = '\0'; addr = inet_addr(host2); if (addr == INADDR_NONE) { goto gsr_end; } /* now we're into the RFC 1413 ident reply */ GSR_SKIP_WHITESPACE(p); remote_port = 0; while (isdigit(*p)) { remote_port <<= 1; remote_port += (remote_port << 2) + (*p & 0x0f); ++p; } GSR_SKIP_WHITESPACE(p); GSR_REQUIRE_CHAR(p, ','); GSR_SKIP_WHITESPACE(p); /* skip the local port, making sure it consists of digits */ while (isdigit(*p)) { ++p; } GSR_SKIP_WHITESPACE(p); GSR_REQUIRE_CHAR(p, ':'); GSR_SKIP_WHITESPACE(p); /* identify the reply type */ if (strncmp(p, "USERID", 6)) { /* the other standard possibility here is "ERROR" */ goto gsr_end; } p += 6; GSR_SKIP_WHITESPACE(p); GSR_REQUIRE_CHAR(p, ':'); GSR_SKIP_WHITESPACE(p); /* don't include the trailing linefeed in the userid */ GSR_STRCHR_INC(userid, p, '\n'); userid[-1] = '\0'; /* go back and skip over the "OS [, charset] : " field */ GSR_STRCHR_INC(userid, p, ':'); GSR_SKIP_WHITESPACE(userid); for (d = descriptor_list; d; d = d->next) { if (ntohs((d->address).sin_port) != remote_port) continue; if ((d->address).sin_addr.s_addr != addr) continue; if (d->player != 0) { if (mudconf.use_hostname) { atr_add_raw(d->player, A_LASTSITE, tprintf("%s@%s", userid, hostname)); } else { atr_add_raw(d->player, A_LASTSITE, tprintf("%s@%s", userid, host2)); } } StringCopyTrunc(d->username, userid, 10); d->username[10] = '\0'; break; } gsr_end: free_lbuf(buf); return 0; } void boot_slave() { int sv[2]; int i; int maxfds; char *s; #ifdef HAVE_GETDTABLESIZE maxfds = getdtablesize(); #else maxfds = sysconf(_SC_OPEN_MAX); #endif if (slave_socket != -1) { close(slave_socket); slave_socket = -1; } if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) < 0) { return; } /* * set to nonblocking */ #ifdef FNDELAY if (fcntl(sv[0], F_SETFL, FNDELAY) == -1) { #else if (fcntl(sv[0], F_SETFL, O_NDELAY) == -1) { #endif close(sv[0]); close(sv[1]); return; } slave_pid = vfork(); switch (slave_pid) { case -1: close(sv[0]); close(sv[1]); return; case 0: /* * child */ close(sv[0]); if (dup2(sv[1], 0) == -1) { _exit(1); } if (dup2(sv[1], 1) == -1) { _exit(1); } for (i = 3; i < maxfds; ++i) { close(i); } s=(char *) XMALLOC(MBUF_SIZE, "boot_slave"); sprintf(s, "%s/slave", mudconf.binhome); execlp(s, "slave", NULL); XFREE(s,"boot_slave"); _exit(1); } close(sv[1]); #ifdef FNDELAY if (fcntl(sv[0], F_SETFL, FNDELAY) == -1) { #else if (fcntl(sv[0], F_SETFL, O_NDELAY) == -1) { #endif close(sv[0]); return; } slave_socket = sv[0]; STARTLOG(LOG_ALWAYS, "NET", "SLAVE") log_printf("DNS lookup slave started on fd %d", slave_socket); ENDLOG } int make_socket(port) int port; { int s, opt; struct sockaddr_in server; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { log_perror("NET", "FAIL", NULL, "creating master socket"); exit(3); } opt = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { log_perror("NET", "FAIL", NULL, "setsockopt"); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = (unsigned short)htons((unsigned short)port); if (!mudstate.restarting) if (bind(s, (struct sockaddr *)&server, sizeof(server))) { log_perror("NET", "FAIL", NULL, "bind"); close(s); exit(4); } listen(s, 5); return s; } void shovechars(port) int port; { fd_set input_set, output_set; struct timeval last_slice, current_time, next_slice, timeout, slice_timeout; int found, check; DESC *d, *dnext, *newd; int avail_descriptors, maxfds; struct stat fstatbuf; #define CheckInput(x) FD_ISSET(x, &input_set) #define CheckOutput(x) FD_ISSET(x, &output_set) mudstate.debug_cmd = (char *)"< shovechars >"; if (!mudstate.restarting) { sock = make_socket(port); } if (!mudstate.restarting) maxd = sock + 1; get_tod(&last_slice); #ifdef HAVE_GETDTABLESIZE maxfds = getdtablesize(); #else maxfds = sysconf(_SC_OPEN_MAX); #endif avail_descriptors = maxfds - 7; while (mudstate.shutdown_flag == 0) { get_tod(¤t_time); last_slice = update_quotas(last_slice, current_time); process_commands(); if (mudstate.shutdown_flag) break; /* We've gotten a signal to dump flatfiles */ if (mudstate.flatfile_flag && !mudstate.dumping) { if (*mudconf.dump_msg) raw_broadcast(0, "%s", mudconf.dump_msg); mudstate.dumping = 1; STARTLOG(LOG_DBSAVES, "DMP", "CHKPT") log_printf("Flatfiling: %s.#%d#", mudconf.gdbm, mudstate.epoch); ENDLOG dump_database_internal(DUMP_DB_FLATFILE); mudstate.dumping = 0; if (*mudconf.postdump_msg) raw_broadcast(0, "%s", mudconf.postdump_msg); mudstate.flatfile_flag = 0; } /* * test for events */ dispatch(); /* * any queued robot commands waiting? */ timeout.tv_sec = que_next(); timeout.tv_usec = 0; next_slice = msec_add(last_slice, mudconf.timeslice); slice_timeout = timeval_sub(next_slice, current_time); FD_ZERO(&input_set); FD_ZERO(&output_set); /* * Listen for new connections if there are free descriptors */ if (ndescriptors < avail_descriptors) { FD_SET(sock, &input_set); } /* * Listen for replies from the slave socket */ if (slave_socket != -1) { FD_SET(slave_socket, &input_set); } /* * Mark sockets that we want to test for change in status */ DESC_ITER_ALL(d) { if (!d->input_head) FD_SET(d->descriptor, &input_set); if (d->output_head) FD_SET(d->descriptor, &output_set); } /* * Wait for something to happen */ found = select(maxd, &input_set, &output_set, (fd_set *) NULL, &timeout); if (found < 0) { if (errno == EBADF) { /* This one is bad, as it results in a * spiral of doom, unless we can figure * out what the bad file descriptor is * and get rid of it. */ log_perror("NET", "FAIL", "checking for activity", "select"); DESC_ITER_ALL(d) { if (fstat(d->descriptor, &fstatbuf) < 0) { /* It's a player. Just toss the * connection. */ STARTLOG(LOG_PROBLEMS, "ERR", "EBADF") log_printf("Bad descriptor %d", d->descriptor); ENDLOG shutdownsock(d, R_SOCKDIED); } } if ((slave_socket == -1) || (fstat(slave_socket, &fstatbuf) < 0)) { /* Try to restart the slave, since * it presumably died. */ STARTLOG(LOG_PROBLEMS, "ERR", "EBADF") log_printf("Bad slave descriptor %d", slave_socket); ENDLOG boot_slave(); } if ((mudstate.sql_socket != -1) && (fstat(mudstate.sql_socket, &fstatbuf) < 0)) { /* Just mark it dead. */ STARTLOG(LOG_PROBLEMS, "ERR", "EBADF") log_printf("Bad SQL descriptor %d", mudstate.sql_socket); ENDLOG mudstate.sql_socket = -1; } if ((sock != -1) && (fstat(sock, &fstatbuf) < 0)) { /* That's it, game over. */ STARTLOG(LOG_PROBLEMS, "ERR", "EBADF") log_printf("Bad game port descriptor %d", sock); ENDLOG break; } } else if (errno != EINTR) { log_perror("NET", "FAIL", "checking for activity", "select"); } continue; } /* * if !found then time for robot commands */ if (!found) { if (mudconf.queue_chunk) do_top(mudconf.queue_chunk); continue; } else { do_top(mudconf.active_q_chunk); } /* * Get usernames and hostnames */ if (slave_socket != -1 && FD_ISSET(slave_socket, &input_set)) { while (get_slave_result() == 0) ; } /* * Check for new connection requests */ if (CheckInput(sock)) { newd = new_connection(sock); if (!newd) { check = (errno && (errno != EINTR) && (errno != EMFILE) && (errno != ENFILE)); if (check) { log_perror("NET", "FAIL", NULL, "new_connection"); } } else { if (newd->descriptor >= maxd) maxd = newd->descriptor + 1; } } /* * Check for activity on user sockets */ DESC_SAFEITER_ALL(d, dnext) { /* * Process input from sockets with pending input */ if (CheckInput(d->descriptor)) { /* * Undo AutoDark */ if (d->flags & DS_AUTODARK) { d->flags &= ~DS_AUTODARK; s_Flags(d->player, Flags(d->player) & ~DARK); } /* * Process received data */ #ifdef CONCENTRATE if (!(d->cstatus & C_REMOTE)) #endif if (!process_input(d)) { shutdownsock(d, R_SOCKDIED); continue; } } /* * Process output for sockets with pending output */ if (CheckOutput(d->descriptor)) { if (!process_output(d)) { #ifdef CONCENTRATE if (!(d->cstatus & C_CCONTROL)) #endif shutdownsock(d, R_SOCKDIED); } } } } } #ifdef BROKEN_GCC_PADDING char *inet_ntoa(in) struct in_addr in; { /* gcc 2.8.1 does not correctly pass/return structures which are smaller * than 16 bytes, but are not 8 bytes. Structures get padded at the * wrong end. gcc is consistent with itself, but if you try to link * with library files, there's a problem. This particularly affects * Irix 6, but also affects other 64-bit targets. * * There may be little/big-endian problems with this, too. */ static char buf[MBUF_SIZE]; long a = in.s_addr; sprintf(buf, "%d.%d.%d.%d", (int) ((a >> 24) & 0xff), (int) ((a >> 16) & 0xff), (int) ((a >> 8) & 0xff), (int) (a & 0xff)); return buf; } #endif /* BROKEN_GCC_PADDING */ DESC *new_connection(sock) int sock; { int newsock; char *buff, *cmdsave; DESC *d; struct sockaddr_in addr; int addr_len, len; char *buf; cmdsave = mudstate.debug_cmd; mudstate.debug_cmd = (char *)"< new_connection >"; addr_len = sizeof(struct sockaddr); newsock = accept(sock, (struct sockaddr *)&addr, &addr_len); if (newsock < 0) return 0; if (site_check(addr.sin_addr, mudstate.access_list) & H_FORBIDDEN) { STARTLOG(LOG_NET | LOG_SECURITY, "NET", "SITE") log_printf("[%d/%s] Connection refused. (Remote port %d)", newsock, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); ENDLOG fcache_rawdump(newsock, FC_CONN_SITE); shutdown(newsock, 2); close(newsock); errno = 0; d = NULL; } else { buff = alloc_mbuf("new_connection.address"); buf = alloc_lbuf("new_connection.write"); StringCopy(buff, inet_ntoa(addr.sin_addr)); /* * Ask slave process for host and username */ if ((slave_socket != -1) && mudconf.use_hostname) { sprintf(buf, "%s\n%s,%d,%d\n", inet_ntoa(addr.sin_addr), inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), mudconf.port); len = strlen(buf); if (WRITE(slave_socket, buf, len) < 0) { close(slave_socket); slave_socket = -1; } } free_lbuf(buf); STARTLOG(LOG_NET, "NET", "CONN") log_printf("[%d/%s] Connection opened (remote port %d)", newsock, buff, ntohs(addr.sin_port)); ENDLOG d = initializesock(newsock, &addr); mudstate.debug_cmd = cmdsave; free_mbuf(buff); } mudstate.debug_cmd = cmdsave; return (d); } /* * (Dis)connect reasons that get written to the logfile */ const char *conn_reasons[] = { "Unspecified", "Guest-connected to", "Created", "Connected to", "Dark-connected to", "Quit", "Inactivity Timeout", "Booted", "Remote Close or Net Failure", "Game Shutdown", "Login Retry Limit", "Logins Disabled", "Logout (Connection Not Dropped)", "Too Many Connected Players" }; /* * (Dis)connect reasons that get fed to A_A(DIS)CONNECT via * announce_connattr */ const char *conn_messages[] = { "unknown", "guest", "create", "connect", "cd", "quit", "timeout", "boot", "netdeath", "shutdown", "badlogin", "nologins", "logout" }; void shutdownsock(d, reason) DESC *d; int reason; { char *buff2; time_t now; int num, ncon; DESC *dtemp; if ((reason == R_LOGOUT) && (site_check((d->address).sin_addr, mudstate.access_list) & H_FORBIDDEN)) reason = R_QUIT; if (d->flags & DS_CONNECTED) { /* * Do the disconnect stuff if we aren't doing a LOGOUT * (which keeps the connection open so the player can * connect to a different character). */ if (reason != R_LOGOUT) { if (reason != R_SOCKDIED) { /* If the socket died, there's no reason to * display the quit file. */ fcache_dump(d, FC_QUIT); } STARTLOG(LOG_NET | LOG_LOGIN, "NET", "DISC") log_printf("[%d/%s] Logout by ", d->descriptor, d->addr); log_name(d->player); log_printf(" <%s: %d cmds, %d bytes in, %d bytes out, %d secs>", conn_reasons[reason], d->command_count, d->input_tot, d->output_tot, (int) (time(NULL) - d->connected_at)); ENDLOG } else { STARTLOG(LOG_NET | LOG_LOGIN, "NET", "LOGO") log_printf("[%d/%s] Logout by ", d->descriptor, d->addr); log_name(d->player); log_printf(" <%s: %d cmds, %d bytes in, %d bytes out, %d secs>", conn_reasons[reason], d->command_count, d->input_tot, d->output_tot, (int) (time(NULL) - d->connected_at)); ENDLOG } /* * If requested, write an accounting record of the form: * Plyr# Flags Cmds ConnTime Loc Money [Site] <DiscRsn> Name */ STARTLOG(LOG_ACCOUNTING, "DIS", "ACCT") now = mudstate.now - d->connected_at; buff2 = unparse_flags(GOD, d->player); log_printf("%d %s %d %d %d %d [%s] <%s> ", d->player, buff2, d->command_count, (int) now, Location(d->player), Pennies(d->player), d->addr, conn_reasons[reason]); log_name(d->player); free_sbuf(buff2); ENDLOG announce_disconnect(d->player, d, conn_messages[reason]); } else { if (reason == R_LOGOUT) reason = R_QUIT; STARTLOG(LOG_SECURITY | LOG_NET, "NET", "DISC") log_printf("[%d/%s] Connection closed, never connected. <Reason: %s>", d->descriptor, d->addr, conn_reasons[reason]); ENDLOG } process_output(d); clearstrings(d); if (reason == R_LOGOUT) { /* If this was our only connection, get out of interactive * mode. */ if (d->program_data) { ncon = 0; DESC_ITER_PLAYER(d->player, dtemp) ncon++; if (ncon == 0) { Free_RegData(d->program_data->wait_data); XFREE(d->program_data, "do_prog"); d->program_data = NULL; atr_clr(d->player, A_PROGCMD); } } d->flags &= ~DS_CONNECTED; d->connected_at = time(NULL); d->retries_left = mudconf.retry_limit; d->command_count = 0; d->timeout = mudconf.idle_timeout; d->player = 0; d->doing[0] = '\0'; d->quota = mudconf.cmd_quota_max; d->last_time = 0; d->host_info = site_check((d->address).sin_addr, mudstate.access_list) | site_check((d->address).sin_addr, mudstate.suspect_list); d->input_tot = d->input_size; d->output_tot = 0; welcome_user(d); } else { #ifdef CONCENTRATE if (!(d->cstatus & C_REMOTE)) { if (d->cstatus & C_CCONTROL) { register struct descriptor_data *k; for (k = descriptor_list; k; k = k->next) if (k->parent == d) shutdownsock(k, R_QUIT); } #endif shutdown(d->descriptor, 2); close(d->descriptor); #ifdef CONCENTRATE } else { register struct descriptor_data *k; for (k = descriptor_list; k; k = k->next) if (d->parent == k) send_killconcid(d); } #endif freeqs(d); *d->prev = d->next; if (d->next) d->next->prev = d->prev; /* * Is this desc still in interactive mode? */ if (d->program_data != NULL) { num = 0; DESC_ITER_PLAYER(d->player, dtemp) num++; if (num == 0) { Free_RegData(d->program_data->wait_data); XFREE(d->program_data, "do_prog"); d->program_data = NULL; atr_clr(d->player, A_PROGCMD); } } free_desc(d); #ifdef CONCENTRATE if (!(d->cstatus & C_REMOTE)) #endif ndescriptors--; } } void make_nonblocking(s) int s; { #ifdef HAVE_LINGER struct linger ling; #endif #ifdef FNDELAY if (fcntl(s, F_SETFL, FNDELAY) == -1) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl"); } #else if (fcntl(s, F_SETFL, O_NDELAY) == -1) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl"); } #endif #ifdef HAVE_LINGER ling.l_onoff = 0; ling.l_linger = 0; if (setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&ling, sizeof(ling)) < 0) { log_perror("NET", "FAIL", "linger", "setsockopt"); } #endif } DESC *initializesock(s, a) int s; struct sockaddr_in *a; { DESC *d; if (s == slave_socket) { /* Whoa. We shouldn't be allocating this. If we got this * descriptor, our connection with the slave must have * died somehow. We make sure to take note appropriately. */ STARTLOG(LOG_ALWAYS, "ERR", "SOCK") log_printf("Player descriptor clashes with slave fd %d", slave_socket); ENDLOG slave_socket = -1; } if (s == mudstate.sql_socket) { /* We shouldn't be allocating this either, for the same reason. */ STARTLOG(LOG_ALWAYS, "ERR", "SOCK") log_printf("Player descriptor clashes with SQL server fd %d", mudstate.sql_socket); ENDLOG mudstate.sql_socket = -1; } ndescriptors++; d = alloc_desc("init_sock"); d->descriptor = s; #ifdef CONCENTRATE d->concid = make_concid(); d->cstatus = 0; d->parent = 0; #endif d->flags = 0; d->connected_at = time(NULL); d->retries_left = mudconf.retry_limit; d->command_count = 0; d->timeout = mudconf.idle_timeout; d->host_info = site_check((*a).sin_addr, mudstate.access_list) | site_check((*a).sin_addr, mudstate.suspect_list); d->player = 0; /* * be sure #0 isn't wizard. Shouldn't be. */ d->addr[0] = '\0'; d->doing[0] = '\0'; d->username[0] = '\0'; make_nonblocking(s); d->output_prefix = NULL; d->output_suffix = NULL; d->output_size = 0; d->output_tot = 0; d->output_lost = 0; d->output_head = NULL; d->output_tail = NULL; d->input_head = NULL; d->input_tail = NULL; d->input_size = 0; d->input_tot = 0; d->input_lost = 0; d->raw_input = NULL; d->raw_input_at = NULL; d->quota = mudconf.cmd_quota_max; d->program_data = NULL; d->last_time = 0; d->address = *a; /* * added 5/3/90 SCG */ if (descriptor_list) descriptor_list->prev = &d->next; d->hashnext = NULL; d->next = descriptor_list; d->prev = &descriptor_list; StringCopyTrunc(d->addr, inet_ntoa(a->sin_addr), 50); descriptor_list = d; welcome_user(d); return d; } int process_output(d) DESC *d; { TBLOCK *tb, *save; int cnt; char *cmdsave; cmdsave = mudstate.debug_cmd; mudstate.debug_cmd = (char *)"< process_output >"; tb = d->output_head; #ifdef CONCENTRATE if (d->cstatus & C_REMOTE) { static char buf[10]; static char obuf[2048]; int buflen, k, j; sprintf(buf, "%d ", d->concid); buflen = strlen(buf); memcpy(obuf, buf, buflen); j = buflen; while (tb != NULL) { for (k = 0; k < tb->hdr.nchars; k++) { obuf[j++] = tb->hdr.start[k]; if (tb->hdr.start[k] == '\n') { if (d->parent) queue_write(d->parent, obuf, j); memcpy(obuf, buf, buflen); j = buflen; } } d->output_size -= tb->hdr.nchars; save = tb; tb = tb->hdr.nxt; XFREE(save, "queue_write"); d->output_head = tb; if (tb == NULL) d->output_tail = NULL; } if (j > buflen) queue_write(d, obuf + buflen, j - buflen); return 1; } else { #endif while (tb != NULL) { while (tb->hdr.nchars > 0) { cnt = WRITE(d->descriptor, tb->hdr.start, tb->hdr.nchars); if (cnt < 0) { mudstate.debug_cmd = cmdsave; if (errno == EWOULDBLOCK) return 1; return 0; } d->output_size -= cnt; tb->hdr.nchars -= cnt; tb->hdr.start += cnt; } save = tb; tb = tb->hdr.nxt; XFREE(save, "queue_write"); d->output_head = tb; if (tb == NULL) d->output_tail = NULL; } #ifdef CONCENTRATE } #endif mudstate.debug_cmd = cmdsave; return 1; } int process_input(d) DESC *d; { char *buf; int got, in, lost; char *p, *pend, *q, *qend; char *cmdsave; cmdsave = mudstate.debug_cmd; mudstate.debug_cmd = (char *)"< process_input >"; buf = alloc_lbuf("process_input.buf"); got = in = READ(d->descriptor, buf, LBUF_SIZE); if (got <= 0) { mudstate.debug_cmd = cmdsave; free_lbuf(buf); return 0; } if (!d->raw_input) { d->raw_input = (CBLK *) alloc_lbuf("process_input.raw"); d->raw_input_at = d->raw_input->cmd; } p = d->raw_input_at; pend = d->raw_input->cmd - sizeof(CBLKHDR) - 1 + LBUF_SIZE; lost = 0; for (q = buf, qend = buf + got; q < qend; q++) { if (*q == '\n') { *p = '\0'; if (p > d->raw_input->cmd) { save_command(d, d->raw_input); d->raw_input = (CBLK *) alloc_lbuf("process_input.raw"); p = d->raw_input_at = d->raw_input->cmd; pend = d->raw_input->cmd - sizeof(CBLKHDR) - 1 + LBUF_SIZE; } else { in -= 1; /* for newline */ } } else if ((*q == '\b') || (*q == 127)) { if (*q == 127) queue_string(d, "\b \b"); else queue_string(d, " \b"); in -= 2; if (p > d->raw_input->cmd) p--; if (p < d->raw_input_at) (d->raw_input_at)--; } else if (p < pend && isascii(*q) && isprint(*q)) { *p++ = *q; } else { in--; if (p >= pend) lost++; } } if (in < 0) /* backspace and delete by themselves */ in = 0; if (p > d->raw_input->cmd) { d->raw_input_at = p; } else { free_lbuf(d->raw_input); d->raw_input = NULL; d->raw_input_at = NULL; } d->input_tot += got; d->input_size += in; d->input_lost += lost; free_lbuf(buf); mudstate.debug_cmd = cmdsave; return 1; } void close_sockets(emergency, message) int emergency; char *message; { DESC *d, *dnext; DESC_SAFEITER_ALL(d, dnext) { if (emergency) { WRITE(d->descriptor, message, strlen(message)); if (shutdown(d->descriptor, 2) < 0) log_perror("NET", "FAIL", NULL, "shutdown"); close(d->descriptor); } else { queue_string(d, message); queue_write(d, "\r\n", 2); shutdownsock(d, R_GOING_DOWN); } } close(sock); } void NDECL(emergency_shutdown) { close_sockets(1, (char *)"Going down - Bye"); } /* --------------------------------------------------------------------------- * Print out stuff into error file. */ void NDECL(report) { STARTLOG(LOG_BUGS, "BUG", "INFO") log_printf("Command: '%s'", mudstate.debug_cmd); ENDLOG if (Good_obj(mudstate.curr_player)) { STARTLOG(LOG_BUGS, "BUG", "INFO") log_printf("Player: "); log_name_and_loc(mudstate.curr_player); if ((mudstate.curr_enactor != mudstate.curr_player) && Good_obj(mudstate.curr_enactor)) { log_printf(" Enactor: "); log_name_and_loc(mudstate.curr_enactor); } ENDLOG } } /* --------------------------------------------------------------------------- * * Signal handling routines. */ #ifndef SIGCHLD #define SIGCHLD SIGCLD #endif static RETSIGTYPE FDECL(sighandler, (int)); /* *INDENT-OFF* */ NAMETAB sigactions_nametab[] = { {(char *)"exit", 3, 0, SA_EXIT}, {(char *)"default", 1, 0, SA_DFLT}, { NULL, 0, 0, 0}}; /* *INDENT-ON* */ void NDECL(set_signals) { sigset_t sigs; /* We have to reset our signal mask, because of the possibility * that we triggered a restart on a SIGUSR1. If we did so, then * the signal became blocked, and stays blocked, since control * never returns to the caller; i.e., further attempts to send * a SIGUSR1 would fail. */ sigfillset(&sigs); sigprocmask(SIG_UNBLOCK, &sigs, NULL); signal(SIGALRM, sighandler); signal(SIGCHLD, sighandler); signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGQUIT, sighandler); signal(SIGTERM, sighandler); signal(SIGPIPE, SIG_IGN); signal(SIGUSR1, sighandler); signal(SIGUSR2, sighandler); signal(SIGTRAP, sighandler); #ifdef SIGXCPU signal(SIGXCPU, sighandler); #endif signal(SIGFPE, SIG_IGN); signal(SIGILL, sighandler); signal(SIGSEGV, sighandler); signal(SIGABRT, sighandler); #ifdef SIGFSZ signal(SIGXFSZ, sighandler); #endif #ifdef SIGEMT signal(SIGEMT, sighandler); #endif #ifdef SIGBUS signal(SIGBUS, sighandler); #endif #ifdef SIGSYS signal(SIGSYS, sighandler); #endif } static void unset_signals() { int i; for (i = 0; i < NSIG; i++) signal(i, SIG_DFL); } static void check_panicking(sig) int sig; { int i; /* * If we are panicking, turn off signal catching and resignal */ if (mudstate.panicking) { for (i = 0; i < NSIG; i++) signal(i, SIG_DFL); kill(getpid(), sig); } mudstate.panicking = 1; } void log_signal(signame) const char *signame; { STARTLOG(LOG_PROBLEMS, "SIG", "CATCH") log_printf("Caught signal %s", signame); ENDLOG } static RETSIGTYPE sighandler(sig) int sig; { #ifdef HAVE_SYS_SIGNAME #define signames sys_signame #else #ifdef SYS_SIGLIST_DECLARED #define signames sys_siglist #else static const char *signames[] = { "SIGZERO", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE", "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG", "SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD", "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGLOST", "SIGUSR1", "SIGUSR2"}; #endif /* SYS_SIGLIST_DECLARED */ #endif /* HAVE_SYS_SIGNAME */ int i; pid_t child; #if defined(HAVE_UNION_WAIT) && defined(NEED_WAIT3_DCL) union wait stat; #else int stat; #endif switch (sig) { case SIGUSR1: /* Normal restart now */ log_signal(signames[sig]); do_restart(GOD, GOD, 0); break; case SIGUSR2: /* Dump a flatfile soon */ mudstate.flatfile_flag = 1; break; case SIGALRM: /* Timer */ mudstate.alarm_triggered = 1; break; case SIGCHLD: /* Change in child status */ #ifndef SIGNAL_SIGCHLD_BRAINDAMAGE signal(SIGCHLD, sighandler); #endif while ((child = WAITOPT(&stat, WNOHANG)) > 0) { if (mudconf.fork_dump && mudstate.dumping && child == mudstate.dumper && (WIFEXITED(stat) || WIFSIGNALED(stat))) { mudstate.dumping = 0; mudstate.dumper = 0; } else if (child == slave_pid && (WIFEXITED(stat) || WIFSIGNALED(stat))) { slave_pid = 0; slave_socket = -1; } } break; case SIGHUP: /* Dump database soon */ log_signal(signames[sig]); mudstate.dump_counter = 0; break; case SIGINT: /* Log + ignore */ log_signal(signames[sig]); break; case SIGQUIT: /* Normal shutdown soon */ mudstate.shutdown_flag = 1; break; case SIGTERM: /* Killed shutdown now */ #ifdef SIGXCPU case SIGXCPU: #endif check_panicking(sig); log_signal(signames[sig]); raw_broadcast(0, "GAME: Caught signal %s, exiting.", signames[sig]); dump_database_internal(DUMP_DB_KILLED); exit(0); break; case SIGILL: /* Panic save + restart now, or coredump now */ case SIGFPE: case SIGSEGV: case SIGTRAP: #ifdef SIGXFSZ case SIGXFSZ: #endif #ifdef SIGEMT case SIGEMT: #endif #ifdef SIGBUS case SIGBUS: #endif #ifdef SIGSYS case SIGSYS: #endif check_panicking(sig); log_signal(signames[sig]); report(); if (mudconf.sig_action != SA_EXIT) { raw_broadcast(0, "GAME: Fatal signal %s caught, restarting with previous database.", signames[sig]); /* Don't sync first. Using older db. */ dump_database_internal(DUMP_DB_CRASH); CLOSE; if (slave_socket != -1) { shutdown(slave_socket, 2); close(slave_socket); slave_socket = -1; } if (slave_pid != 0) { kill(slave_pid, SIGKILL); } /* Try our best to dump a usable core by generating * a second signal with the SIG_DFL action. */ if (fork() > 0) { unset_signals(); /* In the parent process (easier to follow * with gdb), we're about to return from * this signal handler and hope that a * second signal is delivered. Meanwhile * let's close all our files to avoid * corrupting the child process. */ for (i = 0; i < maxd; i++) close(i); return; } alarm(0); dump_restart_db(); execl(mudconf.exec_path, mudconf.exec_path, (char *) "-c", mudconf.config_file, (char *) "-l", mudconf.mudlogname, (char *) "-p", mudconf.pid_file, (char *) "-t", mudconf.txthome, (char *) "-b", mudconf.binhome, (char *) "-d", mudconf.dbhome, (char *) "-g", mudconf.gdbm, (char *) "-k", mudconf.crashdb, NULL); break; } else { unset_signals(); fprintf(mainlog_fp, "ABORT! bsd.c, SA_EXIT requested.\n"); abort(); } case SIGABRT: /* Coredump now */ check_panicking(sig); log_signal(signames[sig]); report(); unset_signals(); fprintf(mainlog_fp, "ABORT! bsd.c, SIGABRT received.\n"); abort(); } signal(sig, sighandler); mudstate.panicking = 0; return; }