diff -Naurd rancid-2.3.2/bin/Makefile.am rancid-2.3.2_patched/bin/Makefile.am --- rancid-2.3.2/bin/Makefile.am 2009-04-17 00:04:43.000000000 +0200 +++ rancid-2.3.2_patched/bin/Makefile.am 2009-11-16 15:20:03.000000000 +0100 @@ -55,7 +55,7 @@ jerancid jlogin jrancid mrancid mrvlogin mrvrancid nlogin nrancid \ nslogin nsrancid nxrancid par prancid rancid-fe rancid rivlogin \ rivrancid rrancid srancid tlogin tntlogin tntrancid trancid xrancid \ - zrancid + xrrancid zrancid bin_SCRIPTS += lg.cgi lgform.cgi rancid-cvs rancid-run EXTRA_DIST= lg.cgi.in lgform.cgi.in rancid-cvs.in rancid-run.in diff -Naurd rancid-2.3.2/bin/rancid-fe.in rancid-2.3.2_patched/bin/rancid-fe.in --- rancid-2.3.2/bin/rancid-fe.in 2009-04-17 00:05:51.000000000 +0200 +++ rancid-2.3.2_patched/bin/rancid-fe.in 2009-11-16 15:20:03.000000000 +0100 @@ -58,6 +58,7 @@ 'cat5' => 'cat5rancid', 'cisco' => 'rancid', 'cisco-nx' => 'nxrancid', + 'cisco-xr' => 'xrrancid', 'css' => 'cssrancid', 'enterasys' => 'rivrancid', 'erx' => 'jerancid', diff -Naurd rancid-2.3.2/bin/xrrancid.in rancid-2.3.2_patched/bin/xrrancid.in --- rancid-2.3.2/bin/xrrancid.in 1970-01-01 01:00:00.000000000 +0100 +++ rancid-2.3.2_patched/bin/xrrancid.in 2009-11-16 15:20:03.000000000 +0100 @@ -0,0 +1,854 @@ +#! @PERLV_PATH@ +## +## $Id$ +## +## @PACKAGE@ @VERSION@ +## Copyright (c) 1997-2007 by Terrapin Communications, Inc. +## All rights reserved. +## +## This code is derived from software contributed to and maintained by +## Terrapin Communications, Inc. by Henry Kilmer, John Heasley, Andrew Partan, +## Pete Whiting, Austin Schutz, and Andrew Fort. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions +## are met: +## 1. Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in the +## documentation and/or other materials provided with the distribution. +## 3. All advertising materials mentioning features or use of this software +## must display the following acknowledgement: +## This product includes software developed by Terrapin Communications, +## Inc. and its contributors for RANCID. +## 4. Neither the name of Terrapin Communications, Inc. nor the names of its +## contributors may be used to endorse or promote products derived from +## this software without specific prior written permission. +## 5. It is requested that non-binding fixes and modifications be contributed +## back to Terrapin Communications, Inc. +## +## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. AND CONTRIBUTORS +## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS +## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +# +# RANCID - Really Awesome New Cisco confIg Differ +# +# usage: xrrancid [-dV] [-l] [-f filename | hostname] +# +use Getopt::Long; +use English; + +my $log = 0; +my $debug = 0; +my $file = undef; +my $version = 0; +my $filter_commstr = undef; # SNMP community string filtering +my $filter_pwds = undef; # password filtering mode + +GetOptions( + 'debug!' => \$debug, + 'log!' => \$log, + 'file=s' => \$file, + 'version|V!' => \$version, + 'community!' => \$filter_commstr, + 'passwords=s' => \$filter_pwds, +); + +if ($version) { + print "@PACKAGE@ @VERSION@\n"; + exit(0); +} + +$host = $ARGV[0]; +$clean_run = 0; +$found_end = 0; +$timeo = 90; # clogin timeout in seconds + +my $TIMESTAMP = qr{ \A (Mon|Tue|Wed|Thu|Fri|Sat|Sun) \s* + (Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \s* \d+ }xms; + +# straighten up the filtering options +# cli setting takes precedence over env setting + +# determine community string filtering mode +unless (defined $filter_commstr) { + if (defined($ENV{"NOCOMMSTR"}) && + ($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) { + $filter_commstr = 1; + } + else { + $filter_commstr = 0; + } +} + +# determine password filtering mode +if (defined $filter_pwds || defined $ENV{"FILTER_PWDS"}) { + my $str = $filter_pwds || $ENV{"FILTER_PWDS"}; + + if ($str =~ /no/i) { + $filter_pwds = 0; + } + elsif ($str =~ /all/i) { + $filter_pwds = 2; + } + else { + $filter_pwds = 1; + } +} +else { + $filter_pwds = 1; +} + +if ($debug) { + print STDERR "Options:\n"; + print STDERR " file: ", defined $file ? $file : "", "\n"; + print STDERR " host: ", defined $host ? $host : "", "\n"; + print STDERR " community filter is: ", $filter_commstr ? "on" : "off", "\n"; + print STDERR " password filter is : "; + if ($filter_pwds == 0) { + print STDERR "off\n"; + } + elsif ($filter_pwds == 1) { + print STDERR "on (limited)\n"; + } + else { + print STDERR "on (all)\n"; + } +} + + +my(@commandtable, %commands, @commands); # command lists + +# This routine is used to print out the router configuration +sub ProcessHistory { + my($new_hist_tag,$new_command,$command_string,@string) = (@_); + + if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command)) + && defined %history) { + print eval "$command \%history"; + undef %history; + } + if (($new_hist_tag) && ($new_command) && ($command_string)) { + if ($history{$command_string}) { + $history{$command_string} = "$history{$command_string}@string"; + } else { + $history{$command_string} = "@string"; + } + } elsif (($new_hist_tag) && ($new_command)) { + $history{++$#history} = "@string"; + } else { + print "@string"; + } + $hist_tag = $new_hist_tag; + $command = $new_command; + 1; +} + + +sub numerically { $a <=> $b; } + +# This is a sort routine that will sort numerically on the +# keys of a hash as if it were a normal array. +sub keynsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort numerically keys(%lines)) { + $sorted_lines[$i] = $lines{$key}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# keys of a hash as if it were a normal array. +sub keysort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort keys(%lines)) { + $sorted_lines[$i] = $lines{$key}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# values of a hash as if it were a normal array. +sub valsort{ + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort values %lines) { + $sorted_lines[$i] = $key; + $i++; + } + @sorted_lines; +} + +# This is a numerical sort routine (ascending). +sub numsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $num (sort {$a <=> $b} keys %lines) { + $sorted_lines[$i] = $lines{$num}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# ip address when the ip address is anywhere in +# the strings. +sub ipsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $addr (sort sortbyipaddr keys %lines) { + $sorted_lines[$i] = $lines{$addr}; + $i++; + } + @sorted_lines; +} + +# These two routines will sort based upon IP addresses +sub ipaddrval { + my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#); + $a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0])); +} +sub sortbyipaddr { + &ipaddrval($a) <=> &ipaddrval($b); +} + +# This routine parses "show install summary" +sub ShowInstallSummary { + print STDERR " In ShowInstallSummary: $_" if ($debug); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + next if (/^\s*$/); + + ProcessHistory("COMMENTS", "keysort", "A1", "! $_"); + } + ProcessHistory("COMMENTS", "keysort", "A99", "!\n"); + + return(0); +} + + + + +# This routine parses "show redundancy" +sub ShowRedundancy { + print STDERR " In ShowRedundancy: $_" if ($debug); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + + /^Node\s+(\S+).*ACTIVE/ && do { + ProcessHistory("COMMENTS", "keysort", "B1", "! Node $1 is Active RP\n"); + next; + }; + + /^Partner node \((.*)\).*STANDBY/ && do { + ProcessHistory("COMMENTS", "keysort", "B2", "! Node $1 is Standby RP\n"); + next; + }; + + /^Standby node.*is (.*)/ && do { + ProcessHistory("COMMENTS", "keysort", "B3", "! Standby RP is $1\n"); + next; + }; + } + ProcessHistory("COMMENTS", "keysort", "B99", "!\n"); + + return(0); +} + + +# This routine parses "dir /all ((disk|slot)N|bootflash|nvram):" +sub DirSlotN { + print STDERR " In DirSlotN: $_" if ($debug); + + my($dev) = (/\s([^\s]+):/); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + next if (/^\s*$/); + + return(1) if /^\s*\^\s*$/; + return(1) if /Line has invalid autocommand /; + return(1) if /(Invalid input detected|Type help or )/; + return(1) if /(No such device|Error Sending Request)/i; + return(1) if /\%Error: No such file or directory/; + return(1) if /No space information available/; + return(-1) if /\%Error calling/; + return(-1) if /(: device being squeezed|ATA_Status time out)/i; # busy + return(-1) if (/command authorization failed/i); + return(1) if /(Open device \S+ failed|Error opening \S+:)/; + + if (/.*\((\d+) bytes free\)/) { + my($tmp) = int($1 / (1024 * 1024)); + s/$1 bytes free/$tmp MB free/; + } + ProcessHistory("COMMENTS", "keysort", "C1", "! $dev: $_"); + } + + ProcessHistory("COMMENTS", "keysort", "C1", "!\n"); + return(0); +} + + + +# This routine parses "show platform" +# This will create arrays for hw info. +sub ShowPlatform { + print STDERR " In ShowPlatform: $_" if ($debug); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + next if (/^\s*$/); + + ProcessHistory("COMMENTS", "keysort", "D4", "! $_"); + } + ProcessHistory("COMMENTS", "keysort", "D4", "!\n"); + + return(0); +} + + + +# This routine parses "show version" +sub ShowVersion { + print STDERR " In ShowVersion: $_" if ($debug); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + next if (/^\s*$/); + + /(\S+)\s+(?:\((\S+)\)\s+processor|\(revision[^)]+\)).*\s+with (\S+k) bytes/i && do { + my $model = $1; + my $cpu = $2 || ""; + my $mem = $3; + + # the next line ought to be the more specific cpu info, grab it. + $_ = ; + if (/^$cpu processor at/i) { + $cpu = $_; + chomp($cpu); + } + + ProcessHistory("COMMENTS", "keysort", "D1", "! Chassis: $model\n"); + ProcessHistory("COMMENTS", "keysort", "D2", "! Memory: main $mem\n"); + if ($cpu) { + ProcessHistory("COMMENTS", "keysort", "D1", "! CPU: $cpu\n"); + } + next; + }; + + /^(\d+[kKmM]) bytes of non-volatile/ && do { + ProcessHistory("COMMENTS", "keysort", "D3", "! Memory: nvram $1\n"); + next; + }; + + /^(\d+[kKmM]) bytes of (?:compact )*flash/ && do { + ProcessHistory("COMMENTS", "keysort", "D3", "! Memory: flash $1\n"); + next; + }; + + /^(\d+[kKmM]) bytes of Flash internal/ && do { + ProcessHistory("COMMENTS", "keysort", "D3", "! Memory: bootflash $1\n"); + next; + }; + + /^(\d+[kKmM]) bytes of hard disk/ && do { + ProcessHistory("COMMENTS", "keysort", "D3", "! Memory: hard disk $1\n"); + next; + }; + + /^(\d+[kKmM]) bytes of ATA PCMCIA card at disk (\d+)/i && do { + ProcessHistory("COMMENTS", "keysort", "D3", "! Memory: pcmcia disk$2 $1\n"); + next; + }; + } + ProcessHistory("COMMENTS", "keysort", "D3", "!\n"); + + return(0); +} + + +# This routine parses "show inventory". +sub ShowInventory { + print STDERR " In ShowInventory: $_" if ($debug); + + my $key = "D6"; + + while () { + tr/\015//d; + + return if (/^\s*\^$/); + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if /\s*$cmd\s*$/; + next if (/^\s*$/); + + if (/^NAME: ([^,]+), DESCR: (.*)/) { + my $slot = $1; + my $desc = $2; + $key = ($slot =~ /Chassi/i ? "D5" : "D6"); + ProcessHistory("COMMENTS", "keysort", $key, "! SLOT: $slot\n"); + ProcessHistory("COMMENTS", "keysort", $key, "! DESC: $desc\n"); + next; + } + + # split PID/VID/SN line + if (/^PID: (\S*)\s*, VID: (\S*)\s*, SN: (\S*)\s*$/) { + my $entries = ""; + $entries .= "! PID : $1\n" if ($1); + $entries .= "! VID : $2\n" if ($2 && $2 !~ m!N/A!i); + $entries .= "! SN : $3\n" if ($3); + ProcessHistory("COMMENTS", "keysort", $key, "$entries!\n"); + next; + } + } + ProcessHistory("COMMENTS", "keysort", "D6", "!\n"); + + return(0); +} + + +# This routine parses "show debug" +sub ShowDebug { + print STDERR " In ShowDebug: $_" if ($debug); + + my $lines = 0; + + while () { + tr/\015//d; + last if /^$prompt/; + next if /$TIMESTAMP/; + next if /\s*$cmd\s*$/; + next if (/^\s*$/); + + ProcessHistory("DEBUG", "keysort", "F1", "! $_"); + $lines++; + } + + if ($lines) { + ProcessHistory("DEBUG", "keysort", "F", "!\n! DEBUG\n"); + } + + return(0); +} + + + +# This routine processes a "write term" +sub WriteTerm { + print STDERR " In WriteTerm: $_" if ($debug); + + my $mode = ''; + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + + /Non-Volatile memory is in use/ && return(-1); # NvRAM is locked + + # skip some initial crap + next if /^\s*Building configuration/; + next if /^!+\s+Last configuration/; + next if /^!*\s*IOS XR Configuration/; + + # username/secret command + /^(\s*secret\s*)/ && do { + if ($filter_pwds >= 2) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + # username/password and bgp-ne/password commands + /^(\s*password(?:\s+encrypted)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + # system wide tacacs-key + /^(\s*tacacs-server key)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + # per radius/tacacs-server key + /^(\s*key)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + + # services passwords + /^(\s*ftp client (?:anonymous-)*password (?:encrypted\s+)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + /^(\s*sftp-password (clear|password)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + /(\s*autentication-key \d+ md5 (?:encrypted\s+)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + + # protocols autentication + /^(\s*hsrp \d+ autentication)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1" . " \n"); + next; + } + }; + + /^(\s*vrrp \d+ text-autentication)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + /^(\s*ppp (?:ms-)chap password (?:encrypted\s+)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + /^(\s*ppp pap sent-username \S+ password (?:encrypted\s+)*)/ && do { + if ($filter_pwds >= 1) { + ProcessHistory("", "", "", "!$1\n"); + next; + } + }; + + /^(\s*neighbor.*password (?:encrypted\s+)*)/ && do { + if ($filter_pwd >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + # matches "lsp-password" and "lsp-password accept" commands + /^(\s*lsp-password.*(?:encrypted)*\s+)\S+(.*)/ && do { + if ($filter_pwd >= 1) { + ProcessHistory("", "", "", "!$1 $2\n"); + next; + } + }; + + /^(\s*hello-password.*(?:encrypted)*\s+)\S+(.*)/ && do { + if ($filter_pwd >= 1) { + ProcessHistory("", "", "", "!$1 $2\n"); + next; + } + }; + + /^(\s*authentication-key.*(?:encrypted\s+)*)/ && do { + if ($filter_pwd >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + + /(\s*pre-shared-key.*key)/ && do { + if ($filter_pwd >= 1) { + ProcessHistory("", "", "", "!$1 \n"); + next; + } + }; + + + /^\s*snmp-server host [\d\.]+/ && do { + if ($filter_commstr) { + my($line) = $MATCH; + my(@tokens) = split(' ', $POSTMATCH); + my($token); + while ($token = shift(@tokens)) { + if ($token eq 'version') { + my $ver = shift(@tokens); + $line .= " version $ver"; + if ($ver eq '3') { + $line .= " " . shift(@tokens); + } + } + + elsif ($token =~ /clear|encrypted|traps/) { + $line .= " $token"; + } + + elsif ($token =~ /^\s*$/) { + # do nothing on empty/all white-space strings + } + + else { + # ok the rest are { other options } + # get rid of community + shift(@tokens); + + $line .= " " . join(' ', "", @tokens); + last; + } + } + ProcessHistory("", "", "", "!$line\n"); + } else { + ProcessHistory("", "", "", "$_"); + } + next; + }; + + /^(snmp-server community (?:clear|encrypted)*)\s*(\S+)/ && do { + if ($filter_commstr) { + ProcessHistory("", "", "", "!$1$POSTMATCH"); + next; + } + else { + ProcessHistory("", "", "", "$_"); + next; + } + }; + + /^end$/ && do { + $found_end = 1; + last; + }; + + # catch anything that wasn't matched above. + ProcessHistory("", "", "", "$_"); + + } + + ProcessHistory("", "", "", "$_"); + return 0; +} + +# dummy function +sub DoNothing { + print STDERR " In DoNothing: $_" if ($debug); + + while () { + tr/\015//d; + last if (/^$prompt/); + next if /$TIMESTAMP/; + next if (/\s*$cmd\s*$/); + next if (/^\s*$/); + } + + return 0; +} + +# Main +@commandtable = +( + # Disable timestamps in output + {'terminal no-timestamp' => 'DoNothing' }, # XR 3.6 style + {'terminal exec prompt no-timestamp' => 'DoNothing' }, # XR 3.8 style + + # XR IMAGE commands + {'show install summary' => 'ShowInstallSummary'}, + + # REDUNDANCY commands + {'show redundancy' => 'ShowRedundancy'}, + + # FILESYSTEMS comamnds + {'dir /all bootflash:' => 'DirSlotN'}, + {'dir /all disk0:' => 'DirSlotN'}, + {'dir /all disk0a:' => 'DirSlotN'}, + {'dir /all disk1:' => 'DirSlotN'}, + {'dir /all disk1a:' => 'DirSlotN'}, + {'dir /all compactflash:' => 'DirSlotN'}, + {'dir /all compactflasha:' => 'DirSlotN'}, + {'dir /all harddisk:' => 'DirSlotN'}, + {'dir /all harddiska:' => 'DirSlotN'}, + {'dir /all harddiskb:' => 'DirSlotN'}, + + # HARDWARE commands + {'show version' => 'ShowVersion'}, + {'show platform' => 'ShowPlatform'}, + {'admin' => 'DoNothing'}, + {'show inventory' => 'ShowInventory'}, + {'exit' => 'DoNothing'}, + + # DEBUG commands + {'show debug' => 'ShowDebug'}, + + # RUNNING-CONFIG commands + {'show running-config' => 'WriteTerm'}, +); + +# Use an array to preserve the order of the commands and a hash for mapping +# commands to the subroutine and track commands that have been completed. +@commands = map(keys(%$_), @commandtable); +%commands = map(%$_, @commandtable); + +$cisco_cmds = join(";", @commands); +$cmds_regexp = join("|", @commands); + +unless ($host) { + unless ($file) { + print(STDERR "Too few arguments: file or host name required\n"); + exit(1); + } + else { + $host = $file; + } +} + +open(OUTPUT, "> $host.new") + or die "Can't open $host.new for writing: $!\n"; +select(OUTPUT); + +# make OUTPUT unbuffered if debugging +if ($debug) { + $| = 1; +} + +if ($file) { + print STDERR "opening file $host\n" if ($debug); + print STDOUT "opening file $host\n" if ($log); + open(INPUT,"< $host") + or die "open failed for $host: $!\n"; +} else { + print STDERR "executing clogin -autoenable -t $timeo -c\"$cisco_cmds\" $host\n" if ($debug); + print STDOUT "executing clogin -autoenable -t $timeo -c\"$cisco_cmds\" $host\n" if ($log); + if (defined($ENV{NOPIPE})) { + system "clogin -autoenable -t $timeo -c \"$cisco_cmds\" $host $host.raw 2>&1" + or die "clogin failed for $host: $!\n"; + open(INPUT, "< $host.raw") + or die "clogin failed for $host: $!\n"; + } else { + open(INPUT,"clogin -autoenable -t $timeo -c \"$cisco_cmds\" $host ) { + tr/\015//d; + + # only match exit from exec mode, i.e NOT from admin mode + if (/(?