FAH Application Filter (early beta)

This forum contains information about 3rd party applications which may be of use to those who run the FAH client and one place where you might be able to get help when using one of those apps.

Moderator: Site Moderators

Post Reply
ChristianVirtual
Posts: 1576
Joined: Tue May 28, 2013 12:14 pm
Location: Tokyo

FAH Application Filter (early beta)

Post by ChristianVirtual »

just in case someone need a similar solution: an application filter for FAH client.

Why I hacked it tougher ? I have normally a VPN into my network; but for some app review with Apple I need to provide them access to a folding client. So I could provide them access into my VPN or I just open temporary a port in the firewall and do port forwarding. The later one is easier and faster to establish; of course more insecure.

Since the socket port could be addressed from outside with any telnet and allow all remote command I wanted a bit more (semi) security. That should be delivered by this python script.
It will establish itself a server to be targeted by the port forwarding and will itself be the client for the remote API to the FAH client.
Also I don't want all commands to be used by externals so I filter those commands usefull and block all other.

In case you have similar needs here the code (if you have any suggestions those are more then welcome !!)

Code: Select all

#! /usr/bin/env python

#
# (c) Christian Lohmann,  2013
#
# a little application-level filter for remote FAH client access
# potentially usefull when port forwarding from outside to internal FAH clients
# manage a positive list of commands accepted to be routed from outside to the FAH client
#


import os
import sys
import time
import socket
import select
import string
        
        
def FAHFilterAgent(hn):


    HOST, PORT = hn, 36330
    HOST, PORTs = hn, 36333
    
    backlog = 5
    size = 1024
    
    print "FAHFilter for:", hn, "port", PORTs
    
    
	#
	# establish proxy server on the give host and port
    sockProxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sockProxy.setblocking(0)
    sockProxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sockProxy.bind((HOST, PORTs))
    sockProxy.listen(backlog)
    
    
    # connect to the FAH client (right now same host; different port)
    sockFAH = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sockFAH.connect((HOST, PORT))

    # lists of sockets
    input = [sockProxy, sockFAH, sys.stdin]
    clientList = []
    
	
    running = 1

    while running:
        try:
            ready_to_read, ready_to_write, in_error = \
                  select.select(input, [], [])
            for s in ready_to_read:
                if s == sockProxy:
                    # a new client want to conncet
                    client, adress = sockProxy.accept()
                    input.append(client)
                    clientList.append(client)
                    print "connection ", client, adress, "established"
                elif s == sockFAH:
                    # read a data block
                    data = s.recv(size)
                    for c in clientList:
                        c.send(data)
                elif s == sys.stdin:
                    # close all client connections
                    junk = sys.stdin.readline()
                    for s in clientList:
                        print "close connection", s
                        s.shutdown(socket.SHUT_RDWR)
                        s.close()
                    running = 0
                    break
                else:
                    # read a data block
                    data = s.recv(size)
                    sn = s.getsockname()
                    print "from", s

                    if data:
                        print data, "from", s
                        if data.startswith("auth"):    sockFAH.send(data)
                        elif data.startswith("info"):  sockFAH.send(data)
                        elif data.startswith("exit"):  sockFAH.send(data)
                        elif data.startswith("sleep"): sockFAH.send(data)
                        elif data.startswith("updates"): sockFAH.send(data)
                        elif data.startswith("log-updates"): sockFAH.send(data) 
                        elif data.startswith("pause"): sockFAH.send(data) 
                        elif data.startswith("unpause"): sockFAH.send(data)
                        elif data.startswith("finish"): sockFAH.send(data)
                        else: print "command", data, "ignored"
                    else:
                        s.close()
                        input.remove(s)
        except IOError as e:
            print e


    sockProxy.close()
    sockFAH.close()

FAHFilterAgent("donation")

Update: played more and its quite bumpy ; specially if a connection get disconnected
ImageImage
Please contribute your logs to http://ppd.fahmm.net
bruce
Posts: 20824
Joined: Thu Nov 29, 2007 10:13 pm
Location: So. Cal.

Re: FAH Application Filter (early beta)

Post by bruce »

The security build into V7 includes a) the ability to use any port, b) password protection, and c) address filtering. If you're doing port-forwarding, changing the port isn't useful, but changing your password offers a degree of protection. Have you looked into setting command-allow? I've never experimented with it other than internal to my LAN, but it probably can be used to set it to block anybody except the IP of the desired remote?

I don't know of any existing tools that distinguish between commands that are allowed or disallowed once a connection is established, though, so your application would add the capability if someone needs that.

You probably already have figured this out, but I suspect that "bumpy" has something to do with how PyON manages the beginning and ending of messages. You probably need to add some logic to block/deblock everything. Also, if the remote gets disconnected, you'll need to send an "exit" to the client and do some cleanup on the remote side. That can get "bumpy" too.
ChristianVirtual
Posts: 1576
Joined: Tue May 28, 2013 12:14 pm
Location: Tokyo

Re: FAH Application Filter (early beta)

Post by ChristianVirtual »

I wouldn't know what IP adress they are using (Apple has its own /8 subnet 17.x.x.x) but if they come from there or not ? Therefore the FAH client settings will not help too much.

In addition I want to control what commands one can use over the filter. E.g. No option display/change etc.
You are right: the disconnection actually cause quite some headache; actually it cause the CPU go to 100%. But hey, that's the fun part of making SW (and learning socket programming on pyhton)
ImageImage
Please contribute your logs to http://ppd.fahmm.net
ChristianVirtual
Posts: 1576
Joined: Tue May 28, 2013 12:14 pm
Location: Tokyo

Re: FAH Application Filter (early beta)

Post by ChristianVirtual »

Updated version: less bumpy

1) corrected disconnection issue within the loop
2) added a line-by-line check on case the received data package contain more then one line (often the case)

maybe really not for every one; just in case you looking for something similar: have fun with it

Code: Select all

#! /usr/bin/env python

#
# Author: Christian Lohmann 2013
#
# a little application-level filter for remote FAH client access
# potentially useful when port forwarding from outside to internal FAH clients
# manage a positive list of commands accepted to be routed from outside to the FAH client
#

#
# when run as console the stdin can be used to terminate the script
# when run as daemon we should add a signal handler; right now I'm to lazy for that
#

import os
import sys
import time
import socket
import select
import string
        
        
   
def FAHFilterAgent(hn):


    HOST, PORT = hn, 36330
    HOST, PORTs = hn, 36334
    
    backlog = 5
    size = 1024*8
    
    print "FAHFilter for:", hn, "port", PORTs
    
    
	#
	# establish proxy server on the give host and port
    sockProxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sockProxy.setblocking(0)
    sockProxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sockProxy.bind((HOST, PORTs))
    sockProxy.listen(backlog)
    
    
    # connect to the FAH client (right now same host; different port)
    sockFAH = None
    #sockFAH = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #sockFAH.connect((HOST, PORT))

    # lists of sockets
    # input = [sys.stdin, sockProxy]
    input = [sockProxy]       # when run as daemon we don't need stdin
    output = []
    clientList = []

    print "Proxy server", sockProxy, sockProxy.getsockname(), 
    
    
    cnt = 1
    running = 1

    while running:
        print "\n",cnt, ": entries input", len(input), \
                                 "output", len(output), \
                                "clients", len(clientList)                          

        # error handler for the select.select 
        try:
            ready_to_read, ready_to_write, in_error = \
                  select.select(input, output, [])
            #print cnt, ": entries in rq", len(ready_to_read), \
            #                       " wq", len(ready_to_write), \
            #                       "error", len(in_error)
        except IOError as e:
            print "select", e
            if e.errno == 9:
                break
        except:
            print "select, unexpected error", sys.exc_info()[0]
            print sys.exc_traceback.tb_lineno
            raise

        cnt = cnt + 1

        try:    
            for s in ready_to_read:
                if s is sockProxy:
                    # print "proxy socket", s.getsockname()
                    # a new client want to connect
                    client, adress = sockProxy.accept()
                    if client is not None:
                        input.append(client)
                        clientList.append(client)
                        print "connection ", client, adress, "established"
                        if sockFAH is None:
                            sockFAH = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                            sockFAH.connect((HOST, PORT))
                            input.append(sockFAH)
                            print "FAH Client", sockFAH, sockFAH.getsockname()

                elif s is sockFAH:
                    # print "FAH socket", s.getsockname()
                    # read a data block
                    data = s.recv(size)
                    if data == '':
                        while len(clientList) > 0:
                            c  = clientList[0]
                            print "close connection, FAH client is gone #1", c
                            c.shutdown(socket.SHUT_RDWR)
                            c.close()
                            input.remove(c)
                            clientList.remove(c)
                    	sockFAH.close()
                    	input.remove(sockFAH)
                    	sockFAH = None
                    else:
                        # copy full data block to all clients
                        for c in clientList:
                            c.send(data)
                elif s is sys.stdin:
                    # print "stdin"
                    # close all client connections
                    junk = sys.stdin.readline()
                    while len(clientList) > 0:
                        c  = clientList[0]
                        print "close connection by user, FAH client is gone", c
                        input.remove(c)
                        clientList.remove(c)
                        c.shutdown(socket.SHUT_RDWR)
                        c.close()
                    running = 0
                    break
                else:
                    # print "regular socket", s.getsockname()
                    # read a data block
                    data = s.recv(size)
                    if data:
                        print "from", s, ":\n", data
                        sepline = data.splitlines(1)
                        for l in sepline:
                           # print the command; without linebreak (is part of the command)
                           print "command:", l,   
                           if l.startswith("auth"):    sockFAH.send(l)
                           elif l.startswith("info"):  sockFAH.send(l)
                           elif l.startswith("exit"):  sockFAH.send(l)
                           elif l.startswith("sleep"): sockFAH.send(l)
                           elif l.startswith("updates"): sockFAH.send(l)
                           elif l.startswith("log-updates"): sockFAH.send(l) 
                           elif l.startswith("pause"): sockFAH.send(l) 
                           elif l.startswith("unpause"): sockFAH.send(l)
                           elif l.startswith("finish"): sockFAH.send(l)
                           else: print "command", l, "ignored"
                    else:
                        # print "end of stream, remove", s
                        s.close()
                        clientList.remove(s)
                        input.remove(s)
        except IOError as e:
            print e
            if e.errno == 9:
                break
        except:
            print "unexpected error", sys.exc_info()[0]
            print sys.exc_traceback.tb_lineno
            raise
        sys.stdout.flush()


    sockProxy.close()
    
    if sockFAH is not None:
        sockFAH.shutdown(socket.SHUT_RDWR)
        sockFAH.close()


FAHFilterAgent("donation")

and a way to call it in background

Code: Select all

python fahfilter.py > /home/cl/fahfilter.log &
ImageImage
Please contribute your logs to http://ppd.fahmm.net
Post Reply