Skip to content

Instantly share code, notes, and snippets.

@jelly
Created January 14, 2015 09:32
Show Gist options
  • Save jelly/359d34915f8064b9b8d0 to your computer and use it in GitHub Desktop.
Save jelly/359d34915f8064b9b8d0 to your computer and use it in GitHub Desktop.
WebAccess To WebApp
#!/usr/bin/env python
# MAPI imports
from MAPI import *
from MAPI.Util import *
import pprint
import re
import json
import sys
import sys
import codecs
try:
codecs.lookup_error('surrogateescape')
default_errors = 'surrogateescape'
except LookupError:
default_errors = 'strict'
try:
from StringIO import StringIO as BytesIO
except ImportError:
from io import BytesIO as BytesIO
try:
unicode
except NameError:
# Python 3
unicode = str
basestring = (bytes, str)
try:
long
except NameError:
# Python 3
long = int
try:
xrange
except NameError:
xrange = range
def load(fp, charset='utf-8', errors=default_errors, decode_strings=False,
object_hook=None, array_hook=None):
"""Read a string from the open file object `fp` and interpret it as a
data stream of PHP-serialized objects, reconstructing and returning
the original object hierarchy.
`fp` must provide a `read()` method that takes an integer argument. Both
method should return strings. Thus `fp` can be a file object opened for
reading, a `StringIO` object (`BytesIO` on Python 3), or any other custom
object that meets this interface.
`load` will read exactly one object from the stream. See the docstring of
the module for this chained behavior.
If an object hook is given object-opcodes are supported in the serilization
format. The function is called with the class name and a dict of the
class data members. The data member names are in PHP format which is
usually not what you want. The `simple_object_hook` function can convert
them to Python identifier names.
If an `array_hook` is given that function is called with a list of pairs
for all array items. This can for example be set to
`collections.OrderedDict` for an ordered, hashed dictionary.
"""
if array_hook is None:
array_hook = dict
def _expect(e):
v = fp.read(len(e))
if v != e:
raise ValueError('failed expectation, expected %r got %r' % (e, v))
def _read_until(delim):
buf = []
while 1:
char = fp.read(1)
if char == delim:
break
elif not char:
raise ValueError('unexpected end of stream')
buf.append(char)
return b''.join(buf)
def _load_array():
items = int(_read_until(b':')) * 2
_expect(b'{')
result = []
last_item = Ellipsis
for idx in xrange(items):
item = _unserialize()
if last_item is Ellipsis:
last_item = item
else:
result.append((last_item, item))
last_item = Ellipsis
_expect(b'}')
return result
def _unserialize():
type_ = fp.read(1).lower()
if type_ == b'n':
_expect(b';')
return None
if type_ in b'idb':
_expect(b':')
data = _read_until(b';')
if type_ == b'i':
return int(data)
if type_ == b'd':
return float(data)
return int(data) != 0
if type_ == b's':
_expect(b':')
length = int(_read_until(b':'))
_expect(b'"')
data = fp.read(length)
_expect(b'"')
if decode_strings:
data = data.decode(charset, errors)
_expect(b';')
return data
if type_ == b'a':
_expect(b':')
return array_hook(_load_array())
if type_ == b'o':
if object_hook is None:
raise ValueError('object in serialization dump but '
'object_hook not given.')
_expect(b':')
name_length = int(_read_until(b':'))
_expect(b'"')
name = fp.read(name_length)
_expect(b'":')
if decode_strings:
name = name.decode(charset, errors)
return object_hook(name, dict(_load_array()))
raise ValueError('unexpected opcode')
return _unserialize()
def loads(data, charset='utf-8', errors=default_errors, decode_strings=False,
object_hook=None, array_hook=None):
"""Read a PHP-serialized object hierarchy from a string. Characters in the
string past the object's representation are ignored. On Python 3 the
string must be a bytestring.
"""
return load(BytesIO(data), charset, errors, decode_strings,
object_hook, array_hook)
def check_input():
if len(sys.argv) < 2:
sys.exit('Usage: %s username' % sys.argv[0])
def read_settings(data = None):
settings = None
if not data:
PR_EC_WEBACCESS_SETTINGS = PROP_TAG(PT_STRING8, PR_EC_BASE+0x70)
s = OpenECSession(sys.argv[1], '', 'file:///var/run/zarafa')
st = GetDefaultStore(s)
try:
settings = st.OpenProperty(PR_EC_WEBACCESS_SETTINGS, IID_IStream, 0, 0)
data = settings.Read(4096)
except:
return {}
# Somehow the serialized data can be corrupt, which means that for example ";s:3":"word"
# is invalid which the phpserialize library does not parse, so we need to replace these
# entries manually with the correct length.
result = re.findall('s:(\d+):"(.*?)"', data)
for r in result:
if int(r[0]) != len(r[1]):
data = data.replace('s:%s:"%s"' % (r[0], r[1]), 's:%s:"%s"' % (len(r[1]), r[1]))
return loads(data)
def write_webapp_settings(data):
s = OpenECSession(sys.argv[1], '', 'file:///var/run/zarafa')
st = GetDefaultStore(s)
PR_EC_WEBACCESS_SETTINGS_JSON = PROP_TAG(PT_STRING8, PR_EC_BASE+0x72)
settings = st.OpenProperty(PR_EC_WEBACCESS_SETTINGS_JSON, IID_IStream, 0, MAPI_MODIFY|MAPI_CREATE)
settings.SetSize(0)
settings.Seek(0, STREAM_SEEK_END)
writesettings = settings.Write(data)
if writesettings:
print "Settings for user '%s' were applied." % sys.argv[1]
else:
print "Settings for user '%s' failed to be applied." % sys.argv[1]
settings.Commit(0)
if __name__ == '__main__':
check_input()
debug = False
if debug:
pp = pprint.PrettyPrinter(indent=4)
if len(sys.argv) > 3:
webaccess_dict = read_settings(open(sys.argv[2]).read().rstrip())
else:
webaccess_dict = read_settings()
if webaccess_dict == {}:
print 'No webaccess settings for user'
sys.exit(1)
if debug:
pp.pprint(webaccess_dict)
webaccess_dict = webaccess_dict['settings']
webapp_settings_full = json.loads('{"settings":{"zarafa":{"v1":{"main":{"language":"nl_NL.UTF-8","default_context":"mail","start_working_hour":540,"end_working_hour":1020,"week_start":1,"show_welcome":false},"contexts":{"mail":{},"calendar":{"default_zoom_level":30,"datepicker_show_busy":true}},"state":{"models":{"note":{"current_data_mode":0}},"contexts":{"mail":{"current_view":0,"current_view_mode":1}}}}}}}')
if debug:
pp.pprint(webapp_settings_full)
webapp_settings = webapp_settings_full['settings']['zarafa']['v1']
# CALENDAR
webapp_calendar = webapp_settings['contexts']['calendar']
if 'calendar' in webaccess_dict:
webapp_calendar['default_reminder'] = webaccess_dict['calendar']['reminder'] # reminder true/false
webapp_calendar['default_reminder_time'] = webaccess_dict['calendar']['reminder_minutes'] # reminder minutes
#webapp_calendar['default_allday_reminder_time'] = webaccess_dict['calendar']['reminder'] # ??? not sure if this exists in webaccess
webapp_settings['main']['end_working_hour'] = webaccess_dict['calendar']['workdayend']
webapp_settings['main']['start_working_hour'] = webaccess_dict['calendar']['workdaystart']
#webapp_calendar['default_zoom_level'] webaccess_dict['calendar']['']# calendar resolution
# GENERAL
webapp_settings['main']['language'] = webaccess_dict['global']['language'] # language
# keyboard shortcuts
if 'shortcuts' in webaccess_dict['global']:
if webaccess_dict['global']['shortcuts']['enabled']: # True or empty string
webapp_settings['main']['keycontrols_enabled'] = True
# Location of preview pane
# Webapp 0 => Off, 1 => Right, 2 => Bottom
if 'previewpane' in webaccess_dict['global']:
if webaccess_dict['global']['previewpane'] == 'right':
webapp_settings['state']['contexts']['mail']['current_view_mode'] = 1
elif webaccess_dict['global']['previewpane'] == 'bottom':
webapp_settings['state']['contexts']['mail']['current_view_mode'] = 2
else:
webapp_settings['state']['contexts']['mail']['current_view_mode'] = 0
# TODO: startup folder
#if webaccess_dict['global']['startup']['folder'] != 'inbox':
# MAIL
# read receipt: webapp "always"/"never"/"ask"
if 'readreceipt_handling' in webaccess_dict['global']:
webapp_settings['contexts']['mail']['readreceipt_handling'] = webaccess_dict['global']['readreceipt_handling']
# mark mails as read after
if 'mail_readflagtime' in webaccess_dict['global']:
webapp_settings['contexts']['mail']['readflag_time'] = webaccess_dict['global']['mail_readflagtime']
# autosave email
if 'autosave' in webaccess_dict['createmail']:
if webaccess_dict['createmail']['autosave']:
# Webaccess saves autosave interval in minutes, WebApp in seconds.
webapp_settings['contexts']['mail']['autosave_time'] = webaccess_dict['createmail']['autosave_interval'] * 60
# EDITOR
# Create part of tree
webapp_settings['contexts']['mail']['dialogs'] = {'mailcreate': {}}
if 'mailformat' in webaccess_dict['createmail']:
webapp_settings['contexts']['mail']['dialogs']['mailcreate']['use_html_editor'] = webaccess_dict['createmail']['mailformat']
# SIGNATURES
if webaccess_dict['createmail']['signatures']:
sigs = webaccess_dict['createmail']['signatures']['id_lookup'].split(';')
webapp_settings['contexts']['mail'] = {'signatures': { 'all': {}}}
for sig in sigs:
# content / type / name
tmp = webaccess_dict['createmail']['signatures'][int(sig)]
webapp_settings['contexts']['mail']['signatures']['all'][int(sig)] = { 'name': tmp['name'], 'content': tmp['content'], 'isHTML': tmp['type'] == 'html' }
webapp_settings['contexts']['mail']['signatures']['new_message'] = webaccess_dict['createmail']['signatures']['select_sig_newmsg'] or 0
webapp_settings['contexts']['mail']['signatures']['replyforward_message'] = webaccess_dict['createmail']['signatures']['select_sig_replyfwd'] or 0
# FONT
#if webaccess_dict['createmail']['maildefaultfont'] == 'Tahoma':
# ADDRESSBOOK
# Default selected folder/addressbook
#webapp_settings['main']['default_addressbook'] = webaccess_dict['addressbook']['default']
# Write settings
write_webapp_settings(json.dumps(webapp_settings_full))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment