Logo Search packages:      
Sourcecode: jppy version File versions  Download package

proxy_sync.py

#!/usr/bin/env python

import time
import string
import struct
import socket
import random
import pisock
import os
import sys

import jppy

def proxy_sync_application(sd,command):
    port = random.randint(10000,20000)
    cmd = command % port
    log_info("Starting application to sync with: %s" % cmd)
    os.system("%s &" % cmd)
    time.sleep(3)
    proxy_sync(sd,"localhost",port=port)

class palm_socket:
    def __init__(self,fd,network=1):
        self.fd = fd
        self.net = network
        self.socket = socket.fromfd(self.fd,socket.AF_INET, socket.SOCK_STREAM)

    def send(self,data):
        if self.net:
            self.socket.send(data)
        else:
            os.write(self.fd,data)

    def getpeername(self):
        if self.net:
            return self.socket.getpeername()
        else:
            return "Serial Palm"

    def recv(self,max):
        if self.net:
            return self.socket.recv(max)
        else:
            return os.read(self.fd,max)

    def close(self):
        if self.net:
            self.socket.close()
        else:
            os.close(self.fd)

class proxy_sync(jppy.conduit):
    def log(self,str,sd=None):
        jppy.log("proxy_sync: %s" % str,sd=sd)
    
    def __init__(self,hostname,port=None,ro_db=None,nc_db=None):
        if port == None:
            port = 14238
        self.hostname = hostname
        self.port     = port
        self.ro_db    = ro_db or []
        self.nc_db    = nc_db or []


    def log_info(self,str,sd=None):
        if jppy:    self.log(str,sd=sd)
        else:         print str

    def log_debug(self,str,sd=None):
        if jppy:    self.log(str,sd=sd)
        else:         print str    

    def log_fatal(self,str,sd=None):
        if jppy:    self.log(str,sd=sd)
        else:         print "FATAL: %s" % str

    def hex_to_bitstream(self,hx):
        stream = [int(x,16) for x in hx.strip().split()]
        return apply(struct.pack, ['%dB' % len(stream),] + stream)

    def recv_amt(self,s,amount):
        #print "Waiting for %d: " % amount,
        buf = ""
        amount_left = amount
        while amount_left:
            buf = buf + s.recv(amount_left)
            amount_left = amount - len(buf)
    #        print len(buf),
    #    print
    #    print "Read %d bytes: %s" % (len(buf), [hex(ord(x)) for x in buf])
        return buf

    def read_a_dlp(self,s,rawonly=0,debug=0):
        if debug:
            print "-------Reading from %s, %s-----" % s.getpeername()
        pkt = self.recv_amt(s,6)
        if debug:
            print [hex(ord(x)) for x in pkt]
        hdr = struct.unpack(">BBxxH",pkt)
        if debug:
            print hdr
        id   = hdr[1]
        payload = self.recv_amt(s,hdr[2])
        if debug:
            print [hex(ord(x)) for x in payload]
        if rawonly:
            return pkt + payload
        else:
            return (id, ord(payload[0]), payload[1:], pkt + payload)

    def send_a_dlp(self,s,id,type,payload):
        #    print "-------Sending to %s, %s-----" % s.getpeername()    
        hdr = struct.pack(">BBxxH",01,id,len(payload)+1)
        s.send(hdr)
        s.send(struct.pack(">B",type) + payload)

    def dump_pkt(self,raw):
        print [hex(ord(x)) for x in raw]
        print [struct.pack("B",ord(x)) for x in raw]


    
    def sync(self,sd):
        self.log_info("Attempting to initiate proxy sync",sd=sd)

        try:
            ip = socket.gethostbyname(self.hostname)
        except:
            self.log_fatal("Unable to resolve hostname %s" % self.hostname,sd=sd)
            return None

        rs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            rs.connect((ip,self.port))
        except socket.error, e:
            self.log_fatal( "Unable to contact %s:%s, %s" % (ip,self.port,e),sd=sd)
            return None

        ps = palm_socket(sd)
        try:
            relay_announce = "relay between %s and %s" % (ps.getpeername(),rs.getpeername())
        except socket.error, e:
            self.log_fatal("Unable to work: %s" % (e))
            self.log_fatal("Proxy sync *only* works over net: port, USB and serial will not work.")
            return None
        self.log_info("Setting up %s" % relay_announce,sd=sd)


        #wake up!
        self.send_a_dlp(rs,255,0x90,self.hex_to_bitstream(
            "01 00 00 00 00 00 00 00 20 00 00 00 08 01 00 00 00 00 00 00 00"))

        ask_palm_about_ReadSysInfo = 0 # hmm....

        # useful source of these
        # http://cvs.coldsync.org/cgi-bin/viewcvs.cgi/coldsync/include/pconn/dlp_cmd.h
        # http://cvs.pilot-link.org/index.cgi/include/pi-dlp.h

        always_just_ack     = {0x11: 'WriteUserInfo',
                               0x37: 'WriteNetSyncInfo',
                               0x13: 'SetSysDateTime',
                               0x55: 'SomethingThatPalmDesktop4DoesThatWeHaveNoIdeaWhatItMeans1',
                               0x3c: 'SomethingThatPalmDesktop4DoesThatWeHaveNoIdeaWhatItMeans2'}

        always_pass_through = {0x2e: 'OpenConduit',
                               0x16: 'ReadDBList',
                               0x19: 'CloseDB',
                               0x39: 'FindDB',
                               0x38: 'ReadFeature',
                               0x10: 'ReadUserInfo',
                               0x36: 'ReadNetSyncInfo',
                               0x15: 'ReadStorageInfo',
                               0x23: 'ReadResourceByType',
                               0x2b: 'ReadOpenDBInfo',
                               0x1b: 'ReadAppBlock',
                               0x34: 'ReadAppPreference',
                               0x1d: 'ReadSortBlock',
                               0x13: 'GetSysDateTime',
                               0x20: 'ReadRecord',
                               0x31: 'ReadRecordIDList',
                               0x32: 'ReadNextRecInCategory',
                               0x33: 'ReadNextModifiedRecInCategory',
                               0x1f: 'ReadNextModifiedRec',
                               0x2a: 'AddSyncLogEntry',
                               0x18: 'CreateDB',
                               0x1a: 'DeleteDB',
                               0x2d: 'ProcessRPC',
                               0x28: 'CallApplication',
                               0x29: 'ResetSystem'}

        things_that_write =   {0x1c: 'WriteAppBlock',
                               0x35: 'WriteAppPreference',
                               0x21: 'WriteRecord',
                               0x1e: 'WriteSortBlock',
                               0x3a: 'SetDBInfo',
                               0x22: 'DeleteRecord',
                               0x2c: 'MoveCategory',
                               0x24: 'WriteResource',
                               0x25: 'DeleteResource'}

        things_that_clean =   {0x27: 'ResetSyncFlags',
                               0x26: 'CleanUpDatabase',
                               0x31: 'ResetRecordIndex'}


        current_db     = None
        while 1:
            id, type, payload, raw = self.read_a_dlp(rs)
            #print "Got packet type %s" % hex(type)
            if type in always_pass_through.keys():
                self.log_debug( "Passing through read call %s" % always_pass_through[type])
                ps.send(raw)
                rs.send(self.read_a_dlp(ps,1))
            elif type in things_that_write.keys():
                if current_db in self.ro_db:
                    self.log_debug("Not allowing through write call %s, acking myself" % things_that_write[type])
                    self.send_a_dlp(rs,id,type | 0x80,'\0\0\0')                
                elif type == 0x21 and current_db in self.nc_db:
                    self.log_debug("Passing through rewritten call %s" % things_that_write[type])
                    psn = 5+6
                    # make it dirty
                    new_payload = payload[:psn] + \
                                  struct.pack("B",ord(payload[psn])|pisock.dlpRecAttrDirty) + \
                                  payload[psn+1:]
                    self.send_a_dlp(ps,id,type,new_payload)                                    
                    rs.send(self.read_a_dlp(ps,1))
                elif type == 0x22 and current_db in self.nc_db:
                    self.log_debug("Passing through (not yet) rewritten call  %s" % things_that_write[type])
                    # Maybe this is not necessary though, as jpilot appears to realise
                    # a record has been deleted without it knowing, and slowsyncs that one database
                    # hence picking up the change.
    ##                 # this should ack the DeleteRecord, and perform a Read/Write on the palm
    ##                 # instead (setting the dirty bit, and the delete bit)
    ##                 #dump_pkt(payload)
    ##                 handle = ord(payload[3])
    ##                 flags = ord(payload[4])
    ##                 rec_part = payload[5:6+4]
    ##                 record_id = struct.unpack("<H",rec_part)
    ##                 if (flags & 0x80):
    ##                     #print "They want to delete ALL!"
    ##                     pass
    ##                 else:
    ##                     pass
    ##                     #print "They want to delete %d" % record_id
    ##                     #print repr(pisock.dlp_ReadRecordById(sd,handle,record_id))
                    ps.send(raw)
                    reply = self.read_a_dlp(ps,1)
                    #dump_pkt(reply)
                    rs.send(reply)                
                else:
                    self.log_debug("Passing through generic write call %s" % things_that_write[type])
                    ps.send(raw)
                    rs.send(self.read_a_dlp(ps,1))
            elif type in things_that_clean.keys():
                if current_db in self.ro_db or current_db in self.nc_db:
                    self.log_debug("Not allowing through reset call %s, acking myself" % things_that_clean[type])
                    self.send_a_dlp(rs,id,type | 0x80,'\0\0\0')                
                else:
                    self.log_debug("Passing through reset call %s" % things_that_clean[type])
                    ps.send(raw)
                    rs.send(self.read_a_dlp(ps,1))            
            elif type == 0x17:
                current_db = payload[5:-1]
                if current_db in self.ro_db:
                    self.log("HotSync client request open of %s, allowing READ ONLY" % current_db)
                elif current_db in self.nc_db:
                    self.log("HotSync client request open of %s, allowing RW but not reset" % current_db)
                else:
                    self.log("HotSync client request open of %s, allowing full access" % current_db)
                ps.send(raw)
                rs.send(self.read_a_dlp(ps,1))
            elif type == 0x12:
                if ask_palm_about_ReadSysInfo:
                    self.log_debug("Asking Palm for answer to ReadSysInfo")
                    self.send_a_dlp(ps,id,type,payload)
                    pid, ptype, ppayload, praw = self.read_a_dlp(ps)
                    self.log_debug("payload for %d from palm is %d long" % (ord(payload[1])
                                                                            ,len(ppayload)))
                    if ord(payload[1]) == 0x00:
                        open("/tmp/00.bin","w").write(ppayload)
                    elif ord(payload[1]) == 0x20:
                        open("/tmp/20.bin","w").write(ppayload) 
                    self.send_a_dlp(rs,pid,ptype,ppayload)
                else:
                    self.log_debug("Sending a boxed answer to ReadSysInfo")
                    if ord(payload[1]) == 0x00:
                        self.log_debug( "Sending boxed response 0x00")
                        # HandEra 330
                        self.send_a_dlp(rs,id,type | 0x80,self.hex_to_bitstream("""
                        01  00 00 00 00 00 00 00 20
                        00 00 00 24 ff ff ff ff  00 3c 00 3c 40 00 00 00
                        01 00 00 00 ac 14 01 01  3d e4 00 00 00 00 00 00
                        00 00 00 00 00 00 00 00  """))
##                         # Tungsten T
##                         self.send_a_dlp(rs,id,type | 0x80,self.hex_to_bitstream("""
##                         00 02 20 00 05 0e 00 00 00 00 00 01 00 00 00 04
##                         00 10 21 00 00 0c 00 01 00 02 00 03 00 00 ff 00
##                         00 e1 """))                        
                    elif  ord(payload[1]) == 0x20:
                        self.log_debug( "Sending boxed response 0x20")
                        self.send_a_dlp(rs,id,type | 0x80,self.hex_to_bitstream("""
                        02  00 00 20 0e 03 53 30 00
                        00 01 00 00 00 04 00 03  00 00 21 0c 00 01 00 02
                        00 03 00 00 00 00 ff e1 """))
            elif type in always_just_ack.keys():
                self.log_debug( "Acking %s" % always_just_ack[type])
                self.send_a_dlp(rs,id,type | 0x80,'\0\0\0')
            elif type == 0x2f:
                self.log_debug( 'EndOfSync')
                break
            else:
                msg = """I don't know what to do with DLP command %s,
                please report to nick-jpilot@nickpiper.co.uk""" % hex(type)
                print "*********", msg
                self.log_fatal(msg)
                break
        rs.close()
        ps.close()
        self.log("Proxy sync over for %s, closing relay." % relay_announce,sd=sd)
        return 1

class fakeJpilotServices:
    JP_LOG_DEBUG = 1  
    JP_LOG_INFO =  2  
    JP_LOG_WARN =  4  
    JP_LOG_FATAL = 8  
    JP_LOG_STDOUT =256
    JP_LOG_FILE =  512
    JP_LOG_GUI =   102

    def log(self, str, type, sd=None):
        print str

if __name__ == "__main__":
    import ConfigParser
    
    print "Proxy Sync"

    jppy = fakeJpilotervices()


    config = ConfigParser.ConfigParser()
    configfilename = os.path.expanduser('~/.proxysyncrc')
    if not os.path.exists(configfilename):
        print "Creating defaults file %s" % configfilename
        f = open(configfilename,"w")
        f.write("""\
[palm]
device = net:

#[redpill.haus]
#read_only_databases = DatebookDB,AddressDB,MemoDB,Saved Preferences
#no_reset_flags_databases = ToDoDB
#
#[bluepill.haus]
#read_only_databases = DatebookDB,AddressDB,MemoDB,Saved Preferences
#no_reset_flags_databases = ToDoDB
""")
        f.close()
    config.read(configfilename)

    if not config.has_section("palm"):
        print "Configuration file does not contain a palm section!"
        sys.exit(10)

    device = config.get("palm","device")

    hosts = config.sections()
    hosts.remove("palm")

    conduit_list = []


    for host in hosts:
        print "Configured to sync with host %s" % host
        if config.has_option(host,"port"):
            port = config.getint(host,"port")
        else:
            port = None
            
        if config.has_option(host,"read_only_databases"):
            read_only_databases = config.get(host,"read_only_databases").split(",")
        else:
            read_only_databases = []
            
        if config.has_option(host,"no_reset_flags_databases"):
            no_reset_flags_databases = config.get(host,"no_reset_flags_databases").split(",")
        else:
            no_reset_flags_databases = []

        conduit_list.append(proxy_sync(host,
                                       port=port,
                                       ro_db=read_only_databases,
                                       nc_db=no_reset_flags_databases))    

    for conduit in conduit_list:
        conduit.pre_sync()

    sd = pisock.pilot_connect(device)

    if sd == -1:
        print "Connection with Palm Pilot failed."
        sys.exit(10)
        
    for conduit in conduit_list:
        conduit.sync(sd)

    pisock.pi_close(sd)

    for conduit in conduit_list:
        conduit.post_sync()

Generated by  Doxygen 1.6.0   Back to index