#!/usr/bin/env python3

# sshdo - controls which commands may be executed via incoming ssh
#
# Copyright (C) 2018, 2020-2021 raf <raf@raf.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see https://www.gnu.org/licenses/.
#
# 20210909 raf <raf@raf.org>

# test_sshdo - perform tests for sshdo
#
# usage: test_sshdo [interactive]
#
# If the "interactive" command line argument is supplied, then we also
# perform the shell_exec() test which executes the user's login shell
# which must be terminated manually. Otherwise, only the non-interactive
# tests are performed.

from __future__ import print_function
import os, sys, glob, shutil, pprint, copy, pwd, grp
py2 = sys.version_info.major == 2
none = None

def test_check_auth():
	'''Test check_auth(). Used by sshdo().'''
	config = {
		'root': {
			none: {
				'echo any': 1,
				'echo any #': 1,
				'echo any # a': 1,
				'echo any ###': 1,
				'echo any ### a': 1,
			},
			'label': {
				'echo label': 1,
				'echo label #': 1,
				'echo label # a': 1,
				'echo label ###': 1,
				'echo label ### a': 1,
			}
		}
	}

	config2 = copy.deepcopy(config)
	config2['match:'] = 'exact'

	config3 = copy.deepcopy(config)
	config3['match:'] = 'hexdigits'

	config4 = copy.deepcopy(config)
	config4['training:'] = {}

	config5 = copy.deepcopy(config)
	config5['training:'] = { 'root': { none: {} } }

	config6 = copy.deepcopy(config)
	config6['training:'] = { 'root': { 'label': {} } }

	config7 = copy.deepcopy(config)
	config7['training:'] = { '+' + rootgroup(): { none: {} } }

	config8 = copy.deepcopy(config)
	config8['training:'] = { '+' + rootgroup(): { 'label': {} } }

	config9 = copy.deepcopy(config)
	config9['training:'] = { '+' + rootgroup(): { none: {} }, '-root': { none: {} } }

	config10 = copy.deepcopy(config)
	config10['training:'] = { '+' + rootgroup(): { 'label': {} }, '-root': { 'label': {} } }

	config11 = {
		'+' + rootgroup(): { none: { 'echo any': 1 }, 'label': { 'echo label': 1 } },
	}

	config12 = {
		'root': { none: { 'echo "abc\ndef"': 1 } }
	}

	config13 = {
		'-root': { none: { 'cmd arg': 1 } },
		'training:': {}
	}

	test_data = [
		# Tests for root with any label as root with no label
		dict(config=config, user='root', label=none, command='echo any',         auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any ',        auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any #',       auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any # ',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any # a',     auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any # a ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 1',       auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 1 ',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 1 a',     auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 1 a ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ##',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ## ',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ## a',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ## a ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 11',      auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 11 ',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 11 a',    auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 11 a ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ###',     auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any ### ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any ### a',   auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any ### a ',  auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 111',     auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 111 ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 111 a',   auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 111 a ',  auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any abc a',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 1111',    auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 1111 ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any 1111 a',  auth='allowed'),
		dict(config=config, user='root', label=none, command='echo any 1111 a ', auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo any abcd a',  auth='disallowed'),

		# Tests for root with any label as root with a label
		dict(config=config, user='root', label='label', command='echo any',         auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any ',        auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any #',       auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any # ',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any # a',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any # a ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 1',       auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 1 ',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 1 a',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 1 a ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ##',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ## ',     auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ## a',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ## a ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 11',      auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 11 ',     auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 11 a',    auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 11 a ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ###',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any ### ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any ### a',   auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any ### a ',  auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 111',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 111 ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 111 a',   auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 111 a ',  auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any abc a',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 1111',    auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 1111 ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any 1111 a',  auth='allowed'),
		dict(config=config, user='root', label='label', command='echo any 1111 a ', auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo any abcd a',  auth='disallowed'),

		# Tests for root with any label as non-root with no label
		dict(config=config, user='nobody', label=none, command='echo any',         auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ',        auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any #',       auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any # ',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any # a',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any # a ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1',       auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1 ',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1 a',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1 a ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ##',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ## ',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ## a',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ## a ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 11',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 11 ',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 11 a',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 11 a ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ###',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ### ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ### a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any ### a ',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 111',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 111 ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 111 a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 111 a ',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any abc a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1111',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1111 ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1111 a',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any 1111 a ', auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo any abcd a',  auth='disallowed'),

		# Tests for root with any label as non-root with a label
		dict(config=config, user='nobody', label='label', command='echo any',         auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ',        auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any #',       auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any # ',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any # a',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any # a ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1',       auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1 ',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1 a',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1 a ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ##',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ## ',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ## a',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ## a ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 11',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 11 ',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 11 a',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 11 a ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ###',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ### ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ### a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any ### a ',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 111',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 111 ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 111 a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 111 a ',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any abc a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1111',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1111 ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1111 a',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any 1111 a ', auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo any abcd a',  auth='disallowed'),

		# Tests for root with label as root with no label
		dict(config=config, user='root', label=none, command='echo label',         auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ',        auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label #',       auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label # ',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label # a',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label # a ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1',       auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1 ',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1 a',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1 a ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ##',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ## ',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ## a',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ## a ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 11',      auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 11 ',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 11 a',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 11 a ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ###',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ### ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ### a',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label ### a ',  auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 111',     auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 111 ',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 111 a',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 111 a ',  auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label abc a',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1111',    auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1111 ',   auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1111 a',  auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label 1111 a ', auth='disallowed'),
		dict(config=config, user='root', label=none, command='echo label abcd a',  auth='disallowed'),

		# Tests for root with label as root with the label
		dict(config=config, user='root', label='label', command='echo label',         auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label ',        auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label #',       auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label # ',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label # a',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label # a ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 1',       auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 1 ',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 1 a',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 1 a ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ##',      auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ## ',     auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ## a',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ## a ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 11',      auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 11 ',     auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 11 a',    auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 11 a ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ###',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label ### ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label ### a',   auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label ### a ',  auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 111',     auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 111 ',    auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 111 a',   auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 111 a ',  auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label abc a',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 1111',    auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 1111 ',   auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label 1111 a',  auth='allowed'),
		dict(config=config, user='root', label='label', command='echo label 1111 a ', auth='disallowed'),
		dict(config=config, user='root', label='label', command='echo label abcd a',  auth='disallowed'),

		# Tests for root with label as root with the wrong label
		dict(config=config, user='root', label='label2', command='echo label',         auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ',        auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label #',       auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label # ',      auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label # a',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label # a ',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1',       auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1 ',      auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1 a',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1 a ',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ##',      auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ## ',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ## a',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ## a ',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 11',      auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 11 ',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 11 a',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 11 a ',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ###',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ### ',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ### a',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label ### a ',  auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 111',     auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 111 ',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 111 a',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 111 a ',  auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label abc a',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1111',    auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1111 ',   auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1111 a',  auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label 1111 a ', auth='disallowed'),
		dict(config=config, user='root', label='label2', command='echo label abcd a',  auth='disallowed'),

		# Tests for root with label as non-root with no label
		dict(config=config, user='nobody', label=none, command='echo label',         auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ',        auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label #',       auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label # ',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label # a',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label # a ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1',       auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1 ',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1 a',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1 a ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ##',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ## ',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ## a',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ## a ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 11',      auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 11 ',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 11 a',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 11 a ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ###',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ### ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ### a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label ### a ',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 111',     auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 111 ',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 111 a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 111 a ',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label abc a',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1111',    auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1111 ',   auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1111 a',  auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label 1111 a ', auth='disallowed'),
		dict(config=config, user='nobody', label=none, command='echo label abcd a',  auth='disallowed'),

		# Tests for root with label as non-root with the label
		dict(config=config, user='nobody', label='label', command='echo label',         auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ',        auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label #',       auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label # ',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label # a',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label # a ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1',       auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1 ',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1 a',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1 a ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ##',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ## ',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ## a',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ## a ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 11',      auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 11 ',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 11 a',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 11 a ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ###',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ### ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ### a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label ### a ',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 111',     auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 111 ',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 111 a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 111 a ',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label abc a',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1111',    auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1111 ',   auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1111 a',  auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label 1111 a ', auth='disallowed'),
		dict(config=config, user='nobody', label='label', command='echo label abcd a',  auth='disallowed'),

		# Tests for root with any label as root with no label with exact matching
		dict(config=config2, user='root', label=none, command='echo any',         auth='allowed'),
		dict(config=config2, user='root', label=none, command='echo any ',        auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any #',       auth='allowed'),
		dict(config=config2, user='root', label=none, command='echo any # ',      auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any # a',     auth='allowed'),
		dict(config=config2, user='root', label=none, command='echo any # a ',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1',       auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1 ',      auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1 a',     auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1 a ',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ##',      auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ## ',     auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ## a',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ## a ',   auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 11',      auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 11 ',     auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 11 a',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 11 a ',   auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ###',     auth='allowed'),
		dict(config=config2, user='root', label=none, command='echo any ### ',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any ### a',   auth='allowed'),
		dict(config=config2, user='root', label=none, command='echo any ### a ',  auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 111',     auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 111 ',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 111 a',   auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 111 a ',  auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any abc a',   auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1111',    auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1111 ',   auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1111 a',  auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any 1111 a ', auth='disallowed'),
		dict(config=config2, user='root', label=none, command='echo any abcd a',  auth='disallowed'),

		# Tests for root with any label as root with no label with hexdigits matching
		dict(config=config3, user='root', label=none, command='echo any',         auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any ',        auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any #',       auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any # ',      auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any # a',     auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any # a ',    auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 1',       auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 1 ',      auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 1 a',     auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 1 a ',    auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ##',      auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ## ',     auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ## a',    auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ## a ',   auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 11',      auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 11 ',     auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 11 a',    auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 11 a ',   auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ###',     auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any ### ',    auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any ### a',   auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any ### a ',  auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 111',     auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 111 ',    auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 111 a',   auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 111 a ',  auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any abc a',   auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 1111',    auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 1111 ',   auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any 1111 a',  auth='allowed'),
		dict(config=config3, user='root', label=none, command='echo any 1111 a ', auth='disallowed'),
		dict(config=config3, user='root', label=none, command='echo any abcd a',  auth='allowed'),

		# Test with training turned on for all users and keys
		dict(config=config4, user='root',   label=none,     command='echo other', auth='training'),
		dict(config=config4, user='root',   label='label',  command='echo other', auth='training'),
		dict(config=config4, user='root',   label='label2', command='echo other', auth='training'),
		dict(config=config4, user='nobody', label=none,     command='echo other', auth='training'),
		dict(config=config4, user='nobody', label='label',  command='echo other', auth='training'),
		dict(config=config4, user='nobody', label='label2', command='echo other', auth='training'),

		# Test with training turned on for root user
		dict(config=config5, user='root',   label=none,     command='echo other', auth='training'),
		dict(config=config5, user='root',   label='label',  command='echo other', auth='training'),
		dict(config=config5, user='root',   label='label2', command='echo other', auth='training'),
		dict(config=config5, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config5, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config5, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for root/label user/key
		dict(config=config6, user='root',   label=none,     command='echo other', auth='disallowed'),
		dict(config=config6, user='root',   label='label',  command='echo other', auth='training'),
		dict(config=config6, user='root',   label='label2', command='echo other', auth='disallowed'),
		dict(config=config6, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config6, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config6, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for +root group
		dict(config=config7, user='root',   label=none,     command='echo other', auth='training-group-' + rootgroup()),
		dict(config=config7, user='root',   label='label',  command='echo other', auth='training-group-' + rootgroup()),
		dict(config=config7, user='root',   label='label2', command='echo other', auth='training-group-' + rootgroup()),
		dict(config=config7, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config7, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config7, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for +root/label group
		dict(config=config8, user='root',   label=none,     command='echo other', auth='disallowed'),
		dict(config=config8, user='root',   label='label',  command='echo other', auth='training-group-' + rootgroup()),
		dict(config=config8, user='root',   label='label2', command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for +root/label group
		dict(config=config8, user='root',   label=none,     command='echo other', auth='disallowed'),
		dict(config=config8, user='root',   label='label',  command='echo other', auth='training-group-' + rootgroup()),
		dict(config=config8, user='root',   label='label2', command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config8, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for +root group but -root user
		dict(config=config9, user='root',   label=none,     command='echo other', auth='disallowed'),
		dict(config=config9, user='root',   label='label',  command='echo other', auth='disallowed'),
		dict(config=config9, user='root',   label='label2', command='echo other', auth='disallowed'),
		dict(config=config9, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config9, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config9, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test with training turned on for +root/label user but -root/label
		dict(config=config10, user='root',   label=none,     command='echo other', auth='disallowed'),
		dict(config=config10, user='root',   label='label',  command='echo other', auth='disallowed'),
		dict(config=config10, user='root',   label='label2', command='echo other', auth='disallowed'),
		dict(config=config10, user='nobody', label=none,     command='echo other', auth='disallowed'),
		dict(config=config10, user='nobody', label='label',  command='echo other', auth='disallowed'),
		dict(config=config10, user='nobody', label='label2', command='echo other', auth='disallowed'),

		# Test groups with any label
		dict(config=config11, user='root',   label=none,     command='echo any', auth='allowed-group-' + rootgroup()),
		dict(config=config11, user='root',   label='label',  command='echo any', auth='allowed-group-' + rootgroup()),
		dict(config=config11, user='root',   label='label2', command='echo any', auth='allowed-group-' + rootgroup()),
		dict(config=config11, user='nobody', label=none,     command='echo any', auth='disallowed'),
		dict(config=config11, user='nobody', label='label',  command='echo any', auth='disallowed'),
		dict(config=config11, user='nobody', label='label2', command='echo any', auth='disallowed'),

		# Test groups with a particular label
		dict(config=config11, user='root',   label=none,     command='echo label', auth='disallowed'),
		dict(config=config11, user='root',   label='label',  command='echo label', auth='allowed-group-' + rootgroup()),
		dict(config=config11, user='root',   label='label2', command='echo label', auth='disallowed'),
		dict(config=config11, user='nobody', label=none,     command='echo label', auth='disallowed'),
		dict(config=config11, user='nobody', label='label',  command='echo label', auth='disallowed'),
		dict(config=config11, user='nobody', label='label2', command='echo label', auth='disallowed'),

		# Test command with binary characters
		dict(config=config12, user='root',   label=none,    command='echo "abc\ndef"', auth='allowed'),
		dict(config=config12, user='root',   label='label', command='echo "abc\ndef"', auth='allowed'),

		# Test with training turned on for all users and keys but -root: cmd arg
		dict(config=config13, user='root',   label=none,     command='cmd arg', auth='disallowed'),
		dict(config=config13, user='root',   label=none,     command='cmd arg arg2', auth='training'),
	]

	import sshdo
	num_tests = 0
	num_errors = 0
	for t in test_data:
		num_tests += 1
		actual_auth = sshdo.check_auth(t['config'], t['user'], t['label'], t['command'])
		if actual_auth != t['auth']:
			num_errors += 1
			print('Test %d: check_auth(match=%r user=%r label=%r command=%r) = %s (expected %s)' % (num_tests, t['config']['match:'] if 'match:' in t['config'] else 'digits', t['user'], t['label'], t['command'], actual_auth, t['auth']))
			print(pprint.pformat(t['config']))
	print(('All %s check_auth tests passed' % num_tests) if num_errors == 0 else ('%s/%s check_auth tests failed' % (num_errors, num_tests)))
	return num_errors

def test_check_command():
	'''Test check_command(). Used by check_auth().'''
	# For testing single variable parts
	patterns = {
		'echo single #': '',
		'echo double ##': '',
		'echo triple ###': '',
	}

	# For testing multiple variable parts with digits matching
	patterns2 = {
		'echo #': '',
		'echo aaa ##': '',
		'echo bbb 1 11 # x': '',
		'echo ccc # ## # y': '',
	}

	# For testing multiple variable parts with hexdigits matching
	patterns3 = {
		'echo #': '',
		'echo aaa ##': '',
		'echo ### 1 11 # x': '',
		'echo ccc # ## # y': '',
	}

	test_data = [
		# A single variable part with a single hash and exact hashing
		['exact', patterns, 'echo single', 0],
		['exact', patterns, 'echo single ', 0],
		['exact', patterns, 'echo single #', 1],
		['exact', patterns, 'echo single 1', 0],
		['exact', patterns, 'echo single 9', 0],
		['exact', patterns, 'echo single a', 0],
		['exact', patterns, 'echo single f', 0],
		['exact', patterns, 'echo single g', 0],
		['exact', patterns, 'echo single A', 0],
		['exact', patterns, 'echo single F', 0],
		['exact', patterns, 'echo single G', 0],
		['exact', patterns, 'echo single # ', 0],
		['exact', patterns, 'echo single 1 ', 0],
		['exact', patterns, 'echo single ##', 0],
		['exact', patterns, 'echo single 11', 0],
		['exact', patterns, 'echo single af', 0],
		['exact', patterns, 'echo single gh', 0],
		['exact', patterns, 'echo single AF', 0],
		['exact', patterns, 'echo single GH', 0],
		['exact', patterns, 'echo single ###', 0],
		['exact', patterns, 'echo single 111', 0],
		['exact', patterns, 'echo single acf', 0],
		['exact', patterns, 'echo single ACF', 0],

		# A single variable part with a double hash and exact hashing
		['exact', patterns, 'echo double', 0],
		['exact', patterns, 'echo double ', 0],
		['exact', patterns, 'echo double #', 0],
		['exact', patterns, 'echo double ##', 1],
		['exact', patterns, 'echo double 11', 0],
		['exact', patterns, 'echo double 99', 0],
		['exact', patterns, 'echo double af', 0],
		['exact', patterns, 'echo double gh', 0],
		['exact', patterns, 'echo double AF', 0],
		['exact', patterns, 'echo double GH', 0],
		['exact', patterns, 'echo double ## ', 0],
		['exact', patterns, 'echo double 11 ', 0],
		['exact', patterns, 'echo double ###', 0],
		['exact', patterns, 'echo double 111', 0],
		['exact', patterns, 'echo double 1#', 0],
		['exact', patterns, 'echo double #1', 0],
		['exact', patterns, 'echo double a#', 0],
		['exact', patterns, 'echo double #f', 0],

		# A single variable part with a triple hash and exact hashing
		['exact', patterns, 'echo triple', 0],
		['exact', patterns, 'echo triple ', 0],
		['exact', patterns, 'echo triple #', 0],
		['exact', patterns, 'echo triple ##', 0],
		['exact', patterns, 'echo triple ###', 1],
		['exact', patterns, 'echo triple 111', 0],
		['exact', patterns, 'echo triple 999', 0],
		['exact', patterns, 'echo triple abc', 0],
		['exact', patterns, 'echo triple DEF', 0],
		['exact', patterns, 'echo triple ghi', 0],
		['exact', patterns, 'echo triple ### ', 0],
		['exact', patterns, 'echo triple ####', 0],
		['exact', patterns, 'echo triple 1##', 0],
		['exact', patterns, 'echo triple #1#', 0],
		['exact', patterns, 'echo triple ##1', 0],
		['exact', patterns, 'echo triple 11#', 0],
		['exact', patterns, 'echo triple #11', 0],
		['exact', patterns, 'echo triple 1#1', 0],
		['exact', patterns, 'echo triple a##', 0],
		['exact', patterns, 'echo triple #a#', 0],
		['exact', patterns, 'echo triple ##a', 0],
		['exact', patterns, 'echo triple aa#', 0],
		['exact', patterns, 'echo triple #aa', 0],
		['exact', patterns, 'echo triple a#a', 0],

		# A single variable part with a single hash and digits hashing
		['digits', patterns, 'echo single', 0],
		['digits', patterns, 'echo single ', 0],
		['digits', patterns, 'echo single #', 1],
		['digits', patterns, 'echo single 1', 1],
		['digits', patterns, 'echo single 9', 1],
		['digits', patterns, 'echo single a', 0],
		['digits', patterns, 'echo single f', 0],
		['digits', patterns, 'echo single g', 0],
		['digits', patterns, 'echo single A', 0],
		['digits', patterns, 'echo single F', 0],
		['digits', patterns, 'echo single G', 0],
		['digits', patterns, 'echo single # ', 0],
		['digits', patterns, 'echo single 1 ', 0],
		['digits', patterns, 'echo single ##', 0],
		['digits', patterns, 'echo single 11', 1],
		['digits', patterns, 'echo single af', 0],
		['digits', patterns, 'echo single gh', 0],
		['digits', patterns, 'echo single AF', 0],
		['digits', patterns, 'echo single GH', 0],
		['digits', patterns, 'echo single ###', 0],
		['digits', patterns, 'echo single 111', 1],
		['digits', patterns, 'echo single acf', 0],
		['digits', patterns, 'echo single ACF', 0],

		# A single variable part with a double hash and digits hashing
		['digits', patterns, 'echo double', 0],
		['digits', patterns, 'echo double ', 0],
		['digits', patterns, 'echo double #', 0],
		['digits', patterns, 'echo double ##', 1],
		['digits', patterns, 'echo double 11', 1],
		['digits', patterns, 'echo double 99', 1],
		['digits', patterns, 'echo double af', 0],
		['digits', patterns, 'echo double gh', 0],
		['digits', patterns, 'echo double AF', 0],
		['digits', patterns, 'echo double GH', 0],
		['digits', patterns, 'echo double ## ', 0],
		['digits', patterns, 'echo double 11 ', 0],
		['digits', patterns, 'echo double ###', 0],
		['digits', patterns, 'echo double 111', 0],
		['digits', patterns, 'echo double 1#', 1],
		['digits', patterns, 'echo double #1', 1],
		['digits', patterns, 'echo double a#', 0],
		['digits', patterns, 'echo double #f', 0],

		# A single variable part with a triple hash and digits hashing
		['digits', patterns, 'echo triple', 0],
		['digits', patterns, 'echo triple ', 0],
		['digits', patterns, 'echo triple #', 0],
		['digits', patterns, 'echo triple ##', 0],
		['digits', patterns, 'echo triple ###', 1],
		['digits', patterns, 'echo triple 111', 1],
		['digits', patterns, 'echo triple 999', 1],
		['digits', patterns, 'echo triple abc', 0],
		['digits', patterns, 'echo triple DEF', 0],
		['digits', patterns, 'echo triple ghi', 0],
		['digits', patterns, 'echo triple ### ', 0],
		['digits', patterns, 'echo triple ####', 0],
		['digits', patterns, 'echo triple 1##', 1],
		['digits', patterns, 'echo triple #1#', 1],
		['digits', patterns, 'echo triple ##1', 1],
		['digits', patterns, 'echo triple 11#', 1],
		['digits', patterns, 'echo triple #11', 1],
		['digits', patterns, 'echo triple 1#1', 1],
		['digits', patterns, 'echo triple a##', 0],
		['digits', patterns, 'echo triple #a#', 0],
		['digits', patterns, 'echo triple ##a', 0],
		['digits', patterns, 'echo triple aa#', 0],
		['digits', patterns, 'echo triple #aa', 0],
		['digits', patterns, 'echo triple a#a', 0],

		# A single variable part with a single hash and hexdigits hashing
		['hexdigits', patterns, 'echo single', 0],
		['hexdigits', patterns, 'echo single ', 0],
		['hexdigits', patterns, 'echo single #', 1],
		['hexdigits', patterns, 'echo single 1', 1],
		['hexdigits', patterns, 'echo single 9', 1],
		['hexdigits', patterns, 'echo single a', 1],
		['hexdigits', patterns, 'echo single f', 1],
		['hexdigits', patterns, 'echo single g', 0],
		['hexdigits', patterns, 'echo single A', 1],
		['hexdigits', patterns, 'echo single F', 1],
		['hexdigits', patterns, 'echo single G', 0],
		['hexdigits', patterns, 'echo single # ', 0],
		['hexdigits', patterns, 'echo single 1 ', 0],
		['hexdigits', patterns, 'echo single ##', 0],
		['hexdigits', patterns, 'echo single 11', 1],
		['hexdigits', patterns, 'echo single af', 1],
		['hexdigits', patterns, 'echo single gh', 0],
		['hexdigits', patterns, 'echo single AF', 1],
		['hexdigits', patterns, 'echo single Gh', 0],
		['hexdigits', patterns, 'echo single ###', 0],
		['hexdigits', patterns, 'echo single 111', 1],
		['hexdigits', patterns, 'echo single acf', 1],
		['hexdigits', patterns, 'echo single ACF', 1],

		# A single variable part with a double hash and hexdigits hashing
		['hexdigits', patterns, 'echo double', 0],
		['hexdigits', patterns, 'echo double ', 0],
		['hexdigits', patterns, 'echo double #', 0],
		['hexdigits', patterns, 'echo double ##', 1],
		['hexdigits', patterns, 'echo double 11', 1],
		['hexdigits', patterns, 'echo double 99', 1],
		['hexdigits', patterns, 'echo double af', 1],
		['hexdigits', patterns, 'echo double gh', 0],
		['hexdigits', patterns, 'echo double AF', 1],
		['hexdigits', patterns, 'echo double GH', 0],
		['hexdigits', patterns, 'echo double ## ', 0],
		['hexdigits', patterns, 'echo double 11 ', 0],
		['hexdigits', patterns, 'echo double ###', 0],
		['hexdigits', patterns, 'echo double 111', 0],
		['hexdigits', patterns, 'echo double 1#', 1],
		['hexdigits', patterns, 'echo double #1', 1],
		['hexdigits', patterns, 'echo double a#', 1],
		['hexdigits', patterns, 'echo double #f', 1],

		# A single variable part with a triple hash and hexdigits hashing
		['hexdigits', patterns, 'echo triple', 0],
		['hexdigits', patterns, 'echo triple ', 0],
		['hexdigits', patterns, 'echo triple #', 0],
		['hexdigits', patterns, 'echo triple ##', 0],
		['hexdigits', patterns, 'echo triple ###', 1],
		['hexdigits', patterns, 'echo triple 111', 1],
		['hexdigits', patterns, 'echo triple 999', 1],
		['hexdigits', patterns, 'echo triple abc', 1],
		['hexdigits', patterns, 'echo triple DEF', 1],
		['hexdigits', patterns, 'echo triple ghi', 0],
		['hexdigits', patterns, 'echo triple ### ', 0],
		['hexdigits', patterns, 'echo triple ####', 0],
		['hexdigits', patterns, 'echo triple 1##', 1],
		['hexdigits', patterns, 'echo triple #1#', 1],
		['hexdigits', patterns, 'echo triple ##1', 1],
		['hexdigits', patterns, 'echo triple 11#', 1],
		['hexdigits', patterns, 'echo triple #11', 1],
		['hexdigits', patterns, 'echo triple 1#1', 1],
		['hexdigits', patterns, 'echo triple a##', 1],
		['hexdigits', patterns, 'echo triple #a#', 1],
		['hexdigits', patterns, 'echo triple ##a', 1],
		['hexdigits', patterns, 'echo triple aa#', 1],
		['hexdigits', patterns, 'echo triple #aa', 1],
		['hexdigits', patterns, 'echo triple a#a', 1],

		# Multiple variable parts with digits matching
		['digits', patterns2, 'echo 1', 1],
		['digits', patterns2, 'echo 12', 1],
		['digits', patterns2, 'echo 123', 1],
		['digits', patterns2, 'echo aaa 12', 1],
		['digits', patterns2, 'echo aaa 23', 1],
		['digits', patterns2, 'echo aaa 34', 1],
		['digits', patterns2, 'echo aaa 1', 0],
		['digits', patterns2, 'echo aaa 123', 0],
		['digits', patterns2, 'echo bbb 1 11 1 x', 1],
		['digits', patterns2, 'echo bbb 1 11 11 x', 1],
		['digits', patterns2, 'echo bbb 1 11 111 x', 1],
		['digits', patterns2, 'echo ccc 1 11 1 y', 1],
		['digits', patterns2, 'echo ccc 2 22 22 y', 1],
		['digits', patterns2, 'echo ccc 3 33 333 y', 1],
		['digits', patterns2, 'echo ccc 1 1 1 y', 0],
		['digits', patterns2, 'echo ccc 1 111 1 y', 0],

		# Multiple variable parts with hexdigits matching
		['hexdigits', patterns3, 'echo 1', 1],
		['hexdigits', patterns3, 'echo 12', 1],
		['hexdigits', patterns3, 'echo 123', 1],
		['hexdigits', patterns3, 'echo aaa 12', 1],
		['hexdigits', patterns3, 'echo aaa 23', 1],
		['hexdigits', patterns3, 'echo aaa 34', 1],
		['hexdigits', patterns3, 'echo aaa 1', 0],
		['hexdigits', patterns3, 'echo aaa 123', 0],
		['hexdigits', patterns3, 'echo bbb 1 11 1 x', 1],
		['hexdigits', patterns3, 'echo bbb 1 11 11 x', 1],
		['hexdigits', patterns3, 'echo bbb 1 11 111 x', 1],
		['hexdigits', patterns3, 'echo ddd 1 11 1 x', 1],
		['hexdigits', patterns3, 'echo ddd 1 11 11 x', 1],
		['hexdigits', patterns3, 'echo ddd 1 11 111 x', 1],
		['hexdigits', patterns3, 'echo ccc 1 11 1 y', 1],
		['hexdigits', patterns3, 'echo ccc 2 22 22 y', 1],
		['hexdigits', patterns3, 'echo ccc 3 33 333 y', 1],
		['hexdigits', patterns3, 'echo ccc 1 1 1 y', 0],
		['hexdigits', patterns3, 'echo ccc 1 111 1 y', 0],
	]

	import sshdo
	num_tests = 0
	num_errors = 0

	for scheme, patterns, command, expected in test_data:
		num_tests += 1
		result = sshdo.check_command({ 'match:': scheme }, command, patterns)
		if result != expected:
			num_errors += 1
			print('Test %d: check_command(%s) command = %r result = %s expected = %s' % (num_tests, scheme, command, result, expected))
	print(('All %s check_command tests passed' % num_tests) if num_errors == 0 else ('%s/%s check_command tests failed' % (num_errors, num_tests)))
	return num_errors

def test_coalesce_commands():
	'''Test coalesce_commands(). Used by --learn and --unlearn.'''
	test_data = [
		['exact', {
			"/usr/bin/bzip2 /tmp/blah2": { "bob": '' },
			"/usr/bin/bzip2 /tmp/blah13": { "jude": '' },
			"/usr/bin/bzip2 /tmp/blah2": { "bob": '# ', "jude/label": '# ' },
			"/usr/bin/bzip2 /tmp/blah13": { "john": '# ' },
		}, {
			"/usr/bin/bzip2 /tmp/blah2": { "bob": '' },
			"/usr/bin/bzip2 /tmp/blah13": { "jude": '' },
			"/usr/bin/bzip2 /tmp/blah2": { "bob": '# ', "jude/label": '# ' },
			"/usr/bin/bzip2 /tmp/blah13": { "john": '# ' },
		}],
		['digits', {
			'echo 1': { 'bob': '' },
			'echo 12': { 'jude/label': '# ', 'kim': '' },
			'echo 123': { 'drew': '# ' },

			'echo aaa 12': { 'bob': '' },
			'echo aaa 23': { 'jude': '', 'bob': '# ' },
			'echo aaa 34': { 'jude': '# ' },

			'echo bbb 1 11 1 x': { 'jude': '' },
			'echo bbb 1 11 11 x': { 'jude': '' },
			'echo bbb 1 11 111 x': { 'jude': '' },

			'echo ccc 1 11 1 y': { 'jude': '' },
			'echo ccc 2 22 22 y': { 'jude': '' },
			'echo ccc 3 33 333 y': { 'jude': '' },
		}, {
			'echo #': { 'bob': '', 'jude/label': '# ', 'kim': '', 'drew': '# ', 'commands:': { 'echo 1': '', 'echo 12': '', 'echo 123': '' } },
			'echo aaa ##': { 'bob': '# ', 'jude': '# ', 'commands:': { 'echo aaa 12': '', 'echo aaa 23': '', 'echo aaa 34': '' } },
			'echo bbb 1 11 # x': { 'jude': '', 'commands:': { 'echo bbb 1 11 1 x': '', 'echo bbb 1 11 11 x': '', 'echo bbb 1 11 111 x': '' } },
			'echo ccc # ## # y': { 'jude': '', 'commands:': { 'echo ccc 1 11 1 y': '', 'echo ccc 2 22 22 y': '', 'echo ccc 3 33 333 y': '' } },
		}],
		['hexdigits', {
			'echo 1': { 'bob': '' },
			'echo 12': { 'jude/label': '# ', 'kim': '' },
			'echo 123': { 'drew': '# ' },

			'echo aaa 12': { 'bob': '' },
			'echo aaa 23': { 'jude': '', 'bob': '# ' },
			'echo aaa 34': { 'jude': '# ' },

			'echo bbb 1 11 1 x': { 'jude': '' },
			'echo bbb 1 11 11 x': { 'jude': '' },
			'echo bbb 1 11 111 x': { 'jude': '' },

			'echo ddd 1 11 1 x': { 'jude': '' },
			'echo ddd 1 11 11 x': { 'jude': '' },
			'echo ddd 1 11 111 x': { 'jude': '' },

			'echo ccc 1 11 1 y': { 'jude': '' },
			'echo ccc 2 22 22 y': { 'jude': '' },
			'echo ccc 3 33 333 y': { 'jude': '' },
		}, {
			'echo #': { 'bob': '', 'jude/label': '# ', 'kim': '', 'drew': '# ', 'commands:': { 'echo 1': '', 'echo 12': '', 'echo 123': '' } },
			'echo aaa ##': { 'bob': '# ', 'jude': '# ', 'commands:': { 'echo aaa 12': '', 'echo aaa 23': '', 'echo aaa 34': '' } },
			'echo ### 1 11 # x': { 'jude': '', 'commands:': { 'echo bbb 1 11 1 x': '', 'echo bbb 1 11 11 x': '', 'echo bbb 1 11 111 x': '', 'echo ddd 1 11 1 x': '', 'echo ddd 1 11 11 x': '', 'echo ddd 1 11 111 x': '' } },
			'echo ccc # ## # y': { 'jude': '', 'commands:': { 'echo ccc 1 11 1 y': '', 'echo ccc 2 22 22 y': '', 'echo ccc 3 33 333 y': '' } },
		}],
		['digits', {
			'echo "\n" 1': { 'bob': '' },
			'echo "\n" 12': { 'jude/label': '# ', 'kim': '' },
			'echo "\n" 123': { 'drew': '# ' },
		}, {
			'echo "\n" #': { 'bob': '', 'jude/label': '# ', 'kim': '', 'drew': '# ', 'commands:': { 'echo "\n" 1': '', 'echo "\n" 12': '', 'echo "\n" 123': '' } },
		}],
	]

	import sshdo
	num_tests = 0
	num_errors = 0

	for scheme, commands, coalesced in test_data:
		num_tests += 1
		result = sshdo.coalesce_commands(scheme, commands)
		if result != coalesced:
			num_errors += 1
			print('Test %d: coalesce_commands(scheme=%r)\ncommands = %s\nresult = %s\nexpected = %s\n' % (num_tests, scheme, pprint.pformat(commands), pprint.pformat(result), pprint.pformat(coalesced)))
	print(('All %s coalesce_commands tests passed' % num_tests) if num_errors == 0 else ('%s/%s coalesce_commands tests failed' % (num_errors, num_tests)))
	return num_errors

def test_options():
	'''Test command line option checks().'''
	import sshdo
	test_output_fname = '.test.output'
	test_error_fname = '.test.error'
	test_data = [
		['-w', '', 'error: option -w not recognized\n'],
		['--wrong', '', 'error: option --wrong not recognized\n'],

		['-h', 'usage:\n %s [label]               # For use as a forced command\n' % sshdo.prog_name, ''],
		['--help', 'usage:\n %s [label]               # For use as a forced command\n' % sshdo.prog_name, ''],

		['-V', '%s-%s\n' % (sshdo.prog_name, sshdo.prog_version), ''],
		['--version', '%s-%s\n' % (sshdo.prog_name, sshdo.prog_version), ''],

		['-c -l', '', 'error: The --check and --learn options are mutually exclusive\n'],
		['-c --learn', '', 'error: The --check and --learn options are mutually exclusive\n'],
		['--check -l', '', 'error: The --check and --learn options are mutually exclusive\n'],
		['--check --learn', '', 'error: The --check and --learn options are mutually exclusive\n'],

		['-c -u', '', 'error: The --check and --unlearn options are mutually exclusive\n'],
		['-c --unlearn', '', 'error: The --check and --unlearn options are mutually exclusive\n'],
		['--check -u', '', 'error: The --check and --unlearn options are mutually exclusive\n'],
		['--check --unlearn', '', 'error: The --check and --unlearn options are mutually exclusive\n'],

		['-c -l -u', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['-c --learn -u', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['--check -l -u', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['--check --learn -u', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['-c -l --unlearn', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['-c --learn --unlearn', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['--check -l --unlearn', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],
		['--check --learn --unlearn', '', 'error: The --check and --learn and --unlearn options are mutually exclusive\n'],

		['-a', '', 'error: The --accepting option requires the --learn or --unlearn option\n'],
		['--accepting', '', 'error: The --accepting option requires the --learn or --unlearn option\n'],
	]

	num_tests = 0
	num_errors = 0

	for test_options, expected_output, expected_error in test_data:
		num_tests += 1
		os.system("./sshdo %s > %s 2> %s" % (test_options, test_output_fname, test_error_fname))
		result_output = readfile(test_output_fname)
		result_error = readfile(test_error_fname)
		os.unlink(test_output_fname)
		os.unlink(test_error_fname)
		if result_output[:len(expected_output)] != expected_output or result_error != expected_error:
			num_errors += 1
			if result_output[:len(expected_output)] != expected_output:
				print('Test %d: options %r\nresult_output =   %r\nexpected_output = %r' % (num_tests, test_options, result_output, expected_output))
			if result_error != expected_error:
				print('Test %d: options %r\nresult_error =   %r\nexpected_error = %r' % (num_tests, test_options, result_error, expected_error))
	print(('All %s options tests passed' % num_tests) if num_errors == 0 else ('%s/%s options tests failed' % (num_errors, num_tests)))
	return num_errors

def test_check_config():
	'''Test load_config(). Used by --check.'''
	test_config_fname = '.test.sshdoers'
	test_configd_dname = test_config_fname + '.d'
	test_configd_fname = test_configd_dname + '/local'
	test_output_fname = '.test.output'
	test_error_fname = '.test.error'
	test_auth_log = '.test.auth.log'
	test_data = [
		# Authorization directives
		['+unlikely_group_name: echo\n', none, '', 'warning: No such group: %s [%s line %s]\n' % ('unlikely_group_name', test_config_fname, 1)],
		['+unlikely_group_name/label: echo\n', none, '', 'warning: No such group: %s [%s line %s]\n' % ('unlikely_group_name', test_config_fname, 1)],
		['unlikely_user_name: echo\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['unlikely_user_name/label: echo\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['-unlikely_user_name: echo\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['-unlikely_user_name/label: echo\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['root: echo\n-root: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('-root', 'root', 'echo', test_config_fname, 2)],
		['root/label: echo\n-root/label: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('-root/label', 'root/label', 'echo', test_config_fname, 2)],
		['-root: echo\nroot: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('root', '-root', 'echo', test_config_fname, 2)],
		['-root/label: echo\nroot/label: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('root/label', '-root/label', 'echo', test_config_fname, 2)],
		['root/label: echo\n-root: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('-root', 'root/label', 'echo', test_config_fname, 2)],
		['-root/label: echo\nroot: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('root', '-root/label', 'echo', test_config_fname, 2)],
		['root: echo\n-root/any: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('-root/any', 'root', 'echo', test_config_fname, 2)],
		['-root: echo\nroot/any: echo\n', none, '', 'warning: Clashing allow/disallow: %s and %s: %s [%s line %s]\n' % ('root/any', '-root', 'echo', test_config_fname, 2)],
		# Training directives
		['training +unlikely_group_name\n', none, '', 'warning: No such group: %s [%s line %s]\n' % ('unlikely_group_name', test_config_fname, 1)],
		['training +unlikely_group_name/label\n', none, '', 'warning: No such group: %s [%s line %s]\n' % ('unlikely_group_name', test_config_fname, 1)],
		['training unlikely_user_name\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['training unlikely_user_name/label\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['training -unlikely_user_name\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['training -unlikely_user_name/label\n', none, '', 'warning: No such user: %s [%s line %s]\n' % ('unlikely_user_name', test_config_fname, 1)],
		['training root\ntraining -root\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('-root', 'root', test_config_fname, 2)],
		['training root/label\ntraining -root/label\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('-root/label', 'root/label', test_config_fname, 2)],
		['training -root\ntraining root\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('root', '-root', test_config_fname, 2)],
		['training -root/label\ntraining root/label\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('root/label', '-root/label', test_config_fname, 2)],
		['training root/label\ntraining -root\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('-root', 'root/label', test_config_fname, 2)],
		['training -root/label\ntraining root\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('root', '-root/label', test_config_fname, 2)],
		['training root\ntraining -root/any\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('-root/any', 'root', test_config_fname, 2)],
		['training -root\ntraining root/any\n', none, '', 'warning: Clashing training mode: %s and %s [%s line %s]\n' % ('root/any', '-root', test_config_fname, 2)],
		# Syntax OK and not OK
		['root: echo\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['root +%s -nobody: echo\n' % rootgroup(), none, '%s syntax OK\n' % test_config_fname, ''],
		['training\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['training root\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['training root +%s -nobody\n' % rootgroup(), none, '%s syntax OK\n' % test_config_fname, ''],
		['traning\n', none, '', 'error: Invalid config: traning [%s line 1]\n' % test_config_fname],
		['syslog auth\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog daemon\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog user\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local0\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local1\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local2\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local3\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local4\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local5\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local6\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog local7\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['syslog other\n', none, '', 'error: Invalid config: syslog other [%s line 1]\n' % test_config_fname],
		['syslogg auth\n', none, '', 'error: Invalid config: syslogg auth [%s line 1]\n' % test_config_fname],
		['logfiles test_sshd?\n', none, '%s syntax OK\n' % test_config_fname, ''], # File must exist
		['logfiles\n', none, '', 'error: Invalid config: logfiles [%s line 1]\n' % test_config_fname],
		['logfiles /unlikelyvar/log/auth.log*\n', none, '', 'warning: No such logfiles: /unlikelyvar/log/auth.log* [%s line 1]\n' % test_config_fname],
		['logfiles -\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['logfile /var/log/auth.log*\n', none, '', 'error: Invalid config: logfile /var/log/auth.log* [%s line 1]\n' % test_config_fname],
		['banner /unlikelyfilename\n', '', '', 'warning: No such banner: /unlikelyfilename [%s line 1]\n' % test_config_fname],
		['banner\n', '', '', 'error: Invalid config: banner [%s line 1]\n' % test_config_fname],
		['baner\n', '', '', 'error: Invalid config: baner [%s line 1]\n' % test_config_fname],
		['match exact\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['match digits\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['match hexdigits\n', none, '%s syntax OK\n' % test_config_fname, ''],
		['match other\n', none, '', 'error: Invalid config: match other [%s line 1]\n' % test_config_fname],
		['matc exact\n', none, '', 'error: Invalid config: matc exact [%s line 1]\n' % test_config_fname],
		['', 'training\n', '', 'warning: Invalid config: training (only allowed in %s) [%s line 1]\n' % (test_config_fname, test_configd_fname)],
		['', 'training root\n', '%s syntax OK\n' % test_config_fname, ''],
		['', 'syslog auth\n', '', 'warning: Invalid config: syslog auth (only allowed in %s) [%s line 1]\n' % (test_config_fname, test_configd_fname)],
		['', 'logfiles /var/log/auth.log*\n', '', 'warning: Invalid config: logfiles /var/log/auth.log* (only allowed in %s) [%s line 1]\n' % (test_config_fname, test_configd_fname)],
		['', 'banner /unlikelyfilename\n', '', 'warning: Invalid config: banner /unlikelyfilename (only allowed in %s) [%s line 1]\n' % (test_config_fname, test_configd_fname)],
		['', 'match digits\n', '', 'warning: Invalid config: match digits (only allowed in %s) [%s line 1]\n' % (test_config_fname, test_configd_fname)],
		['', '', '%s syntax OK\n' % test_config_fname, ''],
		# Check warnings about multiple directives
		['syslog user\nsyslog daemon\n', none, '', "warning: syslog specified more than once: 'user' and 'daemon' [.test.sshdoers line 2]\n"],
		['logfiles Makefile*\nlogfiles LICENSE*\n', none, '.test.sshdoers syntax OK\n', ''],
		['match exact\nmatch digits\n', none, '', "warning: match specified more than once: 'exact' and 'digits' [.test.sshdoers line 2]\n"],
		['banner Makefile\nbanner LICENSE\n', none, '', "warning: banner specified more than once: 'Makefile' and 'LICENSE' [.test.sshdoers line 2]\n"],
		# Check binary garbage in the config file
		[b'\xbb\xe7Z\xfe)(\x1f\x00\xcfI\xdef\x81\x8a\xfc\xe9Oz|\xf4\xe9/\x03\x1cn\xb1\xd8\x92G\xf8-\x04\xfe\xee@h\xc3\n\xa29\x8f\xd2\xbb\x89\xdd\xfa\x19\x881\xa8\xb3\xc7\x996\xd5\x8b&\x87\xf0d\x01\xc6\x8c\x85\xa32D\x98\xa5\xa4$E3\x86\xb4\x13\xa4E\xb0r\xcd\xa4\xc2\xa1Fk\x9d>\x18#n\x00lH\xbc5}4\x08\x1a\n\xf6b\xe3\x9f\xd90n\xac\xa6A\xcb6\x95\xfdK\xbaJ4 C4+\xae\x8faM\xe8T\x83\xf4.\xb3\xbds\x06\x01\xb6\x9eb\xda\x11A i\x85\x95y\xa2H\xa3oG\xf2c(\x9e\x93\xf6\xfbX:\xe6\'\x98\x8fXsL\xde\xf2\xa2\xc6u3~M\x03d\x00\x12wv\xaa\x8c\xa2>,Y\xd2\x8f\xed3g\x16\x96:\xc1,\xaak\xac\xbd\xcdBC\x01\xde\xe0\'\x89x\\>9Es=\xff|@\x9f97eD?\x80\xed!T\x1d\x8f>\xbd\x886g\x16>#\xb2\xe1\x91\x8a\x08\x85\xa8`\x95\xfcQ\xc74\x0f\x1f\xc0g\xed\x89\xd4[\x12s\x13F\xad\x9f\xf8MV\x95\'2\x03^\xd7\xbcK\x99\x11D\xaf\x89>\x13\'iL\xbe%\xc8\xbf\xc8\tz\xe4\'\x828\xa6\xc3\x06=7\xfb+\x9d&\x11j\xca}~=h`\xc0\xb2\xae\xa4<D\x07\x9e\xa2\x12\x88\x14\xb7\xb48\xcc\xd9\xe0^3\xf1n\xf9U\x99p\xeb\xd5\xaf\xbe4\xad\x1a\xa3\x88\x1f\x9d]H\x14a$"Q`\x90\x08\x02\x13X}\xb4\xb3{\x1fdwoV\xec\xff\x1c\x146\xf6"H\xae\x03\x0f\xa4\xeaI\xab\x85\\\xf6L\xf1\x92\xa7\xd7\xa3\r*\x8aA\xa1\xf9Kr\x7f\xe8v:C\x96\xc54q\x9dVX\xc5\xc9w\xa0\x14\xd0\x91\x80\xd2`\x1a\xaf\x0e0//\n+\rD\xd1e\x1c\xde\xc7[\x12\x06\xa1\xf4\x10\xce\xe1pUU\xa0\xd1H\x867\\\x00\xb7\xc8#&6G\x1a\x82F\xee\xd6\x19l\n\x9a\xe7\xa8\x89HO\xa6Z`Z\xc8\'O\x84}\x1d=16\xdb\xad\xb7o|NR\xe1\xdc\xed-Nfc\x07}\xfb\x0c\xc79\x8a\x13\xc2?\x9bM\xaeg\xf8\xc9\xbd\xa6\xfaW\xcc\xa7\\|\\#\xff\xd3 \xec\xb2\x83q\x12\x02\x1f8\x94\xc9\xb4*g\xa2\x1fq$\xe8\xfb)a\xf1@\xab\x92\xfb]K9\xdf\xe0\xe5\xbd\xbd\x14\x82\xe9\xf0u\x80\xcfi`\xe6\xc3\xf8B!\x8e\x0c\xacNV\xc0\x8d\xd9\xb7\xa2\xe4T\x12\xe8\'R\n\xe4\xdc\xcfG\xa6\xaf\xb3g\xea\x13{\xd2(\x9d:\xaf\xa0\x16$\xbbzr6B\x1d\xe4Y\x8e4%pwb>\xe4\xebr\xd4\\\x9c\xe4\xdc\x82\x86VNo\xad\xcf\t\xd7fK\x8fu\x15\xaa\xff\x97\xbf\xce\x1e\xfen\xa9S\xdc\xc5L\xd7S\xe6\xfd[V\x9d\x85L\xb0\xa3+R\xa1\xb1}\xde~O\xc9\xd3\xf7B\x15_d\x9a\x14|W\xb0[\xa5\xba\xbc\xd2\xd3\xf8\x16\xe7\x04\xaa~\x02@\xfc)"\xa5\xd6\x90\x08\x86g\xd5\xbc\x8e\xe6\xe5(\x954\xd6\xaf\xe2\x1dt\x19%\x1a\x87\x84\xf3\xcf\x16\x18\xe3\x8dHl~\xa3\x89b\xa3\xe0\xa1D\xff+\xa0\xe4\xba(\xa5\x86\x81X\xb0\x91w\xbd\xda}\xe4Q\xb0I\xd8\xb9\xe5\xa2\xa9+\xfc\xc5\xc3X\t5\xcb\t\x05\xe0+9\xfdw\xb1\xc0\xb3\xdcA$G\x8e\xb3\xf3\xcb\x01\xa9\xec\xea*.(0G\xd5\x9e\x1a\xf8\x91M/\x17\xa2\x87v\x10O\xe4\x87\x10 \xf7\xea\x11\x9e\xcc\xb7uP\\\xa9BP\x12\xf9\xdc\n\xa3\x92\x0bd\xe2\xd7\\\x9c\xf5\xf3>\xfaF\xbbk\xdc\x85u\x84;\x8d\xca\x92\x8c#\xb0H\x9b\xbd\xca\xf5\x05\xae\xa7=\x97\xc1\x1b\xb4\x9a\xa39\tSB\xff\xf8\xe2\xcc\x83}\x07\xc4\xd3\xf2?\xe7\xbd|\x14@\xac\xe72\xfe16\x82\xcch\xe5\x84\xfbj\xb8F\xec\xdc\xcaQ\xad\xceK\xe3\'\xc2&<\x1f2\x18S\xe3\xfd\x10uF\x13\x10|\xe6\x01\x87\xa1\x1f\xb9\x90\xdexU\xff\xd3&\xfc]\x1f3\x99.c\x915\xc5ho\xe2J\xd6R\x16\xb6i\x80\x12\xd9\xda*Z\x10\xb6\xe1\xac\xc5\x80\xb8\xb7-3 T\x8cG\xa5\x157\xed.o(I4\xb6\xc6\x91\xea\x08\xdd\xa5@a', none, b'',
			# Cheat: Python2 and Python3 behave differently here but they're both OK
			b"error: Invalid config: \xbb\xe7Z\xfe)(\x1f\x00\xcfI\xdef\x81\x8a\xfc\xe9Oz|\xf4\xe9/\x03\x1cn\xb1\xd8\x92G\xf8-\x04\xfe\xee@h\xc3 [.test.sshdoers line 1]\nerror: Invalid config: \xa29\x8f\xd2\xbb\x89\xdd\xfa\x19\x881\xa8\xb3\xc7\x996\xd5\x8b&\x87\xf0d\x01\xc6\x8c\x85\xa32D\x98\xa5\xa4$E3\x86\xb4\x13\xa4E\xb0r\xcd\xa4\xc2\xa1Fk\x9d>\x18#n\x00lH\xbc5}4\x08\x1a [.test.sshdoers line 2]\nwarning: No such user: \xf6b\xe3\x9f\xd90n\xac\xa6A\xcb6\x95\xfdK\xbaJ4 [.test.sshdoers line 3]\nwarning: No such user: C4+\xae\x8faM\xe8T\x83\xf4.\xb3\xbds\x06\x01\xb6\x9eb\xda\x11A [.test.sshdoers line 3]\nwarning: No such user: i\x85\x95y\xa2H\xa3oG\xf2c(\x9e\x93\xf6\xfbX [.test.sshdoers line 3]\nwarning: No such user: *\x8aA\xa1\xf9Kr\x7f\xe8v [.test.sshdoers line 4]\nerror: Invalid config: + [.test.sshdoers line 5]\nerror: Invalid config: D\xd1e\x1c\xde\xc7[\x12\x06\xa1\xf4\x10\xce\xe1pUU\xa0\xd1H\x867\\\x00\xb7\xc8#&6G\x1a\x82F\xee\xd6\x19l [.test.sshdoers line 6]\nerror: Invalid config: \x9a\xe7\xa8\x89HO\xa6Z`Z\xc8'O\x84}\x1d=16\xdb\xad\xb7o|NR\xe1\xdc\xed-Nfc\x07}\xfb\x0c\xc79\x8a\x13\xc2?\x9bM\xaeg\xf8\xc9\xbd\xa6\xfaW\xcc\xa7\\|\\#\xff\xd3 \xec\xb2\x83q\x12\x02\x1f8\x94\xc9\xb4*g\xa2\x1fq$\xe8\xfb)a\xf1@\xab\x92\xfb]K9\xdf\xe0\xe5\xbd\xbd\x14\x82\xe9\xf0u\x80\xcfi`\xe6\xc3\xf8B!\x8e\x0c\xacNV\xc0\x8d\xd9\xb7\xa2\xe4T\x12\xe8'R [.test.sshdoers line 7]\nwarning: No such user: \xe4\xdc\xcfG\xa6\xaf\xb3g\xea\x13{\xd2(\x9d [.test.sshdoers line 8]\nerror: Invalid config: \xa3\x92\x0bd\xe2\xd7\\\x9c\xf5\xf3>\xfaF\xbbk\xdc\x85u\x84;\x8d\xca\x92\x8c#\xb0H\x9b\xbd\xca\xf5\x05\xae\xa7=\x97\xc1\x1b\xb4\x9a\xa39\tSB\xff\xf8\xe2\xcc\x83}\x07\xc4\xd3\xf2?\xe7\xbd|\x14@\xac\xe72\xfe16\x82\xcch\xe5\x84\xfbj\xb8F\xec\xdc\xcaQ\xad\xceK\xe3'\xc2&<\x1f2\x18S\xe3\xfd\x10uF\x13\x10|\xe6\x01\x87\xa1\x1f\xb9\x90\xdexU\xff\xd3&\xfc]\x1f3\x99.c\x915\xc5ho\xe2J\xd6R\x16\xb6i\x80\x12\xd9\xda*Z\x10\xb6\xe1\xac\xc5\x80\xb8\xb7-3 T\x8cG\xa5\x157\xed.o(I4\xb6\xc6\x91\xea\x08\xdd\xa5@a [.test.sshdoers line 9]\n" if py2 else
			[
				b"error: Failed to read: .test.sshdoers: 'utf-8' codec can't decode byte 0xbb in position 0: invalid start byte\n",
				b"error: Failed to read: .test.sshdoers: 'utf-8' codec can't decode byte 0xbb in position 0: invalid start byte\nwarning: No default log files: /var/log/auth.log*\n"
			]
		],
		# Check that duplicate authorization directives are OK
		['root: echo\n', none, '.test.sshdoers syntax OK\n', ''],
		['root root: echo\n', none, '.test.sshdoers syntax OK\n', ''],
		['root root root: echo\n', none, '.test.sshdoers syntax OK\n', ''],
		['root: echo\nroot: echo\n', none, '.test.sshdoers syntax OK\n', ''],
		['root: echo\nroot: echo\nroot: echo\n', none, '.test.sshdoers syntax OK\n', ''],
		# Check that duplicate training directives are OK
		['training root\n', none, '.test.sshdoers syntax OK\n', ''],
		['training root root\n', none, '.test.sshdoers syntax OK\n', ''],
		['training root root root\n', none, '.test.sshdoers syntax OK\n', ''],
		['training root\ntraining root\n', none, '.test.sshdoers syntax OK\n', ''],
		['training root\ntraining root\ntraining root\n', none, '.test.sshdoers syntax OK\n', ''],
	]
	if not os.path.exists('/var/log/auth.log'):
		test_data.append(['# suppress logfiles\n', none, '', 'warning: No default log files: /var/log/auth.log*\n'])
	else:
		test_data.append(['# suppress logfiles\n', none, '.test.sshdoers syntax OK\n', ''])

	num_tests = 0
	num_errors = 0

	if not os.path.isdir(test_configd_dname):
		os.mkdir(test_configd_dname)
	writefile(test_auth_log, '')
	for config_text, configd_text, expected_output, expected_error in test_data:
		num_tests += 1
		# Override the default log files because not all systems have the default
		need_bytes = not py2 and type(config_text) == bytes
		nl = b'\n' if need_bytes else '\n'
		if not config_text.endswith(nl):
			config_text += nl
		if (b'logfiles ' if need_bytes else 'logfiles ') not in config_text and (b'suppress logfiles' if need_bytes else 'suppress logfiles') not in config_text:
			config_text += (b'logfiles %s' if need_bytes else 'logfiles %s') % (test_auth_log.encode('utf-8') if need_bytes else test_auth_log)
		writefile(test_config_fname, config_text)
		if configd_text:
			writefile(test_configd_fname, configd_text)
		os.system("./sshdo --check %s > %s 2> %s" % (test_config_fname, test_output_fname, test_error_fname))
		result_output = readfile(test_output_fname, need_bytes)
		result_error = readfile(test_error_fname, need_bytes)
		os.unlink(test_config_fname)
		if configd_text:
			os.unlink(test_configd_fname)
		os.unlink(test_output_fname)
		os.unlink(test_error_fname)
		if result_output != expected_output or not any([result_error == _ for _ in enlist(expected_error)]):
			num_errors += 1
			if result_output != expected_output:
				print('Test %d: check_config %r %r\nresult_output =   %r\nexpected_output = %r' % (num_tests, config_text, configd_text, result_output, expected_output))
			if not any([result_error == _ for _ in expected_error]):
				print('Test %d: check_config %r %r\nresult_error =   %r\nexpected_error = %r' % (num_tests, config_text, configd_text, result_error, enlist(expected_error)[0]))
	os.rmdir(test_configd_dname)
	os.unlink(test_auth_log)
	print(('All %s check_config tests passed' % num_tests) if num_errors == 0 else ('%s/%s check_config tests failed' % (num_errors, num_tests)))
	return num_errors

def enlist(arg):
	'''If arg is a list, return it. Otherwise, return a list containing arg as its only element.'''
	return arg if isinstance(arg, list) else [arg]

def test_learn():
	'''Test --learn.'''
	test_config_fname = '.test.sshdoers'
	test_logfile_fname = '.test.logfile'
	test_output_fname = '.test.output'
	test_data = [
		[
			'''
			# Test training with no existing config, no labels, digits matching and mismatching config=""
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers2"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers2"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c" group="staff" config=".test.sshdoers2"
			''',
			'''
			bin: echo
			root: echo 2
			root: echo a
			+staff: echo b
			'''
		],
		[
			'''
			# Test training with no existing config, no labels, digits matching
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo #
			root: echo a
			+staff nobody: echo b
			+staff: echo c
			'''
		],
		[
			'''
			# Test training with some existing config, no labels, digits matching
			training
			root: echo
			+staff: echo b
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c
			'''
		],
		[
			'''
			# Test training with sufficient existing config, no labels, digits matching
			training
			bin nobody root: echo
			root: echo #
			root: echo a
			+staff nobody: echo b
			+staff: echo c
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training with no existing config, labels, digits matching
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			+staff/label nobody/label: echo b
			+staff/label: echo c
			'''
		],
		[
			'''
			# Test training with some existing config, labels, digits matching
			training
			root: echo
			+staff/label: echo b
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c
			'''
		],
		[
			'''
			# Test training with sufficient existing config, labels, digits matching
			training
			bin nobody root: echo
			root: echo #
			root: echo a
			+staff nobody: echo b
			+staff: echo c
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training with no existing config, no labels, hexdigits matching
			training
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			nobody root: echo #
			+staff: echo x #
			'''
		],
		[
			'''
			# Test training with some existing config, no labels, hexdigits matching
			training
			match hexdigits
			root: echo
			+staff: echo x #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody: echo
			nobody root: echo #
			+staff: echo y #
			'''
		],
		[
			'''
			# Test training with sufficient existing config, no labels, hexdigits matching
			training
			match hexdigits
			bin nobody root: echo
			nobody root: echo #
			+staff: echo x #
			+staff: echo y #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training with no existing config, labels, hexdigits matching
			training
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			nobody/label root/label: echo #
			+staff/label: echo x #
			+staff/label: echo y #
			'''
		],
		[
			'''
			# Test training with some existing config, labels, hexdigits matching
			training
			match hexdigits
			root: echo
			+staff: echo x #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label: echo
			nobody/label root/label: echo #
			+staff/label: echo y #
			'''
		],
		[
			'''
			# Test training with sufficient existing config, labels, hexdigits matching
			training
			match hexdigits
			bin nobody root: echo
			nobody root: echo #
			+staff/label: echo x #
			+staff: echo y #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training no existing config, no labels, exact matching
			training
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x a
			+staff: echo y 1
			+staff: echo y a
			'''
		],
		[
			'''
			# Test training with some existing config, no labels, exact matching
			training
			match exact
			root: echo
			+staff: echo x 1
			+staff: echo x a
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody: echo
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo y 1
			+staff: echo y a
			'''
		],
		[
			'''
			# Test training with sufficient existing config, no labels, exact matching
			training
			match exact
			bin nobody root: echo
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x a
			+staff: echo y 1
			+staff: echo y a
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training no existing config, labels, exact matching
			training
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x a
			+staff/label: echo y 1
			+staff/label: echo y a
			'''
		],
		[
			'''
			# Test training with some existing config, labels, exact matching
			training
			match exact
			root: echo
			+staff/label: echo x 1
			+staff/label: echo x a
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label: echo
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo y 1
			+staff/label: echo y a
			'''
		],
		[
			'''
			# Test training with sufficient existing config, labels, exact matching
			training
			match exact
			bin nobody root: echo
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff/label: echo x 1
			+staff/label: echo x a
			+staff/label: echo y 1
			+staff/label: echo y a
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x a" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y a" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test training with no existing config, no labels, digit matching, quoted characters in command
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a   b\\"" config=".test.sshdoers"
			''',
			'''
			root: echo "a   b"
			'''
		],
		[
			'''
			# Test training with no existing config, no labels, digit matching, binary characters in command
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a\\x0ab\\"" config=".test.sshdoers"
			''',
			'''
			root: <binary> echo "a\\x0ab"
			'''
		],
		[
			'''
			# Test binary garbage in log file
			training
			''',
			'''
			\xbb\xe7Z\xfe)(\x1f\x00\xcfI\xdef\x81\x8a\xfc\xe9Oz|\xf4\xe9/\x03\x1cn\xb1\xd8\x92G\xf8-\x04\xfe\xee@h\xc3\n\xa29\x8f\xd2\xbb\x89\xdd\xfa\x19\x881\xa8\xb3\xc7\x996\xd5\x8b&\x87\xf0d\x01\xc6\x8c\x85\xa32D\x98\xa5\xa4$E3\x86\xb4\x13\xa4E\xb0r\xcd\xa4\xc2\xa1Fk\x9d>\x18#n\x00lH\xbc5}4\x08\x1a\n\xf6b\xe3\x9f\xd90n\xac\xa6A\xcb6\x95\xfdK\xbaJ4 C4+\xae\x8faM\xe8T\x83\xf4.\xb3\xbds\x06\x01\xb6\x9eb\xda\x11A i\x85\x95y\xa2H\xa3oG\xf2c(\x9e\x93\xf6\xfbX:\xe6\'\x98\x8fXsL\xde\xf2\xa2\xc6u3~M\x03d\x00\x12wv\xaa\x8c\xa2>,Y\xd2\x8f\xed3g\x16\x96:\xc1,\xaak\xac\xbd\xcdBC\x01\xde\xe0\'\x89x\\>9Es=\xff|@\x9f97eD?\x80\xed!T\x1d\x8f>\xbd\x886g\x16>#\xb2\xe1\x91\x8a\x08\x85\xa8`\x95\xfcQ\xc74\x0f\x1f\xc0g\xed\x89\xd4[\x12s\x13F\xad\x9f\xf8MV\x95\'2\x03^\xd7\xbcK\x99\x11D\xaf\x89>\x13\'iL\xbe%\xc8\xbf\xc8\tz\xe4\'\x828\xa6\xc3\x06=7\xfb+\x9d&\x11j\xca}~=h`\xc0\xb2\xae\xa4<D\x07\x9e\xa2\x12\x88\x14\xb7\xb48\xcc\xd9\xe0^3\xf1n\xf9U\x99p\xeb\xd5\xaf\xbe4\xad\x1a\xa3\x88\x1f\x9d]H\x14a$"Q`\x90\x08\x02\x13X}\xb4\xb3{\x1fdwoV\xec\xff\x1c\x146\xf6"H\xae\x03\x0f\xa4\xeaI\xab\x85\\\xf6L\xf1\x92\xa7\xd7\xa3\r*\x8aA\xa1\xf9Kr\x7f\xe8v:C\x96\xc54q\x9dVX\xc5\xc9w\xa0\x14\xd0\x91\x80\xd2`\x1a\xaf\x0e0//\n+\rD\xd1e\x1c\xde\xc7[\x12\x06\xa1\xf4\x10\xce\xe1pUU\xa0\xd1H\x867\\\x00\xb7\xc8#&6G\x1a\x82F\xee\xd6\x19l\n\x9a\xe7\xa8\x89HO\xa6Z`Z\xc8\'O\x84}\x1d=16\xdb\xad\xb7o|NR\xe1\xdc\xed-Nfc\x07}\xfb\x0c\xc79\x8a\x13\xc2?\x9bM\xaeg\xf8\xc9\xbd\xa6\xfaW\xcc\xa7\\|\\#\xff\xd3 \xec\xb2\x83q\x12\x02\x1f8\x94\xc9\xb4*g\xa2\x1fq$\xe8\xfb)a\xf1@\xab\x92\xfb]K9\xdf\xe0\xe5\xbd\xbd\x14\x82\xe9\xf0u\x80\xcfi`\xe6\xc3\xf8B!\x8e\x0c\xacNV\xc0\x8d\xd9\xb7\xa2\xe4T\x12\xe8\'R\n\xe4\xdc\xcfG\xa6\xaf\xb3g\xea\x13{\xd2(\x9d:\xaf\xa0\x16$\xbbzr6B\x1d\xe4Y\x8e4%pwb>\xe4\xebr\xd4\\\x9c\xe4\xdc\x82\x86VNo\xad\xcf\t\xd7fK\x8fu\x15\xaa\xff\x97\xbf\xce\x1e\xfen\xa9S\xdc\xc5L\xd7S\xe6\xfd[V\x9d\x85L\xb0\xa3+R\xa1\xb1}\xde~O\xc9\xd3\xf7B\x15_d\x9a\x14|W\xb0[\xa5\xba\xbc\xd2\xd3\xf8\x16\xe7\x04\xaa~\x02@\xfc)"\xa5\xd6\x90\x08\x86g\xd5\xbc\x8e\xe6\xe5(\x954\xd6\xaf\xe2\x1dt\x19%\x1a\x87\x84\xf3\xcf\x16\x18\xe3\x8dHl~\xa3\x89b\xa3\xe0\xa1D\xff+\xa0\xe4\xba(\xa5\x86\x81X\xb0\x91w\xbd\xda}\xe4Q\xb0I\xd8\xb9\xe5\xa2\xa9+\xfc\xc5\xc3X\t5\xcb\t\x05\xe0+9\xfdw\xb1\xc0\xb3\xdcA$G\x8e\xb3\xf3\xcb\x01\xa9\xec\xea*.(0G\xd5\x9e\x1a\xf8\x91M/\x17\xa2\x87v\x10O\xe4\x87\x10 \xf7\xea\x11\x9e\xcc\xb7uP\\\xa9BP\x12\xf9\xdc\n\xa3\x92\x0bd\xe2\xd7\\\x9c\xf5\xf3>\xfaF\xbbk\xdc\x85u\x84;\x8d\xca\x92\x8c#\xb0H\x9b\xbd\xca\xf5\x05\xae\xa7=\x97\xc1\x1b\xb4\x9a\xa39\tSB\xff\xf8\xe2\xcc\x83}\x07\xc4\xd3\xf2?\xe7\xbd|\x14@\xac\xe72\xfe16\x82\xcch\xe5\x84\xfbj\xb8F\xec\xdc\xcaQ\xad\xceK\xe3\'\xc2&<\x1f2\x18S\xe3\xfd\x10uF\x13\x10|\xe6\x01\x87\xa1\x1f\xb9\x90\xdexU\xff\xd3&\xfc]\x1f3\x99.c\x915\xc5ho\xe2J\xd6R\x16\xb6i\x80\x12\xd9\xda*Z\x10\xb6\xe1\xac\xc5\x80\xb8\xb7-3 T\x8cG\xa5\x157\xed.o(I4\xb6\xc6\x91\xea\x08\xdd\xa5@a
			\xbb\xe7Z\xfe)(\x1f\x00\xcfI\xdef\x81\x8a\xfc\xe9Oz|\xf4\xe9/\x03\x1cn\xb1\xd8\x92G\xf8-\x04\xfe\xee@h\xc3\n\xa29\x8f\xd2\xbb\x89\xdd\xfa\x19\x881\xa8\xb3\xc7\x996\xd5\x8b&\x87\xf0d\x01\xc6\x8c\x85\xa32D\x98\xa5\xa4$E3\x86\xb4\x13\xa4E\xb0r\xcd\xa4\xc2\xa1Fk\x9d>\x18#n\x00lH\xbc5}4\x08\x1a\n\xf6b\xe3\x9f\xd90n\xac\xa6A\xcb6\x95\xfdK\xbaJ4 C4+\xae\x8faM\xe8T\x83\xf4.\xb3\xbds\x06\x01\xb6\x9eb\xda\x11A i\x85\x95y\xa2H\xa3oG\xf2c(\x9e\x93\xf6\xfbX:\xe6\'\x98\x8fXsL\xde\xf2\xa2\xc6u3~M\x03d\x00\x12wv\xaa\x8c\xa2>,Y\xd2\x8f\xed3g\x16\x96:\xc1,\xaak\xac\xbd\xcdBC\x01\xde\xe0\'\x89x\\>9Es=\xff|@\x9f97eD?\x80\xed!T\x1d\x8f>\xbd\x886g\x16>#\xb2\xe1\x91\x8a\x08\x85\xa8`\x95\xfcQ\xc74\x0f\x1f\xc0g\xed\x89\xd4[\x12s\x13F\xad\x9f\xf8MV\x95\'2\x03^\xd7\xbcK\x99\x11D\xaf\x89>\x13\'iL\xbe%\xc8\xbf\xc8\tz\xe4\'\x828\xa6\xc3\x06=7\xfb+\x9d&\x11j\xca}~=h`\xc0\xb2\xae\xa4<D\x07\x9e\xa2\x12\x88\x14\xb7\xb48\xcc\xd9\xe0^3\xf1n\xf9U\x99p\xeb\xd5\xaf\xbe4\xad\x1a\xa3\x88\x1f\x9d]H\x14a$"Q`\x90\x08\x02\x13X}\xb4\xb3{\x1fdwoV\xec\xff\x1c\x146\xf6"H\xae\x03\x0f\xa4\xeaI\xab\x85\\\xf6L\xf1\x92\xa7\xd7\xa3\r*\x8aA\xa1\xf9Kr\x7f\xe8v:C\x96\xc54q\x9dVX\xc5\xc9w\xa0\x14\xd0\x91\x80\xd2`\x1a\xaf\x0e0//\n+\rD\xd1e\x1c\xde\xc7[\x12\x06\xa1\xf4\x10\xce\xe1pUU\xa0\xd1H\x867\\\x00\xb7\xc8#&6G\x1a\x82F\xee\xd6\x19l\n\x9a\xe7\xa8\x89HO\xa6Z`Z\xc8\'O\x84}\x1d=16\xdb\xad\xb7o|NR\xe1\xdc\xed-Nfc\x07}\xfb\x0c\xc79\x8a\x13\xc2?\x9bM\xaeg\xf8\xc9\xbd\xa6\xfaW\xcc\xa7\\|\\#\xff\xd3 \xec\xb2\x83q\x12\x02\x1f8\x94\xc9\xb4*g\xa2\x1fq$\xe8\xfb)a\xf1@\xab\x92\xfb]K9\xdf\xe0\xe5\xbd\xbd\x14\x82\xe9\xf0u\x80\xcfi`\xe6\xc3\xf8B!\x8e\x0c\xacNV\xc0\x8d\xd9\xb7\xa2\xe4T\x12\xe8\'R\n\xe4\xdc\xcfG\xa6\xaf\xb3g\xea\x13{\xd2(\x9d:\xaf\xa0\x16$\xbbzr6B\x1d\xe4Y\x8e4%pwb>\xe4\xebr\xd4\\\x9c\xe4\xdc\x82\x86VNo\xad\xcf\t\xd7fK\x8fu\x15\xaa\xff\x97\xbf\xce\x1e\xfen\xa9S\xdc\xc5L\xd7S\xe6\xfd[V\x9d\x85L\xb0\xa3+R\xa1\xb1}\xde~O\xc9\xd3\xf7B\x15_d\x9a\x14|W\xb0[\xa5\xba\xbc\xd2\xd3\xf8\x16\xe7\x04\xaa~\x02@\xfc)"\xa5\xd6\x90\x08\x86g\xd5\xbc\x8e\xe6\xe5(\x954\xd6\xaf\xe2\x1dt\x19%\x1a\x87\x84\xf3\xcf\x16\x18\xe3\x8dHl~\xa3\x89b\xa3\xe0\xa1D\xff+\xa0\xe4\xba(\xa5\x86\x81X\xb0\x91w\xbd\xda}\xe4Q\xb0I\xd8\xb9\xe5\xa2\xa9+\xfc\xc5\xc3X\t5\xcb\t\x05\xe0+9\xfdw\xb1\xc0\xb3\xdcA$G\x8e\xb3\xf3\xcb\x01\xa9\xec\xea*.(0G\xd5\x9e\x1a\xf8\x91M/\x17\xa2\x87v\x10O\xe4\x87\x10 \xf7\xea\x11\x9e\xcc\xb7uP\\\xa9BP\x12\xf9\xdc\n\xa3\x92\x0bd\xe2\xd7\\\x9c\xf5\xf3>\xfaF\xbbk\xdc\x85u\x84;\x8d\xca\x92\x8c#\xb0H\x9b\xbd\xca\xf5\x05\xae\xa7=\x97\xc1\x1b\xb4\x9a\xa39\tSB\xff\xf8\xe2\xcc\x83}\x07\xc4\xd3\xf2?\xe7\xbd|\x14@\xac\xe72\xfe16\x82\xcch\xe5\x84\xfbj\xb8F\xec\xdc\xcaQ\xad\xceK\xe3\'\xc2&<\x1f2\x18S\xe3\xfd\x10uF\x13\x10|\xe6\x01\x87\xa1\x1f\xb9\x90\xdexU\xff\xd3&\xfc]\x1f3\x99.c\x915\xc5ho\xe2J\xd6R\x16\xb6i\x80\x12\xd9\xda*Z\x10\xb6\xe1\xac\xc5\x80\xb8\xb7-3 T\x8cG\xa5\x157\xed.o(I4\xb6\xc6\x91\xea\x08\xdd\xa5@a
			''',
			'''
			'''
		]
	]

	num_tests = 0
	num_errors = 0

	import sshdo
	for config_text, logfile_text, expected_text in test_data:
		config_text = sshdo.s('^\t+', '', config_text, 'm')
		logfile_text = sshdo.s('^\t+', '', logfile_text, 'm')
		expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', expected_text, 'm'))
		num_tests += 1
		writefile(test_config_fname, config_text)
		writefile(test_logfile_fname, logfile_text)
		os.system("./sshdo --config %s --learn %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		os.unlink(test_config_fname)
		os.unlink(test_logfile_fname)
		os.unlink(test_output_fname)
		if result != expected_text:
			num_errors += 1
			print('Test %d: learn\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, expected_text))
	print(('All %s learn tests passed' % num_tests) if num_errors == 0 else ('%s/%s learn tests failed' % (num_errors, num_tests)))
	return num_errors

def test_learn_accepting():
	'''Test --learn --accepting.'''
	test_config_fname = '.test.sshdoers'
	test_logfile_fname = '.test.logfile'
	test_output_fname = '.test.output'
	test_data = [
		[
			'''
			# Test learn with/without accepting with no labels, digits matching (not needed)
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo 2
			# drew: echo 2
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with no labels, digits matching
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo #
			# drew mary: echo #
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim mary root: echo #
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with no labels, hexdigits matching
			training
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			+staff jim root: echo #
			# +other drew mary: echo #
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			+other +staff drew jim mary root: echo #
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with no labels, exact matching
			training
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo 2
			# drew: echo 2
			jim root: echo 3
			# drew: echo 3
			mary: echo 4
			# mary: echo 5
			mary: echo 6
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo 3
			mary: echo 4
			mary: echo 5
			mary: echo 6
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with labels, digits matching (not needed)
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo 2
			# drew/label: echo 2
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with labels, digits matching
			training
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo #
			# drew/label mary/label: echo #
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with labels, hexdigits matching
			training
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			+staff/label1 +staff/label2 jim/label root/label: echo #
			# +other/label1 drew/label mary/label: echo #
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			+other/label1 +staff/label1 +staff/label2 drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test learn with/without accepting with labels, exact matching
			training
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo 2
			# drew/label: echo 2
			jim/label root/label: echo 3
			# drew/label: echo 3
			mary/label: echo 4
			# mary/label: echo 5
			mary/label: echo 6
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo 3
			mary/label: echo 4
			mary/label: echo 5
			mary/label: echo 6
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
	]

	num_tests = 0
	num_errors = 0

	import sshdo
	for config_text, logfile_text, expected_text, accepting_expected_text in test_data:
		config_text = sshdo.s('^\t+', '', config_text, 'm')
		logfile_text = sshdo.s('^\t+', '', logfile_text, 'm')
		expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', expected_text, 'm'))
		accepting_expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', accepting_expected_text, 'm'))
		writefile(test_config_fname, config_text)
		writefile(test_logfile_fname, logfile_text)
		num_tests += 1
		os.system("./sshdo --config %s --learn %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		os.unlink(test_output_fname)
		if result != expected_text:
			num_errors += 1
			print('Test %d: learn without accepting\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, expected_text))
		num_tests += 1
		os.system("./sshdo --config %s --learn --accepting %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		os.unlink(test_output_fname)
		if result != accepting_expected_text:
			num_errors += 1
			print('Test %d: learn with accepting\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, accepting_expected_text))
		os.unlink(test_config_fname)
		os.unlink(test_logfile_fname)
	print(('All %s learn accepting tests passed' % num_tests) if num_errors == 0 else ('%s/%s learn tests failed' % (num_errors, num_tests)))
	return num_errors

def test_unlearn():
	'''Test --unlearn.'''
	test_config_fname = '.test.sshdoers'
	test_logfile_fname = '.test.logfile'
	test_output_fname = '.test.output'
	test_data = [
		[
			'''
			# Test unlearn with no config, no labels, exact matching
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient-surplus config, no labels, exact matching but mismatching config=""
			match exact
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo 1
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers2"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers2"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo 2" group="staff"
			''',
			'''
			bin: echo
			# nobody root: echo
			root: echo #
			+staff: echo 1
			# root: echo 1
			root: echo 2
			root: echo a
			# nobody: echo b
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels, exact matching
			match exact
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo 1
			+staff: echo 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo #
			+staff root: echo 1
			+staff root: echo 2
			root: echo a
			nobody: echo b
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels, exact matching
			match exact
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			+staff: echo y 1
			+staff: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			# root: echo #
			# root: echo 1
			# root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			# +staff: echo y 1
			# +staff: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with no config, no labels in config, labels in log messages, exact matching
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels in config, labels in log messages, exact matching
			match exact
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			+staff: echo y 1
			+staff: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			+staff: echo y 1
			+staff: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels in config, labels in log messages, exact matching
			match exact
			bin nobody root: echo
			root: echo #
			root: echo 1
			root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			+staff: echo y 1
			+staff: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			# root: echo #
			# root: echo 1
			# root: echo 2
			root: echo a
			nobody: echo b
			+staff: echo x 1
			+staff: echo x 2
			# +staff: echo y 1
			# +staff: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with no config, labels in config (not really), labels in log messages, exact matching
			match exact
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, labels in log messages, exact matching
			match exact
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			+staff/label: echo y 1
			+staff/label: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			+staff/label: echo y 1
			+staff/label: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, different labels in log messages, exact matching
			# (existing config doesn't apply and so is commented out)
			match exact
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			+staff/label: echo y 1
			+staff/label: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# root/label: echo #
			# root/label: echo 1
			# root/label: echo 2
			# root/label: echo a
			# nobody/label: echo b
			# +staff/label: echo x 1
			# +staff/label: echo x 2
			# +staff/label: echo y 1
			# +staff/label: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, no labels in log messages, exact matching
			# (existing config doesn't apply and so is commented out)
			match exact
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			+staff/label: echo y 1
			+staff/label: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# root/label: echo #
			# root/label: echo 1
			# root/label: echo 2
			# root/label: echo a
			# nobody/label: echo b
			# +staff/label: echo x 1
			# +staff/label: echo x 2
			# +staff/label: echo y 1
			# +staff/label: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with surplus config, labels in config, labels in log messages, exact matching
			match exact
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo 1
			root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			+staff/label: echo y 1
			+staff/label: echo y 2
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			# root/label: echo #
			# root/label: echo 1
			# root/label: echo 2
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo x 1
			+staff/label: echo x 2
			# +staff/label: echo y 1
			# +staff/label: echo y 2
			'''
		],
		[
			'''
			# Test unlearn with no config, no labels, digits matching
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels, digits matching
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels, digits matching
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			# root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			# +staff: echo d #
			'''
		],
		[
			'''
			# Test unlearn with no config, no labels in config, labels in log messages, digits matching
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels in config, labels in log messages, digits matching
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels in config, labels in log messages, digits matching
			bin nobody root: echo
			root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			+staff: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			# root: echo #
			root: echo a
			nobody: echo b
			+staff: echo c #
			# +staff: echo d #
			'''
		],
		[
			'''
			# Test unlearn with no config, labels in config (not really), labels in log messages, digits matching
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, labels in log messages, digits matching
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			+staff/label: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			+staff/label: echo d #
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, different labels in log messages, digits matching
			# (existing config doesn't apply and so is commented out)
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			+staff/label: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# root/label: echo #
			# root/label: echo a
			# nobody/label: echo b
			# +staff/label: echo c #
			# +staff/label: echo d #
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, no labels in log messages, digits matching
			# (existing config doesn't apply and so is commented out)
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			+staff/label: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# root/label: echo #
			# root/label: echo a
			# nobody/label: echo b
			# +staff/label: echo c #
			# +staff/label: echo d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, labels in config, labels in log messages, digits matching
			bin/label nobody/label root/label: echo
			root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			+staff/label: echo d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			# root/label: echo #
			root/label: echo a
			nobody/label: echo b
			+staff/label: echo c #
			# +staff/label: echo d #
			'''
		],
		[
			'''
			# Test unlearn with no config, no labels, hexdigits matching
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels, hexdigits matching
			match hexdigits
			bin nobody root: echo
			root: echo #
			+staff: echo # #
			nobody: echo b
			root: echo j
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			nobody root: echo #
			+staff: echo # #
			root: echo j
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels, hexdigits matching
			match hexdigits
			bin nobody root: echo
			root: echo #
			nobody: echo b
			root: echo j
			+staff: echo x c #
			+staff: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x c 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			nobody root: echo #
			root: echo j
			+staff: echo x c #
			# +staff: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with no config, no labels in config, labels in log messages, hexdigits matching
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, no labels in config, labels in log messages, hexdigits matching
			match hexdigits
			bin nobody root: echo
			root: echo #
			nobody: echo b
			root: echo j
			+staff: echo # #
			+staff: echo x c #
			+staff: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			nobody root: echo #
			+staff: echo # #
			root: echo j
			+staff: echo x c #
			+staff: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, no labels in config, labels in log messages, hexdigits matching
			match hexdigits
			bin nobody root: echo
			root: echo #
			nobody: echo b
			root: echo j
			+staff: echo # #
			+staff: echo x c #
			+staff: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin nobody root: echo
			nobody: echo #
			# root: echo #
			+staff: echo # #
			root: echo j
			+staff: echo x c #
			# +staff: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with no config, labels in config (not really), labels in log messages, hexdigits matching
			match hexdigits
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, labels in log messages, hexdigits matching
			match hexdigits
			bin/label nobody/label root/label: echo
			root/label: echo #
			nobody/label: echo b
			root/label: echo j
			+staff/label: echo # #
			+staff/label: echo x c #
			+staff/label: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			nobody/label root/label: echo #
			+staff/label: echo # #
			root/label: echo j
			+staff/label: echo x c #
			+staff/label: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, different labels in log messages, hexdigits matching
			# (existing config doesn't apply and so is commented out)
			match hexdigits
			bin/label nobody/label root/label: echo
			root/label: echo #
			nobody/label: echo b
			root/label: echo j
			+staff/label: echo # #
			+staff/label: echo x c #
			+staff/label: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label2" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label2" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label2" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label2" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# nobody/label root/label: echo #
			# +staff/label: echo # #
			# root/label: echo j
			# +staff/label: echo x c #
			# +staff/label: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with sufficient config, labels in config, no labels in log messages, hexdigits matching
			# (existing config doesn't apply and so is commented out)
			match hexdigits
			bin/label nobody/label root/label: echo
			root/label: echo #
			nobody/label: echo b
			root/label: echo j
			+staff/label: echo # #
			+staff/label: echo x c #
			+staff/label: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo #" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 1" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo d 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			# bin/label nobody/label root/label: echo
			# nobody/label root/label: echo #
			# +staff/label: echo # #
			# root/label: echo j
			# +staff/label: echo x c #
			# +staff/label: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, labels in config, labels in log messages, hexdigits matching
			match hexdigits
			bin/label nobody/label root/label: echo
			root/label: echo #
			nobody/label: echo b
			root/label: echo j
			+staff/label: echo # #
			+staff/label: echo x c #
			+staff/label: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 2" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo y d 2" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			nobody/label: echo #
			# root/label: echo #
			# +staff/label: echo # #
			root/label: echo j
			+staff/label: echo x c #
			+staff/label: echo y d #
			'''
		],
		[
			'''
			# Test unlearn with surplus config, labels in config, labels in log messages, hexdigits matching, different
			match hexdigits
			bin/label nobody/label root/label: echo
			root/label: echo #
			nobody/label: echo b
			root/label: echo j
			+staff/label: echo # #
			+staff/label: echo x c #
			+staff/label: echo y d #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo j" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="nobody" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo c 1" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jude" remoteip="10.0.0.1" label="label" command="echo x c 1" group="staff" config=".test.sshdoers"
			''',
			'''
			bin/label nobody/label root/label: echo
			nobody/label: echo #
			# root/label: echo #
			+staff/label: echo # #
			root/label: echo j
			+staff/label: echo x c #
			# +staff/label: echo y d #
			'''
		],

		[
			'''
			# Test sufficient config, quoted characters in command
			root: echo "a   b"
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a   b\\"" config=".test.sshdoers"
			''',
			'''
			root: echo "a   b"
			'''
		],
		[
			'''
			# Test surplus config, no log messages, quoted characters in command
			root: echo "a   b"
			''',
			'''
			''',
			'''
			# root: echo "a   b"
			'''
		],
		[
			'''
			# Test sufficient config, binary characters in command
			root: <binary> echo "a\\x0ab"
			root: <binary>echo "a\\x0abc"
			root: <binary>  echo "a\\x0abd"
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a\\x0ab\\"" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a\\x0abc\\"" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo \\"a\\x0abd\\"" config=".test.sshdoers"
			''',
			'''
			root: <binary> echo "a\\x0ab"
			root: <binary> echo "a\\x0abc"
			root: <binary> echo "a\\x0abd"
			'''
		],
		[
			'''
			# Test surplus config, no log messages, binary characters in command
			root: <binary> echo "a\\x0ab"
			root: <binary>echo "a\\x0abc"
			root: <binary>  echo "a\\x0abd"
			''',
			'''
			''',
			'''
			# root: <binary> echo "a\\x0ab"
			# root: <binary> echo "a\\x0abc"
			# root: <binary> echo "a\\x0abd"
			'''
		],

		[
			'''
			# Test config with no label, log message with label, command retained
			root: echo
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			''',
			'''
			root: echo
			'''
		],
		[
			'''
			# Test config with label, log message with no label, command commented out
			root/label: echo
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			''',
			'''
			# root/label: echo
			'''
		],

		[
			'''
			# Test config with group, log message with group, command retained
			+%s: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" group="%s" config=".test.sshdoers"
			''' % rootgroup(),
			'''
			+%s: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with group, log message without group, command commented out
			+%s: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			''',
			'''
			# +%s: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with group and label, log message with group and label, command retained
			+%s/label: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" group="%s" config=".test.sshdoers"
			''' % rootgroup(),
			'''
			+%s/label: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with group and label, log message without group but with label, command commented out
			+%s/label: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			''',
			'''
			# +%s/label: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with group and label, log message with group but no label, command commented out
			+%s/label: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" command="echo" group="%s" config=".test.sshdoers"
			''' % rootgroup(),
			'''
			# +%s/label: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with group and label, log message with group but different label, command commented out
			+%s/label: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="root" remoteip="10.0.0.1" label="label2" command="echo" group="%s" config=".test.sshdoers"
			''' % rootgroup(),
			'''
			# +%s/label: echo
			''' % rootgroup()
		],

		[
			'''
			# Test config with -user, no log message, -user retained
			-root: echo
			''',
			'''
			''',
			'''
			-root: echo
			'''
		],
		[
			'''
			# Test config with -user and label, no log message, -user retained
			-root/label: echo
			''',
			'''
			''',
			'''
			-root/label: echo
			'''
		],

		[
			'''
			# Test config with +group -user, log message with group, both retained
			+%s -root: echo
			''' % rootgroup(),
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="other_root" remoteip="10.0.0.1" command="echo" group="%s" config=".test.sshdoers"
			''' % rootgroup(),
			'''
			+%s -root: echo
			''' % rootgroup()
		],
		[
			'''
			# Test config with +group -user, no log message, only -user retained
			+%s -root: echo
			''' % rootgroup(),
			'''
			''',
			'''
			-root: echo
			# +%s: echo
			''' % rootgroup()
		],
	]

	num_tests = 0
	num_errors = 0

	import sshdo
	for config_text, logfile_text, expected_text in test_data:
		config_text = sshdo.s('^\t+', '', config_text, 'm')
		logfile_text = sshdo.s('^\t+', '', logfile_text, 'm')
		expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', expected_text, 'm'))
		num_tests += 1
		writefile(test_config_fname, config_text)
		writefile(test_logfile_fname, logfile_text)
		os.system("./sshdo --config %s --unlearn %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		if result.startswith('# 2'):
			result = sshdo.s('^.*', '', result)
		os.unlink(test_config_fname)
		os.unlink(test_logfile_fname)
		os.unlink(test_output_fname)
		if result != expected_text:
			num_errors += 1
			print('Test %d: unlearn\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, expected_text))
	print(('All %s unlearn tests passed' % num_tests) if num_errors == 0 else ('%s/%s unlearn tests failed' % (num_errors, num_tests)))
	return num_errors

def test_unlearn_accepting():
	'''Test --unlearn --accepting.'''
	test_config_fname = '.test.sshdoers'
	test_logfile_fname = '.test.logfile'
	test_output_fname = '.test.output'
	test_data = [
		[
			'''
			# Test unlearn with/without accepting with no labels, digits matching (not needed)
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo 2
			# drew: echo 2
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, digits matching
			bin bob jim: echo
			drew jim mary root: echo #
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo #
			# drew mary: echo #
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim mary root: echo #
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, digits matching (mixed)
			mary: echo #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary: echo #
			''',
			'''
			mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, hexdigits matching
			match hexdigits
			bin bob jim: echo
			+other +staff drew jim mary root: echo #
			drew jim root: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			+staff jim root: echo #
			# +other drew mary: echo #
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			+other +staff drew jim mary root: echo #
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, hexdigits matching (mixed)
			match hexdigits
			mary: echo #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary: echo #
			''',
			'''
			mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, exact matching
			match exact
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo 3
			mary: echo 4
			mary: echo 5
			mary: echo 6
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin jim: echo
			# bob: echo
			jim root: echo 2
			# drew: echo 2
			jim root: echo 3
			# drew: echo 3
			# mary: echo 4
			# mary: echo 5
			# mary: echo 6
			jim root: echo a
			# drew: echo a
			+staff jim: echo b
			# +other: echo b
			jim root: echo k
			# drew: echo k
			''',
			'''
			bin bob jim: echo
			drew jim root: echo 2
			drew jim root: echo 3
			mary: echo 4
			mary: echo 5
			mary: echo 6
			drew jim root: echo a
			+other +staff jim: echo b
			drew jim root: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with no labels, exact matching (mixed)
			match exact
			mary: echo 4
			mary: echo 5
			mary: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary: echo 4
			# mary: echo 5
			mary: echo 6
			''',
			'''
			mary: echo 4
			mary: echo 5
			mary: echo 6
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, digits matching (not needed)
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo 2
			# drew/label: echo 2
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, digits matching
			bin/label bob/label jim/label: echo
			drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo #
			# drew/label mary/label: echo #
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, digits matching (mixed)
			mary/label: echo #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary/label: echo #
			''',
			'''
			mary/label: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, hexdigits matching
			match hexdigits
			bin/label bob/label jim/label: echo
			+other/label1 +staff/label1 +staff/label2 drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			+staff/label1 +staff/label2 jim/label root/label: echo #
			# +other/label1 drew/label mary/label: echo #
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			+other/label1 +staff/label1 +staff/label2 drew/label jim/label mary/label root/label: echo #
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, hexdigits matching (mixed)
			match hexdigits
			mary/label: echo #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary/label: echo #
			''',
			'''
			mary/label: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, exact matching
			match exact
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo 3
			mary/label: echo 4
			mary/label: echo 5
			mary/label: echo 6
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="bin" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="bob" remoteip="10.0.0.1" label="label" command="echo" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 2" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo 3" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo a" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo b" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff2" remoteip="10.0.0.1" label="label2" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="staff1" remoteip="10.0.0.1" label="label1" command="echo b" group="staff" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="other1" remoteip="10.0.0.1" label="label1" command="echo b" group="other" config=".test.sshdoers"

			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="jim" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" label="label" command="echo k" config=".test.sshdoers"
			''',
			'''
			bin/label jim/label: echo
			# bob/label: echo
			jim/label root/label: echo 2
			# drew/label: echo 2
			jim/label root/label: echo 3
			# drew/label: echo 3
			# mary/label: echo 4
			# mary/label: echo 5
			# mary/label: echo 6
			jim/label root/label: echo a
			# drew/label: echo a
			+staff/label1 +staff/label2 jim/label: echo b
			# +other/label1: echo b
			jim/label root/label: echo k
			# drew/label: echo k
			''',
			'''
			bin/label bob/label jim/label: echo
			drew/label jim/label root/label: echo 2
			drew/label jim/label root/label: echo 3
			mary/label: echo 4
			mary/label: echo 5
			mary/label: echo 6
			drew/label jim/label root/label: echo a
			+other/label1 +staff/label1 +staff/label2 jim/label: echo b
			drew/label jim/label root/label: echo k
			'''
		],
		[
			'''
			# Test unlearn with/without accepting with labels, exact matching (mixed)
			match exact
			mary/label: echo 4
			mary/label: echo 5
			mary/label: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" label="label" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="training" user="mary" remoteip="10.0.0.1" label="label" command="echo 6" config=".test.sshdoers"
			''',
			'''
			mary/label: echo 4
			# mary/label: echo 5
			mary/label: echo 6
			''',
			'''
			mary/label: echo 4
			mary/label: echo 5
			mary/label: echo 6
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 1 (all allowed)
			kim: echo 4
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew kim mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 2 (1st disallowed)
			kim: echo 4
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew mary: echo #
			# kim: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 3 (2nd disallowed)
			kim: echo 4
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew kim: echo #
			# mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 4 (3rd disallowed)
			kim: echo 4
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			kim mary: echo #
			# drew: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 5 (all disallowed)
			kim: echo 4
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			# drew kim mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 6 (all disallowed, 1 existing # 3rd)
			kim: echo 4
			mary: echo 5
			drew: echo #
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			# drew kim mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 7 (all disallowed, existing # 2nd)
			kim: echo 4
			mary: echo #
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			# drew kim mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 8 (all disallowed, existing # 1st)
			kim: echo #
			mary: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="mary" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="disallowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			# drew kim mary: echo #
			''',
			'''
			drew kim mary: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 9 (unused, unused)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			''',
			'''
			# drew kim: echo #
			''',
			'''
			# drew kim: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 10 (used, unused)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			''',
			'''
			kim: echo #
			# drew: echo #
			''',
			'''
			kim: echo #
			# drew: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 11 (unused, used)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew: echo #
			# kim: echo #
			''',
			'''
			drew: echo #
			# kim: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 12 (used, used)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew kim: echo #
			''',
			'''
			drew kim: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 13 (partial, used)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew kim: echo #
			''',
			'''
			drew kim: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 14 (partial, used)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="drew" remoteip="10.0.0.1" command="echo 6" config=".test.sshdoers"
			''',
			'''
			drew kim: echo #
			''',
			'''
			drew kim: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 15 (partial, unused)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 4" config=".test.sshdoers"
			''',
			'''
			kim: echo #
			# drew: echo #
			''',
			'''
			kim: echo #
			# drew: echo #
			'''
		],
		[
			'''
			# Test unlearn with/without accepting, digits matching, coalesce 16 (partial, unused)
			kim: echo 4
			kim: echo 5
			drew: echo 6
			''',
			'''
			Jul 11 22:24:47 host sshdo[5757]: type="allowed" user="kim" remoteip="10.0.0.1" command="echo 5" config=".test.sshdoers"
			''',
			'''
			kim: echo #
			# drew: echo #
			''',
			'''
			kim: echo #
			# drew: echo #
			'''
		],
	]

	num_tests = 0
	num_errors = 0

	import sshdo
	for config_text, logfile_text, expected_text, accepting_expected_text in test_data:
		config_text = sshdo.s('^\t+', '', config_text, 'm')
		logfile_text = sshdo.s('^\t+', '', logfile_text, 'm')
		expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', expected_text, 'm'))
		accepting_expected_text = sshdo.s('^\n', '', sshdo.s('^\t+', '', accepting_expected_text, 'm'))
		writefile(test_config_fname, config_text)
		writefile(test_logfile_fname, logfile_text)
		num_tests += 1
		os.system("./sshdo --config %s --unlearn %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		os.unlink(test_output_fname)
		if result != expected_text:
			num_errors += 1
			print('Test %d: unlearn without accepting\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, expected_text))
		num_tests += 1
		os.system("./sshdo --config %s --unlearn --accepting %s > %s" % (test_config_fname, test_logfile_fname, test_output_fname))
		result = readfile(test_output_fname)
		os.unlink(test_output_fname)
		if result != accepting_expected_text:
			num_errors += 1
			print('Test %d: unlearn with accepting\nconfig:\n%s\nlogfile:\n%s\nresult:\n%s\nexpected:\n%s\n' % (num_tests, config_text, logfile_text, result, accepting_expected_text))
		os.unlink(test_config_fname)
		os.unlink(test_logfile_fname)
	print(('All %s unlearn accepting tests passed' % num_tests) if num_errors == 0 else ('%s/%s learn tests failed' % (num_errors, num_tests)))
	return num_errors

def test_logfiles():
	'''Test the logfiles directive.'''
	test_config_fname = '.test.sshdoers'
	test_logfiles_fname = '.test.logfiles'
	test_log_message = 'Jul 11 22:24:47 host sshdo[5757]: type="training" user="root" remoteip="10.0.0.1" command="echo" config=".test.sshdoers"\n'
	test_output_fname = '.test.output'
	test_data = [
		# From files via the command line
		[test_logfiles_fname, none, test_log_message, 'root: echo\n'],
		# From stdin via the command line
		['-', none, test_log_message, 'root: echo\n'],
		# From files via /etc/sshdoers
		[none, test_logfiles_fname, test_log_message, 'root: echo\n'],
		# From stdin via /etc/sshdoers
		[none, '-', test_log_message, 'root: echo\n'],
		# From files via the command line but empty
		[test_logfiles_fname, none, '', ''],
		# From stdin via the command line but empty
		['-', none, '', ''],
		# From files via /etc/sshdoers but empty
		[none, test_logfiles_fname, '', ''],
		# From stdin via /etc/sshdoers but empty
		[none, '-', '', ''],
	]

	num_tests = 0
	num_errors = 0

	for cmdline_arg, config_logfiles_arg, log_message, expected_output in test_data:
		num_tests += 1
		writefile(test_config_fname, ('logfiles %s' % config_logfiles_arg) if config_logfiles_arg else '')
		writefile(test_logfiles_fname, log_message)
		cmdline_arg = '' if cmdline_arg is none else cmdline_arg
		input = ('< %s' % test_logfiles_fname) if cmdline_arg == '-' or config_logfiles_arg == '-' else ''
		os.system("./sshdo --config %s --learn %s %s > %s" % (test_config_fname, cmdline_arg, input, test_output_fname))
		output_result = readfile(test_output_fname)
		os.unlink(test_config_fname)
		os.unlink(test_output_fname)
		os.unlink(test_logfiles_fname)
		if output_result != expected_output:
			num_errors += 1
			print('Test %d: logfiles config_logfiles_arg %r cmdline_arg %r' % (num_tests, config_logfiles_arg, cmdline_arg))
			print('output_result = %r\nexpected =      %r' % (output_result, expected_output))
	print(('All %s logfiles tests passed' % num_tests) if num_errors == 0 else ('%s/%s logfiles tests failed' % (num_errors, num_tests)))
	return num_errors

def test_banner():
	'''Test the banner directive.'''
	test_config_fname = '.test.sshdoers'
	test_banner_fname = '.test.banner'
	test_output_fname = '.test.output'
	test_error_fname = '.test.error'
	username = pwd.getpwuid(os.getuid()).pw_name
	banner_text = 'This is the banner text.\n'
	test_data = [
		# Allowed, no banner
		['%s: echo\n' % username, '', '\n', ''],
		# Disallowed, no banner
		['', '', '', ''],
		# Allowed, banner
		['%s: echo\nbanner %s\n' % (username, test_banner_fname), banner_text, '\n', ''],
		# Disallowed, banner
		['banner %s\n' % test_banner_fname, banner_text, '', banner_text],
	]

	num_tests = 0
	num_errors = 0

	for config_text, banner_text, expected_output, expected_error in test_data:
		num_tests += 1
		writefile(test_config_fname, config_text)
		if banner_text:
			writefile(test_banner_fname, banner_text)
		os.system("SSH_ORIGINAL_COMMAND='echo' ./sshdo --config %s > %s 2> %s" % (test_config_fname, test_output_fname, test_error_fname))
		output_result = readfile(test_output_fname)
		error_result = readfile(test_error_fname)
		os.unlink(test_config_fname)
		if banner_text:
			os.unlink(test_banner_fname)
		os.unlink(test_output_fname)
		os.unlink(test_error_fname)
		if output_result != expected_output or error_result != expected_error:
			num_errors += 1
			print('Test %d: banner %r' % (num_tests, config_text))
			if output_result != expected_output:
				print('output_result = %r\nexpected =      %r' % (output_result, expected_output))
			if error_result != expected_error:
				print('error_result =  %r\nexpected =      %r' % (error_result, expected_error))
	print(('All %s banner tests passed' % num_tests) if num_errors == 0 else ('%s/%s banner tests failed' % (num_errors, num_tests)))
	return num_errors

def test_encode_log():
	'''Test encode_log() and decode_log().'''
	test_data = [
		[1, '', ''],
		[2, '"', '\\"'],
		[3, '\\', '\\\\'],
		[4, '\n', '\\x0a'],
		[5, '\\x0a', '\\\\x0a'],
		[6, 'abcdef', 'abcdef'],
		[7, 'ab"c\nd\\ef', 'ab\\"c\\x0ad\\\\ef'],
		[8, 'ab"c\nd\\', 'ab\\"c\\x0ad\\\\'],
		[9, 'ab\\c\nd"', 'ab\\\\c\\x0ad\\"'],
		[10, 'ab\\c"d\n', 'ab\\\\c\\"d\\x0a'],
		[11, '\\"\n\\x0a', '\\\\\\"\\x0a\\\\x0a'],
		[12, '\\"\\x0a\n', '\\\\\\"\\\\x0a\\x0a'],
		[13, '\\\n"\\x0a', '\\\\\\x0a\\"\\\\x0a'],
		[14, '\\\n\\x0a"', '\\\\\\x0a\\\\x0a\\"'],
		[15, '\\\\x0a"\n', '\\\\\\\\x0a\\"\\x0a'],
		[16, '\\\\x0a\n"', '\\\\\\\\x0a\\x0a\\"'],
		[17, '"\\\n\\x0a', '\\"\\\\\\x0a\\\\x0a'],
		[18, '"\\\\x0a\n', '\\"\\\\\\\\x0a\\x0a'],
		[19, '"\n\\\\x0a', '\\"\\x0a\\\\\\\\x0a'],
		[20, '"\n\\x0a\\', '\\"\\x0a\\\\x0a\\\\'],
		[21, '"\\x0a\\\n', '\\"\\\\x0a\\\\\\x0a'],
		[22, '"\\x0a\n\\', '\\"\\\\x0a\\x0a\\\\'],
		[23, '\n\\"\\x0a', '\\x0a\\\\\\"\\\\x0a'],
		[24, '\n\\\\x0a"', '\\x0a\\\\\\\\x0a\\"'],
		[25, '\n"\\\\x0a', '\\x0a\\"\\\\\\\\x0a'],
		[26, '\n"\\x0a\\', '\\x0a\\"\\\\x0a\\\\'],
		[27, '\n\\x0a\\"', '\\x0a\\\\x0a\\\\\\"'],
		[28, '\n\\x0a"\\', '\\x0a\\\\x0a\\"\\\\'],
		[29, '\\x0a\\"\n', '\\\\x0a\\\\\\"\\x0a'],
		[30, '\\x0a\\\n"', '\\\\x0a\\\\\\x0a\\"'],
		[31, '\\x0a"\\\n', '\\\\x0a\\"\\\\\\x0a'],
		[32, '\\x0a"\n\\', '\\\\x0a\\"\\x0a\\\\'],
		[33, '\\x0a\n\\"', '\\\\x0a\\x0a\\\\\\"'],
		[34, '\\x0a\n"\\', '\\\\x0a\\x0a\\"\\\\'],
	]
	import sshdo
	num_tests = 0
	num_errors = 0
	for id, input, expected in test_data:
		num_tests += 1
		output = sshdo.encode_log(input)
		if output != expected:
			num_errors += 1
			print('Test %d: [%s] encode_log(%r) = %r (expected %r)' % (num_tests, id, input, output, expected))
		else:
			num_tests += 1
			output2 = sshdo.decode_log(output)
			if output2 != input:
				num_errors += 1
				print('Test %d: decode_log(%r) = %r (expected %r)' % (num_tests, output, output2, input))
	print(('All %s encode_log tests passed' % num_tests) if num_errors == 0 else ('%s/%s encode_log tests failed' % (num_errors, num_tests)))
	return num_errors

def test_encode_conf():
	'''Test encode_conf() and decode_conf().'''
	test_data = [
		[1, '', ''],
		[2, '"', '"'],
		[3, '\\', '\\'],
		[4, '\n', '<binary> \\x0a'],
		[5, '\\x0a', '\\x0a'],
		[6, 'abcdef', 'abcdef'],
		[7, 'ab"c\nd\\ef', '<binary> ab"c\\x0ad\\\\ef'],
		[8, 'ab"c\nd\\', '<binary> ab"c\\x0ad\\\\'],
		[9, 'ab\\c\nd"', '<binary> ab\\\\c\\x0ad"'],
		[10, 'ab\\c"d\n', '<binary> ab\\\\c"d\\x0a'],
		[11, '\\"\n\\x0a', '<binary> \\\\"\\x0a\\\\x0a'],
		[12, '\\"\\x0a\n', '<binary> \\\\"\\\\x0a\\x0a'],
		[13, '\\\n"\\x0a', '<binary> \\\\\\x0a"\\\\x0a'],
		[14, '\\\n\\x0a"', '<binary> \\\\\\x0a\\\\x0a"'],
		[15, '\\\\x0a"\n', '<binary> \\\\\\\\x0a"\\x0a'],
		[16, '\\\\x0a\n"', '<binary> \\\\\\\\x0a\\x0a"'],
		[17, '"\\\n\\x0a', '<binary> "\\\\\\x0a\\\\x0a'],
		[18, '"\\\\x0a\n', '<binary> "\\\\\\\\x0a\\x0a'],
		[19, '"\n\\\\x0a', '<binary> "\\x0a\\\\\\\\x0a'],
		[20, '"\n\\x0a\\', '<binary> "\\x0a\\\\x0a\\\\'],
		[21, '"\\x0a\\\n', '<binary> "\\\\x0a\\\\\\x0a'],
		[22, '"\\x0a\n\\', '<binary> "\\\\x0a\\x0a\\\\'],
		[23, '\n\\"\\x0a', '<binary> \\x0a\\\\"\\\\x0a'],
		[24, '\n\\\\x0a"', '<binary> \\x0a\\\\\\\\x0a"'],
		[25, '\n"\\\\x0a', '<binary> \\x0a"\\\\\\\\x0a'],
		[26, '\n"\\x0a\\', '<binary> \\x0a"\\\\x0a\\\\'],
		[27, '\n\\x0a\\"', '<binary> \\x0a\\\\x0a\\\\"'],
		[28, '\n\\x0a"\\', '<binary> \\x0a\\\\x0a"\\\\'],
		[29, '\\x0a\\"\n', '<binary> \\\\x0a\\\\"\\x0a'],
		[30, '\\x0a\\\n"', '<binary> \\\\x0a\\\\\\x0a"'],
		[31, '\\x0a"\\\n', '<binary> \\\\x0a"\\\\\\x0a'],
		[32, '\\x0a"\n\\', '<binary> \\\\x0a"\\x0a\\\\'],
		[33, '\\x0a\n\\"', '<binary> \\\\x0a\\x0a\\\\"'],
		[34, '\\x0a\n"\\', '<binary> \\\\x0a\\x0a"\\\\'],
	]
	import sshdo
	num_tests = 0
	num_errors = 0
	for id, input, expected in test_data:
		num_tests += 1
		output = sshdo.encode_conf(input)
		if output != expected:
			num_errors += 1
			print('Test %d: [%s] encode_conf(%r) = %r (expected %r)' % (num_tests, id, input, output, expected))
		else:
			num_tests += 1
			output2 = sshdo.decode_conf(output)
			if output2 != input:
				num_errors += 1
				print('Test %d: [%s] decode_conf(%r) = %r (expected %r)' % (num_tests, id, output, output2, input))
	print(('All %s encode_conf tests passed' % num_tests) if num_errors == 0 else ('%s/%s encode_conf tests failed' % (num_errors, num_tests)))
	return num_errors

def test_shell_exec():
	'''Test shell_exec(). Used by sshdo().'''
	test_config_fname = '.test.sshdoers'
	test_output_fname = '.test.output'
	username = pwd.getpwuid(os.getuid()).pw_name
	usergroup = grp.getgrgid(pwd.getpwuid(os.getuid()).pw_gid).gr_name
	test_data = [
		# Interactive login allowed
		['%s: <interactive>\n' % username, none, none, 0],
		# Specific command allowed
		['%s: echo 1\n' % username, 'echo 1', '1\n', 0],
		# Specific command allowed by group
		['+' + usergroup + ': echo 1\n', 'echo 1', '1\n', 0],
		# Command training by group
		['training +' + usergroup + '\n', 'echo 1', '1\n', 0],
	]

	num_tests = 0
	num_errors = 0

	for config_text, cmd, expected_output, expected_rc in test_data:
		num_tests += 1
		writefile(test_config_fname, config_text)
		ssh_orig_cmd = ("SSH_ORIGINAL_COMMAND='%s' " % cmd) if cmd is not none else ''
		output_spec = (' > %s' % test_output_fname) if cmd is not none else ''
		if cmd is none:
			print('Please enter "exit 0" to terminate the interactive login shell.')
		rc = os.system("%s./sshdo --config %s%s" % (ssh_orig_cmd, test_config_fname, output_spec))
		if cmd is not none:
			output_result = readfile(test_output_fname)
		os.unlink(test_config_fname)
		if cmd is not none:
			os.unlink(test_output_fname)
		if (cmd is not none and output_result != expected_output) or rc != expected_rc:
			num_errors += 1
			print('Test %d: shell_exec %r' % (num_tests, config_text))
			if cmd is not none and output_result != expected_output:
				print('output_result = %r\nexpected =      %r' % (output_result, expected_output))
			if rc != expected_rc:
				print('rc = %r expected = %r' % (rc, expected_rc))
	print(('All %s shell_exec tests passed' % num_tests) if num_errors == 0 else ('%s/%s shell_exec tests failed' % (num_errors, num_tests)))
	return num_errors

def writefile(filename, content):
	'''Write content to a new file with the given filename.'''
	f = open(filename, 'wb' if not py2 and type(content) == bytes else 'w')
	f.write(content)
	f.close()

def readfile(filename, need_bytes=False):
	'''Read the file with the given filename and return its contents.'''
	f = open(filename, 'rb' if need_bytes else 'r')
	content = f.read()
	f.close()
	return content

def rootgroup():
	'''Return the name of the superuser's primary group.'''
	return grp.getgrgid(pwd.getpwuid(0).pw_gid).gr_name

def main():
	'''Perform all the tests.'''
	do_interactive = 0
	if len(sys.argv) > 1:
		if len(sys.argv) == 2 and sys.argv[1] == 'interactive':
			do_interactive = 1
		else:
			print('usage: %s [interactive]' % sys.argv[0])
			sys.exit(0)
	shutil.copy2('sshdo', 'sshdo.py')
	try:
		errors = 0
		errors += test_check_auth()
		errors += test_check_command()
		errors += test_coalesce_commands()
		errors += test_check_config()
		errors += test_learn()
		errors += test_learn_accepting()
		errors += test_unlearn()
		errors += test_unlearn_accepting()
		errors += test_logfiles()
		errors += test_banner()
		errors += test_encode_log()
		errors += test_encode_conf()
		errors += test_options()
		if do_interactive:
			errors += test_shell_exec()
	finally:
		# Delete the sshdo module
		for fname in glob.glob('sshdo.py*'):
			try:
				os.unlink(fname)
			except OSError as e:
				print('Failed to remove %s: %s' % (fname, e))
		shutil.rmtree('__pycache__', ignore_errors=True)
	sys.exit(min(errors, 255))

if __name__ == '__main__':
	main()

# vi:set ts=4 sw=4:
