1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Updating connection plugins not yet updated in v2 to catch new code

This commit is contained in:
James Cammarata 2015-04-23 18:54:48 -05:00
parent 716a87ff36
commit 8c08f1b302
8 changed files with 255 additions and 87 deletions

View file

@ -14,8 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import os
@ -52,6 +50,7 @@ class Connection(object):
self.accport = port[1]
self.is_connected = False
self.has_pipelining = False
self.become_methods_supported=['sudo']
if not self.port:
self.port = constants.DEFAULT_REMOTE_PORT
@ -142,7 +141,7 @@ class Connection(object):
# shutdown, so we'll reconnect.
wrong_user = True
except AnsibleError as e:
except AnsibleError, e:
if allow_ssh:
if "WRONG_USER" in e:
vvv("Switching users, waiting for the daemon on %s to shutdown completely..." % self.host)
@ -228,11 +227,11 @@ class Connection(object):
else:
return response.get('rc') == 0
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if su or su_user:
raise AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
@ -240,8 +239,8 @@ class Connection(object):
if executable == "":
executable = constants.DEFAULT_EXECUTABLE
if self.runner.sudo and sudoable and sudo_user:
cmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
if self.runner.become and sudoable:
cmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '', self.runner.become_exe)
vvv("EXEC COMMAND %s" % cmd)
@ -294,8 +293,8 @@ class Connection(object):
if fd.tell() >= fstat.st_size:
last = True
data = dict(mode='put', data=base64.b64encode(data), out_path=out_path, last=last)
if self.runner.sudo:
data['user'] = self.runner.sudo_user
if self.runner.become:
data['user'] = self.runner.become_user
data = utils.jsonify(data)
data = utils.encrypt(self.key, data)

View file

@ -15,8 +15,6 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import distutils.spawn
import traceback
@ -26,6 +24,7 @@ import subprocess
from ansible import errors
from ansible import utils
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local chroot based connections '''
@ -33,6 +32,7 @@ class Connection(object):
def __init__(self, runner, host, port, *args, **kwargs):
self.chroot = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("chroot connection requires running as root")
@ -62,16 +62,16 @@ class Connection(object):
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter chroot as root so sudo stuff can be ignored
# We enter chroot as root so we ignore privlege escalation?
if executable:
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]

View file

@ -18,9 +18,6 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# ---
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# The func transport permit to use ansible over func. For people who have already setup
# func and that wish to play with ansible, this permit to move gradually to ansible
# without having to redo completely the setup of the network.
@ -56,16 +53,14 @@ class Connection(object):
self.client = fc.Client(self.host)
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False,
executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False,
executable='/bin/sh', in_data=None):
''' run a command on the remote minion '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# totally ignores privlege escalation
vvv("EXEC %s" % (cmd), host=self.host)
p = self.client.command.run(cmd)[self.host]
return (p[0], '', p[1], p[2])

View file

@ -16,8 +16,6 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import distutils.spawn
import traceback
@ -26,6 +24,7 @@ import shutil
import subprocess
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local chroot based connections '''
@ -63,6 +62,7 @@ class Connection(object):
self.runner = runner
self.host = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("jail connection requires running as root")
@ -93,16 +93,16 @@ class Connection(object):
local_cmd = '%s "%s" %s' % (self.jexec_cmd, self.jail, cmd)
return local_cmd
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter chroot as root so sudo stuff can be ignored
# Ignores privilege escalation
local_cmd = self._generate_cmd(executable, cmd)
vvv("EXEC %s" % (local_cmd), host=self.jail)

View file

@ -16,14 +16,13 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import distutils.spawn
import os
import subprocess
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local lxc based connections '''
@ -52,6 +51,7 @@ class Connection(object):
self.host = host
# port is unused, since this is local
self.port = port
self.become_methods_supported=C.BECOME_METHODS
def connect(self, port=None):
''' connect to the lxc; nothing to do here '''
@ -67,16 +67,16 @@ class Connection(object):
local_cmd = '%s -q -c lxc:/// lxc-enter-namespace %s -- %s' % (self.cmd, self.lxc, cmd)
return local_cmd
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter lxc as root so sudo stuff can be ignored
# We ignore privelege escalation!
local_cmd = self._generate_cmd(executable, cmd)
vvv("EXEC %s" % (local_cmd), host=self.lxc)

View file

@ -14,8 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# ---
# The paramiko transport is provided because many distributions, in particular EL6 and before
@ -126,6 +125,9 @@ class Connection(object):
self.private_key_file = private_key_file
self.has_pipelining = False
# TODO: add pbrun, pfexec
self.become_methods_supported=['sudo', 'su', 'pbrun']
def _cache_key(self):
return "%s__%s__" % (self.host, self.user)
@ -171,7 +173,7 @@ class Connection(object):
key_filename=key_filename, password=self.password,
timeout=self.runner.timeout, port=self.port)
except Exception as e:
except Exception, e:
msg = str(e)
if "PID check failed" in msg:
@ -185,9 +187,12 @@ class Connection(object):
return ssh
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if self.runner.become and sudoable and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
@ -198,7 +203,7 @@ class Connection(object):
self.ssh.get_transport().set_keepalive(5)
chan = self.ssh.get_transport().open_session()
except Exception as e:
except Exception, e:
msg = "Failed to open session"
if len(str(e)) > 0:
@ -207,7 +212,7 @@ class Connection(object):
no_prompt_out = ''
no_prompt_err = ''
if not (self.runner.sudo and sudoable) and not (self.runner.su and su):
if not (self.runner.become and sudoable):
if executable:
quoted_command = executable + ' -c ' + pipes.quote(cmd)
@ -225,50 +230,46 @@ class Connection(object):
chan.get_pty(term=os.getenv('TERM', 'vt100'),
width=int(os.getenv('COLUMNS', 0)),
height=int(os.getenv('LINES', 0)))
if self.runner.sudo or sudoable:
shcmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
elif self.runner.su or su:
shcmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
if self.runner.become and sudoable:
shcmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '', self.runner.become_exe)
vvv("EXEC %s" % shcmd, host=self.host)
sudo_output = ''
become_output = ''
try:
chan.exec_command(shcmd)
if self.runner.sudo_pass or self.runner.su_pass:
if self.runner.become_pass:
while True:
if success_key in sudo_output or \
(self.runner.sudo_pass and sudo_output.endswith(prompt)) or \
(self.runner.su_pass and utils.su_prompts.check_su_prompt(sudo_output)):
if success_key in become_output or \
(prompt and become_output.endswith(prompt)) or \
utils.su_prompts.check_su_prompt(become_output):
break
chunk = chan.recv(bufsize)
if not chunk:
if 'unknown user' in sudo_output:
if 'unknown user' in become_output:
raise errors.AnsibleError(
'user %s does not exist' % sudo_user)
'user %s does not exist' % become_user)
else:
raise errors.AnsibleError('ssh connection ' +
'closed waiting for password prompt')
sudo_output += chunk
become_output += chunk
if success_key not in sudo_output:
if success_key not in become_output:
if sudoable:
chan.sendall(self.runner.sudo_pass + '\n')
elif su:
chan.sendall(self.runner.su_pass + '\n')
chan.sendall(self.runner.become_pass + '\n')
else:
no_prompt_out += sudo_output
no_prompt_err += sudo_output
no_prompt_out += become_output
no_prompt_err += become_output
except socket.timeout:
raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output)
raise errors.AnsibleError('ssh timed out waiting for privilege escalation.\n' + become_output)
stdout = ''.join(chan.makefile('rb', bufsize))
stderr = ''.join(chan.makefile_stderr('rb', bufsize))
@ -285,7 +286,7 @@ class Connection(object):
try:
self.sftp = self.ssh.open_sftp()
except Exception as e:
except Exception, e:
raise errors.AnsibleError("failed to open a SFTP connection (%s)" % e)
try:
@ -309,7 +310,7 @@ class Connection(object):
try:
self.sftp = self._connect_sftp()
except Exception as e:
except Exception, e:
raise errors.AnsibleError("failed to open a SFTP connection (%s)", e)
try:

View file

@ -14,18 +14,15 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import absolute_import
import base64
import hashlib
import imp
import os
import re
import shlex
import traceback
from six.moves.urllib import parse as urlparse
import urlparse
from ansible import errors
from ansible import utils
from ansible.callbacks import vvv, vvvv, verbose
@ -38,9 +35,12 @@ try:
except ImportError:
raise errors.AnsibleError("winrm is not installed")
_winrm_cache = {
# 'user:pwhash@host:port': <protocol instance>
}
HAVE_KERBEROS = False
try:
import kerberos
HAVE_KERBEROS = True
except ImportError:
pass
def vvvvv(msg, host=None):
verbose(msg, host=host, caplevel=4)
@ -48,6 +48,11 @@ def vvvvv(msg, host=None):
class Connection(object):
'''WinRM connections over HTTP/HTTPS.'''
transport_schemes = {
'http': [('kerberos', 'http'), ('plaintext', 'http'), ('plaintext', 'https')],
'https': [('kerberos', 'https'), ('plaintext', 'https')],
}
def __init__(self, runner, host, port, user, password, *args, **kwargs):
self.runner = runner
self.host = host
@ -61,6 +66,10 @@ class Connection(object):
self.shell_id = None
self.delegate = None
# Add runas support
#self.become_methods_supported=['runas']
self.become_methods_supported=[]
def _winrm_connect(self):
'''
Establish a WinRM connection over HTTP/HTTPS.
@ -69,23 +78,22 @@ class Connection(object):
vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \
(self.user, port, self.host), host=self.host)
netloc = '%s:%d' % (self.host, port)
cache_key = '%s:%s@%s:%d' % (self.user, hashlib.md5(self.password).hexdigest(), self.host, port)
if cache_key in _winrm_cache:
vvvv('WINRM REUSE EXISTING CONNECTION: %s' % cache_key, host=self.host)
return _winrm_cache[cache_key]
transport_schemes = [('plaintext', 'https'), ('plaintext', 'http')] # FIXME: ssl/kerberos
if port == 5985:
transport_schemes = reversed(transport_schemes)
exc = None
for transport, scheme in transport_schemes:
for transport, scheme in self.transport_schemes['http' if port == 5985 else 'https']:
if transport == 'kerberos' and (not HAVE_KERBEROS or not '@' in self.user):
continue
if transport == 'kerberos':
realm = self.user.split('@', 1)[1].strip() or None
else:
realm = None
endpoint = urlparse.urlunsplit((scheme, netloc, '/wsman', '', ''))
vvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint),
host=self.host)
protocol = Protocol(endpoint, transport=transport,
username=self.user, password=self.password)
username=self.user, password=self.password,
realm=realm)
try:
protocol.send_message('')
_winrm_cache[cache_key] = protocol
return protocol
except WinRMTransportError, exc:
err_msg = str(exc)
@ -97,7 +105,6 @@ class Connection(object):
if code == 401:
raise errors.AnsibleError("the username/password specified for this server was incorrect")
elif code == 411:
_winrm_cache[cache_key] = protocol
return protocol
vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host)
continue
@ -133,7 +140,11 @@ class Connection(object):
self.protocol = self._winrm_connect()
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable=None, in_data=None):
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
cmd = cmd.encode('utf-8')
cmd_parts = shlex.split(cmd, posix=False)
if '-EncodedCommand' in cmd_parts:
@ -144,11 +155,11 @@ class Connection(object):
vvv("EXEC %s" % cmd, host=self.host)
# For script/raw support.
if cmd_parts and cmd_parts[0].lower().endswith('.ps1'):
script = powershell._build_file_cmd(cmd_parts)
script = powershell._build_file_cmd(cmd_parts, quote_args=False)
cmd_parts = powershell._encode_script(script, as_list=True)
try:
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True)
except Exception as e:
except Exception, e:
traceback.print_exc()
raise errors.AnsibleError("failed to exec cmd %s" % cmd)
return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8'))
@ -194,7 +205,7 @@ class Connection(object):
def fetch_file(self, in_path, out_path):
out_path = out_path.replace('\\', '/')
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
buffer_size = 2**20 # 1MB chunks
buffer_size = 2**19 # 0.5MB chunks
if not os.path.exists(os.path.dirname(out_path)):
os.makedirs(os.path.dirname(out_path))
out_file = None

View file

@ -0,0 +1,162 @@
# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# and chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com>
# and jail.py (c) 2013, Michael Scherer <misc@zarb.org>
# (c) 2015, Dagobert Michelsen <dam@baltic-online.de>
#
# This file is part of Ansible
#
# Ansible 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 3 of the License, or
# (at your option) any later version.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
import distutils.spawn
import traceback
import os
import shutil
import subprocess
from subprocess import Popen,PIPE
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local zone based connections '''
def _search_executable(self, executable):
cmd = distutils.spawn.find_executable(executable)
if not cmd:
raise errors.AnsibleError("%s command not found in PATH") % executable
return cmd
def list_zones(self):
pipe = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'],
cwd=self.runner.basedir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#stdout, stderr = p.communicate()
zones = []
for l in pipe.stdout.readlines():
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
s = l.split(':')
if s[1] != 'global':
zones.append(s[1])
return zones
def get_zone_path(self):
#solaris10vm# zoneadm -z cswbuild list -p
#-:cswbuild:installed:/zones/cswbuild:479f3c4b-d0c6-e97b-cd04-fd58f2c0238e:native:shared
pipe = subprocess.Popen([self.zoneadm_cmd, '-z', self.zone, 'list', '-p'],
cwd=self.runner.basedir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#stdout, stderr = p.communicate()
path = pipe.stdout.readlines()[0].split(':')[3]
return path + '/root'
def __init__(self, runner, host, port, *args, **kwargs):
self.zone = host
self.runner = runner
self.host = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("zone connection requires running as root")
self.zoneadm_cmd = self._search_executable('zoneadm')
self.zlogin_cmd = self._search_executable('zlogin')
if not self.zone in self.list_zones():
raise errors.AnsibleError("incorrect zone name %s" % self.zone)
self.host = host
# port is unused, since this is local
self.port = port
def connect(self, port=None):
''' connect to the zone; nothing to do here '''
vvv("THIS IS A LOCAL ZONE DIR", host=self.zone)
return self
# a modifier
def _generate_cmd(self, executable, cmd):
if executable:
local_cmd = [self.zlogin_cmd, self.zone, executable, cmd]
else:
local_cmd = '%s "%s" %s' % (self.zlogin_cmd, self.zone, cmd)
return local_cmd
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable=None, in_data=None):
''' run a command on the zone '''
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We happily ignore privelege escalation
if executable == '/bin/sh':
executable = None
local_cmd = self._generate_cmd(executable, cmd)
vvv("EXEC %s" % (local_cmd), host=self.zone)
p = subprocess.Popen(local_cmd, shell=isinstance(local_cmd, basestring),
cwd=self.runner.basedir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return (p.returncode, '', stdout, stderr)
def _normalize_path(self, path, prefix):
if not path.startswith(os.path.sep):
path = os.path.join(os.path.sep, path)
normpath = os.path.normpath(path)
return os.path.join(prefix, normpath[1:])
def _copy_file(self, in_path, out_path):
if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
try:
shutil.copyfile(in_path, out_path)
except shutil.Error:
traceback.print_exc()
raise errors.AnsibleError("failed to copy: %s and %s are the same" % (in_path, out_path))
except IOError:
traceback.print_exc()
raise errors.AnsibleError("failed to transfer file to %s" % out_path)
def put_file(self, in_path, out_path):
''' transfer a file from local to zone '''
out_path = self._normalize_path(out_path, self.get_zone_path())
vvv("PUT %s TO %s" % (in_path, out_path), host=self.zone)
self._copy_file(in_path, out_path)
def fetch_file(self, in_path, out_path):
''' fetch a file from zone to local '''
in_path = self._normalize_path(in_path, self.get_zone_path())
vvv("FETCH %s TO %s" % (in_path, out_path), host=self.zone)
self._copy_file(in_path, out_path)
def close(self):
''' terminate the connection; nothing to do here '''
pass