/* * Unreal Internet Relay Chat Daemon, src/misc.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * Copyright (C) 1999-present UnrealIRCd team * * See file AUTHORS in IRC package for additional names of * the programmers. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file * @brief Miscellaneous functions that don't fit in other files. * Generally these are either simple helper functions or larger * functions that don't fit in either user.c, channel.c. */ #include "unrealircd.h" static void exit_one_client(Client *, MessageTag *mtags_i, const char *); static const char *months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static const char *weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static const char *short_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static const char *short_weekdays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", }; typedef struct { int value; /** Unique integer value of item */ char character; /** Unique character assigned to item */ char *name; /** Name of item */ } BanActTable; static BanActTable banacttable[] = { { BAN_ACT_KILL, 'K', "kill" }, { BAN_ACT_SOFT_KILL, 'i', "soft-kill" }, { BAN_ACT_TEMPSHUN, 'S', "tempshun" }, { BAN_ACT_SOFT_TEMPSHUN,'T', "soft-tempshun" }, { BAN_ACT_SHUN, 's', "shun" }, { BAN_ACT_SOFT_SHUN, 'H', "soft-shun" }, { BAN_ACT_KLINE, 'k', "kline" }, { BAN_ACT_SOFT_KLINE, 'I', "soft-kline" }, { BAN_ACT_ZLINE, 'z', "zline" }, { BAN_ACT_GLINE, 'g', "gline" }, { BAN_ACT_SOFT_GLINE, 'G', "soft-gline" }, { BAN_ACT_GZLINE, 'Z', "gzline" }, { BAN_ACT_BLOCK, 'b', "block" }, { BAN_ACT_SOFT_BLOCK, 'B', "soft-block" }, { BAN_ACT_DCCBLOCK, 'd', "dccblock" }, { BAN_ACT_SOFT_DCCBLOCK,'D', "soft-dccblock" }, { BAN_ACT_VIRUSCHAN, 'v', "viruschan" }, { BAN_ACT_SOFT_VIRUSCHAN,'V', "soft-viruschan" }, { BAN_ACT_WARN, 'w', "warn" }, { BAN_ACT_SOFT_WARN, 'W', "soft-warn" }, { 0, 0, 0 } }; typedef struct { int value; /** Unique integer value of item */ char character; /** Unique character assigned to item */ char *name; /** Name of item */ char *irccommand; /** Raw IRC command of item (not unique!) */ } SpamfilterTargetTable; SpamfilterTargetTable spamfiltertargettable[] = { { SPAMF_CHANMSG, 'c', "channel", "PRIVMSG" }, { SPAMF_USERMSG, 'p', "private", "PRIVMSG" }, { SPAMF_USERNOTICE, 'n', "private-notice", "NOTICE" }, { SPAMF_CHANNOTICE, 'N', "channel-notice", "NOTICE" }, { SPAMF_PART, 'P', "part", "PART" }, { SPAMF_QUIT, 'q', "quit", "QUIT" }, { SPAMF_DCC, 'd', "dcc", "PRIVMSG" }, { SPAMF_USER, 'u', "user", "NICK" }, { SPAMF_AWAY, 'a', "away", "AWAY" }, { SPAMF_TOPIC, 't', "topic", "TOPIC" }, { SPAMF_MTAG, 'T', "message-tag", "message-tag" }, { 0, 0, 0, 0 } }; /** IRC Statistics (quite useless?) */ struct IRCStatistics ircstats; /** Returns the date in rather long string */ const char *long_date(time_t clock) { static char buf[80], plus; struct tm *lt, *gm; struct tm gmbuf; int minswest; if (!clock) time(&clock); gm = gmtime(&clock); memcpy(&gmbuf, gm, sizeof(gmbuf)); gm = &gmbuf; lt = localtime(&clock); #ifndef _WIN32 if (lt->tm_yday == gm->tm_yday) minswest = (gm->tm_hour - lt->tm_hour) * 60 + (gm->tm_min - lt->tm_min); else if (lt->tm_yday > gm->tm_yday) minswest = (gm->tm_hour - (lt->tm_hour + 24)) * 60; else minswest = ((gm->tm_hour + 24) - lt->tm_hour) * 60; #else minswest = (_timezone / 60); #endif plus = (minswest > 0) ? '-' : '+'; if (minswest < 0) minswest = -minswest; ircsnprintf(buf, sizeof(buf), "%s %s %d %d -- %02d:%02d %c%02d:%02d", weekdays[lt->tm_wday], months[lt->tm_mon], lt->tm_mday, 1900 + lt->tm_year, lt->tm_hour, lt->tm_min, plus, minswest / 60, minswest % 60); return buf; } /** Convert timestamp to a short date, a la: Wed Jun 30 21:49:08 1993 * @returns A short date string, or NULL if the timestamp is invalid * (out of range) * @param ts The timestamp * @param buf The buffer to store the string (minimum size: 128 bytes), * or NULL to use temporary static storage. */ const char *short_date(time_t ts, char *buf) { struct tm *t = gmtime(&ts); static char retbuf[128]; if (!buf) buf = retbuf; *buf = '\0'; if (!t) return NULL; if (!strftime(buf, 128, "%a %b %d %H:%M:%S %Y", t)) return NULL; return buf; } /** Return a string with the "pretty date" - yeah, another variant */ const char *pretty_date(time_t t) { static char buf[128]; struct tm *tm; if (!t) time(&t); tm = gmtime(&t); snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d GMT", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return buf; } /** Helper function for make_user_host() and friends. * Fixes a string so that the first white space found becomes an end of * string marker (`\-`). returns the 'fixed' string or "*" if the string * was NULL length or a NULL pointer. */ const char *check_string(const char *s) { static char buf[512]; static char star[2] = "*"; const char *str = s; if (BadPtr(s)) return star; for (; *s; s++) { if (isspace(*s)) { /* Because this is an unlikely scenario, we have * delayed the copy until here: */ strlncpy(buf, s, sizeof(buf), s - str); str = buf; break; } } return (BadPtr(str)) ? star : str; } /** Create a user@host based on the provided name and host */ char *make_user_host(const char *name, const char *host) { static char namebuf[USERLEN + HOSTLEN + 6]; strlncpy(namebuf, check_string(name), sizeof(namebuf), USERLEN+1); strlcat(namebuf, "@", sizeof(namebuf)); strlncat(namebuf, check_string(host), sizeof(namebuf), HOSTLEN+1); return namebuf; } /** Create a nick!user@host string based on the provided variables. * If any of the variables are NULL, it becomes * (asterisk) * This is the reentrant safe version. */ char *make_nick_user_host_r(char *namebuf, size_t namebuflen, const char *nick, const char *name, const char *host) { strlncpy(namebuf, check_string(nick), namebuflen, NICKLEN+1); strlcat(namebuf, "!", namebuflen); strlncat(namebuf, check_string(name), namebuflen, USERLEN+1); strlcat(namebuf, "@", namebuflen); strlncat(namebuf, check_string(host), namebuflen, HOSTLEN+1); return namebuf; } /** Create a nick!user@host string based on the provided variables. * If any of the variables are NULL, it becomes * (asterisk) * This version uses static storage. */ char *make_nick_user_host(const char *nick, const char *name, const char *host) { static char namebuf[NICKLEN + USERLEN + HOSTLEN + 24]; return make_nick_user_host_r(namebuf, sizeof(namebuf), nick, name, host); } /** Similar to ctime() but without a potential newline and * also takes a time_t value rather than a pointer. */ const char *myctime(time_t value) { static char buf[28]; char *p; strlcpy(buf, ctime(&value), sizeof buf); if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; return buf; } /* ** get_client_name ** Return the name of the client for various tracking and ** admin purposes. The main purpose of this function is to ** return the "socket host" name of the client, if that ** differs from the advertised name (other than case). ** But, this can be used to any client structure. ** ** Returns: ** "name[user@ip#.port]" if 'showip' is true; ** "name[sockethost]", if name and sockhost are different and ** showip is false; else ** "name". ** ** NOTE 1: ** Watch out the allocation of "nbuf", if either client->name ** or client->local->sockhost gets changed into pointers instead of ** directly allocated within the structure... ** ** NOTE 2: ** Function return either a pointer to the structure (client) or ** to internal buffer (nbuf). *NEVER* use the returned pointer ** to modify what it points!!! */ const char *get_client_name(Client *client, int showip) { static char nbuf[HOSTLEN * 2 + USERLEN + 5]; if (MyConnect(client)) { if (showip) ircsnprintf(nbuf, sizeof(nbuf), "%s[%s@%s.%u]", client->name, IsIdentSuccess(client) ? client->ident : "", client->ip ? client->ip : "???", (unsigned int)client->local->port); else { if (mycmp(client->name, client->local->sockhost)) ircsnprintf(nbuf, sizeof(nbuf), "%s[%s]", client->name, client->local->sockhost); else return client->name; } return nbuf; } return client->name; } const char *get_client_host(Client *client) { static char nbuf[HOSTLEN * 2 + USERLEN + 5]; if (!MyConnect(client)) return client->name; if (!client->local->hostp) return get_client_name(client, FALSE); ircsnprintf(nbuf, sizeof(nbuf), "%s[%-.*s@%-.*s]", client->name, USERLEN, IsIdentSuccess(client) ? client->ident : "", HOSTLEN, client->local->hostp->h_name); return nbuf; } /* * Set sockhost to 'host'. Skip the user@ part of 'host' if necessary. */ void set_sockhost(Client *client, const char *host) { const char *s; if ((s = strchr(host, '@'))) s++; else s = host; strlcpy(client->local->sockhost, s, sizeof(client->local->sockhost)); } /** Returns 1 if 'from' is on the allow list of 'to' */ int on_dccallow_list(Client *to, Client *from) { Link *lp; for(lp = to->user->dccallow; lp; lp = lp->next) if (lp->flags == DCC_LINK_ME && lp->value.client == from) return 1; return 0; } /** Delete all DCCALLOW references. * Ultimately, this should be moved to modules/dccallow.c */ void remove_dcc_references(Client *client) { Client *acptr; Link *lp, *nextlp; Link **lpp, *tmp; int found; lp = client->user->dccallow; while(lp) { nextlp = lp->next; acptr = lp->value.client; for(found = 0, lpp = &(acptr->user->dccallow); *lpp; lpp=&((*lpp)->next)) { if (lp->flags == (*lpp)->flags) continue; /* match only opposite types for sanity */ if ((*lpp)->value.client == client) { if ((*lpp)->flags == DCC_LINK_ME) { sendto_one(acptr, NULL, ":%s %d %s :%s has been removed from " "your DCC allow list for signing off", me.name, RPL_DCCINFO, acptr->name, client->name); } tmp = *lpp; *lpp = tmp->next; free_link(tmp); found++; break; } } if (!found) { unreal_log(ULOG_WARNING, "main", "BUG_REMOVE_DCC_REFERENCES", acptr, "[BUG] remove_dcc_references: $client was in dccallowme " "list of $existing_client but not in dccallowrem list!", log_data_client("existing_client", client)); } free_link(lp); lp = nextlp; } } /* * Remove all clients that depend on source_p; assumes all (S)QUITs have * already been sent. we make sure to exit a server's dependent clients * and servers before the server itself; exit_one_client takes care of * actually removing things off llists. tweaked from +CSr31 -orabidoo */ static void recurse_remove_clients(Client *client, MessageTag *mtags, const char *comment) { Client *acptr, *next; list_for_each_entry_safe(acptr, next, &client_list, client_node) { if (acptr->uplink != client) continue; exit_one_client(acptr, mtags, comment); } list_for_each_entry_safe(acptr, next, &global_server_list, client_node) { if (acptr->uplink != client) continue; recurse_remove_clients(acptr, mtags, comment); exit_one_client(acptr, mtags, comment); } } /* ** Remove *everything* that depends on source_p, from all lists, and sending ** all necessary QUITs and SQUITs. source_p itself is still on the lists, ** and its SQUITs have been sent except for the upstream one -orabidoo */ static void remove_dependents(Client *client, Client *from, MessageTag *mtags, const char *comment, const char *splitstr) { Client *acptr; list_for_each_entry(acptr, &global_server_list, client_node) { if (acptr != from && !(acptr->direction && (acptr->direction == from))) sendto_one(acptr, mtags, "SQUIT %s :%s", client->name, comment); } recurse_remove_clients(client, mtags, splitstr); } /* ** Exit one client, local or remote. Assuming all dependants have ** been already removed, and socket closed for local client. */ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *comment) { Link *lp; Membership *mp; assert(!IsMe(client)); if (IsUser(client)) { MessageTag *mtags_o = NULL; if (!MyUser(client)) RunHook(HOOKTYPE_REMOTE_QUIT, client, mtags_i, comment); new_message_special(client, mtags_i, &mtags_o, ":%s QUIT", client->name); sendto_local_common_channels(client, NULL, 0, mtags_o, ":%s QUIT :%s", client->name, comment); free_message_tags(mtags_o); while ((mp = client->user->channel)) remove_user_from_channel(client, mp->channel, 1); /* again, this is all that is needed */ /* Clean up dccallow list and (if needed) notify other clients * that have this person on DCCALLOW that the user just left/got removed. */ remove_dcc_references(client); /* For remote clients, we need to check for any outstanding async * connects attached to this 'client', and set those records to NULL. * Why not for local? Well, we already do that in close_connection ;) */ if (!MyConnect(client)) unrealdns_delreq_bycptr(client); } /* Free module related data for this client */ moddata_free_client(client); if (MyConnect(client)) moddata_free_local_client(client); /* Remove client from the client list */ if (*client->id) { del_from_id_hash_table(client->id, client); *client->id = '\0'; } if (*client->name) del_from_client_hash_table(client->name, client); if (remote_rehash_client == client) remote_rehash_client = NULL; /* client did a /REHASH and QUIT before rehash was complete */ remove_client_from_list(client); } /** Exit this IRC client, and all the dependents (users, servers) if this is a server. * @param client The client to exit. * @param recv_mtags Message tags to use as a base (if any). * @param comment The (s)quit message */ void exit_client(Client *client, MessageTag *recv_mtags, const char *comment) { exit_client_ex(client, client->direction, recv_mtags, comment); } /** Exit this IRC client, and all the dependents (users, servers) if this is a server. * @param client The client to exit. * @param recv_mtags Message tags to use as a base (if any). * @param comment The (s)quit message */ void exit_client_fmt(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...) { char comment[512]; va_list vl; va_start(vl, pattern); vsnprintf(comment, sizeof(comment), pattern, vl); va_end(vl); exit_client_ex(client, client->direction, recv_mtags, comment); } /** Exit this IRC client, and all the dependents (users, servers) if this is a server. * @param client The client to exit. * @param recv_mtags Message tags to use as a base (if any). * @param comment The (s)quit message */ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment) { long long on_for; ConfigItem_listen *listen_conf; MessageTag *mtags_generated = NULL; if (IsDead(client)) return; /* Already marked as exited */ /* We replace 'recv_mtags' here with a newly * generated id if 'recv_mtags' is NULL or is * non-NULL and contains no msgid etc. * This saves us from doing a new_message() * prior to the exit_client() call at around * 100+ places elsewhere in the code. */ new_message(client, recv_mtags, &mtags_generated); recv_mtags = mtags_generated; if (MyConnect(client)) { if (client->local->class) { client->local->class->clients--; if ((client->local->class->flag.temporary) && !client->local->class->clients && !client->local->class->xrefcount) { delete_classblock(client->local->class); client->local->class = NULL; } } if (IsUser(client)) irccounts.me_clients--; if (client->server && client->server->conf) { client->server->conf->refcount--; if (!client->server->conf->refcount && client->server->conf->flag.temporary) { delete_linkblock(client->server->conf); client->server->conf = NULL; } } if (IsServer(client)) { irccounts.me_servers--; if (!IsServerDisconnectLogged(client)) { unreal_log(ULOG_ERROR, "link", "LINK_DISCONNECTED", client, "Lost server link to $client [$client.ip]: $reason", log_data_string("reason", comment)); } } free_pending_net(client); if (client->local->listener) if (client->local->listener && !IsOutgoing(client)) { listen_conf = client->local->listener; listen_conf->clients--; if (listen_conf->flag.temporary && (listen_conf->clients == 0)) { /* Call listen cleanup */ listen_cleanup(); } } SetClosing(client); if (IsUser(client)) { long connected_time = TStime() - client->local->creationtime; RunHook(HOOKTYPE_LOCAL_QUIT, client, recv_mtags, comment); unreal_log(ULOG_INFO, "connect", "LOCAL_CLIENT_DISCONNECT", client, "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)", log_data_string("extended_client_info", get_connect_extinfo(client)), log_data_string("reason", comment), log_data_integer("connected_time", connected_time)); } else if (IsUnknown(client)) { RunHook(HOOKTYPE_UNKUSER_QUIT, client, recv_mtags, comment); } if (client->local->fd >= 0 && !IsConnecting(client)) { sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", get_client_name(client, FALSE), comment); } close_connection(client); } else if (IsUser(client) && !IsULine(client)) { if (client->uplink != &me) { unreal_log(ULOG_INFO, "connect", "REMOTE_CLIENT_DISCONNECT", client, "Client exiting: $client ($client.user.username@$client.hostname) [$client.ip] ($reason)", log_data_string("extended_client_info", get_connect_extinfo(client)), log_data_string("reason", comment), log_data_string("from_server_name", client->user->server)); } } /* * Recurse down the client list and get rid of clients who are no * longer connected to the network (from my point of view) * Only do this expensive stuff if exited==server -Donwulff */ if (IsServer(client)) { char splitstr[HOSTLEN + HOSTLEN + 2]; assert(client->server != NULL && client->uplink != NULL); if (FLAT_MAP) strlcpy(splitstr, "*.net *.split", sizeof splitstr); else ircsnprintf(splitstr, sizeof splitstr, "%s %s", client->uplink->name, client->name); remove_dependents(client, origin, recv_mtags, comment, splitstr); RunHook(HOOKTYPE_SERVER_QUIT, client, recv_mtags); } else if (IsUser(client) && !IsKilled(client)) { sendto_server(client, 0, 0, recv_mtags, ":%s QUIT :%s", client->id, comment); } /* Finally, the client/server itself exits.. */ exit_one_client(client, recv_mtags, comment); free_message_tags(mtags_generated); } /** Initialize the (quite useless) IRC statistics */ void initstats(void) { memset(&ircstats, 0, sizeof(ircstats)); } /** Verify operator count, to catch bugs introduced by flawed services */ void verify_opercount(Client *orig, const char *tag) { int counted = 0; Client *client; char text[2048]; list_for_each_entry(client, &client_list, client_node) { if (IsOper(client) && !IsHideOper(client)) counted++; } if (counted == irccounts.operators) return; unreal_log(ULOG_WARNING, "main", "BUG_LUSERS_OPERS", orig, "[BUG] Operator count bug at $where! Value in /LUSERS is $opers, " "we counted $counted_opers, " "triggered by $client.details on $client.user.servername", log_data_integer("opers", irccounts.operators), log_data_integer("counted_opers", counted), log_data_string("where", tag)); irccounts.operators = counted; } /** Check if the specified hostname does not contain forbidden characters. * @param host The host name to check * @param strict If set to 1 then we also check if the hostname * resembles an IP address (eg contains ':') and * some other stuff that we don't consider valid * in actual DNS names (eg '/'). * @returns 1 if valid, 0 if not. */ int valid_host(const char *host, int strict) { const char *p; if (!*host) return 0; /* must at least contain something */ if (strlen(host) > HOSTLEN) return 0; /* too long hosts are invalid too */ if (strict) { for (p=host; *p; p++) if (!isalnum(*p) && !strchr("_-.", *p)) return 0; } else { for (p=host; *p; p++) if (!isalnum(*p) && !strchr("_-.:/", *p)) return 0; } return 1; } /*|| BAN ACTION ROUTINES FOLLOW ||*/ /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */ BanAction banact_stringtoval(const char *s) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (!strcasecmp(s, b->name)) return b->value; return 0; } /** Converts a banaction character (eg: 'K') to an integer value (eg: BAN_ACT_KILL) */ BanAction banact_chartoval(char c) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (b->character == c) return b->value; return 0; } /** Converts a banaction value (eg: BAN_ACT_KILL) to a character value (eg: 'k') */ char banact_valtochar(BanAction val) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (b->value == val) return b->character; return '\0'; } /** Converts a banaction value (eg: BAN_ACT_KLINE) to a string (eg: "kline") */ const char *banact_valtostring(BanAction val) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (b->value == val) return b->name; return "UNKNOWN"; } /*|| BAN TARGET ROUTINES FOLLOW ||*/ /** Extract target flags from string 's'. */ int spamfilter_gettargets(const char *s, Client *client) { SpamfilterTargetTable *e; int flags = 0; for (; *s; s++) { for (e = &spamfiltertargettable[0]; e->value; e++) if (e->character == *s) { flags |= e->value; break; } if (!e->value && client) { sendnotice(client, "Unknown target type '%c'", *s); return 0; } } return flags; } /** Convert a string with a targetname to an integer value */ int spamfilter_getconftargets(const char *s) { SpamfilterTargetTable *e; for (e = &spamfiltertargettable[0]; e->value; e++) if (!strcmp(s, e->name)) return e->value; return 0; } /** Create a string with (multiple) targets from an integer mask */ char *spamfilter_target_inttostring(int v) { static char buf[128]; SpamfilterTargetTable *e; char *p = buf; for (e = &spamfiltertargettable[0]; e->value; e++) if (v & e->value) *p++ = e->character; *p = '\0'; return buf; } /** Replace underscores back to the space character. * This is used for the spamfilter reason. */ char *unreal_decodespace(char *s) { static char buf[512], *i, *o; for (i = s, o = buf; (*i) && (o < buf+510); i++) if (*i == '_') { if (i[1] != '_') *o++ = ' '; else { *o++ = '_'; i++; } } else *o++ = *i; *o = '\0'; return buf; } /** Replace spaces to underscore characters. * This is used for the spamfilter reason. */ char *unreal_encodespace(char *s) { static char buf[512], *i, *o; if (!s) return NULL; /* NULL in = NULL out */ for (i = s, o = buf; (*i) && (o < buf+509); i++) { if (*i == ' ') *o++ = '_'; else if (*i == '_') { *o++ = '_'; *o++ = '_'; } else *o++ = *i; } *o = '\0'; return buf; } /** This is basically only used internally by match_spamfilter()... */ const char *cmdname_by_spamftarget(int target) { SpamfilterTargetTable *e; for (e = &spamfiltertargettable[0]; e->value; e++) if (e->value == target) return e->irccommand; return "???"; } /** Returns 1 if this is a channel from set::auto-join or set::oper-auto-join */ int is_autojoin_chan(const char *chname) { char buf[512]; char *p, *name; if (OPER_AUTO_JOIN_CHANS) { strlcpy(buf, OPER_AUTO_JOIN_CHANS, sizeof(buf)); for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ",")) if (!strcasecmp(name, chname)) return 1; } if (AUTO_JOIN_CHANS) { strlcpy(buf, AUTO_JOIN_CHANS, sizeof(buf)); for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ",")) if (!strcasecmp(name, chname)) return 1; } return 0; } /** Free all masks in the mask list */ void unreal_delete_masks(ConfigItem_mask *m) { ConfigItem_mask *m_next; for (; m; m = m_next) { m_next = m->next; safe_free(m->mask); safe_free(m); } } /** Internal function to add one individual mask to the list */ static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce) { ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask)); /* Since we allow both mask "xyz"; and mask { abc; def; };... */ if (ce->value) safe_strdup(m->mask, ce->value); else safe_strdup(m->mask, ce->name); add_ListItem((ListStruct *)m, (ListStruct **)head); } /** Add mask entries from config */ void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce) { if (ce->items) { ConfigEntry *cep; for (cep = ce->items; cep; cep = cep->next) unreal_add_mask(head, cep); } else { unreal_add_mask(head, ce); } } /** Check if a client matches any of the masks in the mask list. * The following rules apply: * - If you have only negating entries, like '!abc' and '!def', then * we assume an implicit * rule first, since that is clearly what * the user wants. * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the * implicit * is dropped and we assume you only want to match *.com, * with the exception of irc1*.com and irc2*.com. * - If you only have normal entries without ! then things are * as they always are. * @param client The client to run the mask match against * @param mask The mask entry from the config file * @returns 1 on match, 0 on non-match. */ int unreal_mask_match(Client *client, ConfigItem_mask *mask) { int retval = 1; ConfigItem_mask *m; if (!mask) return 0; /* Empty mask block is no match */ /* First check normal matches (without ! prefix) */ for (m = mask; m; m = m->next) { if (m->mask[0] != '!') { retval = 0; /* no implicit * */ if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) { retval = 1; break; } } } if (retval) { /* We matched. Check for exceptions (with ! prefix) */ for (m = mask; m; m = m->next) { if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED)) return 0; } } return retval; } /** Check if a string matches any of the masks in the mask list. * The following rules apply: * - If you have only negating entries, like '!abc' and '!def', then * we assume an implicit * rule first, since that is clearly what * the user wants. * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the * implicit * is dropped and we assume you only want to match *.com, * with the exception of irc1*.com and irc2*.com. * - If you only have normal entries without ! then things are * as they always are. * @param name The name to run the mask matching on * @param mask The mask entry from the config file * @returns 1 on match, 0 on non-match. */ int unreal_mask_match_string(const char *name, ConfigItem_mask *mask) { int retval = 1; ConfigItem_mask *m; if (!mask) return 0; /* Empty mask block is no match */ /* First check normal matches (without ! prefix) */ for (m = mask; m; m = m->next) { if (m->mask[0] != '!') { retval = 0; /* no implicit * */ if (match_simple(m->mask, name)) { retval = 1; break; } } } if (retval) { /* We matched. Check for exceptions (with ! prefix) */ for (m = mask; m; m = m->next) { if ((m->mask[0] == '!') && match_simple(m->mask+1, name)) return 0; } } return retval; } /** Our own strcasestr implementation because strcasestr is * often not available or is not working correctly. */ char *our_strcasestr(const char *haystack, const char *needle) { int i; int nlength = strlen(needle); int hlength = strlen(haystack); if (nlength > hlength) return NULL; if (hlength <= 0) return NULL; if (nlength <= 0) return (char *)haystack; for (i = 0; i <= (hlength - nlength); i++) { if (strncasecmp (haystack + i, needle, nlength) == 0) return (char *)(haystack + i); } return NULL; /* not found */ } /** Add a title to the users' WHOIS ("special whois"). Broadcast change to servers. * @param client The client * @param tag A tag used internally and for server-to-server traffic, * not visible to end-users. * @param priority Priority - for ordering multiple swhois entries * (lower number = further up in the swhoises list in WHOIS) * @param swhois The actual special whois title (string) you want to add to the user * @param from Who added this entry * @param skip Which server(-side) to skip broadcasting this entry to. */ int swhois_add(Client *client, const char *tag, int priority, const char *swhois, Client *from, Client *skip) { SWhois *s; /* Make sure the line isn't added yet. If so, then bail out silently. */ for (s = client->user->swhois; s; s = s->next) if (!strcmp(s->line, swhois)) return -1; /* exists */ s = safe_alloc(sizeof(SWhois)); safe_strdup(s->line, swhois); safe_strdup(s->setby, tag); s->priority = priority; AddListItemPrio(s, client->user->swhois, s->priority); sendto_server(skip, 0, PROTO_EXTSWHOIS, NULL, ":%s SWHOIS %s :%s", from->id, client->id, swhois); sendto_server(skip, PROTO_EXTSWHOIS, 0, NULL, ":%s SWHOIS %s + %s %d :%s", from->id, client->id, tag, priority, swhois); return 0; } /** Delete swhois title(s). * Delete swhois by tag and swhois. Then broadcast this change to all other servers. * @param client The client * @param tag A tag used internally and for server-to-server traffic, * not visible to end-users. * @param swhois The actual special whois title (string) you are removing * @param from Who added this entry earlier on * @param skip Which server(-side) to skip broadcasting this entry to. * @note If you use swhois "*" then it will remove all swhois titles for that tag */ int swhois_delete(Client *client, const char *tag, const char *swhois, Client *from, Client *skip) { SWhois *s, *s_next; int ret = -1; /* default to 'not found' */ for (s = client->user->swhois; s; s = s_next) { s_next = s->next; /* If ( same swhois or "*" ) AND same tag */ if ( ((!strcmp(s->line, swhois) || !strcmp(swhois, "*")) && !strcmp(s->setby, tag))) { DelListItem(s, client->user->swhois); safe_free(s->line); safe_free(s->setby); safe_free(s); sendto_server(skip, 0, PROTO_EXTSWHOIS, NULL, ":%s SWHOIS %s :", from->id, client->id); sendto_server(skip, PROTO_EXTSWHOIS, 0, NULL, ":%s SWHOIS %s - %s %d :%s", from->id, client->id, tag, 0, swhois); ret = 0; } } return ret; } /** Is this user using a websocket? (LOCAL USERS ONLY) */ int IsWebsocket(Client *client) { ModDataInfo *md = findmoddata_byname("websocket", MODDATATYPE_CLIENT); if (!md) return 0; /* websocket module not loaded */ return (MyConnect(client) && moddata_client(client, md).ptr) ? 1 : 0; } /** Generic function to inform the user he/she has been banned. * @param client The affected client. * @param bantype The ban type, such as: "K-Lined", "G-Lined" or "realname". * @param reason The specified reason. * @param global Whether the ban is global (1) or for this server only (0) * @param noexit Set this to NO_EXIT_CLIENT to make us not call exit_client(). * This is really only needed from the accept code, do not * use it anywhere else. No really, never. * * @note This function will call exit_client() appropriately. */ void banned_client(Client *client, const char *bantype, const char *reason, int global, int noexit) { char buf[512]; char *fmt = global ? iConf.reject_message_gline : iConf.reject_message_kline; const char *vars[6], *values[6]; if (!MyConnect(client)) abort(); /* hmm... or be more flexible? */ /* This was: "You are not welcome on this %s. %s: %s. %s" but is now dynamic: */ vars[0] = "bantype"; values[0] = bantype; vars[1] = "banreason"; values[1] = reason; vars[2] = "klineaddr"; values[2] = KLINE_ADDRESS; vars[3] = "glineaddr"; values[3] = GLINE_ADDRESS ? GLINE_ADDRESS : KLINE_ADDRESS; /* fallback to klineaddr */ vars[4] = "ip"; values[4] = GetIP(client); vars[5] = NULL; values[5] = NULL; buildvarstring(fmt, buf, sizeof(buf), vars, values); /* This is a bit extensive but we will send both a YOUAREBANNEDCREEP * and a notice to the user. * The YOUAREBANNEDCREEP will be helpful for the client since it makes * clear the user should not quickly reconnect, as (s)he is banned. * The notice still needs to be there because it stands out well * at most IRC clients. */ if (noexit != NO_EXIT_CLIENT) { sendnumeric(client, ERR_YOUREBANNEDCREEP, buf); sendnotice(client, "%s", buf); } else { send_raw_direct(client, ":%s %d %s :%s", me.name, ERR_YOUREBANNEDCREEP, (*client->name ? client->name : "*"), buf); send_raw_direct(client, ":%s NOTICE %s :%s", me.name, (*client->name ? client->name : "*"), buf); } /* The final message in the ERROR is shorter. */ if (HIDE_BAN_REASON && IsRegistered(client)) snprintf(buf, sizeof(buf), "Banned (%s)", bantype); else snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason); if (noexit != NO_EXIT_CLIENT) { exit_client(client, NULL, buf); } else { /* Special handling for direct Z-line code */ send_raw_direct(client, "ERROR :Closing Link: [%s] (%s)", client->ip, buf); } } /** Our stpcpy implementation - discouraged due to lack of bounds checking */ char *mystpcpy(char *dst, const char *src) { for (; *src; src++) *dst++ = *src; *dst = '\0'; return dst; } /** Helper function for send_channel_modes_sjoin3() and cmd_sjoin() * to build the SJSBY prefix which is to * communicate when the ban was set and by whom. * @param buf The buffer to write to * @param setby The setter of the "ban" * @param seton The time the "ban" was set * @retval The number of bytes written EXCLUDING the NUL byte, * so similar to what strlen() would have returned. * @note Caller must ensure that the buffer 'buf' is of sufficient size. */ size_t add_sjsby(char *buf, const char *setby, time_t seton) { char tbuf[32]; char *p = buf; snprintf(tbuf, sizeof(tbuf), "%ld", (long)seton); *p++ = '<'; p = mystpcpy(p, tbuf); *p++ = ','; p = mystpcpy(p, setby); *p++ = '>'; *p = '\0'; return p - buf; } /** Concatenate the entire parameter string. * The function will take care of spaces in the final parameter (if any). * @param buf The buffer to output in. * @param len Length of the buffer. * @param parc Parameter count, ircd style. * @param parv Parameters, ircd style, so we will start at parv[1]. * @section ex1 Example * @code * char buf[512]; * concat_params(buf, sizeof(buf), parc, parv); * sendto_server(client, 0, 0, recv_mtags, ":%s SOMECOMMAND %s", client->name, buf); * @endcode */ void concat_params(char *buf, int len, int parc, const char *parv[]) { int i; *buf = '\0'; for (i = 1; i < parc; i++) { const char *param = parv[i]; if (!param) break; if (*buf) strlcat(buf, " ", len); if (strchr(param, ' ') || (*param == ':')) { /* Last parameter, with : */ strlcat(buf, ":", len); strlcat(buf, parv[i], len); break; } strlcat(buf, parv[i], len); } } /** Find a particular message-tag in the 'mtags' list */ MessageTag *find_mtag(MessageTag *mtags, const char *token) { for (; mtags; mtags = mtags->next) if (!strcmp(mtags->name, token)) return mtags; return NULL; } /** Free all message tags in the list 'm' */ void free_message_tags(MessageTag *m) { MessageTag *m_next; for (; m; m = m_next) { m_next = m->next; safe_free(m->name); safe_free(m->value); safe_free(m); } } /** Duplicate a MessageTag structure. * @note This duplicate a single MessageTag. * It does not duplicate an entire linked list. */ MessageTag *duplicate_mtag(MessageTag *mtag) { MessageTag *m = safe_alloc(sizeof(MessageTag)); safe_strdup(m->name, mtag->name); safe_strdup(m->value, mtag->value); return m; } /** New message. Either really brand new, or inherited from other servers. * This function calls modules so they can add tags, such as: * msgid, time and account. */ void new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list) { Hook *h; for (h = Hooks[HOOKTYPE_NEW_MESSAGE]; h; h = h->next) (*(h->func.voidfunc))(sender, recv_mtags, mtag_list, NULL); } /** New message - SPECIAL edition. Either really brand new, or inherited * from other servers. * This function calls modules so they can add tags, such as: * msgid, time and account. * This special version deals in a special way with msgid in particular. * The pattern and vararg create a 'signature', this is normally * identical to the message that is sent to clients (end-users). * For example ":xyz JOIN #chan". */ void new_message_special(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, FORMAT_STRING(const char *pattern), ...) { Hook *h; va_list vl; char buf[512]; va_start(vl, pattern); ircvsnprintf(buf, sizeof(buf), pattern, vl); va_end(vl); for (h = Hooks[HOOKTYPE_NEW_MESSAGE]; h; h = h->next) (*(h->func.voidfunc))(sender, recv_mtags, mtag_list, buf); } /** Default handler for parse_message_tags(). * This is only used if the 'mtags' module is NOT loaded, * which would be quite unusual, but possible. */ void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list) { /* Just skip everything until the space character */ for (; **str && **str != ' '; *str = *str + 1); } /** Default handler for mtags_to_string(). * This is only used if the 'mtags' module is NOT loaded, * which would be quite unusual, but possible. */ const char *mtags_to_string_default_handler(MessageTag *m, Client *client) { return NULL; } /** Default handler for add_silence(). * This is only used if the 'silence' module is NOT loaded, * which would be unusual, but possible. */ int add_silence_default_handler(Client *client, const char *mask, int senderr) { return 0; } /** Default handler for del_silence(). * This is only used if the 'silence' module is NOT loaded, * which would be unusual, but possible. */ int del_silence_default_handler(Client *client, const char *mask) { return 0; } /** Default handler for is_silenced(). * This is only used if the 'silence' module is NOT loaded, * which would be unusual, but possible. */ int is_silenced_default_handler(Client *client, Client *acptr) { return 0; } /** Generate a BATCH id. * This can be used in a :serv BATCH +%s ... message */ void generate_batch_id(char *str) { gen_random_alnum(str, BATCHLEN); } /** A default handler if labeled-response module is not loaded. * Normally a NOOP, but since caller will safe_free it * later we do actually allocate something. */ void *labeled_response_save_context_default_handler(void) { return safe_alloc(8); } /** A default handler for if labeled-response module is not loaded */ void labeled_response_set_context_default_handler(void *ctx) { } /** A default handler for if labeled-response module is not loaded */ void labeled_response_force_end_default_handler(void) { } /** Ad default handler for if the slog module is not loaded */ void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized) { } /** my_timegm: mktime()-like function which will use GMT/UTC. * Strangely enough there is no standard function for this. * On some *NIX OS's timegm() may be available, sometimes only * with the help of certain #define's which we may or may * not do. * Windows provides _mkgmtime(). * In the other cases the man pages and basically everyone * suggests to set TZ to empty prior to calling mktime and * restoring it after the call. Whut? How ridiculous is that? */ time_t my_timegm(struct tm *tm) { #if HAVE_TIMEGM return timegm(tm); #elif defined(_WIN32) return _mkgmtime(tm); #else time_t ret; char *tz = NULL; safe_strdup(tz, getenv("TZ")); setenv("TZ", "", 1); ret = mktime(tm); if (tz) { setenv("TZ", tz, 1); safe_free(tz); } else { unsetenv("TZ"); } tzset(); return ret; #endif } /** Convert an ISO 8601 timestamp ('server-time') to UNIX time */ time_t server_time_to_unix_time(const char *tbuf) { struct tm tm; int dontcare = 0; time_t ret; if (!tbuf) return 0; if (strlen(tbuf) < 20) return 0; memset(&tm, 0, sizeof(tm)); ret = sscanf(tbuf, "%d-%d-%dT%d:%d:%d.%dZ", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &dontcare); if (ret != 7) return 0; tm.tm_year -= 1900; tm.tm_mon -= 1; ret = my_timegm(&tm); return ret; } /** Convert an RFC 2616 timestamp (used in HTTP headers) to UNIX time */ time_t rfc2616_time_to_unix_time(const char *tbuf) { struct tm tm; int dontcare = 0; time_t ret; char month[8]; int i; if (!tbuf) return 0; if (strlen(tbuf) < 20) return 0; memset(&tm, 0, sizeof(tm)); *month = '\0'; ret = sscanf(tbuf, "%*[a-zA-Z,] %d %3s %d %d:%d:%d", &tm.tm_mday, month, &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (ret < 6) return 0; for (i=0; i < 12; i++) { if (!strcmp(short_months[i], month)) { tm.tm_mon = i; break; } } if (i == 12) return 0; /* Month not found */ if (tm.tm_year < 1900) return 0; tm.tm_year -= 1900; ret = my_timegm(&tm); return ret; /* can still be 0 */ } /** Returns an RFC 2616 timestamp (used in HTTP headers) */ const char *rfc2616_time(time_t clock) { static char buf[80], plus; struct tm *lt, *gm; struct tm gmbuf; int minswest; if (!clock) time(&clock); gm = gmtime(&clock); snprintf(buf, sizeof(buf), "%s, %02d %.3s %4d %02d:%02d:%02d GMT", short_weekdays[gm->tm_wday], gm->tm_mday, short_months[gm->tm_mon], gm->tm_year + 1900, gm->tm_hour, gm->tm_min, gm->tm_sec); return buf; } /** Write a 64 bit integer to a file. * @param fd File descriptor * @param t The value to write * @returns 1 on success, 0 on failure. */ int write_int64(FILE *fd, uint64_t t) { if (fwrite(&t, 1, sizeof(t), fd) < sizeof(t)) return 0; return 1; } /** Write a 32 bit integer to a file. * @param fd File descriptor * @param t The value to write * @returns 1 on success, 0 on failure. */ int write_int32(FILE *fd, uint32_t t) { if (fwrite(&t, 1, sizeof(t), fd) < sizeof(t)) return 0; return 1; } /** Read a 64 bit integer from a file. * @param fd File descriptor * @param t The value to write * @returns 1 on success, 0 on failure. */ int read_int64(FILE *fd, uint64_t *t) { if (fread(t, 1, sizeof(uint64_t), fd) < sizeof(uint64_t)) return 0; return 1; } /** Read a 32 bit integer from a file. * @param fd File descriptor * @param t The value to write * @returns 1 on success, 0 on failure. */ int read_int32(FILE *fd, uint32_t *t) { if (fread(t, 1, sizeof(uint32_t), fd) < sizeof(uint32_t)) return 0; return 1; } /** Read binary data from a file. * @param fd File descriptor * @param buf Pointer to buffer * @param len Size of buffer * @note This function is not used much, in most cases * you should use read_str(), read_int32() or * read_int64() instead. * @returns 1 on success, 0 on failure. */ int read_data(FILE *fd, void *buf, size_t len) { if (fread(buf, 1, len, fd) < len) return 0; return 1; } /** Write binary data to a file. * @param fd File descriptor * @param buf Pointer to buffer * @param len Size of buffer * @note This function is not used much, in most cases * you should use write_str(), write_int32() or * write_int64() instead. * @returns 1 on success, 0 on failure. */ int write_data(FILE *fd, const void *buf, size_t len) { if (fwrite(buf, 1, len, fd) < len) return 0; return 1; } /** Write a string to a file. * @param fd File descriptor * @param x Pointer to string * @note This function can write a string up to 65534 * characters, which should be plenty for usage * in UnrealIRCd. * Note that 'x' can safely be NULL. * @returns 1 on success, 0 on failure. */ int write_str(FILE *fd, const char *x) { uint16_t len; len = x ? strlen(x) : 0xffff; if (!write_data(fd, &len, sizeof(len))) return 0; if ((len > 0) && (len < 0xffff)) { if (!write_data(fd, x, len)) return 0; } return 1; } /** Read a string from a file. * @param fd File descriptor * @param x Pointer to string pointer * @note This function will allocate memory for the data * and set the string pointer to this value. * If a NULL pointer was written via write_str() * then read_str() may also return a NULL pointer. * @returns 1 on success, 0 on failure. */ int read_str(FILE *fd, char **x) { uint16_t len; size_t size; *x = NULL; if (!read_data(fd, &len, sizeof(len))) return 0; if (len == 0xffff) { /* Magic value meaning NULL */ *x = NULL; return 1; } if (len == 0) { /* 0 means empty string */ safe_strdup(*x, ""); return 1; } if (len > 10000) return 0; size = len; *x = safe_alloc(size + 1); if (!read_data(fd, *x, size)) { safe_free(*x); return 0; } (*x)[len] = 0; return 1; } /** Convert binary 'data' of size 'len' to a hexadecimal string 'str'. * The caller is responsible to ensure that 'str' is sufficiently large. */ void binarytohex(void *data, size_t len, char *str) { const char hexchars[16] = "0123456789abcdef"; char *datastr = (char *)data; int i, n = 0; for (i=0; i> 4) & 0xF]; str[n++] = hexchars[datastr[i] & 0xF]; } str[n] = '\0'; } /** Generates an MD5 checksum - binary version. * @param mdout[out] Buffer to store result in, the result will be 16 bytes in binary * (not ascii printable!). * @param src[in] The input data used to generate the checksum. * @param n[in] Length of data. * @deprecated The MD5 algorithm is deprecated and insecure, * so only use this if absolutely needed. */ void DoMD5(char *mdout, const char *src, unsigned long n) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L unsigned int md_len; EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); if (EVP_DigestInit_ex(mdctx, md5_function, NULL) != 1) abort(); EVP_DigestUpdate(mdctx, src, n); EVP_DigestFinal_ex(mdctx, mdout, &md_len); EVP_MD_CTX_free(mdctx); #else MD5_CTX hash; MD5_Init(&hash); MD5_Update(&hash, src, n); MD5_Final(mdout, &hash); #endif } /** Generates an MD5 checksum - ASCII printable string (0011223344..etc..). * @param dst[out] Buffer to store result in, this will be the result will be * 32 characters + nul terminator, so needs to be at least 33 characters. * @param src[in] The input data used to generate the checksum. * @param n[in] Length of data. * @deprecated The MD5 algorithm is deprecated and insecure, * so only use this if absolutely needed. */ char *md5hash(char *dst, const char *src, unsigned long n) { char tmp[16]; DoMD5(tmp, src, n); binarytohex(tmp, sizeof(tmp), dst); return dst; } /** Generates a SHA256 checksum - binary version. * Most people will want to use sha256hash() instead which outputs hex. * @param dst[out] Buffer to store result in, which needs to be 32 bytes in length * (SHA256_DIGEST_LENGTH). * @param src[in] The input data used to generate the checksum. * @param n[in] Length of data. */ void sha256hash_binary(char *dst, const char *src, unsigned long n) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L unsigned int md_len; EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1) abort(); EVP_DigestUpdate(mdctx, src, n); EVP_DigestFinal_ex(mdctx, dst, &md_len); EVP_MD_CTX_free(mdctx); #else SHA256_CTX hash; SHA256_Init(&hash); SHA256_Update(&hash, src, n); SHA256_Final(dst, &hash); #endif } /** Generates a SHA256 checksum - ASCII printable string (0011223344..etc..). * @param dst[out] Buffer to store result in, which needs to be 65 bytes minimum. * @param src[in] The input data used to generate the checksum. * @param n[in] Length of data. */ char *sha256hash(char *dst, const char *src, unsigned long n) { char binaryhash[SHA256_DIGEST_LENGTH]; sha256hash_binary(binaryhash, src, n); binarytohex(binaryhash, sizeof(binaryhash), dst); return dst; } /** Calculate the SHA256 checksum of a file */ const char *sha256sum_file(const char *fname) { FILE *fd; char buf[2048]; SHA256_CTX hash; char binaryhash[SHA256_DIGEST_LENGTH]; static char hexhash[SHA256_DIGEST_LENGTH*2+1]; int n; #if OPENSSL_VERSION_NUMBER >= 0x30000000L unsigned int md_len; EVP_MD_CTX *mdctx; mdctx = EVP_MD_CTX_new(); if (EVP_DigestInit_ex(mdctx, sha256_function, NULL) != 1) abort(); #else SHA256_Init(&hash); #endif fd = fopen(fname, "rb"); if (!fd) return NULL; while ((n = fread(buf, 1, sizeof(buf), fd)) > 0) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_DigestUpdate(mdctx, buf, n); #else SHA256_Update(&hash, buf, n); #endif } fclose(fd); #if OPENSSL_VERSION_NUMBER >= 0x30000000L EVP_DigestFinal_ex(mdctx, binaryhash, &md_len); EVP_MD_CTX_free(mdctx); #else SHA256_Final(binaryhash, &hash); #endif binarytohex(binaryhash, sizeof(binaryhash), hexhash); return hexhash; } /** Generates a SHA1 checksum - binary version. * @param dst[out] Buffer to store result in, which needs to be 32 bytes in length * (SHA1_DIGEST_LENGTH). * @param src[in] The input data used to generate the checksum. * @param n[in] Length of data. * @deprecated The SHA1 algorithm is deprecated and insecure, * so only use this if absolutely needed. */ void sha1hash_binary(char *dst, const char *src, unsigned long n) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L unsigned int md_len; EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); if (EVP_DigestInit_ex(mdctx, sha1_function, NULL) != 1) abort(); EVP_DigestUpdate(mdctx, src, n); EVP_DigestFinal_ex(mdctx, dst, &md_len); EVP_MD_CTX_free(mdctx); #else SHA_CTX hash; SHA1_Init(&hash); SHA1_Update(&hash, src, n); SHA1_Final(dst, &hash); #endif } /** Remove a suffix from a filename, eg ".c" (if it is present) */ char *filename_strip_suffix(const char *fname, const char *suffix) { static char buf[512]; strlcpy(buf, fname, sizeof(buf)); if (suffix) { int buf_len = strlen(buf); int suffix_len = strlen(suffix); if (buf_len >= suffix_len) { if (!strncmp(buf+buf_len-suffix_len, suffix, suffix_len)) buf[buf_len-suffix_len] = '\0'; } } else { char *p = strrchr(buf, '.'); if (p) *p = '\0'; } return buf; } /** Add a suffix to a filename, eg ".c" */ char *filename_add_suffix(const char *fname, const char *suffix) { static char buf[512]; snprintf(buf, sizeof(buf), "%s%s", fname, suffix); return buf; } /* Returns 1 if the filename has the suffix, eg ".c" */ int filename_has_suffix(const char *fname, const char *suffix) { char buf[256]; char *p; strlcpy(buf, fname, sizeof(buf)); p = strrchr(buf, '.'); if (!p) return 0; if (!strcmp(p, suffix)) return 1; return 0; } /** Check if the specified file or directory exists */ int file_exists(const char *file) { #ifdef _WIN32 if (_access(file, 0) == 0) #else if (access(file, 0) == 0) #endif return 1; return 0; } /** Get the file creation time */ time_t get_file_time(const char *fname) { struct stat st; if (stat(fname, &st) != 0) return 0; return (time_t)st.st_ctime; } /** Get the size of a file */ long get_file_size(const char *fname) { struct stat st; if (stat(fname, &st) != 0) return -1; return (long)st.st_size; } /** Add a line to a MultiLine list */ void addmultiline(MultiLine **l, const char *line) { MultiLine *m = safe_alloc(sizeof(MultiLine)); safe_strdup(m->line, line); append_ListItem((ListStruct *)m, (ListStruct **)l); } /** Free an entire MultiLine list */ void freemultiline(MultiLine *l) { MultiLine *l_next; for (; l; l = l_next) { l_next = l->next; safe_free(l->line); safe_free(l); } } /** Convert a line regular string containing \n's to a MultiLine linked list */ MultiLine *line2multiline(const char *str) { static char buf[8192]; char *p, *p2; MultiLine *ml = NULL; strlcpy(buf, str, sizeof(buf)); p = buf; do { p2 = strchr(p, '\n'); if (p2) *p2++ = '\0'; addmultiline(&ml, p); p = p2; } while(p2 && *p2); return ml; } /** Convert a sendtype to a command string */ const char *sendtype_to_cmd(SendType sendtype) { if (sendtype == SEND_TYPE_PRIVMSG) return "PRIVMSG"; if (sendtype == SEND_TYPE_NOTICE) return "NOTICE"; if (sendtype == SEND_TYPE_TAGMSG) return "TAGMSG"; return NULL; } /** Check password strength. * @param pass The password to check * @param min_length The minimum length of the password * @param strict Whether to require UPPER+lower+digits * @returns 1 if good, 0 if not. */ int check_password_strength(const char *pass, int min_length, int strict, char **err) { static char buf[256]; char has_lowercase=0, has_uppercase=0, has_digit=0; const char *p; if (err) *err = NULL; if (strlen(pass) < min_length) { if (err) { snprintf(buf, sizeof(buf), "Password must be at least %d characters", min_length); *err = buf; } return 0; } for (p=pass; *p; p++) { if (islower(*p)) has_lowercase = 1; else if (isupper(*p)) has_uppercase = 1; else if (isdigit(*p)) has_digit = 1; } if (strict) { if (!has_lowercase) { if (err) *err = "Password must contain at least 1 lowercase character"; return 0; } else if (!has_uppercase) { if (err) *err = "Password must contain at least 1 UPPERcase character"; return 0; } else if (!has_digit) { if (err) *err = "Password must contain at least 1 digit (number)"; return 0; } } return 1; } int valid_secret_password(const char *pass, char **err) { return check_password_strength(pass, 10, 1, err); } int running_interactively(void) { #ifndef _WIN32 char *s; if (!isatty(0)) return 0; s = getenv("TERM"); if (!s || !strcasecmp(s, "dumb") || !strcasecmp(s, "none")) return 0; return 1; #else return IsService ? 0 : 1; #endif } int terminal_supports_color(void) { #ifndef _WIN32 char *s; /* Yeah we check all of stdin, stdout, stderr, because * or more may be redirected (bin/unrealircd >log 2>&1), * and then we want to say no to color support. */ if (!isatty(0) || !isatty(1) || !isatty(2)) return 0; s = getenv("TERM"); /* Yeah this is a lazy way to detect color-capable terminals * but it is good enough for me. */ if (s) { if (strstr(s, "color") || strstr(s, "ansi")) return 1; } return 0; #else return 0; #endif } /** Skip whitespace (if any) */ void skip_whitespace(char **p) { for (; **p == ' ' || **p == '\t'; *p = *p + 1); } /** Keep reading '*p' until we hit any of the 'stopchars'. * Actually behaves like strstr() but then hit the end * of the string (\0) i guess? */ void read_until(char **p, char *stopchars) { for (; **p && !strchr(stopchars, **p); *p = *p + 1); } void write_pidfile_failed(void) { char *errstr = strerror(errno); unreal_log(ULOG_WARNING, "config", "WRITE_PID_FILE_FAILED", NULL, "Unable to write to pid file '$filename': $system_error", log_data_string("filename", conf_files->pid_file), log_data_string("system_error", errstr)); } /** Write PID file */ void write_pidfile(void) { #ifdef IRCD_PIDFILE int fd; char buff[20]; if ((fd = open(conf_files->pid_file, O_CREAT | O_WRONLY, 0600)) < 0) { write_pidfile_failed(); return; } ircsnprintf(buff, sizeof(buff), "%5d\n", (int)getpid()); if (write(fd, buff, strlen(buff)) < 0) write_pidfile_failed(); if (close(fd) < 0) write_pidfile_failed(); #endif } /* * Determines if the given string is a valid URL. Since libcurl * supports telnet, ldap, and dict such strings are treated as * invalid URLs here since we don't want them supported in * unreal. */ int url_is_valid(const char *string) { if (strstr(string, " ") || strstr(string, "\t")) return 0; if (strstr(string, "telnet://") == string || strstr(string, "ldap://") == string || strstr(string, "dict://") == string) { return 0; } return (strstr(string, "://") != NULL); } /** A displayable URL for in error messages and such. * This leaves out any authentication information (user:pass) * the URL may contain. */ const char *displayurl(const char *url) { static char buf[512]; char *proto, *rest; /* protocol://user:pass@host/etc.. */ rest = strchr(url, '@'); if (!rest) return url; /* contains no auth information */ rest++; /* now points to the rest (remainder) of the URL */ proto = strstr(url, "://"); if (!proto || (proto > rest) || (proto == url)) return url; /* incorrectly formatted, just show entire URL. */ strlncpy(buf, url, sizeof(buf), proto - url); strlcat(buf, "://***:***@", sizeof(buf)); strlcat(buf, rest, sizeof(buf)); return buf; } /* * Returns the filename portion of the URL. The returned string * is malloc()'ed and must be freed by the caller. If the specified * URL does not contain a filename, a '-' is allocated and returned. */ char *url_getfilename(const char *url) { const char *c, *start; if ((c = strstr(url, "://"))) c += 3; else c = url; while (*c && *c != '/') c++; if (*c == '/') { c++; if (!*c || *c == '?') return raw_strdup("-"); start = c; while (*c && *c != '?') c++; if (!*c) return raw_strdup(start); else return raw_strldup(start, c-start+1); } return raw_strdup("-"); } #ifdef _WIN32 // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess // mode value Checks file for // 04 Read-only #define R_OK 04 #endif /* * Checks whether a file can be opened for reading. */ int is_file_readable(const char *file, const char *dir) { char *filename = strdup(file); convert_to_absolute_path(&filename, dir); if (access(filename, R_OK)){ safe_free(filename); return 0; } safe_free(filename); return 1; } void delletterfromstring(char *s, char letter) { if (s == NULL) return; for (; *s; s++) { if (*s == letter) { for (; *s; s++) *s = s[1]; break; } } } int sort_character_lowercase_before_uppercase(char x, char y) { /* Lower before upper */ if (islower(x) && isupper(y)) return 1; if (isupper(x) && islower(y)) return 0; /* Other than that, easy */ return x < y ? 1 : 0; } /* Helper function, mainly used by snomask code */ void addlettertodynamicstringsorted(char **str, char letter) { char *i, *o; char *newbuf; size_t newbuflen; /* NULL string? Easy! */ if (*str == NULL) { *str = safe_alloc(2); **str = letter; return; } /* Exists? Then nothing to do */ if (strchr(*str, letter)) return; /* Ok, we really need to add it */ newbuflen = strlen(*str) + 2; newbuf = safe_alloc(newbuflen); for (i = *str, o = newbuf; *i; i++) { /* Insert before a higher letter */ if (letter && sort_character_lowercase_before_uppercase(letter, *i)) { *o++ = letter; letter = '\0'; } *o++ = *i; } /* Or maybe we should be at the final spot? */ if (letter) *o++ = letter; *o = '\0'; safe_free_raw(*str); *str = newbuf; }