kopano-mailbox-permissions 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. #!/usr/bin/env python
  2. """
  3. Set the delegate permissions of a Kopano mailbox
  4. Script prerequisites:
  5. - kopano package installed
  6. - python-mapi package installed
  7. """
  8. import os
  9. import time
  10. import locale
  11. import sys
  12. import getopt
  13. try:
  14. import __builtin__
  15. except ImportError:
  16. import builtins
  17. import codecs
  18. if sys.version_info < (3,):
  19. # Wrap sys.stdout into a StreamWriter to allow writing unicode.
  20. sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
  21. try:
  22. import MAPI
  23. from MAPI.Util import *
  24. from MAPI.Time import *
  25. from MAPI.Struct import *
  26. except ImportError as e:
  27. print("Not all modules can be loaded. The following modules are required:")
  28. print("- MAPI (Kopano)")
  29. print("")
  30. print(e)
  31. sys.exit(1)
  32. if sys.version_info > (3,):
  33. long = int
  34. verbose = False
  35. ecRightsNone = 0x00000000
  36. ecRightsReadAny = 0x00000001
  37. ecRightsCreate = 0x00000002
  38. ecRightsEditOwned = 0x00000008
  39. ecRightsDeleteOwned = 0x00000010
  40. ecRightsEditAny = 0x00000020
  41. ecRightsDeleteAny = 0x00000040
  42. ecRightsCreateSubfolder = 0x00000080
  43. ecRightsFolderAccess = 0x00000100
  44. ecRightsFolderVisible = 0x00000400
  45. ecRightsFullControl = long(0x000004FB)
  46. ecRightsTemplateNoRights = ecRightsFolderVisible
  47. ecRightsTemplateReadOnly = ecRightsTemplateNoRights | ecRightsReadAny
  48. ecRightsTemplateSecretary = ecRightsTemplateReadOnly | ecRightsCreate | ecRightsEditOwned | ecRightsDeleteOwned | ecRightsEditAny | ecRightsDeleteAny
  49. ecRightsTemplateOwner = ecRightsTemplateSecretary | ecRightsCreateSubfolder | ecRightsFolderAccess
  50. EMS_AB_ADDRESS_LOOKUP = 0x1
  51. ########################
  52. # Util functions
  53. class MyTable(object):
  54. def __init__(self):
  55. self.column = []
  56. self.align = []
  57. self.rows = []
  58. self.maxwith= {}
  59. def getTable(self):
  60. cols = len(self.column)
  61. header = u''
  62. line = u''
  63. for i in range(0, cols):
  64. header += (u"| %-"+str(self.maxwith[i]) + u"s ") % self.column[i]
  65. header += u"|"
  66. line = u'+'
  67. for i in range(0, cols ):
  68. if i == 0:
  69. line += ((self.maxwith[i]+2) * u'-')
  70. else:
  71. line += ((self.maxwith[i]+3) * u'-')
  72. header += u'\n'
  73. line += u'+\n'
  74. data = u''
  75. for i in range(0, len(self.rows)):
  76. for j in range(0, cols ):
  77. data += (u"| %-"+str(self.maxwith[j]) + u"s ") % self.rows[i][j]
  78. data += '|\n'
  79. return line + header + line + data + line
  80. def addColumns(self, columns):
  81. for column in columns:
  82. self.addColumn(column)
  83. def addColumn(self, column, alignment='c'):
  84. col = len(self.column)
  85. self.column.append(column)
  86. self.align.append(alignment)
  87. self.maxwith[col] = len(column)
  88. def addRow(self, row):
  89. for i in range(0, len(row)):
  90. w = len(row[i])
  91. if w > self.maxwith[i]:
  92. self.maxwith[i] = w
  93. self.rows.append(row)
  94. # Get Kopano's global addressbook
  95. def getGlobalAddressbook(session):
  96. PR_EMS_AB_CONTAINERID = 0xFFFD0003
  97. abook = session.OpenAddressBook(0, None, 0)
  98. rootitem = abook.OpenEntry(None, None, MAPI_BEST_ACCESS)
  99. table = rootitem.GetHierarchyTable(MAPI_DEFERRED_ERRORS)
  100. rest = SAndRestriction(
  101. [SPropertyRestriction(RELOP_EQ, PR_EMS_AB_CONTAINERID, SPropValue(PR_EMS_AB_CONTAINERID, 0)),
  102. SOrRestriction([
  103. SPropertyRestriction(RELOP_EQ, PR_AB_PROVIDER_ID, SPropValue(PR_AB_PROVIDER_ID, 'AC21A95040D3EE48B319FBA753304425'.decode('hex'))),
  104. SPropertyRestriction(RELOP_EQ, PR_AB_PROVIDER_ID, SPropValue(PR_AB_PROVIDER_ID, 'DCA740C8C042101AB4B908002B2FE182'.decode('hex'))) ] )
  105. ])
  106. table.Restrict(rest, TBL_BATCH)
  107. table.SetColumns([PR_ENTRYID, PR_DISPLAY_TYPE, PR_OBJECT_TYPE], TBL_BATCH)
  108. rows = table.QueryRows(1, 0)
  109. if len(rows) == 0:
  110. raise MAPIError(MAPI_E_NOT_FOUND)
  111. gab = abook.OpenEntry(rows[0][0].Value, None, MAPI_BEST_ACCESS)
  112. return gab
  113. def getUserlistNormal(gab):
  114. users = []
  115. table = gab.GetContentsTable(0)
  116. users = buildUserList(table)
  117. if (len(users) == 0):
  118. raise MAPIError(MAPI_E_NOT_FOUND)
  119. return users
  120. def buildUserList(table):
  121. users = []
  122. table.Restrict(SPropertyRestriction(RELOP_EQ, PR_DISPLAY_TYPE, SPropValue(PR_DISPLAY_TYPE, DT_MAILUSER)), TBL_BATCH)
  123. table.SetColumns([PR_ACCOUNT_W], TBL_BATCH)
  124. while True:
  125. rows = table.QueryRows(50, 0)
  126. if len(rows) == 0:
  127. break
  128. for row in rows:
  129. if (PROP_TYPE(row[0].ulPropTag) != PT_ERROR and row[0].Value not in ['SYSTEM']):
  130. users.append(row[0].Value)
  131. return users
  132. def getUserListHosted(gab):
  133. users = []
  134. htable = gab.GetHierarchyTable(0)
  135. companies = htable.QueryRows(-1, 0)
  136. for company in companies:
  137. ceid = PpropFindProp(company, PR_ENTRYID)
  138. company = gab.OpenEntry(ceid.Value, None, 0)
  139. ctable = company.GetContentsTable(MAPI_DEFERRED_ERRORS)
  140. users.extend(buildUserList(ctable))
  141. return users
  142. def getUserList(session):
  143. users = []
  144. gab = getGlobalAddressbook(session)
  145. try:
  146. users = getUserlistNormal(gab)
  147. except MAPIError as err:
  148. if (err.hr == MAPI_E_NOT_FOUND):
  149. users = getUserListHosted(gab)
  150. else:
  151. raise
  152. return users
  153. def parsePermissionsToName(permissions):
  154. if (permissions == ecRightsFullControl):
  155. return u"fullcontrol"
  156. elif (permissions == ecRightsTemplateOwner):
  157. return u"owner"
  158. elif (permissions == ecRightsTemplateSecretary):
  159. return u"secretary"
  160. elif (permissions == ecRightsTemplateReadOnly):
  161. return u"readonly"
  162. elif (permissions == ecRightsTemplateNoRights):
  163. return u"norights"
  164. elif (permissions == ecRightsNone):
  165. return u"denied"
  166. return u"0x%04x" % permissions
  167. def parsePermissionsToId(permissions):
  168. if permissions == 'fullcontrol':
  169. return ecRightsFullControl
  170. elif permissions == 'owner':
  171. return ecRightsTemplateOwner
  172. elif permissions == 'secretary':
  173. return ecRightsTemplateSecretary
  174. elif permissions == 'readonly':
  175. return ecRightsTemplateReadOnly
  176. elif permissions == 'norights':
  177. return ecRightsTemplateNoRights
  178. # assume 'norights' if 'none' is passed
  179. elif permissions == 'none':
  180. return ecRightsTemplateNoRights
  181. elif permissions == 'denied':
  182. return ecRightsNone
  183. elif permissions[0:2] == '0x':
  184. return int(permissions, 16)
  185. raise ValueError("error unknown permission type %s" % permissions )
  186. # Get the mailbox of the given user
  187. def getStore(session, username):
  188. ema = GetDefaultStore(session).QueryInterface(IID_IExchangeManageStore)
  189. try:
  190. usereid = UserAccountsToEntryIDs(session, [username])[0][0]
  191. user = session.OpenEntry(usereid, None, 0)
  192. email = user.GetProps([PR_EMAIL_ADDRESS], 0)[0].Value
  193. storeid = ema.CreateStoreEntryID(None, email, 0)
  194. except MAPIError as err:
  195. if err.hr == MAPI_E_NOT_FOUND:
  196. print("Unable to find mailbox for user %s" % username)
  197. return
  198. raise
  199. return session.OpenMsgStore(0, storeid, IID_IMsgStore, MDB_WRITE | MAPI_DEFERRED_ERRORS)
  200. def boolText(b):
  201. if b:
  202. return "True"
  203. else:
  204. return "False"
  205. def UserAccountsToEntryIDs(session, users):
  206. gab = getGlobalAddressbook(session)
  207. addrlist = []
  208. flaglist = []
  209. for user in users:
  210. user = unicode(user)
  211. addrlist.append([SPropValue(PR_DISPLAY_NAME_W, user)])
  212. flaglist.append(MAPI_UNRESOLVED)
  213. # We need flag EMS_AB_ADDRESS_LOOKUP in kopano to avoid ambiguous users
  214. (rows, flags) = gab.ResolveNames([PR_DISPLAY_NAME_W, PR_ACCOUNT_W, PR_ENTRYID], MAPI_UNICODE | EMS_AB_ADDRESS_LOOKUP, addrlist, flaglist)
  215. # workaround until ZCP-10454 is merged
  216. if not isinstance(rows[0], list):
  217. rows = [rows]
  218. entryids = []
  219. errors = 0
  220. for i in xrange(0, len(rows)):
  221. if flags[i] == MAPI_UNRESOLVED or flags[i] == MAPI_AMBIGUOUS:
  222. errors += 1
  223. print("unable to resolve user '%s' with flags 0x%08x" % (users[i], flags[i]))
  224. continue
  225. row = rows[i]
  226. entryid = PpropFindProp(row, PR_ENTRYID)
  227. account = PpropFindProp(row, PR_ACCOUNT_W)
  228. username= PpropFindProp(row, PR_DISPLAY_NAME_W)
  229. if not account or account.ulPropTag != PR_ACCOUNT_W:
  230. print("error missing PR_ACCOUNT for name %s" % users[i])
  231. errors += 1
  232. continue
  233. elif not entryid or entryid.ulPropTag != PR_ENTRYID:
  234. print("error missing PR_ENTRYID for name %s" % users[i])
  235. errors += 1
  236. continue
  237. elif not username or username.ulPropTag != PR_DISPLAY_NAME_W:
  238. print("error missing PR_DISPLAY_NAME for name %s" % users[i])
  239. errors += 1
  240. continue
  241. entryids.append([entryid.Value, username.Value])
  242. if errors > 0:
  243. raise MAPIError(MAPI_E_NOT_FOUND)
  244. return entryids
  245. def RebuildFreeBusyEntries(store):
  246. print("Rebuild freebusy entries not implemented")
  247. def setMailboxPermissions(session, listfolderrights, members, mailboxes, privateflag, sendcopy, onlySendToDelegates):
  248. inboxtags = [PR_IPM_APPOINTMENT_ENTRYID,
  249. PR_IPM_CONTACT_ENTRYID,
  250. PR_IPM_JOURNAL_ENTRYID,
  251. PR_IPM_NOTE_ENTRYID,
  252. PR_IPM_TASK_ENTRYID,
  253. PR_FREEBUSY_ENTRYIDS,
  254. ]
  255. ab = session.OpenAddressBook(0, None, MAPI_UNICODE)
  256. # build permission items
  257. for mailbox in mailboxes:
  258. # open store
  259. store = getStore(session, mailbox)
  260. # get inbox
  261. inboxid = store.GetReceiveFolder('IPM', 0)[0]
  262. inbox = store.OpenEntry(inboxid, None, MAPI_MODIFY)
  263. # Get the default properties from the inbox
  264. inboxprops = inbox.GetProps(inboxtags, 0)
  265. if inboxprops[5].ulPropTag != PR_FREEBUSY_ENTRYIDS or len(inboxprops[5].Value) < 4:
  266. RebuildFreeBusyEntries(store)
  267. try:
  268. # test Freebusy data folder
  269. fbdatafolder = store.OpenEntry(inboxprops[5].Value[3], None, MAPI_MODIFY)
  270. del fbdatafolder
  271. except MAPIError as err:
  272. print("Free busy data folder does not exist, error: 0x%x" % err.hr)
  273. sys.exit(1)
  274. try:
  275. fbmsg = store.OpenEntry(inboxprops[5].Value[1], None, MAPI_MODIFY)
  276. except MAPIError as err:
  277. print("Free busy message does not exist, error: 0x%x" % err.hr)
  278. sys.exit(1)
  279. try:
  280. fbmsgOl2k = store.OpenEntry(inboxprops[5].Value[0], None, MAPI_MODIFY)
  281. except:
  282. #ignore errors, we dont care
  283. fbmsgOl2k = None
  284. pass
  285. fbrights = ecRightsNone
  286. # Store permissions
  287. setMailboxFolderPermissions(store, None, members, listfolderrights.get('store', None))
  288. # Calendar
  289. setMailboxFolderPermissions(store, inboxprops[0].Value, members, listfolderrights.get('calendar', ecRightsNone))
  290. if listfolderrights.get('calendar'):
  291. # if we have write access, give correct permission on fb
  292. if listfolderrights.get('calendar', ecRightsNone) & (ecRightsCreate|ecRightsEditOwned|ecRightsDeleteOwned|ecRightsEditAny|ecRightsDeleteAny) != ecRightsNone:
  293. fbrights = ecRightsTemplateSecretary
  294. else:
  295. fbrights = listfolderrights.get('calendar', ecRightsNone)
  296. # Contacts
  297. setMailboxFolderPermissions(store, inboxprops[1].Value, members, listfolderrights.get('contacts', ecRightsNone))
  298. # Notes
  299. setMailboxFolderPermissions(store, inboxprops[3].Value, members, listfolderrights.get('notes', ecRightsNone))
  300. # Tasks
  301. setMailboxFolderPermissions(store, inboxprops[4].Value, members, listfolderrights.get('tasks', ecRightsNone))
  302. # Inbox
  303. setMailboxFolderPermissions(store, inboxid, members, listfolderrights.get('inbox', ecRightsNone))
  304. if fbrights == ecRightsNone and listfolderrights.get('inbox', ecRightsNone) != ecRightsNone:
  305. fbrights = ecRightsTemplateReadOnly; # need atleast read-only rights to be a delegate
  306. # Journal
  307. setMailboxFolderPermissions(store, inboxprops[2].Value, members, listfolderrights.get('journal', ecRightsNone))
  308. # update freebusy data
  309. fbProps = fbmsg.GetProps([PR_SCHDINFO_DELEGATE_ENTRYIDS, CHANGE_PROP_TYPE(PR_SCHDINFO_DELEGATE_NAMES, PT_MV_UNICODE), PR_DELEGATE_FLAGS], 0)
  310. fbProps = updateDelegateEntries(ab, fbProps, members, privateflag)
  311. fbmsg.SetProps(fbProps)
  312. fbmsg.SaveChanges(0)
  313. if fbmsgOl2k:
  314. fbmsgOl2k.SetProps(fbProps)
  315. fbmsgOl2k.SaveChanges(0)
  316. # Set permissions on the Freebusy Data folder else outlook will give errors
  317. setMailboxFolderPermissions(store, inboxprops[5].Value[3], members, fbrights)
  318. if sendcopy != None or onlySendToDelegates != None:
  319. drd = DelegateRuleData(session, store, inbox)
  320. if (onlySendToDelegates != None):
  321. drd.setOnlySendToDelegates(onlySendToDelegates)
  322. if sendcopy == True and listfolderrights.get('calendar', ecRightsNone) > ecRightsNone:
  323. memberids = []
  324. for member in members:
  325. memberids.append(member[0])
  326. drd.addDelegators(memberids)
  327. drd.saveChanges()
  328. print("Permissions set on mailbox '%s'" % mailbox)
  329. class DelegateRuleData(object):
  330. userprops = [PR_ENTRYID, PR_ADDRTYPE_W, PR_EMAIL_ADDRESS_W, PR_DISPLAY_NAME_W, PR_SEARCH_KEY, PR_SMTP_ADDRESS_W, PR_OBJECT_TYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TYPE]
  331. delegateruleres = SAndRestriction([SPropertyRestriction(RELOP_EQ, PR_RULE_PROVIDER, SPropValue(PR_RULE_PROVIDER, "Schedule+ EMS Interface"))])
  332. def __init__(self, session, store, inbox):
  333. self.session = session
  334. self.store = store
  335. self.inbox = inbox
  336. self.onlysendtodelegates = False
  337. self.usermap = {}
  338. self.dirty = False
  339. self.rulekey = None
  340. self.replace = False
  341. self._load()
  342. def _load(self):
  343. self.gab = getGlobalAddressbook(self.session)
  344. self.rulesemt = self.inbox.OpenProperty(PR_RULES_TABLE, IID_IExchangeModifyTable, 0, 0)
  345. self.table = self.rulesemt.GetTable(MAPI_UNICODE)
  346. self.table.SetColumns([PR_RULE_ACTIONS, PR_RULE_ID], TBL_BATCH)
  347. try:
  348. self.table.FindRow(self.delegateruleres, BOOKMARK_BEGINNING, TBL_BATCH)
  349. except MAPIError as err:
  350. if err.hr == MAPI_E_NOT_FOUND:
  351. # no rule exists
  352. return
  353. rows = self.table.QueryRows(1,0)
  354. if len(rows) == 1:
  355. if rows[0][0].ulPropTag == PR_RULE_ACTIONS:
  356. if rows[0][0].Value.lpAction[0].acttype == ACTTYPE.OP_DELEGATE:
  357. # Get the Delegate users
  358. for addrentry in rows[0][0].Value.lpAction[0].actobj.lpadrlist:
  359. entryid = PpropFindProp(addrentry, PR_ENTRYID)
  360. if entryid:
  361. self.usermap[entryid.Value] = addrentry
  362. if len(rows[0][0].Value.lpAction) >= 2 and rows[0][0].Value.lpAction[1].acttype == ACTTYPE.OP_DELETE:
  363. self.onlysendtodelegates = True
  364. if rows[0][1].ulPropTag == PR_RULE_ID:
  365. self.rulekey = rows[0][1]
  366. def getOnlySendToDelegates(self):
  367. return self.onlysendtodelegates
  368. def setOnlySendToDelegates(self, enable):
  369. self.onlysendtodelegates = enable
  370. self.dirty = True
  371. def isDelegate(self, entryid):
  372. return entryid in self.usermap
  373. def addDelegators(self, memberids):
  374. for memberid in memberids:
  375. userdata = self._getUserProperties(memberid)
  376. self.usermap[memberid] = userdata
  377. self.dirty = True
  378. def _getUserProperties(self, entryid):
  379. user = self.gab.OpenEntry(entryid, None, MAPI_BEST_ACCESS)
  380. return user.GetProps(self.userprops, MAPI_UNICODE)
  381. def removeAllDelegators(self):
  382. self.usermap = {}
  383. self.replace = True
  384. self.dirty = True
  385. def saveChanges(self):
  386. if self.dirty == False:
  387. return
  388. columns = self.table.QueryColumns(TBL_ALL_COLUMNS)
  389. self.table.SetColumns(columns, TBL_BATCH)
  390. try:
  391. self.table.FindRow(self.delegateruleres, BOOKMARK_BEGINNING, TBL_BATCH)
  392. rows = self.table.QueryRows(1,0)
  393. except MAPIError as err:
  394. if err.hr != MAPI_E_NOT_FOUND:
  395. raise
  396. rows = []
  397. rulerows = []
  398. if len(self.usermap) == 0:
  399. if len(rows) == 1:
  400. rulerows = [ROWENTRY(ROW_REMOVE, [PpropFindProp(rows[0], PR_RULE_ID)])]
  401. else:
  402. if len(rows) == 1:
  403. row = rows[0]
  404. for i in range(len(row) - 1, 0, -1):
  405. if row[i].ulPropTag in (PR_RULE_ACTIONS, PR_RULE_CONDITION):
  406. row.pop(i)
  407. else:
  408. row = [ SPropValue(PR_RULE_LEVEL, 0),
  409. SPropValue(PR_RULE_NAME, "Delegate Meetingrequest service"),
  410. SPropValue(PR_RULE_PROVIDER, "Schedule+ EMS Interface"),
  411. SPropValue(PR_RULE_SEQUENCE, 0),
  412. SPropValue(PR_RULE_STATE, 1),
  413. SPropValue(PR_RULE_PROVIDER_DATA, ''),
  414. ]
  415. actions = []
  416. actions.append(ACTION( ACTTYPE.OP_DELEGATE, 0, None, None, 0, actFwdDelegate(self.usermap.values())))
  417. if self.onlysendtodelegates:
  418. actions.append(ACTION( ACTTYPE.OP_DELETE, 0, None, None, 0, None))
  419. row.append(SPropValue(PR_RULE_ACTIONS, ACTIONS(1, actions)))
  420. cond = SAndRestriction([SContentRestriction(FL_PREFIX, PR_MESSAGE_CLASS_W, SPropValue(PR_MESSAGE_CLASS_W, u"IPM.Schedule.Meeting")),
  421. SNotRestriction( SExistRestriction(PR_DELEGATED_BY_RULE) ),
  422. SOrRestriction([SNotRestriction( SExistRestriction(PR_SENSITIVITY)),
  423. SPropertyRestriction(RELOP_NE, PR_SENSITIVITY, SPropValue(PR_SENSITIVITY, 2))])
  424. ])
  425. row.append(SPropValue(PR_RULE_CONDITION, cond))
  426. rulerows = [ROWENTRY(ROW_MODIFY, row)]
  427. if (len(rulerows) > 0):
  428. flags = 0
  429. if self.replace == True:
  430. flags = ROWLIST_REPLACE
  431. self.rulesemt.ModifyTable(flags, rulerows)
  432. def updateDelegateEntries(ab, fbProps, members, privateflag):
  433. if fbProps[0].ulPropTag != PR_SCHDINFO_DELEGATE_ENTRYIDS:
  434. fbProps[0].Value = []
  435. fbProps[0].ulPropTag = PR_SCHDINFO_DELEGATE_ENTRYIDS
  436. fbProps[1].Value = []
  437. fbProps[1].ulPropTag = CHANGE_PROP_TYPE(PR_SCHDINFO_DELEGATE_NAMES, PT_MV_UNICODE)
  438. fbProps[2].Value = []
  439. fbProps[2].ulPropTag = PR_DELEGATE_FLAGS
  440. for member in members:
  441. found = False
  442. for i in range (0, len(fbProps[0].Value)):
  443. if ab.CompareEntryIDs(fbProps[0].Value[i], member[0], 0) == True:
  444. fbProps[1].Value[i] = member[1]
  445. fbProps[2].Value[i] = privateflag
  446. found = True
  447. break
  448. if found == False:
  449. fbProps[0].Value.append(member[0])
  450. fbProps[1].Value.append(member[1])
  451. fbProps[2].Value.append(privateflag)
  452. return fbProps
  453. def setMailboxFolderPermissions(store, entryid, members, rights):
  454. if entryid == None:
  455. folder = store
  456. else:
  457. folder = store.OpenEntry(entryid, None, MAPI_MODIFY)
  458. aclemt = folder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, 0)
  459. rows = []
  460. for member in members:
  461. if rights != None:
  462. rows.append(ROWENTRY(ROW_ADD, [SPropValue(PR_MEMBER_ENTRYID, member[0]), SPropValue(PR_MEMBER_RIGHTS, rights)]))
  463. else:
  464. pass
  465. if len(rows) > 0:
  466. aclemt.ModifyTable(0, rows)
  467. def validatePermissionsAndConvert(permissions):
  468. for (folder, right) in permissions.iteritems():
  469. permissions[folder] = parsePermissionsToId(right.lower())
  470. return permissions
  471. def parseToBool(b):
  472. if b and b.lower()[0] in ('y','t', '1'): # Yes, True, 1
  473. return True
  474. else: # no, false, 0
  475. return False
  476. def removeFolderPermissions(folder):
  477. aclemt = folder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY)
  478. acltable = aclemt.GetTable(MAPI_UNICODE)
  479. acltable.SetColumns([PR_MEMBER_ID], TBL_BATCH)
  480. removelist = []
  481. while(True):
  482. rows = acltable.QueryRows(50, 0)
  483. if len(rows) == 0:
  484. break
  485. for row in rows:
  486. removelist.append(ROWENTRY(ROW_REMOVE, row))
  487. if len(removelist) > 0:
  488. aclemt.ModifyTable(0, removelist)
  489. def removeRecuriveFolderPermissions(session, store, entryid, depth):
  490. folder = store.OpenEntry(entryid, None, 0)
  491. removeFolderPermissions(folder)
  492. table = folder.GetHierarchyTable(0)
  493. table.SetColumns([PR_ENTRYID, PR_FOLDER_TYPE], TBL_BATCH)
  494. while(True):
  495. rows = table.QueryRows(50, 0)
  496. if len(rows) == 0:
  497. break
  498. for row in rows:
  499. if (row[1].Value == FOLDER_GENERIC):
  500. removeRecuriveFolderPermissions(session, store, row[0].Value, depth+1)
  501. def removeDelegatePermissions(session, store):
  502. # get inbox
  503. inboxid = store.GetReceiveFolder('IPM', 0)[0]
  504. inbox = store.OpenEntry(inboxid, None, MAPI_MODIFY)
  505. # Get the default properties from the inbox
  506. inboxprops = inbox.GetProps([PR_FREEBUSY_ENTRYIDS], 0)
  507. if inboxprops[0].ulPropTag != PR_FREEBUSY_ENTRYIDS or len(inboxprops[0].Value) < 2:
  508. return 0
  509. try:
  510. fbmsg = store.OpenEntry(inboxprops[0].Value[1], None, MAPI_MODIFY)
  511. except MAPIError as err:
  512. return 0
  513. # Remove delegate rules
  514. drd = DelegateRuleData(session, store, inbox)
  515. drd.removeAllDelegators()
  516. drd.saveChanges()
  517. fbmsg.DeleteProps([PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_SCHDINFO_DELEGATE_NAMES, PR_DELEGATE_FLAGS])
  518. fbmsg.SaveChanges(0)
  519. def removeAllPermissions(session, usersmailbox):
  520. with_errors = False
  521. for mailbox in usersmailbox:
  522. try:
  523. store = getStore(session, mailbox)
  524. if store == None:
  525. continue
  526. removeFolderPermissions(store)
  527. removeRecuriveFolderPermissions(session, store, None, 0)
  528. removeDelegatePermissions(session, store)
  529. except MAPIError as err:
  530. print("Unexpected error while processing store for user %s. hr=%08x" % (mailbox, err.hr))
  531. with_errors = True
  532. return with_errors
  533. #######################
  534. # Print functions
  535. # Print recursive
  536. def printFolderACLSTable(store, showall):
  537. pt = MyTable()
  538. pt.addColumns([u"Folder", u"Permissions"])
  539. printFolderACLs(pt, store, showall, 0)
  540. printFolderListACLs(pt, store, showall, None, 1)
  541. print('Folder permissions:')
  542. print(pt.getTable())
  543. def printFolderListACLs(printtable, store, showall, entryid, depth):
  544. folder = store.OpenEntry(entryid, None, 0)
  545. printFolderACLs(printtable, folder, showall, depth)
  546. table = folder.GetHierarchyTable(0)
  547. table.SetColumns([PR_ENTRYID, PR_FOLDER_TYPE], TBL_BATCH)
  548. while(True):
  549. rows = table.QueryRows(50, 0)
  550. if len(rows) == 0:
  551. break
  552. for row in rows:
  553. if (row[1].Value == FOLDER_GENERIC):
  554. printFolderListACLs(printtable, store, showall, row[0].Value, depth+1)
  555. # Print ACLS of one folder
  556. def printFolderACLs(printtable, folder, showall, depth):
  557. props = folder.GetProps([PR_DISPLAY_NAME_W], 0)
  558. if props[0].ulPropTag != PR_DISPLAY_NAME_W:
  559. props[0].Value = u"<UNKNOWN>"
  560. try:
  561. aclemt = folder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY)
  562. except:
  563. printtable.addRow([(depth*' ') + props[0].Value, u''])
  564. return
  565. acltable = aclemt.GetTable(MAPI_UNICODE)
  566. acltable.SetColumns([PR_ENTRYID, PR_MEMBER_ID, CHANGE_PROP_TYPE(PR_MEMBER_NAME, PT_UNICODE), PR_MEMBER_RIGHTS], TBL_BATCH)
  567. permissions = u''
  568. found = 0
  569. while(True):
  570. rows = acltable.QueryRows(50, 0)
  571. if len(rows) == 0:
  572. break
  573. for row in rows:
  574. if permissions != u'':
  575. permissions += u', '
  576. permissions += u"%s:%s" % (row[2].Value, parsePermissionsToName(row[3].Value))
  577. found+=1
  578. if (showall == False and found == 0):
  579. return
  580. printtable.addRow([(depth*' ') + props[0].Value, permissions])
  581. # Print the delegate information of a store
  582. def printDelegateInformation(session, store):
  583. # get inbox
  584. inboxid = store.GetReceiveFolder('IPM', 0)[0]
  585. inbox = store.OpenEntry(inboxid, None, MAPI_MODIFY)
  586. drd = DelegateRuleData(session, store, inbox)
  587. if drd.getOnlySendToDelegates() == True:
  588. onlysentodelegates = 'Enabled'
  589. else:
  590. onlysentodelegates = 'Disabled'
  591. # Get the default properties from the inbox
  592. inboxprops = inbox.GetProps([PR_FREEBUSY_ENTRYIDS], 0)
  593. if inboxprops[0].ulPropTag != PR_FREEBUSY_ENTRYIDS or len(inboxprops[0].Value) < 2:
  594. print("No delegate information exists")
  595. return 0
  596. try:
  597. fbmsg = store.OpenEntry(inboxprops[0].Value[1], None, MAPI_MODIFY)
  598. except MAPIError as err:
  599. print("No delegate information exists")
  600. return 0
  601. fbProps = fbmsg.GetProps([PR_SCHDINFO_DELEGATE_ENTRYIDS, CHANGE_PROP_TYPE(PR_SCHDINFO_DELEGATE_NAMES, PT_MV_UNICODE), PR_DELEGATE_FLAGS], 0)
  602. pt = MyTable()
  603. pt.addColumns(["Username", "See private", "Send copy"])
  604. if fbProps[0].ulPropTag == PR_SCHDINFO_DELEGATE_ENTRYIDS:
  605. for i in range(0, len(fbProps[0].Value)):
  606. sendcopy = drd.isDelegate(fbProps[0].Value[i])
  607. pt.addRow([fbProps[1].Value[i], boolText(fbProps[2].Value[i]), boolText(sendcopy)])
  608. else:
  609. pt.addRow(["-", "-", "-"])
  610. print("Delegate information:")
  611. print(pt.getTable())
  612. print("")
  613. print('Send meeting requests and response only to the delegator, not to the mailbox owner. [%s]' % (onlysentodelegates))
  614. print("")
  615. # Print permissions of a mailbox of one or more users
  616. def printUserMailBoxPermissions(session, showall, users):
  617. with_errors = False
  618. for user in users:
  619. try:
  620. store = getStore(session, user)
  621. if store == None:
  622. continue
  623. print('')
  624. print('Store information %s' % user)
  625. printDelegateInformation(session, store)
  626. printFolderACLSTable(store, showall)
  627. except MAPIError as err:
  628. print("Unexpected error while processing store for user %s. hr=%08x" % (user, err.hr))
  629. with_errors = True
  630. return with_errors
  631. def print_help():
  632. print("Usage: %s [ACTION] [mailboxes...]" % sys.argv[0])
  633. print("")
  634. print("Manage mailbox delegate permissions")
  635. print("")
  636. print("Actions:")
  637. print(" --update-delegate usernames Add or update users or groups who get the permissions for the given mailbox.")
  638. # print(" --remove-delegate usernames Remove user or group permissions for the given mailbox")
  639. print(" --remove-all-permissions Remove all the permissions from the mailbox")
  640. print(" --list-permissions Show all the folder with the set permissions.")
  641. print(" --list-permissions-per-folder Show only the folders with permissions.")
  642. print("")
  643. print("Mailbox user permissions:")
  644. print(" --calendar [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Calendar folder. Default <denied>")
  645. print(" --tasks [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Tasks folder. Default <denied>")
  646. print(" --inbox [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Inbox folder. Default <denied>")
  647. print(" --contacts [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Contacts folder. Default <denied>")
  648. print(" --notes [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Notes folder. Default <denied>")
  649. print(" --journal [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Journal folder. Default <denied>")
  650. print(" --store [denied|norights|readonly|secretary|owner|fullcontrol] Set the permissions for the Store folder. Default <none>")
  651. print("")
  652. print(" --seeprivate [no|yes] Delegator can see private items")
  653. print(" --sendcopy [no|yes] Delegator receives copies of the meeting-related messages sent to mailbox owner.")
  654. print("")
  655. print("Delegate options:")
  656. print(" --send-only-to-delegators [no|yes] Send meeting requests and response only to the delegator, not to the mailbox owner.")
  657. print("")
  658. # print(" --convert-permission-mask number Convert the hex permission mask to text.")
  659. print("")
  660. print("optional arguments:")
  661. print(" -a, --all All mailboxes")
  662. print(" -h, --host Host to connect with. Default: file:///var/run/kopano/server.sock")
  663. print(" -s, --sslkey-file SSL key file to authenticate as admin.")
  664. print(" -p, --sslkey-pass Password for the SSL key file.")
  665. # print(" -v, --verbose Print more information.")
  666. print(" -?, --help Show this help message and exit.")
  667. print("")
  668. print("")
  669. print("Example:")
  670. print(" Show delegates and folder permissions of mailbox user1")
  671. print(" $ %s --list-permissions user1" % sys.argv[0])
  672. print("")
  673. print(" Show folder permissions of all mailboxes")
  674. print(" $ %s --list-permissions -a" % sys.argv[0])
  675. print("")
  676. print(" User1 becomes a delegate and receives specified permissions on the calendar of user2")
  677. print(" $ %s --update-delegate \"user1\" --calendar secretary --sendcopy true --seeprivate true user2" % sys.argv[0])
  678. print("")
  679. def main(argv = None):
  680. if argv is None:
  681. argv = sys.argv
  682. try:
  683. opts, args = getopt.gnu_getopt(argv[1:], 'avh:s:p:',
  684. ['all', 'help', 'host', 'sslkey-file', 'sslkey-pass','verbose',
  685. 'calendar=', 'tasks=', 'inbox=', 'contacts=', 'notes=', 'journal=', 'store=',
  686. 'seeprivate=', 'sendcopy=', 'send-only-to-delegators=',
  687. 'update-delegate=','remove-delegate=','remove-all-permissions',
  688. 'list-permissions', 'list-permissions-per-folder',
  689. 'profile='
  690. ])
  691. except getopt.GetoptError as err:
  692. # print help information and exit:
  693. print(str(err))
  694. print_help()
  695. return 1
  696. global verbose
  697. # defaults
  698. host = os.getenv("KOPANO_SOCKET", "default:")
  699. sslkey_file = None
  700. sslkey_pass = None
  701. users = []
  702. listpermissions = None
  703. showall = None
  704. seeprivate = False
  705. sendcopy = None
  706. onlysendtodelegators = None
  707. action = None
  708. allmailboxes = False
  709. profile = None
  710. permissions = {}
  711. for o, a in opts:
  712. if o in ('-h', '--host'):
  713. host = a
  714. elif o in ('-s', '--sslkey-file'):
  715. sslkey_file = a
  716. elif o in ('-p', '--sslkey-pass'):
  717. sslkey_pass = a
  718. elif o in ('-a', '--all'):
  719. allmailboxes = True
  720. elif o in ('--profile'):
  721. profile = a
  722. elif o == '--help':
  723. print_help()
  724. return 0
  725. elif o in ('--calendar'): permissions['calendar'] = a
  726. elif o in ('--tasks'): permissions['tasks'] = a
  727. elif o in ('--inbox'): permissions['inbox'] = a
  728. elif o in ('--store'): permissions['store'] = a
  729. elif o in ('--contacts'): permissions['contacts'] = a
  730. elif o in ('--notes'): permissions['notes'] = a
  731. elif o in ('--journal'): permissions['journal'] = a
  732. elif o in ('--seeprivate'):
  733. seeprivate = parseToBool(a)
  734. elif o in ('--sendcopy'):
  735. sendcopy = parseToBool(a)
  736. elif o in ('--send-only-to-delegators'):
  737. onlysendtodelegators = parseToBool(a)
  738. elif o in ('--update-delegate'):
  739. users = a.split()
  740. action = 'add'
  741. elif o in ('--remove-delegate'):
  742. users = a.split()
  743. action = 'remove'
  744. elif o in ('--remove-all-permissions'):
  745. action = 'removeall'
  746. elif o in ('--list-permissions'):
  747. listpermissions = True
  748. showall = True
  749. elif o == '--list-permissions-per-folder':
  750. listpermissions = True
  751. showall = False
  752. elif o in ('-v', '--verbose'):
  753. verbose = True
  754. else:
  755. assert False, ("unhandled option '%s'" % o)
  756. if allmailboxes == True and len(args) > 0:
  757. print("Show all mailboxes and show specific users are given, please choose one option")
  758. return 0
  759. if allmailboxes == False and not profile and len(args) == 0:
  760. print_help()
  761. return 0
  762. try:
  763. if profile:
  764. session = MAPILogonEx(0, profile, None, MAPI_EXTENDED | MAPI_NEW_SESSION | MAPI_NO_MAIL)
  765. else:
  766. session = OpenECSession('SYSTEM', '', host, sslkey_file = sslkey_file, sslkey_pass = sslkey_pass)
  767. except MAPIError as err:
  768. if err.hr == MAPI_E_LOGON_FAILED:
  769. print("Failed to logon. Make sure your SSL certificate is correct.")
  770. elif err.hr == MAPI_E_NETWORK_ERROR:
  771. print("Unable to connect to server. Make sure you specified the correct server.")
  772. else:
  773. print("Unexpected error occurred. hr=0x%08x" % err.hr)
  774. return 1
  775. if allmailboxes:
  776. args = getUserList(session)
  777. if (action == 'add' and len(users) > 0 and len(permissions) > 0):
  778. permissions = validatePermissionsAndConvert(permissions)
  779. memberids = UserAccountsToEntryIDs(session, users)
  780. setMailboxPermissions(session, permissions, memberids, args, seeprivate, sendcopy, onlysendtodelegators)
  781. elif action == 'remove':
  782. print("Not supported yet")
  783. elif action == 'removeall':
  784. removeAllPermissions(session, args)
  785. if (listpermissions):
  786. printUserMailBoxPermissions(session, showall, args)
  787. return 0
  788. if __name__ == '__main__':
  789. locale.setlocale(locale.LC_ALL, '')
  790. sys.exit(main())