1 /*
2  * Copyright (c) 2016 Atheme Development Group <http://atheme.github.io>
3  * Copyright (c) 2012 William Pitcock <nenolod@dereferenced.org>.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
10  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
13  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
14  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
15  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
16  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
18  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
19  * POSSIBILITY OF SUCH DAMAGE.
20  */
21 
22 #include "atheme.h"
23 #include "chanserv.h"
24 
25 DECLARE_MODULE_V1
26 (
27 	"chanserv/moderate", false, _modinit, _moddeinit,
28 	PACKAGE_STRING,
29 	"Atheme Development Group <http://atheme.github.io>"
30 );
31 
32 static void cs_cmd_activate(sourceinfo_t *si, int parc, char *parv[]);
33 static void cs_cmd_reject(sourceinfo_t *si, int parc, char *parv[]);
34 static void cs_cmd_waiting(sourceinfo_t *si, int parc, char *parv[]);
35 static void can_register(hook_channel_register_check_t *req);
36 
37 static command_t cs_activate = { "ACTIVATE", N_("Activates a pending registration"), PRIV_CHAN_ADMIN,
38 				 2, cs_cmd_activate, { .path = "cservice/activate" } };
39 static command_t cs_reject   = { "REJECT", N_("Rejects a pending registration"), PRIV_CHAN_ADMIN,
40 				 2, cs_cmd_reject, { .path = "cservice/reject" } };
41 static command_t cs_waiting  = { "WAITING", N_("View pending registrations"), PRIV_CHAN_ADMIN,
42 				 1, cs_cmd_waiting, { .path = "cservice/waiting" } };
43 
44 typedef struct {
45 	char *name;
46 	myentity_t *mt;
47 	time_t ts;
48 } csreq_t;
49 
50 static mowgli_patricia_t *csreq_list = NULL;
51 static char *groupmemo;
52 
53 /*****************************************************************************/
54 
csreq_create(const char * name,myentity_t * mt)55 static csreq_t *csreq_create(const char *name, myentity_t *mt)
56 {
57 	csreq_t *cs;
58 
59 	return_val_if_fail(name != NULL, NULL);
60 	return_val_if_fail(mt != NULL, NULL);
61 
62 	cs = smalloc(sizeof(csreq_t));
63 	cs->name = sstrdup(name);
64 	cs->mt = mt;
65 	cs->ts = CURRTIME;
66 
67 	mowgli_patricia_add(csreq_list, cs->name, cs);
68 
69 	return cs;
70 }
71 
csreq_find(const char * name)72 static csreq_t *csreq_find(const char *name)
73 {
74 	return_val_if_fail(name != NULL, NULL);
75 
76 	return mowgli_patricia_retrieve(csreq_list, name);
77 }
78 
csreq_destroy(csreq_t * cs)79 static void csreq_destroy(csreq_t *cs)
80 {
81 	return_if_fail(cs != NULL);
82 
83 	mowgli_patricia_delete(csreq_list, cs->name);
84 	free(cs->name);
85 	free(cs);
86 }
87 
csreq_demarshal(database_handle_t * db,const char * type)88 static void csreq_demarshal(database_handle_t *db, const char *type)
89 {
90         const char *chan = db_sread_word(db);
91         const char *nick = db_sread_word(db);
92         time_t req_ts = db_sread_time(db);
93 	myentity_t *mt;
94 	csreq_t *cs;
95 
96 	mt = myentity_find(nick);
97 	if (mt == NULL)
98 	{
99 		slog(LG_INFO, "csreq_demarshal(): couldn't find entity for '%s'", nick);
100 		return;
101 	}
102 
103 	cs = csreq_create(chan, mt);
104 	cs->ts = req_ts;
105 }
106 
csreq_marshal_set(database_handle_t * db)107 static void csreq_marshal_set(database_handle_t *db)
108 {
109 	mowgli_patricia_iteration_state_t state;
110 	csreq_t *cs;
111 
112 	MOWGLI_PATRICIA_FOREACH(cs, &state, csreq_list)
113 	{
114 		db_start_row(db, "CSREQ");
115 		db_write_word(db, cs->name);
116 		db_write_word(db, cs->mt->name);
117 		db_write_time(db, cs->ts);
118 		db_commit_row(db);
119 	}
120 }
121 
122 /*****************************************************************************/
123 
send_memo(sourceinfo_t * si,myuser_t * mu,const char * memo,...)124 static void send_memo(sourceinfo_t *si, myuser_t *mu, const char *memo, ...)
125 {
126 	service_t *msvs;
127 	va_list va;
128 	char buf[BUFSIZE];
129 
130 	return_if_fail(si != NULL);
131 	return_if_fail(mu != NULL);
132 	return_if_fail(memo != NULL);
133 
134 	va_start(va, memo);
135 	vsnprintf(buf, BUFSIZE, memo, va);
136 	va_end(va);
137 
138 	if ((msvs = service_find("memoserv")) == NULL)
139 		myuser_notice(chansvs.nick, mu, "%s", buf);
140 	else
141 	{
142 		char cmdbuf[BUFSIZE];
143 
144 		mowgli_strlcpy(cmdbuf, entity(mu)->name, BUFSIZE);
145 		mowgli_strlcat(cmdbuf, " ", BUFSIZE);
146 		mowgli_strlcat(cmdbuf, buf, BUFSIZE);
147 
148 		command_exec_split(msvs, si, "SEND", cmdbuf, msvs->commands);
149 	}
150 }
151 
send_group_memo(sourceinfo_t * si,const char * memo,...)152 static void send_group_memo(sourceinfo_t *si, const char *memo, ...)
153 {
154 	service_t *msvs;
155 	va_list va;
156 	char buf[BUFSIZE];
157 
158 	return_if_fail(si != NULL);
159 	return_if_fail(memo != NULL);
160 
161 	va_start(va, memo);
162 	vsnprintf(buf, BUFSIZE, memo, va);
163 	va_end(va);
164 
165 	if ((msvs = service_find("memoserv")) == NULL)
166 		return;
167 	else
168 	{
169 		char cmdbuf[BUFSIZE];
170 
171 		mowgli_strlcpy(cmdbuf, groupmemo, BUFSIZE);
172 		mowgli_strlcat(cmdbuf, " ", BUFSIZE);
173 		mowgli_strlcat(cmdbuf, buf, BUFSIZE);
174 
175 		command_exec_split(msvs, si, "SEND", cmdbuf, msvs->commands);
176 	}
177 }
178 
179 /*****************************************************************************/
180 
181 /* deny chanserv registrations but turn them into a request */
can_register(hook_channel_register_check_t * req)182 static void can_register(hook_channel_register_check_t *req)
183 {
184 	csreq_t *cs;
185 
186 	return_if_fail(req != NULL);
187 
188 	/* no point in moderating registrations from those who have PRIV_CHAN_ADMIN since they can
189 	 * approve them anyway. --nenolod
190 	 */
191 	if (has_priv(req->si, PRIV_CHAN_ADMIN))
192 		return;
193 
194 	req->approved++;
195 
196 	cs = csreq_create(req->name, entity(req->si->smu));
197 	command_success_nodata(req->si, _("\2%s\2 reviews every Channel Registration request.  Your request to register \2%s\2 has been received and should be reviewed shortly."),
198 			       me.netname, cs->name);
199 
200 	if (groupmemo != NULL)
201 		send_group_memo(req->si, "[auto memo] Please review channel \2%s\2 for me.", req->name);
202 
203 	logcommand(req->si, CMDLOG_REGISTER, "REGISTER: \2%s\2 (pending)", req->name);
204 }
205 
206 /*****************************************************************************/
207 
cs_cmd_activate(sourceinfo_t * si,int parc,char * parv[])208 static void cs_cmd_activate(sourceinfo_t *si, int parc, char *parv[])
209 {
210 	myuser_t *mu;
211 	mychan_t *mc;
212 	csreq_t *cs;
213 	chanuser_t *cu;
214 	user_t *u;
215 	channel_t *c;
216 	char str[BUFSIZE];
217 	hook_channel_req_t hdata;
218 	sourceinfo_t baked_si;
219 	unsigned int fl;
220 
221 	if (!parv[0])
222 	{
223 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACTIVATE");
224 		command_fail(si, fault_needmoreparams, _("Syntax: ACTIVATE <#channel>"));
225 		return;
226 	}
227 
228 	cs = csreq_find(parv[0]);
229 	if (cs == NULL)
230 	{
231 		command_fail(si, fault_nosuch_target, _("\2%s\2 is not pending registration."), parv[0]);
232 		return;
233 	}
234 
235 	mu = user(cs->mt);
236 	if (mu != NULL)
237 		send_memo(si, mu, "Your registration request for \2%s\2 was approved.", parv[0]);
238 
239 	c = channel_find(cs->name);
240 	mc = mychan_add(cs->name);
241 	mc->registered = CURRTIME;
242 	mc->used = CURRTIME;
243 	mc->mlock_on |= (CMODE_NOEXT | CMODE_TOPIC);
244 	if (c != NULL && c->limit == 0)
245 		mc->mlock_off |= CMODE_LIMIT;
246 	if (c != NULL && c->key == NULL)
247 		mc->mlock_off |= CMODE_KEY;
248 	mc->flags |= config_options.defcflags;
249 	slog(LG_DEBUG, "cs_cmd_activate(): defcflags = %d, mc->flags = %d, guard? %s", config_options.defcflags, mc->flags, (mc->flags & MC_GUARD) ? "YES" : "NO");
250 
251 	chanacs_add(mc, cs->mt, custom_founder_check(), CURRTIME, entity(si->smu));
252 
253 	command_success_nodata(si, _("\2%s\2 is now registered to \2%s\2."), mc->name, cs->mt->name);
254 
255 	if (c != NULL && c->ts > 0)
256 	{
257 		snprintf(str, sizeof str, "%lu", (unsigned long)c->ts);
258 		metadata_add(mc, "private:channelts", str);
259 	}
260 
261 	if (chansvs.deftemplates != NULL && *chansvs.deftemplates != '\0')
262 		metadata_add(mc, "private:templates", chansvs.deftemplates);
263 
264 	if (mu != NULL && MOWGLI_LIST_LENGTH(&mu->logins) > 0)
265 	{
266 		u = mu->logins.head->data;
267 
268 		baked_si.su = u;
269 		baked_si.smu = mu;
270 		baked_si.service = si->service;
271 
272 	        hdata.si = &baked_si;
273 	        hdata.mc = mc;
274         	hook_call_channel_register(&hdata);
275 
276 		if (mc->chan != NULL)
277 		{
278 		        /* Allow the hook to override this. */
279 		        fl = chanacs_source_flags(mc, &baked_si);
280 		        cu = chanuser_find(mc->chan, u);
281 		        if (cu == NULL)
282 	        	        ;
283 	        	else if (ircd->uses_owner && fl & CA_USEOWNER && fl & CA_AUTOOP &&
284 		                        !(cu->modes & CSTATUS_OWNER))
285         		{
286 		                modestack_mode_param(si->service->nick, mc->chan, MTYPE_ADD,
287         	        	                ircd->owner_mchar[1], CLIENT_NAME(u));
288         		        cu->modes |= CSTATUS_OWNER;
289 		        }
290         		else if (ircd->uses_protect && fl & CA_USEPROTECT && fl & CA_AUTOOP &&
291 	        	                !(cu->modes & CSTATUS_PROTECT))
292 		        {
293 		                modestack_mode_param(si->service->nick, mc->chan, MTYPE_ADD,
294                 		                ircd->protect_mchar[1], CLIENT_NAME(u));
295         	        	cu->modes |= CSTATUS_PROTECT;
296 		        }
297 		}
298 	}
299 
300 	csreq_destroy(cs);
301 	/* Check if GUARD is enabled by default and if so, ChanServ should join even
302 	 * if the founder is no longer present or identified. --siniStar
303 	 */
304 	if (mc->flags & MC_GUARD)
305 		join(mc->name, chansvs.nick);
306 	logcommand(si, CMDLOG_ADMIN, "ACTIVATE: \2%s\2", parv[0]);
307 }
308 
cs_cmd_reject(sourceinfo_t * si,int parc,char * parv[])309 static void cs_cmd_reject(sourceinfo_t *si, int parc, char *parv[])
310 {
311 	csreq_t *cs;
312 	myuser_t *mu;
313 
314 	if (!parv[0])
315 	{
316 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "REJECT");
317 		command_fail(si, fault_needmoreparams, _("Syntax: REJECT <#channel>"));
318 		return;
319 	}
320 
321 	cs = csreq_find(parv[0]);
322 	if (cs == NULL)
323 	{
324 		command_fail(si, fault_nosuch_target, _("\2%s\2 is not pending registration."), parv[0]);
325 		return;
326 	}
327 
328 	mu = user(cs->mt);
329 	if (mu != NULL)
330 		send_memo(si, mu, "Your registration request for \2%s\2 was rejected.", parv[0]);
331 
332 	csreq_destroy(cs);
333 
334 	command_success_nodata(si, _("\2%s\2 was rejected."), parv[0]);
335 	logcommand(si, CMDLOG_ADMIN, "REJECT: \2%s\2", parv[0]);
336 }
337 
cs_cmd_waiting(sourceinfo_t * si,int parc,char * parv[])338 static void cs_cmd_waiting(sourceinfo_t *si, int parc, char *parv[])
339 {
340 	mowgli_patricia_iteration_state_t state;
341 	csreq_t *cs;
342 	struct tm tm;
343 	char strfbuf[BUFSIZE];
344 
345 	MOWGLI_PATRICIA_FOREACH(cs, &state, csreq_list)
346 	{
347 		tm = *localtime(&cs->ts);
348 		strftime(strfbuf, sizeof strfbuf, TIME_FORMAT, &tm);
349 
350 		command_success_nodata(si, _("\2%s\2 (\2%s\2) [%s (%s ago)]"),
351 				       cs->name, cs->mt->name, strfbuf, time_ago(cs->ts));
352 	}
353 
354 	command_success_nodata(si, _("End of list."));
355 	logcommand(si, CMDLOG_GET, "WAITING");
356 }
357 
358 /*****************************************************************************/
359 
_modinit(module_t * m)360 void _modinit(module_t *m)
361 {
362 	csreq_list = mowgli_patricia_create(strcasecanon);
363 
364 	service_named_bind_command("chanserv", &cs_activate);
365 	service_named_bind_command("chanserv", &cs_reject);
366 	service_named_bind_command("chanserv", &cs_waiting);
367 
368 	hook_add_event("channel_can_register");
369 	hook_add_channel_can_register(can_register);
370 
371 	hook_add_db_write(csreq_marshal_set);
372 
373 	add_dupstr_conf_item("REGGROUP", &chansvs.me->conf_table, 0, &groupmemo, NULL);
374 
375 	db_register_type_handler("CSREQ", csreq_demarshal);
376 }
377 
_moddeinit(module_unload_intent_t intent)378 void _moddeinit(module_unload_intent_t intent)
379 {
380 	mowgli_patricia_destroy(csreq_list, NULL, NULL);
381 
382 	service_named_unbind_command("chanserv", &cs_activate);
383 	service_named_unbind_command("chanserv", &cs_reject);
384 	service_named_unbind_command("chanserv", &cs_waiting);
385 
386 	hook_del_channel_can_register(can_register);
387 	hook_del_db_write(csreq_marshal_set);
388 
389 	del_conf_item("REGGROUP", &chansvs.me->conf_table);
390 
391 	db_unregister_type_handler("CSREQ");
392 }
393