Package bitfling :: Module bitfling
[hide private]
[frames] | no frames]

Source Code for Module bitfling.bitfling

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2004 Roger Binns <rogerb@rogerbinns.com> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: bitfling.py 4228 2007-05-10 02:59:06Z djpham $ 
  9   
 10  """This is the BitFling client 
 11   
 12  It acts as an XML-RPC server over SSH.  The UI consists of a tray icon (Windows) 
 13  or a small icon (Linux, Mac) that you can click on to get the dialog.""" 
 14   
 15  # Standard Modules 
 16  import sys 
 17  import cStringIO 
 18  import os 
 19  import random 
 20  import sha 
 21  import thread 
 22  import fnmatch 
 23  import socket 
 24  import threading 
 25  import time 
 26  from xmlrpclib import Fault, Binary 
 27   
 28  # wx stuff 
 29  import wx 
 30  import wx.html 
 31  import wx.lib.newevent 
 32  import wx.lib.masked.textctrl 
 33  import wx.lib.mixins.listctrl 
 34   
 35  # others 
 36  import paramiko 
 37   
 38  # My stuff 
 39  try: 
 40      import native.usb as usb 
 41  except ImportError: 
 42      usb=None 
 43  import usbscan 
 44  import comscan 
 45  import commport 
 46       
 47  import guihelper 
 48  import xmlrpcstuff 
 49  import version 
 50   
 51  ID_CONFIG=wx.NewId() 
 52  ID_LOG=wx.NewId() 
 53  ID_RESCAN=wx.NewId() 
 54  ID_EXIT=wx.NewId() 
 55   
 56   
 57  XmlServerEvent, EVT_XMLSERVER = wx.lib.newevent.NewEvent() 
 58   
 59  guithreadid=thread.get_ident() 
 60   
 61  # in theory this should also work for GTK, but in practise it doesn't 
 62  if guihelper.IsMSWindows(): parentclass=wx.TaskBarIcon 
 63  else: parentclass=wx.Frame 
 64   
65 -class MyTaskBarIcon(parentclass):
66
67 - def __init__(self, mw, menu):
68 self.mw=mw 69 self.menu=menu 70 iconfile="bitfling.png" 71 if parentclass is wx.Frame: 72 parentclass.__init__(self, None, -1, "BitFling Window", size=(32,32), style=wx.FRAME_TOOL_WINDOW) 73 self.genericinit(iconfile) 74 else: 75 parentclass.__init__(self) 76 self.windowsinit(iconfile) 77 78 self.leftdownpos=0,0 79 #wx.EVT_MENU(menu, ID_CONFIG, self.OnConfig) 80 #wx.EVT_MENU(menu, ID_LOG, self.OnLog) 81 wx.EVT_MENU(menu, ID_EXIT, self.OnExit)
82 #wx.EVT_MENU(menu, ID_RESCAN, self.OnRescan) 83
84 - def GoAway(self):
85 if parentclass is wx.Frame: 86 self.Close(True) 87 else: 88 self.RemoveIcon() 89 self.Destroy()
90
91 - def OnConfig(self,_):
92 print "I would do config at this point"
93
94 - def OnLog(self,_):
95 print "I would do log at this point"
96
97 - def OnHelp(self,_):
98 print "I would do help at this point"
99
100 - def OnRescan(self, _):
101 print "I would do rescan at this point"
102
103 - def OnExit(self,_):
104 self.mw.Close(True)
105
106 - def OnRButtonUp(self, evt=None):
107 if parentclass is wx.Frame: 108 self.PopupMenu(self.menu, evt.GetPosition()) 109 else: 110 self.PopupMenu(self.menu)
111
112 - def OnLButtonUp(self, evt=None):
113 if self.leftdownpos is None: 114 return # cleared out by motion stuff 115 if self.mw.IsShown(): 116 self.mw.Show(False) 117 else: 118 self.mw.Show(True) 119 self.mw.Raise()
120
121 - def OnLeftDown(self, evt):
122 if guihelper.IsMSWindows(): 123 self.leftdownpos=0 124 else: 125 self.leftdownpos=evt.GetPosition() 126 self.motionorigin=self.leftdownpos
127
128 - def OnMouseMotion(self, evt):
129 if not evt.Dragging(): 130 return 131 if evt.RightIsDown() or evt.MiddleIsDown(): 132 return 133 if not evt.LeftIsDown(): 134 return 135 self.leftdownpos=None 136 x,y=evt.GetPosition() 137 xdelta=x-self.motionorigin[0] 138 ydelta=y-self.motionorigin[1] 139 screenx,screeny=self.GetPositionTuple() 140 self.MoveXY(screenx+xdelta, screeny+ydelta)
141
142 - def windowsinit(self, iconfile):
143 bitmap=wx.Bitmap(guihelper.getresourcefile(iconfile), wx.BITMAP_TYPE_PNG) 144 icon=wx.EmptyIcon() 145 icon.CopyFromBitmap(bitmap) 146 self.SetIcon(icon, "BitFling") 147 wx.EVT_TASKBAR_RIGHT_UP(self, self.OnRButtonUp) 148 wx.EVT_TASKBAR_LEFT_UP(self, self.OnLButtonUp) 149 #wx.EVT_TASKBAR_MOVE(self, self.OnMouseMotion) 150 wx.EVT_TASKBAR_LEFT_DOWN(self, self.OnLeftDown)
151
152 - def genericinit(self, iconfile):
153 self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) 154 bitmap=wx.Bitmap(guihelper.getresourcefile(iconfile), wx.BITMAP_TYPE_PNG) 155 bit=wx.StaticBitmap(self, -1, bitmap) 156 self.Show(True) 157 wx.EVT_RIGHT_UP(bit, self.OnRButtonUp) 158 wx.EVT_LEFT_UP(bit, self.OnLButtonUp) 159 wx.EVT_MOTION(bit, self.OnMouseMotion) 160 wx.EVT_LEFT_DOWN(bit, self.OnLeftDown) 161 self.bit=bit
162
163 -class ConfigPanel(wx.Panel, wx.lib.mixins.listctrl.ColumnSorterMixin):
164
165 - def __init__(self, mw, parent, id=-1):
166 wx.Panel.__init__(self, parent, id) 167 self.mw=mw 168 vbs=wx.BoxSizer(wx.VERTICAL) 169 170 # General 171 bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "General"), wx.HORIZONTAL) 172 bs.Add(wx.StaticText(self, -1, "Fingerprint"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 173 self.fingerprint=wx.TextCtrl(self, -1, "a", style=wx.TE_READONLY, size=(300,-1)) 174 bs.Add(self.fingerprint, 0, wx.EXPAND|wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 175 bs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer 176 bs.Add(wx.StaticText(self, -1, "Port"), 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 177 self.porttext=wx.StaticText(self, -1, "<No Port>") 178 bs.Add(self.porttext, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 179 vbs.Add(bs, 0, wx.EXPAND|wx.ALL, 5) 180 181 # authorization 182 bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Authorization"), wx.VERTICAL) 183 hbs=wx.BoxSizer(wx.HORIZONTAL) 184 butadd=wx.Button(self, wx.NewId(), "Add ...") 185 hbs.Add(butadd, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 186 hbs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer 187 self.butedit=wx.Button(self, wx.NewId(), "Edit ...") 188 self.butedit.Enable(False) 189 hbs.Add(self.butedit, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 190 hbs.Add(wx.StaticText(self, -1, ""), 0, wx.ALL, 5) # spacer 191 self.butdelete=wx.Button(self, wx.NewId(), "Delete") 192 self.butdelete.Enable(False) 193 hbs.Add(self.butdelete, 0, wx.ALL|wx.ALIGN_CENTRE_VERTICAL, 5) 194 bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 195 196 wx.EVT_BUTTON(self, butadd.GetId(), self.OnAddAuth) 197 wx.EVT_BUTTON(self, self.butedit.GetId(), self.OnEditAuth) 198 wx.EVT_BUTTON(self, self.butdelete.GetId(), self.OnDeleteAuth) 199 200 # and the authorization listview 201 self.authlist=wx.ListCtrl(self, wx.NewId(), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) 202 self.authlist.InsertColumn(0, "User") 203 self.authlist.InsertColumn(1, "Allowed Addresses") 204 self.authlist.InsertColumn(2, "Expires") 205 self.authlist.SetColumnWidth(0, 300) 206 self.authlist.SetColumnWidth(1, 300) 207 self.authlist.SetColumnWidth(2, 100) 208 bs.Add(self.authlist, 1, wx.EXPAND|wx.ALL, 5) 209 210 vbs.Add(bs, 1, wx.EXPAND|wx.ALL, 5) 211 self.itemDataMap={} 212 wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self,3) 213 214 wx.EVT_LIST_ITEM_ACTIVATED(self.authlist, self.authlist.GetId(), self.OnEditAuth) 215 wx.EVT_LIST_ITEM_SELECTED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled) 216 wx.EVT_LIST_ITEM_DESELECTED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled) 217 wx.EVT_LIST_ITEM_FOCUSED(self.authlist, self.authlist.GetId(), self.OnAuthListItemFondled) 218 219 # devices 220 bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, "Devices"), wx.VERTICAL) 221 buttoggle=wx.Button(self, wx.NewId(), "Toggle Allowed") 222 bs.Add(buttoggle, 0, wx.ALL, 5) 223 self.devicelist=wx.ListCtrl(self, wx.NewId(), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) 224 self.devicelist.InsertColumn(0, "Allowed") 225 self.devicelist.InsertColumn(1, "Name") 226 self.devicelist.InsertColumn(2, "Available") 227 self.devicelist.InsertColumn(3, "Description") 228 self.devicelist.SetColumnWidth(0, 100) 229 self.devicelist.SetColumnWidth(1, 300) 230 self.devicelist.SetColumnWidth(2, 100) 231 self.devicelist.SetColumnWidth(3, 300) 232 bs.Add(self.devicelist, 1, wx.EXPAND|wx.ALL, 5) 233 234 vbs.Add(bs, 1, wx.EXPAND|wx.ALL, 5) 235 236 self.setupauthorization() 237 self.SortListItems() 238 239 self.SetSizer(vbs) 240 self.SetAutoLayout(True)
241
242 - def _updateauthitemmap(self, itemnum):
243 pos=-1 244 if itemnum in self.itemDataMap: 245 # find item by looking for ItemData, and set pos 246 # to corresponding pos in list 247 for i in range(self.authlist.GetItemCount()): 248 if self.authlist.GetItemData(i)==itemnum: 249 pos=i 250 break 251 assert pos!=-1 252 # clear the is connection allowed cache 253 self.mw.icacache={} 254 v=self.mw.authinfo[itemnum] 255 username=v[0] 256 expires=v[2] 257 addresses=" ".join(v[3]) 258 if pos<0: 259 pos=self.authlist.GetItemCount() 260 self.authlist.InsertStringItem(pos, username) 261 else: 262 self.authlist.SetStringItem(pos, 0, username) 263 self.authlist.SetStringItem(pos, 2, `expires`) 264 self.authlist.SetStringItem(pos, 1, addresses) 265 self.authlist.SetItemData(pos, itemnum) 266 self.itemDataMap[itemnum]=(username, addresses, expires)
267
268 - def GetListCtrl(self):
269 "Used by the ColumnSorter mixin" 270 return self.authlist
271
272 - def setupauthorization(self):
273 dict={} 274 items=[] 275 for i in range(1000): 276 if self.mw.config.HasEntry("user-"+`i`): 277 username,password,expires,addresses=self.mw.config.Read("user-"+`i`).split(":") 278 expires=int(expires) 279 addresses=addresses.split() 280 dict[i]=username,password,expires,addresses 281 items.append(i) 282 self.mw.authinfo=dict 283 for i in items: 284 self._updateauthitemmap(i)
285 286
287 - def OnAddAuth(self,_):
288 dlg=AuthItemDialog(self, "Add Entry") 289 if dlg.ShowModal()==wx.ID_OK: 290 username,password,expires,addresses=dlg.GetValue() 291 for i in range(1000): 292 if i not in self.mw.authinfo: 293 self.mw.config.Write("user-"+`i`, "%s:%s:%d:%s" % (username, password, expires, " ".join(addresses))) 294 self.mw.config.Flush() 295 self.mw.authinfo[i]=username,password,expires,addresses 296 self._updateauthitemmap(i) 297 self.SortListItems() 298 break 299 dlg.Destroy()
300
301 - def OnDeleteAuth(self, _):
302 item=self._getselectedlistitem(self.authlist) 303 key=self.authlist.GetItemData(item) 304 del self.mw.authinfo[key] 305 self.authlist.DeleteItem(item) 306 self.mw.config.DeleteEntry("user-"+`key`) 307 self.mw.config.Flush()
308
309 - def _getselectedlistitem(self, listctrl):
310 "Finds the selected item in a listctrl since the wx methods don't actually work" 311 i=-1 312 while True: 313 nexti=listctrl.GetNextItem(i, state=wx.LIST_STATE_SELECTED) 314 if nexti<0: 315 break 316 i=nexti 317 return i 318 return None
319
320 - def OnAuthListItemFondled(self, _):
321 "Called whenever list items are selected, unselectected or similar fondling" 322 selitem=self._getselectedlistitem(self.authlist) 323 self.butedit.Enable(selitem is not None) 324 self.butdelete.Enable(selitem is not None)
325
326 - def OnEditAuth(self, _):
327 "Called to edit the currently selected entry" 328 item=self._getselectedlistitem(self.authlist) 329 key=self.authlist.GetItemData(item) 330 username,password,expires,addresses=self.mw.authinfo[key] 331 dlg=AuthItemDialog(self, "Edit Entry", username=username, password=password, expires=expires, addresses=addresses) 332 if dlg.ShowModal()==wx.ID_OK: 333 username,password,expires,addresses=dlg.GetValue() 334 self.mw.authinfo[key]=username,password,expires,addresses 335 self._updateauthitemmap(key) 336 dlg.Destroy()
337
338 -class AuthItemDialog(wx.Dialog):
339 340 _password_sentinel="\x01\x02\x03\x04\x05\x06\x07\x08" # magic value used to detect if user has changed the field 341
342 - def __init__(self, parent, title, username="New User", password="", expires=0, addresses=[]):
343 wx.Dialog.__init__(self, parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) 344 345 p=self 346 gs=wx.FlexGridSizer(4, 2, 5, 5) 347 gs.AddGrowableCol(1) 348 gs.AddGrowableRow(3) 349 350 gs.Add(wx.StaticText(p, -1, "Username/Email")) 351 self.username=wx.TextCtrl(self, -1, username) 352 gs.Add(self.username,0, wx.EXPAND) 353 gs.Add(wx.StaticText(p, -1, "Password")) 354 self.password=wx.TextCtrl(self, -1, "", style=wx.TE_PASSWORD) 355 self.origpassword=password 356 if len(password): self.password.SetValue(self._password_sentinel) 357 gs.Add(self.password, 0, wx.EXPAND) 358 gs.Add(wx.StaticText(p, -1, "Expires")) 359 self.expires=wx.lib.masked.textctrl.TextCtrl(p, -1, "", autoformat="EUDATETIMEYYYYMMDD.HHMM") 360 gs.Add(self.expires) 361 gs.Add(wx.StaticText(p, -1, "Allowed Addresses")) 362 self.addresses=wx.TextCtrl(self, -1, "\n".join(addresses), style=wx.TE_MULTILINE) 363 gs.Add(self.addresses, 1, wx.EXPAND) 364 365 366 vbs=wx.BoxSizer(wx.VERTICAL) 367 vbs.Add(gs,1, wx.EXPAND|wx.ALL, 5) 368 vbs.Add(wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5) 369 vbs.Add(self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTER|wx.ALL, 5) 370 371 self.SetSizer(vbs) 372 vbs.Fit(self)
373 374
375 - def GenPassword(self, string):
376 # random salt 377 salt="".join([chr(random.randint(0,127)) for x in range(8)]) 378 saltstr="".join(["%02x" % (ord(x),) for x in salt]) 379 # we use a sha of the salt followed by the string 380 val=sha.new(salt+string) 381 # return generated password as $ seperated hex strings 382 return "$".join([saltstr, val.hexdigest()])
383 384
385 - def GetValue(self):
386 # ::TODO:: ensure no colons in username or addresses 387 # figure out password 388 if self.password.GetValue()!=self._password_sentinel: 389 password=self.GenPassword(self.password.GetValue()) 390 else: 391 password=self.origpassword 392 return [self.username.GetValue(), password, 0, self.addresses.GetValue().split()]
393
394 -class MainWindow(wx.Frame):
395
396 - def __init__(self, parent, id, title):
397 self.taskwin=None # set later 398 wx.Frame.__init__(self, parent, id, title, style=wx.RESIZE_BORDER|wx.SYSTEM_MENU|wx.CAPTION) 399 400 sys.excepthook=self.excepthook 401 402 self.authinfo={} # updated by config panel 403 self.icacache={} # used by IsConnectionAllowed 404 405 # Establish config stuff 406 cfgstr='bitfling' 407 if guihelper.IsMSWindows(): 408 cfgstr="BitFling" # nicely capitalized on Windows 409 self.config=wx.Config(cfgstr, style=wx.CONFIG_USE_LOCAL_FILE) 410 # self.config.SetRecordDefaults(True) 411 # for help to save prefs 412 wx.GetApp().SetAppName(cfgstr) 413 wx.GetApp().SetVendorName(cfgstr) 414 415 self.setuphelp() 416 417 wx.EVT_CLOSE(self, self.CloseRequested) 418 419 panel=wx.Panel(self, -1) 420 421 bs=wx.BoxSizer(wx.VERTICAL) 422 423 self.nb=wx.Notebook(panel, -1) 424 bs.Add(self.nb, 1, wx.EXPAND|wx.ALL, 5) 425 bs.Add(wx.StaticLine(panel, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 426 427 gs=wx.GridSizer(1,4, 5, 5) 428 429 for name in ("Rescan", "Hide", "Help", "Exit" ): 430 but=wx.Button(panel, wx.NewId(), name) 431 setattr(self, name.lower(), but) 432 gs.Add(but) 433 bs.Add(gs,0,wx.ALIGN_CENTRE|wx.ALL, 5) 434 435 panel.SetSizer(bs) 436 panel.SetAutoLayout(True) 437 438 # the notebook pages 439 self.configpanel=ConfigPanel(self, self.nb) 440 self.nb.AddPage(self.configpanel, "Configuration") 441 self.lw=guihelper.LogWindow(self.nb) 442 self.nb.AddPage(self.lw, "Log") 443 444 wx.EVT_BUTTON(self, self.hide.GetId(), self.OnHideButton) 445 wx.EVT_BUTTON(self, self.help.GetId(), self.OnHelpButton) 446 wx.EVT_BUTTON(self, self.exit.GetId(), self.OnExitButton) 447 448 EVT_XMLSERVER(self, self.OnXmlServerEvent) 449 450 self.xmlrpcserver=None 451 wx.CallAfter(self.StartIfICan)
452
453 - def setuphelp(self):
454 """Does all the nonsense to get help working""" 455 import wx.html 456 # Add the Zip filesystem 457 wx.FileSystem_AddHandler(wx.ZipFSHandler()) 458 # Get the help working 459 self.helpcontroller=wx.html.HtmlHelpController() 460 self.helpcontroller.AddBook(guihelper.getresourcefile("bitpim.htb")) 461 self.helpcontroller.UseConfig(self.config, "help")
462 463 # now context help 464 # (currently borken) 465 # self.helpprovider=wx.HelpControllerHelpProvider(self.helpcontroller) 466 # wx.HelpProvider_Set(provider) 467
468 - def IsConnectionAllowed(self, peeraddr, username=None, password=None):
469 """Verifies if a connection is allowed 470 471 If username and password are supplied (as should be the case if calling this method 472 before executing some code) then they are checked as being from a valid address 473 as well. 474 475 If username and password are not supplied then this method checks if any 476 of the authentication rules allow a connection from the peeraddr. This form 477 is used immediately after calling accept() on a socket, but before doing 478 anything else.""" 479 # Note that this method is not called in the main thread, and any variables could be 480 # updated underneath us. Be threadsafe and only use atomic methods on shared data! 481 482 v=(peeraddr[0], username, password) 483 if username is not None and password is None: 484 self.Log("%s: No password supplied for user %s" % (peeraddr, `username`)) 485 assert False, "No password supplied" 486 return False # not allowed to have None as password 487 print "ica of "+`v` 488 val=self.icacache.get(v, None) 489 if val is not None: 490 allowed, expires = val 491 if allowed: 492 if self._has_expired(expires): 493 msg="Connection from %s no longer allowed due to expiry" % (peeraddr[0],) 494 if username is not None: 495 msg+=". Username "+`username` 496 self.Log(msg) 497 return False 498 return True 499 return False 500 501 ret_allowed=False 502 ret_expiry=0 # an expiry of zero is infinite, so this will be overridden by any specific expiries 503 for uname, pwd, expires, addresses in self.authinfo.values(): # values() is threadsafe 504 # do any of the addresses match? 505 if not self._does_address_match(peeraddr[0], addresses): 506 continue 507 # check username/password if username supplied 508 if username is not None: 509 if username!=uname: 510 continue 511 # check password 512 if not self._verify_password(password, pwd): 513 self.Log("Wrong password supplied for user %s from %s" % (`username`, peeraddr[0])) 514 continue 515 # remember expiry value (largest value) 516 ret_expiry=max(ret_expiry, expires) 517 ret_allowed=True 518 519 if not ret_allowed: 520 if username is not None: 521 self.Log("No valid credentials for user %s from %s" % (username, peeraddr[0])) 522 else: 523 self.Log("No defined users for address "+`peeraddr`) 524 525 # recurse so that correct log messages about expiry get generated 526 self.icacache[v]=ret_allowed, ret_expiry 527 return self.IsConnectionAllowed(peeraddr, username, password)
528
529 - def _verify_password(self, password, pwd):
530 """Returns True if password matches pwd 531 @param password: password supplied by user 532 @param pwd: password as we store it (salt $ hash)""" 533 salt,hash=pwd.split("$", 1) 534 # turn salt back into binary 535 x="" 536 for i in range(0, len(salt), 2): 537 x+=chr(int(salt[i:i+2], 16)) 538 salt=x 539 # we do this silly string stuff to avoid issues with encoding - it only works for iso 8859-1 540 str=[] 541 str.extend([ord(x) for x in salt]) 542 str.extend([ord(x) for x in password]) 543 val=sha.new("".join([chr(x) for x in str])) 544 print password, pwd, val.hexdigest(), val.hexdigest()==hash 545 return val.hexdigest()==hash
546
547 - def _does_address_match(self, peeraddr, addresses):
548 """Returns if the peeraddr matches any of the supplied addresses""" 549 # note this function can be called from any thread. do not access any data 550 for addr in addresses: 551 # the easy case 552 if peeraddr==addr: return True 553 # is addr a glob pattern? 554 if '*' in addr or '?' in addr or '[' in addr: 555 if fnmatch.fnmatch(peeraddr, addr): 556 return True 557 # ::TODO:: addr/bits style checking - see Python cookbook 10.5 for code 558 # ok, do dns lookup on it 559 ips=[] 560 try: 561 ips=socket.getaddrinfo(addr, None) 562 except: 563 pass 564 for _, _, _, _, ip in ips: 565 if peeraddr==ip[0]: 566 return True 567 return False
568
569 - def _has_expired(self, expires):
570 if expires==0: 571 return False 572 if time.time()>expires: 573 return True 574 return False
575
576 - def CloseRequested(self, evt):
577 if evt.CanVeto(): 578 self.Show(False) 579 evt.Veto() 580 return 581 self.taskwin.GoAway() 582 evt.Skip() 583 sys.excepthook=sys.__excepthook__
584
585 - def OnXmlServerEvent(self, msg):
586 if msg.cmd=="log": 587 self.Log(msg.data) 588 elif msg.cmd=="logexception": 589 self.LogException(msg.data) 590 else: 591 assert False, "bad message "+`msg` 592 pass
593 594
595 - def OnExitButton(self, _):
596 self.Close(True)
597
598 - def OnHideButton(self, _):
599 self.Show(False)
600
601 - def OnHelpButton(self, _):
602 import helpids 603 self.helpcontroller.Display(helpids.ID_BITFLING)
604
605 - def Log(self, text):
606 if thread.get_ident()!=guithreadid: 607 wx.PostEvent(self, XmlServerEvent(cmd="log", data=text)) 608 else: 609 self.lw.log(text)
610
611 - def LogException(self, exc):
612 if thread.get_ident()!=guithreadid: 613 # need to send it to guithread 614 wx.PostEvent(self, XmlServerEvent(cmd="log", data="Exception in thread "+threading.currentThread().getName())) 615 wx.PostEvent(self, XmlServerEvent(cmd="logexception", data=exc)) 616 else: 617 self.lw.logexception(exc)
618
619 - def excepthook(self, *args):
620 """Replacement exception handler that sends stuff to our log window""" 621 self.LogException(args)
622
623 - def GetCertificateFilename(self):
624 """Return certificate filename 625 626 By default $HOME (or My Documents) / .bitfling.key 627 but can be overridden with "certificatefile" config key""" 628 629 if guihelper.IsMSWindows(): # we want subdir of my documents on windows 630 # nice and painful 631 from win32com.shell import shell, shellcon 632 path=shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) 633 path=os.path.join(path, ".bitfling.key") 634 else: 635 path=os.path.expanduser("~/.bitfling.key") 636 return self.config.Read("certificatefile", path)
637 638
639 - def StartIfICan(self):
640 certfile=self.GetCertificateFilename() 641 if not os.path.isfile(certfile): 642 wx.BeginBusyCursor(wx.StockCursor(wx.CURSOR_ARROWWAIT)) 643 bi=wx.BusyInfo("Creating BitFling host certificate and keys") 644 try: 645 generate_certificate(certfile) 646 if not os.path.isfile(certfile): 647 # ::TODO:: report that we failed 648 certfile=None 649 finally: 650 del bi 651 wx.EndBusyCursor() 652 port=self.config.ReadInt("port", 12652) 653 if port<1 or port>65535: port=None 654 host=self.config.Read("bindaddress", "") 655 if certfile is None or port is None: 656 return 657 self.Log("Starting on port "+`port`) 658 key=paramiko.DSSKey.from_private_key_file(certfile) 659 fp=paramiko.util.hexify(key.get_fingerprint()) 660 self.configpanel.fingerprint.SetValue(fp) 661 self.configpanel.porttext.SetLabel(`port`) 662 self.configpanel.GetSizer().Layout() 663 self.xmlrpcserver=BitFlingService(self, host, port, key) 664 self.xmlrpcserver.setDaemon(True) 665 self.xmlrpcserver.start()
666 667
668 -def generate_certificate(outfile):
669 key=paramiko.DSSKey.generate() 670 key.write_private_key_file(outfile, None)
671 672 673
674 -class XMLRPCService(xmlrpcstuff.Server):
675
676 - def __init__(self, mainwin, host, port, servercert):
677 self.mainwin=mainwin 678 xmlrpcstuff.Server.__init__(self, host, port, servercert)
679
680 - def OnLog(self, msg):
681 wx.PostEvent(self.mainwin, XmlServerEvent(cmd="log", data=msg))
682
683 - def OnLogException(self, exc):
684 wx.PostEvent(self.mainwin, XmlServerEvent(cmd="logexception", data=exc))
685
686 - def OnNewAccept(self, clientaddr):
687 return self.mainwin.IsConnectionAllowed(clientaddr)
688
689 - def OnNewUser(self, clientaddr, username, password):
690 return self.mainwin.IsConnectionAllowed(clientaddr, username, password)
691
692 - def OnMethodDispatch(self, method, params, username, clientaddr):
693 method="exp_"+method 694 if not hasattr(self, method): 695 raise Fault(3, "No such method") 696 697 context={ 'username': username, 'clientaddr': clientaddr } 698 699 return getattr(self, method)(*params, **{'context': context})
700 701
702 -class BitFlingService(XMLRPCService):
703
704 - def __init__(self, mainwin, host, port, servercert):
705 XMLRPCService.__init__(self, mainwin, host, port, servercert) 706 self.handles={}
707
708 - def stashhandle(self, context, comm):
709 for i in range(10000): 710 if i not in self.handles: 711 self.handles[i]=[context, comm] 712 return i
713
714 - def gethandle(self, context, num):
715 # ::TODO:: check context has access 716 return self.handles[num][1]
717 718 # Only methods begining with exp are exported to XML-RPC 719
720 - def exp_scan(self, context):
721 if usb is None: 722 return comscan.comscan() 723 else: 724 return usbscan.usbscan()+comscan.comscan()
725
726 - def exp_getversion(self, context):
727 return version.description
728
729 - def exp_deviceopen(self, port, baud, timeout, hardwareflow, softwareflow, context):
730 # ::TODO:: None is pointer to log object 731 return self.stashhandle(context, commport.CommConnection(None, port, baud, timeout, 732 hardwareflow, softwareflow))
733
734 - def exp_deviceclose(self, handle, context):
735 comm=self.gethandle(context, handle) 736 comm.close() 737 del self.handles[handle] 738 return True
739
740 - def exp_devicesetbaudrate(self, handle, rate, context):
741 return self.gethandle(context, handle).setbaudrate(rate)
742
743 - def exp_devicesetdtr(self, handle, dtr, context):
744 return self.gethandle(context, handle).setdtr(dtr)
745
746 - def exp_devicesetrts(self, handle, rts, context):
747 return self.gethandle(context, handle).setrts(rts)
748
749 - def exp_devicewrite(self, handle, data, context):
750 self.gethandle(context, handle).write(data.data) 751 return len(data.data)
752
753 - def exp_devicesendatcommand(self, handle, atcommand, ignoreerror, context):
754 "Special handling for empty lists and exceptions" 755 try: 756 res=self.gethandle(context, handle).sendatcommand(atcommand.data, ignoreerror=ignoreerror) 757 if len(res)==0: 758 res=1 759 except: 760 res=0 761 return res
762
763 - def exp_devicereaduntil(self, handle, char, numfailures, context):
764 return Binary(self.gethandle(context, handle).readuntil(char.data, numfailures=numfailures))
765
766 - def exp_deviceread(self, handle, numchars, context):
767 return Binary(self.gethandle(context, handle).read(numchars))
768
769 - def exp_devicereadsome(self, handle, numchars, context):
770 if numchars==-1: 771 numchars=None 772 return Binary(self.gethandle(context, handle).readsome(log=True,numchars=numchars))
773
774 - def exp_devicewritethenreaduntil(self, handle, data, char, numfailures, context):
775 return Binary(self.gethandle(context, handle).writethenreaduntil(data.data, False, char.data, False, False, numfailures))
776
777 -def run(args):
778 theApp=wx.PySimpleApp() 779 780 menu=wx.Menu() 781 #menu.Append(ID_CONFIG, "Configuration") 782 #menu.Append(ID_LOG, "Log") 783 #menu.Append(ID_RESCAN, "Rescan devices") 784 menu.Append(ID_EXIT, "Exit") 785 786 mw=MainWindow(None, -1, "BitFling") 787 taskwin=MyTaskBarIcon(mw, menu) 788 mw.taskwin=taskwin 789 theApp.MainLoop()
790 791 if __name__ == '__main__': 792 run() 793