Changeset 160 for etherws/trunk
- Timestamp:
- 05/20/12 02:39:34 (12 years ago)
- Location:
- etherws/trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
etherws/trunk/README.rst
r158 r160 110 110 It will return *401 Authorization Required*. 111 111 112 On client side, etherws requires username asoption, and password from113 stdin::112 On client side, etherws requires username from option, and password from 113 option or stdin:: 114 114 115 # etherws client --uri ws://<address>/ --user username --passwd password 115 116 # etherws client --uri ws://<address>/ --user username 116 117 Password: … … 123 124 History 124 125 ======= 126 0.5 (2012-05-20 JST) 127 - added passwd option to client mode 128 - fixed bug: basic authentication password cannot contain colon 129 - fixed bug: client loops meaninglessly even if server stops 130 125 131 0.4 (2012-05-19 JST) 126 132 - server certificate verification support -
etherws/trunk/etherws.py
r158 r160 44 44 import sys 45 45 import ssl 46 import time 46 47 import base64 47 48 import hashlib … … 52 53 import pytun 53 54 import websocket 55 import tornado.web 56 import tornado.ioloop 57 import tornado.websocket 54 58 import tornado.httpserver 55 import tornado.ioloop 56 import tornado.web 57 import tornado.websocket 58 59 60 class TapHandler(object): 59 60 61 class DebugMixIn(object): 62 def dprintf(self, msg, *args): 63 if self._debug: 64 prefix = '[%s] %s - ' % (time.asctime(), self.__class__.__name__) 65 sys.stderr.write(prefix + (msg % args)) 66 67 68 class TapHandler(DebugMixIn): 61 69 def __init__(self, dev, debug=False): 62 70 self._debug = debug … … 64 72 self._tap = pytun.TunTapDevice(dev, pytun.IFF_TAP | pytun.IFF_NO_PI) 65 73 self._tap.up() 66 self._ write_lock = threading.Lock()74 self._glock = threading.Lock() 67 75 68 76 def fileno(self): 69 return self._tap.fileno() 77 with self._glock: 78 return self._tap.fileno() 70 79 71 80 def register_client(self, client): 72 self._clients.append(client) 81 with self._glock: 82 self._clients.append(client) 73 83 74 84 def unregister_client(self, client): 75 self._clients.remove(client) 85 with self._glock: 86 self._clients.remove(client) 76 87 77 88 def write(self, caller, message): 78 if self._debug: 79 sys.stderr.write('%s: %s\n' % (caller.__class__.__name__, 80 message.encode('hex'))) 81 with self._write_lock: 89 with self._glock: 82 90 clients = self._clients[:] 83 91 … … 90 98 91 99 def __call__(self, fd, events): 92 self.write(self, self._tap.read(self._tap.mtu)) 93 94 95 class EtherWebSocket(tornado.websocket.WebSocketHandler): 100 with self._glock: 101 data = self._tap.read(self._tap.mtu) 102 self.write(self, data) 103 104 105 class EtherWebSocketHandler(tornado.websocket.WebSocketHandler, DebugMixIn): 96 106 def __init__(self, app, req, tap, debug=False): 97 super(EtherWebSocket , self).__init__(app, req)107 super(EtherWebSocketHandler, self).__init__(app, req) 98 108 self._tap = tap 99 109 self._debug = debug … … 101 111 def open(self): 102 112 self._tap.register_client(self) 113 self.dprintf('connected: %s\n', self.request.remote_ip) 103 114 104 115 def on_message(self, message): 105 116 self._tap.write(self, message) 117 self.dprintf('received: %s %s\n', 118 self.request.remote_ip, message.encode('hex')) 106 119 107 120 def on_close(self): 108 121 self._tap.unregister_client(self) 109 110 111 class EtherWebSocketClient(object): 112 def __init__(self, tap, url, user=None, passwd=None): 122 self.dprintf('disconnected: %s\n', self.request.remote_ip) 123 124 125 class EtherWebSocketClient(DebugMixIn): 126 def __init__(self, tap, url, user=None, passwd=None, debug=False): 113 127 self._sock = None 114 128 self._tap = tap 115 129 self._url = url 130 self._debug = debug 116 131 self._options = {} 117 132 … … 121 136 self._options['header'] = auth 122 137 138 @property 139 def closed(self): 140 return not self._sock 141 123 142 def open(self): 143 if not self.closed: 144 raise websocket.WebSocketException('already opened') 124 145 self._sock = websocket.WebSocket() 125 146 self._sock.connect(self._url, **self._options) 147 self.dprintf('connected: %s\n', self._url) 126 148 127 149 def close(self): 150 if self.closed: 151 raise websocket.WebSocketException('already closed') 128 152 self._sock.close() 129 153 self._sock = None 154 self.dprintf('disconnected: %s\n', self._url) 130 155 131 156 def write_message(self, message, binary=False): 132 flag = websocket.ABNF.OPCODE_TEXT 157 if self.closed: 158 raise websocket.WebSocketException('closed socket') 133 159 if binary: 134 160 flag = websocket.ABNF.OPCODE_BINARY 161 else: 162 flag = websocket.ABNF.OPCODE_TEXT 135 163 self._sock.send(message, flag) 164 self.dprintf('sent: %s %s\n', self._url, message.encode('hex')) 136 165 137 166 def run_forever(self): 138 167 try: 139 if not self._sock:168 if self.closed: 140 169 self.open() 141 170 while True: … … 173 202 174 203 204 def realpath(ns, *keys): 205 for k in keys: 206 v = getattr(ns, k, None) 207 if v is not None: 208 v = os.path.realpath(v) 209 setattr(ns, k, v) 210 open(v).close() # check readable 211 return ns 212 213 175 214 def server_main(args): 176 def may_auth_required(cls, users): 215 def wrap_basic_auth(cls, users): 216 o_exec = cls._execute 217 177 218 if not users: 178 219 return cls 179 220 180 orig_execute = cls._execute 181 182 def _execute(self, transforms, *args, **kwargs): 221 def execute(self, transforms, *args, **kwargs): 183 222 def auth_required(): 184 223 self.stream.write(tornado.escape.utf8( … … 188 227 self.stream.close() 189 228 229 creds = self.request.headers.get('Authorization') 230 231 if not creds or not creds.startswith('Basic '): 232 return auth_required() 233 190 234 try: 191 creds = self.request.headers.get('Authorization') 192 193 if not creds or not creds.startswith('Basic '): 194 return auth_required() 195 196 creds = base64.b64decode(creds[6:]) 197 198 if creds.find(':') < 0: 199 return auth_required() 200 201 name, passwd = creds.split(':', 2) 235 name, passwd = base64.b64decode(creds[6:]).split(':', 1) 202 236 passwd = base64.b64encode(hashlib.sha1(passwd).digest()) 203 237 … … 205 239 return auth_required() 206 240 207 return o rig_execute(self, transforms, *args, **kwargs)241 return o_exec(self, transforms, *args, **kwargs) 208 242 209 243 except: 210 244 return auth_required() 211 245 212 cls._execute = _execute246 cls._execute = execute 213 247 return cls 214 248 … … 220 254 line = line.strip() 221 255 if 0 <= line.find(':'): 222 name, passwd = line.split(':', 2)256 name, passwd = line.split(':', 1) 223 257 if passwd.startswith('{SHA}'): 224 258 users[name] = passwd[5:] 225 259 if not users: 226 raise RuntimeError('no valid users found')260 raise ValueError('no valid users found') 227 261 except TypeError: 228 262 pass 229 263 return users 230 264 231 handler = may_auth_required(EtherWebSocket, load_htpasswd(args.htpasswd)) 232 ssl_options = {} 233 234 for k in ['keyfile', 'certfile']: 235 v = getattr(args, k, None) 236 if v: 237 v = os.path.realpath(v) 238 ssl_options[k] = v 239 open(v).close() # readable test 240 241 if len(ssl_options) == 1: 265 realpath(args, 'keyfile', 'certfile', 'htpasswd') 266 267 if args.keyfile and args.certfile: 268 ssl_options = {'keyfile': args.keyfile, 'certfile': args.certfile} 269 elif args.keyfile or args.certfile: 242 270 raise ValueError('both keyfile and certfile are required') 243 el if not ssl_options:271 else: 244 272 ssl_options = None 245 273 246 if not args.port:274 if args.port is None: 247 275 if ssl_options: 248 276 args.port = 443 249 277 else: 250 278 args.port = 80 279 elif not (0 <= args.port <= 65535): 280 raise ValueError('invalid port: %s' % args.port) 281 282 handler = wrap_basic_auth(EtherWebSocketHandler, 283 load_htpasswd(args.htpasswd)) 251 284 252 285 tap = TapHandler(args.device, debug=args.debug) … … 267 300 268 301 def client_main(args): 302 realpath(args, 'cacerts') 303 269 304 if args.debug: 270 305 websocket.enableTrace(True) … … 275 310 ca_certs=args.cacerts) 276 311 277 passwd = None 278 if args.user: 279 passwd = getpass.getpass() 312 if args.user and args.passwd is None: 313 args.passwd = getpass.getpass() 280 314 281 315 tap = TapHandler(args.device, debug=args.debug) 282 client = EtherWebSocketClient(tap, args.uri, args.user, passwd) 316 client = EtherWebSocketClient(tap, args.uri, 317 args.user, args.passwd, args.debug) 283 318 284 319 tap.register_client(client) 285 320 client.open() 286 321 287 t = threading.Thread(target=client.run_forever)288 t.setDaemon(True)289 290 322 ioloop = tornado.ioloop.IOLoop.instance() 291 323 ioloop.add_handler(tap.fileno(), tap, ioloop.READ) 292 324 325 t = threading.Thread(target=ioloop.start) 326 t.setDaemon(True) 327 293 328 if not args.foreground: 294 329 daemonize() 295 330 296 331 t.start() 297 ioloop.start()332 client.run_forever() 298 333 299 334 … … 319 354 parser_c.add_argument('--cacerts', action='store') 320 355 parser_c.add_argument('--user', action='store') 356 parser_c.add_argument('--passwd', action='store') 321 357 322 358 args = parser.parse_args() -
etherws/trunk/setup.py
r157 r160 37 37 setup( 38 38 name='etherws', 39 version='0. 4',39 version='0.5', 40 40 description='Ethernet over WebSocket tunneling server/client', 41 41 long_description=longdesc,
Note: See TracChangeset
for help on using the changeset viewer.