initial import

git-svn-id: https://kippo.googlecode.com/svn/trunk@1 951d7100-d841-11de-b865-b3884708a8e2
This commit is contained in:
desaster 2009-11-10 18:57:59 +00:00
commit 2954787fd7
16 changed files with 150339 additions and 0 deletions

15
README Normal file
View File

@ -0,0 +1,15 @@
Kippo
http://www.rpg.fi/kippo/
Copyright (C) 2009 Upi Tamminen
Kippo is a low interaction SSH honeypot intended to become a bit more higher
interaction honeypot than an average low interactive honeypot.
-------------------------------------------------------------------------------
Kippo was inspired by Kojoney, http://kojoney.sourceforge.net/
-------------------------------------------------------------------------------
Thanks for testing, ideas:
- Viacheslav Slavinsky
-------------------------------------------------------------------------------

0
commands/__init__.py Normal file
View File

106
commands/base.py Normal file
View File

@ -0,0 +1,106 @@
import os, time
from core.Kippo import HoneyPotCommand
from core.fstypes import *
class command_whoami(HoneyPotCommand):
def call(self, args):
self.honeypot.writeln(self.honeypot.user.username)
class command_cat(HoneyPotCommand):
def call(self, args):
path = self.honeypot.fs.resolve_path(args, self.honeypot.cwd)
if not path or not self.honeypot.fs.exists(path):
self.honeypot.writeln(
'bash: cat: %s: No such file or directory' % args)
return
fakefile = './honeyfs/%s' % path
if os.path.exists(fakefile) and \
not os.path.islink(fakefile) and os.path.isfile(fakefile):
f = file(fakefile, 'r')
self.honeypot.terminal.write(f.read())
f.close()
class command_cd(HoneyPotCommand):
def call(self, args):
if not args:
args = '/root'
try:
newpath = self.honeypot.fs.resolve_path(args, self.honeypot.cwd)
newdir = self.honeypot.fs.get_path(newpath)
except:
newdir = None
if newdir is None:
self.honeypot.writeln(
'bash: cd: %s: No such file or directory' % args)
return
self.honeypot.cwd = newpath
class command_rm(HoneyPotCommand):
def call(self, args):
for f in args.split(' '):
path = self.honeypot.fs.resolve_path(f, self.honeypot.cwd)
dir = self.honeypot.fs.get_path('/'.join(path.split('/')[:-1]))
basename = path.split('/')[-1]
contents = [x for x in dir]
for i in dir[:]:
if i[A_NAME] == basename:
if i[A_TYPE] == T_DIR:
self.honeypot.writeln(
'rm: cannot remove `%s\': Is a directory' % \
i[A_NAME])
else:
dir.remove(i)
class command_uptime(HoneyPotCommand):
def call(self, args):
self.honeypot.writeln(
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
time.strftime('%T'))
#self.honeypot.writeln('USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT')
class command_w(HoneyPotCommand):
def call(self, args):
self.honeypot.writeln(
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
time.strftime('%T'))
self.honeypot.writeln('USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT')
class command_echo(HoneyPotCommand):
def call(self, args):
self.honeypot.writeln(args)
class command_quit(HoneyPotCommand):
def call(self, args):
self.honeypot.terminal.loseConnection()
class command_clear(HoneyPotCommand):
def call(self, args):
self.honeypot.terminal.reset()
class command_vi(HoneyPotCommand):
def call(self, args):
self.honeypot.writeln('E558: Terminal entry not found in terminfo')
class command_mount(HoneyPotCommand):
def call(self, args):
if len(args.strip()):
return
for i in [
'/dev/sda1 on / type ext3 (rw,errors=remount-ro)',
'tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)',
'proc on /proc type proc (rw,noexec,nosuid,nodev)',
'sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)',
'udev on /dev type tmpfs (rw,mode=0755)',
'tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)',
'devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)',
]:
self.honeypot.writeln(i)
class command_nop(HoneyPotCommand):
def call(self, args):
pass

72
commands/ls.py Normal file
View File

@ -0,0 +1,72 @@
from core.Kippo import HoneyPotCommand
from core.fstypes import *
import stat, time
class command_ls(HoneyPotCommand):
def uid2name(self, uid):
if uid == 0:
return 'root'
return uid
def gid2name(self, gid):
if gid == 0:
return 'root'
return gid
def call(self, args):
path = self.honeypot.cwd
paths = []
if len(args):
for arg in args.split():
if not arg.startswith('-'):
paths.append(self.honeypot.fs.resolve_path(arg,
self.honeypot.cwd))
if not paths:
self.do_ls_l(path)
else:
for path in paths:
self.do_ls_l(path)
def do_ls_l(self, path):
try:
files = self.honeypot.fs.list_files(path)
except:
self.honeypot.writeln(
'ls: cannot access %s: No such file or directory' % path)
return
largest = 0
if len(files):
largest = max([x[A_SIZE] for x in files])
for file in files:
perms = ['-'] * 10
if file[A_MODE] & stat.S_IRUSR: perms[1] = 'r'
if file[A_MODE] & stat.S_IWUSR: perms[2] = 'w'
if file[A_MODE] & stat.S_IXUSR: perms[3] = 'x'
if file[A_MODE] & stat.S_IRGRP: perms[4] = 'r'
if file[A_MODE] & stat.S_IWGRP: perms[5] = 'w'
if file[A_MODE] & stat.S_IXGRP: perms[6] = 'x'
if file[A_MODE] & stat.S_IROTH: perms[7] = 'r'
if file[A_MODE] & stat.S_IWOTH: perms[8] = 'w'
if file[A_MODE] & stat.S_IXOTH: perms[9] = 'x'
if file[A_TYPE] == T_DIR:
perms[0] = 'd'
perms = ''.join(perms)
ctime = time.localtime(file[A_CTIME])
l = '%s 1 %s %s %s %s %s' % \
(perms,
self.uid2name(file[A_UID]),
self.gid2name(file[A_GID]),
str(file[A_SIZE]).rjust(len(str(largest))),
time.strftime('%Y-%m-%d %H:%M', ctime),
file[A_NAME])
self.honeypot.writeln(l)

35
commands/tar.py Normal file
View File

@ -0,0 +1,35 @@
from core.Kippo import HoneyPotCommand
from core.fstypes import *
import stat, time, urlparse, random
class command_tar(HoneyPotCommand):
def call(self, args):
if len(args.split()) < 2:
self.honeypot.writeln('tar: You must specify one of the `-Acdtrux\' options')
self.honeypot.wirteln('Try `tar --help\' or `tar --usage\' for more information.')
return
filename = args.split()[1]
path = self.honeypot.fs.resolve_path(filename, self.honeypot.cwd)
if not path or not self.honeypot.fs.exists(path):
self.honeypot.writeln('tar: rs: Cannot open: No such file or directory')
self.honeypot.writeln('tar: Error is not recoverable: exiting now')
self.honeypot.writeln('tar: Child returned status 2')
self.honeypot.writeln('tar: Error exit delayed from previous errors')
return
cwd = self.honeypot.fs.get_path(self.honeypot.cwd)
for f in (
'tiffany1.jpg',
'tiffany3.jpg',
'tiffany4.jpg',
'tiffany5.jpg',
'tiffany6.jpg',
'XxX Anal Thunder 5 XxX.AVI',
):
size = 1000000 + int(random.random() * 4000000)
cwd.append((
f, T_FILE, 0, 0, size, 33188, time.time(), [], None))
self.honeypot.writeln('./%s' % f)

43
commands/wget.py Normal file
View File

@ -0,0 +1,43 @@
from core.Kippo import HoneyPotCommand
from core.fstypes import *
import stat, time, urlparse, random
class command_wget(HoneyPotCommand):
def call(self, args):
if not len(args):
self.honeypot.writeln('wget: missing URL')
self.honeypot.writeln('Usage: wget [OPTION]... [URL]...')
self.honeypot.terminal.nextLine()
self.honeypot.writeln('Try `wget --help\' for more options.')
return
# ('http', 'www.google.fi', '/test.txt', '', '', '')
url = urlparse.urlparse(args)
size = 10000 + int(random.random() * 40000)
speed = 50 + int(random.random() * 300)
output = """
--%(stamp)s-- %(url)s
Connecting to %(host)s:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: `%(file)s'
[ <=> ] 6,214 --.-K/s in 0.04s
%(stamp)s (%(speed)s KB/s) - `%(file)s' saved [%(size)s]
""" % {
'stamp': time.strftime('%Y-%m-%d %T'),
'url': args,
'file': url[2].split('/')[-1],
'host': url[1],
'size': size,
'speed': speed,
}
self.honeypot.writeln(output)
cwd = self.honeypot.fs.get_path(self.honeypot.cwd)
cwd.append((
url[2].split('/')[-1],
T_FILE, 0, 0, size, 33188, time.time(), [], None))

235
core/Kippo.py Normal file
View File

@ -0,0 +1,235 @@
from twisted.cred import portal, checkers, credentials
from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common
from twisted.conch.insults import insults
from twisted.application import service, internet
from twisted.protocols.policies import TrafficLoggingFactory
from twisted.internet import reactor, protocol
from twisted.python import log
from zope.interface import implements
from copy import deepcopy
import sys, os, random, pickle, time, stat, copy
from core import ttylog
from core.fstypes import *
moi = 1
class HoneyPotProtocol(recvline.HistoricRecvLine):
def __init__(self, user, env):
self.user = user
self.env = env
self.cwd = '/root'
self.fs = HoneyPotFilesystem(deepcopy(self.env.fs))
def connectionMade(self):
recvline.HistoricRecvLine.connectionMade(self)
self.showPrompt()
# Overriding to prevent terminal.reset()
def initializeScreen(self):
self.setInsertMode()
def showPrompt(self):
path = self.cwd
if path == '/root':
path = '~'
self.terminal.write('sales:%s# ' % path)
def getCommandFunc(self, cmd):
return getattr(self, 'do_' + cmd, None)
def getCommand(self, cmd, args):
path = None
if cmd in self.env.commands:
return self.env.commands[cmd](self)
if cmd[0] in ('.', '/'):
path = self.fs.resolve_path(cmd, self.cwd)
if not self.fs.exists(path):
return None
else:
for i in ['%s/%s' % (x, cmd) for x in \
'/bin', '/usr/bin', '/sbin', '/usr/sbin']:
if self.fs.exists(i):
path = i
break
if path in self.env.commands:
return self.env.commands[path](self)
return None
def lineReceived(self, line):
line = line.strip()
if line:
print 'CMD: %s' % line
cmdAndArgs = line.split(' ', 1)
cmd = cmdAndArgs[0]
args = ''
if len(cmdAndArgs) > 1:
args = cmdAndArgs[1]
obj = self.getCommand(cmd, args)
if obj:
try:
obj.call(args)
del obj
except Exception, e:
print e
self.writeln("Segmentation fault")
else:
self.writeln('bash: %s: command not found' % cmd)
self.showPrompt()
def keystrokeReceived(self, keyID, modifier):
ttylog.ttylog_write(self.terminal.ttylog_file, len(keyID),
ttylog.DIR_READ, time.time(), keyID)
recvline.HistoricRecvLine.keystrokeReceived(self, keyID, modifier)
def writeln(self, data):
self.terminal.write(data)
self.terminal.nextLine()
class HoneyPotCommand(object):
def __init__(self, honeypot):
self.honeypot = honeypot
def call(self, *args):
self.honeypot.writeln('Hello World!')
class LoggingServerProtocol(insults.ServerProtocol):
def connectionMade(self):
print dir(self.transport.session)
#print self.transport.session.getHost()
#print self.transport.session.getPeer()
print self.transport.session.id
self.ttylog_file = './log/tty/%s-%s.log' % \
(time.strftime('%Y%m%d-%H%M%S'), int(random.random() * 10000))
print 'Opening TTY log: %s' % self.ttylog_file
ttylog.ttylog_open(self.ttylog_file, time.time())
self.ttylog_open = True
insults.ServerProtocol.connectionMade(self)
def write(self, bytes):
if self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(bytes),
ttylog.DIR_WRITE, time.time(), bytes)
insults.ServerProtocol.write(self, bytes)
def connectionLost(self, reason):
if self.ttylog_open:
ttylog.ttylog_close(self.ttylog_file, time.time())
self.ttylog_open = False
class HoneyPotAvatar(avatar.ConchUser):
implements(conchinterfaces.ISession)
def __init__(self, username, env):
avatar.ConchUser.__init__(self)
self.username = username
self.env = env
self.channelLookup.update({'session':session.SSHSession})
def openShell(self, protocol):
serverProtocol = LoggingServerProtocol(HoneyPotProtocol, self, self.env)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol, cmd):
raise NotImplementedError
def closed(self):
pass
class HoneyPotEnvironment(object):
def __init__(self):
from core.cmdl import cmdl
self.commands = cmdl
print 'Loading filesystem...',
sys.stdout.flush()
self.fs = pickle.load(file('fs.pickle'))
print 'done'
class HoneyPotFilesystem(object):
def __init__(self, fs):
self.fs = fs
def resolve_path(self, path, cwd):
pieces = path.rstrip('/').split('/')
if path[0] == '/':
cwd = []
else:
cwd = [x for x in cwd.split('/') if len(x) and x is not None]
while 1:
if not len(pieces):
break
piece = pieces.pop(0)
if piece == '..':
if len(cwd): cwd.pop()
continue
if piece in ('.', ''):
continue
cwd.append(piece)
return '/%s' % '/'.join(cwd)
def get_path(self, path):
p = self.fs
for i in path.split('/'):
if not i:
continue
p = [x for x in p[A_CONTENTS] if x[A_NAME] == i][0]
return p[A_CONTENTS]
def list_files(self, path):
return self.get_path(path)
def exists(self, path):
pieces = path.strip('/').split('/')
p = self.fs
while 1:
if not len(pieces):
break
piece = pieces.pop(0)
if piece not in [x[A_NAME] for x in p[A_CONTENTS]]:
return False
p = [x for x in p[A_CONTENTS] \
if x[A_NAME] == piece][0]
return True
class HoneyPotRealm:
implements(portal.IRealm)
def __init__(self):
# I don't know if i'm supposed to keep static stuff here
self.env = HoneyPotEnvironment()
def requestAvatar(self, avatarId, mind, *interfaces):
if conchinterfaces.IConchUser in interfaces:
return interfaces[0], \
HoneyPotAvatar(avatarId, self.env), lambda: None
else:
raise Exception, "No supported interfaces found."
def getRSAKeys():
if not (os.path.exists('public.key') and os.path.exists('private.key')):
# generate a RSA keypair
print "Generating RSA keypair..."
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
publicKeyString = keys.makePublicKeyString(rsaKey)
privateKeyString = keys.makePrivateKeyString(rsaKey)
# save keys for next time
file('public.key', 'w+b').write(publicKeyString)
file('private.key', 'w+b').write(privateKeyString)
print "done."
else:
publicKeyString = file('public.key').read()
privateKeyString = file('private.key').read()
return publicKeyString, privateKeyString

0
core/__init__.py Normal file
View File

20
core/cmdl.py Normal file
View File

@ -0,0 +1,20 @@
from commands import base, ls, wget, tar
cmdl = {
'/bin/echo': base.command_echo,
'cd': base.command_cd,
'/bin/cat': base.command_cat,
'/usr/bin/whoami': base.command_whoami,
'quit': base.command_quit,
'/usr/bin/clear': base.command_clear,
'/bin/rm': base.command_rm,
'/usr/bin/uptime': base.command_uptime,
'/usr/bin/w': base.command_w,
'/usr/bin/who': base.command_w,
'/usr/bin/vi': base.command_vi,
'/usr/bin/vim': base.command_vi,
'/bin/mount': base.command_mount,
'/bin/ls': ls.command_ls,
'/usr/bin/wget': wget.command_wget,
'/bin/tar': tar.command_tar,
}

17
core/fstypes.py Normal file
View File

@ -0,0 +1,17 @@
A_NAME, \
A_TYPE, \
A_UID, \
A_GID, \
A_SIZE, \
A_MODE, \
A_CTIME, \
A_CONTENTS, \
A_TARGET = range(0, 9)
T_LINK, \
T_DIR, \
T_FILE, \
T_BLK, \
T_CHR, \
T_SOCK, \
T_FIFO = range(0, 7)

25
core/ttylog.py Normal file
View File

@ -0,0 +1,25 @@
# Should be compatible with user mode linux
import struct, sys
OP_OPEN, OP_CLOSE, OP_WRITE, OP_EXEC = 1, 2, 3, 4
DIR_READ, DIR_WRITE = 1, 2
def ttylog_write(logfile, len, direction, stamp, data = None):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 3, 0, len, direction, sec, usec))
f.write(data)
f.close()
def ttylog_open(logfile, stamp):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 1, 0, 0, 0, sec, usec))
f.close()
def ttylog_close(logfile, stamp):
f = file(logfile, 'a')
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('iLiiLL', 2, 0, 0, 0, sec, usec))
f.close()

149373
fs.pickle Normal file

File diff suppressed because it is too large Load Diff

50
honeyfs/proc/cpuinfo Normal file
View File

@ -0,0 +1,50 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Duo CPU E8200 @ 2.66GHz
stepping : 6
cpu MHz : 2133.305
cache size : 6144 KB
physical id : 0
siblings : 2
core id : 0
cpu cores : 2
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 lahf_lm
bogomips : 4270.03
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Duo CPU E8200 @ 2.66GHz
stepping : 6
cpu MHz : 2133.305
cache size : 6144 KB
physical id : 0
siblings : 2
core id : 1
cpu cores : 2
apicid : 1
initial apicid : 1
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc arch_perfmon pebs bts rep_good pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 lahf_lm
bogomips : 4266.61
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

25
kippo.py Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
from twisted.cred import portal, checkers
from twisted.conch.ssh import factory, keys
from twisted.internet import reactor
from core import Kippo
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(Kippo.HoneyPotRealm())
users = {'root': 'root'}
sshFactory.portal.registerChecker(
checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKeyString, privKeyString = Kippo.getRSAKeys()
sshFactory.publicKeys = {
'ssh-rsa': keys.Key.fromString(data=pubKeyString)}
sshFactory.privateKeys = {
'ssh-rsa': keys.Key.fromString(data=privKeyString)}
reactor.listenTCP(2222, sshFactory)
reactor.run()
# vim: set sw=4 et:

61
utils/createfs.py Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
import os, pickle, sys, locale
from stat import *
def recurse(root, tree, count = 0):
A_NAME, A_TYPE, A_UID, A_GID, A_SIZE, A_MODE, \
A_CTIME, A_CONTENTS, A_TARGET = range(0, 9)
T_LINK, T_DIR, T_FILE, T_BLK, T_CHR, T_SOCK, T_FIFO = range(0, 7)
for name in os.listdir(root):
path = os.path.join(root, name)
if path in (
'/root/realfs.txt',
'/root/createfs.py',
'/root/.bash_history',
):
continue
if os.path.islink(path):
s = os.lstat(path)
else:
s = os.stat(path)
entry = [name, T_FILE, s.st_uid, s.st_gid, s.st_size, s.st_mode, \
int(s.st_ctime), [], None]
if S_ISLNK(s[ST_MODE]):
entry[A_TYPE] = T_LINK
entry[TARGET] = os.path.realpath(path)
elif S_ISDIR(s[ST_MODE]):
entry[A_TYPE] = T_DIR
if path not in ('/proc', '/sys'):
recurse(path, entry[A_CONTENTS])
elif S_ISREG(s[ST_MODE]):
entry[A_TYPE] = T_FILE
elif S_ISBLK(s[ST_MODE]):
entry[A_TYPE] = T_BLK
elif S_ISCHR(s[ST_MODE]):
entry[A_TYPE] = T_CHR
elif S_ISSOCK(s[ST_MODE]):
entry[A_TYPE] = T_SOCK
elif S_ISFIFO(s[ST_MODE]):
entry[A_TYPE] = T_FIFO
else:
sys.stderr.write('We should handle %s' % path)
sys.exit(1)
tree.append(entry)
if __name__ == '__main__':
A_NAME, A_TYPE, A_UID, A_GID, A_SIZE, A_MODE, \
A_CTIME, A_CONTENTS, A_TARGET = range(0, 9)
T_LINK, T_DIR, T_FILE, T_BLK, T_CHR, T_SOCK, T_FIFO = range(0, 7)
tree = ['/', T_DIR, 0, 0, 0, 0, 0, [], '']
recurse('.', tree[A_CONTENTS], tree[A_CONTENTS])
sys.stderr.write('Doing stuff\n')
print pickle.dumps(tree)

262
utils/playlog.py Executable file
View File

@ -0,0 +1,262 @@
#!/usr/bin/env python
#
# Copyright (C) 2003 Upi Tamminen <desaster@dragonlight.fi>
#
# Last update: Mon Sep 15 20:01:15 EEST 2003
# * Sessions can now be extracted to a file
# * The first data seen is considered 'output'.
# If this guess is wrong, the new -i option can be used
# * Rewritten to use file positions instead of tty IDs
# * Added -f that works like tail -f
# * Delays longer than 5 seconds are shortened to 5 seconds
#
import os, sys, time, struct, string, getopt, fcntl, termios
OP_OPEN, OP_CLOSE, OP_WRITE, OP_EXEC = 1, 2, 3, 4
DIR_READ, DIR_WRITE = 1, 2
def termwidth():
return struct.unpack("hhhh",
fcntl.ioctl(0, termios.TIOCGWINSZ ,"\000" * 8))[1]
def maxcolumnlen(list, column):
maxlen = 0
for row in list:
if len(row[column]) > maxlen:
maxlen = len(row[column])
return maxlen
def playlog(fd, pos, settings):
ssize = struct.calcsize('iLiiLL')
currtty, prevtime, prefdir = 0, 0, 0
fd.seek(int(pos))
while 1:
try:
(op, tty, length, dir, sec, usec) = \
struct.unpack('iLiiLL', fd.read(ssize))
data = fd.read(length)
except struct.error:
if settings['tail']:
prevtime = 0
time.sleep(0.1)
settings['maxdelay'] = 0
continue
break
if currtty == 0: currtty = tty
if str(tty) == str(currtty) and op == OP_WRITE:
# the first stream seen is considered 'output'
if prefdir == 0:
prefdir = dir
# use the other direction
if settings['input_only']:
prefdir = DIR_READ
if dir == DIR_READ: prefdir = DIR_WRITE
if dir == prefdir or settings['both_dirs']:
curtime = float(sec) + float(usec) / 1000000
if prevtime != 0:
sleeptime = curtime - prevtime
if sleeptime > settings['maxdelay']:
sleeptime = settings['maxdelay']
if settings['maxdelay'] > 0:
time.sleep(sleeptime)
prevtime = curtime
sys.stdout.write(data)
sys.stdout.flush()
elif str(tty) == str(currtty) and op == OP_CLOSE:
break
def writelog(fd, outfile, pos):
ssize = struct.calcsize('iLiiLL')
currtty, prevtime, prefdir = 0, 0, 0
try:
outfd = open(outfile, 'ab')
except IOError:
print "Couldn't open the output file!"
sys.exit(3)
fd.seek(int(pos))
written = 0
while 1:
try:
structdata = fd.read(ssize)
op, tty, length, dir, sec, usec = \
struct.unpack('iLiiLL', structdata)
data = fd.read(length)
except struct.error:
op = -1
if currtty == 0: currtty = tty
if str(tty) == str(currtty):
outfd.write(structdata + data)
written += len(structdata + data)
if op == OP_CLOSE or op == -1:
print 'Total %d bytes written' % written
break
def showsessions(fd):
ssize = struct.calcsize('iLiiLL')
ttys, chunches, currpos = {}, [], 0
# no point in reading more...
maxglancelen = termwidth()
while 1:
try:
structdata = fd.read(ssize)
op, tty, length, dir, sec, usec = \
struct.unpack('iLiiLL', structdata)
data = fd.read(length)
except struct.error:
op = -1
if op == OP_OPEN:
ttys[tty] = {
'pos': currpos,
'start': sec,
'end': sec,
'size': 0,
'glance': '',
'prefdir': 0,
}
elif op == OP_CLOSE or op == -1:
if ttys.has_key(tty):
chunch = ttys[tty]
chunch['end'] = sec
chunches.append(chunch)
del ttys[tty]
elif op == OP_WRITE:
if ttys[tty]['prefdir'] == 0:
ttys[tty]['prefdir'] = dir
if dir == ttys[tty]['prefdir']:
ttys[tty]['size'] += len(data)
if len(ttys[tty]['glance']) <= maxglancelen:
ttys[tty]['glance'] += data
if op == -1:
break
currpos += len(structdata) + length
# unclosed sessions
for tty in ttys.keys():
chunch = ttys[tty]
chunch['end'] = -1
chunches.append(chunch)
sessions = [['id', 'start', 'end', 'size', 'glance']]
for chunch in chunches:
session = [str(chunch['pos'])]
startdate = time.localtime(chunch['start'])
enddate = time.localtime(chunch['end'])
session.append(time.strftime('%Y-%m-%d %R', startdate))
if chunch['end'] == -1:
session.append('still open')
else:
if time.strftime('%Y-%m-%d', enddate) == \
time.strftime('%Y-%m-%d', startdate):
session.append(time.strftime('%R', enddate))
else:
session.append(time.strftime('%Y-%m-%d %R', enddate))
session.append('%s' % chunch['size'])
session.append(chunch['glance'].translate(
string.maketrans('\t\n\r\x1b', ' ')).strip())
sessions.append(session)
maxidlen = maxcolumnlen(sessions, 0) + 1
maxstartdatelen = maxcolumnlen(sessions, 1) + 1
maxenddatelen = maxcolumnlen(sessions, 2) + 1
maxcounterlen = maxcolumnlen(sessions, 3) + 1
spaceleft = termwidth() - \
(maxidlen + maxstartdatelen + maxenddatelen + maxcounterlen + 5)
for session in sessions:
print '%s %s %s %s %s' % (session[0].ljust(maxidlen),
session[1].ljust(maxstartdatelen),
session[2].ljust(maxenddatelen),
session[3].ljust(maxcounterlen),
session[4][:spaceleft])
print
help(brief = 1)
def help(brief = 0):
print 'Usage: %s [-bfhi] [-m secs] [-w file] <tty-log-file> [id]\n' % \
os.path.basename(sys.argv[0])
if not brief:
print ' -f keep trying to read the log until it\'s closed'
print ' -m <seconds> maximum delay in seconds, to avoid' + \
' boredom or fast-forward\n' + \
' to the end. (default is 3.0)'
print ' -i show the input stream instead of output'
print ' -b show both input and output streams'
print ' -w <file> extract the session to a file'
print ' -h display this help\n'
sys.exit(1)
if __name__ == '__main__':
settings = {
'tail': 0,
'maxdelay': 3.0,
'input_only': 0,
'both_dirs': 0,
'outfile': '',
}
try:
optlist, args = getopt.getopt(sys.argv[1:], 'fhibm:w:', ['help'])
except getopt.GetoptError, error:
print 'Error: %s\n' % error
help()
for o, a in optlist:
if o == '-f': settings['tail'] = 1
elif o == '-m': settings['maxdelay'] = float(a) # takes decimals
elif o == '-i': settings['input_only'] = 1
elif o == '-b': settings['both_dirs'] = 1
elif o == '-w': settings['outfile'] = a
elif o in ['-h', '--help']: help()
if len(args) < 1: help()
try:
logfd = open(args[0], 'rb')
except IOError:
print "Couldn't open log file!"
sys.exit(2)
if len(args) > 1:
if len(settings['outfile']):
writelog(logfd, settings['outfile'], args[1])
else:
playlog(logfd, args[1], settings)
else:
showsessions(logfd)
# vim: set sw=4: