diff options
Diffstat (limited to 'third_party/Python/module/pexpect-2.4/examples/cgishell.cgi')
-rw-r--r-- | third_party/Python/module/pexpect-2.4/examples/cgishell.cgi | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi b/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi new file mode 100644 index 000000000000..1e3affc1cab6 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi @@ -0,0 +1,762 @@ +#!/usr/bin/python +##!/usr/bin/env python +"""CGI shell server + +This exposes a shell terminal on a web page. +It uses AJAX to send keys and receive screen updates. +The client web browser needs nothing but CSS and Javascript. + + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request + +This project is probably not the most security concious thing I've ever built. +This should be considered an experimental tool -- at best. +""" +import sys,os +sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules +import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal +import pxssh, pexpect, ANSI + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + os._exit(exit_code) + +def client (command, host='localhost', port=-1): + """This sends a request to the server and returns the response. + If port <= 0 then host is assumed to be the filename of a Unix domain socket. + If port > 0 then host is an inet hostname. + """ + if port <= 0: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(host) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.send(command) + data = s.recv (2500) + s.close() + return data + +def server (hostname, username, password, socket_filename='/tmp/server_sock', daemon_mode = True, verbose=False): + """This starts and services requests from a client. + If daemon_mode is True then this forks off a separate daemon process and returns the daemon's pid. + If daemon_mode is False then this does not return until the server is done. + """ + if daemon_mode: + mypid_name = '/tmp/%d.pid' % os.getpid() + daemon_pid = daemonize(daemon_pid_filename=mypid_name) + time.sleep(1) + if daemon_pid != 0: + os.unlink(mypid_name) + return daemon_pid + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + try: + child.login (hostname, username, password, login_naked=True) + except: + return + if verbose: print 'login OK' + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists(socket_filename): os.remove(socket_filename) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.bind(socket_filename) + os.chmod(socket_filename, 0777) + if verbose: print 'Listen' + s.listen(1) + + r = roller (endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + if verbose: print "started screen-poll-updater in background thread" + sys.stdout.flush() + try: + while True: + conn, addr = s.accept() + if verbose: print 'Connected by', addr + data = conn.recv(1024) + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + arg = '' + + if cmd == 'exit': + r.cancel() + break + elif cmd == 'sendline': + child.sendline (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'send' or cmd=='xsend': + if cmd=='xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'cursor': + shell_window = '%x,%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == 'refresh': + shell_window = str(virtual_screen) + elif cmd == 'hash': + shell_window = str(hash(str(virtual_screen))) + + response = [] + response.append (shell_window) + if verbose: print '\n'.join(response) + sent = conn.send('\n'.join(response)) + if sent < len (response): + if verbose: print "Sent is too short. Some data was cut off." + conn.close() + except e: + pass + r.cancel() + if verbose: print "cleaning up socket" + s.close() + if os.path.exists(socket_filename): os.remove(socket_filename) + if verbose: print "server done!" + +class roller (threading.Thread): + """This class continuously loops a function in a thread. + This is basically a thin layer around Thread with a + while loop and a cancel. + """ + def __init__(self, function, args=[], kwargs={}): + threading.Thread.__init__(self) + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + def cancel(self): + """Stop the roller.""" + self.finished.set() + def run(self): + while not self.finished.isSet(): + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + """This keeps the screen updated with the output of the child. + This will be run in a separate thread. See roller class. + """ + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + +def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None): + """This runs the current process in the background as a daemon. + The arguments stdin, stdout, stderr allow you to set the filename that the daemon reads and writes to. + If they are set to None then all stdio for the daemon will be directed to /dev/null. + If daemon_pid_filename is set then the pid of the daemon will be written to it as plain text + and the pid will be returned. If daemon_pid_filename is None then this will return None. + """ + UMASK = 0 + WORKINGDIR = "/" + MAXFD = 1024 + + # The stdio file descriptors are redirected to /dev/null by default. + if hasattr(os, "devnull"): + DEVNULL = os.devnull + else: + DEVNULL = "/dev/null" + if stdin is None: stdin = DEVNULL + if stdout is None: stdout = DEVNULL + if stderr is None: stderr = DEVNULL + + try: + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: # The first child. + os.waitpid(pid,0) + if daemon_pid_filename is not None: + daemon_pid = int(file(daemon_pid_filename,'r').read()) + return daemon_pid + else: + return None + + # first child + os.setsid() + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + try: + pid = os.fork() # fork second child + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: + if daemon_pid_filename is not None: + file(daemon_pid_filename,'w').write(str(pid)) + os._exit(0) # exit parent (the first child) of the second child. + + # second child + os.chdir(WORKINGDIR) + os.umask(UMASK) + + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if maxfd == resource.RLIM_INFINITY: + maxfd = MAXFD + + # close all file descriptors + for fd in xrange(0, maxfd): + try: + os.close(fd) + except OSError: # fd wasn't open to begin with (ignored) + pass + + os.open (DEVNULL, os.O_RDWR) # standard input + + # redirect standard file descriptors + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + return 0 + +def client_cgi (): + """This handles the request if this script was called as a cgi. + """ + sys.stderr = sys.stdout + ajax_mode = False + TITLE="Shell" + SHELL_OUTPUT="" + SID="NOT" + print "Content-type: text/html;charset=utf-8\r\n" + try: + form = cgi.FieldStorage() + if form.has_key('ajax'): + ajax_mode = True + ajax_cmd = form['ajax'].value + SID=form['sid'].value + if ajax_cmd == 'send': + command = 'xsend' + arg = form['arg'].value.encode('hex') + result = client (command + ' ' + arg, '/tmp/'+SID) + print result + elif ajax_cmd == 'refresh': + command = 'refresh' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'cursor': + command = 'cursor' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'exit': + command = 'exit' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'hash': + command = 'hash' + result = client (command, '/tmp/'+SID) + print result + elif not form.has_key('sid'): + SID=random_sid() + print LOGIN_HTML % locals(); + else: + SID=form['sid'].value + if form.has_key('start_server'): + USERNAME = form['username'].value + PASSWORD = form['password'].value + dpid = server ('127.0.0.1', USERNAME, PASSWORD, '/tmp/'+SID) + SHELL_OUTPUT="daemon pid: " + str(dpid) + else: + if form.has_key('cli'): + command = 'sendline ' + form['cli'].value + else: + command = 'sendline' + SHELL_OUTPUT = client (command, '/tmp/'+SID) + print CGISH_HTML % locals() + except: + tb_dump = traceback.format_exc() + if ajax_mode: + print str(tb_dump) + else: + SHELL_OUTPUT=str(tb_dump) + print CGISH_HTML % locals() + +def server_cli(): + """This is the command line interface to starting the server. + This handles things if the script was not called as a CGI + (if you run it from the command line). + """ + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + #port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + server (hostname, username, password, '/tmp/mysock', daemon_mode) + +def random_sid (): + a=random.randint(0,65535) + b=random.randint(0,65535) + return '%04x%04x.sid' % (a,b) + +def parse_host_connect_string (hcs): + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. + """ + if '@' in hcs: + p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + else: + p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +def pretty_box (s, rows=24, cols=80): + """This puts an ASCII text box around the given string. + """ + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def main (): + if os.getenv('REQUEST_METHOD') is None: + server_cli() + else: + client_cgi() + +# It's mostly HTML and Javascript from here on out. +CGISH_HTML="""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<title>%(TITLE)s %(SID)s</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<style type=text/css> +a {color: #9f9; text-decoration: none} +a:hover {color: #0f0} +hr {color: #0f0} +html,body,textarea,input,form +{ +font-family: "Courier New", Courier, mono; +font-size: 8pt; +color: #0c0; +background-color: #020; +margin:0; +padding:0; +border:0; +} +input { background-color: #010; } +textarea { +border-width:1; +border-style:solid; +border-color:#0c0; +padding:3; +margin:3; +} +</style> + +<script language="JavaScript"> +function focus_first() +{if (document.forms.length > 0) +{var TForm = document.forms[0]; +for (i=0;i<TForm.length;i++){ +if ((TForm.elements[i].type=="text")|| +(TForm.elements[i].type=="textarea")|| +(TForm.elements[i].type.toString().charAt(0)=="s")) +{document.forms[0].elements[i].focus();break;}}}} + +// JavaScript Virtual Keyboard +// If you like this code then buy me a sandwich. +// Noah Spurrier <noah@noah.org> +var flag_shift=0; +var flag_shiftlock=0; +var flag_ctrl=0; +var ButtonOnColor="#ee0"; + +function init () +{ + // hack to set quote key to show both single quote and double quote + document.form['quote'].value = "'" + ' "'; + //refresh_screen(); + poll(); + document.form["cli"].focus(); +} +function get_password () +{ + var username = prompt("username?",""); + var password = prompt("password?",""); + start_server (username, password); +} +function multibrowser_ajax () +{ + var xmlHttp = false; +/*@cc_on @*/ +/*@if (@_jscript_version >= 5) + try + { + xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e) + { + try + { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + catch (e2) + { + xmlHttp = false; + } + } +@end @*/ + + if (!xmlHttp && typeof XMLHttpRequest != 'undefined') + { + xmlHttp = new XMLHttpRequest(); + } + return xmlHttp; +} +function load_url_to_screen(url) +{ + xmlhttp = multibrowser_ajax(); + //window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP"); + xmlhttp.onreadystatechange = update_virtual_screen; + xmlhttp.open("GET", url); + xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); + xmlhttp.send(null); +} +function update_virtual_screen() +{ + if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) + { + var screen_text = xmlhttp.responseText; + document.form["screen_text"].value = screen_text; + //var json_data = json_parse(xmlhttp.responseText); + } +} +function poll() +{ + refresh_screen(); + timerID = setTimeout("poll()", 2000); + // clearTimeout(timerID); +} +//function start_server (username, password) +//{ +// load_url_to_screen('cgishell.cgi?ajax=serverstart&username=' + escape(username) + '&password=' + escape(password); +//} +function refresh_screen() +{ + load_url_to_screen('cgishell.cgi?ajax=refresh&sid=%(SID)s'); +} +function query_hash() +{ + load_url_to_screen('cgishell.cgi?ajax=hash&sid=%(SID)s'); +} +function query_cursor() +{ + load_url_to_screen('cgishell.cgi?ajax=cursor&sid=%(SID)s'); +} +function exit_server() +{ + load_url_to_screen('cgishell.cgi?ajax=exit&sid=%(SID)s'); +} +function type_key (chars) +{ + var ch = '?'; + if (flag_shiftlock || flag_shift) + { + ch = chars.substr(1,1); + } + else if (flag_ctrl) + { + ch = chars.substr(2,1); + } + else + { + ch = chars.substr(0,1); + } + load_url_to_screen('cgishell.cgi?ajax=send&sid=%(SID)s&arg=' + escape(ch)); + if (flag_shift || flag_ctrl) + { + flag_shift = 0; + flag_ctrl = 0; + } + update_button_colors(); +} + +function key_shiftlock() +{ + flag_ctrl = 0; + flag_shift = 0; + if (flag_shiftlock) + { + flag_shiftlock = 0; + } + else + { + flag_shiftlock = 1; + } + update_button_colors(); +} + +function key_shift() +{ + if (flag_shift) + { + flag_shift = 0; + } + else + { + flag_ctrl = 0; + flag_shiftlock = 0; + flag_shift = 1; + } + update_button_colors(); +} +function key_ctrl () +{ + if (flag_ctrl) + { + flag_ctrl = 0; + } + else + { + flag_ctrl = 1; + flag_shiftlock = 0; + flag_shift = 0; + } + + update_button_colors(); +} +function update_button_colors () +{ + if (flag_ctrl) + { + document.form['Ctrl'].style.backgroundColor = ButtonOnColor; + document.form['Ctrl2'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['Ctrl'].style.backgroundColor = document.form.style.backgroundColor; + document.form['Ctrl2'].style.backgroundColor = document.form.style.backgroundColor; + } + if (flag_shift) + { + document.form['Shift'].style.backgroundColor = ButtonOnColor; + document.form['Shift2'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['Shift'].style.backgroundColor = document.form.style.backgroundColor; + document.form['Shift2'].style.backgroundColor = document.form.style.backgroundColor; + } + if (flag_shiftlock) + { + document.form['ShiftLock'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['ShiftLock'].style.backgroundColor = document.form.style.backgroundColor; + } + +} +function keyHandler(e) +{ + var pressedKey; + if (document.all) { e = window.event; } + if (document.layers) { pressedKey = e.which; } + if (document.all) { pressedKey = e.keyCode; } + pressedCharacter = String.fromCharCode(pressedKey); + type_key(pressedCharacter+pressedCharacter+pressedCharacter); + alert(pressedCharacter); +// alert(' Character = ' + pressedCharacter + ' [Decimal value = ' + pressedKey + ']'); +} +//document.onkeypress = keyHandler; +//if (document.layers) +// document.captureEvents(Event.KEYPRESS); +//http://sniptools.com/jskeys +//document.onkeyup = KeyCheck; +function KeyCheck(e) +{ + var KeyID = (window.event) ? event.keyCode : e.keyCode; + type_key(String.fromCharCode(KeyID)); + e.cancelBubble = true; + window.event.cancelBubble = true; +} +</script> + +</head> + +<body onload="init()"> +<form id="form" name="form" action="/cgi-bin/cgishell.cgi" method="POST"> +<input name="sid" value="%(SID)s" type="hidden"> +<textarea name="screen_text" cols="81" rows="25">%(SHELL_OUTPUT)s</textarea> +<hr noshade="1"> + <input name="cli" id="cli" type="text" size="80"><br> +<table border="0" align="left"> +<tr> +<td width="86%%" align="center"> + <input name="submit" type="submit" value="Submit"> + <input name="refresh" type="button" value="REFRESH" onclick="refresh_screen()"> + <input name="refresh" type="button" value="CURSOR" onclick="query_cursor()"> + <input name="hash" type="button" value="HASH" onclick="query_hash()"> + <input name="exit" type="button" value="EXIT" onclick="exit_server()"> + <br> + <input type="button" value="Esc" onclick="type_key('\\x1b\\x1b')" /> + <input type="button" value="` ~" onclick="type_key('`~')" /> + <input type="button" value="1!" onclick="type_key('1!')" /> + <input type="button" value="2@" onclick="type_key('2@\\x00')" /> + <input type="button" value="3#" onclick="type_key('3#')" /> + <input type="button" value="4$" onclick="type_key('4$')" /> + <input type="button" value="5%%" onclick="type_key('5%%')" /> + <input type="button" value="6^" onclick="type_key('6^\\x1E')" /> + <input type="button" value="7&" onclick="type_key('7&')" /> + <input type="button" value="8*" onclick="type_key('8*')" /> + <input type="button" value="9(" onclick="type_key('9(')" /> + <input type="button" value="0)" onclick="type_key('0)')" /> + <input type="button" value="-_" onclick="type_key('-_\\x1F')" /> + <input type="button" value="=+" onclick="type_key('=+')" /> + <input type="button" value="BkSp" onclick="type_key('\\x08\\x08\\x08')" /> + <br> + <input type="button" value="Tab" onclick="type_key('\\t\\t')" /> + <input type="button" value="Q" onclick="type_key('qQ\\x11')" /> + <input type="button" value="W" onclick="type_key('wW\\x17')" /> + <input type="button" value="E" onclick="type_key('eE\\x05')" /> + <input type="button" value="R" onclick="type_key('rR\\x12')" /> + <input type="button" value="T" onclick="type_key('tT\\x14')" /> + <input type="button" value="Y" onclick="type_key('yY\\x19')" /> + <input type="button" value="U" onclick="type_key('uU\\x15')" /> + <input type="button" value="I" onclick="type_key('iI\\x09')" /> + <input type="button" value="O" onclick="type_key('oO\\x0F')" /> + <input type="button" value="P" onclick="type_key('pP\\x10')" /> + <input type="button" value="[ {" onclick="type_key('[{\\x1b')" /> + <input type="button" value="] }" onclick="type_key(']}\\x1d')" /> + <input type="button" value="\\ |" onclick="type_key('\\\\|\\x1c')" /> + <br> + <input type="button" id="Ctrl" value="Ctrl" onclick="key_ctrl()" /> + <input type="button" value="A" onclick="type_key('aA\\x01')" /> + <input type="button" value="S" onclick="type_key('sS\\x13')" /> + <input type="button" value="D" onclick="type_key('dD\\x04')" /> + <input type="button" value="F" onclick="type_key('fF\\x06')" /> + <input type="button" value="G" onclick="type_key('gG\\x07')" /> + <input type="button" value="H" onclick="type_key('hH\\x08')" /> + <input type="button" value="J" onclick="type_key('jJ\\x0A')" /> + <input type="button" value="K" onclick="type_key('kK\\x0B')" /> + <input type="button" value="L" onclick="type_key('lL\\x0C')" /> + <input type="button" value="; :" onclick="type_key(';:')" /> + <input type="button" id="quote" value="'" onclick="type_key('\\x27\\x22')" /> + <input type="button" value="Enter" onclick="type_key('\\n\\n')" /> + <br> + <input type="button" id="ShiftLock" value="Caps Lock" onclick="key_shiftlock()" /> + <input type="button" id="Shift" value="Shift" onclick="key_shift()" /> + <input type="button" value="Z" onclick="type_key('zZ\\x1A')" /> + <input type="button" value="X" onclick="type_key('xX\\x18')" /> + <input type="button" value="C" onclick="type_key('cC\\x03')" /> + <input type="button" value="V" onclick="type_key('vV\\x16')" /> + <input type="button" value="B" onclick="type_key('bB\\x02')" /> + <input type="button" value="N" onclick="type_key('nN\\x0E')" /> + <input type="button" value="M" onclick="type_key('mM\\x0D')" /> + <input type="button" value=", <" onclick="type_key(',<')" /> + <input type="button" value=". >" onclick="type_key('.>')" /> + <input type="button" value="/ ?" onclick="type_key('/?')" /> + <input type="button" id="Shift2" value="Shift" onclick="key_shift()" /> + <input type="button" id="Ctrl2" value="Ctrl" onclick="key_ctrl()" /> + <br> + <input type="button" value=" FINAL FRONTIER " onclick="type_key(' ')" /> +</td> +</tr> +</table> +</form> +</body> +</html> +""" + +LOGIN_HTML="""<html> +<head> +<title>Shell Login</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<style type=text/css> +a {color: #9f9; text-decoration: none} +a:hover {color: #0f0} +hr {color: #0f0} +html,body,textarea,input,form +{ +font-family: "Courier New", Courier, mono; +font-size: 8pt; +color: #0c0; +background-color: #020; +margin:3; +padding:0; +border:0; +} +input { background-color: #010; } +input,textarea { +border-width:1; +border-style:solid; +border-color:#0c0; +padding:3; +margin:3; +} +</style> +<script language="JavaScript"> +function init () +{ + document.login_form["username"].focus(); +} +</script> +</head> +<body onload="init()"> +<form name="login_form" method="POST"> +<input name="start_server" value="1" type="hidden"> +<input name="sid" value="%(SID)s" type="hidden"> +username: <input name="username" type="text" size="30"><br> +password: <input name="password" type="password" size="30"><br> +<input name="submit" type="submit" value="enter"> +</form> +<br> +</body> +</html> +""" + +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) + |