From 2575d32764863d3d571794f433e407d21a06032a Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Tue, 13 Jan 2009 02:30:25 +0000 Subject: [PATCH] Author: Entrope Description: Make G-line parsing considerably more robust for unusual input forms. Fix affected-user counting for G-lines with CIDR host portions. Add tests to exercise the new parsing behaviors. git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1900 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- ChangeLog | 33 +++++ include/gline.h | 3 + ircd/gline.c | 52 ++++++- ircd/m_gline.c | 32 +++-- tests/glines.cmd | 330 +++++++++++++++++++++++++++++++++++++++++++ tests/ircd-2.conf | 1 + tests/ircd.conf | 1 + tests/readme.txt | 86 +++++++++++ tests/test-driver.pl | 9 +- 9 files changed, 530 insertions(+), 17 deletions(-) create mode 100644 tests/glines.cmd diff --git a/ChangeLog b/ChangeLog index 378f262..86a672d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,36 @@ +2009-01-12 Michael Poole + + * include/gline.h (gline_forward_deactivation): Declare. + + * ircd/gline.c (count_users): Use ipmask-based checks too. + (gline_add): Require flags to have exactly one of the GLINE_GLOBAL + and GLINE_LOCAL bits set. + (gline_forward_deactivation): Implement new function. + (gline_find): Only require the GLINE_LOCAL flag to be set in the + gline structure; infer GLINE_GLOBAL when it is cleared. (This + matches the value and usage of GLINE_MASK.) + + * ircd/m_gline.c (ms_gline): Default lastmod to the known lastmod + time for GLINE_LOCAL_{DE,}ACTIVATE. + (mo_gline): Check that expiration times parse as expected. Reject + "*" as a target for GLINE_LOCAL_{DE,}ACTIVATE. Require reason and + expiration time for new G-lines. Allow deactivation of an unknown + G-line without creating it ("so-and-so ... creating new G-line" is + a confusing message for a deactivation). + + * tests/glines.cmd: New test script for G-line parsing tests. + + * tests/ircd.conf: Enable CONFIG_OPERCMDS for glines.cmd. + + * tests/ircd-2.conf: Likewise. + + * tests/readme.txt: Add section on command syntax. + + * tests/test-driver.pl: Report line numbers more clearly. + Fix (somewhat kludgily) the brokenness of consecutive "expect" + lines, as demonstrated by the numeric 219 and 281 expects in + glines.cmd. + 2008-11-17 Michael Poole * ircd/m_kick.c (ms_kick): Properly handle crossing net rider and diff --git a/include/gline.h b/include/gline.h index 44798a6..e5623f4 100644 --- a/include/gline.h +++ b/include/gline.h @@ -128,6 +128,9 @@ extern int gline_activate(struct Client *cptr, struct Client *sptr, extern int gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline, time_t lastmod, unsigned int flags); +extern int gline_forward_deactivation(struct Client *cptr, struct Client *sptr, + char *userhost, time_t expire, time_t lastmod, + time_t lifetime, unsigned int flags); extern int gline_modify(struct Client *cptr, struct Client *sptr, struct Gline *gline, enum GlineAction action, char *reason, time_t expire, time_t lastmod, diff --git a/ircd/gline.c b/ircd/gline.c index f294d99..487dde9 100644 --- a/ircd/gline.c +++ b/ircd/gline.c @@ -354,11 +354,15 @@ gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline) static int count_users(char *mask) { + struct irc_in_addr ipmask; struct Client *acptr; int count = 0; + int ipmask_valid; char namebuf[USERLEN + HOSTLEN + 2]; char ipbuf[USERLEN + SOCKIPLEN + 2]; + unsigned char ipmask_len; + ipmask_valid = ipmask_parse(mask, &ipmask, &ipmask_len); for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) { if (!IsUser(acptr)) continue; @@ -368,7 +372,9 @@ count_users(char *mask) ircd_snprintf(0, ipbuf, sizeof(ipbuf), "%s@%s", cli_user(acptr)->username, ircd_ntoa(&cli_ip(acptr))); - if (!match(mask, namebuf) || !match(mask, ipbuf)) + if (!match(mask, namebuf) + || !match(mask, ipbuf) + || (ipmask_valid && ipmask_check(&cli_ip(acptr), &ipmask, ipmask_len))) count++; } @@ -429,6 +435,8 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost, assert(0 != userhost); assert(0 != reason); + assert(((flags & (GLINE_GLOBAL | GLINE_LOCAL)) == GLINE_GLOBAL) || + ((flags & (GLINE_GLOBAL | GLINE_LOCAL)) == GLINE_LOCAL)); Debug((DEBUG_DEBUG, "gline_add(\"%s\", \"%s\", \"%s\", \"%s\", %Tu, %Tu " "%Tu, 0x%04x)", cli_name(cptr), cli_name(sptr), userhost, reason, @@ -664,6 +672,42 @@ gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline, return 0; } +/** Send a deactivation request for a locally unknown G-line. + * @param[in] cptr Client that sent us the G-line modification. + * @param[in] sptr Client that originated the G-line modification. + * @param[in] userhost Text representation of G-line target. + * @param[in] expire Expiration time of G-line. + * @param[in] lastmod Last modification time of G-line. + * @param[in] lifetime Lifetime of G-line. + * @param[in] flags Bitwise combination of GLINE_* flags. + * @return Zero. + */ +int +gline_forward_deactivation(struct Client *cptr, struct Client *sptr, + char *userhost, time_t expire, time_t lastmod, + time_t lifetime, unsigned int flags) +{ + char *msg = "deactivating unknown global"; + + if (!lifetime) + lifetime = expire; + + /* Inform ops and log it */ + sendto_opmask_butone(0, SNO_GLINE, "%s %s GLINE for %s, expiring at %Tu", + (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? + cli_name(sptr) : cli_name((cli_user(sptr))->server), + msg, userhost, expire + TSoffset); + + log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, + "%#C %s GLINE for %s, expiring at %Tu", sptr, msg, userhost, + expire); + + sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* -%s %Tu %Tu %Tu", + userhost, expire, lastmod, lifetime); + + return 0; +} + /** Modify a global G-line. * @param[in] cptr Client that sent us the G-line modification. * @param[in] sptr Client that originated the G-line modification. @@ -918,8 +962,7 @@ gline_find(char *userhost, unsigned int flags) if (flags & (GLINE_BADCHAN | GLINE_ANY)) { gliter(BadChanGlineList, gline, sgline) { - if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || - (flags & GLINE_LOCAL && gline->gl_flags & GLINE_GLOBAL) || + if ((flags & (GlineIsLocal(gline) ? GLINE_GLOBAL : GLINE_LOCAL)) || (flags & GLINE_LASTMOD && !gline->gl_lastmod)) continue; else if ((flags & GLINE_EXACT ? ircd_strcmp(gline->gl_user, userhost) : @@ -936,8 +979,7 @@ gline_find(char *userhost, unsigned int flags) canon_userhost(t_uh, &user, &host, "*"); gliter(GlobalGlineList, gline, sgline) { - if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || - (flags & GLINE_LOCAL && gline->gl_flags & GLINE_GLOBAL) || + if ((flags & (GlineIsLocal(gline) ? GLINE_GLOBAL : GLINE_LOCAL)) || (flags & GLINE_LASTMOD && !gline->gl_lastmod)) continue; else if (flags & GLINE_EXACT) { diff --git a/ircd/m_gline.c b/ircd/m_gline.c index 95e2ac1..c099a63 100644 --- a/ircd/m_gline.c +++ b/ircd/m_gline.c @@ -279,6 +279,7 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) case GLINE_LOCAL_DEACTIVATE: /* locally deactivating a G-line */ if (!agline) /* no G-line to locally activate or deactivate? */ return send_reply(sptr, ERR_NOSUCHGLINE, mask); + lastmod = agline->gl_lastmod; break; /* no additional parameters to manipulate */ case GLINE_ACTIVATE: /* activating a G-line */ @@ -379,7 +380,7 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) unsigned int flags = 0; enum GlineAction action = GLINE_MODIFY; time_t expire = 0; - char *mask = parv[1], *target = 0, *reason = 0; + char *mask = parv[1], *target = 0, *reason = 0, *end; if (parc < 2) return gline_list(sptr, 0); @@ -422,7 +423,9 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) return need_more_params(sptr, "GLINE"); target = parv[2]; /* get the target... */ - expire = atoi(parv[3]) + CurrentTime; /* and the expiration */ + expire = strtol(parv[3], &end, 10) + CurrentTime; /* and the expiration */ + if (*end != '\0') + return send_reply(sptr, SND_EXPLICIT | ERR_BADEXPIRE, "%s :Bad expire time", parv[3]); flags |= GLINE_EXPIRE; /* remember that we got an expire time */ @@ -442,8 +445,11 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) case GLINE_LOCAL_ACTIVATE: /* locally activate a G-line */ case GLINE_LOCAL_DEACTIVATE: /* locally deactivate a G-line */ - if (parc > 2) /* if target is available, pick it */ + if (parc > 2) { /* if target is available, pick it */ target = parv[2]; + if (target[0] == '*' && target[1] == '\0') + return send_reply(sptr, ERR_NOSUCHSERVER, target); + } break; case GLINE_ACTIVATE: /* activating/adding a G-line */ @@ -453,8 +459,10 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) if (parc > 3) { /* get expiration and target */ - expire = atoi(parv[parc - 2]) + CurrentTime; reason = parv[parc - 1]; + expire = strtol(parv[parc - 2], &end, 10) + CurrentTime; + if (*end != '\0') + return send_reply(sptr, SND_EXPLICIT | ERR_BADEXPIRE, "%s :Bad expire time", parv[parc - 2]); flags |= GLINE_EXPIRE | GLINE_REASON; /* remember that we got 'em */ @@ -499,8 +507,8 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) action == GLINE_LOCAL_ACTIVATE ? '>' : '<')); sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s", acptr, - flags & GLINE_OPERFORCE ? "!" : "", - action == GLINE_LOCAL_ACTIVATE ? '>' : '<', mask); + flags & GLINE_OPERFORCE ? "!" : "", + action == GLINE_LOCAL_ACTIVATE ? '>' : '<', mask); return 0; /* all done */ } @@ -582,10 +590,12 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) } } - /* can't modify a G-line that doesn't exist... */ + /* can't modify a G-line that doesn't exist... + * (and if we are creating a new one, we need a reason and expiration) + */ if (!agline && (action == GLINE_MODIFY || action == GLINE_LOCAL_ACTIVATE || - action == GLINE_LOCAL_DEACTIVATE)) + action == GLINE_LOCAL_DEACTIVATE || !reason || !expire)) return send_reply(sptr, ERR_NOSUCHGLINE, mask); /* check for G-line permissions... */ @@ -600,6 +610,12 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) return send_reply(sptr, ERR_NOPRIVILEGES); } + /* If globally disabling a G-line that we do not already have, avoid + * creating a temporary one. */ + if (!agline && action == GLINE_DEACTIVATE) { + return gline_forward_deactivation(cptr, sptr, mask, expire, CurrentTime, 0, flags); + } + Debug((DEBUG_DEBUG, "I have a global G-line I am acting upon now; " "target %s, mask %s, operforce %s, action %s, expire %Tu, " "reason: %s; gline %s! (fields present: %s %s)", target, diff --git a/tests/glines.cmd b/tests/glines.cmd new file mode 100644 index 0000000..f7a3f12 --- /dev/null +++ b/tests/glines.cmd @@ -0,0 +1,330 @@ +define srv1 localhost:7601 +define srv1-name irc.example.net +define srv2 localhost:7611 +define srv2-name irc-2.example.net +define cl1-nick Op3rm4n +define cl2-nick Monitor + +# Connect a client, oper it up and force G-line server notices on (0x8200). +connect cl1 %cl1-nick% oper %srv1% :Some IRC Operator +:cl1 oper oper oper +:cl1 mode %cl1-nick% +s +33280 + +# Do the same for the second server, for monitoring snotices. +# This is useful while debugging the remote operations in this script. +connect cl2 %cl2-nick% oper %srv2% :Some IRC Operator +:cl2 oper oper oper +:cl2 mode %cl2-nick% +s +33280 + +# For an operator, the syntax is: +# GLINE [[!][+|-|>|<] [] [ [:]]] +# By itself, that's 2 * 5 * 3 (target missing/self, other or global) +# * 3 = 90 possibilities. +# +# In each case, we would want to try it with various combinations: +# - Local gline for absent or present +# - Global gline for absent, or present but locally and/or globally activated or deactivated (5 combinations) +# So ten pre-existing states for each syntax variant. +# For sanity's sake, we don't do all 900 combinations. +# +# Perl code to generate a fairly complete list: +# foreach my $operator (split(//, ' +-><')) { +# foreach my $mask ('test@example.com') { +# foreach my $target ('', '%srv1-name%', '%srv2-name%', '*') { +# foreach my $expiration ('', '100000') { +# foreach my $reason ('', ':foo') { +# my $str = "GLINE ${operator}${mask} ${target} ${expiration} ${reason}"; +# $str =~ s/ +/ /g; +# $str =~ s/ +$//; +# print "$str\n"; +# } +# } +# } +# print "\n"; +# } +# } + +# Initial query: verify that our target G-line does not exist. +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 512 test@example.com :No such gline + +# Try a bunch of operations, only one of which should generate an actual G-line. +:cl1 raw :GLINE test@example.com :foo +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE test@example.com 100000 +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE test@example.com 100000 :foo +:cl1 expect %srv1-name% 515 foo :Bad expire time +:cl1 raw :GLINE test@example.com %srv1-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE test@example.com %srv1-name% :foo +:cl1 expect %srv1-name% 515 foo :Bad expire time +:cl1 raw :GLINE test@example.com %srv1-name% 100000 +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters + +# Check that we still have no G-line, and that we create it as expected. +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% adding local GLINE for test@example.com, expiring at \\d+: foo +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 280 test@example.com \\d+ 0 \\d+ %srv1-name% \\+ :foo +:cl1 expect %srv1-name% 281 :End of G-line List +# Now remove it (and verify removal). +:cl1 raw :GLINE -test@example.com 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% removing local GLINE for test@example.com +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 512 test@example.com :No such gline + +# Try doing remote operations. +:cl1 raw :GLINE test@example.com %srv2-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE test@example.com %srv2-name% :foo +:cl1 expect %srv1-name% 515 foo :Bad expire time +:cl1 raw :GLINE test@example.com %srv2-name% 100000 +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE test@example.com %srv2-name% 100000 :foo +# No response expected for remote commands; do a remote stats query to check. +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ 0 \\d+ \\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report +:cl1 raw :GLINE -test@example.com %srv2-name% 100000 :foo +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 219 g :End of /STATS report + +# Try doing network-wide operations. +:cl1 raw :GLINE test@example.com * +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +# These should fail because no existing G-line matches. +:cl1 raw :GLINE test@example.com * :foo +:cl1 expect %srv1-name% 515 foo :Bad expire time +:cl1 raw :GLINE test@example.com * 100000 +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE test@example.com * 100000 :foo +:cl1 expect %srv1-name% 512 test@example.com :No such gline + +# Try explicit create/activate operations. +:cl1 raw :GLINE +test@example.com +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE +test@example.com :foo +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE +test@example.com 100000 +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +# This next one should create the G-line. +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE +test@example.com 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% adding local GLINE for test@example.com, expiring at \\d+: foo +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 280 test@example.com \\d+ 0 \\d+ %srv1-name% \\+ :foo +:cl1 expect %srv1-name% 281 :End of G-line List +:cl1 raw :GLINE -test@example.com 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% removing local GLINE for test@example.com + +# Local create/activate operations? +:cl1 raw :GLINE +test@example.com %srv1-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE +test@example.com %srv1-name% :foo +:cl1 expect %srv1-name% 515 .+ :Bad expire time +:cl1 raw :GLINE +test@example.com %srv1-name% 100000 +:cl1 expect %srv1-name% 515 .+ :Bad expire time +:cl1 raw :GLINE +test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% adding local GLINE for test@example.com, expiring at \\d+: foo +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 280 test@example.com \\d+ 0 \\d+ %srv1-name% \\+ :foo +:cl1 expect %srv1-name% 281 :End of G-line List +:cl1 raw :GLINE -test@example.com 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% removing local GLINE for test@example.com +:cl1 raw :GLINE +test@example.com %srv1-name% 100000 :foo +:cl1 raw :GLINE -test@example.com 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% removing local GLINE for test@example.com +:cl1 raw :GLINE +test@example.com %srv1-name% 100000 :foo +:cl1 raw :GLINE -test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% removing local GLINE for test@example.com + +# Remote create/activate operations? +:cl1 raw :GLINE +test@example.com %srv2-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE +test@example.com %srv2-name% :foo +:cl1 expect %srv1-name% 515 .+ :Bad expire time +:cl1 raw :GLINE +test@example.com %srv2-name% 100000 +:cl1 expect %srv1-name% 515 .+ :Bad expire time +:cl1 raw :GLINE +test@example.com %srv2-name% 100000 :foo +# No response expected for remote commands; do a remote stats query to check. +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ 0 \\d+ \\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report +:cl1 raw :GLINE -test@example.com %srv2-name% 100000 :foo +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 219 g :End of /STATS report + +# Global create/activate operations? +:cl1 raw :GLINE +test@example.com * +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE +test@example.com * :foo +:cl1 expect %srv1-name% 515 .+ :Bad expire time +:cl1 raw :GLINE +test@example.com * 100000 +:cl1 expect %srv1-name% 515 .+ :Bad expire time + +# Local G-line deactivation? +:cl1 raw :GLINE -test@example.com +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE -test@example.com :foo +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE -test@example.com 100000 +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE -test@example.com 100000 :foo +:cl1 expect %srv1-name% 512 test@example.com :No such gline + +# .. with a specified server? +:cl1 raw :GLINE -test@example.com %srv1-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE -test@example.com %srv1-name% :foo +:cl1 expect %srv1-name% 515 %srv1-name% :Bad expire time +:cl1 raw :GLINE -test@example.com %srv1-name% 100000 +:cl1 expect %srv1-name% 515 %srv1-name% :Bad expire time +:cl1 raw :GLINE -test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE -test@example.com %srv2-name% +:cl1 expect %srv1-name% 461 GLINE :Not enough parameters +:cl1 raw :GLINE -test@example.com %srv2-name% :foo +:cl1 expect %srv1-name% 515 %srv2-name% :Bad expire time +:cl1 raw :GLINE -test@example.com %srv2-name% 100000 +:cl1 expect %srv1-name% 515 %srv2-name% :Bad expire time +:cl1 raw :GLINE -test@example.com %srv2-name% 100000 :foo +:cl1 expect %srv2-name% 512 test@example.com :No such gline + +# Global deactivations? +:cl1 raw :GLINE -test@example.com * +:cl1 expect %srv1-name% 512 test@example.com :No such gline +:cl1 raw :GLINE -test@example.com * :foo +:cl1 expect %srv1-name% 515 \\* :Bad expire time +:cl1 raw :GLINE -test@example.com * 100000 +:cl1 expect %srv1-name% 515 \\* :Bad expire time +:cl1 raw :GLINE -test@example.com * 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% deactivating unknown global GLINE for test@example.com + +# Now start with the operations that create or need a global G-line. + +# Global activations and deactivations? +:cl1 raw :GLINE +test@example.com * 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% adding global GLINE for test@example.com +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 280 test@example.com \\d+ \\d+ \\d+ \\* \\+ :foo +:cl1 raw :GLINE test@example.com * 100000 +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: changing expiration time to \\d+; and extending record lifetime to \\d+ +:cl1 raw :GLINE test@example.com * 100000 :food +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: changing expiration time to \\d+; extending record lifetime to \\d+; and changing reason to "food" +:cl1 raw :GLINE -test@example.com * +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: globally deactivating G-line +:cl1 raw :GLINE -test@example.com * 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: changing expiration time to \\d+; extending record lifetime to \\d+; and changing reason to "foo" +:cl1 raw :GLINE test@example.com +:cl1 expect %srv1-name% 280 test@example.com \\d+ \\d+ \\d+ \\* - :foo + +# Failed local activations and deactivations? +:cl1 raw :GLINE >test@example.com +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com :foo +:cl1 expect %srv1-name% 402 foo :No such server +:cl1 raw :GLINE test@example.com 100000 +:cl1 expect %srv1-name% 402 100000 :No such server +:cl1 raw :GLINE test@2.example.com +:cl1 expect %srv1-name% 512 test@2.example.com :No such gline +:cl1 raw :GLINE >test@example.com %srv1-name% + +# What about successes for the local server? +# (For simplicity, single-server activations and deactivations are not +# allowed to change any other parameters.) +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com %srv1-name% :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com %srv1-name% 100000 +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com %srv1-name% 100000 :foo +:cl1 expect %srv1-name% NOTICE :\\*\\*\\* Notice -- %cl1-nick% modifying global GLINE for test@example.com: locally activating G-line +:cl1 raw :GLINE test@example.com %srv2-name% +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ \\d+ \\d+ >\\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report +# Form: "GLINE test@example.com %srv2-name% :foo +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ \\d+ \\d+ >\\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report +# Form: "GLINE test@example.com %srv2-name% 100000 +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ \\d+ \\d+ >\\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report +# Form: "GLINE test@example.com %srv2-name% 100000 :foo +:cl1 raw :STATS g %srv2-name% +:cl1 expect %srv2-name% 247 G test@example.com \\d+ \\d+ \\d+ >\\+ :foo +:cl1 expect %srv2-name% 219 g :End of /STATS report + +# What about activations/deactivations that might go across the whole network? +# (These are not permitted because "global local [de]activation" does not +# make sense: just globally [de]activate the G-line.) +:cl1 raw :GLINE >test@example.com * +:cl1 expect %srv1-name% 402 \\* :No such server +:cl1 raw :GLINE test@example.com * :foo +:cl1 expect %srv1-name% 402 \\* :No such server +:cl1 raw :GLINE test@example.com * 100000 +:cl1 expect %srv1-name% 402 \\* :No such server +:cl1 raw :GLINE test@example.com * 100000 :foo +:cl1 expect %srv1-name% 402 \\* :No such server +:cl1 raw :GLINE + +A variable is expanded by writing %variablename% in later commands. +If a variable is dereferenced without a definition, the test will +abort. + +Following the variable definitions is usually one or more "connect" +statements. These have the syntax: + connect : +This creates a client and starts a connection to an IRC server. The +tag is used to issue commands for that client in the rest of the file. +The remaining fields have their usual meanings for IRC. + +A number of IRC commands are supported natively, including: + : join + : mode [ ...] + : nick + : notice : + : oper + : part [:] + : privmsg : + : quit [:] + : raw : + +Other commands are used to implement tests: + : expect + : sleep + : wait + +The test commands are discussed at more length below. + +expect Syntax +============= + +The command to look for data coming from the irc server is "expect": + : expect + +The contents are treated as a regular expression and matched against +the start of the line. If the line from the IRC server began with +':', that is removed before the match is performed. + +Because the contents are a regular expression, and because \ is used +as an escape character both in parsing both the script line and the +regular expression, some common things become awkward to match: + :cl1 mode %channel% +D + :cl1 expect %cl1-nick% mode %channel% \\+D +or a more drastic example: + :cl1 mode %channel% +b *!*@*.bar.example.* + :cl1 mode %channel% +b + :cl1 expect %srv1-name% 367 %channel% \\*!\\*@\\*\\.bar\\.example\\.* %cl1-nick% \\d+ + +sleep Syntax +============ + +The command to make a client stop operating for a fixed period of time +is "sleep": + : sleep + +This will deactivate the identified client for at least +seconds (which may be a floating point number). Other clients will +continue to process commands, but if another command for the +identified client is encountered, it will block execution until the +time expires. + +wait Syntax +=========== + +The command to synchronize one client to another is "wait": + : wait + +This is syntactic sugar for something like this: + : expect NOTICE :SYNC + : notice :SYNC + : expect NOTICE :SYNC + : notice :SYNC + +In other words, the wait command uses in-IRC messages to make sure +that other clients have already executed commands up to a certain +point in the test script. diff --git a/tests/test-driver.pl b/tests/test-driver.pl index e20745c..8ba77a2 100755 --- a/tests/test-driver.pl +++ b/tests/test-driver.pl @@ -164,7 +164,7 @@ sub drv_heartbeat { # expand any macros in the line $line =~ s/(?<=[^\\])%(\S+?)%/$heap->{macros}->{$1} - or die "Use of undefined macro $1 at $heap->{lineno}\n"/eg; + or die "Use of undefined macro $1 at line $heap->{lineno}\n"/eg; # remove any \-escapes $line =~ s/\\(.)/$1/g; # figure out the type of line @@ -235,7 +235,7 @@ sub drv_heartbeat { my $client = $heap->{clients}->{$c}; if (not $client) { print "ERROR: Unknown session name $c (line $heap->{lineno}; ignoring)\n"; - } elsif (($used->{$c} and not $zero_time->{$cmd}) or not $client->{ready}) { + } elsif (($used->{$c} and not $zero_time->{$cmd}) or ($cmd ne 'expect' and not $client->{ready})) { push @unavail, $c; } else { push @avail, $c; @@ -276,8 +276,8 @@ sub drv_heartbeat { } sub drv_timeout_expect { - my ($kernel, $session, $client) = @_[KERNEL, SESSION, ARG0]; - print "ERROR: Dropping timed-out expectation by $client->{name}: ".join(',', @{$client->{expect}->[0]})."\n"; + my ($kernel, $session, $client, $heap) = @_[KERNEL, SESSION, ARG0, HEAP]; + print "\nERROR: Dropping timed-out expectation by $client->{name} (line $heap->{expect_lineno}): ".join(',', @{$client->{expect}->[0]})."\n"; $client->{expect_alarms}->[0] = undef; unexpect($kernel, $session, $client); } @@ -370,6 +370,7 @@ sub cmd_wait { sub cmd_expect { my ($kernel, $session, $heap, $client, $args) = @_[KERNEL, SESSION, HEAP, ARG0, ARG1]; die "Missing argument" unless $#$args >= 0; + $heap->{expect_lineno} = $heap->{lineno}; push @{$client->{expect}}, $args; push @{$client->{expect_alarms}}, $kernel->delay_set('timeout_expect', EXPECT_TIMEOUT, $client); $kernel->call($session, 'disable_client', $client); -- 2.20.1