runtime helpfile splicing
authorEntrope <entrope@clan-dk.org>
Sat, 28 Feb 2004 00:29:08 +0000 (00:29 +0000)
committerEntrope <entrope@clan-dk.org>
Sat, 28 Feb 2004 00:29:08 +0000 (00:29 +0000)
* Add a miniature parser to the helpfile reader, allowing
nickserv.help's subsections to be selected at runtime.

* Move nickserv.help.m4 to nickserv.help and rewrite the
selection code to use the above.
git-archimport-id: srvx@srvx.net--2004-srvx/srvx--devo--1.3--patch-8

Makefile.am
TODO
src/Makefile.am
src/helpfile.c
src/nickserv.help [new file with mode: 0644]
src/nickserv.help.m4 [deleted file]

index 63f84d99a29f87aab052d1414b7ebd8b301ed5cd..dfad57139f6e3f5e4c250d63a07075d0168cbd1f 100644 (file)
@@ -5,7 +5,7 @@ DIST_SUBDIRS = src rx
 all: srvx
 
 srvx: src/srvx
-       cp ./src/srvx $(srcdir)/src/*.help ./src/nickserv.help .
+       cp ./src/srvx $(srcdir)/src/*.help .
 
 install-exec-local:
        $(INSTALL) -d -m 755 $(prefix)
diff --git a/TODO b/TODO
index 8c9bb50d809b0d7f95afb5457eaf10a8117c0859..cc40e98a5d30272872dea86d00f14508bb4b449b 100644 (file)
--- a/TODO
+++ b/TODO
 [FEATREQ] Suspension durations and/or comments when suspending user access to a channel
 
 [FEATREQ] support "unregistered": ?ctrace print mode +s unregistered
-
-[FEATREQ] runtime construction of nickserv.help, rather than compile time
-  - Can do something like this:
-    "<INDEX>" {
-        "/services/nickserv/disable_nicks:false and /services/nickserv/email_enabled:false" ( "Help content with nick ownership enabled and email disabled" );
-        // etc
-    };
-  - Need a mini-language interpreter for the logic blocks; should skip
-    leading "[0-9A-Za-z]+: " prefix to allow sorting the entries by
-    priority
index 205d4c69daa2ee086317af2d1a3205ff53c8af5a..15f02be8bd8b01098663b47d337fec31b7c93322 100644 (file)
@@ -2,22 +2,12 @@ AM_CPPFLAGS = @RX_INCLUDES@
 LIBS = @LIBS@ @RX_LIBS@
 
 ARCH_REVISION=$(shell tla logs -f | tail -n 1)
-noinst_PROGRAMS = srvx expnhelp
+noinst_PROGRAMS = srvx
 EXTRA_PROGRAMS = checkdb globtest
 noinst_DATA = chanserv.help global.help modcmd.help nickserv.help opserv.help saxdb.help sendmail.help mod-sockcheck.help mod-helpserv.help mod-memoserv.help
-EXTRA_DIST = nickserv.help.m4 $(noinst_DATA)
+EXTRA_DIST = $(noinst_DATA)
 BUILT_SOURCES = arch-version.h
 noinst_HEADERS = arch-version.h
-nickserv.help: nickserv.help.m4 expnhelp
-       ./expnhelp < $(srcdir)/nickserv.help.m4 > $@
-arch-version.h:
-       @if [ -e $@ ] ; then OLD_REVISION=`cat $@` ; else OLD_REVISION="" ; fi ; \
-       ARCH_REVISION=`tla logs -f | tail -n 1` ; \
-       VERSION_CONTENTS="#define ARCH_VERSION \"$$ARCH_REVISION\"" ; \
-       if [ "z" != "z$$ARCH_REVISION" -a "z$$OLD_REVISION" != "z$$VERSION_CONTENTS" ] ; then \
-           echo "Putting new arch version into $@" ; \
-           echo $$VERSION_CONTENTS > $@ ; \
-       fi
 
 EXTRA_srvx_SOURCES = proto-bahamut.c proto-common.c proto-p10.c mod-snoop.c mod-memoserv.c mod-helpserv.c mod-sockcheck.c
 srvx_LDADD = @MODULE_OBJS@
@@ -51,6 +41,5 @@ srvx_SOURCES = \
        timeq.c timeq.h \
        tools.c
 
-expnhelp_SOURCES = common.h compat.c compat.h dict-splay.c dict.h expnhelp.c log.h recdb.c recdb.h tools.c
 checkdb_SOURCES = checkdb.c common.h compat.c compat.h dict-splay.c dict.h recdb.c recdb.h saxdb.c saxdb.h tools.c conf.h log.h modcmd.h saxdb.h timeq.h
 globtest_SOURCES = common.h compat.c compat.h dict-splay.c dict.h globtest.c tools.c
index 71dcd91c8ef6710153241d6e973f3d6584eb71f2..9aa6b943d07cb2d11de5ca006182c2eb71683827 100644 (file)
  * along with this program; if not, email srvx-maintainers@srvx.net.
  */
 
+#include "conf.h"
 #include "helpfile.h"
 #include "log.h"
 #include "nickserv.h"
-#include "recdb.h"
 
 #include <dirent.h>
 
@@ -696,22 +696,215 @@ send_help(struct userNode *dest, struct userNode *src, struct helpfile *hf, cons
     return _send_help(dest, src, hf->expand, rec->d.qstring);
 }
 
-int
+/* Grammar supported by this parser:
+ * condition = expr | prefix expr
+ * expr = atomicexpr | atomicexpr op atomicexpr
+ * op = '&&' | '||' | 'and' | 'or'
+ * atomicexpr = '(' expr ')' | identifier
+ * identifier = ( '0'-'9' 'A'-'Z' 'a'-'z' '-' '_' '/' )+ | ! identifier
+ *
+ * Whitespace is ignored. The parser is implemented as a recursive
+ * descent parser by functions like:
+ *   static int helpfile_eval_<element>(const char *start, const char **end);
+ */
+
+enum helpfile_op {
+    OP_INVALID,
+    OP_BOOL_AND,
+    OP_BOOL_OR
+};
+
+static const struct {
+    const char *str;
+    enum helpfile_op op;
+} helpfile_operators[] = {
+    { "&&", OP_BOOL_AND },
+    { "and", OP_BOOL_AND },
+    { "||", OP_BOOL_OR },
+    { "or", OP_BOOL_OR },
+    { NULL, OP_INVALID }
+};
+
+static const char *identifier_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_/";
+
+static int helpfile_eval_expr(const char *start, const char **end);
+static int helpfile_eval_atomicexpr(const char *start, const char **end);
+static int helpfile_eval_identifier(const char *start, const char **end);
+
+static int
+helpfile_eval_identifier(const char *start, const char **end)
+{
+    /* Skip leading whitespace. */
+    while (isspace(*start) && (start < *end))
+        start++;
+    if (start == *end) {
+        log_module(MAIN_LOG, LOG_FATAL, "Expected identifier in helpfile condition.");
+        return -1;
+    }
+
+    if (start[0] == '!') {
+        int res = helpfile_eval_identifier(start+1, end);
+        if (res < 0)
+            return res;
+        return !res;
+    } else if (start[0] == '/') {
+        const char *sep;
+        char *id_str, *value;
+
+        for (sep = start;
+             strchr(identifier_chars, sep[0]) && (sep < *end);
+             ++sep) ;
+        memcpy(id_str = alloca(sep+1-start), start, sep-start);
+        id_str[sep-start] = '\0';
+        value = conf_get_data(id_str+1, RECDB_QSTRING);
+        *end = sep;
+        if (!value)
+            return 0;
+        return enabled_string(value) || true_string(value);
+    } else if ((*end - start >= 4) && !ircncasecmp(start, "true", 4)) {
+        *end = start + 4;
+        return 1;
+    } else if ((*end - start >= 5) && !ircncasecmp(start, "false", 5)) {
+        *end = start + 5;
+        return 0;
+    } else {
+        log_module(MAIN_LOG, LOG_FATAL, "Unexpected helpfile identifier '%.*s'.", *end-start, start);
+        return -1;
+    }
+}
+
+static int
+helpfile_eval_atomicexpr(const char *start, const char **end)
+{
+    const char *sep;
+    int res;
+
+    /* Skip leading whitespace. */
+    while (isspace(*start) && (start < *end))
+        start++;
+    if (start == *end) {
+        log_module(MAIN_LOG, LOG_FATAL, "Expected atomic expression in helpfile condition.");
+        return -1;
+    }
+
+    /* If it's not parenthesized, it better be a valid identifier. */
+    if (*start != '(')
+        return helpfile_eval_identifier(start, end);
+
+    /* Parse the internal expression. */
+    start++;
+    sep = *end;
+    res = helpfile_eval_expr(start, &sep);
+
+    /* Check for the closing parenthesis. */
+    while (isspace(*sep) && (sep < *end))
+        sep++;
+    if ((sep == *end) || (sep[0] != ')')) {
+        log_module(MAIN_LOG, LOG_FATAL, "Expected close parenthesis at '%.*s'.", *end-sep, sep);
+        return -1;
+    }
+
+    /* Report the end location and result. */
+    *end = sep + 1;
+    return res;
+}
+
+static int
+helpfile_eval_expr(const char *start, const char **end)
+{
+    const char *sep, *sep2;
+    unsigned int ii, len;
+    int res_a, res_b;
+    enum helpfile_op op;
+
+    /* Parse the first atomicexpr. */
+    sep = *end;
+    res_a = helpfile_eval_atomicexpr(start, &sep);
+    if (res_a < 0)
+        return res_a;
+
+    /* Figure out what follows that. */
+    while (isspace(*sep) && (sep < *end))
+        sep++;
+    if (sep == *end)
+        return res_a;
+    op = OP_INVALID;
+    for (ii = 0; helpfile_operators[ii].str; ++ii) {
+        len = strlen(helpfile_operators[ii].str);
+        if (ircncasecmp(sep, helpfile_operators[ii].str, len))
+            continue;
+        op = helpfile_operators[ii].op;
+        sep += len;
+    }
+    if (op == OP_INVALID) {
+        log_module(MAIN_LOG, LOG_FATAL, "Unrecognized helpfile operator at '%.*s'.", *end-sep, sep);
+        return -1;
+    }
+
+    /* Parse the next atomicexpr. */
+    sep2 = *end;
+    res_b = helpfile_eval_atomicexpr(sep, &sep2);
+    if (res_b < 0)
+        return res_b;
+
+    /* Make sure there's no trailing garbage */
+    while (isspace(*sep2) && (sep2 < *end))
+        sep2++;
+    if (sep2 != *end) {
+        log_module(MAIN_LOG, LOG_FATAL, "Trailing garbage in helpfile expression: '%.*s'.", *end-sep2, sep2);
+        return -1;
+    }
+
+    /* Record where we stopped parsing. */
+    *end = sep2;
+
+    /* Do the logic on the subexpressions. */
+    switch (op) {
+    case OP_BOOL_AND:
+        return res_a && res_b;
+    case OP_BOOL_OR:
+        return res_a || res_b;
+    default:
+        return -1;
+    }
+}
+
+static int
+helpfile_eval_condition(const char *start, const char **end)
+{
+    const char *term;
+
+    /* Skip the prefix if there is one. */
+    for (term = start; isalnum(*term) && (term < *end); ++term) ;
+    if (term != start) {
+        if ((term + 2 >= *end) || (term[0] != ':') || (term[1] != ' ')) {
+            log_module(MAIN_LOG, LOG_FATAL, "In helpfile condition '%.*s' expected prefix to end with ': '.", *end-start, start);
+            return -1;
+        }
+        start = term + 2;
+    }
+
+    /* Evaluate the remaining string as an expression. */
+    return helpfile_eval_expr(start, end);
+}
+
+static int
 unlistify_help(const char *key, void *data, void *extra)
 {
     struct record_data *rd = data;
     dict_t newdb = extra;
-    key = strdup(key);
-    if (rd->type == RECDB_QSTRING) {
-       dict_insert(newdb, key, alloc_record_data_qstring(GET_RECORD_QSTRING(rd)));
+
+    switch (rd->type) {
+    case RECDB_QSTRING:
+       dict_insert(newdb, strdup(key), alloc_record_data_qstring(GET_RECORD_QSTRING(rd)));
        return 0;
-    } else if (rd->type == RECDB_STRING_LIST) {
+    case RECDB_STRING_LIST: {
        struct string_list *slist = GET_RECORD_STRING_LIST(rd);
        char *dest;
        unsigned int totlen, len, i;
-       for (i=totlen=0; i<slist->used; i++) {
+
+       for (i=totlen=0; i<slist->used; i++)
            totlen = totlen + strlen(slist->list[i]) + 1;
-       }
        dest = alloca(totlen+1);
        for (i=totlen=0; i<slist->used; i++) {
            len = strlen(slist->list[i]);
@@ -720,9 +913,37 @@ unlistify_help(const char *key, void *data, void *extra)
            totlen = totlen + len + 1;
        }
        dest[totlen] = 0;
-       dict_insert(newdb, key, alloc_record_data_qstring(dest));
+       dict_insert(newdb, strdup(key), alloc_record_data_qstring(dest));
        return 0;
-    } else {
+    }
+    case RECDB_OBJECT: {
+        dict_iterator_t it;
+
+        for (it = dict_first(GET_RECORD_OBJECT(rd)); it; it = iter_next(it)) {
+            const char *k2, *end;
+            int res;
+
+            /* Evaluate the expression for this subentry. */
+            k2 = iter_key(it);
+            end = k2 + strlen(k2);
+            res = helpfile_eval_condition(k2, &end);
+            /* If the evaluation failed, bail. */
+            if (res < 0) {
+                log_module(MAIN_LOG, LOG_FATAL, " .. while processing entry '%s' condition '%s'.", key, k2);
+                return 1;
+            }
+            /* If the condition was false, try another. */
+            if (!res)
+                continue;
+            /* If we cannot unlistify the contents, bail. */
+            if (unlistify_help(key, iter_data(it), extra))
+                return 1;
+            return 0;
+        }
+        /* If none of the conditions apply, just omit the entry. */
+        return 0;
+    }
+    default:
        return 1;
     }
 }
@@ -752,7 +973,8 @@ open_helpfile(const char *fname, expand_func_t expand)
 
 void close_helpfile(struct helpfile *hf)
 {
-    if (!hf) return;
+    if (!hf)
+        return;
     free((char*)hf->name);
     free_database(hf->db);
     free(hf);
diff --git a/src/nickserv.help b/src/nickserv.help
new file mode 100644 (file)
index 0000000..039a8b7
--- /dev/null
@@ -0,0 +1,498 @@
+"<INDEX>" {
+        "/services/nickserv/disable_nicks" {
+        "/services/nickserv/email_enabled" (
+                "$b$N Help$b",
+                "$b$N$b is a nickname and authentication service, intended to serve as a central authentication point for all other network services. $b$C$b, $b$O$b, and $b$G$b all depend on $b$N$b to verify that users are valid. The other component allows for ownership of a nickname, but is not necessarily enabled.",
+               "$b$N$b command categories:",
+               "  ACCOUNT    Account management.",
+                "  NOT NICKSERV   A note on what this service does and does not do.",
+                "  EMAIL      Email maintenance commands",
+               "  OTHERS     Other functions.",
+                "  COMMANDS   A list of all available commands."
+        );
+        "!/services/nickserv/email_enabled" (
+                "$b$N Help$b",
+                "$b$N$b is a nickname and authentication service, intended to serve as a central authentication point for all other network services. $b$C$b, $b$O$b, and $b$G$b all depend on $b$N$b to verify that users are valid. The other component allows for ownership of a nickname, but is not necessarily enabled.",
+               "$b$N$b command categories:",
+               "  ACCOUNT    Account management.",
+                "  NOT NICKSERV   A note on what this service does and does not do.",
+               "  OTHERS     Other functions.",
+                "  COMMANDS   A list of all available commands."
+        );
+        };
+        "!/services/nickserv/disable_nicks" {
+        "/services/nickserv/email_enabled" (
+                "$b$N Help$b",
+                "$b$N$b is a nickname and authentication service, intended to serve as a central authentication point for all other network services. $b$C$b, $b$O$b, and $b$G$b all depend on $b$N$b to verify that users are valid. The other component allows for ownership of a nickname, but is not necessarily enabled.",
+               "$b$N$b command categories:",
+               "  ACCOUNT    Account management.",
+                "  NICK       Nick management.",
+                "  EMAIL      Email maintenance commands",
+               "  OTHERS     Other functions.",
+                "  COMMANDS   A list of all available commands."
+        );
+        "!/services/nickserv/email_enabled" (
+                "$b$N Help$b",
+                "$b$N$b is a nickname and authentication service, intended to serve as a central authentication point for all other network services. $b$C$b, $b$O$b, and $b$G$b all depend on $b$N$b to verify that users are valid. The other component allows for ownership of a nickname, but is not necessarily enabled.",
+               "$b$N$b command categories:",
+               "  ACCOUNT    Account management.",
+                "  NICK       Nick management.",
+               "  OTHERS     Other functions.",
+                "  COMMANDS   A list of all available commands."
+        );
+        };
+};
+
+"HANDLE" ("The term $uhandle$u from earlier versions was confusing to many new users.  Therefore, it has been changed to $uaccount$u.");
+
+"ACCOUNT" {
+        "/services/nickserv/enable_ghost" (
+                "Accounts are the way that $b$C$b identifies you for access to channels.  They are slightly similar to IRC nicks, but only have meaning to the services bots.  Until you authenticate to $b$N$b on an account, you can only use the $bREGISTER$b and $bAUTH$b commands.",
+                "Account management commands are:",
+                "  REGISTER   Register a new account.",
+                "  AUTH       Authenticate yourself to $b$N$b using an existing account.",
+                "  PASS       Change your account's password.",
+                "  ADDMASK    Add a hostmask to your account.",
+                "  DELMASK    Remove a hostmask from your account.",
+                "  SET        Set per-account options.",
+                "  UNREGISTER Unregister an account.",
+                "  RENAME     Renames an account",
+                "  GHOST      Disconnects your old clients",
+                "  ACCOUNT FLAGS Definition for each account flag"
+        );
+        "!/services/nickserv/enable_ghost" (
+                "Accounts are the way that $b$C$b identifies you for access to channels.  They are slightly similar to IRC nicks, but only have meaning to the services bots.  Until you authenticate to $b$N$b on an account, you can only use the $bREGISTER$b and $bAUTH$b commands.",
+                "Account management commands are:",
+                "  REGISTER   Register a new account.",
+                "  AUTH       Authenticate yourself to $b$N$b using an existing account.",
+                "  PASS       Change your account's password.",
+                "  ADDMASK    Add a hostmask to your account.",
+                "  DELMASK    Remove a hostmask from your account.",
+                "  SET        Set per-account options.",
+                "  UNREGISTER Unregister an account.",
+                "  RENAME     Renames an account",
+                "  GHOST      Disconnects your old clients",
+                "  ACCOUNT FLAGS Definition for each account flag"
+        );
+};
+
+"NOT NICKSERV" {
+        "/services/nickserv/disable_nicks" (
+        "$bNOT NICKSERV$b",
+        "This $b$N$b is not a standard NickServ.",
+        "Most NickServs provide \"nick ownership\", and will either issue a /KILL or a forced nick change if you try to use a registered nick without providing the password.",
+        "This $b$N$b will not do this.  It only allows you to register an $baccount$b, which identifies users to $b$C$b.  In a way, it is a virtual nick.  When you authenticate to $b$N$b, it does not care what your IRC nick is -- only account you are logged in as.",
+        "$b$N$b can tell you what account a user is authenticated to using the $bUSERINFO$b command.  Any problems with account registration or $b$N$b should be directed to the normal support channel."
+        );
+};
+
+"OUNREGISTER" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N OUNREGISTER <nick|*account>",
+        "Un-registers the specified account.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u oregister"
+        );
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N OUNREGISTER <nick|*account>",
+        "Un-registers the specified account, and any nicks that have been registered to that account.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u oregister, oregnick, ounregnick"
+        );
+};
+
+"UNREGISTER" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N@$s UNREGISTER <password>",
+        "Un-registers the account you are authenticated as.",
+        "$uSee Also:$u register"
+        );
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N@$s UNREGISTER <password>",
+        "Un-registers the account you are authenticated with, and any nicks that have been registered to that account.",
+        "$uSee Also:$u register, regnick, unregnick"
+        );
+};
+
+"NICK" {
+        "!/services/nickserv/disable_nicks" (
+        "You may register IRC nicknames to be associated with your accounts, and will be able to request a KILL for anyone using a nickname registered to you.",
+       "Nick management commands are:",
+        "  NICKINFO   Find out who has registered a nick.",
+        "  REGNICK    Register a nickname.",
+        "  UNREGNICK  Unregister a nickname.",
+        "  RECLAIM    Reclaim a nick registered to you."
+        );
+};
+
+"NICKINFO" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N NICKINFO <nick>",
+        "Displays information on the nick specified.",
+        "$uSee Also:$u accountinfo, userinfo"
+        );
+};
+
+"REGNICK" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N REGNICK ",
+        "Registers your current nick to the account you are authenticated to.",
+        "$uSee Also:$u register, unregister, unregnick"
+        );
+};
+
+"OREGNICK" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N OREGNICK [<nick|*account> <nick>]",
+        "Registers specified nick to the specified account. If nick and account are not specified, then $boregnick$b registers your current nick to the account you are authenticated to.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u oregister, ounregister, ounregnick"
+        );
+};
+
+"OUNREGNICK" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N OUNREGNICK <nick>",
+        "Un-registers a nick that was previously registered to an account.",
+        "$uSee Also:$u oregister, oregnick, ounregister"
+        );
+};
+
+"UNREGNICK" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N UNREGNICK [nick]",
+        "Un-registers a nick that was previously registered to your account.  If you do not specify a nick, your current nick will be un-registered.",
+        "$uSee Also:$u register, regnick, unregister"
+        );
+};
+
+"RECLAIM" {
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N RECLAIM <nick>",
+        "Reclaims the specified nick. You must be authenticated to the account that registered the nick.",
+        "Depending on configuration, this may do nothing, may ask the user nicely, may force a nick change on them, or may /KILL (disconnect) the target user."
+        );
+};
+
+"EMAIL" {
+        "/services/nickserv/email_enabled" (
+        "Email-based maintenance commands and topics are:",
+        "  AUTHCOOKIE Email a cookie to allow you to authenticate (auth) without a matching hostmask.",
+        "  RESETPASS  Request a password change if you forgot your old password.",
+        "  COOKIE     Complete an email-based maintenance action.",
+        "  DELCOOKIE  For AUTHCOOKIE or RESETPASS, cancel the requested cookie.",
+        "  EMAIL POLICY  This network's policy on account email addresses."
+        );
+};
+
+"AUTHCOOKIE" {
+        "/services/nickserv/email_enabled" (
+        "/msg $N AUTHCOOKIE <account>",
+        "Requests that $N send you email with a cookie that allows you to auth to your account if you do not have a matching hostmask.  (For example, if your ISP changed your IP or hostname.)",
+        "Once you receive the cookie in email, you can use the $bcookie$b command to log in.",
+        "$uSee Also:$u cookie, delcookie"
+        );
+};
+
+"RESETPASS" {
+        "/services/nickserv/email_enabled" (
+        "/msg $N@$s RESETPASS <account> <newpassword>",
+        "Requests that $N send you email with a cookie that will change your password (in case you have forgotten it).  Once you receive the cookie in email, use the $bcookie$b command to actually change your password.",
+        "$bYour password will not be changed, and you will not be able to use it to log in, until you confirm the change using the $ucookie$u command.$b",
+        "$uSee Also:$u cookie, delcookie"
+        );
+};
+
+"DELCOOKIE" {
+        "/services/nickserv/email_enabled" (
+        "/msg $N DELCOOKIE",
+        "Requests that $N cancel your authcookie or resetpass cookie.",
+        "(Since set-email cookies and registration cookies send email to unverified addresses, to prevent mail flooding, they cannot be cancelled.)",
+        "$uSee Also:$u authcookie, resetpass, cookie"
+        );
+};
+
+"COOKIE" {
+        "/services/nickserv/email_enabled" (
+        "/msg $N@$s COOKIE <account> <cookie>",
+        "Completes the maintenance action (for example, activating an account or changing your password) for which a cookie was issued.  The cookie will then be forgotten.",
+        "$uSee Also:$u authcookie, resetpass, set, delcookie"
+        );
+};
+
+"EMAIL POLICY" {
+        "/services/nickserv/email_enabled" (
+        "$bEMAIL POLICY",
+        "FooNET has utmost respect for the privacy of its users.  We will submit your email address to as many spam databases as we can find, and we will even post it on our web site.",
+        "(No, not really.  It looks like somebody forgot to edit nickserv.help or nickserv.help.m4 to remove this entry.  Make sure they edit the mail section of srvx.conf while they are at it.)"
+        );
+};
+
+"OTHERS" ("Other commands are:",
+        "  USERINFO    Displays the account a user is authenticated to.",
+        "  ACCOUNTINFO Displays information about an account.",
+        "  VERSION     $b$N$b version information.",
+        "  STATUS      $b$N$b status.",
+        "  SEARCH      Search for accounts by various criteria.",
+        "  MERGE       Merge one account into another.",
+        "  MERGEDB     Load a database into memory.",
+        "  HELP        Get help on $b$N$b.");
+
+"ADDMASK" ("/msg $N ADDMASK [user@host]",
+        "Adds the specified user@host to the account you are authenticated to with $b$N$b.  If no mask is given, it uses your current mask.",
+        "$uSee Also:$u auth, delmask");
+"ALLOWAUTH" ("/msg $N ALLOWAUTH <nick> [account] [STAFF]",
+        "Allows the specified nick to $bauth$b to the specified account. $bAllowauth$b does NOT add the hostmask of that nick to the specified account.",
+        "If no account is given, it will cancel the allowauth for the user (assuming the user has an allowauth).",
+        "If the account is marked as a helper or oper, the STAFF qualifier must be given afterwards.  This reduces social engineering attacks.",
+        "$uSee Also:$u addmask, auth");
+"AUTH" {
+        "/services/nickserv/email_enabled" (
+        "/msg $N@$s AUTH [account] <password>",
+        "Authenticates yourself with $b$N$b to the specified account. You must use $bauth$b before you have any access to network services, including channels that are registered with $b$C$b.",
+        "If you omit the account, it uses your current nick as your account name.",
+        "$uSee Also:$u pass, resetpass, authcookie",
+        );
+        "!/services/nickserv/email_enabled" (
+        "/msg $N@$s AUTH [account] <password>",
+        "Authenticates yourself with $b$N$b to the specified account. You must use $bauth$b before you have any access to network services, including channels that are registered with $b$C$b.",
+        "If you omit the account, it uses your current nick as your account name.",
+        "$uSee Also:$u pass",
+        );
+};
+"DELMASK" ("/msg $N DELMASK <user@host>",
+        "Removes a hostmask from the account you are authenticated on.",
+        "An account must have at least one hostmask; you cannot remove the last mask for an account.",
+        "$uSee Also:$u addmask");
+"ACCOUNTINFO" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N ACCOUNTINFO <nick|*account>",
+        "Displays infomation on the specified account, including the date the account was registered, the last time that person was seen, the account's $b$N$b info, its flags, its hostmask(s), its channels, and the account's current nickname.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u userinfo, account flags"
+        );
+        "! /services/nickserv/disable_nicks" (
+        "/msg $N ACCOUNTINFO <nick|*account>",
+        "Displays infomation on the specified account, including the date the account was registered, the last time that person was seen, the account's $b$N$b info, its flags, its hostmask(s), its channels, and the account's current nickname.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u nickinfo, userinfo, account flags"
+        );
+};
+"ACCOUNT FLAGS" ("$bACCOUNT FLAGS$b",
+        "The following flags on accounts are defined:",
+        "$bS$b  $O access suspended",
+        "$bp$b  Use PRIVMSG for messages rather than NOTICE",
+        "$bh$b  User is a support helper (must be in support channel to override security)",
+        "$bH$b  User is a network helper (can toggle security override)",
+        "$bg$b  God mode (security override for IRC staff)",
+        "$bs$b  Account suspended",
+        "$bc$b  Use mIRC color codes in responses",
+        "$bf$b  Account frozen/on vacation (will not be unregistered for inactivity; cleared when account is authenticated against)",
+        "$bn$b  No-delete (will never be unregistered for inactivity)",
+        "$uSee Also:$u accountinfo, set");
+"OADDMASK" ("/msg $N OADDMASK <nick|*account> <user@host>",
+        "Adds a hostmask to the specified account.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u odelmask");
+"ODELMASK" ("/msg $N ODELMASK <nick|*account> <user@host>",
+        "Removes a hostmask from the specified account.",
+        "An account must have at least one hostmask; you cannot remove the last mask for an account.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u oaddmask");
+"OREGISTER" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N@$s OREGISTER <account> <password> <user@host|nick>",
+        "Registers an account with $b$N$b using the specified account, password, and user@host. If then nick of an online user is specified, then that user's user@host is used.  If no nickname or hostmask is used, it uses a default hostmask the first time the user authenticates.",
+        "$uSee Also:$u ounregister"
+        );
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N@$s OREGISTER <account> <password> <user@host|nick>",
+        "Registers an account with $b$N$b using the specified account, password, and user@host. If then nick of an online user is specified, then that user's user@host is used.  If no nickname or hostmask is used, it uses a default hostmask the first time the user authenticates.",
+        "$uSee Also:$u oregnick, ounregister, ounregnick"
+        );
+};
+"OSET" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N OSET <nick|*account> [<setting> <value>]",
+        "Changes an account's settings for srvx. In addition to the normal $bset$b settings, you may set:",
+        "$bPASSWORD$b: Sets user's password.",
+        "$bFLAGS$b: Changes account flags for user.",
+        "$bLEVEL$b: Sets $O access level.",
+        "$bEPITHET$b: The description $C shows for the user's access.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u accountinfo, account flags, set, userinfo"
+        );
+        "!/services/nickserv/disable_nicks" (
+        "/msg $N OSET <nick|*account> [<setting> <value>]",
+        "Changes an account's settings for srvx. In addition to the normal $bset$b settings, you may set:",
+        "$bPASSWORD$b: Sets user's password.",
+        "$bFLAGS$b: Changes account flags for user.",
+        "$bLEVEL$b: Sets $O access level.",
+        "$bEPITHET$b: The description $C shows for the user's access.",
+        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
+        "$uSee Also:$u accountinfo, account flags, nickinfo, set, userinfo"
+        );
+};
+"PASS" ("/msg $N@$s PASS <oldpass> <newpass>",
+        "Changes your $b$N$b password.",
+        "$uSee Also:$u auth");
+
+"REGISTER" {
+        "0: /services/nickserv/email_required" (
+        "/msg $N@$s REGISTER <account> <password> <email>",
+        "Registers a specified account with $b$N$b, adding your current user@host to your new account. You must the password you specify with $bregister$b to use $bauth$b to authenticate to your account.",
+        "An email will be sent to the email address you give containing a cookie that will let you activate your account.  Once you have that cookie, you must use the $bcookie$b command to be able to use your account.",
+        "$uSee Also:$u auth, unregister"
+        );
+        "1: /services/nickserv/email_enabled" (
+        "/msg $N@$s REGISTER <account> <password> [email]",
+        "Registers a specified account with $b$N$b, adding your current user@host to your new account. You must the password you specify with $bregister$b to use $bauth$b to authenticate to your account.",
+        "If you specify an email address, an email will be sent to it containing a cookie that will let you activate your account.  Once you have that cookie, you must use the $bcookie$b command to be able to use your account.",
+        "$uSee Also:$u auth, unregister"
+        );
+        "2: !/services/nickserv/email_enabled" (
+        "/msg $N@$s REGISTER <account> <password>",
+        "Registers a specified account with $b$N$b, adding your current user@host to your new account. You must the password you specify with $bregister$b to use $bauth$b to authenticate to your account.",
+        "$uSee Also:$u auth, unregister"
+        );
+};
+"SET" {
+        "/services/nickserv/email_enabled && /services/nickserv/disable_nicks" (
+        "/msg $N SET [<setting> [value]]",
+        "Changes your account settings for srvx. Settings are:",
+        "$bANNOUNCEMENTS$b: Indicates whether you wish to receive community announcements via the $G service.",
+        "$bCOLOR$b: If set, $b$N$b and $b$C$b will use $bbold$b and $uunderlines$u in text they send you.",
+        "$bEMAIL$b: Sets (or changes) your email address.",
+        "$bINFO$b:  Your infoline for $b$N$b (which can be viewed with the $baccountinfo$b command).",
+        "$bLANGUAGE$b: Your preferred language for private messages from the services.",
+        "$bPRIVMSG$b: If set, $b$N$b and $b$C$b will send text to you using PRIVMSGs rather than NOTICEs.",
+        "$bSTYLE$b: The style you want srvx to use for channel userlists it sends you. $bSTYLE$b can be either $bDef$b (default) or $bZoot$b.",
+        "$bTABLEWIDTH$b: Sets the width for wrapping table-formatted text. (Use 0 for the default.)",
+        "$bWIDTH$b: The width you want srvx to wrap text it sends you.  (Use 0 for the default.)",
+        "$bMAXLOGINS$b: The number of users that can log into your account at once.  (Use 0 for the default.)",
+        "$bset$b with no arguments returns your current settings.",
+        "$uSee Also:$u accountinfo, userinfo"
+        );
+        "/services/nickserv/email_enabled && !/services/nickserv/disable_nicks" (
+        "/msg $N SET [<setting> [value]]",
+        "Changes your account settings for srvx. Settings are:",
+        "$bANNOUNCEMENTS$b: Indicates whether you wish to receive community announcements via the $G service.",
+        "$bCOLOR$b: If set, $b$N$b and $b$C$b will use $bbold$b and $uunderlines$u in text they send you.",
+        "$bEMAIL$b: Sets (or changes) your email address.",
+        "$bINFO$b:  Your infoline for $b$N$b (which can be viewed with the $baccountinfo$b command).",
+        "$bLANGUAGE$b: Your preferred language for private messages from the services.",
+        "$bPRIVMSG$b: If set, $b$N$b and $b$C$b will send text to you using PRIVMSGs rather than NOTICEs.",
+        "$bSTYLE$b: The style you want srvx to use for channel userlists it sends you. $bSTYLE$b can be either $bDef$b (default) or $bZoot$b.",
+        "$bTABLEWIDTH$b: Sets the width for wrapping table-formatted text. (Use 0 for the default.)",
+        "$bWIDTH$b: The width you want srvx to wrap text it sends you.  (Use 0 for the default.)",
+        "$bMAXLOGINS$b: The number of users that can log into your account at once.  (Use 0 for the default.)",
+        "$bset$b with no arguments returns your current settings.",
+        "$uSee Also:$u accountinfo, nickinfo, userinfo"
+        );
+        "!/services/nickserv/email_enabled && /services/nickserv/disable_nicks" (
+        "/msg $N SET [<setting> [value]]",
+        "Changes your account settings for srvx. Settings are:",
+        "$bANNOUNCEMENTS$b: Indicates whether you wish to receive community announcements via the $G service.",
+        "$bCOLOR$b: If set, $b$N$b and $b$C$b will use $bbold$b and $uunderlines$u in text they send you.",
+        "$bINFO$b:  Your infoline for $b$N$b (which can be viewed with the $baccountinfo$b command).",
+        "$bLANGUAGE$b: Your preferred language for private messages from the services.",
+        "$bPRIVMSG$b: If set, $b$N$b and $b$C$b will send text to you using PRIVMSGs rather than NOTICEs.",
+        "$bSTYLE$b: The style you want srvx to use for channel userlists it sends you. $bSTYLE$b can be either $bDef$b (default) or $bZoot$b.",
+        "$bTABLEWIDTH$b: Sets the width for wrapping table-formatted text. (Use 0 for the default.)",
+        "$bWIDTH$b: The width you want srvx to wrap text it sends you.  (Use 0 for the default.)",
+        "$bMAXLOGINS$b: The number of users that can log into your account at once.  (Use 0 for the default.)",
+        "$bset$b with no arguments returns your current settings.",
+        "$uSee Also:$u accountinfo, userinfo"
+        );
+        "!/services/nickserv/email_enabled && !/services/nickserv/disable_nicks" (
+        "/msg $N SET [<setting> [value]]",
+        "Changes your account settings for srvx. Settings are:",
+        "$bANNOUNCEMENTS$b: Indicates whether you wish to receive community announcements via the $G service.",
+        "$bCOLOR$b: If set, $b$N$b and $b$C$b will use $bbold$b and $uunderlines$u in text they send you.",
+        "$bINFO$b:  Your infoline for $b$N$b (which can be viewed with the $baccountinfo$b command).",
+        "$bLANGUAGE$b: Your preferred language for private messages from the services.",
+        "$bPRIVMSG$b: If set, $b$N$b and $b$C$b will send text to you using PRIVMSGs rather than NOTICEs.",
+        "$bSTYLE$b: The style you want srvx to use for channel userlists it sends you. $bSTYLE$b can be either $bDef$b (default) or $bZoot$b.",
+        "$bTABLEWIDTH$b: Sets the width for wrapping table-formatted text. (Use 0 for the default.)",
+        "$bWIDTH$b: The width you want srvx to wrap text it sends you.  (Use 0 for the default.)",
+        "$bMAXLOGINS$b: The number of users that can log into your account at once.  (Use 0 for the default.)",
+        "$bset$b with no arguments returns your current settings.",
+        "$uSee Also:$u accountinfo, nickinfo, userinfo"
+        );
+};
+"STATUS" {
+        "/services/nickserv/disable_nicks" (
+        "/msg $N STATUS",
+        "Displays information about the status of $b$N$b, including the total number of accounts in its database."
+        );
+        "!/services/nickserv/disable_nicks" (
+        "Displays information about the status of $b$N$b, including the total number of accounts and nicks that are registered in its database, and how many nicks are registered to your account (if you are authenticated to one)."
+        );
+};
+"USERINFO" ("/msg $N USERINFO <nick>",
+        "Shows what account the nick specified is authenticated to.",
+        "$uSee Also:$u auth, accountinfo");
+"VERSION" ("/msg $N VERSION",
+        "Sends you the srvx version and some additional version information that is specific to $b$N$b.");
+"GHOST" ("/msg $N GHOST <nick>",
+        "This disconnects an old client that is authed to your account.  This is $bnot$b the same thing as nick ownership; the user $bmust$b be authenticated to the same account you are.",
+        "$uSee Also:$u auth");
+"RENAME" ("/msg $N RENAME <nick|*old-account> <new-account>",
+        "Renames an account.",
+        "This command is only accessible to helpers and IRC operators.",
+        "$uSee Also:$u merge");
+"VACATION" ("/msg $N VACATION",
+        "Marks your account as \"on vacation\" until the next time you authenticate to $N.",
+        "While you are \"on vacation\", your account will not be deleted for inactivity.");
+"SEARCH" ("/msg $N SEARCH <action> <criteria> <value> [<criteria> <value>]...",
+        "Searches for accounts matching the critera, and then does something to them.",
+        "$uSee Also:$u search action, search criteria");
+"SEARCH ACTION" ("$bSEARCH ACTION$b",
+        "The following actions are valid:",
+        "  PRINT      - Print matching accounts",
+        "  COUNT      - Count matching accounts",
+        "  UNREGISTER - Unregister matching accounts",
+        "$uSee Also:$u search, search criteria");
+"SEARCH CRITERIA" {
+        "/services/nickserv/disable_nicks" (
+        "$bSEARCH CRITERIA$b",
+        "The following account search criteria are valid.  Each takes an additional argument, giving the actual criteria:",
+        "  LIMIT      - Limits the number of matches",
+        "  FLAGS      - Bits that must be turned on (e.g. +h) and/or off (e.g. -S) in an account",
+        "  REGISTERED - Registered time constraint (<Nu, <=Nu, =Nu, >=Nu or >Nu)",
+        "  SEEN       - Accounts not seen for at least this long",
+        "  ACCOUNTMASK - A glob that must match the account name",
+        "  EMAIL      - A glob that must match the account's email address",
+        "  HOSTMASK SUPERSET - Account matches if someone with this hostmask can auth to the account",
+        "  HOSTMASK EXACT - Account matches if this exact hostmask is in list",
+        "  HOSTMASK SUBSET - Account matches if this mask \"covers\" one in their userlist",
+        "  HOSTMASK   - A glob that must match a hostmask for the account (equivalent to HOSTMASK SUPERSET)",
+        "  ACCESS     - An $O access constraint (<nnn, <=nnn, =nnn, >=nnn or >nnn)",
+        "$uSee Also:$u search, search action"
+        );
+        "!/services/nickserv/disable_nicks" (
+        "$bSEARCH CRITERIA$b",
+        "The following account search criteria are valid.  Each takes an additional argument, giving the actual criteria:",
+        "  LIMIT      - Limits the number of matches",
+        "  FLAGS      - Bits that must be turned on (e.g. +h) and/or off (e.g. -S) in an account",
+        "  REGISTERED - Registered time constraint (<Nu, <=Nu, =Nu, >=Nu or >Nu)",
+        "  SEEN       - Accounts not seen for at least this long",
+        "  ACCOUNTMASK - A glob that must match the account name",
+        "  EMAIL      - A glob that must match the account's email address",
+        "  NICKMASK   - A glob that must match a nick registered to the account",
+        "  HOSTMASK SUPERSET - Account matches if someone with this hostmask can auth to the account",
+        "  HOSTMASK EXACT - Account matches if this exact hostmask is in list",
+        "  HOSTMASK SUBSET - Account matches if this mask \"covers\" one in their userlist",
+        "  HOSTMASK   - A glob that must match a hostmask for the account (equivalent to HOSTMASK SUPERSET)",
+        "  ACCESS     - An $O access constraint (<nnn, <=nnn, =nnn, >=nnn or >nnn)",
+        "$uSee Also:$u search, search action"
+        );
+};
+"MERGE" ("/msg $N MERGE <from-nick|*from-account> <to-nick|*to-account>",
+        "Merge access from $bfrom-account$b into $bto-account$b.  This includes hostmasks, registered nicks, authed users, access in channels, and OpServ access (if any).  If $bto-account$b has equal  or greater access than $bfrom-account$b (or more a general hostmask, etc), $bto-account$b keeps that information.",
+        "This command is only accessible to helpers and IRC operators.",
+        "$uSee Also:$u rename");
+"MERGEDB" ("/msg $N MERGE <dbfilename>",
+        "Merge contents of $bdbfilename$b into in-memory database.  Any accounts in both will be $bOVERWRITTEN$b with the information from $bdbfilename$b, although authed users will be authed to the new account.",
+        "This command is only accessible to IRC operators.",
+        "$uSee Also:$u write");
diff --git a/src/nickserv.help.m4 b/src/nickserv.help.m4
deleted file mode 100644 (file)
index 9010624..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-changequote({,})
-"<INDEX>" ("$b$N Help$b",
-        "$b$N$b is a nickname and authentication service, intended to serve as a central authentication point for all other network services. $b$C$b, $b$O$b, and $b$G$b all depend on $b$N$b to verify that users are valid. The other component allows for ownership of a nickname, but is not necessarily enabled.",
-       "$b$N$b command categories:",
-       "  ACCOUNT    Account management.",
-ifdef({/services/nickserv/disable_nicks},
-{        "  NOT NICKSERV   A note on what this service does and does not do.",},
-{        "  NICK       Nick management.",})
-ifdef({/services/nickserv/email_enabled},
-{        "  EMAIL      Email maintenance commands",})
-       "  OTHERS     Other functions.",
-        "  COMMANDS   A list of all available commands.");
-
-"HANDLE" ("The term $uhandle$u from earlier versions was confusing to many new users.  Therefore, it has been changed to $uaccount$u.");
-
-"ACCOUNT" ("Accounts are the way that $b$C$b identifies you for access to channels.  They are slightly similar to IRC nicks, but only have meaning to the services bots.  Until you authenticate to $b$N$b on an account, you can only use the $bREGISTER$b and $bAUTH$b commands.",
-        "Account management commands are:",
-        "  REGISTER   Register a new account.",
-        "  AUTH       Authenticate yourself to $b$N$b using an existing account.",
-        "  PASS       Change your account's password.",
-        "  ADDMASK    Add a hostmask to your account.",
-        "  DELMASK    Remove a hostmask from your account.",
-        "  SET        Set per-account options.",
-        "  UNREGISTER Unregister an account.",
-        "  RENAME     Renames an account",
-ifdef({/services/nickserv/enable_ghost},
-{        "  GHOST      Disconnects your old clients",})
-        "  ACCOUNT FLAGS Definition for each account flag");
-
-ifdef({/services/nickserv/disable_nicks},
-{"NOT NICKSERV" ("$bNOT NICKSERV$b",
-        "This $b$N$b is not a standard NickServ.",
-        "Most NickServs provide \"nick ownership\", and will either issue a /KILL or a forced nick change if you try to use a registered nick without providing the password.",
-        "This $b$N$b will not do this.  It only allows you to register an $baccount$b, which identifies users to $b$C$b.  In a way, it is a virtual nick.  When you authenticate to $b$N$b, it does not care what your IRC nick is -- only account you are logged in as.",
-        "$b$N$b can tell you what account a user is authenticated to using the $bUSERINFO$b command.  Any problems with account registration or $b$N$b should be directed to the normal support channel.");
-
-"OUNREGISTER" ("/msg $N OUNREGISTER <nick|*account>",
-        "Un-registers the specified account.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-        "$uSee Also:$u oregister");
-
-"UNREGISTER" ("/msg $N@$s UNREGISTER <password>",
-        "Un-registers the account you are authenticated as.",
-        "$uSee Also:$u register");},
-{"NICK" ("You may register IRC nicknames to be associated with your accounts, and will be able to request a KILL for anyone using a nickname registered to you.",
-       "Nick management commands are:",
-        "  NICKINFO   Find out who has registered a nick.",
-        "  REGNICK    Register a nickname.",
-        "  UNREGNICK  Unregister a nickname.",
-        "  RECLAIM    Reclaim a nick registered to you.");
-
-"NICKINFO" ("/msg $N NICKINFO <nick>",
-        "Displays information on the nick specified.",
-        "$uSee Also:$u accountinfo, userinfo");
-
-"REGNICK" ("/msg $N REGNICK ",
-        "Registers your current nick to the account you are authenticated to.",
-        "$uSee Also:$u register, unregister, unregnick");
-
-"OUNREGISTER" ("/msg $N OUNREGISTER <nick|*account>",
-        "Un-registers the specified account, and any nicks that have been registered to that account.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-        "$uSee Also:$u oregister, oregnick, ounregnick");
-
-"OREGNICK" ("/msg $N OREGNICK [<nick|*account> <nick>]",
-        "Registers specified nick to the specified account. If nick and account are not specified, then $boregnick$b registers your current nick to the account you are authenticated to.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-        "$uSee Also:$u oregister, ounregister, ounregnick");
-
-"OUNREGNICK" ("/msg $N OUNREGNICK <nick>",
-        "Un-registers a nick that was previously registered to an account.",
-        "$uSee Also:$u oregister, oregnick, ounregister");
-
-"UNREGISTER" ("/msg $N@$s UNREGISTER <password>",
-        "Un-registers the account you are authenticated with, and any nicks that have been registered to that account.",
-        "$uSee Also:$u register, regnick, unregnick");
-
-"UNREGNICK" ("/msg $N UNREGNICK [nick]",
-        "Un-registers a nick that was previously registered to your account.  If you do not specify a nick, your current nick will be un-registered.",
-        "$uSee Also:$u register, regnick, unregister");
-
-"RECLAIM" ("/msg $N RECLAIM <nick>",
-        "Reclaims the specified nick. You must be authenticated to the account that registered the nick.",
-        "Depending on configuration, this may do nothing, may ask the user nicely, may force a nick change on them, or may /KILL (disconnect) the target user.");})
-
-ifdef({/services/nickserv/email_enabled},
-{"EMAIL" ("Email-based maintenance commands and topics are:",
-        "  AUTHCOOKIE Email a cookie to allow you to authenticate (auth) without a matching hostmask.",
-        "  RESETPASS  Request a password change if you forgot your old password.",
-        "  COOKIE     Complete an email-based maintenance action.",
-        "  DELCOOKIE  For AUTHCOOKIE or RESETPASS, cancel the requested cookie.",
-        "  EMAIL POLICY  This network's policy on account email addresses.");
-
-"AUTHCOOKIE" ("/msg $N AUTHCOOKIE <account>",
-        "Requests that $N send you email with a cookie that allows you to auth to your account if you do not have a matching hostmask.  (For example, if your ISP changed your IP or hostname.)",
-        "Once you receive the cookie in email, you can use the $bcookie$b command to log in.",
-        "$uSee Also:$u cookie, delcookie");
-
-"RESETPASS" ("/msg $N@$s RESETPASS <account> <newpassword>",
-        "Requests that $N send you email with a cookie that will change your password (in case you have forgotten it).  Once you receive the cookie in email, use the $bcookie$b command to actually change your password.",
-        "$bYour password will not be changed, and you will not be able to use it to log in, until you confirm the change using the $ucookie$u command.$b",
-        "$uSee Also:$u cookie, delcookie");
-
-"DELCOOKIE" ("/msg $N DELCOOKIE",
-        "Requests that $N cancel your authcookie or resetpass cookie.",
-        "(Since set-email cookies and registration cookies send email to unverified addresses, to prevent mail flooding, they cannot be cancelled.)",
-        "$uSee Also:$u authcookie, resetpass, cookie");
-
-"COOKIE" ("/msg $N@$s COOKIE <account> <cookie>",
-        "Completes the maintenance action (for example, activating an account or changing your password) for which a cookie was issued.  The cookie will then be forgotten.",
-        "$uSee Also:$u authcookie, resetpass, set, delcookie");
-
-"EMAIL POLICY" ("$bEMAIL POLICY",
-        "FooNET has utmost respect for the privacy of its users.  We will submit your email address to as many spam databases as we can find, and we will even post it on our web site.",
-        "(No, not really.  It looks like somebody forgot to edit nickserv.help or nickserv.help.m4 to remove this entry.  Make sure they edit the mail section of srvx.conf while they are at it.)");})
-
-"OTHERS" ("Other commands are:",
-        "  USERINFO    Displays the account a user is authenticated to.",
-        "  ACCOUNTINFO Displays information about an account.",
-        "  VERSION     $b$N$b version information.",
-        "  STATUS      $b$N$b status.",
-        "  SEARCH      Search for accounts by various criteria.",
-        "  MERGE       Merge one account into another.",
-        "  MERGEDB     Load a database into memory.",
-        "  HELP        Get help on $b$N$b.");
-
-"ADDMASK" ("/msg $N ADDMASK [user@host]",
-        "Adds the specified user@host to the account you are authenticated to with $b$N$b.  If no mask is given, it uses your current mask.",
-        "$uSee Also:$u auth, delmask");
-"ALLOWAUTH" ("/msg $N ALLOWAUTH <nick> [account] [STAFF]",
-        "Allows the specified nick to $bauth$b to the specified account. $bAllowauth$b does NOT add the hostmask of that nick to the specified account.",
-        "If no account is given, it will cancel the allowauth for the user (assuming the user has an allowauth).",
-        "If the account is marked as a helper or oper, the STAFF qualifier must be given afterwards.  This reduces social engineering attacks.",
-        "$uSee Also:$u addmask, auth");
-"AUTH" ("/msg $N@$s AUTH [account] <password>",
-        "Authenticates yourself with $b$N$b to the specified account. You must use $bauth$b before you have any access to network services, including channels that are registered with $b$C$b.",
-        "If you omit the account, it uses your current nick as your account name.",
-ifdef({/services/nickserv/email_enabled},
-{        "$uSee Also:$u pass, resetpass, authcookie"},
-{        "$uSee Also:$u pass"})
-);
-"DELMASK" ("/msg $N DELMASK <user@host>",
-        "Removes a hostmask from the account you are authenticated on.",
-        "An account must have at least one hostmask; you cannot remove the last mask for an account.",
-        "$uSee Also:$u addmask");
-"ACCOUNTINFO" ("/msg $N ACCOUNTINFO <nick|*account>",
-        "Displays infomation on the specified account, including the date the account was registered, the last time that person was seen, the account's $b$N$b info, its flags, its hostmask(s), its channels, and the account's current nickname.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-ifdef({/services/nickserv/disable_nicks},
-{        "$uSee Also:$u userinfo, account flags"},
-{        "$uSee Also:$u nickinfo, userinfo, account flags"}));
-"ACCOUNT FLAGS" ("$bACCOUNT FLAGS$b",
-        "The following flags on accounts are defined:",
-        "$bS$b  $O access suspended",
-        "$bp$b  Use PRIVMSG for messages rather than NOTICE",
-        "$bh$b  User is a support helper (must be in support channel to override security)",
-        "$bH$b  User is a network helper (can toggle security override)",
-        "$bg$b  God mode (security override for IRC staff)",
-        "$bs$b  Account suspended",
-        "$bc$b  Use mIRC color codes in responses",
-        "$bf$b  Account frozen/on vacation (will not be unregistered for inactivity; cleared when account is authenticated against)",
-        "$bn$b  No-delete (will never be unregistered for inactivity)",
-        "$uSee Also:$u accountinfo, set");
-"OADDMASK" ("/msg $N OADDMASK <nick|*account> <user@host>",
-        "Adds a hostmask to the specified account.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-        "$uSee Also:$u odelmask");
-"ODELMASK" ("/msg $N ODELMASK <nick|*account> <user@host>",
-        "Removes a hostmask from the specified account.",
-        "An account must have at least one hostmask; you cannot remove the last mask for an account.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-        "$uSee Also:$u oaddmask");
-"OREGISTER" ("/msg $N@$s OREGISTER <account> <password> <user@host|nick>",
-        "Registers an account with $b$N$b using the specified account, password, and user@host. If then nick of an online user is specified, then that user's user@host is used.",
-ifdef({/services/nickserv/disable_nicks},
-{        "$uSee Also:$u ounregister"},
-{        "$uSee Also:$u oregnick, ounregister, ounregnick"}));
-"OSET" ("/msg $N OSET <nick|*account> [<setting> <value>]",
-        "Changes an account's settings for srvx. In addition to the normal $bset$b settings, you may set:",
-        "$bPASSWORD$b: Sets user's password.",
-        "$bFLAGS$b: Changes account flags for user.",
-        "$bLEVEL$b: Sets $O access level.",
-        "$bEPITHET$b: The description $C shows for the user's access.",
-        "You may use *Account instead of Nick as the name argument; the * makes $N use the name of an account directly (useful if the user is not online).",
-ifdef({/services/nickserv/disable_nicks},
-{        "$uSee Also:$u accountinfo, account flags, set, userinfo"},
-{        "$uSee Also:$u accountinfo, account flags, nickinfo, set, userinfo"}));
-"PASS" ("/msg $N@$s PASS <oldpass> <newpass>",
-        "Changes your $b$N$b password.",
-        "$uSee Also:$u auth");
-"REGISTER" (
-ifdef({/services/nickserv/email_enabled},
-{ifdef({/services/nickserv/email_required},
-{        "/msg $N@$s REGISTER <account> <password> <email>",},
-{        "/msg $N@$s REGISTER <account> <password> [email]",})},
-{        "/msg $N@$s REGISTER <account> <password>",})
-        "Registers a specified account with $b$N$b, adding your current user@host to your new account. You will be required to know the password you specify with $bregister$b in order to be able to use $bauth$b to authenticate to your account.",
-ifdef({/services/nickserv/email_enabled},
-{ifdef({/services/nickserv/email_required},
-{        "An email will be sent to the email address you give containing a cookie that will let you activate your account.  Once you have that cookie, you must use the $bcookie$b command to be able to use your account.",},
-{        "If you specify an email address, an email will be sent to it containing a cookie that will let you activate your account.  Once you have that cookie, you must use the $bcookie$b command to be able to use your account.",})})
-        "NOTE: It is strongly recommended that you use the long form ($N@$s) rather than just nick ($N) for this command, to protect against impersonators on other networks.",
-ifdef({/services/nickserv/disable_nicks},
-{        "$uSee Also:$u auth, unregister"},
-{        "$uSee Also:$u auth, regnick, unregister, unregnick"}));
-"SET" ("/msg $N SET [<setting> [value]]",
-        "Changes your account settings for srvx. Settings are:",
-        "$bANNOUNCEMENTS$b: Indicates whether you wish to receive community announcements via the $G service.",
-        "$bCOLOR$b: If set, $b$N$b and $b$C$b will use $bbold$b and $uunderlines$u in text they send you.",
-ifdef({/services/nickserv/email_enabled},
-{        "$bEMAIL$b: Sets (or changes) your email address.",})
-        "$bINFO$b:  Your infoline for $b$N$b (which can be viewed with the $baccountinfo$b command).",
-        "$bLANGUAGE$b: Your preferred language for private messages from the services.",
-        "$bPRIVMSG$b: If set, $b$N$b and $b$C$b will send text to you using PRIVMSGs rather than NOTICEs.",
-        "$bSTYLE$b: The style you want srvx to use for channel userlists it sends you. $bSTYLE$b can be either $bDef$b (default) or $bZoot$b.",
-        "$bTABLEWIDTH$b: Sets the width for wrapping table-formatted text. (Use 0 for the default.)",
-        "$bWIDTH$b: The width you want srvx to wrap text it sends you.  (Use 0 for the default.)",
-        "$bMAXLOGINS$b: The number of users that can log into your account at once.  (Use 0 for the default.)",
-        "$bset$b with no arguments returns your current settings.",
-ifdef({/services/nickserv/disable_nicks},
-{        "$uSee Also:$u accountinfo, userinfo"},
-{        "$uSee Also:$u accountinfo, nickinfo, userinfo"}));
-"STATUS" ("/msg $N STATUS",
-ifdef({/services/nickserv/disable_nicks},
-{        "Displays information about the status of $b$N$b, including the total number of accounts in its database."},
-{        "Displays information about the status of $b$N$b, including the total number of accounts and nicks that are registered in its database, and how many nicks are registered to your account (if you are authenticated to one)."}));
-"USERINFO" ("/msg $N USERINFO <nick>",
-        "Shows what account the nick specified is authenticated to.",
-        "$uSee Also:$u auth, accountinfo");
-"VERSION" ("/msg $N VERSION",
-        "Sends you the srvx version and some additional version information that is specific to $b$N$b.");
-"GHOST" ("/msg $N GHOST <nick>",
-        "This disconnects an old client that is authed to your account.  This is $bnot$b the same thing as nick ownership; the user $bmust$b be authenticated to the same account you are.",
-        "$uSee Also:$u auth");
-"RENAME" ("/msg $N RENAME <nick|*old-account> <new-account>",
-        "Renames an account.",
-        "This command is only accessible to helpers and IRC operators.",
-        "$uSee Also:$u merge");
-"VACATION" ("/msg $N VACATION",
-        "Marks your account as \"on vacation\" until the next time you authenticate to $N.",
-        "While you are \"on vacation\", your account will not be deleted for inactivity.");
-"SEARCH" ("/msg $N SEARCH <action> <criteria> <value> [<criteria> <value>]...",
-        "Searches for accounts matching the critera, and then does something to them.",
-        "$uSee Also:$u search action, search criteria");
-"SEARCH ACTION" ("$bSEARCH ACTION$b",
-        "The following actions are valid:",
-        "  PRINT      - Print matching accounts",
-        "  COUNT      - Count matching accounts",
-        "  UNREGISTER - Unregister matching accounts",
-        "$uSee Also:$u search, search criteria");
-"SEARCH CRITERIA" ("$bSEARCH CRITERIA$b",
-        "The following account search criteria are valid.  Each takes an additional argument, giving the actual criteria:",
-        "  LIMIT      - Limits the number of matches",
-        "  FLAGS      - Bits that must be turned on (e.g. +h) and/or off (e.g. -S) in an account",
-        "  REGISTERED - Registered time constraint (<Nu, <=Nu, =Nu, >=Nu or >Nu)",
-        "  SEEN       - Accounts not seen for at least this long",
-        "  ACCOUNTMASK - A glob that must match the account name",
-        "  EMAIL      - A glob that must match the account's email address",
-ifdef({/services/nickserv/disable_nicks},,
-{        "  NICKMASK   - A glob that must match a nick registered to the account",})
-        "  HOSTMASK SUPERSET - Account matches if someone with this hostmask can auth to the account",
-        "  HOSTMASK EXACT - Account matches if this exact hostmask is in list",
-        "  HOSTMASK SUBSET - Account matches if this mask \"covers\" one in their userlist",
-        "  HOSTMASK   - A glob that must match a hostmask for the account (equivalent to HOSTMASK SUPERSET)",
-        "  ACCESS     - An $O access constraint (<nnn, <=nnn, =nnn, >=nnn or >nnn)",
-        "$uSee Also:$u search, search action");
-"MERGE" ("/msg $N MERGE <from-nick|*from-account> <to-nick|*to-account>",
-        "Merge access from $bfrom-account$b into $bto-account$b.  This includes hostmasks, registered nicks, authed users, access in channels, and OpServ access (if any).  If $bto-account$b has equal  or greater access than $bfrom-account$b (or more a general hostmask, etc), $bto-account$b keeps that information.",
-        "This command is only accessible to helpers and IRC operators.",
-        "$uSee Also:$u rename");
-"MERGEDB" ("/msg $N MERGE <dbfilename>",
-        "Merge contents of $bdbfilename$b into in-memory database.  Any accounts in both will be $bOVERWRITTEN$b with the information from $bdbfilename$b, although authed users will be authed to the new account.",
-        "This command is only accessible to IRC operators.",
-        "$uSee Also:$u write");