1#
2# This file is a part of DNSViz, a tool suite for DNS/DNSSEC monitoring,
3# analysis, and visualization.
4# Created by Casey Deccio (casey@deccio.net)
5#
6# Copyright 2012-2014 Sandia Corporation. Under the terms of Contract
7# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
8# certain rights in this software.
9#
10# Copyright 2014-2016 VeriSign, Inc.
11#
12# Copyright 2016-2021 Casey Deccio
13#
14# DNSViz is free software; you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation; either version 2 of the License, or
17# (at your option) any later version.
18#
19# DNSViz is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License along
25# with DNSViz.  If not, see <http://www.gnu.org/licenses/>.
26#
27
28from __future__ import unicode_literals
29
30import calendar
31import codecs
32import datetime
33import re
34import time
35
36import dns.name, dns.rdatatype
37
38DNSKEY_FLAGS = {'ZONE': 0x0100, 'SEP': 0x0001, 'revoke': 0x0080}
39DNSKEY_PROTOCOLS = { 3: 'DNSSEC' }
40DNSKEY_ALGORITHMS = { 1: 'RSA/MD5', 2: 'Diffie-Hellman', 3: 'DSA/SHA1', 5: 'RSA/SHA-1', 6: 'DSA-NSEC3-SHA1', 7: 'RSASHA1-NSEC3-SHA1', \
41        8: 'RSA/SHA-256', 10: 'RSA/SHA-512', 12: 'GOST R 34.10-2001', 13: 'ECDSA Curve P-256 with SHA-256', 14: 'ECDSA Curve P-384 with SHA-384',
42        15: 'Ed25519', 16: 'Ed448' }
43DS_DIGEST_TYPES = { 1: 'SHA-1', 2: 'SHA-256', 3: 'GOST 34.11-94', 4: 'SHA-384' }
44
45NSEC3_FLAGS = {'OPTOUT': 0x01}
46
47DNS_FLAG_DESCRIPTIONS = {
48        32768: 'Query Response', 1024: 'Authoritative Answer', 512: 'Truncated Response',
49        256: 'Recursion Desired', 128: 'Recursion Available', 32: 'Authentic Data', 16: 'Checking Disabled'
50}
51
52EDNS_FLAG_DESCRIPTIONS = { 32768: 'DNSSEC answer OK' }
53
54EDNS_OPT_DESCRIPTIONS = { 3: 'NSID', 8: 'edns-client-subnet', 10: 'COOKIE' }
55
56FMT_MS = '%Y-%m-%d %H:%M:%S.%f %Z'
57FMT_NO_MS = '%Y-%m-%d %H:%M:%S %Z'
58
59ZERO = datetime.timedelta(0)
60class UTC(datetime.tzinfo):
61    '''UTC'''
62
63    def utcoffset(self, dt):
64        return ZERO
65
66    def tzname(self, dt):
67        # python3/python2 dual compatibility
68        if type(b'') is str:
69            return b'UTC'
70        else:
71            return 'UTC'
72
73    def dst(self, dt):
74        return ZERO
75
76utc = UTC()
77
78#################
79# Timestamp conversions
80def timestamp_to_datetime(timestamp, tz=utc):
81    return datetime.datetime.fromtimestamp(timestamp, tz)
82
83def datetime_to_timestamp(dt):
84    return calendar.timegm(dt.timetuple()) + dt.microsecond/1.0e6
85
86def str_to_datetime(s, tz=utc):
87    return timestamp_to_datetime(str_to_timestamp(s), tz)
88
89def str_to_timestamp(s):
90    try:
91        return calendar.timegm(time.strptime(s, FMT_NO_MS))
92    except ValueError:
93        return calendar.timegm(time.strptime(s, FMT_MS))
94
95def datetime_to_str(dt):
96    if dt.microsecond:
97        return dt.strftime(FMT_MS)
98    else:
99        return dt.strftime(FMT_NO_MS)
100
101def timestamp_to_str(timestamp):
102    return datetime_to_str(timestamp_to_datetime(timestamp))
103
104#################
105# Human representation of time
106def humanize_time(seconds, days=None):
107    if days is None:
108        days, remainder = divmod(seconds, 86400)
109    else:
110        remainder = seconds
111    hours, remainder = divmod(remainder, 3600)
112    minutes, seconds = divmod(remainder, 60)
113
114    output = ''
115    if days > 0:
116        if days != 1:
117            plural = 's'
118        else:
119            plural = ''
120        output += '%d day%s' % (days, plural)
121    else:
122        if hours > 0:
123            if hours != 1:
124                plural = 's'
125            else:
126                plural = ''
127            output += '%d hour%s' % (hours, plural)
128        if minutes > 0:
129            if output:
130                output += ', '
131            if minutes != 1:
132                plural = 's'
133            else:
134                plural = ''
135            output += '%d minute%s' % (minutes, plural)
136        if not output:
137            if seconds != 1:
138                plural = 's'
139            else:
140                plural = ''
141            output += '%d second%s' % (seconds, plural)
142    return output
143
144def format_diff(date_now, date_relative):
145    if date_now > date_relative:
146        diff = date_now - date_relative
147        suffix = 'in the past'
148    else:
149        diff = date_relative - date_now
150        suffix = 'in the future'
151    return '%s %s' % (humanize_time(diff.seconds, diff.days), suffix)
152
153#################
154# Human representation of DNS names
155def format_nsec3_name(name):
156    return lb2s(dns.name.from_text(name.labels[0].upper(), name.parent().canonicalize()).to_text())
157
158def format_nsec3_rrset_text(nsec3_rrset_text):
159    return re.sub(r'^(\d+\s+\d+\s+\d+\s+\S+\s+)([0-9a-zA-Z]+)', lambda x: '%s%s' % (x.group(1), x.group(2).upper()), nsec3_rrset_text).rstrip('.')
160
161def humanize_name(name, idn=False, canonicalize=True):
162    if canonicalize:
163        name = name.canonicalize()
164    if idn:
165        try:
166            name = name.to_unicode()
167        except UnicodeError:
168            name = lb2s(name.to_text())
169    else:
170        name = lb2s(name.to_text())
171    if name == '.':
172        return name
173    return name.rstrip('.')
174
175def latin1_binary_to_string(s):
176    # python3/python2 dual compatibility
177    #XXX In places where this method wraps calls to dns.name.Name.to_text(),
178    # this is no longer needed with dnspython 1.15.0
179    if isinstance(s, bytes):
180        return codecs.decode(s, 'latin1')
181    return s
182lb2s = latin1_binary_to_string
183