Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3752613
D3157.id8056.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
D3157.id8056.diff
View Options
diff --git a/roles/shellserver/userland-software/base.sls b/roles/shellserver/userland-software/base.sls
--- a/roles/shellserver/userland-software/base.sls
+++ b/roles/shellserver/userland-software/base.sls
@@ -44,7 +44,6 @@
- toilet
- unrar
- whois
- - woof
- zip
{% if grains['os_family'] == 'Debian' %}
- bsdmainutils
@@ -52,6 +51,8 @@
- sockstat
- sysvbanner
- toilet-fonts
+ {% else %}
+ - woof
{% endif %}
{% if grains['os'] == 'FreeBSD' %}
- bind-tools
@@ -68,6 +69,17 @@
- lynx
- w3m
+{% if grains['os_family'] == 'Debian' %}
+{% set network = salt["node.resolve_network"]() %}
+{{ dirs.bin }}/woof:
+ file.managed:
+ - source: salt://roles/shellserver/userland-software/files/woof.py.jinja
+ - mode: 755
+ - template: jinja
+ - context:
+ ip: {{ network["ipv4_address"] }}
+{% endif %}
+
# -------------------------------------------------------------
# More exotic shells
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/roles/shellserver/userland-software/files/woof.py.jinja b/roles/shellserver/userland-software/files/woof.py.jinja
new file mode 100755
--- /dev/null
+++ b/roles/shellserver/userland-software/files/woof.py.jinja
@@ -0,0 +1,556 @@
+#!/usr/bin/env python3
+# -*- encoding: utf-8 -*-
+#
+# woof -- an ad-hoc single file webserver
+# Copyright (C) 2004-2009 Simon Budig <simon@budig.de>
+#
+# 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.
+#
+# A copy of the GNU General Public License is available at
+# http://www.fsf.org/licenses/gpl.txt, you can also write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+# Darwin support with the help from Mat Caughron, <mat@phpconsulting.com>
+# Solaris support by Colin Marquardt, <colin.marquardt@zmd.de>
+# FreeBSD support with the help from Andy Gimblett, <A.M.Gimblett@swansea.ac.uk>
+# Cygwin support by Stefan Reichör <stefan@xsteve.at>
+# tarfile usage suggested by Morgan Lefieux <comete@geekandfree.org>
+# File upload support loosely based on code from Stephen English <steve@secomputing.co.uk>
+
+import sys, os, errno, socket, getopt, subprocess, tempfile
+import cgi, urllib.request, urllib.parse, http.server
+import readline
+import configparser
+import shutil, tarfile, zipfile
+import struct
+
+maxdownloads = 1
+cpid = -1
+compressed = 'gz'
+upload = False
+
+
+# Utility function to guess the IP (as a string) where the server can be
+# reached from the outside. Quite nasty problem actually.
+
+def find_ip ():
+ # Only for Eglide
+ # Up-to-date for 2023-05-30
+ return "{{ ip }}"
+
+# our own HTTP server class, fixing up a change in python 2.7
+# since we do our fork() in the request handler
+# the server must not shutdown() the socket.
+
+class ForkingHTTPServer (http.server.HTTPServer):
+ def process_request (self, request, client_address):
+ self.finish_request (request, client_address)
+ self.close_request (request)
+
+
+# Main class implementing an HTTP-Requesthandler, that serves just a single
+# file and redirects all other requests to this file (this passes the actual
+# filename to the client).
+# Currently it is impossible to serve different files with different
+# instances of this class.
+
+class FileServHTTPRequestHandler (http.server.BaseHTTPRequestHandler):
+ server_version = "Simons FileServer"
+ protocol_version = "HTTP/1.0"
+
+ filename = "."
+
+ def log_request (self, code='-', size='-'):
+ if code == 200:
+ http.server.BaseHTTPRequestHandler.log_request (self, code, size)
+
+
+ def do_POST (self):
+ global maxdownloads, upload
+
+ if not upload:
+ self.send_error (501, "Unsupported method (POST)")
+ return
+
+ # taken from
+ # http://mail.python.org/pipermail/python-list/2006-September/402441.html
+
+ ctype, pdict = cgi.parse_header (self.headers['Content-Type'])
+ form = cgi.FieldStorage (fp = self.rfile,
+ headers = self.headers,
+ environ = {'REQUEST_METHOD' : 'POST'},
+ keep_blank_values = 1,
+ strict_parsing = 1)
+ if "upfile" not in form:
+ self.send_error (403, "No upload provided")
+ return
+
+ upfile = form["upfile"]
+
+ if not upfile.file or not upfile.filename:
+ self.send_error (403, "No upload provided")
+ return
+
+ upfilename = upfile.filename
+
+ if "\\" in upfilename:
+ upfilename = upfilename.split ("\\")[-1]
+
+ upfilename = os.path.basename (upfile.filename)
+
+ destfile = None
+ for suffix in ["", ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]:
+ destfilename = os.path.join (".", upfilename + suffix)
+ try:
+ destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
+ break
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ continue
+ raise
+
+ if not destfile:
+ upfilename += "."
+ destfile, destfilename = tempfile.mkstemp (prefix = upfilename, dir = ".")
+
+ print ("accepting uploaded file: %s -> %s" % (upfilename, destfilename), file=sys.stderr)
+
+ shutil.copyfileobj (upfile.file, os.fdopen (destfile, "wb"))
+
+ if upfile.done == -1:
+ self.send_error (408, "upload interrupted")
+
+ txt = b"""\
+ <html>
+ <head><title>Woof Upload</title></head>
+ <body>
+ <h1>Woof Upload complete</title></h1>
+ <p>Thanks a lot!</p>
+ </body>
+ </html>
+ """
+ self.send_response (200)
+ self.send_header ("Content-Type", "text/html")
+ self.send_header ("Content-Length", str (len (txt)))
+ self.end_headers ()
+ self.wfile.write (txt)
+
+ maxdownloads -= 1
+
+ return
+
+
+ def do_GET (self):
+ global maxdownloads, cpid, compressed, upload
+
+ # Form for uploading a file
+ if upload:
+ txt = b"""\
+ <html>
+ <head><title>Woof Upload</title></head>
+ <body>
+ <h1>Woof Upload</title></h1>
+ <form name="upload" method="POST" enctype="multipart/form-data">
+ <p><input type="file" name="upfile" /></p>
+ <p><input type="submit" value="Upload!" /></p>
+ </form>
+ </body>
+ </html>
+ """
+ self.send_response (200)
+ self.send_header ("Content-Type", "text/html")
+ self.send_header ("Content-Length", str (len (txt)))
+ self.end_headers ()
+ self.wfile.write (txt)
+ return
+
+ # Redirect any request to the filename of the file to serve.
+ # This hands over the filename to the client.
+
+ self.path = urllib.parse.quote (urllib.parse.unquote (self.path))
+ location = "/" + urllib.parse.quote (os.path.basename (self.filename))
+ if os.path.isdir (self.filename):
+ if compressed == 'gz':
+ location += ".tar.gz"
+ elif compressed == 'bz2':
+ location += ".tar.bz2"
+ elif compressed == 'zip':
+ location += ".zip"
+ else:
+ location += ".tar"
+
+ if self.path != location:
+ txt = """\
+ <html>
+ <head><title>302 Found</title></head>
+ <body>302 Found <a href="%s">here</a>.</body>
+ </html>\n""" % location
+ txt = txt.encode ('ascii')
+ self.send_response (302)
+ self.send_header ("Location", location)
+ self.send_header ("Content-Type", "text/html")
+ self.send_header ("Content-Length", str (len (txt)))
+ self.end_headers ()
+ self.wfile.write (txt)
+ return
+
+ maxdownloads -= 1
+
+ # let a separate process handle the actual download, so that
+ # multiple downloads can happen simultaneously.
+
+ cpid = os.fork ()
+
+ if cpid == 0:
+ # Child process
+ child = None
+ type = None
+
+ if os.path.isfile (self.filename):
+ type = "file"
+ elif os.path.isdir (self.filename):
+ type = "dir"
+
+ if not type:
+ print ("can only serve files or directories. Aborting.", file=sys.stderr)
+ sys.exit (1)
+
+ self.send_response (200)
+ self.send_header ("Content-Type", "application/octet-stream")
+ self.send_header ("Content-Disposition", "attachment;filename=%s" % urllib.parse.quote (os.path.basename (self.filename + self.archive_ext)))
+ if os.path.isfile (self.filename):
+ self.send_header ("Content-Length",
+ os.path.getsize (self.filename))
+ self.end_headers ()
+
+ try:
+ if type == "file":
+ datafile = open (self.filename, "rb")
+ shutil.copyfileobj (datafile, self.wfile)
+ datafile.close ()
+ elif type == "dir":
+ if compressed == 'zip':
+ zfile = zipfile.ZipFile (self.wfile, 'w', zipfile.ZIP_DEFLATED)
+ stripoff = os.path.dirname (self.filename) + os.sep
+
+ for root, dirs, files in os.walk (self.filename):
+ for f in files:
+ filename = os.path.join (root, f)
+ if filename[:len (stripoff)] != stripoff:
+ raise RuntimeException ("invalid filename assumptions, please report!")
+ zfile.write (filename, filename[len (stripoff):])
+ zfile.close ()
+ else:
+ tfile = tarfile.open (mode=('w|' + compressed),
+ fileobj=self.wfile)
+ tfile.add (self.filename,
+ arcname=os.path.basename (self.filename))
+ tfile.close ()
+
+ except Exception as e:
+ print (e)
+ print ("Connection broke. Aborting", file=sys.stderr)
+
+
+def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
+ global maxdownloads
+
+ maxdownloads = maxdown
+
+ archive_ext = ""
+ if filename and os.path.isdir (filename):
+ if compressed == 'gz':
+ archive_ext = ".tar.gz"
+ elif compressed == 'bz2':
+ archive_ext = ".tar.bz2"
+ elif compressed == 'zip':
+ archive_ext = ".zip"
+ else:
+ archive_ext = ".tar"
+
+ # We have to somehow push the filename of the file to serve to the
+ # class handling the requests. This is an evil way to do this...
+
+ FileServHTTPRequestHandler.filename = filename
+ FileServHTTPRequestHandler.archive_ext = archive_ext
+
+ try:
+ httpd = ForkingHTTPServer ((ip_addr, port), FileServHTTPRequestHandler)
+ except socket.error:
+ print ("cannot bind to IP address '%s' port %d" % (ip_addr, port), file=sys.stderr)
+ sys.exit (1)
+
+ if not ip_addr:
+ ip_addr = find_ip ()
+ if ip_addr:
+ if filename:
+ location = (f"http://{ip_addr}:{httpd.server_port}/" +
+ urllib.parse.quote (os.path.basename (filename + archive_ext)))
+ else:
+ location = "http://%s:%s/" % (ip_addr, httpd.server_port)
+
+ print ("Now serving on %s" % location)
+
+ while cpid != 0 and maxdownloads > 0:
+ httpd.handle_request ()
+
+
+
+def usage (defport, defmaxdown, errmsg = None):
+ name = os.path.basename (sys.argv[0])
+ print ("""
+ Usage: %s [-i <ip_addr>] [-p <port>] [-c <count>] <file>
+ %s [-i <ip_addr>] [-p <port>] [-c <count>] [-z|-j|-Z|-u] <dir>
+ %s [-i <ip_addr>] [-p <port>] [-c <count>] -s
+ %s [-i <ip_addr>] [-p <port>] [-c <count>] -U
+
+ %s <url>
+
+ Serves a single file <count> times via http on port <port> on IP
+ address <ip_addr>.
+ When a directory is specified, an tar archive gets served. By default
+ it is gzip compressed. You can specify -z for gzip compression,
+ -j for bzip2 compression, -Z for ZIP compression or -u for no compression.
+ You can configure your default compression method in the configuration
+ file described below.
+
+ When -s is specified instead of a filename, %s distributes itself.
+
+ When -U is specified, woof provides an upload form, allowing file uploads.
+
+ defaults: count = %d, port = %d
+
+ If started with an url as an argument, woof acts as a client,
+ downloading the file and saving it in the current directory.
+
+ You can specify different defaults in two locations: /etc/woofrc
+ and ~/.woofrc can be INI-style config files containing the default
+ port and the default count. The file in the home directory takes
+ precedence. The compression methods are "off", "gz", "bz2" or "zip".
+
+ Sample file:
+
+ [main]
+ port = 8008
+ count = 2
+ ip = 127.0.0.1
+ compressed = gz
+ """ % (name, name, name, name, name, name, defmaxdown, defport), file=sys.stderr)
+
+ if errmsg:
+ print (errmsg, file=sys.stderr)
+ print (file=sys.stderr)
+ sys.exit (1)
+
+
+
+def woof_client (url):
+ urlparts = urllib.parse.urlparse (url, "http")
+ if urlparts[0] not in [ "http", "https" ] or urlparts[1] == '':
+ return None
+
+ fname = None
+
+ f = urllib.request.urlopen (url)
+
+ f_meta = f.info ()
+ disp = f_meta["Content-Disposition"]
+
+ if disp:
+ disp = disp.split (";")
+
+ if disp and disp[0].lower () == 'attachment':
+ fname = [x[9:] for x in disp[1:] if x[:9].lower () == "filename="]
+ if len (fname):
+ fname = fname[0]
+ else:
+ fname = None
+
+ if fname == None:
+ url = f.geturl ()
+ urlparts = urllib.parse.urlparse (url)
+ fname = urlparts[2]
+
+ if not fname:
+ fname = "woof-out.bin"
+
+ if fname:
+ fname = urllib.parse.unquote (fname)
+ fname = os.path.basename (fname)
+
+ readline.set_startup_hook (lambda: readline.insert_text (fname))
+ fname = input ("Enter target filename: ")
+ readline.set_startup_hook (None)
+
+ override = False
+
+ destfile = None
+ destfilename = os.path.join (".", fname)
+ try:
+ destfile = os.open (destfilename,
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ override = input ("File exists. Overwrite (y/n)? ")
+ override = override.lower () in [ "y", "yes" ]
+ else:
+ raise
+
+ if destfile == None:
+ if override == True:
+ destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT, 0o644)
+ else:
+ for suffix in [".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]:
+ destfilename = os.path.join (".", fname + suffix)
+ try:
+ destfile = os.open (destfilename,
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
+ break
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ continue
+ raise
+
+ if not destfile:
+ destfile, destfilename = tempfile.mkstemp (prefix = fname + ".",
+ dir = ".")
+ print ("alternate filename is:", destfilename)
+
+ print ("downloading file: %s -> %s" % (fname, destfilename))
+
+ shutil.copyfileobj (f, os.fdopen (destfile, "wb"))
+
+ return 1;
+
+
+
+def main ():
+ global cpid, upload, compressed
+
+ maxdown = 1
+ port = 8080
+ ip_addr = ''
+
+ config = configparser.ConfigParser ()
+ config.read (['/etc/woofrc', os.path.expanduser ('~/.woofrc')])
+
+ if config.has_option ('main', 'port'):
+ port = config.getint ('main', 'port')
+
+ if config.has_option ('main', 'count'):
+ maxdown = config.getint ('main', 'count')
+
+ if config.has_option ('main', 'ip'):
+ ip_addr = config.get ('main', 'ip')
+
+ if config.has_option ('main', 'compressed'):
+ formats = { 'gz' : 'gz',
+ 'true' : 'gz',
+ 'bz' : 'bz2',
+ 'bz2' : 'bz2',
+ 'zip' : 'zip',
+ 'off' : '',
+ 'false' : '' }
+ compressed = config.get ('main', 'compressed')
+ compressed = formats.get (compressed, 'gz')
+
+ defaultport = port
+ defaultmaxdown = maxdown
+
+ try:
+ options, filenames = getopt.gnu_getopt (sys.argv[1:], "hUszjZui:c:p:")
+ except getopt.GetoptError as desc:
+ usage (defaultport, defaultmaxdown, desc)
+
+ for option, val in options:
+ if option == '-c':
+ try:
+ maxdown = int (val)
+ if maxdown <= 0:
+ raise ValueError
+ except ValueError:
+ usage (defaultport, defaultmaxdown,
+ "invalid download count: %r. "
+ "Please specify an integer >= 0." % val)
+
+ elif option == '-i':
+ ip_addr = val
+
+ elif option == '-p':
+ try:
+ port = int (val)
+ except ValueError:
+ usage (defaultport, defaultmaxdown,
+ "invalid port number: %r. Please specify an integer" % val)
+
+ elif option == '-s':
+ filenames.append (__file__)
+
+ elif option == '-h':
+ usage (defaultport, defaultmaxdown)
+
+ elif option == '-U':
+ upload = True
+
+ elif option == '-z':
+ compressed = 'gz'
+ elif option == '-j':
+ compressed = 'bz2'
+ elif option == '-Z':
+ compressed = 'zip'
+ elif option == '-u':
+ compressed = ''
+
+ else:
+ usage (defaultport, defaultmaxdown, "Unknown option: %r" % option)
+
+ if upload:
+ if len (filenames) > 0:
+ usage (defaultport, defaultmaxdown,
+ "Conflicting usage: simultaneous up- and download not supported.")
+ filename = None
+
+ else:
+ if len (filenames) == 1:
+ if woof_client (filenames[0]) != None:
+ sys.exit (0)
+
+ filename = os.path.abspath (filenames[0])
+ else:
+ usage (defaultport, defaultmaxdown,
+ "Can only serve single files/directories.")
+
+ if not os.path.exists (filename):
+ usage (defaultport, defaultmaxdown,
+ "%s: No such file or directory" % filenames[0])
+
+ if not (os.path.isfile (filename) or os.path.isdir (filename)):
+ usage (defaultport, defaultmaxdown,
+ "%s: Neither file nor directory" % filenames[0])
+
+ serve_files (filename, maxdown, ip_addr, port)
+
+ # wait for child processes to terminate
+ if cpid != 0:
+ try:
+ while 1:
+ os.wait ()
+ except OSError:
+ pass
+
+
+
+if __name__=='__main__':
+ try:
+ main ()
+ except KeyboardInterrupt:
+ print ()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 18, 20:17 (19 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2251123
Default Alt Text
D3157.id8056.diff (19 KB)
Attached To
Mode
D3157: Deploy woof on Debian
Attached
Detach File
Event Timeline
Log In to Comment