aboutsummaryrefslogtreecommitdiff
blob: 3ecbd1b010d360987bca094f389d67c5826001c1 (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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
#
#-*- coding:utf-8 -*-

"""
    Gentoo-keys - base.py

    Command line interface argsparse options module
    and common functions

    @copyright: 2012-2015 by Brian Dolbec <dol-sen@gentoo.org>
    @license: GNU GPL2, see COPYING for details.
"""

from __future__ import print_function


import argparse
import os
import sys
import copy

from gkeys.fileops import ensure_dirs
from gkeys.log import log_levels, set_logger
from gkeys.gkey import GKEY

if sys.version_info[0] >= 3:
    from urllib.request import urlopen
    py_input = input
    _unicode = str
else:
    from urllib2 import urlopen
    py_input = raw_input
    _unicode = unicode

if sys.version_info[0] >= 3:
    unicode = str


class Args(object):
    '''Basic argsparser replacement for using gkeys Actions via an API

    Holds the full spectrum of possible options supported.
    Not all options used by all actions.'''


    def __init__(self):
        self.action = None
        self.all = False
        self.category = None
        self.cleankey = False
        self.destination = None
        self.exact = False
        self.filename = None
        self.fingerprint = None
        self.keyid = None
        self.keyring = None
        self.keys = None
        self.nick = None
        self.name = None
        self.keydir = None
        self.seedfile = None
        self.signature = None
        self.status = False
        self.timestamp = None
        self.uid = None


class CliBase(object):
    '''Common cli and argsparse options class'''


    def __init__(self):
        self.cli_config = {
            'Actions': None,
            'Available_Actions': [],
            'Action_Map': {},
            'Base_Options': [],
            'prog': 'gkeys',
            'description': 'Gentoo-keys manager program',
            'epilog': '''Caution: adding UNTRUSTED keys can be HAZARDOUS to your system!'''
        }
        self.config = None
        self.args = None
        self.seeds = None
        self.actions = None
        self.logger = None
        self.version = None
        self.need_Action = True


    @staticmethod
    def _option_status(parser=None):
        parser.add_argument('-A', '--status', action='store_true',
            default=False,
            help='The active status of the member')

    @staticmethod
    def _option_all(parser=None):
        parser.add_argument('-a', '--all', dest='all',
            action='store_true', default=False,
            help='Match all inputs arguments in searches')

    @staticmethod
    def _option_category(parser=None):
        parser.add_argument('-C', '--category',
            dest='category', default=None,
            help='The key or seed directory category to use or update')

    @staticmethod
    def _option_cleankey(parser=None):
        parser.add_argument('--clean-key',
            dest='cleankey', default=False,
            help='Clean the key from the keyring due to failures')

    @staticmethod
    def _option_cleanseed(parser=None):
        parser.add_argument('--clean-seed',
            dest='cleanseed', default=False,
            help='Clean the seed from the seedfile due to failures.  '
                'Used during binary keyring release creation.')

    @staticmethod
    def _option_dest(parser=None):
        parser.add_argument('-d', '--dest', dest='destination', default=None,
            help='The destination for move, copy, create operations')

    @staticmethod
    def _option_exact(parser=None):
        parser.add_argument('-e', '--exact', dest='exact',
            action='store_true', default=False,
            help='Use CASE matching in searches')

    @staticmethod
    def _option_file(parser=None):
        parser.add_argument('-F', '--file', dest='filename', default=None,
            nargs='+',
            help='The path/URL to use for the (signed) file')

    @staticmethod
    def _option_1file(parser=None):
        parser.add_argument('-F', '--file', dest='filename', default=None,
            help='The path/URL to use for the (signed) file')

    @staticmethod
    def _option_fingerprint(parser=None):
        parser.add_argument('-f', '--fingerprint', dest='fingerprint',
            default=None, nargs='+',
            help='The fingerprint(s) of the the key or subkey')

    @staticmethod
    def _option_gpgsearch(parser=None):
        parser.add_argument('-g', '--gpgsearch', dest='gpgsearch',
            action='store_true', default=False,
            help='Do a gpg search operation, rather than a gkey search')

    @staticmethod
    def _option_homedir(parser=None):
        parser.add_argument('-H', '--homedir', dest='homedir', default=None,
                            help='The destination for the generated key')

    @staticmethod
    def _option_keyid(parser=None):
        parser.add_argument('-i', '--keyid', dest='keyid', default=None,
            nargs='+',
            help='The long keyid of the gpg key to search for')

    @staticmethod
    def _option_justdoit(parser=None):
        parser.add_argument('--justdoit', dest='justdoit',
            action='store_true', default=False,
            help='Just Do It')

    @staticmethod
    def _option_keyring(parser=None):
        parser.add_argument('-k', '--keyring', dest='keyring', default=None,
            help='The name of the keyring to use for verification, etc.')

    @staticmethod
    def _option_keys(parser=None):
        parser.add_argument('-K', '--keys', dest='keys', nargs='*',
            default=None,
            help='The fingerprint(s) of the primary keys in the keyring.')

    @staticmethod
    def _option_mail(parser=None):
        parser.add_argument('-m', '--mail', dest='mail', default=None,
            help='The email address to search for or use.')

    @staticmethod
    def _option_nick(parser=None):
        parser.add_argument('-n', '--nick', dest='nick', default=None,
            help='The nick associated with the the key')

    @staticmethod
    def _option_name(parser=None):
        parser.add_argument('-N', '--name', dest='name', nargs='*',
            default=None, help='The name of the the key')

    @staticmethod
    def _option_1name(parser=None):
        parser.add_argument('-N', '--name', dest='name',
            default=None, help='The name of the the key')

    @staticmethod
    def _option_keydir(parser=None):
        parser.add_argument('-r', '--keydir', dest='keydir', default=None,
            help='The keydir to use, update or search for/in')

    @staticmethod
    def _option_seedfile(parser=None):
        parser.add_argument('-S', '--seedfile', dest='seedfile', default=None,
            help='The seedfile to use from the gkeys.conf file')

    @staticmethod
    def _option_signature(parser=None):
        parser.add_argument('-s','--signature', dest='signature', default=None,
           help='The path/URL to use for the signature')

    @staticmethod
    def _option_spec(parser=None):
        parser.add_argument('-S', '--spec', dest='spec', default=None,
            help='The spec file to use from the gkeys-gen.conf file')

    @staticmethod
    def _option_timestamp(parser=None):
        parser.add_argument('-t', '--timestamp', dest='timestamp',
            action='store_true', default=False,
            help='Turn on timestamp use')

    @staticmethod
    def _option_uid(parser=None):
        parser.add_argument('-u', '--uid', dest='uid', nargs='+', default=None,
            help='The user ID, gpg key uid')

    @staticmethod
    def _option_email(parser=None):
        parser.add_argument('-E', '--email', dest='email', default=None,
            help='Email parameter for sending email reminders')

    @staticmethod
    def _option_user(parser=None):
        parser.add_argument('-U', '--user', dest='user', default=None,
            help='User parameter for service login')

    def parse_args(self, argv):
        '''Parse a list of aruments

        @param argv: list
        @returns argparse.Namespace object
        '''
        #self.logger.debug('CliBase: parse_args; args: %s' % args)
        parser = argparse.ArgumentParser(
            prog=self.cli_config['prog'],
            description=self.cli_config['description'],
            epilog=self.cli_config['epilog'])

        # options
        parser.add_argument('-c', '--config', dest='config', default=None,
            help='The path to an alternate config file')
        parser.add_argument('-D', '--debug', default='DEBUG',
            choices=list(log_levels),
            help='The logging level to set for the logfile')
        parser.add_argument('-V', '--version', action = 'version',
                          version = self.version)

        # Add any additional options to the command base
        self._add_options(parser, self.cli_config['Base_Options'])

        if self.cli_config['Available_Actions']:
            subparsers = parser.add_subparsers(
                title='Subcommands',
                description='Valid subcommands',
                help='Additional help')
            for name in self.cli_config['Available_Actions']:
                actiondoc = self.cli_config['Action_Map'][name]['desc']
                try:
                    text = actiondoc.splitlines()[0]
                except AttributeError:
                    text = ""
                action_parser = subparsers.add_parser(
                    name,
                    help=text,
                    description=actiondoc,
                    formatter_class=argparse.RawDescriptionHelpFormatter)
                action_parser.set_defaults(action=name)
                options = self.cli_config['Action_Map'][name]['options']
                self._add_options(action_parser, options)

        parsed_args = parser.parse_args(argv)
        action = getattr(parsed_args, 'action', None)
        if self.need_Action and not action:
            parser.print_usage()
            sys.exit(1)
        elif action in ['---general---', '----keys-----', '----seeds----']:
            parser.print_help()
            sys.exit(1)
        return parsed_args


    def _add_options(self, parser, options):
        for opt in options:
            getattr(self, '_option_%s' % opt)(parser)

    def warning_output(self, info):
        ''' We don't want this message to be spammed 4 times everytime gkeys is run'''
        if "Re-fetch cycle timeout of" not in info:
            print(info)

    def setup(self, args, configs):
        '''Set up the args and configs passed in

        @param args: list or argparse.Namespace object
        @param configs: list
        '''
        message = None
        if not args:
            message = "Main: run; invalid args argument passed in"
        if isinstance(args, list):
            args = self.parse_args(args)
        if args.config:
            self.config.defaults['config'] = args.config
            self.config.defaults['configdir'] = os.path.dirname(args.config)
            if args.email:
                configs = [self.config.defaults['config'], os.path.abspath(os.path.join(self.config.defaults['configdir'], "email.conf"))]
                self.config.read_config(configs)
            else:
                self.config.read_config()
        else:
            self.config.read_config(configs)

        # check for permissions and adjust configs accordngly
        if not self.config.defaults['homedir']:
            self.config.defaults['homedir'] = os.path.expanduser('~')
        if not os.access(self.config['logdir'], os.W_OK):
            self.config.options['logdir'] = os.path.join(self.config['userconfigdir'], 'logs')
            ensure_dirs(self.config.options['logdir'])
        # establish our logger and update it in the imported files
        self.logger = set_logger(self.cli_config['prog'], self.config['logdir'], args.debug,
            dirmode=int(self.config.get_key('permissions', 'directories'),0),
            filemask=int(self.config.get_key('permissions', 'files'),0))
        self.config.logger = self.logger

        if message:
            self.logger.error(message)

        # now that we have a logger, record the alternate config setting
        if args.config:
            self.logger.debug("Main: run; Found alternate config request: %s"
                % args.config)
        self.logger.debug("Main: run; Using config: %s" % self.config['config'])

        # check if a -C, --category was input
        # if it was, check if the category is listed in the [seeds]
        cat = None
        if 'category' in args:
            cat = args.category
        if not self._check_category(cat):
            return False
        return True


    def run(self, args):
        '''Run the action selected

        @param args: list of argumanets to parse
        '''
        # establish our actions instance
        self.actions = self.cli_config['Actions'](self.config, self.output_results, self.logger)
        # check for seed update
        from sslfetch.connections import Connector
        connector_output = {
             'info': self.logger.info,
             'debug': self.logger.debug,
             'error': self.logger.error,
             'exception': self.logger.exception,
             'warning': self.warning_output,
             'kwargs-info': {},
             'kwargs-debug': {},
             'kwargs-error': {},
             'kwargs-exception': {},
             'kwargs-warning': {},
        }
        fetcher = Connector(connector_output, None, "Gentoo Keys")
        successes = []
        up_to_date = True
        categories = list(self.config.defaults['seeds'])
        '''Attemp to download seed and seed.sig files for each available category'''
        for category in categories:
            filepath = self.config.defaults['seedsdir'] + "/" + category + ".seeds"
            timestamp_path = filepath + ".timestamp"
            url = self.config.defaults['seedurls'][category]
            success, signedfile, timestamp = fetcher.fetch_file(
                url, filepath, timestamp_path)
            if timestamp != "":
                up_to_date = False
            successes.append(success)
            url += ".sig"
            filepath += ".sig"
            success, signedfile, timestamp = fetcher.fetch_file(
                url, filepath, timestamp_path)
            if timestamp != "":
                up_to_date = False
            successes.append(success)
        if False not in successes and not up_to_date:
            print("Seeds need to be updated")
            ack = None
            while ack not in ("y", "yes", "n", "no"):
                ack = py_input("Would you like to update the seeds now? (y/n) ").lower()
            if ack in ("y", "yes"):
                custom_args = copy.copy(args)
                for attr in GKEY._fields:
                    if attr != "debug":
                        custom_args.attr = None
                custom_args.category = None
                custom_args.action = "update-seed"
                print("Updating seeds")
                self.run(custom_args)
        elif False not in successes:
            print("Seeds are up to date")
        else:
<<<<<<< HEAD
            print("Seed update check failed, check your internet connection.")
=======
            print("Seed update check failed, check your internet connection.")
>>>>>>> b9e64a9... gkeys actions: Added automatic seeds,keys update capability
        # run the action
        func = getattr(self.actions, '%s'
            % self.cli_config['Action_Map'][args.action]['func'])
        self.logger.debug('Main: run; Found action: %s' % args.action)
        success, results = func(args)
        if not results:
            print("No results found.  Check your configuration and that the",
                "seed file exists.")
            return success
        if self.config.options['print_results'] and 'done' not in list(results):
            self.output_results(results, '\n Gkey task results:')
        return success


    @staticmethod
    def output_results(results, header=None):
        # super simple output for the time being
        if header:
            print(header)
        for msg in results:
            if type(msg) in [str, unicode]:
                print('   ', msg)
            else:
                try:
                    print(unicode("\n").join([x.pretty_print for x in msg]))
                except AttributeError:
                    for x in msg:
                        print('    ', x)
        print()


    def output_failed(self, failed):
        pass


    def _check_category(self, category=None):
        '''Checks that the category (seedfile) is listed
        in the [seeds] config or defaults['seeds'] section

        @param args: configparser instance
        @return boolean
        '''
        available_cats = list(self.config.defaults['seeds'])
        if category and category not in available_cats:
            self.logger.error("Invalid category or seedfile entered: %s" % category)
            self.logger.error("Available categories or seedfiles: %s" % ', '.join(sorted(available_cats)))
            return False
        return True