From e51957ff12270ba4316475e207eeca6117fcdf33 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 26 Dec 2012 12:17:31 +0100 Subject: [PATCH 01/13] On ajoute le fichier de commande shell "server" --- server | 2 ++ 1 file changed, 2 insertions(+) create mode 100755 server diff --git a/server b/server new file mode 100755 index 0000000..ac12868 --- /dev/null +++ b/server @@ -0,0 +1,2 @@ +#!/bin/bash +sudo /root/cranspasswords/server.py $* From 9bc3d2993d07f281dc9861b6f9a0876cf8e0da5d Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Fri, 28 Dec 2012 01:56:46 +0100 Subject: [PATCH 02/13] Crash gentillement si pas de clientconfig --- cranspasswords.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cranspasswords.py b/cranspasswords.py index ee9d25c..b162d2f 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -10,7 +10,11 @@ import os import atexit import argparse import re -import clientconfig as config +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?$', \ From d7bcb936fee3f9d9f05672f28a71b95a92284a65 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Fri, 28 Dec 2012 01:57:05 +0100 Subject: [PATCH 03/13] no xclip => no clipboard --- cranspasswords.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cranspasswords.py b/cranspasswords.py index b162d2f..1e77aac 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -33,7 +33,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 From 90e46d70a14ef5e4d7b9da29cfc4c8e1d913c66f Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Fri, 25 Jan 2013 01:40:12 +0100 Subject: [PATCH 04/13] cranspasswords est aussi sur ovh --- clientconfig.example.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clientconfig.example.py b/clientconfig.example.py index 6448da7..3d8ea7f 100755 --- a/clientconfig.example.py +++ b/clientconfig.example.py @@ -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 } } From 5fbc58fb618126af32052dbedb4c00bcefd865e2 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Fri, 25 Jan 2013 01:42:02 +0100 Subject: [PATCH 05/13] On trie les fichiers avant de les afficher --- cranspasswords.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cranspasswords.py b/cranspasswords.py index 1e77aac..1a00bb1 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -217,7 +217,11 @@ def show_files(): 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""" % \ From 9857aae8f705eedefd52b6365f45ee9fc546ab8b Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Fri, 15 Feb 2013 03:23:30 +0100 Subject: [PATCH 06/13] =?UTF-8?q?Ajouts=20de=20commentaires=20dans=20l'?= =?UTF-8?q?=C3=A9diteur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Il permet de tester d'un coup d'œil les destinataires d'un chiffrement. Ceci évite donc de se faire flouter par un serveur fournissant la mauvaise liste de destinataires. --- cranspasswords.py | 68 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/cranspasswords.py b/cranspasswords.py index 1a00bb1..b798c44 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -61,6 +61,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 @@ -89,14 +101,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") @@ -113,6 +128,7 @@ 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() @@ -145,17 +161,28 @@ 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): + allkeys = all_keys() + return ["%s (%s -> %s)" % (rec, allkeys[rec][0], allkeys[rec][1]) 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: @@ -199,18 +226,28 @@ 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) @@ -312,7 +349,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: From 2caa9f974bd25ad81b3cfe4fb062416e370296cb Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sat, 16 Feb 2013 18:01:45 +0100 Subject: [PATCH 07/13] =?UTF-8?q?Ajout=20d'un=20premier=20script=20de=20ba?= =?UTF-8?q?sh=20compl=C3=A9tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cranspasswords.py | 8 +++ cranspasswords_bash_completion | 90 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 cranspasswords_bash_completion diff --git a/cranspasswords.py b/cranspasswords.py index b798c44..9c2de2b 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -273,6 +273,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 @@ -472,6 +477,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)") diff --git a/cranspasswords_bash_completion b/cranspasswords_bash_completion new file mode 100644 index 0000000..6b9aced --- /dev/null +++ b/cranspasswords_bash_completion @@ -0,0 +1,90 @@ +# 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 + server=$last; + fi + else + server="default"; + fi + + # 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 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 From 3b1bf0e77fd98eb52f1cc8ef5c4691cace485e1a Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sat, 16 Feb 2013 18:12:26 +0100 Subject: [PATCH 08/13] Oups, on ne stocker pas le serveur courant juste dans le scope de la fonction de bash completion. --- cranspasswords_bash_completion | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cranspasswords_bash_completion b/cranspasswords_bash_completion index 6b9aced..fd9bb6a 100644 --- a/cranspasswords_bash_completion +++ b/cranspasswords_bash_completion @@ -48,12 +48,14 @@ _cranspasswords(){ # On détermine si on utilsie un serveur alternatif if contain "--server" "${COMP_WORDS[*]}"; then if [[ "$prev" = "--server" ]]; then - server=$last; + _cranspasswords_server=$last; fi else - server="default"; + _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 ) ) From 2b1f77b24c93cda574076c466f1b3b7ff05004a3 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Mon, 18 Feb 2013 02:31:30 +0100 Subject: [PATCH 09/13] =?UTF-8?q?On=20autocompl=C3=A8te=20--edit=20que=20s?= =?UTF-8?q?i=20ont=20peut=20=C3=A9diter=20les=20fichiers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cranspasswords_bash_completion | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cranspasswords_bash_completion b/cranspasswords_bash_completion index fd9bb6a..236e63c 100644 --- a/cranspasswords_bash_completion +++ b/cranspasswords_bash_completion @@ -77,9 +77,18 @@ server=$_cranspasswords_server 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" + 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 ) ) From e6b1a41a54403d30b2b528eb2f763de02918ac29 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Fri, 22 Feb 2013 04:10:20 +0100 Subject: [PATCH 10/13] generates new password at first edit --- cranspasswords.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cranspasswords.py b/cranspasswords.py index 9c2de2b..88e10cb 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -10,6 +10,9 @@ import os import atexit import argparse import re +import random +import string +import datetime try: import clientconfig as config except ImportError: @@ -134,6 +137,13 @@ def get_my_roles(): 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 @@ -335,12 +345,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 @@ -356,7 +369,7 @@ def edit_file(fname): texte = sout.read() value['roles'] = NROLES or value['roles'] - annotations = u"Ce fichier sera chiffré pour les rôles suivants :\n%s\n\ + 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'])) From 16b72c68adb3d23df6236f6cbdfbf03c23b10ee7 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Tue, 26 Feb 2013 04:51:07 +0100 Subject: [PATCH 11/13] On peut aussi faire --no-clip ou --noclip --- cranspasswords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cranspasswords.py b/cranspasswords.py index 88e10cb..ea77ed0 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -461,7 +461,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, From 7b354dcb225dea7114c56742c811c38528a15b83 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Tue, 26 Feb 2013 23:08:10 +0100 Subject: [PATCH 12/13] improves recipients' details --- README | 3 ++- cranspasswords.py | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README b/README index 26c1c62..006355c 100644 --- a/README +++ b/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 * ??? diff --git a/cranspasswords.py b/cranspasswords.py index 88e10cb..d613af3 100755 --- a/cranspasswords.py +++ b/cranspasswords.py @@ -182,9 +182,19 @@ def get_recipients_of_roles(roles): return recipients def get_dest_of_roles(roles): + """ Summarize recipients of a role """ allkeys = all_keys() - return ["%s (%s -> %s)" % (rec, allkeys[rec][0], allkeys[rec][1]) for rec in \ - get_recipients_of_roles(roles)] + 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""" From 0c127bdf1c4b4cd3653a495ddd3a5b32fb3c7316 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Mon, 4 Mar 2013 15:30:45 +0100 Subject: [PATCH 13/13] [.gitignore] On ne versionne surtout pas le dossier contenant les mots de passe. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3b5fe9a..a2a2957 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ clientconfig.py serverconfig.py *.pyc + +# Dossier contenant les mots de passe +db