1 /*
2  * atheme-services: A collection of minimalist IRC services
3  * function.c: Miscillaneous functions.
4  *
5  * Copyright (c) 2005-2007 Atheme Project (http://www.atheme.org)
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21  * POSSIBILITY OF SUCH DAMAGE.
22  */
23 
24 #include "atheme.h"
25 
26 char ch[] = "abcdefghijklmnopqrstuvwxyz";
27 
28 /* This function uses smalloc() to allocate memory.
29  * You MUST free the result when you are done with it!
30  */
random_string(int sz)31 char *random_string(int sz)
32 {
33 	int i;
34 	char *buf = smalloc(sz + 1); /* padding */
35 
36 	for (i = 0; i < sz; i++)
37 	{
38 		buf[i] = ch[arc4random() % 26];
39 	}
40 
41 	buf[sz] = 0;
42 
43 	return buf;
44 }
45 
create_challenge(sourceinfo_t * si,const char * name,int v,char * dest)46 void create_challenge(sourceinfo_t *si, const char *name, int v, char *dest)
47 {
48 	char buf[256];
49 	int digest[4];
50 	md5_state_t ctx;
51 
52 	snprintf(buf, sizeof buf, "%lu:%s:%s",
53 			(unsigned long)(CURRTIME / 300) - v,
54 			get_source_name(si),
55 			name);
56 	md5_init(&ctx);
57 	md5_append(&ctx, (unsigned char *)buf, strlen(buf));
58 	md5_finish(&ctx, (unsigned char *)digest);
59 	/* note: this depends on byte order, but that's ok because
60 	 * it's only going to work in the same atheme instance anyway
61 	 */
62 	snprintf(dest, 80, "%x:%x", digest[0], digest[1]);
63 }
64 
65 #ifdef HAVE_GETTIMEOFDAY
66 /* starts a timer */
s_time(struct timeval * sttime)67 void s_time(struct timeval *sttime)
68 {
69 	gettimeofday(sttime, NULL);
70 }
71 #endif
72 
73 #ifdef HAVE_GETTIMEOFDAY
74 /* ends a timer */
e_time(struct timeval sttime,struct timeval * ttime)75 void e_time(struct timeval sttime, struct timeval *ttime)
76 {
77 	struct timeval now;
78 
79 	gettimeofday(&now, NULL);
80 	timersub(&now, &sttime, ttime);
81 }
82 #endif
83 
84 #ifdef HAVE_GETTIMEOFDAY
85 /* translates microseconds into miliseconds */
tv2ms(struct timeval * tv)86 int tv2ms(struct timeval *tv)
87 {
88 	return (tv->tv_sec * 1000) + (int) (tv->tv_usec / 1000);
89 }
90 #endif
91 
92 /* replaces tabs with a single ASCII 32 */
tb2sp(char * line)93 void tb2sp(char *line)
94 {
95 	char *c;
96 
97 	while ((c = strchr(line, '\t')))
98 		*c = ' ';
99 }
100 
101 /* replace all occurances of 'old' with 'new' */
replace(char * s,int size,const char * old,const char * new)102 char *replace(char *s, int size, const char *old, const char *new)
103 {
104 	char *ptr = s;
105 	int left = strlen(s);
106 	int avail = size - (left + 1);
107 	int oldlen = strlen(old);
108 	int newlen = strlen(new);
109 	int diff = newlen - oldlen;
110 
111 	while (left >= oldlen)
112 	{
113 		if (strncmp(ptr, old, oldlen))
114 		{
115 			left--;
116 			ptr++;
117 			continue;
118 		}
119 
120 		if (diff > avail)
121 			break;
122 
123 		if (diff != 0)
124 			memmove(ptr + oldlen + diff, ptr + oldlen, left + 1 - oldlen);
125 
126 		memcpy(ptr, new, newlen);
127 		ptr += newlen;
128 		left -= oldlen;
129 	}
130 
131 	return s;
132 }
133 
134 /* reverse of atoi() */
number_to_string(int num)135 const char *number_to_string(int num)
136 {
137 	static char ret[32];
138 	snprintf(ret, 32, "%d", num);
139 	return ret;
140 }
141 
142 /* return the time elapsed since an event */
time_ago(time_t event)143 char *time_ago(time_t event)
144 {
145 	static char ret[128];
146 	int years, weeks, days, hours, minutes, seconds;
147 
148 	event = CURRTIME - event;
149 	years = weeks = days = hours = minutes = 0;
150 
151 	while (event >= 60 * 60 * 24 * 365)
152 	{
153 		event -= 60 * 60 * 24 * 365;
154 		years++;
155 	}
156 	while (event >= 60 * 60 * 24 * 7)
157 	{
158 		event -= 60 * 60 * 24 * 7;
159 		weeks++;
160 	}
161 	while (event >= 60 * 60 * 24)
162 	{
163 		event -= 60 * 60 * 24;
164 		days++;
165 	}
166 	while (event >= 60 * 60)
167 	{
168 		event -= 60 * 60;
169 		hours++;
170 	}
171 	while (event >= 60)
172 	{
173 		event -= 60;
174 		minutes++;
175 	}
176 
177 	seconds = event;
178 
179 	if (years)
180 		snprintf(ret, sizeof(ret), "%dy %dw %dd", years, weeks, days);
181 	else if (weeks)
182 		snprintf(ret, sizeof(ret), "%dw %dd %dh", weeks, days, hours);
183 	else if (days)
184 		snprintf(ret, sizeof(ret), "%dd %dh %dm %ds", days, hours, minutes, seconds);
185 	else if (hours)
186 		snprintf(ret, sizeof(ret), "%dh %dm %ds", hours, minutes, seconds);
187 	else if (minutes)
188 		snprintf(ret, sizeof(ret), "%dm %ds", minutes, seconds);
189 	else
190 		snprintf(ret, sizeof(ret), "%ds", seconds);
191 
192 	return ret;
193 }
194 
timediff(time_t seconds)195 char *timediff(time_t seconds)
196 {
197 	static char buf[BUFSIZE];
198 	long unsigned days, hours, minutes;
199 
200 	days = seconds / 86400;
201 	seconds %= 86400;
202 	hours = seconds / 3600;
203 	hours %= 3600;
204 	minutes = seconds / 60;
205 	minutes %= 60;
206 	seconds %= 60;
207 
208 	snprintf(buf, sizeof(buf), "%lu day%s, %lu:%02lu:%02lu", days, (days == 1) ? "" : "s", hours, minutes, (long unsigned) seconds);
209 
210 	return buf;
211 }
212 
213 /* generate a random number, for use as a key */
makekey(void)214 unsigned long makekey(void)
215 {
216 	unsigned long k;
217 
218 	k = arc4random() & 0x7FFFFFFF;
219 
220 	/* shorten or pad it to 9 digits */
221 	if (k > 1000000000)
222 		k = k - 1000000000;
223 	if (k < 100000000)
224 		k = k + 100000000;
225 
226 	return k;
227 }
228 
is_internal_client(user_t * u)229 bool is_internal_client(user_t *u)
230 {
231 	return (u && (!u->server || u->server == me.me));
232 }
233 
validemail(const char * email)234 int validemail(const char *email)
235 {
236 	int i, valid = 1, chars = 0, atcnt = 0, dotcnt1 = 0;
237 	char c;
238 	const char *lastdot = NULL;
239 
240 	/* sane length */
241 	if (strlen(email) >= EMAILLEN)
242 		return 0;
243 
244 #if 0
245 	/* RFC2822 */
246 #define EXTRA_ATEXTCHARS "!#$%&'*+-/=?^_`{|}~"
247 #else
248 	/* commonly used subset */
249 #define EXTRA_ATEXTCHARS "%+-=^_"
250 #endif
251 	/* note that we do not allow domain literals or quoted strings */
252 	for (i = 0; email[i] != '\0'; i++)
253 	{
254 		c = email[i];
255 		if (c == '.')
256 		{
257 			dotcnt1++;
258 			lastdot = &email[i];
259 			/* dot may not be first or last, no consecutive dots */
260 			if (i == 0 || email[i - 1] == '.' ||
261 					email[i - 1] == '@' ||
262 					email[i + 1] == '\0' ||
263 					email[i + 1] == '@')
264 				return 0;
265 		}
266 		else if (c == '@')
267 			atcnt++, dotcnt1 = 0;
268 		else if ((c >= 'a' && c <= 'z') ||
269 				(c >= 'A' && c <= 'Z') ||
270 				(c >= '0' && c <= '9') ||
271 				strchr(EXTRA_ATEXTCHARS, c))
272 			chars++;
273 		else
274 			return 0;
275 	}
276 
277 	/* must have exactly one @, and at least one . after the @ */
278 	if (atcnt != 1 || dotcnt1 == 0)
279 		return 0;
280 
281 	/* no mail to IP addresses, this should be done using [10.2.3.4]
282 	 * like syntax but we do not allow that either
283 	 */
284 	if (isdigit((unsigned char)lastdot[1]))
285 		return 0;
286 
287 	/* make sure there are at least 4 characters besides the above
288 	 * mentioned @ and .
289 	 */
290 	if (chars < 4)
291 		return 0;
292 
293 	return valid;
294 }
295 
296 static mowgli_list_t email_canonicalizers;
297 
298 /* Re-canonicalize email addresses.
299  * Call this after adding or removing an email_canonicalize hook.
300  */
canonicalize_emails(void)301 static void canonicalize_emails(void)
302 {
303 	myentity_iteration_state_t state;
304 	myentity_t *mt;
305 
306 	MYENTITY_FOREACH_T(mt, &state, ENT_USER)
307 	{
308 		myuser_t *mu = user(mt);
309 
310 		strshare_unref(mu->email_canonical);
311 		mu->email_canonical = canonicalize_email(mu->email);
312 	}
313 }
314 
315 void
register_email_canonicalizer(email_canonicalizer_t func,void * user_data)316 register_email_canonicalizer(email_canonicalizer_t func, void *user_data)
317 {
318 	email_canonicalizer_item_t *item;
319 
320 	item = smalloc(sizeof(email_canonicalizer_item_t));
321 	item->func = func;
322 	item->user_data = user_data;
323 
324 	mowgli_node_add(item, &item->node, &email_canonicalizers);
325 
326 	canonicalize_emails();
327 }
328 
329 void
unregister_email_canonicalizer(email_canonicalizer_t func,void * user_data)330 unregister_email_canonicalizer(email_canonicalizer_t func, void *user_data)
331 {
332 	mowgli_node_t *n, *tn;
333 
334 	MOWGLI_LIST_FOREACH_SAFE(n, tn, email_canonicalizers.head)
335 	{
336 		email_canonicalizer_item_t *item = n->data;
337 
338 		if (item->func == func && item->user_data == user_data)
339 		{
340 			mowgli_node_delete(&item->node, &email_canonicalizers);
341 			free(item);
342 
343 			canonicalize_emails();
344 
345 			return;
346 		}
347 	}
348 }
349 
canonicalize_email(const char * email)350 stringref canonicalize_email(const char *email)
351 {
352 	mowgli_node_t *n, *tn;
353 	char buf[EMAILLEN + 1];
354 
355 	if (email == NULL)
356 		return NULL;
357 
358 	mowgli_strlcpy(buf, email, sizeof buf);
359 
360 	MOWGLI_LIST_FOREACH_SAFE(n, tn, email_canonicalizers.head)
361 	{
362 		email_canonicalizer_item_t *item = n->data;
363 
364 		item->func(buf, item->user_data);
365 	}
366 
367 	return strshare_get(buf);
368 }
369 
canonicalize_email_case(char email[EMAILLEN+1],void * user_data)370 void canonicalize_email_case(char email[EMAILLEN + 1], void *user_data)
371 {
372 	strcasecanon(email);
373 }
374 
email_within_limits(const char * email)375 bool email_within_limits(const char *email)
376 {
377 	mowgli_node_t *n;
378 	myentity_iteration_state_t state;
379 	myentity_t *mt;
380 	unsigned int tcnt = 0;
381 	stringref email_canonical;
382 	bool result = true;
383 
384 	if (me.maxusers <= 0)
385 		return true;
386 
387 	MOWGLI_ITER_FOREACH(n, nicksvs.emailexempts.head)
388 	{
389 		if (0 == match(n->data, email))
390 			return true;
391 	}
392 
393 	email_canonical = canonicalize_email(email);
394 
395 	MYENTITY_FOREACH_T(mt, &state, ENT_USER)
396 	{
397 		myuser_t *mu = user(mt);
398 
399 		if (mu->email_canonical == email_canonical)
400 			tcnt++;
401 
402 		/* optimization: if tcnt >= me.maxusers, quit iterating. -nenolod */
403 		if (tcnt >= me.maxusers) {
404 			result = false;
405 			break;
406 		}
407 	}
408 
409 	strshare_unref(email_canonical);
410 	return result;
411 }
412 
validhostmask(const char * host)413 bool validhostmask(const char *host)
414 {
415 	char *p, *q;
416 
417 	if (strchr(host, ' '))
418 		return false;
419 
420 	/* make sure it has ! and @ in that order and only once */
421 	p = strchr(host, '!');
422 	q = strchr(host, '@');
423 	if (p == NULL || q == NULL || p > q || strchr(p + 1, '!') ||
424 			strchr(q + 1, '@'))
425 		return false;
426 
427 	/* XXX this NICKLEN is too long */
428 	if (strlen(host) > NICKLEN + USERLEN + HOSTLEN + 1)
429 		return false;
430 
431 	if (host[0] == ',' || host[0] == '-' || host[0] == '#' ||
432 			host[0] == '@' || host[0] == '!' || host[0] == ':')
433 		return false;
434 
435 	return true;
436 }
437 
438 /* char *
439  * pretty_mask(char *mask);
440  *
441  * Input: A mask.
442  * Output: A "user-friendly" version of the mask, in mask_buf.
443  * Side-effects: mask_buf is appended to. mask_pos is incremented.
444  * Notes: The following transitions are made:
445  *  x!y@z =>  x!y@z
446  *  y@z   =>  *!y@z
447  *  x!y   =>  x!y@*
448  *  x     =>  x!*@*
449  *  z.d   =>  *!*@z.d
450  *
451  * If either nick/user/host are > than their respective limits, they are
452  * chopped
453  */
pretty_mask(char * mask)454 char *pretty_mask(char *mask)
455 {
456 	static char mask_buf[BUFSIZE];
457         int old_mask_pos;
458         char star[] = "*";
459         char *nick = star, *user = star, *host = star;
460 	int mask_pos = 0;
461 
462         char *t, *at, *ex;
463         char ne = 0, ue = 0, he = 0;    /* save values at nick[NICKLEN], et all */
464 
465 	/* No point wasting CPU time if the mask is already valid */
466 	if (validhostmask(mask))
467 		return mask;
468 
469         if((size_t) (BUFSIZE - mask_pos) < strlen(mask) + 5)
470                 return (NULL);
471 
472         old_mask_pos = mask_pos;
473 
474         at = ex = NULL;
475 	if(is_valid_host(mask))
476 	{
477 		if (*mask != '\0')
478 			host = mask;
479 	}
480 	else if((t = strchr(mask, '@')) != NULL)
481 	{
482                 at = t;
483                 *t++ = '\0';
484                 if(*t != '\0')
485                         host = t;
486 
487                 if((t = strchr(mask, '!')) != NULL)
488                 {
489                         ex = t;
490                         *t++ = '\0';
491                         if(*t != '\0')
492                                 user = t;
493                         if(*mask != '\0')
494                                 nick = mask;
495                 }
496                 else
497                 {
498                         if(*mask != '\0')
499                                 user = mask;
500                 }
501         }
502         else if((t = strchr(mask, '!')) != NULL)
503         {
504                 ex = t;
505                 *t++ = '\0';
506                 if(*mask != '\0')
507                         nick = mask;
508                 if(*t != '\0')
509                         user = t;
510         }
511         else if(strchr(mask, '.') != NULL && strchr(mask, ':') != NULL)
512         {
513                 if(*mask != '\0')
514                         host = mask;
515         }
516         else
517         {
518                 if(*mask != '\0')
519                         nick = mask;
520         }
521 
522         /* truncate values to max lengths */
523         if(strlen(nick) > NICKLEN - 1)
524         {
525                 ne = nick[NICKLEN - 1];
526                 nick[NICKLEN - 1] = '\0';
527         }
528         if(strlen(user) > USERLEN)
529         {
530                 ue = user[USERLEN];
531                 user[USERLEN] = '\0';
532         }
533         if(strlen(host) > HOSTLEN)
534         {
535                 he = host[HOSTLEN];
536                 host[HOSTLEN] = '\0';
537         }
538 
539 	snprintf(mask_buf, sizeof mask_buf, "%s!%s@%s", nick, user, host);
540 
541         /* restore mask, since we may need to use it again later */
542         if(at)
543                 *at = '@';
544         if(ex)
545                 *ex = '!';
546         if(ne)
547                 nick[NICKLEN - 1] = ne;
548         if(ue)
549                 user[USERLEN] = ue;
550         if(he)
551                 host[HOSTLEN] = he;
552 
553 	return mask_buf;
554 }
555 
validtopic(const char * topic)556 bool validtopic(const char *topic)
557 {
558 	int i;
559 
560 	/* Most server protocols support less than this (depending on
561 	 * the lengths of the identifiers), but this should catch the
562 	 * ludicrous stuff.
563 	 */
564 	if (strlen(topic) > 450)
565 		return false;
566 	for (i = 0; topic[i] != '\0'; i++)
567 	{
568 		switch (topic[i])
569 		{
570 			case '\r':
571 			case '\n':
572 				return false;
573 		}
574 	}
575 	if (ircd->flags & IRCD_TOPIC_NOCOLOUR)
576 	{
577 		for (i = 0; topic[i] != '\0'; i++)
578 		{
579 			switch (topic[i])
580 			{
581 				case 2:
582 				case 3:
583 				case 6:
584 				case 7:
585 				case 22:
586 				case 23:
587 				case 27:
588 				case 31:
589 					return false;
590 			}
591 		}
592 	}
593 	return true;
594 }
595 
has_ctrl_chars(const char * text)596 bool has_ctrl_chars(const char *text)
597 {
598 	int i;
599 
600 	for (i = 0; text[i] != '\0'; i++)
601 	{
602 		if (text[i] > 0 && text[i] < 32)
603 			return true;
604 	}
605 	return false;
606 }
607 
608 #ifndef MOWGLI_OS_WIN
sendemail_waited(pid_t pid,int status,void * data)609 static void sendemail_waited(pid_t pid, int status, void *data)
610 {
611 	char *email;
612 
613 	email = data;
614 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
615 		slog(LG_INFO, "sendemail_waited(): email for %s failed", email);
616 	free(email);
617 }
618 #endif
619 
620 /* send the specified type of email.
621  *
622  * u is whoever caused this to be called, the corresponding service
623  *   in case of xmlrpc
624  * type is EMAIL_*, see include/tools.h
625  * mu is the recipient user
626  * param depends on type, also see include/tools.h
627  */
sendemail(user_t * u,myuser_t * mu,const char * type,const char * email,const char * param)628 int sendemail(user_t *u, myuser_t *mu, const char *type, const char *email, const char *param)
629 {
630 #ifndef MOWGLI_OS_WIN
631 	char *date = NULL;
632 	char timebuf[BUFSIZE], to[BUFSIZE], from[BUFSIZE], buf[BUFSIZE], pathbuf[BUFSIZE], sourceinfo[BUFSIZE];
633 	FILE *in, *out;
634 	time_t t;
635 	struct tm tm;
636 	int pipfds[2];
637 	pid_t pid;
638 	int rc;
639 	static time_t period_start = 0, lastwallops = 0;
640 	static unsigned int emailcount = 0;
641 	service_t *svs;
642 
643 	if (u == NULL || mu == NULL)
644 		return 0;
645 
646 	if (me.mta == NULL)
647 	{
648 		if (strcmp(type, EMAIL_MEMO) && !is_internal_client(u))
649 		{
650 			svs = service_find("operserv");
651 			notice(svs ? svs->nick : me.name, u->nick, "Sending email is administratively disabled.");
652 		}
653 		return 0;
654 	}
655 
656 	if (!validemail(email))
657 	{
658 		if (strcmp(type, EMAIL_MEMO) && !is_internal_client(u))
659 		{
660 			svs = service_find("operserv");
661 			notice(svs ? svs->nick : me.name, u->nick, "The email address is considered invalid.");
662 		}
663 		return 0;
664 	}
665 
666 	if ((unsigned int)(CURRTIME - period_start) > me.emailtime)
667 	{
668 		emailcount = 0;
669 		period_start = CURRTIME;
670 	}
671 	emailcount++;
672 	if (emailcount > me.emaillimit)
673 	{
674 		if (CURRTIME - lastwallops > 60)
675 		{
676 			wallops(_("Rejecting email for %s[%s@%s] due to too high load (type '%s' to %s <%s>)"),
677 					u->nick, u->user, u->vhost,
678 					type, entity(mu)->name, email);
679 			slog(LG_ERROR, "sendemail(): rejecting email for %s[%s@%s] (%s) due to too high load (type '%s' to %s <%s>)",
680 					u->nick, u->user, u->vhost,
681 					u->ip ? u->ip : u->host,
682 					type, entity(mu)->name, email);
683 			lastwallops = CURRTIME;
684 		}
685 		return 0;
686 	}
687 
688 	snprintf(pathbuf, sizeof pathbuf, "%s/%s", SHAREDIR "/email", type);
689 	if ((in = fopen(pathbuf, "r")) == NULL)
690 	{
691 		slog(LG_ERROR, "sendemail(): rejecting email for %s[%s@%s] (%s), due to unknown type '%s'",
692 			       u->nick, u->user, u->vhost, email, type);
693 		return 0;
694 	}
695 
696 	slog(LG_INFO, "sendemail(): email for %s[%s@%s] (%s) type %s to %s <%s>",
697 			u->nick, u->user, u->vhost, u->ip ? u->ip : u->host,
698 			type, entity(mu)->name, email);
699 
700 	/* set up the email headers */
701 	time(&t);
702 	tm = *localtime(&t);
703 	strftime(timebuf, sizeof timebuf, "%a, %d %b %Y %H:%M:%S %z", &tm);
704 
705 	date = timebuf;
706 
707 	snprintf(from, sizeof from, "\"%s Network Services\" <%s>",
708 			me.netname, me.register_email);
709 	snprintf(to, sizeof to, "\"%s\" <%s>", entity(mu)->name, email);
710 	/* \ is special here; escape it */
711 	replace(to, sizeof to, "\\", "\\\\");
712 	snprintf(sourceinfo, sizeof sourceinfo, "%s[%s@%s]", u->nick, u->user, u->vhost);
713 
714 	/* now set up the email */
715 	if (pipe(pipfds) < 0)
716 	{
717 		fclose(in);
718 		return 0;
719 	}
720 	switch (pid = fork())
721 	{
722 		case -1:
723 			fclose(in);
724 			return 0;
725 		case 0:
726 			connection_close_all_fds();
727 			close(pipfds[1]);
728 			dup2(pipfds[0], 0);
729 			execl(me.mta, me.mta, "-t", "-f", me.register_email, NULL);
730 			_exit(255);
731 	}
732 	close(pipfds[0]);
733 	childproc_add(pid, "email", sendemail_waited, sstrdup(email));
734 	out = fdopen(pipfds[1], "w");
735 
736 	while (fgets(buf, BUFSIZE, in))
737 	{
738 		strip(buf);
739 
740 		replace(buf, sizeof buf, "&from&", from);
741 		replace(buf, sizeof buf, "&to&", to);
742 		replace(buf, sizeof buf, "&replyto&", me.adminemail);
743 		replace(buf, sizeof buf, "&date&", date);
744 		replace(buf, sizeof buf, "&accountname&", entity(mu)->name);
745 		replace(buf, sizeof buf, "&entityname&", u->myuser ? entity(u->myuser)->name : u->nick);
746 		replace(buf, sizeof buf, "&netname&", me.netname);
747 		replace(buf, sizeof buf, "&param&", param);
748 		replace(buf, sizeof buf, "&sourceinfo&", sourceinfo);
749 		if ((svs = service_find("nickserv")) != NULL)
750 			replace(buf, sizeof buf, "&nicksvs&", svs->me->nick);
751 		if ((svs = service_find("chanserv")) != NULL)
752 			replace(buf, sizeof buf, "&chansvs&", svs->me->nick);
753 		if ((svs = service_find("memoserv")) != NULL)
754 			replace(buf, sizeof buf, "&memosvs&", svs->me->nick);
755 		if ((svs = service_find("operserv")) != NULL)
756 			replace(buf, sizeof buf, "&opersvs&", svs->me->nick);
757 
758 		fprintf(out, "%s\n", buf);
759 	}
760 
761 	fclose(in);
762 
763 	rc = 1;
764 	if (ferror(out))
765 		rc = 0;
766 	if (fclose(out) < 0)
767 		rc = 0;
768 	if (rc == 0)
769 		slog(LG_ERROR, "sendemail(): mta failure");
770 	return rc;
771 #else
772 # warning implement me :(
773 	return 0;
774 #endif
775 }
776 
777 /* various access level checkers */
is_founder(mychan_t * mychan,myentity_t * mt)778 bool is_founder(mychan_t *mychan, myentity_t *mt)
779 {
780 	if (mt == NULL)
781 		return false;
782 
783 	if (chanacs_entity_has_flag(mychan, mt, CA_FOUNDER))
784 		return true;
785 
786 	return false;
787 }
788 
is_ircop(user_t * user)789 bool is_ircop(user_t *user)
790 {
791 	if (UF_IRCOP & user->flags)
792 		return true;
793 
794 	return false;
795 }
796 
is_admin(user_t * user)797 bool is_admin(user_t *user)
798 {
799 	if (UF_ADMIN & user->flags)
800 		return true;
801 
802 	return false;
803 }
804 
is_autokline_exempt(user_t * user)805 bool is_autokline_exempt(user_t *user)
806 {
807 	mowgli_node_t *n;
808 	char buf[BUFSIZE];
809 
810 	snprintf(buf, sizeof(buf), "%s@%s", user->user, user->host);
811 	MOWGLI_ITER_FOREACH(n, config_options.exempts.head)
812 	{
813 		if (0 == match(n->data, buf))
814 			return true;
815 	}
816 	return false;
817 }
818 
is_service(user_t * user)819 bool is_service(user_t *user)
820 {
821 	if (UF_SERVICE & user->flags)
822 		return true;
823 
824 	return false;
825 }
826 
sbytes(float x)827 char *sbytes(float x)
828 {
829 	if (x > 1073741824.0)
830 		return "GB";
831 
832 	else if (x > 1048576.0)
833 		return "MB";
834 
835 	else if (x > 1024.0)
836 		return "KB";
837 
838 	return "B";
839 }
840 
bytes(float x)841 float bytes(float x)
842 {
843 	if (x > 1073741824.0)
844 		return (x / 1073741824.0);
845 
846 	if (x > 1048576.0)
847 		return (x / 1048576.0);
848 
849 	if (x > 1024.0)
850 		return (x / 1024.0);
851 
852 	return x;
853 }
854 
srename(const char * old_fn,const char * new_fn)855 int srename(const char *old_fn, const char *new_fn)
856 {
857 #ifdef MOWGLI_OS_WIN
858 	unlink(new_fn);
859 #endif
860 
861 	return rename(old_fn, new_fn);
862 }
863 
combine_path(const char * parent,const char * child)864 char *combine_path(const char *parent, const char *child)
865 {
866 	char buf[BUFSIZE];
867 
868 	return_val_if_fail(parent != NULL, NULL);
869 	return_val_if_fail(child != NULL, NULL);
870 
871 	mowgli_strlcpy(buf, parent, sizeof buf);
872 	mowgli_strlcat(buf, "/", sizeof buf);
873 	mowgli_strlcat(buf, child, sizeof buf);
874 
875 	return sstrdup(buf);
876 }
877 
878 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
879  * vim:ts=8
880  * vim:sw=8
881  * vim:noexpandtab
882  */
883