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