Skip to content

Commit 57d1cb1

Browse files
committed
Merge pull request apenwarr#5 from seanzxx/yosemite_support_sudo_fix
fix sudo issue in yosemite
2 parents 8be9270 + 6e32d14 commit 57d1cb1

File tree

2 files changed

+163
-84
lines changed

2 files changed

+163
-84
lines changed

src/client.py

Lines changed: 17 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
import sys
1313
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
1414
from helpers import log, debug1, debug2, debug3, Fatal, islocal
15-
from fcntl import ioctl
16-
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, sizeof, addressof, memmove
1715

1816
recvmsg = None
1917
try:
@@ -186,79 +184,22 @@ def daemon_cleanup():
186184
else:
187185
raise
188186

189-
190-
class pf_state_xport(Union):
191-
_fields_ = [("port", c_uint16),
192-
("call_id", c_uint16),
193-
("spi", c_uint32)]
194-
195-
class pf_addr(Structure):
196-
class _pfa(Union):
197-
_fields_ = [("v4", c_uint32), # struct in_addr
198-
("v6", c_uint32 * 4), # struct in6_addr
199-
("addr8", c_uint8 * 16),
200-
("addr16", c_uint16 * 8),
201-
("addr32", c_uint32 * 4)]
202-
203-
_fields_ = [("pfa", _pfa)]
204-
_anonymous_ = ("pfa",)
205-
206-
class pfioc_natlook(Structure):
207-
_fields_ = [("saddr", pf_addr),
208-
("daddr", pf_addr),
209-
("rsaddr", pf_addr),
210-
("rdaddr", pf_addr),
211-
("sxport", pf_state_xport),
212-
("dxport", pf_state_xport),
213-
("rsxport", pf_state_xport),
214-
("rdxport", pf_state_xport),
215-
("af", c_uint8), # sa_family_t
216-
("proto", c_uint8),
217-
("proto_variant", c_uint8),
218-
("direction", c_uint8)]
219-
220-
DIOCNATLOOK = ((0x40000000L | 0x80000000L) | ((sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23))
221-
PF_OUT = 2
222-
223-
_pf_fd = None
187+
pf_command_file = None
224188

225189
def pf_dst(sock):
226-
global _pf_fd
227-
try:
228-
peer = sock.getpeername()
229-
proxy = sock.getsockname()
230-
231-
pnl = pfioc_natlook()
232-
pnl.proto = socket.IPPROTO_TCP
233-
pnl.direction = PF_OUT
234-
if sock.family == socket.AF_INET:
235-
pnl.af = socket.AF_INET
236-
memmove(addressof(pnl.saddr), socket.inet_pton(socket.AF_INET, peer[0]), 4)
237-
pnl.sxport.port = socket.htons(peer[1])
238-
memmove(addressof(pnl.daddr), socket.inet_pton(socket.AF_INET, proxy[0]), 4)
239-
pnl.dxport.port = socket.htons(proxy[1])
240-
elif sock.family == socket.AF_INET6:
241-
pnl.af = socket.AF_INET6
242-
memmove(addressof(pnl.saddr), socket.inet_pton(socket.AF_INET6, peer[0]), 16)
243-
pnl.sxport.port = socket.htons(peer[1])
244-
memmove(addressof(pnl.daddr), socket.inet_pton(socket.AF_INET6, proxy[0]), 16)
245-
pnl.dxport.port = socket.htons(proxy[1])
246-
247-
if _pf_fd == None:
248-
_pf_fd = open('/dev/pf', 'r')
249-
250-
ioctl(_pf_fd, DIOCNATLOOK, (c_char * sizeof(pnl)).from_address(addressof(pnl)))
251-
252-
if pnl.af == socket.AF_INET:
253-
ip = socket.inet_ntop(socket.AF_INET, (c_char * 4).from_address(addressof(pnl.rdaddr)))
254-
elif pnl.af == socket.AF_INET6:
255-
ip = socket.inet_ntop(socket.AF_INET6, (c_char * 16).from_address(addressof(pnl.rdaddr)))
256-
port = socket.ntohs(pnl.rdxport.port)
257-
return (ip, port)
258-
except IOError, e:
259-
return sock.getsockname()
260-
raise
190+
peer = sock.getpeername()
191+
proxy = sock.getsockname()
192+
193+
argv = (sock.family, socket.IPPROTO_TCP, peer[0], peer[1], proxy[0], proxy[1])
194+
pf_command_file.write("QUERY_PF_NAT %r,%r,%s,%r,%s,%r\n" % argv)
195+
pf_command_file.flush()
196+
line = pf_command_file.readline()
197+
debug2("QUERY_PF_NAT %r,%r,%s,%r,%s,%r" % argv + ' > ' + line)
198+
if line.startswith('QUERY_PF_NAT_SUCCESS '):
199+
(ip, port) = line[21:].split(',')
200+
return (ip, int(port))
261201

202+
return sock.getsockname()
262203

263204
def original_dst(sock):
264205
try:
@@ -815,6 +756,10 @@ def main(listenip_v6, listenip_v4,
815756
if dns_listener.v6 is not None:
816757
dns_listener.v6.setsockopt(SOL_IPV6, IPV6_RECVORIGDSTADDR, 1)
817758

759+
if fw.method == "pf":
760+
global pf_command_file
761+
pf_command_file = fw.pfile
762+
818763
try:
819764
return _main(tcp_listener, udp_listener, fw, ssh_cmd, remotename,
820765
python, latency_control, dns_listener,

src/firewall.py

Lines changed: 146 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
import ssyslog
88
import sys
99
import os
10+
import re
1011
from helpers import log, debug1, debug3, islocal, Fatal, family_to_string, \
1112
resolvconf_nameservers
13+
from fcntl import ioctl
14+
from ctypes import c_char, c_uint8, c_uint16, c_uint32, Union, Structure, \
15+
sizeof, addressof, memmove
16+
1217

1318
# python doesn't have a definition for this
1419
IPPROTO_DIVERT = 254
@@ -463,15 +468,23 @@ def do_wait():
463468
return do_wait
464469

465470

466-
def pfctl(*args):
467-
argv = ['pfctl'] + list(args)
471+
def pfctl(args, stdin = None):
472+
argv = ['pfctl'] + list(args.split(" "))
468473
debug1('>> %s\n' % ' '.join(argv))
469-
rv = ssubprocess.Popen(argv, stderr=ssubprocess.PIPE).wait()
470-
if rv:
471-
raise Fatal('%r returned %d' % (argv, rv))
472474

475+
p = ssubprocess.Popen(argv, stdin = ssubprocess.PIPE,
476+
stdout = ssubprocess.PIPE,
477+
stderr = ssubprocess.PIPE)
478+
o = p.communicate(stdin)
479+
if p.returncode:
480+
raise Fatal('%r returned %d' % (argv, p.returncode))
481+
482+
return o
483+
484+
_pf_context = {'started_by_sshuttle': False, 'Xtoken':''}
473485

474486
def do_pf(port, dnsport, family, subnets, udp):
487+
global _pf_started_by_sshuttle
475488
tables = []
476489
translating_rules = []
477490
filtering_rules = []
@@ -494,14 +507,27 @@ def do_pf(port, dnsport, family, subnets, udp):
494507
translating_rules.append('rdr pass on lo0 proto udp to <dns_servers> port 53 -> 127.0.0.1 port %r' % dnsport)
495508
filtering_rules.append('pass out route-to lo0 inet proto udp to <dns_servers> port 53 keep state')
496509

497-
pf_config_file = '/etc/pf-sshuttle.conf'
498-
with open(pf_config_file, 'w+') as f:
499-
f.write('\n'.join(tables + translating_rules + filtering_rules) + '\n')
500-
501-
pfctl('-Ef', pf_config_file)
502-
os.remove(pf_config_file)
510+
rules = '\n'.join(tables + translating_rules + filtering_rules) + '\n'
511+
512+
pf_status = pfctl('-s all')[0]
513+
if not '\nrdr-anchor "sshuttle" all\n' in pf_status:
514+
pf_add_anchor_rule(PF_RDR, "sshuttle")
515+
if not '\nanchor "sshuttle" all\n' in pf_status:
516+
pf_add_anchor_rule(PF_PASS, "sshuttle")
517+
518+
pfctl('-a sshuttle -f /dev/stdin', rules)
519+
if sys.platform == "darwin":
520+
o = pfctl('-E')
521+
_pf_context['Xtoken'] = re.search(r'Token : (.+)', o[1]).group(1)
522+
elif 'INFO:\nStatus: Disabled' in pf_status:
523+
pfctl('-e')
524+
_pf_context['started_by_sshuttle'] = True
503525
else:
504-
pfctl('-dF', 'all')
526+
pfctl('-a sshuttle -F all')
527+
if sys.platform == "darwin":
528+
pfctl('-X %s' % _pf_context['Xtoken'])
529+
elif _pf_context['started_by_sshuttle']:
530+
pfctl('-d')
505531

506532

507533
def program_exists(name):
@@ -556,6 +582,106 @@ def restore_etc_hosts(port):
556582
rewrite_etc_hosts(port)
557583

558584

585+
# This are some classes and functions used to support pf in yosemite.
586+
class pf_state_xport(Union):
587+
_fields_ = [("port", c_uint16),
588+
("call_id", c_uint16),
589+
("spi", c_uint32)]
590+
591+
class pf_addr(Structure):
592+
class _pfa(Union):
593+
_fields_ = [("v4", c_uint32), # struct in_addr
594+
("v6", c_uint32 * 4), # struct in6_addr
595+
("addr8", c_uint8 * 16),
596+
("addr16", c_uint16 * 8),
597+
("addr32", c_uint32 * 4)]
598+
599+
_fields_ = [("pfa", _pfa)]
600+
_anonymous_ = ("pfa",)
601+
602+
class pfioc_natlook(Structure):
603+
_fields_ = [("saddr", pf_addr),
604+
("daddr", pf_addr),
605+
("rsaddr", pf_addr),
606+
("rdaddr", pf_addr),
607+
("sxport", pf_state_xport),
608+
("dxport", pf_state_xport),
609+
("rsxport", pf_state_xport),
610+
("rdxport", pf_state_xport),
611+
("af", c_uint8), # sa_family_t
612+
("proto", c_uint8),
613+
("proto_variant", c_uint8),
614+
("direction", c_uint8)]
615+
616+
pfioc_rule = c_char * 3104 # sizeof(struct pfioc_rule)
617+
618+
pfioc_pooladdr = c_char * 1136 # sizeof(struct pfioc_pooladdr)
619+
620+
MAXPATHLEN = 1024
621+
622+
DIOCNATLOOK = ((0x40000000L | 0x80000000L) | ((sizeof(pfioc_natlook) & 0x1fff) << 16) | ((ord('D')) << 8) | (23))
623+
DIOCCHANGERULE = ((0x40000000L | 0x80000000L) | ((sizeof(pfioc_rule) & 0x1fff) << 16) | ((ord('D')) << 8) | (26))
624+
DIOCBEGINADDRS = ((0x40000000L | 0x80000000L) | ((sizeof(pfioc_pooladdr) & 0x1fff) << 16) | ((ord('D')) << 8) | (51))
625+
626+
PF_CHANGE_ADD_TAIL = 2
627+
PF_CHANGE_GET_TICKET = 6
628+
629+
PF_PASS = 0
630+
PF_RDR = 8
631+
632+
PF_OUT = 2
633+
634+
_pf_fd = None
635+
636+
def pf_get_dev():
637+
global _pf_fd
638+
if _pf_fd == None:
639+
_pf_fd = os.open('/dev/pf', os.O_RDWR)
640+
641+
return _pf_fd
642+
643+
def pf_query_nat(family, proto, src_ip, src_port, dst_ip, dst_port):
644+
[proto, family, src_port, dst_port] = [int(v) for v in [proto, family, src_port, dst_port]]
645+
646+
length = 4 if family == socket.AF_INET else 16
647+
648+
pnl = pfioc_natlook()
649+
pnl.proto = proto
650+
pnl.direction = PF_OUT
651+
pnl.af = family
652+
memmove(addressof(pnl.saddr), socket.inet_pton(pnl.af, src_ip), length)
653+
pnl.sxport.port = socket.htons(src_port)
654+
memmove(addressof(pnl.daddr), socket.inet_pton(pnl.af, dst_ip), length)
655+
pnl.dxport.port = socket.htons(dst_port)
656+
657+
ioctl(pf_get_dev(), DIOCNATLOOK, (c_char * sizeof(pnl)).from_address(addressof(pnl)))
658+
659+
ip = socket.inet_ntop(pnl.af, (c_char * length).from_address(addressof(pnl.rdaddr)))
660+
port = socket.ntohs(pnl.rdxport.port)
661+
return (ip, port)
662+
663+
def pf_add_anchor_rule(type, name):
664+
ACTION_OFFSET = 0
665+
POOL_TICKET_OFFSET = 8
666+
ANCHOR_CALL_OFFSET = 1040
667+
RULE_ACTION_OFFSET = 3068
668+
669+
pr = pfioc_rule()
670+
ppa = pfioc_pooladdr()
671+
672+
ioctl(pf_get_dev(), DIOCBEGINADDRS, ppa)
673+
674+
memmove(addressof(pr) + POOL_TICKET_OFFSET, ppa[4:8], 4) #pool_ticket
675+
memmove(addressof(pr) + ANCHOR_CALL_OFFSET, name, min(MAXPATHLEN, len(name))) #anchor_call = name
676+
memmove(addressof(pr) + RULE_ACTION_OFFSET, struct.pack('I', type), 4) #rule.action = type
677+
678+
memmove(addressof(pr) + ACTION_OFFSET, struct.pack('I', PF_CHANGE_GET_TICKET), 4) #action = PF_CHANGE_GET_TICKET
679+
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
680+
681+
memmove(addressof(pr) + ACTION_OFFSET, struct.pack('I', PF_CHANGE_ADD_TAIL), 4) #action = PF_CHANGE_ADD_TAIL
682+
ioctl(pf_get_dev(), DIOCCHANGERULE, pr)
683+
684+
559685
# This is some voodoo for setting up the kernel's transparent
560686
# proxying stuff. If subnets is empty, we just delete our sshuttle rules;
561687
# otherwise we delete it, then make them from scratch.
@@ -682,6 +808,14 @@ def main(port_v6, port_v4, dnsport_v6, dnsport_v4, method, udp, syslog):
682808
(name, ip) = line[5:].strip().split(',', 1)
683809
hostmap[name] = ip
684810
rewrite_etc_hosts(port_v6 or port_v4)
811+
elif line.startswith('QUERY_PF_NAT '):
812+
try:
813+
dst = pf_query_nat(*(line[13:].split(',')))
814+
sys.stdout.write('QUERY_PF_NAT_SUCCESS %s,%r\n' % dst)
815+
except IOError, e:
816+
sys.stdout.write('QUERY_PF_NAT_FAILURE %s\n' % e)
817+
818+
sys.stdout.flush()
685819
elif line:
686820
raise Fatal('expected EOF, got %r' % line)
687821
else:

0 commit comments

Comments
 (0)