aboutsummaryrefslogtreecommitdiff
blob: 27c961ddcc862791091211438cc157454c1bef5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#
#-*- 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, OrderedDict
from gkeys.seed import Seeds
from gkeyldap.config import UID, gkey2ldap, gkey2SEARCH
from gkeyldap.search import LdapSearch
from gkeys.fileops import updatefiles


Action_Map = OrderedDict({
    'ldap-search':  {
        'func': 'ldapsearch',
        'options': ['fingerprint', 'mail', 'name', 'nick', 'status'],
        'desc': '''Performs a search in LDAP for the input arguments''',
        'long_desc': '''Performs a search in LDAP for the input arguments''',
        'example': '''''',
        },
    'update-seeds':  {
        'func': 'updateseeds',
        'options': ['fingerprint', 'mail', 'name', 'nick', 'category', 'status'],
        'desc': '''Performs a search for all active developers and generates a new seeds file''',
        'long_desc': '''Performs a search for all active developers and generates a new seeds file''',
        'example': '''''',
        },
})

Available_Actions = list(Action_Map)


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):
        '''Performs a search in LDAP for the input arguments'''
        l = LdapSearch(logger=self.logger)
        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, ['Completed'])


    def updateseeds(self, args):
        '''Performs a search for all active developers and
        generates a new seeds file'''
        l = LdapSearch(logger=self.logger)
        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.category:
            filename = self.config.get_key('seeds', args.category) + '.new'
        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, ['Completed'])


    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........: %s" % count)
        self.output('', "Seeds created... Saving file.........: %s" % filename)
        self.output('', "Total number of Dev's with GPG errors: %s" % 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)
                keyinfo['keys'] = keyinfo['fingerprint']
                keyinfo['uid'] = ["%s <%s@gentoo.org>" % (info['cn'][0], info['uid'][0])]
                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