| #!/usr/bin/env python |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import os |
| import re |
| import sqlite3 |
| import sys |
| |
| |
| def create_tables(db_name): |
| cxn = sqlite3.connect(db_name) |
| cur = cxn.cursor() |
| |
| cur.execute( |
| 'CREATE TABLE loginfo(' |
| + 'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' |
| + 'log_id TEXT, ' |
| + 'public_key TEXT, ' # path to PEM-encoded file |
| + 'distrusted INTEGER, ' # non-zero if not trusted |
| + 'min_valid_timestamp INTEGER, ' |
| + 'max_valid_timestamp INTEGER, ' |
| + 'url TEXT)' |
| ) |
| cur.close() |
| cxn.commit() |
| cxn.close() |
| |
| |
| def record_id_arg(cur, args, required=False): |
| if len(args) < 1 or args[0][0] != '#' or len(args[0]) < 2: |
| if required: |
| print >> sys.stderr, 'A record id was not provided' |
| sys.exit(1) |
| return None |
| record_id = args.pop(0)[1:] |
| stmt = 'SELECT * FROM loginfo WHERE id = ?' |
| cur.execute(stmt, [record_id]) |
| recs = list(cur.fetchall()) |
| assert len(recs) < 2 |
| if len(recs) == 0: |
| print >> sys.stderr, 'Record #%s was not found' % record_id |
| sys.exit(1) |
| return record_id |
| |
| |
| def log_id_arg(cur, args, required=True): |
| if len(args) < 1 or len(args[0]) != 64: |
| if not required: |
| return None |
| print >> sys.stderr, 'A log id was not provided' |
| sys.exit(1) |
| log_id = args.pop(0).upper() |
| if len(re.compile(r'[A-Z0-9]').findall(log_id)) != len(log_id): |
| print >> sys.stderr, 'The log id is not formatted properly' |
| sys.exit(1) |
| return log_id |
| |
| |
| def public_key_arg(args): |
| if len(args) < 1: |
| print >> sys.stderr, 'A public key file was not provided' |
| sys.exit(1) |
| pubkey = args.pop(0) |
| if not os.path.exists(pubkey): |
| print >> sys.stderr, 'Public key file %s could not be read' % pubkey |
| sys.exit(1) |
| return pubkey |
| |
| |
| def time_arg(args): |
| if len(args) < 1: |
| print >> sys.stderr, 'A timestamp was not provided' |
| sys.exit(1) |
| t = args.pop(0) |
| if t == '-': |
| return None |
| bad_val = False |
| val = None |
| try: |
| val = int(t) |
| except ValueError: |
| bad_val = True |
| |
| if bad_val or val < 1: |
| print >> sys.stderr, 'The timestamp "%s" is invalid' % t |
| sys.exit(1) |
| |
| return val |
| |
| |
| def configure_public_key(cur, args): |
| record_id = record_id_arg(cur, args, False) |
| public_key = public_key_arg(args) |
| if len(args) != 0: |
| usage() |
| if not record_id: |
| stmt = 'INSERT INTO loginfo (public_key) VALUES(?)' |
| cur.execute(stmt, [public_key]) |
| else: |
| stmt = 'UPDATE loginfo SET public_key = ? WHERE id = ?' |
| cur.execute(stmt, [public_key, record_id]) |
| |
| |
| def configure_url(cur, args): |
| # can't specify more than one of record-id and log-id |
| log_id = None |
| record_id = record_id_arg(cur, args, False) |
| if not record_id: |
| log_id = log_id_arg(cur, args, False) |
| if len(args) != 1: |
| usage() |
| url = args.pop(0) |
| |
| if record_id: |
| stmt = 'UPDATE loginfo SET url = ? WHERE id = ?' |
| args = [url, record_id] |
| elif log_id: |
| stmt = 'INSERT INTO loginfo (log_id, url) VALUES(?, ?)' |
| args = [log_id, url] |
| else: |
| stmt = 'INSERT INTO loginfo (url) VALUES(?)' |
| args = [url] |
| |
| cur.execute(stmt, args) |
| |
| |
| def forget_log(cur, args): |
| record_id = record_id_arg(cur, args, False) |
| log_id = None |
| if not record_id: |
| log_id = log_id_arg(cur, args, True) |
| if len(args) != 0: |
| usage() |
| if record_id: |
| stmt = 'DELETE FROM loginfo WHERE id = ?' |
| args = [record_id] |
| else: |
| stmt = 'DELETE FROM loginfo WHERE log_id = ?' |
| args = [log_id] |
| cur.execute(stmt, args) |
| |
| |
| def trust_distrust_log(cur, args): |
| # could take a record id or a log id |
| record_id = record_id_arg(cur, args, False) |
| if record_id: |
| log_id = None |
| else: |
| log_id = log_id_arg(cur, args) |
| |
| if len(args) != 1: |
| usage() |
| flag = args.pop(0) |
| |
| if not record_id: |
| stmt = 'INSERT INTO loginfo (log_id, distrusted) VALUES(?, ?)' |
| cur.execute(stmt, [log_id, flag]) |
| else: |
| stmt = 'UPDATE loginfo SET distrusted = ? WHERE id = ?' |
| cur.execute(stmt, [flag, record_id]) |
| |
| |
| def trust_log(cur, args): |
| trust_distrust_log(cur, args + [0]) |
| |
| |
| def distrust_log(cur, args): |
| trust_distrust_log(cur, args + [1]) |
| |
| |
| def time_range(cur, args): |
| # could take a record id or a log id |
| record_id = record_id_arg(cur, args, False) |
| if record_id: |
| log_id = None |
| else: |
| log_id = log_id_arg(cur, args) |
| |
| min_valid_time = time_arg(args) |
| max_valid_time = time_arg(args) |
| if len(args) != 0: |
| usage() |
| if not record_id: |
| stmt = 'INSERT INTO loginfo ' + \ |
| '(log_id, min_valid_timestamp, max_valid_timestamp) ' + \ |
| 'VALUES(?, ?, ?)' |
| cur.execute(stmt, [log_id, min_valid_time, max_valid_time]) |
| else: |
| stmt = 'UPDATE loginfo SET min_valid_timestamp = ?, ' + \ |
| 'max_valid_timestamp = ? WHERE id = ?' |
| cur.execute(stmt, [min_valid_time, max_valid_time, record_id]) |
| |
| |
| class ConfigEntry: |
| |
| pass |
| |
| |
| def dump_ll(cur): |
| stmt = 'SELECT * FROM loginfo' |
| cur.execute(stmt) |
| recs = [] |
| for row in cur.fetchall(): |
| obj = ConfigEntry() |
| obj.id = row[0] |
| obj.log_id = row[1] |
| obj.public_key = row[2] |
| obj.distrusted = row[3] |
| obj.min_valid_timestamp = row[4] |
| obj.max_valid_timestamp = row[5] |
| obj.url = row[6] |
| recs += [obj] |
| return recs |
| |
| |
| def dump(cur, args): |
| if len(args) != 0: |
| usage() |
| recs = dump_ll(cur) |
| for rec in recs: |
| not_conf = '(not configured)' |
| |
| mint = \ |
| str(rec.min_valid_timestamp) if rec.min_valid_timestamp else '-INF' |
| maxt = \ |
| str(rec.max_valid_timestamp) if rec.max_valid_timestamp else '+INF' |
| print 'Log entry:' |
| print ' Record ' + str(rec.id) + \ |
| (' (DISTRUSTED)' if rec.distrusted else '') |
| print ' Log id : ' + (rec.log_id if rec.log_id else not_conf) |
| print ' Public key file: ' + \ |
| (rec.public_key if rec.public_key else not_conf) |
| print ' URL : ' + (rec.url if rec.url else not_conf) |
| print ' Time range : ' + mint + ' to ' + maxt |
| print '' |
| |
| |
| def usage(): |
| help = """Usage: %s /path/to/log-config-db command args |
| |
| Commands: |
| display config-db contents: |
| dump |
| configure public key: |
| configure-public-key [log-id|record-id] /path/log-pub-key.pem |
| configure URL: |
| configure-url [log-id|record-id] http://www.example.com/path/ |
| configure min and/or max valid timestamps: |
| valid-time-range log-id|record-id min-range max-range |
| mark log as trusted (default): |
| trust log-id|record-id |
| mark log as untrusted: |
| distrust log-id|record-id |
| remove log config from config-db: |
| forget log-id|record-id |
| |
| log-id is a 64-character hex string representation of a log id |
| |
| record-id references an existing entry and is in the form: |
| #<record-number> |
| (displayable with the dump command) |
| """ % sys.argv[0] |
| print >> sys.stderr, help |
| sys.exit(1) |
| |
| |
| def main(argv): |
| if len(argv) < 3: |
| usage() |
| |
| db_name = argv[1] |
| cmd = argv[2] |
| args = argv[3:] |
| |
| cmds = {'configure-public-key': configure_public_key, |
| 'configure-url': configure_url, |
| 'distrust': distrust_log, |
| 'trust': trust_log, |
| 'forget': forget_log, |
| 'valid-time-range': time_range, |
| 'dump': dump, |
| } |
| |
| cmds_requiring_db = ['dump', 'forget'] # db must already exist |
| |
| if not cmd in cmds: |
| usage() |
| |
| if not os.path.exists(db_name): |
| if not cmd in cmds_requiring_db: |
| create_tables(db_name) |
| else: |
| print >> sys.stderr, 'Database "%s" does not exist' % db_name |
| sys.exit(1) |
| |
| cxn = sqlite3.connect(db_name) |
| cur = cxn.cursor() |
| |
| cmds[cmd](cur, args) |
| |
| cur.close() |
| cxn.commit() |
| cxn.close() |
| |
| if __name__ == "__main__": |
| main(sys.argv) |