]> jfr.im git - irc/evilnet/x3.git/blame_incremental - src/mod-memoserv.c
crash fix
[irc/evilnet/x3.git] / src / mod-memoserv.c
... / ...
CommitLineData
1/* mod-memoserv.c - MemoServ module for srvx
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
3 * Copyright 2005-2006 X3 Development Team
4 *
5 * This file is part of x3.
6 *
7 * x3 is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with srvx; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 */
21
22/*
23 * /msg opserv bind nickserv * *memoserv.*
24 *
25 * If you want a dedicated MemoServ bot, make sure the service control
26 * commands are bound to OpServ:
27 * /msg opserv bind opserv service *modcmd.joiner
28 * /msg opserv bind opserv service\ add *modcmd.service\ add
29 * /msg opserv bind opserv service\ rename *modcmd.service\ rename
30 * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
31 * /msg opserv bind opserv service\ remove *modcmd.service\ remove
32 * Add the bot:
33 * /msg opserv service add MemoServ User-to-user Memorandum Service
34 * /msg opserv bind memoserv help *modcmd.help
35 * Restart srvx with the updated conf file (as above, butwith "bot"
36 * "MemoServ"), and bind the commands to it:
37 * /msg opserv bind memoserv * *memoserv.*
38 * /msg opserv bind memoserv set *modcmd.joiner
39 */
40
41#include "chanserv.h"
42#include "conf.h"
43#include "modcmd.h"
44#include "nickserv.h"
45#include "saxdb.h"
46#include "timeq.h"
47
48#define KEY_MAIN_ACCOUNTS "accounts"
49#define KEY_FLAGS "flags"
50#define KEY_LIMIT "limit"
51
52#define KEY_MAIN_MEMOS "memos"
53#define KEY_SENT "sent"
54#define KEY_RECIPIENT "to"
55#define KEY_FROM "from"
56#define KEY_MESSAGE "msg"
57#define KEY_READ "read"
58#define KEY_RECIEPT "reciept"
59#define KEY_ID "id"
60
61static const struct message_entry msgtab[] = {
62 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
63 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b (ID# %d)." },
64 { "MSMSG_NO_MESSAGES", "You have no messages." },
65 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
66 { "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." },
67 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
68 { "MSMSG_LIST_FORMAT", "%-2u %s $b%s$b %s" },
69 { "MSMSG_HISTORY_HEADER", "$bID$b $bTo$b $bTime Sent$b" },
70 { "MSMSG_HISTORY_FORMAT", "%-2u %s %s" },
71 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
72 { "MSMSG_MEMO_RECIEPT", "$bRead Reciept$b requested, %s." },
73 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
74 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
75 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
76 { "MSMSG_MEMO_CANCEL_NUMBER", "You must specify a number id" },
77 { "MSMSG_MEMO_DONT_OWN", "You did not send memo# %d" },
78 { "MSMSG_MEMO_READ", "Memo# %d has already been read, you cannot cancel it." },
79 { "MSMSG_MEMO_CANT_LOCATE", "Could not locate memo# %d" },
80 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
81 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
82 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
83 { "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." },
84 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. /msg $S LIST" },
85 { "MSMSG_FULL_INBOX", "$b%s$b cannot recieve anymore memos as their inbox is full" },
86 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
87 { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
88
89 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
90 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
91 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
92
93 { "MSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
94 { "MSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
95 { "MSMSG_SET_NOTIFY", "$bNotify: $b %s" },
96 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify: $b %s" },
97 { "MSMSG_SET_PRIVATE", "$bPrivate: $b %s" },
98 { "MSMSG_SET_IGNORERECIEPTS", "$bIgnoreReciepts:$b %s" },
99 { "MSMSG_SET_SENDRECIEPTS", "$bSendReciepts: $b %s" },
100 { "MSMSG_SET_LIMIT", "$bLimit: $b %d" },
101 { "MSMSG_SET_OPTIONS", "$bMessaging Options$b" },
102 { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
103
104 { "MSMSG_LIST_END", "--------------End of Memos--------------" },
105 { "MSMSG_BAR", "----------------------------------------"},
106
107 { NULL, NULL }
108};
109
110struct memo {
111 struct memo_account *recipient;
112 struct memo_account *sender;
113 char *message;
114 time_t sent;
115 unsigned long id;
116 unsigned int is_read : 1;
117 unsigned int reciept : 1;
118};
119
120DECLARE_LIST(memoList, struct memo*);
121DEFINE_LIST(memoList, struct memo*);
122
123/* memo_account.flags fields */
124#define MEMO_NOTIFY_NEW 0x00000001
125#define MEMO_NOTIFY_LOGIN 0x00000002
126#define MEMO_DENY_NONCHANNEL 0x00000004
127#define MEMO_IGNORE_RECIEPTS 0x00000008
128#define MEMO_ALWAYS_RECIEPTS 0x00000010
129
130struct memo_account {
131 struct handle_info *handle;
132 unsigned int flags;
133 unsigned int limit;
134 struct memoList sent;
135 struct memoList recvd;
136};
137
138static struct {
139 struct userNode *bot;
140 int message_expiry;
141 unsigned int limit;
142} memoserv_conf;
143
144#define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
145#define OPTION_FUNC(NAME) int NAME(struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
146typedef OPTION_FUNC(option_func_t);
147
148unsigned long memo_id;
149
150extern struct string_list *autojoin_channels;
151const char *memoserv_module_deps[] = { NULL };
152static struct module *memoserv_module;
153static struct log_type *MS_LOG;
154static unsigned long memosSent, memosExpired;
155static struct dict *memos; /* memo_account->handle->handle -> memo_account */
156static dict_t memoserv_opt_dict; /* contains option_func_t* */
157
158static struct memo_account *
159memoserv_get_account(struct handle_info *hi)
160{
161 struct memo_account *ma;
162 if (!hi)
163 return NULL;
164 ma = dict_find(memos, hi->handle, NULL);
165 if (ma)
166 return ma;
167 ma = calloc(1, sizeof(*ma));
168 if (!ma)
169 return ma;
170 ma->handle = hi;
171 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
172 ma->limit = memoserv_conf.limit;
173 dict_insert(memos, ma->handle->handle, ma);
174 return ma;
175}
176
177static void
178delete_memo(struct memo *memo)
179{
180 memoList_remove(&memo->recipient->recvd, memo);
181 memoList_remove(&memo->sender->sent, memo);
182 free(memo->message);
183 free(memo);
184}
185
186static void
187delete_memo_account(void *data)
188{
189 struct memo_account *ma = data;
190
191 while (ma->recvd.used)
192 delete_memo(ma->recvd.list[0]);
193 while (ma->sent.used)
194 delete_memo(ma->sent.list[0]);
195 memoList_clean(&ma->recvd);
196 memoList_clean(&ma->sent);
197 free(ma);
198}
199
200void
201do_expire(void)
202{
203 dict_iterator_t it;
204 for (it = dict_first(memos); it; it = iter_next(it)) {
205 struct memo_account *acct = iter_data(it);
206 unsigned int ii;
207 for (ii = 0; ii < acct->sent.used; ++ii) {
208 struct memo *memo = acct->sent.list[ii];
209 if ((now - memo->sent) > memoserv_conf.message_expiry) {
210 delete_memo(memo);
211 memosExpired++;
212 ii--;
213 }
214 }
215 }
216}
217
218static void
219expire_memos(UNUSED_ARG(void *data))
220{
221 if (memoserv_conf.message_expiry) {
222 do_expire();
223 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
224 }
225}
226
227static struct memo*
228add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message, int nfrom_read)
229{
230 struct memo *memo;
231
232 memo = calloc(1, sizeof(*memo));
233 if (!memo)
234 return NULL;
235
236 if (nfrom_read) {
237 memo_id++;
238 memo->id = memo_id;
239 }
240
241 memo->recipient = recipient;
242 memoList_append(&recipient->recvd, memo);
243 memo->sender = sender;
244 memoList_append(&sender->sent, memo);
245 memo->sent = sent;
246 memo->message = strdup(message);
247 memosSent++;
248 return memo;
249}
250
251static int
252memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *acct)
253{
254 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
255 struct userData *dest;
256
257 if (!user->handle_info)
258 return 0;
259
260 /* Sanity checks here because if the user doesnt have a limit set
261 the limit comes out at like 21233242 if you try and use it. */
262 if (acct->limit > memoserv_conf.limit)
263 acct->limit = memoserv_conf.limit;
264
265 if (acct->recvd.used > acct->limit) {
266 send_message(user, bot, "MSMSG_FULL_INBOX", acct->handle->handle);
267 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
268 return 0;
269 }
270
271 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
272 return 1;
273
274 for (dest = acct->handle->channels; dest; dest = dest->u_next)
275 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
276 return 1;
277
278 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
279 return 0;
280}
281
282static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
283{
284 unsigned int memoid;
285 if (!isdigit(msgid[0])) {
286 if (ma->recvd.used)
287 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
288 else
289 reply("MSMSG_NO_MESSAGES");
290 return NULL;
291 }
292 memoid = atoi(msgid);
293 if (memoid >= ma->recvd.used) {
294 reply("MSMSG_NO_SUCH_MEMO");
295 return NULL;
296 }
297 return ma->recvd.list[*id = memoid];
298}
299
300static MODCMD_FUNC(cmd_send)
301{
302 char *message;
303 int s = 0, brk = 0;
304 int reciept = 0, inc = 2;
305 struct handle_info *hi;
306 struct memo_account *ma, *sender;
307 struct memo *memo;
308
309 if (!(hi = modcmd_get_handle_info(user, argv[1])))
310 return 0;
311
312 if (!(sender = memoserv_get_account(user->handle_info))
313 || !(ma = memoserv_get_account(hi))) {
314 reply("MSG_INTERNAL_FAILURE");
315 return 0;
316 }
317
318 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
319 return 0;
320
321 char *flags = argv[2];
322 while (*flags) {
323 switch (*flags) {
324 case '-':
325 if (s != 0)
326 brk = 1;
327 break;
328
329 case 'r':
330 if (s > 0)
331 reciept = 1;
332 break;
333
334 default:
335 break;
336 }
337
338 if (brk == 1)
339 break;
340 else {
341 s++;
342 flags++;
343 }
344 }
345
346 if (s > 0)
347 inc = 3;
348
349 message = unsplit_string(argv + inc, argc - inc, NULL);
350 memo = add_memo(now, ma, sender, message, 1);
351 if ((reciept == 1) || (ma->flags & MEMO_ALWAYS_RECIEPTS))
352 memo->reciept = 1;
353
354 if (ma->flags & MEMO_NOTIFY_NEW) {
355 struct userNode *other;
356
357 for (other = ma->handle->users; other; other = other->next_authed)
358 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
359 }
360
361 reply("MSMSG_MEMO_SENT", ma->handle->handle, memo_id);
362 return 1;
363}
364
365static MODCMD_FUNC(cmd_list)
366{
367 struct memo_account *ma;
368 struct memo *memo;
369 unsigned int ii;
370 char posted[24];
371 struct tm tm;
372
373 if (!(ma = memoserv_get_account(user->handle_info)))
374 return 0;
375
376 reply("MSMSG_LIST_HEAD");
377
378 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
379 reply("MSMSG_BAR");
380
381 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
382 memo = ma->recvd.list[ii];
383 localtime_r(&memo->sent, &tm);
384 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
385 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, memo->reciept ? "(r)" : "", posted);
386 }
387 if (ii == 0)
388 reply("MSG_NONE");
389 else if (ii == 15)
390 reply("MSMSG_CLEAN_INBOX", ii);
391 else
392 reply("MSMSG_MEMOS_FOUND", ii);
393
394 reply("MSMSG_LIST_END");
395
396 return 1;
397}
398
399static MODCMD_FUNC(cmd_history)
400{
401 struct memo_account *ma;
402 struct memo *memo;
403 dict_iterator_t it;
404 unsigned int ii = 0;
405 unsigned int cc = 0;
406 char posted[24];
407 struct tm tm;
408
409 if (!(ma = memoserv_get_account(user->handle_info)))
410 return 0;
411
412 reply("MSMSG_HISTORY_HEADER");
413
414 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
415 reply("MSMSG_BAR");
416
417 for (it = dict_first(memos); it; it = iter_next(it)) {
418 ma = iter_data(it);
419 for (ii = 0; ii < ma->recvd.used; ++ii) {
420 memo = ma->recvd.list[ii];
421 if (!strcasecmp(memo->sender->handle->handle, user->handle_info->handle)) {
422 cc++;
423 localtime_r(&memo->sent, &tm);
424 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
425 reply("MSMSG_HISTORY_FORMAT", memo->id, memo->sender->handle->handle, posted);
426 }
427 }
428 }
429
430 if (cc == 0)
431 reply("MSG_NONE");
432 else
433 reply("MSMSG_MEMOS_FOUND", cc);
434
435 reply("MSMSG_LIST_END");
436
437 return 1;
438}
439
440static MODCMD_FUNC(cmd_read)
441{
442 struct memo_account *ma;
443 unsigned int memoid;
444 int rignore = 0, brk = 0, s = 0;
445 struct memo *memo;
446 struct memo *memob;
447 char posted[24];
448 struct tm tm;
449
450 if (!(ma = memoserv_get_account(user->handle_info)))
451 return 0;
452 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
453 return 0;
454
455 if (argv[2]) {
456 char *argtwo = argv[2];
457 while (*argtwo) {
458 switch (*argtwo) {
459 case '-':
460 if (s != 0)
461 brk = 1;
462 break;
463
464 case 'i':
465 if (s > 0)
466 rignore = 1;
467 break;
468
469 default: break;
470 }
471
472 if (brk == 1)
473 break;
474 else {
475 s++;
476 argtwo++;
477 }
478 }
479 }
480
481 localtime_r(&memo->sent, &tm);
482 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
483
484 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
485 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
486 memo->is_read = 1;
487 memob = memo;
488
489 if (ma->flags & MEMO_IGNORE_RECIEPTS)
490 rignore = 1;
491
492 if (memo->reciept == 1) {
493 memo->reciept = 0;
494 reply("MSMSG_MEMO_RECIEPT", rignore ? "ignoring" : "sending");
495 if (rignore == 0) {
496 struct memo_account *ma;
497 struct memo_account *sender;
498 char content[MAXLEN];
499
500 ma = memoserv_get_account(user->handle_info);
501 sender = memoserv_get_account(memo->sender->handle);
502
503 sprintf(content, "%s has read your memo dated %s.", ma->handle->handle, posted);
504
505 memo = add_memo(now, sender, ma, content, 1);
506 reply("MSMSG_MEMO_SENT", memob->sender->handle->handle, memo_id);
507
508 if (sender->flags & MEMO_NOTIFY_NEW) {
509 struct userNode *other;
510
511 for (other = sender->handle->users; other; other = other->next_authed)
512 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
513 }
514
515
516 }
517 }
518 return 1;
519}
520
521static MODCMD_FUNC(cmd_delete)
522{
523 struct memo_account *ma;
524 struct memo *memo;
525 unsigned int memoid;
526
527 if (!(ma = memoserv_get_account(user->handle_info)))
528 return 0;
529 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
530 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
531 reply("MSMSG_USE_CONFIRM");
532 return 0;
533 }
534 while (ma->recvd.used)
535 delete_memo(ma->recvd.list[0]);
536 reply("MSMSG_DELETED_ALL");
537 return 1;
538 }
539
540 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
541 return 0;
542 delete_memo(memo);
543 reply("MSMSG_MEMO_DELETED", memoid);
544 return 1;
545}
546
547static MODCMD_FUNC(cmd_cancel)
548{
549 unsigned long id;
550 unsigned int ii;
551 dict_iterator_t it;
552 struct memo *memo;
553 struct memo_account *ma;
554
555 if (isdigit(argv[1][0])) {
556 id = strtoul(argv[1], NULL, 0);
557 } else {
558 reply("MSMSG_MEMO_CANCEL_NUMBER");
559 return 0;
560 }
561
562 for (it = dict_first(memos); it; it = iter_next(it)) {
563 ma = iter_data(it);
564 for (ii = 0; ii < ma->recvd.used; ++ii) {
565 memo = ma->recvd.list[ii];
566
567 if (id == memo->id) {
568 if (!strcasecmp(memo->sender->handle->handle, user->handle_info->handle)) {
569 if (memo->is_read) {
570 reply("MSMSG_MEMO_READ", id);
571 return 0;
572 } else {
573 delete_memo(memo);
574 reply("MSMSG_MEMO_DELETED", id);
575 return 1;
576 }
577 } else {
578 reply("MSMSG_MEMO_DONT_OWN", id);
579 return 0;
580 }
581 }
582 }
583 }
584
585 reply("MSMSG_MEMO_CANT_LOCATE", id);
586 return 0;
587}
588
589static MODCMD_FUNC(cmd_expire)
590{
591 unsigned long old_expired = memosExpired;
592 do_expire();
593 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
594 return 1;
595}
596
597static MODCMD_FUNC(cmd_expiry)
598{
599 char interval[INTERVALLEN];
600
601 if (!memoserv_conf.message_expiry) {
602 reply("MSMSG_EXPIRY_OFF");
603 return 1;
604 }
605
606 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
607 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
608 return 1;
609}
610
611
612static void
613set_list(struct userNode *user, struct handle_info *hi, int override)
614{
615 option_func_t *opt;
616 unsigned int i;
617 char *set_display[] = {"AUTHNOTIFY", "NOTIFY", "PRIVATE", "LIMIT",
618 "IGNORERECIEPTS", "SENDRECIEPTS"};
619
620 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS");
621 send_message(user, memoserv_conf.bot, "MSMSG_BAR");
622
623 /* Do this so options are presented in a consistent order. */
624 for (i = 0; i < ArrayLength(set_display); ++i)
625 if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
626 opt(user, hi, override, 0, NULL);
627 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS_END");
628}
629
630static MODCMD_FUNC(cmd_set)
631{
632 struct handle_info *hi;
633 option_func_t *opt;
634
635 hi = user->handle_info;
636 if (argc < 2) {
637 set_list(user, hi, 0);
638 return 1;
639 }
640
641 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL))) {
642 reply("MSMSG_INVALID_OPTION", argv[1]);
643 return 0;
644 }
645
646 return opt(user, hi, 0, argc-1, argv+1);
647}
648
649static MODCMD_FUNC(cmd_oset)
650{
651 struct handle_info *hi;
652 option_func_t *opt;
653
654 if (!(hi = get_victim_oper(user, argv[1])))
655 return 0;
656
657 if (argc < 3) {
658 set_list(user, hi, 0);
659 return 1;
660 }
661
662 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL))) {
663 reply("MSMSG_INVALID_OPTION", argv[2]);
664 return 0;
665 }
666
667 return opt(user, hi, 1, argc-2, argv+2);
668}
669
670static OPTION_FUNC(opt_notify)
671{
672 struct memo_account *ma;
673 char *choice;
674
675 if (!(ma = memoserv_get_account(hi)))
676 return 0;
677 if (argc > 1) {
678 choice = argv[1];
679 if (enabled_string(choice)) {
680 ma->flags |= MEMO_NOTIFY_NEW;
681 } else if (disabled_string(choice)) {
682 ma->flags &= ~MEMO_NOTIFY_NEW;
683 } else {
684 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
685 return 0;
686 }
687 }
688
689 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
690 send_message(user, memoserv_conf.bot, "MSMSG_SET_NOTIFY", choice);
691 return 1;
692}
693
694static OPTION_FUNC(opt_authnotify)
695{
696 struct memo_account *ma;
697 char *choice;
698
699 if (!(ma = memoserv_get_account(hi)))
700 return 0;
701 if (argc > 1) {
702 choice = argv[1];
703 if (enabled_string(choice)) {
704 ma->flags |= MEMO_NOTIFY_LOGIN;
705 } else if (disabled_string(choice)) {
706 ma->flags &= ~MEMO_NOTIFY_LOGIN;
707 } else {
708 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
709 return 0;
710 }
711 }
712
713 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
714 send_message(user, memoserv_conf.bot, "MSMSG_SET_AUTHNOTIFY", choice);
715 return 1;
716}
717
718static OPTION_FUNC(opt_ignorereciepts)
719{
720 struct memo_account *ma;
721 char *choice;
722
723 if (!(ma = memoserv_get_account(hi)))
724 return 0;
725 if (argc > 1) {
726 choice = argv[1];
727 if (enabled_string(choice)) {
728 ma->flags |= MEMO_IGNORE_RECIEPTS;
729 } else if (disabled_string(choice)) {
730 ma->flags &= ~MEMO_IGNORE_RECIEPTS;
731 } else {
732 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
733 return 0;
734 }
735 }
736
737 choice = (ma->flags & MEMO_IGNORE_RECIEPTS) ? "on" : "off";
738 send_message(user, memoserv_conf.bot, "MSMSG_SET_IGNORERECIEPTS", choice);
739 return 1;
740}
741
742static OPTION_FUNC(opt_sendreciepts)
743{
744 struct memo_account *ma;
745 char *choice;
746
747 if (!(ma = memoserv_get_account(hi)))
748 return 0;
749 if (argc > 1) {
750 choice = argv[1];
751 if (enabled_string(choice)) {
752 ma->flags |= MEMO_ALWAYS_RECIEPTS;
753 } else if (disabled_string(choice)) {
754 ma->flags &= ~MEMO_ALWAYS_RECIEPTS;
755 } else {
756 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
757 return 0;
758 }
759 }
760
761 choice = (ma->flags & MEMO_ALWAYS_RECIEPTS) ? "on" : "off";
762 send_message(user, memoserv_conf.bot, "MSMSG_SET_SENDRECIEPTS", choice);
763 return 1;
764}
765
766static OPTION_FUNC(opt_private)
767{
768 struct memo_account *ma;
769 char *choice;
770
771 if (!(ma = memoserv_get_account(hi)))
772 return 0;
773 if (argc > 1) {
774 choice = argv[1];
775 if (enabled_string(choice)) {
776 ma->flags |= MEMO_DENY_NONCHANNEL;
777 } else if (disabled_string(choice)) {
778 ma->flags &= ~MEMO_DENY_NONCHANNEL;
779 } else {
780 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
781 return 0;
782 }
783 }
784
785 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
786 send_message(user, memoserv_conf.bot, "MSMSG_SET_PRIVATE", choice);
787 return 1;
788}
789
790static OPTION_FUNC(opt_limit)
791{
792 struct memo_account *ma;
793 unsigned int choice;
794
795 if (!(ma = memoserv_get_account(hi)))
796 return 0;
797 if (argc > 1) {
798 choice = atoi(argv[1]);
799 if (choice > memoserv_conf.limit)
800 choice = memoserv_conf.limit;
801
802 ma->limit = choice;
803 }
804
805 send_message(user, memoserv_conf.bot, "MSMSG_SET_LIMIT", ma->limit);
806 return 1;
807}
808
809static MODCMD_FUNC(cmd_status)
810{
811 reply("MSMSG_STATUS_TOTAL", dict_size(memos));
812 reply("MSMSG_STATUS_EXPIRED", memosExpired);
813 reply("MSMSG_STATUS_SENT", memosSent);
814 return 1;
815}
816
817static void
818memoserv_conf_read(void)
819{
820 dict_t conf_node;
821 const char *str;
822
823 str = "modules/memoserv";
824 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
825 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
826 return;
827 }
828
829 str = database_get_data(conf_node, "limit", RECDB_QSTRING);
830 memoserv_conf.limit = str ? atoi(str) : 50;
831
832 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
833 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
834}
835
836static int
837memoserv_user_read(const char *key, struct record_data *hir)
838{
839 char *str;
840 struct memo_account *ma;
841 struct handle_info *hi;
842
843 if (!(hi = get_handle_info(key)))
844 return 0;
845
846 ma = dict_find(memos, hi->handle, NULL);
847 if (ma)
848 return 0;
849
850
851 ma = calloc(1, sizeof(*ma));
852 if (!ma)
853 return 0;
854
855 ma->handle = hi;
856
857 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
858 if (!str) {
859 log_module(MS_LOG, LOG_ERROR, "Flags not present in memo %s; skipping", key);
860 return 0;
861 }
862 ma->flags = strtoul(str, NULL, 0);
863
864 str = database_get_data(hir->d.object, KEY_LIMIT, RECDB_QSTRING);
865 if (!str) {
866 log_module(MS_LOG, LOG_ERROR, "Limit not present in memo %s; skipping", key);
867 return 0;
868 }
869 ma->limit = strtoul(str, NULL, 0);
870
871 dict_insert(memos, ma->handle->handle, ma);
872
873 return 0;
874}
875
876static int
877memoserv_memo_read(const char *key, struct record_data *hir)
878{
879 char *str;
880 struct handle_info *sender, *recipient;
881 struct memo *memo;
882 unsigned long id;
883 time_t sent;
884
885 if (hir->type != RECDB_OBJECT) {
886 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
887 return 0;
888 }
889
890 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
891 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", key);
892 return 0;
893 }
894
895 sent = atoi(str);
896
897 if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
898 log_module(MS_LOG, LOG_ERROR, "ID sent not present in memo %s; skipping", key);
899 return 0;
900 }
901 id = strtoul(str, NULL, 0);
902 if (id > memo_id)
903 memo_id = id;
904
905 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
906 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", key);
907 return 0;
908 } else if (!(recipient = get_handle_info(str))) {
909 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, key);
910 return 0;
911 }
912
913 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
914 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", key);
915 return 0;
916 } else if (!(sender = get_handle_info(str))) {
917 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, key);
918 return 0;
919 }
920
921 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
922 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", key);
923 return 0;
924 }
925
926 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str, 0);
927 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
928 memo->is_read = 1;
929
930 if ((str = database_get_data(hir->d.object, KEY_RECIEPT, RECDB_QSTRING)))
931 memo->reciept = 1;
932
933 memo->id = id;
934
935 return 0;
936}
937
938static int
939memoserv_saxdb_read(struct dict *database)
940{
941 struct dict *section;
942 dict_iterator_t it;
943
944 if((section = database_get_data(database, KEY_MAIN_ACCOUNTS, RECDB_OBJECT)))
945 for(it = dict_first(section); it; it = iter_next(it))
946 memoserv_user_read(iter_key(it), iter_data(it));
947
948 if((section = database_get_data(database, KEY_MAIN_MEMOS, RECDB_OBJECT)))
949 for(it = dict_first(section); it; it = iter_next(it))
950 memoserv_memo_read(iter_key(it), iter_data(it));
951
952 return 0;
953}
954
955static int
956memoserv_write_users(struct saxdb_context *ctx, struct memo_account *ma)
957{
958 saxdb_start_record(ctx, ma->handle->handle, 0);
959
960 saxdb_write_int(ctx, KEY_FLAGS, ma->flags);
961 saxdb_write_int(ctx, KEY_LIMIT, ma->limit);
962
963 saxdb_end_record(ctx);
964 return 0;
965}
966
967static int
968memoserv_write_memos(struct saxdb_context *ctx, struct memo *memo)
969{
970 char str[7];
971
972 saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)), 0);
973
974 saxdb_write_int(ctx, KEY_SENT, memo->sent);
975 saxdb_write_int(ctx, KEY_ID, memo->id);
976 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
977 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
978 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
979
980 if (memo->is_read)
981 saxdb_write_int(ctx, KEY_READ, 1);
982
983 if (memo->reciept)
984 saxdb_write_int(ctx, KEY_RECIEPT, 1);
985
986 saxdb_end_record(ctx);
987 return 0;
988}
989
990static int
991memoserv_saxdb_write(struct saxdb_context *ctx)
992{
993 dict_iterator_t it;
994 struct memo_account *ma;
995 struct memo *memo;
996 unsigned int ii;
997
998 /* Accounts */
999 saxdb_start_record(ctx, KEY_MAIN_ACCOUNTS, 1);
1000 for (it = dict_first(memos); it; it = iter_next(it)) {
1001 ma = iter_data(it);
1002 memoserv_write_users(ctx, ma);
1003 }
1004 saxdb_end_record(ctx);
1005
1006 /* Channels */
1007 saxdb_start_record(ctx, KEY_MAIN_MEMOS, 1);
1008 for (it = dict_first(memos); it; it = iter_next(it)) {
1009 ma = iter_data(it);
1010 for (ii = 0; ii < ma->recvd.used; ++ii) {
1011 memo = ma->recvd.list[ii];
1012 memoserv_write_memos(ctx, memo);
1013 }
1014 }
1015 saxdb_end_record(ctx);
1016
1017 return 0;
1018}
1019
1020static void
1021memoserv_cleanup(void)
1022{
1023 dict_delete(memos);
1024}
1025
1026static void
1027memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
1028{
1029 unsigned int ii, unseen;
1030 struct memo_account *ma;
1031 struct memo *memo;
1032
1033 if (!user->uplink->burst) {
1034 if (!(ma = memoserv_get_account(user->handle_info))
1035 || !(ma->flags & MEMO_NOTIFY_LOGIN))
1036 return;
1037 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
1038 memo = ma->recvd.list[ii];
1039 if (!memo->is_read)
1040 unseen++;
1041 }
1042 if (ma->recvd.used && memoserv_conf.bot)
1043 if(unseen) send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
1044 }
1045}
1046
1047static void
1048memoserv_rename_account(struct handle_info *hi, const char *old_handle)
1049{
1050 struct memo_account *ma;
1051 if (!(ma = dict_find(memos, old_handle, NULL)))
1052 return;
1053 dict_remove2(memos, old_handle, 1);
1054 dict_insert(memos, hi->handle, ma);
1055}
1056
1057static void
1058memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
1059{
1060 dict_remove(memos, handle->handle);
1061}
1062
1063int
1064memoserv_init(void)
1065{
1066 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
1067 memos = dict_new();
1068 dict_set_free_data(memos, delete_memo_account);
1069 reg_auth_func(memoserv_check_messages);
1070 reg_handle_rename_func(memoserv_rename_account);
1071 reg_unreg_func(memoserv_unreg_account);
1072 conf_register_reload(memoserv_conf_read);
1073 reg_exit_func(memoserv_cleanup);
1074 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
1075
1076 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
1077 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
1078 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
1079 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
1080 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
1081 modcmd_register(memoserv_module, "cancel", cmd_cancel, 2, MODCMD_REQUIRE_AUTHED, NULL);
1082 modcmd_register(memoserv_module, "history", cmd_history, 1, MODCMD_REQUIRE_AUTHED, NULL);
1083 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1084 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
1085 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
1086 modcmd_register(memoserv_module, "set", cmd_set, 1, MODCMD_REQUIRE_AUTHED, NULL);
1087 modcmd_register(memoserv_module, "oset", cmd_oset, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
1088
1089 memoserv_opt_dict = dict_new();
1090 dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
1091 dict_insert(memoserv_opt_dict, "NOTIFY", opt_notify);
1092 dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
1093 dict_insert(memoserv_opt_dict, "IGNORERECIEPTS", opt_ignorereciepts);
1094 dict_insert(memoserv_opt_dict, "SENDRECIEPTS", opt_sendreciepts);
1095 dict_insert(memoserv_opt_dict, "LIMIT", opt_limit);
1096
1097 message_register_table(msgtab);
1098
1099 if (memoserv_conf.message_expiry)
1100 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
1101
1102 return 1;
1103}
1104
1105int
1106memoserv_finalize(void) {
1107 struct chanNode *chan;
1108 unsigned int i;
1109 dict_t conf_node;
1110 const char *str;
1111
1112 str = "modules/memoserv";
1113 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
1114 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
1115 return 0;
1116 }
1117
1118 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
1119 if (str)
1120 memoserv_conf.bot = GetUserH(str);
1121
1122 if (autojoin_channels && memoserv_conf.bot) {
1123 for (i = 0; i < autojoin_channels->used; i++) {
1124 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
1125 AddChannelUser(memoserv_conf.bot, chan)->modes |= MODE_CHANOP;
1126 }
1127 }
1128
1129 return 1;
1130}