#define NOTIFY_TRACE /* This software is Copyright 1989, 1990, 1992, 1993 by various individuals. Please see the accompanying file COPYRIGHT for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* extern XXX */ char **argvec; #define WRITEV_MAXCNT 16 /* this should be in ... */ #include "db.h" #include "defs.h" #include "ctype.h" #include "match.h" #include "prims.h" #include "utils.h" #include "config.h" #include "params.h" #include "random.h" #include "externs.h" #include "strings.h" #include "property.h" #include "hostname.h" #include "username.h" #include "interface.h" #include "unused-arg.h" #define TN_IAC 255 #define TN_DONT 254 #define TN_DO 253 #define TN_WONT 252 #define TN_WILL 251 #define TN_SB 250 #define TN_SE 240 #ifdef MUD_ID #include #endif #if defined(HOSTNAMES) && !defined(SYNC_HOSTNAMES) #define ASYNC_HOSTNAMES #endif #if !defined(HOSTNAMES) #undef SYNC_HOSTNAMES #undef ASYNC_HOSTNAMES #endif #if defined(USERNAMES) && !defined(SYNC_USERNAMES) #define ASYNC_USERNAMES #endif #if !defined(USERNAMES) #undef SYNC_USERNAMES #undef ASYNC_USERNAMES #endif static void createpidfile(void); static void serverisalive(int); void do_setuid(void); extern int alarm_block, alarm_triggered; extern void time_keeper(void); static int may_announce_status = 0; static int no_output_flushed = 0; /* for debugger patching */ static char *infile = 0; static char *dumpfile = 0; static char *portstr = 0; static char *stderrfile = 0; static int liststrings = 0; #ifdef CMDRING_SIZE static char cmdring_buf[CMDRING_SIZE]; static unsigned int cmdring_head; static unsigned int cmdring_size; #endif #ifdef __linux__ /* Goddam Linux just _has_ to be incompatible - has no sa_len or sin_len. */ #define NO_SOCKADDR_LEN #endif /* * next two subroutines grabbed from mjr's Ubermud * * Put here by WOZ, because he thought it was a * Pretty Neat Trick (tm) and liked being able to see if the server * was still up w/o having to log in. */ /* this is a bizarre little hack. if the server catches a SIGUSR2 ,it will attempt to unlink the file. this is so that a program can test the aliveness of the server, by creating the file, signalling the server, and seeing if the file is still there. the choice to remove the file rather than to create it is because of the possibility of there being a dearth of file descriptors. it does not take an fd to remove a file. */ static void serverisalive(UNUSED_ARG(int sig)) { unlink("server_lives"); log_status("PING: caught."); signal(SIGUSR2, serverisalive); } /* create a file in the server directory with the current process id in it. this is also generally useful, costs nothing, etc. */ /* static */ void createpidfile() { FILE *ff; ff = fopen("server_pid","w"); if (ff == 0) { log_status("PID: cannot open pid file."); return; } fprintf(ff,"%d\n",getpid()); fclose(ff); } #ifdef NOLOGINS /* hack to allow only wizards to log in if NOLOGIN_FILE exists in GAMEDIR, spitting the contents of the NOLOGIN_FILE to non wizards who attempt to connect */ static int no_login (struct descriptor_data *d, dbref player) { int handle; char buf[BUFFER_LEN]; FILE *f; if (Wizard(player)) return(0); handle = open(NOLOGIN_FILE,O_RDONLY,0); if (handle >= 0) { f = fdopen(handle,"r"); if (f) { while (fgets(&buf[0],sizeof(buf),f)) { int n; n = strlen(&buf[0]); if (n && (buf[n-1] == '\0')) n --; queue_write(d,&buf[0],n); queue_write(d,"\r\n",2); } fclose(f); } else { close(handle); } return(1); } else { return(0); } } #endif /*NOLOGINS*/ int shutdown_flag = 0; time_t time_started; extern time_t last_checkpoint; #define CONNECT_FAIL \ "That player either does not exist or has a different password.\r\n" #ifndef REGISTRATION #define CREATE_FAIL \ "Either there is already a player with that name, \ or that name is illegal.\r\n" #endif /* REGISTRATION */ #define FLUSHED_MESSAGE "\r\n" #define SHUTDOWN_MESSAGE "\r\nGoing down - Bye\r\n" #define CRASHING_MESSAGE "\r\nGoing down in flames - Bye\r\n" typedef struct listensocket LISTENSOCKET; struct listensocket { int s; struct sockaddr *listen_sa; int listen_sa_len; int nautocmds; char **autocmds; } ; static LISTENSOCKET **sockets; static int nsockets; static int maxdesc; int big_fat_descripto_lock = 0; const char *shutdown_message = SHUTDOWN_MESSAGE; const char *newcon_warn = 0; struct descriptor_data *descriptor_list = 0; void process_commands(void); void shovechars(void); void make_nonblocking(int); void freeqs(struct descriptor_data *); void welcome_user(struct descriptor_data *); void con_connect(struct descriptor_data *, const char *, const char *); void con_create(struct descriptor_data *, const char *, const char *); void close_sockets(void); int boot_off(dbref); void dump_to_desc(void *, const char *); void dump_to_player(void *, const char *); void dump_descriptors(dbref, const char *); void set_signals(void); struct descriptor_data *new_connection(int); void parse_connect (const char *, char *, char *, char *); void set_userstring (char **, const char *); int do_command (struct descriptor_data *, char *); char *strsave (const char *); int process_input(struct descriptor_data *); void bailout(int); void announce_connect(dbref); void announce_disconnect(dbref); #ifdef BUILTIN_WHO void dump_users(dbref, const char *, void (*)(void *, const char *), void *); #endif #define MALLOC(result, type, number) \ if (!((result) = (type *) malloc ((number) * sizeof (type)))) \ panic("Out of memory"); \ static void sigshutdown(int sig) { log_status("SHUTDOWN: on signal %d",sig); shutdown_flag = 1; } static void cmdring_init(void) { #ifdef CMDRING_SIZE bzero(&cmdring_buf[0],sizeof(cmdring_buf)); cmdring_head = 0; cmdring_size = 0; #endif } #ifdef CMDRING_SIZE static void cmdring_stash_str(const char *s) { unsigned int t; t = strlen(s); if (cmdring_head+t > CMDRING_SIZE) { bcopy(s,&cmdring_buf[cmdring_head],CMDRING_SIZE-cmdring_head); bcopy(s+CMDRING_SIZE-cmdring_head,&cmdring_buf[0],t-(CMDRING_SIZE-cmdring_head)); } else { bcopy(s,&cmdring_buf[cmdring_head],t); } cmdring_head = (cmdring_head + t) % CMDRING_SIZE; cmdring_size += t; if (cmdring_size > CMDRING_SIZE) cmdring_size = CMDRING_SIZE; } #endif void cmdring_dump(int fd) { #ifdef CMDRING_SIZE unsigned int t; t = (cmdring_head + CMDRING_SIZE - cmdring_size) % CMDRING_SIZE; if (t >= cmdring_head) { write(fd,&cmdring_buf[t],CMDRING_SIZE-t); write(fd,&cmdring_buf[0],cmdring_head); } else { write(fd,&cmdring_buf[t],cmdring_size); } #endif } #ifdef CMDRING_SIZE static void cmdring_stash(struct descriptor_data *d, const char *command) { char tmp[64]; if (d->connected) { sprintf(&tmp[0],"%d (%d in %d)",d->descriptor,(int)d->player,(int)DBFETCH(d->player)->location); } else { sprintf(&tmp[0],"%d (unconnected)",d->descriptor); } cmdring_stash_str(&tmp[0]); cmdring_stash_str(command); cmdring_stash_str("\n"); } #endif #ifdef LOCKOUT static int userhostmatch(const char *u, const char *h, const char *l) { #ifdef USERNAMES int ulen; int hlen; int llen; if (!strcmp(h,l)) return(1); ulen = strlen(u); hlen = strlen(h); llen = strlen(l); if ( (llen == hlen+2) && (l[0] == '*') && (l[1] == '@') && !bcmp(l+2,h,hlen) ) return(1); if ( (llen == ulen+2) && !bcmp(l,u,ulen) && (l[ulen] == '@') && (l[ulen+1] == '*') ) return(1); if ( (llen == ulen+1+hlen) && !bcmp(l,u,ulen) && (l[ulen] == '@') && !bcmp(l+ulen+1,h,hlen) ) return(1); return(0); #else u=u; return(!strcmp(h,l)); #endif } #endif static int spit_file_d(const char *file, struct descriptor_data *d) { FILE *f; char buf[BUFFER_LEN]; f = fopen(file,"r"); if (f) { while (fgets(buf,sizeof(buf),f)) { buf[strlen(buf)-1] = '\0'; queue_string(d,buf); queue_write(d,"\r\n",2); } process_output(d); fclose(f); return(0); } else { return(1); } } #ifdef LOCKOUT static int forbidden_site(struct descriptor_data *d) { char buf[512]; char *bp; FILE *fp; int i; int neg; char *at; char *spc; fp = fopen(LOCKOUT_FILE,"r"); if (fp == 0) return(0); while (fgets(&buf[0],sizeof(buf),fp) == &buf[0]) { if (buf[0] == '#') continue; i = strlen(&buf[0]); if ((i > 0) && (buf[i-1] == '\n')) buf[--i] = '\0'; bp = &buf[0]; neg = 0; if (*bp == '!') { bp ++; neg = 1; } at = index(bp,'@'); spc = index(at?at:bp,' '); if (spc) { *spc = '\0'; do spc++; while (*spc == ' '); if (!*spc) spc = 0; } if (d->usernamewait || d->hostnamewait) panic("checking un-obtained strings"); if (userhostmatch(d->user.text,d->host.text,bp)) { fclose(fp); if (spc && spit_file_d(spc,d)) { log_status("spit_file_d %s for locked out desc %d failed",spc,d->descriptor); } return(!neg); } } fclose(fp); return(0); } #endif static void check_lockout(struct descriptor_data *d) { #ifdef LOCKOUT if (forbidden_site(d)) { log_status("LOCKED OUT: connection from %s@%s on descriptor %d",d->user.text,d->host.text,d->descriptor); shutdownsock(d); close(d->descriptor); d->descriptor = -1; } #endif } static void check_waits(struct descriptor_data *d) { d->waiting = d->hostnamewait || d->usernamewait; if (d->waiting) return; check_lockout(d); if (! d->booted) welcome_user(d); } static int addrs_match( const struct sockaddr_storage *a, const struct sockaddr_storage *b, int len, int doport ) { if (a->ss_family != b->ss_family) return(0); switch (a->ss_family) { case AF_INET: if (len != sizeof(struct sockaddr_in)) return(0); if ( ((const struct sockaddr_in *)a)->sin_addr.s_addr != ((const struct sockaddr_in *)b)->sin_addr.s_addr ) return(0); if ( doport && ( ((const struct sockaddr_in *)a)->sin_port != ((const struct sockaddr_in *)b)->sin_port ) ) return(0); return(1); break; case AF_INET6: if (len != sizeof(struct sockaddr_in6)) return(0); if (bcmp( &((const struct sockaddr_in6 *)a)->sin6_addr, &((const struct sockaddr_in6 *)b)->sin6_addr, sizeof(((const struct sockaddr_in6 *)b)->sin6_addr) )) return(0); if ( doport && ( ((const struct sockaddr_in6 *)a)->sin6_port != ((const struct sockaddr_in6 *)b)->sin6_port ) ) return(0); return(1); break; } panic("addrs_match: bad AF"); } static const char *ssaddr_to_text(const struct sockaddr_storage *ss, int sslen) { static char rbuf[256]; switch (ss->ss_family) { case AF_INET: if (sslen != sizeof(struct sockaddr_in)) panic("ssaddr_to_text: length wrong"); return(inet_ntop(AF_INET,&((const struct sockaddr_in *)ss)->sin_addr,&rbuf[0],sizeof(rbuf))); break; case AF_INET6: if (sslen != sizeof(struct sockaddr_in6)) panic("ssaddr_to_text: length wrong"); return(inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)ss)->sin6_addr,&rbuf[0],sizeof(rbuf))); break; } panic("ssaddr_to_text: bad AF"); } #ifdef ASYNC_HOSTNAMES static void process_hostname_output(void) { #define PRESTRING (sizeof(int) + sizeof(struct sockaddr_storage)) char resp[PRESTRING+1]; char name[256]; int len; struct descriptor_data *d; int sslen; struct sockaddr_storage ss; if (Read(hostname_fd,&resp[0],sizeof(resp)) == 0) { len = resp[PRESTRING]; if ((len < 0) || (len > 255)) panic("HOSTNAME: impossible string length in response"); if (Read(hostname_fd,&name[0],len) == 0) { name[len] = '\0'; bcopy(&resp[0],&sslen,sizeof(int)); if ((sslen < 1) || (sslen > sizeof(struct sockaddr_storage))) { panic("HOSTNAME: impossible address length in response"); } bcopy(&resp[sizeof(int)],&ss,sizeof(struct sockaddr_storage)); for (d=descriptor_list;d;d=d->flink) { if ( d->hostnamewait && (d->host.addr.len == sslen) && addrs_match(&ss,d->host.addr.ss,sslen,0) ) { const char *numeric; char *text; numeric = ssaddr_to_text(d->host.addr.ss,d->host.addr.len); if (len > 0) { text = dup_string(&name[0]); log_status("HOSTNAME: %s -> %s for descriptor %d",numeric,text,d->descriptor); } else { text = dup_string(numeric); log_status("HOSTNAME: %s failed for descriptor %d",numeric,d->descriptor); } free(d->host.addr.ss); d->host.text = text; d->hostnamewait = 0; check_waits(d); } } } } #undef PRESTRING } #endif #ifdef ASYNC_USERNAMES static void process_username_output(void) { #define PRESTRING (sizeof(int) + sizeof(struct sockaddr_storage [2])) char resp[PRESTRING+1]; char name[256]; int len; struct descriptor_data *d; int sslen; struct sockaddr_storage ss[2]; if (Read(username_fd,&resp[0],sizeof(resp)) == 0) { len = resp[PRESTRING]; if ((len < 0) || (len > 255)) panic("USERNAME: impossible string length in response"); if (Read(username_fd,&name[0],len) == 0) { name[len] = '\0'; bcopy(&resp[0],&sslen,sizeof(sslen)); if ((sslen < 1) || (sslen > sizeof(struct sockaddr_storage))) { panic("USERNAME: impossible address length in response"); } bcopy(&resp[sizeof(sslen)],&ss[0],sizeof(ss)); for (d=descriptor_list;d;d=d->flink) { if ( d->usernamewait && (d->user.addr.len == sslen) && addrs_match(&ss[0],&(*d->user.addr.ss)[0],sslen,1) && addrs_match(&ss[1],&(*d->user.addr.ss)[1],sslen,1) ) { free(d->user.addr.ss); d->user.text = dup_string(&name[0]); log_status("USERNAME: %s for descriptor %d",d->user.text,d->descriptor); d->usernamewait = 0; check_waits(d); } } } } } #endif static int handleargs(int ac, char **av) { if (!strcmp(av[1],"-strings") && (ac == 3)) { liststrings = 1; infile = av[2]; return(0); } if (ac != 4) return(1); infile = av[1]; dumpfile = av[2]; portstr = av[3]; return(0); } int main(int, char **); int main(int argc, char **argv) { argvec = argv; /* XXX */ if (handleargs(argc,argv)) { fprintf(stderr,"Usage: %s infile dumpfile port[/port]\n",argvec[0]); fprintf(stderr," or: %s -strings infile\n",argvec[0]); exit(1); } #ifdef MMMALLOC if (! liststrings) { /* FILE *f; f = fopen("logs/malstats","w"); mal_setstatsfile(f); mal_trace(1); mal_sbrkset(65536); */ } #endif if (! liststrings) { #ifdef ASYNC_HOSTNAMES fork_hostname_process(); #endif #ifdef ASYNC_USERNAMES fork_username_process(); #endif } time_started = curtm(); last_checkpoint = curtm(); if (stderrfile && strcmp(stderrfile,"-")) { freopen(stderrfile,"w",stderr); setbuf(stderr,0); } if (! liststrings) log_status("INIT: TinyMUCK %s starting.","version"); init_str(liststrings); if (init_game(infile,dumpfile,liststrings) < 0) { fprintf(stderr,"Couldn't load %s!\n",infile); exit(2); } if (liststrings) { dump_strings(); exit(0); } may_announce_status = 1; set_signals(); #ifdef MUD_ID do_setuid(); #endif cmdring_init(); /* set the file creation mask so no one snarfs the db */ umask(077); /* go do it */ #ifndef AUTODEBUG shovechars(); close_sockets(); #else while(1) sleep(30); #endif dump_database(); free_game(); malloc_leakcheck(); exit(0); } void set_signals(void) { struct sigaction sa; /* we don't care about SIGPIPE, we notice it in select() and write() */ signal(SIGPIPE,SIG_IGN); /* standard termination signals */ signal(SIGINT,sigshutdown); sa.sa_handler = &bailout; sigemptyset(&sa.sa_mask); sa.sa_flags = 0 /*SA_ONSTACK*/; sigaction(SIGTERM,&sa,0); /* catch these because we might as well */ sigaction(SIGQUIT,&sa,0); sigaction(SIGTRAP,&sa,0); sigaction(SIGIOT,&sa,0); #ifdef SIGEMT sigaction(SIGEMT,&sa,0); #endif sigaction(SIGFPE,&sa,0); sigaction(SIGBUS,&sa,0); sigaction(SIGSEGV,&sa,0); sigaction(SIGSYS,&sa,0); sigaction(SIGTERM,&sa,0); sigaction(SIGXCPU,&sa,0); sigaction(SIGXFSZ,&sa,0); sigaction(SIGVTALRM,&sa,0); /* ubermud like checkserver support */ signal(SIGUSR2,&serverisalive); } static int notify_aux(dbref player, const char *msg) { struct descriptor_data *d; int retval; if (player == NOTHING) return(0); retval = 0; for (d=descriptor_list;d;d=d->flink) { if (d->connected && (d->player == player)) { queue_string(d,msg); retval ++; } } return(retval); } #ifdef NOTIFY_TRACE dbref last_notify_player[32]; const char *last_notify_msg[32]; unsigned long int last_notify_point; #define SETPOINT(n) do { last_notify_point |= 1 << (n); last_notify_player[(n)] = player; last_notify_msg[(n)] = msg; } while (0) #else #define SETPOINT(n) do { } while (0) #endif int notify(dbref player, const char *msg) { #ifdef NOTIFY_TRACE last_notify_point = 0; #endif SETPOINT(0); if (player == NOTHING) return(0); SETPOINT(1); SETPOINT(2); switch (Typeof(player)) { case TYPE_DAEMON: SETPOINT(3); player = OWNER(player); SETPOINT(4); /* fall through */ case TYPE_PLAYER: SETPOINT(5); if (notify_aux(player,msg)) { int rv; SETPOINT(6); rv = notify_aux(player,"\r\n"); SETPOINT(7); return(rv); } SETPOINT(8); break; } SETPOINT(9); return(0); } int notify_str(dbref player, STR s) { char *t; int rv; t = fetch_str(s); rv = notify(player,t); free(t); return(rv); } int notify_nnl(dbref player, const char *msg) { if (player == NOTHING) return(0); switch (Typeof(player)) { case TYPE_DAEMON: player = OWNER(player); /* fall through */ case TYPE_PLAYER: return(notify_aux(player,msg)); break; } return(0); } #if 0 /* unused now */ static struct timeval timeval_sub(struct timeval now, struct timeval then) { now.tv_sec -= then.tv_sec; if (then.tv_usec > now.tv_usec) { now.tv_sec --; now.tv_usec += 1000000 - then.tv_usec; } else { now.tv_usec -= then.tv_usec; } return(now); } #endif static int msec_diff(struct timeval now, struct timeval then) { int dsec; int dusec; if (now.tv_sec > then.tv_sec) { dsec = now.tv_sec - then.tv_sec; } else { dsec = -(int)(then.tv_sec - now.tv_sec); } if (now.tv_usec > then.tv_usec) { dusec = now.tv_usec - then.tv_usec; } else { dusec = -(int)(then.tv_usec - now.tv_usec); } return((dsec*1000)+(dusec/1000)); } static struct timeval msec_add(struct timeval t, int x) { t.tv_sec += x / 1000; t.tv_usec += (x % 1000) * 1000; if (t.tv_usec >= 1000000) { t.tv_sec += t.tv_usec / 1000000; t.tv_usec = t.tv_usec % 1000000; } return t; } static struct timeval update_quotas(struct timeval last, struct timeval current) { int nslices; int cmds_per_time; struct descriptor_data *d; nslices = msec_diff (current, last) / COMMAND_TIME_MSEC; if (nslices > 0) { for (d = descriptor_list; d; d = d -> flink) { if (d -> connected) cmds_per_time = ((FLAGS(d->player) & INTERACTIVE) ? COMMAND_BURST_SIZE : COMMANDS_PER_TIME); else cmds_per_time = COMMANDS_PER_TIME; d -> quota += cmds_per_time * nslices; if (d -> quota > COMMAND_BURST_SIZE) d -> quota = COMMAND_BURST_SIZE; } } return msec_add (last, nslices * COMMAND_TIME_MSEC); } void newcon_warn_set(const char *msg) { newcon_warn = msg; } static int accept_a_conn(struct pollfd *pv) { struct descriptor_data *d; int i; LISTENSOCKET *s; d = 0; for (i=0;is); if (d) { int a; for (a=0;anautocmds;a++) { if (! do_command(d,s->autocmds[a])) { shutdownsock(d); return(1); } } break; } } } if (! d) return(0); if (newcon_warn && !d->booted) { queue_string(d,newcon_warn); queue_write(d,"\r\n",2); queue_string(d,"Anything you type will be handled once this is finished.\r\n"); queue_string(d,"Please be patient...\r\n"); } return(1); } void newcon_check(void) { struct descriptor_data *d; int i; int n; if (! newcon_warn) return; { struct pollfd pfds[nsockets]; do { for (i=0;is; pfds[i].events = POLLIN | POLLRDNORM; } if (poll(&pfds[0],nsockets,0) < 0) return; } while (accept_a_conn(&pfds[0])); } n = 0; for (d=descriptor_list;d;d=d->flink) { if (d->booted) continue; if (d->output.head) n ++; } { struct pollfd pfds[n]; i = 0; for (d=descriptor_list;d;d=d->flink) { if (d->booted) continue; if (d->output.head) { if (i >= n) panic("impossible table overflow"); pfds[i].fd = d->descriptor; pfds[i].events = POLLOUT | POLLWRNORM; i ++; } } if (i != n) panic("impossible table underflow"); if (poll(&pfds[0],n,0) < 0) return; i = 0; for (d=descriptor_list;d;d=d->flink) { if (d->booted) continue; if (d->output.head && (pfds[i].revents & (POLLOUT|POLLWRNORM|POLLERR|POLLHUP))) { process_output(d); } i ++; } } } static void add_socket(LISTENSOCKET *s) { int sock; int opt; sock = socket(s->listen_sa->sa_family,SOCK_STREAM,0); if (sock < 0) { fprintf(stderr,"%s: socket: %s\n",argvec[0],strerror(errno)); exit(1); } opt = 1; if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void *)&opt,sizeof(opt)) < 0) { fprintf(stderr,"%s: setsockopt SO_REUSEADDR: %s\n",argvec[0],strerror(errno)); exit(1); } if (bind(sock,(void *)s->listen_sa,s->listen_sa_len) < 0) { int err; err = errno; fprintf(stderr,"%s: bind: %s\n",argvec[0],strerror(err)); log_status("bind: %s",strerror(err)); exit(1); } listen(sock,10); s->s = sock; sockets = realloc(sockets,(nsockets+1)*sizeof(LISTENSOCKET *)); sockets[nsockets++] = s; } static void add_autocmd(LISTENSOCKET *s, const char *cmd) { s->autocmds = realloc(s->autocmds,(s->nautocmds+1)*sizeof(char *)); s->autocmds[s->nautocmds++] = dup_string(cmd); } static void sethints(struct addrinfo *hints) { hints->ai_flags = AI_PASSIVE; hints->ai_family = AF_UNSPEC; hints->ai_socktype = SOCK_STREAM; hints->ai_protocol = 0; hints->ai_addrlen = 0; hints->ai_addr = 0; hints->ai_canonname = 0; hints->ai_next = 0; } static void setup_sockets_compat(void) { char *psp; char *psp2; int telnet_y; struct addrinfo *ai0; struct addrinfo *ai; struct addrinfo hints; char *ps; int l; int rv; telnet_y = 0; psp = portstr; while (*psp) { switch (*psp) { case ',': psp ++; continue; break; case '/': psp ++; telnet_y = 1; continue; break; } strtol(psp,&psp2,0); if (psp2 == psp) { fprintf(stderr,"%s: invalid port string %s\n",argvec[0],portstr); exit(1); } l = psp2 - psp; ps = malloc(l+1); bcopy(psp,ps,l); ps[l] = '\0'; psp = psp2; sethints(&hints); hints.ai_flags |= AI_NUMERICSERV; rv = getaddrinfo(0,ps,&hints,&ai0); if (rv) { fprintf(stderr,"%s: invalid port number %s in port string %s\n",argvec[0],ps,portstr); exit(1); } if (! ai0) { fprintf(stderr,"%s: successful lookup but no addresses?!\n",argvec[0]); exit(1); } free(ps); for (ai=ai0;ai;ai=ai->ai_next) { LISTENSOCKET *s; s = malloc(sizeof(LISTENSOCKET)); s->listen_sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,s->listen_sa,ai->ai_addrlen); s->nautocmds = 0; s->autocmds = 0; s->listen_sa_len = ai->ai_addrlen; if (telnet_y) add_autocmd(s,"TELNET Y"); add_socket(s); } freeaddrinfo(ai0); } } static int add_sockets_for_ai(struct addrinfo *ai) { int n; LISTENSOCKET *s; n = 0; for (;ai;ai=ai->ai_next) { s = malloc(sizeof(LISTENSOCKET)); s->listen_sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,s->listen_sa,ai->ai_addrlen); s->nautocmds = 0; s->autocmds = 0; s->listen_sa_len = ai->ai_addrlen; add_socket(s); n ++; } return(n); } static void setup_sockets_config(void) { FILE *f; char line[512]; int base; int n; char *lp; char *cp; int l; int lno; struct addrinfo *ai0; struct addrinfo hints; int rv; auto void err(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); auto void err(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: \"%s\", line %d: ",argvec[0],portstr,lno); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } auto void warn(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void warn(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: \"%s\", line %d: ",argvec[0],portstr,lno); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); } f = fopen(portstr,"r"); base = -1; lno = 0; while (fgets(&line[0],sizeof(line),f)) { lno ++; l = strlen(&line[0]); if ((l > 0) && (line[l-1] == '\n')) line[l-1] = '\0'; lp = &line[0]; while (*lp && Cisspace(*lp)) lp++; if (*lp == '#') continue; if (! *lp) { base = -1; continue; } cp = lp; do lp++; while (*lp && !Cisspace(*lp)); l = lp - cp; if ((l == 4) && !bcmp(cp,"port",4)) { if (base >= 0) err("`port' command not at beginning of stanza"); while (*lp && Cisspace(*lp)) lp ++; strtol(lp,&cp,0); if (cp == lp) err("missing/unrecognizable port number"); l = cp - lp; while (*cp && Cisspace(*cp)) cp ++; if (*cp && (*cp != '#')) err("junk after port number"); lp[l] = '\0'; sethints(&hints); rv = getaddrinfo(0,lp,&hints,&ai0); if (rv) { err("%s",gai_strerror(rv)); } if (! ai0) { err("successful lookup but no addresses?!"); } base = nsockets; n = add_sockets_for_ai(ai0); freeaddrinfo(ai0); } else if ((l == 6) && !bcmp(cp,"listen",6)) { char *astr; char *pstr; if (base >= 0) err("`listen' command not at beginning of stanza"); for (;*lp&&Cisspace(*lp);lp++) ; if (! *lp) err("missing address and port number"); astr = lp; for (;*lp&&!Cisspace(*lp);lp++) ; if (! *lp) err("missing port number"); *lp++ = '\0'; for (;*lp&&Cisspace(*lp);lp++) ; pstr = lp; for (;*lp&&!Cisspace(*lp);lp++) ; if (*lp) { *lp++ = '\0'; for (;*lp&&Cisspace(*lp);lp++) ; if (*lp && (*lp != '#')) err("junk after port number"); } sethints(&hints); rv = getaddrinfo(astr,pstr,&hints,&ai0); if (rv) { err("%s",gai_strerror(rv)); } if (! ai0) { err("successful lookup but no addresses?!"); } base = nsockets; n = add_sockets_for_ai(ai0); freeaddrinfo(ai0); } else if ((l == 3) && !bcmp(cp,"cmd",3)) { int i; if (base < 0) err("`cmd' line without a preceding `port' line"); while (*lp && Cisspace(*lp)) lp++; for (i=0;iflink) { if (d->booted) { if (d->descriptor >= 0) close(d->descriptor); freeqs(d); if (d->hostnamewait || d->usernamewait) panic("shutting down waiting descriptor"); free(d->host.text); free(d->user.text); if (d->flink) d->flink->blink = d->blink; if (d->blink) d->blink->flink = d->flink; else descriptor_list = d->flink; free(d); goto top; } if (d->waiting) continue; if (d->output.head || !d->input.head) pfdn ++; } #ifdef ASYNC_HOSTNAMES pfdn ++; #endif #ifdef ASYNC_USERNAMES pfdn ++; #endif if (pfdn > pfda) { free(pfds); pfda = pfdn; pfds = malloc(pfda*sizeof(struct pollfd)); } for (i=0;is; pfds[i].events = POLLIN | POLLRDNORM; } #ifdef ASYNC_HOSTNAMES hostname_px = i; pfds[i].fd = hostname_fd; pfds[i].events = POLLIN | POLLRDNORM; i ++; #endif #ifdef ASYNC_USERNAMES username_px = i; pfds[i].fd = username_fd; pfds[i].events = POLLIN | POLLRDNORM; i ++; #endif for (d=descriptor_list;d;d=d->flink) { unsigned int events; if (d->booted) panic("booted descriptor still in list"); if (d->waiting) continue; events = 0; if (d->input.head) { timeout = slice_timeout; } else { events |= POLLIN | POLLRDNORM; } if (d->output.head) events |= POLLOUT | POLLWRNORM; if (events) { d->pollx = i; pfds[i].fd = d->descriptor; pfds[i].events = events; i ++; } else { d->pollx = -1; } } if (i != pfdn) panic("impossible poll vector size mismatch"); unblock_alarm(); prv = poll(pfds,i,timeout); perr = errno; block_alarm(); if (prv < 0) { if (perr != EINTR) { log_status("poll() error: %s",strerror(perr)); perror("poll"); return; } } else { now = curtm(); accept_a_conn(&pfds[0]); #ifdef ASYNC_HOSTNAMES if (pfds[hostname_px].revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP)) { process_hostname_output(); continue; /* because p_h_o() can boot a locked out descriptor */ } #endif #ifdef ASYNC_USERNAMES if (pfds[username_px].revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP)) { process_username_output(); continue; /* because p_u_o() can boot a locked out descriptor */ } #endif for (d=descriptor_list;d;d=dnext) { dnext = d->flink; if (d->pollx < 0) continue; if (pfds[d->pollx].revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP)) { d->last_time = now; if (! process_input(d)) { shutdownsock(d); continue; } } if (pfds[d->pollx].revents & (POLLOUT|POLLWRNORM|POLLERR|POLLHUP)) { if (! process_output(d)) shutdownsock(d); } } } } } struct descriptor_data *new_connection(int accsock) { int newsock; struct sockaddr_storage remaddr; struct sockaddr_storage lcladdr; socklen_t remal; socklen_t lclal; struct descriptor_data *d; remal = sizeof(remaddr); newsock = accept(accsock,(struct sockaddr *)&remaddr,&remal); if (newsock < 0) { if (errno != EINTR) { fprintf(stderr,"%s: new connection accept: %s\n",argvec[0],strerror(errno)); } return(0); } if ((remal < 1) || (remal > sizeof(struct sockaddr_storage))) { fprintf(stderr,"%s: impossible peer address size %d\n",argvec[0],remal); close(newsock); return(0); } switch (remaddr.ss_family) { case AF_INET: case AF_INET6: break; default: fprintf(stderr,"%s: bad address family %d\n",argvec[0],remaddr.ss_family); close(newsock); return(0); break; } lclal = sizeof(lcladdr); if (getsockname(newsock,(struct sockaddr *)&lcladdr,&lclal) < 0) { fprintf(stderr,"%s: new connection getsockname: %s\n",argvec[0],strerror(errno)); close(newsock); return(0); } if (lclal != remal) { fprintf(stderr,"%s: address size mismatch (local %d, remote %d)\n",argvec[0],lclal,remal); close(newsock); return(0); } if (lcladdr.ss_family != remaddr.ss_family) { fprintf(stderr,"%s: address family mismatch (local %d, remote %d)\n",argvec[0],lcladdr.ss_family,remaddr.ss_family); close(newsock); return(0); } MALLOC(d,struct descriptor_data,1); d->descriptor = newsock; d->pollx = -1; d->connected = 0; d->booted = 0; /* not set because no need: wordfold, waiting, hostnamewait, usernamewait */ d->telnetstate = TNS_DONT; d->windows_kludge = 0; d->player = NOTHING; make_nonblocking(newsock); d->output_prefix = 0; d->output_suffix = 0; d->output_fold = 0; d->output_col = 0; d->output_size = 0; d->output.head = 0; d->output.tail = &d->output.head; d->input.head = 0; d->input.tail = &d->input.head; d->raw_input = 0; d->raw_input_at = 0; d->quota = COMMAND_BURST_SIZE; d->last_time = 0; d->connected_at = curtm(); d->flink = descriptor_list; d->blink = 0; descriptor_list = d; if (d->flink) d->flink->blink = d; d->host.text = 0; d->user.text = 0; log_status( "ACCEPT: from %s at %s, descriptor %d", addr_and_port((struct sockaddr *)&remaddr), addr_and_port((struct sockaddr *)&lcladdr), newsock ); if (newsock >= maxdesc) { freeqs(d); queue_string(d,NO_DESC_MESSAGE); process_output(d); shutdownsock(d); close(newsock); d->descriptor = -1; log_status("ACCEPT: descriptor table overflow, rejecting"); } d->hostnamewait = 0; d->usernamewait = 0; #if defined(SYNC_HOSTNAMES) { char host[NI_MAXHOST]; int gnirv; const char *ns; const char *es; gnirv = getnameinfo((struct sockaddr *)&remaddr,&host[0],NI_MAXHOST,0,0,0); ns = ntop((struct sockaddr *)&remaddr); if (! ns) es = strerror(errno); if (gnirv) { if (ns) { d->host.text = dup_string(ns); log_status("HOSTNAME: descriptor %d: getnameinfo failed (%s), using %s",newsock,gai_strerror(gnirv),ns); } else { d->host.text = dup_string("?""?""?"); log_status("HOSTNAME: descriptor %d: can't get string form: getnameinfo->%s, inet_ntop->%s",newsock,gai_strerror(gnirv),es); } } else { d->host.text = dup_string(&host[0]); if (ns) { log_status("HOSTNAME: %s -> %s for descriptor %d",ns,d->host.text,newsock); } else { log_status("HOSTNAME: ?""?""? (%s) -> %s for descriptor %d",es,d->host.text,newsock); } } } #elif defined(ASYNC_HOSTNAMES) { struct iovec iov[2]; int len; len = remal; iov[0].iov_base = &len; iov[0].iov_len = sizeof(int); iov[1].iov_base = &remaddr; iov[1].iov_len = sizeof(struct sockaddr_storage); d->hostnamewait = 1; d->host.addr.ss = malloc(remal); bcopy(&remaddr,d->host.addr.ss,remal); d->host.addr.len = remal; writev(hostname_fd,&iov[0],2); } #else { const char *ns; const char *es; ns = ntop((struct sockaddr *)&remaddr); if (! ns) es = strerror(errno); if (ns) { d->host.text = dup_string(ns); } else { d->host.text = dup_string("?""?""?"); log_status("HOSTNAME: descriptor %d: can't get string form: %s",newsock,es); } } #endif #if defined(SYNC_USERNAMES) { d->user.text = run_identd(&lcladdr,&remaddr,lclal); log_status("USERNAME: %s for descriptor %d",d->user.text,newsock); } #elif defined(ASYNC_USERNAMES) { struct iovec iov[3]; int len; len = lclal; iov[0].iov_base = &len; iov[0].iov_len = sizeof(int); iov[1].iov_base = &lcladdr; iov[1].iov_len = sizeof(struct sockaddr_storage); iov[2].iov_base = &remaddr; iov[2].iov_len = sizeof(struct sockaddr_storage); d->user.addr.len = lclal; d->user.addr.ss = malloc(sizeof(struct sockaddr_in [2])); bcopy(&lcladdr,&(*d->user.addr.ss)[0],lclal); bcopy(&remaddr,&(*d->user.addr.ss)[1],remal); d->usernamewait = 1; writev(username_fd,&iov[0],3); } #else d->user.text = dup_string("(no usernames)"); #endif check_waits(d); return(d); } static void clearstrings(struct descriptor_data *d) { if (d->output_prefix) { free(d->output_prefix); d->output_prefix = 0; } if (d->output_suffix) { free(d->output_suffix); d->output_suffix = 0; } } void shutdownsock(struct descriptor_data *d) { if (d->connected) { #ifdef MUFCONNECTS char dnbuf[64]; sprintf(&dnbuf[0],"%d",d->descriptor); run_global_hook(d->player,"do_disconnect",&dnbuf[0],makearray(1,makeinst(PROG_STRING,dup_string("disconnect")))); process_output(d); #endif log_status("DISCONNECT: descriptor %d player %s(%d)",d->descriptor,NAME(d->player),d->player); announce_disconnect(d->player); } else { log_status("DISCONNECT: descriptor %d never connected.",d->descriptor); } clearstrings(d); d->booted = 1; } static struct text_block *make_text_block(const char *s, int n, int esc) { struct text_block *p; MALLOC(p,struct text_block,1); if (esc) { const unsigned char *cp; unsigned char *tp; int realn; int i; realn = n; cp = (const unsigned char *) s; for (i=0;ibuf,char,realn); if (realn == n) { bcopy(s,p->buf,n); } else { tp = (unsigned char *) p->buf; cp = (const unsigned char *) s; for (i=0;inchars = realn; } else { MALLOC(p->buf,char,n); bcopy(s,p->buf,n); p->nchars = n; } p->start = p->buf; p->nxt = 0; return(p); } static void free_text_block(struct text_block *t) { free(t->buf); free(t); } static void add_to_queue(struct text_queue *q, const char *b, int n, int esc) { struct text_block *p; if (n == 0) return; p = make_text_block(b,n,esc); p->nxt = 0; *q->tail = p; q->tail = &p->nxt; } static int flush_queue(struct text_queue *q, int n) { struct text_block *p; int really_flushed; really_flushed = 0; n += strlen(FLUSHED_MESSAGE); while ((n > 0) && (p = q->head)) { n -= p->nchars; really_flushed += p->nchars; q->head = p->nxt; free_text_block(p); } p = make_text_block(FLUSHED_MESSAGE,strlen(FLUSHED_MESSAGE),0); p->nxt = q->head; q->head = p; if (! p->nxt) q->tail = &p->nxt; really_flushed -= p->nchars; return(really_flushed); } static void queue_write_(struct descriptor_data *d, const char *b, int n, int esc) { int space; space = MAX_OUTPUT - d->output_size - n; if (space < 0) { if (no_output_flushed) { while ( process_output(d) && d->output.head && (d->output_size+n >= MAX_OUTPUT) ) nap(1); } else { d->output_size -= flush_queue(&d->output,-space); } } if (d->output_fold) { const char *bp; const char *end; int c; c = d->output_col; end = b + n; bp = b; while (bp < end) { if ((*bp == '\r') || (*bp == '\n')) { c = 0; bp ++; } else { c ++; if (c > d->output_fold) { if (d->wordfold) { const char *t; for (t=bp;(t>b)&&!Cisspace(*t);t--) ; if (t == b) { if (bp > b) add_to_queue(&d->output,b,bp-b,esc); b = bp; } else { bp = t + 1; while ((t > b) && Cisspace(*t)) t --; if (! Cisspace(*t)) add_to_queue(&d->output,b,(t+1)-b,esc); for (b=bp;(b b) add_to_queue(&d->output,b,bp-b,esc); b = bp; } add_to_queue(&d->output,"\r\n",2,0); c = 0; } else { bp ++; } } } d->output_col = c; if (bp > b) add_to_queue(&d->output,b,bp-b,esc); } else { add_to_queue(&d->output,b,n,esc); d->output_size += n; } } void queue_write(struct descriptor_data *d, const void *b, int n) { queue_write_(d,b,n,d->telnetstate!=TNS_DONT); } static void queue_write_noesc(struct descriptor_data *d, const void *b, int n) { queue_write_(d,b,n,0); } void queue_string(struct descriptor_data *d, const char *s) { queue_write(d,s,strlen(s)); } int process_output(struct descriptor_data *d) { struct text_block *tbp; struct iovec iov[WRITEV_MAXCNT]; int niov; int did; int want; if (d->booted) return(0); tbp = d->output.head; if (! tbp) return(1); niov = 0; want = 0; while (1) { if ((niov >= WRITEV_MAXCNT) || ((niov > 0) && !tbp)) { did = writev(d->descriptor,&iov[0],niov); if (did < 0) { return(errno==EWOULDBLOCK); } d->output_size -= did; want -= did; while (did > 0) { if (did >= d->output.head->nchars) { struct text_block *tmp; tmp = d->output.head; did -= tmp->nchars; d->output.head = tmp->nxt; free_text_block(tmp); if (d->output.head == 0) d->output.tail = &d->output.head; } else { d->output.head->start += did; d->output.head->nchars -= did; break; } } if (want > 0) return(1); niov = 0; } if (! tbp) break; iov[niov].iov_base = tbp->start; iov[niov].iov_len = tbp->nchars; want += tbp->nchars; niov ++; tbp = tbp->nxt; } return(1); } void make_nonblocking(int s) { if (fcntl (s, F_SETFL, FNDELAY) == -1) { perror ("make_nonblocking: fcntl"); panic ("FNDELAY fcntl failed"); } } void freeqs(struct descriptor_data *d) { struct text_block *cur; struct text_block *next; cur = d->output.head; while (cur) { next = cur->nxt; free_text_block(cur); cur = next; } d->output.head = 0; d->output.tail = &d->output.head; cur = d->input.head; while (cur) { next = cur->nxt; free_text_block(cur); cur = next; } d->input.head = 0; d->input.tail = &d->input.head; if (d->raw_input) free(d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } static void goodbye_user(struct descriptor_data *d) { write (d->descriptor, LEAVE_MESSAGE, strlen (LEAVE_MESSAGE)); } char *strsave (const char *s) { char *p; MALLOC (p, char, strlen(s) + 1); if (p) strcpy (p, s); return p; } void save_command(struct descriptor_data *d, const void *command) { add_to_queue(&d->input,command,strlen(command)+1,0); } inline static int stripped_char(unsigned char c) { return( ((c < 32) && (c != '\t')) || ((c >= 127) && (c < 160)) ); } inline static int delete_char(unsigned char c) { return( (c == 8) || (c == 127) ); } int process_input(struct descriptor_data *d) { unsigned char buf[1024]; int got; unsigned char *p; unsigned char *pend; unsigned char *q; unsigned char *qend; unsigned char reply[3]; got = read(d->descriptor,&buf[0],sizeof(buf)); if (d->booted || (got <= 0)) return(0); seed_random(&buf[0],got); if (! d->raw_input) { MALLOC(d->raw_input,unsigned char,MAX_COMMAND_LEN); d->raw_input_at = d->raw_input; } p = d->raw_input_at; pend = d->raw_input + MAX_COMMAND_LEN - 1; for (q=buf,qend=buf+got;qtelnetstate) { case TNS_DATA: if (*q == TN_IAC) { d->telnetstate = TNS_IAC; break; } /* fall through */ case TNS_DONT: if (*q == '\n') { *p = '\0'; #ifdef DROP_EMPTY_LINES if (p > d->raw_input) #endif save_command(d,d->raw_input); p = d->raw_input; } else if (d->windows_kludge && delete_char(*q)) { if (p > d->raw_input) p --; } else if ((p < pend) && !stripped_char(*q)) { *p++ = *q; } break; case TNS_IAC: switch (*q) { case TN_IAC: *p++ = TN_IAC; d->telnetstate = TNS_DATA; break; case TN_DO: d->telnetstate = TNS_DO; break; case TN_WILL: d->telnetstate = TNS_WILL; break; case TN_DONT: case TN_WONT: d->telnetstate = TNS_SKIP; break; case TN_SB: d->telnetstate = TNS_SB; break; default: d->telnetstate = TNS_DATA; break; } break; case TNS_DO: reply[0] = TN_IAC; reply[1] = TN_WONT; reply[2] = *q; queue_write_noesc(d,&reply[0],3); d->telnetstate = TNS_DATA; break; case TNS_WILL: reply[0] = TN_IAC; reply[1] = TN_DONT; reply[2] = *q; queue_write_noesc(d,&reply[0],3); d->telnetstate = TNS_DATA; break; case TNS_SKIP: d->telnetstate = TNS_DATA; break; case TNS_SB: if (*q == TN_IAC) d->telnetstate = TNS_SBI; break; case TNS_SBI: d->telnetstate = (*q == TN_SE) ? TNS_DATA : TNS_SB; break; default: fprintf(stderr,"descriptor %d telnetstate %d\n",d->descriptor,d->telnetstate); d->telnetstate = TNS_DONT; break; } } if (p > d->raw_input) { d->raw_input_at = p; } else { free(d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } return(1); } void set_userstring(char **userstring, const char *command) { if (*userstring) { free(*userstring); *userstring = 0; } while (*command && Cisspace(*command)) command++; if (*command) *userstring = strsave(command); } void process_commands() { int nprocessed; struct descriptor_data *d; struct descriptor_data *dnext; struct text_block *t; alarm_block = 1; do { nprocessed = 0; for (d=descriptor_list;d;d=dnext) { dnext = d->flink; if ( d->connected && DBFETCH(d->player)->sp.player.run && !DBFETCH(d->player)->sp.player.run->reading ) { interp_resume(d->player); } else { t = d->input.head; if ((d->quota > 0) && t) { d->quota --; nprocessed ++; if (!d->booted && !do_command(d,t->start)) { shutdownsock(d); } else { d->input.head = t->nxt; if (!d->input.head) d->input.tail = &d->input.head; free_text_block(t); } } } } } while (nprocessed > 0); big_fat_descripto_lock = 0; alarm_block = 0; if (alarm_triggered) time_keeper(); } static void boolean_conncmd(struct descriptor_data *d, const char *cmdrest, const char *cmd, void (*ifno)(struct descriptor_data *), void (*ifyes)(struct descriptor_data *), int isyes) { const char *cp; cp = cmdrest; while (*cp && Cisspace(*cp)) cp ++; switch (*cp) { case 'y': case 'Y': case '1': case 't': case 'T': (*ifyes)(d); break; case 'n': case 'N': case '0': case 'f': case 'F': (*ifno)(d); break; case '?': queue_string(d,isyes?"Y\r\n":"N\r\n"); break; case '\0': queue_string(d,cmd); queue_string(d," [yY1tTnN0fF?]\r\n"); break; } } static int question_mark(const char *cp) { while (*cp && Cisspace(*cp)) cp ++; return(*cp=='?'); } static void report_folding(struct descriptor_data *d) { if (d->output_fold < 1) { queue_string(d,"No folding\r\n"); } else { char *num; queue_string(d,d->wordfold?"OUTPUTWRAP":"OUTPUTFOLD"); asprintf(&num," %d",d->output_fold); queue_string(d,num); queue_string(d,"\r\n"); (free)(num); } } int do_command(struct descriptor_data *d, char *command) { dbref i; dbref player; SERIAL playser; int count; struct descriptor_data *d2; clearforce(); player = d->player; if (d->connected) { struct propref *pr; pr = lookup_property(player,".last",LP_CREATE,PPERM_COMPAT_PRIVATE); propref_set_type_and_value(pr,PTYPE_STRING,command); propref_done(pr); } #ifdef CMDRING_SIZE cmdring_stash(d,command); #endif if (!strcmp(command,"QUIT")) { goodbye_user(d); process_output(d); return(0); } else if (!strncmp(command,"OUTPUTPREFIX",12)) { set_userstring(&d->output_prefix,command+12); } else if (!strncmp(command,"OUTPUTSUFFIX",12)) { set_userstring(&d->output_suffix,command+12); } else if (!strncmp(command,"OUTPUTFOLD",10)) { if (question_mark(command+10)) { report_folding(d); } else { d->output_fold = atoi(command+10); if (d->output_fold < 0) d->output_fold = 0; d->wordfold = 0; } } else if (!strncmp(command,"OUTPUTWRAP",10)) { if (question_mark(command+10)) { report_folding(d); } else { d->output_fold = atoi(command+10); if (d->output_fold < 0) d->output_fold = 0; d->wordfold = 1; } } else if (!strncmp(command,"TELNET",6)) { boolean_conncmd(d,command+6,"TELNET", ({ void foo(struct descriptor_data *d) { d->telnetstate = TNS_DONT; } &foo; }), ({ void foo(struct descriptor_data *d) { if (d->telnetstate == TNS_DONT) { d->telnetstate = TNS_DATA; } } &foo; }), d->telnetstate!=TNS_DONT ); } else if ( !strncmp(command,"STUPID-WIN",10) || !strncmp(command,"STUPID_WIN",10) || !strncmp(command,"STUPIDWIN",9) ) { boolean_conncmd(d,command+((command[7]=='W')?10:9),"STUPID-WIN", ({ void foo(struct descriptor_data *d) { d->windows_kludge = 0; } &foo; }), ({ void foo(struct descriptor_data *d) { d->windows_kludge = 1; } &foo; }), d->windows_kludge ); } else if (!strncmp(command,"HOLDSHUT",8)) { boolean_conncmd(d,command+8,"HOLDSHUT", ({ void foo(struct descriptor_data *d) { d->holdshutdown = 0; } &foo; }), ({ void foo(struct descriptor_data *d) { d->holdshutdown = 1; } &foo; }), d->holdshutdown ); } else if (d->connected) { if (d->output_prefix) { queue_string(d,d->output_prefix); queue_write(d,"\r\n",2); } playser = DBFETCH(player)->serial; process_command(player,command); if (DBFETCH(player)->serial != playser) return(0); /* make sure we haven't been booted off... */ for (d2=descriptor_list;d2;d2=d2->flink) if (d2 == d) break; if (d2 && d->output_suffix) { queue_string(d,d->output_suffix); queue_write(d,"\r\n",2); } } else { char cmd[MAX_COMMAND_LEN]; char user[MAX_COMMAND_LEN]; char password[MAX_COMMAND_LEN]; int runcount; parse_connect(command,&cmd[0],&user[0],&password[0]); if (!cmd[0]) { } else if (!string_compare(&cmd[0],"connect")) { con_connect(d,&user[0],&password[0]); } else if (!string_compare(&cmd[0],"create")) { con_create(d,&user[0],&password[0]); } else { runcount = 0; DOLIST(i,DBFETCH(GLOBAL_ENVIRONMENT)->sp.room.exits) { if (FLAGS(i) & UNCONNECTED) { char *xn; int lxn; SERIAL iser; iser = DBFETCH(i)->serial; xn = DBFETCH(i)->name; lxn = strlen(xn); if ( string_prefix(command,xn) && (!command[lxn] || Cisspace(command[lxn])) ) { for (count=0;countsp.exit.ndest;count++) { if (Typeof(DBFETCH(i)->sp.exit.dest[count]) == TYPE_PROGRAM) { if (runcount == 0) { if (d->output_prefix) { queue_string(d,d->output_prefix); queue_write(d,"\r\n",2); } } unconnected_interp(d->descriptor,DBFETCH(i)->sp.exit.dest[count],i,command+lxn,xn); if (DBFETCH(i)->serial != iser) goto out; /* break 2 */ runcount ++; } } } } } out:; if (runcount == 0) { #ifdef BUILTIN_WHO if (!strcmp(&cmd[0],"WHO")) { if (d->output_prefix) { queue_string(d,d->output_prefix); queue_write(d,"\r\n",2); } dump_users(NOTHING,command+3,dump_to_desc,d); runcount = 1; } else #endif if (string_prefix(&cmd[0],"co")) { con_connect(d,&user[0],&password[0]); runcount = -1; } else if (string_prefix(&cmd[0],"cr")) { con_create(d,&user[0],&password[0]); runcount = -1; } } if (runcount > 0) { if (d->output_suffix) { queue_string(d,d->output_suffix); queue_write(d,"\r\n",2); } } else if (runcount == 0) { welcome_user(d); } } } return(1); } void con_connect(struct descriptor_data *d, const char *user, const char *pw) { dbref player; SERIAL playser; player = connect_player(user,pw); if (player == NOTHING) { queue_string(d,CONNECT_FAIL); log_status("FAILED CONNECT %s on descriptor %d",user,d->descriptor); } #ifdef NOLOGINS else if (no_login(d,player)) { log_status("FAILED CONNECT-NOLOGIN %s on descriptor %d",user,d->descriptor); } #endif /*NOLOGINS*/ else { int runprog; int mufedit; int mufinsert; char buf[512]; runprog = 0; mufedit = 0; mufinsert = 0; if (FLAGS(player) & INTERACTIVE) { if (DBFETCH(player)->sp.player.run) runprog = 1; else if (DBFETCH(player)->sp.player.insert_mode) mufinsert = 1; else mufedit = 1; } log_status("CONNECTED: %s(%d) on descriptor %d",NAME(player),player,d->descriptor); d->connected = 1; d->connected_at = curtm(); d->player = player; announce_connect(player); #ifdef MUFCONNECTS { char dnbuf[64]; sprintf(&dnbuf[0],"%d",d->descriptor); run_global_hook(player,"do_connect",&dnbuf[0],makearray(1,makeinst(PROG_STRING,dup_string("connect")))); } #endif #ifdef TIMESTAMPS DBFETCH(player)->time_used = d->connected_at; #endif playser = DBFETCH(player)->serial; do_look_around(player); if (DBFETCH(player)->serial != playser) { process_output(d); shutdownsock(d); return; } buf[0] = '\0'; if (runprog) { sprintf(&buf[0],"*** You are currently using a program. Use \"%s\" to return to a more reasonable state of control. ***",BREAK_COMMAND); } else if (mufinsert) { sprintf(&buf[0],"*** You are currently inserting program text. Use \".\" to return to the editor, then \"%c\" if you wish to return to your regularly scheduled Muck universe. ***",QUIT_EDIT_COMMAND); } else if (mufedit) { sprintf(&buf[0],"*** You are currently using the MUF program editor. ***"); } if (buf[0]) notify(player,buf); } } #ifndef REGISTRATION void con_create(struct descriptor_data *d, const char *user, const char *pw) { dbref player; SERIAL playser; player = create_player(user,pw); if (player == NOTHING) { queue_string(d,CREATE_FAIL); log_status("FAILED CREATE %s on descriptor %d",user,d->descriptor); } else { log_status("CREATED %s(%d) on descriptor %d",NAME(player),player,d->descriptor); d->connected = 1; d->connected_at = curtm(); d->player = player; announce_connect(player); playser = DBFETCH(player)->serial; do_look_around(player); if (DBFETCH(player)->serial != playser) { process_output(d); shutdownsock(d); } } } #else /* REGISTRATION */ void con_create(struct descriptor_data *d, const char *user, UNUSED_ARG(const char *pw)) { queue_string(d,REG_MSG); log_status("FAILED CREATE %s on descriptor %d",user,d->descriptor); } #endif /* REGISTRATION */ void parse_connect (const char *msg, char *command, char *user, char *pass) { char *p; while (*msg && Cisspace (*msg)) msg++; p = command; while (*msg && !Cisspace (*msg)) *p++ = *msg++; *p = '\0'; while (*msg && Cisspace (*msg)) msg++; p = user; while (*msg && !Cisspace (*msg)) *p++ = *msg++; *p = '\0'; while (*msg && Cisspace (*msg)) msg++; p = pass; while (*msg && !Cisspace (*msg)) *p++ = *msg++; *p = '\0'; } int boot_off(dbref player) { struct descriptor_data *d; for (d = descriptor_list; d; d = d->flink) { if (d->connected && !d->booted && (d->player == player)) { process_output (d); shutdownsock(d); return 1; } } return 0; } int dboot_off(int desc) { struct descriptor_data *d; for (d = descriptor_list; d; d = d->flink) { if ( d->descriptor == desc) { process_output (d); shutdownsock(d); return 1; } } return 0; } void close_sockets() { struct descriptor_data *d; int i; while (descriptor_list) { d = descriptor_list; descriptor_list = d->flink; queue_string(d,shutdown_message); process_output(d); clearstrings(d); freeqs(d); if (d->hostnamewait) { free(d->host.addr.ss); } else { free(d->host.text); } if (d->usernamewait) { free(d->user.addr.ss); } else { free(d->user.text); } if ( (d->descriptor >= 0) && (!shutdown_flag || !d->holdshutdown) ) close(d->descriptor); free(d); } for (i=0;is); free(sockets[i]); } free(sockets); nsockets = 0; } void emergency_shutdown() { close_sockets(); } void bailout(int sig) { char message[1024]; shutdown_message = CRASHING_MESSAGE; setpanicmalloc(); sprintf(message,"BAILOUT: caught signal %d",sig); panic(message); _exit(7); } void dump_descriptors(dbref player, const char *user) { struct descriptor_data *d; int wizard; int players; long int now; char buf[2048]; char pbuf[64]; char *bp; int doit; int loc; int n; wizard = Wizard(player); if (! wizard) { notify(player,"Sorry."); return; } while (*user && Cisspace(*user)) user++; if (!*user) user = 0; now = curtm(); notify(player,"Player Name Desc. Loc. On For Idle From"); players = 0; for (d=descriptor_list;d;d=d->flink) { doit = 0; if (d->connected) { players ++; if (!user || string_prefix(NAME(d->player),user)) { sprintf(&pbuf[0],"%.*s(#%d)",PLAYER_NAME_LIMIT,NAME(d->player),(int)d->player); loc = DBFETCH(d->player)->location; doit = 1; } } else { pbuf[0] = '\0'; loc = -1; doit = 1; } if (doit) { bp = &buf[0]; sprintf(bp,"%-*s [%4d] [%6d] %5s %4s ", PLAYER_NAME_LIMIT+9, &pbuf[0], d->descriptor, loc, time_format_1(now-d->connected_at), time_format_2(now-d->last_time) ); bp += strlen(bp); #ifdef USERNAMES if (d->usernamewait) { bcopy("",bp,9); bp += 9; } else { n = strlen(d->user.text); if (n > 20) n = 20; bcopy(d->user.text,bp,n); bp += n; } *bp++ = '@'; #endif if (d->hostnamewait) { bcopy("",bp,9); bp += 9; } else { n = strlen(d->host.text); if (n > 32) n = 32; bcopy(d->host.text,bp,n); bp += n; } *bp = '\0'; notify(player,&buf[0]); } } sprintf(&buf[0],"%d %s connected.", players, (players==1)?"player is":"players are" ); notify(player,&buf[0]); } void dump_to_desc(void *arg, const char *str) { queue_string((struct descriptor_data *)arg,str); queue_write((struct descriptor_data *)arg,"\r\n",2); } void dump_to_player(void *arg, const char *str) { notify_listener(*(dbref *)arg,*(dbref *)arg,str); } #ifdef BUILTIN_WHO #define LW 78 /* line width */ void dump_users( dbref dumper, const char *user, void (*eachline)(void *, const char *), void *arg) { struct descriptor_data *d; int wizard; int pcount; long now; char buf[2048]; char *bp; int l; int m; wizard = PERMIT_EXTENDEDWHO(dumper); while (*user && isspace(*user)) user++; if (!*user) user = NULL; now = curtm(); if (wizard) { sprintf(&buf[0],"%-*sWhere &d On For Idle From",2+LW-2-5-4-9-5-1-24,"Player"); (*eachline)(arg,&buf[0]); } else { (*eachline)(arg,"Player Name On For Idle Doing"); } pcount = 0; for (d=descriptor_list;d;d=d->next) { if ( d->connected && PERMIT_SEEWHO(dumper,d->player) && (pcount++,1) && (!user || string_prefix(NAME(d->player),user)) ) { bp = &buf[0]; if (wizard) { sprintf(bp,"%s%s%-*s%5d%4d%9s%5s ", (FLAGS(d->player)&UNSEEN)?"*":" ", (FLAGS(d->player)&INTERACTIVE)?"+":" ", LW-2-5-4-9-5-1-24, unparse_object(dumper,d->player), (int)DBFETCH(d->player)->location, d->descriptor, time_format_1(now-d->connected_at), time_format_2(now-d->last_time) ); bp += strlen(bp); l = 24; m = strlen(d->host.text); #ifdef USERNAMES { int n; n = strlen(d->user.text); if ((n+1+m > l) && (n > 12)) n = 12; bcopy(d->user.text,bp,n); bp += n; *bp++ = '@'; l -= n + 1; } #endif if (m > l) m = l; bcopy(d->host.text,bp,m); bp += m; *bp = '\0'; } else { char *doing; doing = get_property_value(d->player,PROP_RDONLY,"doing"); sprintf(buf,"%s%s%-19s%9s%5s %-.41s", (FLAGS(d->player)&UNSEEN)?"*":" ", (FLAGS(d->player)&INTERACTIVE)?"+":" ", NAME(d->player), time_format_1(now-d->connected_at), time_format_2(now-d->last_time), DoNullInd(doing) ); free(doing); } (*eachline)(arg,&buf[0]); } } sprintf(buf,"%d player%s connected.",pcount,(pcount==1)?" is":"s are"); (*eachline)(arg,&buf[0]); } #undef LW #endif #ifdef BUILTIN_WHO void do_who(dbref player, char *arg1, char *arg2) { dump_users(player,arg1,dump_to_player,&player); } #endif char *time_format_1(long int dt) { static char buf[64]; if (dt >= 86400) { sprintf(&buf[0],"%ldd %02ld:%02ld",dt/86400,(dt%86400)/3600,(dt%3600)/60); } else { sprintf(&buf[0],"%02ld:%02ld",dt/3600,(dt%3600)/60); } return(&buf[0]); } char *time_format_2(long dt) { static char buf[64]; if (dt >= 60*60*24) { sprintf(&buf[0],"%ldd",dt/(60*60*24)); } else if (dt >= 60*60) { sprintf(&buf[0],"%dh",(int)(dt/(60*60))); } else if (dt >= 60) { sprintf(&buf[0],"%dm",(int)(dt/60)); } else { sprintf(&buf[0],"%ds",(int)dt); } return(&buf[0]); } char *time_format_3(long dt) { static char buf[64]; char *bp; bp = &buf[0]; #define FOO(factor,ch) \ if (dt >= (factor)) { sprintf(bp,"%ld" #ch,dt/(factor)); bp += strlen(bp); dt %= factor; } FOO(60*60*24,d) FOO(60*60,h); FOO(60,m); if (dt || (bp == &buf[0])) sprintf(bp,"%lds",dt); return(&buf[0]); } static int set_private(dbref d) { struct propref *pr; if ((Typeof(d) == TYPE_PLAYER) && (FLAGS(d) & DARK)) return(1); pr = lookup_property(d,"_private",0); if (! pr) pr = lookup_property(d,"private",0); if (! pr) pr = lookup_property(d,"_dark",0); if (! pr) pr = lookup_property(d,"dark",0); if (pr) { propref_done(pr); return(1); } return(0); } void announce_connect(dbref player) { dbref loc; dbref i; const char *locstr; char buf[BUFFER_LEN]; loc = getloc(player); if (loc == NOTHING) return; locstr = (set_private(player) || set_private(loc)) ? "a private location" : NAME(loc); for (i=0;ipw_uid) < 0) { int e; e = errno; log_status("can't setuid(%s->%d): %m",MUD_ID,pw->pw_uid,e); errno = e; perror("setuid"); exit(1); } } #endif void welcome_user(struct descriptor_data *d) { if (spit_file_d(WELC_FILE,d)) { queue_string(d,DEFAULT_WELCOME_MESSAGE); fprintf(stderr,"welcome_user: %s: %s\n",WELC_FILE,strerror(errno)); } }