unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

misc.c (69113B)

      1 /*
      2  *   Unreal Internet Relay Chat Daemon, src/misc.c
      3  *   Copyright (C) 1990 Jarkko Oikarinen and
      4  *                      University of Oulu, Computing Center
      5  *   Copyright (C) 1999-present UnrealIRCd team
      6  *
      7  *   See file AUTHORS in IRC package for additional names of
      8  *   the programmers.
      9  *
     10  *   This program is free software; you can redistribute it and/or modify
     11  *   it under the terms of the GNU General Public License as published by
     12  *   the Free Software Foundation; either version 1, or (at your option)
     13  *   any later version.
     14  *
     15  *   This program is distributed in the hope that it will be useful,
     16  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  *   GNU General Public License for more details.
     19  *
     20  *   You should have received a copy of the GNU General Public License
     21  *   along with this program; if not, write to the Free Software
     22  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     23  */
     24 
     25 /** @file
     26  * @brief Miscellaneous functions that don't fit in other files.
     27  * Generally these are either simple helper functions or larger
     28  * functions that don't fit in either user.c, channel.c.
     29  */
     30 
     31 #include "unrealircd.h"
     32 
     33 static void exit_one_client(Client *, MessageTag *mtags_i, const char *);
     34 
     35 static const char *months[] = {
     36 	"January", "February", "March", "April",
     37 	"May", "June", "July", "August",
     38 	"September", "October", "November", "December"
     39 };
     40 
     41 static const char *weekdays[] = {
     42 	"Sunday", "Monday", "Tuesday", "Wednesday",
     43 	"Thursday", "Friday", "Saturday"
     44 };
     45 
     46 static const char *short_months[12] = {
     47     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
     48     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
     49 };
     50 
     51 static const char *short_weekdays[7] = {
     52     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
     53 };
     54 
     55 typedef struct {
     56 	int value;			/** Unique integer value of item */
     57 	char character;		/** Unique character assigned to item */
     58 	char *name;			/** Name of item */
     59 } BanActTable;
     60 
     61 static BanActTable banacttable[] = {
     62 	{ BAN_ACT_KILL,		'K',	"kill" },
     63 	{ BAN_ACT_SOFT_KILL,	'i',	"soft-kill" },
     64 	{ BAN_ACT_TEMPSHUN,	'S',	"tempshun" },
     65 	{ BAN_ACT_SOFT_TEMPSHUN,'T',	"soft-tempshun" },
     66 	{ BAN_ACT_SHUN,		's',	"shun" },
     67 	{ BAN_ACT_SOFT_SHUN,	'H',	"soft-shun" },
     68 	{ BAN_ACT_KLINE,	'k',	"kline" },
     69 	{ BAN_ACT_SOFT_KLINE,	'I',	"soft-kline" },
     70 	{ BAN_ACT_ZLINE,	'z',	"zline" },
     71 	{ BAN_ACT_GLINE,	'g',	"gline" },
     72 	{ BAN_ACT_SOFT_GLINE,	'G',	"soft-gline" },
     73 	{ BAN_ACT_GZLINE,	'Z',	"gzline" },
     74 	{ BAN_ACT_BLOCK,	'b',	"block" },
     75 	{ BAN_ACT_SOFT_BLOCK,	'B',	"soft-block" },
     76 	{ BAN_ACT_DCCBLOCK,	'd',	"dccblock" },
     77 	{ BAN_ACT_SOFT_DCCBLOCK,'D',	"soft-dccblock" },
     78 	{ BAN_ACT_VIRUSCHAN,	'v',	"viruschan" },
     79 	{ BAN_ACT_SOFT_VIRUSCHAN,'V',	"soft-viruschan" },
     80 	{ BAN_ACT_WARN,		'w',	"warn" },
     81 	{ BAN_ACT_SOFT_WARN,	'W',	"soft-warn" },
     82 	{ 0, 0, 0 }
     83 };
     84 
     85 typedef struct {
     86 	int value;			/** Unique integer value of item */
     87 	char character;		/** Unique character assigned to item */
     88 	char *name;			/** Name of item */
     89 	char *irccommand;	/** Raw IRC command of item (not unique!) */
     90 } SpamfilterTargetTable;
     91 
     92 SpamfilterTargetTable spamfiltertargettable[] = {
     93 	{ SPAMF_CHANMSG,	'c',	"channel",		"PRIVMSG" },
     94 	{ SPAMF_USERMSG,	'p',	"private",		"PRIVMSG" },
     95 	{ SPAMF_USERNOTICE,	'n',	"private-notice",	"NOTICE" },
     96 	{ SPAMF_CHANNOTICE,	'N',	"channel-notice",	"NOTICE" },
     97 	{ SPAMF_PART,		'P',	"part",			"PART" },
     98 	{ SPAMF_QUIT,		'q',	"quit",			"QUIT" },
     99 	{ SPAMF_DCC,		'd',	"dcc",			"PRIVMSG" },
    100 	{ SPAMF_USER,		'u',	"user",			"NICK" },
    101 	{ SPAMF_AWAY,		'a',	"away",			"AWAY" },
    102 	{ SPAMF_TOPIC,		't',	"topic",		"TOPIC" },
    103 	{ SPAMF_MTAG,		'T',	"message-tag",		"message-tag" },
    104 	{ 0, 0, 0, 0 }
    105 };
    106 
    107 /** IRC Statistics (quite useless?) */
    108 struct IRCStatistics ircstats;
    109 
    110 /** Returns the date in rather long string */
    111 const char *long_date(time_t clock)
    112 {
    113 	static char buf[80], plus;
    114 	struct tm *lt, *gm;
    115 	struct tm gmbuf;
    116 	int  minswest;
    117 
    118 	if (!clock)
    119 		time(&clock);
    120 	gm = gmtime(&clock);
    121 	memcpy(&gmbuf, gm, sizeof(gmbuf));
    122 	gm = &gmbuf;
    123 	lt = localtime(&clock);
    124 #ifndef _WIN32
    125 	if (lt->tm_yday == gm->tm_yday)
    126 		minswest = (gm->tm_hour - lt->tm_hour) * 60 +
    127 		    (gm->tm_min - lt->tm_min);
    128 	else if (lt->tm_yday > gm->tm_yday)
    129 		minswest = (gm->tm_hour - (lt->tm_hour + 24)) * 60;
    130 	else
    131 		minswest = ((gm->tm_hour + 24) - lt->tm_hour) * 60;
    132 #else
    133 	minswest = (_timezone / 60);
    134 #endif
    135 	plus = (minswest > 0) ? '-' : '+';
    136 	if (minswest < 0)
    137 		minswest = -minswest;
    138 	ircsnprintf(buf, sizeof(buf), "%s %s %d %d -- %02d:%02d %c%02d:%02d",
    139 	    weekdays[lt->tm_wday], months[lt->tm_mon], lt->tm_mday,
    140 	    1900 + lt->tm_year,
    141 	    lt->tm_hour, lt->tm_min, plus, minswest / 60, minswest % 60);
    142 
    143 	return buf;
    144 }
    145 
    146 /** Convert timestamp to a short date, a la: Wed Jun 30 21:49:08 1993
    147  * @returns A short date string, or NULL if the timestamp is invalid
    148  * (out of range)
    149  * @param ts   The timestamp
    150  * @param buf  The buffer to store the string (minimum size: 128 bytes),
    151  *             or NULL to use temporary static storage.
    152  */
    153 const char *short_date(time_t ts, char *buf)
    154 {
    155 	struct tm *t = gmtime(&ts);
    156 	static char retbuf[128];
    157 
    158 	if (!buf)
    159 		buf = retbuf;
    160 
    161 	*buf = '\0';
    162 	if (!t)
    163 		return NULL;
    164 
    165 	if (!strftime(buf, 128, "%a %b %d %H:%M:%S %Y", t))
    166 		return NULL;
    167 
    168 	return buf;
    169 }
    170 
    171 /** Return a string with the "pretty date" - yeah, another variant */
    172 const char *pretty_date(time_t t)
    173 {
    174 	static char buf[128];
    175 	struct tm *tm;
    176 
    177 	if (!t)
    178 		time(&t);
    179 	tm = gmtime(&t);
    180 	snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d GMT",
    181 	         1900 + tm->tm_year,
    182 	         tm->tm_mon + 1,
    183 	         tm->tm_mday,
    184 	         tm->tm_hour,
    185 	         tm->tm_min,
    186 	         tm->tm_sec);
    187 
    188 	return buf;
    189 }
    190 
    191 /** Helper function for make_user_host() and friends.
    192  * Fixes a string so that the first white space found becomes an end of
    193  * string marker (`\-`).  returns the 'fixed' string or "*" if the string
    194  * was NULL length or a NULL pointer.
    195  */
    196 const char *check_string(const char *s)
    197 {
    198 	static char buf[512];
    199 	static char star[2] = "*";
    200 	const char *str = s;
    201 
    202 	if (BadPtr(s))
    203 		return star;
    204 
    205 	for (; *s; s++)
    206 	{
    207 		if (isspace(*s))
    208 		{
    209 			/* Because this is an unlikely scenario, we have
    210 			 * delayed the copy until here:
    211 			 */
    212 			strlncpy(buf, s, sizeof(buf), s - str);
    213 			str = buf;
    214 			break;
    215 		}
    216 	}
    217 
    218 	return (BadPtr(str)) ? star : str;
    219 }
    220 
    221 /** Create a user@host based on the provided name and host */
    222 char *make_user_host(const char *name, const char *host)
    223 {
    224 	static char namebuf[USERLEN + HOSTLEN + 6];
    225 
    226 	strlncpy(namebuf, check_string(name), sizeof(namebuf), USERLEN+1);
    227 	strlcat(namebuf, "@", sizeof(namebuf));
    228 	strlncat(namebuf, check_string(host), sizeof(namebuf), HOSTLEN+1);
    229 	return namebuf;
    230 }
    231 
    232 /** Create a nick!user@host string based on the provided variables.
    233  * If any of the variables are NULL, it becomes * (asterisk)
    234  * This is the reentrant safe version.
    235  */
    236 char *make_nick_user_host_r(char *namebuf, size_t namebuflen, const char *nick, const char *name, const char *host)
    237 {
    238 	strlncpy(namebuf, check_string(nick), namebuflen, NICKLEN+1);
    239 	strlcat(namebuf, "!", namebuflen);
    240 	strlncat(namebuf, check_string(name), namebuflen, USERLEN+1);
    241 	strlcat(namebuf, "@", namebuflen);
    242 	strlncat(namebuf, check_string(host), namebuflen, HOSTLEN+1);
    243 	return namebuf;
    244 }
    245 
    246 /** Create a nick!user@host string based on the provided variables.
    247  * If any of the variables are NULL, it becomes * (asterisk)
    248  * This version uses static storage.
    249  */
    250 char *make_nick_user_host(const char *nick, const char *name, const char *host)
    251 {
    252 	static char namebuf[NICKLEN + USERLEN + HOSTLEN + 24];
    253 
    254 	return make_nick_user_host_r(namebuf, sizeof(namebuf), nick, name, host);
    255 }
    256 
    257 
    258 /** Similar to ctime() but without a potential newline and
    259  * also takes a time_t value rather than a pointer.
    260  */
    261 const char *myctime(time_t value)
    262 {
    263 	static char buf[28];
    264 	char *p;
    265 
    266 	strlcpy(buf, ctime(&value), sizeof buf);
    267 	if ((p = strchr(buf, '\n')) != NULL)
    268 		*p = '\0';
    269 
    270 	return buf;
    271 }
    272 
    273 /*
    274 ** get_client_name
    275 **      Return the name of the client for various tracking and
    276 **      admin purposes. The main purpose of this function is to
    277 **      return the "socket host" name of the client, if that
    278 **	differs from the advertised name (other than case).
    279 **	But, this can be used to any client structure.
    280 **
    281 **	Returns:
    282 **	  "name[user@ip#.port]" if 'showip' is true;
    283 **	  "name[sockethost]", if name and sockhost are different and
    284 **	  showip is false; else
    285 **	  "name".
    286 **
    287 ** NOTE 1:
    288 **	Watch out the allocation of "nbuf", if either client->name
    289 **	or client->local->sockhost gets changed into pointers instead of
    290 **	directly allocated within the structure...
    291 **
    292 ** NOTE 2:
    293 **	Function return either a pointer to the structure (client) or
    294 **	to internal buffer (nbuf). *NEVER* use the returned pointer
    295 **	to modify what it points!!!
    296 */
    297 const char *get_client_name(Client *client, int showip)
    298 {
    299 	static char nbuf[HOSTLEN * 2 + USERLEN + 5];
    300 
    301 	if (MyConnect(client))
    302 	{
    303 		if (showip)
    304 			ircsnprintf(nbuf, sizeof(nbuf), "%s[%s@%s.%u]",
    305 			    client->name,
    306 			    IsIdentSuccess(client) ? client->ident : "",
    307 			    client->ip ? client->ip : "???",
    308 			    (unsigned int)client->local->port);
    309 		else
    310 		{
    311 			if (mycmp(client->name, client->local->sockhost))
    312 				ircsnprintf(nbuf, sizeof(nbuf), "%s[%s]",
    313 				    client->name, client->local->sockhost);
    314 			else
    315 				return client->name;
    316 		}
    317 		return nbuf;
    318 	}
    319 	return client->name;
    320 }
    321 
    322 const char *get_client_host(Client *client)
    323 {
    324 	static char nbuf[HOSTLEN * 2 + USERLEN + 5];
    325 
    326 	if (!MyConnect(client))
    327 		return client->name;
    328 	if (!client->local->hostp)
    329 		return get_client_name(client, FALSE);
    330 	ircsnprintf(nbuf, sizeof(nbuf), "%s[%-.*s@%-.*s]",
    331 	    client->name, USERLEN,
    332   	    IsIdentSuccess(client) ? client->ident : "",
    333 	    HOSTLEN, client->local->hostp->h_name);
    334 	return nbuf;
    335 }
    336 
    337 /*
    338  * Set sockhost to 'host'. Skip the user@ part of 'host' if necessary.
    339  */
    340 void set_sockhost(Client *client, const char *host)
    341 {
    342 	const char *s;
    343 	if ((s = strchr(host, '@')))
    344 		s++;
    345 	else
    346 		s = host;
    347 	strlcpy(client->local->sockhost, s, sizeof(client->local->sockhost));
    348 }
    349 
    350 /** Returns 1 if 'from' is on the allow list of 'to' */
    351 int on_dccallow_list(Client *to, Client *from)
    352 {
    353 	Link *lp;
    354 
    355 	for(lp = to->user->dccallow; lp; lp = lp->next)
    356 		if (lp->flags == DCC_LINK_ME && lp->value.client == from)
    357 			return 1;
    358 	return 0;
    359 }
    360 
    361 /** Delete all DCCALLOW references.
    362  * Ultimately, this should be moved to modules/dccallow.c
    363  */
    364 void remove_dcc_references(Client *client)
    365 {
    366 	Client *acptr;
    367 	Link *lp, *nextlp;
    368 	Link **lpp, *tmp;
    369 	int found;
    370 
    371 	lp = client->user->dccallow;
    372 	while(lp)
    373 	{
    374 		nextlp = lp->next;
    375 		acptr = lp->value.client;
    376 		for(found = 0, lpp = &(acptr->user->dccallow); *lpp; lpp=&((*lpp)->next))
    377 		{
    378 			if (lp->flags == (*lpp)->flags)
    379 				continue; /* match only opposite types for sanity */
    380 			if ((*lpp)->value.client == client)
    381 			{
    382 				if ((*lpp)->flags == DCC_LINK_ME)
    383 				{
    384 					sendto_one(acptr, NULL, ":%s %d %s :%s has been removed from "
    385 						"your DCC allow list for signing off",
    386 						me.name, RPL_DCCINFO, acptr->name, client->name);
    387 				}
    388 				tmp = *lpp;
    389 				*lpp = tmp->next;
    390 				free_link(tmp);
    391 				found++;
    392 				break;
    393 			}
    394 		}
    395 
    396 		if (!found)
    397 		{
    398 			unreal_log(ULOG_WARNING, "main", "BUG_REMOVE_DCC_REFERENCES", acptr,
    399 			           "[BUG] remove_dcc_references: $client was in dccallowme "
    400 			           "list of $existing_client but not in dccallowrem list!",
    401 			           log_data_client("existing_client", client));
    402 		}
    403 
    404 		free_link(lp);
    405 		lp = nextlp;
    406 	}
    407 }
    408 
    409 /*
    410  * Remove all clients that depend on source_p; assumes all (S)QUITs have
    411  * already been sent.  we make sure to exit a server's dependent clients
    412  * and servers before the server itself; exit_one_client takes care of
    413  * actually removing things off llists.   tweaked from +CSr31  -orabidoo
    414  */
    415 static void recurse_remove_clients(Client *client, MessageTag *mtags, const char *comment)
    416 {
    417 	Client *acptr, *next;
    418 
    419 	list_for_each_entry_safe(acptr, next, &client_list, client_node)
    420 	{
    421 		if (acptr->uplink != client)
    422 			continue;
    423 
    424 		exit_one_client(acptr, mtags, comment);
    425 	}
    426 
    427 	list_for_each_entry_safe(acptr, next, &global_server_list, client_node)
    428 	{
    429 		if (acptr->uplink != client)
    430 			continue;
    431 
    432 		recurse_remove_clients(acptr, mtags, comment);
    433 		exit_one_client(acptr, mtags, comment);
    434 	}
    435 }
    436 
    437 /*
    438 ** Remove *everything* that depends on source_p, from all lists, and sending
    439 ** all necessary QUITs and SQUITs.  source_p itself is still on the lists,
    440 ** and its SQUITs have been sent except for the upstream one  -orabidoo
    441 */
    442 static void remove_dependents(Client *client, Client *from, MessageTag *mtags, const char *comment, const char *splitstr)
    443 {
    444 	Client *acptr;
    445 
    446 	list_for_each_entry(acptr, &global_server_list, client_node)
    447 	{
    448 		if (acptr != from && !(acptr->direction && (acptr->direction == from)))
    449 			sendto_one(acptr, mtags, "SQUIT %s :%s", client->name, comment);
    450 	}
    451 
    452 	recurse_remove_clients(client, mtags, splitstr);
    453 }
    454 
    455 /*
    456 ** Exit one client, local or remote. Assuming all dependants have
    457 ** been already removed, and socket closed for local client.
    458 */
    459 static void exit_one_client(Client *client, MessageTag *mtags_i, const char *comment)
    460 {
    461 	Link *lp;
    462 	Membership *mp;
    463 
    464 	assert(!IsMe(client));
    465 
    466 	if (IsUser(client))
    467 	{
    468 		MessageTag *mtags_o = NULL;
    469 
    470 		if (!MyUser(client))
    471 			RunHook(HOOKTYPE_REMOTE_QUIT, client, mtags_i, comment);
    472 
    473 		new_message_special(client, mtags_i, &mtags_o, ":%s QUIT", client->name);
    474 		if (find_mtag(mtags_o, "unrealircd.org/real-quit-reason"))
    475 			quit_sendto_local_common_channels(client, mtags_o, comment);
    476 		else
    477 			sendto_local_common_channels(client, NULL, 0, mtags_o, ":%s QUIT :%s", client->name, comment);
    478 		free_message_tags(mtags_o);
    479 
    480 		while ((mp = client->user->channel))
    481 			remove_user_from_channel(client, mp->channel, 1);
    482 		/* again, this is all that is needed */
    483 
    484 		/* Clean up dccallow list and (if needed) notify other clients
    485 		 * that have this person on DCCALLOW that the user just left/got removed.
    486 		 */
    487 		remove_dcc_references(client);
    488 
    489 		/* For remote clients, we need to check for any outstanding async
    490 		 * connects attached to this 'client', and set those records to NULL.
    491 		 * Why not for local? Well, we already do that in close_connection ;)
    492 		 */
    493 		if (!MyConnect(client))
    494 			unrealdns_delreq_bycptr(client);
    495 	}
    496 
    497 	/* Free module related data for this client */
    498 	moddata_free_client(client);
    499 	if (MyConnect(client))
    500 		moddata_free_local_client(client);
    501 
    502 	/* Remove client from the client list */
    503 	if (*client->id)
    504 	{
    505 		del_from_id_hash_table(client->id, client);
    506 		*client->id = '\0';
    507 	}
    508 	if (*client->name)
    509 		del_from_client_hash_table(client->name, client);
    510 	if (remote_rehash_client == client)
    511 		remote_rehash_client = NULL; /* client did a /REHASH and QUIT before rehash was complete */
    512 	remove_client_from_list(client);
    513 }
    514 
    515 /** Exit this IRC client, and all the dependents (users, servers) if this is a server.
    516  * @param client        The client to exit.
    517  * @param recv_mtags  Message tags to use as a base (if any).
    518  * @param comment     The (s)quit message
    519  */
    520 void exit_client(Client *client, MessageTag *recv_mtags, const char *comment)
    521 {
    522 	exit_client_ex(client, client->direction, recv_mtags, comment);
    523 }
    524 
    525 /** Exit this IRC client, and all the dependents (users, servers) if this is a server.
    526  * @param client        The client to exit.
    527  * @param recv_mtags  Message tags to use as a base (if any).
    528  * @param comment     The (s)quit message
    529  */
    530 void exit_client_fmt(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...)
    531 {
    532 	char comment[512];
    533 
    534 	va_list vl;
    535 	va_start(vl, pattern);
    536 	vsnprintf(comment, sizeof(comment), pattern, vl);
    537 	va_end(vl);
    538 
    539 	exit_client_ex(client, client->direction, recv_mtags, comment);
    540 }
    541 
    542 /** Exit this IRC client, and all the dependents (users, servers) if this is a server.
    543  * @param client        The client to exit.
    544  * @param recv_mtags  Message tags to use as a base (if any).
    545  * @param comment     The (s)quit message
    546  */
    547 void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment)
    548 {
    549 	long long on_for;
    550 	ConfigItem_listen *listen_conf;
    551 	MessageTag *mtags_generated = NULL;
    552 
    553 	if (IsDead(client))
    554 		return; /* Already marked as exited */
    555 
    556 	/* We replace 'recv_mtags' here with a newly
    557 	 * generated id if 'recv_mtags' is NULL or is
    558 	 * non-NULL and contains no msgid etc.
    559 	 * This saves us from doing a new_message()
    560 	 * prior to the exit_client() call at around
    561 	 * 100+ places elsewhere in the code.
    562 	 */
    563 	new_message(client, recv_mtags, &mtags_generated);
    564 	recv_mtags = mtags_generated;
    565 
    566 	if (MyConnect(client))
    567 	{
    568 		if (client->local->class)
    569 		{
    570 			client->local->class->clients--;
    571 			if ((client->local->class->flag.temporary) && !client->local->class->clients && !client->local->class->xrefcount)
    572 			{
    573 				delete_classblock(client->local->class);
    574 				client->local->class = NULL;
    575 			}
    576 		}
    577 		if (IsUser(client))
    578 			irccounts.me_clients--;
    579 		if (client->server && client->server->conf)
    580 		{
    581 			client->server->conf->refcount--;
    582 			if (!client->server->conf->refcount
    583 			  && client->server->conf->flag.temporary)
    584 			{
    585 				delete_linkblock(client->server->conf);
    586 				client->server->conf = NULL;
    587 			}
    588 		}
    589 		if (IsServer(client))
    590 		{
    591 			irccounts.me_servers--;
    592 			if (!IsServerDisconnectLogged(client))
    593 			{
    594 				unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client,
    595 					   "Lost server link to $client [$client.ip]: $reason",
    596 					   log_data_string("reason", comment));
    597 			}
    598 		}
    599 		free_pending_net(client);
    600 		SetClosing(client);
    601 		if (IsUser(client))
    602 		{
    603 			long connected_time = TStime() - client->local->creationtime;
    604 			RunHook(HOOKTYPE_LOCAL_QUIT, client, recv_mtags, comment);
    605 			unreal_log(ULOG_INFO, "connect", "LOCAL_CLIENT_DISCONNECT", client,
    606 				   "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)",
    607 				   log_data_string("extended_client_info", get_connect_extinfo(client)),
    608 				   log_data_string("reason", comment),
    609 				   log_data_integer("connected_time", connected_time));
    610 		} else
    611 		if (IsUnknown(client))
    612 		{
    613 			RunHook(HOOKTYPE_UNKUSER_QUIT, client, recv_mtags, comment);
    614 		}
    615 
    616 		if (client->local->fd >= 0 && !IsConnecting(client))
    617 		{
    618 			if (!IsControl(client) && !IsRPC(client))
    619 				sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", get_client_name(client, FALSE), comment);
    620 		}
    621 		close_connection(client);
    622 	}
    623 	else if (IsUser(client) && !IsULine(client))
    624 	{
    625 		if (client->uplink != &me)
    626 		{
    627 			unreal_log(ULOG_INFO, "connect", "REMOTE_CLIENT_DISCONNECT", client,
    628 				   "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)",
    629 				   log_data_string("extended_client_info", get_connect_extinfo(client)),
    630 				   log_data_string("reason", comment),
    631 				   log_data_string("from_server_name", client->user->server));
    632 		}
    633 	}
    634 
    635 	/*
    636 	 * Recurse down the client list and get rid of clients who are no
    637 	 * longer connected to the network (from my point of view)
    638 	 * Only do this expensive stuff if exited==server -Donwulff
    639 	 */
    640 	if (IsServer(client))
    641 	{
    642 		char splitstr[HOSTLEN + HOSTLEN + 2];
    643 		Client *acptr, *next;
    644 
    645 		assert(client->server != NULL && client->uplink != NULL);
    646 
    647 		if (FLAT_MAP)
    648 			strlcpy(splitstr, "*.net *.split", sizeof splitstr);
    649 		else
    650 			ircsnprintf(splitstr, sizeof splitstr, "%s %s", client->uplink->name, client->name);
    651 
    652 		remove_dependents(client, origin, recv_mtags, comment, splitstr);
    653 
    654 		/* Special case for remote async RPC, server.rehash in particular.. */
    655 		list_for_each_entry_safe(acptr, next, &rpc_remote_list, client_node)
    656 			if (!strncmp(client->id, acptr->id, SIDLEN))
    657 				free_client(acptr);
    658 
    659 		RunHook(HOOKTYPE_SERVER_QUIT, client, recv_mtags);
    660 	}
    661 	else if (IsUser(client) && !IsKilled(client))
    662 	{
    663 		sendto_server(client, 0, 0, recv_mtags, ":%s QUIT :%s", client->id, comment);
    664 	}
    665 
    666 	/* Finally, the client/server itself exits.. */
    667 	exit_one_client(client, recv_mtags, comment);
    668 
    669 	free_message_tags(mtags_generated);
    670 }
    671 
    672 /** Initialize the (quite useless) IRC statistics */
    673 void initstats(void)
    674 {
    675 	memset(&ircstats, 0, sizeof(ircstats));
    676 }
    677 
    678 /** Verify operator count, to catch bugs introduced by flawed services */
    679 void verify_opercount(Client *orig, const char *tag)
    680 {
    681 	int counted = 0;
    682 	Client *client;
    683 	char text[2048];
    684 
    685 	list_for_each_entry(client, &client_list, client_node)
    686 	{
    687 		if (IsOper(client) && !IsHideOper(client))
    688 			counted++;
    689 	}
    690 	if (counted == irccounts.operators)
    691 		return;
    692 	unreal_log(ULOG_WARNING, "main", "BUG_LUSERS_OPERS", orig,
    693 	           "[BUG] Operator count bug at $where! Value in /LUSERS is $opers, "
    694 	           "we counted $counted_opers, "
    695 	           "triggered by $client.details on $client.user.servername",
    696 	           log_data_integer("opers", irccounts.operators),
    697 	           log_data_integer("counted_opers", counted),
    698 	           log_data_string("where", tag));
    699 	irccounts.operators = counted;
    700 }
    701 
    702 /** Check if the specified hostname does not contain forbidden characters.
    703  * @param host		The host name to check
    704  * @param strict	If set to 1 then we also check if the hostname
    705  *                      resembles an IP address (eg contains ':') and
    706  *                      some other stuff that we don't consider valid
    707  *                      in actual DNS names (eg '/').
    708  * @returns 1 if valid, 0 if not.
    709  */
    710 int valid_host(const char *host, int strict)
    711 {
    712 	const char *p;
    713 
    714 	if (!*host)
    715 		return 0; /* must at least contain something */
    716 
    717 	if (strlen(host) > HOSTLEN)
    718 		return 0; /* too long hosts are invalid too */
    719 
    720 	if (strict)
    721 	{
    722 		for (p=host; *p; p++)
    723 			if (!isalnum(*p) && !strchr("_-.", *p))
    724 				return 0;
    725 	} else {
    726 		for (p=host; *p; p++)
    727 			if (!isalnum(*p) && !strchr("_-.:/", *p))
    728 				return 0;
    729 	}
    730 
    731 	return 1;
    732 }
    733 
    734 /** Check if the specified ident / user name does not contain forbidden characters.
    735  * @param username	The username / ident to check
    736  * @returns 1 if valid, 0 if not.
    737  */
    738 int valid_username(const char *username)
    739 {
    740 	const char *s;
    741 
    742 	if (strlen(username) > USERLEN)
    743 		return 0; /* Too long */
    744 
    745 	for (s = username; *s; s++)
    746 	{
    747 		if ((*s == '~') && (s == username))
    748 			continue;
    749 		if (!isallowed(*s))
    750 			return 0;
    751 	}
    752 
    753 	return 1;
    754 }
    755 
    756 /** Check validity of a vhost which can be both in 'host' or 'user@host' format.
    757  * This will call valid_username() and valid_host(xxx, 0) accordingly.
    758  * @param userhost the "host" or "user@host"
    759  * @returns 1 if valid, 0 if not.
    760  */
    761 int valid_vhost(const char *userhost)
    762 {
    763 	char uhost[512], *p;
    764 	const char *host = userhost;
    765 
    766         strlcpy(uhost, userhost, sizeof(uhost));
    767 
    768 	if ((p = strchr(uhost, '@')))
    769 	{
    770 		*p++ = '\0';
    771 		if (!valid_username(uhost))
    772 			return 0;
    773 		host = p;
    774 	}
    775 
    776 	if (!valid_host(host, 0))
    777 		return 0;
    778 
    779 	return 1;
    780 }
    781 
    782 /*|| BAN ACTION ROUTINES FOLLOW ||*/
    783 
    784 /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */
    785 BanAction banact_stringtoval(const char *s)
    786 {
    787 	BanActTable *b;
    788 
    789 	for (b = &banacttable[0]; b->value; b++)
    790 		if (!strcasecmp(s, b->name))
    791 			return b->value;
    792 	return 0;
    793 }
    794 
    795 /** Converts a banaction character (eg: 'K') to an integer value (eg: BAN_ACT_KILL) */
    796 BanAction banact_chartoval(char c)
    797 {
    798 	BanActTable *b;
    799 
    800 	for (b = &banacttable[0]; b->value; b++)
    801 		if (b->character == c)
    802 			return b->value;
    803 	return 0;
    804 }
    805 
    806 /** Converts a banaction value (eg: BAN_ACT_KILL) to a character value (eg: 'k') */
    807 char banact_valtochar(BanAction val)
    808 {
    809 	BanActTable *b;
    810 
    811 	for (b = &banacttable[0]; b->value; b++)
    812 		if (b->value == val)
    813 			return b->character;
    814 	return '\0';
    815 }
    816 
    817 /** Converts a banaction value (eg: BAN_ACT_KLINE) to a string (eg: "kline") */
    818 const char *banact_valtostring(BanAction val)
    819 {
    820 	BanActTable *b;
    821 
    822 	for (b = &banacttable[0]; b->value; b++)
    823 		if (b->value == val)
    824 			return b->name;
    825 	return "UNKNOWN";
    826 }
    827 
    828 /*|| BAN TARGET ROUTINES FOLLOW ||*/
    829 
    830 /** Extract target flags from string 's'. */
    831 int spamfilter_gettargets(const char *s, Client *client)
    832 {
    833 SpamfilterTargetTable *e;
    834 int flags = 0;
    835 
    836 	for (; *s; s++)
    837 	{
    838 		for (e = &spamfiltertargettable[0]; e->value; e++)
    839 			if (e->character == *s)
    840 			{
    841 				flags |= e->value;
    842 				break;
    843 			}
    844 		if (!e->value && client)
    845 		{
    846 			sendnotice(client, "Unknown target type '%c'", *s);
    847 			return 0;
    848 		}
    849 	}
    850 	return flags;
    851 }
    852 
    853 /** Convert a string with a targetname to an integer value */
    854 int spamfilter_getconftargets(const char *s)
    855 {
    856 SpamfilterTargetTable *e;
    857 
    858 	for (e = &spamfiltertargettable[0]; e->value; e++)
    859 		if (!strcmp(s, e->name))
    860 			return e->value;
    861 	return 0;
    862 }
    863 
    864 /** Create a string with (multiple) targets from an integer mask */
    865 char *spamfilter_target_inttostring(int v)
    866 {
    867 	static char buf[128];
    868 	SpamfilterTargetTable *e;
    869 	char *p = buf;
    870 
    871 	for (e = &spamfiltertargettable[0]; e->value; e++)
    872 		if (v & e->value)
    873 			*p++ = e->character;
    874 	*p = '\0';
    875 	return buf;
    876 }
    877 
    878 /** Replace underscores back to the space character.
    879  * This is used for the spamfilter reason.
    880  */
    881 char *unreal_decodespace(const char *s)
    882 {
    883 	const char *i;
    884 	static char buf[512], *o;
    885 
    886 	for (i = s, o = buf; (*i) && (o < buf+510); i++)
    887 		if (*i == '_')
    888 		{
    889 			if (i[1] != '_')
    890 				*o++ = ' ';
    891 			else {
    892 				*o++ = '_';
    893 				i++;
    894 			}
    895 		}
    896 		else
    897 			*o++ = *i;
    898 	*o = '\0';
    899 	return buf;
    900 }
    901 
    902 /** Replace spaces to underscore characters.
    903  * This is used for the spamfilter reason.
    904  */
    905 char *unreal_encodespace(const char *s)
    906 {
    907 	const char *i;
    908 	static char buf[512], *o;
    909 
    910 	if (!s)
    911 		return NULL; /* NULL in = NULL out */
    912 
    913 	for (i = s, o = buf; (*i) && (o < buf+509); i++)
    914 	{
    915 		if (*i == ' ')
    916 			*o++ = '_';
    917 		else if (*i == '_')
    918 		{
    919 			*o++ = '_';
    920 			*o++ = '_';
    921 		}
    922 		else
    923 			*o++ = *i;
    924 	}
    925 	*o = '\0';
    926 	return buf;
    927 }
    928 
    929 /** This is basically only used internally by match_spamfilter()... */
    930 const char *cmdname_by_spamftarget(int target)
    931 {
    932 	SpamfilterTargetTable *e;
    933 
    934 	for (e = &spamfiltertargettable[0]; e->value; e++)
    935 		if (e->value == target)
    936 			return e->irccommand;
    937 	return "???";
    938 }
    939 
    940 /** Returns 1 if this is a channel from set::auto-join or set::oper-auto-join */
    941 int is_autojoin_chan(const char *chname)
    942 {
    943 	char buf[512];
    944 	char *p, *name;
    945 
    946 	if (OPER_AUTO_JOIN_CHANS)
    947 	{
    948 		strlcpy(buf, OPER_AUTO_JOIN_CHANS, sizeof(buf));
    949 
    950 		for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
    951 			if (!strcasecmp(name, chname))
    952 				return 1;
    953 	}
    954 
    955 	if (AUTO_JOIN_CHANS)
    956 	{
    957 		strlcpy(buf, AUTO_JOIN_CHANS, sizeof(buf));
    958 
    959 		for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
    960 			if (!strcasecmp(name, chname))
    961 				return 1;
    962 	}
    963 
    964 	return 0;
    965 }
    966 
    967 /** Add name entries from config */
    968 void unreal_add_names(NameList **n, ConfigEntry *ce)
    969 {
    970 	if (ce->items)
    971 	{
    972 		ConfigEntry *cep;
    973 		for (cep = ce->items; cep; cep = cep->next)
    974 			_add_name_list(n, cep->value ? cep->value : cep->name);
    975 	} else
    976 	if (ce->value)
    977 	{
    978 		_add_name_list(n, ce->value);
    979 	}
    980 }
    981 
    982 /** Add name/value entries from config */
    983 void unreal_add_name_values(NameValuePrioList **n, const char *name, ConfigEntry *ce)
    984 {
    985 	if (ce->items)
    986 	{
    987 		ConfigEntry *cep;
    988 		for (cep = ce->items; cep; cep = cep->next)
    989 			add_nvplist(n, 0, name, cep->value ? cep->value : cep->name);
    990 	} else
    991 	if (ce->value)
    992 	{
    993 		add_nvplist(n, 0, name, ce->value);
    994 	}
    995 }
    996 
    997 /** Prints the name:value pair of a NameValuePrioList */
    998 const char *namevalue(NameValuePrioList *n)
    999 {
   1000 	static char buf[512];
   1001 
   1002 	if (!n->name)
   1003 		return "";
   1004 
   1005 	if (!n->value)
   1006 		return n->name;
   1007 
   1008 	snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value);
   1009 	return buf;
   1010 }
   1011 
   1012 /** Version of namevalue() but replaces spaces with underscores.
   1013  * Used in for example numeric sending routines where a field
   1014  * may not contain any spaces.
   1015  */
   1016 const char *namevalue_nospaces(NameValuePrioList *n)
   1017 {
   1018 	static char buf[512];
   1019 	char *p;
   1020 
   1021 	if (!n->name)
   1022 		return "";
   1023 
   1024 	if (!n->value)
   1025 		strlcpy(buf, n->name, sizeof(buf));
   1026 
   1027 	snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value);
   1028 
   1029 	/* Replace spaces with underscores */
   1030 	for (p=buf; *p; p++)
   1031 		if (*p == ' ')
   1032 			*p = '_';
   1033 
   1034 	return buf;
   1035 }
   1036 
   1037 /** Our own strcasestr implementation because strcasestr is
   1038  * often not available or is not working correctly.
   1039  */
   1040 char *our_strcasestr(const char *haystack, const char *needle)
   1041 {
   1042 	int i;
   1043 	int nlength = strlen(needle);
   1044 	int hlength = strlen(haystack);
   1045 
   1046 	if (nlength > hlength)
   1047 		return NULL;
   1048 
   1049 	if (hlength <= 0)
   1050 		return NULL;
   1051 
   1052 	if (nlength <= 0)
   1053 		return (char *)haystack;
   1054 
   1055 	for (i = 0; i <= (hlength - nlength); i++)
   1056 	{
   1057 		if (strncasecmp (haystack + i, needle, nlength) == 0)
   1058 			return (char *)(haystack + i);
   1059 	}
   1060 
   1061 	return NULL; /* not found */
   1062 }
   1063 
   1064 /** Add a title to the users' WHOIS ("special whois"). Broadcast change to servers.
   1065  * @param client	The client
   1066  * @param tag		A tag used internally and for server-to-server traffic,
   1067  *			not visible to end-users.
   1068  * @param priority	Priority - for ordering multiple swhois entries
   1069  *                      (lower number = further up in the swhoises list in WHOIS)
   1070  * @param swhois	The actual special whois title (string) you want to add to the user
   1071  * @param from		Who added this entry
   1072  * @param skip		Which server(-side) to skip broadcasting this entry to.
   1073  */
   1074 int swhois_add(Client *client, const char *tag, int priority, const char *swhois, Client *from, Client *skip)
   1075 {
   1076 	SWhois *s;
   1077 
   1078 	/* Make sure the line isn't added yet. If so, then bail out silently. */
   1079 	for (s = client->user->swhois; s; s = s->next)
   1080 		if (!strcmp(s->line, swhois))
   1081 			return -1; /* exists */
   1082 
   1083 	s = safe_alloc(sizeof(SWhois));
   1084 	safe_strdup(s->line, swhois);
   1085 	safe_strdup(s->setby, tag);
   1086 	s->priority = priority;
   1087 	AddListItemPrio(s, client->user->swhois, s->priority);
   1088 
   1089 	sendto_server(skip, 0, PROTO_EXTSWHOIS, NULL, ":%s SWHOIS %s :%s",
   1090 		from->id, client->id, swhois);
   1091 
   1092 	sendto_server(skip, PROTO_EXTSWHOIS, 0, NULL, ":%s SWHOIS %s + %s %d :%s",
   1093 		from->id, client->id, tag, priority, swhois);
   1094 
   1095 	return 0;
   1096 }
   1097 
   1098 /** Delete swhois title(s).
   1099  * Delete swhois by tag and swhois. Then broadcast this change to all other servers.
   1100  * @param client	The client
   1101  * @param tag		A tag used internally and for server-to-server traffic,
   1102  *			not visible to end-users.
   1103  * @param swhois	The actual special whois title (string) you are removing
   1104  * @param from		Who added this entry earlier on
   1105  * @param skip		Which server(-side) to skip broadcasting this entry to.
   1106  * @note If you use swhois "*" then it will remove all swhois titles for that tag
   1107  */
   1108 int swhois_delete(Client *client, const char *tag, const char *swhois, Client *from, Client *skip)
   1109 {
   1110 	SWhois *s, *s_next;
   1111 	int ret = -1; /* default to 'not found' */
   1112 
   1113 	for (s = client->user->swhois; s; s = s_next)
   1114 	{
   1115 		s_next = s->next;
   1116 
   1117 		/* If ( same swhois or "*" ) AND same tag */
   1118 		if ( ((!strcmp(s->line, swhois) || !strcmp(swhois, "*")) &&
   1119 		    !strcmp(s->setby, tag)))
   1120 		{
   1121 			DelListItem(s, client->user->swhois);
   1122 			safe_free(s->line);
   1123 			safe_free(s->setby);
   1124 			safe_free(s);
   1125 
   1126 			sendto_server(skip, 0, PROTO_EXTSWHOIS, NULL, ":%s SWHOIS %s :",
   1127 				from->id, client->id);
   1128 
   1129 			sendto_server(skip, PROTO_EXTSWHOIS, 0, NULL, ":%s SWHOIS %s - %s %d :%s",
   1130 				from->id, client->id, tag, 0, swhois);
   1131 
   1132 			ret = 0;
   1133 		}
   1134 	}
   1135 
   1136 	return ret;
   1137 }
   1138 
   1139 /** Is this user using a websocket? (LOCAL USERS ONLY) */
   1140 int IsWebsocket(Client *client)
   1141 {
   1142 	ModDataInfo *md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
   1143 	if (!md)
   1144 		return 0; /* websocket module not loaded */
   1145 	return (MyConnect(client) && moddata_client(client, md).ptr) ? 1 : 0;
   1146 }
   1147 
   1148 /** Generic function to inform the user he/she has been banned.
   1149  * @param client   The affected client.
   1150  * @param bantype  The ban type, such as: "K-Lined", "G-Lined" or "realname".
   1151  * @param reason   The specified reason.
   1152  * @param global   Whether the ban is global (1) or for this server only (0)
   1153  * @param noexit   Set this to NO_EXIT_CLIENT to make us not call exit_client().
   1154  *                 This is really only needed from the accept code, do not
   1155  *                 use it anywhere else. No really, never.
   1156  *
   1157  * @note This function will call exit_client() appropriately.
   1158  */
   1159 void banned_client(Client *client, const char *bantype, const char *reason, int global, int noexit)
   1160 {
   1161 	char buf[512];
   1162 	char *fmt = global ? iConf.reject_message_gline : iConf.reject_message_kline;
   1163 	const char *vars[6], *values[6];
   1164 	MessageTag *mtags = NULL;
   1165 
   1166 	if (!MyConnect(client))
   1167 		abort();
   1168 
   1169 	/* This was: "You are not welcome on this %s. %s: %s. %s" but is now dynamic: */
   1170 	vars[0] = "bantype";
   1171 	values[0] = bantype;
   1172 	vars[1] = "banreason";
   1173 	values[1] = reason;
   1174 	vars[2] = "klineaddr";
   1175 	values[2] = KLINE_ADDRESS;
   1176 	vars[3] = "glineaddr";
   1177 	values[3] = GLINE_ADDRESS ? GLINE_ADDRESS : KLINE_ADDRESS; /* fallback to klineaddr */
   1178 	vars[4] = "ip";
   1179 	values[4] = GetIP(client);
   1180 	vars[5] = NULL;
   1181 	values[5] = NULL;
   1182 	buildvarstring(fmt, buf, sizeof(buf), vars, values);
   1183 
   1184 	/* This is a bit extensive but we will send both a YOUAREBANNEDCREEP
   1185 	 * and a notice to the user.
   1186 	 * The YOUAREBANNEDCREEP will be helpful for the client since it makes
   1187 	 * clear the user should not quickly reconnect, as (s)he is banned.
   1188 	 * The notice still needs to be there because it stands out well
   1189 	 * at most IRC clients.
   1190 	 */
   1191 	if (noexit != NO_EXIT_CLIENT)
   1192 	{
   1193 		sendnumeric(client, ERR_YOUREBANNEDCREEP, buf);
   1194 		sendnotice(client, "%s", buf);
   1195 	} else {
   1196 		send_raw_direct(client, ":%s %d %s :%s",
   1197 		         me.name, ERR_YOUREBANNEDCREEP,
   1198 		         (*client->name ? client->name : "*"),
   1199 		         buf);
   1200 		send_raw_direct(client, ":%s NOTICE %s :%s",
   1201 		         me.name, (*client->name ? client->name : "*"), buf);
   1202 	}
   1203 
   1204 	/* The final message in the ERROR is shorter. */
   1205 	if (HIDE_BAN_REASON && IsRegistered(client))
   1206 	{
   1207 		/* Hide the ban reason, but put the real reason in unrealircd.org/real-quit-reason */
   1208 		MessageTag *m = safe_alloc(sizeof(MessageTag));
   1209 		safe_strdup(m->name, "unrealircd.org/real-quit-reason");
   1210 		snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
   1211 		safe_strdup(m->value, buf);
   1212 		AddListItem(m, mtags);
   1213 		/* And the quit reason for anyone else, goes here.. */
   1214 		snprintf(buf, sizeof(buf), "Banned (%s)", bantype);
   1215 	} else {
   1216 		snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
   1217 	}
   1218 
   1219 	if (noexit != NO_EXIT_CLIENT)
   1220 	{
   1221 		exit_client(client, mtags, buf);
   1222 	} else {
   1223 		/* Special handling for direct Z-line code */
   1224 		send_raw_direct(client, "ERROR :Closing Link: [%s] (%s)",
   1225 		           client->ip, buf);
   1226 	}
   1227 	safe_free_message_tags(mtags);
   1228 }
   1229 
   1230 /** Our stpcpy implementation - discouraged due to lack of bounds checking */
   1231 char *mystpcpy(char *dst, const char *src)
   1232 {
   1233 	for (; *src; src++)
   1234 		*dst++ = *src;
   1235 	*dst = '\0';
   1236 	return dst;
   1237 }
   1238 
   1239 /** Helper function for send_channel_modes_sjoin3() and cmd_sjoin()
   1240  * to build the SJSBY prefix which is <seton,setby> to
   1241  * communicate when the ban was set and by whom.
   1242  * @param buf   The buffer to write to
   1243  * @param setby The setter of the "ban"
   1244  * @param seton The time the "ban" was set
   1245  * @retval The number of bytes written EXCLUDING the NUL byte,
   1246  *         so similar to what strlen() would have returned.
   1247  * @note Caller must ensure that the buffer 'buf' is of sufficient size.
   1248  */
   1249 size_t add_sjsby(char *buf, const char *setby, time_t seton)
   1250 {
   1251 	char tbuf[32];
   1252 	char *p = buf;
   1253 
   1254 	snprintf(tbuf, sizeof(tbuf), "%ld", (long)seton);
   1255 
   1256 	*p++ = '<';
   1257 	p = mystpcpy(p, tbuf);
   1258 	*p++ = ',';
   1259 	p = mystpcpy(p, setby);
   1260 	*p++ = '>';
   1261 	*p = '\0';
   1262 
   1263 	return p - buf;
   1264 }
   1265 
   1266 /** Concatenate the entire parameter string.
   1267  * The function will take care of spaces in the final parameter (if any).
   1268  * @param buf   The buffer to output in.
   1269  * @param len   Length of the buffer.
   1270  * @param parc  Parameter count, ircd style.
   1271  * @param parv  Parameters, ircd style, so we will start at parv[1].
   1272  * @section ex1 Example
   1273  * @code
   1274  * char buf[512];
   1275  * concat_params(buf, sizeof(buf), parc, parv);
   1276  * sendto_server(client, 0, 0, recv_mtags, ":%s SOMECOMMAND %s", client->name, buf);
   1277  * @endcode
   1278  */
   1279 void concat_params(char *buf, int len, int parc, const char *parv[])
   1280 {
   1281 	int i;
   1282 
   1283 	*buf = '\0';
   1284 	for (i = 1; i < parc; i++)
   1285 	{
   1286 		const char *param = parv[i];
   1287 
   1288 		if (!param)
   1289 			break;
   1290 
   1291 		if (*buf)
   1292 			strlcat(buf, " ", len);
   1293 
   1294 		if (strchr(param, ' ') || (*param == ':'))
   1295 		{
   1296 			/* Last parameter, with : */
   1297 			strlcat(buf, ":", len);
   1298 			strlcat(buf, parv[i], len);
   1299 			break;
   1300 		}
   1301 		strlcat(buf, parv[i], len);
   1302 	}
   1303 }
   1304 
   1305 /** Find a particular message-tag in the 'mtags' list */
   1306 MessageTag *find_mtag(MessageTag *mtags, const char *token)
   1307 {
   1308 	for (; mtags; mtags = mtags->next)
   1309 		if (!strcmp(mtags->name, token))
   1310 			return mtags;
   1311 	return NULL;
   1312 }
   1313 
   1314 /** Free all message tags in the list 'm' */
   1315 void free_message_tags(MessageTag *m)
   1316 {
   1317 	MessageTag *m_next;
   1318 
   1319 	for (; m; m = m_next)
   1320 	{
   1321 		m_next = m->next;
   1322 		safe_free(m->name);
   1323 		safe_free(m->value);
   1324 		safe_free(m);
   1325 	}
   1326 }
   1327 
   1328 /** Duplicate a MessageTag structure.
   1329  * @note  This duplicate a single MessageTag.
   1330  *        It does not duplicate an entire linked list.
   1331  */
   1332 MessageTag *duplicate_mtag(MessageTag *mtag)
   1333 {
   1334 	MessageTag *m = safe_alloc(sizeof(MessageTag));
   1335 	safe_strdup(m->name, mtag->name);
   1336 	safe_strdup(m->value, mtag->value);
   1337 	return m;
   1338 }
   1339 
   1340 /** New message. Either really brand new, or inherited from other servers.
   1341  * This function calls modules so they can add tags, such as:
   1342  * msgid, time and account.
   1343  */
   1344 void new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list)
   1345 {
   1346 	Hook *h;
   1347 	for (h = Hooks[HOOKTYPE_NEW_MESSAGE]; h; h = h->next)
   1348 		(*(h->func.voidfunc))(sender, recv_mtags, mtag_list, NULL);
   1349 }
   1350 
   1351 /** New message - SPECIAL edition. Either really brand new, or inherited
   1352  * from other servers.
   1353  * This function calls modules so they can add tags, such as:
   1354  * msgid, time and account.
   1355  * This special version deals in a special way with msgid in particular.
   1356  * The pattern and vararg create a 'signature', this is normally
   1357  * identical to the message that is sent to clients (end-users).
   1358  * For example ":xyz JOIN #chan".
   1359  */
   1360 void new_message_special(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, FORMAT_STRING(const char *pattern), ...)
   1361 {
   1362 	Hook *h;
   1363 	va_list vl;
   1364 	char buf[512];
   1365 
   1366 	va_start(vl, pattern);
   1367 	ircvsnprintf(buf, sizeof(buf), pattern, vl);
   1368 	va_end(vl);
   1369 
   1370 	for (h = Hooks[HOOKTYPE_NEW_MESSAGE]; h; h = h->next)
   1371 		(*(h->func.voidfunc))(sender, recv_mtags, mtag_list, buf);
   1372 }
   1373 
   1374 /** Default handler for parse_message_tags().
   1375  * This is only used if the 'mtags' module is NOT loaded,
   1376  * which would be quite unusual, but possible.
   1377  */
   1378 void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list)
   1379 {
   1380 	/* Just skip everything until the space character */
   1381 	for (; **str && **str != ' '; *str = *str + 1);
   1382 }
   1383 
   1384 /** Default handler for mtags_to_string().
   1385  * This is only used if the 'mtags' module is NOT loaded,
   1386  * which would be quite unusual, but possible.
   1387  */
   1388 const char *mtags_to_string_default_handler(MessageTag *m, Client *client)
   1389 {
   1390 	return NULL;
   1391 }
   1392 
   1393 /** Default handler for add_silence().
   1394  * This is only used if the 'silence' module is NOT loaded,
   1395  * which would be unusual, but possible.
   1396  */
   1397 int add_silence_default_handler(Client *client, const char *mask, int senderr)
   1398 {
   1399 	return 0;
   1400 }
   1401 
   1402 /** Default handler for del_silence().
   1403  * This is only used if the 'silence' module is NOT loaded,
   1404  * which would be unusual, but possible.
   1405  */
   1406 int del_silence_default_handler(Client *client, const char *mask)
   1407 {
   1408 	return 0;
   1409 }
   1410 
   1411 /** Default handler for is_silenced().
   1412  * This is only used if the 'silence' module is NOT loaded,
   1413  * which would be unusual, but possible.
   1414  */
   1415 int is_silenced_default_handler(Client *client, Client *acptr)
   1416 {
   1417 	return 0;
   1418 }
   1419 
   1420 /** Generate a BATCH id.
   1421  * This can be used in a :serv BATCH +%s ... message
   1422  */
   1423 void generate_batch_id(char *str)
   1424 {
   1425 	gen_random_alnum(str, BATCHLEN);
   1426 }
   1427 
   1428 /** A default handler if labeled-response module is not loaded.
   1429  * Normally a NOOP, but since caller will safe_free it
   1430  * later we do actually allocate something.
   1431  */
   1432 void *labeled_response_save_context_default_handler(void)
   1433 {
   1434 	return safe_alloc(8);
   1435 }
   1436 
   1437 /** A default handler for if labeled-response module is not loaded */
   1438 void labeled_response_set_context_default_handler(void *ctx)
   1439 {
   1440 }
   1441 
   1442 /** A default handler for if labeled-response module is not loaded */
   1443 void labeled_response_force_end_default_handler(void)
   1444 {
   1445 }
   1446 
   1447 /** Ad default handler for if the slog module is not loaded */
   1448 void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
   1449 {
   1450 }
   1451 
   1452 int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost)
   1453 {
   1454 	return 0;
   1455 }
   1456 
   1457 void webserver_send_response_default_handler(Client *client, int status, char *msg)
   1458 {
   1459 }
   1460 
   1461 void webserver_close_client_default_handler(Client *client)
   1462 {
   1463 }
   1464 
   1465 int webserver_handle_body_default_handler(Client *client, WebRequest *web, const char *readbuf, int length)
   1466 {
   1467 	return 0;
   1468 }
   1469 
   1470 void rpc_response_default_handler(Client *client, json_t *request, json_t *result)
   1471 {
   1472 }
   1473 
   1474 void rpc_error_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *error_message)
   1475 {
   1476 }
   1477 
   1478 void rpc_error_fmt_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...)
   1479 {
   1480 }
   1481 
   1482 void rpc_send_request_to_remote_default_handler(Client *source, Client *target, json_t *request)
   1483 {
   1484 }
   1485 
   1486 void rpc_send_response_to_remote_default_handler(Client *source, Client *target, json_t *response)
   1487 {
   1488 }
   1489 
   1490 int rrpc_supported_simple_default_handler(Client *target, char **problem_server)
   1491 {
   1492 	if (problem_server)
   1493 		*problem_server = me.name;
   1494 	return 0;
   1495 }
   1496 
   1497 int rrpc_supported_default_handler(Client *target, const char *module, const char *minimum_version, char **problem_server)
   1498 {
   1499 	if (problem_server)
   1500 		*problem_server = me.name;
   1501 	return 0;
   1502 }
   1503 
   1504 int websocket_handle_websocket_default_handler(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
   1505 {
   1506 	return -1;
   1507 }
   1508 
   1509 int websocket_create_packet_default_handler(int opcode, char **buf, int *len)
   1510 {
   1511 	return -1;
   1512 }
   1513 
   1514 int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
   1515 {
   1516 	return -1;
   1517 }
   1518 
   1519 int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len)
   1520 {
   1521 	return -1;
   1522 }
   1523 
   1524 void mtag_add_issued_by_default_handler(MessageTag **mtags, Client *client, MessageTag *recv_mtags)
   1525 {
   1526 }
   1527 
   1528 /** my_timegm: mktime()-like function which will use GMT/UTC.
   1529  * Strangely enough there is no standard function for this.
   1530  * On some *NIX OS's timegm() may be available, sometimes only
   1531  * with the help of certain #define's which we may or may
   1532  * not do.
   1533  * Windows provides _mkgmtime().
   1534  * In the other cases the man pages and basically everyone
   1535  * suggests to set TZ to empty prior to calling mktime and
   1536  * restoring it after the call. Whut? How ridiculous is that?
   1537  */
   1538 time_t my_timegm(struct tm *tm)
   1539 {
   1540 #if HAVE_TIMEGM
   1541 	return timegm(tm);
   1542 #elif defined(_WIN32)
   1543 	return _mkgmtime(tm);
   1544 #else
   1545 	time_t ret;
   1546 	char *tz = NULL;
   1547 
   1548 	safe_strdup(tz, getenv("TZ"));
   1549 	setenv("TZ", "", 1);
   1550 	ret = mktime(tm);
   1551 	if (tz)
   1552 	{
   1553 		setenv("TZ", tz, 1);
   1554 		safe_free(tz);
   1555 	} else {
   1556 		unsetenv("TZ");
   1557 	}
   1558 	tzset();
   1559 
   1560 	return ret;
   1561 #endif
   1562 }
   1563 
   1564 /** Convert an ISO 8601 timestamp ('server-time') to UNIX time */
   1565 time_t server_time_to_unix_time(const char *tbuf)
   1566 {
   1567 	struct tm tm;
   1568 	int dontcare = 0;
   1569 	time_t ret;
   1570 
   1571 	if (!tbuf)
   1572 		return 0;
   1573 
   1574 	if (strlen(tbuf) < 20)
   1575 		return 0;
   1576 
   1577 	memset(&tm, 0, sizeof(tm));
   1578 	ret = sscanf(tbuf, "%d-%d-%dT%d:%d:%d.%dZ",
   1579 		&tm.tm_year,
   1580 		&tm.tm_mon,
   1581 		&tm.tm_mday,
   1582 		&tm.tm_hour,
   1583 		&tm.tm_min,
   1584 		&tm.tm_sec,
   1585 		&dontcare);
   1586 
   1587 	if (ret != 7)
   1588 		return 0;
   1589 
   1590 	tm.tm_year -= 1900;
   1591 	tm.tm_mon -= 1;
   1592 
   1593 	ret = my_timegm(&tm);
   1594 	return ret;
   1595 }
   1596 
   1597 /** Convert an RFC 2616 timestamp (used in HTTP headers) to UNIX time */
   1598 time_t rfc2616_time_to_unix_time(const char *tbuf)
   1599 {
   1600 	struct tm tm;
   1601 	int dontcare = 0;
   1602 	time_t ret;
   1603 	char month[8];
   1604 	int i;
   1605 
   1606 	if (!tbuf)
   1607 		return 0;
   1608 
   1609 	if (strlen(tbuf) < 20)
   1610 		return 0;
   1611 
   1612 	memset(&tm, 0, sizeof(tm));
   1613 	*month = '\0';
   1614 	ret = sscanf(tbuf, "%*[a-zA-Z,] %d %3s %d %d:%d:%d",
   1615 	             &tm.tm_mday, month, &tm.tm_year,
   1616 	             &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
   1617 
   1618 	if (ret < 6)
   1619 		return 0;
   1620 
   1621 	for (i=0; i < 12; i++)
   1622 	{
   1623 		if (!strcmp(short_months[i], month))
   1624 		{
   1625 			tm.tm_mon = i;
   1626 			break;
   1627 		}
   1628 	}
   1629 	if (i == 12)
   1630 		return 0; /* Month not found */
   1631 	if (tm.tm_year < 1900)
   1632 		return 0;
   1633 
   1634 	tm.tm_year -= 1900;
   1635 	ret = my_timegm(&tm);
   1636 	return ret; /* can still be 0 */
   1637 }
   1638 
   1639 /** Returns an RFC 2616 timestamp (used in HTTP headers) */
   1640 const char *rfc2616_time(time_t clock)
   1641 {
   1642 	static char buf[80], plus;
   1643 	struct tm *lt, *gm;
   1644 	struct tm gmbuf;
   1645 	int  minswest;
   1646 
   1647 	if (!clock)
   1648 		time(&clock);
   1649 	gm = gmtime(&clock);
   1650 
   1651 	snprintf(buf, sizeof(buf),
   1652 	         "%s, %02d %.3s %4d %02d:%02d:%02d GMT",
   1653 	         short_weekdays[gm->tm_wday], gm->tm_mday, short_months[gm->tm_mon],
   1654 	         gm->tm_year + 1900, gm->tm_hour, gm->tm_min, gm->tm_sec);
   1655 
   1656 	return buf;
   1657 }
   1658 
   1659 /** Write a 64 bit integer to a file.
   1660  * @param fd   File descriptor
   1661  * @param t    The value to write
   1662  * @returns 1 on success, 0 on failure.
   1663  */
   1664 int write_int64(FILE *fd, uint64_t t)
   1665 {
   1666 	if (fwrite(&t, 1, sizeof(t), fd) < sizeof(t))
   1667 		return 0;
   1668 	return 1;
   1669 }
   1670 
   1671 /** Write a 32 bit integer to a file.
   1672  * @param fd   File descriptor
   1673  * @param t    The value to write
   1674  * @returns 1 on success, 0 on failure.
   1675  */
   1676 int write_int32(FILE *fd, uint32_t t)
   1677 {
   1678 	if (fwrite(&t, 1, sizeof(t), fd) < sizeof(t))
   1679 		return 0;
   1680 	return 1;
   1681 }
   1682 
   1683 /** Read a 64 bit integer from a file.
   1684  * @param fd   File descriptor
   1685  * @param t    The value to write
   1686  * @returns 1 on success, 0 on failure.
   1687  */
   1688 int read_int64(FILE *fd, uint64_t *t)
   1689 {
   1690 	if (fread(t, 1, sizeof(uint64_t), fd) < sizeof(uint64_t))
   1691 		return 0;
   1692 	return 1;
   1693 }
   1694 
   1695 /** Read a 32 bit integer from a file.
   1696  * @param fd   File descriptor
   1697  * @param t    The value to write
   1698  * @returns 1 on success, 0 on failure.
   1699  */
   1700 int read_int32(FILE *fd, uint32_t *t)
   1701 {
   1702 	if (fread(t, 1, sizeof(uint32_t), fd) < sizeof(uint32_t))
   1703 		return 0;
   1704 	return 1;
   1705 }
   1706 
   1707 /** Read binary data from a file.
   1708  * @param fd   File descriptor
   1709  * @param buf  Pointer to buffer
   1710  * @param len  Size of buffer
   1711  * @note  This function is not used much, in most cases
   1712  *        you should use read_str(), read_int32() or
   1713  *        read_int64() instead.
   1714  * @returns 1 on success, 0 on failure.
   1715  */
   1716 int read_data(FILE *fd, void *buf, size_t len)
   1717 {
   1718 	if (fread(buf, 1, len, fd) < len)
   1719 		return 0;
   1720 	return 1;
   1721 }
   1722 
   1723 /** Write binary data to a file.
   1724  * @param fd   File descriptor
   1725  * @param buf  Pointer to buffer
   1726  * @param len  Size of buffer
   1727  * @note  This function is not used much, in most cases
   1728  *        you should use write_str(), write_int32() or
   1729  *        write_int64() instead.
   1730  * @returns 1 on success, 0 on failure.
   1731  */
   1732 int write_data(FILE *fd, const void *buf, size_t len)
   1733 {
   1734 	if (fwrite(buf, 1, len, fd) < len)
   1735 		return 0;
   1736 	return 1;
   1737 }
   1738 
   1739 /** Write a string to a file.
   1740  * @param fd   File descriptor
   1741  * @param x    Pointer to string
   1742  * @note  This function can write a string up to 65534
   1743  *        characters, which should be plenty for usage
   1744  *        in UnrealIRCd.
   1745  *        Note that 'x' can safely be NULL.
   1746  * @returns 1 on success, 0 on failure.
   1747  */
   1748 int write_str(FILE *fd, const char *x)
   1749 {
   1750 	uint16_t len;
   1751 
   1752 	len = x ? strlen(x) : 0xffff;
   1753 	if (!write_data(fd, &len, sizeof(len)))
   1754 		return 0;
   1755 	if ((len > 0) && (len < 0xffff))
   1756 	{
   1757 		if (!write_data(fd, x, len))
   1758 			return 0;
   1759 	}
   1760 	return 1;
   1761 }
   1762 
   1763 /** Read a string from a file.
   1764  * @param fd   File descriptor
   1765  * @param x    Pointer to string pointer
   1766  * @note  This function will allocate memory for the data
   1767  *        and set the string pointer to this value.
   1768  *        If a NULL pointer was written via write_str()
   1769  *        then read_str() may also return a NULL pointer.
   1770  * @returns 1 on success, 0 on failure.
   1771  */
   1772 int read_str(FILE *fd, char **x)
   1773 {
   1774 	uint16_t len;
   1775 	size_t size;
   1776 
   1777 	*x = NULL;
   1778 
   1779 	if (!read_data(fd, &len, sizeof(len)))
   1780 		return 0;
   1781 
   1782 	if (len == 0xffff)
   1783 	{
   1784 		/* Magic value meaning NULL */
   1785 		*x = NULL;
   1786 		return 1;
   1787 	}
   1788 
   1789 	if (len == 0)
   1790 	{
   1791 		/* 0 means empty string */
   1792 		safe_strdup(*x, "");
   1793 		return 1;
   1794 	}
   1795 
   1796 	if (len > 10000)
   1797 		return 0;
   1798 
   1799 	size = len;
   1800 	*x = safe_alloc(size + 1);
   1801 	if (!read_data(fd, *x, size))
   1802 	{
   1803 		safe_free(*x);
   1804 		return 0;
   1805 	}
   1806 	(*x)[len] = 0;
   1807 	return 1;
   1808 }
   1809 
   1810 /** Convert binary 'data' of size 'len' to a hexadecimal string 'str'.
   1811  * The caller is responsible to ensure that 'str' is sufficiently large.
   1812  */
   1813 void binarytohex(void *data, size_t len, char *str)
   1814 {
   1815 	const char hexchars[16] = "0123456789abcdef";
   1816 	char *datastr = (char *)data;
   1817 	int i, n = 0;
   1818 
   1819 	for (i=0; i<len; i++)
   1820 	{
   1821 		str[n++] = hexchars[(datastr[i] >> 4) & 0xF];
   1822 		str[n++] = hexchars[datastr[i] & 0xF];
   1823 	}
   1824 	str[n] = '\0';
   1825 }
   1826 
   1827 /** Generates an MD5 checksum - binary version.
   1828  * @param mdout[out] Buffer to store result in, the result will be 16 bytes in binary
   1829  *                   (not ascii printable!).
   1830  * @param src[in]    The input data used to generate the checksum.
   1831  * @param n[in]      Length of data.
   1832  * @deprecated       The MD5 algorithm is deprecated and insecure,
   1833  *                   so only use this if absolutely needed.
   1834  */
   1835 void DoMD5(char *mdout, const char *src, unsigned long n)
   1836 {
   1837 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1838 	unsigned int md_len;
   1839 	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
   1840 	if (EVP_DigestInit_ex(mdctx, md5_function, NULL) != 1)
   1841 		abort();
   1842 	EVP_DigestUpdate(mdctx, src, n);
   1843 	EVP_DigestFinal_ex(mdctx, mdout, &md_len);
   1844 	EVP_MD_CTX_free(mdctx);
   1845 #else
   1846 	MD5_CTX hash;
   1847 
   1848 	MD5_Init(&hash);
   1849 	MD5_Update(&hash, src, n);
   1850 	MD5_Final(mdout, &hash);
   1851 #endif
   1852 }
   1853 
   1854 /** Generates an MD5 checksum - ASCII printable string (0011223344..etc..).
   1855  * @param dst[out]  Buffer to store result in, this will be the result will be
   1856  *                  32 characters + nul terminator, so needs to be at least 33 characters.
   1857  * @param src[in]   The input data used to generate the checksum.
   1858  * @param n[in]     Length of data.
   1859  * @deprecated      The MD5 algorithm is deprecated and insecure,
   1860  *                  so only use this if absolutely needed.
   1861  */
   1862 char *md5hash(char *dst, const char *src, unsigned long n)
   1863 {
   1864 	char tmp[16];
   1865 
   1866 	DoMD5(tmp, src, n);
   1867 	binarytohex(tmp, sizeof(tmp), dst);
   1868 	return dst;
   1869 }
   1870 
   1871 /** Generates a SHA256 checksum - binary version.
   1872  * Most people will want to use sha256hash() instead which outputs hex.
   1873  * @param dst[out]  Buffer to store result in, which needs to be 32 bytes in length
   1874  *                  (SHA256_DIGEST_LENGTH).
   1875  * @param src[in]   The input data used to generate the checksum.
   1876  * @param n[in]     Length of data.
   1877  */
   1878 void sha256hash_binary(char *dst, const char *src, unsigned long n)
   1879 {
   1880 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1881 	unsigned int md_len;
   1882 	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
   1883 	if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1)
   1884 		abort();
   1885 	EVP_DigestUpdate(mdctx, src, n);
   1886 	EVP_DigestFinal_ex(mdctx, dst, &md_len);
   1887 	EVP_MD_CTX_free(mdctx);
   1888 #else
   1889 	SHA256_CTX hash;
   1890 
   1891 	SHA256_Init(&hash);
   1892 	SHA256_Update(&hash, src, n);
   1893 	SHA256_Final(dst, &hash);
   1894 #endif
   1895 }
   1896 
   1897 /** Generates a SHA256 checksum - ASCII printable string (0011223344..etc..).
   1898  * @param dst[out]  Buffer to store result in, which needs to be 65 bytes minimum.
   1899  * @param src[in]   The input data used to generate the checksum.
   1900  * @param n[in]     Length of data.
   1901  */
   1902 char *sha256hash(char *dst, const char *src, unsigned long n)
   1903 {
   1904 	char binaryhash[SHA256_DIGEST_LENGTH];
   1905 
   1906 	sha256hash_binary(binaryhash, src, n);
   1907 	binarytohex(binaryhash, sizeof(binaryhash), dst);
   1908 	return dst;
   1909 }
   1910 
   1911 /** Calculate the SHA256 checksum of a file */
   1912 const char *sha256sum_file(const char *fname)
   1913 {
   1914 	FILE *fd;
   1915 	char buf[2048];
   1916 	SHA256_CTX hash;
   1917 	char binaryhash[SHA256_DIGEST_LENGTH];
   1918 	static char hexhash[SHA256_DIGEST_LENGTH*2+1];
   1919 	int n;
   1920 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1921 	unsigned int md_len;
   1922 	EVP_MD_CTX *mdctx;
   1923 
   1924 	mdctx = EVP_MD_CTX_new();
   1925 	if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1)
   1926 		abort();
   1927 #else
   1928 	SHA256_Init(&hash);
   1929 #endif
   1930 
   1931 	fd = fopen(fname, "rb");
   1932 	if (!fd)
   1933 		return NULL;
   1934 
   1935 	while ((n = fread(buf, 1, sizeof(buf), fd)) > 0)
   1936 	{
   1937 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1938 		EVP_DigestUpdate(mdctx, buf, n);
   1939 #else
   1940 		SHA256_Update(&hash, buf, n);
   1941 #endif
   1942 	}
   1943 	fclose(fd);
   1944 
   1945 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1946 	EVP_DigestFinal_ex(mdctx, binaryhash, &md_len);
   1947 	EVP_MD_CTX_free(mdctx);
   1948 #else
   1949 	SHA256_Final(binaryhash, &hash);
   1950 #endif
   1951 	binarytohex(binaryhash, sizeof(binaryhash), hexhash);
   1952 	return hexhash;
   1953 }
   1954 
   1955 /** Generates a SHA1 checksum - binary version.
   1956  * @param dst[out]  Buffer to store result in, which needs to be 32 bytes in length
   1957  *                  (SHA1_DIGEST_LENGTH).
   1958  * @param src[in]   The input data used to generate the checksum.
   1959  * @param n[in]     Length of data.
   1960  * @deprecated      The SHA1 algorithm is deprecated and insecure,
   1961  *                  so only use this if absolutely needed.
   1962  */
   1963 void sha1hash_binary(char *dst, const char *src, unsigned long n)
   1964 {
   1965 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
   1966 	unsigned int md_len;
   1967 	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
   1968 	if (EVP_DigestInit_ex(mdctx, sha1_function, NULL) != 1)
   1969 		abort();
   1970 	EVP_DigestUpdate(mdctx, src, n);
   1971 	EVP_DigestFinal_ex(mdctx, dst, &md_len);
   1972 	EVP_MD_CTX_free(mdctx);
   1973 #else
   1974 	SHA_CTX hash;
   1975 
   1976 	SHA1_Init(&hash);
   1977 	SHA1_Update(&hash, src, n);
   1978 	SHA1_Final(dst, &hash);
   1979 #endif
   1980 }
   1981 
   1982 /** Remove a suffix from a filename, eg ".c" (if it is present) */
   1983 char *filename_strip_suffix(const char *fname, const char *suffix)
   1984 {
   1985 	static char buf[512];
   1986 
   1987 	strlcpy(buf, fname, sizeof(buf));
   1988 
   1989 	if (suffix)
   1990 	{
   1991 		int buf_len = strlen(buf);
   1992 		int suffix_len = strlen(suffix);
   1993 		if (buf_len >= suffix_len)
   1994 		{
   1995 			if (!strncmp(buf+buf_len-suffix_len, suffix, suffix_len))
   1996 				buf[buf_len-suffix_len] = '\0';
   1997 		}
   1998 	} else {
   1999 		char *p = strrchr(buf, '.');
   2000 		if (p)
   2001 			*p = '\0';
   2002 	}
   2003 	return buf;
   2004 }
   2005 
   2006 /** Add a suffix to a filename, eg ".c" */
   2007 char *filename_add_suffix(const char *fname, const char *suffix)
   2008 {
   2009 	static char buf[512];
   2010 	snprintf(buf, sizeof(buf), "%s%s", fname, suffix);
   2011 	return buf;
   2012 }
   2013 
   2014 /* Returns 1 if the filename has the suffix, eg ".c" */
   2015 int filename_has_suffix(const char *fname, const char *suffix)
   2016 {
   2017 	char buf[256];
   2018 	char *p;
   2019 	strlcpy(buf, fname, sizeof(buf));
   2020 	p = strrchr(buf, '.');
   2021 	if (!p)
   2022 		return 0;
   2023 	if (!strcmp(p, suffix))
   2024 		return 1;
   2025 	return 0;
   2026 }
   2027 
   2028 /** Check if the specified file or directory exists */
   2029 int file_exists(const char *file)
   2030 {
   2031 #ifdef _WIN32
   2032 	if (_access(file, 0) == 0)
   2033 #else
   2034 	if (access(file, 0) == 0)
   2035 #endif
   2036 		return 1;
   2037 	return 0;
   2038 }
   2039 
   2040 /** Get the file creation time */
   2041 time_t get_file_time(const char *fname)
   2042 {
   2043 	struct stat st;
   2044 
   2045 	if (stat(fname, &st) != 0)
   2046 		return 0;
   2047 
   2048 	return (time_t)st.st_ctime;
   2049 }
   2050 
   2051 /** Get the size of a file */
   2052 long get_file_size(const char *fname)
   2053 {
   2054 	struct stat st;
   2055 
   2056 	if (stat(fname, &st) != 0)
   2057 		return -1;
   2058 
   2059 	return (long)st.st_size;
   2060 }
   2061 
   2062 /** Add a line to a MultiLine list */
   2063 void addmultiline(MultiLine **l, const char *line)
   2064 {
   2065 	MultiLine *m = safe_alloc(sizeof(MultiLine));
   2066 	safe_strdup(m->line, line);
   2067 	append_ListItem((ListStruct *)m, (ListStruct **)l);
   2068 }
   2069 
   2070 /** Free an entire MultiLine list */
   2071 void freemultiline(MultiLine *l)
   2072 {
   2073 	MultiLine *l_next;
   2074 	for (; l; l = l_next)
   2075 	{
   2076 		l_next = l->next;
   2077 		safe_free(l->line);
   2078 		safe_free(l);
   2079 	}
   2080 }
   2081 
   2082 /** Convert a line regular string containing \n's to a MultiLine linked list */
   2083 MultiLine *line2multiline(const char *str)
   2084 {
   2085 	static char buf[8192];
   2086 	char *p, *p2;
   2087 	MultiLine *ml = NULL;
   2088 
   2089 	strlcpy(buf, str, sizeof(buf));
   2090 	p = buf;
   2091 	do {
   2092 		p2 = strchr(p, '\n');
   2093 		if (p2)
   2094 			*p2++ = '\0';
   2095 		addmultiline(&ml, p);
   2096 		p = p2;
   2097 	} while(p2 && *p2);
   2098 	return ml;
   2099 }
   2100 
   2101 /** Convert a sendtype to a command string */
   2102 const char *sendtype_to_cmd(SendType sendtype)
   2103 {
   2104 	if (sendtype == SEND_TYPE_PRIVMSG)
   2105 		return "PRIVMSG";
   2106 	if (sendtype == SEND_TYPE_NOTICE)
   2107 		return "NOTICE";
   2108 	if (sendtype == SEND_TYPE_TAGMSG)
   2109 		return "TAGMSG";
   2110 	return NULL;
   2111 }
   2112 
   2113 /** Check password strength.
   2114  * @param pass		The password to check
   2115  * @param min_length	The minimum length of the password
   2116  * @param strict	Whether to require UPPER+lower+digits
   2117  * @returns 1 if good, 0 if not.
   2118  */
   2119 int check_password_strength(const char *pass, int min_length, int strict, char **err)
   2120 {
   2121 	static char buf[256];
   2122 	char has_lowercase=0, has_uppercase=0, has_digit=0;
   2123 	const char *p;
   2124 
   2125 	if (err)
   2126 		*err = NULL;
   2127 
   2128 	if (strlen(pass) < min_length)
   2129 	{
   2130 		if (err)
   2131 		{
   2132 			snprintf(buf, sizeof(buf), "Password must be at least %d characters", min_length);
   2133 			*err = buf;
   2134 		}
   2135 		return 0;
   2136 	}
   2137 
   2138 	for (p=pass; *p; p++)
   2139 	{
   2140 		if (islower(*p))
   2141 			has_lowercase = 1;
   2142 		else if (isupper(*p))
   2143 			has_uppercase = 1;
   2144 		else if (isdigit(*p))
   2145 			has_digit = 1;
   2146 	}
   2147 
   2148 	if (strict)
   2149 	{
   2150 		if (!has_lowercase)
   2151 		{
   2152 			if (err)
   2153 				*err = "Password must contain at least 1 lowercase character";
   2154 			return 0;
   2155 		} else
   2156 		if (!has_uppercase)
   2157 		{
   2158 			if (err)
   2159 				*err = "Password must contain at least 1 UPPERcase character";
   2160 			return 0;
   2161 		} else
   2162 		if (!has_digit)
   2163 		{
   2164 			if (err)
   2165 				*err = "Password must contain at least 1 digit (number)";
   2166 			return 0;
   2167 		}
   2168 	}
   2169 
   2170 	return 1;
   2171 }
   2172 
   2173 int valid_secret_password(const char *pass, char **err)
   2174 {
   2175 	return check_password_strength(pass, 10, 1, err);
   2176 }
   2177 
   2178 int running_interactively(void)
   2179 {
   2180 #ifndef _WIN32
   2181 	char *s;
   2182 
   2183 	if (!isatty(0))
   2184 		return 0;
   2185 
   2186 	s = getenv("TERM");
   2187 	if (!s || !strcasecmp(s, "dumb") || !strcasecmp(s, "none"))
   2188 		return 0;
   2189 
   2190 	return 1;
   2191 #else
   2192 	return IsService ? 0 : 1;
   2193 #endif
   2194 }
   2195 
   2196 int terminal_supports_color(void)
   2197 {
   2198 #ifndef _WIN32
   2199 	char *s;
   2200 
   2201 	/* Support NO_COLOR as per https://no-color.org */
   2202 	s = getenv("NO_COLOR");
   2203 	if (s != NULL && s[0] != '\0')
   2204 		return 0;
   2205 
   2206 	/* Yeah we check all of stdin, stdout, stderr, because one
   2207 	 * or more may be redirected (bin/unrealircd >log 2>&1),
   2208 	 * and then we want to say no to color support.
   2209 	 */
   2210 	if (!isatty(0) || !isatty(1) || !isatty(2))
   2211 		return 0;
   2212 
   2213 	s = getenv("TERM");
   2214 	/* Yeah this is a lazy way to detect color-capable terminals
   2215 	 * but it is good enough for me.
   2216 	 */
   2217 	if (s)
   2218 	{
   2219 		if (strstr(s, "color") || strstr(s, "ansi"))
   2220 			return 1;
   2221 	}
   2222 
   2223 	return 0;
   2224 #else
   2225 	return 0;
   2226 #endif
   2227 }
   2228 
   2229 /** Skip whitespace (if any) */
   2230 void skip_whitespace(char **p)
   2231 {
   2232 	for (; **p == ' ' || **p == '\t'; *p = *p + 1);
   2233 }
   2234 
   2235 /** Keep reading '*p' until we hit any of the 'stopchars'.
   2236  * Actually behaves like strstr() but then hit the end
   2237  * of the string (\0) i guess?
   2238  */
   2239 void read_until(char **p, char *stopchars)
   2240 {
   2241 	for (; **p && !strchr(stopchars, **p); *p = *p + 1);
   2242 }
   2243 
   2244 void write_pidfile_failed(void)
   2245 {
   2246 	char *errstr = strerror(errno);
   2247 	unreal_log(ULOG_WARNING, "config", "WRITE_PID_FILE_FAILED", NULL,
   2248 		   "Unable to write to pid file '$filename': $system_error",
   2249 		   log_data_string("filename", conf_files->pid_file),
   2250 		   log_data_string("system_error", errstr));
   2251 }
   2252 
   2253 /** Write PID file */
   2254 void write_pidfile(void)
   2255 {
   2256 #ifdef IRCD_PIDFILE
   2257 	int fd;
   2258 	char buff[20];
   2259 	if ((fd = open(conf_files->pid_file, O_CREAT | O_WRONLY, 0600)) < 0)
   2260 	{
   2261 		write_pidfile_failed();
   2262 		return;
   2263 	}
   2264 	ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid());
   2265 	if (write(fd, buff, strlen(buff)) < 0)
   2266 		write_pidfile_failed();
   2267 	if (close(fd) < 0)
   2268 		write_pidfile_failed();
   2269 #endif
   2270 }
   2271 
   2272 /*
   2273  * Determines if the given string is a valid URL. Since libcurl
   2274  * supports telnet, ldap, and dict such strings are treated as
   2275  * invalid URLs here since we don't want them supported in
   2276  * unreal.
   2277  */
   2278 int url_is_valid(const char *string)
   2279 {
   2280 	if (strstr(string, " ") || strstr(string, "\t"))
   2281 		return 0;
   2282 
   2283 	if (strstr(string, "telnet://") == string ||
   2284 	    strstr(string, "ldap://") == string ||
   2285 	    strstr(string, "dict://") == string)
   2286 	{
   2287 		return 0;
   2288 	}
   2289 	return (strstr(string, "://") != NULL);
   2290 }
   2291 
   2292 /** A displayable URL for in error messages and such.
   2293  * This leaves out any authentication information (user:pass)
   2294  * the URL may contain.
   2295  */
   2296 const char *displayurl(const char *url)
   2297 {
   2298 	static char buf[512];
   2299 	char *proto, *rest;
   2300 
   2301 	/* protocol://user:pass@host/etc.. */
   2302 	rest = strchr(url, '@');
   2303 
   2304 	if (!rest)
   2305 		return url; /* contains no auth information */
   2306 
   2307 	rest++; /* now points to the rest (remainder) of the URL */
   2308 
   2309 	proto = strstr(url, "://");
   2310 	if (!proto || (proto > rest) || (proto == url))
   2311 		return url; /* incorrectly formatted, just show entire URL. */
   2312 
   2313 	strlncpy(buf, url, sizeof(buf), proto - url);
   2314 	strlcat(buf, "://***:***@", sizeof(buf));
   2315 	strlcat(buf, rest, sizeof(buf));
   2316 
   2317 	return buf;
   2318 }
   2319 
   2320 /*
   2321  * Returns the filename portion of the URL. The returned string
   2322  * is malloc()'ed and must be freed by the caller. If the specified
   2323  * URL does not contain a filename, a '-' is allocated and returned.
   2324  */
   2325 char *url_getfilename(const char *url)
   2326 {
   2327 	const char *c, *start;
   2328 
   2329 	if ((c = strstr(url, "://")))
   2330 		c += 3;
   2331 	else
   2332 		c = url;
   2333 
   2334 	while (*c && *c != '/')
   2335 		c++;
   2336 
   2337 	if (*c == '/')
   2338 	{
   2339 		c++;
   2340 		if (!*c || *c == '?')
   2341 			return raw_strdup("-");
   2342 		start = c;
   2343 		while (*c && *c != '?')
   2344 			c++;
   2345 		if (!*c)
   2346 			return raw_strdup(start);
   2347 		else
   2348 			return raw_strldup(start, c-start+1);
   2349 
   2350 	}
   2351 	return raw_strdup("-");
   2352 }
   2353 
   2354 #ifdef _WIN32
   2355  // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess
   2356  // mode value   Checks file for
   2357  // 04 	         Read-only
   2358  #define R_OK 04
   2359 #endif
   2360 
   2361 /*
   2362  * Checks whether a file can be opened for reading.
   2363  */
   2364 int is_file_readable(const char *file, const char *dir)
   2365 {
   2366 	char *filename = strdup(file);
   2367 	convert_to_absolute_path(&filename, dir);
   2368 	if (access(filename, R_OK)){
   2369 		safe_free(filename);
   2370 		return 0;
   2371 	}
   2372 	safe_free(filename);
   2373 	return 1;
   2374 }
   2375 
   2376 void delletterfromstring(char *s, char letter)
   2377 {
   2378 	if (s == NULL)
   2379 		return;
   2380 	for (; *s; s++)
   2381 	{
   2382 		if (*s == letter)
   2383 		{
   2384 			for (; *s; s++)
   2385 				*s = s[1];
   2386 			break;
   2387 		}
   2388 	}
   2389 }
   2390 
   2391 int sort_character_lowercase_before_uppercase(char x, char y)
   2392 {
   2393 	/* Lower before upper */
   2394 	if (islower(x) && isupper(y))
   2395 		return 1;
   2396 	if (isupper(x) && islower(y))
   2397 		return 0;
   2398 	/* Other than that, easy */
   2399 	return x < y ? 1 : 0;
   2400 }
   2401 
   2402 /* Helper function, mainly used by snomask code */
   2403 void addlettertodynamicstringsorted(char **str, char letter)
   2404 {
   2405 	char *i, *o;
   2406 	char *newbuf;
   2407 	size_t newbuflen;
   2408 
   2409 	/* NULL string? Easy! */
   2410 	if (*str == NULL)
   2411 	{
   2412 		*str = safe_alloc(2);
   2413 		**str = letter;
   2414 		return;
   2415 	}
   2416 
   2417 	/* Exists? Then nothing to do */
   2418 	if (strchr(*str, letter))
   2419 		return;
   2420 
   2421 	/* Ok, we really need to add it */
   2422 	newbuflen = strlen(*str) + 2;
   2423 	newbuf = safe_alloc(newbuflen);
   2424 	for (i = *str, o = newbuf; *i; i++)
   2425 	{
   2426 		/* Insert before a higher letter */
   2427 		if (letter && sort_character_lowercase_before_uppercase(letter, *i))
   2428 		{
   2429 			*o++ = letter;
   2430 			letter = '\0';
   2431 		}
   2432 		*o++ = *i;
   2433 	}
   2434 	/* Or maybe we should be at the final spot? */
   2435 	if (letter)
   2436 		*o++ = letter;
   2437 	*o = '\0';
   2438 	safe_free_raw(*str);
   2439 	*str = newbuf;
   2440 }
   2441 
   2442 void s_die()
   2443 {
   2444 #ifdef _WIN32
   2445 	Client *client;
   2446 	if (!IsService)
   2447 	{
   2448 		loop.terminating = 1;
   2449 		unload_all_modules();
   2450 
   2451 		list_for_each_entry(client, &lclient_list, lclient_node)
   2452 			(void) send_queued(client);
   2453 
   2454 		exit(-1);
   2455 	}
   2456 	else {
   2457 		SERVICE_STATUS status;
   2458 		SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
   2459 		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP);
   2460 		ControlService(hService, SERVICE_CONTROL_STOP, &status);
   2461 	}
   2462 #else
   2463 	loop.terminating = 1;
   2464 	unload_all_modules();
   2465 	unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
   2466 	exit(0);
   2467 #endif
   2468 }
   2469 
   2470 #ifndef _WIN32
   2471 void s_rehash()
   2472 {
   2473 	struct sigaction act;
   2474 	dorehash = 1;
   2475 	act.sa_handler = s_rehash;
   2476 	act.sa_flags = 0;
   2477 	(void)sigemptyset(&act.sa_mask);
   2478 	(void)sigaddset(&act.sa_mask, SIGHUP);
   2479 	(void)sigaction(SIGHUP, &act, NULL);
   2480 }
   2481 
   2482 void s_reloadcert()
   2483 {
   2484 	struct sigaction act;
   2485 	doreloadcert = 1;
   2486 	act.sa_handler = s_reloadcert;
   2487 	act.sa_flags = 0;
   2488 	(void)sigemptyset(&act.sa_mask);
   2489 	(void)sigaddset(&act.sa_mask, SIGUSR1);
   2490 	(void)sigaction(SIGUSR1, &act, NULL);
   2491 }
   2492 #endif // #ifndef _WIN32
   2493 
   2494 void restart(const char *mesg)
   2495 {
   2496 	server_reboot(mesg);
   2497 }
   2498 
   2499 void s_restart()
   2500 {
   2501 	dorestart = 1;
   2502 }
   2503 
   2504 #ifndef _WIN32
   2505 /** Signal handler for signals which we ignore,
   2506  * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc.
   2507  */
   2508 void ignore_this_signal()
   2509 {
   2510 	struct sigaction act;
   2511 
   2512 	act.sa_handler = ignore_this_signal;
   2513 	act.sa_flags = 0;
   2514 	(void)sigemptyset(&act.sa_mask);
   2515 	(void)sigaddset(&act.sa_mask, SIGALRM);
   2516 	(void)sigaddset(&act.sa_mask, SIGPIPE);
   2517 	(void)sigaction(SIGALRM, &act, (struct sigaction *)NULL);
   2518 	(void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL);
   2519 #ifdef SIGWINCH
   2520 	(void)sigaddset(&act.sa_mask, SIGWINCH);
   2521 	(void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL);
   2522 #endif
   2523 }
   2524 #endif /* #ifndef _WIN32 */
   2525 
   2526 
   2527 void server_reboot(const char *mesg)
   2528 {
   2529 	int i;
   2530 	Client *client;
   2531 	unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL,
   2532 	           "Restarting server: $reason",
   2533 	           log_data_string("reason", mesg));
   2534 
   2535 	list_for_each_entry(client, &lclient_list, lclient_node)
   2536 		(void) send_queued(client);
   2537 
   2538 	/*
   2539 	 * ** fd 0 must be 'preserved' if either the -d or -i options have
   2540 	 * ** been passed to us before restarting.
   2541 	 */
   2542 #ifdef HAVE_SYSLOG
   2543 	(void)closelog();
   2544 #endif
   2545 #ifndef _WIN32
   2546 	for (i = 3; i < MAXCONNECTIONS; i++)
   2547 		(void)close(i);
   2548 	if (!(bootopt & (BOOT_TTY | BOOT_DEBUG)))
   2549 		(void)close(2);
   2550 	(void)close(1);
   2551 	(void)close(0);
   2552 	close_std_descriptors();
   2553 	(void)execv(MYNAME, myargv);
   2554 #else
   2555 	close_connections();
   2556 	if (!IsService)
   2557 	{
   2558 		CleanUp();
   2559 		WinExec(cmdLine, SW_SHOWDEFAULT);
   2560 	}
   2561 #endif
   2562 	loop.terminating = 1;
   2563 	unload_all_modules();
   2564 #ifdef _WIN32
   2565 	if (IsService)
   2566 	{
   2567 		SERVICE_STATUS status;
   2568 		PROCESS_INFORMATION pi;
   2569 		STARTUPINFO si;
   2570 		char fname[MAX_PATH];
   2571 		memset(&status, 0, sizeof(status));
   2572 		memset(&si, 0, sizeof(si));
   2573 		IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING;
   2574 		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
   2575 		GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
   2576 		CreateProcess(fname, "restartsvc", NULL, NULL, FALSE,
   2577 			0, NULL, NULL, &si, &pi);
   2578 		IRCDStatus.dwCurrentState = SERVICE_STOPPED;
   2579 		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
   2580 		ExitProcess(0);
   2581 	}
   2582 	else
   2583 #endif
   2584 	exit(-1);
   2585 }
   2586 
   2587 /** Check if at least 'minimum' seconds passed by since last run.
   2588  * @param tv_old   Pointer to a timeval struct to keep track of things.
   2589  * @param minimum  The time specified in milliseconds (eg: 1000 for 1 second)
   2590  * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned.
   2591  */
   2592 int minimum_msec_since_last_run(struct timeval *tv_old, long minimum)
   2593 {
   2594 	long v;
   2595 
   2596 	if (tv_old->tv_sec == 0)
   2597 	{
   2598 		/* First call ever */
   2599 		tv_old->tv_sec = timeofday_tv.tv_sec;
   2600 		tv_old->tv_usec = timeofday_tv.tv_usec;
   2601 		return 0;
   2602 	}
   2603 	v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000);
   2604 	if (v >= minimum)
   2605 	{
   2606 		tv_old->tv_sec = timeofday_tv.tv_sec;
   2607 		tv_old->tv_usec = timeofday_tv.tv_usec;
   2608 		return 1;
   2609 	}
   2610 	return 0;
   2611 }
   2612 
   2613 /** Strip color, bold, underline, and reverse codes from a string.
   2614  * @param text			The input text
   2615  * @param output		The buffer for the output text
   2616  * @param outputlen		The length of the output buffer
   2617  * @param strip_flags		Zero or (a combination of) UNRL_STRIP_LOW_ASCII / UNRL_STRIP_KEEP_LF.
   2618  * @returns The new string, which will be 'output', or in unusual cases (outputlen==0) will be NULL.
   2619  */
   2620 const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags)
   2621 {
   2622 	int i = 0, len = strlen(text), save_len=0;
   2623 	char nc = 0, col = 0, rgb = 0;
   2624 	char *o = output;
   2625 	const char *save_text=NULL;
   2626 
   2627 	/* Handle special cases first.. */
   2628 
   2629 	if (outputlen == 0)
   2630 		return NULL;
   2631 
   2632 	if (outputlen == 1)
   2633 	{
   2634 		*output = '\0';
   2635 		return output;
   2636 	}
   2637 
   2638 	/* Reserve room for the NUL byte */
   2639 	outputlen--;
   2640 
   2641 	while (len > 0) 
   2642 	{
   2643 		if ((col && isdigit(*text) && nc < 2) ||
   2644 		    ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3)))
   2645 		{
   2646 			nc++;
   2647 			if (*text == ',')
   2648 			{
   2649 				nc = 0;
   2650 				col++;
   2651 			}
   2652 		}
   2653 		/* Syntax for RGB is ^DHHHHHH where H is a hex digit.
   2654 		 * If < 6 hex digits are specified, the code is displayed
   2655 		 * as text
   2656 		 */
   2657 		else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7))
   2658 		{
   2659 			nc++;
   2660 			if (*text == ',')
   2661 				nc = 0;
   2662 		}
   2663 		else 
   2664 		{
   2665 			if (col)
   2666 				col = 0;
   2667 			if (rgb)
   2668 			{
   2669 				if (nc != 6)
   2670 				{
   2671 					text = save_text+1;
   2672 					len = save_len-1;
   2673 					rgb = 0;
   2674 					continue;
   2675 				}
   2676 				rgb = 0;
   2677 			}
   2678 			switch (*text)
   2679 			{
   2680 			case 3:
   2681 				/* color */
   2682 				col = 1;
   2683 				nc = 0;
   2684 				break;
   2685 			case 4:
   2686 				/* RGB */
   2687 				save_text = text;
   2688 				save_len = len;
   2689 				rgb = 1;
   2690 				nc = 0;
   2691 				break;
   2692 			case 2:
   2693 				/* bold */
   2694 				break;
   2695 			case 31:
   2696 				/* underline */
   2697 				break;
   2698 			case 22:
   2699 				/* reverse */
   2700 				break;
   2701 			case 15:
   2702 				/* plain */
   2703 				break;
   2704 			case 29:
   2705 				/* italic */
   2706 				break;
   2707 			case 30:
   2708 				/* strikethrough */
   2709 				break;
   2710 			case 17:
   2711 				/* monospace */
   2712 				break;
   2713 			case 0xe2:
   2714 				if (!strncmp(text+1, "\x80\x8b", 2))
   2715 				{
   2716 					/* +2 means we skip 3 */
   2717 					text += 2;
   2718 					len  -= 2;
   2719 					break;
   2720 				}
   2721 				/*fallthrough*/
   2722 			default:
   2723 				if ((*text >= ' ') ||
   2724 				    !(strip_flags & UNRL_STRIP_LOW_ASCII) ||
   2725 				    ((strip_flags & UNRL_STRIP_KEEP_LF) && (*text == '\n'))
   2726 				    )
   2727 				{
   2728 					*o++ = *text;
   2729 					outputlen--;
   2730 					if (outputlen == 0)
   2731 					{
   2732 						*o = '\0';
   2733 						return output;
   2734 					}
   2735 				}
   2736 				break;
   2737 			}
   2738 		}
   2739 		text++;
   2740 		len--;
   2741 	}
   2742 
   2743 	*o = '\0';
   2744 	return output;
   2745 }
   2746 
   2747 /* strip color, bold, underline, and reverse codes from a string */
   2748 const char *StripControlCodes(const char *text)
   2749 {
   2750 	static unsigned char new_str[4096];
   2751 
   2752 	return StripControlCodesEx(text, new_str, sizeof(new_str), 0);
   2753 }
   2754 
   2755 const char *command_issued_by_rpc(MessageTag *mtags)
   2756 {
   2757 	MessageTag *m = find_mtag(mtags, "unrealircd.org/issued-by");
   2758 	if (m && m->value && !strncmp(m->value, "RPC:", 4))
   2759 		return m->value;
   2760 	return NULL;
   2761 }