mirror of
https://github.com/sshuttle/sshuttle.git
synced 2024-11-21 23:43:18 +01:00
Use argparse for command line options
Fixes the kind of problems reported on #75 but does break the command line "API" (hopefully).
This commit is contained in:
parent
dea3f21943
commit
05bacf6fd6
@ -0,0 +1,4 @@
|
|||||||
|
try:
|
||||||
|
from sshuttle.version import version as __version__
|
||||||
|
except ImportError:
|
||||||
|
__version__ = "unknown"
|
@ -2,198 +2,47 @@ import sys
|
|||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sshuttle.helpers as helpers
|
import sshuttle.helpers as helpers
|
||||||
import sshuttle.options as options
|
|
||||||
import sshuttle.client as client
|
import sshuttle.client as client
|
||||||
import sshuttle.firewall as firewall
|
import sshuttle.firewall as firewall
|
||||||
import sshuttle.hostwatch as hostwatch
|
import sshuttle.hostwatch as hostwatch
|
||||||
import sshuttle.ssyslog as ssyslog
|
import sshuttle.ssyslog as ssyslog
|
||||||
|
from sshuttle.options import parser
|
||||||
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
from sshuttle.helpers import family_ip_tuple, log, Fatal
|
||||||
|
|
||||||
|
|
||||||
# 1.2.3.4/5 or just 1.2.3.4
|
|
||||||
def parse_subnet4(s):
|
|
||||||
m = re.match(r'(\d+)(?:\.(\d+)\.(\d+)\.(\d+))?(?:/(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%r is not a valid IP subnet format' % s)
|
|
||||||
(a, b, c, d, width) = m.groups()
|
|
||||||
(a, b, c, d) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0))
|
|
||||||
if width is None:
|
|
||||||
width = 32
|
|
||||||
else:
|
|
||||||
width = int(width)
|
|
||||||
if a > 255 or b > 255 or c > 255 or d > 255:
|
|
||||||
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
|
||||||
if width > 32:
|
|
||||||
raise Fatal('*/%d is greater than the maximum of 32' % width)
|
|
||||||
return(socket.AF_INET, '%d.%d.%d.%d' % (a, b, c, d), width)
|
|
||||||
|
|
||||||
|
|
||||||
# 1:2::3/64 or just 1:2::3
|
|
||||||
def parse_subnet6(s):
|
|
||||||
m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%r is not a valid IP subnet format' % s)
|
|
||||||
(net, width) = m.groups()
|
|
||||||
if width is None:
|
|
||||||
width = 128
|
|
||||||
else:
|
|
||||||
width = int(width)
|
|
||||||
if width > 128:
|
|
||||||
raise Fatal('*/%d is greater than the maximum of 128' % width)
|
|
||||||
return(socket.AF_INET6, net, width)
|
|
||||||
|
|
||||||
|
|
||||||
# Subnet file, supporting empty lines and hash-started comment lines
|
|
||||||
def parse_subnet_file(s):
|
|
||||||
try:
|
|
||||||
handle = open(s, 'r')
|
|
||||||
except OSError:
|
|
||||||
raise Fatal('Unable to open subnet file: %s' % s)
|
|
||||||
|
|
||||||
raw_config_lines = handle.readlines()
|
|
||||||
config_lines = []
|
|
||||||
for line_no, line in enumerate(raw_config_lines):
|
|
||||||
line = line.strip()
|
|
||||||
if len(line) == 0:
|
|
||||||
continue
|
|
||||||
if line[0] == '#':
|
|
||||||
continue
|
|
||||||
config_lines.append(line)
|
|
||||||
|
|
||||||
return config_lines
|
|
||||||
|
|
||||||
|
|
||||||
# list of:
|
|
||||||
# 1.2.3.4/5 or just 1.2.3.4
|
|
||||||
# 1:2::3/64 or just 1:2::3
|
|
||||||
def parse_subnets(subnets_str):
|
|
||||||
subnets = []
|
|
||||||
for s in subnets_str:
|
|
||||||
if ':' in s:
|
|
||||||
subnet = parse_subnet6(s)
|
|
||||||
else:
|
|
||||||
subnet = parse_subnet4(s)
|
|
||||||
subnets.append(subnet)
|
|
||||||
return subnets
|
|
||||||
|
|
||||||
|
|
||||||
# 1.2.3.4:567 or just 1.2.3.4 or just 567
|
|
||||||
def parse_ipport4(s):
|
|
||||||
s = str(s)
|
|
||||||
m = re.match(r'(?:(\d+)\.(\d+)\.(\d+)\.(\d+))?(?::)?(?:(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%r is not a valid IP:port format' % s)
|
|
||||||
(a, b, c, d, port) = m.groups()
|
|
||||||
(a, b, c, d, port) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0),
|
|
||||||
int(port or 0))
|
|
||||||
if a > 255 or b > 255 or c > 255 or d > 255:
|
|
||||||
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
|
||||||
if port > 65535:
|
|
||||||
raise Fatal('*:%d is greater than the maximum of 65535' % port)
|
|
||||||
if a is None:
|
|
||||||
a = b = c = d = 0
|
|
||||||
return ('%d.%d.%d.%d' % (a, b, c, d), port)
|
|
||||||
|
|
||||||
|
|
||||||
# [1:2::3]:456 or [1:2::3] or 456
|
|
||||||
def parse_ipport6(s):
|
|
||||||
s = str(s)
|
|
||||||
m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
|
|
||||||
if not m:
|
|
||||||
raise Fatal('%s is not a valid IP:port format' % s)
|
|
||||||
(ip, port) = m.groups()
|
|
||||||
(ip, port) = (ip or '::', int(port or 0))
|
|
||||||
return (ip, port)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_list(list):
|
|
||||||
return re.split(r'[\s,]+', list.strip()) if list else []
|
|
||||||
|
|
||||||
|
|
||||||
optspec = """
|
|
||||||
sshuttle [-l [ip:]port] [-r [username@]sshserver[:port]] <subnets...>
|
|
||||||
sshuttle --firewall <port> <subnets...>
|
|
||||||
sshuttle --hostwatch
|
|
||||||
--
|
|
||||||
l,listen= transproxy to this ip address and port number
|
|
||||||
H,auto-hosts scan for remote hostnames and update local /etc/hosts
|
|
||||||
N,auto-nets automatically determine subnets to route
|
|
||||||
dns capture local DNS requests and forward to the remote DNS server
|
|
||||||
ns-hosts= capture and forward remote DNS requests to the following servers
|
|
||||||
method= auto, nat, tproxy or pf
|
|
||||||
python= path to python interpreter on the remote server
|
|
||||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
|
||||||
x,exclude= exclude this subnet (can be used more than once)
|
|
||||||
X,exclude-from= exclude the subnets in a file (whitespace separated)
|
|
||||||
v,verbose increase debug message verbosity
|
|
||||||
V,version print the sshuttle version number and exit
|
|
||||||
e,ssh-cmd= the command to use to connect to the remote [ssh]
|
|
||||||
seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
|
|
||||||
no-latency-control sacrifice latency to improve bandwidth benchmarks
|
|
||||||
wrap= restart counting channel numbers after this number (for testing)
|
|
||||||
disable-ipv6 disables ipv6 support
|
|
||||||
D,daemon run in the background as a daemon
|
|
||||||
s,subnets= file where the subnets are stored, instead of on the command line
|
|
||||||
syslog send log messages to syslog (default if you use --daemon)
|
|
||||||
pidfile= pidfile name (only if using --daemon) [./sshuttle.pid]
|
|
||||||
server (internal use only)
|
|
||||||
firewall (internal use only)
|
|
||||||
hostwatch (internal use only)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
o = options.Options(optspec)
|
opt = parser.parse_args()
|
||||||
(opt, flags, extra) = o.parse(sys.argv[1:])
|
|
||||||
|
|
||||||
if opt.version:
|
|
||||||
from sshuttle.version import version
|
|
||||||
print(version)
|
|
||||||
return 0
|
|
||||||
if opt.daemon:
|
if opt.daemon:
|
||||||
opt.syslog = 1
|
opt.syslog = 1
|
||||||
if opt.wrap:
|
if opt.wrap:
|
||||||
import sshuttle.ssnet as ssnet
|
import sshuttle.ssnet as ssnet
|
||||||
ssnet.MAX_CHANNEL = int(opt.wrap)
|
ssnet.MAX_CHANNEL = opt.wrap
|
||||||
helpers.verbose = opt.verbose or 0
|
helpers.verbose = opt.verbose
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if opt.firewall:
|
if opt.firewall:
|
||||||
if len(extra) != 0:
|
if opt.subnets:
|
||||||
o.fatal('exactly zero arguments expected')
|
parser.error('exactly zero arguments expected')
|
||||||
return firewall.main(opt.method, opt.syslog)
|
return firewall.main(opt.method, opt.syslog)
|
||||||
elif opt.hostwatch:
|
elif opt.hostwatch:
|
||||||
return hostwatch.hw_main(extra)
|
return hostwatch.hw_main(opt.subnets)
|
||||||
else:
|
else:
|
||||||
if len(extra) < 1 and not opt.auto_nets and not opt.subnets:
|
includes = opt.subnets_from_file or opt.subnets
|
||||||
o.fatal('at least one subnet, subnet file, or -N expected')
|
excludes = opt.exclude
|
||||||
includes = extra
|
if not includes and not opt.auto_nets:
|
||||||
excludes = ['127.0.0.0/8']
|
parser.error('at least one subnet, subnet file, or -N expected')
|
||||||
for k, v in flags:
|
|
||||||
if k in ('-x', '--exclude'):
|
|
||||||
excludes.append(v)
|
|
||||||
if k in ('-X', '--exclude-from'):
|
|
||||||
excludes += open(v).read().split()
|
|
||||||
remotename = opt.remote
|
remotename = opt.remote
|
||||||
if remotename == '' or remotename == '-':
|
if remotename == '' or remotename == '-':
|
||||||
remotename = None
|
remotename = None
|
||||||
nslist = [family_ip_tuple(ns) for ns in parse_list(opt.ns_hosts)]
|
nslist = [family_ip_tuple(ns) for ns in opt.ns_hosts]
|
||||||
if opt.seed_hosts and not opt.auto_hosts:
|
if opt.seed_hosts and not opt.auto_hosts:
|
||||||
o.fatal('--seed-hosts only works if you also use -H')
|
parser.error('--seed-hosts only works if you also use -H')
|
||||||
if opt.seed_hosts:
|
if opt.seed_hosts:
|
||||||
sh = re.split(r'[\s,]+', (opt.seed_hosts or "").strip())
|
sh = re.split(r'[\s,]+', (opt.seed_hosts or "").strip())
|
||||||
elif opt.auto_hosts:
|
elif opt.auto_hosts:
|
||||||
sh = []
|
sh = []
|
||||||
else:
|
else:
|
||||||
sh = None
|
sh = None
|
||||||
if opt.subnets:
|
|
||||||
includes = parse_subnet_file(opt.subnets)
|
|
||||||
if not opt.method:
|
|
||||||
method_name = "auto"
|
|
||||||
elif opt.method in ["auto", "nat", "tproxy", "pf"]:
|
|
||||||
method_name = opt.method
|
|
||||||
else:
|
|
||||||
o.fatal("method_name %s not supported" % opt.method)
|
|
||||||
if opt.listen:
|
if opt.listen:
|
||||||
ipport_v6 = None
|
ipport_v6 = None
|
||||||
ipport_v4 = None
|
ipport_v4 = None
|
||||||
@ -218,11 +67,11 @@ def main():
|
|||||||
opt.latency_control,
|
opt.latency_control,
|
||||||
opt.dns,
|
opt.dns,
|
||||||
nslist,
|
nslist,
|
||||||
method_name,
|
opt.method,
|
||||||
sh,
|
sh,
|
||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
parse_subnets(includes),
|
includes,
|
||||||
parse_subnets(excludes),
|
excludes,
|
||||||
opt.daemon, opt.pidfile)
|
opt.daemon, opt.pidfile)
|
||||||
|
|
||||||
if return_code == 0:
|
if return_code == 0:
|
||||||
|
@ -1,215 +1,312 @@
|
|||||||
"""Command-line options parser.
|
|
||||||
With the help of an options spec string, easily parse command-line options.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import textwrap
|
|
||||||
import getopt
|
|
||||||
import re
|
import re
|
||||||
import struct
|
import socket
|
||||||
|
from argparse import ArgumentParser, Action, ArgumentTypeError as Fatal
|
||||||
|
from sshuttle import __version__
|
||||||
|
from sshuttle.helpers import family_ip_tuple
|
||||||
|
|
||||||
|
# 1.2.3.4/5 or just 1.2.3.4
|
||||||
|
def parse_subnet4(s):
|
||||||
|
m = re.match(r'(\d+)(?:\.(\d+)\.(\d+)\.(\d+))?(?:/(\d+))?$', s)
|
||||||
|
if not m:
|
||||||
|
raise Fatal('%r is not a valid IP subnet format' % s)
|
||||||
|
(a, b, c, d, width) = m.groups()
|
||||||
|
(a, b, c, d) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0))
|
||||||
|
if width is None:
|
||||||
|
width = 32
|
||||||
|
else:
|
||||||
|
width = int(width)
|
||||||
|
if a > 255 or b > 255 or c > 255 or d > 255:
|
||||||
|
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
||||||
|
if width > 32:
|
||||||
|
raise Fatal('*/%d is greater than the maximum of 32' % width)
|
||||||
|
return(socket.AF_INET, '%d.%d.%d.%d' % (a, b, c, d), width)
|
||||||
|
|
||||||
|
|
||||||
class OptDict:
|
# 1:2::3/64 or just 1:2::3
|
||||||
|
def parse_subnet6(s):
|
||||||
def __init__(self):
|
m = re.match(r'(?:([a-fA-F\d:]+))?(?:/(\d+))?$', s)
|
||||||
self._opts = {}
|
if not m:
|
||||||
|
raise Fatal('%r is not a valid IP subnet format' % s)
|
||||||
def __setitem__(self, k, v):
|
(net, width) = m.groups()
|
||||||
if k.startswith('no-') or k.startswith('no_'):
|
if width is None:
|
||||||
k = k[3:]
|
width = 128
|
||||||
v = not v
|
else:
|
||||||
self._opts[k] = v
|
width = int(width)
|
||||||
|
if width > 128:
|
||||||
def __getitem__(self, k):
|
raise Fatal('*/%d is greater than the maximum of 128' % width)
|
||||||
if k.startswith('no-') or k.startswith('no_'):
|
return(socket.AF_INET6, net, width)
|
||||||
return not self._opts[k[3:]]
|
|
||||||
return self._opts[k]
|
|
||||||
|
|
||||||
def __getattr__(self, k):
|
|
||||||
return self[k]
|
|
||||||
|
|
||||||
|
|
||||||
def _default_onabort(msg):
|
# Subnet file, supporting empty lines and hash-started comment lines
|
||||||
sys.exit(97)
|
def parse_subnet_file(s):
|
||||||
|
|
||||||
|
|
||||||
def _intify(v):
|
|
||||||
try:
|
try:
|
||||||
vv = int(v or '')
|
handle = open(s, 'r')
|
||||||
if str(vv) == v:
|
except OSError:
|
||||||
return vv
|
raise Fatal('Unable to open subnet file: %s' % s)
|
||||||
except ValueError:
|
|
||||||
pass
|
raw_config_lines = handle.readlines()
|
||||||
return v
|
subnets = []
|
||||||
|
for line_no, line in enumerate(raw_config_lines):
|
||||||
|
line = line.strip()
|
||||||
|
if len(line) == 0:
|
||||||
|
continue
|
||||||
|
if line[0] == '#':
|
||||||
|
continue
|
||||||
|
subnets.append(parse_subnet(line))
|
||||||
|
|
||||||
|
return subnets
|
||||||
|
|
||||||
|
|
||||||
def _atoi(v):
|
# 1.2.3.4/5 or just 1.2.3.4
|
||||||
try:
|
# 1:2::3/64 or just 1:2::3
|
||||||
return int(v or 0)
|
def parse_subnet(subnet_str):
|
||||||
except ValueError:
|
if ':' in subnet_str:
|
||||||
return 0
|
return parse_subnet6(subnet_str)
|
||||||
|
else:
|
||||||
|
return parse_subnet4(subnet_str)
|
||||||
|
|
||||||
|
|
||||||
def _remove_negative_kv(k, v):
|
# 1.2.3.4:567 or just 1.2.3.4 or just 567
|
||||||
if k.startswith('no-') or k.startswith('no_'):
|
def parse_ipport4(s):
|
||||||
return k[3:], not v
|
s = str(s)
|
||||||
return k, v
|
m = re.match(r'(?:(\d+)\.(\d+)\.(\d+)\.(\d+))?(?::)?(?:(\d+))?$', s)
|
||||||
|
if not m:
|
||||||
|
raise Fatal('%r is not a valid IP:port format' % s)
|
||||||
|
(a, b, c, d, port) = m.groups()
|
||||||
|
(a, b, c, d, port) = (int(a or 0), int(b or 0), int(c or 0), int(d or 0),
|
||||||
|
int(port or 0))
|
||||||
|
if a > 255 or b > 255 or c > 255 or d > 255:
|
||||||
|
raise Fatal('%d.%d.%d.%d has numbers > 255' % (a, b, c, d))
|
||||||
|
if port > 65535:
|
||||||
|
raise Fatal('*:%d is greater than the maximum of 65535' % port)
|
||||||
|
if a is None:
|
||||||
|
a = b = c = d = 0
|
||||||
|
return ('%d.%d.%d.%d' % (a, b, c, d), port)
|
||||||
|
|
||||||
|
|
||||||
def _remove_negative_k(k):
|
# [1:2::3]:456 or [1:2::3] or 456
|
||||||
return _remove_negative_kv(k, None)[0]
|
def parse_ipport6(s):
|
||||||
|
s = str(s)
|
||||||
|
m = re.match(r'(?:\[([^]]*)])?(?::)?(?:(\d+))?$', s)
|
||||||
|
if not m:
|
||||||
|
raise Fatal('%s is not a valid IP:port format' % s)
|
||||||
|
(ip, port) = m.groups()
|
||||||
|
(ip, port) = (ip or '::', int(port or 0))
|
||||||
|
return (ip, port)
|
||||||
|
|
||||||
|
|
||||||
def _tty_width():
|
def parse_list(list):
|
||||||
if not hasattr(sys.stderr, "fileno"):
|
return re.split(r'[\s,]+', list.strip()) if list else []
|
||||||
return _atoi(os.environ.get('WIDTH')) or 70
|
|
||||||
s = struct.pack("HHHH", 0, 0, 0, 0)
|
|
||||||
try:
|
|
||||||
import fcntl
|
|
||||||
import termios
|
|
||||||
s = fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, s)
|
|
||||||
except (IOError, ImportError):
|
|
||||||
return _atoi(os.environ.get('WIDTH')) or 70
|
|
||||||
(ysize, xsize, ypix, xpix) = struct.unpack('HHHH', s)
|
|
||||||
return xsize or 70
|
|
||||||
|
|
||||||
|
|
||||||
class Options:
|
class Concat(Action):
|
||||||
|
def __init__(self, option_strings, dest, nargs=None, **kwargs):
|
||||||
|
if nargs is not None:
|
||||||
|
raise ValueError("nargs not supported")
|
||||||
|
super(Concat, self).__init__(option_strings, dest, **kwargs)
|
||||||
|
|
||||||
"""Option parser.
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
When constructed, two strings are mandatory. The first one is the command
|
curr_value = getattr(namespace, self.dest, [])
|
||||||
name showed before error messages. The second one is a string called an
|
setattr(namespace, self.dest, curr_value + values)
|
||||||
optspec that specifies the synopsis and option flags and their description.
|
|
||||||
For more information about optspecs, consult the bup-options(1) man page.
|
|
||||||
|
|
||||||
Two optional arguments specify an alternative parsing function and an
|
|
||||||
alternative behaviour on abort (after having output the usage string).
|
|
||||||
|
|
||||||
By default, the parser function is getopt.gnu_getopt, and the abort
|
parser = ArgumentParser(
|
||||||
behaviour is to exit the program.
|
prog="sshuttle",
|
||||||
|
usage="%(prog)s [-l [ip:]port] [-r [user@]sshserver[:port]] <subnets...>"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"subnets",
|
||||||
|
metavar="IP/MASK [IP/MASK...]",
|
||||||
|
nargs="*",
|
||||||
|
type=parse_subnet,
|
||||||
|
help="""
|
||||||
|
capture and forward traffic to these subnets (whitespace separated)
|
||||||
"""
|
"""
|
||||||
|
)
|
||||||
def __init__(self, optspec, optfunc=getopt.gnu_getopt,
|
parser.add_argument(
|
||||||
onabort=_default_onabort):
|
"-l", "--listen",
|
||||||
self.optspec = optspec
|
metavar="[IP:]PORT",
|
||||||
self._onabort = onabort
|
help="""
|
||||||
self.optfunc = optfunc
|
transproxy to this ip address and port number
|
||||||
self._aliases = {}
|
|
||||||
self._shortopts = 'h?'
|
|
||||||
self._longopts = ['help']
|
|
||||||
self._hasparms = {}
|
|
||||||
self._defaults = {}
|
|
||||||
self._usagestr = self._gen_usage()
|
|
||||||
|
|
||||||
def _gen_usage(self):
|
|
||||||
out = []
|
|
||||||
lines = self.optspec.strip().split('\n')
|
|
||||||
lines.reverse()
|
|
||||||
first_syn = True
|
|
||||||
while lines:
|
|
||||||
l = lines.pop()
|
|
||||||
if l == '--':
|
|
||||||
break
|
|
||||||
out.append('%s: %s\n' % (first_syn and 'usage' or ' or', l))
|
|
||||||
first_syn = False
|
|
||||||
out.append('\n')
|
|
||||||
last_was_option = False
|
|
||||||
while lines:
|
|
||||||
l = lines.pop()
|
|
||||||
if l.startswith(' '):
|
|
||||||
out.append('%s%s\n' % (last_was_option and '\n' or '',
|
|
||||||
l.lstrip()))
|
|
||||||
last_was_option = False
|
|
||||||
elif l:
|
|
||||||
(flags, extra) = l.split(' ', 1)
|
|
||||||
extra = extra.strip()
|
|
||||||
if flags.endswith('='):
|
|
||||||
flags = flags[:-1]
|
|
||||||
has_parm = 1
|
|
||||||
else:
|
|
||||||
has_parm = 0
|
|
||||||
g = re.search(r'\[([^\]]*)\]$', extra)
|
|
||||||
if g:
|
|
||||||
defval = g.group(1)
|
|
||||||
else:
|
|
||||||
defval = None
|
|
||||||
flagl = flags.split(',')
|
|
||||||
flagl_nice = []
|
|
||||||
for _f in flagl:
|
|
||||||
f, dvi = _remove_negative_kv(_f, _intify(defval))
|
|
||||||
self._aliases[f] = _remove_negative_k(flagl[0])
|
|
||||||
self._hasparms[f] = has_parm
|
|
||||||
self._defaults[f] = dvi
|
|
||||||
if len(f) == 1:
|
|
||||||
self._shortopts += f + (has_parm and ':' or '')
|
|
||||||
flagl_nice.append('-' + f)
|
|
||||||
else:
|
|
||||||
f_nice = re.sub(r'\W', '_', f)
|
|
||||||
self._aliases[f_nice] = _remove_negative_k(flagl[0])
|
|
||||||
self._longopts.append(f + (has_parm and '=' or ''))
|
|
||||||
self._longopts.append('no-' + f)
|
|
||||||
flagl_nice.append('--' + _f)
|
|
||||||
flags_nice = ', '.join(flagl_nice)
|
|
||||||
if has_parm:
|
|
||||||
flags_nice += ' ...'
|
|
||||||
prefix = ' %-20s ' % flags_nice
|
|
||||||
argtext = '\n'.join(textwrap.wrap(extra, width=_tty_width(),
|
|
||||||
initial_indent=prefix,
|
|
||||||
subsequent_indent=' ' * 28))
|
|
||||||
out.append(argtext + '\n')
|
|
||||||
last_was_option = True
|
|
||||||
else:
|
|
||||||
out.append('\n')
|
|
||||||
last_was_option = False
|
|
||||||
return ''.join(out).rstrip() + '\n'
|
|
||||||
|
|
||||||
def usage(self, msg=""):
|
|
||||||
"""Print usage string to stderr and abort."""
|
|
||||||
sys.stderr.write(self._usagestr)
|
|
||||||
e = self._onabort and self._onabort(msg) or None
|
|
||||||
if e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def fatal(self, s):
|
|
||||||
"""Print an error message to stderr and abort with usage string."""
|
|
||||||
msg = 'error: %s\n' % s
|
|
||||||
sys.stderr.write(msg)
|
|
||||||
return self.usage(msg)
|
|
||||||
|
|
||||||
def parse(self, args):
|
|
||||||
"""Parse a list of arguments and return (options, flags, extra).
|
|
||||||
|
|
||||||
In the returned tuple, "options" is an OptDict with known options,
|
|
||||||
"flags" is a list of option flags that were used on the command-line,
|
|
||||||
and "extra" is a list of positional arguments.
|
|
||||||
"""
|
"""
|
||||||
try:
|
)
|
||||||
(flags, extra) = self.optfunc(
|
parser.add_argument(
|
||||||
args, self._shortopts, self._longopts)
|
"-H", "--auto-hosts",
|
||||||
except getopt.GetoptError as e:
|
action="store_true",
|
||||||
self.fatal(e)
|
help="""
|
||||||
|
scan for remote hostnames and update local /etc/hosts
|
||||||
opt = OptDict()
|
"""
|
||||||
|
)
|
||||||
for k, v in self._defaults.items():
|
parser.add_argument(
|
||||||
k = self._aliases[k]
|
"-N", "--auto-nets",
|
||||||
opt[k] = v
|
action="store_true",
|
||||||
|
help="""
|
||||||
for (k, v) in flags:
|
automatically determine subnets to route
|
||||||
k = k.lstrip('-')
|
"""
|
||||||
if k in ('h', '?', 'help'):
|
)
|
||||||
self.usage()
|
parser.add_argument(
|
||||||
if k.startswith('no-'):
|
"--dns",
|
||||||
k = self._aliases[k[3:]]
|
action="store_true",
|
||||||
v = 0
|
help="""
|
||||||
else:
|
capture local DNS requests and forward to the remote DNS server
|
||||||
k = self._aliases[k]
|
"""
|
||||||
if not self._hasparms[k]:
|
)
|
||||||
assert(v == '')
|
parser.add_argument(
|
||||||
v = (opt._opts.get(k) or 0) + 1
|
"--ns-hosts",
|
||||||
else:
|
metavar="IP[,IP]",
|
||||||
v = _intify(v)
|
default=[],
|
||||||
opt[k] = v
|
type=parse_list,
|
||||||
for (f1, f2) in self._aliases.items():
|
help="""
|
||||||
opt[f1] = opt._opts.get(f2)
|
capture and forward DNS requests made to the following servers
|
||||||
return (opt, flags, extra)
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--method",
|
||||||
|
choices=["auto", "nat", "tproxy", "pf"],
|
||||||
|
metavar="TYPE",
|
||||||
|
default="auto",
|
||||||
|
help="""
|
||||||
|
%(choices)s
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--python",
|
||||||
|
metavar="PATH",
|
||||||
|
help="""
|
||||||
|
path to python interpreter on the remote server
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--remote",
|
||||||
|
metavar="[USERNAME@]ADDR[:PORT]",
|
||||||
|
help="""
|
||||||
|
ssh hostname (and optional username) of remote %(prog)s server
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-x", "--exclude",
|
||||||
|
metavar="IP/MASK",
|
||||||
|
action="append",
|
||||||
|
default=[parse_subnet('127.0.0.1/8')],
|
||||||
|
type=parse_subnet,
|
||||||
|
help="""
|
||||||
|
exclude this subnet (can be used more than once)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-X", "--exclude-from",
|
||||||
|
metavar="PATH",
|
||||||
|
action=Concat,
|
||||||
|
dest="exclude",
|
||||||
|
type=parse_subnet_file,
|
||||||
|
help="""
|
||||||
|
exclude the subnets in a file (whitespace separated)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose",
|
||||||
|
action="count",
|
||||||
|
default=0,
|
||||||
|
help="""
|
||||||
|
increase debug message verbosity
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-V", "--version",
|
||||||
|
action="version",
|
||||||
|
version=__version__,
|
||||||
|
help="""
|
||||||
|
print the %(prog)s version number and exit
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e", "--ssh-cmd",
|
||||||
|
metavar="CMD",
|
||||||
|
default="ssh",
|
||||||
|
help="""
|
||||||
|
the command to use to connect to the remote [%(default)s]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--seed-hosts",
|
||||||
|
metavar="HOSTNAME[,HOSTNAME]",
|
||||||
|
default=[],
|
||||||
|
help="""
|
||||||
|
with -H, use these hostnames for initial scan (comma-separated)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-latency-control",
|
||||||
|
action="store_false",
|
||||||
|
dest="latency_control",
|
||||||
|
help="""
|
||||||
|
sacrifice latency to improve bandwidth benchmarks
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--wrap",
|
||||||
|
metavar="NUM",
|
||||||
|
type=int,
|
||||||
|
help="""
|
||||||
|
restart counting channel numbers after this number (for testing)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--disable-ipv6",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
disable IPv6 support
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-D", "--daemon",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
run in the background as a daemon
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s", "--subnets",
|
||||||
|
metavar="PATH",
|
||||||
|
dest="subnets_from_file",
|
||||||
|
type=parse_subnet_file,
|
||||||
|
help="""
|
||||||
|
file where the subnets are stored, instead of on the command line
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--syslog",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
send log messages to syslog (default if you use --daemon)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pidfile",
|
||||||
|
metavar="PATH",
|
||||||
|
default="./sshuttle.pid",
|
||||||
|
help="""
|
||||||
|
pidfile name (only if using --daemon) [%(default)s]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--server",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
(internal use only)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--firewall",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
(internal use only)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--hostwatch",
|
||||||
|
action="store_true",
|
||||||
|
help="""
|
||||||
|
(internal use only)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user