#!/usr/bin/env python
# cliechti@gmx.net
# python license

import select, socket, sys, threading

class Observer:
    """base class for views in the Observer Pattern"""
    def update(self, subject, *args, **kwargs):
        """Implement this method in a concrete observer."""
        raise "not implemented"

class Subject:
    """base class for a model in the Observer Pattern."""
    def __init__(self):
        """don't forget to call this in derrived classes.
        it initalizes the list of observers"""
        self.observerList = []
    def attach(self, observer):
        """attach an observer to this model."""
        self.observerList.append(observer)
    def detach(self, observer):
        """detach an observer from this model."""
        self.observerList.remove(observer)
    def notify(self, *args, **kwargs):
        """for use by the model. this method is called to notify
        observers about changes in the model."""
        for observer in self.observerList:
            observer.update(self, *args, **kwargs)

class DirectForwarder(Subject, threading.Thread):
    def __init__(self, localhp, targethp):
        Subject.__init__(self)
        threading.Thread.__init__(self)
        self.localhp  = localhp
        self.targethp = targethp
        self.verbose = 0
        self.active = 1
        self.setName('%d->%s:%d' % (localhp[1], targethp[0], targethp[1]))
        self.setDaemon(1)

    def run(self, forever = 1):
        sserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sserver.bind(self.localhp)
        sserver.listen(1)
        while forever and self.active:
            self.notify('listening on %s:%d' % self.localhp)
            while self.active:
                ready, _, _= select.select([sserver],[],[sserver], 3) # with timeout
                if ready: break
            else:
                self.notify('deactivated')
                break
            self.slocal, addr = sserver.accept()
            self.notify('Connected by %s:%d' % addr)

            #connecting to target
            self.notify('Connecting to %s:%d' % self.targethp)
            self.starget = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.starget.connect(self.targethp)
            self.notify('Connection open')

            self.slocal.setblocking(0)
            self.starget.setblocking(0)
            try:
                alive = 1
                while alive:
                    ready_to_read, ready_to_write, in_error = \
                        select.select(
                          [self.slocal, self.starget],  #potential_readers
                          [],                           #potential_writers
                          [self.slocal, self.starget],  #potential_errs
                          3)                            #timeout
                    for s in ready_to_read:
                        if s is self.slocal:
                            r = self.slocal.recv(8192)
                            if r:
                                self.starget.send(r)
                                if self.verbose: self.notify('->t %d' % len(r))
                            else:
                                alive = 0
                        elif s is self.starget:
                            r = self.starget.recv(8192)
                            if r:
                                self.slocal.send(r)
                                if self.verbose: self.notify('<-t %d' % len(r))
                            else:
                                alive = 0
                    if in_error: alive = 0
            except socket.error, msg:
                print msg

            self.notify("closing connections...")
            #switch off blocking to force threads to exit
            #t.setblocking(0);
            #conn.setblocking(0);

            self.slocal.close()
            self.starget.close()
        self.notify("Shutdown...")
        self.active = 0
        
    def stop(self):
        if self.active:
            self.active = 0
            self.join()

    def stat(self):
        return ('STOPPED','SERVICE')[self.active!=0]

if __name__ == '__main__':
    import os
    if len(sys.argv) != 3:
        print "USAGE: %s username host " % sys.argv[0]
        sys.exit(1);
    username, host = sys.argv[1:3]

    class MSG(Observer):
        def update(self, model, arg):
            print arg
    f = DirectForwarder(('127.0.0.2', 139), ('127.0.0.1', 1390))
    f.attach(MSG())
    f.start()
    os.system('ssh -c blowfish -L 1390:%s:139 %s@%s' % (host, username, host))
    f.stop()
