Skip to content

Commit

Permalink
Windows datetime/strftime behavior workaround
Browse files Browse the repository at this point in the history
  • Loading branch information
strictlymike committed Aug 16, 2017
1 parent 798b6b6 commit 22b94f6
Showing 1 changed file with 56 additions and 4 deletions.
60 changes: 56 additions & 4 deletions fakenet/listeners/BannerFactory.py
@@ -1,3 +1,4 @@
import re
import random
import socket
import string
Expand All @@ -7,34 +8,85 @@ class Banner():
"""Act like a string, but actually get date/time components on the fly."""

def __init__(self, banner, insertions):
self.banner = banner
# On Windows, datetime.datetime.now().strftime() will choke on %(...)s
# and similar insertion tokens. To pacify it, prepend an additional
# percent sign before each such token so that strftime() will quietly
# translate the double percent sign into a single percent sign for
# later consumption by the format operation in which insertion strings
# such as %(servername)s will be substituted.
banner_safe = re.sub(r'%\(([^\)]+)\)', r'%%(\1)', banner)

# Enable/disable test path to simulate length of 75 but actual string
# of length 76 to test what happens around pyftpdlib/handlers.py:1321
# when the return value of len() is <= the threshold of 75 characters
# but the string's actual length (which is generated in a subsequent
# call to __repr__()) exceeds that threshold value.
self.test_pyftpdlib_handler_banner_threshold75 = False

if self.test_pyftpdlib_handler_banner_threshold75:
self.len_75 = len(self.str_75)
self.str_76 = 'a' * 76

self.banner = banner_safe
self.insertions = insertions

# Indicate an error in the banner early-on as opposed to
# when a login or other event occurs.
test = self.failEarly()
testbanner, testlen = self.failEarly()

def failEarly(self):
"""Raise exceptions upon construction rather than later."""
return self.fmt()

# Test generating banner
banner_generated = str(self)

# Test generating and getting length of banner
banner_generated_len = len(self)

return banner_generated, banner_generated_len

def __len__(self):
"""Needed for pyftpdlib.
If the length changes between the time when the caller obtains the
length and the time when they reference the string, then... *shrug*?
length and the time when the caller obtains the latest generated
string, then there is not much that could reasonably be done. It would
be possible to cache the formatted banner with a short expiry so that
temporally clustered __len__ and __repr__ call sequences would view
consistent and coherent string contents, however this seems like
overkill since the use case is really just allowing pyftpdlib to
determine which way to send the response (directly versus push() if the
length exceeds a threshold of 75 characters). In this case, if the
banner string length and contents are inconsistent, it appears that the
only effect will be to erroneously send the message differently. Test
code has been left in place for easy repro in case this proves to be an
issue on some future/other platform.
"""
# Test path: simulate length of 75 but actual string of length 76 (part
# 1/2) to test pyftpdlib/handlers.py:1321
if self.test_pyftpdlib_handler_banner_threshold75:
return self.len_75

# Normal path: return the length of the banner generated by self.fmt()
return len(self.fmt())

def __repr__(self):
return self.fmt()

def fmt(self):
# Test path: simulate length of 75 but actual string of length 76 (part
# 2/2) to test pyftpdlib/handlers.py:1321
if self.test_pyftpdlib_handler_banner_threshold75:
return self.str_76

# Normal path: generate banner
banner = self.banner
banner = datetime.datetime.now().strftime(banner)
banner = banner % self.insertions
banner = banner.replace('\\n', '\n').replace('\\t', '\t')
return banner


class BannerFactory():
def genBanner(self, config, bannerdict, defaultbannerkey='!generic'):
"""Select and format a banner.
Expand Down

0 comments on commit 22b94f6

Please sign in to comment.