]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/mod-memoserv.c
Finally fixed all those pesky warnings
[irc/evilnet/x3.git] / src / mod-memoserv.c
index ea245fc4f7c81f1737bedfd88058797b05103696..036902a1616fd06e58fd33f5999d8a906a352979 100644 (file)
@@ -6,7 +6,7 @@
  *
  * x3 is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * Restart srvx with the updated conf file (as above, butwith "bot"
  * "MemoServ"), and bind the commands to it:
  * /msg opserv bind memoserv * *memoserv.*
- * /msg opserv bind memoserv set *modcmd.joiner
+ * /msg opserv bind memoserv set *memoserv.set
  */
 
 #include "chanserv.h"
 #include "conf.h"
 #include "modcmd.h"
 #include "nickserv.h"
+#include "opserv.h"
 #include "saxdb.h"
+#include "mail.h"
 #include "timeq.h"
 
 #define KEY_MAIN_ACCOUNTS "accounts"
 #define KEY_FLAGS "flags"
 #define KEY_LIMIT "limit"
 
+#define KEY_MAIN_HISTORY "history"
 #define KEY_MAIN_MEMOS "memos"
 #define KEY_SENT "sent"
 #define KEY_RECIPIENT "to"
 #define KEY_RECIEPT "reciept"
 #define KEY_ID "id"
 
+
 static const struct message_entry msgtab[] = {
     { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
+    { "MSMSG_UNKNOWN_SEND_FLAG", "Unreccognised send flag '%c', message not sent." },
     { "MSMSG_MEMO_SENT", "Message sent to $b%s$b (ID# %d)." },
     { "MSMSG_NO_MESSAGES", "You have no messages." },
-    { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
-    { "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." },
+    { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches." },
+    { "MSMSG_HOWTO_READ", "Use READ <ID> to read a message." },
+    { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse READ <ID> to read a message." },
     { "MSMSG_LIST_HEAD",      "$bID$b   $bFrom$b       $bTime Sent$b" },
     { "MSMSG_LIST_FORMAT",    "%-2u     %s $b%s$b          %s" },
     { "MSMSG_HISTORY_HEADER", "$bID$b   $bTo$b          $bTime Sent$b" },
@@ -80,30 +86,37 @@ static const struct message_entry msgtab[] = {
     { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
     { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
     { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
-    { "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." },
-    { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. /msg $S LIST" },
+    { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages.  Use LIST to list them." },
+    { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. Use LIST to see your messages." },
     { "MSMSG_FULL_INBOX",  "$b%s$b cannot recieve anymore memos as their inbox is full" },
     { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
-    { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
+    { "MSMSG_USE_CONFIRM", "Please use DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
 
+    { "MSMSG_STATUS_HIST_TOTAL",   "I have $b%u$b history entries in my database." },
     { "MSMSG_STATUS_TOTAL",   "I have $b%u$b memos in my database." },
     { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
     { "MSMSG_STATUS_SENT",    "$b%ld$b memos have been sent." },
 
     { "MSMSG_INVALID_OPTION",  "$b%s$b is not a valid option." },
     { "MSMSG_INVALID_BINARY",  "$b%s$b is an invalid binary value." },
-    { "MSMSG_SET_NOTIFY",          "$bNotify:        $b %s" },
-    { "MSMSG_SET_AUTHNOTIFY",      "$bAuthNotify:    $b %s" },
-    { "MSMSG_SET_PRIVATE",         "$bPrivate:       $b %s" },
-    { "MSMSG_SET_IGNORERECIEPTS",  "$bIgnoreReciepts:$b %s" },
-    { "MSMSG_SET_SENDRECIEPTS",    "$bSendReciepts:  $b %s" },
-    { "MSMSG_SET_LIMIT",           "$bLimit:         $b %d" },
+    { "MSMSG_SET_AUTHNOTIFY",      "$bAuthNotify$b:       %s" },
+    { "MSMSG_SET_NEWNOTIFY",       "$bNewNotify$b:        %s" },
+    { "MSMSG_SET_PRIVMSG",         "$bPrivmsg$b:          %s" },
+    { "MSMSG_SET_PRIVATE",         "$bPrivate$b:          %s" },
+    { "MSMSG_SET_IGNORERECIEPTS",  "$bIgnoreReciepts$b:   %s" },
+    { "MSMSG_SET_SENDRECIEPTS",    "$bSendReciepts$b:     %s" },
+    { "MSMSG_SET_LIMIT",           "$bLimit$b:            %d" },
     { "MSMSG_SET_OPTIONS",         "$bMessaging Options$b" },
     { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
 
     { "MSMSG_LIST_END",        "--------------End of Memos--------------" },
     { "MSMSG_BAR",             "----------------------------------------"},
 
+    { "MSEMAIL_NEWMEMO_SUBJECT", "New %s %s message from %s" },
+    { "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." },
+
+    { "MSMSG_DEFCON_NO_NEW_MEMOS", "You cannot send new memos at this time, please try again soon." },
+
     { NULL, NULL }
 };
 
@@ -117,8 +130,26 @@ struct memo {
     unsigned int reciept : 1;
 };
 
+struct history {
+    struct memo_account *recipient;
+    struct memo_account *sender;
+    time_t sent;
+    unsigned long id;
+};
+
+struct userNode *memoserv;
+
+#define MEMOSERV_FUNC(NAME)         MODCMD_FUNC(NAME)
+#define MEMOSERV_SYNTAX()           svccmd_send_help_brief(user, memoserv, cmd)
+#define MEMOSERV_MIN_PARAMS(N)      if(argc < (N)) {            \
+                                     reply("MSG_MISSING_PARAMS", argv[0]); \
+                                     MEMOSERV_SYNTAX(); \
+                                     return 0; }
+
 DECLARE_LIST(memoList, struct memo*);
-DEFINE_LIST(memoList, struct memo*);
+DEFINE_LIST(memoList, struct memo*)
+DECLARE_LIST(historyList, struct history*);
+DEFINE_LIST(historyList, struct history*)
 
 /* memo_account.flags fields */
 #define MEMO_NOTIFY_NEW      0x00000001
@@ -126,13 +157,16 @@ DEFINE_LIST(memoList, struct memo*);
 #define MEMO_DENY_NONCHANNEL 0x00000004
 #define MEMO_IGNORE_RECIEPTS 0x00000008
 #define MEMO_ALWAYS_RECIEPTS 0x00000010
+#define MEMO_USE_PRIVMSG     0x00000020
 
 struct memo_account {
     struct handle_info *handle;
-    unsigned int flags;
+    unsigned int flags : 6;
     unsigned int limit;
     struct memoList sent;
     struct memoList recvd;
+    struct historyList hsent;
+    struct historyList hrecvd;
 };
 
 static struct {
@@ -142,7 +176,7 @@ static struct {
 } memoserv_conf;
 
 #define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
-#define OPTION_FUNC(NAME) int NAME(struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
+#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[])
 typedef OPTION_FUNC(option_func_t);
 
 unsigned long memo_id;
@@ -151,8 +185,11 @@ extern struct string_list *autojoin_channels;
 const char *memoserv_module_deps[] = { NULL };
 static struct module *memoserv_module;
 static struct log_type *MS_LOG;
-static unsigned long memosSent, memosExpired;
+static unsigned long memoCount;
+static unsigned long memosSent;
+static unsigned long memosExpired;
 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
+static struct dict *historys;
 static dict_t memoserv_opt_dict; /* contains option_func_t* */
 
 static struct memo_account *
@@ -168,9 +205,10 @@ memoserv_get_account(struct handle_info *hi)
     if (!ma)
         return ma;
     ma->handle = hi;
-    ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
+    ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN | MEMO_USE_PRIVMSG;
     ma->limit = memoserv_conf.limit;
     dict_insert(memos, ma->handle->handle, ma);
+    dict_insert(historys, ma->handle->handle, ma);
     return ma;
 }
 
@@ -181,6 +219,15 @@ delete_memo(struct memo *memo)
     memoList_remove(&memo->sender->sent, memo);
     free(memo->message);
     free(memo);
+    memoCount--;
+}
+
+static void
+delete_history(struct history *history)
+{
+    historyList_remove(&history->recipient->hrecvd, history);
+    historyList_remove(&history->sender->hsent, history);
+    free(history);
 }
 
 static void
@@ -194,6 +241,13 @@ delete_memo_account(void *data)
         delete_memo(ma->sent.list[0]);
     memoList_clean(&ma->recvd);
     memoList_clean(&ma->sent);
+
+    while (ma->hrecvd.used)
+        delete_history(ma->hrecvd.list[0]);
+    while (ma->hsent.used)
+        delete_history(ma->hsent.list[0]);
+    historyList_clean(&ma->hrecvd);
+    historyList_clean(&ma->hsent);
     free(ma);
 }
 
@@ -202,10 +256,10 @@ do_expire(void)
 {
     dict_iterator_t it;
     for (it = dict_first(memos); it; it = iter_next(it)) {
-        struct memo_account *acct = iter_data(it);
+        struct memo_account *account = iter_data(it);
         unsigned int ii;
-        for (ii = 0; ii < acct->sent.used; ++ii) {
-            struct memo *memo = acct->sent.list[ii];
+        for (ii = 0; ii < account->sent.used; ++ii) {
+            struct memo *memo = account->sent.list[ii];
             if ((now - memo->sent) > memoserv_conf.message_expiry) {
                 delete_memo(memo);
                 memosExpired++;
@@ -213,6 +267,19 @@ do_expire(void)
             }
         }
     }
+
+    for (it = dict_first(historys); it; it = iter_next(it)) {
+        struct memo_account *account = iter_data(it);
+        unsigned int ii;
+        for (ii = 0; ii < account->hsent.used; ++ii) {
+            struct history *history = account->hsent.list[ii];
+            if ((now - history->sent) > memoserv_conf.message_expiry) {
+                delete_history(history);
+                memosExpired++;
+                ii--;
+            }
+        }
+    }
 }
 
 static void
@@ -224,6 +291,26 @@ expire_memos(UNUSED_ARG(void *data))
     }
 }
 
+static struct history*
+add_history(time_t sent, struct memo_account *recipient, struct memo_account *sender, unsigned long id)
+{
+    struct history *history;
+
+    history = calloc(1, sizeof(*history));
+    if (!history)
+        return NULL;
+
+    history->id = id;
+    history->recipient = recipient;
+    historyList_append(&recipient->hrecvd, history);
+    history->sender = sender;
+    historyList_append(&sender->hsent, history);
+    history->sent = sent;
+
+    return history;
+}
+
+
 static struct memo*
 add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message, int nfrom_read)
 {
@@ -245,6 +332,11 @@ add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sende
     memo->sent = sent;
     memo->message = strdup(message);
     memosSent++;
+    memoCount++;
+
+    if (nfrom_read)
+        add_history(sent, recipient, sender, memo->id);
+
     return memo;
 }
 
@@ -271,7 +363,7 @@ memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_accou
 
     if (acct->handle->ignores->used) {
         for (i=0; i < acct->handle->ignores->used; i++) {
-            if (user_matches_glob(user, acct->handle->ignores->list[i], MATCH_USENICK)) {
+            if (user_matches_glob(user, acct->handle->ignores->list[i], MATCH_USENICK, 0)) {
                 match = 1;
                 break;
             }
@@ -315,11 +407,20 @@ static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct
 static MODCMD_FUNC(cmd_send)
 {
     char *message;
-    int s = 0, brk = 0;
-    int reciept = 0, inc = 2;
+    int reciept = 0, inc = 2, email = 0;
     struct handle_info *hi;
     struct memo_account *ma, *sender;
     struct memo *memo;
+    char subject[128], body[4096];
+    char *estr;
+    const char *netname, *fmt;
+
+    MEMOSERV_MIN_PARAMS(3);
+
+    if (checkDefCon(DEFCON_NO_NEW_MEMOS) && !IsOper(user)) {
+        reply("MSMSG_DEFCON_NO_NEW_MEMOS");
+        return 0;
+    }
 
     if (!(hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
@@ -333,33 +434,25 @@ static MODCMD_FUNC(cmd_send)
     if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
         return 0;
 
-    char *flags = argv[2];
-    while (*flags) {
-        switch (*flags) {
-            case '-':
-                if (s != 0)
-                    brk = 1;
-                break;
-
-            case 'r':
-                if (s > 0)
+    inc = 2; /* Start of message on 3rd ([2]) word */
+    if(argv[2][0] == '-' && argv[2][1] != '-') { /* first word is flags ('-r')*/
+        char *flags = argv[2];
+        inc++; /* Start of message is now 1 word later */
+        for(flags++;*flags;flags++) {
+            switch (*flags) {
+                case 'r':
                     reciept = 1;
                 break;
 
             default:
-                break;
-        }
-
-        if (brk == 1)
-            break;
-        else {
-            s++;
-            flags++;
+                /* Unknown mode. Give an error */
+                reply("MSMSG_UNKNOWN_SEND_FLAG", *flags);
+                return 0;
+            }
         }
     }
-
-    if (s > 0)
-        inc = 3;
+    else
+        inc = 2; /* Start of message is word 2 */
 
     message = unsplit_string(argv + inc, argc - inc, NULL);
     memo = add_memo(now, ma, sender, message, 1);
@@ -370,7 +463,20 @@ static MODCMD_FUNC(cmd_send)
         struct userNode *other;
 
         for (other = ma->handle->users; other; other = other->next_authed)
-            send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
+            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);
+    }
+
+    estr = conf_get_data("services/nickserv/email_enabled", RECDB_QSTRING);
+    netname = conf_get_data("server/network", RECDB_QSTRING);
+    email = atoi(estr);
+    if (email && (ma->flags & MEMO_NOTIFY_NEW)) {
+        fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_SUBJECT");
+        snprintf(subject, sizeof(subject), fmt, netname, memoserv->nick, user->nick);
+
+        fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_BODY");
+        snprintf(body, sizeof(body), fmt, user->nick, memoserv->nick, message, memoserv->nick, memo_id, netname);
+
+        mail_send(memoserv, hi, subject, body, 0);
     }
 
     reply("MSMSG_MEMO_SENT", ma->handle->handle, memo_id);
@@ -403,8 +509,10 @@ static MODCMD_FUNC(cmd_list)
         reply("MSG_NONE");
     else if (ii == 15)
         reply("MSMSG_CLEAN_INBOX", ii);
-    else
+    else {
         reply("MSMSG_MEMOS_FOUND", ii);
+        reply("MSMSG_HOWTO_READ");
+    }
 
     reply("MSMSG_LIST_END");
 
@@ -414,7 +522,7 @@ static MODCMD_FUNC(cmd_list)
 static MODCMD_FUNC(cmd_history)
 {
     struct memo_account *ma;
-    struct memo *memo;
+    struct history *history;
     dict_iterator_t it;
     unsigned int ii = 0;
     unsigned int cc = 0;
@@ -429,15 +537,15 @@ static MODCMD_FUNC(cmd_history)
     if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
         reply("MSMSG_BAR");
 
-    for (it = dict_first(memos); it; it = iter_next(it)) {
+    for (it = dict_first(historys); it; it = iter_next(it)) {
         ma = iter_data(it);
-        for (ii = 0; ii < ma->recvd.used; ++ii) {
-            memo = ma->recvd.list[ii];
-            if (!strcasecmp(memo->sender->handle->handle, user->handle_info->handle)) {
+        for (ii = 0; ii < ma->hrecvd.used; ++ii) {
+            history = ma->hrecvd.list[ii];
+            if (!strcasecmp(history->sender->handle->handle, user->handle_info->handle)) {
                 cc++;
-                localtime_r(&memo->sent, &tm);
+                localtime_r(&history->sent, &tm);
                 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
-                reply("MSMSG_HISTORY_FORMAT", memo->id, memo->sender->handle->handle, posted);
+                reply("MSMSG_HISTORY_FORMAT", history->id, history->recipient->handle->handle, posted);
             }
         }
     }
@@ -462,12 +570,7 @@ static MODCMD_FUNC(cmd_read)
     char posted[24];
     struct tm tm;
 
-    if (!(ma = memoserv_get_account(user->handle_info)))
-        return 0;
-    if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
-        return 0;
-
-    if (argv[2]) {
+    if (argc > 2) {
         char *argtwo = argv[2];
         while (*argtwo) {
             switch (*argtwo) {
@@ -493,6 +596,12 @@ static MODCMD_FUNC(cmd_read)
         }
     }
 
+    if (!(ma = memoserv_get_account(user->handle_info)))
+        return 0;
+    
+    if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
+        return 0;
+
     localtime_r(&memo->sent, &tm);
     strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
 
@@ -524,7 +633,7 @@ static MODCMD_FUNC(cmd_read)
                 struct userNode *other;
 
                 for (other = sender->handle->users; other; other = other->next_authed)
-                    send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
+                    send_message_type((ma->flags & MEMO_USE_PRIVMSG)? MSG_TYPE_PRIVMSG : MSG_TYPE_NOTICE, other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
             }
 
 
@@ -539,6 +648,8 @@ static MODCMD_FUNC(cmd_delete)
     struct memo *memo;
     unsigned int memoid;
 
+    MEMOSERV_MIN_PARAMS(2);
+
     if (!(ma = memoserv_get_account(user->handle_info)))
         return 0;
     if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
@@ -567,6 +678,8 @@ static MODCMD_FUNC(cmd_cancel)
     struct memo *memo;
     struct memo_account *ma;
 
+    MEMOSERV_MIN_PARAMS(2);
+
     if (isdigit(argv[1][0])) {
         id = strtoul(argv[1], NULL, 0);
     } else {
@@ -625,21 +738,21 @@ static MODCMD_FUNC(cmd_expiry)
 
 
 static void
-set_list(struct userNode *user, struct handle_info *hi, int override)
+set_list(struct svccmd *cmd, struct userNode *user, struct handle_info *hi, int override)
 {
     option_func_t *opt;
     unsigned int i;
-    char *set_display[] = {"AUTHNOTIFY", "NOTIFY", "PRIVATE", "LIMIT",
+    char *set_display[] = {"AUTHNOTIFY", "NEWNOTIFY", "PRIVMSG", "PRIVATE", "LIMIT",
                            "IGNORERECIEPTS", "SENDRECIEPTS"};
 
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS");
-    send_message(user, memoserv_conf.bot, "MSMSG_BAR");
+    reply("MSMSG_SET_OPTIONS");
+    reply("MSMSG_BAR");
 
     /* Do this so options are presented in a consistent order. */
     for (i = 0; i < ArrayLength(set_display); ++i)
         if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
-            opt(user, hi, override, 0, NULL);
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS_END");
+            opt(cmd, user, hi, override, 0, NULL);
+    reply("MSMSG_SET_OPTIONS_END");
 }
 
 static MODCMD_FUNC(cmd_set)
@@ -649,7 +762,7 @@ static MODCMD_FUNC(cmd_set)
 
     hi = user->handle_info;
     if (argc < 2) {
-        set_list(user, hi, 0);
+        set_list(cmd, user, hi, 0);
         return 1;
     }
 
@@ -658,7 +771,7 @@ static MODCMD_FUNC(cmd_set)
         return 0;
     }
 
-    return opt(user, hi, 0, argc-1, argv+1);
+    return opt(cmd, user, hi, 0, argc-1, argv+1);
 }
 
 static MODCMD_FUNC(cmd_oset)
@@ -666,11 +779,13 @@ static MODCMD_FUNC(cmd_oset)
     struct handle_info *hi;
     option_func_t *opt;
 
+    MEMOSERV_MIN_PARAMS(2);
+
     if (!(hi = get_victim_oper(user, argv[1])))
         return 0;
 
     if (argc < 3) {
-        set_list(user, hi, 0);
+        set_list(cmd, user, hi, 0);
         return 1;
     }
 
@@ -679,10 +794,10 @@ static MODCMD_FUNC(cmd_oset)
         return 0;
     }
 
-    return opt(user, hi, 1, argc-2, argv+2);
+    return opt(cmd, user, hi, 1, argc-2, argv+2);
 }
 
-static OPTION_FUNC(opt_notify)
+static OPTION_FUNC(opt_newnotify)
 {
     struct memo_account *ma;
     char *choice;
@@ -696,13 +811,36 @@ static OPTION_FUNC(opt_notify)
         } else if (disabled_string(choice)) {
             ma->flags &= ~MEMO_NOTIFY_NEW;
         } else {
-            send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
+            reply("MSMSG_INVALID_BINARY", choice);
             return 0;
         }
     }
 
     choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_NOTIFY", choice);
+    reply("MSMSG_SET_NEWNOTIFY", choice);
+    return 1;
+}
+
+static OPTION_FUNC(opt_privmsg)
+{
+    struct memo_account *ma;
+    char *choice;
+
+    if (!(ma = memoserv_get_account(hi)))
+        return 0;
+    if (argc > 1) {
+        choice = argv[1];
+        if (enabled_string(choice)) {
+            ma->flags |= MEMO_USE_PRIVMSG;
+        } else if (disabled_string(choice)) {
+            ma->flags &= ~MEMO_USE_PRIVMSG;
+        } else {
+            reply("MSMSG_INVALID_BINARY", choice);
+            return 0;
+        }
+    }
+    choice = (ma->flags & MEMO_USE_PRIVMSG) ? "on" : "off";
+    reply("MSMSG_SET_PRIVMSG", choice);
     return 1;
 }
 
@@ -720,13 +858,13 @@ static OPTION_FUNC(opt_authnotify)
         } else if (disabled_string(choice)) {
             ma->flags &= ~MEMO_NOTIFY_LOGIN;
         } else {
-            send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
+            reply("MSMSG_INVALID_BINARY", choice);
             return 0;
         }
     }
 
     choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_AUTHNOTIFY", choice);
+    reply("MSMSG_SET_AUTHNOTIFY", choice);
     return 1;
 }
 
@@ -744,13 +882,13 @@ static OPTION_FUNC(opt_ignorereciepts)
         } else if (disabled_string(choice)) {
             ma->flags &= ~MEMO_IGNORE_RECIEPTS;
         } else {
-            send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
+            reply("MSMSG_INVALID_BINARY", choice);
             return 0;
         }
     }
 
     choice = (ma->flags & MEMO_IGNORE_RECIEPTS) ? "on" : "off";
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_IGNORERECIEPTS", choice);
+    reply("MSMSG_SET_IGNORERECIEPTS", choice);
     return 1;
 }
 
@@ -768,13 +906,13 @@ static OPTION_FUNC(opt_sendreciepts)
         } else if (disabled_string(choice)) {
             ma->flags &= ~MEMO_ALWAYS_RECIEPTS;
         } else {
-            send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
+            reply("MSMSG_INVALID_BINARY", choice);
             return 0;
         }
     }
 
     choice = (ma->flags & MEMO_ALWAYS_RECIEPTS) ? "on" : "off";
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_SENDRECIEPTS", choice);
+    reply("MSMSG_SET_SENDRECIEPTS", choice);
     return 1;
 }
 
@@ -792,13 +930,13 @@ static OPTION_FUNC(opt_private)
         } else if (disabled_string(choice)) {
             ma->flags &= ~MEMO_DENY_NONCHANNEL;
         } else {
-            send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
+            reply("MSMSG_INVALID_BINARY", choice);
             return 0;
         }
     }
 
     choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_PRIVATE", choice);
+    reply("MSMSG_SET_PRIVATE", choice);
     return 1;
 }
 
@@ -817,13 +955,31 @@ static OPTION_FUNC(opt_limit)
         ma->limit = choice;
     }
 
-    send_message(user, memoserv_conf.bot, "MSMSG_SET_LIMIT", ma->limit);
+    reply("MSMSG_SET_LIMIT", ma->limit);
     return 1;
 }
 
 static MODCMD_FUNC(cmd_status)
 {
-    reply("MSMSG_STATUS_TOTAL", dict_size(memos));
+    struct memo_account *ma;
+    dict_iterator_t it;
+    int mc = 0, hc = 0;
+    unsigned int ii;
+
+    for (it = dict_first(memos); it; it = iter_next(it)) {
+        ma = iter_data(it);
+        for (ii = 0; ii < ma->recvd.used; ++ii)
+            mc++;
+    }
+
+    for (it = dict_first(historys); it; it = iter_next(it)) {
+        ma = iter_data(it);
+        for (ii = 0; ii < ma->hrecvd.used; ++ii)
+            hc++;
+    }
+
+    reply("MSMSG_STATUS_HIST_TOTAL", hc);
+    reply("MSMSG_STATUS_TOTAL", memoCount);
     reply("MSMSG_STATUS_EXPIRED", memosExpired);
     reply("MSMSG_STATUS_SENT", memosSent);
     return 1;
@@ -884,6 +1040,7 @@ memoserv_user_read(const char *key, struct record_data *hir)
     ma->limit = strtoul(str, NULL, 0);
 
     dict_insert(memos, ma->handle->handle, ma);
+    dict_insert(historys, ma->handle->handle, ma);
 
     return 0;
 }
@@ -950,6 +1107,53 @@ memoserv_memo_read(const char *key, struct record_data *hir)
     return 0;
 }
 
+static int
+memoserv_history_read(const char *key, struct record_data *hir)
+{
+    char *str;
+    struct handle_info *sender, *recipient;
+    unsigned long id;
+    time_t sent;
+
+    if (hir->type != RECDB_OBJECT) {
+        log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
+        return 0;
+    }
+
+    if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
+        log_module(MS_LOG, LOG_ERROR, "Date sent not present in history %s; skipping", key);
+        return 0;
+    }
+
+    sent = atoi(str);
+
+    if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
+        log_module(MS_LOG, LOG_ERROR, "ID sent not present in history %s; skipping", key);
+        return 0;
+    }
+    id = strtoul(str, NULL, 0);
+
+    if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
+        log_module(MS_LOG, LOG_ERROR, "Recipient not present in history %s; skipping", key);
+        return 0;
+    } else if (!(recipient = get_handle_info(str))) {
+        log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in history %s; skipping", str, key);
+        return 0;
+    }
+
+    if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
+        log_module(MS_LOG, LOG_ERROR, "Sender not present in history %s; skipping", key);
+        return 0;
+    } else if (!(sender = get_handle_info(str))) {
+        log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in history %s; skipping", str, key);
+        return 0;
+    }
+
+    add_history(sent, memoserv_get_account(recipient), memoserv_get_account(sender), id);
+
+    return 0;
+}
+
 static int
 memoserv_saxdb_read(struct dict *database)
 {
@@ -964,6 +1168,10 @@ memoserv_saxdb_read(struct dict *database)
         for(it = dict_first(section); it; it = iter_next(it))
             memoserv_memo_read(iter_key(it), iter_data(it));
 
+    if((section = database_get_data(database, KEY_MAIN_HISTORY, RECDB_OBJECT)))
+        for(it = dict_first(section); it; it = iter_next(it))
+            memoserv_history_read(iter_key(it), iter_data(it));
+
     return 0;
 }
 
@@ -982,9 +1190,10 @@ memoserv_write_users(struct saxdb_context *ctx, struct memo_account *ma)
 static int
 memoserv_write_memos(struct saxdb_context *ctx, struct memo *memo)
 {
-    char str[7];
+    char str[20];
 
-    saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)), 0);
+    memset(str, '\0', sizeof(str));
+    saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)-1), 0);
 
     saxdb_write_int(ctx, KEY_SENT, memo->sent);
     saxdb_write_int(ctx, KEY_ID, memo->id);
@@ -1002,12 +1211,30 @@ memoserv_write_memos(struct saxdb_context *ctx, struct memo *memo)
     return 0;
 }
 
+static int
+memoserv_write_history(struct saxdb_context *ctx, struct history *history)
+{
+    char str[20];
+
+    memset(str, '\0', sizeof(str));
+    saxdb_start_record(ctx, inttobase64(str, history->id, sizeof(str)-1), 0);
+
+    saxdb_write_int(ctx, KEY_SENT, history->sent);
+    saxdb_write_int(ctx, KEY_ID, history->id);
+    saxdb_write_string(ctx, KEY_RECIPIENT, history->recipient->handle->handle);
+    saxdb_write_string(ctx, KEY_FROM, history->sender->handle->handle);
+
+    saxdb_end_record(ctx);
+    return 0;
+}
+
 static int
 memoserv_saxdb_write(struct saxdb_context *ctx)
 {
     dict_iterator_t it;
     struct memo_account *ma;
     struct memo *memo;
+    struct history *history;
     unsigned int ii;
 
     /* Accounts */
@@ -1018,7 +1245,7 @@ memoserv_saxdb_write(struct saxdb_context *ctx)
     }
     saxdb_end_record(ctx);
 
-    /* Channels */
+    /* Memos */
     saxdb_start_record(ctx, KEY_MAIN_MEMOS, 1);
     for (it = dict_first(memos); it; it = iter_next(it)) {
         ma = iter_data(it);
@@ -1029,17 +1256,29 @@ memoserv_saxdb_write(struct saxdb_context *ctx)
     }
     saxdb_end_record(ctx);
 
+    /* History */
+    saxdb_start_record(ctx, KEY_MAIN_HISTORY, 1);
+    for (it = dict_first(historys); it; it = iter_next(it)) {
+        ma = iter_data(it);
+        for (ii = 0; ii < ma->hrecvd.used; ++ii) {
+            history = ma->hrecvd.list[ii];
+            memoserv_write_history(ctx, history);
+        }
+    }
+    saxdb_end_record(ctx);
+
     return 0;
 }
 
 static void
-memoserv_cleanup(void)
+memoserv_cleanup(UNUSED_ARG(void *extra))
 {
     dict_delete(memos);
+    dict_delete(historys);
 }
 
 static void
-memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
+memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle), UNUSED_ARG(void *extra))
 {
     unsigned int ii, unseen;
     struct memo_account *ma;
@@ -1054,25 +1293,29 @@ memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *ol
             if (!memo->is_read)
                 unseen++;
         }
-        if (ma->recvd.used && memoserv_conf.bot)
-            if(unseen) send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
+        if (ma->recvd.used && memoserv)
+            if(unseen) send_message_type((ma->flags & MEMO_USE_PRIVMSG)? 1 : 0, user, memoserv, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
     }
 }
 
 static void
-memoserv_rename_account(struct handle_info *hi, const char *old_handle)
+memoserv_rename_account(struct handle_info *hi, const char *old_handle, UNUSED_ARG(void *extra))
 {
     struct memo_account *ma;
     if (!(ma = dict_find(memos, old_handle, NULL)))
         return;
     dict_remove2(memos, old_handle, 1);
     dict_insert(memos, hi->handle, ma);
+
+    dict_remove2(historys, old_handle, 1);
+    dict_insert(historys, hi->handle, ma);
 }
 
 static void
-memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
+memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle, UNUSED_ARG(void *extra))
 {
     dict_remove(memos, handle->handle);
+    dict_remove(historys, handle->handle);
 }
 
 int
@@ -1080,12 +1323,13 @@ memoserv_init(void)
 {
     MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
     memos = dict_new();
+    historys = dict_new();
     dict_set_free_data(memos, delete_memo_account);
-    reg_auth_func(memoserv_check_messages);
-    reg_handle_rename_func(memoserv_rename_account);
-    reg_unreg_func(memoserv_unreg_account);
+    reg_auth_func(memoserv_check_messages, NULL);
+    reg_handle_rename_func(memoserv_rename_account, NULL);
+    reg_unreg_func(memoserv_unreg_account, NULL);
     conf_register_reload(memoserv_conf_read);
-    reg_exit_func(memoserv_cleanup);
+    reg_exit_func(memoserv_cleanup, NULL);
     saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
 
     memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
@@ -1103,7 +1347,8 @@ memoserv_init(void)
 
     memoserv_opt_dict = dict_new();
     dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
-    dict_insert(memoserv_opt_dict, "NOTIFY", opt_notify);
+    dict_insert(memoserv_opt_dict, "NEWNOTIFY", opt_newnotify);
+    dict_insert(memoserv_opt_dict, "PRIVMSG", opt_privmsg);
     dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
     dict_insert(memoserv_opt_dict, "IGNORERECIEPTS", opt_ignorereciepts);
     dict_insert(memoserv_opt_dict, "SENDRECIEPTS", opt_sendreciepts);
@@ -1131,13 +1376,20 @@ memoserv_finalize(void) {
     }
 
     str = database_get_data(conf_node, "bot", RECDB_QSTRING);
-    if (str)
-        memoserv_conf.bot = GetUserH(str);
+    if (str) {
+        memoserv = memoserv_conf.bot;
+        const char *modes = conf_get_data("modules/memoserv/modes", RECDB_QSTRING);
+        memoserv = AddLocalUser(str, str, NULL, "User-User Memorandum Services", modes);
+        service_register(memoserv);
+    } else {
+        log_module(MS_LOG, LOG_ERROR, "database_get_data for memoserv_conf.bot failed!");
+        exit(1);
+    }
 
-    if (autojoin_channels && memoserv_conf.bot) {
+    if (autojoin_channels && memoserv) {
         for (i = 0; i < autojoin_channels->used; i++) {
             chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
-            AddChannelUser(memoserv_conf.bot, chan)->modes |= MODE_CHANOP;
+            AddChannelUser(memoserv, chan)->modes |= MODE_CHANOP;
         }
     }