/* * Copyright (c) 2005 Atheme Development Group * Rights to this code are documented in doc/LICENSE. * * This file contains the main() routine. */ #include "atheme-compat.h" DECLARE_MODULE_V1 ( "contrib/ircd_announceserv", false, _modinit, _moddeinit, PACKAGE_STRING, "JD and Taros" ); service_t *announcesvs; static void as_cmd_help(sourceinfo_t *si, int parc, char *parv[]); static void account_drop_request(myuser_t *mu); static void as_cmd_request(sourceinfo_t *si, int parc, char *parv[]); static void as_cmd_waiting(sourceinfo_t *si, int parc, char *parv[]); static void as_cmd_reject(sourceinfo_t *si, int parc, char *parv[]); static void as_cmd_activate(sourceinfo_t *si, int parc, char *parv[]); static void as_cmd_cancel(sourceinfo_t *si, int parc, char *parv[]); static void write_asreqdb(database_handle_t *db); static void db_h_ar(database_handle_t *db, const char *type); command_t as_help = { "HELP", N_(N_("Displays contextual help information.")), AC_NONE, 2, as_cmd_help, { .path = "help/help" } }; command_t as_request = { "REQUEST", N_("Requests new announcement."), AC_AUTHENTICATED, 2, as_cmd_request, { .path = "contrib/as_request" } }; command_t as_waiting = { "WAITING", N_("Lists announcements currently waiting for activation."), PRIV_GLOBAL, 1, as_cmd_waiting, { .path = "contrib/as_waiting" } }; command_t as_reject = { "REJECT", N_("Reject the requested announcement for the given nick."), PRIV_GLOBAL, 2, as_cmd_reject, { .path = "contrib/as_reject" } }; command_t as_activate = { "ACTIVATE", N_("Activate the requested announcement for a given nick."), PRIV_GLOBAL, 2, as_cmd_activate, { .path = "contrib/as_activate" } }; command_t as_cancel = { "CANCEL", N_("Cancels your requested announcement."), AC_AUTHENTICATED, 0, as_cmd_cancel, { .path = "contrib/as_cancel" } }; struct asreq_ { stringref nick; char *subject; time_t announce_ts; stringref creator; char *text; }; typedef struct asreq_ asreq_t; mowgli_list_t as_reqlist; void _modinit(module_t *m) { announcesvs = service_add("announceserv", NULL); hook_add_event("user_drop"); hook_add_user_drop(account_drop_request); hook_add_db_write(write_asreqdb); db_register_type_handler("AR", db_h_ar); if (announcesvs == NULL) return; service_bind_command(announcesvs, &as_help); service_bind_command(announcesvs, &as_request); service_bind_command(announcesvs, &as_waiting); service_bind_command(announcesvs, &as_reject); service_bind_command(announcesvs, &as_activate); service_bind_command(announcesvs, &as_cancel); } void _moddeinit(module_unload_intent_t intent) { hook_del_user_drop(account_drop_request); hook_del_db_write(write_asreqdb); db_unregister_type_handler("AR"); if (announcesvs != NULL) { service_unbind_command(announcesvs, &as_help); service_unbind_command(announcesvs, &as_request); service_unbind_command(announcesvs, &as_waiting); service_unbind_command(announcesvs, &as_reject); service_unbind_command(announcesvs, &as_activate); service_unbind_command(announcesvs, &as_cancel); service_delete(announcesvs); announcesvs = NULL; } } static void write_asreqdb(database_handle_t *db) { mowgli_node_t *n; MOWGLI_LIST_FOREACH(n, as_reqlist.head) { asreq_t *l = n->data; db_start_row(db, "AR"); db_write_word(db, l->nick); db_write_word(db, l->subject); db_write_time(db, l->announce_ts); db_write_word(db, l->creator); db_write_str(db, l->text); db_commit_row(db); } } static void db_h_ar(database_handle_t *db, const char *type) { const char *nick = db_sread_word(db); const char *subject = db_sread_word(db); time_t announce_ts = db_sread_time(db); const char *creator = db_sread_word(db); const char *text = db_sread_str(db); asreq_t *l = smalloc(sizeof(asreq_t)); l->nick = strshare_get(nick); l->creator = strshare_get(creator); l->subject = sstrdup(subject); l->announce_ts = announce_ts; l->text = sstrdup(text); mowgli_node_add(l, mowgli_node_create(), &as_reqlist); } /* Properly remove announcement requests from the DB if an account is dropped */ static void account_drop_request(myuser_t *mu) { mowgli_node_t *n; asreq_t *l; MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; if (!irccasecmp(l->nick, entity(mu)->name)) { slog(LG_REGISTER, "ANNOUNCEREQ:DROPACCOUNT: \2%s\2 %s\2", l->nick, l->text); mowgli_node_delete(n, &as_reqlist); strshare_unref(l->nick); strshare_unref(l->creator); free(l->subject); free(l->text); free(l); return; } } } /* HELP [params] */ void as_cmd_help(sourceinfo_t *si, int parc, char *parv[]) { char *command = parv[0]; if (!command) { command_success_nodata(si, _("***** \2%s Help\2 *****"), si->service->nick); command_success_nodata(si, _("\2%s\2 allows users to request a network announcement."), si->service->nick); command_success_nodata(si, " "); command_success_nodata(si, _("For more information on a command, type:")); command_success_nodata(si, "\2/%s%s help \2", (ircd->uses_rcommand == false) ? "msg " : "", si->service->disp); command_success_nodata(si, " "); command_help(si, si->service->commands); command_success_nodata(si, _("***** \2End of Help\2 *****")); return; } /* take the command through the hash table */ help_display(si, si->service, command, si->service->commands); } static void as_cmd_request(sourceinfo_t *si, int parc, char *parv[]) { char *subject = parv[0]; char *text = parv[1]; stringref target; char *subject2; char buf [BUFSIZE]; mowgli_node_t *n; asreq_t *l; if (!text || !subject) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "REQUEST"); command_fail(si, fault_needmoreparams, _("Syntax: REQUEST ")); return; } if (metadata_find(si->smu, "private:restrict:setter")) { command_fail(si, fault_noprivs, _("You have been restricted from requesting announcements by network staff.")); return; } target = entity(si->smu)->name; MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; if (!irccasecmp(l->nick, target)) { 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.")); return; } } /* Check the subject for being too long as well. 35 chars is probably a safe limit here. * Used here because we don't want users that don't know any better making too-long messages. */ if (strlen(subject) > 35) { command_fail(si, fault_badparams, _("Your subject is too long. Subjects need to be under 35 characters.")); return; } /* Check if the announcement is too long or not. 450 characters is safe for our usecase */ if (strlen(text) > 450) { command_fail(si, fault_badparams, _("Your announcement is too long. Announcements need to be under 450 characters.")); return; } snprintf(buf, BUFSIZE, "%s", text); l = smalloc(sizeof(asreq_t)); l->nick = strshare_ref(target); l->subject = sstrdup(subject); l->announce_ts = CURRTIME;; l->creator = strshare_ref(target); l->text = sstrdup(buf); n = mowgli_node_create(); mowgli_node_add(l, n, &as_reqlist); subject2 = sstrdup(l->subject); /* This doesn't need to be as efficient as InfoServ, so let's just use replace() */ replace(subject2, BUFSIZE, "_", " "); command_success_nodata(si, _("You have requested the following announcement: ")); command_success_nodata(si, _("[%s - %s] %s"), subject2, l->creator, buf); /* This is kind of hacky, and the slog will come from operserv, not announceserv. * Still, it's required so the message will cut off properly, and I can't find a less hacky way to do it. */ logcommand(si, CMDLOG_REQUEST, "REQUEST:"); slog(CMDLOG_REQUEST, "[%s - %s] %s", subject2, l->creator, buf); free(subject2); return; } static void as_cmd_activate(sourceinfo_t *si, int parc, char *parv[]) { char *nick = parv[0]; user_t *u; char *subject2; char buf[BUFSIZE]; asreq_t *l; mowgli_node_t *n; if (!nick) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACTIVATE"); command_fail(si, fault_needmoreparams, _("Syntax: ACTIVATE ")); return; } MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; if (!irccasecmp(l->nick, nick)) { if ((u = user_find_named(nick)) != NULL) notice(si->service->nick, u->nick, "[auto memo] Your requested announcement has been approved."); subject2 = sstrdup(l->subject); replace(subject2, BUFSIZE, "_", " "); logcommand(si, CMDLOG_REQUEST, "ACTIVATE: \2%s\2", nick); snprintf(buf, BUFSIZE, "[%s - %s] %s", subject2, l->creator, l->text); mowgli_node_delete(n, &as_reqlist); free(subject2); strshare_unref(l->nick); free(l->subject); strshare_unref(l->creator); free(l->text); free(l); notice_global_sts(si->service->me, "*", buf); return; } } command_success_nodata(si, _("Nick \2%s\2 not found in announce request database."), nick); } static void as_cmd_reject(sourceinfo_t *si, int parc, char *parv[]) { char *nick = parv[0]; user_t *u; asreq_t *l; mowgli_node_t *n; if (!nick) { command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "REJECT"); command_fail(si, fault_needmoreparams, _("Syntax: REJECT ")); return; } MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; if (!irccasecmp(l->nick, nick)) { if ((u = user_find_named(nick)) != NULL) notice(si->service->nick, u->nick, "[auto memo] Your requested announcement has been rejected."); logcommand(si, CMDLOG_REQUEST, "REJECT: \2%s\2", nick); mowgli_node_delete(n, &as_reqlist); strshare_unref(l->nick); free(l->subject); strshare_unref(l->creator); free(l->text); free(l); return; } } command_success_nodata(si, _("Nick \2%s\2 not found in announcement request database."), nick); } static void as_cmd_waiting(sourceinfo_t *si, int parc, char *parv[]) { asreq_t *l; mowgli_node_t *n; char *subject2; char buf[BUFSIZE]; struct tm tm; MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; tm = *localtime(&l->announce_ts); strftime(buf, BUFSIZE, TIME_FORMAT, &tm); subject2 = sstrdup(l->subject); replace(subject2, BUFSIZE, "_", " "); /* This needs to be two lines for cutoff purposes */ command_success_nodata(si, "Account:\2%s\2, Subject: %s, Requested On: \2%s\2, Announcement:", l->nick, subject2, buf); command_success_nodata(si, "%s", l->text); free(subject2); } command_success_nodata(si, "End of list."); logcommand(si, CMDLOG_GET, "WAITING"); } static void as_cmd_cancel(sourceinfo_t *si, int parc, char *parv[]) { asreq_t *l; mowgli_node_t *n; stringref target; target = entity(si->smu)->name; MOWGLI_LIST_FOREACH(n, as_reqlist.head) { l = n->data; if (!irccasecmp(l->nick, target)) { mowgli_node_delete(n, &as_reqlist); strshare_unref(l->nick); strshare_unref(l->creator); free(l->subject); free(l->text); free(l); command_success_nodata(si, "Your pending announcement has been canceled."); logcommand(si, CMDLOG_REQUEST, "CANCEL"); return; } } command_fail(si, fault_badparams, _("You do not have a pending announcement to cancel.")); } /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs * vim:ts=8 * vim:sw=8 * vim:noexpandtab */