/* * 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 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 */ char config_only; } BanActTable; static BanActTable banacttable[] = { { BAN_ACT_KILL, 'K', "kill", 0 }, { BAN_ACT_SOFT_KILL, 'i', "soft-kill", 0 }, { BAN_ACT_TEMPSHUN, 'S', "tempshun", 0 }, { BAN_ACT_SOFT_TEMPSHUN,'T', "soft-tempshun", 0 }, { BAN_ACT_SHUN, 's', "shun", 0 }, { BAN_ACT_SOFT_SHUN, 'H', "soft-shun", 0 }, { BAN_ACT_KLINE, 'k', "kline", 0 }, { BAN_ACT_SOFT_KLINE, 'I', "soft-kline", 0 }, { BAN_ACT_ZLINE, 'z', "zline", 0 }, { BAN_ACT_GLINE, 'g', "gline", 0 }, { BAN_ACT_SOFT_GLINE, 'G', "soft-gline", 0 }, { BAN_ACT_GZLINE, 'Z', "gzline", 0 }, { BAN_ACT_BLOCK, 'b', "block", 0 }, { BAN_ACT_SOFT_BLOCK, 'B', "soft-block", 0 }, { BAN_ACT_DCCBLOCK, 'd', "dccblock", 0 }, { BAN_ACT_SOFT_DCCBLOCK,'D', "soft-dccblock", 0 }, { BAN_ACT_VIRUSCHAN, 'v', "viruschan", 0 }, { BAN_ACT_SOFT_VIRUSCHAN,'V', "soft-viruschan", 0 }, { BAN_ACT_WARN, 'w', "warn", 0 }, { BAN_ACT_SOFT_WARN, 'W', "soft-warn", 0 }, { BAN_ACT_SET, '1', "set", 1 }, { BAN_ACT_REPORT, 'r', "report", 1 }, { BAN_ACT_STOP, '0', "stop", 1 }, { 0, 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" }, { SPAMF_RAW, 'R', "raw", "cmd" }, { 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; } /** Return the server port the client connected to (e.g. 6697) */ int get_server_port(Client *client) { ModData *m; if (MyConnect(client) && client->local->listener) return client->local->listener->port; m = moddata_client_get_raw(client, "server_port"); if (!m) return 0; return m->i; } /** Return the client-side port (e.g. 60123) for the 'client' connection. * Note: don't confuse this with the server-side port (e.g. 6697). */ int get_client_port(Client *client) { ModData *m = moddata_client_get_raw(client, "local_port"); if (!m) return 0; return m->i; } void set_client_port(Client *client, int port) { char buf[32]; snprintf(buf, sizeof(buf), "%d", port); moddata_client_set(client, "local_port", buf); } void set_server_port(Client *client, int port) { char buf[32]; snprintf(buf, sizeof(buf), "%d", port); moddata_client_set(client, "server_port", 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.%d]", client->name, IsIdentSuccess(client) ? client->ident : "", client->ip ? client->ip : "???", get_client_port(client)); } 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; } /** 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; } /** Check if the specified ident / user name does not contain forbidden characters. * @param username The username / ident to check * @returns 1 if valid, 0 if not. */ int valid_username(const char *username) { const char *s; if (strlen(username) > USERLEN) return 0; /* Too long */ for (s = username; *s; s++) { if ((*s == '~') && (s == username)) continue; if (!isallowed(*s)) return 0; } return 1; } /** Check validity of a vhost which can be both in 'host' or 'user@host' format. * This will call valid_username() and valid_host(xxx, 0) accordingly. * @param userhost the "host" or "user@host" * @returns 1 if valid, 0 if not. */ int valid_vhost(const char *userhost) { char uhost[512], *p; const char *host = userhost; strlcpy(uhost, userhost, sizeof(uhost)); if ((p = strchr(uhost, '@'))) { *p++ = '\0'; if (!valid_username(uhost)) return 0; host = p; } if (!valid_host(host, 0)) return 0; return 1; } /** Weird variant of valid_vhost() that allows $varnames through. * So should only be used for checking things like vhost::vhost, * oper::vhost and such in the CONFIG TEST routines. * At runtime you should use valid_vhost() to check the final host * again after variable expansion (by unreal_expand_string usually). */ int potentially_valid_vhost(const char *userhost) { char uhost[512], *p; strlcpy(uhost, userhost, sizeof(uhost)); for (p = uhost; *p; p++) if (*p == '$') *p = '_'; return valid_vhost(uhost); } /*|| BAN ACTION ROUTINES FOLLOW ||*/ int parse_ban_action_set(const char *str, char **var, VarActionValue *op, int *value, char **error) { /* SCORE=1 * SCORE++ * SCORE+=5 * SCORE-- * SCORE-=5 */ static char buf[512]; char *p; *error = NULL; *var = NULL; *op = 0; *value = 0; strlcpy(buf, str, sizeof(buf)); for (p = buf; *p; p++) if (!isupper(*p) && !isdigit(*p) && !strchr("_", *p)) break; if (!*p) { *error = "Missing value to set"; return 0; } *var = buf; if (!strncmp(p, "++", 2)) { *op = VAR_ACT_INCREASE; *p = '\0'; p+=2; *value = 1; } else if (!strncmp(p, "--", 2)) { *op = VAR_ACT_DECREASE; *p = '\0'; p+=2; *value = 1; } else if (!strncmp(p, "+=", 2)) { *op = VAR_ACT_INCREASE; *p = '\0'; p+=2; } else if (!strncmp(p, "-=", 2)) { *op = VAR_ACT_DECREASE; *p = '\0'; p+=2; } else if (!strncmp(p, "=", 1)) { *op = VAR_ACT_SET; *p = '\0'; p+=1; } else { *error = "Unknown set action, should be one of: ++, --, +=, -= or ="; return 0; } if (*value != 0) { if (*p) { *error = "Set action has trailing data after a ++ or --. This is not allowed."; return 0; } return 1; } if (!*p) { *error = "Missing value to set or increase"; return 0; } *value = atoi(p); return 1; } int test_ban_action_config_helper(ConfigEntry *ce, const char *name, const char *value) { int errors = 0; BanActionValue action; action = banact_stringtoval(name); if (!action) { config_error("%s:%d: unknown action: %s", ce->file->filename, ce->line_number, name); errors++; } else if (action == BAN_ACT_SET) { if (!value) { config_error("%s:%d: action set is missing a value", ce->file->filename, ce->line_number); errors++; } else { char *var; VarActionValue op; int varvalue; char *error; if (!parse_ban_action_set(value, &var, &op, &varvalue, &error)) { config_error("%s:%d: action: %s", ce->file->filename, ce->line_number, error); errors++; } } } else if (action == BAN_ACT_REPORT) { } return errors; } /** Test an action item in the config. * @param ce The config entry to parse * @returns Number of errors, so 0 means OK. */ int test_ban_action_config(ConfigEntry *ce) { ConfigEntry *cep; int errors = 0; if (ce->items && !ce->value) { /* action { xxx; } */ for (cep = ce->items; cep; cep = cep->next) errors += test_ban_action_config_helper(cep, cep->name, cep->value); } else if (!ce->value) { config_error("%s:%d: action has no value", ce->file->filename, ce->line_number); errors++; } else { /* action xxx; */ errors += test_ban_action_config_helper(ce, ce->value, NULL); } return errors; } BanAction *parse_ban_action_config_helper(const char *name, const char *value) { int errors = 0; BanAction *action = safe_alloc(sizeof(BanAction)); action->action = banact_stringtoval(name); if (!action->action) abort(); /* impossible, is config tested earlier */ if (action->action == BAN_ACT_SET) { char *var; VarActionValue op; int varvalue; char *error; if (!parse_ban_action_set(value, &var, &op, &varvalue, &error)) abort(); safe_strdup(action->var, var); action->value = varvalue; action->var_action = op; } else if (action->action == BAN_ACT_REPORT) { safe_strdup(action->var, value); // can be NULL, means all } return action; } /** Parse an action item in the config. * @param ce The config entry to parse * @param store_actions Where to store the linked list in, any previously stored actions are freed. * @notes Be sure to initialize *store_actions before calling (eg to NULL). * The reason this function does not return a BanAction but uses a parameter is because * it is common to have a default config setting and then parsing of config items later, * which would have made it too easy to create memory leaks. */ void parse_ban_action_config(ConfigEntry *ce, BanAction **store_actions) { ConfigEntry *cep; BanAction *action; if (*store_actions) { free_all_ban_actions(*store_actions); *store_actions = NULL; } if (ce->items && !ce->value) { /* action { xxx; } */ for (cep = ce->items; cep; cep = cep->next) { action = parse_ban_action_config_helper(cep->name, cep->value); if (action) append_ListItem((ListStruct *)action, (ListStruct **)store_actions); } } else if (ce->value) { /* action xxx; */ action = parse_ban_action_config_helper(ce->value, NULL); if (action) append_ListItem((ListStruct *)action, (ListStruct **)store_actions); } } /** Return 1 if this is "config only" ban action, like BAN_ACT_SET is */ int banact_config_only(BanActionValue action) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (b->value == action) return b->config_only; return 0; } /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */ BanActionValue 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) */ BanActionValue 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(BanActionValue 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(BanActionValue val) { BanActTable *b; for (b = &banacttable[0]; b->value; b++) if (b->value == val) return b->name; return "UNKNOWN"; } BanAction *banact_value_to_struct(BanActionValue val) { BanAction *action = safe_alloc(sizeof(BanAction)); action->action = val; return action; } /** Check if action is only of type 'what', eg if they are all BAN_ACT_WARN */ int only_actions_of_type(BanAction *actions, BanActionValue what) { for (; actions; actions = actions->next) if (actions->action != what) return 0; return 1; } /** Check if action 'what' is in any of 'actions' */ int has_actions_of_type(BanAction *actions, BanActionValue what) { for (; actions; actions = actions->next) if (actions->action == what) return 1; return 0; } /** Check if only soft ban actions */ int only_soft_actions(BanAction *actions) { for (; actions; actions = actions->next) if (!IsSoftBanAction(actions->action)) return 0; return 1; } /** Turn a linked list BanAction * into a string of actions. * Returns something like "gline" or "set, gline" */ const char *ban_actions_to_string(BanAction *actions) { static char buf[512]; const char *s; BanAction *action; *buf = '\0'; for (action = actions; action; action = action->next) { s = banact_valtostring(action->action); if (!s) s = "???"; strlcat(buf, s, sizeof(buf)); strlcat(buf, ",", sizeof(buf)); } /* Cut off trailing "," */ if (*buf) buf[strlen(buf)-1] = '\0'; return buf; } /* Find the highest value in a BanAction linked list (the strongest action, eg gline>block) */ int highest_ban_action(BanAction *action) { int highest = 0; for (; action; action = action->next) if (action->action > highest) highest = action->action; return highest; } /** Lower any 'actions' to a maximum of 'limit_action'. * See the BanActionValue table for what is considered higher/lower. */ void lower_ban_action_to_maximum(BanAction *actions, BanActionValue limit_action) { for (; actions; actions = actions->next) if (actions->action > limit_action) actions->action = limit_action; } void free_single_ban_action(BanAction *action) { safe_free(action->var); safe_free(action); } void free_all_ban_actions(BanAction *actions) { BanAction *next; for (; actions; actions = next) { next = actions->next; free_single_ban_action(actions); } } /** Duplicate an entire BanAction linked list (deep copy) */ BanAction *duplicate_ban_actions(BanAction *actions) { BanAction *newlist = NULL; BanAction *action, *n; for (action = actions; action; action = action->next) { n = safe_alloc(sizeof(BanAction)); // This matches the struct.h order: n->action = action->action; safe_strdup(n->var, action->var); n->value = action->value; n->var_action = action->var_action; /* And add to the list, maintain action ordering */ AppendListItem(n, newlist); } return newlist; } /*|| 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 ? buf : "-"; } /** Replace underscores back to the space character. * This is used for the spamfilter reason. */ char *unreal_decodespace(const char *s) { const char *i; static char buf[512], *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(const char *s) { const char *i; static char buf[512], *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 "???"; } /** Convert the value for set::spamfilter::show-message-content-on-hit * and spamfilter::show-message-content-on-hit. */ SpamfilterShowMessageContentOnHit spamfilter_show_message_content_on_hit_strtoval(const char *s) { if (!strcmp(s, "always")) return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_ALWAYS; if (!strcmp(s, "channel-only")) return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_CHANNEL_ONLY; if (!strcmp(s, "never")) return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_NEVER; return 0; } /** Add name entries from config */ void unreal_add_names(NameList **n, ConfigEntry *ce) { if (ce->items) { ConfigEntry *cep; for (cep = ce->items; cep; cep = cep->next) _add_name_list(n, cep->value ? cep->value : cep->name); } else if (ce->value) { _add_name_list(n, ce->value); } } /** Add name/value entries from config */ void unreal_add_name_values(NameValuePrioList **n, const char *name, ConfigEntry *ce) { if (ce->items) { ConfigEntry *cep; for (cep = ce->items; cep; cep = cep->next) add_nvplist(n, 0, name, cep->value ? cep->value : cep->name); } else if (ce->value) { add_nvplist(n, 0, name, ce->value); } } /** Prints the name:value pair of a NameValuePrioList */ const char *namevalue(NameValuePrioList *n) { static char buf[512]; if (!n->name) return ""; if (!n->value) return n->name; snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value); return buf; } /** Version of namevalue() but replaces spaces with underscores. * Used in for example numeric sending routines where a field * may not contain any spaces. */ const char *namevalue_nospaces(NameValuePrioList *n) { static char buf[512]; char *p; if (!n->name) return ""; if (!n->value) strlcpy(buf, n->name, sizeof(buf)); snprintf(buf, sizeof(buf), "%s:%s", n->name, n->value); /* Replace spaces with underscores */ for (p=buf; *p; p++) if (*p == ' ') *p = '_'; return buf; } /** 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; } const char *compressed_ip(const char *ip) { char scratch[64]; static char ret[64]; if (ip && strchr(ip, ':') && (inet_pton(AF_INET6, ip, scratch) == 1)) if (inet_ntop(AF_INET6, scratch, ret, sizeof(ret))) return ret; return ip; } /** 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; } /** Duplicate a message tag list. */ MessageTag *duplicate_mtags(MessageTag *mtags) { MessageTag *new_list = NULL; MessageTag *m; for (m = mtags; m; m = m->next) { MessageTag *dup = duplicate_mtag(m); AppendListItem(dup, new_list); } return new_list; } /** Duplicate a message tag list, excluding tags with MTAG_HANDLER_FLAGS_FIRST_ONLY. * Used to build the tag set for subsequent lines (lines 2..N), * where tags like msgid and +draft/reply should not be repeated. */ MessageTag *duplicate_mtags_for_subsequent_lines(MessageTag *mtags) { MessageTag *new_list = NULL; MessageTag *m; for (m = mtags; m; m = m->next) { MessageTagHandler *handler = MessageTagHandlerFind(m->name); if (handler && (handler->flags & MTAG_HANDLER_FLAGS_FIRST_ONLY)) continue; MessageTag *dup = duplicate_mtag(m); AppendListItem(dup, new_list); } return new_list; } /** 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; } int spamreport_default_handler(Client *client, const char *ip, NameValuePrioList *details, const char *spamreport_block, Client *by) { return -1; } /** 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) { } /** Default handler for get_connections_from_ip(). * Returns 0 if the maxperip module is not loaded. */ int get_connections_from_ip_default_handler(Client *client) { return 0; } int get_floodprot_channel_max_lines_default_handler(Channel *channel) { return INT_MAX; } int floodprot_check_multiline_batch_default_handler(Channel *channel, Client *client, int line_count) { return 0; } 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, const char *autojoin_channels) { return 0; } void webserver_send_response_default_handler(Client *client, int status, char *msg) { } void webserver_close_client_default_handler(Client *client) { } int webserver_handle_body_default_handler(Client *client, WebRequest *web, const char *readbuf, int length) { return 0; } void rpc_response_default_handler(Client *client, json_t *request, json_t *result) { } void rpc_error_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *error_message) { } void rpc_error_fmt_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...) { } void rpc_send_request_to_remote_default_handler(Client *source, Client *target, json_t *request) { } void rpc_send_response_to_remote_default_handler(Client *source, Client *target, json_t *response) { } int rrpc_supported_simple_default_handler(Client *target, char **problem_server) { if (problem_server) *problem_server = me.name; return 0; } int rrpc_supported_default_handler(Client *target, const char *module, const char *minimum_version, char **problem_server) { if (problem_server) *problem_server = me.name; return 0; } int websocket_handle_websocket_default_handler(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len)) { return -1; } int websocket_create_packet_default_handler(int opcode, char **buf, int *len) { return -1; } int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize) { return -1; } int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len) { return -1; } void mtag_add_issued_by_default_handler(MessageTag **mtags, Client *client, MessageTag *recv_mtags) { } void cancel_ident_lookup_default_handler(Client *client) { } void ban_act_set_reputation_default_handler(Client *client, BanAction *action) { } const char *get_central_api_key_default_handler(void) { return NULL; } int central_spamreport_default_handler(Client *target, Client *by, const char *url) { return 0; } int central_spamreport_enabled_default_handler(void) { return 0; } void sasl_succeeded_default_handler(Client *client) { } void sasl_failed_default_handler(Client *client) { } int decode_authenticate_plain_default_handler(const char *param, char **authorization_id, char **authentication_id, char **passwd) { return 0; } char *utf8_convert_confusables_default_handler(const char *i, char *obuf, int olen) { strlcpy(obuf, i, olen); return obuf; } const char *utf8_get_block_name_default_handler(int i) { return NULL; } int utf8_get_block_number_default_handler(const char *name) { return -1; } /** 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; if (!md5_function) { unreal_log(ULOG_FATAL, "tls", "MD5_UNAVAILABLE_FATAL", NULL, "DoMD5() was called but the MD5 algorithm is not available " "in your OpenSSL/LibreSSL version. -- ABORTING"); abort(); } 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; static EVP_MD_CTX *mdctx = NULL; if (!mdctx) 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); #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; } /** Read an entire file into memory. * @param fname File path to read * @param filesize Optional pointer to store the file size (may be NULL) * @returns A safe_alloc()'d buffer with the file contents, * null-terminated but also binary safe. * Returns NULL on failure (file not found, empty, etc.) * Caller must free with safe_free(). */ char *file_get_contents(const char *fname, long *filesize) { FILE *fd; struct stat st; long size; char *buf; if (filesize) *filesize = 0; fd = fopen(fname, "rb"); if (!fd) return NULL; if (fstat(fileno(fd), &st) != 0 || st.st_size <= 0) { fclose(fd); return NULL; } size = (long)st.st_size; buf = safe_alloc(size + 1); if (fread(buf, 1, size, fd) != size) { safe_free(buf); fclose(fd); return NULL; } fclose(fd); if (filesize) *filesize = size; return buf; } /** 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; /* Support NO_COLOR as per https://no-color.org */ s = getenv("NO_COLOR"); if (s != NULL && s[0] != '\0') return 0; /* Yeah we check all of stdin, stdout, stderr, because one * 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; } void s_die() { #ifdef _WIN32 Client *client; if (!IsService) { loop.terminating = 1; unload_all_modules(); list_for_each_entry(client, &lclient_list, lclient_node) (void) send_queued(client); exit(-1); } else { SERVICE_STATUS status; SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP); ControlService(hService, SERVICE_CONTROL_STOP, &status); } #else loop.terminating = 1; unload_all_modules(); unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE); exit(0); #endif } #ifndef _WIN32 void s_rehash() { struct sigaction act; dorehash = 1; act.sa_handler = s_rehash; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGHUP); (void)sigaction(SIGHUP, &act, NULL); } void s_reloadcert() { struct sigaction act; doreloadcert = 1; act.sa_handler = s_reloadcert; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGUSR1); (void)sigaction(SIGUSR1, &act, NULL); } #endif // #ifndef _WIN32 void restart(const char *mesg) { server_reboot(mesg); } void s_restart() { dorestart = 1; } #ifndef _WIN32 /** Signal handler for signals which we ignore, * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc. */ void ignore_this_signal() { struct sigaction act; act.sa_handler = ignore_this_signal; act.sa_flags = 0; (void)sigemptyset(&act.sa_mask); (void)sigaddset(&act.sa_mask, SIGALRM); (void)sigaddset(&act.sa_mask, SIGPIPE); (void)sigaction(SIGALRM, &act, (struct sigaction *)NULL); (void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL); #ifdef SIGWINCH (void)sigaddset(&act.sa_mask, SIGWINCH); (void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL); #endif } #endif /* #ifndef _WIN32 */ void server_reboot(const char *mesg) { int i; Client *client; unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL, "Restarting server: $reason", log_data_string("reason", mesg)); list_for_each_entry(client, &lclient_list, lclient_node) (void) send_queued(client); /* * ** fd 0 must be 'preserved' if either the -d or -i options have * ** been passed to us before restarting. */ #ifdef HAVE_SYSLOG (void)closelog(); #endif #ifndef _WIN32 for (i = 3; i < MAXCONNECTIONS; i++) (void)close(i); if (!(bootopt & (BOOT_TTY | BOOT_DEBUG))) (void)close(2); (void)close(1); (void)close(0); close_std_descriptors(); (void)execv(MYNAME, myargv); #else close_connections(); if (!IsService) { CleanUp(); WinExec(cmdLine, SW_SHOWDEFAULT); } #endif loop.terminating = 1; unload_all_modules(); #ifdef _WIN32 if (IsService) { SERVICE_STATUS status; PROCESS_INFORMATION pi; STARTUPINFO si; char fname[MAX_PATH]; memset(&status, 0, sizeof(status)); memset(&si, 0, sizeof(si)); IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(IRCDStatusHandle, &IRCDStatus); GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH); CreateProcess(fname, "restartsvc", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); IRCDStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(IRCDStatusHandle, &IRCDStatus); ExitProcess(0); } else #endif exit(-1); } /** Check if at least 'minimum' seconds passed by since last run. * @param tv_old Pointer to a timeval struct to keep track of things. * @param minimum The time specified in milliseconds (eg: 1000 for 1 second) * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned. */ int minimum_msec_since_last_run(struct timeval *tv_old, long minimum) { long v; if (tv_old->tv_sec == 0) { /* First call ever */ tv_old->tv_sec = timeofday_tv.tv_sec; tv_old->tv_usec = timeofday_tv.tv_usec; return 0; } v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000); if (v >= minimum) { tv_old->tv_sec = timeofday_tv.tv_sec; tv_old->tv_usec = timeofday_tv.tv_usec; return 1; } return 0; } /** Strip color, bold, underline, and reverse codes from a string. * @param text The input text * @param output The buffer for the output text * @param outputlen The length of the output buffer * @param strip_flags Zero or (a combination of) UNRL_STRIP_LOW_ASCII / UNRL_STRIP_KEEP_LF. * @returns The new string, which will be 'output', or in unusual cases (outputlen==0) will be NULL. */ const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags) { int i = 0, len = strlen(text), save_len=0; char nc = 0, col = 0, rgb = 0; char *o = output; const char *save_text=NULL; /* Handle special cases first.. */ if (outputlen == 0) return NULL; if (outputlen == 1) { *output = '\0'; return output; } /* Reserve room for the NUL byte */ outputlen--; while (len > 0) { if ((col && isdigit(*text) && nc < 2) || ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3))) { nc++; if (*text == ',') { nc = 0; col++; } } /* Syntax for RGB is ^DHHHHHH where H is a hex digit. * If < 6 hex digits are specified, the code is displayed * as text */ else if ((rgb && isxdigit(*text) && nc < 6) || (rgb && *text == ',' && nc < 7)) { nc++; if (*text == ',') nc = 0; } else { if (col) col = 0; if (rgb) { if (nc != 6) { text = save_text+1; len = save_len-1; rgb = 0; continue; } rgb = 0; } switch (*text) { case 3: /* color */ col = 1; nc = 0; break; case 4: /* RGB */ save_text = text; save_len = len; rgb = 1; nc = 0; break; case 2: /* bold */ break; case 31: /* underline */ break; case 22: /* reverse */ break; case 15: /* plain */ break; case 29: /* italic */ break; case 30: /* strikethrough */ break; case 17: /* monospace */ break; case 0xe2: if (!strncmp(text+1, "\x80\x8b", 2)) { /* +2 means we skip 3 */ text += 2; len -= 2; break; } /*fallthrough*/ default: if ((*text >= ' ') || !(strip_flags & UNRL_STRIP_LOW_ASCII) || ((strip_flags & UNRL_STRIP_KEEP_LF) && (*text == '\n')) ) { *o++ = *text; outputlen--; if (outputlen == 0) { *o = '\0'; return output; } } break; } } text++; len--; } *o = '\0'; return output; } /* strip color, bold, underline, and reverse codes from a string */ const char *StripControlCodes(const char *text) { static unsigned char new_str[8192]; return StripControlCodesEx(text, new_str, sizeof(new_str), 0); } /** Check if string is valid UTF8 and contains no low ASCII (0-31) */ int valid_text(const char *str) { const char *p; if (!unrl_utf8_validate(str, NULL)) return 0; for (p = str; *p; p++) if (*p < 32) return 0; return 1; } /** Check if string is valid UTF8 and contains no space (ASCII 32) or low ASCII (<32) */ int valid_text_nospaces(const char *str) { const char *p; if (!unrl_utf8_validate(str, NULL)) return 0; for (p = str; *p; p++) if (*p < 33) return 0; return 1; } const char *command_issued_by_rpc(MessageTag *mtags) { MessageTag *m = find_mtag(mtags, "unrealircd.org/issued-by"); if (m && m->value && !strncmp(m->value, "RPC:", 4)) return m->value; return NULL; } /** Is 's' a valid spamfilter id? A-Z, 0-9 and _ and with a max length. */ int valid_spamfilter_id(const char *s) { if (strlen(s) > MAXSPAMFILTERIDLEN) return 0; return 1; } /** Check if a batch reference tag contains only valid characters. * Per the batch spec: only ASCII letters, numbers, and hyphens. */ int valid_batch_reference_tag(const char *ref) { const char *p; if (BadPtr(ref) || (strlen(ref) > MAXBATCHREFLEN)) return 0; for (p = ref; *p; p++) { if (!isalnum(*p) && *p != '-') return 0; } return 1; } void download_complete_dontcare(OutgoingWebRequest *request, OutgoingWebResponse *response) { #ifdef DEBUGMODE if (response->memory) { unreal_log(ULOG_DEBUG, "url", "DEBUG_URL_RESPONSE", NULL, "Response for '$url': $response", log_data_string("url", request->url), log_data_string("response", response->memory)); } #endif } int valid_operclass_character(char c) { /* allow alpha, numeric, -, _ */ if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_", c)) return 0; return 1; } int valid_operclass_name(const char *str) { const char *p; if (strlen(str) > OPERCLASSLEN) return 0; for (p = str; *p; p++) if (!valid_operclass_character(*p)) return 0; return 1; } /** Free an OutgoingWebRequest struct - note: use safe_free_outgoingwebrequest() instead (which calls us). */ void free_outgoingwebrequest(OutgoingWebRequest *r) { safe_free(r->apicallback); safe_free(r->url); safe_free(r->actual_url); safe_free(r->body); safe_free_nvplist(r->headers); safe_free(r); } /** Safely duplicate an OutgoingWebRequest struct (eg for https redirects) */ OutgoingWebRequest *duplicate_outgoingwebrequest(OutgoingWebRequest *orig) { OutgoingWebRequest *e = safe_alloc(sizeof(OutgoingWebRequest)); e->callback = orig->callback; safe_strdup(e->apicallback, orig->apicallback); e->callback_data = orig->callback_data; safe_strdup(e->url, orig->url); safe_strdup(e->actual_url, orig->actual_url); e->http_method = orig->http_method; safe_strdup(e->body, orig->body); e->headers = duplicate_nvplist(orig->headers); e->store_in_file = orig->store_in_file; e->cachetime = orig->cachetime; e->max_redirects = orig->max_redirects; e->keep_file = orig->keep_file; e->connect_timeout = orig->connect_timeout; e->transfer_timeout = orig->transfer_timeout; e->minimum_tls_version = orig->minimum_tls_version; return e; } /* * Handles asynchronous downloading of a file (simple non-flexible version). * NOTE: url_start_async() is the more advanced one. * * This function allows a download to be made transparently without * the caller having any knowledge of how libcurl works. The specified * callback function is called when the download completes, or the * download fails. The callback function is defined as: * * void callback(const char *url, const char *filename, const char *memory_data, int memory_data_len, char *errorbuf, int cached, void *data); * - url will contain the original URL used to download the file. * - filename will contain the name of the file (if successful, NULL on error or if cached). * This file will be cleaned up after the callback returns, so save a copy to support caching. * - errorbuf will contain the error message (if failed, NULL otherwise). * - cached 1 if the specified cachetime is >= the current file on the server, * if so, errorbuf will be NULL, filename will contain the path to the file. * - data will be the value of callback_data, allowing you to figure * out how to use the data contained in the downloaded file ;-). * Make sure that if you access the contents of this pointer, you * know that this pointer will persist. A download could take more * than 10 seconds to happen and the config file can be rehashed * multiple times during that time. */ void download_file_async(const char *url, time_t cachetime, void (*callback)(OutgoingWebRequest *request, OutgoingWebResponse *response), void *callback_data, int maxredirects) { OutgoingWebRequest *request = safe_alloc(sizeof(OutgoingWebRequest)); safe_strdup(request->url, url); request->http_method = HTTP_METHOD_GET; request->cachetime = cachetime; request->callback = callback; request->callback_data = callback_data; request->max_redirects = maxredirects; request->store_in_file = 1; url_start_async(request); } void url_callback(OutgoingWebRequest *r, const char *file, const char *memory, int memory_len, const char *errorbuf, int cached, void *ptr) { OutgoingWebResponse *response; if (!r->callback && !r->apicallback) return; /* Nothing to do */ response = safe_alloc(sizeof(OutgoingWebResponse)); response->file = file; response->memory = memory; response->memory_len = memory_len; response->errorbuf = errorbuf; response->cached = cached; response->ptr = ptr; if (r->callback) { r->callback(r, response); } else if (r->apicallback) { APICallback *cb = APICallbackFind(r->apicallback, API_CALLBACK_WEB_RESPONSE); if (cb && !cb->unloaded) cb->callback.web_response(r, response); } safe_free(response); } static int synchronous_http_request_in_progress; static char synchronous_http_request_tmpfile[512]; void synchronous_http_request_handle_response(OutgoingWebRequest *request, OutgoingWebResponse *response) { if (response->errorbuf) { config_error("%s: %s", request->url, response->errorbuf); synchronous_http_request_in_progress = -1; return; } if (response->file) { strlcpy(synchronous_http_request_tmpfile, response->file, sizeof(synchronous_http_request_tmpfile)); synchronous_http_request_in_progress = 0; } else { config_error("%s: Unexpected error, no error but no file", request->url); synchronous_http_request_in_progress = -1; } return; } const char *synchronous_http_request(const char *url, int max_redirects, int connect_timeout, int transfer_timeout) { OutgoingWebRequest *request; if (loop.booted) abort(); /* this function is NOT for modules or the like */ request = safe_alloc(sizeof(OutgoingWebRequest)); safe_strdup(request->url, url); request->http_method = HTTP_METHOD_GET; request->callback = synchronous_http_request_handle_response; request->max_redirects = max_redirects; request->store_in_file = 1; request->keep_file = 1; request->connect_timeout = connect_timeout; request->transfer_timeout = transfer_timeout; synchronous_http_request_in_progress = 1; url_start_async(request); while (synchronous_http_request_in_progress == 1) { gettimeofday(&timeofday_tv, NULL); timeofday = timeofday_tv.tv_sec; url_socket_timeout(NULL); unrealdns_timeout(NULL); fd_select(500); } if (synchronous_http_request_in_progress == 0) return synchronous_http_request_tmpfile; return NULL; /* failure */ } // TODO: move all that url shit to url_generic.c ? ;)