/*
* ChatServices: A collection of IRC services
* function.c: Miscellaneous functions.
*
* Copyright (c) 2005-2007 Atheme Project (http://www.atheme.org)
* Copyright (c) 2017-2018 ChatLounge IRC Network Development Team (http://www.chatlounge.net)
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "atheme.h"
#include "conf.h"
char ch[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
/* This function uses smalloc() to allocate memory.
* You MUST free the result when you are done with it!
*/
char *random_string(int sz)
{
int i;
char *buf = smalloc(sz + 1); /* padding */
for (i = 0; i < sz; i++)
{
buf[i] = ch[arc4random() % 64];
}
buf[sz] = 0;
return buf;
}
/* Shamelessly ripped from https://en.wikipedia.org/wiki/Base36#C_implementation
* - Ben
*/
char *base36_encode(long unsigned int value)
{
static char base36[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/* log(2**64) / log(36) = 12.38 => max 13 char + '\0' */
static char buffer[14];
unsigned int offset = sizeof(buffer);
buffer[--offset] = '\0';
do {
buffer[--offset] = base36[value % 36];
} while (value /= 36);
return strncpy(buffer, &buffer[offset], sizeof(buffer));
}
long unsigned int base36_decode(const char *text)
{
return strtoul(text, NULL, 36);
}
/* Similar to base36_encode but instead utilizes the Natural Area Code which
* is base30, as the vowels have been removed.
* https://en.wikipedia.org/wiki/Natural_Area_Code
* - Ben
*/
char *naturalareacode_encode(long unsigned int value)
{
static char naturalareacode[30] = "0123456789BCDFGHJKLMNPQRSTVWXZ";
static char buffer[15];
unsigned int offset = sizeof(buffer);
buffer[--offset] = '\0';
do {
buffer[--offset] = naturalareacode[value % 30];
} while (value /= 30);
return strncpy(buffer, &buffer[offset], sizeof(buffer));
}
long unsigned int naturalareacode_decode(const char *text)
{
return strtoul(text, NULL, 30);
}
void create_challenge(sourceinfo_t *si, const char *name, int v, char *dest)
{
char buf[256];
int digest[4];
md5_state_t ctx;
snprintf(buf, sizeof buf, "%lu:%s:%s",
(unsigned long)(CURRTIME / 300) - v,
get_source_name(si),
name);
md5_init(&ctx);
md5_append(&ctx, (unsigned char *)buf, strlen(buf));
md5_finish(&ctx, (unsigned char *)digest);
/* note: this depends on byte order, but that's ok because
* it's only going to work in the same atheme instance anyway
*/
snprintf(dest, 80, "%x:%x", digest[0], digest[1]);
}
#ifdef HAVE_GETTIMEOFDAY
/* starts a timer */
void s_time(struct timeval *sttime)
{
gettimeofday(sttime, NULL);
}
#endif
#ifdef HAVE_GETTIMEOFDAY
/* ends a timer */
void e_time(struct timeval sttime, struct timeval *ttime)
{
struct timeval now;
gettimeofday(&now, NULL);
timersub(&now, &sttime, ttime);
}
#endif
#ifdef HAVE_GETTIMEOFDAY
/* translates microseconds into miliseconds */
int tv2ms(struct timeval *tv)
{
return (tv->tv_sec * 1000) + (int) (tv->tv_usec / 1000);
}
#endif
/* replaces tabs with a single ASCII 32 */
void tb2sp(char *line)
{
char *c;
while ((c = strchr(line, '\t')))
*c = ' ';
}
/* replace all occurances of 'old' with 'new' */
char *replace(char *s, int size, const char *old, const char *new)
{
char *ptr = s;
int left = strlen(s);
int avail = size - (left + 1);
int oldlen = strlen(old);
int newlen = strlen(new);
int diff = newlen - oldlen;
while (left >= oldlen)
{
if (strncmp(ptr, old, oldlen))
{
left--;
ptr++;
continue;
}
if (diff > avail)
break;
if (diff != 0)
memmove(ptr + oldlen + diff, ptr + oldlen, left + 1 - oldlen);
memcpy(ptr, new, newlen);
ptr += newlen;
left -= oldlen;
}
return s;
}
/* reverse of atoi() */
const char *number_to_string(int num)
{
static char ret[32];
snprintf(ret, 32, "%d", num);
return ret;
}
/* Used in /NS LIST, /CS LIST, and similar. */
void build_criteriastr(char *buf, int parc, char *parv[])
{
int i;
return_if_fail(buf != NULL);
*buf = 0;
for (i = 0; i < parc; i++)
{
mowgli_strlcat(buf, parv[i], BUFSIZE);
mowgli_strlcat(buf, " ", BUFSIZE);
}
}
/* Used in /NS LIST, /CS LIST, and similar.
* Accepts a time argument as a character array, returns a time_t. */
time_t parse_age(char *s)
{
time_t duration;
duration = (atol(s) * 60);
while (isdigit((unsigned char)*s))
s++;
if (*s == 'y' || *s == 'Y')
duration *= 525949; /* 365.2422 days per year is 525,948.768 minutes. time_t is an integer, so round it. */
else if (*s == 'h' || *s == 'H')
duration *= 60;
else if (*s == 'd' || *s == 'D')
duration *= 1440;
else if (*s == 'w' || *s == 'W')
duration *= 10080;
else if (*s == '\0')
;
else
duration = 0;
return duration;
}
/* return the time elapsed since an event */
char *time_ago(time_t event)
{
static char ret[128];
int years, weeks, days, hours, minutes, seconds;
event = CURRTIME - event;
years = weeks = days = hours = minutes = 0;
while (event >= 60 * 60 * 24 * 365)
{
event -= 60 * 60 * 24 * 365;
years++;
}
while (event >= 60 * 60 * 24 * 7)
{
event -= 60 * 60 * 24 * 7;
weeks++;
}
while (event >= 60 * 60 * 24)
{
event -= 60 * 60 * 24;
days++;
}
while (event >= 60 * 60)
{
event -= 60 * 60;
hours++;
}
while (event >= 60)
{
event -= 60;
minutes++;
}
seconds = event;
if (years)
snprintf(ret, sizeof(ret), "%dy %dw %dd", years, weeks, days);
else if (weeks)
snprintf(ret, sizeof(ret), "%dw %dd %dh", weeks, days, hours);
else if (days)
snprintf(ret, sizeof(ret), "%dd %dh %dm %ds", days, hours, minutes, seconds);
else if (hours)
snprintf(ret, sizeof(ret), "%dh %dm %ds", hours, minutes, seconds);
else if (minutes)
snprintf(ret, sizeof(ret), "%dm %ds", minutes, seconds);
else
snprintf(ret, sizeof(ret), "%ds", seconds);
return ret;
}
char *timediff(time_t seconds)
{
static char buf[BUFSIZE];
long unsigned days, hours, minutes;
days = seconds / 86400;
seconds %= 86400;
hours = seconds / 3600;
hours %= 3600;
minutes = seconds / 60;
minutes %= 60;
seconds %= 60;
snprintf(buf, sizeof(buf), "%lu day%s, %lu:%02lu:%02lu", days, (days == 1) ? "" : "s", hours, minutes, (long unsigned) seconds);
return buf;
}
/* generate a random number, for use as a key */
unsigned long makekey(void)
{
unsigned long k;
k = arc4random() & 0x7FFFFFFF;
/* shorten or pad it to 9 digits */
if (k > 1000000000)
k = k - 1000000000;
if (k < 100000000)
k = k + 100000000;
return k;
}
bool is_internal_client(user_t *u)
{
return (u && (!u->server || u->server == me.me));
}
int validemail(const char *email)
{
int i, valid = 1, chars = 0, atcnt = 0, dotcnt1 = 0;
char c;
const char *lastdot = NULL;
/* sane length */
if (strlen(email) >= EMAILLEN)
return 0;
#if 0
/* RFC2822 */
#define EXTRA_ATEXTCHARS "!#$%&'*+-/=?^_`{|}~"
#else
/* commonly used subset */
#define EXTRA_ATEXTCHARS "%+-=^_"
#endif
/* note that we do not allow domain literals or quoted strings */
for (i = 0; email[i] != '\0'; i++)
{
c = email[i];
if (c == '.')
{
dotcnt1++;
lastdot = &email[i];
/* dot may not be first or last, no consecutive dots */
if (i == 0 || email[i - 1] == '.' ||
email[i - 1] == '@' ||
email[i + 1] == '\0' ||
email[i + 1] == '@')
return 0;
}
else if (c == '@')
atcnt++, dotcnt1 = 0;
else if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
strchr(EXTRA_ATEXTCHARS, c))
chars++;
else
return 0;
}
/* must have exactly one @, and at least one . after the @ */
if (atcnt != 1 || dotcnt1 == 0)
return 0;
/* no mail to IP addresses, this should be done using [10.2.3.4]
* like syntax but we do not allow that either
*/
if (isdigit((unsigned char)lastdot[1]))
return 0;
/* make sure there are at least 4 characters besides the above
* mentioned @ and .
*/
if (chars < 4)
return 0;
return valid;
}
static mowgli_list_t email_canonicalizers;
/* Re-canonicalize email addresses.
* Call this after adding or removing an email_canonicalize hook.
*/
static void canonicalize_emails(void)
{
myentity_iteration_state_t state;
myentity_t *mt;
MYENTITY_FOREACH_T(mt, &state, ENT_USER)
{
myuser_t *mu = user(mt);
strshare_unref(mu->email_canonical);
mu->email_canonical = canonicalize_email(mu->email);
}
}
void
register_email_canonicalizer(email_canonicalizer_t func, void *user_data)
{
email_canonicalizer_item_t *item;
item = smalloc(sizeof(email_canonicalizer_item_t));
item->func = func;
item->user_data = user_data;
mowgli_node_add(item, &item->node, &email_canonicalizers);
canonicalize_emails();
}
void
unregister_email_canonicalizer(email_canonicalizer_t func, void *user_data)
{
mowgli_node_t *n, *tn;
MOWGLI_LIST_FOREACH_SAFE(n, tn, email_canonicalizers.head)
{
email_canonicalizer_item_t *item = n->data;
if (item->func == func && item->user_data == user_data)
{
mowgli_node_delete(&item->node, &email_canonicalizers);
free(item);
canonicalize_emails();
return;
}
}
}
stringref canonicalize_email(const char *email)
{
mowgli_node_t *n, *tn;
char buf[EMAILLEN + 1];
if (email == NULL)
return NULL;
mowgli_strlcpy(buf, email, sizeof buf);
MOWGLI_LIST_FOREACH_SAFE(n, tn, email_canonicalizers.head)
{
email_canonicalizer_item_t *item = n->data;
item->func(buf, item->user_data);
}
return strshare_get(buf);
}
void canonicalize_email_case(char email[EMAILLEN + 1], void *user_data)
{
strcasecanon(email);
}
bool email_within_limits(const char *email)
{
mowgli_node_t *n;
myentity_iteration_state_t state;
myentity_t *mt;
unsigned int tcnt = 0;
stringref email_canonical;
bool result = true;
if (me.maxusers <= 0)
return true;
MOWGLI_ITER_FOREACH(n, nicksvs.emailexempts.head)
{
if (0 == match(n->data, email))
return true;
}
email_canonical = canonicalize_email(email);
MYENTITY_FOREACH_T(mt, &state, ENT_USER)
{
myuser_t *mu = user(mt);
if (mu->email_canonical == email_canonical)
tcnt++;
/* optimization: if tcnt >= me.maxusers, quit iterating. -nenolod */
if (tcnt >= me.maxusers) {
result = false;
break;
}
}
strshare_unref(email_canonical);
return result;
}
bool validhostmask(const char *host)
{
char *p, *q;
if (strchr(host, ' '))
return false;
/* make sure it has ! and @ in that order and only once */
p = strchr(host, '!');
q = strchr(host, '@');
if (p == NULL || q == NULL || p > q || strchr(p + 1, '!') ||
strchr(q + 1, '@'))
return false;
/* XXX this NICKLEN is too long */
if (strlen(host) > NICKLEN + USERLEN + HOSTLEN + 1)
return false;
if (host[0] == ',' || host[0] == '-' || host[0] == '#' ||
host[0] == '@' || host[0] == '!' || host[0] == ':')
return false;
return true;
}
/* char *
* pretty_mask(char *mask);
*
* Input: A mask.
* Output: A "user-friendly" version of the mask, in mask_buf.
* Side-effects: mask_buf is appended to. mask_pos is incremented.
* Notes: The following transitions are made:
* x!y@z => x!y@z
* y@z => *!y@z
* x!y => x!y@*
* x => x!*@*
* z.d => *!*@z.d
*
* If either nick/user/host are > than their respective limits, they are
* chopped
*/
char *pretty_mask(char *mask)
{
static char mask_buf[BUFSIZE];
int old_mask_pos;
char star[] = "*";
char *nick = star, *user = star, *host = star;
int mask_pos = 0;
char *t, *at, *ex;
char ne = 0, ue = 0, he = 0; /* save values at nick[NICKLEN], et all */
/* No point wasting CPU time if the mask is already valid */
if (validhostmask(mask))
return mask;
if ((size_t) (BUFSIZE - mask_pos) < strlen(mask) + 5)
return (NULL);
old_mask_pos = mask_pos;
at = ex = NULL;
if (is_valid_host(mask))
{
if (*mask != '\0')
host = mask;
}
else if ((t = strchr(mask, '@')) != NULL)
{
at = t;
*t++ = '\0';
if(*t != '\0')
host = t;
if((t = strchr(mask, '!')) != NULL)
{
ex = t;
*t++ = '\0';
if(*t != '\0')
user = t;
if(*mask != '\0')
nick = mask;
}
else
{
if(*mask != '\0')
user = mask;
}
}
else if((t = strchr(mask, '!')) != NULL)
{
ex = t;
*t++ = '\0';
if(*mask != '\0')
nick = mask;
if(*t != '\0')
user = t;
}
else if(strchr(mask, '.') != NULL && strchr(mask, ':') != NULL)
{
if(*mask != '\0')
host = mask;
}
else
{
if(*mask != '\0')
nick = mask;
}
/* truncate values to max lengths */
if(strlen(nick) > NICKLEN - 1)
{
ne = nick[NICKLEN - 1];
nick[NICKLEN - 1] = '\0';
}
if(strlen(user) > USERLEN)
{
ue = user[USERLEN];
user[USERLEN] = '\0';
}
if(strlen(host) > HOSTLEN)
{
he = host[HOSTLEN];
host[HOSTLEN] = '\0';
}
snprintf(mask_buf, sizeof mask_buf, "%s!%s@%s", nick, user, host);
/* restore mask, since we may need to use it again later */
if(at)
*at = '@';
if(ex)
*ex = '!';
if(ne)
nick[NICKLEN - 1] = ne;
if(ue)
user[USERLEN] = ue;
if(he)
host[HOSTLEN] = he;
return mask_buf;
}
/* check_not_enough_non_wildcard_chars:
*
* Generalized function to count the number of non wildcard characters
* in either a n!u@h or u@h mask.
*
* Inputs:
* - char *: mask
* - bool usenick; - Whether a nick is in the mask.
* (Used by ChanServ for ACLs. No nick for OperServ AKILL or NickServ ACCESS.)
* Side Effects:
* None
* Returns:
* Whether the number of non-wildcard characters.
*/
bool check_not_enough_non_wildcard_chars(const char* mask, int min_non_wildcard_chars, bool usenick)
{
int count = 0;
const char *p;
if(usenick) /* Nick is used, so exclude '!'. */
{
for (p = mask; *p; p++)
{
if (*p != '*' && *p != '?' && *p != '.' && *p != '!' && *p != '@')
count++;
}
}
else /* No nick is used, so don't look for '!'. */
{
for (p = mask; *p; p++)
{
if (*p != '*' && *p != '?' && *p != '.' && *p != '@')
count++;
}
}
if (min_non_wildcard_chars > count)
return true;
return false;
}
bool validtopic(const char *topic)
{
int i;
/* Most server protocols support less than this (depending on
* the lengths of the identifiers), but this should catch the
* ludicrous stuff.
*/
if (strlen(topic) > 450)
return false;
for (i = 0; topic[i] != '\0'; i++)
{
switch (topic[i])
{
case '\r':
case '\n':
return false;
}
}
if (ircd->flags & IRCD_TOPIC_NOCOLOUR)
{
for (i = 0; topic[i] != '\0'; i++)
{
switch (topic[i])
{
case 2:
case 3:
case 6:
case 7:
case 15:
case 22:
case 23:
case 27:
case 31:
return false;
}
}
}
return true;
}
bool has_ctrl_chars(const char *text)
{
int i;
for (i = 0; text[i] != '\0'; i++)
{
if (text[i] > 0 && text[i] < 32)
return true;
}
return false;
}
#ifndef MOWGLI_OS_WIN
static void sendemail_waited(pid_t pid, int status, void *data)
{
char *email;
email = data;
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
slog(LG_INFO, "sendemail_waited(): email for %s failed", email);
free(email);
}
#endif
/* send the specified type of email.
*
* u is whoever caused this to be called, the corresponding service
* in case of xmlrpc
* type is EMAIL_*, see include/tools.h
* mu is the recipient user
* param depends on type, also see include/tools.h
*/
int sendemail(user_t *u, myuser_t *mu, const char *type, const char *email, const char *param)
{
#ifndef MOWGLI_OS_WIN
char *date = NULL;
char timebuf[BUFSIZE], to[BUFSIZE], from[BUFSIZE], buf[BUFSIZE], pathbuf[BUFSIZE], sourceinfo[BUFSIZE];
FILE *in, *out;
time_t t;
struct tm tm;
int pipfds[2];
pid_t pid;
int rc;
static time_t period_start = 0, lastwallops = 0;
static unsigned int emailcount = 0;
service_t *svs;
if (u == NULL || mu == NULL)
return 0;
if (me.mta == NULL)
{
if (strcmp(type, EMAIL_MEMO) && !is_internal_client(u))
{
svs = service_find("operserv");
notice(svs ? svs->nick : me.name, u->nick, "Sending email is administratively disabled.");
}
return 0;
}
if (!validemail(email))
{
if (strcmp(type, EMAIL_MEMO) && !is_internal_client(u))
{
svs = service_find("operserv");
notice(svs ? svs->nick : me.name, u->nick, "The email address is considered invalid.");
}
return 0;
}
if ((unsigned int)(CURRTIME - period_start) > me.emailtime)
{
emailcount = 0;
period_start = CURRTIME;
}
emailcount++;
if (emailcount > me.emaillimit)
{
if (CURRTIME - lastwallops > 60)
{
wallops(_("Rejecting email for %s[%s@%s] due to too high load (type '%s' to %s <%s>)"),
u->nick, u->user, u->vhost,
type, entity(mu)->name, email);
slog(LG_ERROR, "sendemail(): rejecting email for %s[%s@%s] (%s) due to too high load (type '%s' to %s <%s>)",
u->nick, u->user, u->vhost,
u->ip ? u->ip : u->host,
type, entity(mu)->name, email);
lastwallops = CURRTIME;
}
return 0;
}
snprintf(pathbuf, sizeof pathbuf, "%s/%s", SHAREDIR "/email", type);
if ((in = fopen(pathbuf, "r")) == NULL)
{
slog(LG_ERROR, "sendemail(): rejecting email for %s[%s@%s] (%s), due to unknown type '%s'",
u->nick, u->user, u->vhost, email, type);
return 0;
}
slog(LG_INFO, "sendemail(): email for %s[%s@%s] (%s) type %s to %s <%s>",
u->nick, u->user, u->vhost, u->ip ? u->ip : u->host,
type, entity(mu)->name, email);
/* set up the email headers */
time(&t);
tm = *localtime(&t);
strftime(timebuf, sizeof timebuf, "%a, %d %b %Y %H:%M:%S %z", &tm);
date = timebuf;
snprintf(from, sizeof from, "\"%s Network Services\" <%s>",
me.netname, me.register_email);
snprintf(to, sizeof to, "\"%s\" <%s>", entity(mu)->name, email);
/* \ is special here; escape it */
replace(to, sizeof to, "\\", "\\\\");
snprintf(sourceinfo, sizeof sourceinfo, "%s[%s@%s]", u->nick, u->user, u->vhost);
/* now set up the email */
if (pipe(pipfds) < 0)
{
fclose(in);
return 0;
}
switch (pid = fork())
{
case -1:
fclose(in);
return 0;
case 0:
connection_close_all_fds();
close(pipfds[1]);
dup2(pipfds[0], 0);
execl(me.mta, me.mta, "-t", "-f", me.register_email, NULL);
_exit(255);
}
close(pipfds[0]);
childproc_add(pid, "email", sendemail_waited, sstrdup(email));
out = fdopen(pipfds[1], "w");
while (fgets(buf, BUFSIZE, in))
{
strip(buf);
replace(buf, sizeof buf, "&from&", from);
replace(buf, sizeof buf, "&to&", to);
replace(buf, sizeof buf, "&replyto&", me.adminemail);
replace(buf, sizeof buf, "&date&", date);
replace(buf, sizeof buf, "&accountname&", entity(mu)->name);
replace(buf, sizeof buf, "&entityname&", u->myuser ? entity(u->myuser)->name : u->nick);
replace(buf, sizeof buf, "&netname&", me.netname);
replace(buf, sizeof buf, "¶m&", param);
replace(buf, sizeof buf, "&sourceinfo&", sourceinfo);
if ((svs = service_find("nickserv")) != NULL)
replace(buf, sizeof buf, "&nicksvs&", svs->me->nick);
if ((svs = service_find("chanserv")) != NULL)
replace(buf, sizeof buf, "&chansvs&", svs->me->nick);
if ((svs = service_find("memoserv")) != NULL)
replace(buf, sizeof buf, "&memosvs&", svs->me->nick);
if ((svs = service_find("operserv")) != NULL)
replace(buf, sizeof buf, "&opersvs&", svs->me->nick);
fprintf(out, "%s\n", buf);
}
fclose(in);
rc = 1;
if (ferror(out))
rc = 0;
if (fclose(out) < 0)
rc = 0;
if (rc == 0)
slog(LG_ERROR, "sendemail(): mta failure");
return rc;
#else
# warning implement me :(
return 0;
#endif
}
/* various access level checkers */
bool is_founder(mychan_t *mychan, myentity_t *mt)
{
if (mt == NULL)
return false;
if (chanacs_entity_has_flag(mychan, mt, CA_FOUNDER))
return true;
return false;
}
bool is_ircop(user_t *user)
{
if (UF_IRCOP & user->flags)
return true;
return false;
}
bool is_admin(user_t *user)
{
if (UF_ADMIN & user->flags)
return true;
return false;
}
bool is_autokline_exempt(user_t *user)
{
mowgli_node_t *n;
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "%s@%s", user->user, user->host);
MOWGLI_ITER_FOREACH(n, config_options.exempts.head)
{
if (0 == match(n->data, buf))
return true;
}
return false;
}
bool is_service(user_t *user)
{
if (UF_SERVICE & user->flags)
return true;
return false;
}
char *sbytes(float x)
{
if (x > 1073741824.0)
return "GB";
else if (x > 1048576.0)
return "MB";
else if (x > 1024.0)
return "KB";
return "B";
}
float bytes(float x)
{
if (x > 1073741824.0)
return (x / 1073741824.0);
if (x > 1048576.0)
return (x / 1048576.0);
if (x > 1024.0)
return (x / 1024.0);
return x;
}
int srename(const char *old_fn, const char *new_fn)
{
#ifdef MOWGLI_OS_WIN
unlink(new_fn);
#endif
return rename(old_fn, new_fn);
}
char *combine_path(const char *parent, const char *child)
{
char buf[BUFSIZE];
return_val_if_fail(parent != NULL, NULL);
return_val_if_fail(child != NULL, NULL);
mowgli_strlcpy(buf, parent, sizeof buf);
mowgli_strlcat(buf, "/", sizeof buf);
mowgli_strlcat(buf, child, sizeof buf);
return sstrdup(buf);
}
/* get_default_uflags:
*
* Returns a string of the current default uflags
* a user receives upon registration. Used in /ns register
* and /os info.
*
* - Ben
*/
char *get_default_uflags()
{
char buf[BUFSIZE];
mowgli_strlcpy(buf, "", sizeof buf);
int i = 0;
for(struct Token *it = uflags; it->value != 0; it++)
{
if(config_options.defuflags & it->value)
{
if(i > 0)
mowgli_strlcat(buf, ", ", sizeof buf);
mowgli_strlcat(buf, it->text, sizeof buf);
i++;
}
}
return sstrdup(buf);
}
/* get_default_uflags:
*
* Returns a string of the current default uflags
* a user receives upon registration. Used in /ns register
* and /os info.
*
* - Ben
*/
char *get_default_cflags()
{
char buf[BUFSIZE];
mowgli_strlcpy(buf, "", sizeof buf);
int i = 0;
for(struct Token *it = cflags; it->value != 0; it++)
{
if(config_options.defcflags & it->value)
{
if(i > 0)
mowgli_strlcat(buf, ", ", sizeof buf);
mowgli_strlcat(buf, it->text, sizeof buf);
i++;
}
}
return sstrdup(buf);
}
/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
* vim:ts=8
* vim:sw=8
* vim:noexpandtab
*/