1 /*
2  * Copyright (c) 2003-2004 E. Will et al.
3  * Copyright (c) 2005-2006 Atheme Development Group
4  * Rights to this code are documented in doc/LICENSE.
5  *
6  * This file contains functionality which implements
7  * the OperServ SQLINE command.
8  *
9  */
10 
11 #include "atheme.h"
12 
13 DECLARE_MODULE_V1
14 (
15 	"operserv/sqline", false, _modinit, _moddeinit,
16 	PACKAGE_STRING,
17 	VENDOR_STRING
18 );
19 
20 static void os_sqline_newuser(hook_user_nick_t *data);
21 static void os_sqline_chanjoin(hook_channel_joinpart_t *hdata);
22 
23 static void os_cmd_sqline(sourceinfo_t *si, int parc, char *parv[]);
24 static void os_cmd_sqline_add(sourceinfo_t *si, int parc, char *parv[]);
25 static void os_cmd_sqline_del(sourceinfo_t *si, int parc, char *parv[]);
26 static void os_cmd_sqline_list(sourceinfo_t *si, int parc, char *parv[]);
27 static void os_cmd_sqline_sync(sourceinfo_t *si, int parc, char *parv[]);
28 
29 
30 command_t os_sqline = { "SQLINE", N_("Manages network name bans."), PRIV_MASS_AKILL, 3, os_cmd_sqline, { .path = "oservice/sqline" } };
31 
32 command_t os_sqline_add = { "ADD", N_("Adds a network name ban"), AC_NONE, 2, os_cmd_sqline_add, { .path = "" } };
33 command_t os_sqline_del = { "DEL", N_("Deletes a network name ban"), AC_NONE, 1, os_cmd_sqline_del, { .path = "" } };
34 command_t os_sqline_list = { "LIST", N_("Lists all network name bans"), AC_NONE, 1, os_cmd_sqline_list, { .path = "" } };
35 command_t os_sqline_sync = { "SYNC", N_("Synchronises network name bans to servers"), AC_NONE, 0, os_cmd_sqline_sync, { .path = "" } };
36 
37 mowgli_patricia_t *os_sqline_cmds;
38 
_modinit(module_t * m)39 void _modinit(module_t *m)
40 {
41 	if (ircd != NULL && qline_sts == generic_qline_sts)
42 	{
43 		slog(LG_INFO, "Module %s requires qline support, refusing to load.",
44 				m->name);
45 		m->mflags = MODTYPE_FAIL;
46 		return;
47 	}
48 
49 	service_named_bind_command("operserv", &os_sqline);
50 
51 	os_sqline_cmds = mowgli_patricia_create(strcasecanon);
52 
53 	/* Add sub-commands */
54 	command_add(&os_sqline_add, os_sqline_cmds);
55 	command_add(&os_sqline_del, os_sqline_cmds);
56 	command_add(&os_sqline_list, os_sqline_cmds);
57 	command_add(&os_sqline_sync, os_sqline_cmds);
58 
59 	hook_add_event("user_add");
60 	hook_add_user_add(os_sqline_newuser);
61 	hook_add_event("user_nickchange");
62 	hook_add_user_nickchange(os_sqline_newuser);
63 	hook_add_event("channel_join");
64 	hook_add_channel_join(os_sqline_chanjoin);
65 }
66 
_moddeinit(module_unload_intent_t intent)67 void _moddeinit(module_unload_intent_t intent)
68 {
69 	service_named_unbind_command("operserv", &os_sqline);
70 
71 	/* Delete sub-commands */
72 	command_delete(&os_sqline_add, os_sqline_cmds);
73 	command_delete(&os_sqline_del, os_sqline_cmds);
74 	command_delete(&os_sqline_list, os_sqline_cmds);
75 	command_delete(&os_sqline_sync, os_sqline_cmds);
76 
77 	hook_del_user_add(os_sqline_newuser);
78 	hook_del_user_nickchange(os_sqline_newuser);
79 	hook_del_channel_join(os_sqline_chanjoin);
80 
81 	mowgli_patricia_destroy(os_sqline_cmds, NULL, NULL);
82 }
83 
os_sqline_newuser(hook_user_nick_t * data)84 static void os_sqline_newuser(hook_user_nick_t *data)
85 {
86 	user_t *u = data->u;
87 	qline_t *q;
88 
89 	/* If the user has been killed, don't do anything. */
90 	if (!u)
91 		return;
92 
93 	if (is_internal_client(u))
94 		return;
95 	q = qline_find_user(u);
96 	if (q != NULL)
97 	{
98 		/* Server didn't have that qline, send it again.
99 		 * To ensure qline exempt works on sqlines too, do
100 		 * not send a KILL. -- jilles */
101 		qline_sts("*", q->mask, q->duration ? q->expires - CURRTIME : 0, q->reason);
102 	}
103 }
104 
os_sqline_chanjoin(hook_channel_joinpart_t * hdata)105 static void os_sqline_chanjoin(hook_channel_joinpart_t *hdata)
106 {
107 	chanuser_t *cu = hdata->cu;
108 	qline_t *q;
109 
110 	if (cu == NULL)
111 		return;
112 
113 	if (is_internal_client(cu->user))
114 		return;
115 	q = qline_find_channel(cu->chan);
116 	if (q != NULL)
117 	{
118 		/* Server didn't have that qline, send it again.
119 		 * To ensure qline exempt works on sqlines too, do
120 		 * not send a KICK. -- jilles */
121 		qline_sts("*", q->mask, q->duration ? q->expires - CURRTIME : 0, q->reason);
122 	}
123 }
124 
os_cmd_sqline(sourceinfo_t * si,int parc,char * parv[])125 static void os_cmd_sqline(sourceinfo_t *si, int parc, char *parv[])
126 {
127 	/* Grab args */
128 	char *cmd = parv[0];
129 	command_t *c;
130 
131 	/* Bad/missing arg */
132 	if (!cmd)
133 	{
134 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SQLINE");
135 		command_fail(si, fault_needmoreparams, _("Syntax: SQLINE ADD|DEL|LIST"));
136 		return;
137 	}
138 
139 	c = command_find(os_sqline_cmds, cmd);
140 	if(c == NULL)
141 	{
142 		command_fail(si, fault_badparams, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == FALSE) ? "msg " : "", si->service->disp);
143 		return;
144 	}
145 
146 	command_exec(si->service, si, c, parc - 1, parv + 1);
147 }
148 
os_cmd_sqline_add(sourceinfo_t * si,int parc,char * parv[])149 static void os_cmd_sqline_add(sourceinfo_t *si, int parc, char *parv[])
150 {
151 	char *target = parv[0];
152 	char *token = strtok(parv[1], " ");
153 	char *treason, reason[BUFSIZE];
154 	long duration;
155 	char *s;
156 	qline_t *q;
157 
158 	if (!target || !token)
159 	{
160 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SQLINE ADD");
161 		command_fail(si, fault_needmoreparams, _("Syntax: SQLINE ADD <nick|chan> [!P|!T <minutes>] <reason>"));
162 		return;
163 	}
164 
165 	if (IsDigit(*target))
166 	{
167 		command_fail(si, fault_badparams, _("Invalid target: \2%s\2. You can not SQLINE UIDs."), target);
168 		return;
169 	}
170 
171 	if (!strcasecmp(token, "!P"))
172 	{
173 		duration = 0;
174 		treason = strtok(NULL, "");
175 
176 		if (treason)
177 			mowgli_strlcpy(reason, treason, BUFSIZE);
178 		else
179 			mowgli_strlcpy(reason, "No reason given", BUFSIZE);
180 	}
181 	else if (!strcasecmp(token, "!T"))
182 	{
183 		s = strtok(NULL, " ");
184 		treason = strtok(NULL, "");
185 		if (treason)
186 			mowgli_strlcpy(reason, treason, BUFSIZE);
187 		else
188 			mowgli_strlcpy(reason, "No reason given", BUFSIZE);
189 		if (s)
190 		{
191 			duration = (atol(s) * 60);
192 			while (isdigit((unsigned char)*s))
193 				s++;
194 			if (*s == 'h' || *s == 'H')
195 				duration *= 60;
196 			else if (*s == 'd' || *s == 'D')
197 				duration *= 1440;
198 			else if (*s == 'w' || *s == 'W')
199 				duration *= 10080;
200 			else if (*s == '\0')
201 				;
202 			else
203 				duration = 0;
204 			if (duration == 0)
205 			{
206 				command_fail(si, fault_badparams, _("Invalid duration given."));
207 				command_fail(si, fault_badparams, _("Syntax: SQLINE ADD <nick|chan> [!P|!T <minutes>] <reason>"));
208 				return;
209 			}
210 		}
211 		else {
212 			command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SQLINE ADD");
213 			command_fail(si, fault_needmoreparams, _("Syntax: SQLINE ADD <nick|chan> [!P|!T <minutes>] <reason>"));
214 			return;
215 		}
216 
217 	}
218 	else
219 	{
220 		duration = config_options.kline_time;
221 		mowgli_strlcpy(reason, token, BUFSIZE);
222 		treason = strtok(NULL, "");
223 
224 		if (treason)
225 		{
226 			mowgli_strlcat(reason, " ", BUFSIZE);
227 			mowgli_strlcat(reason, treason, BUFSIZE);
228 		}
229 	}
230 
231 	char *p;
232 	int i = 0;
233 
234 	if (!VALID_CHANNEL_PFX(target))
235 	{
236 		/* make sure there's at least 3 non-wildcards */
237 		/* except if there are no wildcards at all */
238 		for (p = target; *p; p++)
239 		{
240 			if (*p != '*' && *p != '?' && *p != '.')
241 				i++;
242 		}
243 
244 		if (i < 3 && (strchr(target, '*') || strchr(target, '?')) && !has_priv(si, PRIV_AKILL_ANYMASK))
245 		{
246 			command_fail(si, fault_badparams, _("Invalid target: \2%s\2. At least three non-wildcard characters are required."), target);
247 			return;
248 		}
249 	}
250 
251 	if (qline_find(target))
252 	{
253 		command_fail(si, fault_nochange, _("SQLINE \2%s\2 is already matched in the database."), target);
254 		return;
255 	}
256 
257 	q = qline_add(target, reason, duration, get_storage_oper_name(si));
258 
259 	if (duration)
260 		command_success_nodata(si, _("Timed SQLINE on \2%s\2 was successfully added and will expire in %s."), q->mask, timediff(duration));
261 	else
262 		command_success_nodata(si, _("SQLINE on \2%s\2 was successfully added."), q->mask);
263 
264 	verbose_wallops("\2%s\2 is \2adding\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2", get_oper_name(si), q->mask,
265 		q->reason);
266 	logcommand(si, CMDLOG_ADMIN, "SQLINE:ADD: \2%s\2 (reason: \2%s\2)", q->mask, q->reason);
267 }
268 
os_cmd_sqline_del(sourceinfo_t * si,int parc,char * parv[])269 static void os_cmd_sqline_del(sourceinfo_t *si, int parc, char *parv[])
270 {
271 	char *target = parv[0];
272 	qline_t *q;
273 	unsigned int number;
274 	char *s;
275 
276 	if (!target)
277 	{
278 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SQLINE DEL");
279 		command_fail(si, fault_needmoreparams, _("Syntax: SQLINE DEL <nick|chan>"));
280 		return;
281 	}
282 
283 	if (strchr(target, ','))
284 	{
285 		unsigned int start = 0, end = 0, i;
286 		char t[16];
287 
288 		s = strtok(target, ",");
289 
290 		do
291 		{
292 			if (strchr(s, ':'))
293 			{
294 				for (i = 0; *s != ':'; s++, i++)
295 					t[i] = *s;
296 
297 				t[++i] = '\0';
298 				start = atoi(t);
299 
300 				s++;	/* skip past the : */
301 
302 				for (i = 0; *s != '\0'; s++, i++)
303 					t[i] = *s;
304 
305 				t[++i] = '\0';
306 				end = atoi(t);
307 
308 				for (i = start; i <= end; i++)
309 				{
310 					if (!(q = qline_find_num(i)))
311 					{
312 						command_fail(si, fault_nosuch_target, _("No such SQLINE with number \2%d\2."), i);
313 						continue;
314 					}
315 
316 					command_success_nodata(si, _("SQLINE on \2%s\2 has been successfully removed."), q->mask);
317 					verbose_wallops("\2%s\2 is \2removing\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2",
318 						get_oper_name(si), q->mask, q->reason);
319 
320 					logcommand(si, CMDLOG_ADMIN, "SQLINE:DEL: \2%s\2", q->mask);
321 					qline_delete(q->mask);
322 				}
323 
324 				continue;
325 			}
326 
327 			number = atoi(s);
328 
329 			if (!(q = qline_find_num(number)))
330 			{
331 				command_fail(si, fault_nosuch_target, _("No such SQLINE with number \2%d\2."), number);
332 				return;
333 			}
334 
335 			command_success_nodata(si, _("SQLINE on \2%s\2 has been successfully removed."), q->mask);
336 			verbose_wallops("\2%s\2 is \2removing\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2",
337 				get_oper_name(si), q->mask, q->reason);
338 
339 			logcommand(si, CMDLOG_ADMIN, "SQLINE:DEL: \2%s\2", q->mask);
340 			qline_delete(q->mask);
341 		} while ((s = strtok(NULL, ",")));
342 
343 		return;
344 	}
345 
346 	if (IsDigit(*target))
347 	{
348 		unsigned int start = 0, end = 0, i;
349 		char t[16];
350 
351 		if (strchr(target, ':'))
352 		{
353 			for (i = 0; *target != ':'; target++, i++)
354 				t[i] = *target;
355 
356 			t[++i] = '\0';
357 			start = atoi(t);
358 
359 			target++;	/* skip past the : */
360 
361 			for (i = 0; *target != '\0'; target++, i++)
362 				t[i] = *target;
363 
364 			t[++i] = '\0';
365 			end = atoi(t);
366 
367 			for (i = start; i <= end; i++)
368 			{
369 				if (!(q = qline_find_num(i)))
370 				{
371 					command_fail(si, fault_nosuch_target, _("No such SQLINE with number \2%d\2."), i);
372 					continue;
373 				}
374 
375 				command_success_nodata(si, _("SQLINE on \2%s\2 has been successfully removed."), q->mask);
376 				verbose_wallops("\2%s\2 is \2removing\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2",
377 					get_oper_name(si), q->mask, q->reason);
378 
379 				logcommand(si, CMDLOG_ADMIN, "SQLINE:DEL: \2%s\2", q->mask);
380 				qline_delete(q->mask);
381 			}
382 
383 			return;
384 		}
385 
386 		number = atoi(target);
387 
388 		if (!(q = qline_find_num(number)))
389 		{
390 			command_fail(si, fault_nosuch_target, _("No such SQLINE with number \2%d\2."), number);
391 			return;
392 		}
393 
394 		command_success_nodata(si, _("SQLINE on \2%s\2 has been successfully removed."), q->mask);
395 
396 		verbose_wallops("\2%s\2 is \2removing\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2",
397 			get_oper_name(si), q->mask, q->reason);
398 
399 		logcommand(si, CMDLOG_ADMIN, "SQLINE:DEL: \2%s\2", q->mask);
400 		qline_delete(q->mask);
401 		return;
402 	}
403 
404 
405 	if (!(q = qline_find(target)))
406 	{
407 		command_fail(si, fault_nosuch_target, _("No such SQLINE: \2%s\2."), target);
408 		return;
409 	}
410 
411 	command_success_nodata(si, _("SQLINE on \2%s\2 has been successfully removed."), target);
412 
413 	verbose_wallops("\2%s\2 is \2removing\2 an \2SQLINE\2 for \2%s\2 -- reason: \2%s\2",
414 		get_oper_name(si), q->mask, q->reason);
415 
416 	logcommand(si, CMDLOG_ADMIN, "SQLINE:DEL: \2%s\2", target);
417 	qline_delete(target);
418 }
419 
os_cmd_sqline_list(sourceinfo_t * si,int parc,char * parv[])420 static void os_cmd_sqline_list(sourceinfo_t *si, int parc, char *parv[])
421 {
422 	char *param = parv[0];
423 	bool full = false;
424 	mowgli_node_t *n;
425 	qline_t *q;
426 
427 	if (param != NULL && !strcasecmp(param, "FULL"))
428 		full = true;
429 
430 	if (full)
431 		command_success_nodata(si, _("SQLINE list (with reasons):"));
432 	else
433 		command_success_nodata(si, _("SQLINE list:"));
434 
435 	MOWGLI_ITER_FOREACH(n, qlnlist.head)
436 	{
437 		q = (qline_t *)n->data;
438 
439 		if (q->duration && full)
440 			command_success_nodata(si, _("%d: %s - by \2%s\2 - expires in \2%s\2 - (%s)"), q->number, q->mask, q->setby, timediff(q->expires > CURRTIME ? q->expires - CURRTIME : 0), q->reason);
441 		else if (q->duration && !full)
442 			command_success_nodata(si, _("%d: %s - by \2%s\2 - expires in \2%s\2"), q->number, q->mask, q->setby, timediff(q->expires > CURRTIME ? q->expires - CURRTIME : 0));
443 		else if (!q->duration && full)
444 			command_success_nodata(si, _("%d: %s - by \2%s\2 - \2permanent\2 - (%s)"), q->number, q->mask, q->setby, q->reason);
445 		else
446 			command_success_nodata(si, _("%d: %s - by \2%s\2 - \2permanent\2"), q->number, q->mask, q->setby);
447 	}
448 
449 	command_success_nodata(si, _("Total of \2%zu\2 %s in SQLINE list."), qlnlist.count, (qlnlist.count == 1) ? "entry" : "entries");
450 	logcommand(si, CMDLOG_GET, "SQLINE:LIST: \2%s\2", full ? " FULL" : "");
451 }
452 
os_cmd_sqline_sync(sourceinfo_t * si,int parc,char * parv[])453 static void os_cmd_sqline_sync(sourceinfo_t *si, int parc, char *parv[])
454 {
455 	mowgli_node_t *n;
456 	qline_t *q;
457 
458 	logcommand(si, CMDLOG_DO, "SQLINE:SYNC");
459 
460 	MOWGLI_ITER_FOREACH(n, qlnlist.head)
461 	{
462 		q = (qline_t *)n->data;
463 
464 		if (q->duration == 0)
465 			qline_sts("*", q->mask, 0, q->reason);
466 		else if (q->expires > CURRTIME)
467 			qline_sts("*", q->mask, q->expires - CURRTIME, q->reason);
468 	}
469 
470 	command_success_nodata(si, _("SQLINE list synchronized to servers."));
471 }
472 
473 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
474  * vim:ts=8
475  * vim:sw=8
476  * vim:noexpandtab
477  */
478