PyXR

c:\projects\bitpim\src \ bitfling \ bitfling.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2004 Roger Binns <rogerb@rogerbinns.com>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: bitfling.py 4228 2007-05-10 02:59:06Z djpham $
0009 
0010 """This is the BitFling client
0011 
0012 It acts as an XML-RPC server over SSH.  The UI consists of a tray icon (Windows)
0013 or a small icon (Linux, Mac) that you can click on to get the dialog."""
0014 
0015 # Standard Modules
0016 import sys
0017 import cStringIO
0018 import os
0019 import random
0020 import sha
0021 import thread
0022 import fnmatch
0023 import socket
0024 import threading
0025 import time
0026 from xmlrpclib import Fault, Binary
0027 
0028 # wx stuff
0029 import wx
0030 import wx.html
0031 import wx.lib.newevent
0032 import wx.lib.masked.textctrl
0033 import wx.lib.mixins.listctrl
0034 
0035 # others
0036 import paramiko
0037 
0038 # My stuff
0039 try:
0040     import native.usb as usb
0041 except ImportError:
0042     usb=None
0043 import usbscan
0044 import comscan
0045 import commport
0046     
0047 import guihelper
0048 import xmlrpcstuff
0049 import version
0050 
0051 ID_CONFIG=wx.NewId()
0052 ID_LOG=wx.NewId()
0053 ID_RESCAN=wx.NewId()
0054 ID_EXIT=wx.NewId()
0055 
0056 
0057 XmlServerEvent, EVT_XMLSERVER = wx.lib.newevent.NewEvent()
0058 
0059 guithreadid=thread.get_ident()
0060 
0061 # in theory this should also work for GTK, but in practise it doesn't
0062 if guihelper.IsMSWindows(): parentclass=wx.TaskBarIcon
0063 else: parentclass=wx.Frame
0064 
0065 class MyTaskBarIcon(parentclass):
0066 
0067     def __init__(self, mw, menu):
0068         self.mw=mw
0069         self.menu=menu
0070         iconfile="bitfling.png"
0071         if parentclass is wx.Frame:
0072             parentclass.__init__(self, None, -1, "BitFling Window", size=(32,32), style=wx.FRAME_TOOL_WINDOW)
0073             self.genericinit(iconfile)
0074         else:
0075             parentclass.__init__(self)
0076             self.windowsinit(iconfile)
0077             
0078         self.leftdownpos=0,0
0079         #wx.EVT_MENU(menu, ID_CONFIG, self.OnConfig)
0080         #wx.EVT_MENU(menu, ID_LOG, self.OnLog)
0081         wx.EVT_MENU(menu, ID_EXIT, self.OnExit)
0082         #wx.EVT_MENU(menu, ID_RESCAN, self.OnRescan)
0083 
0084     def GoAway(self):
0085         if parentclass is wx.Frame:
0086             self.Close(True)
0087         else:
0088             self.RemoveIcon()
0089         self.Destroy()
0090 
0091     def OnConfig(self,_):
0092         print "I would do config at this point"
0093 
0094     def OnLog(self,_):
0095         print "I would do log at this point"
0096 
0097     def OnHelp(self,_):
0098         print "I would do help at this point"
0099 
0100     def OnRescan(self, _):
0101         print "I would do rescan at this point"
0102 
0103     def OnExit(self,_):
0104         self.mw.Close(True)
0105 
0106     def OnRButtonUp(self, evt=None):
0107         if parentclass is wx.Frame:
0108             self.PopupMenu(self.menu, evt.GetPosition())
0109         else:
0110             self.PopupMenu(self.menu)
0111 
0112     def OnLButtonUp(self, evt=None):
0113         if self.leftdownpos is None:
0114             return # cleared out by motion stuff
0115         if self.mw.IsShown():
0116             self.mw.Show(False)
0117         else:
0118             self.mw.Show(True)
0119             self.mw.Raise()
0120 
0121     def OnLeftDown(self, evt):
0122         if guihelper.IsMSWindows():
0123             self.leftdownpos=0
0124         else:
0125             self.leftdownpos=evt.GetPosition()
0126         self.motionorigin=self.leftdownpos
0127 
0128     def OnMouseMotion(self, evt):
0129         if not evt.Dragging():
0130             return
0131         if evt.RightIsDown() or evt.MiddleIsDown():
0132             return
0133         if not evt.LeftIsDown():
0134             return
0135         self.leftdownpos=None
0136         x,y=evt.GetPosition()
0137         xdelta=x-self.motionorigin[0]
0138         ydelta=y-self.motionorigin[1]
0139         screenx,screeny=self.GetPositionTuple()
0140         self.MoveXY(screenx+xdelta, screeny+ydelta)
0141 
0142     def windowsinit(self, iconfile):
0143         bitmap=wx.Bitmap(guihelper.getresourcefile(iconfile), wx.BITMAP_TYPE_PNG)
0144         icon=wx.EmptyIcon()
0145         icon.CopyFromBitmap(bitmap)
0146         self.SetIcon(icon, "BitFling")
0147         wx.EVT_TASKBAR_RIGHT_UP(self, self.OnRButtonUp)
0148         wx.EVT_TASKBAR_LEFT_UP(self, self.OnLButtonUp)
0149         #wx.EVT_TASKBAR_MOVE(self, self.OnMouseMotion)
0150         wx.EVT_TASKBAR_LEFT_DOWN(self, self.OnLeftDown)
0151         
0152     def genericinit(self, iconfile):
0153         self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
0154         bitmap=wx.Bitmap(guihelper.getresourcefile(iconfile), wx.BITMAP_TYPE_PNG)
0155         bit=wx.StaticBitmap(self, -1, bitmap)
0156         self.Show(True)
0157         wx.EVT_RIGHT_UP(bit, self.OnRButtonUp)
0158         wx.EVT_LEFT_UP(bit, self.OnLButtonUp)
0159         wx.EVT_MOTION(bit, self.OnMouseMotion)
0160         wx.EVT_LEFT_DOWN(bit, self.OnLeftDown)
0161         self.bit=bit
0162 
0163 class ConfigPanel(wx.Panel, wx.lib.mixins.listctrl.ColumnSorterMixin):
0164 
0165     def __init__(self, mw, parent, id=-1):
0166         wx.Panel.__init__(self, parent, id)
0167         self.mw=mw
0168         vbs=wx.BoxSizer(wx.VERTICAL)
0169 
0170         # General
0171         bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "General"), wx.HORIZONTAL)
0172         bs.Add(wx.StaticText(self, -1, "Fingerprint"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0173         self.fingerprint=wx.TextCtrl(self, -1, "a", style=wx.TE_READONLY,  size=(300,-1))
0174         bs.Add(self.fingerprint, 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0175         bs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer
0176         bs.Add(wx.StaticText(self, -1, "Port"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0177         self.porttext=wx.StaticText(self, -1, "<No Port>")
0178         bs.Add(self.porttext, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0179         vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5)
0180 
0181         # authorization
0182         bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Authorization"), wx.VERTICAL)
0183         hbs=wx.BoxSizer(wx.HORIZONTAL)
0184         butadd=wx.Button(self, wx.NewId(), "Add ...")
0185         hbs.Add(butadd, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0186         hbs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer
0187         self.butedit=wx.Button(self, wx.NewId(), "Edit ...")
0188         self.butedit.Enable(False)
0189         hbs.Add(self.butedit, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0190         hbs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer
0191         self.butdelete=wx.Button(self, wx.NewId(), "Delete")
0192         self.butdelete.Enable(False)
0193         hbs.Add(self.butdelete, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5)
0194         bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5)
0195 
0196         wx.EVT_BUTTON(self, butadd.GetId(), self.OnAddAuth)
0197         wx.EVT_BUTTON(self, self.butedit.GetId(), self.OnEditAuth)
0198         wx.EVT_BUTTON(self, self.butdelete.GetId(), self.OnDeleteAuth)
0199 
0200         # and the authorization listview
0201         self.authlist=wx.ListCtrl(self, wx.NewId(), style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
0202         self.authlist.InsertColumn(0, "User")
0203         self.authlist.InsertColumn(1, "Allowed Addresses")
0204         self.authlist.InsertColumn(2, "Expires")
0205         self.authlist.SetColumnWidth(0, 300)
0206         self.authlist.SetColumnWidth(1, 300)
0207         self.authlist.SetColumnWidth(2, 100)
0208         bs.Add(self.authlist, 1, wx.EXPAND|wx.ALL, 5)
0209         
0210         vbs.Add(bs, 1, wx.EXPAND|wx.ALL, 5)
0211         self.itemDataMap={}
0212         wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self,3)
0213 
0214         wx.EVT_LIST_ITEM_ACTIVATED(self.authlist, self.authlist.GetId(), self.OnEditAuth)
0215         wx.EVT_LIST_ITEM_SELECTED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled)
0216         wx.EVT_LIST_ITEM_DESELECTED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled)
0217         wx.EVT_LIST_ITEM_FOCUSED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled)
0218 
0219         # devices
0220         bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Devices"), wx.VERTICAL)
0221         buttoggle=wx.Button(self, wx.NewId(), "Toggle Allowed")
0222         bs.Add(buttoggle, 0, wx.ALL, 5)
0223         self.devicelist=wx.ListCtrl(self, wx.NewId(), style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
0224         self.devicelist.InsertColumn(0, "Allowed")
0225         self.devicelist.InsertColumn(1, "Name")
0226         self.devicelist.InsertColumn(2, "Available")
0227         self.devicelist.InsertColumn(3, "Description")
0228         self.devicelist.SetColumnWidth(0, 100)
0229         self.devicelist.SetColumnWidth(1, 300)
0230         self.devicelist.SetColumnWidth(2, 100)
0231         self.devicelist.SetColumnWidth(3, 300)
0232         bs.Add(self.devicelist, 1, wx.EXPAND|wx.ALL, 5)
0233 
0234         vbs.Add(bs, 1, wx.EXPAND|wx.ALL, 5)
0235         
0236         self.setupauthorization()
0237         self.SortListItems()
0238         
0239         self.SetSizer(vbs)
0240         self.SetAutoLayout(True)
0241 
0242     def _updateauthitemmap(self, itemnum):
0243         pos=-1
0244         if itemnum in self.itemDataMap:
0245             # find item by looking for ItemData, and set pos
0246             # to corresponding pos in list
0247             for i in range(self.authlist.GetItemCount()):
0248                 if self.authlist.GetItemData(i)==itemnum:
0249                     pos=i
0250                     break
0251             assert pos!=-1
0252         # clear the is connection allowed cache
0253         self.mw.icacache={}
0254         v=self.mw.authinfo[itemnum]
0255         username=v[0]
0256         expires=v[2]
0257         addresses=" ".join(v[3])
0258         if pos<0:
0259             pos=self.authlist.GetItemCount()
0260             self.authlist.InsertStringItem(pos, username)
0261         else:
0262             self.authlist.SetStringItem(pos, 0, username)
0263         self.authlist.SetStringItem(pos, 2, `expires`)
0264         self.authlist.SetStringItem(pos, 1, addresses)
0265         self.authlist.SetItemData(pos, itemnum)
0266         self.itemDataMap[itemnum]=(username, addresses, expires)
0267 
0268     def GetListCtrl(self):
0269         "Used by the ColumnSorter mixin"
0270         return self.authlist
0271 
0272     def setupauthorization(self):
0273         dict={}
0274         items=[]
0275         for i in range(1000):
0276             if self.mw.config.HasEntry("user-"+`i`):
0277                 username,password,expires,addresses=self.mw.config.Read("user-"+`i`).split(":")
0278                 expires=int(expires)
0279                 addresses=addresses.split()
0280                 dict[i]=username,password,expires,addresses
0281                 items.append(i)
0282         self.mw.authinfo=dict
0283         for i in items:
0284             self._updateauthitemmap(i)
0285 
0286 
0287     def OnAddAuth(self,_):
0288         dlg=AuthItemDialog(self, "Add Entry")
0289         if dlg.ShowModal()==wx.ID_OK:
0290             username,password,expires,addresses=dlg.GetValue()
0291             for i in range(1000):
0292                 if i not in self.mw.authinfo:
0293                     self.mw.config.Write("user-"+`i`, "%s:%s:%d:%s" % (username, password, expires, " ".join(addresses)))
0294                     self.mw.config.Flush()
0295                     self.mw.authinfo[i]=username,password,expires,addresses
0296                     self._updateauthitemmap(i)
0297                     self.SortListItems()
0298                     break
0299         dlg.Destroy()
0300 
0301     def OnDeleteAuth(self, _):
0302         item=self._getselectedlistitem(self.authlist)
0303         key=self.authlist.GetItemData(item)
0304         del self.mw.authinfo[key]
0305         self.authlist.DeleteItem(item)
0306         self.mw.config.DeleteEntry("user-"+`key`)
0307         self.mw.config.Flush()
0308 
0309     def _getselectedlistitem(self, listctrl):
0310         "Finds the selected item in a listctrl since the wx methods don't actually work"
0311         i=-1
0312         while True:
0313             nexti=listctrl.GetNextItem(i, state=wx.LIST_STATE_SELECTED)
0314             if nexti<0:
0315                 break
0316             i=nexti
0317             return i
0318         return None
0319 
0320     def OnAuthListItemFondled(self, _):
0321         "Called whenever list items are selected, unselectected or similar fondling"
0322         selitem=self._getselectedlistitem(self.authlist)
0323         self.butedit.Enable(selitem is not None)
0324         self.butdelete.Enable(selitem is not None)
0325 
0326     def OnEditAuth(self, _):
0327         "Called to edit the currently selected entry"
0328         item=self._getselectedlistitem(self.authlist)
0329         key=self.authlist.GetItemData(item)
0330         username,password,expires,addresses=self.mw.authinfo[key]
0331         dlg=AuthItemDialog(self, "Edit Entry", username=username, password=password, expires=expires, addresses=addresses)
0332         if dlg.ShowModal()==wx.ID_OK:
0333             username,password,expires,addresses=dlg.GetValue()
0334             self.mw.authinfo[key]=username,password,expires,addresses
0335             self._updateauthitemmap(key)
0336         dlg.Destroy()
0337 
0338 class AuthItemDialog(wx.Dialog):
0339 
0340     _password_sentinel="\x01\x02\x03\x04\x05\x06\x07\x08" # magic value used to detect if user has changed the field
0341 
0342     def __init__(self, parent, title, username="New User", password="", expires=0, addresses=[]):
0343         wx.Dialog.__init__(self, parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
0344 
0345         p=self
0346         gs=wx.FlexGridSizer(4, 2, 5, 5)
0347         gs.AddGrowableCol(1)
0348         gs.AddGrowableRow(3)
0349 
0350         gs.Add(wx.StaticText(p, -1, "Username/Email"))
0351         self.username=wx.TextCtrl(self, -1, username)
0352         gs.Add(self.username,0, wx.EXPAND)
0353         gs.Add(wx.StaticText(p, -1, "Password"))
0354         self.password=wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD)
0355         self.origpassword=password
0356         if len(password): self.password.SetValue(self._password_sentinel)
0357         gs.Add(self.password, 0, wx.EXPAND)
0358         gs.Add(wx.StaticText(p, -1, "Expires"))
0359         self.expires=wx.lib.masked.textctrl.TextCtrl(p, -1, "", autoformat="EUDATETIMEYYYYMMDD.HHMM")
0360         gs.Add(self.expires)
0361         gs.Add(wx.StaticText(p, -1, "Allowed Addresses"))
0362         self.addresses=wx.TextCtrl(self, -1, "\n".join(addresses), style=wx.TE_MULTILINE)
0363         gs.Add(self.addresses, 1, wx.EXPAND)
0364 
0365 
0366         vbs=wx.BoxSizer(wx.VERTICAL)
0367         vbs.Add(gs,1, wx.EXPAND|wx.ALL, 5)
0368         vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
0369         vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5)
0370 
0371         self.SetSizer(vbs)
0372         vbs.Fit(self)
0373 
0374 
0375     def GenPassword(self, string):
0376         # random salt
0377         salt="".join([chr(random.randint(0,127)) for x in range(8)])
0378         saltstr="".join(["%02x" % (ord(x),) for x in salt])
0379         # we use a sha of the salt followed by the string
0380         val=sha.new(salt+string)
0381         # return generated password as $ seperated hex strings
0382         return "$".join([saltstr, val.hexdigest()])
0383         
0384 
0385     def GetValue(self):
0386         # ::TODO:: ensure no colons in username or addresses
0387         # figure out password
0388         if self.password.GetValue()!=self._password_sentinel:
0389             password=self.GenPassword(self.password.GetValue())
0390         else:
0391             password=self.origpassword
0392         return [self.username.GetValue(), password, 0, self.addresses.GetValue().split()]
0393 
0394 class MainWindow(wx.Frame):
0395 
0396     def __init__(self, parent, id, title):
0397         self.taskwin=None # set later
0398         wx.Frame.__init__(self, parent, id, title, style=wx.RESIZE_BORDER|wx.SYSTEM_MENU|wx.CAPTION)
0399 
0400         sys.excepthook=self.excepthook
0401 
0402         self.authinfo={}  # updated by config panel
0403         self.icacache={}  # used by IsConnectionAllowed
0404 
0405         # Establish config stuff
0406         cfgstr='bitfling'
0407         if guihelper.IsMSWindows():
0408             cfgstr="BitFling"  # nicely capitalized on Windows
0409         self.config=wx.Config(cfgstr, style=wx.CONFIG_USE_LOCAL_FILE)
0410         # self.config.SetRecordDefaults(True)
0411         # for help to save prefs
0412         wx.GetApp().SetAppName(cfgstr)
0413         wx.GetApp().SetVendorName(cfgstr)
0414 
0415         self.setuphelp()
0416         
0417         wx.EVT_CLOSE(self, self.CloseRequested)
0418 
0419         panel=wx.Panel(self, -1)
0420         
0421         bs=wx.BoxSizer(wx.VERTICAL)
0422 
0423         self.nb=wx.Notebook(panel, -1)
0424         bs.Add(self.nb, 1, wx.EXPAND|wx.ALL, 5)
0425         bs.Add(wx.StaticLine(panel, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5)
0426 
0427         gs=wx.GridSizer(1,4, 5, 5)
0428 
0429         for name in ("Rescan", "Hide", "Help", "Exit" ):
0430             but=wx.Button(panel, wx.NewId(), name)
0431             setattr(self, name.lower(), but)
0432             gs.Add(but)
0433         bs.Add(gs,0,wx.ALIGN_CENTRE|wx.ALL, 5)
0434 
0435         panel.SetSizer(bs)
0436         panel.SetAutoLayout(True)
0437 
0438         # the notebook pages
0439         self.configpanel=ConfigPanel(self, self.nb)
0440         self.nb.AddPage(self.configpanel, "Configuration")
0441         self.lw=guihelper.LogWindow(self.nb)
0442         self.nb.AddPage(self.lw, "Log")
0443 
0444         wx.EVT_BUTTON(self, self.hide.GetId(), self.OnHideButton)
0445         wx.EVT_BUTTON(self, self.help.GetId(), self.OnHelpButton)
0446         wx.EVT_BUTTON(self, self.exit.GetId(), self.OnExitButton)
0447 
0448         EVT_XMLSERVER(self, self.OnXmlServerEvent)
0449 
0450         self.xmlrpcserver=None
0451         wx.CallAfter(self.StartIfICan)
0452 
0453     def setuphelp(self):
0454         """Does all the nonsense to get help working"""
0455         import wx.html
0456         # Add the Zip filesystem
0457         wx.FileSystem_AddHandler(wx.ZipFSHandler())
0458         # Get the help working
0459         self.helpcontroller=wx.html.HtmlHelpController()
0460         self.helpcontroller.AddBook(guihelper.getresourcefile("bitpim.htb"))
0461         self.helpcontroller.UseConfig(self.config, "help")
0462 
0463         # now context help
0464         # (currently borken)
0465         # self.helpprovider=wx.HelpControllerHelpProvider(self.helpcontroller)
0466         # wx.HelpProvider_Set(provider)
0467         
0468     def IsConnectionAllowed(self, peeraddr, username=None, password=None):
0469         """Verifies if a connection is allowed
0470 
0471         If username and password are supplied (as should be the case if calling this method
0472         before executing some code) then they are checked as being from a valid address
0473         as well.
0474 
0475         If username and password are not supplied then this method checks if any
0476         of the authentication rules allow a connection from the peeraddr.  This form
0477         is used immediately after calling accept() on a socket, but before doing
0478         anything else."""
0479         # Note that this method is not called in the main thread, and any variables could be
0480         # updated underneath us.  Be threadsafe and only use atomic methods on shared data!
0481         
0482         v=(peeraddr[0], username, password)
0483         if username is not None and password is None:
0484             self.Log("%s: No password supplied for user %s" % (peeraddr, `username`))
0485             assert False, "No password supplied"
0486             return False # not allowed to have None as password
0487         print "ica of "+`v`
0488         val=self.icacache.get(v, None)
0489         if val is not None:
0490             allowed, expires = val
0491             if allowed:
0492                 if self._has_expired(expires):
0493                     msg="Connection from %s no longer allowed due to expiry" % (peeraddr[0],)
0494                     if username is not None:
0495                         msg+=".  Username "+`username`
0496                     self.Log(msg)
0497                     return False
0498                 return True
0499             return False
0500 
0501         ret_allowed=False
0502         ret_expiry=0  # an expiry of zero is infinite, so this will be overridden by any specific expiries
0503         for uname, pwd, expires, addresses in self.authinfo.values():  # values() is threadsafe
0504             # do any of the addresses match?
0505             if not self._does_address_match(peeraddr[0], addresses):
0506                 continue
0507             # check username/password if username supplied
0508             if username is not None:
0509                 if  username!=uname:
0510                     continue
0511                 # check password
0512                 if not self._verify_password(password, pwd):
0513                     self.Log("Wrong password supplied for user %s from %s" % (`username`, peeraddr[0]))
0514                     continue
0515             # remember expiry value (largest value)
0516             ret_expiry=max(ret_expiry, expires)
0517             ret_allowed=True
0518 
0519         if not ret_allowed:
0520             if username is not None:
0521                 self.Log("No valid credentials for user %s from %s" % (username, peeraddr[0]))
0522             else:
0523                 self.Log("No defined users for address "+`peeraddr`)
0524             
0525         # recurse so that correct log messages about expiry get generated
0526         self.icacache[v]=ret_allowed, ret_expiry
0527         return self.IsConnectionAllowed(peeraddr, username, password)
0528 
0529     def _verify_password(self, password, pwd):
0530         """Returns True if password matches pwd
0531         @param password: password supplied by user
0532         @param pwd:  password as we store it (salt $ hash)"""
0533         salt,hash=pwd.split("$", 1)
0534         # turn salt back into binary
0535         x=""
0536         for i in range(0, len(salt), 2):
0537             x+=chr(int(salt[i:i+2], 16))
0538         salt=x
0539         # we do this silly string stuff to avoid issues with encoding - it only works for iso 8859-1
0540         str=[]
0541         str.extend([ord(x) for x in salt])
0542         str.extend([ord(x) for x in password])
0543         val=sha.new("".join([chr(x) for x in str]))
0544         print password, pwd, val.hexdigest(), val.hexdigest()==hash
0545         return val.hexdigest()==hash
0546             
0547     def _does_address_match(self, peeraddr, addresses):
0548         """Returns if the peeraddr matches any of the supplied addresses"""
0549         # note this function can be called from any thread.  do not access any data
0550         for addr in addresses:
0551             # the easy case
0552             if peeraddr==addr: return True
0553             # is addr a glob pattern?
0554             if '*' in addr or '?' in addr or '[' in addr:
0555                 if fnmatch.fnmatch(peeraddr, addr):
0556                     return True
0557             # ::TODO::  addr/bits style checking - see Python cookbook 10.5 for code
0558             # ok, do dns lookup on it
0559             ips=[]
0560             try:
0561                 ips=socket.getaddrinfo(addr, None)
0562             except:
0563                 pass
0564             for _, _, _, _, ip in ips:
0565                 if peeraddr==ip[0]:
0566                     return True
0567         return False
0568 
0569     def _has_expired(self, expires):
0570         if expires==0:
0571             return False
0572         if time.time()>expires:
0573             return True
0574         return False
0575                             
0576     def CloseRequested(self, evt):
0577         if evt.CanVeto():
0578             self.Show(False)
0579             evt.Veto()
0580             return
0581         self.taskwin.GoAway()
0582         evt.Skip()
0583         sys.excepthook=sys.__excepthook__
0584 
0585     def OnXmlServerEvent(self, msg):
0586         if msg.cmd=="log":
0587             self.Log(msg.data)
0588         elif msg.cmd=="logexception":
0589             self.LogException(msg.data)
0590         else:
0591             assert False, "bad message "+`msg`
0592             pass
0593             
0594 
0595     def OnExitButton(self, _):
0596         self.Close(True)
0597 
0598     def OnHideButton(self, _):
0599         self.Show(False)
0600 
0601     def OnHelpButton(self, _):
0602         import helpids
0603         self.helpcontroller.Display(helpids.ID_BITFLING)
0604 
0605     def Log(self, text):
0606         if thread.get_ident()!=guithreadid:
0607             wx.PostEvent(self, XmlServerEvent(cmd="log", data=text))
0608         else:
0609             self.lw.log(text)
0610 
0611     def LogException(self, exc):
0612         if thread.get_ident()!=guithreadid:
0613             # need to send it to guithread
0614             wx.PostEvent(self, XmlServerEvent(cmd="log", data="Exception in thread "+threading.currentThread().getName()))
0615             wx.PostEvent(self, XmlServerEvent(cmd="logexception", data=exc))
0616         else:
0617             self.lw.logexception(exc)
0618 
0619     def excepthook(self, *args):
0620         """Replacement exception handler that sends stuff to our log window"""
0621         self.LogException(args)
0622 
0623     def GetCertificateFilename(self):
0624         """Return certificate filename
0625 
0626         By default $HOME (or My Documents) / .bitfling.key
0627         but can be overridden with "certificatefile" config key"""
0628         
0629         if guihelper.IsMSWindows(): # we want subdir of my documents on windows
0630             # nice and painful
0631             from win32com.shell import shell, shellcon
0632             path=shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
0633             path=os.path.join(path, ".bitfling.key")
0634         else:
0635             path=os.path.expanduser("~/.bitfling.key")
0636         return self.config.Read("certificatefile", path)
0637         
0638 
0639     def StartIfICan(self):
0640         certfile=self.GetCertificateFilename()
0641         if not os.path.isfile(certfile):
0642             wx.BeginBusyCursor(wx.StockCursor(wx.CURSOR_ARROWWAIT))
0643             bi=wx.BusyInfo("Creating BitFling host certificate and keys")
0644             try:
0645                 generate_certificate(certfile)
0646                 if not os.path.isfile(certfile):
0647                     # ::TODO:: report that we failed
0648                     certfile=None
0649             finally:
0650                 del bi
0651                 wx.EndBusyCursor()
0652         port=self.config.ReadInt("port", 12652)
0653         if port<1 or port>65535: port=None
0654         host=self.config.Read("bindaddress", "")
0655         if certfile is None or port is None:
0656             return
0657         self.Log("Starting on port "+`port`)
0658         key=paramiko.DSSKey.from_private_key_file(certfile)
0659         fp=paramiko.util.hexify(key.get_fingerprint())
0660         self.configpanel.fingerprint.SetValue(fp)
0661         self.configpanel.porttext.SetLabel(`port`)
0662         self.configpanel.GetSizer().Layout()
0663         self.xmlrpcserver=BitFlingService(self, host, port, key)
0664         self.xmlrpcserver.setDaemon(True)
0665         self.xmlrpcserver.start()
0666         
0667 
0668 def generate_certificate(outfile):
0669     key=paramiko.DSSKey.generate()
0670     key.write_private_key_file(outfile, None)
0671 
0672 
0673 
0674 class XMLRPCService(xmlrpcstuff.Server):
0675 
0676     def __init__(self, mainwin, host, port, servercert):
0677         self.mainwin=mainwin
0678         xmlrpcstuff.Server.__init__(self, host, port, servercert)
0679 
0680     def OnLog(self, msg):
0681         wx.PostEvent(self.mainwin, XmlServerEvent(cmd="log", data=msg))
0682 
0683     def OnLogException(self, exc):
0684         wx.PostEvent(self.mainwin, XmlServerEvent(cmd="logexception", data=exc))
0685 
0686     def OnNewAccept(self, clientaddr):
0687         return self.mainwin.IsConnectionAllowed(clientaddr)
0688 
0689     def OnNewUser(self, clientaddr, username, password):
0690         return self.mainwin.IsConnectionAllowed(clientaddr, username, password)
0691 
0692     def OnMethodDispatch(self, method, params, username, clientaddr):
0693         method="exp_"+method
0694         if not hasattr(self, method):
0695             raise Fault(3, "No such method")
0696 
0697         context={ 'username': username, 'clientaddr': clientaddr }
0698         
0699         return getattr(self, method)(*params, **{'context': context})
0700 
0701 
0702 class BitFlingService(XMLRPCService):
0703 
0704     def __init__(self, mainwin, host, port, servercert):
0705         XMLRPCService.__init__(self, mainwin, host, port, servercert)
0706         self.handles={}
0707 
0708     def stashhandle(self, context, comm):
0709         for i in range(10000):
0710             if i not in self.handles:
0711                 self.handles[i]=[context, comm]
0712                 return i
0713 
0714     def gethandle(self, context, num):
0715         # ::TODO:: check context has access
0716         return self.handles[num][1]
0717 
0718     # Only methods begining with exp are exported to XML-RPC
0719     
0720     def exp_scan(self, context):
0721         if usb is None:
0722             return comscan.comscan()
0723         else:
0724             return usbscan.usbscan()+comscan.comscan()
0725     
0726     def exp_getversion(self, context):
0727         return version.description
0728 
0729     def exp_deviceopen(self, port, baud, timeout, hardwareflow, softwareflow, context):
0730         # ::TODO:: None is pointer to log object
0731         return self.stashhandle(context, commport.CommConnection(None, port, baud, timeout,
0732                                                                  hardwareflow, softwareflow))
0733 
0734     def exp_deviceclose(self, handle, context):
0735         comm=self.gethandle(context, handle)
0736         comm.close()
0737         del self.handles[handle]
0738         return True
0739 
0740     def exp_devicesetbaudrate(self, handle, rate, context):
0741         return self.gethandle(context, handle).setbaudrate(rate)
0742 
0743     def exp_devicesetdtr(self, handle, dtr, context):
0744         return self.gethandle(context, handle).setdtr(dtr)
0745 
0746     def exp_devicesetrts(self, handle, rts, context):
0747         return self.gethandle(context, handle).setrts(rts)
0748 
0749     def exp_devicewrite(self, handle, data, context):
0750         self.gethandle(context, handle).write(data.data)
0751         return len(data.data)
0752 
0753     def exp_devicesendatcommand(self, handle, atcommand, ignoreerror, context):
0754         "Special handling for empty lists and exceptions"
0755         try:
0756             res=self.gethandle(context, handle).sendatcommand(atcommand.data, ignoreerror=ignoreerror)
0757             if len(res)==0:
0758                 res=1
0759         except:
0760             res=0
0761         return res
0762     
0763     def exp_devicereaduntil(self, handle, char, numfailures, context):
0764         return Binary(self.gethandle(context, handle).readuntil(char.data, numfailures=numfailures))
0765 
0766     def exp_deviceread(self, handle, numchars, context):
0767         return Binary(self.gethandle(context, handle).read(numchars))
0768 
0769     def exp_devicereadsome(self, handle, numchars, context):
0770         if numchars==-1:
0771             numchars=None
0772         return Binary(self.gethandle(context, handle).readsome(log=True,numchars=numchars))
0773 
0774     def exp_devicewritethenreaduntil(self, handle, data, char, numfailures, context):
0775         return Binary(self.gethandle(context, handle).writethenreaduntil(data.data, False, char.data, False, False, numfailures))
0776 
0777 def run(args):
0778     theApp=wx.PySimpleApp()
0779 
0780     menu=wx.Menu()
0781     #menu.Append(ID_CONFIG, "Configuration")
0782     #menu.Append(ID_LOG, "Log")
0783     #menu.Append(ID_RESCAN, "Rescan devices")
0784     menu.Append(ID_EXIT, "Exit")
0785 
0786     mw=MainWindow(None, -1, "BitFling")
0787     taskwin=MyTaskBarIcon(mw, menu)
0788     mw.taskwin=taskwin
0789     theApp.MainLoop()
0790     
0791 if __name__ == '__main__':
0792     run()
0793 

Generated by PyXR 0.9.4