[rancid] aerohive/hiveos support

Nick Nauwelaerts <[email protected]> Wed, 19 Dec 2018 21:23:57 +0000
Newsgroups gmane.network.rancid
Message-ID <[email protected]>
(also included: removal of fortinet/fnrancid references since it's now a module & a few minor nitpicks)

support for aerohive/hiveos access points:

login script ahlogin:
* originally by erik muller: https://github.com/ermuller/rancid-stuff.
 * should be up to date with the latest changes of svn 3937.
 * saving on changed config works, -x & -c have also been tested.
* .cvsignore entry added

module hiveos.pm:
* respects nocommstr; filter_pwds & filter_osc
* tested against multiple hardware options & operating system versions
* does not sort acls/snmp yet
* does not yet handle all passwords/secrets - it does filter the items we use with respect to filter_* settings
* also fetches additional info depending on filter_* settings
* no clear end of run marker
* rancid.types.base entries

other:
* docs, makefiles & config scripts have been updated completely i hope.
* hiveos.pm.in has a lot of comments, they can be deleted if they add little/no value.

has been in use for almost 6 months. perhaps naming the login script ahlogin (aerohive login) and the module hiveos.pm seems strange, just rename if you don't like it.



git code here but git & me just dont get along:
https://github.com/inphobia/rancid-aerohive-support

// nick

________________________________

Volg Aquafin op Facebook<https://www.facebook.com/AquafinNV> | Twitter<https://twitter.com/aquafinnv> | YouTube<http://www.youtube.com/channel/UCk_4P5BJ-MtEEDCkCsR_KqQ?feature=mhee> | LinkedIN<http://www.linkedin.com/company/aquafin/products>

In het kader van de uitoefening van onze taken verzamelen we bij Aquafin persoonsgegevens. Hoe we omgaan met deze gegevens en wat de rechten van de betrokkenen zijn, kan je nalezen in onze privacy policy<https://www.aquafin.be/nl-be/privacy-policy>.

  P Denk aan het milieu. Druk deze mail niet onnodig af.

_______________________________________________
Rancid-discuss mailing list
[email protected]
http://www.shrubbery.net/mailman/listinfo/rancid-discuss
ah.diff (application/octet-stream, 55.5 KB)
diff -rNau ./bin/ahlogin.in /home/testranc/src/rancid-aerohive-support/./bin/ahlogin.in
--- ./bin/ahlogin.in	1970-01-01 01:00:00.000000000 +0100
+++ /home/testranc/src/rancid-aerohive-support/./bin/ahlogin.in	2018-11-07 02:06:44.613958426 +0100
@@ -0,0 +1,1046 @@
+#! @EXPECT_PATH@ --
+##
+## $Id$
+##
+## @PACKAGE@ @VERSION@
+@copyright@
+# 
+# ahlogin - Aerohive hiveos login
+#
+# Derived from cisco clogin to support Aerohive HiveOS devices by Erik Muller <[email protected]>
+# https://github.com/ermuller/rancid-stuff
+# then adapted by [email protected]
+#
+
+# XXX need to import login_top.
+
+# Usage line
+set usage "Usage: $argv0 \[-diSV\] \[-autoenable\] \[-noenable\] \
+\[-c command\] \[-Evar=x\] \[-e enable-password\] \[-f cloginrc-file\] \
+\[-p user-password\] \[-s script-file\] \[-t timeout\] \[-u username\] \
+\[-v vty-password\] \[-w enable-username\] \[-x command-file\] \
+\[-y ssh_cypher_type\] router \[router...\]\n"
+
+# env(CLOGIN) may contain:
+#	x == do not set xterm banner or name
+
+# Password file
+set password_file $env(HOME)/.cloginrc
+# Default is to login to the access point
+set do_command 0
+set do_interact 0
+set do_script 0
+# The default is to automatically enable
+set avenable 1
+# The default is that you login non-enabled (tacacs can have you login already
+# enabled)
+set avautoenable 0
+# The default is to look in the password file to find the passwords.  This
+# tracks if we receive them on the command line.
+set do_passwd 1
+set do_enapasswd 1
+# Save config, if prompted
+set do_saveconfig 0
+# cloginrc debugging knob
+set do_cloginrcdbg 0
+# Sometimes routers take awhile to answer (the default is 10 sec)
+set timeoutdflt 45
+# intialize cloginrc parsing stacks
+set int_file {}
+set int_lineno {}
+#
+set send_human {.2 .1 .4 .2 1}
+# new option to provide "login" command capabilities
+set loginonly 0
+
+# Find the user in the ENV, or use the unix userid.
+if {[info exists env(CISCO_USER)]} {
+    set default_user $env(CISCO_USER)
+} elseif {[info exists env(USER)]} {
+    set default_user $env(USER)
+} elseif {[info exists env(LOGNAME)]} {
+    set default_user $env(LOGNAME)
+} else {
+    # This uses "id" which I think is portable.  At least it has existed
+    # (without options) on all machines/OSes I've been on recently -
+    # unlike whoami or id -nu.
+    if [catch {exec id} reason] {
+	send_error "\nError: could not exec id: $reason\n"
+	exit 1
+    }
+    regexp {\(([^)]*)} "$reason" junk default_user
+}
+if {[info exists env(CLOGINRC)]} {
+    set password_file $env(CLOGINRC)
+}
+
+# Process the command line
+for {set i 0} {$i < $argc} {incr i} {
+    set arg [lindex $argv $i]
+
+    switch  -glob -- $arg {
+	# Expect debug mode
+	-d* {
+	    exp_internal 1
+	# Username
+	} -u* {
+	    if {! [regexp .\[uU\](.+) $arg ignore user]} {
+		incr i
+		set username [lindex $argv $i]
+	    }
+	# cloginrc debugging knobs
+	} -m* {
+	    set do_cloginrcdbg 1
+	} -M* {
+	    set do_cloginrcdbg 2
+	# interactive
+	} -i* {
+	    set do_interact 1
+	# VTY Password
+	} -p* {
+	    if {! [regexp .\[pP\](.+) $arg ignore userpasswd]} {
+		incr i
+		set userpasswd [lindex $argv $i]
+	    }
+	    set do_passwd 0
+	# ssh passphrase
+	} -r* {
+	    if {! [regexp .\[rR\](.+) $arg ignore passphrase]} {
+		incr i
+		set vapassphrase [lindex $argv $i]
+	    }
+	# VTY Password
+	} -v* {
+	    if {! [regexp .\[vV\](.+) $arg ignore passwd]} {
+		incr i
+		set passwd [lindex $argv $i]
+	    }
+	    set do_passwd 0
+	# Version string
+	} -V* {
+	    send_user "@PACKAGE@ @VERSION@\n"
+	    exit 0
+	# Enable Username
+	} -w* {
+	    if {! [regexp .\[wW\](.+) $arg ignore enauser]} {
+		incr i
+		set enausername [lindex $argv $i]
+	    }
+	# Environment variable to pass to -s scripts
+	} -E* {
+	    if {[regexp .\[E\](.+)=(.+) $arg ignore varname varvalue]} {
+		set E$varname $varvalue
+	    } else {
+		send_user "\nError: invalid format for -E in $arg\n"
+		exit 1
+	    }
+	# Enable Password
+	} -e* {
+	    if {! [regexp .\[e\](.+) $arg ignore enapasswd]} {
+		incr i
+		set enapasswd [lindex $argv $i]
+	    }
+	    set do_enapasswd 0
+	# Command to run.
+	} -c* {
+	    if {! [regexp .\[cC\](.+) $arg ignore command]} {
+		incr i
+		set command [lindex $argv $i]
+	    }
+	    set do_command 1
+	# Expect script to run.
+	} -s* {
+	    if {! [regexp .\[sS\](.+) $arg ignore sfile]} {
+		incr i
+		set sfile [lindex $argv $i]
+	    }
+	    if { ! [file readable $sfile] } {
+		send_user "\nError: Can't read $sfile\n"
+		exit 1
+	    }
+	    set do_script 1
+	# save config on exit
+	} -S* {
+	    set do_saveconfig 1
+	# 'ssh -c' cypher type
+	} -y* {
+	    if {! [regexp .\[eE\](.+) $arg ignore cypher]} {
+		incr i
+		set cypher [lindex $argv $i]
+	    }
+	# alternate cloginrc file
+	} -f* {
+	    if {! [regexp .\[fF\](.+) $arg ignore password_file]} {
+		incr i
+		set password_file [lindex $argv $i]
+	    }
+	# Timeout
+	} -t* {
+	    if {! [regexp .\[tT\](.+) $arg ignore timeout]} {
+		incr i
+	        set timeoutdflt [lindex $argv $i]
+	    }
+	# Command file
+	} -x* {
+	    if {! [regexp .\[xX\](.+) $arg ignore cmd_file]} {
+		incr i
+		set cmd_file [lindex $argv $i]
+	    }
+	    if [catch {set cmd_fd [open $cmd_file r]} reason] {
+		send_user "\nError: $reason\n"
+		exit 1
+	    }
+	    set cmd_text [read $cmd_fd]
+	    close $cmd_fd
+	    set command [join [split $cmd_text \n] \;]
+	    set do_command 1
+	# Do we enable?
+	} -noenable {
+	    set avenable 0
+	# Does tacacs automatically enable us?
+	} -autoenable {
+	    set avautoenable 1
+	    set avenable 0
+	} -* {
+	    send_user "\nError: Unknown argument! $arg\n"
+	    send_user $usage
+	    exit 1
+	} default {
+	    break
+	}
+    }
+}
+# Process routers...no routers listed is an error.
+if { $i == $argc } {
+    send_user "\nError: $usage"
+}
+
+# Only be quiet if we are running a script (it can log its output
+# on its own)
+if { $do_script } {
+    log_user 0
+} else {
+    log_user 1
+}
+
+#
+# Done configuration/variable setting.  Now run with it...
+#
+
+# Sets Xterm title if interactive...if its an xterm and the user cares
+proc label {host} {
+    global env
+    # if CLOGIN has an 'x' in it, don't set the xterm name/banner
+    if [info exists env(CLOGIN)] {
+	if {[string first "x" $env(CLOGIN)] != -1} { return }
+    }
+    # take host from ENV(TERM)
+    if [info exists env(TERM)] {
+	if [regexp \^(xterm|vs) $env(TERM) ignore] {
+	    send_user "\033]1;[lindex [split $host "."] 0]\a"
+	    send_user "\033]2;$host\a"
+	}
+    }
+}
+
+# This is a helper function to make the password file easier to
+# maintain.  Using this the password file has the form:
+# add password sl*	pete cow
+# add password at*	steve
+# add password *	hanky-pie
+proc add {var args} {
+    global int_file int_lineno int_$var
+    set file [lindex $int_file 0]
+    set lineno [lindex $int_lineno 0]
+    lappend int_$var "$var:$file:$lineno: $args"
+}
+proc include {args} {
+    global env
+    regsub -all "(^{|}$)" $args {} args
+    if {[regexp "^/" $args ignore] == 0} {
+	set args $env(HOME)/$args
+    }
+    source_password_file $args
+}
+
+proc find {var router} {
+    global do_cloginrcdbg
+    upvar int_$var list
+    if {[info exists list]} {
+	foreach line $list {
+	    if {[string match -nocase [lindex $line 1] $router]} {
+		if {$do_cloginrcdbg > 0} {
+		    send_error -- [join [list [lindex $line 0] [lrange $line 1 end] "\r\n"]]
+		}
+		if {$do_cloginrcdbg == 2} {
+		    # save return value
+		    if {! [info exists result]} {
+			set result [lrange $line 2 end]
+		    }
+		} else {
+		    return [lrange $line 2 end]
+		}
+	    }
+	}
+    }
+
+    if {$do_cloginrcdbg == 2} {
+	if {[info exists result]} {
+	    return $result
+	}
+    }
+    return {}
+}
+
+# Loads the password file.  Note that as this file is tcl, and that
+# it is sourced, the user better know what to put in there, as it
+# could install more than just password info...  I will assume however,
+# that a "bad guy" could just as easy put such code in the clogin
+# script, so I will leave .cloginrc as just an extention of that script
+proc source_password_file {file} {
+    global env int_file int_lineno
+    if {! [file exists $file]} {
+	send_user "\nError: password file ($file) does not exist\n"
+	exit 1
+    }
+    file stat $file fileinfo
+    if {[expr ($fileinfo(mode) & 007)] != 0000} {
+	send_user "\nError: $file must not be world readable/writable\n"
+	exit 1
+    }
+    if [catch {set fd [open $file "r"]} reason] {
+	send_user "\nError: $reason\n"
+	exit 1
+    }
+    set int_file [linsert $int_file 0 $file]
+    set int_lineno [linsert $int_lineno 0 0]
+    while {[gets $fd line] >= 0} {
+	set tmp [lindex $int_lineno 0]; incr tmp
+	lset int_lineno 0 $tmp
+	eval $line
+    }
+    set int_file [lrange $int_file 1 end]
+    set int_lineno [lrange $int_lineno 1 end]
+    close $fd
+}
+
+# Log into the router.
+# returns: 0 on success, 1 on failure, -1 if rsh was used successfully
+proc login { router user userpswd passwd enapasswd cmethod cyphertype identfile } {
+    global command spawn_id in_proc do_command do_script platform passphrase
+    global prompt prompt_match u_prompt p_prompt e_prompt sshcmd
+    set in_proc 1
+    set uprompt_seen 0
+
+    # try each of the connection methods in $cmethod until one is successful
+    set progs [llength $cmethod]
+    foreach prog [lrange $cmethod 0 end] {
+	incr progs -1
+	if [string match "telnet*" $prog] {
+	    regexp {telnet(:([^[:space:]]+))*} $prog methcmd suffix port
+	    if {"$port" == ""} {
+		set retval [catch {spawn telnet $router} reason]
+	    } else {
+		set retval [catch {spawn telnet $router $port} reason]
+	    }
+	    if { $retval } {
+		send_user "\nError: telnet failed: $reason\n"
+		return 1
+	    }
+	} elseif [string match "ssh*" $prog] {
+	    # ssh to the router & try to login with or without an identfile.
+	    regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port
+	    set cmd $sshcmd
+	    if {"$port" != ""} {
+		set cmd "$cmd -p $port"
+	    }
+	    if {"$cyphertype" != ""} {
+		set cmd "$cmd -c $cyphertype"
+	    }
+	    if {"$identfile" != ""} {
+		set cmd "$cmd -i $identfile"
+	    }
+	    set retval [catch {eval spawn [split "$cmd -x -l $user $router" { }]} reason]
+	    if { $retval } {
+		send_user "\nError: $cmd failed: $reason\n"
+		return 1
+	    }
+	} elseif ![string compare $prog "rsh"] {
+	    if { ! $do_command } {
+		if { [llength $cmethod] == 1 } {
+		    send_user "\nError: rsh is an invalid method for -x and "
+		    send_user "interactive logins\n"
+		}
+		if { $progs == 0 } {
+		    return 1
+		}
+		continue;
+	    }
+
+	    # handle escaped ;s in commands, and ;; and ^;
+	    regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
+	    regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
+	    regsub {^;} $command "\u0002;" esccommand
+	    regsub -all {[\\];} $esccommand ";" command
+	    regsub -all {\u0002;} $command "\u0002" esccommand
+	    set sep "\u0002"
+	    set commands [split $esccommand $sep]
+	    set num_commands [llength $commands]
+	    set rshfail 0
+	    for {set i 0} {$i < $num_commands && !$rshfail} { incr i} {
+		log_user 0
+		set retval [catch {spawn rsh $user@$router [lindex $commands $i] } reason]
+		if { $retval } {
+		    send_user "\nError: rsh failed: $reason\n"
+		    log_user 1; return 1
+		}
+		send_user "$router# [lindex $commands $i]\n"
+
+		# rcmd does not get a pager and no prompts, so we just have to
+		# look for failures & lines.
+		expect {
+		  "Connection refused"	{ catch {close}; catch {wait};
+					  send_user "\nError: Connection\
+						    Refused ($prog): $router\n"
+					  set rshfail 1
+					}
+		  -re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
+					  catch {close}; catch {wait};
+					  send_user "\nError: Connection\
+						    closed ($prog): $router\n"
+					  set rshfail 1
+					}
+		  "Host is unreachable"	{ catch {close}; catch {wait};
+					  send_user "\nError: Host Unreachable:\
+						    $router\n"
+					  set rshfail 1
+					}
+		  "No address associated with" {
+					  catch {close}; catch {wait};
+					  send_user "\nError: Unknown host\
+						    $router\n"
+					  set rshfail 1
+					}
+		  -re "\b+"		{ exp_continue }
+		  -re "\[\n\r]+"	{ send_user -- "$expect_out(buffer)"
+					  exp_continue
+					}
+		  timeout		{ catch {close}; catch {wait};
+					  send_user "\nError: TIMEOUT reached\n"
+					  set rshfail 1
+					}
+		  eof			{ catch {close}; catch {wait}; }
+		}
+		log_user 1
+	    }
+	    if { $rshfail } {
+		if { !$progs } {
+		    return 1
+		} else {
+		    continue
+		}
+	    }
+	    # fake the end of the session for rancid.
+	    send_user "$router# exit\n"
+	    # return rsh "success"
+	    return -1
+	} else {
+	    send_user "\nError: unknown connection method: $prog\n"
+	    return 1
+	}
+	sleep 0.3
+
+	# This helps cleanup each expect clause.
+	expect_after {
+	    timeout {
+		global in_proc
+		send_user "\nError: TIMEOUT reached\n"
+		catch {close}; catch {wait};
+		if {$in_proc} {
+		    return 1
+		} else {
+		    continue
+		}
+	    } eof {
+		global in_proc
+		send_user "\nError: EOF received\n"
+		catch {close}; catch {wait};
+		if {$in_proc} {
+		    return 1
+		} else {
+		    continue
+		}
+	    }
+	}
+
+    # Here we get a little tricky.  There are several possibilities:
+    # the router can ask for a username and passwd and then
+    # talk to the TACACS server to authenticate you, or if the
+    # TACACS server is not working, then it will use the enable
+    # passwd.  Or, the router might not have TACACS turned on,
+    # then it will just send the passwd.
+    # if telnet fails with connection refused, try ssh
+    expect {
+	-re "^<-+ More -+>\[^\n\r]*" {
+	    # ASA will use the pager for long banners
+	    send " ";
+	    exp_continue
+	}
+	-re "(Connection refused|Secure connection \[^\n\r]+ refused)" {
+	    catch {close}; catch {wait};
+	    if !$progs {
+		send_user "\nError: Connection Refused ($prog): $router\n"
+		return 1
+	    }
+	}
+	-re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
+	    catch {close}; catch {wait};
+	    if !$progs {
+		send_user "\nError: Connection closed ($prog): $router\n"
+		return 1
+	    }
+	}
+	eof { send_user "\nError: Couldn't login: $router\n"; wait; return 1 }
+	-nocase "unknown host\r" {
+	    send_user "\nError: Unknown host $router\n";
+	    catch {close}; catch {wait};
+	    return 1
+	}
+	"Host is unreachable" {
+	    send_user "\nError: Host Unreachable: $router\n";
+	    catch {close}; catch {wait};
+	    return 1
+	}
+	"No address associated with name" {
+	    send_user "\nError: Unknown host $router\n";
+	    catch {close}; catch {wait};
+	    return 1
+	}
+	-re "(Host key not found |The authenticity of host .* be established)" {
+	    expect {
+		-re "\\(yes\/no\\)\\?"	{ send "yes\r";
+					  send_user "\nHost $router added to the list of known hosts.\n"
+					 }
+		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
+	    }
+	    exp_continue
+	}
+	-re "HOST IDENTIFICATION HAS CHANGED" {
+	    send_user "\nError: The host key for $router has changed.  Update the SSH known_hosts file accordingly.\n"
+	    expect {
+		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
+		-re " strict checking\.\[\r\n]+" { }
+		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
+	    }
+	    catch {close}; catch {wait};
+	    return 1
+	}
+	-re "Offending key for " {
+	    send_user "\nError: host key mismatch for $router.  Update the SSH known_hosts file accordingly.\n"
+	    expect {
+		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
+		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
+	    }
+	    catch {close}; catch {wait};
+	    return 1
+	}
+	-nocase -re "^warning: remote host denied authentication agent forwarding." {
+	    exp_continue;
+	}
+	-re "(denied|Sorry)"	{
+				  send_user "\nError: Check your passwd for $router\n"
+				  catch {close}; catch {wait}; return 1
+				}
+	"Login failed"		{
+				  send_user "\nError: Check your passwd for $router\n"
+				  catch {close}; catch {wait}; return 1
+				}
+	-re "% (Bad passwords|Authentication failed)"	{
+				  send_user "\nError: Check your passwd for $router\n"
+				  catch {close}; catch {wait}; return 1
+				}
+	"Press any key to continue" {
+				  # send_user "Pressing the ANY key\n"
+				  send "\r"
+				  exp_continue
+				}
+	-re "Enter Selection: " {
+				  # Catalyst 1900s have some lame menu.  Enter
+				  # K to reach a command-line.
+				  send "K\r"
+				  exp_continue
+				}
+	-re "Last login:"	{
+				  exp_continue
+				}
+	-re "@\[^\r\n]+ $p_prompt"	{
+				  # ssh pwd prompt
+				  sleep 1
+				  send -- "$userpswd\r"
+				  exp_continue
+				}
+	-re "Enter passphrase.*: " {
+				  # sleep briefly to allow time for stty -echo
+				  sleep .3
+				  send -- "$passphrase\r"
+				  exp_continue
+				}
+	-re "$u_prompt"		{
+				  send -- "$user\r"
+				  set uprompt_seen 1
+				  exp_continue
+				}
+	-re "$p_prompt"		{
+				  sleep 1
+				  if {$uprompt_seen == 1} {
+					send -- "$userpswd\r"
+				  } else {
+					send -- "$passwd\r"
+				  }
+				  exp_continue
+				}
+	-re "$prompt"		{
+				  set prompt_match $expect_out(0,string);
+				  break;
+				}
+	"Login invalid"		{
+				  send_user "\nError: Invalid login: $router\n";
+				  catch {close}; catch {wait}; return 1
+				}
+     }
+    }
+
+    set in_proc 0
+    return 0
+}
+
+# New subroutine to provide "login" command capabilities, using the enable user and enable password
+# Login
+proc do_login { enauser enapasswd } {
+    global prompt in_proc
+    global u_prompt e_prompt
+    set in_proc 1
+
+    send "login\r"
+    expect {
+       -re "$u_prompt" { send "$enauser\r"; exp_continue}
+       -re "$e_prompt" { send "$enapasswd\r"; exp_continue}
+       "#"             { set prompt "#" }
+       "(login)"       { set prompt "> (login) " }
+       -re "(denied|Sorry|Incorrect)"  {
+                         # % Access denied - from local auth and poss. others
+                         send_user "\nError: Check your Login passwd\n";
+                         return 1
+                       }
+       "% Error in authentication" {
+                         send_user "\nError: Check your Login passwd\n"
+                         return 1
+                       }
+       "% Bad passwords" {
+                         send_user "\nError: Check your Login passwd\n"
+                         return 1
+                       }
+    }
+    # We set the prompt variable (above) so script files don't need
+    # to know what it is.
+    set in_proc 0
+    return 0
+}
+
+# Enable
+proc do_enable { enauser enapasswd } {
+    global do_saveconfig in_proc
+    global prompt u_prompt e_prompt
+    set in_proc 1
+
+    send "enable\r"
+    expect {
+	-re "$u_prompt"	{ send -- "$enauser\r"; exp_continue}
+	-re "$e_prompt"	{ send -- "$enapasswd\r"; exp_continue}
+	"#"		{ set prompt "#" }
+	"(enable)"	{ set prompt "> \\(enable\\) " }
+	-re "(denied|Sorry|Incorrect)"	{
+			  # % Access denied - from local auth and poss. others
+			  send_user "\nError: Check your Enable passwd\n";
+			  return 1
+			}
+	"% Error in authentication" {
+			  send_user "\nError: Check your Enable passwd\n"
+			  return 1
+			}
+	"% Bad passwords" {
+			  send_user "\nError: Check your Enable passwd\n"
+			  return 1
+			}
+    }
+    # We set the prompt variable (above) so script files don't need
+    # to know what it is.
+    set in_proc 0
+    return 0
+}
+
+# Run commands given on the command line.
+proc run_commands { prompt command } {
+    global do_interact do_saveconfig in_proc platform
+    set in_proc 1
+
+    # If the prompt is (enable), then we are on a switch and the
+    # command is "set length 0"; otherwise its "terminal length 0".
+    # skip if its an extreme (since the pager can not be disabled on a
+    # per-vty basis).
+    if { [string compare "extreme" "$platform"] } {
+	# match cisco config mode prompts too, such as router(config-if)#,
+	# but catalyst does not change in this fashion.
+	regsub -all {^(.{1,11}).*([#>])$} $prompt {\1([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?} reprompt
+    } else {
+	set reprompt $prompt
+    }
+
+    # this is the only way i see to get rid of more prompts in o/p..grrrrr
+    log_user 0
+
+    # handle escaped ;s in commands, and ;; and ^;
+    regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
+    regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
+    regsub {^;} $command "\u0002;" esccommand
+    regsub -all {[\\];} $esccommand ";" command
+    regsub -all {\u0002;} $command "\u0002" esccommand
+    set sep "\u0002"
+    set commands [split $esccommand $sep]
+    set num_commands [llength $commands]
+    # the pager can not be turned off on the PIX, so we have to look
+    # for the "More" prompt.  the extreme is equally obnoxious in pre-12.3 XOS,
+    # with a global switch in the config.
+    for {set i 0} {$i < $num_commands} { incr i} {
+	send -- "[subst -nocommands [lindex $commands $i]]\r"
+	expect {
+	    -re "\b+"				{ exp_continue }
+	    -re "^\[^\n\r *]*$reprompt"		{ send_user -- "$expect_out(buffer)"
+						}
+	    -re "^\[^\n\r]*$reprompt."		{ send_user -- "$expect_out(buffer)"
+						  exp_continue
+						}
+	    -re "^--More--\[\r\n]+"		{ # specific match c1900 pager
+						  send " "
+						  exp_continue
+						}
+	    -re "\[^\r\n]*\[\n\r]+"		{ send_user -- "$expect_out(buffer)"
+						  exp_continue
+						}
+	    -re "\[^\r\n]*Press <SPACE> to cont\[^\r\n]*"	{
+						  send " "
+						  # bloody ^[[2K after " "
+						  expect {
+							-re "^\[^\r\n]*\r" {}
+							}
+						  exp_continue
+						}
+	    -re "^ *--More--\[^\n\r]*"		{
+						  send " "
+						  exp_continue }
+	    -re "^<-+ More -+>\[^\n\r]*"	{
+						  send_user -- "$expect_out(buffer)"
+						  send " "
+						  exp_continue }
+	}
+    }
+    log_user 1
+
+    if { $do_interact == 1 } {
+	interact
+	return 0
+    }
+
+    if { [string compare "extreme" "$platform"] } {
+	send -h "exit\r"
+    } else {
+	send -h "quit\r"
+    }
+    expect {
+	-re "^\[^\n\r *]*$reprompt"		{
+						  # the Cisco CE and Jnx ERX
+						  # return to non-enabled mode
+						  # on exit in enabled mode.
+						  send -h "exit\r"
+						  exp_continue;
+						}
+	"The system has unsaved changes"	{ # Force10 SFTOS
+						  if {$do_saveconfig} {
+						    catch {send "y\r"}
+						  } else {
+						    catch {send "n\r"}
+						  }
+						  exp_continue
+						}
+	"The config is changed.Do you want to save it? (Y/N)"	{ # Aerohive
+						  if {$do_saveconfig} {
+						    catch {send "y\r"}
+						  } else {
+						    catch {send "n\r"}
+						  }
+						  exp_continue
+						}
+	"Would you like to save them now"	{ # Force10
+						  if {$do_saveconfig} {
+						    catch {send "y\r"}
+						  } else {
+						    catch {send "n\r"}
+						  }
+						  exp_continue
+						}
+	-re "(Profile|Configuration) changes have occurred.*"	{
+						  # Cisco CSS
+						  if {$do_saveconfig} {
+						    catch {send "y\r"}
+						  } else {
+						    catch {send "n\r"}
+						  }
+						  exp_continue
+						}
+	"Do you wish to save your configuration changes" {
+						  if {$do_saveconfig} {
+						    catch {send "y\r"}
+						  } else {
+						    catch {send "n\r"}
+						  }
+						  exp_continue
+						}
+	-re "\[\n\r]+"				{ exp_continue }
+	timeout					{ catch {close}; catch {wait};
+						  return 0
+						}
+	eof					{ return 0 }
+    }
+    set in_proc 0
+}
+
+#
+# For each router... (this is main loop)
+#
+source_password_file $password_file
+set in_proc 0
+set exitval 0
+set prompt_match ""
+set enable 0
+foreach router [lrange $argv $i end] {
+    set router [string tolower $router]
+    # attempt at platform switching.
+    set platform ""
+    send_user -- "$router\n"
+
+    # device timeout
+    set timeout [find timeout $router]
+    if { [llength $timeout] == 0 } {
+	set timeout $timeoutdflt
+    }
+
+    # Default prompt.
+    set prompt "(>|#| \\(enable\\))"
+
+    # If a "login" option is used, no "enable" will be required
+    # look for login option in .cloginrc
+    if { [find login $router] != "" } {
+       set enable 0
+       set loginonly 1
+    }
+
+    # look for noenable option in .cloginrc
+    if { [find noenable $router] == "1" } {
+	set enable 0
+    }
+
+    # Figure out passwords
+    if { $do_passwd || $do_enapasswd } {
+      set pswd [find password $router]
+      if { [llength $pswd] == 0 } {
+	send_user -- "\nError: no password for $router in $password_file.\n"
+	continue
+      }
+      if { $enable && $do_enapasswd && $autoenable == 0 && [llength $pswd] < 2 } {
+	send_user -- "\nError: no enable password for $router in $password_file.\n"
+	continue
+      }
+      set passwd [join [lindex $pswd 0] ""]
+      set enapasswd [join [lindex $pswd 1] ""]
+    } else {
+	set passwd $userpasswd
+	set enapasswd $enapasswd
+    }
+
+    # Figure out username
+    if {[info exists username]} {
+      # command line username
+      set ruser $username
+    } else {
+      set ruser [join [find user $router] ""]
+      if { "$ruser" == "" } { set ruser $default_user }
+    }
+
+    # Figure out username's password (if different from the vty password)
+    if {[info exists userpasswd]} {
+      # command line username
+      set userpswd $userpasswd
+    } else {
+      set userpswd [join [find userpassword $router] ""]
+      if { "$userpswd" == "" } { set userpswd $passwd }
+    }
+
+    # Figure out enable username
+    if {[info exists enausername]} {
+      # command line enausername
+      set enauser $enausername
+    } else {
+      set enauser [join [find enauser $router] ""]
+      if { "$enauser" == "" } { set enauser $ruser }
+    }
+
+    # Figure out prompts
+    set u_prompt [find userprompt $router]
+    if { "$u_prompt" == "" } {
+	set u_prompt "(Username|Login|login|user name|User):"
+    } else {
+	set u_prompt [join [lindex $u_prompt 0] ""]
+    }
+    set p_prompt [find passprompt $router]
+    if { "$p_prompt" == "" } {
+	set p_prompt "(\[Pp]assword|passwd|Enter password for \[^ :]+):"
+    } else {
+	set p_prompt [join [lindex $p_prompt 0] ""]
+    }
+    set e_prompt [find enableprompt $router]
+    if { "$e_prompt" == "" } {
+	set e_prompt "\[Pp]assword:"
+    } else {
+	set e_prompt [join [lindex $e_prompt 0] ""]
+    }
+
+    # Figure out identity file to use
+    set identfile [join [lindex [find identity $router] 0] ""]
+
+    # Figure out passphrase to use
+    if {[info exists avpassphrase]} {
+	set passphrase $avpassphrase
+    } else {
+	set passphrase [join [lindex [find passphrase $router] 0] ""]
+    }
+    if { ! [string length "$passphrase"]} {
+	set passphrase $passwd
+    }
+
+    # Figure out cypher type
+    if {[info exists cypher]} {
+        # command line cypher type
+        set cyphertype $cypher
+    } else {
+        set cyphertype [find cyphertype $router]
+    }
+
+    # Figure out connection method
+    set cmethod [find method $router]
+    if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} }
+
+    # Figure out the SSH executable name
+    set sshcmd [join [lindex [find sshcmd $router] 0] ""]
+    if { "$sshcmd" == "" } { set sshcmd {ssh} }
+
+    # if [-mM], skip do not login
+    if { $do_cloginrcdbg > 0 } { continue; }
+
+    # Login to the router
+    if {[login $router $ruser $userpswd $passwd $enapasswd $cmethod $cyphertype $identfile]} {
+	incr exitval
+	# if login failed or rsh was unsuccessful, move on to the next device
+	continue
+    }
+    # Figure out the prompt.
+    if { [regexp -- "(#| \\(enable\\))" $prompt_match junk] == 1 } {
+	set enable 0
+    } else {
+	if { $avenable == 0 } {
+	    set enable 0
+	} else {
+	    set ne [find noenable $router]
+	    set ae [find autoenable $router]
+	    if { "$ne" == "1" || "$ae" == "1" || $avautoenable } {
+		set enable 0
+	    } else {
+		set enable 1
+	    }
+	}
+    }
+
+    # login required?
+    if { $loginonly } {
+       if {[do_login $enauser $enapasswd]} {
+           if { $do_command || $do_script } {
+               close; wait
+               continue
+           }
+       }
+    }
+
+    if { $enable } {
+	if {[do_enable $enauser $enapasswd]} {
+	    if { $do_command || $do_script } {
+		incr exitval
+		catch {close}; catch {wait};
+		continue
+	    }
+	}
+    }
+
+    # we are logged in, now figure out the full prompt
+    send "\r"
+    expect {
+	-re "\[\r\n]+"	{ exp_continue; }
+	-re "^(.+\[:.])1 ($prompt)" { # stoopid extreme cmd-line numbers and
+				  # prompt based on state of config changes,
+				  # which may have an * at the beginning.
+				  set junk $expect_out(1,string)
+				  regsub -all "^\\\* " $expect_out(1,string) {} junk
+				  regsub -all "\[\]\[\(\)]" $junk {\\&} junk;
+				  set prompt ".? ?$junk\[0-9]+ $expect_out(2,string)";
+				  set platform "extreme"
+				}
+	-re "^.+$prompt"	{ set junk $expect_out(0,string);
+				  regsub -all "\[\]\[\(\)]" $junk {\\&} prompt;
+				}
+    }
+    if { $do_command || $do_script } {
+	if { [ string compare "extreme" "$platform" ] } {
+	    # If the prompt is (enable), then we are on a switch and the
+	    # command is "set length 0"; otherwise its "terminal length 0".
+	    if [regexp -- ".*> .*enable" "$prompt"] {
+		send "set length 0\r"
+		expect -re $prompt  	{}
+		send "set width 132\r"
+		expect -re $prompt	{}
+		send "set logging session disable\r"
+	    } else {
+		send "console page 0\r"
+	    }
+	    expect -re $prompt		{}
+	} else {
+	    send "disable clipaging\r"
+	    expect -re $prompt		{}
+	}
+    }
+    if { $do_command } {
+	if {[run_commands $prompt $command]} {
+	    incr exitval
+	    continue
+	}
+    } elseif { $do_script } {
+	source $sfile
+	catch {close};
+    } else {
+	label $router
+	log_user 1
+	interact
+    }
+
+    # End of for each router
+    catch {wait};
+    sleep 0.3
+}
+exit $exitval
diff -rNau ./bin/.cvsignore /home/testranc/src/rancid-aerohive-support/./bin/.cvsignore
--- ./bin/.cvsignore	2018-11-06 18:49:06.411929004 +0100
+++ /home/testranc/src/rancid-aerohive-support/./bin/.cvsignore	2018-11-07 02:06:44.613958426 +0100
@@ -2,6 +2,7 @@
 Makefile
 a10login
 agmrancid
+ahlogin
 alogin
 anlogin
 arancid
diff -rNau ./bin/Makefile.am /home/testranc/src/rancid-aerohive-support/./bin/Makefile.am
--- ./bin/Makefile.am	2018-11-06 18:49:07.047998204 +0100
+++ /home/testranc/src/rancid-aerohive-support/./bin/Makefile.am	2018-11-07 02:06:44.613958426 +0100
@@ -12,7 +12,7 @@
 AUTOMAKE_OPTIONS=foreign
 
 bin_PROGRAMS = hpuifilter par
-bin_SCRIPTS = a10login alogin agmrancid anlogin arancid \
+bin_SCRIPTS = a10login alogin agmrancid ahlogin anlogin arancid \
 	avologin avorancid blogin brancid brlogin cat5rancid clogin \
 	control_rancid csblogin cssrancid \
 	dllogin elogin erancid f5rancid f10rancid \
diff -rNau ./bin/Makefile.in /home/testranc/src/rancid-aerohive-support/./bin/Makefile.in
--- ./bin/Makefile.in	2018-11-06 18:49:07.055999074 +0100
+++ /home/testranc/src/rancid-aerohive-support/./bin/Makefile.in	2018-11-07 02:06:44.613958426 +0100
@@ -97,7 +97,7 @@
 DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
 mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
 CONFIG_HEADER = $(top_builddir)/include/config.h
-CONFIG_CLEAN_FILES = login_top a10login agmrancid alogin arancid \
+CONFIG_CLEAN_FILES = login_top a10login agmrancid ahlogin alogin arancid \
 	anlogin avologin avorancid blogin brancid brlogin cat5rancid \
 	clogin csblogin cssrancid dllogin elogin erancid f5rancid \
 	f10rancid flogin fnlogin fxlogin hlogin htlogin htrancid \
@@ -198,7 +198,7 @@
 ETAGS = etags
 CTAGS = ctags
 am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/a10login.in \
-	$(srcdir)/agmrancid.in $(srcdir)/alogin.in \
+	$(srcdir)/agmrancid.in $(srcdir)/ahlogin.in $(srcdir)/alogin.in \
 	$(srcdir)/anlogin.in $(srcdir)/arancid.in \
 	$(srcdir)/avologin.in $(srcdir)/avorancid.in \
 	$(srcdir)/blogin.in $(srcdir)/brancid.in $(srcdir)/brlogin.in \
@@ -351,7 +351,7 @@
 
 #AUTOMAKE_OPTIONS=foreign no-dependencies
 AUTOMAKE_OPTIONS = foreign
-bin_SCRIPTS = a10login alogin agmrancid anlogin arancid avologin \
+bin_SCRIPTS = a10login alogin agmrancid ahlogin anlogin arancid avologin \
 	avorancid blogin brancid brlogin cat5rancid clogin \
 	control_rancid csblogin cssrancid dllogin elogin erancid \
 	f5rancid f10rancid flogin fnlogin fxlogin hlogin hrancid \
@@ -446,6 +446,8 @@
 	cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
 agmrancid: $(top_builddir)/config.status $(srcdir)/agmrancid.in
 	cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ahlogin: $(top_builddir)/config.status $(srcdir)/ahlogin.in
+	cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
 alogin: $(top_builddir)/config.status $(srcdir)/alogin.in
 	cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
 arancid: $(top_builddir)/config.status $(srcdir)/arancid.in
diff -rNau ./configure /home/testranc/src/rancid-aerohive-support/./configure
--- ./configure	2018-11-06 18:49:07.076001251 +0100
+++ /home/testranc/src/rancid-aerohive-support/./configure	2018-11-07 02:06:44.621959228 +0100
@@ -6123,6 +6123,8 @@
 
 ac_config_files="$ac_config_files bin/agmrancid"
 
+ac_config_files="$ac_config_files bin/ahlogin"
+
 ac_config_files="$ac_config_files bin/alogin bin/arancid"
 
 ac_config_files="$ac_config_files bin/anlogin"
@@ -6948,6 +6950,7 @@
     "bin/login_top") CONFIG_FILES="$CONFIG_FILES bin/login_top" ;;
     "bin/a10login") CONFIG_FILES="$CONFIG_FILES bin/a10login" ;;
     "bin/agmrancid") CONFIG_FILES="$CONFIG_FILES bin/agmrancid" ;;
+    "bin/ahlogin") CONFIG_FILES="$CONFIG_FILES bin/ahlogin" ;;
     "bin/alogin") CONFIG_FILES="$CONFIG_FILES bin/alogin" ;;
     "bin/arancid") CONFIG_FILES="$CONFIG_FILES bin/arancid" ;;
     "bin/anlogin") CONFIG_FILES="$CONFIG_FILES bin/anlogin" ;;
@@ -7728,6 +7731,7 @@
  ;;
     "bin/a10login":F) chmod a+x $ac_file ;;
     "bin/agmrancid":F) chmod a+x $ac_file ;;
+    "bin/ahlogin":F) chmod a+x $ac_file ;;
     "bin/alogin":F) chmod a+x $ac_file ;;
     "bin/arancid":F) chmod a+x $ac_file ;;
     "bin/anlogin":F) chmod a+x $ac_file ;;
diff -rNau ./configure.ac /home/testranc/src/rancid-aerohive-support/./configure.ac
--- ./configure.ac	2018-11-06 18:49:07.072000815 +0100
+++ /home/testranc/src/rancid-aerohive-support/./configure.ac	2018-11-07 02:06:44.621959228 +0100
@@ -495,6 +495,7 @@
 
 AC_CONFIG_FILES(bin/a10login, [chmod a+x $ac_file])
 AC_CONFIG_FILES(bin/agmrancid, [chmod a+x $ac_file])
+AC_CONFIG_FILES(bin/ahlogin, [chmod a+x $ac_file])
 AC_CONFIG_FILES(bin/alogin bin/arancid, [chmod a+x $ac_file])
 AC_CONFIG_FILES(bin/anlogin, [chmod a+x $ac_file])
 dnl AC_CONFIG_FILES(bin/atrancid, [chmod a+x $ac_file])
diff -rNau ./etc/rancid.types.base /home/testranc/src/rancid-aerohive-support/./etc/rancid.types.base
--- ./etc/rancid.types.base	2018-12-19 21:33:15.055237360 +0100
+++ /home/testranc/src/rancid-aerohive-support/./etc/rancid.types.base	2018-11-07 02:06:44.621959228 +0100
@@ -499,6 +497,20 @@
 hitachi;script;htrancid
 hitachi;login;htlogin
 #
+# aerohive hiveos
+hiveos;script;rancid -t hiveos
+hiveos;login;ahlogin
+hiveos;module;hiveos
+hiveos;inloop;hiveos::inloop
+hiveos;command;hiveos::ShowVersion;show version
+hiveos;command;hiveos::ShowVersion;show hw-info
+hiveos;command;hiveos::ShowBoot;show boot-param
+hiveos;command;hiveos::ShowLicense;show license
+hiveos;command;hiveos::ShowCapWap;show capwap client
+hiveos;command;hiveos::ShowIDManager;show idm
+hiveos;command;hiveos::WriteTerm;show running-config password
+hiveos;command;hiveos::WriteTerm;show running-config users password
+#
 # HP Procurve switch such as the 2524, 4108 or J9086A (aka. 2610) procurve 
 # switches, J9091A, and J8698A.  
 hp;script;hrancid -t hp
diff -rNau ./lib/.cvsignore /home/testranc/src/rancid-aerohive-support/./lib/.cvsignore
--- ./lib/.cvsignore	2018-11-06 18:49:07.072000815 +0100
+++ /home/testranc/src/rancid-aerohive-support/./lib/.cvsignore	2018-11-07 02:06:44.621959228 +0100
@@ -15,6 +15,7 @@
 foundry.pm
 fss2.pm
 fxos.pm
+hiveos.pm
 ios.pm
 iossb.pm
 iosshtech.pm
diff -rNau ./lib/hiveos.pm.in /home/testranc/src/rancid-aerohive-support/./lib/hiveos.pm.in
--- ./lib/hiveos.pm.in	1970-01-01 01:00:00.000000000 +0100
+++ /home/testranc/src/rancid-aerohive-support/./lib/hiveos.pm.in	2018-11-07 02:06:44.625959630 +0100
@@ -0,0 +1,355 @@
+package hiveos;
+##
+## $Id$
+##
+## @PACKAGE@ @VERSION@
+@copyright@
+#
+# RANCID - Really Awesome New Cisco confIg Differ
+#
+# hiveos.pm - aerohive hiveos rancid procedures
+#
+# based on iossb.pm.in revision 3472 &
+# nxos.pm.in version 3843
+#
+# done by [email protected]
+#
+# tested on configs pushed by hivemanager-ng 12.8.2.2:
+#   model ap121, hiveos version 6.5r8a
+#   model ap121, hiveos version 6.5r8b
+#   model ap121, hiveos version 6.5r9
+#   model ap121, hiveos version 6.5r10
+#   model ap230, hiveos version 6.5r10
+#   model ap250, hiveos version 8.0r1a
+#   model ap250, hiveos version 8.2r4
+#   model ap250, hiveos version 8.3r2
+#   model ap250, hiveos version 8.3r5
+#   model ap250, hiveos version 8.4r4
+#
+# hiveos cli references can be found here
+# https://docs.aerohive.com/330000/docs/help/english/ng/Content/reference/docs/cli-reference-guides.htm
+
+
+use 5.010;
+use strict 'vars';
+use warnings;
+no warnings 'uninitialized';
+require(Exporter);
+our @ISA = qw(Exporter);
+
+use rancid @VERSION@;
+
+@ISA = qw(Exporter rancid main);
+#XXX @Exporter::EXPORT = qw($VERSION @commandtable %commands @commands);
+
+# load-time initialization
+sub import {
+    0;
+}
+
+# post-open(collection file) initialization
+sub init {
+
+    # add content lines and separators
+    ProcessHistory("","","","!RANCID-CONTENT-TYPE: $devtype\n!\n");
+
+    0;
+}
+
+# main loop of input of device output
+sub inloop {
+    my($INPUT, $OUTPUT) = @_;
+    my($cmd, $rval);
+
+TOP: while(<$INPUT>) {
+	tr/\015//d;
+	if (/[>#]\s?exit$/) {
+	    $clean_run = 1;
+	    print STDERR "exiting\n" if ($debug);
+	    last;
+	}
+	if (/^Error:/) {
+	    print STDOUT ("$host ahlogin error: $_");
+	    print STDERR ("$host ahlogin error: $_") if ($debug);
+	    $clean_run = 0;
+	    last;
+	}
+	while (/#\s*($cmds_regexp)\s*$/) {
+	    $cmd = $1;
+	    if (!defined($prompt)) {
+		$prompt = ($_ =~ /^([^#]+#)/)[0];
+		$prompt =~ s/([][}{)(\\])/\\$1/g;
+		print STDERR ("PROMPT MATCH: $prompt\n") if ($debug);
+	    }
+	    print STDERR ("HIT COMMAND:$_\n") if ($debug);
+	    if (!defined($commands{$cmd})) {
+		print STDERR "$host: found unexpected command - \"$cmd\"\n";
+		$clean_run = 0;
+		last TOP;
+	    }
+	    if (!defined(&{$commands{$cmd}})) {
+		printf(STDERR "$host: undefined function - \"%s\"\n",
+		       $commands{$cmd});
+		$clean_run = 0;
+		last TOP;
+	    }
+	    $rval = &{$commands{$cmd}}($INPUT, $OUTPUT, $cmd);
+	    delete($commands{$cmd});
+	    if ($rval == -1) {
+		$clean_run = 0;
+		last TOP;
+	    }
+	    if (/[>#]\s?exit$/) {
+		print STDERR ("$host: found exit\n") if ($debug);
+		$clean_run = 1;
+		last TOP;
+	    }
+	}
+    }
+}
+
+
+# This routine parses "show version" and "show hw-info"
+sub ShowVersion {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    print STDERR "    In ShowVersion: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+	next if (/^(Copyright|Uptime|Aerohive (Networks|hardware))/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	ProcessHistory("COMMENTS","","","!ver: $_");
+    }
+    ProcessHistory("COMMENTS","","","!\n");
+    return 0;
+}
+
+# This routine parses "show boot-param"
+sub ShowBoot {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    print STDERR "    In ShowBoot: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+	next if (/^boot parameters:/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	ProcessHistory("COMMENTS","","","!boot-param: $_");
+    }
+    ProcessHistory("COMMENTS","","","!\n");
+    return 0;
+}
+
+# This routine parses "show capwap client"
+sub ShowCapWap {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    print STDERR "    In ShowCapWap: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	last if ($filter_osc >= 1);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+	next if (/^(max )?(retransmit|discovery) count:/i);
+	next if (/(tries|Keepalives|drop)/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	ProcessHistory("COMMENTS","","","!capwap: $_");
+    }
+    if ($filter_osc >= 1) {ProcessHistory("COMMENTS","","","!capwap: <removed>\n");}
+    ProcessHistory("COMMENTS","","","!\n");
+    return 0;
+}
+
+# This routine parses "show idm"
+sub ShowIDManager {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    print STDERR "    In ShowIDManager: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	last if ($filter_osc >= 1);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	ProcessHistory("COMMENTS","","","!id-manager: $_");
+    }
+    if ($filter_osc >= 1) {ProcessHistory("COMMENTS","","","!id-manager: <removed>\n");}
+    ProcessHistory("COMMENTS","","","!\n");
+    return 0;
+}
+
+# This routine parses "show license"
+sub ShowLicense {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    print STDERR "    In ShowLicense: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	ProcessHistory("COMMENTS","","","!license: $_");
+    }
+    ProcessHistory("COMMENTS","","","!\n");
+    return 0;
+}
+
+# This routine parses "show running-config password"
+# and "show running-config users password"
+sub WriteTerm {
+    my($INPUT, $OUTPUT, $cmd) = @_;
+    my $linecnt = 0;
+    print STDERR "    In WriteTerm: $_" if ($debug);
+
+    while (<$INPUT>) {
+	tr/\015//d;
+	last if (/^$prompt/);
+	next if (/^(\s*|\s*$cmd\s*)$/);
+
+	/^\s+\^-- unknown keyword or invalid input\s+$/ && return(1);
+	/^\s+\^-- Ambiguous input\s+$/ && return(1);
+	/^ERROR: / && return(1);
+
+	$linecnt++;
+
+	# XXX hiveos likes to add single spaces behind some config lines. this mostly happens
+	# when the command is not using all possible flags/options, but sometimes also on complete
+	# commands ("admin root-admin ..." for example). keep this in mind when using regular
+	# expressions and testing for EOL, checking for a trailing space with \s? at the end is
+	# advised. this is seems inherent to how hiveos deals with its config, since it is also
+	# present when saving the config via scp.
+	# on the plus side, is does make sure the arguments will always be in a fixed order.
+
+	# filter passwords
+
+	# hive <string> password <string>
+	if(/^(hive \S+ password )\S+\s?$/ && $filter_pwds >= 2) {
+		ProcessHistory("","","","!$1<removed>\n");
+		next;
+	}
+
+	# security-object <string> security aaa radius-server accounting {primary|backup1|backup2|backup3} \
+	#   <ip_addr|ipv6_addr|string_64> [ shared-secret <string> ] [ acct-port <number> ] [ via-vpn-tunnel ]
+	# security-object <string> security aaa radius-server {primary|backup1|backup2|backup3} \
+	#   <ip_addr|ipv6_addr|string_64> [ shared-secret <string> ] [ auth-port <number> ] [ acct-port <number> ] [ via-vpn-tunnel ]
+	if (/^(security-object \S+ security aaa radius-server (accounting )?(primary|backup(1|2|3)) \S+ shared-secret )\S+ (\S+.*)?$/ && $filter_pwds >= 2) {
+		ProcessHistory("","","","!$1<removed> $5\n");
+		next;
+	}
+
+	# admin root-admin <string> password <string>
+	# admin {read-write|read-only} <string> password <string>
+	if(/^(admin (root-admin|read-write|read-only) \S+ password )\S+\s?$/ && $filter_pwds >= 2) {
+		ProcessHistory("","","","!$1<removed>\n");
+		next;
+	}
+
+	# security-object <string> security protocol-suite wep-open <number> {hex-key|ascii-key} <string> [ default ]
+	# security-object <string> security protocol-suite wep-shared <number> {hex-key|ascii-key} <string> [ default ]
+	#
+
+
+	# security-object <string> security protocol-suite wpa-auto-psk {hex-key|ascii-key} <string> [ rekey-period <number> ] \
+	#    [ {non-strict|strict} ] [ gmk-rekey-period <number> ] [ ptk-timeout <number> ] \
+	#    [ ptk-retry <number> ] [ gtk-timeout <number> ] [ gtk-retry <number> ] [ ptk-rekey-period <number> ]
+	# security-object <string> security protocol-suite wpa2-aes-psk {hex-key|ascii-key} <string> [ rekey-period <number> ] \
+	#    [ {non-strict|strict} ] [ gmk-rekey-period <number> ] [ ptk-timeout <number> ] \
+	#    [ ptk-retry <number> ] [ gtk-timeout <number> ] [ gtk-retry <number> ] [ ptk-rekey-period <number> ]
+	# security-object <string> security protocol-suite wpa2-tkip-psk {hex-key|ascii-key} <string> [ rekey-period <number> ] \
+	#    [ {non-strict|strict} ] [ gmk-rekey-period <number> ] [ ptk-timeout <number> ] \
+	#    [ ptk-retry <number> ] [ gtk-timeout <number> ] [ gtk-retry <number> ] [ ptk-rekey-period <number> ]
+	if(/^(security-object \S+ security protocol-suite wpa(-auto|2-aes|2-tkip)-psk (hex|ascii)-key )\S+\s?(\S+.*)?$/ && $filter_pwds >= 1) {
+		ProcessHistory("","","","!$1<removed> $4\n");
+		next;
+	}
+
+	# capwap client dtls hm-defined-passphrase <string> key-id <number>
+	if(/^(capwap client dtls hm-defined-passphrase )\S+( key-id (\S+))\s?$/ && $filter_pwds >= 2) {
+		ProcessHistory("","","","!$1<removed>$2\n");
+		next;
+	}
+
+	# application reporting upload <url> time-window <number> [ admin <string> password <string> {basic|digest} ]
+	# basic is base64 so reversable, digest should be md5 hashed
+	if(/^(application reporting upload \S+ time-window \d+ admin \S+ password )\S+ (\S+)\s?$/ &&
+		(($filter_pwds >= 1 && $2 eq "basic") || $filter_pwds >= 2)) {
+		ProcessHistory("","","","!$1<removed> $2\n");
+		next;
+	}
+
+	# mobility-policy <string> inxp gre-tunnel from <ip_addr/netmask> password <string>
+	# mobility-policy <string> inxp gre-tunnel from ipv6 <ipv6_addr/mask> password <string>
+	# mobility-policy <string> inxp gre-tunnel to <ip_addr> <ip_addr> password <string>
+	# mobility-policy <string> inxp gre-tunnel to <ip_addr> password <string>
+	# mobility-policy <string> inxp gre-tunnel to ipv6 <ipv6_addr> <ipv6_addr> password <string>
+	# mobility-policy <string> inxp gre-tunnel to ipv6 <ipv6_addr> password <string>
+	if(/^(mobility-policy \S+ inxp gre-tunnel (to|from) (ipv6 )?\S+ (\S+ )?password )\S+\s?$/ && $filter_pwds >= 2) {
+		ProcessHistory("","","","!$1<removed>\n");
+		next;
+	}
+
+	# user <string> password <string>
+	# username can contain spaces but then it will be quoted:
+	# user "my username" password 2yB3FLs3tv20oybmIBrt7VzGePrDqEaUdKW0p
+	if(/^(user .*? password )\S+\s?$/ && $filter_pwds >= 1) {
+		ProcessHistory("","","","!$1<removed>\n");
+		next;
+	}
+
+	# prune snmp statements
+
+	# snmp trap-host {v1|v2c} <ip_addr|string_64> [ port <number> ] [ {via-vpn-tunnel} ] [ community <string> ]
+
+	# snmp trap-host {v3} <ip_addr|string_64> [ port <number> ] [ {via-vpn-tunnel} ] admin <string>
+	# snmp trap-host {v3} admin <string> auth {md5|sha} password <string> [ encryption {aes|aes128|des} password <string> ]
+
+	# snmp reader version {v1|v2c|any} community <string> [ <string> ]
+	if (/^(snmp reader version (any|v1|v2c) community )\S+ (\S+)?$/ && $filter_commstr) {
+		ProcessHistory("","","","!$1<removed> $3\n");
+		next;
+	}
+
+	# snmp reader version v3 admin <string> [ auth {md5|sha} password <string> ] [ encryption {aes|aes128|des} password <string> ]
+#	/^(snmp reader version v3 admin \S+)( auth (md5|sha) password \S+)?( encryption (aes|aes128|des) password \S+)?/
+
+
+	# catch anything that wasnt matched above.
+	ProcessHistory("","","","$_");
+
+	# end of config.
+	# XXX no definitive end of config marker.
+    }
+
+    # hiveos lacks a definitive "end of config" marker.  If we have seen at least
+    # 5 lines of write term output, we can be reasonably sure that we have the config.
+    if ($linecnt > 5) {
+        $found_end = 1;
+    }
+
+    return(0);
+}
+
+1;
diff -rNau ./lib/Makefile.am /home/testranc/src/rancid-aerohive-support/./lib/Makefile.am
--- ./lib/Makefile.am	2018-11-06 18:49:07.076001251 +0100
+++ /home/testranc/src/rancid-aerohive-support/./lib/Makefile.am	2018-11-07 02:06:44.621959228 +0100
@@ -10,7 +10,7 @@
 my_pkglib_DATA = rancid.pm acos.pm aeos.pm arbor.pm arcos.pm \
 		 bigip.pm ciscowlc.pm \
 		 dell.pm edgemax.pm edgerouter.pm exos.pm \
-		 fortigate.pm foundry.pm fss2.pm fxos.pm \
+		 fortigate.pm foundry.pm fss2.pm fxos.pm hiveos.pm \
 		 ios.pm iossb.pm iosshtech.pm iosxr.pm junos.pm mrv.pm nxos.pm \
 		 panos.pm rbt.pm sros.pm vrp.pm wavesvros.pm
 pkglib_in = $(my_pkglib_DATA:%=%.in)
@@ -70,6 +70,7 @@
 foundry.pm: foundry.pm.in
 fss2.pm: fss2.pm.in
 fxos.pm: fxos.pm.in
+hiveos.pm: hiveos.pm.in
 ios.pm: ios.pm.in
 iossb.pm: iossb.pm.in
 iosshtech.pm: iosshtech.pm.in
diff -rNau ./lib/Makefile.in /home/testranc/src/rancid-aerohive-support/./lib/Makefile.in
--- ./lib/Makefile.in	2018-11-06 18:49:07.200014742 +0100
+++ /home/testranc/src/rancid-aerohive-support/./lib/Makefile.in	2018-11-07 02:06:44.621959228 +0100
@@ -274,7 +274,7 @@
 my_pkglib_DATA = rancid.pm acos.pm aeos.pm arbor.pm arcos.pm \
 		 bigip.pm ciscowlc.pm \
 		 dell.pm edgemax.pm edgerouter.pm exos.pm \
-		 fortigate.pm foundry.pm fss2.pm fxos.pm \
+		 fortigate.pm foundry.pm fss2.pm fxos.pm hiveos.pm \
 		 ios.pm iossb.pm iosshtech.pm iosxr.pm junos.pm mrv.pm nxos.pm \
 		 panos.pm rbt.pm sros.pm vrp.pm wavesvros.pm
 
@@ -544,6 +544,7 @@
 foundry.pm: foundry.pm.in
 fss2.pm: fss2.pm.in
 fxos.pm: fxos.pm.in
+hiveos.pm: hiveos.pm.in
 ios.pm: ios.pm.in
 iossb.pm: iossb.pm.in
 iosshtech.pm: iosshtech.pm.in
diff -rNau ./man/ahlogin.1 /home/testranc/src/rancid-aerohive-support/./man/ahlogin.1
--- ./man/ahlogin.1	1970-01-01 01:00:00.000000000 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/ahlogin.1	2018-11-07 02:06:44.625959630 +0100
@@ -0,0 +1 @@
+.so man1/clogin.1
diff -rNau ./man/clogin.1 /home/testranc/src/rancid-aerohive-support/./man/clogin.1
--- ./man/clogin.1	2018-11-06 18:49:07.796079589 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/clogin.1	2018-11-07 02:06:44.625959630 +0100
@@ -56,6 +56,7 @@
 or Redback router.
 There are complementary scripts for
 A10,
+Aerohive,
 Alteon,
 Avocent (Cyclades),
 Bay Networks (nortel),
@@ -76,7 +77,8 @@
 Extreme devices
 and Xirrus arrays,
 named
-.B a10login
+.B a10login,
+.B ahlogin,
 .B alogin,
 .B avologin,
 .B blogin,
diff -rNau ./man/cloginrc.5.in /home/testranc/src/rancid-aerohive-support/./man/cloginrc.5.in
--- ./man/cloginrc.5.in	2018-11-06 18:49:07.796079589 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/cloginrc.5.in	2018-11-07 02:06:44.625959630 +0100
@@ -8,6 +8,7 @@
 .SH DESCRIPTION
 .B .cloginrc
 contains configuration information for
+.BR ahlogin (1),
 .BR alogin (1),
 .BR blogin (1),
 .BR clogin (1),
diff -rNau ./man/Makefile.am /home/testranc/src/rancid-aerohive-support/./man/Makefile.am
--- ./man/Makefile.am	2018-11-06 18:49:07.080001685 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/Makefile.am	2018-11-07 02:06:44.625959630 +0100
@@ -14,7 +14,7 @@
 
 man_gen_MANS = cloginrc.5 lg.conf.5 rancid.3 rancid.conf.5 \
 		rancid.types.conf.5 lg_intro.1
-man_nogen_MANS = agmrancid.1 alogin.1 anlogin.1 anrancid.1 \
+man_nogen_MANS = agmrancid.1 ahlogin.1 alogin.1 anlogin.1 anrancid.1 \
 		arancid.1 arrancid.1 avologin.1 avorancid.1 blogin.1 brancid.1 \
 		cat5rancid.1 clogin.1 control_rancid.1 csblogin.1 cssrancid.1 \
 		dllogin.1 \
diff -rNau ./man/Makefile.in /home/testranc/src/rancid-aerohive-support/./man/Makefile.in
--- ./man/Makefile.in	2018-11-06 18:49:07.080001685 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/Makefile.in	2018-11-07 02:06:44.625959630 +0100
@@ -277,7 +277,7 @@
 man_gen_MANS = cloginrc.5 lg.conf.5 rancid.3 rancid.conf.5 \
 		rancid.types.conf.5 lg_intro.1
 
-man_nogen_MANS = agmrancid.1 alogin.1 anlogin.1 anrancid.1 \
+man_nogen_MANS = agmrancid.1 ahlogin.1 alogin.1 anlogin.1 anrancid.1 \
 		arancid.1 arrancid.1 avologin.1 avorancid.1 blogin.1 brancid.1 \
 		cat5rancid.1 clogin.1 control_rancid.1 csblogin.1 cssrancid.1 \
 		dllogin.1 \
diff -rNau ./man/rancid.1 /home/testranc/src/rancid-aerohive-support/./man/rancid.1
--- ./man/rancid.1	2018-11-06 18:49:07.796079589 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/rancid.1	2018-11-07 02:06:44.629960032 +0100
@@ -75,15 +75,12 @@
 .B f5rancid
 F5 BigIPs
 .TP
-.B fnrancid
-Fortinet Firewalls
-.TP
 .B rancid
-The generic rancid script; supporting Allied Telesis AW+ devices,
-Arbor Networks Appliances, Ciena Waverserver, Cisco IOS, Cisco IOS-XR,
-Cisco NX-OS, Cisco WLC, (some) Dell switches, Extreme switches,
-Foundry (aka some Brocade) devices, Juniper JUNOS, Nokia (Alcatel-Lucent)
-SR OS, and UBNT Edgemax and EdgeRouter.
+The generic rancid script; supporting Aerohive HiveOS access points,
+Allied Telesis AW+ devices, Arbor Networks Appliances, Ciena Waverserver,
+Cisco IOS, Cisco IOS-XR, Cisco NX-OS, Cisco WLC, (some) Dell switches,
+Extreme switches, Foundry (aka some Brocade) devices, Juniper JUNOS,
+Nokia (Alcatel-Lucent) SR OS, and UBNT Edgemax and EdgeRouter.
 It uses the device O/S modules for parsing routines as determined by the
 .BR rancid.types.conf (5)
 file(s).
diff -rNau ./man/rancid_intro.1 /home/testranc/src/rancid-aerohive-support/./man/rancid_intro.1
--- ./man/rancid_intro.1	2018-11-06 18:49:07.800080023 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/rancid_intro.1	2018-11-07 02:06:44.629960032 +0100
@@ -2,13 +2,14 @@
 .\" $Id: rancid_intro.1 3857 2018-08-10 14:29:20Z heas $
 .\"
 .hys 50
-.TH "rancid_intro" "1" "10 August 2018"
+.TH "rancid_intro" "1" "6 November 2018"
 .SH NAME
 rancid_intro \- introduction to the Really Awesome New Cisco confIg Differ
 .SH INTRODUCTION
 .B rancid
 is really more than just a Cisco configuration differ.  It handles several
 different device's configurations; currently including
+Aerohive,
 Alteon,
 Arista,
 Bay Networks (Nortel),
@@ -19,8 +20,8 @@
 Fortinet,
 Foundry,
 HP Procurve switches,
-Hauwei VRP,
 Hitachi,
+Huawei VRP,
 Juniper Routers and edge (ERX) routers,
 MRTd daemon,
 Microtik routers,
diff -rNau ./man/router.db.5 /home/testranc/src/rancid-aerohive-support/./man/router.db.5
--- ./man/router.db.5	2018-11-06 18:49:07.800080023 +0100
+++ /home/testranc/src/rancid-aerohive-support/./man/router.db.5	2018-11-07 02:06:44.629960032 +0100
@@ -165,6 +165,10 @@
 A Hitachi router.
 .TP
 .ID 15n
+.B hiveos
+An Aerohive access point running HiveOS.
+.TP
+.ID 15n
 .B hp
 A HP Procurve switch such as the 2524, 4108 or J9086A (aka. 2610) procurve
 switches, J9091A, and J8698A.
diff -rNau ./README /home/testranc/src/rancid-aerohive-support/./README
--- ./README	2018-11-06 18:49:06.411929004 +0100
+++ /home/testranc/src/rancid-aerohive-support/./README	2018-11-07 02:06:44.613958426 +0100
@@ -59,7 +59,6 @@
 	erancid.in	Version of rancid.in for ADC EZ-T3 muxes.
 	f10rancid.in	Version of rancid.in for Force10 routers.
 	f5rancid.in	Version of rancid.in for F5 BigIPs.
-	fnrancid.in	Version of rancid.in for Fortinet Firewalls.
 	hrancid.in	Version of rancid.in for HP Procurve switches.
 	htrancid.in	Version of rancid.in for Hitatchi routers.
 	jerancid.in	Version of rancid.in for Juniper E-series routers.
@@ -77,7 +76,8 @@
 	xirancid.in	Version of rancid.in for Xirrus arrays.
 	zrancid.in	Version of rancid.in for Zebra routers.
 
-        a10login.in     Version of clogin.in for A10 load balancers.
+	a10login.in     Version of clogin.in for A10 load balancers.
+	ahlogin.in	Version of clogin.in for aerohive hiveos access points.
 	alogin.in	Version of clogin.in for Alteon switches.
 	anlogin.in	Version of clogin.in for Arbor Networks appliances.
 	avologin.in	Version of clogin.in for Avocent appliances.
@@ -118,7 +118,9 @@
 	dell.pm.in	rancid library for some Dell D-Link models.
 	eos.pm.in	rancid library for Compass EOS.
 	exos.in		rancid library for Extreme switches.
+	fortigate.pm.in	rancid library for Fortinet Firewalls.
 	foundry.pm.in	rancid library for Foundry (Brocade) switches.
+	hiveos.pm.in	rancid library for aerohive hiveos access points.
 	ios.pm.in	rancid library for Cisco IOS and IOS-XE.
 	iosxr.pm.in	rancid library for Cisco IOS-XR.
 	junos.pm.in	rancid library for Juniper JunOS.