/* * bsd.c */ #include "copyright.h" #include "config.h" #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <sys/file.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <signal.h> #include <errno.h> #include "mudconf.h" #include "db.h" #include "file_c.h" #include "externs.h" #include "interface.h" #include "flags.h" #include "powers.h" #include "alloc.h" #include "command.h" #include "attrs.h" #include "rbtree.h" #include <errno.h> #include "logcache.h" #include "debug.h" #if SQL_SUPPORT #include "sqlchild.h" #endif extern void dispatch(void); extern char *silly_atr_get(dbref, int); struct event listen_sock_ev; #ifdef IPV6_SUPPORT struct event listen6_sock_ev; #endif int mux_bound_socket = -1; #ifdef IPV6_SUPPORT int mux_bound_socket6 = -1; #endif int ndescriptors = 0; DESC *descriptor_list = NULL; void mux_release_socket(); void make_nonblocking(int s); DESC *initializesock(int, struct sockaddr_storage *, int); DESC *new_connection(int); int process_input(DESC *); void accept_new_connection(int, short, void *); void bind_descriptor(DESC *d) { d->refcount++; #if 0 dprintk("%p bound, count %d", d, d->refcount); #endif } void release_descriptor(DESC *d) { d->refcount--; #if 0 dprintk("%p released, count %d", d, d->refcount); #endif if(d->refcount == 0) { dprintk("%p destructing", d); freeqs(d); /* * Is this desc still in interactive mode? */ if(d->program_data != NULL) { int num = 0; DESC *dtemp; DESC_ITER_PLAYER(d->player, dtemp) num++; if(num == 0) { for(int i = 0; i < MAX_GLOBAL_REGS; i++) { free_lbuf(d->program_data->wait_regs[i]); } free(d->program_data); } } free(d); } } void set_lastsite(DESC * d, char *lastsite) { char buf[LBUF_SIZE]; if(d->player) { if(!lastsite) lastsite = silly_atr_get(d->player, A_LASTSITE); strcpy(buf, lastsite); atr_add_raw(d->player, A_LASTSITE, buf); } } void shutdown_services() { dnschild_destruct(); flush_sockets(); #ifdef SQL_SUPPORT sqlchild_destruct(); #endif #ifdef ARBITRARY_LOGFILES logcache_destruct(); #endif event_loopexit(NULL); } int bind_mux_socket(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"); } if(fcntl(s, F_SETFD, FD_CLOEXEC) < 0) { log_perror("LOGCACHE", "FAIL", NULL, "fcntl(fd, F_SETFD, FD_CLOEXEC)"); close(s); abort(); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(port); if(bind(s, (struct sockaddr *) &server, sizeof(server))) { log_perror("NET", "FAIL", NULL, "bind"); close(s); exit(4); } dprintk("connection socket raised and bound, %d", s); listen(s, 5); return s; } void mux_release_socket() { dprintk("releasing mux main socket."); event_del(&listen_sock_ev); close(mux_bound_socket); mux_bound_socket = -1; #ifdef IPV6_SUPPORT event_del(&listen6_sock_ev); close(mux_bound_socket6); mux_bound_socket6 = -1; #endif } int eradicate_broken_fd(int fd) { struct stat statbuf; DESC *d; DESC_ITER_ALL(d) { if((fd && d->descriptor == fd) || (!fd && fstat(d->descriptor, &statbuf) < 0)) { /* An invalid player connection... eject, eject, eject. */ log_error(LOG_PROBLEMS, "ERR", "EBADF", "Broken descriptor %d for player #%d", d->descriptor, d->player); shutdownsock(d, R_SOCKDIED); } } if(mux_bound_socket != -1 && fstat(mux_bound_socket, &statbuf) < 0) { log_error(LOG_PROBLEMS, "ERR", "EBADF", "Broken descriptor on our main port."); mux_bound_socket = -1; return -1; } #ifdef IPV6_SUPPORT if(mux_bound_socket6 != -1 && fstat(mux_bound_socket6, &statbuf) < 0) { log_error(LOG_PROBLEMS, "ERR", "EBADF", "Broken descriptor for our ipv6 port."); mux_bound_socket6 = -1; return -1; } #endif return 0; } void accept_client_input(int fd, short event, void *arg) { DESC *connection = (DESC *) arg; if(connection->flags & DS_AUTODARK) { connection->flags &= ~DS_AUTODARK; s_Flags(connection->player, Flags(connection->player) & ~DARK); } if(!process_input(connection)) { shutdownsock(connection, R_SOCKDIED); } } void bsd_write_callback(struct bufferevent *bufev, void *arg) { } void bsd_read_callback(struct bufferevent *bufev, void *arg) { } void bsd_error_callback(struct bufferevent *bufev, short whut, void *arg) { dprintk("error %d", whut); } #ifndef HAVE_GETTIMEOFDAY #define get_tod(x) { (x)->tv_sec = time(NULL); (x)->tv_usec = 0; } #else #define get_tod(x) gettimeofday(x, (struct timezone *)0) #endif struct timeval queue_slice = { 0, 0 }; struct event queue_ev; struct timeval last_slice, current_time; void runqueues(int fd, short event, void *arg) { pid_t pchild; int status = 0; event_add(&queue_ev, &queue_slice); get_tod(¤t_time); last_slice = update_quotas(last_slice, current_time); pchild = waitpid(-1, &status, WNOHANG); if(pchild > 0) { dprintk("unexpected child %d exited with exit status %d.", pchild, WEXITSTATUS(status)); } if(mudconf.queue_chunk) do_top(mudconf.queue_chunk); } void shovechars(int port) { queue_slice.tv_sec = 0; queue_slice.tv_usec = mudconf.timeslice * 1000; mudstate.debug_cmd = (char *) "< shovechars >"; dprintk("shovechars starting, sock is %d.", mux_bound_socket); #ifdef IPV6_SUPPORT dprintk("shovechars starting, ipv6 sock is %d.", mux_bound_socket); #endif if(mux_bound_socket < 0) { mux_bound_socket = bind_mux_socket(port); } event_set(&listen_sock_ev, mux_bound_socket, EV_READ | EV_PERSIST, accept_new_connection, NULL); event_add(&listen_sock_ev, NULL); #ifdef IPV6_SUPPORT if(mux_bound_socket6 < 0) { mux_bound_socket6 = bind_mux6_socket(port); } event_set(&listen6_sock_ev, mux_bound_socket6, EV_READ | EV_PERSIST, accept_new6_connection, NULL); event_add(&listen6_sock_ev, NULL); #endif evtimer_set(&queue_ev, runqueues, NULL); evtimer_add(&queue_ev, &queue_slice); get_tod(&last_slice); get_tod(¤t_time); event_dispatch(); } void accept_new_connection(int sock, short event, void *arg) { int newsock, addr_len, len; char *buff, *buff1, *cmdsave; DESC *d; struct sockaddr_storage addr; char addrname[1024]; char addrport[32]; cmdsave = mudstate.debug_cmd; mudstate.debug_cmd = (char *) "< new_connection >"; addr_len = sizeof(struct sockaddr); newsock = accept(sock, (struct sockaddr *) &addr, (unsigned int *) &addr_len); if(newsock < 0) return; getnameinfo((struct sockaddr *) &addr, addr_len, addrname, 1024, addrport, 32, NI_NUMERICHOST | NI_NUMERICSERV); if(site_check(&addr, addr_len, mudstate.access_list) == H_FORBIDDEN) { log_error(LOG_NET | LOG_SECURITY, "NET", "SITE", "Connection refused from %s %s.", addrname, addrport); fcache_rawdump(newsock, FC_CONN_SITE); shutdown(newsock, 2); close(newsock); errno = 0; d = NULL; } else { log_error(LOG_NET, "NET", "CONN", "Connection opened from %s %s.", addrname, addrport); d = initializesock(newsock, &addr, addr_len); } mudstate.debug_cmd = cmdsave; return; } /* * Disconnect reasons that get written to the logfile */ static const char *disc_reasons[] = { "Unspecified", "Quit", "Inactivity Timeout", "Booted", "Remote Close or Net Failure", "Game Shutdown", "Login Retry Limit", "Logins Disabled", "Logout (Connection Not Dropped)", "Too Many Connected Players" }; /* * Disconnect reasons that get fed to A_ADISCONNECT via announce_disconnect */ static const char *disc_messages[] = { "unknown", "quit", "timeout", "boot", "netdeath", "shutdown", "badlogin", "nologins", "logout" }; void shutdownsock(DESC * d, int reason) { char *buff, *buff2; time_t now; int i, num; DESC *dtemp; if((reason == R_LOGOUT) && (site_check(&d->saddr, d->saddr_len, mudstate.access_list) == H_FORBIDDEN)) reason = R_QUIT; if(d->flags & DS_CONNECTED) { if(d->outstanding_dnschild_query) dnschild_kill(d->outstanding_dnschild_query); /* * 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) { fcache_dump(d, FC_QUIT); } log_error(LOG_NET | LOG_LOGIN, "NET", "DISC", "[%d/%s] Logout by %s(#%d), <Reason: %s>", d->descriptor, d->addr, Name(d->player), d->player, disc_reasons[reason]); /* * If requested, write an accounting record of the form: * * * * * * * Plyr# Flags Cmds ConnTime Loc Money [Site] * <DiscRsn> * * * Name */ log_error(LOG_ACCOUNTING, "DIS", "ACCT", "%d %s %d %d %d %d [%s] <%s> %s", d->player, decode_flags(GOD, Flags(d->player), Flags2(d->player), Flags3(d->player)), d->command_count, mudstate.now - d->connected_at, Location(d->player), Pennies(d->player), d->addr, disc_reasons[reason], Name(d->player)); announce_disconnect(d->player, d, disc_messages[reason]); } else { if(reason == R_LOGOUT) { reason = R_QUIT; log_error(LOG_SECURITY | LOG_NET, "NET", "DISC", "[%d/%s] Connection closed, never connected. <Reason: %s>", d->descriptor, d->addr, disc_reasons[reason]); } } clearstrings(d); if(reason == R_LOGOUT) { d->flags &= ~DS_CONNECTED; d->connected_at = mudstate.now; d->retries_left = mudconf.retry_limit; d->command_count = 0; d->timeout = mudconf.idle_timeout; d->player = 0; d->doing[0] = '\0'; d->hudkey[0] = '\0'; d->quota = mudconf.cmd_quota_max; d->last_time = 0; d->host_info = site_check(&d->saddr, d->saddr_len, mudstate.access_list) | site_check(&d->saddr, d->saddr_len, mudstate.suspect_list); d->input_tot = d->input_size; d->output_tot = 0; welcome_user(d); } else { event_del(&d->sock_ev); shutdown(d->descriptor, 2); close(d->descriptor); bufferevent_free(d->sock_buff); *d->prev = d->next; if(d->next) d->next->prev = d->prev; d->flags |= DS_DEAD; release_descriptor(d); ndescriptors--; } } void make_nonblocking(int s) { long flags = 0; if(fcntl(s, F_GETFL, &flags) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_GETFL"); } flags |= O_NONBLOCK; if(fcntl(s, F_SETFL, flags) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "fcntl F_SETFL"); } flags = 1; if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) { log_perror("NET", "FAIL", "make_nonblocking", "setsockopt NDELAY"); } } void make_blocking(int s) { long flags = 0; if(fcntl(s, F_GETFL, &flags) < 0) { log_perror("NET", "FAIL", "make_blocking", "fcntl F_GETFL"); } flags &= ~O_NONBLOCK; if(fcntl(s, F_SETFL, flags) < 0) { log_perror("NET", "FAIL", "make_blocking", "fcntl F_SETFL"); } flags = 0; if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)) < 0) { log_perror("NET", "FAIL", "make_blocking", "setsockopt NDELAY"); } } extern int fcache_conn_c; DESC *initializesock(int s, struct sockaddr_storage *saddr, int saddr_len) { DESC *d; ndescriptors++; d = malloc(sizeof(DESC)); memset(d, 0, sizeof(DESC)); d->descriptor = s; d->flags = 0; d->connected_at = mudstate.now; d->retries_left = mudconf.retry_limit; d->command_count = 0; d->timeout = mudconf.idle_timeout; d->host_info = site_check(saddr, saddr_len, mudstate.access_list) | site_check(saddr, saddr_len, mudstate.suspect_list); d->player = 0; /* * be sure #0 isn't wizard. Shouldn't be. */ d->chokes = 0; d->addr[0] = '\0'; d->doing[0] = '\0'; d->hudkey[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->input_size = 0; d->input_tot = 0; d->input_lost = 0; d->raw_input_at = (char *) d->input; memset(d->input, 0, sizeof(d->input)); d->quota = mudconf.cmd_quota_max; d->program_data = NULL; d->last_time = 0; memcpy(&d->saddr, saddr, saddr_len); d->saddr_len = saddr_len; d->refcount = 1; if(descriptor_list) descriptor_list->prev = &d->next; d->hashnext = NULL; d->next = descriptor_list; d->prev = &descriptor_list; getnameinfo((struct sockaddr *) saddr, saddr_len, d->addr, sizeof(d->addr), NULL, 0, NI_NUMERICHOST); descriptor_list = d; d->outstanding_dnschild_query = dnschild_request(d); d->sock_buff = bufferevent_new(d->descriptor, bsd_write_callback, bsd_read_callback, bsd_error_callback, NULL); bufferevent_disable(d->sock_buff, EV_READ); bufferevent_enable(d->sock_buff, EV_WRITE); event_set(&d->sock_ev, d->descriptor, EV_READ | EV_PERSIST, accept_client_input, d); event_add(&d->sock_ev, NULL); welcome_user(d); return d; } int process_input(DESC * d) { static char buf[LBUF_SIZE]; int got, in, lost; char *p, *pend, *q, *qend; char *cmdsave; cmdsave = mudstate.debug_cmd; mudstate.debug_cmd = (char *) "< process_input >"; got = in = read(d->descriptor, buf, (sizeof buf - 1)); if(got <= 0) { if(errno == EINTR) return 1; else if(errno == EAGAIN) return 1; else return 0; } bind_descriptor(d); if(Wizard(d->player) && strncmp("@segfault", buf, 9) == 0) { queue_string(d, "@segfault failed. (check logfile for reason.)\n"); *(char *) 0xDEADBEEF = '9'; } buf[got] = 0; if(!d->raw_input_at) d->raw_input_at = (char *) d->input; p = d->raw_input_at; pend = (char *) d->input + sizeof(d->input) - 1; lost = 0; for(q = buf, qend = buf + got; q < qend; q++) { if(*q == '\n') { *p = '\0'; if(p > (char *) d->input) { if(d->flags & DS_DEAD) { dprintk("bailing '%s' on dead descriptor %p for user #%d", d->input, d, d->player); break; } run_command(d, (char *) d->input); memset(d->input, 0, sizeof(d->input)); p = d->raw_input_at = (char *) d->input; pend = (char *) d->input + sizeof(d->input) - 1; } 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 > (char *) d->input) p--; if(p < d->raw_input_at) (d->raw_input_at)--; } else if(p < pend && ((isascii(*q) && isprint(*q)) || *q < 0)) { *p++ = *q; } else { in--; if(p >= pend) lost++; } } if(p > (char *) d->input) { d->raw_input_at = p; } d->input_tot += got; d->input_size += in; d->input_lost += lost; release_descriptor(d); mudstate.debug_cmd = cmdsave; return 1; } void flush_sockets() { int null = 0; DESC *d, *dnext; DESC_SAFEITER_ALL(d, dnext) { if(d->chokes) { #if TCP_CORK setsockopt(d->descriptor, IPPROTO_TCP, TCP_CORK, &null, sizeof(null)); #else #ifdef TCP_NOPUSH setsockopt(d->descriptor, IPPROTO_TCP, TCP_NOPUSH, &null, sizeof(null)); #endif #endif d->chokes = 0; } if(d->sock_buff && EVBUFFER_LENGTH(d->sock_buff->output)) { dprintk("sock %d for user #%d output evbuffer misalign: %d, totallen: %d, off: %d, pending %d.", (int) d->descriptor, (int) d->player, (int) d->sock_buff->output->misalign, (int) d->sock_buff->output->totallen, (int) d->sock_buff->output->off, EVBUFFER_LENGTH(d->sock_buff->output)); evbuffer_write(d->sock_buff->output, d->descriptor); } fsync(d->descriptor); } } void close_sockets(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"); dprintk("shutting down fd %d", d->descriptor); dprintk("output evbuffer misalign: %d, totallen: %d, off: %d", d->sock_buff->output->misalign, d->sock_buff->output->totallen, d->sock_buff->output->off); fsync(d->descriptor); if(d->outstanding_dnschild_query) dnschild_kill(d->outstanding_dnschild_query); event_loop(EVLOOP_ONCE); event_del(&d->sock_ev); bufferevent_free(d->sock_buff); close(d->descriptor); } else { queue_string(d, message); queue_write(d, "\r\n", 2); shutdownsock(d, R_GOING_DOWN); } } close(mux_bound_socket); event_del(&listen_sock_ev); } void emergency_shutdown(void) { close_sockets(1, (char *) "Going down - Bye.\n"); }