package asa

import (
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"

	"github.rackspace.com/gorax/asaapi/pkg/asaModels"
	"github.rackspace.com/gorax/clients/sshAPI"
	"github.rackspace.com/gorax/tools/util"
)

var (
	VALIDRE  = regexp.MustCompile(`(^\S+$)`)
	LOGOFFRE = regexp.MustCompile(`(logged\s+off\s+:\s+1)`)
)

func (a *ASA) IPName(ip string) *asaModels.Name {
	return a.Config.Names.IPName(ip)
}

func (a *ASA) ObjectFind(name string) *asaModels.NetworkObject {
	return a.Config.Objects.FindObject(name)
}

func (a *ASA) ObjectFindHost(host string) []*asaModels.NetworkObject {
	return a.Config.Objects.FindHost(host)
}

func (a *ASA) ObjectGroupFindHost(host string) []*asaModels.NetworkObjGroup {
	return a.Config.ObjectGroups.FindHost(host)
}

func (a *ASA) ObjectGroupFindGroup(host string) []*asaModels.NetworkObjGroup {
	return a.Config.ObjectGroups.FindGroup(host)
}

func (a *ASA) GPModifySplitTunnelNet(gps []*asaModels.GroupPolicy) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.CommandCreate("conf t", 5, "# "))
	for _, gp := range gps {
		cmd = append(cmd, sshAPI.CommandCreate(fmt.Sprintf("group-policy %s attributes", *gp.Name), 5, ")# "))
		cmd = append(cmd, sshAPI.CommandCreate(fmt.Sprintf("split-tunnel-network-list value %s", *gp.Attributes.SplitTunnelNetwork), 5, ")# "))
	}
	cmd = append(cmd, sshAPI.CommandCreate("end", 5, "# "))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return nil
}

func (a *ASA) ObjectGroupFindObject(host string) []*asaModels.NetworkObjGroup {
	return a.Config.ObjectGroups.FindObject(host)
}

func (a *ASA) ObjectGroupRemoveHost(host string) {
	a.Config.ObjectGroups.RemoveHost(host)
}

func (a *ASA) ObjectGroupRemoveObject(object string) {
	a.Config.ObjectGroups.RemoveObject(object)
}

func (a *ASA) ObjectGroupRemoveGroup(group string) {
	a.Config.ObjectGroups.RemoveGroup(group)
}

func (a *ASA) ShowAAAServer(ssh bool) (*asaModels.AAAServerGroups, error) {
	var res string

	// if they didn't specifically request ssh, try http first
	if !ssh {
		a.log.Debug("Connecting to firewall over http")
		var err error
		res, err = a.httpClient.GetCommand("show aaa-server")
		if err != nil {
			a.log.Warn("unable to connect to firewall with http, falling back to ssh")
			ssh = true
		}
	}

	// we test ssh instead of an else because the first if may have changed it
	if ssh {
		a.log.Debug("Connecting to firewall over ssh")
		resp, err := a.sshClient.PostCommand("show aaa-server", "# ", 30)
		if err != nil {
			return nil, err
		}
		res = *resp.Response
	}

	var status asaModels.AAAServerGroups
	if err := status.Parse(res); err != nil {
		return nil, err
	}
	return &status, nil
}

func (a *ASA) ShowIPVerifyReversePath() (*asaModels.IPVerifyReversePath, error) {
	res, err := a.sshClient.PostCommand("show run ip verify reverse-path", "# ", 10)
	if err != nil {
		return nil, err
	}
	i := asaModels.NewIPVerifyStatus(*res.Response)
	return i, nil
}

func (a *ASA) SetNamesSetting(enable bool) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.CommandCreate("conf t", 5, ")# "))
	if enable {
		cmd = append(cmd, sshAPI.CommandCreate("names", 5, ")# "))
	} else {
		cmd = append(cmd, sshAPI.CommandCreate("no names", 5, ")# "))
	}

	cmd = append(cmd, sshAPI.CommandCreate("end", 5, "# "))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	var n asaModels.Names
	a.Config.Names = n
	return a.GetNames()
}

func (a *ASA) PasswordPolicyCreate(p asaModels.PasswordPolicy) error {
	var cmd []*sshAPI.Command
	var createErr []string
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	if p.MinLength != 0 {
		if a.Config.PasswordPolicy.MinLength != 0 {
			createErr = append(createErr, "minimum_length")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-length %v", p.MinLength), 5, configPrompt))
	}
	if p.MinChange != 0 {
		if a.Config.PasswordPolicy.MinChange != 0 {
			createErr = append(createErr, "minimum_changes")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-changes %v", p.MinChange), 5, configPrompt))
	}
	if p.MinLower != 0 {
		if a.Config.PasswordPolicy.MinLower != 0 {
			createErr = append(createErr, "minimum_lowercase")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-lowercase %v", p.MinLower), 5, configPrompt))
	}
	if p.MinUpper != 0 {
		if a.Config.PasswordPolicy.MinUpper != 0 {
			createErr = append(createErr, "minimum_uppercase")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-uppercase %v", p.MinUpper), 5, configPrompt))
	}
	if p.MinNumeric != 0 {
		if a.Config.PasswordPolicy.MinNumeric != 0 {
			createErr = append(createErr, "minimum_numeric")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-numeric %v", p.MinNumeric), 5, configPrompt))
	}
	if p.MinSpecial != 0 {
		if a.Config.PasswordPolicy.MinSpecial != 0 {
			createErr = append(createErr, "minimum_special")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-special %v", p.MinSpecial), 5, configPrompt))
	}
	if p.MinLifetime != 0 {
		if a.Config.PasswordPolicy.MinLifetime != 0 {
			createErr = append(createErr, "lifetime")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy lifetime %v", p.MinLifetime), 5, configPrompt))
	}
	if p.AuthEnable {
		cmd = append(cmd, sshAPI.ReCommandCreate("password-policy authenticate-enable", 5, configPrompt))
	}
	if createErr != nil {
		return fmt.Errorf("The following values exists and need to be update with PUT/PATCH: %v", createErr)
	}

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return a.GetPasswordPolicy()
}

func (a *ASA) PasswordPolicyPatch(p asaModels.PasswordPolicy) error {
	var cmd []*sshAPI.Command
	var createErr []string
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	if p.MinLength != 0 {
		if a.Config.PasswordPolicy.MinLength == 0 {
			createErr = append(createErr, "minimum_length")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-length %v", p.MinLength), 5, configPrompt))
	}
	if p.MinChange != 0 {
		if a.Config.PasswordPolicy.MinChange == 0 {
			createErr = append(createErr, "minimum_changes")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-changes %v", p.MinChange), 5, configPrompt))
	}
	if p.MinLower != 0 {
		if a.Config.PasswordPolicy.MinLower == 0 {
			createErr = append(createErr, "minimum_lowercase")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-lowercase %v", p.MinLower), 5, configPrompt))
	}
	if p.MinUpper != 0 {
		if a.Config.PasswordPolicy.MinUpper == 0 {
			createErr = append(createErr, "minimum_uppercase")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-uppercase %v", p.MinUpper), 5, configPrompt))
	}
	if p.MinNumeric != 0 {
		if a.Config.PasswordPolicy.MinNumeric == 0 {
			createErr = append(createErr, "minimum_numeric")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-numeric %v", p.MinNumeric), 5, configPrompt))
	}
	if p.MinSpecial != 0 {
		if a.Config.PasswordPolicy.MinSpecial == 0 {
			createErr = append(createErr, "minimum_special")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-special %v", p.MinSpecial), 5, configPrompt))
	}
	if p.MinLifetime != 0 {
		if a.Config.PasswordPolicy.MinLifetime == 0 {
			createErr = append(createErr, "lifetime")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy lifetime %v", p.MinLifetime), 5, configPrompt))
	}
	if p.AuthEnable {
		cmd = append(cmd, sshAPI.ReCommandCreate("password-policy authenticate-enable", 5, configPrompt))
	}
	if createErr != nil {
		return fmt.Errorf("The following elements do not exists and need to be created with POST/PUT: %v", createErr)
	}

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return a.GetPasswordPolicy()
}

func (a *ASA) PasswordPolicyUpdate(p asaModels.PasswordPolicy) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	if p.MinLength != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-length %v", p.MinLength), 5, configPrompt))
	}
	if p.MinChange != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-changes %v", p.MinChange), 5, configPrompt))
	}
	if p.MinLower != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-lowercase %v", p.MinLower), 5, configPrompt))
	}
	if p.MinUpper != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-uppercase %v", p.MinUpper), 5, configPrompt))
	}
	if p.MinNumeric != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-numeric %v", p.MinNumeric), 5, configPrompt))
	}
	if p.MinSpecial != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy minimum-special %v", p.MinSpecial), 5, configPrompt))
	}
	if p.MinLifetime != 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("password-policy lifetime %v", p.MinLifetime), 5, configPrompt))
	}
	if p.AuthEnable {
		cmd = append(cmd, sshAPI.ReCommandCreate("password-policy authenticate-enable", 5, configPrompt))
	} else if a.Config.PasswordPolicy.AuthEnable && !p.AuthEnable {
		cmd = append(cmd, sshAPI.ReCommandCreate("no password-policy authenticate-enable", 5, configPrompt))
	}

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return a.GetPasswordPolicy()
}

func (a *ASA) PasswordPolicyDelete(p string) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no password-policy %v", p), 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return a.GetPasswordPolicy()
}

func (a *ASA) ObjectGroupICMPAdd(o *asaModels.IcmpObjGroup) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group icmp-type %s", *o.Name), 5, configPrompt))
	if o.Desc != nil {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("description %s", *o.Desc), 5, configPrompt))
	}
	for _, r := range o.IcmpObj {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("icmp-object %s", *r.Name), 5, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	// reset our active config and refetch the live acl's
	var og asaModels.ObjectGroups
	a.Config.ObjectGroups = og
	return a.GetObjectGroups()
}

func (a *ASA) ObjectGroupICMPDelete(o *asaModels.IcmpObjGroup) error {
	if a.Config.ObjectGroups.IsEmpty() {
		if err := a.GetObjectGroups(); err != nil {
			return err
		}
	}
	if !a.Config.ObjectGroups.Exists("icmp", *o.Name) {
		return errors.New(fmt.Sprintf("object-group icmp-type `%s` does not exist.", *o.Name))
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group icmp-type %s", *o.Name), 5, configPrompt))
	for _, r := range o.IcmpObj {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no icmp-object %s", *r.Name), 5, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if err := a.GetObjectGroups(); err != nil {
		return err
	}
	for _, ob := range a.Config.ObjectGroups.Icmp {
		if *ob.Name == *o.Name && ob.IsEmpty() {
			a.ObjectGroupDelete("icmp", *o.Name, false)
		}
	}
	return a.GetObjectGroups()
}

func (a *ASA) BannerSet(b []string, remove bool) error {
	// Replace #'s with *'s for compatibility
	for _, line := range b {
		if strings.Contains(line, "#") {
			line = strings.Replace(line, "#", "*", -1)
		}
	}
	var cmds []*sshAPI.Command
	cmds = append(cmds, sshAPI.CommandCreate("conf t", 5, ")# "))
	if remove {
		cmds = append(cmds, sshAPI.CommandCreate("no banner motd", 5, ")# "))
	}
	for _, line := range b {
		cmds = append(cmds, sshAPI.CommandCreate(fmt.Sprintf("banner motd %s", line), 5, ")# "))
	}
	cmds = append(cmds, sshAPI.CommandCreate("end", 5, "# "))
	for _, cmd := range cmds {
		fmt.Println(cmd.Command)
	}
	_, err := a.sshClient.PostCommands(cmds)
	if err != nil {
		return err
	}
	return a.GetBanner()
}

func (a *ASA) BannerDelete() error {
	if err := a.GetHostname(); err != nil {
		return err
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.CommandCreate("conf t", 5, ")# "))
	cmd = append(cmd, sshAPI.CommandCreate("no banner motd", 5, ")# "))
	cmd = append(cmd, sshAPI.CommandCreate("end", 5, "# "))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	return a.GetBanner()
}

func (a *ASA) DeleteUsernames(u *[]*asaModels.UserName) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, n := range *u {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no username %s attributes", *n.Name), 5, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no username %s", *n.Name), 5, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) DeleteUsernames_HTTP(u *[]*asaModels.UserName) error {
	for _, n := range *u {
		_, err := a.httpClient.PostCommand(fmt.Sprintf("clear configure username %s", *n.Name))
		if err != nil {
			return err
		}
	}
	return nil
}

func (a *ASA) AddUsernames(u *[]*asaModels.UserName) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, user := range *u {
		if user.Password != nil {
			cmdToSend := fmt.Sprintf("username %s %s%s",
				*user.Name,
				func() string {
					if user.Encrypted {
						return fmt.Sprintf("password %s encrypted", *user.Password)
					}
					return fmt.Sprintf("password %s", *user.Password)
				}(),
				func() string {
					if user.Privilege != nil {
						return fmt.Sprintf(" privilege %v", *user.Privilege)
					}
					return ""
				}(),
			)
			cmd = append(cmd, sshAPI.ReCommandCreate(cmdToSend, 5, configPrompt))
		}
		if user.Attributes == nil {
			continue
		}
		if user.Attributes.GroupLock == nil && user.Attributes.ServiceType == nil {
			continue
		}

		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("username %s attributes", *user.Name), 5, configPrompt))
		if user.Attributes.GroupLock != nil {
			if strings.ToLower(*user.Attributes.GroupLock) == "none" {
				cmd = append(cmd, sshAPI.ReCommandCreate("group-lock none", 5, configPrompt))
			} else {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("group-lock value %s", *user.Attributes.GroupLock), 5, configPrompt))
			}
		}
		if user.Attributes.ServiceType != nil {
			if strings.ToLower(*user.Attributes.ServiceType) == "none" {
				cmd = append(cmd, sshAPI.ReCommandCreate("no service-type", 5, configPrompt))
			} else {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("service-type %s", *user.Attributes.ServiceType), 5, configPrompt))
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("exit", 5, configPrompt))

	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	// spew.Dump(cmd)
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SetAAACommandAuth(n *string) error {
	if a.Config.AAAConfig == nil {
		a.GetAAAConfig()
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))

	if *n == "" && a.Config.AAAConfig.CommandAuth != nil {
		// if n = blank and there's something in the active config, remove it
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no aaa authorization command %s", util.StringSliceToString(a.Config.AAAConfig.CommandAuth)), 5, prompt))
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
		_, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return err
		}
		return nil
	}

	if a.Config.AAAConfig.CommandAuth != nil {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no aaa authorization command %s", util.StringSliceToString(a.Config.AAAConfig.CommandAuth)), 5, prompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("aaa authorization command %s", *n), 5, prompt))
	for _, i := range cmd {
		a.log.Println(i.Command)
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SetAAAConfig(d *[]*asaModels.AAAType, t string) error {

	if a.Config.AAAConfig == nil {
		a.GetAAAConfig()
	}
	if a.Config.AAAServers == nil {
		a.GetAAAServers()
	}

	if a.Config.AAAConfig == nil {
		return errors.New("Error: AAAConfig not found")
	}

	var target *[]*asaModels.AAAType
	if t == "authentication" {
		for _, aaa := range *d {
			info := aaa.Options
			if info[0] != "LOCAL" && !a.Config.AAAServers.Exists(info[0]) {
				return errors.New(fmt.Sprintf("Error: aaa-server group %s does not exist.", info[0]))
			}
		}
		target = &a.Config.AAAConfig.Authentication
	} else {
		target = &a.Config.AAAConfig.Authorization
	}

	if target == nil {
		// Short circuit if there are no existing commands at all, just apply everything requested
		var cmd []*sshAPI.Command
		cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
		for _, newMethod := range *d {
			if t == "authentication" {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("aaa authentication %s %s %s", *newMethod.Name, *newMethod.Type, util.StringSliceToString(newMethod.Options)), 5, prompt))
			} else {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("aaa authorization %s `%s`", *newMethod.Name, func() string {
					var output string
					for i, s := range newMethod.Options {
						if i > 0 {
							output = output + " "
						}
						output = output + s
					}
					return output
				}()), 5, prompt))
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return err
		}
		a.AppendCommands(res)
		return nil
	}

	var cmds []string

	for _, newMethod := range *d {
		found := false
		removeOnly := false

		if len(newMethod.Options) == 0 {
			removeOnly = true
		}

		for _, oldMethod := range *target {
			if *newMethod.Name == *oldMethod.Name && *newMethod.Type == *oldMethod.Type {
				found = true
				// Set the appropriate command
				if !util.StringSliceCompare(newMethod.Options, oldMethod.Options) {
					// Only run if the options have changed
					if t == "authentication" {
						cmds = append(cmds, fmt.Sprintf("no aaa authentication %s %s `%s`", *oldMethod.Name, *oldMethod.Type, util.StringSliceToString(oldMethod.Options)))
						if !removeOnly {
							cmds = append(cmds, fmt.Sprintf("aaa authentication %s %s %s", *newMethod.Name, *newMethod.Type, util.StringSliceToString(newMethod.Options)))
						}
					} else {
						cmds = append(cmds, fmt.Sprintf("no aaa authentication %s `%s`", *oldMethod.Name, util.StringSliceToString(oldMethod.Options)))
						if !removeOnly {
							cmds = append(cmds, fmt.Sprintf("aaa authorization %s `%s`", *newMethod.Name, util.StringSliceToString(newMethod.Options)))
						}
					}
				}
			}
		}

		if !found && !removeOnly {
			// There's no existing entry for this method (http, ssh, etc) Only need to create the new entry:
			if t == "authentication" {
				cmds = append(cmds, fmt.Sprintf("aaa authentication %s %s %s", *newMethod.Name, *newMethod.Type, util.StringSliceToString(newMethod.Options)))
			} else {
				cmds = append(cmds, fmt.Sprintf("aaa authorization %s `%s`", *newMethod.Name, util.StringSliceToString(newMethod.Options)))
			}
		}
	}
	if len(cmds) > 0 {
		var cmd []*sshAPI.Command
		cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
		for _, command := range cmds {
			cmd = append(cmd, sshAPI.ReCommandCreate(command, 5, configPrompt))
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
		_, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return err
		}
		return nil
	}
	return errors.New("No configuration change commands were generated.")
}

func (a *ASA) ObjectNetworkAdd(o *asaModels.NetworkObject) error {
	if o.Type != "network" {
		return errors.New("object.Type needs to be 'network' to add a network object")
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object network %v", o.Name), 5, configPrompt))
	switch o.NetworkType {
	case "host":
		if o.Network == nil {
			return errors.New("type 'host' needs *object.Network to contain an IP")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("host %v", *o.Network), 5, configPrompt))
	case "subnet":
		if o.Network == nil || o.Subnet == nil {
			return errors.New("type 'host' needs *object.Network to contain an IP and *object.Subnet to contain a mask")
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("host %v", *o.Network), 5, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("subnet %v %v", *o.Network, *o.Subnet), 5, configPrompt))
	default:
		return fmt.Errorf("object network type %v not supported", o.Type)
	}
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("description \"%v\"", o.Description), 5, configPrompt))

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)

	return a.GetObjects()
}

func (a *ASA) ObjectNetworkDelete(name string) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no object network %v", name), 5, configPrompt))

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)

	return a.GetObjects()
}

func (a *ASA) ObjectGroupNetworkAdd(o *asaModels.NetworkObjGroup) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group network %s", *o.Name), 5, configPrompt))

	if o.Desc != nil {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("description %s", *o.Desc), 5, configPrompt))
	}

	for _, n := range o.NetworkObj {
		switch n.Type {
		case "network":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("network-object %s %s", *n.Network, *n.Mask), 5, configPrompt))
		case "group-object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("group-object %s", *n.Name), 5, configPrompt))
		case "host":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("network-object host %s", *n.Network), 5, configPrompt))
		case "object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("network-object %s %s", n.Type, *n.Name), 5, configPrompt))
		default:
			return errors.New("No valid object type found")
		}
	}

	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return a.GetObjectGroups()
}

func (a *ASA) ObjectGroupNetworkDelete(o *asaModels.NetworkObjGroup) error {
	if a.Config.ObjectGroups.IsEmpty() {
		if err := a.GetObjectGroups(); err != nil {
			return err
		}
	}
	if !a.Config.ObjectGroups.Exists("network", *o.Name) {
		return errors.New(fmt.Sprintf("object-group network `%s` does not exist.", *o.Name))
	}

	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group network %s", *o.Name), 5, configPrompt))
	for _, netObj := range o.NetworkObj {
		switch netObj.Type {
		case "group-object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		case "network":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", *netObj.Network, *netObj.Mask), 5, configPrompt))
		case "host":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Network), 5, configPrompt))
		case "object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		default:
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if err := a.GetObjectGroups(); err != nil {
		return err
	}
	for _, checkObj := range a.Config.ObjectGroups.Network {
		if *checkObj.Name == *o.Name && checkObj.IsEmpty() {
			if err := a.ObjectGroupDelete("network", *o.Name, false); err != nil {
				return err
			}
		}
	}
	return a.GetObjectGroups()
}

func (a *ASA) ObjectGroupNetworkRemoveNetworkObjects(ogName string, netObjects []*asaModels.NetworkObj) error {
	if !a.Config.ObjectGroups.Exists("network", ogName) {
		return errors.New(fmt.Sprintf("object-group network `%s` does not exist.", ogName))
	}

	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group network %s", ogName), 5, configPrompt))
	for _, netObj := range netObjects {
		switch netObj.Type {
		case "group-object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		case "network":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", *netObj.Network, *netObj.Mask), 5, configPrompt))
		case "host":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Network), 5, configPrompt))
		case "object":
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		default:
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no network-object %s %s", netObj.Type, *netObj.Name), 5, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if err := a.GetObjectGroups(); err != nil {
		return err
	}
	for _, checkObj := range a.Config.ObjectGroups.Network {
		if *checkObj.Name == ogName && checkObj.IsEmpty() {
			if err := a.ObjectGroupDelete("network", ogName, false); err != nil {
				return err
			}
		}
	}
	return a.GetObjectGroups()
}

func (a *ASA) ObjectGroupServiceAdd(o *asaModels.ServiceObjGroup) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	if o.Proto != nil {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group service %s %s", *o.Name, *o.Proto), 5, configPrompt))
	} else {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group service %s", *o.Name), 5, configPrompt))
	}
	if o.Desc != nil {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("description %s", *o.Desc), 5, configPrompt))
	}
	// begin
	var cmdToSend string
	for _, r := range o.ServiceObj {
		// Standard port-object
		if o.Proto != nil {
			cmdToSend = fmt.Sprintf("port-object %v %v", r.MatchType, r.MatchItem)
		} else {
			switch r.Type {
			case "service-object":
				switch r.Proto {
				case "tcp", "udp", "tcp-udp":
					cmdToSend = fmt.Sprintf("service-object %s%s%s",
						r.Proto,
						func() string {
							if r.SourceType != "" {
								return fmt.Sprintf(" source %s %s", r.SourceType, r.SourceItem)
							}
							return ""
						}(),
						func() string {
							if r.DestType != "" {
								return fmt.Sprintf(" destination %s %s", r.DestType, r.DestItem)
							}
							return ""
						}(),
					)
				case "icmp", "icmp6":
					cmdToSend = "service-object icmp"
					if r.DestItem != "" {
						cmdToSend += fmt.Sprintf(" %v", r.DestItem)
					}
				case "ah", "eigrp", "esp", "gre", "igmp", "igrp", "ip", "ipinip", "ipsec", "nos", "ospf", "pcp", "pim", "pptp", "snp":
					cmdToSend = fmt.Sprintf("service-object %v", r.Proto)
				case "object":
					cmdToSend = fmt.Sprintf("service-object object %v", r.Name)
				default:
					return fmt.Errorf("service-object type %v is not yet supported", r.Type)
				}
			default:
				return fmt.Errorf("service-group object type %v is not yet supported", r.Type)
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(cmdToSend, 5, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return a.GetObjectGroups()
}

// ObjectGroupServiceDelete deletes entries from an object group
func (a *ASA) ObjectGroupServiceDelete(o *asaModels.ServiceObjGroup) error {
	if err := a.GetObjectGroups(); err != nil {
		return err
	}
	if !a.Config.ObjectGroups.Exists("service", *o.Name) {
		return errors.New(fmt.Sprintf("object-group service `%s` does not exist.", *o.Name))
	}

	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group service %s%s", *o.Name, func() string {
		if o.Proto != nil {
			return fmt.Sprintf(" %s", *o.Proto)
		}
		return ""
	}()), 5, configPrompt))
	var cmdToSend string
	for _, r := range o.ServiceObj {
		if o.Proto != nil {
			cmdToSend = fmt.Sprintf("no port-object %v %v", r.MatchType, r.MatchItem)
		} else {
			switch r.Type {
			case "service-object":
				switch r.Proto {
				case "tcp", "udp", "tcp-udp":
					cmdToSend = fmt.Sprintf("no service-object %s%s%s",
						r.Proto,
						func() string {
							if r.SourceType != "" {
								return fmt.Sprintf(" source %s %s", r.SourceType, r.SourceItem)
							}
							return ""
						}(),
						func() string {
							if r.DestType != "" {
								return fmt.Sprintf("destination %s %s", r.DestType, r.DestItem)
							}
							return ""
						}(),
					)
				case "icmp", "icmp6":
					cmdToSend = "no service-object icmp"
					if r.DestItem != "" {
						cmdToSend += fmt.Sprintf(" %v", r.DestItem)
					}
				case "ah", "eigrp", "esp", "gre", "igmp", "igrp", "ip", "ipinip", "ipsec", "nos", "ospf", "pcp", "pim", "pptp", "snp":
					cmdToSend = fmt.Sprintf("no service-object %v", r.Proto)
				case "object":
					cmdToSend = fmt.Sprintf("no service-object object %v", r.Name)
				default:
					return fmt.Errorf("service-object type %v is not yet supported", r.Type)
				}
			default:
				return fmt.Errorf("service-group object type %v is not yet supported", r.Type)
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(cmdToSend, 5, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if err := a.GetObjectGroups(); err != nil {
		return err
	}
	for _, checkObj := range a.Config.ObjectGroups.Service {
		if *checkObj.Name == *o.Name && checkObj.IsEmpty() {
			if err := a.ObjectGroupDelete("service", *o.Name, false); err != nil {
				return err
			}
		}
	}
	return a.GetObjectGroups()
}

// ObjectGroupDelete deletes an entire object group from the ASA
func (a *ASA) ObjectGroupDelete(t string, g string, refresh bool) error {
	if err := a.ObjectGroupInUse(g); err != nil {
		return err
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no object-group %s %s", t, g), 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if refresh {
		return a.GetObjectGroups()
	}
	return nil
}

// ObjectGroupDescription sets the description for an object group to the input string
func (a *ASA) ObjectGroupDescription(groupName, groupType, desc string, refresh bool) error {
	if !a.Config.ObjectGroups.Exists(groupType, groupName) {
		return fmt.Errorf("Group %s does not exist as type %s.", groupName, groupType)
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object-group %s %s", groupType, groupName), 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("description %s", desc), 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	if refresh {
		return a.GetObjectGroups()
	}
	return nil
}

func (a *ASA) ACLFindHost(host string) []*asaModels.AccessList {
	return asaModels.ACLReSearch(fmt.Sprintf("(?i)host\\s+(%s)(\\s+|$)", host), a.Config.AccessLists)
}

func (a *ASA) ACLFindObject(object string) []*asaModels.AccessList {
	return asaModels.ACLReSearch(fmt.Sprintf("(?i)object\\s+(%s)(\\s+|$)", object), a.Config.AccessLists)
}

func (a *ASA) ACLFindGroup(group string) []*asaModels.AccessList {
	return asaModels.ACLReSearch(fmt.Sprintf("(?i)object-group\\s+(%s)(\\s+|$)", group), a.Config.AccessLists)
}

func (a *ASA) ARPFindIP(ip string) *asaModels.ARPEntry {
	return asaModels.FindIP4(ip, a.Status.ARP)
}

func (a *ASA) ACLRemoveObject(object string) {
	asaModels.ACLReRemove(fmt.Sprintf("(?i)object\\s+(%s)($|\\s+)", object), a.Config.AccessLists)
}

func (a *ASA) ACLRemoveGroup(group string) {
	asaModels.ACLReRemove(fmt.Sprintf("(?i)object-group\\s+(%s)($|\\s+)", group), a.Config.AccessLists)
}

func (a *ASA) ACLRemoveHost(host string) {
	asaModels.ACLReRemove(fmt.Sprintf("(?i)host\\s+(%s)($|\\s+)", host), a.Config.AccessLists)
}

func (a *ASA) ACLAddEntry(newacls []*asaModels.AccessList) error {

	if len(newacls) == 0 {
		return nil
	}

	var cmd []*sshAPI.Command

	// refresh our ACL's so we can check for a deny line
	a.GetAccessLists()

	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	var targetACL *asaModels.AccessList
	for _, accesslist := range newacls {
		for _, existing := range a.Config.AccessLists {
			if existing.Name == accesslist.Name {
				targetACL = existing
			}
		}
		for _, ace := range accesslist.Parsed {
			// access-list <name> (line #) <action> <proto> <srcnet> (srcprt) <dstnet> (dstport) (log) (inactive)
			if targetACL != nil {
				if *targetACL.Rules[len(targetACL.Rules)-1] == "extended deny ip any any" && (ace.Line == 0 || ace.Line > len(targetACL.Rules)-1) {
					ace.Line = len(targetACL.Rules)
					a.log.Println("Found deny all rule at end of target ACL, modifying line to insert above it. New line Number:", ace.Line)
				}
			}
			command := fmt.Sprintf("access-list %v%v%v%v%v%v%v%v%v%v%v",
				accesslist.Name,
				// Line # if not 0
				func() string {
					if ace.Line != 0 {
						return fmt.Sprintf(" line %v", ace.Line)
					}
					return ""
				}(),
				// Action
				fmt.Sprintf(" %v", ace.Rule.Action),
				// Protocol
				func() string {
					if ace.Rule.Proto.Type == "object-group" {
						return " object-group"
					}
					return ""
				}(),
				fmt.Sprintf(" %v", *ace.Rule.Proto.Name),
				// source
				func() string {
					if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.SrcNet.Type) {
						return fmt.Sprintf(" %s", ace.Rule.SrcNet.Type)
					}
					if ace.Rule.SrcNet.Type == "ipv4" {
						return fmt.Sprintf(" %v %v", ace.Rule.SrcNet.Network, ace.Rule.SrcNet.Netmask)
					}
					return fmt.Sprintf(" %v %v", ace.Rule.SrcNet.Type, ace.Rule.SrcNet.Name)
				}(),
				// source port if not nil
				func() string {
					if ace.Rule.SrcPort != nil {
						if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.SrcPort.Type) {
							return fmt.Sprintf(" %v", ace.Rule.SrcPort.Type)
						}
						return fmt.Sprintf(" %v %v", ace.Rule.SrcPort.Type, ace.Rule.SrcPort.Item)
					}
					return ""
				}(),
				// Destination
				func() string {
					if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.DstNet.Type) {
						return fmt.Sprintf(" %v", ace.Rule.DstNet.Type)
					}
					if ace.Rule.DstNet.Type == "ipv4" {
						return fmt.Sprintf(" %v %v", ace.Rule.DstNet.Network, ace.Rule.DstNet.Netmask)
					}
					return fmt.Sprintf(" %v %v", ace.Rule.DstNet.Type, ace.Rule.DstNet.Name)
				}(),
				// Destination port if not nil
				func() string {
					if ace.Rule.DstPort != nil {
						if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.DstPort.Type) {
							return fmt.Sprintf(" %v", ace.Rule.DstPort.Type)
						}
						return fmt.Sprintf(" %v %v", ace.Rule.DstPort.Type, ace.Rule.DstPort.Item)
					}
					return ""
				}(),
				func() string {
					if ace.Rule.Log {
						return " log"
					}
					return ""
				}(),
				func() string {
					if ace.Rule.Inactive {
						return " inactive"
					}
					return ""
				}())

			cmd = append(cmd, sshAPI.ReCommandCreate(command, 10, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	// reset our active config and refetch the live acl's
	var ac []*asaModels.AccessList
	a.Config.AccessLists = ac
	return a.GetAccessLists()
}

func (a *ASA) ACLDeleteEntry(delacls []*asaModels.AccessList) error {
	if len(delacls) == 0 {
		return nil
	}

	var cmd []*sshAPI.Command

	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, accesslist := range delacls {
		for _, ace := range accesslist.Parsed {
			// access-list <name> <action> <proto> <srcnet> (srcprt) <dstnet> (dstport)
			command := fmt.Sprintf("no access-list %v%v%v%v%v%v%v",
				accesslist.Name,
				// Action
				fmt.Sprintf(" %v", ace.Rule.Action),
				// Protocol
				fmt.Sprintf(" %v", *ace.Rule.Proto.Name),
				// source
				func() string {
					if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.SrcNet.Type) {
						return fmt.Sprintf(" %s", ace.Rule.SrcNet.Type)
					}
					if ace.Rule.SrcNet.Type == "ipv4" {
						return fmt.Sprintf(" %v %v", ace.Rule.SrcNet.Network, ace.Rule.SrcNet.Netmask)
					}
					return fmt.Sprintf(" %v %v", ace.Rule.SrcNet.Type, ace.Rule.SrcNet.Name)
				}(),
				// source port if not nil
				func() string {
					if ace.Rule.SrcPort != nil {
						if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.SrcPort.Type) {
							return fmt.Sprintf(" %v", ace.Rule.SrcPort.Type)
						}
						return fmt.Sprintf(" %v %v", ace.Rule.SrcPort.Type, ace.Rule.SrcPort.Item)
					}
					return ""
				}(),
				// Destination
				func() string {
					if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.DstNet.Type) {
						return fmt.Sprintf(" %v", ace.Rule.DstNet.Type)
					}
					if ace.Rule.DstNet.Type == "ipv4" {
						return fmt.Sprintf(" %v %v", ace.Rule.DstNet.Network, ace.Rule.DstNet.Netmask)
					}
					return fmt.Sprintf(" %v %v", ace.Rule.DstNet.Type, ace.Rule.DstNet.Name)
				}(),
				// Destination port if not nil
				func() string {
					if ace.Rule.DstPort != nil {
						if regexp.MustCompile(`(?i)(any4|any6|any)`).MatchString(ace.Rule.DstPort.Type) {
							return fmt.Sprintf(" %v", ace.Rule.DstPort.Type)
						}
						return fmt.Sprintf(" %v %v", ace.Rule.DstPort.Type, ace.Rule.DstPort.Item)
					}
					return ""
				}())

			cmd = append(cmd, sshAPI.ReCommandCreate(command, 10, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	_, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	// reset our active config and refetch the live acl's
	var ac []*asaModels.AccessList
	a.Config.AccessLists = ac
	return a.GetAccessLists()
}

func (a *ASA) GetEmptyNetworkGroups() []*asaModels.NetworkObjGroup {
	return a.Config.ObjectGroups.GetEmptyNetworkGroups()
}

func (a *ASA) EmptyAccessLists() []*asaModels.AccessList {
	return asaModels.EmptyACLs(a.Config.AccessLists)
}

func (a *ASA) Active() bool {
	if err := a.IsActive(); err == nil {
		return true
	}
	return false
}

func (a *ASA) IsActive() error {
	if a.Status.Failover == nil {
		var fo asaModels.FailoverStatus
		a.Status.Failover = &fo
		if err := a.GetFailover(); err != nil {
			return err
		}
	}
	return a.Status.Failover.IsActive()
}

func (a *ASA) IsStandby() error {
	if a.Status.Failover == nil {
		var fo asaModels.FailoverStatus
		a.Status.Failover = &fo
		if err := a.GetFailover(); err != nil {
			return err
		}
	}
	return a.Status.Failover.IsStandby()
}

func (a *ASA) HasDynamicMap() bool {
	return a.Config.Crypto.HasDynamic()
}

func (a *ASA) RemoteTunnels() []*asaModels.TunnelGroup {
	return asaModels.RemoteTunnels(a.Config.TunnelGroups)

}

func (a *ASA) RemoteTunnelNames() []string {
	return asaModels.RemoteTunnelNames(a.Config.TunnelGroups)
}

func (a *ASA) NonPrivilegedUsers(pw bool) []*asaModels.UserName {
	return asaModels.NonPrivilegedUsers(a.Config.UserNames, pw)
}

func (a *ASA) IsLegacy() (bool, error) {
	if a.Status.Version == nil {
		if err := a.GetVersion(); err != nil {
			return false, err
		}
	}
	if a.Status.Version.SoftwareVer == nil {
		return false, errors.New("Error parsing software version")
	}
	return a.Status.Version.LegacyASA(), nil
}

func (a *ASA) ConfigRegValid() error {
	if a.Status.Version == nil {
		if err := a.GetVersion(); err != nil {
			return err
		}
	}
	if a.Status.Version.ConfReg == "" {
		return errors.New("unable to parse config register")
	}
	if a.Status.Version.ConfReg != "0x1" {
		return errors.New("")
	}
	return nil
}

func (a *ASA) FindGroupPolicy(policy string) *asaModels.GroupPolicy {
	return asaModels.FindPolicy(policy, a.Config.GroupPolicies)
}

func (a *ASA) FindCryptoPeer(peer string) *asaModels.MapEntry {
	return a.Config.Crypto.FindCryptoPeer(peer)
}

func (a *ASA) LogoffVPNSession(id string) bool {
	if !VALIDRE.MatchString(id) {
		return false
	}
	cmd, err := a.sshClient.PostReCommand("vpn-sessiondb logoff name "+id+" noconfirm", prompt, 10)
	if err != nil {
		a.log.Println(err)
		return false
	}
	if LOGOFFRE.MatchString(*cmd.Response) {
		return true
	}

	return false
}

func (a *ASA) VersionSoftware() (string, error) {
	if a.Status.Version == nil {
		if err := a.GetVersion(); err != nil {
			return "", err
		}
	}
	return a.Status.Version.SysImageFile, nil
}

func (a *ASA) VersionString() (*string, error) {
	if a.Status.Version == nil {
		if err := a.GetVersion(); err != nil {
			return nil, err
		}
	}
	if a.Status.Version.SoftwareVer == nil {
		return nil, errors.New("Unable to parse software version")
	}
	return a.Status.Version.SoftwareVer.Version, nil
}

func (a *ASA) ConfReg() (string, error) {
	if a.Status.Version == nil {
		if err := a.GetVersion(); err != nil {
			return "", err
		}
	}
	return a.Status.Version.ConfReg, nil
}

func (a *ASA) VerifyFile(method, file string) (*asaModels.Verify, error) {
	if method != "md5" && method != "sha-512" && method != "" {
		return nil, fmt.Errorf("%s is not a valid verify method", method)
	}
	if method != "" {
		method = "/" + method
	}
	cmd, err := a.sshClient.PostReCommand(fmt.Sprintf("verify %s %s", method, file), prompt, 60)
	if err != nil {
		return nil, err
	}
	if err := asaRespHandler(*cmd.Response); err != nil {
		return nil, err
	}
	var v asaModels.Verify
	v.Parse(*cmd.Response)
	return &v, nil
}

func (a *ASA) VerifyFile_HTTP(method, file string) (*asaModels.Verify, error) {
	if method != "md5" && method != "sha-512" && method != "" {
		return nil, fmt.Errorf("%s is not a valid verify method", method)
	}
	if method != "" {
		method = "/" + method
	}
	cmd, err := a.httpClient.GetCommand(fmt.Sprintf("verify %s %s", method, file))
	if err != nil {
		return nil, err
	}
	if err := asaRespHandler(cmd); err != nil {
		return nil, err
	}
	var v asaModels.Verify
	v.Parse(cmd)
	return &v, nil
}

func (a *ASA) FileExist(fn string) (bool, error) {
	if a.Status.Disk == nil {
		if err := a.ListDisk(); err != nil {
			return false, err
		}
	}
	return a.Status.Disk.FileExist(fn), nil
}

func (a *ASA) RemoveBoot(order int) error {
	if order == -1 {
		return a.RemoveAllBootFiles()
	}
	return a.RemoveBootItem(order)
}

func (a *ASA) RemoveBootItem(order int) error {
	fn := asaModels.FindBootImage(order, a.Config.BootList)
	if fn == nil {
		return errors.New("File order not found in list")
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no boot system %s", *fn), 10, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return a.ListDisk()
}

func (a *ASA) RemoveAllBootFiles() error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, i := range a.Config.BootList {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no boot system %s", i.DiskImage), 10, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return a.BootList()
}

func (a *ASA) RemoveBootFiles(files []string) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, i := range files {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no boot system %s", i), 10, configPrompt))
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return a.BootList()
}

func (a *ASA) SetBoot(image string) error {
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, prompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("boot system disk0:/%s", image), 10, prompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return fmt.Errorf("SetBoot/sshPost/%v", err)
	}

	for _, c := range res.Commands {
		if err := asaRespHandler(*c.Response); err != nil {
			return err
		}
	}

	a.AppendCommands(res)
	return a.BootList()
}

func (a *ASA) BootFileIndex(fn string) (bool, int) {
	return asaModels.FindBootFile(fn, a.Config.BootList)
}

func (a *ASA) EnableScopy() error {
	if a.Config.SSHConfig.Scopy == true {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("ssh scopy enable", 10, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	var s asaModels.SSHConfig
	a.Config.SSHConfig = s
	return a.GetSSHConfig()
}

func (a *ASA) DisableScopy() error {
	if a.Config.SSHConfig.Scopy == false {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("no ssh scopy enable", 10, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))

	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	var s asaModels.SSHConfig
	a.Config.SSHConfig = s
	return a.GetSSHConfig()
}

func (a *ASA) SSHTimeout(t int) error {
	if t < 1 || t > 60 {
		return errors.New("Idle Time must be between 1 and 60")
	}
	if a.Config.SSHConfig.Timeout == t {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ssh timeout %d", t), 10, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	var s asaModels.SSHConfig
	a.Config.SSHConfig = s
	return a.GetSSHConfig()
}

func (a *ASA) SSHAddHosts(hosts []*asaModels.SSHHost) error {
	if len(hosts) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, h := range hosts {
		if err := h.Valid(); err == nil {
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ssh %s %s %s", h.Network, h.Netmask, h.IfName), 10, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	var s asaModels.SSHConfig
	a.Config.SSHConfig = s
	return a.GetSSHConfig()
}

func (a *ASA) SSHDeleteHosts(hosts []*asaModels.SSHHost) error {
	if len(hosts) == 0 {
		return nil
	}
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, h := range hosts {
		if err := h.Valid(); err == nil {
			cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no ssh %s %s %s", h.Network, h.Netmask, h.IfName), 10, configPrompt))
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	var s asaModels.SSHConfig
	a.Config.SSHConfig = s
	return a.GetSSHConfig()
}

func (a *ASA) AddAAAGroups(groups *[]*asaModels.AAAGroup) error {
	// a.Config.AAAServers = append(a.Config.AAAServers, server)
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, group := range *groups {
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("aaa-server %s protocol %s", group.Name, group.Protocol), 10, configPrompt))
		if group.Options != nil {
			if group.Options.ReactivationMode != nil {
				if group.Options.ReactivationMode.Mode == "depletion" {
					cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("reactivation-mode depletion deadtime %v", group.Options.ReactivationMode.Deadtime), 10, configPrompt))
				} else {
					cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("reactivation-mode %s", group.Options.ReactivationMode.Mode), 10, configPrompt))
				}
			}
			if group.Options.MaxFailedAttempts != nil {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("max-failed-attempts %v", *group.Options.MaxFailedAttempts), 10, configPrompt))
			}
			if group.Options.AccountingMode != nil {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("accounting-mode %v", *group.Options.AccountingMode), 10, configPrompt))
			}
			if group.Options.ADAgentMode {
				cmd = append(cmd, sshAPI.ReCommandCreate("ad-agent-mode", 10, configPrompt))
			}
			if group.Options.IntrmAcntngUpdt {
				cmd = append(cmd, sshAPI.ReCommandCreate("interim-accounting-update", 10, configPrompt))
			}
			if group.Options.MergeDacl != nil {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("merge-dacl %v", *group.Options.AccountingMode), 10, configPrompt))
			}
		}
		if group.Hosts != nil {
			for _, host := range group.Hosts {
				cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("aaa-server %s (%s) host %s", group.Name, host.Interface, host.Address), 10, configPrompt))
				if host.Options != nil {
					if host.Options.Timeout != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("timeout %v", *host.Options.Timeout), 10, configPrompt))
					}
					if host.Options.Key != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("key %s", *host.Options.Key), 10, configPrompt))
					}
					if host.Options.GroupSearchTimeout != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("group-search-timeout %v", *host.Options.GroupSearchTimeout), 10, configPrompt))
					}
					if host.Options.ProxyAuthMap != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("proxy-auth_map %s", *host.Options.ProxyAuthMap), 10, configPrompt))
					}
					if host.Options.ServerPort != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("server-port %v", *host.Options.ServerPort), 10, configPrompt))
					}
					// LDAP Options
					if host.Options.ServerType != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("server-type %v", *host.Options.ServerType), 10, configPrompt))
					}
					if host.Options.LdapAttributeMap != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-attribute-map %v", *host.Options.LdapAttributeMap), 10, configPrompt))
					}
					if host.Options.LdapBaseDn != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-base-dn %v", *host.Options.LdapBaseDn), 10, configPrompt))
					}
					if host.Options.LdapGroupBaseDn != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-group-base-dn %v", *host.Options.LdapGroupBaseDn), 10, configPrompt))
					}
					if host.Options.LdapLoginDn != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-login-dn %v", *host.Options.LdapLoginDn), 10, configPrompt))
					}
					if host.Options.LdapLoginPassword != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-login-password %v", *host.Options.LdapLoginPassword), 10, configPrompt))
					}
					if host.Options.LdapNamingAttribute != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-naming-attribute %v", *host.Options.ServerPort), 10, configPrompt))
					}
					if host.Options.LdapOverSSL {
						cmd = append(cmd, sshAPI.ReCommandCreate("ldap-over-ssl", 10, configPrompt))
					}
					if host.Options.LdapScope != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("ldap-scope %v", *host.Options.LdapScope), 10, configPrompt))
					}
					if host.Options.SaslMechanism != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("sasl-mechanism %v", *host.Options.SaslMechanism), 10, configPrompt))
					}
					// RADIUS Options
					if host.Options.AccountingPort != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("accounting-port %v", *host.Options.AccountingPort), 10, configPrompt))
					}
					if host.Options.ACLNetmaskConvert != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("acl-netmask-convert %v", *host.Options.ACLNetmaskConvert), 10, configPrompt))
					}
					if host.Options.AuthenticationPort != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("authentication-port %v", *host.Options.AuthenticationPort), 10, configPrompt))
					}
					if host.Options.Mschapv2Capable {
						cmd = append(cmd, sshAPI.ReCommandCreate("mschapv2-capable", 10, configPrompt))
					}
					if host.Options.RadiusCommonPw != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("radius-common-pw %v", *host.Options.RadiusCommonPw), 10, configPrompt))
					}
					if host.Options.RetryInterval != nil {
						cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("retry-interval %v", *host.Options.RetryInterval), 10, configPrompt))
					}
				}
			}
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	a.Config.AAAServers = new(asaModels.AAAGroups)
	return a.GetAAAServers()
}

func (a *ASA) DeleteAAAGroups(servers *[]*asaModels.AAAGroup) error {
	// Make sure we have the list of groups in memory
	if a.Config.AAAServers == nil {
		a.GetAAAServers()
	}
	var newGroups asaModels.AAAGroups
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 5, configPrompt))
	for _, existingGroup := range a.Config.AAAServers.Groups {
		found := false
		for _, groupToDelete := range *servers {
			if groupToDelete.Name == existingGroup.Name {
				found = true
				// Found the group we're working on
				if groupToDelete.Hosts != nil {
					// We're deleting just specific hosts
					var newHosts []*asaModels.AAAHost
					for _, hostToDelete := range groupToDelete.Hosts {
						for _, existingHost := range existingGroup.Hosts {
							if existingHost.Address == hostToDelete.Address && existingHost.Interface == hostToDelete.Interface {
								// found the host, skip to next run of loop to 'delete' this host.
								cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no aaa-server %s (%s) host %s", groupToDelete.Name, hostToDelete.Interface, hostToDelete.Address), 10, configPrompt))
							} else {
								newHosts = append(newHosts, existingHost)
							}
						}
					}
					existingGroup.Hosts = newHosts
					newGroups.Groups = append(newGroups.Groups, existingGroup)
				} else {
					// We're deleting the entire group
					cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("no aaa-server %s protocol %s", groupToDelete.Name, groupToDelete.Protocol), 10, configPrompt))
				}
			}
		}
		if !found {
			newGroups.Groups = append(newGroups.Groups, existingGroup)
		}
	}
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 5, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	a.AppendCommands(res)

	// Clean up our groups and make sure we don't return one of the ones we just deleted.
	var blank []*asaModels.AAAGroup
	a.Config.AAAServers.Groups = blank
	for _, newGroup := range newGroups.Groups {
		found := false
		for _, deleteGroup := range *servers {
			if newGroup.Name == deleteGroup.Name && newGroup.Protocol == deleteGroup.Protocol {
				found = true
			}
		}
		if !found {
			a.Config.AAAServers.Groups = append(a.Config.AAAServers.Groups, newGroup)
		}
	}
	return nil
}

func (a *ASA) ReloadStandby() error {
	// sleep before failover active, to let console catup
	time.Sleep(2 * time.Second)
	cmd, err := a.sshClient.PostReCommand("failover reload-standby", prompt, 5)
	if err != nil {
		return fmt.Errorf("ReloadStandby/sshPost/%v", err)
	}
	if err := asaRespHandler(*cmd.Response); err != nil {
		return err
	}
	a.AppendCommand(cmd)
	return nil
}

func (a *ASA) Reload() error {
	cmd, err := a.sshClient.PostReCommand("reload noconfirm", prompt, 2)
	if err != nil {
		return fmt.Errorf("Reload/sshPost/%v", err)
	}
	if err := asaRespHandler(*cmd.Response); err != nil {
		return err
	}
	a.AppendCommand(cmd)
	return nil
}

func (a *ASA) GetLevel(sec int) (m []string) {
	return
}

func (a *ASA) AccessGroupACL(match string) *string {
	return asaModels.FindAccessGroup(match, a.Config.AccessGroups)
}

func (a *ASA) AccessGroupIfNameByACL(match string) []*asaModels.AccessGroup {
	return asaModels.FindACL(match, a.Config.AccessGroups)
}

func (a *ASA) AccessGroupACLByIfname(ifname string) *string {
	ag := asaModels.FindIfName(ifname, a.Config.AccessGroups)
	if ag != nil {
		return &ag.AccessList
	}
	return nil
}

func (a *ASA) InterfaceSecMatch(sec int) (_ []string) {
	return asaModels.SecLevelFind(sec, a.Config.Interfaces)
}

func (a *ASA) FindInterfaceConfig(name string) *asaModels.InterfaceConfig {
	return asaModels.FindInterfaceConfig(name, a.Config.Interfaces)
}

func (a *ASA) FindInterfaceStatus(name string) *asaModels.InterfaceStatus {
	return asaModels.FindInterfaceStatus(name, a.Status.Interfaces)
}

func (a *ASA) FindInterfaceByIfName(name string) *string {
	if len(a.Config.Interfaces) == 0 {
		if err := a.GetInterfaces(); err != nil {
			return nil
		}
	}
	for _, i := range a.Config.Interfaces {
		if i.Nameif != nil {
			if *i.Nameif == name {
				return &i.Name
			}
		}
		for _, si := range i.SubInt {
			if si.Nameif != nil {
				if *si.Nameif == name {
					return &si.Name
				}
			}
		}
	}
	return nil
}

func (a *ASA) FindInterfacesByIfName(name string) (ic []*asaModels.InterfaceConfig) {
	if len(a.Config.Interfaces) == 0 {
		if err := a.GetInterfaces(); err != nil {
			return nil
		}
	}
	for _, i := range a.Config.Interfaces {
		if i.Nameif != nil {
			if *i.Nameif == name {
				ic = append(ic, i)
			}
		}
		for _, si := range i.SubInt {
			if si.Nameif != nil {
				if *si.Nameif == name {
					ic = append(ic, &asaModels.InterfaceConfig{
						Name:     si.Name,
						SecLevel: si.SecLevel,
						Nameif:   si.Nameif,
					})
				}
			}
		}
	}
	return ic
}

func (a *ASA) FindInterfacesBySecurityLevel(level int) (ic []*asaModels.InterfaceConfig) {
	if len(a.Config.Interfaces) == 0 {
		if err := a.GetInterfaces(); err != nil {
			return nil
		}
	}
	for _, i := range a.Config.Interfaces {
		if i.SecLevel != nil {
			if *i.SecLevel == level {
				ic = append(ic, i)
			}
		}
		for _, si := range i.SubInt {
			if si.SecLevel != nil {
				if *si.SecLevel == level {
					ic = append(ic, &asaModels.InterfaceConfig{
						Name:     si.Name,
						SecLevel: si.SecLevel,
						Nameif:   si.Nameif,
					})
				}
			}
		}
	}
	return ic
}

func (a *ASA) VlanTrunkInterfaces(vlan int) []*asaModels.InterfaceConfig {
	return asaModels.VlanTrunkInterfaces(vlan, a.Config.Interfaces)
}

func (a *ASA) VlanActiveInterfaces(vlan int) []*asaModels.InterfaceConfig {
	return asaModels.VlanActiveInterfaces(vlan, a.Config.Interfaces)
}

func (a *ASA) FindACL(acl string) *asaModels.AccessList {
	for _, a := range a.Config.AccessLists {
		if a.Name == acl {
			return a
		}
	}
	return nil
}

func (a *ASA) FindStatic(public, private string) *asaModels.StaticNat {
	for _, static := range a.Config.Statics {
		if static.PublicIp == public && static.PrivateIp == private {
			return static
		}
	}
	return nil

}

func (a *ASA) FindNat(public, private string) (*asaModels.NetworkObject, *asaModels.Auto) {
	for _, o := range a.Config.Objects.FindHost(private) {
		auto := a.Config.Nat.FindAutoNat(o.Name)
		if auto != nil {
			return o, auto
		}
	}
	return nil, nil
}

func (a *ASA) FindTunnelGroup(group string) *asaModels.TunnelGroup {
	for _, t := range a.Config.TunnelGroups {
		if t.Name == group {
			return t
		}
	}
	return nil
}

func (a *ASA) CreateNAT(n asaModels.Nat) error {
	for _, nat := range n.Auto {
		var cmd []*sshAPI.Command
		cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 15, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object network %s", nat.Name), 15, configPrompt))
		cmdStr := fmt.Sprintf("nat (%s,%s) %s %s", nat.Source, nat.Destination, nat.Type, nat.Xlate)
		switch {
		case nat.Dns:
			cmdStr = cmdStr + " dns"
		case nat.Service != nil:
			cmdStr = cmdStr + fmt.Sprintf(" service %s ", nat.Service.Protocol)
			for _, p := range nat.Service.Ports {
				cmdStr = cmdStr + fmt.Sprintf("%s ", p)
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(cmdStr, 15, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return err
		}
		if err := invalidInput(*res); err != nil {
			return err
		}
		a.AppendCommands(res)
	}
	return nil
}

func (a *ASA) CopyToStartup(file string) error {
	cmd, err := a.sshClient.PostReCommand(fmt.Sprintf("copy /noconfirm %s startup-config", file), prompt, 60)
	if err != nil {
		return err
	}
	a.AppendCommand(cmd)
	return nil
}

func (a *ASA) CopyRunningToDisk(file string) error {
	cmd, err := a.sshClient.PostReCommand(fmt.Sprintf("copy /noconfirm running-config  %s", file), prompt, 60)
	if err != nil {
		return err
	}
	a.AppendCommand(cmd)
	return nil
}

func (a *ASA) DeleteNAT(n asaModels.Nat) error {
	for _, nat := range n.Auto {
		var cmd []*sshAPI.Command
		cmd = append(cmd, sshAPI.ReCommandCreate("conf t", 15, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate(fmt.Sprintf("object network %s", nat.Name), 15, configPrompt))
		cmdStr := fmt.Sprintf("no nat (%s,%s) %s %s", nat.Source, nat.Destination, nat.Type, nat.Xlate)
		switch {
		case nat.Dns:
			cmdStr = cmdStr + " dns"
		case nat.Service != nil:
			cmdStr = cmdStr + fmt.Sprintf(" service %s ", nat.Service.Protocol)
			for _, p := range nat.Service.Ports {
				cmdStr = cmdStr + fmt.Sprintf("%s ", p)
			}
		}
		cmd = append(cmd, sshAPI.ReCommandCreate(cmdStr, 15, configPrompt))
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return err
		}
		if err := invalidInput(*res); err != nil {
			return err
		}
		a.AppendCommands(res)
	}
	return nil
}

func invalidInput(s sshAPI.SSHCommands) error {
	for _, cmd := range s.Commands {
		if strings.Contains(strings.ToLower(*cmd.Response), "invalid") {
			return errors.New(*cmd.Response)
		}
	}
	return nil
}

func (a *ASA) HttpConfigAdd(nw string, nm string, iface string) (*asaModels.HTTPAllowed, error) {
	var cmd []*sshAPI.Command
	var cmdStr []string
	var added *asaModels.HTTPAllowed

	//add entry to the add command list only if it doesn't exist on the FW already
	existsOnFW := false
	for _, alwd := range a.Config.HTTP.Allowed {
		if alwd.Net == nw && alwd.Mask == nm && alwd.Int == iface {
			existsOnFW = true
			break
		}
	}
	if !existsOnFW {
		cmdStr = append(cmdStr, fmt.Sprintf("http %v %v %v", nw, nm, iface))
	}

	// If there are changes to be made, build the SSHAPI command struct
	if len(cmdStr) > 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, prompt))
		for _, c := range cmdStr {
			cmd = append(cmd, sshAPI.ReCommandCreate(c, 15, prompt))
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))

		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return nil, err
		}
		if err := invalidInput(*res); err != nil {
			return nil, err
		}
		a.AppendCommands(res)
		return added, nil
	}

	return nil, nil
}

func (a *ASA) HttpConfigPOST(http asaModels.HTTPConfig) ([]*asaModels.HTTPAllowed, error) {
	var cmd []*sshAPI.Command
	var cmdStr []string
	var add []*asaModels.HTTPAllowed

	// If the HTTP server port is NOT specified in the call
	if http.Server.Port == 0 {
		if a.Config.HTTP.Server.Port != 0 {
			// If the HTTP server port is specified on the ASA,
			// use the ASA configured http port
			http.Server.Port = a.Config.HTTP.Server.Port
		} else {
			// If the HTTP server port is not configured on the ASA,
			// select a default port based on single or context mode
			if err := a.GetContext(); err != nil {
				return nil, errors.New("Unable to get context status for auto http port determination")
			}
			if a.Status.Context.Mode == "multiple" {
				http.Server.Port = 443
			} else {
				http.Server.Port = 8443
			}
		}
	}

	if (http.Server.Enabled == true) && (a.Config.HTTP.Server.Enabled == false) {
		// If the POST data is enabling HTTP and the device does not have it enabled
		cmdStr = append(cmdStr, fmt.Sprintf("http server enable %v", http.Server.Port))

	} else if (http.Server.Enabled == false) && (a.Config.HTTP.Server.Enabled == true) {
		// If the POST data is disabling HTTP and the device has HTTP enabled
		cmdStr = append(cmdStr, "no http server enable")

	} else if http.Server.Port != a.Config.HTTP.Server.Port {
		// If the POST data has a configured HTTP server port that does not match what is on the device.
		cmdStr = append(cmdStr, fmt.Sprintf("http server enable %v", http.Server.Port))
	}

	// Gather route table info for default interface selection (using the default route interface)
	if err := a.GetRoutes(); err != nil {
		return nil, errors.New("Unable to get default route for auto http allowed interface determination")
	}
	defaultInt := ""
	for _, route := range a.Status.Routes {
		if route.LastResort == true {
			defaultInt = route.IfName
		}
	}

HTTPAllowed:
	for _, allowed := range http.Allowed {
		// Iterate through the list of HTTP allowed hosts.  If any
		// host does NOT have an interface configured, use the interface
		// associated with the default route.
		if allowed.Int == "" {
			if defaultInt == "" {
				return nil, errors.New("Unable to get default route for auto http allowed interface determination")
			}
			allowed.Int = defaultInt
		}
		for _, alwd := range a.Config.HTTP.Allowed {
			// Here, we check to see if the specific HTTP allowed host entry matches any of the
			// already configured hosts on the device, if found, skip the ADD command and
			// move to the next entry.
			if (alwd.Net == allowed.Net) && (alwd.Mask == allowed.Mask) && (alwd.Int == allowed.Int) {
				continue HTTPAllowed
			}
		}
		add = append(add, &asaModels.HTTPAllowed{Net: allowed.Net, Mask: allowed.Mask, Int: allowed.Int})
		cmdStr = append(cmdStr, fmt.Sprintf("http %v %v %v", allowed.Net, allowed.Mask, allowed.Int))
	}

	// If there are changes to be made, build the SSHAPI command struct
	if len(cmdStr) > 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, prompt))
		for _, c := range cmdStr {
			cmd = append(cmd, sshAPI.ReCommandCreate(c, 15, prompt))
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))

		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return nil, err
		}
		if err := invalidInput(*res); err != nil {
			return nil, err
		}
		a.AppendCommands(res)
		return add, nil
	}

	return nil, nil
}

func (a *ASA) HttpConfigDELETE(nw string, nm string) (*asaModels.HTTPAllowed, error) {
	var cmd []*sshAPI.Command
	var cmdStr []string
	var del *asaModels.HTTPAllowed

	for _, alwd := range a.Config.HTTP.Allowed {
		// add entry to the del command list if it exists on the FW
		if (alwd.Net == nw) && (alwd.Mask == nm) {
			del = &asaModels.HTTPAllowed{
				Net:  nw,
				Mask: nm,
				Int:  alwd.Int,
			}
			cmdStr = append(cmdStr, fmt.Sprintf("no http %v %v %v", nw, nm, alwd.Int))
		}
	}

	// If there are changes to be made, build the SSHAPI command struct
	if len(cmdStr) > 0 {
		cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, prompt))
		for _, c := range cmdStr {
			cmd = append(cmd, sshAPI.ReCommandCreate(c, 15, prompt))
		}
		cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))

		res, err := a.sshClient.PostCommands(cmd)
		if err != nil {
			return nil, err
		}
		if err := invalidInput(*res); err != nil {
			return nil, err
		}
		a.AppendCommands(res)
		return del, nil
	}

	return nil, nil
}

func (a *ASA) SNMPAddServer(s *asaModels.SNMPServer, errorOnExisting bool) error {
	//retrieve SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for duplicate server
	for _, svr := range a.Config.SNMP.SnmpServers {
		if svr.IpAddress == s.IpAddress && errorOnExisting {
			return fmt.Errorf("Server '%s' already exists.", s.IpAddress)
		}
	}
	//make sure needed info is present and valid
	//host is populated
	if s.IpAddress == "" {
		return errors.New("Host IP (or name) needs to be populated.")
	}
	//	(interface exists temporarily removed because GetInterfaces is not returning all interfaces.)
	// For now we'll catch incorrect interfaces with the "invalid" check below.
	/*err = a.GetInterfaces()
	if err != nil {
		return err
	}
	interfaceFound := false
	interfaces := ""
	for _,i := range a.Config.Interfaces {
		if i.Nameif == nil {
			//skip unconfigured (unnamed) interfaces
			continue
		}
		interfaces += fmt.Sprintf("%s(%s),", *i.Nameif, i.Name)
		if s.IfName == *i.Nameif {
			interfaceFound = true
		}
	}
	if len(interfaces) < 1 {
		interfaces = "None,"
	}
	interfaces = interfaces[:len(interfaces)-1]
	if !interfaceFound {
		return fmt.Errorf("Interface '%s' does not exist (found '%s')", s.IfName, interfaces)
	}
	*/
	//	(poll and trap are mutually exclusive)
	if s.Poll && s.Trap {
		return errors.New("Poll and Trap mutually exclusive (only 1 can be set).")
	}
	//build command string
	hostOrGroup := "host"
	if s.HostGroup {
		hostOrGroup = "host-group"
	}
	cmdString := fmt.Sprintf("snmp-server %s %s %s ", hostOrGroup, s.IfName, s.IpAddress)
	if s.Poll {
		cmdString += "poll "
	} else if s.Trap {
		cmdString += "trap "
	}
	//modify command string based on version
	if s.Version == "3" {
		//community should be blank
		if s.Community != "" {
			return errors.New("Community string not used for SNMP v3")
		}
		//user should be populated and exist on fw
		if s.User == "" {
			return errors.New("SNMP v3 needs a User")
		}
		userFound := false
		for _, u := range a.Config.SNMP.SnmpUsers {
			if u.Name == s.User {
				userFound = true
			}
		}
		if !userFound {
			return fmt.Errorf("User '%s' does not exist on fw.", s.User)
		}
		//build our command string
		cmdString += fmt.Sprintf("version 3 %s ", s.User)

	} else {
		//user should be blank
		if s.User != "" {
			return errors.New("User field only used for SNMP v3")
		}
		//community should be populated
		if s.Community == "" {
			return errors.New("SNMP version lower than 3 need a community string")
		}
		//version should be 1 or 2c (2 will be assumed as 2c)
		if !(s.Version == "1" || s.Version == "2" || s.Version == "2c") {
			return errors.New("Invalid SNMP version (must be 1, 2c, or 3)")
		}
		//build our command string
		cmdString += fmt.Sprintf("community %s version %s ", s.Community, s.Version)
	}
	if s.UDPPort != 0 {
		cmdString += fmt.Sprintf("%d", s.UDPPort)
	}
	//add the server
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SNMPDeleteServer(ifName string, host string, errorOnMissing bool) error {
	hostType := "host"
	//pull SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for existing host
	hostFound := false
	for _, svr := range a.Config.SNMP.SnmpServers {
		if svr.IpAddress == host && svr.IfName == ifName {
			hostFound = true
			if svr.HostGroup {
				hostType = "host-group"
			}
			break
		}
	}
	if !hostFound {
		if errorOnMissing {
			return fmt.Errorf("Host '%s' not configured for interface %s on fw.", host, ifName)
		} else {
			return nil
		}
	}
	//delete host
	cmdString := fmt.Sprintf("no snmp-server %s %s %s", hostType, ifName, host)
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SNMPAddGroup(g *asaModels.SNMPGroup, errorOnExisting bool) error {
	//retrieve SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for duplicate Group
	for _, grp := range a.Config.SNMP.SnmpGroups {
		if grp.Name == g.Name && errorOnExisting {
			return fmt.Errorf("Group '%s' already exists.", g.Name)
		}
	}
	//make sure needed info is present and valid
	if g.Name == "" {
		return errors.New("Group name must be populated")
	}
	if g.Version == "" {
		g.Version = "3"
	}
	if !(g.Security == "auth" || g.Security == "noauth" || g.Security == "priv") {
		return errors.New("Group security must be auth, noauth, or priv.")
	}
	//build command string
	cmdString := fmt.Sprintf("snmp-server group %s v%s %s", g.Name, g.Version, g.Security)
	//add group
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SNMPDeleteGroup(name string, errorOnMissing bool) error {
	//pull SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for existing group
	groupFound := false
	version := "3"
	security := "priv"
	for _, grp := range a.Config.SNMP.SnmpGroups {
		if grp.Name == name {
			groupFound = true
			version = grp.Version
			security = grp.Security
			break
		}
	}
	if !groupFound {
		if errorOnMissing {
			return fmt.Errorf("Group '%s' does not exist on fw.", name)
		} else {
			return nil
		}
	}
	//delete group
	cmdString := fmt.Sprintf("no snmp-server group %s v%s %s", name, version, security)
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SNMPAddUser(u *asaModels.SNMPUser, errorOnExisting bool) error {
	//retrieve SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for duplicate user
	for _, usr := range a.Config.SNMP.SnmpUsers {
		if usr.Name == u.Name && errorOnExisting {
			return fmt.Errorf("User '%s' already exists.", u.Name)
		}
	}

	//make sure needed info is present and valid
	if u.Name == "" {
		return errors.New("User name needs to be populated.")
	}
	//	group needs to be present
	groupFound := false
	groupType := ""
	for _, grp := range a.Config.SNMP.SnmpGroups {
		if groupFound {
			continue
		}
		if grp.Name == u.Group {
			groupFound = true
			groupType = grp.Security
		}
	}
	if !groupFound {
		return fmt.Errorf("Group '%s' does not exist on fw.", u.Group)
	}
	if u.Version == "" {
		u.Version = "3"
	}

	//build command string
	cmdString := fmt.Sprintf("snmp-server user %s %s v%s ", u.Name, u.Group, u.Version)
	switch groupType {
	case "auth":
		if u.PrivType != "" || u.PrivPassword != "" {
			return errors.New("Priv info is not used when the group security is only 'auth'")
		}
		if !(u.AuthType == "md5" || u.AuthType == "sha") {
			return errors.New("Auth type must be md5 or sha")
		}
		if u.AuthPassword == "" {
			return errors.New("Auth password must be populated")
		}
		if u.Encrypted {
			cmdString += "encrypted "
		}
		cmdString += fmt.Sprintf("auth %s %s ", u.AuthType, u.AuthPassword)
	case "priv":
		if !(u.AuthType == "md5" || u.AuthType == "sha") {
			return errors.New("Auth type must be md5 or sha")
		}
		if u.AuthPassword == "" {
			return errors.New("Auth password must be populated")
		}
		switch u.PrivType {
		case "des", "3des", "aes 128", "aes 192", "aes 256":
		default:
			return errors.New("Priv type must be des, 3des, or aes (128,192,256)")
		}
		if u.PrivPassword == "" {
			return errors.New("Priv password must be populated")
		}
		if u.Encrypted {
			cmdString += "encrypted "
		}
		cmdString += fmt.Sprintf("auth %s %s priv %s %s ", u.AuthType, u.AuthPassword, u.PrivType, u.PrivPassword)
	}
	//add user
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}

func (a *ASA) SNMPDeleteUser(name string, errorOnMissing bool) error {
	//pull SNMP config
	err := a.GetSNMP()
	if err != nil {
		return err
	}
	//check for existing user
	userFound := false
	userGroup := ""
	userVersion := ""
	for _, usr := range a.Config.SNMP.SnmpUsers {
		if usr.Name == name {
			userFound = true
			userGroup = usr.Group
			userVersion = usr.Version
			break
		}
	}
	if !userFound {
		if errorOnMissing {
			return fmt.Errorf("User '%s' does not exist on fw.", name)
		} else {
			return nil
		}
	}
	//delete user
	cmdString := fmt.Sprintf("no snmp-server user %s %s v%s", name, userGroup, userVersion)
	var cmd []*sshAPI.Command
	cmd = append(cmd, sshAPI.ReCommandCreate("configure terminal", 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate(cmdString, 15, configPrompt))
	cmd = append(cmd, sshAPI.ReCommandCreate("end", 15, prompt))
	res, err := a.sshClient.PostCommands(cmd)
	if err != nil {
		return err
	}
	//check for "invalid input"
	err = invalidInput(*res)
	if err != nil {
		return err
	}
	a.AppendCommands(res)
	return nil
}
