Mini Shell
# emacs: -*- mode: python; coding: utf-8; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import re
import time
import calendar
import datetime
from _strptime import LocaleTime, TimeRE, _calc_julian_from_U_or_W
from .mytime import MyTime
locale_time = LocaleTime()
TZ_ABBR_RE = r"[A-Z](?:[A-Z]{2,4})?"
FIXED_OFFSET_TZ_RE = re.compile(r"(%s)?([+-][01]\d(?::?\d{2})?)?$" % (TZ_ABBR_RE,))
timeRE = TimeRE()
# %k - one- or two-digit number giving the hour of the day (0-23) on a 24-hour clock,
# (corresponds %H, but allows space if not zero-padded).
# %l - one- or two-digit number giving the hour of the day (12-11) on a 12-hour clock,
# (corresponds %I, but allows space if not zero-padded).
timeRE['k'] = r" ?(?P<H>[0-2]?\d)"
timeRE['l'] = r" ?(?P<I>1?\d)"
# TODO: because python currently does not support mixing of case-sensitive with case-insensitive matching,
# check how TZ (in uppercase) can be combined with %a/%b etc. (that are currently case-insensitive),
# to avoid invalid date-time recognition in strings like '11-Aug-2013 03:36:11.372 error ...'
# with wrong TZ "error", which is at least not backwards compatible.
# Hence %z currently match literal Z|UTC|GMT only (and offset-based), and %Exz - all zone abbreviations.
timeRE['Z'] = r"(?P<Z>Z|[A-Z]{3,5})"
timeRE['z'] = r"(?P<z>Z|UTC|GMT|[+-][01]\d(?::?\d{2})?)"
# Note: this extended tokens supported zone abbreviations, but it can parse 1 or 3-5 char(s) in lowercase,
# see todo above. Don't use them in default date-patterns (if not anchored, few precise resp. optional).
timeRE['ExZ'] = r"(?P<Z>%s)" % (TZ_ABBR_RE,)
timeRE['Exz'] = r"(?P<z>(?:%s)?[+-][01]\d(?::?\d{2})?|%s)" % (TZ_ABBR_RE, TZ_ABBR_RE)
# overwrite default patterns, since they can be non-optimal:
timeRE['d'] = r"(?P<d>[1-2]\d|[0 ]?[1-9]|3[0-1])"
timeRE['m'] = r"(?P<m>0?[1-9]|1[0-2])"
timeRE['Y'] = r"(?P<Y>\d{4})"
timeRE['H'] = r"(?P<H>[0-1]?\d|2[0-3])"
timeRE['M'] = r"(?P<M>[0-5]?\d)"
timeRE['S'] = r"(?P<S>[0-5]?\d|6[0-1])"
# Extend build-in TimeRE with some exact patterns
# exact two-digit patterns:
timeRE['Exd'] = r"(?P<d>[1-2]\d|0[1-9]|3[0-1])"
timeRE['Exm'] = r"(?P<m>0[1-9]|1[0-2])"
timeRE['ExH'] = r"(?P<H>[0-1]\d|2[0-3])"
timeRE['Exk'] = r" ?(?P<H>[0-1]?\d|2[0-3])"
timeRE['Exl'] = r" ?(?P<I>1[0-2]|\d)"
timeRE['ExM'] = r"(?P<M>[0-5]\d)"
timeRE['ExS'] = r"(?P<S>[0-5]\d|6[0-1])"
def _updateTimeRE():
def _getYearCentRE(cent=(0,3), distance=3, now=(MyTime.now(), MyTime.alternateNow)):
""" Build century regex for last year and the next years (distance).
Thereby respect possible run in the test-cases (alternate date used there)
"""
cent = lambda year, f=cent[0], t=cent[1]: str(year)[f:t]
def grp(exprset):
c = None
if len(exprset) > 1:
for i in exprset:
if c is None or i[0:-1] == c:
c = i[0:-1]
else:
c = None
break
if not c:
for i in exprset:
if c is None or i[0] == c:
c = i[0]
else:
c = None
break
if c:
return "%s%s" % (c, grp([i[len(c):] for i in exprset]))
return ("(?:%s)" % "|".join(exprset) if len(exprset[0]) > 1 else "[%s]" % "".join(exprset)) \
if len(exprset) > 1 else "".join(exprset)
exprset = set( cent(now[0].year + i) for i in (-1, distance) )
if len(now) > 1 and now[1]:
exprset |= set( cent(now[1].year + i) for i in range(-1, now[0].year-now[1].year+1, distance) )
return grp(sorted(list(exprset)))
# more precise year patterns, within same century of last year and
# the next 3 years (for possible long uptime of fail2ban); thereby
# consider possible run in the test-cases (alternate date used there),
# so accept years: 20xx (from test-date or 2001 up to current century)
timeRE['ExY'] = r"(?P<Y>%s\d)" % _getYearCentRE(cent=(0,3), distance=3,
now=(datetime.datetime.now(), datetime.datetime.fromtimestamp(
min(MyTime.alternateNowTime or 978393600, 978393600))
)
)
timeRE['Exy'] = r"(?P<y>\d{2})"
_updateTimeRE()
def getTimePatternRE():
keys = list(timeRE.keys())
patt = (r"%%(%%|%s|[%s])" % (
"|".join([k for k in keys if len(k) > 1]),
"".join([k for k in keys if len(k) == 1]),
))
names = {
'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day",
'H': "24hour", 'I': "12hour", 'j': "Yearday", 'm': "Month",
'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek",
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%",
'z': "Zone offset", 'f': "Microseconds", 'Z': "Zone name",
}
for key in set(keys) - set(names): # may not have them all...
if key.startswith('Ex'):
kn = names.get(key[2:])
if kn:
names[key] = "Ex" + kn
continue
names[key] = "%%%s" % key
return (patt, names)
def validateTimeZone(tz):
"""Validate a timezone and convert it to offset if it can (offset-based TZ).
For now this accepts the UTC[+-]hhmm format (UTC has aliases GMT/Z and optional).
Additionally it accepts all zone abbreviations mentioned below in TZ_STR.
Note that currently this zone abbreviations are offset-based and used fixed
offset without automatically DST-switch (if CET used then no automatically CEST-switch).
In the future, it may be extended for named time zones (such as Europe/Paris)
present on the system, if a suitable tz library is present (pytz).
"""
if tz is None:
return None
m = FIXED_OFFSET_TZ_RE.match(tz)
if m is None:
raise ValueError("Unknown or unsupported time zone: %r" % tz)
tz = m.groups()
return zone2offset(tz, 0)
def zone2offset(tz, dt):
"""Return the proper offset, in minutes according to given timezone at a given time.
Parameters
----------
tz: symbolic timezone or offset (for now only TZA?([+-]hh:?mm?)? is supported,
as value are accepted:
int offset;
string in form like 'CET+0100' or 'UTC' or '-0400';
tuple (or list) in form (zone name, zone offset);
dt: datetime instance for offset computation (currently unused)
"""
if isinstance(tz, int):
return tz
if isinstance(tz, str):
return validateTimeZone(tz)
tz, tzo = tz
if tzo is None or tzo == '': # without offset
return TZ_ABBR_OFFS[tz]
if len(tzo) <= 3: # short tzo (hh only)
# [+-]hh --> [+-]hh*60
return TZ_ABBR_OFFS[tz] + int(tzo)*60
if tzo[3] != ':':
# [+-]hhmm --> [+-]1 * (hh*60 + mm)
return TZ_ABBR_OFFS[tz] + (-1 if tzo[0] == '-' else 1) * (int(tzo[1:3])*60 + int(tzo[3:5]))
else:
# [+-]hh:mm --> [+-]1 * (hh*60 + mm)
return TZ_ABBR_OFFS[tz] + (-1 if tzo[0] == '-' else 1) * (int(tzo[1:3])*60 + int(tzo[4:6]))
def reGroupDictStrptime(found_dict, msec=False, default_tz=None):
"""Return time from dictionary of strptime fields
This is tweaked from python built-in _strptime.
Parameters
----------
found_dict : dict
Dictionary where keys represent the strptime fields, and values the
respective value.
default_tz : default timezone to apply if nothing relevant is in found_dict
(may be a non-fixed one in the future)
Returns
-------
float
Unix time stamp.
"""
now = \
year = month = day = tzoffset = \
weekday = julian = week_of_year = None
hour = minute = second = fraction = 0
for key, val in found_dict.items():
if val is None: continue
# Directives not explicitly handled below:
# c, x, X
# handled by making out of other directives
# U, W
# worthless without day of the week
if key == 'y':
year = int(val)
# Fail2ban year should be always in the current century (>= 2000)
if year <= 2000:
year += 2000
elif key == 'Y':
year = int(val)
elif key == 'm':
month = int(val)
elif key == 'B':
month = locale_time.f_month.index(val.lower())
elif key == 'b':
month = locale_time.a_month.index(val.lower())
elif key == 'd':
day = int(val)
elif key == 'H':
hour = int(val)
elif key == 'I':
hour = int(val)
ampm = found_dict.get('p', '').lower()
# If there was no AM/PM indicator, we'll treat this like AM
if ampm in ('', locale_time.am_pm[0]):
# We're in AM so the hour is correct unless we're
# looking at 12 midnight.
# 12 midnight == 12 AM == hour 0
if hour == 12:
hour = 0
elif ampm == locale_time.am_pm[1]:
# We're in PM so we need to add 12 to the hour unless
# we're looking at 12 noon.
# 12 noon == 12 PM == hour 12
if hour != 12:
hour += 12
elif key == 'M':
minute = int(val)
elif key == 'S':
second = int(val)
elif key == 'f':
if msec: # pragma: no cover - currently unused
s = val
# Pad to always return microseconds.
s += "0" * (6 - len(s))
fraction = int(s)
elif key == 'A':
weekday = locale_time.f_weekday.index(val.lower())
elif key == 'a':
weekday = locale_time.a_weekday.index(val.lower())
elif key == 'w':
weekday = int(val) - 1
if weekday < 0: weekday = 6
elif key == 'j':
julian = int(val)
elif key in ('U', 'W'):
week_of_year = int(val)
# U starts week on Sunday, W - on Monday
week_of_year_start = 6 if key == 'U' else 0
elif key in ('z', 'Z'):
z = val
if z in ("Z", "UTC", "GMT"):
tzoffset = 0
else:
tzoffset = zone2offset(z, 0); # currently offset-based only
# Fail2Ban will assume it's this year
assume_year = False
if year is None:
if not now: now = MyTime.now()
year = now.year
assume_year = True
if month is None or day is None:
# If we know the week of the year and what day of that week, we can figure
# out the Julian day of the year.
if julian is None and week_of_year is not None and weekday is not None:
julian = _calc_julian_from_U_or_W(year, week_of_year, weekday,
(week_of_year_start == 0))
# Cannot pre-calculate datetime.datetime() since can change in Julian
# calculation and thus could have different value for the day of the week
# calculation.
if julian is not None:
datetime_result = datetime.datetime.fromordinal((julian - 1) + datetime.datetime(year, 1, 1).toordinal())
year = datetime_result.year
month = datetime_result.month
day = datetime_result.day
# Fail2Ban assume today
assume_today = False
if month is None and day is None:
if not now: now = MyTime.now()
month = now.month
day = now.day
assume_today = True
# Actully create date
date_result = datetime.datetime(
year, month, day, hour, minute, second, fraction)
# Correct timezone if not supplied in the log linge
if tzoffset is None and default_tz is not None:
tzoffset = zone2offset(default_tz, date_result)
# Add timezone info
if tzoffset is not None:
date_result -= datetime.timedelta(seconds=tzoffset * 60)
if assume_today:
if not now: now = MyTime.now()
if date_result > now:
# Rollover at midnight, could mean it's yesterday...
date_result -= datetime.timedelta(days=1)
if assume_year:
if not now: now = MyTime.now()
if date_result > now + datetime.timedelta(days=1): # ignore by timezone issues (+24h)
# assume last year - also reset month and day as it's not yesterday...
date_result = date_result.replace(
year=year-1, month=month, day=day)
# make time:
if tzoffset is not None:
tm = calendar.timegm(date_result.utctimetuple())
else:
tm = time.mktime(date_result.timetuple())
if msec: # pragma: no cover - currently unused
tm += fraction/1000000.0
return tm
TZ_ABBR_OFFS = {'':0, None:0}
TZ_STR = '''
-12 Y
-11 X NUT SST
-10 W CKT HAST HST TAHT TKT
-9 V AKST GAMT GIT HADT HNY
-8 U AKDT CIST HAY HNP PST PT
-7 T HAP HNR MST PDT
-6 S CST EAST GALT HAR HNC MDT
-5 R CDT COT EASST ECT EST ET HAC HNE PET
-4 Q AST BOT CLT COST EDT FKT GYT HAE HNA PYT
-3 P ADT ART BRT CLST FKST GFT HAA PMST PYST SRT UYT WGT
-2 O BRST FNT PMDT UYST WGST
-1 N AZOT CVT EGT
0 Z EGST GMT UTC WET WT
1 A CET DFT WAT WEDT WEST
2 B CAT CEDT CEST EET SAST WAST
3 C EAT EEDT EEST IDT MSK
4 D AMT AZT GET GST KUYT MSD MUT RET SAMT SCT
5 E AMST AQTT AZST HMT MAWT MVT PKT TFT TJT TMT UZT YEKT
6 F ALMT BIOT BTT IOT KGT NOVT OMST YEKST
7 G CXT DAVT HOVT ICT KRAT NOVST OMSST THA WIB
8 H ACT AWST BDT BNT CAST HKT IRKT KRAST MYT PHT SGT ULAT WITA WST
9 I AWDT IRKST JST KST PWT TLT WDT WIT YAKT
10 K AEST ChST PGT VLAT YAKST YAPT
11 L AEDT LHDT MAGT NCT PONT SBT VLAST VUT
12 M ANAST ANAT FJT GILT MAGST MHT NZST PETST PETT TVT WFT
13 FJST NZDT
11.5 NFT
10.5 ACDT LHST
9.5 ACST
6.5 CCT MMT
5.75 NPT
5.5 SLT
4.5 AFT IRDT
3.5 IRST
-2.5 HAT NDT
-3.5 HNT NST NT
-4.5 HLV VET
-9.5 MART MIT
'''
def _init_TZ_ABBR():
"""Initialized TZ_ABBR_OFFS dictionary (TZ -> offset in minutes)"""
for tzline in map(str.split, TZ_STR.split('\n')):
if not len(tzline): continue
tzoffset = int(float(tzline[0]) * 60)
for tz in tzline[1:]:
TZ_ABBR_OFFS[tz] = tzoffset
_init_TZ_ABBR()
Zerion Mini Shell 1.0