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