6b260508c719b34922282b2568b72e69c5277aeb
[srvx.git] / src / mod-memoserv.c
1 /* mod-memoserv.c - MemoServ module for srvx
2  * Copyright 2003-2004 Martijn Smit and srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 /* Adds new section to srvx.conf:
22  * "modules" {
23  *     "memoserv" {
24  *         "bot" "NickServ";
25  *         "message_expiry" "30d"; // age when messages are deleted; set
26  *                                 // to 0 to disable message expiration
27  *     };
28  *  };
29  *
30  * After that, to make the module active on an existing bot:
31  * /msg opserv bind nickserv * *memoserv.*
32  *
33  * If you want a dedicated MemoServ bot, make sure the service control
34  * commands are bound to OpServ:
35  * /msg opserv bind opserv service *modcmd.joiner
36  * /msg opserv bind opserv service\ add *modcmd.service\ add
37  * /msg opserv bind opserv service\ rename *modcmd.service\ rename
38  * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
39  * /msg opserv bind opserv service\ remove *modcmd.service\ remove
40  * Add the bot:
41  * /msg opserv service add MemoServ User-to-user Memorandum Service
42  * /msg opserv bind memoserv help *modcmd.help
43  * Restart srvx with the updated conf file (as above, butwith "bot"
44  * "MemoServ"), and bind the commands to it:
45  * /msg opserv bind memoserv * *memoserv.*
46  * /msg opserv bind memoserv set *modcmd.joiner
47  */
48
49 #include "chanserv.h"
50 #include "conf.h"
51 #include "modcmd.h"
52 #include "saxdb.h"
53 #include "timeq.h"
54
55 #define KEY_SENT "sent"
56 #define KEY_RECIPIENT "to"
57 #define KEY_FROM "from"
58 #define KEY_MESSAGE "msg"
59 #undef KEY_READ /* thanks microsoft! */
60 #define KEY_READ "read"
61 #define KEY_ACCOUNTS "accounts"
62 #define KEY_MESSAGES "messages"
63
64 static const struct message_entry msgtab[] = {
65     { "MSMSG_CANNOT_SEND_SELF", "You cannot send to yourself." },
66     { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
67     { "MSMSG_MEMO_SENT", "Message sent to $b%s$b." },
68     { "MSMSG_NO_MESSAGES", "You have no messages." },
69     { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
70     { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse /msg $S READ <ID> to read a message." },
71     { "MSMSG_LIST_HEAD", "$bID$b   $bFrom$b       $bTime Sent$b" },
72     { "MSMSG_LIST_FORMAT", "%-2u     %s           %s" },
73     { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
74     { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
75     { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
76     { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
77     { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
78     { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
79     { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
80     { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages.  Use /msg $S LIST to list them." },
81     { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b (ID $b%d$b)." },
82     { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
83     { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
84     { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
85     { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
86     { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
87     { "MSMSG_SET_NOTIFY",     "$bNotify:       $b %s" },
88     { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify:   $b %s" },
89     { "MSMSG_SET_PRIVATE",    "$bPrivate:      $b %s" },
90     { NULL, NULL }
91 };
92
93 struct memo {
94     struct memo_account *recipient;
95     struct memo_account *sender;
96     char *message;
97     unsigned long sent;
98     unsigned int is_read : 1;
99 };
100
101 DECLARE_LIST(memoList, struct memo*);
102 DEFINE_LIST(memoList, struct memo*)
103
104 /* memo_account.flags fields */
105 #define MEMO_NOTIFY_NEW   1
106 #define MEMO_NOTIFY_LOGIN 2
107 #define MEMO_DENY_NONCHANNEL 4
108
109 struct memo_account {
110     struct handle_info *handle;
111     unsigned int flags;
112     struct memoList sent;
113     struct memoList recvd;
114 };
115
116 static struct {
117     struct userNode *bot;
118     unsigned long message_expiry;
119 } memoserv_conf;
120
121 const char *memoserv_module_deps[] = { NULL };
122 static struct module *memoserv_module;
123 static struct log_type *MS_LOG;
124 static unsigned long memoCount;
125 static unsigned long memosSent;
126 static unsigned long memosExpired;
127 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
128
129 static struct memo_account *
130 memoserv_get_account(struct handle_info *hi)
131 {
132     struct memo_account *ma;
133     if (!hi)
134         return NULL;
135     ma = dict_find(memos, hi->handle, NULL);
136     if (ma)
137         return ma;
138     ma = calloc(1, sizeof(*ma));
139     if (!ma)
140         return ma;
141     ma->handle = hi;
142     ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
143     dict_insert(memos, ma->handle->handle, ma);
144     return ma;
145 }
146
147 static void
148 delete_memo(struct memo *memo)
149 {
150     memoList_remove(&memo->recipient->recvd, memo);
151     memoList_remove(&memo->sender->sent, memo);
152     free(memo->message);
153     free(memo);
154     memoCount--;
155 }
156
157 static void
158 delete_memo_account(void *data)
159 {
160     struct memo_account *ma = data;
161
162     while (ma->recvd.used)
163         delete_memo(ma->recvd.list[0]);
164     while (ma->sent.used)
165         delete_memo(ma->sent.list[0]);
166     memoList_clean(&ma->recvd);
167     memoList_clean(&ma->sent);
168     free(ma);
169 }
170
171 void
172 do_expire(void)
173 {
174     dict_iterator_t it;
175     for (it = dict_first(memos); it; it = iter_next(it)) {
176         struct memo_account *account = iter_data(it);
177         unsigned int ii;
178         for (ii = 0; ii < account->sent.used; ++ii) {
179             struct memo *memo = account->sent.list[ii];
180             if ((now - memo->sent) > memoserv_conf.message_expiry) {
181                 delete_memo(memo);
182                 memosExpired++;
183                 ii--;
184             }
185         }
186     }
187 }
188
189 static void
190 expire_memos(UNUSED_ARG(void *data))
191 {
192     if (memoserv_conf.message_expiry) {
193         do_expire();
194         timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
195     }
196 }
197
198 static struct memo*
199 add_memo(unsigned long sent, struct memo_account *recipient, struct memo_account *sender, char *message)
200 {
201     struct memo *memo;
202
203     memo = calloc(1, sizeof(*memo));
204     if (!memo)
205         return NULL;
206
207     memo->recipient = recipient;
208     memoList_append(&recipient->recvd, memo);
209     memo->sender = sender;
210     memoList_append(&sender->sent, memo);
211     memo->sent = sent;
212     memo->message = strdup(message);
213     memosSent++;
214     memoCount++;
215     return memo;
216 }
217
218 static int
219 memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *account)
220 {
221     extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
222     struct userData *dest;
223
224     if (!user->handle_info)
225         return 0;
226     if (user->handle_info == account->handle) {
227         send_message(user, bot, "MSMSG_CANNOT_SEND_SELF");
228         return 0;
229     }
230     if (!(account->flags & MEMO_DENY_NONCHANNEL))
231         return 1;
232     for (dest = account->handle->channels; dest; dest = dest->u_next) {
233         struct userData *recip = _GetChannelUser(dest->channel, user->handle_info, 1, 0);
234         if (recip != NULL && recip->seen != 0)
235             return 1;
236     }
237     send_message(user, bot, "MSMSG_CANNOT_SEND", account->handle->handle);
238     return 0;
239 }
240
241 static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
242 {
243     unsigned int memoid;
244     if (!isdigit(msgid[0])) {
245         if (ma->recvd.used)
246             reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
247         else
248             reply("MSMSG_NO_MESSAGES");
249         return NULL;
250     }
251     memoid = atoi(msgid);
252     if (memoid >= ma->recvd.used) {
253         reply("MSMSG_NO_SUCH_MEMO");
254         return NULL;
255     }
256     return ma->recvd.list[*id = memoid];
257 }
258
259 static MODCMD_FUNC(cmd_send)
260 {
261     char *message;
262     struct handle_info *hi;
263     struct memo_account *ma, *sender;
264
265     if (!(hi = modcmd_get_handle_info(user, argv[1])))
266         return 0;
267     if (!(sender = memoserv_get_account(user->handle_info))
268         || !(ma = memoserv_get_account(hi))) {
269         reply("MSG_INTERNAL_FAILURE");
270         return 0;
271     }
272     if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
273         return 0;
274     message = unsplit_string(argv + 2, argc - 2, NULL);
275     add_memo(now, ma, sender, message);
276     if (ma->flags & MEMO_NOTIFY_NEW) {
277         struct userNode *other;
278         for (other = ma->handle->users; other; other = other->next_authed)
279             send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick, ma->recvd.used - 1);
280     }
281     reply("MSMSG_MEMO_SENT", ma->handle->handle);
282     return 1;
283 }
284
285 static MODCMD_FUNC(cmd_list)
286 {
287     struct memo_account *ma;
288     struct memo *memo;
289     unsigned int ii;
290     char posted[24];
291     time_t feh;
292
293     if (!(ma = memoserv_get_account(user->handle_info)))
294         return 0;
295     reply("MSMSG_LIST_HEAD");
296     for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
297         memo = ma->recvd.list[ii];
298         feh = memo->sent;
299         strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&feh));
300         reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, posted);
301     }
302     if (ii == 0)
303         reply("MSG_NONE");
304     else if (ii == 15)
305         reply("MSMSG_CLEAN_INBOX", ii);
306     else
307         reply("MSMSG_MEMOS_FOUND", ii);
308     return 1;
309 }
310
311 static MODCMD_FUNC(cmd_read)
312 {
313     struct memo_account *ma;
314     unsigned int memoid;
315     struct memo *memo;
316     char posted[24];
317     time_t memo_sent;
318
319     if (!(ma = memoserv_get_account(user->handle_info)))
320         return 0;
321     if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
322         return 0;
323     memo_sent = memo->sent;
324     strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&memo_sent));
325     reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
326     send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
327     memo->is_read = 1;
328     return 1;
329 }
330
331 static MODCMD_FUNC(cmd_delete)
332 {
333     struct memo_account *ma;
334     struct memo *memo;
335     unsigned int memoid;
336
337     if (!(ma = memoserv_get_account(user->handle_info)))
338         return 0;
339     if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
340         if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
341             reply("MSMSG_USE_CONFIRM");
342             return 0;
343         }
344         while (ma->recvd.used)
345             delete_memo(ma->recvd.list[0]);
346         reply("MSMSG_DELETED_ALL");
347         return 1;
348     }
349
350     if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
351         return 0;
352     delete_memo(memo);
353     reply("MSMSG_MEMO_DELETED", memoid);
354     return 1;
355 }
356
357 static MODCMD_FUNC(cmd_expire)
358 {
359     unsigned long old_expired = memosExpired;
360     do_expire();
361     reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
362     return 1;
363 }
364
365 static MODCMD_FUNC(cmd_expiry)
366 {
367     char interval[INTERVALLEN];
368
369     if (!memoserv_conf.message_expiry) {
370         reply("MSMSG_EXPIRY_OFF");
371         return 1;
372     }
373
374     intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
375     reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
376     return 1;
377 }
378
379 static MODCMD_FUNC(cmd_set_notify)
380 {
381     struct memo_account *ma;
382     char *choice;
383
384     if (!(ma = memoserv_get_account(user->handle_info)))
385         return 0;
386     if (argc > 1) {
387         choice = argv[1];
388         if (enabled_string(choice)) {
389             ma->flags |= MEMO_NOTIFY_NEW;
390         } else if (disabled_string(choice)) {
391             ma->flags &= ~MEMO_NOTIFY_NEW;
392         } else {
393             reply("MSG_INVALID_BINARY", choice);
394             return 0;
395         }
396     }
397
398     choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
399     reply("MSMSG_SET_NOTIFY", choice);
400     return 1;
401 }
402
403 static MODCMD_FUNC(cmd_set_authnotify)
404 {
405     struct memo_account *ma;
406     char *choice;
407
408     if (!(ma = memoserv_get_account(user->handle_info)))
409         return 0;
410     if (argc > 1) {
411         choice = argv[1];
412         if (enabled_string(choice)) {
413             ma->flags |= MEMO_NOTIFY_LOGIN;
414         } else if (disabled_string(choice)) {
415             ma->flags &= ~MEMO_NOTIFY_LOGIN;
416         } else {
417             reply("MSG_INVALID_BINARY", choice);
418             return 0;
419         }
420     }
421
422     choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
423     reply("MSMSG_SET_AUTHNOTIFY", choice);
424     return 1;
425 }
426
427 static MODCMD_FUNC(cmd_set_private)
428 {
429     struct memo_account *ma;
430     char *choice;
431
432     if (!(ma = memoserv_get_account(user->handle_info)))
433         return 0;
434     if (argc > 1) {
435         choice = argv[1];
436         if (enabled_string(choice)) {
437             ma->flags |= MEMO_DENY_NONCHANNEL;
438         } else if (disabled_string(choice)) {
439             ma->flags &= ~MEMO_DENY_NONCHANNEL;
440         } else {
441             reply("MSG_INVALID_BINARY", choice);
442             return 0;
443         }
444     }
445
446     choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
447     reply("MSMSG_SET_PRIVATE", choice);
448     return 1;
449 }
450
451 static MODCMD_FUNC(cmd_status)
452 {
453     reply("MSMSG_STATUS_TOTAL", memoCount);
454     reply("MSMSG_STATUS_EXPIRED", memosExpired);
455     reply("MSMSG_STATUS_SENT", memosSent);
456     return 1;
457 }
458
459 static void
460 memoserv_conf_read(void)
461 {
462     dict_t conf_node;
463     const char *str;
464
465     str = "modules/memoserv";
466     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
467         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
468         return;
469     }
470
471     str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
472     memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
473 }
474
475 static int
476 memoserv_saxdb_read_messages(struct dict *db)
477 {
478     char *str;
479     struct handle_info *sender, *recipient;
480     struct record_data *hir;
481     struct memo *memo;
482     dict_iterator_t it;
483     unsigned long sent;
484
485     for (it = dict_first(db); it; it = iter_next(it)) {
486         hir = iter_data(it);
487         if (hir->type != RECDB_OBJECT) {
488             log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
489             continue;
490         }
491
492         if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
493             log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", iter_key(it));
494             continue;
495         }
496         sent = strtoul(str, NULL, 0);
497
498         if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
499             log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", iter_key(it));
500             continue;
501         } else if (!(recipient = get_handle_info(str))) {
502             log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, iter_key(it));
503             continue;
504         }
505
506         if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
507             log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", iter_key(it));
508             continue;
509         } else if (!(sender = get_handle_info(str))) {
510             log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, iter_key(it));
511             continue;
512         }
513
514         if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
515             log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", iter_key(it));
516             continue;
517         }
518
519         memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str);
520         if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
521             memo->is_read = 1;
522     }
523     return 0;
524 }
525
526 static void
527 memoserv_saxdb_read_accounts(struct dict *db)
528 {
529     struct memo_account *ma;
530     struct handle_info *hi;
531     struct record_data *rd;
532     dict_iterator_t it;
533     const char *str;
534
535     for (it = dict_first(db); it; it = iter_next(it)) {
536         hi = get_handle_info(iter_key(it));
537         if (hi == NULL) {
538             log_module(MS_LOG, LOG_WARNING, "No account known for %s.", iter_key(it));
539             continue;
540         }
541
542         ma = memoserv_get_account(hi);
543         if (ma == NULL) {
544             log_module(MS_LOG, LOG_WARNING, "Unable to allocate memory for account %s.", iter_key(it));
545             continue;
546         }
547
548         rd = iter_data(it);
549         if (rd->type == RECDB_QSTRING) {
550             str = rd->d.qstring;
551         } else {
552             log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for accounts/%s.", rd->type, iter_key(it));
553             continue;
554         }
555
556         if (str != NULL)
557             ma->flags = strtol(str, NULL, 0);
558     }
559 }
560
561 static int
562 memoserv_saxdb_read(struct dict *db)
563 {
564     struct dict *obj;
565
566     obj = database_get_data(db, KEY_ACCOUNTS, RECDB_OBJECT);
567     if (obj == NULL) {
568         return memoserv_saxdb_read_messages(db);
569     } else {
570         memoserv_saxdb_read_accounts(obj);
571         obj = database_get_data(db, KEY_MESSAGES, RECDB_OBJECT);
572         return memoserv_saxdb_read_messages(obj);
573     }
574 }
575
576 static int
577 memoserv_saxdb_write(struct saxdb_context *ctx)
578 {
579     dict_iterator_t it;
580     struct memo_account *ma;
581     struct memo *memo;
582     char str[17];
583     unsigned int id = 0, ii;
584
585     saxdb_start_record(ctx, "accounts", 1);
586     for (it = dict_first(memos); it; it = iter_next(it)) {
587         ma = iter_data(it);
588         saxdb_write_int(ctx, ma->handle->handle, ma->flags);
589     }
590     saxdb_end_record(ctx);
591
592     saxdb_start_record(ctx, "messages", 1);
593     for (it = dict_first(memos); it; it = iter_next(it)) {
594         ma = iter_data(it);
595         for (ii = 0; ii < ma->recvd.used; ++ii) {
596             memo = ma->recvd.list[ii];
597             snprintf(str, sizeof(str), "%x", id++);
598             saxdb_start_record(ctx, str, 0);
599             saxdb_write_int(ctx, KEY_SENT, memo->sent);
600             saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
601             saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
602             saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
603             if (memo->is_read)
604                 saxdb_write_int(ctx, KEY_READ, 1);
605             saxdb_end_record(ctx);
606         }
607     }
608     saxdb_end_record(ctx);
609
610     return 0;
611 }
612
613 static void
614 memoserv_cleanup(void)
615 {
616     dict_delete(memos);
617 }
618
619 static void
620 memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
621 {
622     unsigned int ii, unseen;
623     struct memo_account *ma;
624     struct memo *memo;
625
626     if (!(ma = memoserv_get_account(user->handle_info))
627         || !(ma->flags & MEMO_NOTIFY_LOGIN))
628         return;
629     for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
630         memo = ma->recvd.list[ii];
631         if (!memo->is_read)
632             unseen++;
633     }
634     if (ma->recvd.used && memoserv_conf.bot)
635         send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
636 }
637
638 static void
639 memoserv_rename_account(struct handle_info *hi, const char *old_handle)
640 {
641     struct memo_account *ma;
642     if (!(ma = dict_find(memos, old_handle, NULL)))
643         return;
644     dict_remove2(memos, old_handle, 1);
645     dict_insert(memos, hi->handle, ma);
646 }
647
648 static void
649 memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
650 {
651     dict_remove(memos, handle->handle);
652 }
653
654 int
655 memoserv_init(void)
656 {
657     MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
658     memos = dict_new();
659     dict_set_free_data(memos, delete_memo_account);
660     reg_auth_func(memoserv_check_messages);
661     reg_handle_rename_func(memoserv_rename_account);
662     reg_unreg_func(memoserv_unreg_account);
663     conf_register_reload(memoserv_conf_read);
664     reg_exit_func(memoserv_cleanup);
665     saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
666
667     memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
668     modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
669     modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
670     modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
671     modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
672     modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
673     modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
674     modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
675     modcmd_register(memoserv_module, "set notify", cmd_set_notify, 1, 0, NULL);
676     modcmd_register(memoserv_module, "set authnotify", cmd_set_authnotify, 1, 0, NULL);
677     modcmd_register(memoserv_module, "set private", cmd_set_private, 1, 0, NULL);
678     message_register_table(msgtab);
679
680     if (memoserv_conf.message_expiry)
681         timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
682     return 1;
683 }
684
685 int
686 memoserv_finalize(void) {
687     dict_t conf_node;
688     const char *str;
689
690     str = "modules/memoserv";
691     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
692         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
693         return 0;
694     }
695
696     str = database_get_data(conf_node, "bot", RECDB_QSTRING);
697     if (str)
698         memoserv_conf.bot = GetUserH(str);
699     return 1;
700 }