Merge branch 'master' of ssh://git.crans.org/git/cranspasswords
This commit is contained in:
commit
57ff241359
6 changed files with 216 additions and 17 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
||||||
clientconfig.py
|
clientconfig.py
|
||||||
serverconfig.py
|
serverconfig.py
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Dossier contenant les mots de passe
|
||||||
|
db
|
||||||
|
|
3
README
3
README
|
@ -3,9 +3,10 @@ Ce dépôt git contient à la fois le programme client (à utiliser sur votre
|
||||||
ordinateur) et le serveur.
|
ordinateur) et le serveur.
|
||||||
|
|
||||||
== Installation et configuration du client ==
|
== Installation et configuration du client ==
|
||||||
* Copier le dépôt git sur votre ordinateur:
|
* Copier le dépôt git sur votre ordinateur :
|
||||||
$ git clone git://git.crans.org/git/cranspasswords.git
|
$ git clone git://git.crans.org/git/cranspasswords.git
|
||||||
* Copier clientconfig.example.py en clientconfig.py et adapter
|
* Copier clientconfig.example.py en clientconfig.py et adapter
|
||||||
à vos besoins
|
à vos besoins
|
||||||
|
* Si ce n'est déjà fait, indiquer votre clé publique sur gest_crans
|
||||||
* ???
|
* ???
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,12 @@ servers = {
|
||||||
'/root/cranspasswords/server'],
|
'/root/cranspasswords/server'],
|
||||||
'user' : os.getenv('USER') # À définir à la main pour les personnes
|
'user' : os.getenv('USER') # À définir à la main pour les personnes
|
||||||
# n'ayant pas le même login sur leur pc
|
# n'ayant pas le même login sur leur pc
|
||||||
|
},
|
||||||
|
'ovh': {
|
||||||
|
'server_cmd': ['/usr/bin/ssh', 'ovh.crans.org',\
|
||||||
|
'/root/cranspasswords/server'],
|
||||||
|
'user' : os.getenv('USER') # À définir à la main pour les personnes
|
||||||
|
# n'ayant pas le même login sur leur pc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,14 @@ import os
|
||||||
import atexit
|
import atexit
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import clientconfig as config
|
import random
|
||||||
|
import string
|
||||||
|
import datetime
|
||||||
|
try:
|
||||||
|
import clientconfig as config
|
||||||
|
except ImportError:
|
||||||
|
print "Read the README"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
## Password pattern in files:
|
## Password pattern in files:
|
||||||
PASS = re.compile('[\t ]*pass(?:word)?[\t ]*:[\t ]*(.*)\r?\n?$', \
|
PASS = re.compile('[\t ]*pass(?:word)?[\t ]*:[\t ]*(.*)\r?\n?$', \
|
||||||
|
@ -29,7 +36,8 @@ GPG_ARGS = {
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
VERB = False
|
VERB = False
|
||||||
CLIPBOARD = bool(os.getenv('DISPLAY')) # Par défaut, place-t-on le mdp dans le presse-papier ?
|
# Par défaut, place-t-on le mdp dans le presse-papier ?
|
||||||
|
CLIPBOARD = bool(os.getenv('DISPLAY')) and os.path.exists('/usr/bin/xclip')
|
||||||
FORCED = False #Mode interactif qui demande confirmation
|
FORCED = False #Mode interactif qui demande confirmation
|
||||||
NROLES = None # Droits à définir sur le fichier en édition
|
NROLES = None # Droits à définir sur le fichier en édition
|
||||||
SERVER = None
|
SERVER = None
|
||||||
|
@ -56,6 +64,18 @@ def gpg(command, args = None):
|
||||||
proc.stderr.close()
|
proc.stderr.close()
|
||||||
return proc.stdin, proc.stdout
|
return proc.stdin, proc.stdout
|
||||||
|
|
||||||
|
|
||||||
|
class simple_memoize(object):
|
||||||
|
""" Memoization/Lazy """
|
||||||
|
def __init__(self, f):
|
||||||
|
self.f = f
|
||||||
|
self.val = None
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
if self.val==None:
|
||||||
|
self.val = self.f()
|
||||||
|
return self.val
|
||||||
|
|
||||||
######
|
######
|
||||||
## Remote commands
|
## Remote commands
|
||||||
|
|
||||||
|
@ -84,14 +104,17 @@ def remote_command(command, arg = None, stdin_contents = None):
|
||||||
sshin.close()
|
sshin.close()
|
||||||
return json.loads(sshout.read())
|
return json.loads(sshout.read())
|
||||||
|
|
||||||
|
@simple_memoize
|
||||||
def all_keys():
|
def all_keys():
|
||||||
"""Récupère les clés du serveur distant"""
|
"""Récupère les clés du serveur distant"""
|
||||||
return remote_command("listkeys")
|
return remote_command("listkeys")
|
||||||
|
|
||||||
|
@simple_memoize
|
||||||
def all_roles():
|
def all_roles():
|
||||||
"""Récupère les roles du serveur distant"""
|
"""Récupère les roles du serveur distant"""
|
||||||
return remote_command("listroles")
|
return remote_command("listroles")
|
||||||
|
|
||||||
|
@simple_memoize
|
||||||
def all_files():
|
def all_files():
|
||||||
"""Récupère les fichiers du serveur distant"""
|
"""Récupère les fichiers du serveur distant"""
|
||||||
return remote_command("listfiles")
|
return remote_command("listfiles")
|
||||||
|
@ -108,11 +131,19 @@ def rm_file(filename):
|
||||||
"""Supprime le fichier sur le serveur distant"""
|
"""Supprime le fichier sur le serveur distant"""
|
||||||
return remote_command("rmfile", filename)
|
return remote_command("rmfile", filename)
|
||||||
|
|
||||||
|
@simple_memoize
|
||||||
def get_my_roles():
|
def get_my_roles():
|
||||||
"""Retoure la liste des rôles perso"""
|
"""Retoure la liste des rôles perso"""
|
||||||
allr = all_roles()
|
allr = all_roles()
|
||||||
return filter(lambda role: SERVER['user'] in allr[role],allr.keys())
|
return filter(lambda role: SERVER['user'] in allr[role],allr.keys())
|
||||||
|
|
||||||
|
def gen_password():
|
||||||
|
"""Generate random password"""
|
||||||
|
random.seed(datetime.datetime.now().microsecond)
|
||||||
|
chars = string.letters + string.digits + '/=+*'
|
||||||
|
length = 15
|
||||||
|
return ''.join([random.choice(chars) for _ in xrange(length)])
|
||||||
|
|
||||||
######
|
######
|
||||||
## Local commands
|
## Local commands
|
||||||
|
|
||||||
|
@ -140,17 +171,38 @@ def check_keys():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def encrypt(roles, contents):
|
def get_recipients_of_roles(roles):
|
||||||
"""Chiffre le contenu pour les roles donnés"""
|
"""Renvoie les destinataires d'un rôle"""
|
||||||
|
|
||||||
recipients = set()
|
recipients = set()
|
||||||
allroles = all_roles()
|
allroles = all_roles()
|
||||||
allkeys = all_keys()
|
|
||||||
|
|
||||||
email_recipients = []
|
|
||||||
for role in roles:
|
for role in roles:
|
||||||
for recipient in allroles[role]:
|
for recipient in allroles[role]:
|
||||||
recipients.add(recipient)
|
recipients.add(recipient)
|
||||||
|
|
||||||
|
return recipients
|
||||||
|
|
||||||
|
def get_dest_of_roles(roles):
|
||||||
|
""" Summarize recipients of a role """
|
||||||
|
allkeys = all_keys()
|
||||||
|
def additionnal_info(rec):
|
||||||
|
""" Gives additionnal information for a given recipient """
|
||||||
|
if len(allkeys[rec]) == 0:
|
||||||
|
return ""
|
||||||
|
out = allkeys[rec][0]
|
||||||
|
if len(allkeys[rec]) > 1:
|
||||||
|
out += " -> " + allkeys[rec][1]
|
||||||
|
return "(%s)" % out
|
||||||
|
|
||||||
|
return ["%s %s" % (rec, additionnal_info(rec)) for rec in \
|
||||||
|
get_recipients_of_roles(roles) ]
|
||||||
|
|
||||||
|
def encrypt(roles, contents):
|
||||||
|
"""Chiffre le contenu pour les roles donnés"""
|
||||||
|
|
||||||
|
allkeys = all_keys()
|
||||||
|
recipients = get_recipients_of_roles(roles)
|
||||||
|
|
||||||
|
email_recipients = []
|
||||||
for recipient in recipients:
|
for recipient in recipients:
|
||||||
email, key = allkeys[recipient]
|
email, key = allkeys[recipient]
|
||||||
if key:
|
if key:
|
||||||
|
@ -194,25 +246,39 @@ def get_password(name):
|
||||||
|
|
||||||
## Interface
|
## Interface
|
||||||
|
|
||||||
def editor(texte):
|
def editor(texte, annotations=""):
|
||||||
""" Lance $EDITOR sur texte"""
|
""" Lance $EDITOR sur texte.
|
||||||
f = tempfile.NamedTemporaryFile()
|
Renvoie le nouveau texte si des modifications ont été apportées, ou None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Avoid syntax hilight with ".txt". Would be nice to have some colorscheme
|
||||||
|
# for annotations ...
|
||||||
|
f = tempfile.NamedTemporaryFile(suffix='.txt')
|
||||||
atexit.register(f.close)
|
atexit.register(f.close)
|
||||||
f.write(texte)
|
f.write(texte)
|
||||||
|
for l in annotations.split('\n'):
|
||||||
|
f.write("# %s\n" % l.encode('utf-8'))
|
||||||
f.flush()
|
f.flush()
|
||||||
proc = subprocess.Popen(os.getenv('EDITOR') + ' ' + f.name,shell=True)
|
proc = subprocess.Popen(os.getenv('EDITOR') + ' ' + f.name,shell=True)
|
||||||
os.waitpid(proc.pid,0)
|
os.waitpid(proc.pid,0)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
ntexte = f.read()
|
ntexte = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return texte <> ntexte and ntexte or None
|
ntexte = '\n'.join(filter(lambda l: not l.startswith('#'), ntexte.split('\n')))
|
||||||
|
if texte != ntexte:
|
||||||
|
return ntexte
|
||||||
|
return None
|
||||||
|
|
||||||
def show_files():
|
def show_files():
|
||||||
proc = subprocess.Popen("cat",stdin=subprocess.PIPE,shell=True)
|
proc = subprocess.Popen("cat",stdin=subprocess.PIPE,shell=True)
|
||||||
out = proc.stdin
|
out = proc.stdin
|
||||||
out.write("""Liste des fichiers disponibles\n""" )
|
out.write("""Liste des fichiers disponibles\n""" )
|
||||||
my_roles = get_my_roles()
|
my_roles = get_my_roles()
|
||||||
for (fname,froles) in all_files().iteritems():
|
files = all_files()
|
||||||
|
keys = files.keys()
|
||||||
|
keys.sort()
|
||||||
|
for fname in keys:
|
||||||
|
froles = files[fname]
|
||||||
access = set(my_roles).intersection(froles) != set([])
|
access = set(my_roles).intersection(froles) != set([])
|
||||||
out.write(" %s %s (%s)\n" % ((access and '+' or '-'),fname,", ".join(froles)))
|
out.write(" %s %s (%s)\n" % ((access and '+' or '-'),fname,", ".join(froles)))
|
||||||
out.write("""--Mes roles: %s\n""" % \
|
out.write("""--Mes roles: %s\n""" % \
|
||||||
|
@ -227,6 +293,11 @@ def show_roles():
|
||||||
if role.endswith('-w'): continue
|
if role.endswith('-w'): continue
|
||||||
print " * " + role
|
print " * " + role
|
||||||
|
|
||||||
|
def show_servers():
|
||||||
|
print """Liste des serveurs disponibles"""
|
||||||
|
for server in config.servers.keys():
|
||||||
|
print " * " + server
|
||||||
|
|
||||||
old_clipboard = None
|
old_clipboard = None
|
||||||
def saveclipboard(restore=False):
|
def saveclipboard(restore=False):
|
||||||
global old_clipboard
|
global old_clipboard
|
||||||
|
@ -284,12 +355,15 @@ def show_file(fname):
|
||||||
def edit_file(fname):
|
def edit_file(fname):
|
||||||
value = get_file(fname)
|
value = get_file(fname)
|
||||||
nfile = False
|
nfile = False
|
||||||
|
annotations = u""
|
||||||
if value == False:
|
if value == False:
|
||||||
nfile = True
|
nfile = True
|
||||||
print "Fichier introuvable"
|
print "Fichier introuvable"
|
||||||
if not confirm("Créer fichier ?"):
|
if not confirm("Créer fichier ?"):
|
||||||
return
|
return
|
||||||
texte = ""
|
annotations += u"""Ceci est un fichier initial contenant un mot de passe
|
||||||
|
aléatoire, pensez à rajouter une ligne "login: ${login}" """
|
||||||
|
texte = "pass: %s\n" % gen_password()
|
||||||
roles = get_my_roles()
|
roles = get_my_roles()
|
||||||
# Par défaut les roles d'un fichier sont ceux en écriture de son
|
# Par défaut les roles d'un fichier sont ceux en écriture de son
|
||||||
# créateur
|
# créateur
|
||||||
|
@ -303,7 +377,16 @@ def edit_file(fname):
|
||||||
sin.write(value['contents'])
|
sin.write(value['contents'])
|
||||||
sin.close()
|
sin.close()
|
||||||
texte = sout.read()
|
texte = sout.read()
|
||||||
ntexte = editor(texte)
|
value['roles'] = NROLES or value['roles']
|
||||||
|
|
||||||
|
annotations += u"Ce fichier sera chiffré pour les rôles suivants :\n%s\n\
|
||||||
|
C'est-à-dire pour les utilisateurs suivants :\n%s" % (
|
||||||
|
', '.join(value['roles']),
|
||||||
|
'\n'.join(' %s' % rec for rec in get_dest_of_roles(value['roles']))
|
||||||
|
)
|
||||||
|
|
||||||
|
ntexte = editor(texte, annotations)
|
||||||
|
|
||||||
if ntexte == None and not nfile and NROLES == None:
|
if ntexte == None and not nfile and NROLES == None:
|
||||||
print "Pas de modifications effectuées"
|
print "Pas de modifications effectuées"
|
||||||
else:
|
else:
|
||||||
|
@ -388,7 +471,7 @@ if __name__ == "__main__":
|
||||||
help="Mode verbeux")
|
help="Mode verbeux")
|
||||||
parser.add_argument('-c','--clipboard',action='store_true',default=None,
|
parser.add_argument('-c','--clipboard',action='store_true',default=None,
|
||||||
help="Stocker le mot de passe dans le presse papier")
|
help="Stocker le mot de passe dans le presse papier")
|
||||||
parser.add_argument('--noclipboard',action='store_false',default=None,
|
parser.add_argument('--no-clip', '--noclip', '--noclipboard',action='store_false',default=None,
|
||||||
dest='clipboard',
|
dest='clipboard',
|
||||||
help="Ne PAS stocker le mot de passe dans le presse papier")
|
help="Ne PAS stocker le mot de passe dans le presse papier")
|
||||||
parser.add_argument('-f','--force',action='store_true',default=False,
|
parser.add_argument('-f','--force',action='store_true',default=False,
|
||||||
|
@ -417,6 +500,9 @@ if __name__ == "__main__":
|
||||||
action_grp.add_argument('--list-roles',action='store_const',dest='action',
|
action_grp.add_argument('--list-roles',action='store_const',dest='action',
|
||||||
default=show_file,const=show_roles,
|
default=show_file,const=show_roles,
|
||||||
help="Lister les rôles des gens")
|
help="Lister les rôles des gens")
|
||||||
|
action_grp.add_argument('--list-servers',action='store_const',dest='action',
|
||||||
|
default=show_file,const=show_servers,
|
||||||
|
help="Lister les rôles serveurs")
|
||||||
action_grp.add_argument('--recrypt-role',action='store_const',dest='action',
|
action_grp.add_argument('--recrypt-role',action='store_const',dest='action',
|
||||||
default=show_file,const=update_role,
|
default=show_file,const=update_role,
|
||||||
help="Met à jour (reencode les roles)")
|
help="Met à jour (reencode les roles)")
|
||||||
|
|
101
cranspasswords_bash_completion
Normal file
101
cranspasswords_bash_completion
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
# Fonction de notre auto completion
|
||||||
|
|
||||||
|
contain(){
|
||||||
|
local i
|
||||||
|
for i in $2; do
|
||||||
|
if [[ "$i" = "$1" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "$EDITOR" = "" ]]; then
|
||||||
|
export EDITOR="nano";
|
||||||
|
fi
|
||||||
|
|
||||||
|
_cranspasswords(){
|
||||||
|
# declaration des variables locales
|
||||||
|
local argc first last prev cur cur_first_char opts_short opts role_dir pass_dir server server_list role_list pass_list timeout
|
||||||
|
|
||||||
|
role_dir="/tmp/cranspasswords-$USER-role/"
|
||||||
|
pass_dir="/tmp/cranspasswords-$USER-passwords/"
|
||||||
|
# Combien de temps on garde les réponses du serveur en cache (en minutes)
|
||||||
|
timeout=5
|
||||||
|
|
||||||
|
#COMPREPLY désigne la réponse à renvoyer pour la complétion actuelle
|
||||||
|
COMPREPLY=()
|
||||||
|
# argc : vaut le nombre d'argument actuel sur la ligne de commande
|
||||||
|
argc=${COMP_CWORD};
|
||||||
|
|
||||||
|
# cur : désigne la chaine de caractère actuelle pour le dernier mot de la ligne de commande
|
||||||
|
first="${COMP_WORDS[1]}"
|
||||||
|
last="${COMP_WORDS[$(($argc - 1 ))]}"
|
||||||
|
prev="${COMP_WORDS[$(($argc - 2 ))]}"
|
||||||
|
cur="${COMP_WORDS[argc]}"
|
||||||
|
cur_first_char=${cur:0:1}
|
||||||
|
opts_short="-h -v -c -f -l"
|
||||||
|
opts="--help --server --verbose --clipboard --noclipboard --force --edit --view --remove --list --check-keys --update-keys --list-roles --recrypt-roles --roles --list-servers"
|
||||||
|
|
||||||
|
|
||||||
|
mkdir -p -m 700 "$role_dir"
|
||||||
|
mkdir -p -m 700 "$pass_dir"
|
||||||
|
|
||||||
|
find "$role_dir" -type f -mmin +$timeout -exec rm -f {} \;
|
||||||
|
find "$pass_dir" -type f -mmin +$timeout -exec rm -f {} \;
|
||||||
|
|
||||||
|
# On détermine si on utilsie un serveur alternatif
|
||||||
|
if contain "--server" "${COMP_WORDS[*]}"; then
|
||||||
|
if [[ "$prev" = "--server" ]]; then
|
||||||
|
_cranspasswords_server=$last;
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_cranspasswords_server="default";
|
||||||
|
fi
|
||||||
|
|
||||||
|
server=$_cranspasswords_server
|
||||||
|
|
||||||
|
# les options possibles pour notre auto-complétion
|
||||||
|
if [[ $cur_first_char = "-" ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W "$opts" -- $cur ) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$last" = "--server" ]]; then
|
||||||
|
server_list="`cranspasswords --list-servers | grep -- "*" | awk '{print $2}'`"
|
||||||
|
COMPREPLY=( $(compgen -W "$server_list" -- $cur ) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$last" = "--roles" ]]; then
|
||||||
|
if [ ! -f "${role_dir}$server" ]; then
|
||||||
|
echo "`cranspasswords --server $server --list-roles | grep -- "*" | awk '{print $2}'`" > "${role_dir}$server"
|
||||||
|
fi
|
||||||
|
role_list="`cat "${role_dir}$server"`"
|
||||||
|
COMPREPLY=( $(compgen -W "$role_list" -- $cur ) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$last" = "--edit" ]]; then
|
||||||
|
if [ ! -f "${pass_dir}${server}-w" ]; then
|
||||||
|
echo "`cranspasswords --server $server -l | grep "+" | awk '{print $2}'`" > "${pass_dir}${server}-w"
|
||||||
|
fi
|
||||||
|
pass_list="`cat "${pass_dir}${server}-w"`"
|
||||||
|
COMPREPLY=( $(compgen -W "$pass_list" -- $cur ) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if true; then
|
||||||
|
if [ ! -f "${pass_dir}$server" ]; then
|
||||||
|
echo "`cranspasswords --server $server -l | grep "\( +\| -\)" | awk '{print $2}'`" > "${pass_dir}$server"
|
||||||
|
fi
|
||||||
|
pass_list="`cat "${pass_dir}$server"`"
|
||||||
|
COMPREPLY=( $(compgen -W "$pass_list" -- $cur ) )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# On active l'auto-completion
|
||||||
|
complete -F _cranspasswords cranspasswords
|
2
server
Executable file
2
server
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
sudo /root/cranspasswords/server.py $*
|
Loading…
Add table
Add a link
Reference in a new issue