1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 03:03:13 +02:00
Files
unrealircd/src/misc.c
T
Bram Matthys 31d51fbb04 * UnrealIRCd can now be used if your OpenSSL does not provide MD5
(there will be an error if you use `cloak_md5`, but everything
  will work fine if you use `cloak_sha256`).

We phased out MD5 usage years ago, so it is only contained to
the old cloaking module. In fact that was the only reason we
started to provide the SHA256 cloaking module, simply so it
isn't using old MD5.

Of course, for module coders this means they should not call
DoMD5() or md5hash(), but that would be rare. Currently zero
modules in unrealircd contrib do this and it makes no sense
to start using it nowadays anyway.
2025-07-15 19:09:32 +02:00

3000 lines
71 KiB
C

/*
* 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;
}
/*
** 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;
}
/** 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 <seton,setby> 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;
}
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)
{
}
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<len; i++)
{
str[n++] = hexchars[(datastr[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;
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;
/* 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[4096];
return StripControlCodesEx(text, new_str, sizeof(new_str), 0);
}
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;
}
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;
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;
url_start_async(request);
synchronous_http_request_in_progress = 1;
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 ? ;)