/** * \file notify.c * * \brief Notification of objects with messages, for PennMUSH. * * The functions in this file are primarily concerned with maintaining * queues of blocks of text to transmit to a player descriptor. * */ #include "copyrite.h" #include "config.h" #include <stdio.h> #include <stdarg.h> #ifdef I_SYS_TYPES #include <sys/types.h> #endif #ifdef WIN32 #include <windows.h> #include <winsock.h> #include <io.h> #else /* !WIN32 */ #ifdef I_SYS_FILE #include <sys/file.h> #endif #ifdef I_SYS_TIME #include <sys/time.h> #endif #include <sys/ioctl.h> #ifdef I_SYS_SOCKET #include <sys/socket.h> #endif #ifdef I_NETINET_IN #include <netinet/in.h> #endif #ifdef I_NETDB #include <netdb.h> #endif #ifdef I_SYS_PARAM #include <sys/param.h> #endif #ifdef I_SYS_STAT #include <sys/stat.h> #endif #endif /* !WIN32 */ #include <time.h> #include <fcntl.h> #include <ctype.h> #include <signal.h> #include <string.h> #include <stdlib.h> #ifdef I_UNISTD #include <unistd.h> #endif #include <limits.h> #ifdef I_FLOATINGPOINT #include <floatingpoint.h> #endif #include "conf.h" #include "mushdb.h" #include "externs.h" #include "flags.h" #include "dbdefs.h" #include "lock.h" #include "help.h" #include "match.h" #include "ansi.h" #include "pueblo.h" #include "parse.h" #include "access.h" #include "version.h" #include "patches.h" #include "mysocket.h" #include "ident.h" #include "strtree.h" #include "log.h" #include "mymalloc.h" #include "extchat.h" extern CHAN *channels; #include "extmail.h" #include "attrib.h" #include "game.h" #include "confmagic.h" static int under_limit = 1; /** Default connection, nothing special */ #define CONN_DEFAULT 0 /** Using Pueblo, Smial, Mushclient, Simplemu, or some other * pueblo-style HTML aware client */ #define CONN_HTML 0x1 /** Using a client that understands telnet options */ #define CONN_TELNET 0x2 /** Send a telnet option to test client */ #define CONN_TELNET_QUERY 0x4 /* When the mush gets a new connection, it tries sending a telnet * option negotiation code for setting client-side line-editing mode * to it. If it gets a reply, a flag in the descriptor struct is * turned on indicated telnet-awareness. * * If the reply indicates that the client supports linemode, further * instructions as to what linemode options are to be used is sent. * Those options: Client-side line editing, and expanding literal * client-side-entered tabs into spaces. * * Option negotation requests sent by the client are processed, * with the only one we confirm rather than refuse outright being * suppress-go-ahead, since a number of telnet clients try it. * * The character 255 is the telnet option escape character, so when it * is sent to a telnet-aware client by itself (Since it's also often y-umlaut) * it must be doubled to escape it for the client. This is done automatically, * and is the original purpose of adding telnet option support. */ /* Telnet codes */ #define IAC 255 /**< telnet: interpret as command */ /** Iterate through a list of descriptors, and do something with those * that are connected. */ #define DESC_ITER_CONN(d) \ for(d = descriptor_list;(d);d=(d)->next) \ if((d)->connected) #ifdef NT_TCP /* for Windows NT IO-completion-port method of TCP/IP - NJG */ /* Windows NT TCP/IP routines written by Nick Gammon <nick@gammon.com.au> */ #include <process.h> HANDLE CompletionPort; /* IOs are queued up on this port */ SOCKET MUDListenSocket; /* for our listening thread */ DWORD dwMUDListenThread; /* thread handle for listening thread */ SOCKADDR_IN saServer; /* for listening thread */ void __cdecl MUDListenThread(void *pVoid); /* the listening thread */ DWORD platform; /* which version of Windows are we using? */ OVERLAPPED lpo_aborted; /* special to indicate a player has finished TCP IOs */ OVERLAPPED lpo_shutdown; /* special to indicate a player should do a shutdown */ void ProcessWindowsTCP(void); /* handle NT-style IOs */ CRITICAL_SECTION cs; /* for thread synchronisation */ #endif static const char *flushed_message = "\r\n<Output Flushed>\x1B[0m\r\n"; extern DESC *descriptor_list; #ifdef WIN32 static WSADATA wsadata; #endif static struct text_block *make_text_block(const unsigned char *s, int n); void free_text_block(struct text_block *t); void add_to_queue(struct text_queue *q, const unsigned char *b, int n); static int flush_queue(struct text_queue *q, int n); int queue_write(DESC *d, const unsigned char *b, int n); int queue_newwrite(DESC *d, const unsigned char *b, int n); int queue_string(DESC *d, const char *s); int queue_string_eol(DESC *d, const char *s); int queue_eol(DESC *d); void freeqs(DESC *d); int process_output(DESC *d); /** Types of text renderings we can do in notify_anything(). */ enum na_type { NA_ASCII = 0, /**< Plain old ascii. */ NA_ANSI, /**< ANSI flag */ NA_COLOR, /**< ANSI and COLOR flags */ NA_PUEBLO, /**< html */ NA_PASCII, /**< Player without any of the above */ NA_TANSI, /**< Like above with telnet-aware client */ NA_TCOLOR, /**< Like above with telnet-aware client */ NA_TPASCII, /**< Like above with telnet-aware client */ NA_NANSI, /**< ANSI and NOACCENTS */ NA_NCOLOR, /**< ANSI, COLOR, NOACCENTS */ NA_NPUEBLO, /**< html & NOACCENTS */ NA_NPASCII /**< NOACCENTS */ }; /** Number of possible message text renderings */ #define MESSAGE_TYPES 12 #define TA_BGC 0 /**< Text attribute background color */ #define TA_FGC 1 /**< Text attribute foreground color */ #define TA_BOLD 2 /**< Text attribute bold/hilite */ #define TA_REV 3 /**< Text attribute reverse/inverse */ #define TA_BLINK 4 /**< Text attribute blinking/flashing */ #define TA_ULINE 5 /**< Text attribute underline */ static int na_depth = 0; /** A place to store a rendered message. */ struct notify_strings { unsigned char *message; /**< The message text. */ size_t len; /**< Length of message. */ int made; /**< True if message has been rendered. */ }; static void fillstate(int state[], const unsigned char **f); static void ansi_change_state(char *t, char **o, int color, int *state, int *newstate); static enum na_type notify_type(DESC *d); static void free_strings(struct notify_strings messages[]); static void zero_strings(struct notify_strings messages[]); static unsigned char *notify_makestring(const char *message, struct notify_strings messages[], enum na_type type); static void fillstate(int state[6], const unsigned char **f) { const unsigned char *p; int i; int n; p = *f; p++; if (*p != '[') { while (*p && *p != 'm') p++; } else { p++; while (*p && *p != 'm') { if ((*p > '9') || (*p < '0')) { /* Nada */ } else if (!(*(p + 1)) || (*(p + 1) == 'm') || (*(p + 1) == ';')) { /* ShortCode */ ; switch (*p) { case '0': for (i = 0; i < 6; i++) state[i] = 0; break; case '1': state[TA_BOLD] = 1; break; case '7': state[TA_REV] = 1; break; case '5': state[TA_BLINK] = 1; break; case '4': state[TA_ULINE] = 1; break; } } else { n = (*p - '0') * 10; p++; n += (*p - '0'); if ((n >= 30) && (n <= 37)) state[TA_FGC] = n - 29; else if ((n >= 40) && (n <= 47)) state[TA_BGC] = n - 39; } p++; } } if ((p != *f) && (*p != 'm')) p--; *f = p; } /** Add an ansi tag if it's needed here */ #define add_ansi_if(x,c) \ do { \ if (newstate[(x)] && (newstate[(x)]!=state[(x)])) { \ if (i) safe_chr(';',t,o); \ safe_str((c),t,o); \ i=1; \ } \ } while (0) static void ansi_change_state(char *t, char **o, int color, int *state, int *newstate) { int i, n; if ((state[TA_BOLD] && !newstate[TA_BOLD]) || (state[TA_REV] && !newstate[TA_REV]) || (state[TA_BLINK] && !newstate[TA_BLINK]) || (state[TA_ULINE] && !newstate[TA_ULINE]) || (color && state[TA_FGC] && !newstate[TA_FGC]) || (color && state[TA_BGC] && !newstate[TA_BGC])) { for (n = 0; n < 6; n++) state[n] = 0; safe_str(ANSI_NORMAL, t, o); } if ((newstate[TA_BOLD] && (newstate[TA_BOLD] != state[TA_BOLD])) || (newstate[TA_REV] && (newstate[TA_REV] != state[TA_REV])) || (newstate[TA_BLINK] && (newstate[TA_BLINK] != state[TA_BLINK])) || (newstate[TA_ULINE] && (newstate[TA_ULINE] != state[TA_ULINE])) || (color && newstate[TA_FGC] && (newstate[TA_FGC] != state[TA_FGC])) || (color && newstate[TA_BGC] && (newstate[TA_BGC] != state[TA_BGC]))) { safe_chr(ESC_CHAR, t, o); safe_chr('[', t, o); i = 0; add_ansi_if(TA_BOLD, "1"); add_ansi_if(TA_REV, "7"); add_ansi_if(TA_BLINK, "5"); add_ansi_if(TA_ULINE, "4"); if (color) { add_ansi_if(TA_FGC, unparse_integer(newstate[TA_FGC] + 29)); add_ansi_if(TA_BGC, unparse_integer(newstate[TA_BGC] + 39)); } safe_chr('m', t, o); } for (n = 0; n < 6; n++) state[n] = newstate[n]; } #undef add_ansi_if static void zero_strings(struct notify_strings messages[]) { int n; for (n = 0; n < MESSAGE_TYPES; n++) { messages[n].message = NULL; messages[n].len = 0; messages[n].made = 0; } } static void free_strings(struct notify_strings messages[]) { int n; for (n = 0; n < MESSAGE_TYPES; n++) if (messages[n].message) mush_free(messages[n].message, "string"); } static unsigned char * notify_makestring(const char *message, struct notify_strings messages[], enum na_type type) { char *o; const unsigned char *p; char *t; int state[6] = { 0, 0, 0, 0, 0, 0 }; int newstate[6] = { 0, 0, 0, 0, 0, 0 }; int changed = 0; int color = 0; int strip = 0; int pueblo = 0; static char tbuf[BUFFER_LEN]; if (messages[type].made) return messages[type].message; messages[type].made = 1; p = (unsigned char *) message; o = tbuf; t = o; /* Since well over 50% is this type, we do it quick */ switch (type) { case NA_ASCII: while (*p) { switch (*p) { case TAG_START: while (*p && *p != TAG_END) p++; break; case '\r': case BEEP_CHAR: break; case ESC_CHAR: while (*p && *p != 'm') p++; break; default: safe_chr(*p, t, &o); } p++; } *o = '\0'; messages[type].message = (unsigned char *) mush_strdup(tbuf, "string"); messages[type].len = o - tbuf; return messages[type].message; case NA_NPASCII: strip = 1; case NA_PASCII: case NA_TPASCII: /* PLAYER Ascii. Different output. \n is \r\n */ if (type == NA_NPASCII) strip = 1; while (*p) { switch (*p) { case IAC: if (type == NA_TPASCII) safe_str("\xFF\xFF", t, &o); else if (strip) safe_str(accent_table[IAC].base, t, &o); else safe_chr((char) IAC, t, &o); break; case TAG_START: while (*p && *p != TAG_END) p++; break; case ESC_CHAR: while (*p && *p != 'm') p++; break; case '\r': break; case '\n': safe_str("\r\n", t, &o); break; default: if (strip && accent_table[(unsigned char) *p].base) safe_str(accent_table[(unsigned char) *p].base, t, &o); else safe_chr(*p, t, &o); } p++; } *o = '\0'; messages[type].message = (unsigned char *) mush_strdup(tbuf, "string"); messages[type].len = o - tbuf; return messages[type].message; case NA_PUEBLO: case NA_NPUEBLO: pueblo = 1; /* FALLTHROUGH */ case NA_COLOR: case NA_TCOLOR: case NA_NCOLOR: color = 1; /* FALLTHROUGH */ case NA_ANSI: case NA_TANSI: case NA_NANSI: if (type == NA_NCOLOR || type == NA_NANSI || type == NA_NPUEBLO) strip = 1; while (*p) { switch ((unsigned char) *p) { case IAC: if (changed) { changed = 0; ansi_change_state(t, &o, color, state, newstate); } if (type == NA_TANSI || type == NA_TCOLOR) safe_str("\xFF\xFF", t, &o); else if (strip && accent_table[IAC].base) safe_str(accent_table[IAC].base, t, &o); else safe_chr((char) IAC, t, &o); break; case TAG_START: if (pueblo) { safe_chr('<', t, &o); p++; while ((*p) && (*p != TAG_END)) { safe_chr(*p, t, &o); p++; } safe_chr('>', t, &o); } else { /* Non-pueblo */ while (*p && *p != TAG_END) p++; } break; case TAG_END: /* Should never be seen alone */ break; case '\r': break; case ESC_CHAR: fillstate(newstate, &p); changed = 1; break; default: if (changed) { changed = 0; ansi_change_state(t, &o, color, state, newstate); } if (pueblo) { if (strip) { /* Even if we're NOACCENTS, we must still translate a few things */ switch ((unsigned char) *p) { case '\n': case '&': case '<': case '>': case '"': safe_str(accent_table[(unsigned char) *p].entity, t, &o); break; default: if (accent_table[(unsigned char) *p].base) safe_str(accent_table[(unsigned char) *p].base, t, &o); else safe_chr(*p, t, &o); break; } } else if (accent_table[(unsigned char) *p].entity) safe_str(accent_table[(unsigned char) *p].entity, t, &o); else safe_chr(*p, t, &o); } else { /* Non-pueblo */ if ((unsigned char) *p == '\n') safe_str("\r\n", t, &o); else if (strip && accent_table[(unsigned char) *p].base) safe_str(accent_table[(unsigned char) *p].base, t, &o); else safe_chr(*p, t, &o); } } p++; } if (state[TA_BOLD] || state[TA_REV] || state[TA_BLINK] || state[TA_ULINE] || (color && (state[TA_FGC] || state[TA_BGC]))) safe_str(ANSI_NORMAL, t, &o); break; } *o = '\0'; messages[type].message = (unsigned char *) mush_strdup(tbuf, "string"); messages[type].len = o - tbuf; return messages[type].message; } /*-------------------------------------------------------------- * Iterators for notify_anything. * notify_anything calls these functions repeatedly to get the * next object to notify, passing in the last object notified. * On the first pass, it passes in NOTHING. When it finally * receives NOTHING back, it stops. */ /** notify_anthing() iterator for a single dbref. * \param current last dbref from iterator. * \param data memory address containing first object in chain. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_one(dbref current, void *data) { if (current == NOTHING) return *((dbref *) data); else return NOTHING; } /** notify_anthing() iterator for following a contents/exit chain. * \param current last dbref from iterator. * \param data memory address containing first object in chain. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_next(dbref current, void *data) { if (current == NOTHING) return *((dbref *) data); else return Next(current); } /** notify_anthing() iterator for a location and its contents. * \param current last dbref from iterator. * \param data memory address containing dbref of location. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_loc(dbref current, void *data) { dbref loc = *((dbref *) data); if (current == NOTHING) return loc; else if (current == loc) return Contents(current); else return Next(current); } /** notify_anthing() iterator for a contents/exit chain, with a dbref to skip. * \param current last dbref from iterator. * \param data memory address containing array of two dbrefs: the start of the chain and the dbref to skip. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_nextbut(dbref current, void *data) { dbref *dbrefs = data; do { if (current == NOTHING) current = dbrefs[0]; else current = Next(current); } while (current == dbrefs[1]); return current; } /** notify_anthing() iterator for a location and its contents, with a dbref to skip. * \param current last dbref from iterator. * \param data memory address containing array of two dbrefs: the location and the dbref to skip. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_except(dbref current, void *data) { dbref *dbrefs = data; do { if (current == NOTHING) current = dbrefs[0]; else if (current == dbrefs[0]) current = Contents(current); else current = Next(current); } while (current == dbrefs[1]); return current; } /** notify_anthing() iterator for a location and its contents, with 2 dbrefs to skip. * \param current last dbref from iterator. * \param data memory address containing array of three dbrefs: the location and the dbrefs to skip. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_except2(dbref current, void *data) { dbref *dbrefs = data; do { if (current == NOTHING) current = dbrefs[0]; else if (current == dbrefs[0]) current = Contents(current); else current = Next(current); } while ((current == dbrefs[1]) || (current == dbrefs[2])); return current; } /** notify_anthing() iterator for a location and its contents, with N dbrefs to skip. * \param current last dbref from iterator. * \param data memory address containing array of three or more values: the number of dbrefs to skip, the location, and the dbrefs to skip. * \return dbref of next object to notify, or NOTHING when done. */ dbref na_exceptN(dbref current, void *data) { dbref *dbrefs = data; int i, check; do { if (current == NOTHING) current = dbrefs[1]; else if (current == dbrefs[1]) current = Contents(current); else current = Next(current); check = 0; for (i = 2; i < dbrefs[0] + 2; i++) if (current == dbrefs[i]) check = 1; } while (check); return current; } static enum na_type notify_type(DESC *d) { enum na_type poutput; int strip; if (!d->connected) { /* These are the settings used at, e.g., the connect screen, * when there's no connected player yet. If you want to use * ansified connect screens, you'd probably change NA_NPASCII * to NA_NCOLOR (for no accents) or NA_COLOR (for accents). * We don't recommend it. If you want to use accented characters, * change NA_NPUEBLO and NA_NPASCII to NA_PUEBLO and NA_PASCII, * respectively. That's not so bad. */ return (d->conn_flags & CONN_HTML) ? NA_NPUEBLO : NA_NPASCII; } /* At this point, we have a connected player on the descriptor */ strip = IS(d->player, TYPE_PLAYER, "NOACCENTS"); if (d->conn_flags & CONN_HTML) { poutput = strip ? NA_NPUEBLO : NA_PUEBLO; } else if (ShowAnsi(d->player)) { if (ShowAnsiColor(d->player)) { if (strip) poutput = NA_NCOLOR; else poutput = (d->conn_flags & CONN_TELNET) ? NA_TCOLOR : NA_COLOR; } else { if (strip) poutput = NA_NANSI; else poutput = (d->conn_flags & CONN_TELNET) ? NA_TANSI : NA_ANSI; } } else { if (strip) poutput = NA_NPASCII; else poutput = (d->conn_flags & CONN_TELNET) ? NA_TPASCII : NA_PASCII; } return poutput; } /** Send a message to a series of dbrefs. * This key function takes a speaker's utterance and looks up each * object that should hear it. For each, it may need to render * the utterance in a different fashion (with or without ansi, html, * accents), but we cache each rendered version for efficiency. * \param speaker dbref of object producing the message. * \param func pointer to iterator function to look up each receiver. * \param fdata initial data to pass to func. * \param nsfunc function to call to do NOSPOOF formatting, or NULL. * \param flags flags to pass in (such as NA_INTERACT) * \param message message to render and transmit. * \param loc location the message was sent to. */ void notify_anything_loc(dbref speaker, na_lookup func, void *fdata, char *(*nsfunc) (dbref, na_lookup func, void *, int), int flags, const char *message, dbref loc) { dbref target; dbref passalong[3]; struct notify_strings messages[MESSAGE_TYPES]; struct notify_strings nospoofs[MESSAGE_TYPES]; struct notify_strings paranoids[MESSAGE_TYPES]; int i, j; DESC *d; enum na_type poutput; unsigned char *pstring; size_t plen; char *bp; ATTR *a; char *asave; char const *ap; char *preserve[NUMQ]; int havespoof = 0; int havepara = 0; char *wsave[10]; char *tbuf1 = NULL, *nospoof = NULL, *paranoid = NULL, *msgbuf; static dbref puppet = NOTHING; int nsflags; if (!message || *message == '\0' || !func) return; /* Depth check */ if (na_depth > 7) return; na_depth++; /* Only allocate these buffers when needed */ for (i = 0; i < MESSAGE_TYPES; i++) { messages[i].message = NULL; messages[i].made = 0; nospoofs[i].message = NULL; nospoofs[i].made = 0; paranoids[i].message = NULL; paranoids[i].made = 0; } msgbuf = mush_strdup(message, "string"); target = NOTHING; while ((target = func(target, fdata)) != NOTHING) { if ((flags & NA_PONLY) && !IsPlayer(target)) continue; if (IsPlayer(target)) { if (!Connected(target) && options.login_allow && under_limit) continue; if (flags & NA_INTERACTION) { int pass_interact = 1; if ((flags & NA_INTER_SEE) && !can_interact(speaker, target, INTERACT_SEE)) pass_interact = 0; if (pass_interact && (flags & NA_INTER_PRESENCE) && !can_interact(speaker, target, INTERACT_PRESENCE)) pass_interact = 0; if (pass_interact && (flags & NA_INTER_HEAR) && !can_interact(speaker, target, INTERACT_HEAR)) pass_interact = 0; if (!pass_interact) continue; } for (d = descriptor_list; d; d = d->next) { if (d->connected && d->player == target) { poutput = notify_type(d); if ((flags & NA_PONLY) && (poutput != NA_PUEBLO)) continue; if (!(flags & NA_SPOOF) && (nsfunc && ((Nospoof(target) && (target != speaker)) || (flags & NA_NOSPOOF)))) { if (Paranoid(target) || (flags & NA_PARANOID)) { if (!havepara) { paranoid = nsfunc(speaker, func, fdata, 1); havepara = 1; } pstring = notify_makestring(paranoid, paranoids, poutput); plen = paranoids[poutput].len; } else { if (!havespoof) { nospoof = nsfunc(speaker, func, fdata, 0); havespoof = 1; } pstring = notify_makestring(nospoof, nospoofs, poutput); plen = nospoofs[poutput].len; } queue_newwrite(d, pstring, plen); } pstring = notify_makestring(msgbuf, messages, poutput); plen = messages[poutput].len; if (pstring && *pstring) queue_newwrite(d, pstring, plen); if (!(flags & NA_NOENTER)) { if ((poutput == NA_PUEBLO) || (poutput == NA_NPUEBLO)) { if (flags & NA_NOPENTER) queue_newwrite(d, (unsigned char *) "\n", 1); else queue_newwrite(d, (unsigned char *) "<BR>\n", 5); } else { queue_newwrite(d, (unsigned char *) "\r\n", 2); } } } } } else if (Puppet(target) && ((Location(target) != Location(Owner(target))) || Verbose(target) || (flags & NA_MUST_PUPPET)) && ((flags & NA_PUPPET) || !(flags & NA_NORELAY))) { dbref last = puppet; if (flags & NA_INTERACTION) { int pass_interact = 1; if ((flags & NA_INTER_SEE) && !can_interact(speaker, target, INTERACT_SEE)) pass_interact = 0; if (pass_interact && (flags & NA_INTER_PRESENCE) && !can_interact(speaker, target, INTERACT_PRESENCE)) pass_interact = 0; if (pass_interact && (flags & NA_INTER_HEAR) && !can_interact(speaker, target, INTERACT_HEAR)) pass_interact = 0; if (!pass_interact) continue; } puppet = target; if (!tbuf1) tbuf1 = (char *) mush_malloc(BUFFER_LEN, "string"); bp = tbuf1; safe_str(Name(target), tbuf1, &bp); safe_str("> ", tbuf1, &bp); *bp = '\0'; notify_anything(GOD, na_one, &Owner(target), NULL, NA_NOENTER | NA_PUPPET2 | NA_NORELAY | flags, tbuf1); nsflags = 0; if (!(flags & NA_SPOOF)) { if (Nospoof(target)) nsflags |= NA_NOSPOOF; if (Paranoid(target)) nsflags |= NA_PARANOID; } notify_anything(speaker, na_one, &Owner(target), ns_esnotify, flags | nsflags | NA_NORELAY | NA_PUPPET2, msgbuf); puppet = last; } if ((flags & NA_NOLISTEN) || (!PLAYER_LISTEN && IsPlayer(target)) || IsExit(target)) continue; /* do @listen stuff */ a = atr_get_noparent(target, "LISTEN"); if (a) { if (!tbuf1) tbuf1 = (char *) mush_malloc(BUFFER_LEN, "string"); strcpy(tbuf1, atr_value(a)); if (AF_Regexp(a) ? regexp_match_case(tbuf1, (char *) notify_makestring(msgbuf, messages, NA_ASCII), AF_Case(a)) : wild_match_case(tbuf1, (char *) notify_makestring(msgbuf, messages, NA_ASCII), AF_Case(a))) { if (eval_lock(speaker, target, Listen_Lock)) if (PLAYER_AHEAR || (!IsPlayer(target))) { if (speaker != target) charge_action(speaker, target, "AHEAR"); else charge_action(speaker, target, "AMHEAR"); charge_action(speaker, target, "AAHEAR"); } if (!(flags & NA_NORELAY) && (loc != target) && !filter_found(target, (char *) notify_makestring(msgbuf, messages, NA_ASCII), 1)) { passalong[0] = target; passalong[1] = target; passalong[2] = Owner(target); a = atr_get(target, "INPREFIX"); if (a) { for (j = 0; j < 10; j++) wsave[j] = global_eval_context.wenv[j]; global_eval_context.wenv[0] = (char *) msgbuf; for (j = 1; j < 10; j++) global_eval_context.wenv[j] = NULL; save_global_regs("inprefix_save", preserve); asave = safe_atr_value(a); ap = asave; bp = tbuf1; process_expression(tbuf1, &bp, &ap, target, speaker, speaker, PE_DEFAULT, PT_DEFAULT, NULL); if (bp != tbuf1) safe_chr(' ', tbuf1, &bp); safe_str(msgbuf, tbuf1, &bp); *bp = 0; free(asave); restore_global_regs("inprefix_save", preserve); for (j = 0; j < 10; j++) global_eval_context.wenv[j] = wsave[j]; } notify_anything(speaker, Puppet(target) ? na_except2 : na_except, passalong, NULL, flags | NA_NORELAY | NA_PUPPET, (a) ? tbuf1 : msgbuf); } } } /* if object is flagged LISTENER, check for ^ listen patterns * * these are like AHEAR - object cannot trigger itself. * * unlike normal @listen, don't pass the message on. * */ if ((speaker != target) && (ThingListen(target) || RoomListen(target)) && eval_lock(speaker, target, Listen_Lock) ) atr_comm_match(target, speaker, '^', ':', (char *) notify_makestring(msgbuf, messages, NA_ASCII), 0, NULL, NULL, NULL); /* If object is flagged AUDIBLE and has a @FORWARDLIST, send * stuff on */ if ((!(flags & NA_NORELAY) || (flags & NA_PUPPET)) && Audible(target) && ((a = atr_get_noparent(target, "FORWARDLIST")) != NULL) && !filter_found(target, msgbuf, 0)) { notify_list(speaker, target, "FORWARDLIST", msgbuf, flags); } } for (i = 0; i < MESSAGE_TYPES; i++) { if (messages[i].message) mush_free((Malloc_t) messages[i].message, "string"); if (nospoofs[i].message) mush_free((Malloc_t) nospoofs[i].message, "string"); if (paranoids[i].message) mush_free((Malloc_t) paranoids[i].message, "string"); } if (nospoof) mush_free((Malloc_t) nospoof, "string"); if (paranoid) mush_free((Malloc_t) paranoid, "string"); if (tbuf1) mush_free((Malloc_t) tbuf1, "string"); mush_free((Malloc_t) msgbuf, "string"); na_depth--; } /** Send a message to a series of dbrefs. * This key function takes a speaker's utterance and looks up each * object that should hear it. For each, it may need to render * the utterance in a different fashion (with or without ansi, html, * accents), but we cache each rendered version for efficiency. * \param speaker dbref of object producing the message. * \param func pointer to iterator function to look up each receiver. * \param fdata initial data to pass to func. * \param nsfunc function to call to do NOSPOOF formatting, or NULL. * \param flags flags to pass in (such as NA_INTERACT) * \param message message to render and transmit. */ void notify_anything(dbref speaker, na_lookup func, void *fdata, char *(*nsfunc) (dbref, na_lookup func, void *, int), int flags, const char *message) { dbref loc; if (GoodObject(speaker)) loc = Location(speaker); else loc = NOTHING; notify_anything_loc(speaker, func, fdata, nsfunc, flags, message, loc); } /** Basic 'notify player with message */ #define notify(p,m) notify_anything(orator, na_one, &(p), NULL, 0, m) /** Notify a player with a formatted string, easy version. * This is a safer replacement for notify(player, tprintf(fmt, ...)) * \param speaker dbref of object producing the message. * \param func pointer to iterator function to look up each receiver. * \param fdata initial data to pass to func. * \param nsfunc function to call to do NOSPOOF formatting, or NULL. * \param flags flags to pass in (such as NA_INTERACT) * \param fmt format string. */ void WIN32_CDECL notify_format(dbref player, const char *fmt, ...) { #ifdef HAS_VSNPRINTF char buff[BUFFER_LEN]; #else char buff[BUFFER_LEN * 3]; #endif va_list args; va_start(args, fmt); #ifdef HAS_VSNPRINTF vsnprintf(buff, sizeof buff, fmt, args); #else vsprintf(buff, fmt, args); #endif buff[BUFFER_LEN - 1] = '\0'; va_end(args); notify(player, buff); } /** Notify a player with a formatted string, full version. * This is a safer replacement for notify(player, tprintf(fmt, ...)) * \param speaker dbref of object producing the message. * \param func pointer to iterator function to look up each receiver. * \param fdata initial data to pass to func. * \param nsfunc function to call to do NOSPOOF formatting, or NULL. * \param flags flags to pass in (such as NA_INTERACT) * \param fmt format string. */ void WIN32_CDECL notify_anything_format(dbref speaker, na_lookup func, void *fdata, char *(*nsfunc) (dbref, na_lookup func, void *, int), int flags, const char *fmt, ...) { #ifdef HAS_VSNPRINTF char buff[BUFFER_LEN]; #else char buff[BUFFER_LEN * 3]; #endif va_list args; va_start(args, fmt); #ifdef HAS_VSNPRINTF vsnprintf(buff, sizeof buff, fmt, args); #else vsprintf(buff, fmt, args); #endif buff[BUFFER_LEN - 1] = '\0'; va_end(args); notify_anything(speaker, func, fdata, nsfunc, flags, buff); } /** Send a message to a list of dbrefs on an attribute on an object. * Be sure we don't send a message to the object itself! * \param speaker message speaker * \param thing object containing attribute with list of dbrefs * \param atr attribute with list of dbrefs * \param msg message to transmit * \param flags bitmask of notification option flags */ void notify_list(dbref speaker, dbref thing, const char *atr, const char *msg, int flags) { char *fwdstr, *orig, *curr; char tbuf1[BUFFER_LEN], *bp; dbref fwd; ATTR *a; int nsflags; a = atr_get(thing, atr); if (!a) return; orig = safe_atr_value(a); fwdstr = trim_space_sep(orig, ' '); bp = tbuf1; nsflags = 0; if (!(flags & NA_NOPREFIX)) { make_prefixstr(thing, msg, tbuf1); if (!(flags & NA_SPOOF)) { if (Nospoof(thing)) nsflags |= NA_NOSPOOF; if (Paranoid(thing)) nsflags |= NA_PARANOID; } } else { safe_str(msg, tbuf1, &bp); *bp = 0; } while ((curr = split_token(&fwdstr, ' ')) != NULL) { if (is_objid(curr)) { fwd = parse_objid(curr); if (GoodObject(fwd) && !IsGarbage(fwd) && (thing != fwd) && Can_Forward(thing, fwd)) { if (IsRoom(fwd)) { notify_anything(speaker, na_loc, &fwd, ns_esnotify, flags | nsflags | NA_NORELAY, tbuf1); } else { notify_anything(speaker, na_one, &fwd, ns_esnotify, flags | nsflags | NA_NORELAY, tbuf1); } } } } free((Malloc_t) orig); } /** Safely add a tag into a buffer. * If we support pueblo, this function adds the tag start token, * the tag, and the tag end token. If not, it does nothing. * If we can't fit the tag in, we don't put any of it in. * \param a_tag the html tag to add. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \retval 0, successfully added. * \retval 1, tag wouldn't fit in buffer. */ int safe_tag(char const *a_tag, char *buf, char **bp) { int result = 0; char *save = buf; if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_str(a_tag, buf, bp); result = safe_chr(TAG_END, buf, bp); } /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } /** Safely add a closing tag into a buffer. * If we support pueblo, this function adds the tag start token, * a slash, the tag, and the tag end token. If not, it does nothing. * If we can't fit the tag in, we don't put any of it in. * \param a_tag the html tag to add. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \retval 0, successfully added. * \retval 1, tag wouldn't fit in buffer. */ int safe_tag_cancel(char const *a_tag, char *buf, char **bp) { int result = 0; char *save = buf; if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_chr('/', buf, bp); result = safe_str(a_tag, buf, bp); result = safe_chr(TAG_END, buf, bp); } /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } /** Safely add a tag, some text, and a matching closing tag into a buffer. * If we can't fit the stuff, we don't put any of it in. * \param a_tag the html tag to add. * \param params tag parameters. * \param data the text to wrap the tag around. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \param player the player involved in all this, or NOTHING if internal. * \retval 0, successfully added. * \retval 1, tagged text wouldn't fit in buffer. */ int safe_tag_wrap(char const *a_tag, char const *params, char const *data, char *buf, char **bp, dbref player) { int result = 0; char *save = buf; if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_str(a_tag, buf, bp); if (params && *params && ok_tag_attribute(player, params)) { result = safe_chr(' ', buf, bp); result = safe_str(params, buf, bp); } result = safe_chr(TAG_END, buf, bp); } result = safe_str(data, buf, bp); if (SUPPORT_PUEBLO) { result = safe_tag_cancel(a_tag, buf, bp); } /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } /** Wrapper to notify a single player with a message, unconditionally. * \param player player to notify. * \param msg message to send. */ void raw_notify(dbref player, const char *msg) { notify_anything(GOD, na_one, &player, NULL, NA_NOLISTEN, msg); } /** Notify all connected players with the given flag(s). * If no flags are given, everyone is notified. If one flag list is given, * all connected players with some flag in that list are notified. * If two flag lists are given, all connected players with at least one flag * in each list are notified. * \param flag1 first required flag list or NULL * \param flag2 second required flag list or NULL * \param fmt format string for message to notify. */ void WIN32_CDECL flag_broadcast(const char *flag1, const char *flag2, const char *fmt, ...) { va_list args; char tbuf1[BUFFER_LEN]; DESC *d; int ok; va_start(args, fmt); #ifdef HAS_VSNPRINTF (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args); #else (void) vsprintf(tbuf1, fmt, args); #endif va_end(args); tbuf1[BUFFER_LEN - 1] = '\0'; DESC_ITER_CONN(d) { ok = 1; if (flag1) ok = ok && flaglist_check_long("FLAG", GOD, d->player, flag1, 0); if (flag2) ok = ok && flaglist_check_long("FLAG", GOD, d->player, flag2, 0); if (ok) { queue_string_eol(d, tbuf1); process_output(d); } } } static struct text_block * make_text_block(const unsigned char *s, int n) { struct text_block *p; p = (struct text_block *) mush_malloc(sizeof(struct text_block), "text_block"); if (!p) mush_panic("Out of memory"); p->buf = (unsigned char *) mush_malloc(sizeof(unsigned char) * n, "text_block_buff"); if (!p->buf) mush_panic("Out of memory"); memcpy(p->buf, s, n); p->nchars = n; p->start = p->buf; p->nxt = 0; return p; } /** Free a text_block structure. * \param t pointer to text_block to free. */ void free_text_block(struct text_block *t) { if (t) { if (t->buf) mush_free((Malloc_t) t->buf, "text_block_buff"); mush_free((Malloc_t) t, "text_block"); } } /** Add a new chunk of text to a player's output queue. * \param q pointer to text_queue to add the chunk to. * \param b text to add to the queue. * \param n length of text to add. */ void add_to_queue(struct text_queue *q, const unsigned char *b, int n) { struct text_block *p; if (n == 0) return; p = make_text_block(b, n); 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 = 0; n += strlen(flushed_message); while (n > 0 && (p = q->head)) { n -= p->nchars; really_flushed += p->nchars; q->head = p->nxt; #ifdef DEBUG do_rawlog(LT_ERR, "free_text_block(0x%x) at 1.", p); #endif /* DEBUG */ free_text_block(p); } p = make_text_block((unsigned char *) flushed_message, strlen(flushed_message)); p->nxt = q->head; q->head = p; if (!p->nxt) q->tail = &p->nxt; really_flushed -= p->nchars; return really_flushed; } #ifdef HAS_OPENSSL static int ssl_flush_queue(struct text_queue *q) { struct text_block *p; int n = strlen(flushed_message); /* Remove all text blocks except the first one. */ if (q->head) { while ((p = q->head->nxt)) { q->head->nxt = p->nxt; #ifdef DEBUG do_rawlog(LT_ERR, "free_text_block(0x%x) at 1.", p); #endif /* DEBUG */ free_text_block(p); } } q->tail = &q->head->nxt; /* Set up the flushed message if we can */ if (q->head->nchars + n < MAX_OUTPUT) add_to_queue(q, (unsigned char *) flushed_message, n); /* Return the total size of the message */ return q->head->nchars + n; } #endif /** Render and add text to the queue associated with a given descriptor. * \param d pointer to descriptor to receive the text. * \param b text to send. * \param n length of b. * \return number of characters added. */ int queue_write(DESC *d, const unsigned char *b, int n) { char buff[BUFFER_LEN]; struct notify_strings messages[MESSAGE_TYPES]; unsigned char *s; PUEBLOBUFF; size_t len; if ((n == 2) && (b[0] == '\r') && (b[1] == '\n')) { if ((d->conn_flags & CONN_HTML)) queue_newwrite(d, (unsigned char *) "<BR>\n", 5); else queue_newwrite(d, b, 2); return n; } if (n > BUFFER_LEN) n = BUFFER_LEN; memcpy(buff, b, n); buff[n] = '\0'; zero_strings(messages); if (d->conn_flags & CONN_HTML) { PUSE; tag_wrap("SAMP", NULL, buff); PEND; s = notify_makestring(pbuff, messages, NA_PUEBLO); len = messages[NA_PUEBLO].len; } else { s = notify_makestring(buff, messages, notify_type(d)); len = messages[notify_type(d)].len; } queue_newwrite(d, s, len); free_strings(messages); return n; } /** Add text to the queue associated with a given descriptor. * This is the low-level function that works with already-rendered * text. * \param d pointer to descriptor to receive the text. * \param b text to send. * \param n length of b. * \return number of characters added. */ int queue_newwrite(DESC *d, const unsigned char *b, int n) { int space; #ifdef NT_TCP /* queue up an asynchronous write if using Windows NT */ if (platform == VER_PLATFORM_WIN32_NT) { struct text_block **qp, *cur; DWORD nBytes; BOOL bResult; /* don't write if connection dropped */ if (d->bConnectionDropped) return n; /* add to queue - this makes sure output arrives in the right order */ add_to_queue(&d->output, b, n); d->output_size += n; /* if we are already writing, exit and wait for IO completion */ if (d->bWritePending) return n; /* pull head item out of queue - not necessarily the one we just put in */ qp = &d->output.head; if ((cur = *qp) == NULL) return n; /* nothing in queue - rather strange, but oh well. */ /* if buffer too long, write what we can and queue up the rest */ if (cur->nchars <= sizeof(d->output_buffer)) { nBytes = cur->nchars; memcpy(d->output_buffer, cur->start, nBytes); if (!cur->nxt) d->output.tail = qp; *qp = cur->nxt; free_text_block(cur); } /* end of able to write the lot */ else { nBytes = sizeof(d->output_buffer); memcpy(d->output_buffer, cur->start, nBytes); cur->nchars -= nBytes; cur->start += nBytes; } /* end of buffer too long */ d->OutboundOverlapped.Offset = 0; d->OutboundOverlapped.OffsetHigh = 0; bResult = WriteFile((HANDLE) d->descriptor, d->output_buffer, nBytes, NULL, &d->OutboundOverlapped); d->bWritePending = FALSE; if (!bResult) if (GetLastError() == ERROR_IO_PENDING) d->bWritePending = TRUE; else { d->bConnectionDropped = TRUE; /* do no more writes */ /* post a notification that the descriptor should be shutdown */ if (!PostQueuedCompletionStatus (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) { char sMessage[200]; DWORD nError = GetLastError(); GetErrorMessage(nError, sMessage, sizeof sMessage); printf ("Error %ld (%s) on PostQueuedCompletionStatus in queue_newwrite\n", nError, sMessage); boot_desc(d); } } /* end of had write */ return n; } /* end of NT TCP/IP way of doing it */ #endif space = MAX_OUTPUT - d->output_size - n; if (space < SPILLOVER_THRESHOLD) { process_output(d); space = MAX_OUTPUT - d->output_size - n; if (space < 0) { #ifdef HAS_OPENSSL if (d->ssl) { /* Now we have a problem, as SSL works in blocks and you can't * just partially flush stuff. */ d->output_size = ssl_flush_queue(&d->output); } else #endif d->output_size -= flush_queue(&d->output, -space); } } add_to_queue(&d->output, b, n); d->output_size += n; return n; } /** Add an end-of-line to a descriptor's text queue. * \param d pointer to descriptor to send the eol to. * \return number of characters queued. */ int queue_eol(DESC *d) { if (SUPPORT_PUEBLO && (d->conn_flags & CONN_HTML)) return queue_newwrite(d, (unsigned char *) "<BR>\n", 5); else return queue_newwrite(d, (unsigned char *) "\r\n", 2); } /** Add a string and an end-of-line to a descriptor's text queue. * \param d pointer to descriptor to send to. * \param s string to queue. * \return number of characters queued. */ int queue_string_eol(DESC *d, const char *s) { queue_string(d, s); return queue_eol(d); } /** Add a string to a descriptor's text queue. * \param d pointer to descriptor to send to. * \param s string to queue. * \return number of characters queued. */ int queue_string(DESC *d, const char *s) { unsigned char *n; enum na_type poutput; struct notify_strings messages[MESSAGE_TYPES]; dbref target; int ret; zero_strings(messages); target = d->player; poutput = notify_type(d); n = notify_makestring(s, messages, poutput); ret = queue_newwrite(d, n, messages[poutput].len); free_strings(messages); return ret; } /** Free all text queues associated with a descriptor. * \param d pointer to descriptor. */ void freeqs(DESC *d) { struct text_block *cur, *next; cur = d->output.head; while (cur) { next = cur->nxt; #ifdef DEBUG do_rawlog(LT_ERR, "free_text_block(0x%x) at 3.", cur); #endif /* DEBUG */ 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; #ifdef DEBUG do_rawlog(LT_ERR, "free_text_block(0x%x) at 4.", cur); #endif /* DEBUG */ free_text_block(cur); cur = next; } d->input.head = 0; d->input.tail = &d->input.head; if (d->raw_input) { mush_free((Malloc_t) d->raw_input, "descriptor_raw_input"); } d->raw_input = 0; d->raw_input_at = 0; } /** A notify_anything function for formatting speaker data for NOSPOOF. * * \param speaker the speaker. * * \param func unused. * * \param fdata unused. * * \param para if 1, format for paranoid nospoof; if 0, normal nospoof. * * \return formatted string. * */ char * ns_esnotify(dbref speaker, na_lookup func __attribute__ ((__unused__)), void *fdata __attribute__ ((__unused__)), int para) { char *dest, *bp; bp = dest = mush_malloc(BUFFER_LEN, "string"); if (!GoodObject(speaker)) *dest = '\0'; else if (para) { if (speaker == Owner(speaker)) safe_format(dest, &bp, "[%s(#%d)] ", Name(speaker), speaker); else safe_format(dest, &bp, "[%s(#%d)'s %s(#%d)] ", Name(Owner(speaker)), Owner(speaker), Name(speaker), speaker); } else safe_format(dest, &bp, "[%s:] ", spname(speaker)); *bp = '\0'; return dest; }