]> jfr.im git - irc/evilnet/x3.git/blame - src/mod-memoserv.c
ignore stats headers
[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
348683aa 9 * the Free Software Foundation; either version 3 of the License, or
d76ed9a9 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.*
fc4a50fc 38 * /msg opserv bind memoserv set *memoserv.set
d76ed9a9 39 */
40
41#include "chanserv.h"
42#include "conf.h"
43#include "modcmd.h"
e3e5ba49 44#include "nickserv.h"
08895577 45#include "opserv.h"
d76ed9a9 46#include "saxdb.h"
87708af4 47#include "sendmail.h"
d76ed9a9 48#include "timeq.h"
49
34a9e19a 50#define KEY_MAIN_ACCOUNTS "accounts"
51#define KEY_FLAGS "flags"
52#define KEY_LIMIT "limit"
53
1b4a47ca 54#define KEY_MAIN_HISTORY "history"
34a9e19a 55#define KEY_MAIN_MEMOS "memos"
d76ed9a9 56#define KEY_SENT "sent"
57#define KEY_RECIPIENT "to"
58#define KEY_FROM "from"
59#define KEY_MESSAGE "msg"
60#define KEY_READ "read"
82794e1b 61#define KEY_RECIEPT "reciept"
f2e592d3 62#define KEY_ID "id"
d76ed9a9 63
1b4a47ca 64
d76ed9a9 65static const struct message_entry msgtab[] = {
66 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
a76bcc6a 67 { "MSMSG_UNKNOWN_SEND_FLAG", "Unreccognised send flag '%c', message not sent." },
f2e592d3 68 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b (ID# %d)." },
d76ed9a9 69 { "MSMSG_NO_MESSAGES", "You have no messages." },
fc4a50fc 70 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches." },
cf33840c 71 { "MSMSG_HOWTO_READ", "Use READ <ID> to read a message." },
72 { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse READ <ID> to read a message." },
cbc5a1a4 73 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
74 { "MSMSG_LIST_FORMAT", "%-2u %s $b%s$b %s" },
75 { "MSMSG_HISTORY_HEADER", "$bID$b $bTo$b $bTime Sent$b" },
76 { "MSMSG_HISTORY_FORMAT", "%-2u %s %s" },
d76ed9a9 77 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
82794e1b 78 { "MSMSG_MEMO_RECIEPT", "$bRead Reciept$b requested, %s." },
d76ed9a9 79 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
80 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
81 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
f2e592d3 82 { "MSMSG_MEMO_CANCEL_NUMBER", "You must specify a number id" },
83 { "MSMSG_MEMO_DONT_OWN", "You did not send memo# %d" },
84 { "MSMSG_MEMO_READ", "Memo# %d has already been read, you cannot cancel it." },
85 { "MSMSG_MEMO_CANT_LOCATE", "Could not locate memo# %d" },
d76ed9a9 86 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
87 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
88 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
cf33840c 89 { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages. Use LIST to list them." },
90 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. Use LIST to see your messages." },
a8692672 91 { "MSMSG_FULL_INBOX", "$b%s$b cannot recieve anymore memos as their inbox is full" },
d76ed9a9 92 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
cf33840c 93 { "MSMSG_USE_CONFIRM", "Please use DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
e3e5ba49 94
1b4a47ca 95 { "MSMSG_STATUS_HIST_TOTAL", "I have $b%u$b history entries in my database." },
acf3c6d5 96 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
d76ed9a9 97 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
acf3c6d5 98 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
e3e5ba49 99
a8692672 100 { "MSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
e3e5ba49 101 { "MSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
fc4a50fc 102 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify$b: %s" },
103 { "MSMSG_SET_NEWNOTIFY", "$bNewNotify$b: %s" },
104 { "MSMSG_SET_PRIVMSG", "$bPrivmsg$b: %s" },
105 { "MSMSG_SET_PRIVATE", "$bPrivate$b: %s" },
106 { "MSMSG_SET_IGNORERECIEPTS", "$bIgnoreReciepts$b: %s" },
107 { "MSMSG_SET_SENDRECIEPTS", "$bSendReciepts$b: %s" },
108 { "MSMSG_SET_LIMIT", "$bLimit$b: %d" },
2a951803 109 { "MSMSG_SET_OPTIONS", "$bMessaging Options$b" },
e3e5ba49 110 { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
111
112 { "MSMSG_LIST_END", "--------------End of Memos--------------" },
113 { "MSMSG_BAR", "----------------------------------------"},
114
87708af4 115 { "MSEMAIL_NEWMEMO_SUBJECT", "New %s %s message from %s" },
116 { "MSEMAIL_NEWMEMO_BODY", "This email has been sent to let you know that %s has sent you a message via %s.\n\n The message is: %s.\n\nTo delete this message just type in /msg %s delete %d when on %s next." },
117
08895577 118 { "MSMSG_DEFCON_NO_NEW_MEMOS", "You cannot send new memos at this time, please try again soon." },
119
d76ed9a9 120 { NULL, NULL }
121};
122
123struct memo {
124 struct memo_account *recipient;
125 struct memo_account *sender;
126 char *message;
127 time_t sent;
f2e592d3 128 unsigned long id;
d76ed9a9 129 unsigned int is_read : 1;
82794e1b 130 unsigned int reciept : 1;
d76ed9a9 131};
132
1b4a47ca 133struct history {
134 struct memo_account *recipient;
135 struct memo_account *sender;
136 time_t sent;
137 unsigned long id;
138};
139
68cb9c2c 140struct userNode *memoserv;
141
142#define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
143#define MEMOSERV_SYNTAX() svccmd_send_help_brief(user, memoserv, cmd)
144#define MEMOSERV_MIN_PARAMS(N) if(argc < (N)) { \
145 reply("MSG_MISSING_PARAMS", argv[0]); \
146 MEMOSERV_SYNTAX(); \
147 return 0; }
148
d76ed9a9 149DECLARE_LIST(memoList, struct memo*);
150DEFINE_LIST(memoList, struct memo*);
1b4a47ca 151DECLARE_LIST(historyList, struct history*);
152DEFINE_LIST(historyList, struct history*);
d76ed9a9 153
154/* memo_account.flags fields */
2a951803 155#define MEMO_NOTIFY_NEW 0x00000001
156#define MEMO_NOTIFY_LOGIN 0x00000002
157#define MEMO_DENY_NONCHANNEL 0x00000004
158#define MEMO_IGNORE_RECIEPTS 0x00000008
159#define MEMO_ALWAYS_RECIEPTS 0x00000010
fc4a50fc 160#define MEMO_USE_PRIVMSG 0x00000020
d76ed9a9 161
162struct memo_account {
163 struct handle_info *handle;
fc4a50fc 164 unsigned int flags : 6;
a8692672 165 unsigned int limit;
d76ed9a9 166 struct memoList sent;
167 struct memoList recvd;
1b4a47ca 168 struct historyList hsent;
169 struct historyList hrecvd;
d76ed9a9 170};
171
172static struct {
173 struct userNode *bot;
174 int message_expiry;
a8692672 175 unsigned int limit;
d76ed9a9 176} memoserv_conf;
177
d9abe201 178#define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
c092fcad 179#define OPTION_FUNC(NAME) int NAME(struct svccmd *cmd, struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
d9abe201 180typedef OPTION_FUNC(option_func_t);
181
f2e592d3 182unsigned long memo_id;
183
e3e5ba49 184extern struct string_list *autojoin_channels;
d76ed9a9 185const char *memoserv_module_deps[] = { NULL };
186static struct module *memoserv_module;
187static struct log_type *MS_LOG;
188static unsigned long memosSent, memosExpired;
189static struct dict *memos; /* memo_account->handle->handle -> memo_account */
1b4a47ca 190static struct dict *historys;
e3e5ba49 191static dict_t memoserv_opt_dict; /* contains option_func_t* */
192
d76ed9a9 193static struct memo_account *
194memoserv_get_account(struct handle_info *hi)
195{
196 struct memo_account *ma;
197 if (!hi)
198 return NULL;
199 ma = dict_find(memos, hi->handle, NULL);
200 if (ma)
201 return ma;
202 ma = calloc(1, sizeof(*ma));
203 if (!ma)
204 return ma;
205 ma->handle = hi;
fc4a50fc 206 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN | MEMO_USE_PRIVMSG;
512d7958 207 ma->limit = memoserv_conf.limit;
d76ed9a9 208 dict_insert(memos, ma->handle->handle, ma);
1b4a47ca 209 dict_insert(historys, ma->handle->handle, ma);
d76ed9a9 210 return ma;
211}
212
213static void
214delete_memo(struct memo *memo)
215{
216 memoList_remove(&memo->recipient->recvd, memo);
217 memoList_remove(&memo->sender->sent, memo);
218 free(memo->message);
219 free(memo);
220}
221
1b4a47ca 222static void
223delete_history(struct history *history)
224{
225 historyList_remove(&history->recipient->hrecvd, history);
226 historyList_remove(&history->sender->hsent, history);
227 free(history);
228}
229
d76ed9a9 230static void
231delete_memo_account(void *data)
232{
233 struct memo_account *ma = data;
234
235 while (ma->recvd.used)
236 delete_memo(ma->recvd.list[0]);
237 while (ma->sent.used)
238 delete_memo(ma->sent.list[0]);
239 memoList_clean(&ma->recvd);
240 memoList_clean(&ma->sent);
1b4a47ca 241
242 while (ma->hrecvd.used)
243 delete_history(ma->hrecvd.list[0]);
244 while (ma->hsent.used)
245 delete_history(ma->hsent.list[0]);
246 historyList_clean(&ma->hrecvd);
247 historyList_clean(&ma->hsent);
d76ed9a9 248 free(ma);
249}
250
251void
252do_expire(void)
253{
254 dict_iterator_t it;
255 for (it = dict_first(memos); it; it = iter_next(it)) {
256 struct memo_account *acct = iter_data(it);
257 unsigned int ii;
258 for (ii = 0; ii < acct->sent.used; ++ii) {
259 struct memo *memo = acct->sent.list[ii];
260 if ((now - memo->sent) > memoserv_conf.message_expiry) {
261 delete_memo(memo);
262 memosExpired++;
263 ii--;
264 }
265 }
266 }
1b4a47ca 267
268 for (it = dict_first(historys); it; it = iter_next(it)) {
269 struct memo_account *acct = iter_data(it);
270 unsigned int ii;
271 for (ii = 0; ii < acct->hsent.used; ++ii) {
272 struct history *history = acct->hsent.list[ii];
273 if ((now - history->sent) > memoserv_conf.message_expiry) {
274 delete_history(history);
275 memosExpired++;
276 ii--;
277 }
278 }
279 }
d76ed9a9 280}
281
282static void
283expire_memos(UNUSED_ARG(void *data))
284{
285 if (memoserv_conf.message_expiry) {
286 do_expire();
287 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
288 }
289}
290
1b4a47ca 291static struct history*
292add_history(time_t sent, struct memo_account *recipient, struct memo_account *sender, unsigned long id)
293{
294 struct history *history;
295
296 history = calloc(1, sizeof(*history));
297 if (!history)
298 return NULL;
299
300 history->id = id;
301 history->recipient = recipient;
302 historyList_append(&recipient->hrecvd, history);
303 history->sender = sender;
304 historyList_append(&sender->hsent, history);
305 history->sent = sent;
306
307 return history;
308}
309
310
d76ed9a9 311static struct memo*
f2e592d3 312add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message, int nfrom_read)
d76ed9a9 313{
314 struct memo *memo;
1b4a47ca 315 struct history *history;
d76ed9a9 316
317 memo = calloc(1, sizeof(*memo));
318 if (!memo)
319 return NULL;
320
f2e592d3 321 if (nfrom_read) {
322 memo_id++;
323 memo->id = memo_id;
324 }
325
d76ed9a9 326 memo->recipient = recipient;
327 memoList_append(&recipient->recvd, memo);
328 memo->sender = sender;
329 memoList_append(&sender->sent, memo);
330 memo->sent = sent;
331 memo->message = strdup(message);
332 memosSent++;
1b4a47ca 333
334 if (nfrom_read)
335 history = add_history(sent, recipient, sender, memo->id);
336
d76ed9a9 337 return memo;
338}
339
340static int
341memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *acct)
342{
343 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
344 struct userData *dest;
35ca8140 345 unsigned int i = 0, match = 0;
d76ed9a9 346
347 if (!user->handle_info)
348 return 0;
a8692672 349
350 /* Sanity checks here because if the user doesnt have a limit set
351 the limit comes out at like 21233242 if you try and use it. */
352 if (acct->limit > memoserv_conf.limit)
353 acct->limit = memoserv_conf.limit;
354
355 if (acct->recvd.used > acct->limit) {
356 send_message(user, bot, "MSMSG_FULL_INBOX", acct->handle->handle);
357 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
358 return 0;
359 }
360
35ca8140 361 if (acct->handle->ignores->used) {
362 for (i=0; i < acct->handle->ignores->used; i++) {
363 if (user_matches_glob(user, acct->handle->ignores->list[i], MATCH_USENICK)) {
364 match = 1;
365 break;
366 }
367 }
368
369 if (match) {
370 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
371 return 0;
372 }
373 }
374
d76ed9a9 375 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
376 return 1;
a8692672 377
d76ed9a9 378 for (dest = acct->handle->channels; dest; dest = dest->u_next)
379 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
380 return 1;
a8692672 381
d76ed9a9 382 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
383 return 0;
384}
385
386static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
387{
388 unsigned int memoid;
389 if (!isdigit(msgid[0])) {
390 if (ma->recvd.used)
391 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
392 else
393 reply("MSMSG_NO_MESSAGES");
394 return NULL;
395 }
396 memoid = atoi(msgid);
397 if (memoid >= ma->recvd.used) {
398 reply("MSMSG_NO_SUCH_MEMO");
399 return NULL;
400 }
401 return ma->recvd.list[*id = memoid];
402}
403
404static MODCMD_FUNC(cmd_send)
405{
406 char *message;
87708af4 407 int reciept = 0, inc = 2, email = 0;
d76ed9a9 408 struct handle_info *hi;
409 struct memo_account *ma, *sender;
82794e1b 410 struct memo *memo;
87708af4 411 char subject[128], body[4096];
412 char *estr;
413 const char *netname, *fmt;
d76ed9a9 414
68cb9c2c 415 MEMOSERV_MIN_PARAMS(3);
416
08895577 417 if (checkDefCon(DEFCON_NO_NEW_MEMOS) && !IsOper(user)) {
418 reply("MSMSG_DEFCON_NO_NEW_MEMOS");
419 return 0;
420 }
421
d76ed9a9 422 if (!(hi = modcmd_get_handle_info(user, argv[1])))
423 return 0;
a8692672 424
d76ed9a9 425 if (!(sender = memoserv_get_account(user->handle_info))
426 || !(ma = memoserv_get_account(hi))) {
427 reply("MSG_INTERNAL_FAILURE");
428 return 0;
429 }
a8692672 430
d76ed9a9 431 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
432 return 0;
a8692672 433
a76bcc6a 434 inc = 2; /* Start of message on 3rd ([2]) word */
435 if(argv[2][0] == '-' && argv[2][1] != '-') { /* first word is flags ('-r')*/
436 char *flags = argv[2];
437 inc++; /* Start of message is now 1 word later */
438 for(flags++;*flags;flags++) {
439 switch (*flags) {
440 case 'r':
82794e1b 441 reciept = 1;
442 break;
443
444 default:
a76bcc6a 445 /* Unknown mode. Give an error */
446 reply("MSMSG_UNKNOWN_SEND_FLAG", *flags);
447 return 0;
448 }
82794e1b 449 }
450 }
a76bcc6a 451 else
452 inc = 2; /* Start of message is word 2 */
82794e1b 453
454 message = unsplit_string(argv + inc, argc - inc, NULL);
f2e592d3 455 memo = add_memo(now, ma, sender, message, 1);
2a951803 456 if ((reciept == 1) || (ma->flags & MEMO_ALWAYS_RECIEPTS))
82794e1b 457 memo->reciept = 1;
a8692672 458
d76ed9a9 459 if (ma->flags & MEMO_NOTIFY_NEW) {
460 struct userNode *other;
a8692672 461
d76ed9a9 462 for (other = ma->handle->users; other; other = other->next_authed)
cf33840c 463 send_message_type((ma->flags & MEMO_USE_PRIVMSG)? MSG_TYPE_PRIVMSG : MSG_TYPE_NOTICE, other, memoserv ? memoserv : cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
d76ed9a9 464 }
a8692672 465
87708af4 466 estr = conf_get_data("services/nickserv/email_enabled", RECDB_QSTRING);
467 netname = conf_get_data("server/network", RECDB_QSTRING);
468 email = atoi(estr);
469 if (email && (ma->flags & MEMO_NOTIFY_NEW)) {
470 fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_SUBJECT");
471 snprintf(subject, sizeof(subject), fmt, netname, memoserv->nick, user->nick);
472
473 fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_BODY");
474 snprintf(body, sizeof(body), fmt, user->nick, memoserv->nick, message, memoserv->nick, memo_id, netname);
475
476 sendmail(memoserv, hi, subject, body, 0);
477 }
478
f2e592d3 479 reply("MSMSG_MEMO_SENT", ma->handle->handle, memo_id);
d76ed9a9 480 return 1;
481}
482
483static MODCMD_FUNC(cmd_list)
484{
485 struct memo_account *ma;
486 struct memo *memo;
487 unsigned int ii;
488 char posted[24];
489 struct tm tm;
490
491 if (!(ma = memoserv_get_account(user->handle_info)))
492 return 0;
e3e5ba49 493
d76ed9a9 494 reply("MSMSG_LIST_HEAD");
e3e5ba49 495
496 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
497 reply("MSMSG_BAR");
498
d76ed9a9 499 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
500 memo = ma->recvd.list[ii];
501 localtime_r(&memo->sent, &tm);
502 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
82794e1b 503 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, memo->reciept ? "(r)" : "", posted);
d76ed9a9 504 }
505 if (ii == 0)
506 reply("MSG_NONE");
507 else if (ii == 15)
508 reply("MSMSG_CLEAN_INBOX", ii);
fc4a50fc 509 else {
d76ed9a9 510 reply("MSMSG_MEMOS_FOUND", ii);
fc4a50fc 511 reply("MSMSG_HOWTO_READ");
512 }
e3e5ba49 513
514 reply("MSMSG_LIST_END");
515
d76ed9a9 516 return 1;
517}
518
cbc5a1a4 519static MODCMD_FUNC(cmd_history)
520{
521 struct memo_account *ma;
1b4a47ca 522 struct history *history;
cbc5a1a4 523 dict_iterator_t it;
524 unsigned int ii = 0;
525 unsigned int cc = 0;
526 char posted[24];
527 struct tm tm;
528
529 if (!(ma = memoserv_get_account(user->handle_info)))
530 return 0;
531
532 reply("MSMSG_HISTORY_HEADER");
533
534 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
535 reply("MSMSG_BAR");
536
1b4a47ca 537 for (it = dict_first(historys); it; it = iter_next(it)) {
cbc5a1a4 538 ma = iter_data(it);
1b4a47ca 539 for (ii = 0; ii < ma->hrecvd.used; ++ii) {
540 history = ma->hrecvd.list[ii];
541 if (!strcasecmp(history->sender->handle->handle, user->handle_info->handle)) {
cbc5a1a4 542 cc++;
1b4a47ca 543 localtime_r(&history->sent, &tm);
cbc5a1a4 544 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
1b4a47ca 545 reply("MSMSG_HISTORY_FORMAT", history->id, history->recipient->handle->handle, posted);
cbc5a1a4 546 }
547 }
548 }
549
550 if (cc == 0)
551 reply("MSG_NONE");
552 else
553 reply("MSMSG_MEMOS_FOUND", cc);
554
555 reply("MSMSG_LIST_END");
556
557 return 1;
558}
559
d76ed9a9 560static MODCMD_FUNC(cmd_read)
561{
562 struct memo_account *ma;
563 unsigned int memoid;
82794e1b 564 int rignore = 0, brk = 0, s = 0;
d76ed9a9 565 struct memo *memo;
8cfd8013 566 struct memo *memob;
d76ed9a9 567 char posted[24];
568 struct tm tm;
569
8108185c 570 if (argc > 2) {
82794e1b 571 char *argtwo = argv[2];
572 while (*argtwo) {
573 switch (*argtwo) {
574 case '-':
575 if (s != 0)
576 brk = 1;
577 break;
578
579 case 'i':
580 if (s > 0)
581 rignore = 1;
582 break;
583
584 default: break;
585 }
586
587 if (brk == 1)
588 break;
589 else {
590 s++;
591 argtwo++;
592 }
593 }
594 }
595
928e5a35 596 if (!(ma = memoserv_get_account(user->handle_info)))
597 return 0;
598
599 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
600 return 0;
601
d76ed9a9 602 localtime_r(&memo->sent, &tm);
603 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
82794e1b 604
d76ed9a9 605 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
606 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
607 memo->is_read = 1;
8cfd8013 608 memob = memo;
82794e1b 609
2a951803 610 if (ma->flags & MEMO_IGNORE_RECIEPTS)
611 rignore = 1;
612
82794e1b 613 if (memo->reciept == 1) {
614 memo->reciept = 0;
615 reply("MSMSG_MEMO_RECIEPT", rignore ? "ignoring" : "sending");
616 if (rignore == 0) {
617 struct memo_account *ma;
618 struct memo_account *sender;
619 char content[MAXLEN];
620
621 ma = memoserv_get_account(user->handle_info);
622 sender = memoserv_get_account(memo->sender->handle);
623
624 sprintf(content, "%s has read your memo dated %s.", ma->handle->handle, posted);
625
f2e592d3 626 memo = add_memo(now, sender, ma, content, 1);
8cfd8013 627 reply("MSMSG_MEMO_SENT", memob->sender->handle->handle, memo_id);
82794e1b 628
629 if (sender->flags & MEMO_NOTIFY_NEW) {
630 struct userNode *other;
631
632 for (other = sender->handle->users; other; other = other->next_authed)
fc4a50fc 633 send_message_type((ma->flags & MEMO_USE_PRIVMSG)? MSG_TYPE_PRIVMSG : MSG_TYPE_NOTICE, other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
82794e1b 634 }
635
636
637 }
638 }
d76ed9a9 639 return 1;
640}
641
642static MODCMD_FUNC(cmd_delete)
643{
644 struct memo_account *ma;
645 struct memo *memo;
646 unsigned int memoid;
647
68cb9c2c 648 MEMOSERV_MIN_PARAMS(2);
649
d76ed9a9 650 if (!(ma = memoserv_get_account(user->handle_info)))
651 return 0;
652 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
653 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
654 reply("MSMSG_USE_CONFIRM");
655 return 0;
656 }
657 while (ma->recvd.used)
658 delete_memo(ma->recvd.list[0]);
659 reply("MSMSG_DELETED_ALL");
660 return 1;
661 }
662
663 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
664 return 0;
665 delete_memo(memo);
666 reply("MSMSG_MEMO_DELETED", memoid);
667 return 1;
668}
669
f2e592d3 670static MODCMD_FUNC(cmd_cancel)
671{
672 unsigned long id;
673 unsigned int ii;
674 dict_iterator_t it;
675 struct memo *memo;
676 struct memo_account *ma;
677
68cb9c2c 678 MEMOSERV_MIN_PARAMS(2);
679
f2e592d3 680 if (isdigit(argv[1][0])) {
681 id = strtoul(argv[1], NULL, 0);
682 } else {
683 reply("MSMSG_MEMO_CANCEL_NUMBER");
684 return 0;
685 }
686
687 for (it = dict_first(memos); it; it = iter_next(it)) {
688 ma = iter_data(it);
689 for (ii = 0; ii < ma->recvd.used; ++ii) {
690 memo = ma->recvd.list[ii];
691
692 if (id == memo->id) {
693 if (!strcasecmp(memo->sender->handle->handle, user->handle_info->handle)) {
694 if (memo->is_read) {
695 reply("MSMSG_MEMO_READ", id);
696 return 0;
697 } else {
698 delete_memo(memo);
699 reply("MSMSG_MEMO_DELETED", id);
700 return 1;
701 }
702 } else {
703 reply("MSMSG_MEMO_DONT_OWN", id);
704 return 0;
705 }
706 }
707 }
708 }
709
710 reply("MSMSG_MEMO_CANT_LOCATE", id);
711 return 0;
712}
713
d76ed9a9 714static MODCMD_FUNC(cmd_expire)
715{
716 unsigned long old_expired = memosExpired;
717 do_expire();
718 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
719 return 1;
720}
721
722static MODCMD_FUNC(cmd_expiry)
723{
724 char interval[INTERVALLEN];
725
726 if (!memoserv_conf.message_expiry) {
727 reply("MSMSG_EXPIRY_OFF");
728 return 1;
729 }
730
731 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
732 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
733 return 1;
734}
735
e3e5ba49 736
737static void
c092fcad 738set_list(struct svccmd *cmd, struct userNode *user, struct handle_info *hi, int override)
e3e5ba49 739{
740 option_func_t *opt;
d9abe201 741 unsigned int i;
fc4a50fc 742 char *set_display[] = {"AUTHNOTIFY", "NEWNOTIFY", "PRIVMSG", "PRIVATE", "LIMIT",
2a951803 743 "IGNORERECIEPTS", "SENDRECIEPTS"};
e3e5ba49 744
c092fcad 745 reply("MSMSG_SET_OPTIONS");
746 reply("MSMSG_BAR");
e3e5ba49 747
748 /* Do this so options are presented in a consistent order. */
d9abe201 749 for (i = 0; i < ArrayLength(set_display); ++i)
750 if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
c092fcad 751 opt(cmd, user, hi, override, 0, NULL);
752 reply("MSMSG_SET_OPTIONS_END");
e3e5ba49 753}
754
755static MODCMD_FUNC(cmd_set)
756{
757 struct handle_info *hi;
758 option_func_t *opt;
759
760 hi = user->handle_info;
d9abe201 761 if (argc < 2) {
c092fcad 762 set_list(cmd, user, hi, 0);
e3e5ba49 763 return 1;
764 }
765
d9abe201 766 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL))) {
e3e5ba49 767 reply("MSMSG_INVALID_OPTION", argv[1]);
768 return 0;
769 }
d9abe201 770
c092fcad 771 return opt(cmd, user, hi, 0, argc-1, argv+1);
e3e5ba49 772}
773
774static MODCMD_FUNC(cmd_oset)
775{
776 struct handle_info *hi;
777 option_func_t *opt;
778
68cb9c2c 779 MEMOSERV_MIN_PARAMS(2);
eb43ca8c 780
c092fcad 781 if (!(hi = get_victim_oper(cmd, user, argv[1])))
d9abe201 782 return 0;
e3e5ba49 783
d9abe201 784 if (argc < 3) {
c092fcad 785 set_list(cmd, user, hi, 0);
e3e5ba49 786 return 1;
787 }
788
d9abe201 789 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL))) {
e3e5ba49 790 reply("MSMSG_INVALID_OPTION", argv[2]);
791 return 0;
792 }
793
c092fcad 794 return opt(cmd, user, hi, 1, argc-2, argv+2);
e3e5ba49 795}
796
fc4a50fc 797static OPTION_FUNC(opt_newnotify)
d76ed9a9 798{
799 struct memo_account *ma;
800 char *choice;
801
d9abe201 802 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 803 return 0;
804 if (argc > 1) {
805 choice = argv[1];
806 if (enabled_string(choice)) {
807 ma->flags |= MEMO_NOTIFY_NEW;
808 } else if (disabled_string(choice)) {
809 ma->flags &= ~MEMO_NOTIFY_NEW;
810 } else {
c092fcad 811 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9 812 return 0;
813 }
814 }
815
816 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
fc4a50fc 817 reply("MSMSG_SET_NEWNOTIFY", choice);
818 return 1;
819}
820
821static OPTION_FUNC(opt_privmsg)
822{
823 struct memo_account *ma;
824 char *choice;
825
826 if (!(ma = memoserv_get_account(hi)))
827 return 0;
828 if (argc > 1) {
829 choice = argv[1];
830 if (enabled_string(choice)) {
831 ma->flags |= MEMO_USE_PRIVMSG;
832 } else if (disabled_string(choice)) {
833 ma->flags &= ~MEMO_USE_PRIVMSG;
834 } else {
835 reply("MSMSG_INVALID_BINARY", choice);
836 return 0;
837 }
838 }
839 choice = (ma->flags & MEMO_USE_PRIVMSG) ? "on" : "off";
840 reply("MSMSG_SET_PRIVMSG", choice);
d76ed9a9 841 return 1;
842}
843
d9abe201 844static OPTION_FUNC(opt_authnotify)
d76ed9a9 845{
846 struct memo_account *ma;
847 char *choice;
848
d9abe201 849 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 850 return 0;
851 if (argc > 1) {
852 choice = argv[1];
853 if (enabled_string(choice)) {
854 ma->flags |= MEMO_NOTIFY_LOGIN;
855 } else if (disabled_string(choice)) {
856 ma->flags &= ~MEMO_NOTIFY_LOGIN;
857 } else {
c092fcad 858 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9 859 return 0;
860 }
861 }
862
863 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
c092fcad 864 reply("MSMSG_SET_AUTHNOTIFY", choice);
d76ed9a9 865 return 1;
866}
867
2a951803 868static OPTION_FUNC(opt_ignorereciepts)
869{
870 struct memo_account *ma;
871 char *choice;
872
873 if (!(ma = memoserv_get_account(hi)))
874 return 0;
875 if (argc > 1) {
876 choice = argv[1];
877 if (enabled_string(choice)) {
878 ma->flags |= MEMO_IGNORE_RECIEPTS;
879 } else if (disabled_string(choice)) {
880 ma->flags &= ~MEMO_IGNORE_RECIEPTS;
881 } else {
c092fcad 882 reply("MSMSG_INVALID_BINARY", choice);
2a951803 883 return 0;
884 }
885 }
886
887 choice = (ma->flags & MEMO_IGNORE_RECIEPTS) ? "on" : "off";
c092fcad 888 reply("MSMSG_SET_IGNORERECIEPTS", choice);
2a951803 889 return 1;
890}
891
892static OPTION_FUNC(opt_sendreciepts)
893{
894 struct memo_account *ma;
895 char *choice;
896
897 if (!(ma = memoserv_get_account(hi)))
898 return 0;
899 if (argc > 1) {
900 choice = argv[1];
901 if (enabled_string(choice)) {
902 ma->flags |= MEMO_ALWAYS_RECIEPTS;
903 } else if (disabled_string(choice)) {
904 ma->flags &= ~MEMO_ALWAYS_RECIEPTS;
905 } else {
c092fcad 906 reply("MSMSG_INVALID_BINARY", choice);
2a951803 907 return 0;
908 }
909 }
910
911 choice = (ma->flags & MEMO_ALWAYS_RECIEPTS) ? "on" : "off";
c092fcad 912 reply("MSMSG_SET_SENDRECIEPTS", choice);
2a951803 913 return 1;
914}
915
d9abe201 916static OPTION_FUNC(opt_private)
d76ed9a9 917{
918 struct memo_account *ma;
919 char *choice;
920
d9abe201 921 if (!(ma = memoserv_get_account(hi)))
d76ed9a9 922 return 0;
923 if (argc > 1) {
924 choice = argv[1];
925 if (enabled_string(choice)) {
926 ma->flags |= MEMO_DENY_NONCHANNEL;
927 } else if (disabled_string(choice)) {
928 ma->flags &= ~MEMO_DENY_NONCHANNEL;
929 } else {
c092fcad 930 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9 931 return 0;
932 }
933 }
934
935 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
c092fcad 936 reply("MSMSG_SET_PRIVATE", choice);
d76ed9a9 937 return 1;
938}
939
d9abe201 940static OPTION_FUNC(opt_limit)
acf3c6d5 941{
942 struct memo_account *ma;
a8692672 943 unsigned int choice;
acf3c6d5 944
d9abe201 945 if (!(ma = memoserv_get_account(hi)))
acf3c6d5 946 return 0;
947 if (argc > 1) {
948 choice = atoi(argv[1]);
949 if (choice > memoserv_conf.limit)
950 choice = memoserv_conf.limit;
951
952 ma->limit = choice;
953 }
954
c092fcad 955 reply("MSMSG_SET_LIMIT", ma->limit);
acf3c6d5 956 return 1;
957}
958
d76ed9a9 959static MODCMD_FUNC(cmd_status)
960{
1b4a47ca 961 struct memo_account *ma;
962 dict_iterator_t it;
963 int mc = 0, hc = 0;
964 unsigned int ii;
965
966 for (it = dict_first(memos); it; it = iter_next(it)) {
967 ma = iter_data(it);
968 for (ii = 0; ii < ma->recvd.used; ++ii)
969 mc++;
970 }
971
972 for (it = dict_first(historys); it; it = iter_next(it)) {
973 ma = iter_data(it);
974 for (ii = 0; ii < ma->hrecvd.used; ++ii)
975 hc++;
976 }
977
978 reply("MSMSG_STATUS_HIST_TOTAL", hc);
979 reply("MSMSG_STATUS_TOTAL", mc);
d76ed9a9 980 reply("MSMSG_STATUS_EXPIRED", memosExpired);
981 reply("MSMSG_STATUS_SENT", memosSent);
982 return 1;
983}
984
985static void
986memoserv_conf_read(void)
987{
988 dict_t conf_node;
989 const char *str;
990
991 str = "modules/memoserv";
992 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
993 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
994 return;
995 }
996
d9abe201 997 str = database_get_data(conf_node, "limit", RECDB_QSTRING);
a8370a20 998 memoserv_conf.limit = str ? atoi(str) : 50;
d9abe201 999
d76ed9a9 1000 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
1001 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
1002}
1003
1004static int
34a9e19a 1005memoserv_user_read(const char *key, struct record_data *hir)
1006{
1007 char *str;
1008 struct memo_account *ma;
1009 struct handle_info *hi;
1010
1011 if (!(hi = get_handle_info(key)))
1012 return 0;
1013
1014 ma = dict_find(memos, hi->handle, NULL);
1015 if (ma)
1016 return 0;
1017
1018
1019 ma = calloc(1, sizeof(*ma));
1020 if (!ma)
1021 return 0;
1022
1023 ma->handle = hi;
1024
1025 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
1026 if (!str) {
1027 log_module(MS_LOG, LOG_ERROR, "Flags not present in memo %s; skipping", key);
1028 return 0;
1029 }
1030 ma->flags = strtoul(str, NULL, 0);
1031
1032 str = database_get_data(hir->d.object, KEY_LIMIT, RECDB_QSTRING);
1033 if (!str) {
1034 log_module(MS_LOG, LOG_ERROR, "Limit not present in memo %s; skipping", key);
1035 return 0;
1036 }
1037 ma->limit = strtoul(str, NULL, 0);
1038
1039 dict_insert(memos, ma->handle->handle, ma);
1b4a47ca 1040 dict_insert(historys, ma->handle->handle, ma);
34a9e19a 1041
1042 return 0;
1043}
1044
1045static int
1046memoserv_memo_read(const char *key, struct record_data *hir)
d76ed9a9 1047{
1048 char *str;
1049 struct handle_info *sender, *recipient;
d76ed9a9 1050 struct memo *memo;
f2e592d3 1051 unsigned long id;
d76ed9a9 1052 time_t sent;
1053
34a9e19a 1054 if (hir->type != RECDB_OBJECT) {
1055 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
1056 return 0;
1057 }
d76ed9a9 1058
34a9e19a 1059 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
1060 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", key);
1061 return 0;
1062 }
d76ed9a9 1063
34a9e19a 1064 sent = atoi(str);
d76ed9a9 1065
f2e592d3 1066 if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
1067 log_module(MS_LOG, LOG_ERROR, "ID sent not present in memo %s; skipping", key);
1068 return 0;
1069 }
1070 id = strtoul(str, NULL, 0);
1071 if (id > memo_id)
1072 memo_id = id;
1073
34a9e19a 1074 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
1075 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", key);
1076 return 0;
1077 } else if (!(recipient = get_handle_info(str))) {
1078 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, key);
1079 return 0;
1080 }
d76ed9a9 1081
34a9e19a 1082 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
1083 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", key);
1084 return 0;
1085 } else if (!(sender = get_handle_info(str))) {
1086 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, key);
1087 return 0;
1088 }
82794e1b 1089
34a9e19a 1090 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
1091 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", key);
1092 return 0;
d76ed9a9 1093 }
34a9e19a 1094
f2e592d3 1095 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str, 0);
34a9e19a 1096 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
1097 memo->is_read = 1;
1098
1099 if ((str = database_get_data(hir->d.object, KEY_RECIEPT, RECDB_QSTRING)))
1100 memo->reciept = 1;
1101
f2e592d3 1102 memo->id = id;
1103
34a9e19a 1104 return 0;
1105}
1106
1b4a47ca 1107static int
1108memoserv_history_read(const char *key, struct record_data *hir)
1109{
1110 char *str;
1111 struct handle_info *sender, *recipient;
1112 struct history *history;
1113 unsigned long id;
1114 time_t sent;
1115
1116 if (hir->type != RECDB_OBJECT) {
1117 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
1118 return 0;
1119 }
1120
1121 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
1122 log_module(MS_LOG, LOG_ERROR, "Date sent not present in history %s; skipping", key);
1123 return 0;
1124 }
1125
1126 sent = atoi(str);
1127
1128 if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
1129 log_module(MS_LOG, LOG_ERROR, "ID sent not present in history %s; skipping", key);
1130 return 0;
1131 }
1132 id = strtoul(str, NULL, 0);
1133
1134 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
1135 log_module(MS_LOG, LOG_ERROR, "Recipient not present in history %s; skipping", key);
1136 return 0;
1137 } else if (!(recipient = get_handle_info(str))) {
1138 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in history %s; skipping", str, key);
1139 return 0;
1140 }
1141
1142 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
1143 log_module(MS_LOG, LOG_ERROR, "Sender not present in history %s; skipping", key);
1144 return 0;
1145 } else if (!(sender = get_handle_info(str))) {
1146 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in history %s; skipping", str, key);
1147 return 0;
1148 }
1149
1150 history = add_history(sent, memoserv_get_account(recipient), memoserv_get_account(sender), id);
1151
1152 return 0;
1153}
1154
34a9e19a 1155static int
1156memoserv_saxdb_read(struct dict *database)
1157{
1158 struct dict *section;
1159 dict_iterator_t it;
1160
1161 if((section = database_get_data(database, KEY_MAIN_ACCOUNTS, RECDB_OBJECT)))
1162 for(it = dict_first(section); it; it = iter_next(it))
1163 memoserv_user_read(iter_key(it), iter_data(it));
1164
1165 if((section = database_get_data(database, KEY_MAIN_MEMOS, RECDB_OBJECT)))
1166 for(it = dict_first(section); it; it = iter_next(it))
1167 memoserv_memo_read(iter_key(it), iter_data(it));
1168
1b4a47ca 1169 if((section = database_get_data(database, KEY_MAIN_HISTORY, RECDB_OBJECT)))
1170 for(it = dict_first(section); it; it = iter_next(it))
1171 memoserv_history_read(iter_key(it), iter_data(it));
1172
34a9e19a 1173 return 0;
1174}
1175
1176static int
1177memoserv_write_users(struct saxdb_context *ctx, struct memo_account *ma)
1178{
1179 saxdb_start_record(ctx, ma->handle->handle, 0);
1180
1181 saxdb_write_int(ctx, KEY_FLAGS, ma->flags);
1182 saxdb_write_int(ctx, KEY_LIMIT, ma->limit);
1183
1184 saxdb_end_record(ctx);
1185 return 0;
1186}
1187
1188static int
1189memoserv_write_memos(struct saxdb_context *ctx, struct memo *memo)
1190{
fc4a50fc 1191 char str[20];
34a9e19a 1192
fc4a50fc 1193 memset(str, '\0', sizeof(str));
1194 saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)-1), 0);
34a9e19a 1195
1196 saxdb_write_int(ctx, KEY_SENT, memo->sent);
f2e592d3 1197 saxdb_write_int(ctx, KEY_ID, memo->id);
34a9e19a 1198 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
1199 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
1200 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
1201
1202 if (memo->is_read)
1203 saxdb_write_int(ctx, KEY_READ, 1);
1204
1205 if (memo->reciept)
1206 saxdb_write_int(ctx, KEY_RECIEPT, 1);
1207
1208 saxdb_end_record(ctx);
d76ed9a9 1209 return 0;
1210}
1211
1b4a47ca 1212static int
1213memoserv_write_history(struct saxdb_context *ctx, struct history *history)
1214{
fc4a50fc 1215 char str[20];
1b4a47ca 1216
fc4a50fc 1217 memset(str, '\0', sizeof(str));
1218 saxdb_start_record(ctx, inttobase64(str, history->id, sizeof(str)-1), 0);
1b4a47ca 1219
1220 saxdb_write_int(ctx, KEY_SENT, history->sent);
1221 saxdb_write_int(ctx, KEY_ID, history->id);
1222 saxdb_write_string(ctx, KEY_RECIPIENT, history->recipient->handle->handle);
1223 saxdb_write_string(ctx, KEY_FROM, history->sender->handle->handle);
1224
1225 saxdb_end_record(ctx);
1226 return 0;
1227}
1228
d76ed9a9 1229static int
1230memoserv_saxdb_write(struct saxdb_context *ctx)
1231{
1232 dict_iterator_t it;
1233 struct memo_account *ma;
1234 struct memo *memo;
1b4a47ca 1235 struct history *history;
34a9e19a 1236 unsigned int ii;
d76ed9a9 1237
34a9e19a 1238 /* Accounts */
1239 saxdb_start_record(ctx, KEY_MAIN_ACCOUNTS, 1);
1240 for (it = dict_first(memos); it; it = iter_next(it)) {
1241 ma = iter_data(it);
1242 memoserv_write_users(ctx, ma);
1243 }
1244 saxdb_end_record(ctx);
1245
1b4a47ca 1246 /* Memos */
34a9e19a 1247 saxdb_start_record(ctx, KEY_MAIN_MEMOS, 1);
d76ed9a9 1248 for (it = dict_first(memos); it; it = iter_next(it)) {
1249 ma = iter_data(it);
1250 for (ii = 0; ii < ma->recvd.used; ++ii) {
1251 memo = ma->recvd.list[ii];
34a9e19a 1252 memoserv_write_memos(ctx, memo);
d76ed9a9 1253 }
1254 }
34a9e19a 1255 saxdb_end_record(ctx);
1256
1b4a47ca 1257 /* History */
1258 saxdb_start_record(ctx, KEY_MAIN_HISTORY, 1);
1259 for (it = dict_first(historys); it; it = iter_next(it)) {
1260 ma = iter_data(it);
1261 for (ii = 0; ii < ma->hrecvd.used; ++ii) {
1262 history = ma->hrecvd.list[ii];
1263 memoserv_write_history(ctx, history);
1264 }
1265 }
1266 saxdb_end_record(ctx);
1267
d76ed9a9 1268 return 0;
1269}
1270
1271static void
1272memoserv_cleanup(void)
1273{
1274 dict_delete(memos);
1b4a47ca 1275 dict_delete(historys);
d76ed9a9 1276}
1277
1278static void
1279memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
1280{
1281 unsigned int ii, unseen;
1282 struct memo_account *ma;
1283 struct memo *memo;
1284
7637f48f 1285 if (!user->uplink->burst) {
1286 if (!(ma = memoserv_get_account(user->handle_info))
1287 || !(ma->flags & MEMO_NOTIFY_LOGIN))
1288 return;
1289 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
1290 memo = ma->recvd.list[ii];
1291 if (!memo->is_read)
1292 unseen++;
1293 }
68cb9c2c 1294 if (ma->recvd.used && memoserv)
fc4a50fc 1295 if(unseen) send_message_type((ma->flags & MEMO_USE_PRIVMSG)? 1 : 0, user, memoserv, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
d76ed9a9 1296 }
d76ed9a9 1297}
1298
1299static void
1300memoserv_rename_account(struct handle_info *hi, const char *old_handle)
1301{
1302 struct memo_account *ma;
1303 if (!(ma = dict_find(memos, old_handle, NULL)))
1304 return;
1305 dict_remove2(memos, old_handle, 1);
1306 dict_insert(memos, hi->handle, ma);
1b4a47ca 1307
1308 dict_remove2(historys, old_handle, 1);
1309 dict_insert(historys, hi->handle, ma);
d76ed9a9 1310}
1311
1312static void
1313memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
1314{
1315 dict_remove(memos, handle->handle);
1b4a47ca 1316 dict_remove(historys, handle->handle);
d76ed9a9 1317}
1318
1319int
1320memoserv_init(void)
1321{
1322 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
1323 memos = dict_new();
1b4a47ca 1324 historys = dict_new();
d76ed9a9 1325 dict_set_free_data(memos, delete_memo_account);
1326 reg_auth_func(memoserv_check_messages);
1327 reg_handle_rename_func(memoserv_rename_account);
1328 reg_unreg_func(memoserv_unreg_account);
1329 conf_register_reload(memoserv_conf_read);
1330 reg_exit_func(memoserv_cleanup);
1331 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
1332
1333 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
cbc5a1a4 1334 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
1335 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
1336 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
1337 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
1338 modcmd_register(memoserv_module, "cancel", cmd_cancel, 2, MODCMD_REQUIRE_AUTHED, NULL);
1339 modcmd_register(memoserv_module, "history", cmd_history, 1, MODCMD_REQUIRE_AUTHED, NULL);
1340 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1341 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
1342 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
1343 modcmd_register(memoserv_module, "set", cmd_set, 1, MODCMD_REQUIRE_AUTHED, NULL);
1344 modcmd_register(memoserv_module, "oset", cmd_oset, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
e3e5ba49 1345
1346 memoserv_opt_dict = dict_new();
1347 dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
fc4a50fc 1348 dict_insert(memoserv_opt_dict, "NEWNOTIFY", opt_newnotify);
1349 dict_insert(memoserv_opt_dict, "PRIVMSG", opt_privmsg);
e3e5ba49 1350 dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
2a951803 1351 dict_insert(memoserv_opt_dict, "IGNORERECIEPTS", opt_ignorereciepts);
1352 dict_insert(memoserv_opt_dict, "SENDRECIEPTS", opt_sendreciepts);
d9abe201 1353 dict_insert(memoserv_opt_dict, "LIMIT", opt_limit);
e3e5ba49 1354
d76ed9a9 1355 message_register_table(msgtab);
1356
1357 if (memoserv_conf.message_expiry)
1358 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
e3e5ba49 1359
d76ed9a9 1360 return 1;
1361}
1362
1363int
1364memoserv_finalize(void) {
e3e5ba49 1365 struct chanNode *chan;
1366 unsigned int i;
d76ed9a9 1367 dict_t conf_node;
1368 const char *str;
1369
1370 str = "modules/memoserv";
1371 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
1372 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
1373 return 0;
1374 }
1375
1376 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
68cb9c2c 1377 if (str) {
1378 memoserv = memoserv_conf.bot;
14f0e274 1379 const char *modes = conf_get_data("modules/memoserv/modes", RECDB_QSTRING);
0f6fe38c 1380 memoserv = AddService(str, modes ? modes : NULL, "User-User Memorandum Services", NULL);
14f0e274 1381
6a64b9ce 1382 } else {
c092fcad 1383 log_module(MS_LOG, LOG_ERROR, "database_get_data for memoserv_conf.bot failed!");
6a64b9ce 1384 exit(1);
1385 }
e3e5ba49 1386
68cb9c2c 1387 if (autojoin_channels && memoserv) {
e3e5ba49 1388 for (i = 0; i < autojoin_channels->used; i++) {
1389 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
68cb9c2c 1390 AddChannelUser(memoserv, chan)->modes |= MODE_CHANOP;
e3e5ba49 1391 }
1392 }
1393
d76ed9a9 1394 return 1;
1395}