· 9 years ago · Jun 10, 2017, 12:12 PM
1#!/usr/bin/env python
2###
3### This file is part of Pyffle BBS.
4###
5### Pyffle BBS is free software: you can redistribute it and/or modify
6### it under the terms of the GNU General Public License as published by
7### the Free Software Foundation, either version 3 of the License, or
8### (at your option) any later version.
9###
10### Pyffle BBS is distributed in the hope that it will be useful,
11### but WITHOUT ANY WARRANTY; without even the implied warranty of
12### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13### GNU General Public License for more details.
14###
15### You should have received a copy of the GNU General Public License
16### along with Foobar. If not, see <http://www.gnu.org/licenses/>.
17###
18###
19
20
21
22## Models for SqlAlchemy version 6
23from sqlalchemy.ext.declarative import declarative_base
24from sqlalchemy.orm import relation, backref
25from sqlalchemy import *
26from sqlalchemy.dialects.postgresql import *
27from sqlalchemy.orm import sessionmaker
28from pyffle_tables import *
29from datetime import datetime
30from pyffle_dispatch import PyffleDispatch
31import os
32import psutil
33from threading import Lock
34### This is the central class for Pyffle - it deals with:
35###
36### - Message creation/updating/deleting/querying
37### - Logging
38### - Security / ACL/ACE ops
39### - Various housekeeping tasks
40###
41### Almost everything in the system has a pointer to an
42### instance of this class and it is the central "switchboard"
43### of the system.
44###
45### Additionally there are calls to and from the Utility class
46### which provides string formatting and I/O etc, that are typically
47### accessed through the data.util. handle.
48###
49### On start up, Pyffle (and stand alone tools), create an instance
50### of PyffleData once a DB session has been set up, and connect it with
51### an instance of PyffleUtility
52###
53### PS: I know this should be refactored :)
54###
55### sampsa
56
57class PyffleData:
58 currentUser = None ## The currently logged in user, set by PyffleMain typically
59 currentBoard = None ## The currently selected message board
60
61
62 buddyListHasBeenShown = False ## FIXME - module loading issue...Hard to fix.
63 session = None ## Database sesson
64 util = None ## Instance of PyffleUtility, set by PyffleMain typically
65 static = None ## Loaded configuration from STATIC file, dictionary
66
67 toolMode = False ## This is set to true when running as a standalone tool to bypass the SRM
68
69 mtaModules = {} ## Modules for MTAs that handle external mail
70 mtas = [] ## mta entry = [mta module name to be loaded, mta name, mta description]
71
72 loggedIn = False
73 sessionRef = None ## id into currentlyon table
74
75 staticCookies = None
76 loggingOff = False
77 sessionLock = None
78 sessionLocked = False
79 himclient = None ## ugly hack until we get the instantiation thing ironed out
80 instances = {}
81 def __init__ (self):
82 ## self.sessionLock = Lock()
83 pass
84
85
86 #####VERSION STRINGS#### - fixme, should be pulled from git or something
87 def getPyffleVersionString(self):
88 return 'Pyffle v1.37.42 "Creaky Elevator"'
89
90 ## Returns a short form of the version string
91 def getPyffleVersionShortString(self):
92 return "1.37.42"
93
94 def getStaticCookies(self):
95 rv = []
96 if self.staticCookies == None:
97 self.staticCookies = self.util.texts["SYSTEM"]["cookies"].split("|")
98 rv = self.staticCookies
99 return rv
100 ## easiest way to do multithreading - grab a local copy of the DB session...
101 def getLocalSession(self):
102 Session = sessionmaker()
103
104 ## engine = create_engine('postgresql://postgres:f00btron@localhost/pyffledev', echo=False)
105 engine = create_engine(self.static.options['pyffle.dburl'], echo = False)
106 Session.configure(bind=engine)
107
108 localSession = Session()
109 return localSession
110
111 def getSession(self):
112 return self.session
113
114 def lockSession(self):
115 ##self.sessionLock.acquire()
116 ##self.sessionLocked = True
117 pass
118
119 def releaseSession(self):
120 ##if self.sessionLocked:
121 ## self.sessionLock.release()
122 ## self.sessionLocked = False
123 pass
124
125
126 def pluginDeleteSystem(self,key):
127 if not self.currentUser == None:
128 username = self.currentUser.username
129 else:
130 username = "<none>"
131 self.lockSession()
132 for object in self.getSession().query(Pluginsystem).filter(Pluginsystem.key == key):
133 self.getSession().delete(object)
134 self.getSession().commit()
135 self.logEntry(self.LOGINFO,"DELETE/PLUGIN/SYSTEM",str(username),"Deleted |%s|=|%s|" % (str(key),str(object.value)))
136 self.releaseSession()
137
138 def pluginDeleteUser(self,username,key):
139 self.lockSession()
140 for object in self.getSession().query(Pluginuser).filter(Pluginuser.key == key).filter(Pluginuser.username == username):
141 self.getSession().delete(object)
142 self.getSession().commit()
143 self.logEntry(self.LOGINFO,"DELETE/PLUGIN/USER",str(username),"Deleted %s's |%s|=|%s|" % (str(username),str(key),str(object.value)))
144 self.releaseSession()
145
146 def pluginWriteSystem(self,key,value):
147 if not self.currentUser == None:
148 username = self.currentUser.username
149 else:
150 username = "<none>"
151
152 self.pluginDeleteSystem(key)
153 object = Pluginsystem()
154 object.key=key
155 object.value=value
156 self.lockSession()
157 self.getSession().add(object)
158 self.getSession().commit()
159 self.releaseSession()
160 self.logEntry(self.LOGINFO,"CREATE/PLUGIN/SYSTEM",str(username),"Stored |%s|=|%s|" % (str(key),str(value)))
161
162 def pluginWriteUser(self,username,key,value):
163 self.pluginDeleteUser(username,key)
164 object = Pluginuser()
165 object.username=username
166 object.key=key
167 object.value=value
168 self.lockSession()
169 self.getSession().add(object)
170 self.getSession().commit()
171 self.releaseSession()
172 self.logEntry(self.LOGINFO,"CREATE/PLUGIN/USER",str(username),"Stored %s's |%s|=|%s|" % (str(username),str(key),str(value)))
173
174 def pluginReadSystem(self,key):
175 if not self.currentUser == None:
176 username = self.currentUser.username
177 else:
178 username = "<none>"
179
180 rv = None
181 self.lockSession()
182 for object in self.getSession().query(Pluginsystem).filter(Pluginsystem.key == key):
183 rv = object.value
184 self.logEntry(self.LOGINFO,"READ/PLUGIN/SYSTEM",str(username),"Read |%s|=|%s|" % (str(key),str(rv)))
185 break
186 self.releaseSession()
187
188 return rv
189
190
191 def pluginReadUser(self,username,key):
192 rv = None
193 for object in self.getSession().query(Pluginuser).filter(Pluginuser.key == key).filter(Pluginuser.username == username):
194 rv = object.value
195 self.logEntry(self.LOGINFO,"READ/PLUGIN/USER",str(username),"Read %s's |%s|=|%s|" % (str(username),str(key),str(rv)))
196 break
197 return rv
198
199## Logging functions start
200
201###
202### Logging criticality levels
203###
204 LOGCRIT = 5
205 LOGWARN = 4
206 LOGNORMAL = 3
207 LOGINFO = 2
208 LOGDEBUG = 1
209
210 LOGLEVEL=3
211 ## Post an entry to the log
212 def logEntry(self,level,code,subject,description):
213 #print "LOG ENTRY CALLED"
214 if self.loggingOff == True:
215 return
216 else:
217 # HACK
218 if level >= self.LOGLEVEL:
219 entry = Logentry()
220 entry.level = level
221 entry.code = code
222 entry.subject = subject
223 entry.description = description
224 entry.date = datetime.now()
225 self.getSession().add(entry)
226 self.getSession().commit()
227
228 ## Returns the name of the previous caller from the system log
229 def getLastUser(self):
230 ## since our call has already been logged, grab the one before us
231 ## FIXME - check for None
232 lastUsers = self.getLastUsers(2)
233 if lastUsers == None or len(lastUsers) < 2:
234 return " "
235 else:
236 return lastUsers[1]
237
238 ## Log a loginfailure
239 def logLoginFail(self,username,reason="Bad credentials."):
240 self.logEntry(self.LOGWARN,"LOGIN/FAILURE",str(username),"%s failed to log in, %s" % (str(username),str(reason)))
241
242
243 def setCurrentlyonActivity(self, activity):
244 entries = self.getSession().query(Currentlyon).filter(Currentlyon.id == self.sessionRef)
245 entry = entries.first()
246
247 if not entry == None:
248 entry.activity = activity
249
250 self.getSession().add(entry)
251 self.getSession().commit()
252 return entry.id
253 else:
254 return None
255
256 def addToCurrentlyon(self):
257 entry = Currentlyon()
258 entry.activity = ""
259 entry.origin = "" ## FIXME - add origin here
260 entry.username = self.currentUser.username
261 entry.dateon = datetime.now()
262 entry.pid = os.getpid()
263 self.getSession().add(entry)
264 self.getSession().commit()
265 self.sessionRef = entry.id
266
267 def resetCurrentlyOn(self):
268 for entry in self.getSession().query(Currentlyon):
269 self.getSession().delete(entry)
270 self.getSession().commit()
271 self.sessionRef = None
272
273
274 def purgeLogentries(self, maxEntries):
275 entries = self.getSession().query(Logentry).order_by(desc(Logentry.date))
276 count = 0
277 for entry in entries:
278 count = count + 1
279 if count > maxEntries:
280 self.getSession().delete(entry)
281 self.getSession().commit()
282
283 def removeFromCurrentlyon(self):
284 for entry in self.getSession().query(Currentlyon).filter(Currentlyon.id == self.sessionRef):
285 self.getSession().delete(entry)
286 self.getSession().commit()
287 self.sessionRef = None
288
289 def getCurrentlyonEntries(self):
290 rv = []
291 entries = self.getSession().query(Currentlyon)
292 for entry in entries:
293 ## check that the entry is still valid (i.e. the process is still running)
294 if psutil.pid_exists(entry.pid):
295 ## print "%s is still valid" % (entry.pid)
296 rv.append(entry)
297 else:
298 ## print "%s is a stale PID, deleting" % (entry.pid)
299 ## it's not, remove from the table
300 self.getSession().delete(entry)
301 self.getSession().commit()
302 return rv
303
304
305 ## Log a succesful start to session
306 def logCall(self):
307 loggedIn = True
308 self.addToCurrentlyon()
309 if self.currentUser.timescalled == None:
310 self.currentUser.timescalled = 0
311 self.currentUser.timescalled = int(self.currentUser.timescalled) + 1
312 self.getSession().add(self.currentUser)
313 self.getSession().commit()
314 self.logEntry(self.LOGNORMAL,"LOGIN/SUCCESS",self.currentUser.username,"%s logged in normally" % (self.currentUser.username))
315
316 ## Log a succesful logout
317 def logLogout(self):
318 self.removeFromCurrentlyon()
319 loggedIn = False
320 self.currentUser.datefastlogin = datetime.now()
321 if self.currentUser.datefirstlogin == None:
322 self.currentUser.datefirstlogin = self.currentUser.datefastlogin
323 self.getSession().add(self.currentUser)
324 self.getSession().commit()
325 self.logEntry(self.LOGNORMAL,"LOGOUT/SUCCESS",self.currentUser.username,"%s logged out normally" % (self.currentUser.username))
326
327 ## Return a list of num x log entries, each entry being a list:
328 ## [entry.level,entry.code,entry.subject,entry.date,entry.description]
329 def getLog(self,num = 22, level = -1):
330 rv = []
331 i = 0
332 for entry in self.getSession().query(Logentry).filter(Logentry.level >= level).order_by(Logentry.date.desc()):
333 rv.append([entry.level,entry.code,entry.subject,entry.date,entry.description,entry.id])
334 i = i + 1
335 if i == num:
336 break
337 return rv
338
339 def _getLog(self,num = 22, level = -1):
340 rv = []
341 i = 0
342 for entry in self.getSession().query(Logentry).filter(Logentry.level >= level).order_by(Logentry.id.asc()):
343 rv.append([entry.level,entry.code,entry.subject,entry.date,entry.description,entry.id])
344 i = i + 1
345 if i == num:
346 break
347 return rv
348
349 ## Returns the amount of calls to the system from the system log
350 def getSystemCalls(self):
351 rv = self.getSession().query(Logentry).filter(Logentry.code == "LOGIN/SUCCESS").count()
352 return rv
353
354 ## Returns a list of num last callers, each entry being a list of:
355 ## [subject == caller username, date]
356 def getLastUsers(self,num = 20):
357 rv = []
358 i = 0
359 for entry in self.getSession().query(Logentry).filter(Logentry.code == "LOGIN/SUCCESS").order_by(Logentry.date.desc()):
360 rv.append([entry.subject,entry.date])
361 i = i + 1
362 if i > num:
363 break
364 return rv
365
366
367### MTA functions start
368
369 ## Imports the modules for the mtas given
370 def loadMtaList(self,mtas):
371 ### single mta = [module.py, mta name, mta description]
372 self.mtas = mtas
373 for mta in mtas:
374 self.mtaModules[mta[1]] = (__import__(mta[0]))
375
376
377 ## Loops through out MTAs and tries to parse an external addres
378 ## to match an internal user. Returns a string if the parsing was succesful,
379 ## None if not
380 def parseIncomingAddress(self,toname):
381 toname = toname.strip()
382 self.util.debugln("TONAME=" + toname)
383 if self.userExists(toname):
384 ## Got a local user, great, this will be simple
385 return toname;
386 else:
387 ## OK, the user is remote - let's see if anybody will accept delivery
388 self.util.debugln("Address not local, checking MTAs")
389 for mtaName in self.mtaModules.keys():
390 ## Loop through our MTAs, instantiating each one until
391 ## we get one that accepts the address
392 mta = self.mtaModules[mtaName]
393 mtaInstance = mta.PyffleMta()
394 mtaInstance.data = self
395 mtaInstance.currentUser = self.currentUser
396 if mtaInstance.matchIncomingAddress(toname):
397 username,system = mtaInstance.parseIncomingAddress(toname)
398 ## FIXME: check destination system name here too
399 if (not username == None) and (not username == ""):
400 ## Cool, this MTA likes the address, let's jump out of this
401 self.util.debugln("MTA " + mtaName + " thinks this is for " + username)
402 return username
403 ## Address doesn't appear to be valid - bork, return without sending or storing anything
404 self.util.debugln("Destination address invalid, no MTA will accept destination")
405 return None
406
407
408### Event dispatcher
409 dispatcher = None
410 def setDispatcher(self,d):
411 self.dispatcher = d
412
413 def stateChange(self,s,params=None):
414 self.dispatcher.stateChange(s,args=params)
415
416### Housekeeping / misc functions start
417
418 ## Returns a string describing the amount of time the user has left.
419 ## Not implemented.
420 def getTimeLeft(self):
421 return "59 mins"
422
423
424 ## Returns the username of the system operator
425 def getSysopId(self):
426 return "system" ## FIXME Static?
427
428
429 def getNodename(self):
430 return self.static.options["node"]
431
432### JOIN functions start...
433 def unjoinByBoardid(self, boardid, username = None):
434 if username == None:
435 username = self.currentUser.username
436
437 for join in self.getSession().query(Joinedboard).filter(Joinedboard.boardid == boardid).filter(Joinedboard.username == username):
438 self.getSession().delete(join)
439 self.getSession().commit()
440 self.logEntry(self.LOGNORMAL,"UNJOIN/BOARD","BOARDID:" + str(boardid),"%s UNJOINed %s " % (username,str(boardid)))
441
442 def joinAll(self,username = None):
443
444 if username == None:
445 username = self.currentUser.username
446 for boardid in self.getBoardids():
447 self.joinByBoardid(boardid,username)
448
449
450 def unjoinAll(self,username = None):
451
452 if username == None:
453 username = self.currentUser.username
454
455 for boardid in self.getJoinedBoardids():
456 self.unjoinByBoardid(boardid,username)
457
458 def joinByBoardid(self, boardid, username = None):
459
460 if username == None:
461 username = self.currentUser.username
462
463 if self.isJoinedByBoardid(boardid,username):
464 return ## Already joined
465 join = Joinedboard()
466 join.username = username
467 join.boardid = boardid
468 self.getSession().add(join)
469 self.getSession().commit()
470
471 self.logEntry(self.LOGNORMAL,"JOIN/BOARD","BOARDID:" + str(boardid),"%s JOINed %s " % (username,str(boardid)))
472
473 return join.id
474
475 def isJoinedByBoardid(self, boardid, username = None):
476
477 if username == None:
478 username = self.currentUser.username
479
480 rv = False;
481 for joins in self.getSession().query(Joinedboard).filter(Joinedboard.boardid == boardid).filter(Joinedboard.username == username):
482 rv = True
483 break
484 return rv
485
486 def getJoinedBoardids(self, username = None):
487
488 if username == None:
489 username = self.currentUser.username
490
491 rv = []
492 for boardid in self.getBoardids():
493 if self.isJoinedByBoardid(boardid,username):
494 rv.append(boardid)
495 return rv
496
497
498 def joinBoardToggle(self, boardid, username = None):
499
500 print "Toggling %s" % (str(boardid))
501 if username == None:
502 username = self.currentUser.username
503
504
505 if self.isJoinedByBoardid(boardid,username):
506 print "It's on, turning off %s" % (str(boardid))
507 self.unjoinByBoardid(boardid,username)
508 else:
509 print "It's off, turning on %s" % (str(boardid))
510 self.joinByBoardid(boardid,username)
511
512
513#### Group membership functions
514
515 def addGroupMember(self,groupname,member):
516 rv = False
517 group = self.getGroup(groupname)
518 if not group == None:
519 if not self.getUser(member) == None:
520 membership = Groupmember()
521 membership.groupname = group.groupname
522 membership.username = member
523 self.session.add(membership)
524 self.session.commit()
525 rv = True
526 return rv
527
528 def getGroupMemberships(self,member):
529 rv = []
530 for theMembership in self.getSession().query(Groupmember).filter(Groupmember.username == member):
531 rv.append(theMembership.groupname)
532 return rv
533 def getGroupMembership(self,group,member):
534 rv = None
535 for theMembership in self.getSession().query(Groupmember).filter(Groupmember.groupname == group.groupname).filter(Groupmember.username == member):
536 rv = theMembership
537 break
538 return rv
539
540 def getGroupMembers(self,groupname):
541 rv = []
542 for theMembership in self.getSession().query(Groupmember).filter(Groupmember.groupname == groupname):
543 rv.append(theMembership.username)
544 return rv
545 def isGroupMember(self,groupname,member):
546 rv = False
547 members = self.getGroupMembers(groupname)
548 if member in members:
549 rv = True
550 return rv
551
552 def dropGroupMember(self,groupname,member):
553 rv = False
554 group = self.getGroup(groupname)
555 if not group == None:
556 if self.isGroupMember(group,member):
557 membership = self.getGroupMembership(group,member)
558 self.session.delete(membership)
559 self.session.commit()
560 rv = True
561 return rv
562
563
564#### Group functions
565
566 def deleteGroupByGroupname(self,name):
567 ## Get the board
568 group = self.getGroup(name)
569 if not group == None:
570 ## remove any members
571 members = self.getGroupMembers(name)
572 for member in members:
573 self.dropGroupMember(name,member)
574 ## remove ourselves
575 ## but first lets remember our acl id so we can kill it after
576 aclid = group.aclid
577 self.getSession().delete(group)
578 self.getSession().commit()
579 self.deleteAcl(aclid)
580
581
582
583 def createGroup(self,name,description,aclid = None):
584 #### creates a group
585 myGroup = Usergroup()
586 myGroup.groupname = name
587 myGroup.description = description
588 if aclid == None:
589 myAcl = self.createAcl()
590 myAcl.description="GROUP: "+name
591 self.getSession().add(myAcl)
592 self.getSession().commit()
593 aclid = myAcl.id
594 myGroup.aclid = aclid
595 self.getSession().add(myGroup)
596 self.getSession().commit()
597 self.logEntry(self.LOGNORMAL,"CREATE/GROUP","GROUPNAME:" + str(myGroup.groupname),"Group created: %s " % (str(myGroup.groupname)))
598
599 return myGroup.groupname
600
601
602 def getGroups(self):
603 rv = []
604 for theGroup in self.getSession().query(Usergroup).filter(Usergroup.groupname != "MONKEYBUSINESS!!"):
605 rv.append( theGroup)
606 return rv
607 def getGroup(self, name):
608 for theGroup in self.getSession().query(Usergroup).filter(Usergroup.groupname == name):
609 return theGroup
610 return None
611
612### Board functions start
613 ## Returns a descriptive string of the currently selected board
614 def getCurrentBoardString(self):
615 boardname = self.getCurrentBoard()
616 description = ""
617 if not boardname == None:
618 board = self.getBoardByName(boardname)
619 if not board == None:
620 description = str(self.getBoardByName(boardname).description)
621
622 return "[" + boardname + ": " + description +"]"
623
624 def getCurrentBoard(self):
625 return str(self.currentBoard)
626
627 def setCurrentBoard(self,boardname):
628 rv = False
629 board = self.getBoard(self.getBoardid(boardname))
630 if self.srmcheck(board.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
631 self.currentBoard = boardname
632 rv = True
633 return rv
634
635
636 def getBoardids(self):
637 ##
638 rv = []
639 for theBoard in self.getBoards():
640 rv.append(theBoard.id)
641 return rv
642
643 def getBoards(self):
644 ##
645 rv = []
646 for theBoard in self.getSession().query(Board).order_by(Board.id):
647 rv.append(theBoard)
648 return rv
649
650 def getBoardNames(self):
651 rv = []
652 boards = self.getBoards()
653 for board in boards:
654 rv.append(board.name)
655 return rv
656
657 def getBoardByExternalname(self,name):
658 for theBoard in self.getSession().query(Board).filter(Board.externalname == name):
659 return theBoard
660 return None
661
662
663 def getBoardByName(self,name):
664 ##
665 for theBoard in self.getSession().query(Board).filter(Board.name == name):
666 return theBoard
667 return None
668
669 def getMessageIdsOlderThan(self, date, boardname = "__pyffle_email"):
670 board = self.getBoardByName(boardname)
671 boardid = board.id
672 rv = []
673 for theMsg in self.getSession().query(Message).filter(Message.boardid == boardid).filter(Message.sentdate <= date):
674 rv.append(theMsg.id)
675 return rv
676
677 def getOldMessagesOver(self, boardname, maxmsgs):
678 print "Got Boardname: " + boardname
679 msgs = self.getMessagesByBoardname(boardname,checkSrm=False)
680 count = 0
681 rv = []
682 for id in msgs:
683 count = count + 1
684 if count > maxmsgs:
685 rv.append(id)
686 return rv
687
688 def getBoard(self,boardid):
689 ##
690 for theBoard in self.getSession().query(Board).filter(Board.id == boardid):
691 return theBoard
692 return None
693
694 def deleteBoardByBoardid(self, boardid):
695 ## Delete messages first
696 msgids = self.getMessagesByBoardid(boardid)
697 for msgid in msgids:
698 self.deleteMessage(msgid)
699
700 ## Get the board
701 board = self.getBoard(boardid)
702 if not board == None:
703 boardname = board.name
704
705
706 ## remove ourselves
707 self.getSession().delete(board)
708 self.getSession().commit()
709
710 ## Finally delete the ACL
711 self.deleteAcl(board.aclid)
712
713
714 self.logEntry(self.LOGNORMAL,"DELETE/BOARD","BOARDID:" + str(boardid),"Board deleted: %s " % (str(boardname)))
715
716 def deleteBoardByBoardname(self, name):
717 return self.deleteBoardByBoardid(self.getBoardid(name))
718
719
720 def createBoard(self, name,
721 description,
722 owner,
723 externalname,
724 minreadlevel,
725 minpostlevel,
726 minoplevel,
727 network = "usenet",
728 aclid = None):
729 #### creates a board
730 myBoard = Board()
731 myBoard.name = name
732 myBoard.description = description
733 myBoard.owner = owner
734 myBoard.externalname = externalname
735 myBoard.minreadlevel = minreadlevel
736 myBoard.minpostlevel = minpostlevel
737 myBoard.minoplevel = minoplevel
738 myBoard.network = network
739
740 myAcl = self.createAcl()
741 myAcl.description="BOARD: "+name
742 self.getSession().add(myAcl)
743 self.getSession().commit()
744 aclid = myAcl.id
745 myBoard.aclid = aclid
746 self.getSession().add(myBoard)
747 self.getSession().commit()
748 self.logEntry(self.LOGNORMAL,"CREATE/BOARD","BOARDID:" + str(myBoard.id),"Board created: %s " % (str(myBoard.name)))
749
750 return myBoard.id
751
752
753 def getBoardid(self,name):
754 for instance in self.getSession().query(Board).filter(Board.name == name):
755 return instance.id
756 return None
757
758### User functions start
759
760 def updateUserPosts(self):
761 if self.currentUser.messagesposted == None:
762 self.currentUser.messagesposted = 0
763 self.currentUser.messagesposted = self.currentUser.messagesposted + 1
764
765 self.getSession().add(self.currentUser)
766 self.getSession().commit()
767
768
769 def registerUser(self,answers):
770 ## FIXME chck for existing users #CRITICAL
771 theUser = Users()
772 theUser.fullidentity = answers["*IDENTITY"].strip()
773 theUser.username = answers["*NAME"].strip()
774 theAcl = self.createAcl(description="ACL for user " + theUser.username)
775 theUser.aclid = theAcl.id
776 theUser.password = answers["*PASSWORD"].strip()
777 theUser.realname = answers["*FIRST"].strip()
778 theUser.comment = answers["*ASK BACKGROUND"].strip()
779 theUser.timescalled = 0
780 theUser.datefirstlogin = datetime.now()
781 theUser.datefastlogin = datetime.now()
782 theUser.messagesposted = 0
783 theUser.accesslevel = 10 ## FIXME grab from static
784 theUser.fakelevel = "10"
785 theUser.transferprotocol = "K"
786 theUser.kbuploaded = 0
787 theUser.kbdownloaded = 0
788 theUser.datelastnewscan = None
789 theUser.externaleditor = "pico"
790 theUser.consoleeditor = "pico"
791 theUser.terminal = "vt100"
792 theUser.pagelength = 23
793 theUser.disablepagedmsgs = False
794 theUser.minutesontoday = -1
795 theUser.splashfile = None
796
797
798 self.storeUser(theUser)
799 self.currentUser = theUser
800 self.util.currentUser = theUser
801 self.logEntry(self.LOGNORMAL,"CREATE/USER",str(theUser.username),"Registered new user %s" % (str(theUser.username)))
802
803 return None
804
805
806 def storeUser(self,theUser):
807 theUser.username = theUser.username.lower()
808 self.getSession().add(theUser)
809 self.getSession().commit()
810 self.logEntry(self.LOGINFO,"STORE/USER",str(theUser.username),"Stored user record %s" % (str(theUser.username)))
811
812
813 def setPassword(self,password,user = None):
814 if user == None:
815 user = self.currentUser
816 user.password = password
817 self.logEntry(self.LOGNORMAL,"ALTER/USER/PASSWORD",str(user.username),"%s changed password" % (str(user.username)))
818 self.storeUser(user)
819
820 def setRealName(self,name,user = None):
821 if user == None:
822 user = self.currentUser
823 user.realname = name
824 self.logEntry(self.LOGNORMAL,"ALTER/USER/REALNAME",str(user.username),"%s changed real name" % (str(user.username)))
825 self.storeUser(user)
826
827 def setProtocol(self,p):
828 user = self.currentUser
829 user.transferprotocol = p
830 self.logEntry(self.LOGNORMAL,"ALTER/USER/PROTO",str(user.username),"%s changed page protocol " % (str(user.username)))
831 self.storeUser(user)
832
833
834 def setPageLength(self,i):
835 user = self.currentUser
836 user.pagelength = i
837 self.logEntry(self.LOGNORMAL,"ALTER/USER/ROWS",str(user.username),"%s changed page length " % (str(user.username)))
838 self.storeUser(user)
839
840 def setEditor(self,editor,user = None):
841 if user == None:
842 user = self.currentUser
843 user.externaleditor = editor
844 self.logEntry(self.LOGNORMAL,"ALTER/USER/EDITOR",str(user.username),"%s changed editor" % (str(user.username)))
845 self.storeUser(user)
846
847
848 def getUsers(self):
849 ## There will always be at least the system user, hopefully
850 return self.getSession().query(Users)
851
852 def getUser(self,username):
853 ##
854 rv = None
855 for theUser in self.getSession().query(Users).filter(Users.username == username.lower()):
856 rv = theUser
857 return rv
858
859 def userExists(self,name):
860 ##
861
862 found = False
863
864 for myUser in self.getSession().query(Users).filter(Users.username == name):
865 found = True
866# for myUser in self.getSession().query(Users).filter(func.lower(Users.realname) == func.lower(name)):
867# found = True
868 return found
869
870 def _getname(self,name): ## TODO
871 rv = None
872 for myUser in self.getSession().query(Users).filter(func.lower(Users.realname) == func.lower(name)):
873 rv = myUser.username
874 break
875 return rv
876
877
878 def getUsersOlderThan(self, date):
879 rv = []
880 for user in self.getSession().query(Users).filter(Users.datefastlogin <= date):
881 rv.append(user.username)
882 return rv
883
884
885 ## Deletes a message
886 ## Security is checked
887 def deleteUser(self,username):
888 ##
889 ## let's get the message first so that we can delete it's components
890 user = self.getUser(username)
891 if not user == None:
892 ## remember the acl id for the step after this
893 aclid = user.aclid
894 self.deleteAces(aclid)
895 ## delete the user itself
896 self.getSession().delete(user)
897
898 ## finally we delete the ACL
899 self.deleteAcl(aclid)
900 self.logEntry(self.LOGINFO,"DELETE/USER",str(username),"Deleted user: %s" % (str(username)))
901
902 self.deleteAcl(aclid)
903 self.getSession().commit()
904
905 return True
906
907
908
909### ACL functions start
910 def getAcl(self,aclid):
911 ##
912 theAcl = self.getSession().query(Acl).filter(Acl.id == aclid)[0]
913 return theAcl
914
915
916
917 def deleteAcl(self,aclid):
918 ## delete any ACEs under this id
919 self.deleteAces(aclid)
920
921 ## delete the ACL
922 for theAcl in self.getSession().query(Acl).filter(Acl.id == aclid):
923 self.getSession().delete(theAcl)
924 self.getSession().commit()
925 self.logEntry(self.LOGINFO,"DELETE/ACL","ACLID:" + str(aclid),"Deleted ACL: %s" % (str(aclid)))
926
927
928 def createAcl(self,description=""):
929 acl = Acl()
930 acl.description = description
931 self.getSession().add(acl)
932 self.getSession().commit()
933 self.logEntry(self.LOGINFO,"CREATE/ACL","ACLID:" + str(acl.id),"Created ACL: %s" % (str(acl.id)))
934 return acl
935
936 def getGroupAces(self,username,grantordeny):
937 memberships = self.getGroupMemberships(username)
938 rv = []
939 for membership in memberships:
940 group = self.getGroup(membership)
941 aclid = group.aclid
942 aces = self.getSession().query(Ace).filter(Ace.aclid==aclid).filter(Ace.grantordeny == grantordeny)
943 if not aces == None:
944 for myAce in aces:
945 rv.append([myAce.permission,username])
946 return rv
947
948 def getAces(self,aclid,grantordeny):
949 rv = []
950 aces = self.getSession().query(Ace).filter(Ace.aclid==aclid).filter(Ace.grantordeny == grantordeny)
951
952 if not aces == None:
953 for myAce in aces:
954 rv.append([myAce.permission,myAce.subjectid])
955 return rv
956
957 def getAclGrants(self,aclid):
958 return self.getAces(aclid,"GRANT")
959
960 def getAclDenies(self,aclid):
961 return self.getAces(aclid,"DENY")
962
963### ACE functions start
964
965 def deleteAces(self,aclid):
966 ##
967 for ace in self.getSession().query(Ace).filter(Ace.aclid == aclid):
968 aceid = ace.id
969 self.getSession().delete(ace)
970 self.getSession().commit()
971 self.logEntry(self.LOGINFO,"DELETE/ACE","ACEID:" + str(aceid),"Deleted ACE: %s" % (str(aceid)))
972
973
974
975 def dropGrant(self,acl, subject, object):
976 if self.isGranted(acl,subject,object):
977 for myAce in self.getSession().query(Ace).filter(Ace.aclid==acl.id).filter(Ace.subjectid==subject).filter(Ace.permission == object).filter(Ace.grantordeny == "GRANT"):
978 self.getSession().delete(myAce)
979 self.getSession().commit()
980 self.logEntry(self.LOGINFO,"ACE/DROPGRANT",str(subject),"Dropped grant %s to %s" % (str(object),str(subject)))
981
982
983 def dropDeny(self,acl, subject, object):
984 if self.isDenied(acl,subject,object):
985 for myAce in self.getSession().query(Ace).filter(Ace.aclid==acl.id).filter(Ace.subjectid==subject).filter(Ace.permission == object).filter(Ace.grantordeny == "DENY"):
986 self.getSession().delete(myAce)
987 self.getSession().commit()
988 self.logEntry(self.LOGINFO,"ACE/DROPDENY",str(subject),"Dropped deny %s to %s" % (str(object),str(subject)))
989
990
991 def grant(self,acl, subject, object):
992
993 ## FIXME: add checking of pre-existing ACE
994 ##
995 if self.isGranted(acl,subject,object):
996 return; ## This ACE already exists, we don't need to do anything
997 myAce = Ace()
998 myAce.aclid = acl.id
999 myAce.subjectid = subject
1000 myAce.permission = object
1001 myAce.grantordeny = "GRANT"
1002 self.getSession().add(myAce)
1003 self.getSession().commit()
1004 self.logEntry(self.LOGINFO,"ACE/GRANT",str(subject),"Granted %s to %s" % (str(object),str(subject)))
1005
1006
1007
1008
1009 def deny(self,acl, subject, object):
1010 ##
1011 if self.isDenied(acl,subject,object):
1012 return; ## This ACE already exists, we don't need to do anything
1013 myAce = Ace()
1014 myAce.aclid = acl.id
1015 myAce.subjectid = subject
1016 myAce.permission = object
1017 myAce.grantordeny = "DENY"
1018 self.getSession().add(myAce)
1019 self.getSession().commit()
1020 self.logEntry(self.LOGINFO,"ACE/DENY",str(subject),"Denied %s to %s" % (str(object),str(subject)))
1021
1022
1023
1024
1025 def isDenied(self,acl, subject, object, inherited=True):
1026 ## we check the given ACL as well as any group ACLs if inherited is set to true
1027 denied = False
1028 myAces = self.getAces(acl.id,"DENY")
1029 if inherited:
1030 groupAces = self.getGroupAces(subject,"DENY")
1031 for groupAce in groupAces:
1032 myAces.append(groupAce)
1033 for myAce in myAces:
1034 if myAce[0] == object and myAce[1] == subject:
1035 denied = True
1036 break
1037 return denied
1038
1039
1040
1041
1042
1043 def isGranted(self,acl, subject, object, inherited=True):
1044 ##
1045 granted = False
1046 myAces = self.getAces(acl.id,"GRANT")
1047 if inherited:
1048 groupAces = self.getGroupAces(subject,"GRANT")
1049 for groupAce in groupAces:
1050 myAces.append(groupAce)
1051
1052 for myAce in myAces:
1053 if myAce[0] == object and myAce[1] == subject:
1054 granted = True
1055 break
1056 return granted
1057
1058
1059
1060
1061### SRM
1062 def srmcheck(self,aclid,subject,object,minlevel=-1):
1063 passed = False;
1064
1065 ## Are we running in tool mode? If so, don't check security
1066 if self.toolMode:
1067 return True
1068
1069 ## If we're a sysop, we automatically get pass
1070 ## Let's load the user's own ACL to check
1071 user = self.getUser(subject)
1072 usersAcl = self.getAcl(user.aclid)
1073 ## The ACE is subject=user, object=SYSOP
1074 if self.isGranted(usersAcl,subject,"SYSOP"):
1075 ## User has sysop privs, let him pass
1076 # self.logEntry(self.LOGINFO,"SRM/SYSOP/PASS",str(subject),"SYSOP: %s for %s" % (str(object),str(subject)))
1077 return True
1078
1079 ## Non-sysop users go by the following:
1080 ## 1. If level is higher than minlevel then pass, otherwise...
1081 ## 2. If GRANT exists, then pass even if we don't meet the level (+VE override)
1082 ## 3. Unless a DENY exists, in which case you don't pass, even if 1&2 hold (-VE override)
1083 ##
1084 ## Should we look at the level (-1 = ignore)
1085 if minlevel > -1:
1086 ## Yes we should
1087 if self.currentUser.accesslevel >= minlevel:
1088 ## We're higher than the level, so we're good so far
1089 passed = True
1090
1091 acl = self.getAcl(aclid)
1092 if not acl == None:
1093 ## Check for any overriding grants?
1094 if self.isGranted(acl,subject,object):
1095 passed = True
1096 ## Check for any overriding deny's?
1097 if self.isDenied(acl,subject,object):
1098 passed = False
1099 # if passed:
1100 # self.logEntry(self.LOGINFO,"SRM/PASS",str(subject),"PASS: %s for %s" % (str(object),str(subject)))
1101 # else:
1102 # self.logEntry(self.LOGINFO,"SRM/FAIL",str(subject),"FAIL: %s for %s" % (str(object),str(subject)))
1103 return passed
1104
1105
1106
1107
1108### MessageText handling
1109
1110 def getMessagetexts(self, msgid):
1111 rv = []
1112 for msgtext in self.getSession().query(Messagetext).filter(Messagetext.messageid == msgid).order_by(Messagetext.id):
1113 rv.append(msgtext.msgtext)
1114 return rv
1115
1116
1117
1118
1119 def deleteMessagetexts(self,msgid):
1120 for msgText in self.getSession().query(Messagetext).filter(Messagetext.messageid == msgid):
1121 self.getSession().delete(msgText)
1122 self.getSession().commit()
1123
1124 def searchMessagetexts(self,text,boardname=None,searchSpecial = False):
1125 rv = []
1126 query = self.getSession().query(Messagetext).filter(Messagetext.msgtext.like('%' + text + '%'))
1127 for msgtext in query:
1128 msg = self.getMessage(msgtext.messageid)
1129
1130 boardOK = False
1131 if not boardname == None:
1132 board = self.getBoardByName(boardname)
1133 if (msg.boardid == board.id):
1134 boardOK = True
1135 else:
1136 board = self.getBoard(msg.boardid)
1137 if not board == None:
1138 if not searchSpecial:
1139 if not board.name.startswith('__'):
1140 boardOK = True
1141 else:
1142 boardOK = True
1143 if boardOK:
1144 rv.append(msg.id)
1145 return rv
1146
1147
1148### Message functions
1149
1150
1151
1152 def getTotalMessageCount(self):
1153 rv = self.getSession().query(Message).count()
1154 return rv
1155
1156 def getBoardMessageCount(self,boardname):
1157 theboard = self.getBoardByName(boardname)
1158 rv = self.getSession().query(Message).filter(Message.boardid == theboard.id).count()
1159 return rv
1160
1161 ## Returns the message object specified by msgid
1162 def getMessage(self,msgid,callback=None):
1163 ##
1164 if not callback == None:
1165 if not self.util == None:
1166 self.util.printn(".")
1167 theMessage = self.getSession().query(Message).filter(Message.id == msgid)[0]
1168 return theMessage
1169
1170
1171
1172 ## Returns message ids for the board id that have been psoted since date
1173 ## Checks security against current user if specified
1174 def getMessagesSince(self,board,date,checkSrm=True,callback=None):
1175 rv = []
1176 for msg in self.getSession().query(Message).filter(Message.boardid == board.id).filter(Message.sentdate > date).order_by(Message.sentdate):
1177 if not callback == None:
1178 if not self.util == None:
1179 self.util.printn("|")
1180
1181 if checkSrm:
1182 if self.srmcheck(msg.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
1183 rv.append(msg.id)
1184 else:
1185 rv.append(msg.id)
1186 return rv
1187
1188
1189 def getMessagesSinceCount(self,board,date):
1190 rv = self.getSession().query(Message).filter(Message.boardid == board.id).filter(Message.sentdate > date).count()
1191 return rv
1192
1193
1194 def getMessagesByBoardByToUsername(self,board,username,checkSrm = True):
1195 rv = []
1196 for msg in self.getSession().query(Message).filter(Message.boardid == board.id).filter(Message.toname == username):
1197 if checkSrm:
1198 if self.srmcheck(msg.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
1199 rv.append(msg.id)
1200 else:
1201 rv.append(msg.id)
1202 return rv
1203
1204 ## Returns message ids for the board id that have been posted by the given user
1205 ## Checks security against current user if specified
1206 def getMessagesByBoardByUsername(self,board,username,checkSrm = True):
1207 rv = []
1208 for msg in self.getSession().query(Message).filter(Message.boardid == board.id).filter(Message.fromname == username):
1209 if checkSrm:
1210 if self.srmcheck(msg.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
1211 rv.append(msg.id)
1212 else:
1213 rv.append(msg.id)
1214 return rv
1215
1216
1217
1218
1219 ## Returns all message ids for the board id
1220 ## Does NOT check security (why? FIXME?)
1221 def getMessagesByBoardid(self,boardid):
1222 ##
1223 rv = []
1224 for instance in self.getSession().query(Message).filter(Message.boardid == boardid).order_by(Message.sentdate):
1225 rv.append(instance.id)
1226 return rv
1227
1228
1229 ## Returns all message ids for the board NAME
1230 ## Checks security against current user if specified
1231 def getMessagesByBoardname(self,name,checkSrm=True):
1232 ##
1233 board = self.getBoardByName(name)
1234 boardid = board.id
1235 rv = []
1236 for msg in self.getSession().query(Message).filter(Message.boardid == board.id).order_by(Message.sentdate):
1237 if checkSrm:
1238 if self.srmcheck(msg.aclid,self.currentUser.username,"READ",minlevel=board.minreadlevel):
1239 rv.append(msg.id)
1240 else:
1241 rv.append(msg.id)
1242 return rv
1243
1244
1245
1246 ## Returns all message ids with to=username
1247 ## Checks security against username if specified
1248 def getMessagesByUser(self,username,checkSrm=True):
1249 ##
1250 rv = []
1251
1252 for instance in self.getSession().query(Message).filter(func.lower(Message.toname) == func.lower(username)).order_by(Message.sentdate):
1253 if not self.toolMode:
1254 if checkSrm:
1255 if instance:
1256 if self.srmcheck(instance.aclid,username,"READ"):
1257 rv.append(instance.id)
1258 else:
1259 rv.append(instance.id)
1260 return rv
1261
1262
1263 def getMailMessagesByUser(self,username,checkSrm=True):
1264 ##
1265 rv = []
1266 board = self.getBoardByName("__pyffle_email")
1267 for instance in self.getSession().query(Message).filter(Message.boardid == board.id).filter(func.lower(Message.toname) == func.lower(username)).order_by(Message.sentdate):
1268 if not self.toolMode:
1269 if checkSrm:
1270 if instance:
1271 if self.srmcheck(instance.aclid,username,"READ"):
1272 rv.append(instance.id)
1273 else:
1274 rv.append(instance.id)
1275 return rv
1276
1277
1278
1279
1280 def getMessagesAuthoredByUser(self,username,checkSrm=True):
1281 ##
1282 rv = []
1283 for instance in self.getSession().query(Message).filter(Message.fromname == username).order_by(Message.sentdate):
1284 if checkSrm:
1285 if self.srmcheck(instance.aclid,username,"READ"):
1286 rv.append(instance.id)
1287 else:
1288 rv.append(instance.id)
1289 return rv
1290
1291
1292
1293 def searchMessageSubjects(self,text,boardname=None,searchSpecial=False):
1294 rv = []
1295 query = self.getSession().query(Message).filter(Message.subject.like('%' + text + '%'))
1296 for msg in query:
1297 boardOK = False
1298 if not boardname == None:
1299 board = self.getBoardByName(boardname)
1300 if (msg.boardid == board.id):
1301 boardOK = True
1302 else:
1303 board = self.getBoard(msg.boardid)
1304 if not board == None:
1305 if not searchSpecial:
1306 if not board.name.startswith('__'):
1307 boardOK = True
1308 else:
1309 boardOK = True
1310 if boardOK:
1311 rv.append(msg.id)
1312 return rv
1313
1314
1315 def getNewMessagesAllUsers(self,boardname='__pyffle_email'):
1316 rv = []
1317 board = self.getBoardByName(boardname)
1318 for msg in self.getSession().query(Message).filter(Message.boardid == board.id).filter(Message.readbyrecipient == False):
1319 rv.append(msg.id)
1320 return rv
1321
1322 ## Returns all unread message ids with to=user (object)
1323 ## Checks security against user if specified
1324 def getNewMessages(self,user=None,boardname='__pyffle_email',checkSrm=False):
1325 if user == None:
1326 user = self.currentUser
1327 rv = []
1328 board = self.getBoardByName(boardname)
1329 msgids = []
1330 msgids1 = self.getSession().query(Message).filter(Message.boardid == board.id).filter(func.lower(Message.toname) == func.lower(user.username)).filter(Message.readbyrecipient == False).all()
1331 msgids2 = self.getSession().query(Message).filter(Message.boardid == board.id).filter(func.lower(Message.toname) == func.lower(user.realname)).filter(Message.readbyrecipient == False).all()
1332 msgids = msgids1 + msgids2
1333 for msg in msgids:
1334 if checkSrm:
1335 if self.srmcheck(msg.aclid,user.username,"READ"):
1336 rv.append(msg.id)
1337 else:
1338 rv.append(msg.id)
1339 return rv
1340
1341
1342
1343 ## Marks the message read
1344 ## Security is not checked
1345 def setMessageRead(self,msgid):
1346 theMessage = self.getMessage(msgid)
1347 ## FIXME read receipt
1348 theMessage.readbyrecipient = True
1349 self.getSession().add(theMessage)
1350 self.getSession().commit()
1351 self.logEntry(self.LOGINFO,"READ/MESSAGE","MSGID:" + str(msgid),"Recipient read msg: %s" % (str(msgid)))
1352
1353
1354
1355
1356
1357 ## Deletes a message
1358 ## Security is checked
1359 def deleteMessage(self,msgid):
1360 ##
1361 ## let's get the message first so that we can delete it's components
1362 msg = self.getMessage(msgid)
1363 if not msg == None:
1364 ## are we allowd to delete this message? if not, error out
1365 ## first check if it's a forum posting
1366 minlevel = -1
1367 board = self.getBoard(msg.boardid)
1368
1369 if not board == None:
1370 if not board.name.startswith('__'):
1371 ## it's a forum post, get the minimum operator level for the board
1372 minlevel = board.minoplevel
1373
1374 if not self.toolMode:
1375 if not self.srmcheck(msg.aclid,self.currentUser.username,"DELETE",minlevel):
1376 self.util.println("You are not allowed to delete this message")
1377 return False
1378
1379 ## now delete any text
1380 self.deleteMessagetexts(msgid)
1381
1382 ## remember the acl id for the step after this
1383 aclid = msg.aclid
1384
1385 ## delete the message itself
1386 self.getSession().delete(msg)
1387 self.getSession().commit()
1388
1389 ## finally we delete the ACL
1390 self.deleteAcl(aclid)
1391 self.logEntry(self.LOGINFO,"DELETE/MESSAGE","MSGID:" + str(msgid),"Deleted Message: %s" % (str(msgid)))
1392
1393 return True
1394
1395
1396
1397 ## Deletes ALL messages in the system
1398 ## Security is not checked
1399 def clearMessages(self):
1400 ##
1401 for instance in self.getSession().query(Message).order_by(Message.id):
1402 self.util.debugln("Deleting MsgID: " + str(instance.id) + " received at " + str(instance.sentdate))
1403 self.deleteMessage(instance.id)
1404
1405
1406
1407
1408
1409 ## Debug function
1410 def _dumpMessages(self):
1411
1412 if True: ##self.util.DEBUG:
1413 for instance in self.getSession().query(Message).order_by(Message.id):
1414 self.util.debugln("MsgID: " + str(instance.id) + " received at " + str(instance.sentdate))
1415 self.util.debugln("From: " + str(instance.fromname).strip())
1416 self.util.debugln("To: " + str(instance.toname).strip())
1417 self.util.debugln("Subj: " + str(instance.subject))
1418 self.util.debugln("--------------------------------------------------------------")
1419 for msgtext in self.getSession().query(Messagetext).filter(Messagetext.messageid == instance.id).order_by(Messagetext.id):
1420 self.util.debugln(msgtext.msgtext)
1421 self.util.debugln(" ")
1422
1423
1424
1425
1426
1427
1428 ## Creates a message (if board == __pyffle_email), with standard ACLs
1429 ## i.e. if recipient exists locally, grant him R+D. If remote, passes to MTAs
1430 ##
1431 ## If board != __pyffle_email, we just post the message with an empty ACL on
1432 ## the relevant board
1433 ##
1434 ## Security is not checked prior to creating - callers should establish posting/mailing
1435 ## privs
1436 def createMessage(self,fromname,toname,subject,text,board='__pyffle_email'):
1437 ## FIXME - should support unicode, but for now we clean it
1438 import unicodedata
1439 text = unicodedata.normalize('NFKD', unicode(text)).encode('ascii','ignore')
1440 subject = unicodedata.normalize('NFKD', unicode(subject)).encode('ascii','ignore')
1441
1442 ## Find the board id for the requested board
1443 for emailBoard in self.getSession().query(Board).filter(Board.name==board).order_by(Board.id):
1444 pass
1445 emailId = emailBoard.id
1446 msgAcl = None
1447 ## If the recipient is local, setup the permissions for this message
1448 foundMTA = False
1449 mtaInstance = None
1450 if board == '__pyffle_email':
1451 ### This is an email, let's have a look at the address
1452 if self.userExists(toname):
1453 ## Got a local user, great, this will be simple
1454 ## Create an ACL for the message now, we know the recipient is good
1455 msgAcl = self.createAcl()
1456 ## Ensure the recipient can read and delete this message
1457 self.grant(msgAcl,toname,"READ")
1458 self.grant(msgAcl,toname,"DELETE")
1459 self.util.debugln("Recipient can R/D: " + str(self.isGranted(msgAcl,toname,"READ")) + str(self.isGranted(msgAcl,toname,"DELETE")))
1460 else:
1461 ## OK, the user is remote - let's see if anybody will accept delivery
1462 self.util.debugln("User not local, checking MTAs")
1463 for mtaName in self.mtaModules.keys():
1464 ## Loop through our MTAs, instantiating each one until
1465 ## we get one that accepts the address
1466 mta = self.mtaModules[mtaName]
1467 mtaInstance = mta.PyffleMta()
1468 mtaInstance.data = self
1469 mtaInstance.currentUser = self.currentUser
1470 if (mtaInstance.matchAddress(toname)):
1471 ## Cool, this MTA likes the address, let's jump out of this
1472 self.util.debugln("Sending using " + mtaName)
1473 foundMTA = True
1474 break
1475
1476 if foundMTA:
1477 ## We need to create an ACL for the message's local copy
1478 msgAcl = self.createAcl()
1479 else:
1480 ## Address doesn't appear to be valid - bork, return without sending or storing anything
1481 self.util.debugln("Destination address invalid, no MTA will accept destination")
1482 return None
1483 else:
1484 ## Not an email, no need to deal with the addressing bruhaha above...
1485 ## Still need an ACL though
1486 ## So we grab the board's acl and use it for the message
1487 ## FIXME - make a COPY of the board's ACL
1488 msgAcl = self.createAcl()
1489
1490 ## Finally build the message itself.
1491 ##
1492 msg = Message()
1493 msg.fromname = fromname
1494 msg.toname = toname
1495 msg.subject = subject
1496 msg.aclid = msgAcl.id
1497 msg.boardid = emailId
1498 msg.sentdate = datetime.now()
1499 msg.readbyrecipient = False
1500 self.getSession().add(msg)
1501 self.getSession().commit()
1502 self.logEntry(self.LOGNORMAL,"CREATE/MESSAGE","MSGID:"+str(msg.id),"Created Message: %s" % (str(msg.id)))
1503
1504 ## Now the message text
1505 msgtext = Messagetext()
1506 msgtext.messageid = msg.id
1507 msgtext.aclid = msgAcl.id
1508 msgtext.msgtext = text
1509 self.getSession().add(msgtext)
1510 self.getSession().commit()
1511 self.logEntry(self.LOGNORMAL,"CREATE/MESSAGETEXT","MSGTEXTID:"+str(msgtext.id),"Created Messagetext: %s" % (str(msgtext.id)))
1512
1513 ## Finally if this is an external message, pass it to the MTA
1514 if foundMTA:
1515 mtaInstance.sendMessage(msg)
1516 self.logEntry(self.LOGNORMAL,"SEND/MESSAGE","MSGID:"+str(msg.id),"Sent Message: %s using %s" % (str(msg.id),str(mtaInstance.getName())))
1517
1518 return msg.id