1 /*
2  * Copyright (c) 2005 Atheme Development Group
3  * Rights to this code are documented in doc/LICENSE.
4  *
5  * This file contains the main() routine.
6  */
7 
8 #include "atheme-compat.h"
9 
10 DECLARE_MODULE_V1
11 (
12 	"contrib/ircd_announceserv", false, _modinit, _moddeinit,
13 	PACKAGE_STRING,
14 	"JD and Taros"
15 );
16 
17 service_t *announcesvs;
18 
19 static void as_cmd_help(sourceinfo_t *si, int parc, char *parv[]);
20 static void account_drop_request(myuser_t *mu);
21 static void as_cmd_request(sourceinfo_t *si, int parc, char *parv[]);
22 static void as_cmd_waiting(sourceinfo_t *si, int parc, char *parv[]);
23 static void as_cmd_reject(sourceinfo_t *si, int parc, char *parv[]);
24 static void as_cmd_activate(sourceinfo_t *si, int parc, char *parv[]);
25 static void as_cmd_cancel(sourceinfo_t *si, int parc, char *parv[]);
26 static void write_asreqdb(database_handle_t *db);
27 static void db_h_ar(database_handle_t *db, const char *type);
28 
29 command_t as_help = { "HELP", N_(N_("Displays contextual help information.")), AC_NONE, 2, as_cmd_help, { .path = "help/help" } };
30 command_t as_request = { "REQUEST", N_("Requests new announcement."), AC_AUTHENTICATED, 2, as_cmd_request, { .path = "contrib/as_request" } };
31 command_t as_waiting = { "WAITING", N_("Lists announcements currently waiting for activation."), PRIV_GLOBAL, 1, as_cmd_waiting, { .path = "contrib/as_waiting" } };
32 command_t as_reject = { "REJECT", N_("Reject the requested announcement for the given nick."), PRIV_GLOBAL, 2, as_cmd_reject, { .path = "contrib/as_reject" } };
33 command_t as_activate = { "ACTIVATE", N_("Activate the requested announcement for a given nick."), PRIV_GLOBAL, 2, as_cmd_activate, { .path = "contrib/as_activate" } };
34 command_t as_cancel = { "CANCEL", N_("Cancels your requested announcement."), AC_AUTHENTICATED, 0, as_cmd_cancel, { .path = "contrib/as_cancel" } };
35 
36 struct asreq_ {
37 	stringref nick;
38 	char *subject;
39 	time_t announce_ts;
40 	stringref creator;
41 	char *text;
42 };
43 
44 typedef struct asreq_ asreq_t;
45 
46 mowgli_list_t as_reqlist;
47 
_modinit(module_t * m)48 void _modinit(module_t *m)
49 {
50 	announcesvs = service_add("announceserv", NULL);
51 
52 	hook_add_event("user_drop");
53 	hook_add_user_drop(account_drop_request);
54 
55 	hook_add_db_write(write_asreqdb);
56 	db_register_type_handler("AR", db_h_ar);
57 
58 	if (announcesvs == NULL)
59 		return;
60 
61 	service_bind_command(announcesvs, &as_help);
62 	service_bind_command(announcesvs, &as_request);
63 	service_bind_command(announcesvs, &as_waiting);
64 	service_bind_command(announcesvs, &as_reject);
65 	service_bind_command(announcesvs, &as_activate);
66 	service_bind_command(announcesvs, &as_cancel);
67 }
68 
_moddeinit(module_unload_intent_t intent)69 void _moddeinit(module_unload_intent_t intent)
70 {
71 	hook_del_user_drop(account_drop_request);
72 	hook_del_db_write(write_asreqdb);
73 	db_unregister_type_handler("AR");
74 
75 	if (announcesvs != NULL)
76 	{
77 		service_unbind_command(announcesvs, &as_help);
78 		service_unbind_command(announcesvs, &as_request);
79 		service_unbind_command(announcesvs, &as_waiting);
80 		service_unbind_command(announcesvs, &as_reject);
81 		service_unbind_command(announcesvs, &as_activate);
82 		service_unbind_command(announcesvs, &as_cancel);
83 
84 		service_delete(announcesvs);
85 		announcesvs = NULL;
86 	}
87 }
88 
write_asreqdb(database_handle_t * db)89 static void write_asreqdb(database_handle_t *db)
90 {
91 	mowgli_node_t *n;
92 
93 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
94 	{
95 		asreq_t *l = n->data;
96 
97 		db_start_row(db, "AR");
98 		db_write_word(db, l->nick);
99 		db_write_word(db, l->subject);
100 		db_write_time(db, l->announce_ts);
101 		db_write_word(db, l->creator);
102 		db_write_str(db, l->text);
103 		db_commit_row(db);
104 	}
105 
106 }
107 
db_h_ar(database_handle_t * db,const char * type)108 static void db_h_ar(database_handle_t *db, const char *type)
109 {
110 	const char *nick = db_sread_word(db);
111 	const char *subject = db_sread_word(db);
112 	time_t announce_ts = db_sread_time(db);
113 	const char *creator = db_sread_word(db);
114 	const char *text = db_sread_str(db);
115 
116 	asreq_t *l = smalloc(sizeof(asreq_t));
117 	l->nick = strshare_get(nick);
118 	l->creator = strshare_get(creator);
119 	l->subject = sstrdup(subject);
120 	l->announce_ts = announce_ts;
121 	l->text = sstrdup(text);
122 	mowgli_node_add(l, mowgli_node_create(), &as_reqlist);
123 }
124 
125 /* Properly remove announcement requests from the DB if an account is dropped */
account_drop_request(myuser_t * mu)126 static void account_drop_request(myuser_t *mu)
127 {
128 	mowgli_node_t *n;
129 	asreq_t *l;
130 
131 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
132 	{
133 		l = n->data;
134 		if (!irccasecmp(l->nick, entity(mu)->name))
135 		{
136 			slog(LG_REGISTER, "ANNOUNCEREQ:DROPACCOUNT: \2%s\2 %s\2", l->nick, l->text);
137 
138 			mowgli_node_delete(n, &as_reqlist);
139 
140 			strshare_unref(l->nick);
141 			strshare_unref(l->creator);
142 			free(l->subject);
143 			free(l->text);
144 			free(l);
145 
146 			return;
147 		}
148 	}
149 }
150 
151 /* HELP <command> [params] */
as_cmd_help(sourceinfo_t * si,int parc,char * parv[])152 void as_cmd_help(sourceinfo_t *si, int parc, char *parv[])
153 {
154 	char *command = parv[0];
155 
156 	if (!command)
157 	{
158 		command_success_nodata(si, _("***** \2%s Help\2 *****"), si->service->nick);
159 		command_success_nodata(si, _("\2%s\2 allows users to request a network announcement."), si->service->nick);
160 		command_success_nodata(si, " ");
161 		command_success_nodata(si, _("For more information on a command, type:"));
162 		command_success_nodata(si, "\2/%s%s help <command>\2", (ircd->uses_rcommand == false) ? "msg " : "", si->service->disp);
163 		command_success_nodata(si, " ");
164 
165 		command_help(si, si->service->commands);
166 
167 		command_success_nodata(si, _("***** \2End of Help\2 *****"));
168 		return;
169 	}
170 
171 	/* take the command through the hash table */
172 	help_display(si, si->service, command, si->service->commands);
173 }
174 
as_cmd_request(sourceinfo_t * si,int parc,char * parv[])175 static void as_cmd_request(sourceinfo_t *si, int parc, char *parv[])
176 {
177 	char *subject = parv[0];
178 	char *text = parv[1];
179 	stringref target;
180 	char *subject2;
181 	char buf [BUFSIZE];
182 	mowgli_node_t *n;
183 	asreq_t *l;
184 
185 	if (!text || !subject)
186 	{
187 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "REQUEST");
188 		command_fail(si, fault_needmoreparams, _("Syntax: REQUEST <subject> <text>"));
189 		return;
190 	}
191 
192 	if (metadata_find(si->smu, "private:restrict:setter"))
193 	{
194 		command_fail(si, fault_noprivs, _("You have been restricted from requesting announcements by network staff."));
195 		return;
196 	}
197 
198 	target = entity(si->smu)->name;
199 
200 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
201 	{
202 		l = n->data;
203 
204 		if (!irccasecmp(l->nick, target))
205 		{
206 			command_fail(si, fault_badparams, _("You cannot request more than one announcement. Use CANCEL if you wish to cancel your current announcement and submit another."));
207 			return;
208 		}
209 	}
210 
211 	/* Check the subject for being too long as well. 35 chars is probably a safe limit here.
212 	 * Used here because we don't want users that don't know any better making too-long messages.
213 	 */
214 	if (strlen(subject) > 35)
215 	{
216 		command_fail(si, fault_badparams, _("Your subject is too long. Subjects need to be under 35 characters."));
217 		return;
218 	}
219 
220 	/* Check if the announcement is too long or not. 450 characters is safe for our usecase */
221 	if (strlen(text) > 450)
222 	{
223 		command_fail(si, fault_badparams, _("Your announcement is too long. Announcements need to be under 450 characters."));
224 		return;
225 	}
226 
227 	snprintf(buf, BUFSIZE, "%s", text);
228 
229 	l = smalloc(sizeof(asreq_t));
230 	l->nick = strshare_ref(target);
231 	l->subject = sstrdup(subject);
232 	l->announce_ts = CURRTIME;;
233 	l->creator = strshare_ref(target);
234 	l->text = sstrdup(buf);
235 
236 	n = mowgli_node_create();
237 	mowgli_node_add(l, n, &as_reqlist);
238 
239 	subject2 = sstrdup(l->subject);
240 	/* This doesn't need to be as efficient as InfoServ, so let's just use replace() */
241 	replace(subject2, BUFSIZE, "_", " ");
242 
243 	command_success_nodata(si, _("You have requested the following announcement: "));
244 	command_success_nodata(si, _("[%s - %s] %s"), subject2, l->creator, buf);
245 	/* This is kind of hacky, and the slog will come from operserv, not announceserv.
246 	 * Still, it's required so the message will cut off properly, and I can't find a less hacky way to do it. */
247 	logcommand(si, CMDLOG_REQUEST, "REQUEST:");
248 	slog(CMDLOG_REQUEST, "[%s - %s] %s", subject2, l->creator, buf);
249 	free(subject2);
250 
251 	return;
252 }
253 
as_cmd_activate(sourceinfo_t * si,int parc,char * parv[])254 static void as_cmd_activate(sourceinfo_t *si, int parc, char *parv[])
255 {
256 	char *nick = parv[0];
257 	user_t *u;
258 	char *subject2;
259 	char buf[BUFSIZE];
260 	asreq_t *l;
261 	mowgli_node_t *n;
262 
263 	if (!nick)
264 	{
265 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACTIVATE");
266 		command_fail(si, fault_needmoreparams, _("Syntax: ACTIVATE <nick>"));
267 		return;
268 	}
269 
270 
271 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
272 	{
273 		l = n->data;
274 		if (!irccasecmp(l->nick, nick))
275 		{
276 			if ((u = user_find_named(nick)) != NULL)
277 				notice(si->service->nick, u->nick, "[auto memo] Your requested announcement has been approved.");
278 			subject2 = sstrdup(l->subject);
279 			replace(subject2, BUFSIZE, "_", " ");
280 			logcommand(si, CMDLOG_REQUEST, "ACTIVATE: \2%s\2", nick);
281 			snprintf(buf, BUFSIZE, "[%s - %s] %s", subject2, l->creator, l->text);
282 
283 			mowgli_node_delete(n, &as_reqlist);
284 
285 			free(subject2);
286 			strshare_unref(l->nick);
287 			free(l->subject);
288 			strshare_unref(l->creator);
289 			free(l->text);
290 			free(l);
291 
292 			notice_global_sts(si->service->me, "*", buf);
293 			return;
294 		}
295 	}
296 	command_success_nodata(si, _("Nick \2%s\2 not found in announce request database."), nick);
297 }
298 
as_cmd_reject(sourceinfo_t * si,int parc,char * parv[])299 static void as_cmd_reject(sourceinfo_t *si, int parc, char *parv[])
300 {
301 	char *nick = parv[0];
302 	user_t *u;
303 	asreq_t *l;
304 	mowgli_node_t *n;
305 
306 	if (!nick)
307 	{
308 		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "REJECT");
309 		command_fail(si, fault_needmoreparams, _("Syntax: REJECT <nick>"));
310 		return;
311 	}
312 
313 
314 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
315 	{
316 		l = n->data;
317 		if (!irccasecmp(l->nick, nick))
318 		{
319 			if ((u = user_find_named(nick)) != NULL)
320 				notice(si->service->nick, u->nick, "[auto memo] Your requested announcement has been rejected.");
321 			logcommand(si, CMDLOG_REQUEST, "REJECT: \2%s\2", nick);
322 
323 			mowgli_node_delete(n, &as_reqlist);
324 			strshare_unref(l->nick);
325 			free(l->subject);
326 			strshare_unref(l->creator);
327 			free(l->text);
328 			free(l);
329 			return;
330 		}
331 	}
332 	command_success_nodata(si, _("Nick \2%s\2 not found in announcement request database."), nick);
333 }
334 
as_cmd_waiting(sourceinfo_t * si,int parc,char * parv[])335 static void as_cmd_waiting(sourceinfo_t *si, int parc, char *parv[])
336 {
337 	asreq_t *l;
338 	mowgli_node_t *n;
339 	char *subject2;
340 	char buf[BUFSIZE];
341 	struct tm tm;
342 
343 	MOWGLI_LIST_FOREACH(n, as_reqlist.head)
344 	{
345 		l = n->data;
346 
347 		tm = *localtime(&l->announce_ts);
348 		strftime(buf, BUFSIZE, TIME_FORMAT, &tm);
349 		subject2 = sstrdup(l->subject);
350 		replace(subject2, BUFSIZE, "_", " ");
351 		/* This needs to be two lines for cutoff purposes */
352 		command_success_nodata(si, "Account:\2%s\2, Subject: %s, Requested On: \2%s\2, Announcement:",
353 			l->nick, subject2, buf);
354 		command_success_nodata(si, "%s", l->text);
355 		free(subject2);
356 	}
357 	command_success_nodata(si, "End of list.");
358 	logcommand(si, CMDLOG_GET, "WAITING");
359 }
360 
as_cmd_cancel(sourceinfo_t * si,int parc,char * parv[])361 static void as_cmd_cancel(sourceinfo_t *si, int parc, char *parv[])
362 {
363 	asreq_t *l;
364 	mowgli_node_t *n;
365 	stringref target;
366 
367 	target = entity(si->smu)->name;
368 
369         MOWGLI_LIST_FOREACH(n, as_reqlist.head)
370         {
371                 l = n->data;
372 
373                 if (!irccasecmp(l->nick, target))
374 		{
375 			mowgli_node_delete(n, &as_reqlist);
376 
377 			strshare_unref(l->nick);
378 			strshare_unref(l->creator);
379 			free(l->subject);
380 			free(l->text);
381 			free(l);
382 
383 			command_success_nodata(si, "Your pending announcement has been canceled.");
384 
385 			logcommand(si, CMDLOG_REQUEST, "CANCEL");
386 			return;
387                 }
388         }
389 	command_fail(si, fault_badparams, _("You do not have a pending announcement to cancel."));
390 }
391 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
392  * vim:ts=8
393  * vim:sw=8
394  * vim:noexpandtab
395  */
396