Initial commit

This commit is contained in:
a-Sansara 2012-09-11 00:12:39 +02:00
commit ef8cf429e5
7 changed files with 1388 additions and 0 deletions

1
.impra_id_rsa.pub Normal file
View File

@ -0,0 +1 @@
AAAAgKoUbEl0Uwdm4vPLvxFxdSkpm62QHsxV9eUgAbj5F8ctRqfQPCh654MGe0M9w15RqHLuiehsjv7A7r45NAyNUPC1b9bcoAtwMlrFocYvTUtNSFbw0moTQOnYeoulK2Hp7mRjxZ+jDzVu3D/5BMMm40x4KUmljFghGtV8oZd9WNvpAAAAAwEAAQ==

0
impra/__init__.py Executable file
View File

156
impra/cli.py Normal file
View File

@ -0,0 +1,156 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage <http://imprastorage.sourceforge.net/> #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 <http://www.gnu.org/licenses/> #
# author : a-Sansara <http://www.a-sansara.net/> #
# copyright : pluie.org <http://www.pluie.org/> #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This file is part of ImpraStorage.
#
# ImpraStorage is free software (free as in speech) : you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# ImpraStorage is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License
# along with ImpraStorage. If not, see <http://www.gnu.org/licenses/>.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ package cli ~~
from optparse import OptionParser, OptionGroup
desc="""version : 0.41 copyright : pluie.org
author : a-Sansara license : GNU GPLv3
ImpraStorage provided a private imap access to store large files.
Each file stored on the server is split in severals random parts.
Each part also contains random noise data (lenght depends on a crypt key)
to ensure privacy and exclude easy merge without the corresponding key.
An index of files stored is encrypt (rsa 1024) and regularly updated.
Once decrypt, it permit to perform search on the server and
download each part.
transfert process is transparent. Just vizualize locally the index of
stored files and simply select files to download or upload.
ImpraStorage automatically launch the parts to download, then merge parts
in the appropriate way to rebuild the original file. Inversely, a file
to upload is splitt -in several parts with addition of noise data), and
ImpraStorage randomly upload each parts then update the index.
"""
class _SimplerOptionParser(OptionParser):
"""A simplified OptionParser"""
def format_description(self, formatter):
return self.description
def format_epilog(self, formatter):
return self.epilog
parser = _SimplerOptionParser(prog='imprastorage', usage='\n\n %prog COMMAND [OPTION]...',epilog="""
conf command Examples:
Initialize program and set config on default profile with keys generation :
imprastorage conf -K -H imap.gmail.com -P 993 -U login -X password
Set config on a new profile with same keys from previous active profile:
imprastorage conf -A profile1 -H myimapserver.net -P 993 -U login \\
-X password -B boxname
Load config from a profile (wich become active) :
imprastorage conf -DA profile2
List config from profile :
imprastorage conf -LA profile1
data command Examples:
List index on a specified box (different from box on active profile)
imprastorage data -lb boxname
Add file
imprastorage data -a /path/tofile 'my video'
Add file with category (category is also a directory structure recreate
when downloading files)
imprastorage data -a /path/tofile '2009 - en la playa' -c videos/perso/2009
Get file
imprastorage data -g '2009 - en la playa'
Get file by id
imprastorage data -G 22
Remove from server a file by id
imprastorage data -R 22
Search files matching pattern :
imprastorage data -s 'holydays'
Search files upload by a particular user on a category :
imprastorage data -s * -c films -u myfriend
""",description=desc)
gpData = OptionGroup(parser, '\ndata related Options (command data)\n-----------------------------------')
gpConf = OptionGroup(parser, '\nconf related Options (command conf)\n-----------------------------------')
# metavar='<ARG1> <ARG2>', nargs=2
parser.add_option('-v', '--version' , help='show program\'s version number and exit' , dest='version' , action='store_true' , default=False)
parser.add_option('-q', '--quiet' , help='don\'t print status messages to stdout' , dest='verbose' , action='store_false', default=True)
parser.add_option('-f', '--force' , help='dont confirm and force action' , dest='force' , action='store_true' , default=False)
parser.add_option('-d', '--debug' , help='set debug mode' , dest='debug' , action='store_true' , default=False)
gpData.add_option('-l', '--list' , help='list index on imap server' , dest='list_index' , action='store_true' , default=False)
gpData.add_option('-b', '--boxname' , help='switch boxname on imap server' , dest='switch_boxname' , action='store', metavar='BOXN')
gpData.add_option('-a', '--add' , help='add file FILE with specified LABEL on server' , dest='add' , action='store', metavar='FILE LABEL', nargs=2)
gpData.add_option('-g', '--get' , help='get file with specified LABEL from server' , dest='get' , action='store', metavar='LABEL')
gpData.add_option('-G', '--get-by-id' , help='get file with specified ID from server' , dest='get_by_id' , action='store', metavar='ID')
gpData.add_option('-s', '--search' , help='search file with specified PATTERN' , dest='search' , action='store', metavar='PATTERN')
gpData.add_option('-c', '--category' , help='set specified CATEGORY (crit. for opt. -l,-a or -s)' , dest='category' , action='store', metavar='CATG' , default='none')
gpData.add_option('-u', '--user' , help='set specified USER (crit. for opt. -l,-a or -s)' , dest='owner' , action='store', metavar='OWNER' , default='all')
gpData.add_option('-o', '--output-dir' , help='set specified OUTPUT DIR (for opt. -l,-a,-d or -g)' , dest='output' , action='store', metavar='DIR')
gpData.add_option('-r', '--remove' , help='remove FILE with specified LABEL from server' , dest='remove' , action='store', metavar='LABEL')
gpData.add_option('-R', '--remove-by-id' , help='remove FILE with specified ID from server' , dest='remove_by_id' , action='store', metavar='ID')
parser.add_option_group(gpData)
gpConf.add_option('-L', '--list-conf' , help='list configuration' , dest='list_conf' , action='store')
gpConf.add_option('-A', '--active-profile', help='set active profile' , dest='profile' , action='store', metavar='PROFILE', default='default')
gpConf.add_option('-H', '--set-host' , help='set imap host server' , dest='host' , action='store', metavar='HOST')
gpConf.add_option('-U', '--set-user' , help='set imap user login' , dest='user' , action='store', metavar='USER')
gpConf.add_option('-X', '--set-pass' , help='set imap user password' , dest='password' , action='store', metavar='PASS')
gpConf.add_option('-P', '--set-port' , help='set imap port (default:[%default])' , dest='port' , action='store', metavar='PORT' , default=993)
gpConf.add_option('-B', '--set-boxname' , help='set boxName on imap server (default:[%default])' , dest='boxname' , action='store', metavar='BOXN' , default='__IMPRA')
gpConf.add_option('-K', '--gen-keys' , help='generate new pub/private keys' , dest='generate_keys' , action='store_true', default=False)
gpConf.add_option('-D', '--load-conf' , help='load configuration' , dest='load_conf' , action='store_true', default=False)
parser.add_option_group(gpConf)
def show_index():
print('show_index')
(opts, args) = parser.parse_args()
#~ if not 'toto' in opts.__dict__ :
#~ print("mandatory option is missing\n")
#~ parser.print_help()
#~ exit(-1)*/
#~ print('--------')
#~ print(opts)
#~ print('--------')
#~ print(args)

481
impra/core.py Normal file
View File

@ -0,0 +1,481 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage <http://imprastorage.sourceforge.net/> #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 <http://www.gnu.org/licenses/> #
# author : a-Sansara <http://www.a-sansara.net/> #
# copyright : pluie.org <http://www.pluie.org/> #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This file is part of ImpraStorage.
#
# ImpraStorage is free software (free as in speech) : you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# ImpraStorage is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License
# along with ImpraStorage. If not, see <http://www.gnu.org/licenses/>.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ package core ~~
import inspect
from base64 import urlsafe_b64encode
from email.encoders import encode_base64
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
from math import ceil, floor
from mmap import mmap
from os import remove, urandom, sep
from os.path import abspath, dirname, join, realpath, basename, getsize, splitext
from re import split as regsplit
from impra.imap import ImapHelper, ImapConfig
from impra.util import __CALLER__, Rsa, RuTime, Noiser, Randomiz, RuTime, hash_sha256, formatBytes, randomFrom, bstr, quote_escape, stack
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ConfigKey ~~
class ConfigKey:
""""""
def __init__(self, key=None, psize=19710000):
""""""
if key : self.key = bytes(key,'utf-8')
else : self.key = self._build()
self.psize = psize
self.salt = str(self.key[::-4])
self.noiser = Noiser(self.key)
self.rdmz = Randomiz(1)
def getHashList(self,name,count,noSorted=False):
""""""
rt = RuTime('getHashList')
rt = RuTime(eval(__CALLER__('"%s",%s,%i' % (name,count,noSorted))))
self.rdmz.new(count)
dic, lst, hroot = {}, [], hash_sha256(self.salt+name)
for i in range(count) :
self.noiser.build(i)
d = str(i).rjust(2,'0')
# part n°, hash, lns, lne, pos
hpart = hash_sha256(self.salt+name+'.part'+d)[:-3]+str(ord(hroot[i])).rjust(3,'0')
lst.append((d, hpart, self.noiser.lns, self.noiser.lne, self.rdmz.get()))
dic['head'] = (name,count,hroot,self.getKey())
if not noSorted :
lst = sorted(lst, key=lambda lst: lst[4])
dic['data'] = lst
rt.stop()
return dic
def _build(self,l=48):
""""""
return urlsafe_b64encode(urandom(l))
def getKey(self):
""""""
return str(self.key,'utf-8')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class FSplitter ~~
class FSplitter :
""""""
def __init__(self, ck, wkdir='./'):
""""""
self.ck = ck
self.wkdir = wkdir
self.DIR_CACHE = self.wkdir+sep+'cache'+sep
self.DIR_INBOX = self.wkdir+sep+'inbox'+sep
self.DIR_OUTBOX = self.wkdir+sep+'outbox'+sep
self.DIR_DEPLOY = self.wkdir+sep+'deploy'+sep
def addFile(self, fromPath, label):
""""""
rt = RuTime(eval(__CALLER__()))
fsize = getsize(fromPath)
count = ceil(fsize/self.ck.psize)
minp, maxp = 52, 62
if fsize < 4800000 : minp, maxp = 8, 12
elif fsize < 22200000 : minp, maxp = 12, 22
elif fsize < 48000000 : minp, maxp = 22, 32
elif fsize < 222000000 : minp, maxp = 32, 42
if count < minp : count = randomFrom(maxp,minp)
if not count > 62 :
hlst = self._split(fromPath, self.ck.getHashList(label,count, True))
else :
raise Exception(fromPath+' size exceeds limits (max : '+formatBytes(self.ck.psize*62)+' ['+str(self.ck.psize*64)+' bytes])')
rt.stop()
return hlst
def _split(self, fromPath, hlst):
""""""
rt = RuTime(eval(__CALLER__()))
f = open(fromPath, 'rb+')
m = mmap(f.fileno(), 0)
p = 0
psize = ceil(getsize(fromPath)/hlst['head'][1])
while m.tell() < m.size():
self._splitPart(m,p,psize,hlst['data'][p])
p += 1
m.close()
hlst['data'] = sorted(hlst['data'], key=lambda lst: lst[4])
rt.stop()
return hlst
def _splitPart(self,mmap,part,size,phlst):
""""""
rt = RuTime(eval(__CALLER__('mmap,%s,%s,phlist' % (part,size))))
with open(self.DIR_OUTBOX+phlst[1]+'.ipr', mode='wb') as o:
o.write(self.ck.noiser.getNoise(phlst[2])+mmap.read(size)+self.ck.noiser.getNoise(phlst[3]))
rt.stop()
def deployFile(self, hlst, ext='', fake=False):
""""""
rt = RuTime(eval(__CALLER__()))
p = 0
hlst['data'] = sorted(hlst['data'], key=lambda lst: lst[0])
fp = open(self.DIR_DEPLOY+hlst['head'][0]+ext, 'wb+')
depDir = self.DIR_INBOX
if fake : depDir = self.DIR_OUTBOX
while p < hlst['head'][1] :
self._mergePart(fp,p,hlst['data'][p],depDir)
p += 1
fp.close()
rt.stop()
def _mergePart(self,fp,part,phlst,depDir):
""""""
rt = RuTime(eval(__CALLER__('fp,%s,phlist,depDir' % part)))
with open(depDir+phlst[1]+'.ipr', mode='rb') as o:
fp.write(o.read()[phlst[2]:-phlst[3]])
o.close()
remove(depDir+phlst[1]+'.ipr')
rt.stop()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ImpraConf ~~
class ImpraConf:
""""""
SEP_SECTION = '.'
""""""
def __init__(self, iniFile, profile='default'):
""""""
self.profile = profile
self.ini = iniFile
save = False
if self.ini.isEmpty():
save = True
rsa = Rsa()
self.set('host','imap.gmail.com','imap')
self.set('port','993','imap')
self.set('user','login','imap')
self.set('pass','**********','imap')
self.set('box' ,'__SMILF','imap')
self.set('pubKey',rsa.pubKey,'keys')
self.set('prvKey',rsa.prvKey,'keys')
self.set('salt' ,'-¤-ImpraStorage-¤-','keys')
if not self.ini.hasSection(self.profile+self.SEP_SECTION+'catg'):
save = True
self.set('users', self.get('name','infos'),'catg')
self.set('types', 'music,films,doc,images,archives,games','catg')
if save :
self.ini.write()
print(self.ini.toString())
def get(self, key, section='main', profile=None):
""""""
if profile == None : profile = self.profile
return self.ini.get(key, profile+self.SEP_SECTION+section)
def set(self, key, value, section='main', profile=None):
""""""
if profile == None : profile = self.profile
return self.ini.set(key, value, profile+self.SEP_SECTION+section)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ImpraIndex ~~
class ImpraIndex:
"""A representation of the index stored on the server"""
SEP_ITEM = ';'
"""Separator used for entry"""
SEP_TOKEN = '#'
"""Separator used for token"""
SEP_CATEGORY = '¤'
"""Separator used for category section"""
QUOTE_REPL = ''
"""Char replacement of simple quote String"""
SEP_KEY_INTERN = '@'
"""Separator used for internal key such categories"""
def __init__(self, rsa, encdata='', dicCategory={}):
"""Initialize the index with rsa and encoded data
:Parameters:
`rsa` : impra.Rsa
Rsa instance initialized with appropriate private and public
keys to decrypt/encrypt data
`encdata` : str
initial content of the index encrypted with rsa
"""
self.rsa = rsa
self.dic = {}
if encdata =='' : data = encdata
else : data = self.rsa.decrypt(encdata)
data = data.replace(self.QUOTE_REPL, '\'')
ld = regsplit('\n? ?'+self.SEP_CATEGORY+' ?\n?',data)
l = regsplit(self.SEP_ITEM,ld[0])
for row in l:
d = regsplit(self.SEP_TOKEN,row)
# key : count, hash, ext, usr, cat
if len(d)>4 and d!='': self.dic[d[1]] = d
if len(ld)>1:
l = regsplit(self.SEP_ITEM,ld[1].lstrip('\n'))
for row in l:
d = regsplit(' ?= ?',row,1)
if len(d)> 1 and len(d[0]) > 3 :
self.dic[d[0]] = d[1]
else:
for k in dicCategory :
self.dic[self.SEP_KEY_INTERN+k] = dicCategory[k]
def add(self,key, label, count, ext='', usr='', cat=''):
"""Add an entry to the index with appropriate label, key used by entry
to decode data, and parts count
"""
if self.search(label) == None :
self.dic[label] = (key,label,count,ext,usr,cat)
else :
print(label+' already exist')
def rem(self,label):
"""Remove the selected label from the index"""
self.dic.pop(label, None)
def search(self,label):
"""Search the corresponding label in the index"""
return self.dic.get(label)
def toString(self):
"""Make a string representation of the index as it was store on the server"""
data = cdata = ''
for k in sorted(self.dic):
v = self.dic.get(k)
if k[0]==self.SEP_KEY_INTERN and len(k)>1:
cdata += k+'='+v+self.SEP_ITEM
else :
for i in v: data += str(i)+self.SEP_TOKEN
data = data.rstrip(self.SEP_TOKEN)+self.SEP_ITEM
return data+self.SEP_CATEGORY+'\n'+cdata;
def encrypt(self):
""""""
return self.rsa.encrypt(self.toString().replace('\'', self.QUOTE_REPL))
def print(self):
"""Print index content as formated bloc"""
data = self.toString().split(';')
for row in data:
if row.rstrip('\n') != '': print(row)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ImpraStorage ~~
class ImpraStorage:
""""""
def __init__(self, rsa, conf, wkdir=None):
""""""
if wkdir == None : wkdir = abspath(join(dirname( __file__ ), '..', 'wk'))
self.wkdir = wkdir
self.conf = conf
self.rootBox = self.conf.get('box','imap')
iconf = ImapConfig(self.conf.get('host','imap'), self.conf.get('port','imap'), self.conf.get('user', 'imap'), self.conf.get('pass', 'imap'))
self.ih = ImapHelper(iconf,self.rootBox)
self.mb = MailBuilder(self.conf.get('salt','keys'))
self.rsa = rsa
self.fsplit = FSplitter(ConfigKey(),self.wkdir)
self.delids = []
self.index = self.getIndex()
def _getIdIndex(self):
""""""
mid = None
status, resp = self.ih.srv.search(None, '(SUBJECT "%s")' % self.mb.getHashName('index'))
ids = [m for m in resp[0].split()]
if len(ids) > 0 and int(ids[0]) >= 0 :
mid = ids[len(ids)-1]
for i in ids:
if i != mid : self.delids.append(i)
self.idx = mid
return mid
def _getIdsBySubject(self,subject):
""""""
status, resp = self.ih.srv.search(None, '(SUBJECT "%s")' % subject)
ids = [m for m in resp[0].split()]
return ids
def getIndex(self):
""""""
self._getIdIndex()
if self.idx :
msgIndex = self.ih.email(self.idx)
for i in self.delids : self.ih.delete(i)
for part in msgIndex.walk():
ms = part.get_payload(decode=True)
encData = str(ms,'utf-8')
else :
encData = ''
self.ih.deleteBin()
return ImpraIndex(self.rsa,encData, {'catg':self.conf.get('types','catg')})
def saveIndex(self):
""""""
rt = RuTime(eval(__CALLER__()))
if self.idx != None :
self.ih.delete(self.idx)
encData = self.index.encrypt()
msgIndex = self.mb.buildIndex(encData)
print(msgIndex.as_string())
self.ih.send(msgIndex.as_string(), self.rootBox)
#self.index = self.getIndex()
rt.stop()
def addFile(self, path, label, usr='all', catg=''):
""""""
rt = RuTime(eval(__CALLER__('"%s","%s","%s"' % (path[:13]+'...',label,usr))))
#~ hlst = self.fsplit.addFile(path,label)
#~ self.fsplit.deployFile(hlst,True)
_, ext = splitext(path)
try:
if self.index.search(label)==None :
hlst = self.fsplit.addFile(path,label)
print(hlst['head'])
for v in hlst['data']:
print(v)
nameFrom = self.conf.ini.get('name',self.conf.profile+'.infos')
for row in hlst['data'] :
msg = self.mb.build(nameFrom,usr,hlst['head'][2],self.fsplit.DIR_OUTBOX+row[1]+'.ipr')
self.ih.send(msg.as_string(), self.rootBox)
remove(self.fsplit.DIR_OUTBOX+row[1]+'.ipr')
self.index.add(hlst['head'][3],hlst['head'][0],hlst['head'][1],ext,self.mb.getHashName(usr),catg)
self.saveIndex()
else :
raise Exception(label + ' already exist on server')
except Exception as e :
print(e)
rt.stop()
def getFile(self,label):
""""""
rt = RuTime(eval(__CALLER__('"%s"' % label)))
key = self.index.search(label)
if key!=None :
ck = ConfigKey(key[0])
count = int(key[2])
hlst = ck.getHashList(label,count,True)
ids = self._getIdsBySubject(hlst['head'][2])
if len(ids) >= count:
status, resp = self.ih.srv.fetch(ids[0],'(BODY[HEADER.FIELDS (TO)])')
to = bstr(resp[0][1][4:-4])
if to == self.mb.getHashName('all')+'@'+self.mb.DOMAIN_NAME or to == self.mb.getHashName(self.conf.ini.get('name',self.conf.profile+'.infos'))+'@'+self.mb.DOMAIN_NAME :
for mid in ids :
self.ih.downloadAttachment(mid,self.fsplit.DIR_INBOX)
print(hlst['head'])
for v in hlst['data']:
print(v)
self.fsplit.deployFile(hlst, key[3])
else :
raise Exception(label+' is private')
else :
raise Exception(label+' : invalid count parts '+str(len(ids))+'/'+str(count))
else:
raise Exception(label+' not on the server')
rt.stop()
def clean(self):
""""""
rt = RuTime(eval(__CALLER__()))
self.index = self.getIndex()
rt.stop()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class MailBuilder ~~
class MailBuilder:
"""A simple mail builder to create mails for ImpraIndex and parts attchments"""
DOMAIN_NAME = 'impra.storage'
"""Domain name used for from and to mail fields"""
def __init__(self, salt=''):
""""""
self.salt = salt
def getHashName(self, name):
"""Return a simplified hash of specified name
:Returns: `str`
"""
return hash_sha256(self.salt+name)[0:12]
def build(self, nameFrom, nameTo, subject, filePath):
"""Build mail with attachment part
:Returns: 'email.message.Message'
"""
rt = RuTime(eval(__CALLER__('%s' % basename(filePath))))
msg = MIMEMultipart()
msg['From'] = self.getHashName(nameFrom)+'@'+self.DOMAIN_NAME
msg['To'] = self.getHashName(nameTo)+'@'+self.DOMAIN_NAME
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = Header(subject,'utf-8')
part = MIMEBase('application', 'octet-stream')
part.set_payload(open(filePath, 'rb').read())
encode_base64(part)
part.add_header('Content-Disposition','attachment; filename="%s"' % basename(filePath))
msg.attach(part)
rt.stop()
return msg
def buildIndex(self, data):
"""Build mail for ImpraIndex
:Returns: 'email.message.Message'
"""
rt = RuTime(eval(__CALLER__()))
msg = MIMEMultipart()
msg['From'] = self.getHashName('system')+'@'+self.DOMAIN_NAME
msg['To'] = self.getHashName('all')+'@'+self.DOMAIN_NAME
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = Header(self.getHashName('index'),'utf-8')
msg.attach(MIMEText(data,_charset='utf-8'))
rt.stop()
return msg

362
impra/imap.py Executable file
View File

@ -0,0 +1,362 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage <http://imprastorage.sourceforge.net/> #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 <http://www.gnu.org/licenses/> #
# author : a-Sansara <http://www.a-sansara.net/> #
# copyright : pluie.org <http://www.pluie.org/> #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This file is part of ImpraStorage.
#
# ImpraStorage is free software (free as in speech) : you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# ImpraStorage is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License
# along with ImpraStorage. If not, see <http://www.gnu.org/licenses/>.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ package imap ~~
import inspect
from email import message_from_bytes
from email.header import decode_header
from email.message import Message
from imaplib import IMAP4_SSL, Time2Internaldate
from os.path import join
from re import search, split
from time import time
from impra.util import __CALLER__, RuTime, bstr, stack
from binascii import b2a_base64, a2b_base64
from codecs import register, StreamReader, StreamWriter
def _seq_encode(seq,l):
""""""
if len(seq) > 0 :
l.append('&%s-' % str(b2a_base64(bytes(''.join(seq),'utf-16be')),'utf-8').rstrip('\n=').replace('/', ','))
elif l:
l.append('-')
def encode(s):
""""""
l, e, = [], []
for c in s :
if ord(c) in range(0x20,0x7e):
if e : _seq_encode(e,l)
e = []
l.append(c)
if c == '&' : l.append('-')
else :
e.append(c)
if e : _seq_encode(e,l)
return ''.join(l)
def encoder(s):
""""""
e = bytes(encode(s),'utf-8')
return e, len(e)
def _seq_decode(seq,l):
""""""
d = ''.join(seq[1:])
pad = 4-(len(d)%4)
l.append(str(a2b_base64(bytes(d.replace(',', '/')+pad*'=','utf-16be')),'utf-16be'))
def decode(s):
""""""
l, d = [], []
for c in s:
if c == '&' and not d : d.append('&')
elif c == '-' and d:
if len(d) == 1: l.append('&')
else : _seq_decode(d,l)
d = []
elif d: d.append(c)
else: l.append(c)
if d: _seq_decode(d,l)
return ''.join(l)
def decoder(s):
""""""
d = decode(str(s,'utf-8'))
return d, len(d)
def _codec_imap4utf7(name):
""""""
if name == 'imap4-utf-7':
return (encoder, decoder, Imap4Utf7StreamReader, Imap4Utf7StreamWriter)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Imap4Utf7StreamWriter ~~
class Imap4Utf7StreamReader(StreamReader):
""""""
def decode(self, s, errors='strict'):
""""""
return decoder(s)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Imap4Utf7StreamWriter ~~
class Imap4Utf7StreamWriter(StreamWriter):
""""""
def decode(self, s, errors='strict'):
""""""
return encoder(s)
register(_codec_imap4utf7)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ImapConfig ~~
class ImapConfig:
""""""
def __init__(self, host, port, user, pwd):
""""""
self.host = host
self.port = port
self.user = user
self.pwd = pwd
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class ImapHelper ~~
class ImapHelper:
""""""
K_HEAD, K_DATA = 0, 1
""""""
OK = 'OK'
""""""
KO = 'NO'
""""""
ENCODING = 'utf-8'
""""""
REG_SATUS = r'^"(\w*)" \(([^\(]*)\)'
""""""
NO_SELECT = '\\Noselect'
""""""
CHILDREN = '\\HasChildren'
""""""
NO_CHILDREN = '\\HasNoChildren'
""""""
BOX_BIN = '[Gmail]/Corbeille'
""""""
def __init__(self, conf, box='INBOX'):
""""""
rt = RuTime(eval(__CALLER__('conf,"'+box+'"')))
self.srv = IMAP4_SSL(conf.host,conf.port)
self.srv.login(conf.user,conf.pwd)
self.rootBox = box
status, resp = self.srv.select(self.rootBox)
if status == self.KO :
self.createBox(self.rootBox)
self.srv.select(self.rootBox)
rt.stop()
def status(self,box='INBOX'):
""""""
status, resp = ih.srv.status(box, '(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)')
if status == 'OK' :
data = search(self.REG_SATUS,bstr(resp[self.K_HEAD]))
l = split(' ',data.group(2))
dic = {'BOX' : data.group(1)}
for i in range(len(l)):
if i%2 == 0 : dic[l[i]] = int(l[i+1])
else : dic = {}
return dic
def countSeen(self, box='INBOX'):
""""""
s = self.status()
return s['MESSAGES']-s['UNSEEN']
def countUnseen(self, box='INBOX'):
""""""
return self.status()['UNSEEN']
def countMsg(self, box='INBOX'):
""""""
return self.status()['MESSAGES']
def _ids(self, box='INBOX', search='ALL', charset=None):
""""""
status, resp = self.srv.search(charset, '(%s)' % search)
return split(' ',bstr(resp[self.K_HEAD]))
def idsUnseen(self, box='INBOX', charset=None):
""""""
return self._ids(box,'UNSEEN', charset)
def idsMsg(self, box='INBOX', charset=None):
""""""
return self._ids(box,'ALL', charset)
def idsSeen(self, box='INBOX', charset=None):
""""""
return self._ids(box,'NOT UNSEEN', charset)
def listBox(self, box='INBOX', pattern='*'):
""""""
status, resp = self.srv.list(box,pattern)
l = []
for r in resp :
name = bstr(r).split(' "/" ')
l.append((name[0][1:-1].split(' '),decode(name[1][1:-1])))
return l
def createBox(self, box):
""""""
rt = RuTime(eval(__CALLER__(box)))
status, resp = self.srv.create(encode(box))
rt.stop()
return status==self.OK
def deleteBox(self, box):
""""""
rt = RuTime(eval(__CALLER__(box)))
status, resp = self.srv.delete(encode(box))
rt.stop()
return status==self.OK
def subject(self, mid):
""""""
status, resp = self.srv.fetch(mid, '(body[header.fields (subject)])')
subject = decode_header(str(resp[self.K_HEAD][1][9:-4], 'utf-8'))[0]
s = subject[0]
if subject[1] :
s = str(s,subject[1])
return s
def email(self, mid):
""""""
status, resp = self.srv.fetch(mid,'(UID RFC822)')
if status == self.OK :
msg = message_from_bytes(resp[0][1])
else :
msg = None
return msg
def deleteBin(self):
""""""
rt = RuTime(eval(__CALLER__()))
self.srv.select(self.BOX_BIN)
ids = self._ids(self.BOX_BIN)
if len(ids) > 0 and ids[0]!='':
for mid in ids :
print('deleting msg '+mid)
status, resp = self.srv.store(mid, '+FLAGS', '\\Deleted')
self.srv.expunge()
self.srv.select(self.rootBox)
rt.stop()
def delete(self, mid):
""""""
rt = RuTime(eval(__CALLER__('%i' % int(mid))))
status = None
if int(mid) > 0 :
status, resp = self.srv.store(mid, '+FLAGS', '\\Deleted')
self.srv.expunge()
rt.stop()
return status == self.OK
def downloadAttachment(self, msg, toDir='./'):
""""""
rt = RuTime(eval(__CALLER__('%i' % int(msg))))
if not isinstance(msg, Message) :
msg = self.email(msg)
for part in msg.walk():
filename = part.get_filename()
if filename != None : print(filename)
if part.get_content_maintype() == 'multipart' or not filename : continue
fp = open(join(toDir, filename), 'wb')
#print(part.get_payload(decode=True)[::-1])
fp.write(part.get_payload(decode=True))
fp.close()
rt.stop()
def send(self, msg, box='INBOX'):
""""""
rt = RuTime(eval(__CALLER__()))
self.srv.append(box, '\Draft', Time2Internaldate(time()), bytes(msg,'utf-8'))
rt.stop()
if __name__ == '__main__':
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
iconf = ImapConfig("imap.gmail.com", 993, 'gpslot.001', '__gpslot#22')
ih = ImapHelper(iconf,'__SMILF')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
print('\n--------------------------------------------------------------------')
print('-- STATUS DEFAULT BOX --')
print(str(ih.status()))
print('-- STATUS BOX __SMILF --')
print(str(ih.status('__SMILF')))
print('-- UNSEEN COUNT --')
print(str(ih.countUnseen('__SMILF')))
print('-- SEEN COUNT --')
print(str(ih.countSeen('__SMILF')))
print('-- MESSAGE COUNT --')
print(str(ih.countMsg('__SMILF')))
print('-- UNSEEN IDS --')
print(ih.idsUnseen('__SMILF'))
print('-- MESSAGES IDS --')
print(ih.idsMsg('__SMILF'))
print('-- SEEN IDS --')
lunseen = ih.idsSeen('__SMILF')
print(lunseen)
print('-- LIST BOX --')
lb = ih.listBox('')
print(lb[5][1])
print('-- SUBJECT ID 1 --')
print(ih.subject(lunseen[0]))
print('-- BODY ID 1 --')
#print(ih.body(lunseen[0]))
print('-- EMAIL ID 1 --')
# 'partial', ('1', 'RFC822', 1, 1024)),
#status, resp = ih.srv.fetch(lunseen[0],'(UID RFC822)')
#status, resp = ih.srv.fetch('4','(UID body[header.fields (from to subject date)])')
#status, resp = ih.srv.fetch(lunseen[1],'(UID RFC822.SIZE)')
#status, resp = ih.srv.fetch(lunseen[1],'(UID RFC822.HEADER)')
#status, resp = ih.srv.fetch(lunseen[1],'(UID BODYSTRUCTURE)')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#msg = ih.email(lunseen[0])
#print(type(msg))
#print(msg)
#print('-- ATTACHMENT ID 1 --')
#ih.downloadAttachment(lunseen[0])
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ['MIME-Version', 'Received', 'Date', 'Message-ID', 'Subject', 'From', 'To', 'Content-Type']
print('-- CREATE BOX __SMILF/böx --')
print(ih.createBox("__SMILF/böx"))
print('-- DELETE BOX böx --')
print(ih.deleteBox("böx"))
#~ OK
#~ [b'Success']
#~ True
#~ NO
#~ [b'[ALREADYEXISTS] Duplicate folder name b\xc3\xb6x (Failure)']
#~ True
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

322
impra/util.py Executable file
View File

@ -0,0 +1,322 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage <http://imprastorage.sourceforge.net/> #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 <http://www.gnu.org/licenses/> #
# author : a-Sansara <http://www.a-sansara.net/> #
# copyright : pluie.org <http://www.pluie.org/> #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This file is part of ImpraStorage.
#
# ImpraStorage is free software (free as in speech) : you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# ImpraStorage is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License
# along with ImpraStorage. If not, see <http://www.gnu.org/licenses/>.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ package util ~~
from hashlib import sha256
from math import log, floor, ceil
from random import choice
from os import urandom, popen
from os.path import dirname, realpath
from time import time
from re import split as regsplit
from base64 import urlsafe_b64encode
from inspect import stack
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ methods ~~
def quote_escape(data):
"""Escape simple quote
:Returns: `str`
"""
return data.replace('\'', r'\'')
def get_file_content(fileName):
"""Get file content of `fileName`
:Returns: `str`
"""
r = open(fileName, "rt")
data = r.read()
r.close()
return data
def hash_sha256(data):
"""Get a sha256 hash of str `data`
:Returns: `str`
"""
return str(sha256(bytes(data,'utf-8')).hexdigest())
def randomFrom(val, sval=0):
"""Get a random number from range `sval=0` to `val`
:Returns: `int`
"""
lst = list(range(sval,val))
return choice(lst)
def formatBytes(b, p=2):
"""Give a human representation of bytes size `b`
:Returns: `str`
"""
units = ['B', 'KB', 'MB', 'GB', 'TB'];
b = max(b,0);
if b == 0 : lb= 0
else : lb = log(b)
p = floor(lb/log(1024))
p = min(p, len(units)- 1)
#Uncomment one of the following alternatives
b /= pow(1024,p)
#b /= (1 << (10 * p))
return str(round(b, p))+' '+units[p]
def bstr(b,enc='utf-8'):
""""""
return str(b, encoding=enc)
def __CALLER__(args=''):
"""Give basic information of caller method
usage ::
eval(__CALLER())
eval(__CALLER('"%s","%s"' % (arg1,arg2)))
:Returns: `str`
"""
#~ print(inspect.stack()[1][3])
#~ print(print(args))
#~ print('-----')
#~ print(inspect.stack())
#~ print('---------------')
val = "self.__class__.__name__+'.%s' % stack()[1][3]+'("+quote_escape(args)+") l:'+str(stack()[1][2])"
return val
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Noiser ~~
class Noiser:
""""""
KEY_LEN = 64
""""""
def __init__(self, key, part=0):
""""""
if len(key)!=self.KEY_LEN :
raise Exception('Invalid Pass length')
else :
self.key = key
self.build(part)
def build(self, part):
""""""
if not part < self.KEY_LEN-1 : raise Exception('part exceed limit')
else :
self.part, v = part, 0
for i in self.key[::-2] : v += i
v = int(ceil(v/4.22))
self.lns = int(ceil(v/2))-self.key[self.part]
self.lne = int(v-self.lns-self.key[self.part+2])
def getNoise(self, l):
""""""
return urandom(l)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Randomiz ~~
class Randomiz:
""""""
def __init__(self,count):
""""""
self.lst = list(range(0,count))
self.count = len(self.lst)
def new(self,count=None):
""""""
if count : self.count = count
self.__init__(self.count)
def get(self):
""""""
pos = choice(self.lst)
del self.lst[self.lst.index(pos)]
return pos
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class RuTime ~~
class RuTime:
"""Give basics time stats"""
def __init__(self,label):
"""Initialize duration with appropriate label"""
self.label = label
self._start()
def _start(self):
print(' ==> '+self.label)
self.sc = time()
def stop(self):
"""Stop duration and print basics stats duration on console"""
self.ec = time()
self._stats()
def _stats(self):
print(' <== '+self.label+(' [%.9f s]' % (self.ec - self.sc))+' <¤¤ ')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class IniFile ~~
class IniFile:
"""Read a write inifile"""
def __init__(self,path):
""""""
self.path = path
self.dic = {}
self.read()
def isEmpty(self):
""""""
return len(self.dic)==0
def has(self, key, section='main'):
""""""
d = (key in self.dic[section])
return d
def hasSection(self, section):
""""""
d = (section in self.dic)
return d
def get(self, key, section='main'):
""""""
return self.dic[section][key]
def set(self, key, val, section='main'):
""""""
v = None
if not section in self.dic:
self.dic[section] = {}
if key in self.dic[section]:
v = self.dic[section].pop(key)
self.dic[section][key] = val
return v
def rem(self, key, section):
""""""
v = None
if section in self.dic :
if key == '*' :
v = self.dic.pop(section)
elif key in self.dic[section]:
v = self.dic[section].pop(key)
return v
def write(self,path=None):
""""""
if path == None : path = self.path
content = self.toString()
with open(path, mode='w', encoding='utf-8') as o:
o.write(content)
def toString(self,path=None):
""""""
if path == None : path = self.path
content = ''
main = ''
for s in self.dic:
if s!='main':
content += '\n['+s+']\n'
for k in sorted(self.dic[s]):
k = k.rstrip(' ')
if s!='main':
content += k+' = '+self.dic[s][k]+'\n'
else : main += k+' = '+self.dic[s][k]+'\n'
return main + content
def read(self):
""""""
try:
with open(self.path, encoding='utf-8') as o:
csection = 'main'
self.dic[csection] = {}
for l in o:
l = l.rstrip()
d = regsplit(' *= *',l,1)
if len(d)> 1:
self.dic[csection][d[0]] = d[1]
elif len(l)>0 and l[0]=='[':
csection = l.strip('[]')
self.dic[csection] = {}
except IOError : pass
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Rsa ~~
class Rsa:
""""""
def __init__(self, prvKey=None, pubKey=None, dpath='./'):
""""""
self.cpath = dirname(realpath(__file__))+'/../desurveil/scripts/'
self.prvKey = prvKey
self.pubKey = pubKey
self.dpath = dpath
if prvKey == None or pubKey==None : self.key()
def key(self):
""""""
cmd = self.cpath+"desurveil key -a "+self.dpath+".impra_id_rsa -l "+self.dpath+".impra_id_rsa.pub"
#print(cmd)
try :
with open(self.dpath+'.impra_id_rsa','rt') as f: pass
except IOError as e:
d = popen(cmd).read()
#print(d)
self.prvKey = get_file_content(self.dpath+'.impra_id_rsa')
self.pubKey = get_file_content(self.dpath+'.impra_id_rsa.pub')
#print('pubKey : \n'+self.pubKey)
#print('prvKey : \n'+self.prvKey)
def encrypt(self,data):
""""""
key = ''
if self.pubKey != None : key = " -CI '"+self.pubKey+"'"
#if self.pubKey != None : key = " -C '"+self.dpath+".impra_id_rsa.pub'"
cmd = self.cpath+"desurveil encrypt -i '"+data+"'"+key
#print(cmd)
return popen(cmd).read()
def decrypt(self,data):
""""""
key = ''
if self.prvKey != None : key = " -CI '"+self.prvKey+"'"
#if self.prvKey != None : key = " -C '"+self.dpath+".impra_id_rsa'"
cmd = self.cpath+"desurveil decrypt -i '"+data+"'"+key
#print(cmd)
return popen(cmd).read()

66
imprastorage.py Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage <http://imprastorage.sourceforge.net/> #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 <http://www.gnu.org/licenses/> #
# author : a-Sansara <http://www.a-sansara.net/> #
# copyright : pluie.org <http://www.pluie.org/> #
# #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# This file is part of ImpraStorage.
#
# ImpraStorage is free software (free as in speech) : you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# ImpraStorage is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License
# along with ImpraStorage. If not, see <http://www.gnu.org/licenses/>.
from impra.core import ImpraConf, ImpraStorage
from impra.util import IniFile, Rsa, RuTime
if __name__ == '__main__':
rt = RuTime(__name__+'()')
conf = ImpraConf(IniFile('./impra.ini'))
rsa = Rsa(conf.ini.get('prvKey',conf.profile+'.keys'),conf.ini.get('pubKey',conf.profile+'.keys'))
impst = ImpraStorage(rsa, conf)
print('\n -- INDEX DATA -- ')
impst.index.print()
#~ print('-- LIST BOX --')
#~ lb = impst.ih.listBox('/')
#~ print(lb)
#print('-- DELETE BIN --')
#impst.ih.deleteBin()
filePath = '/media/Data/dev/big_toph3.jpg'
lab = 'Meuf\'bonne aussi4'
print('\n -- ADD FILE -- ')
impst.addFile(filePath,lab,conf.ini.get('name',conf.profile+'.infos'),'images')
print('\n -- GET FILE -- ')
impst.getFile(lab)
print('\n -- INDEX DATA -- ')
impst.index.print()
print('\n -- CLEAN -- ')
impst.clean()
rt.stop()
#python -O -m compileall impra/*.py