#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# software : ImpraStorage #
# version : 0.4 #
# date : 2012 #
# licence : GPLv3.0 #
# author : a-Sansara #
# copyright : 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 .
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ package cli ~~
from optparse import OptionParser, OptionGroup
import sys, os, platform
import impra.crypt as crypt
import impra.util as util
import impra.core as core
from impra.util import Clz
LINE_SEP_LEN = 120
LINE_SEP_CHAR = '―'
if not Clz.isUnix : LINE_SEP_CHAR = '-'
APP_TITLE = 'ImpraStorage'
APP_VERSION = '0.5'
APP_AUTHOR = 'a-Sansara'
APP_COPY = 'pluie.org'
APP_LICENSE = 'GNU GPLv3'
APP_DESC = """
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 (with the symmetric-key algorithm
Kirmah) 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 split (in several parts with addition of noise data), and
ImpraStorage randomly upload each parts then update the index.
"""
def printLineSep(sep,lenSep):
""""""
Clz.print(sep*lenSep, Clz.fgN0)
def printHeaderTitle(title):
""""""
Clz.print(' == '+title+' == ', Clz.BG4+Clz.fgB7, False, True)
def printHeaderPart(label,value):
""""""
Clz.print(' [' , Clz.fgB0, False)
Clz.print(label, Clz.fgB3, False)
Clz.print(':' , Clz.fgB0, False)
Clz.print(value, Clz.fgB4, False)
Clz.print('] ' , Clz.fgB0, False)
class _OptionParser(OptionParser):
"""A simplified OptionParser"""
def format_description(self, formatter):
return self.description
def format_epilog(self, formatter):
return self.epilog
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ class Cli ~~
class Cli:
def __init__(self,path):
self.ini = util.IniFile(path+'impra.ini')
parser = _OptionParser(prog='imprastorage', usage='\n\n\
------------------------------------------------------------------------------\n\
-- %s --\n\
------------------------------------------------------------------------------\n\
-- version : %s copyright : %s --\n\
-- author : %s license : %s --\n\
------------------------------------------------------------------------------\n\
\n\
%s conf [-D|-L| -K,-H {host},-U {user},-X {password},-P {port},\n\
-B {boxname}] [-A profileName]\n\
%s data [-l |-a {file, label} |-g {id} |-s {pattern} | -r {id}]\n\
[-c {catg}, -u {owner}]'
% (APP_TITLE,APP_VERSION,APP_COPY,APP_AUTHOR,APP_LICENSE,APP_TITLE.lower(),APP_TITLE.lower()) , 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 -H myimapserver.net -P 993 -U login -X password -B boxname -A profile1
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
imprastorage data -l
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 by id
imprastorage data -g 22
Remove from server a file by id
imprastorage data -g 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=APP_DESC)
gpData = OptionGroup(parser, '\n------------------------------------\ndata related Options (command data)')
gpConf = OptionGroup(parser, '\n------------------------------------\nconf related Options (command conf)')
# metavar=' ', nargs=2
parser.add_option('-v', '--version' , help='show program\'s version number and exit' , action='store_true' , default=False)
parser.add_option('-q', '--quiet' , help='don\'t print status messages to stdout' , action='store_false', default=True)
parser.add_option('-d', '--debug' , help='set debug mode' , action='store_true' , default=False)
gpData.add_option('-l', '--list' , help='list index on imap server' , action='store_true' )
gpData.add_option('-a', '--add' , help='add file FILE with specified LABEL on server' , action='store', metavar='FILE LABEL ', nargs=2)
gpData.add_option('-g', '--get' , help='get file with specified ID from server' , action='store', metavar='ID ')
gpData.add_option('-s', '--search' , help='search file with specified PATTERN' , action='store', metavar='PATTERN ')
gpData.add_option('-r', '--remove' , help='remove FILE with specified ID from server' , action='store', metavar='ID ')
gpData.add_option('-c', '--category' , help='set specified CATEGORY (crit. for opt. -l,-a or -s)' , action='store', metavar='CATG ' , default='')
gpData.add_option('-u', '--user' , help='set specified USER (crit. for opt. -l,-a or -s)' , action='store', metavar='OWNER ' , default='all')
gpData.add_option('-o', '--order' , help='set colon ORDER (crit. for opt. -l and -s)' , action='store', metavar='ORDER ' , default='ID')
gpData.add_option('-O', '--order-inv' , help='set colon ORDER_INVERSE (crit. for opt. -l and -s)' , action='store', metavar='ORDER_INVERSE')
#gpData.add_option('-o', '--output-dir' , help='set specified OUTPUT DIR (for opt. -l,-a,-d or -g)' , action='store', metavar='DIR ')
parser.add_option_group(gpData)
gpConf.add_option('-L', '--list-conf' , help='list configuration' , action='store_true', default=False)
gpConf.add_option('-D', '--load-conf' , help='load configuration' , action='store_true', default=False)
gpConf.add_option('-H', '--set-host' , help='set imap host server' , action='store', metavar='HOST ')
gpConf.add_option('-U', '--set-user' , help='set imap user login' , action='store', metavar='USER ')
gpConf.add_option('-X', '--set-pass' , help='set imap user password' , action='store', metavar='PASS ')
gpConf.add_option('-P', '--set-port' , help='set imap port' , action='store', metavar='PORT ')
gpConf.add_option('-N', '--set-name' , help='set user name' , action='store', metavar='NAME ')
gpConf.add_option('-B', '--set-boxn' , help='set boxName on imap server (default:[%default])' , action='store', metavar='BOXNAME ')
gpConf.add_option('-K', '--gen-keys' , help='generate new key' , action='store_true', default=False)
gpConf.add_option('-A', '--active-profile', help='set active profile' , action='store', metavar='PROFILE ')
parser.add_option_group(gpConf)
(o, a) = parser.parse_args()
# USAGE :
#
# imprastorage add {filePath} [{name} -c {category} -u {owner}]
# imprastorage list [-o {colon}, -c {category} -u {owner}]
# imprastorage get {id|ids} | -n {name}
# imprastorage remove {id} | -n {name}
# imprastorage search {pattern} [-c {category} -u {owner} -o {colon}]
# imprastorage conf [-A profileName] -L | -V |
# -S [-K, -H {host}, -U {user}, -X {password}, -P {port}, -B {box}, -N {name}]
#
# EXEMPLES :
#
# command add :
# imprastorage add /home/Share/2009-mountains.avi
# imprastorage add /home/Share/2009-mountains.avi 'summer 2009 - in mountains'
# imprastorage add /home/Share/2009-mountains.avi -u family
# imprastorage add /home/Share/2009-mountains.avi -c videos/perso/2009
# imprastorage add /home/Share/2009-mountains.avi 'summer 2009 - in mountains' -c videos/perso/2009 -u family
#
# command list :
# imprastorage list
# imprastorage list -o LABEL
# imprastorage list -c videos/perso
# imprastorage list -u family
# imprastorage list -o SIZE -c videos/perso -u family
#
# command get :
# imprastorage get 15
# imprastorage get 15-19
# imprastorage get 22,29,30
# imprastorage get 22,29,30-35,48
# imprastorage get -n 'summer 2009 - in mountains'
#
# command remove :
# imprastorage remove 15
# imprastorage remove -n 'summer 2009 - in mountains'
#
# command search :
# imprastorage search 'mountains'
# imprastorage search 'mountains' -c videos/perso
# imprastorage search 'mountains' -u family
# imprastorage search 'mountains' -o LABEL
# imprastorage search 'mountains' -c videos/perso -u family -o LABEL
# imprastorage search '^mountains'
# imprastorage search 'mountains$'
#
# command conf :
# imprastorage conf -L
# imprastorage conf -LA bobgmail
# imprastorage conf -V
# imprastorage conf -VA bobgmail
# imprastorage conf -S -K
# imprastorage conf -SA bobgmail -K
# imprastorage conf -SA bobgmail -K -H imap.gmail.com -U bob22 -X mypassword -N bob
#
print()
if not a:
parser.print_help()
print()
parser.error(' no commando specified')
sys.exit(1)
else:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ conf CMD ~~
if a[0] == 'conf' :
if o.active_profile==None:
if self.ini.has('profile') : o.active_profile = self.ini.get('profile')
else : o.active_profile = 'default'
if o.load_conf : self.load_profile(o)
elif o.list_conf : print(self.ini.toString(o.active_profile))
else :
if not o.set_host and not o.set_user and not o.set_pass and not o.set_port and not o.set_boxn and not o.set_name and not o.gen_keys:
parser.error(' no options specified')
else :
if o.set_port and not util.represents_int(o.set_port):
parser.error(' port must be a number')
sys.exit(1)
if o.set_boxn: self.ini.set('box' , o.set_boxn,o.active_profile+'.imap')
if o.set_host: self.ini.set('host', o.set_host,o.active_profile+'.imap')
if o.set_user: self.ini.set('user', o.set_user,o.active_profile+'.imap')
if o.set_pass: self.ini.set('pass', o.set_pass,o.active_profile+'.imap')
if o.set_port: self.ini.set('port', o.set_port,o.active_profile+'.imap')
if o.set_name: self.ini.set('name', o.set_name,o.active_profile+'.infos')
if o.gen_keys:
kg = crypt.KeyGen(256)
self.ini.set('key' ,kg.key,o.active_profile+'.keys')
self.ini.set('mark',kg.mark,o.active_profile+'.keys')
self.ini.set('salt','-¤-ImpraStorage-¤-',o.active_profile+'.keys')
if self.check_profile(o.active_profile):
self.ini.set('profile', o.active_profile)
self.ini.write()
self.ini.print(o.active_profile)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ~~ data CMD ~~
elif a[0] == 'data' :
o.active_profile = self.ini.get('profile')
if not o.list and not o.add and not o.get and not o.search and not o.remove :
parser.error(' no options specified')
else :
if self.check_profile(o.active_profile):
conf = core.ImpraConf(self.ini,o.active_profile)
impst = None
try:
impst = core.ImpraStorage(conf)
except crypt.BadKeyException as e :
print()
Clz.print(' it seems that your current profile `' , Clz.fgB1, False)
Clz.print(o.active_profile , Clz.fgB3, False)
Clz.print('` has a wrong key to decrypt index on server.' , Clz.fgB1)
Clz.print(' you can remove index but all presents files on the box `', Clz.fgB1, False)
Clz.print(conf.get('box','imap') , Clz.fgB3, False)
Clz.print('` will be unrecoverable\n' , Clz.fgB1, True, False)
remIndex = input(' remove index ? (yes/no) ')
if remIndex.lower()=='yes':
Clz.print(' ',Clz.OFF)
impst = core.ImpraStorage(conf, True)
else :
print()
print(' bye')
print()
sys.exit(1)
if o.list :
uid = conf.get('uid' ,'index')
date = conf.get('date','index')
account = conf.get('user','imap')
if impst.index != None:
noData = impst.index.isEmpty()
if uid == None or noData : uid = 'EMPTY'
if date == None or noData : date = ''
core.clear()
printLineSep(LINE_SEP_CHAR,LINE_SEP_LEN)
printHeaderTitle(APP_TITLE)
printHeaderPart('account',account)
printHeaderPart('index',uid)
printHeaderPart('box',impst.rootBox)
Clz.print(date, Clz.fgB7, True, True)
printLineSep(LINE_SEP_CHAR,LINE_SEP_LEN)
order = o.order
if o.order_inv is not None:
order = '-'+o.order_inv
impst.index.print(order)
elif o.add :
done = impst.addFile(o.add[0],o.add[1],o.user,o.category)
if done :
print('\n ',end='')
Clz.print(' == OK == ', Clz.bg2+Clz.fgb7)
print()
elif o.get :
ids = []
for sid in o.get.split(',') :
seq = sid.split('-')
if len(seq)==2 : ids.extend(range(int(seq[0]),int(seq[1])+1))
else: ids.append(sid)
for sid in ids :
key = impst.index.getById(str(sid))
if key !=None :
done = impst.getFile(key)
if done :
print('\n ',end='')
Clz.print(' == OK == ', Clz.bg2+Clz.fgb7)
print()
else:
print('\n ',end='')
Clz.print(' == `' , Clz.bg1+Clz.fgB7, False)
Clz.print(str(sid) , Clz.bg1+Clz.fgB3, False)
Clz.print('` is not a valid id == ', Clz.bg1+Clz.fgB7)
print()
elif o.search :
uid = conf.get('uid' ,'index')
date = conf.get('date','index')
account = conf.get('user','imap')
matchIds = impst.index.getByPattern(o.search)
core.clear()
if matchIds is not None:
printLineSep(LINE_SEP_CHAR,LINE_SEP_LEN)
printHeaderTitle(APP_TITLE)
printHeaderPart('account',account)
printHeaderPart('index',uid)
printHeaderPart('box',impst.rootBox)
Clz.print(date, Clz.fgB7, True, True)
printLineSep(LINE_SEP_CHAR,LINE_SEP_LEN)
Clz.print(' searching --' , Clz.fgB3, False)
Clz.print(' `'+o.search+'`' , Clz.fgB7, False)
Clz.print(' -- found ' , Clz.fgB3, False)
Clz.print(str(len(matchIds)), Clz.fgB1, False)
Clz.print(' results --' , Clz.fgB3)
printLineSep(LINE_SEP_CHAR,LINE_SEP_LEN)
order = o.order
if o.order_inv is not None:
order = '-'+o.order_inv
impst.index.print(o.order,matchIds)
else:
print('\n ',end='')
Clz.print(' == no match found for pattern `', Clz.bg3+Clz.fgB4, False)
Clz.print(o.search , Clz.bg3+Clz.fgB1, False)
Clz.print('` == ' , Clz.bg3+Clz.fgB4)
print()
elif o.remove :
key = impst.index.getById(o.remove)
if key !=None :
done = impst.removeFile(key)
if done :
print('\n ',end='')
Clz.print(' == OK == ', Clz.bg2+Clz.fgb7)
print()
else:
print('\n ',end='')
Clz.print(' == `' , Clz.bg1+Clz.fgB7, False)
Clz.print(str(o.remove) , Clz.bg1+Clz.fgB3, False)
Clz.print('` is not a valid id == ', Clz.bg1+Clz.fgB7)
print()
else : parser.print_help()
print()
def check_profile(self,profile):
""""""
return self.ini.hasSection(profile+'.keys') and self.ini.has('host',profile+'.imap') and self.ini.has('user',profile+'.imap') and self.ini.has('pass',profile+'.imap') and self.ini.has('port',profile+'.imap')
def load_profile(self,o):
""""""
if self.check_profile(o.active_profile):
print('profile '+o.active_profile+' loaded')
self.ini.set('profile', o.active_profile)
self.ini.write()
else :
if not self.ini.hasSection(o.active_profile+'.imap'):
print('profile '+o.active_profile+' don\'t exist !')
else :
print('profile '+o.active_profile+' can\'t be load - incomplete\n (did you remember to generate keys ?)')