]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/nickserv.c
This needs more testing *hint hint* rubin :P I diff'ed srvx 1.3.1 to 1.3 then picked...
[irc/evilnet/x3.git] / src / nickserv.c
index 630ef2e0ff4fa0684d168b7eab73f5e794916331..194b2beb0dd94a3b725e37e959b576af6ba20088 100644 (file)
@@ -1,7 +1,7 @@
 /* nickserv.c - Nick/authentication service
  * Copyright 2000-2004 srvx Development Team
  *
- * This file is part of srvx.
+ * This file is part of x3.
  *
  * srvx is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -50,6 +50,8 @@
 #define KEY_SET_TITLE_LEVEL "set_title_level"
 #define KEY_SET_FAKEHOST_LEVEL "set_fakehost_level"
 #define KEY_TITLEHOST_SUFFIX "titlehost_suffix"
+#define KEY_AUTO_OPER "auto_oper"
+#define KEY_AUTO_ADMIN "auto_admin"
 #define KEY_FLAG_LEVELS "flag_levels"
 #define KEY_HANDLE_EXPIRE_FREQ "handle_expire_freq"
 #define KEY_ACCOUNT_EXPIRE_FREQ "account_expire_freq"
 #define KEY_EMAIL_VISIBLE_LEVEL "email_visible_level"
 #define KEY_EMAIL_ENABLED "email_enabled"
 #define KEY_EMAIL_REQUIRED "email_required"
+#define KEY_SYNC_LOG "sync_log"
 #define KEY_COOKIE_TIMEOUT "cookie_timeout"
 #define KEY_ACCOUNTS_PER_EMAIL "accounts_per_email"
 #define KEY_EMAIL_SEARCH_LEVEL "email_search_level"
+#define KEY_DEFAULT_STYLE "default_style"
 
 #define KEY_ID "id"
 #define KEY_PASSWD "passwd"
 #define KEY_ANNOUNCEMENTS "announcements"
 #define KEY_MAXLOGINS "maxlogins"
 #define KEY_FAKEHOST "fakehost"
+#define KEY_NOTE_NOTE "note"
+#define KEY_NOTE_SETTER "setter"
+#define KEY_NOTE_DATE "date"
+
 
 #define NICKSERV_VALID_CHARS   "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
 
@@ -109,7 +117,7 @@ DEFINE_LIST(handle_info_list, struct handle_info*);
 #define NICKSERV_MIN_PARMS(N) do { \
   if (argc < N) { \
     reply("MSG_MISSING_PARAMS", argv[0]); \
-    svccmd_send_help(user, nickserv, cmd); \
+    svccmd_send_help_brief(user, nickserv, cmd); \
     return 0; \
   } } while (0)
 
@@ -133,7 +141,7 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_HANDLE_TOLONG", "The account name %s is too long. Account names must be %lu charactors or less."},
     { "NSMSG_PASSWORD_SHORT", "Your password must be at least %lu characters long." },
     { "NSMSG_PASSWORD_ACCOUNT", "Your password may not be the same as your account name." },
-    { "NSMSG_PASSWORD_DICTIONARY", "Your password should not be the word \"password\", or any other dictionary word." },
+    { "NSMSG_PASSWORD_DICTIONARY", "Your password is too simple. You must choose a password that is not just a word or name." },
     { "NSMSG_PASSWORD_READABLE", "Your password must have at least %lu digit(s), %lu capital letter(s), and %lu lower-case letter(s)." },
     { "NSMSG_PARTIAL_REGISTER", "Account has been registered to you; nick was already registered to someone else." },
     { "NSMSG_OREGISTER_VICTIM", "%s has registered a new account for you (named %s)." },
@@ -150,17 +158,19 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_COOKIE_LIVE", "Account $b%s$b already has a cookie active.  Please either finish using that cookie, wait for it to expire, or auth to the account and use the $bdelcookie$b command." },
     { "NSMSG_EMAIL_UNACTIVATED", "That email address already has an unused cookie outstanding.  Please use the cookie or wait for it to expire." },
     { "NSMSG_NO_COOKIE", "Your account does not have any cookie issued right now." },
+    { "NSMSG_NO_COOKIE_FOREIGN", "The account $b%s$b does not have any cookie issued right now." },
     { "NSMSG_CANNOT_COOKIE", "You cannot use that kind of cookie when you are logged in." },
     { "NSMSG_BAD_COOKIE", "That cookie is not the right one.  Please make sure you are copying it EXACTLY from the email; it is case-sensitive, so $bABC$b is different from $babc$b." },
     { "NSMSG_HANDLE_ACTIVATED", "Your account is now activated (with the password you entered when you registered).  You are now authenticated to your account." },
     { "NSMSG_PASSWORD_CHANGED", "You have successfully changed your password to what you requested with the $bresetpass$b command." },
     { "NSMSG_EMAIL_PROHIBITED", "%s may not be used as an email address: %s" },
-    { "NSMSG_EMAIL_OVERUSED", "There are already the maximum number of accounts associated with that email address." },
+    { "NSMSG_EMAIL_OVERUSED", "That email address already has an account. Use RESETPASS if you forgot your password." },
     { "NSMSG_EMAIL_SAME", "That is the email address already there; no need to change it." },
     { "NSMSG_EMAIL_CHANGED", "You have successfully changed your email address." },
     { "NSMSG_BAD_COOKIE_TYPE", "Your account had bad cookie type %d; sorry.  I am confused.  Please report this bug." },
     { "NSMSG_MUST_TIME_OUT", "You must wait for cookies of that type to time out." },
     { "NSMSG_ATE_COOKIE", "I ate the cookie for your account.  You may now have another." },
+    { "NSMSG_ATE_FOREIGN_COOKIE", "I ate the cookie for account $b%s$b.  It may now have another." },
     { "NSMSG_USE_RENAME", "You are already authenticated to account $b%s$b -- contact the support staff to rename your account." },
     { "NSMSG_ALREADY_REGISTERING", "You have already used $bREGISTER$b once this session; you may not use it again." },
     { "NSMSG_REGISTER_BAD_NICKMASK", "Could not recognize $b%s$b as either a current nick or a hostmask." },
@@ -180,30 +190,33 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_STAMPED_RESETPASS", "You have already authenticated to an account once this session; you may not reset your password to authenticate again." },
     { "NSMSG_STAMPED_AUTHCOOKIE",  "You have already authenticated to an account once this session; you may not use a cookie to authenticate to another account." },
     { "NSMSG_TITLE_INVALID", "Titles cannot contain any dots; please choose another." },
+    { "NSMSG_TITLE_TRUNCATED", "That title combined with the user's account name would result in a truncated host; please choose a shorter title." },
     { "NSMSG_FAKEHOST_INVALID", "Fake hosts must be shorter than %d characters and cannot start with a dot." },
-    { "NSMSG_HANDLEINFO_ON", "Account information for $b%s$b:" },
-    { "NSMSG_HANDLEINFO_ID", "  Account ID: %lu" },
-    { "NSMSG_HANDLEINFO_REGGED", "  Registered on: %s" },
-    { "NSMSG_HANDLEINFO_LASTSEEN", "  Last seen: %s" },
-    { "NSMSG_HANDLEINFO_LASTSEEN_NOW", "  Last seen: Right now!" },
-    { "NSMSG_HANDLEINFO_VACATION", "  On vacation." },
-    { "NSMSG_HANDLEINFO_EMAIL_ADDR", "  Email address: %s" },
-    { "NSMSG_HANDLEINFO_COOKIE_ACTIVATION", "  Cookie: There is currently an activation cookie issued for this account" },
-    { "NSMSG_HANDLEINFO_COOKIE_PASSWORD", "  Cookie: There is currently a password change cookie issued for this account" },
-    { "NSMSG_HANDLEINFO_COOKIE_EMAIL", "  Cookie: There is currently an email change cookie issued for this account" },
-    { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "  Cookie: There is currently an allowauth cookie issued for this account" },
-    { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "  Cookie: There is currently an unknown cookie issued for this account" },
-    { "NSMSG_HANDLEINFO_INFOLINE", "  Infoline: %s" },
-    { "NSMSG_HANDLEINFO_FLAGS", "  Flags: %s" },
-    { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
-    { "NSMSG_HANDLEINFO_FAKEHOST", "  Fake host: %s" },
-    { "NSMSG_HANDLEINFO_LAST_HOST", "  Last quit hostmask: %s" },
-    { "NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN", "  Last quit hostmask: Unknown" },
-    { "NSMSG_HANDLEINFO_NICKS", "  Nickname(s): %s" },
-    { "NSMSG_HANDLEINFO_MASKS", "  Hostmask(s): %s" },
-    { "NSMSG_HANDLEINFO_CHANNELS", "  Channel(s): %s" },
-    { "NSMSG_HANDLEINFO_CURRENT", "  Current nickname(s): %s" },
-    { "NSMSG_HANDLEINFO_DNR", "  Do-not-register (by %s): %s" },
+    { "NSMSG_HANDLEINFO_ON", "$bAccount Information for %s$b" },
+    { "NSMSG_HANDLEINFO_END", "----------End of Account Info-----------" },
+    { "NSMSG_HANDLEINFO_ID", "Account ID: %lu" },
+    { "NSMSG_HANDLEINFO_REGGED", "Registered on: %s" },
+    { "NSMSG_HANDLEINFO_LASTSEEN", "Last seen: %s" },
+    { "NSMSG_HANDLEINFO_LASTSEEN_NOW", "Last seen: Right now!" },
+    { "NSMSG_HANDLEINFO_VACATION", "On vacation." },
+    { "NSMSG_HANDLEINFO_EMAIL_ADDR", "Email address: %s" },
+    { "NSMSG_HANDLEINFO_COOKIE_ACTIVATION", "Cookie: There is currently an activation cookie issued for this account" },
+    { "NSMSG_HANDLEINFO_COOKIE_PASSWORD", "Cookie: There is currently a password change cookie issued for this account" },
+    { "NSMSG_HANDLEINFO_COOKIE_EMAIL", "Cookie: There is currently an email change cookie issued for this account" },
+    { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "Cookie: There is currently an allowauth cookie issued for this account" },
+    { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "Cookie: There is currently an unknown cookie issued for this account" },
+    { "NSMSG_HANDLEINFO_INFOLINE", "Infoline: %s" },
+    { "NSMSG_HANDLEINFO_FLAGS", "Flags: %s" },
+    { "NSMSG_HANDLEINFO_EPITHET", "Epithet: %s" },
+    { "NSMSG_HANDLEINFO_NOTE", "Note (by %s on %s): %s " },
+    { "NSMSG_HANDLEINFO_FAKEHOST", "Fake host: %s" },
+    { "NSMSG_HANDLEINFO_LAST_HOST", "Last quit hostmask: %s" },
+    { "NSMSG_HANDLEINFO_LAST_HOST_UNKNOWN", "Last quit hostmask: Unknown" },
+    { "NSMSG_HANDLEINFO_NICKS", "Nickname(s): %s" },
+    { "NSMSG_HANDLEINFO_MASKS", "Hostmask(s): %s" },
+    { "NSMSG_HANDLEINFO_CHANNELS", "Channel(s): %s" },
+    { "NSMSG_HANDLEINFO_CURRENT", "Current nickname(s): %s" },
+    { "NSMSG_HANDLEINFO_DNR", "Do-not-register (by %s): %s" },
     { "NSMSG_USERINFO_AUTHED_AS", "$b%s$b is authenticated to account $b%s$b." },
     { "NSMSG_USERINFO_NOT_AUTHED", "$b%s$b is not authenticated to any account." },
     { "NSMSG_NICKINFO_OWNER", "Nick $b%s$b is owned by account $b%s$b." },
@@ -269,7 +282,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_RECLAIMED_SVSNICK", "Forcibly changed %s's nick." },
     { "NSMSG_RECLAIMED_KILL",  "Disconnected %s from the network." },
     { "NSMSG_CLONE_AUTH", "Warning: %s (%s@%s) authed to your account." },
-    { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
+    { "NSMSG_SETTING_LIST", "$b$N account settings$b" },
+    { "NSMSG_SETTING_LIST_HEADER", "----------------------------------------" },
+    { "NSMSG_SETTING_LIST_END",    "-------------End Of Settings------------" },
     { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
     { "NSMSG_INVALID_ANNOUNCE", "$b%s$b is an invalid announcements value." },
     { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
@@ -279,6 +294,7 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SET_PRIVMSG", "$bPRIVMSG:      $b%s" },
     { "NSMSG_SET_STYLE", "$bSTYLE:        $b%s" },
     { "NSMSG_SET_ANNOUNCEMENTS", "$bANNOUNCEMENTS: $b%s" },
+    { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE: $b%s" },
     { "NSMSG_SET_PASSWORD", "$bPASSWORD:     $b%s" },
     { "NSMSG_SET_FLAGS", "$bFLAGS:        $b%s" },
     { "NSMSG_SET_EMAIL", "$bEMAIL:        $b%s" },
@@ -286,15 +302,52 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SET_LANGUAGE", "$bLANGUAGE:     $b%s" },
     { "NSMSG_SET_LEVEL", "$bLEVEL:        $b%d" },
     { "NSMSG_SET_EPITHET", "$bEPITHET:      $b%s" },
+    { "NSMSG_SET_NOTE", "$bNOTE:         $b%s"},
     { "NSMSG_SET_TITLE", "$bTITLE:        $b%s" },
     { "NSMSG_SET_FAKEHOST", "$bFAKEHOST:    $b%s" },
+
+    { "NSMSG_AUTO_OPER", "You have been auto-opered" },
+    { "NSMSG_AUTO_OPER_ADMIN", "You have been auto-admined" },
+
     { "NSEMAIL_ACTIVATION_SUBJECT", "Account verification for %s" },
-    { "NSEMAIL_ACTIVATION_BODY", "This email has been sent to verify that this email address belongs to the person who tried to register an account on %1$s.  Your cookie is:\n    %2$s\nTo verify your email address and complete the account registration, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nThis command is only used once to complete your account registration, and never again. Once you have run this command, you will need to authenticate everytime you reconnect to the network. To do this, you will have to type this command every time you reconnect:\n    /msg %3$s@%4$s AUTH %5$s your-password\n Please remember to fill in 'your-password' with the actual password you gave to us when you registered.\n\nIf you did NOT request this account, you do not need to do anything.  Please contact the %1$s staff if you have questions, and be sure to check our website." },
+    { "NSEMAIL_ACTIVATION_BODY", 
+        "This email has been sent to verify that this email address belongs to the person who tried to register an account on %1$s.  Your cookie is:\n"
+        "%2$s\n"
+        "To verify your email address and complete the account registration, log on to %1$s and type the following command:\n"
+        "/msg %3$s@%4$s COOKIE %5$s %2$s\n"
+        "This command is only used once to complete your account registration, and never again. Once you have run this command, you will need to authenticate everytime you reconnect to the network. To do this, you will have to type this command every time you reconnect:\n"
+        "/msg %3$s@%4$s AUTH %5$s your-password\n"
+        "(Please remember to fill in 'your-password' with the actual password you gave to us when you registered.)\n"
+        "OR configure Login-On-Connect (see http://www.afternet.org/login-on-connect for instructions) to connect pre-logged in every time.\n"
+        "\n"
+        "If you did NOT request this account, you do not need to do anything.\n"
+        "Please contact the %1$s staff if you have questions, and be sure to check our website." },
+    { "NSEMAIL_ACTIVATION_BODY_WEB", 
+        "This email has been sent to verify that this email address belongs to the person who tried to register an account on %1$s.  Your cookie is:\n"
+        "%2$s\n"
+        "To verify your email address and complete the account registration, visit the following URL:\n"
+        "http://www.afternet.org/index.php?option=com_registration&task=activate&username=%5$s&cookie=%2$s\n"
+        "\n"
+        "If you did NOT request this account, you do not need to do anything.\n"
+        "Please contact the %1$s staff if you have questions, and be sure to check our website." },
     { "NSEMAIL_PASSWORD_CHANGE_SUBJECT", "Password change verification on %s" },
-    { "NSEMAIL_PASSWORD_CHANGE_BODY", "This email has been sent to verify that you wish to change the password on your account %5$s.  Your cookie is %2$s.\nTo complete the password change, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nIf you did NOT request your password to be changed, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
+    { "NSEMAIL_PASSWORD_CHANGE_BODY", 
+        "This email has been sent to verify that you wish to change the password on your account %5$s.  Your cookie is %2$s.\n"
+        "To complete the password change, log on to %1$s and type the following command:\n"
+        "/msg %3$s@%4$s COOKIE %5$s %2$s\n"
+        "If you did NOT request your password to be changed, you do not need to do anything.\n"
+        "Please contact the %1$s staff if you have questions." },
+    { "NSEMAIL_PASSWORD_CHANGE_BODY_WEB", 
+        "This email has been sent to verify that you wish to change the password on your account %5$s.  Your cookie is %2$s.\n"
+        "To complete the password change, click the following URL:\n"
+        "http://www.afternet.org/index.php?option=com_registration&task=passcookie&username=%5$s&cookie=%2$s\n"
+        "If you did NOT request your password to be changed, you do not need to do anything.\n"
+        "Please contact the %1$s staff if you have questions." },
     { "NSEMAIL_EMAIL_CHANGE_SUBJECT", "Email address change verification for %s" },
+#ifdef stupid_verify_old_email        
     { "NSEMAIL_EMAIL_CHANGE_BODY_NEW", "This email has been sent to verify that your email address belongs to the same person as account %5$s on %1$s.  The SECOND HALF of your cookie is %2$.*6$s.\nTo verify your address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s ?????%2$.*6$s\n(Replace the ????? with the FIRST HALF of the cookie, as sent to your OLD email address.)\nIf you did NOT request this email address to be associated with this account, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
     { "NSEMAIL_EMAIL_CHANGE_BODY_OLD", "This email has been sent to verify that you want to change your email for account %5$s on %1$s from this address to %7$s.  The FIRST HALF of your cookie is %2$.*6$s\nTo verify your new address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$.*6$s?????\n(Replace the ????? with the SECOND HALF of the cookie, as sent to your NEW email address.)\nIf you did NOT request this change of email address, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
+#endif
     { "NSEMAIL_EMAIL_VERIFY_SUBJECT", "Email address verification for %s" },
     { "NSEMAIL_EMAIL_VERIFY_BODY", "This email has been sent to verify that this address belongs to the same person as %5$s on %1$s.  Your cookie is %2$s.\nTo verify your address as associated with this account, log on to %1$s and type the following command:\n    /msg %3$s@%4$s COOKIE %5$s %2$s\nIf you did NOT request this email address to be associated with this account, you do not need to do anything.  Please contact the %1$s staff if you have questions." },
     { "NSEMAIL_ALLOWAUTH_SUBJECT", "Authentication allowed for %s" },
@@ -323,6 +376,7 @@ static struct {
     unsigned int default_hostmask : 1;
     unsigned int warn_nick_owned : 1;
     unsigned int warn_clone_auth : 1;
+    unsigned int sync_log : 1;
     unsigned long nicks_per_handle;
     unsigned long password_min_length;
     unsigned long password_min_digits;
@@ -352,6 +406,9 @@ static struct {
     unsigned long auto_reclaim_delay;
     unsigned char default_maxlogins;
     unsigned char hard_maxlogins;
+    const char *auto_oper;
+    const char *auto_admin;
+    char default_style;
 } nickserv_conf;
 
 /* We have 2^32 unique account IDs to use. */
@@ -369,6 +426,17 @@ canonicalize_hostmask(char *mask)
     return mask;
 }
 
+static struct handle_note *
+nickserv_add_note(const char *setter, time_t date, const char *text)
+{
+    struct handle_note *note = calloc(1, sizeof(*note) + strlen(text));
+
+    strncpy(note->setter, setter, sizeof(note->setter)-1);
+    note->date = date;
+    memcpy(note->note, text, strlen(text));
+    return note;
+}
+
 static struct handle_info *
 register_handle(const char *handle, const char *passwd, UNUSED_ARG(unsigned long id))
 {
@@ -428,13 +496,6 @@ register_nick(const char *nick, struct handle_info *owner)
     dict_insert(nickserv_nick_dict, ni->nick, ni);
 }
 
-static void
-free_nick_info(void *vni)
-{
-    struct nick_info *ni = vni;
-    free(ni);
-}
-
 static void
 delete_nick(struct nick_info *ni)
 {
@@ -506,6 +567,7 @@ free_handle_info(void *vhi)
         delete_nick(hi->nicks);
     free(hi->infoline);
     free(hi->epithet);
+    free(hi->note);
     free(hi->fakehost);
     if (hi->cookie) {
         timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
@@ -523,20 +585,31 @@ free_handle_info(void *vhi)
 static void set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp);
 
 static void
-nickserv_unregister_handle(struct handle_info *hi, struct userNode *notify)
+nickserv_unregister_handle(struct handle_info *hi, struct userNode *notify, struct userNode *bot)
 {
     unsigned int n;
+    struct userNode *uNode;
 
     for (n=0; n<unreg_func_used; n++)
         unreg_func_list[n](notify, hi);
-    while (hi->users)
+    while (hi->users) {
+        if (nickserv_conf.sync_log) {
+            uNode = GetUserH(hi->users->nick);
+            if (uNode)
+                irc_delete(uNode);
+        }
         set_user_handle_info(hi->users, NULL, 0);
+    }
     if (notify) {
         if (nickserv_conf.disable_nicks)
-            send_message(notify, nickserv, "NSMSG_UNREGISTER_SUCCESS", hi->handle);
+            send_message(notify, bot, "NSMSG_UNREGISTER_SUCCESS", hi->handle);
         else
-            send_message(notify, nickserv, "NSMSG_UNREGISTER_NICKS_SUCCESS", hi->handle);
+            send_message(notify, bot, "NSMSG_UNREGISTER_NICKS_SUCCESS", hi->handle);
     }
+
+    if (nickserv_conf.sync_log)
+        SyncLog("UNREGISTER %s", hi->handle);
+
     dict_remove(nickserv_handle_dict, hi->handle);
 }
 
@@ -647,6 +720,7 @@ is_registerable_nick(const char *nick)
     }
     return 1;
 }
+/*  this has been replaced with one in tools.c
 
 static int
 is_valid_email_addr(const char *email)
@@ -654,6 +728,8 @@ is_valid_email_addr(const char *email)
     return strchr(email, '@') != NULL;
 }
 
+*/ 
+
 static const char *
 visible_email_addr(struct userNode *user, struct handle_info *hi)
 {
@@ -757,6 +833,8 @@ is_secure_password(const char *handle, const char *pass, struct userNode *user)
 {
     unsigned int i, len;
     unsigned int cnt_digits = 0, cnt_upper = 0, cnt_lower = 0;
+    int p;
+
     len = strlen(pass);
     if (len < nickserv_conf.password_min_length) {
         if (user)
@@ -768,8 +846,8 @@ is_secure_password(const char *handle, const char *pass, struct userNode *user)
             send_message(user, nickserv, "NSMSG_PASSWORD_ACCOUNT");
         return 0;
     }
-    dict_find(nickserv_conf.weak_password_dict, pass, &i);
-    if (i) {
+    dict_find(nickserv_conf.weak_password_dict, pass, &p);
+    if (p) {
         if (user)
             send_message(user, nickserv, "NSMSG_PASSWORD_DICTIONARY");
         return 0;
@@ -858,6 +936,17 @@ apply_fakehost(struct handle_info *handle)
         assign_fakehost(target, fake, 1);
 }
 
+void send_func_list(struct userNode *user)
+{
+    unsigned int n;
+    struct handle_info *old_info;
+
+    old_info = user->handle_info;
+
+    for (n=0; n<auth_func_used; n++)
+        auth_func_list[n](user, old_info);
+}
+
 static void
 set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
 {
@@ -897,8 +986,13 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
     user->handle_info = hi;
     if (hi && !hi->users && !hi->opserv_level)
         HANDLE_CLEAR_FLAG(hi, HELPING);
-    for (n=0; n<auth_func_used; n++)
-        auth_func_list[n](user, old_info);
+
+    if (GetUserH(user->nick)) {
+        for (n=0; n<auth_func_used; n++)
+            auth_func_list[n](user, old_info);
+    } else
+      user->loc = 1;
+
     if (hi) {
         struct nick_info *ni;
 
@@ -908,6 +1002,7 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
             for (other = hi->users; other; other = other->next_authed)
                 send_message(other, nickserv, "NSMSG_CLONE_AUTH", user->nick, user->ident, user->hostname);
         }
+
        user->next_authed = hi->users;
        hi->users = user;
        hi->lastseen = now;
@@ -937,7 +1032,7 @@ set_user_handle_info(struct userNode *user, struct handle_info *hi, int stamp)
                     }
                 }
             }
-            StampUser(user, id);
+            StampUser(user, id, hi->registered);
         }
 
         if ((ni = get_nick_info(user->nick)) && (ni->owner == hi))
@@ -1002,8 +1097,29 @@ nickserv_bake_cookie(struct handle_cookie *cookie)
     timeq_add(cookie->expires, nickserv_free_cookie, cookie);
 }
 
+/* Contributed by the great sneep of afternet ;) */
+/* Since this gets used in a URL, we want to avoid stuff that confuses
+ * email clients such as ] and ?. a-z, 0-9 only.
+ */
+void genpass(char *str, int len)
+{
+        int i = 0;
+        char c = 0;
+
+        for(i = 0; i < len; i++)
+        {
+                do
+                {
+                        c = (char)((float)rand() / (float)RAND_MAX * (float)256);
+                } while(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')));
+                str[i] = c;
+        }
+        str[i] = '\0';
+        return;
+}
+
 static void
-nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_type type, const char *cookie_data)
+nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_type type, const char *cookie_data, int weblink)
 {
     struct handle_cookie *cookie;
     char subject[128], body[4096], *misc;
@@ -1019,9 +1135,14 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
     cookie->hi = hi;
     cookie->type = type;
     cookie->data = cookie_data ? strdup(cookie_data) : NULL;
+
     cookie->expires = now + nickserv_conf.cookie_timeout;
-    inttobase64(cookie->cookie, rand(), 5);
-    inttobase64(cookie->cookie+5, rand(), 5);
+    /* Adding dedicated password gen function for more control -Rubin */
+    genpass(cookie->cookie, 10);
+    /*
+     *inttobase64(cookie->cookie, rand(), 5);
+     *inttobase64(cookie->cookie+5, rand(), 5);
+     */
 
     netname = nickserv_conf.network_name;
     subject[0] = 0;
@@ -1032,7 +1153,12 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
         send_message(user, nickserv, "NSMSG_USE_COOKIE_REGISTER");
         fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_SUBJECT");
         snprintf(subject, sizeof(subject), fmt, netname);
-        fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_BODY");
+
+        if(weblink)
+            fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_BODY_WEB");
+        else
+            fmt = handle_find_message(hi, "NSEMAIL_ACTIVATION_BODY");
+
         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
         first_time = 1;
         break;
@@ -1040,12 +1166,16 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
         send_message(user, nickserv, "NSMSG_USE_COOKIE_RESETPASS");
         fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_SUBJECT");
         snprintf(subject, sizeof(subject), fmt, netname);
-        fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_BODY");
+        if(weblink)
+            fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_BODY_WEB");
+        else
+            fmt = handle_find_message(hi, "NSEMAIL_PASSWORD_CHANGE_BODY");
         snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
         break;
     case EMAIL_CHANGE:
         misc = hi->email_addr;
         hi->email_addr = cookie->data;
+#ifdef stupid_verify_old_email        
         if (misc) {
             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_2");
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_SUBJECT");
@@ -1056,6 +1186,7 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_CHANGE_BODY_OLD");
             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle, COOKIELEN/2, hi->email_addr);
         } else {
+#endif
             send_message(user, nickserv, "NSMSG_USE_COOKIE_EMAIL_1");
             fmt = handle_find_message(hi, "NSEMAIL_EMAIL_VERIFY_SUBJECT");
             snprintf(subject, sizeof(subject), fmt, netname);
@@ -1063,7 +1194,9 @@ nickserv_make_cookie(struct userNode *user, struct handle_info *hi, enum cookie_
             snprintf(body, sizeof(body), fmt, netname, cookie->cookie, nickserv->nick, self->name, hi->handle);
             sendmail(nickserv, hi, subject, body, 1);
             subject[0] = 0;
+#ifdef stupid_verify_old_email
         }
+#endif
         hi->email_addr = misc;
         break;
     case ALLOWAUTH:
@@ -1122,9 +1255,11 @@ nickserv_set_email_addr(struct handle_info *hi, const char *new_email_addr)
 
 static NICKSERV_FUNC(cmd_register)
 {
+    irc_in_addr_t ip;
     struct handle_info *hi;
     const char *email_addr, *password;
-    int no_auth;
+    char syncpass[MD5_CRYPT_LENGTH];
+    int no_auth, weblink;
 
     if (!IsOper(user) && !dict_size(nickserv_handle_dict)) {
        /* Require the first handle registered to belong to someone +o. */
@@ -1157,6 +1292,7 @@ static NICKSERV_FUNC(cmd_register)
         return 0;
     }
 
+
     if ((argc >= 4) && nickserv_conf.email_enabled) {
         struct handle_info_list *hil;
         const char *str;
@@ -1165,7 +1301,7 @@ static NICKSERV_FUNC(cmd_register)
         email_addr = argv[3];
 
         /* Check that the email address looks valid.. */
-        if (!is_valid_email_addr(email_addr)) {
+        if (!valid_email(email_addr)) {
             reply("NSMSG_BAD_EMAIL_ADDR");
             return 0;
         }
@@ -1199,6 +1335,13 @@ static NICKSERV_FUNC(cmd_register)
 
     password = argv[2];
     argv[2] = "****";
+    /* Webregister hack - send URL instead of IRC cookie 
+     * commands in email
+     */
+    if((argc >= 5) && !strcmp(argv[4],"WEBLINK"))
+        weblink = 1;
+    else
+        weblink = 0;
     if (!(hi = nickserv_register(user, user, argv[1], password, no_auth)))
         return 0;
     /* Add any masks they should get. */
@@ -1206,7 +1349,7 @@ static NICKSERV_FUNC(cmd_register)
         string_list_append(hi->masks, strdup("*@*"));
     } else {
         string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
-        if (user->ip.s_addr && user->hostname[strspn(user->hostname, "0123456789.")])
+        if (irc_in_addr_is_valid(user->ip) && !irc_pton(&ip, NULL, user->hostname))
             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
     }
 
@@ -1222,11 +1365,24 @@ static NICKSERV_FUNC(cmd_register)
 
     /* If they need to do email verification, tell them. */
     if (no_auth)
-        nickserv_make_cookie(user, hi, ACTIVATION, hi->passwd);
+        nickserv_make_cookie(user, hi, ACTIVATION, hi->passwd, weblink);
 
     /* Set registering flag.. */
     user->modes |= FLAGS_REGISTERING; 
 
+    if (nickserv_conf.sync_log) {
+      cryptpass(password, syncpass);
+      /*
+      * An 0 is only sent if theres no email address. Thios should only happen if email functions are
+       * disabled which they wont be for us. Email Required MUST be set on if you are using this.
+       * -SiRVulcaN
+       */
+      SyncLog("REGISTER %s %s %s %s", hi->handle, syncpass, email_addr ? email_addr : "0", user->info);
+    }
+
+    /* this wont work if email is required .. */
+    process_adduser_pending(user);
+
     return 1;
 }
 
@@ -1236,19 +1392,26 @@ static NICKSERV_FUNC(cmd_oregister)
     struct userNode *settee;
     struct handle_info *hi;
 
-    NICKSERV_MIN_PARMS(4);
+    NICKSERV_MIN_PARMS(nickserv_conf.email_required ? 4 : 3);
 
     if (!is_valid_handle(argv[1])) {
         reply("NSMSG_BAD_HANDLE", argv[1]);
         return 0;
     }
 
+    if (nickserv_conf.email_required) {
+        if (!valid_email(argv[4])) {
+            reply("NSMSG_BAD_EMAIL_ADDR");
+            return 0;
+        }
+    }
+
     if (strchr(argv[3], '@')) {
        mask = canonicalize_hostmask(strdup(argv[3]));
        if (argc > 4) {
-           settee = GetUserH(argv[4]);
+           settee = GetUserH(nickserv_conf.email_required ? argv[5] : argv[4]);
            if (!settee) {
-               reply("MSG_NICK_UNKNOWN", argv[4]);
+               reply("MSG_NICK_UNKNOWN", nickserv_conf.email_required ? argv[5] : argv[4]);
                 free(mask);
                return 0;
            }
@@ -1267,6 +1430,11 @@ static NICKSERV_FUNC(cmd_oregister)
         return 0;
     }
     if (!(hi = nickserv_register(user, settee, argv[1], argv[2], 0))) {
+        if (nickserv_conf.email_required) {
+            nickserv_set_email_addr(hi, argv[4]);
+            if (nickserv_conf.sync_log)
+                SyncLog("REGISTER %s %s %s %s", hi->handle, hi->passwd, argv[4], user->info);
+        }
         free(mask);
         return 0;
     }
@@ -1293,6 +1461,7 @@ static NICKSERV_FUNC(cmd_handleinfo)
 
     nsmsg_none = handle_find_message(hi, "MSG_NONE");
     reply("NSMSG_HANDLEINFO_ON", hi->handle);
+    reply("MSG_BAR");
 #ifdef WITH_PROTOCOL_BAHAMUT
     reply("NSMSG_HANDLEINFO_ID", hi->id);
 #endif
@@ -1315,8 +1484,10 @@ static NICKSERV_FUNC(cmd_handleinfo)
             reply("NSMSG_HANDLEINFO_DNR", dnr->setter, dnr->reason);
         if (!oper_outranks(user, hi))
             return 1;
-    } else if (hi != user->handle_info)
+    } else if (hi != user->handle_info) {
+        reply("NSMSG_HANDLEINFO_END");
         return 1;
+    }
 
     if (nickserv_conf.email_enabled)
         reply("NSMSG_HANDLEINFO_EMAIL_ADDR", visible_email_addr(user, hi));
@@ -1352,6 +1523,16 @@ static NICKSERV_FUNC(cmd_handleinfo)
         reply("NSMSG_HANDLEINFO_EPITHET", (hi->epithet ? hi->epithet : nsmsg_none));
     }
 
+    if (IsHelping(user) || IsOper(user))
+    {
+        if (hi->note)
+        {
+            char date[64];
+            strftime(date, 64, "%b %d %Y", localtime(&hi->note->date));
+            reply("NSMSG_HANDLEINFO_NOTE", hi->note->setter, date, hi->note->note);
+        }
+    }
+
     if (hi->fakehost)
         reply("NSMSG_HANDLEINFO_FAKEHOST", (hi->fakehost ? hi->fakehost : handle_find_message(hi, "MSG_NONE")));
 
@@ -1419,7 +1600,7 @@ static NICKSERV_FUNC(cmd_handleinfo)
            }
             if (IsUserSuspended(channel))
                 buff[pos++] = '-';
-            pos += sprintf(buff+pos, "%d:%s ", channel->access, name);
+            pos += sprintf(buff+pos, "%s:%s ", user_level_name_from_level(channel->access), name);
            if (next == NULL) {
              print_chans_buff:
                buff[pos-1] = 0;
@@ -1449,6 +1630,7 @@ static NICKSERV_FUNC(cmd_handleinfo)
        }
     }
 
+    reply("NSMSG_HANDLEINFO_END");
     return 1;
 }
 
@@ -1484,6 +1666,7 @@ static NICKSERV_FUNC(cmd_nickinfo)
 static NICKSERV_FUNC(cmd_rename_handle)
 {
     struct handle_info *hi;
+    struct userNode *uNode;
     char msgbuf[MAXLEN], *old_handle;
     unsigned int nn;
 
@@ -1510,6 +1693,15 @@ static NICKSERV_FUNC(cmd_rename_handle)
     for (nn=0; nn<rf_list_used; nn++)
         rf_list[nn](hi, old_handle);
     snprintf(msgbuf, sizeof(msgbuf), "%s renamed account %s to %s.", user->handle_info->handle, old_handle, hi->handle);
+
+
+    if (nickserv_conf.sync_log) {
+        for (uNode = hi->users; uNode; uNode = uNode->next_authed)
+            irc_rename(uNode, hi->handle);
+
+        SyncLog("RENAME %s %s", old_handle, hi->handle);
+    }
+
     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
     free(old_handle);
@@ -1534,6 +1726,57 @@ reg_failpw_func(failpw_func_t func)
     failpw_func_list[failpw_func_used++] = func;
 }
 
+/*
+ * Return hi if the handle/pass pair matches, NULL if it doesnt.
+ *
+ * called by nefariouses enhanced AC login-on-connect code
+ *
+ */
+struct handle_info *loc_auth(char *handle, char *password)
+{
+    int pw_arg, used, maxlogins;
+    unsigned int ii;
+    int wildmask = 0;
+    struct handle_info *hi;
+    struct userNode *other;
+
+    hi = dict_find(nickserv_handle_dict, handle, NULL);
+        pw_arg = 2;
+    if (!hi) {
+        return NULL;
+    }
+
+    /* We don't know the users hostname, or anything because they
+     * havn't registered yet. So we can only allow LOC if your
+     * account has *@* as a hostmask.
+     */
+    for (ii=0; ii<hi->masks->used; ii++)
+    {
+       if (!strcmp(hi->masks->list[ii], "*@*"))
+       {
+           wildmask++;
+           break;
+       }
+    }
+    if(wildmask < 1)
+        return NULL;
+
+    /* Responses from here on look up the language used by the handle they asked about. */
+    if (!checkpass(password, hi->passwd)) {
+        return NULL;
+    }
+    if (HANDLE_FLAGGED(hi, SUSPENDED)) {
+        return NULL;
+    }
+    maxlogins = hi->maxlogins ? hi->maxlogins : nickserv_conf.default_maxlogins;
+    for (used = 0, other = hi->users; other; other = other->next_authed) {
+        if (++used >= maxlogins) {
+            return NULL;
+        }
+    }
+    return hi;
+}
+
 static NICKSERV_FUNC(cmd_auth)
 {
     int pw_arg, used, maxlogins;
@@ -1574,7 +1817,7 @@ static NICKSERV_FUNC(cmd_auth)
         pw_arg = 1;
     } else {
         reply("MSG_MISSING_PARAMS", argv[0]);
-        svccmd_send_help(user, nickserv, cmd);
+        svccmd_send_help_brief(user, nickserv, cmd);
         return 0;
     }
     if (!hi) {
@@ -1641,7 +1884,34 @@ static NICKSERV_FUNC(cmd_auth)
         reply("NSMSG_WEAK_PASSWORD");
     if (hi->passwd[0] != '$')
         cryptpass(passwd, hi->passwd);
+
+   /* If a channel was waiting for this user to auth, 
+    * finish adding them */
+    process_adduser_pending(user);
+
     reply("NSMSG_AUTH_SUCCESS");
+
+    
+    /* Set +x if autohide is on */
+    if(HANDLE_FLAGGED(hi, AUTOHIDE))
+        irc_umode(user, "+x");
+
+    if(!IsOper(user)) /* If they arnt already opered.. */
+    {
+        /* Auto Oper users with Opserv access -Life4Christ 8-10-2005  */
+        if( nickserv_conf.auto_admin[0] && hi->opserv_level >= opserv_conf_admin_level())
+        {
+            irc_umode(user,nickserv_conf.auto_admin);
+            reply("NSMSG_AUTO_OPER_ADMIN");
+        }
+        else if (nickserv_conf.auto_oper[0] && hi->opserv_level > 0)
+        {
+            irc_umode(user,nickserv_conf.auto_oper);
+            reply("NSMSG_AUTO_OPER");
+        }
+    }
+
+   /* Wipe out the pass for the logs */
     argv[pw_arg] = "****";
     return 1;
 }
@@ -1743,7 +2013,7 @@ static NICKSERV_FUNC(cmd_authcookie)
         reply("MSG_SET_EMAIL_ADDR");
         return 0;
     }
-    nickserv_make_cookie(user, hi, ALLOWAUTH, NULL);
+    nickserv_make_cookie(user, hi, ALLOWAUTH, NULL, 0);
     return 1;
 }
 
@@ -1769,12 +2039,37 @@ static NICKSERV_FUNC(cmd_delcookie)
     return 1;
 }
 
+static NICKSERV_FUNC(cmd_odelcookie)
+{
+    struct handle_info *hi;
+
+    NICKSERV_MIN_PARMS(2);
+
+    if (!(hi = get_victim_oper(user, argv[1])))
+        return 0;
+
+    if (!hi->cookie) {
+        reply("NSMSG_NO_COOKIE_FOREIGN", hi->handle);
+        return 0;
+    }
+
+    nickserv_eat_cookie(hi->cookie);
+    reply("NSMSG_ATE_FOREIGN_COOKIE", hi->handle);
+
+    return 1;
+}
+
 static NICKSERV_FUNC(cmd_resetpass)
 {
     struct handle_info *hi;
     char crypted[MD5_CRYPT_LENGTH];
+    int weblink;
 
     NICKSERV_MIN_PARMS(3);
+    if(argc >= 4 && !strcmp(argv[3], "WEBLINK"))
+        weblink = 1;
+    else
+        weblink = 0;
     if (user->handle_info) {
         reply("NSMSG_ALREADY_AUTHED", user->handle_info->handle);
         return 0;
@@ -1796,7 +2091,7 @@ static NICKSERV_FUNC(cmd_resetpass)
     }
     cryptpass(argv[2], crypted);
     argv[2] = "****";
-    nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted);
+    nickserv_make_cookie(user, hi, PASSWORD_CHANGE, crypted, weblink);
     return 1;
 }
 
@@ -1845,15 +2140,29 @@ static NICKSERV_FUNC(cmd_cookie)
         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
         set_user_handle_info(user, hi, 1);
         reply("NSMSG_HANDLE_ACTIVATED");
+        if (nickserv_conf.sync_log)
+          SyncLog("ACCOUNTACC %s", hi->handle);
         break;
     case PASSWORD_CHANGE:
         set_user_handle_info(user, hi, 1);
         safestrncpy(hi->passwd, hi->cookie->data, sizeof(hi->passwd));
         reply("NSMSG_PASSWORD_CHANGED");
+        if (nickserv_conf.sync_log)
+          SyncLog("PASSCHANGE %s %s", hi->handle, hi->passwd);
         break;
     case EMAIL_CHANGE:
+        if (!hi->email_addr && nickserv_conf.sync_log) {
+          /*
+           * This should only happen if an OREGISTER was sent. Require
+           * email must be enabled! - SiRVulcaN
+           */
+          if (nickserv_conf.sync_log)
+            SyncLog("REGISTER %s %s %s %s", hi->handle, hi->passwd, hi->cookie->data, user->info);
+        }
         nickserv_set_email_addr(hi, hi->cookie->data);
         reply("NSMSG_EMAIL_CHANGED");
+        if (nickserv_conf.sync_log)
+          SyncLog("EMAILCHANGE %s %s", hi->handle, hi->cookie->data);
         break;
     case ALLOWAUTH:
         set_user_handle_info(user, hi, 1);
@@ -1867,6 +2176,8 @@ static NICKSERV_FUNC(cmd_cookie)
 
     nickserv_eat_cookie(hi->cookie);
 
+    process_adduser_pending(user);
+
     return 1;
 }
 
@@ -1934,6 +2245,8 @@ static NICKSERV_FUNC(cmd_pass)
        return 0;
     }
     cryptpass(new_pass, hi->passwd);
+    if (nickserv_conf.sync_log)
+      SyncLog("PASSCHANGE %s %s", hi->handle, hi->passwd);
     argv[1] = "****";
     reply("NSMSG_PASS_SUCCESS");
     return 1;
@@ -2099,15 +2412,18 @@ set_list(struct userNode *user, struct handle_info *hi, int override)
     unsigned int i;
     char *set_display[] = {
         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
-        "EMAIL", "ANNOUNCEMENTS", "MAXLOGINS", "LANGUAGE"
+        "EMAIL", "ANNOUNCEMENTS", "AUTOHIDE", "MAXLOGINS", "LANGUAGE",
+        "FAKEHOST", "TITLE", "EPITHET"
     };
 
     send_message(user, nickserv, "NSMSG_SETTING_LIST");
+    send_message(user, nickserv, "NSMSG_SETTING_LIST_HEADER");
 
     /* Do this so options are presented in a consistent order. */
     for (i = 0; i < ArrayLength(set_display); ++i)
        if ((opt = dict_find(nickserv_opt_dict, set_display[i], NULL)))
            opt(user, hi, override, 0, NULL);
+    send_message(user, nickserv, "NSMSG_SETTING_LIST_END");
 }
 
 static NICKSERV_FUNC(cmd_set)
@@ -2229,24 +2545,46 @@ static OPTION_FUNC(opt_privmsg)
     return 1;
 }
 
+static OPTION_FUNC(opt_autohide)
+{
+    if (argc > 1) {
+       if (enabled_string(argv[1]))
+           HANDLE_SET_FLAG(hi, AUTOHIDE);
+        else if (disabled_string(argv[1]))
+           HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
+       else {
+           send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+           return 0;
+       }
+    }
+
+    send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
+    return 1;
+}
+
 static OPTION_FUNC(opt_style)
 {
     char *style;
 
     if (argc > 1) {
-       if (!irccasecmp(argv[1], "Zoot"))
-           hi->userlist_style = HI_STYLE_ZOOT;
-       else if (!irccasecmp(argv[1], "def"))
-           hi->userlist_style = HI_STYLE_DEF;
-    }
+        if (!irccasecmp(argv[1], "Clean"))
+            hi->userlist_style = HI_STYLE_CLEAN;
+        else if (!irccasecmp(argv[1], "Advanced"))
+            hi->userlist_style = HI_STYLE_ADVANCED;
+        else  /* Default to normal */
+            hi->userlist_style = HI_STYLE_NORMAL;
+    } /* TODO: give error if unknow style is chosen */
 
     switch (hi->userlist_style) {
-    case HI_STYLE_DEF:
-       style = "def";
-       break;
-    case HI_STYLE_ZOOT:
-    default:
-       style = "Zoot";
+        case HI_STYLE_ADVANCED:
+            style = "Advanced";
+            break;
+        case HI_STYLE_CLEAN:
+            style = "Clean";
+            break;
+        case HI_STYLE_NORMAL:
+        default:
+        style = "Normal";
     }
 
     send_message(user, nickserv, "NSMSG_SET_STYLE", style);
@@ -2290,6 +2628,9 @@ static OPTION_FUNC(opt_password)
     if (argc > 1)
        cryptpass(argv[1], hi->passwd);
 
+    if (nickserv_conf.sync_log)
+        SyncLog("PASSCHANGE %s %s", hi->handle, hi->passwd);
+
     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
     return 1;
 }
@@ -2322,7 +2663,7 @@ static OPTION_FUNC(opt_email)
 {
     if (argc > 1) {
         const char *str;
-        if (!is_valid_email_addr(argv[1])) {
+        if (!valid_email(argv[1])) {
             send_message(user, nickserv, "NSMSG_BAD_EMAIL_ADDR");
             return 0;
         }
@@ -2333,7 +2674,7 @@ static OPTION_FUNC(opt_email)
         if (hi->email_addr && !irccasecmp(hi->email_addr, argv[1]))
             send_message(user, nickserv, "NSMSG_EMAIL_SAME");
         else if (!override)
-                nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1]);
+                nickserv_make_cookie(user, hi, EMAIL_CHANGE, argv[1], 0);
         else {
             nickserv_set_email_addr(hi, argv[1]);
             if (hi->cookie)
@@ -2418,13 +2759,15 @@ static OPTION_FUNC(opt_level)
 
 static OPTION_FUNC(opt_epithet)
 {
-    if (!override) {
-        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
-        return 0;
-    }
-
     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_epithet_level, 0)) {
-        char *epithet = unsplit_string(argv+1, argc-1, NULL);
+        char *epithet;
+        if (!override) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+
+        epithet = unsplit_string(argv+1, argc-1, NULL);
+
         if (hi->epithet)
             free(hi->epithet);
         if ((epithet[0] == '*') && !epithet[1])
@@ -2444,17 +2787,23 @@ static OPTION_FUNC(opt_title)
 {
     const char *title;
 
-    if (!override) {
-        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
-        return 0;
-    }
-
     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_title_level, 0)) {
+        if (!override) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+
         title = argv[1];
         if (strchr(title, '.')) {
             send_message(user, nickserv, "NSMSG_TITLE_INVALID");
             return 0;
         }
+        if ((strlen(user->handle_info->handle) + strlen(title) +
+             strlen(nickserv_conf.titlehost_suffix) + 2) > HOSTLEN) {
+            send_message(user, nickserv, "NSMSG_TITLE_TRUNCATED");
+            return 0;
+        }
+
         free(hi->fakehost);
         if (!strcmp(title, "*")) {
             hi->fakehost = NULL;
@@ -2478,15 +2827,15 @@ static OPTION_FUNC(opt_fakehost)
 {
     const char *fake;
 
-    if (!override) {
-        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
-        return 0;
-    }
-
     if ((argc > 1) && oper_has_access(user, nickserv, nickserv_conf.set_fakehost_level, 0)) {
+        if (!override) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+
         fake = argv[1];
         if ((strlen(fake) > HOSTLEN) || (fake[0] == '.')) {
-            send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID");
+            send_message(user, nickserv, "NSMSG_FAKEHOST_INVALID", HOSTLEN);
             return 0;
         }
         free(hi->fakehost);
@@ -2504,6 +2853,31 @@ static OPTION_FUNC(opt_fakehost)
     return 1;
 }
 
+static OPTION_FUNC(opt_note)
+{
+    if (!override) {
+        send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
+    }
+
+    if (argc > 1) {
+        char *text = unsplit_string(argv + 1, argc - 1, NULL);
+
+        if (hi->note)
+            free(hi->note);
+
+        if ((text[0] == '*') && !text[1])
+            hi->note = NULL;
+        else {
+            if (!(hi->note = nickserv_add_note(user->handle_info->handle, now, text)))
+                hi->note = NULL;
+        }
+    }
+
+    send_message(user, nickserv, "NSMSG_SET_NOTE", hi->note->note);
+    return 1;
+}
+
 static NICKSERV_FUNC(cmd_reclaim)
 {
     struct handle_info *hi;
@@ -2590,7 +2964,7 @@ static NICKSERV_FUNC(cmd_unregister)
     passwd = argv[1];
     argv[1] = "****";
     if (checkpass(passwd, hi->passwd)) {
-        nickserv_unregister_handle(hi, user);
+        nickserv_unregister_handle(hi, user, cmd->parent->bot);
         return 1;
     } else {
        log_module(NS_LOG, LOG_INFO, "Account '%s' tried to unregister with the wrong password.", hi->handle);
@@ -2606,8 +2980,8 @@ static NICKSERV_FUNC(cmd_ounregister)
     NICKSERV_MIN_PARMS(2);
     if (!(hi = get_victim_oper(user, argv[1])))
         return 0;
-    nickserv_unregister_handle(hi, user);
-    return 0;
+    nickserv_unregister_handle(hi, user, cmd->parent->bot);
+    return 1;
 }
 
 static NICKSERV_FUNC(cmd_status)
@@ -2704,6 +3078,14 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
             saxdb_write_string(ctx, KEY_EMAIL_ADDR, hi->email_addr);
         if (hi->epithet)
             saxdb_write_string(ctx, KEY_EPITHET, hi->epithet);
+        if (hi->note) {
+            saxdb_start_record(ctx, KEY_NOTE_NOTE, 0);
+            saxdb_write_string(ctx, KEY_NOTE_SETTER, hi->note->setter);
+            saxdb_write_int(ctx, KEY_NOTE_DATE, hi->note->date);
+            saxdb_write_string(ctx, KEY_NOTE_NOTE, hi->note->note);
+            saxdb_end_record(ctx);
+        }
+
         if (hi->fakehost)
             saxdb_write_string(ctx, KEY_FAKEHOST, hi->fakehost);
         if (hi->flags) {
@@ -2872,13 +3254,20 @@ static NICKSERV_FUNC(cmd_merge)
     if (hi_from->lastseen > hi_to->lastseen)
         hi_to->lastseen = hi_from->lastseen;
 
+    /* Does a fakehost carry over?  (This intentionally doesn't set it
+     * for users previously attached to hi_to.  They'll just have to
+     * reconnect.)
+     */
+    if (hi_from->fakehost && !hi_to->fakehost)
+        hi_to->fakehost = strdup(hi_from->fakehost);
+
     /* Notify of success. */
     sprintf(buffer, "%s (%s) merged account %s into %s.", user->nick, user->handle_info->handle, hi_from->handle, hi_to->handle);
     reply("NSMSG_HANDLES_MERGED", hi_from->handle, hi_to->handle);
     global_message(MESSAGE_RECIPIENT_STAFF, buffer);
 
     /* Unregister the "from" handle. */
-    nickserv_unregister_handle(hi_from, NULL);
+    nickserv_unregister_handle(hi_from, NULL, cmd->parent->bot);
 
     return 1;
 }
@@ -3101,7 +3490,7 @@ static void
 search_unregister_func (struct userNode *source, struct handle_info *match)
 {
     if (oper_has_access(source, nickserv, match->opserv_level, 0))
-        nickserv_unregister_handle(match, source);
+        nickserv_unregister_handle(match, source, nickserv); // XXX nickserv hard coded
 }
 
 static int
@@ -3220,9 +3609,12 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     struct string_list *masks, *slist;
     struct handle_info *hi;
     struct userNode *authed_users;
+    struct userData *channels;
     unsigned long int id;
     unsigned int ii;
     dict_t subdb;
+    char *setter, *note;
+    time_t date;
 
     str = database_get_data(obj, KEY_ID, RECDB_QSTRING);
     id = str ? strtoul(str, NULL, 0) : 0;
@@ -3233,10 +3625,13 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     }
     if ((hi = get_handle_info(handle))) {
         authed_users = hi->users;
+        channels = hi->channels;
         hi->users = NULL;
+        hi->channels = NULL;
         dict_remove(nickserv_handle_dict, hi->handle);
     } else {
         authed_users = NULL;
+        channels = NULL;
     }
     hi = register_handle(handle, str, id);
     if (authed_users) {
@@ -3246,6 +3641,7 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             authed_users = authed_users->next_authed;
         }
     }
+    hi->channels = channels;
     masks = database_get_data(obj, KEY_MASKS, RECDB_STRING_LIST);
     hi->masks = masks ? string_list_copy(masks) : alloc_string_list(1);
     str = database_get_data(obj, KEY_MAXLOGINS, RECDB_QSTRING);
@@ -3274,7 +3670,7 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             hi->flags |= 1 << (handle_inverse_flags[(unsigned char)str[ii]] - 1);
     }
     str = database_get_data(obj, KEY_USERLIST_STYLE, RECDB_QSTRING);
-    hi->userlist_style = str ? str[0] : HI_STYLE_ZOOT;
+    hi->userlist_style = str ? str[0] : HI_DEFAULT_STYLE;
     str = database_get_data(obj, KEY_ANNOUNCEMENTS, RECDB_QSTRING);
     hi->announcements = str ? str[0] : '?';
     str = database_get_data(obj, KEY_SCREEN_WIDTH, RECDB_QSTRING);
@@ -3292,6 +3688,19 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     str = database_get_data(obj, KEY_EPITHET, RECDB_QSTRING);
     if (str)
         hi->epithet = strdup(str);
+    subdb = database_get_data(obj, KEY_NOTE_NOTE, RECDB_OBJECT);
+    if (subdb) {
+        setter = database_get_data(subdb, KEY_NOTE_SETTER, RECDB_QSTRING);
+        str = database_get_data(subdb, KEY_NOTE_DATE, RECDB_QSTRING);
+        date = str ? (time_t)strtoul(str, NULL, 0) : now;
+        note = database_get_data(subdb, KEY_NOTE_NOTE, RECDB_QSTRING);
+        if (setter && date && note)
+        {
+            if (!(hi->note = nickserv_add_note(setter, date, note)))
+                hi->note = NULL;
+        }
+    }
+
     str = database_get_data(obj, KEY_FAKEHOST, RECDB_QSTRING);
     if (str)
         hi->fakehost = strdup(str);
@@ -3391,7 +3800,7 @@ expire_handles(UNUSED_ARG(void *data))
         expiry = hi->channels ? nickserv_conf.handle_expire_delay : nickserv_conf.nochan_handle_expire_delay;
         if ((now - hi->lastseen) > expiry) {
             log_module(NS_LOG, LOG_INFO, "Expiring account %s for inactivity.", hi->handle);
-            nickserv_unregister_handle(hi, NULL);
+            nickserv_unregister_handle(hi, NULL, NULL);
         }
     }
 
@@ -3559,6 +3968,8 @@ nickserv_conf_read(void)
     nickserv_conf.email_visible_level = str ? strtoul(str, NULL, 0) : 800;
     str = database_get_data(conf_node, KEY_EMAIL_ENABLED, RECDB_QSTRING);
     nickserv_conf.email_enabled = str ? enabled_string(str) : 0;
+    str = database_get_data(conf_node, KEY_SYNC_LOG, RECDB_QSTRING);
+    nickserv_conf.sync_log = str ? enabled_string(str) : 0;
     str = database_get_data(conf_node, KEY_COOKIE_TIMEOUT, RECDB_QSTRING);
     nickserv_conf.cookie_timeout = str ? ParseInterval(str) : 24*3600;
     str = database_get_data(conf_node, KEY_EMAIL_REQUIRED, RECDB_QSTRING);
@@ -3569,6 +3980,16 @@ nickserv_conf_read(void)
     nickserv_conf.email_search_level = str ? strtoul(str, NULL, 0) : 600;
     str = database_get_data(conf_node, KEY_TITLEHOST_SUFFIX, RECDB_QSTRING);
     nickserv_conf.titlehost_suffix = str ? str : "example.net";
+
+    str = database_get_data(conf_node, KEY_DEFAULT_STYLE, RECDB_QSTRING);
+    nickserv_conf.default_style = str ? str[0] : HI_DEFAULT_STYLE;
+
+    str = database_get_data(conf_node, KEY_AUTO_OPER, RECDB_QSTRING);
+    nickserv_conf.auto_oper = str ? str : "";
+
+    str = database_get_data(conf_node, KEY_AUTO_ADMIN, RECDB_QSTRING);
+    nickserv_conf.auto_admin = str ? str : "";
+
     str = conf_get_data("server/network", RECDB_QSTRING);
     nickserv_conf.network_name = str ? str : "some IRC network";
     if (!nickserv_conf.auth_policer_params) {
@@ -3648,11 +4069,26 @@ void
 handle_account(struct userNode *user, const char *stamp)
 {
     struct handle_info *hi;
+    char *colon;
 
 #ifdef WITH_PROTOCOL_P10
+    time_t timestamp = 0;
+
+    colon = strchr(stamp, ':');
+    if(colon && colon[1])
+    {
+        *colon = 0;
+        timestamp = atoi(colon+1);
+    }
     hi = dict_find(nickserv_handle_dict, stamp, NULL);
+    if(hi && timestamp && hi->registered != timestamp)
+    {
+        log_module(MAIN_LOG, LOG_WARNING, "%s using account %s but timestamp does not match %lu is not %lu.", user->nick, stamp, timestamp, hi->registered);
+        return;
+    }
 #else
     hi = dict_find(nickserv_id_dict, stamp, NULL);
+    log_module(MAIN_LOG, LOG_WARNING, "Using non-P10 code in accounts, not tested at all!");
 #endif
 
     if (hi) {
@@ -3797,6 +4233,7 @@ init_nickserv(const char *nick)
         nickserv_define_func("RESETPASS", cmd_resetpass, -1, 0, 0);
         nickserv_define_func("COOKIE", cmd_cookie, -1, 0, 0);
         nickserv_define_func("DELCOOKIE", cmd_delcookie, -1, 1, 0);
+        nickserv_define_func("ODELCOOKIE", cmd_odelcookie, 0, 1, 0);
         dict_insert(nickserv_opt_dict, "EMAIL", opt_email);
     }
     nickserv_define_func("GHOST", cmd_ghost, -1, 1, 0);
@@ -3812,13 +4249,15 @@ init_nickserv(const char *nick)
     dict_insert(nickserv_opt_dict, "TABLEWIDTH", opt_tablewidth);
     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
-    dict_insert(nickserv_opt_dict, "STYLE", opt_style);
+    dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
+    dict_insert(nickserv_opt_dict, "STYLE", opt_style); 
     dict_insert(nickserv_opt_dict, "PASS", opt_password);
     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
+    dict_insert(nickserv_opt_dict, "NOTE", opt_note);
     if (nickserv_conf.titlehost_suffix) {
         dict_insert(nickserv_opt_dict, "TITLE", opt_title);
         dict_insert(nickserv_opt_dict, "FAKEHOST", opt_fakehost);
@@ -3835,14 +4274,15 @@ init_nickserv(const char *nick)
     dict_set_free_keys(nickserv_id_dict, free);
 
     nickserv_nick_dict = dict_new();
-    dict_set_free_data(nickserv_nick_dict, free_nick_info);
+    dict_set_free_data(nickserv_nick_dict, free);
 
     nickserv_allow_auth_dict = dict_new();
 
     userList_init(&curr_helpers);
 
     if (nick) {
-        nickserv = AddService(nick, "Nick Services", NULL);
+        const char *modes = conf_get_data("services/nickserv/modes", RECDB_QSTRING);
+        nickserv = AddService(nick, modes ? modes : NULL, "Nick Services", NULL);
         nickserv_service = service_register(nickserv);
     }
     saxdb_register("NickServ", nickserv_saxdb_read, nickserv_saxdb_write);