diff options
author | Brian Dolbec <dolsen@gentoo.org> | 2014-12-20 13:17:27 -0800 |
---|---|---|
committer | Brian Dolbec <dolsen@gentoo.org> | 2014-12-22 14:57:48 -0800 |
commit | a153cacf6b47788c9a017c37f78469e009e4ffff (patch) | |
tree | 249a0ea9461276ed921ff5c56b08a474626080f8 /gkeys-ldap | |
parent | Merge pull request #35 from gentoo/dol-sen-PR (diff) | |
download | gentoo-keys-a153cacf6b47788c9a017c37f78469e009e4ffff.tar.gz gentoo-keys-a153cacf6b47788c9a017c37f78469e009e4ffff.tar.bz2 gentoo-keys-a153cacf6b47788c9a017c37f78469e009e4ffff.zip |
Move the 3 pkgs into their own *-pkg dir
This makes releasing each pkg independently easier.
testpath: Update paths for the new directory structure
Diffstat (limited to 'gkeys-ldap')
-rw-r--r-- | gkeys-ldap/MANIFEST.in | 2 | ||||
-rwxr-xr-x | gkeys-ldap/bin/gkeys-ldap | 50 | ||||
-rwxr-xr-x | gkeys-ldap/bin/update-seeds.sh | 55 | ||||
-rw-r--r-- | gkeys-ldap/etc/update-seeds.conf | 11 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/__init__.py | 6 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/actions.py | 276 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/cli.py | 55 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/config.py | 39 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/connect.py | 50 | ||||
-rw-r--r-- | gkeys-ldap/gkeyldap/search.py | 68 | ||||
-rwxr-xr-x | gkeys-ldap/setup.py | 44 |
11 files changed, 656 insertions, 0 deletions
diff --git a/gkeys-ldap/MANIFEST.in b/gkeys-ldap/MANIFEST.in new file mode 100644 index 0000000..c1a7121 --- /dev/null +++ b/gkeys-ldap/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.md diff --git a/gkeys-ldap/bin/gkeys-ldap b/gkeys-ldap/bin/gkeys-ldap new file mode 100755 index 0000000..3d23ac1 --- /dev/null +++ b/gkeys-ldap/bin/gkeys-ldap @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +'''Gentoo-keys is a gpg key manager for managing + gentoo's gpg-signing keys. It is these keys that are + used to verify and validate release media, etc.. + + Distributed under the terms of the GNU General Public License v2 + + Copyright: + (c) 2011 Brian Dolbec + Distributed under the terms of the GNU General Public License v2 + + Author(s): + Brian Dolbec <dolsen@gentoo.org> + +''' + +from __future__ import print_function + +from gkeyldap.cli import Main + +import os +import sys + + +# This block ensures that ^C interrupts are handled quietly. +try: + import signal + + def exithandler(signum,frame): + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + print() + sys.exit(1) + + signal.signal(signal.SIGINT, exithandler) + signal.signal(signal.SIGTERM, exithandler) + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + +except KeyboardInterrupt: + print() + sys.exit(1) + +root = None +if 'ROOT' in os.environ: + root = os.environ['ROOT'] + +main = Main(root=root) +main() diff --git a/gkeys-ldap/bin/update-seeds.sh b/gkeys-ldap/bin/update-seeds.sh new file mode 100755 index 0000000..94852a1 --- /dev/null +++ b/gkeys-ldap/bin/update-seeds.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# $Id: update-seeds.sh,v 0.2.1 2014/10/12 dolsen Exp $ + +# configuration to run from a checkout with a custom config +cwd=$(pwd) +source ${cwd}/update-seeds.conf +source ${cwd}/testpath + +die(){ echo "$@" 1>&2; echo ""; exit 1; } +success(){ echo "$@"; echo ""; exit 0; } + +clone_api(){ + local target=dirname ${API_DIR} + cd target + git clone ${API_URL} +} + +# start update process +echo "Beginning seed file update" + +echo " *** updating api.gentoo.org repo" +# update api checkout +if [[ ! -d ${API_DIR} ]]; then + clone_api +else + cd ${API_DIR} && git pull +fi + +echo " *** Fetching new seeds from LDAP" +cd ${GKEYS_DIR} +gkey-ldap -c ${GKEYS_CONF} updateseeds || die "Seed file generation failed... aborting" + +echo " *** Checking if seed files are up-to-date" +if ! diff -q ${GKEYS_DIR}/${GKEYS_SEEDS} ${API_DIR}/${API_SEEDS} > /dev/null ;then + echo " *** Spotted differences" + echo " *** Updating old seeds with a new one" + # copy seeds to api + echo " ... cp ${GKEYS_SEEDS} ${API_DIR}/${API_SEEDS}" + cp ${GKEYS_SEEDS} ${API_DIR}/${API_SEEDS} +else + success " *** No changes detected" +fi + +echo "Signing new developers.seeds file" +gkeys -c ${GKEYS_CONF} sign -n ${GKEYS_SIGN} -F ${API_DIR}/${API_SEEDS} || die " *** Signing failed... exiting" + +echo "Committing changes to api repo..." +cd ${API_DIR} +git add ${API_SEEDS} || die " *** Failed to add modified developers.seeds file" +git add ${API_SEEDS}.${GKEYS_SIG} || die " *** Failed to add developer.seeds.sig file" +git commit -m "${GKEYS_COMMIT_MSG}" || die " *** Failed to commit updates" +git push origin master || die " *** git push failed" + +success "Successfully updated developer.seeds" + diff --git a/gkeys-ldap/etc/update-seeds.conf b/gkeys-ldap/etc/update-seeds.conf new file mode 100644 index 0000000..71fda58 --- /dev/null +++ b/gkeys-ldap/etc/update-seeds.conf @@ -0,0 +1,11 @@ +#!/bin/sh + +export API_DIR="/var/lib/gkeys/api" +export API_SEEDS="files/gentoo-keys/seeds/developer.seeds" +export API_URL="git+ssh://git@git.gentoo.org/proj/api.git" +export GKEYS_COMMIT_MSG="Gentoo-keys: Update developer.seeds" +export GKEYS_CONF="/var/lib/gkeys/gkeys.conf" +export GKEYS_DIR="/var/lib/gkeys" +export GKEYS_SEEDS="seeds/developer.seeds" +export GKEYS_SIG="sig" +export GKEYS_SIGN="gkeys" diff --git a/gkeys-ldap/gkeyldap/__init__.py b/gkeys-ldap/gkeyldap/__init__.py new file mode 100644 index 0000000..9789d69 --- /dev/null +++ b/gkeys-ldap/gkeyldap/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +__version__ = 'Git' +__license__ = 'GPLv2' + diff --git a/gkeys-ldap/gkeyldap/actions.py b/gkeys-ldap/gkeyldap/actions.py new file mode 100644 index 0000000..cea4648 --- /dev/null +++ b/gkeys-ldap/gkeyldap/actions.py @@ -0,0 +1,276 @@ +# +#-*- coding:utf-8 -*- + +""" + Gentoo-keys - gkeyldap/actions.py + + Primary api interface module + + @copyright: 2012 by Brian Dolbec <dol-sen@gentoo.org> + @license: GNU GPL2, see COPYING for details. +""" + +import re + +from collections import defaultdict +from gkeys.seed import Seeds +from gkeyldap.config import UID, gkey2ldap, gkey2SEARCH +from gkeyldap.search import LdapSearch +from gkeys.fileops import updatefiles + +Available_Actions = ['ldapsearch', 'updateseeds'] + +Action_Options = { + 'ldapsearch': ['fingerprint', 'mail', 'name', 'nick', 'seedfile', 'status'], + 'updateseeds': ['fingerprint', 'mail', 'name', 'nick', 'seedfile', 'status'], +} + + +def get_key_ids(key_len, keyids): + '''Small utility function to return only keyid (short) + or longkeyid's + + @param key_len: string, the key length desired + @param keyids: list of keysid's to process + @return list of the desired key length id's + ''' + result = [] + for keyid in keyids: + target_len = 16 + if keyid.startswith('0x'): + target_len = target_len + 2 + if len(keyid) == target_len: + result.append(keyid) + return result + + +class Actions(object): + + + def __init__(self, config, output=None, logger=None): + self.config = config + self.output = output + self.logger = logger + self.seeds = None + self.fingerprint_re = re.compile('[0-9A-Fa-f]{40}') + + + def ldapsearch(self, args): + l = LdapSearch() + self.logger.debug("MAIN: _action_ldapsearch; args = %s" % str(args)) + self.output("Search... Establishing connection\n") + if not l.status: + self.output("Aborting Search... Connection failed") + return False + attr, target, search_field = self.get_args(args) + results = l.search(target, search_field) + devs = l.result2dict(results, gkey2ldap[attr]) + for dev in sorted(devs): + self.output(dev, devs[dev]) + self.output("============================================") + self.output("Total number of developers in results:", len(devs)) + self.logger.info("============================================") + self.logger.info("Total number of developers in results: %d" % len(devs)) + return True + + + def updateseeds(self, args): + l = LdapSearch() + self.logger.debug("MAIN: _action_updateseeds; args = %s" % str(args)) + self.output("Search... Establishing connection") + if not l.status: + self.output("Aborting update... Connection failed") + return False + results = l.search('*', UID) + info = l.result2dict(results, 'uid') + self.logger.debug( + "MAIN: _action_updateseeds; got results :) converted to info") + if args.seedfile: + filename = self.config.get('seeds', args.seedfile) + '.new' + elif args.file: + filename = arg.file + if not self.create_seedfile(info, filename): + self.logger.error("Developer seed file update failure: " + "Original seed file is intact & untouched.") + self.output("Backing up existing file...") + status = updatefiles(self.config, self.logger) + if not status: + self.output("Develope seed failed to update!") + return False + self.output("Developer seed file updated!") + return True + + + def create_seedfile(self, devs, filename): + self.output("Creating seeds from LDAP data...") + self.seeds = Seeds(filename, self.config) + count = 0 + error_count = 0 + for dev in sorted(devs): + if devs[dev]['gentooStatus'][0] not in ['active']: + continue + #self.logger.debug("create_seedfile, dev = " + # "%s, %s" % (str(dev), str(devs[dev]))) + developer_attrs = self.build_gkey(devs[dev]) + if developer_attrs: + self.seeds.add(dev, developer_attrs) + count += 1 + else: + error_count += 1 + self.output("Total number of seeds created:", count) + self.output("Seeds created... Saving file: %s" % filename) + self.output("Total number of Dev's with GPG errors:", error_count) + self.logger.info("Total number of seeds created: %d" % count) + self.logger.info("Seeds created... Saving file: %s" % filename) + self.logger.info("Total number of Dev's with GPG errors: %d" % error_count) + return self.seeds.save() + + + @staticmethod + def get_args(args): + for attr in ['nick', 'name', 'gpgkey', 'fingerprint', 'status']: + if attr: + target = getattr(args, attr) + search_field = gkey2SEARCH[attr] + break + return (attr, target, search_field) + + + def build_gkey(self, info): + keyinfo = defaultdict() + keyid_missing = False + # self.logger.debug("Actions: build_gkeylist; info = %s" % str(info)) + for attr, field in list(gkey2ldap.items()): + try: + keyinfo[attr], keyid_found, is_good = self._fix_bad_ldap(info, attr, field) + except KeyError: + self.logger.debug('LDAP info for: %s, %s' + % (info['uid'][0], info['cn'][0])) + self.logger.debug(' MISSING or EMPTY LDAP field ' + + '[%s] GPGKey field [%s]' % (field, attr)) + if attr in ['fingerprint', 'keyid', 'longkeyid']: + keyid_missing = True + else: + is_good = False + keyinfo[attr] = None + if not keyid_found and keyid_missing: + keyinfo, is_good = self._check_bad_fingerprint(info, keyinfo) + if is_good: + if keyinfo['fingerprint']: # fingerprints exist check + is_ok = self._check_fingerprint_integrity(info, keyinfo) + is_match = self._check_id_fingerprint_match(info, keyinfo) + if not is_ok or not is_match: + is_good = False + if is_good: + # some developers add lowercase fingerprints + keyinfo['fingerprint'] = [key.upper() for key in keyinfo['fingerprint']] + # drop keyid and longkeyid + keyinfo.pop('keyid', None) + keyinfo.pop('longkeyid', None) + return keyinfo + return None + + + def _fix_bad_ldap(self, info, attr, field): + '''Fix bad LDAP values + + @param info: dictionary with raw LDAP attributes and values + @param attr: string key for new Gentoo Keys attributes + @param field: string key for LDAP attributes + ''' + + is_good = True + keyid_found = False + values = info[field] + # strip errant line feeds + values = [y.strip('\n') for y in values] + # separate out short/long key id's + if values and attr in ['keyid', 'longkeyid']: + if len(get_key_ids(attr, values)): + keyid_found = True + elif values and attr in ['fingerprint']: + values = [v.replace(' ', '') for v in values] + if 'undefined' in values: + self.logger.error('ERROR in LDAP info for: %s, %s' + % (info['uid'][0], info['cn'][0])) + self.logger.error(' %s = "undefined"' % (field)) + is_good = False + if values and attr in ['nick','name', 'keydir']: + values = "".join(values) + return (values, keyid_found, is_good) + + + def _check_bad_fingerprint(self, info, keyinfo): + '''Check and fix bad fingerprints and keyids + + @param info: dictionary with raw LDAP attributes + @param keyinfo: dictionary with new Gentoo Keys attributes + ''' + is_good = True + fingerprint = None + try: + fingerprint = info[gkey2ldap['fingerprint']] + self.logger.debug(' Generate gpgkey, Found LDAP fingerprint field') + except KeyError: + gpgkey = 'Missing fingerprint from LDAP info' + self.logger.debug(' Generate gpgkey, LDAP fingerprint KeyError') + if fingerprint: + values = [y.strip('\n') for y in fingerprint] + values = [v.replace(' ', '') for v in values] + # assign it to gpgkey to prevent a possible + # "gpgkey" undefined error + gpgkey = ['0x' + x[-16:] for x in values] + keyinfo['longkeyid'] = gpgkey + self.logger.debug(' Generate gpgkey, NEW keyinfo[\'fingerprint\'] = %s' + % str(keyinfo['longkeyid'])) + else: + gpgkey = 'Missing or Bad fingerprint from LDAP info' + is_good = False + if not keyinfo['longkeyid']: + self.logger.error('ERROR in ldap info for: %s, %s' + % (info['uid'][0], info['cn'][0])) + self.logger.error(' A valid keyid, longkeyid or fingerprint ' + 'was not found for %s : gpgkey = %s' % (info['cn'][0], gpgkey)) + is_good = False + return (keyinfo, is_good) + + + def _check_id_fingerprint_match(self, info, keyinfo): + # assume it's good until found an error is found + is_good = True + for attr in ['keyid', 'longkeyid']: + # skip blank id field + if not keyinfo[attr]: + continue + for y in keyinfo[attr]: + index = len(y.lstrip('0x')) + if y.lstrip('0x').upper() not in \ + [x[-index:].upper() for x in keyinfo['fingerprint']]: + self.logger.error('ERROR in LDAP info for: %s, %s' + %(info['uid'][0], info['cn'][0])) + self.logger.error(' ' + str(keyinfo)) + self.logger.error(' GPGKey id %s not found in the ' + % y.lstrip('0x') + 'listed fingerprint(s)') + is_good = False + return is_good + + + def _check_fingerprint_integrity(self, info, keyinfo): + # assume it's good until found an error is found + is_good = True + for fingerprint in keyinfo['fingerprint']: + # check fingerprint integrity + if len(fingerprint) != 40: + self.logger.error('ERROR in LDAP info for: %s, %s' + %(info['uid'][0], info['cn'][0])) + self.logger.error(' GPGKey incorrect fingerprint ' + + 'length (%s) for fingerprint: %s' %(len(fingerprint), fingerprint)) + is_good = False + continue + if not self.fingerprint_re.match(fingerprint): + self.logger.error('ERROR in LDAP info for: %s, %s' + % (info['uid'][0], info['cn'][0])) + self.logger.error(' GPGKey: Non hexadecimal digits in ' + + 'fingerprint for fingerprint: ' + fingerprint) + is_good = False + return is_good diff --git a/gkeys-ldap/gkeyldap/cli.py b/gkeys-ldap/gkeyldap/cli.py new file mode 100644 index 0000000..15f4dbd --- /dev/null +++ b/gkeys-ldap/gkeyldap/cli.py @@ -0,0 +1,55 @@ +# +#-*- coding:utf-8 -*- + +from __future__ import print_function + + +import sys + +from gkeys import config +from gkeys import seed + +from gkeys.base import CliBase +from gkeys.config import GKeysConfig +from gkeyldap import connect, search +from gkeyldap.actions import Actions, Available_Actions + + +class Main(CliBase): + '''Main command line interface class''' + + + def __init__(self, root=None, config=None, print_results=True): + """ Main class init function. + + @param root: string, root path to use + @param config: optional GKeysConfig instance, For API use + @param print_results: optional boolean, for API use + """ + self.root = root or "/" + self.config = config or GKeysConfig(root=root) + self.print_results = print_results + self.args = None + self.seeds = None + self.cli_config = { + 'Actions': Actions, + 'Available_Actions': Available_Actions, + 'Action_Options': Action_Options, + 'prog': 'gkey-ldap', + 'description': 'Gentoo-keys LDAP interface and seed file generator program', + 'epilog': '''Caution: adding untrusted keys to these keyrings can + be hazardous to your system!''' + } + + + def __call__(self, args=None): + """Main class call function + + @param args: Optional list of argumanets to parse and action to run + Defaults to sys.argv[1:] + """ + if args: + return self.run(self.parse_args(args)) + else: + return self.run(self.parse_args(sys.argv[1:])) + diff --git a/gkeys-ldap/gkeyldap/config.py b/gkeys-ldap/gkeyldap/config.py new file mode 100644 index 0000000..6e22a5c --- /dev/null +++ b/gkeys-ldap/gkeyldap/config.py @@ -0,0 +1,39 @@ +# +#-*- coding:utf-8 -*- + + +default_server = 'ldap://ldap1.gentoo.org' +# add uid to the results so you don't have to +# separate it out of the results tuple[0] value +default_fields = ['uid', 'cn', 'mail', 'gentooStatus', 'gpgkey', 'gpgfingerprint'] +default_criteria = 'ou=devs,dc=gentoo,dc=org' + +# establish a ldap fields to GKEY._fields map +gkey2ldap = { + 'nick': 'uid', + 'name': 'cn', + 'keyid': 'gpgkey', + 'longkeyid': 'gpgkey', + # map the uid to keydir, since we want + # dev keydir to be separate from each other + 'keydir': 'uid', + 'fingerprint': 'gpgfingerprint' +} + + +# Now for some search field defaults +UID = '(uid=%s)' +CN = '(cn=%s)' +STATUS = '(gentooStatus=%s)' +GPGKEY = '(gpgkey=%s)' +MAIL = '(mail=%s)' +GPGFINGERPRINT = '(gpgfingerprint=%s)' + +gkey2SEARCH = { + 'nick': UID, + 'name': CN, + 'status': STATUS, + 'keyid': GPGKEY, + 'mail': MAIL, + 'fingerprint': GPGFINGERPRINT, +} diff --git a/gkeys-ldap/gkeyldap/connect.py b/gkeys-ldap/gkeyldap/connect.py new file mode 100644 index 0000000..74e393c --- /dev/null +++ b/gkeys-ldap/gkeyldap/connect.py @@ -0,0 +1,50 @@ +# +#-*- coding:utf-8 -*- + + +try: + import ldap +except ImportError: + import sys + # py3.2 + if sys.hexversion >= 0x30200f0: + print('To run "ldap-seeds" in python 3, it requires a python3 ' + 'compatible version of dev-python/python-ldap be installed.\n' + 'Currently only dev-python/python-ldap-9999 has that capability.') + raise + + +from gkeys.log import logger +from gkeyldap.config import default_server + +class LdapConnect(object): + '''Class to connect on the configured LDAP server''' + + def __init__(self, server=None): + self.server = server or default_server + logger.debug('LdapConnect: __init__; server...: %s' % self.server) + self.ldap_connection = None + + def connect(self, server=None, action='LDAP'): + '''Creates our LDAP server connection + + @param server: string URI path for the LDAP server + ''' + logger.info("%s... Establishing connection" % action) + if server: + self.server = server + logger.debug('LdapConnect: connect; new server: %s' % self.server) + try: + self.ldap_connection = ldap.initialize(self.server) + self.ldap_connection.set_option(ldap.OPT_X_TLS_DEMAND, True) + self.ldap_connection.start_tls_s() + self.ldap_connection.simple_bind_s() + except Exception as e: + logger.error( + 'LdapConnect: connect; failed to connect to server: %s' % self.server) + logger.error("Exception was: %s" % str(e)) + logger.error("Aborting %s... Connection failed" % action) + return False + logger.debug( + 'LdapConnect: connect; connection: %s' % self.ldap_connection) + return self.ldap_connection diff --git a/gkeys-ldap/gkeyldap/search.py b/gkeys-ldap/gkeyldap/search.py new file mode 100644 index 0000000..81a9048 --- /dev/null +++ b/gkeys-ldap/gkeyldap/search.py @@ -0,0 +1,68 @@ +# +#-*- coding:utf-8 -*- + + +try: + import ldap +except ImportError: + import sys + # py3.2 + if sys.hexversion >= 0x30200f0: + print('To run "ldap-seeds" in python 3, it requires a python3 ' + 'compatible version of dev-python/python-ldap be installed\n' + 'Currently only dev-python/python-ldap-9999 has that capability.') + raise + + +from gkeyldap.config import default_criteria, default_fields, UID +from gkeyldap.connect import LdapConnect +from gkeys.log import logger + +class LdapSearch(object): + '''Class to perform searches on the configured LDAP server + ''' + + def __init__(self, fields=None, criteria=None): + self.fields = fields or default_fields + self.criteria = criteria or default_criteria + logger.debug('LdapSearch: __init__; fields...: %s' % self.fields) + logger.debug('LdapSearch: __init__; criteria.: %s' % self.criteria) + self.ldap_connection = LdapConnect().connect(action='Search') + self.status = True + if not self.ldap_connection: + self.status = False + + def search(self, target, search_field=UID, fields=None, criteria=None): + '''Perform the LDAP search + ''' + if not target: + logger.debug('LdapSearch: search; invalid target: "%s"' % target) + return {} + if not fields: + fields = self.fields + else: + logger.debug('LdapSearch: search; new fields: %s' % str(fields)) + if not criteria: + criteria = self.criteria + else: + logger.debug('LdapSearch: search; new criteria: %s' % criteria) + results = self.ldap_connection.search_s(criteria, + ldap.SCOPE_ONELEVEL, search_field % target, fields) + #logger.debug('LdapSearch: search; result = %s' % str(results)) + return results + + + def result2dict(self, results, key='uid'): + ''' Convert results from LDAP attributes + to Gentoo Keys compatible attributes + + @param results: dictionary with results + @param key: string to use as a key in the dictionary + ''' + + _dict = {} + for entry in results: + info = entry[1] + key_value = info[key][0] + _dict[key_value] = info + return _dict diff --git a/gkeys-ldap/setup.py b/gkeys-ldap/setup.py new file mode 100755 index 0000000..d4f7d5c --- /dev/null +++ b/gkeys-ldap/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import os +import sys + +from distutils.core import setup +from gkeyldap import __version__, __license__ + +# this affects the names of all the directories we do stuff with +sys.path.insert(0, './') + +#__version__ = os.getenv('VERSION', default='9999') + +# Load EPREFIX from Portage, fall back to the empty string if it fails +try: + from portage.const import EPREFIX +except ImportError: + EPREFIX='' + + +setup( + name='gkeyldap', + version=__version__, + description="Gentoo gpg key management LDAP gkey seed generator", + author='', + author_email='', + maintainer='Gentoo-Keys Team', + maintainer_email='gkeys@gentoo.org', + url="https://wiki.gentoo.org/wiki/Project:Gentoo-keys", + download_url='', + packages=['gkeyldap'], + scripts=['bin/gkey-ldap'], + license=__license__, + long_description=open('README.md').read(), + keywords='gpg', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GPLv2 License', + 'Programming Language :: Python :: 2.7, 3.3, 3.4, +', + 'Operating System :: OS Independent', + 'Topic :: Security :: Cryptography', + ], +) |