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
|
||||
serverconfig.py
|
||||
*.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.
|
||||
|
||||
== 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
|
||||
* Copier clientconfig.example.py en clientconfig.py et adapter
|
||||
à vos besoins
|
||||
* Si ce n'est déjà fait, indiquer votre clé publique sur gest_crans
|
||||
* ???
|
||||
|
||||
|
|
|
@ -9,6 +9,12 @@ servers = {
|
|||
'/root/cranspasswords/server'],
|
||||
'user' : os.getenv('USER') # À définir à la main pour les personnes
|
||||
# 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 argparse
|
||||
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:
|
||||
PASS = re.compile('[\t ]*pass(?:word)?[\t ]*:[\t ]*(.*)\r?\n?$', \
|
||||
|
@ -29,7 +36,8 @@ GPG_ARGS = {
|
|||
|
||||
DEBUG = 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
|
||||
NROLES = None # Droits à définir sur le fichier en édition
|
||||
SERVER = None
|
||||
|
@ -56,6 +64,18 @@ def gpg(command, args = None):
|
|||
proc.stderr.close()
|
||||
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
|
||||
|
||||
|
@ -84,14 +104,17 @@ def remote_command(command, arg = None, stdin_contents = None):
|
|||
sshin.close()
|
||||
return json.loads(sshout.read())
|
||||
|
||||
@simple_memoize
|
||||
def all_keys():
|
||||
"""Récupère les clés du serveur distant"""
|
||||
return remote_command("listkeys")
|
||||
|
||||
@simple_memoize
|
||||
def all_roles():
|
||||
"""Récupère les roles du serveur distant"""
|
||||
return remote_command("listroles")
|
||||
|
||||
@simple_memoize
|
||||
def all_files():
|
||||
"""Récupère les fichiers du serveur distant"""
|
||||
return remote_command("listfiles")
|
||||
|
@ -108,11 +131,19 @@ def rm_file(filename):
|
|||
"""Supprime le fichier sur le serveur distant"""
|
||||
return remote_command("rmfile", filename)
|
||||
|
||||
@simple_memoize
|
||||
def get_my_roles():
|
||||
"""Retoure la liste des rôles perso"""
|
||||
allr = all_roles()
|
||||
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
|
||||
|
||||
|
@ -140,17 +171,38 @@ def check_keys():
|
|||
return True
|
||||
return False
|
||||
|
||||
def encrypt(roles, contents):
|
||||
"""Chiffre le contenu pour les roles donnés"""
|
||||
|
||||
def get_recipients_of_roles(roles):
|
||||
"""Renvoie les destinataires d'un rôle"""
|
||||
recipients = set()
|
||||
allroles = all_roles()
|
||||
allkeys = all_keys()
|
||||
|
||||
email_recipients = []
|
||||
for role in roles:
|
||||
for recipient in allroles[role]:
|
||||
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:
|
||||
email, key = allkeys[recipient]
|
||||
if key:
|
||||
|
@ -194,25 +246,39 @@ def get_password(name):
|
|||
|
||||
## Interface
|
||||
|
||||
def editor(texte):
|
||||
""" Lance $EDITOR sur texte"""
|
||||
f = tempfile.NamedTemporaryFile()
|
||||
def editor(texte, annotations=""):
|
||||
""" Lance $EDITOR sur texte.
|
||||
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)
|
||||
f.write(texte)
|
||||
for l in annotations.split('\n'):
|
||||
f.write("# %s\n" % l.encode('utf-8'))
|
||||
f.flush()
|
||||
proc = subprocess.Popen(os.getenv('EDITOR') + ' ' + f.name,shell=True)
|
||||
os.waitpid(proc.pid,0)
|
||||
f.seek(0)
|
||||
ntexte = f.read()
|
||||
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():
|
||||
proc = subprocess.Popen("cat",stdin=subprocess.PIPE,shell=True)
|
||||
out = proc.stdin
|
||||
out.write("""Liste des fichiers disponibles\n""" )
|
||||
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([])
|
||||
out.write(" %s %s (%s)\n" % ((access and '+' or '-'),fname,", ".join(froles)))
|
||||
out.write("""--Mes roles: %s\n""" % \
|
||||
|
@ -227,6 +293,11 @@ def show_roles():
|
|||
if role.endswith('-w'): continue
|
||||
print " * " + role
|
||||
|
||||
def show_servers():
|
||||
print """Liste des serveurs disponibles"""
|
||||
for server in config.servers.keys():
|
||||
print " * " + server
|
||||
|
||||
old_clipboard = None
|
||||
def saveclipboard(restore=False):
|
||||
global old_clipboard
|
||||
|
@ -284,12 +355,15 @@ def show_file(fname):
|
|||
def edit_file(fname):
|
||||
value = get_file(fname)
|
||||
nfile = False
|
||||
annotations = u""
|
||||
if value == False:
|
||||
nfile = True
|
||||
print "Fichier introuvable"
|
||||
if not confirm("Créer fichier ?"):
|
||||
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()
|
||||
# Par défaut les roles d'un fichier sont ceux en écriture de son
|
||||
# créateur
|
||||
|
@ -303,7 +377,16 @@ def edit_file(fname):
|
|||
sin.write(value['contents'])
|
||||
sin.close()
|
||||
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:
|
||||
print "Pas de modifications effectuées"
|
||||
else:
|
||||
|
@ -388,7 +471,7 @@ if __name__ == "__main__":
|
|||
help="Mode verbeux")
|
||||
parser.add_argument('-c','--clipboard',action='store_true',default=None,
|
||||
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',
|
||||
help="Ne PAS stocker le mot de passe dans le presse papier")
|
||||
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',
|
||||
default=show_file,const=show_roles,
|
||||
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',
|
||||
default=show_file,const=update_role,
|
||||
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