]> jfr.im git - irc/evilnet/x3.git/blame - src/mod-memoserv.c
crash fix
[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;
256
257 if (!user->handle_info)
258 return 0;
a8692672 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
d76ed9a9 271 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
272 return 1;
a8692672 273
d76ed9a9 274 for (dest = acct->handle->channels; dest; dest = dest->u_next)
275 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
276 return 1;
a8692672 277
d76ed9a9 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;
82794e1b 303 int s = 0, brk = 0;
304 int reciept = 0, inc = 2;
d76ed9a9 305 struct handle_info *hi;
306 struct memo_account *ma, *sender;
82794e1b 307 struct memo *memo;
d76ed9a9 308
309 if (!(hi = modcmd_get_handle_info(user, argv[1])))
310 return 0;
a8692672 311
d76ed9a9 312 if (!(sender = memoserv_get_account(user->handle_info))
313 || !(ma = memoserv_get_account(hi))) {
314 reply("MSG_INTERNAL_FAILURE");
315 return 0;
316 }
a8692672 317
d76ed9a9 318 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
319 return 0;
a8692672 320
82794e1b 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);
f2e592d3 350 memo = add_memo(now, ma, sender, message, 1);
2a951803 351 if ((reciept == 1) || (ma->flags & MEMO_ALWAYS_RECIEPTS))
82794e1b 352 memo->reciept = 1;
a8692672 353
d76ed9a9 354 if (ma->flags & MEMO_NOTIFY_NEW) {
355 struct userNode *other;
a8692672 356
d76ed9a9 357 for (other = ma->handle->users; other; other = other->next_authed)
358 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
359 }
a8692672 360
f2e592d3 361 reply("MSMSG_MEMO_SENT", ma->handle->handle, memo_id);
d76ed9a9 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;
e3e5ba49 375
d76ed9a9 376 reply("MSMSG_LIST_HEAD");
e3e5ba49 377
378 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
379 reply("MSMSG_BAR");
380
d76ed9a9 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);
82794e1b 385 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, memo->reciept ? "(r)" : "", posted);
d76ed9a9 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);
e3e5ba49 393
394 reply("MSMSG_LIST_END");
395
d76ed9a9 396 return 1;
397}
398
cbc5a1a4 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
d76ed9a9 440static MODCMD_FUNC(cmd_read)
441{
442 struct memo_account *ma;
443 unsigned int memoid;
82794e1b 444 int rignore = 0, brk = 0, s = 0;
d76ed9a9 445 struct memo *memo;
8cfd8013 446 struct memo *memob;
d76ed9a9 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;
82794e1b 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
d76ed9a9 481 localtime_r(&memo->sent, &tm);
482 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
82794e1b 483
d76ed9a9 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;
8cfd8013 487 memob = memo;
82794e1b 488
2a951803 489 if (ma->flags & MEMO_IGNORE_RECIEPTS)
490 rignore = 1;
491
82794e1b 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
f2e592d3 505 memo = add_memo(now, sender, ma, content, 1);
8cfd8013 506 reply("MSMSG_MEMO_SENT", memob->sender->handle->handle, memo_id);
82794e1b 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 }
d76ed9a9 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
f2e592d3 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
d76ed9a9 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
e3e5ba49 611
612static void
613set_list(struct userNode *user, struct handle_info *hi, int override)
614{
615 option_func_t *opt;
d9abe201 616 unsigned int i;
2a951803 617 char *set_display[] = {"AUTHNOTIFY", "NOTIFY", "PRIVATE", "LIMIT",
618 "IGNORERECIEPTS", "SENDRECIEPTS"};
e3e5ba49 619
620 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS");
d9abe201 621 send_message(user, memoserv_conf.bot, "MSMSG_BAR");
e3e5ba49 622
623 /* Do this so options are presented in a consistent order. */
d9abe201 624 for (i = 0; i < ArrayLength(set_display); ++i)
625 if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
e3e5ba49 626 opt(user, hi, override, 0, NULL);
e3e5ba49 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;
d9abe201 636 if (argc < 2) {
e3e5ba49 637 set_list(user, hi, 0);
638 return 1;
639 }
640
d9abe201 641 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL))) {
e3e5ba49 642 reply("MSMSG_INVALID_OPTION", argv[1]);
643 return 0;
644 }
d9abe201 645
e3e5ba49 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
d9abe201 654 if (!(hi = get_victim_oper(user, argv[1])))
655 return 0;
e3e5ba49 656
d9abe201 657 if (argc < 3) {
e3e5ba49 658 set_list(user, hi, 0);
659 return 1;
660 }
661
d9abe201 662 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL))) {
e3e5ba49 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)
d76ed9a9 671{
672 struct memo_account *ma;
673 char *choice;
674
d9abe201 675 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 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 {
e3e5ba49 684 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
d76ed9a9 685 return 0;
686 }
687 }
688
689 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
e3e5ba49 690 send_message(user, memoserv_conf.bot, "MSMSG_SET_NOTIFY", choice);
d76ed9a9 691 return 1;
692}
693
d9abe201 694static OPTION_FUNC(opt_authnotify)
d76ed9a9 695{
696 struct memo_account *ma;
697 char *choice;
698
d9abe201 699 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 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 {
e3e5ba49 708 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
d76ed9a9 709 return 0;
710 }
711 }
712
713 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
e3e5ba49 714 send_message(user, memoserv_conf.bot, "MSMSG_SET_AUTHNOTIFY", choice);
d76ed9a9 715 return 1;
716}
717
2a951803 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
d9abe201 766static OPTION_FUNC(opt_private)
d76ed9a9 767{
768 struct memo_account *ma;
769 char *choice;
770
d9abe201 771 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 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 {
e3e5ba49 780 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
d76ed9a9 781 return 0;
782 }
783 }
784
785 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
e3e5ba49 786 send_message(user, memoserv_conf.bot, "MSMSG_SET_PRIVATE", choice);
d76ed9a9 787 return 1;
788}
789
d9abe201 790static OPTION_FUNC(opt_limit)
acf3c6d5 791{
792 struct memo_account *ma;
a8692672 793 unsigned int choice;
acf3c6d5 794
d9abe201 795 if (!(ma = memoserv_get_account(hi)))
acf3c6d5 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
d9abe201 805 send_message(user, memoserv_conf.bot, "MSMSG_SET_LIMIT", ma->limit);
acf3c6d5 806 return 1;
807}
808
d76ed9a9 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
d9abe201 829 str = database_get_data(conf_node, "limit", RECDB_QSTRING);
a8370a20 830 memoserv_conf.limit = str ? atoi(str) : 50;
d9abe201 831
d76ed9a9 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
34a9e19a 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)
d76ed9a9 878{
879 char *str;
880 struct handle_info *sender, *recipient;
d76ed9a9 881 struct memo *memo;
f2e592d3 882 unsigned long id;
d76ed9a9 883 time_t sent;
884
34a9e19a 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 }
d76ed9a9 889
34a9e19a 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 }
d76ed9a9 894
34a9e19a 895 sent = atoi(str);
d76ed9a9 896
f2e592d3 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
34a9e19a 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 }
d76ed9a9 912
34a9e19a 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 }
82794e1b 920
34a9e19a 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;
d76ed9a9 924 }
34a9e19a 925
f2e592d3 926 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str, 0);
34a9e19a 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
f2e592d3 933 memo->id = id;
934
34a9e19a 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];
34a9e19a 971
f2e592d3 972 saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)), 0);
34a9e19a 973
974 saxdb_write_int(ctx, KEY_SENT, memo->sent);
f2e592d3 975 saxdb_write_int(ctx, KEY_ID, memo->id);
34a9e19a 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);
d76ed9a9 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;
34a9e19a 996 unsigned int ii;
d76ed9a9 997
34a9e19a 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);
d76ed9a9 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];
34a9e19a 1012 memoserv_write_memos(ctx, memo);
d76ed9a9 1013 }
1014 }
34a9e19a 1015 saxdb_end_record(ctx);
1016
d76ed9a9 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
7637f48f 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)
e3e5ba49 1043 if(unseen) send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
d76ed9a9 1044 }
d76ed9a9 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);
cbc5a1a4 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);
e3e5ba49 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);
2a951803 1093 dict_insert(memoserv_opt_dict, "IGNORERECIEPTS", opt_ignorereciepts);
1094 dict_insert(memoserv_opt_dict, "SENDRECIEPTS", opt_sendreciepts);
d9abe201 1095 dict_insert(memoserv_opt_dict, "LIMIT", opt_limit);
e3e5ba49 1096
d76ed9a9 1097 message_register_table(msgtab);
1098
1099 if (memoserv_conf.message_expiry)
1100 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
e3e5ba49 1101
d76ed9a9 1102 return 1;
1103}
1104
1105int
1106memoserv_finalize(void) {
e3e5ba49 1107 struct chanNode *chan;
1108 unsigned int i;
d76ed9a9 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);
e3e5ba49 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
d76ed9a9 1129 return 1;
1130}