#!/usr/bin/env python # #Copyright (c) 2009, Philip Cass #Copyright (c) 2009, Alan Ainsworth # #Contains code from the Mumble Project: #Copyright (C) 2005-2009, Thorvald Natvig # #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions #are met: # #- Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. #- Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. #- Neither the name of localhost, 127001.org, eve-bot nor the names of its # contributors may be used to endorse or promote products derived from this # software without specific prior written permission. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #http://frymaster.127001.org/mumble import socket import time import struct import sys import select import collections import thread import threading import signal import os import optparse import platform #The next 2 imports may not succeed warning="" try: import ssl except: warning+="WARNING: This python program requires the python ssl module (available in python 2.6; standalone version may be at found http://pypi.python.org/pypi/ssl/)\n" try: import Mumble_pb2 except: warning+="WARNING: Module Mumble_pb2 not found\n" warning+="This program requires the Google Protobuffers library (http://code.google.com/apis/protocolbuffers/) to be installed\n" warning+="You must run the protobuf compiler \"protoc\" on the Mumble.proto file to generate the Mumble_pb2 file\n" warning+="Move the Mumble.proto file from the mumble source code into the same directory as this bot and type \"protoc --python_out=. Mumble.proto\"\n" headerFormat=">HI" eavesdropper=None messageLookupMessage={Mumble_pb2.Version:0,Mumble_pb2.UDPTunnel:1,Mumble_pb2.Authenticate:2,Mumble_pb2.Ping:3,Mumble_pb2.Reject:4,Mumble_pb2.ServerSync:5, Mumble_pb2.ChannelRemove:6,Mumble_pb2.ChannelState:7,Mumble_pb2.UserRemove:8,Mumble_pb2.UserState:9,Mumble_pb2.BanList:10,Mumble_pb2.TextMessage:11,Mumble_pb2.PermissionDenied:12, Mumble_pb2.ACL:13,Mumble_pb2.QueryUsers:14,Mumble_pb2.CryptSetup:15,Mumble_pb2.ContextActionAdd:16,Mumble_pb2.ContextAction:17,Mumble_pb2.UserList:18,Mumble_pb2.VoiceTarget:19, Mumble_pb2.PermissionQuery:20,Mumble_pb2.CodecVersion:21} messageLookupNumber={} threadNumber=0 for i in messageLookupMessage.keys(): messageLookupNumber[messageLookupMessage[i]]=i def discontinue_processing(signl, frme): print time.strftime("%a, %d %b %Y %H:%M:%S +0000"), "Received shutdown notice" if eavesdropper: eavesdropper.wrapUpThread(True) else: sys.exit(0) signal.signal( signal.SIGINT, discontinue_processing ) #signal.signal( signal.SIGQUIT, discontinue_processing ) signal.signal( signal.SIGTERM, discontinue_processing ) class timedWatcher(threading.Thread): def __init__(self, plannedPackets,socketLock,socket): global threadNumber threading.Thread.__init__(self) self.plannedPackets=plannedPackets self.pingTotal=1 self.isRunning=True self.socketLock=socketLock self.socket=socket i = threadNumber threadNumber+=1 self.threadName="Thread " + str(i) def stopRunning(self): self.isRunning=False def run(self): self.nextPing=time.time()-1 while self.isRunning: t=time.time() if t>self.nextPing: pbMess = Mumble_pb2.Ping() pbMess.timestamp=(self.pingTotal*5000000) pbMess.good=0 pbMess.late=0 pbMess.lost=0 pbMess.resync=0 pbMess.udp_packets=0 pbMess.tcp_packets=self.pingTotal pbMess.udp_ping_avg=0 pbMess.udp_ping_var=0.0 pbMess.tcp_ping_avg=50 pbMess.tcp_ping_var=50 self.pingTotal+=1 packet=struct.pack(headerFormat,3,pbMess.ByteSize())+pbMess.SerializeToString() self.socketLock.acquire() while len(packet)>0: sent=self.socket.send(packet) packet = packet[sent:] self.socketLock.release() self.nextPing=t+5 if len(self.plannedPackets) > 0: if t > self.plannedPackets[0][0]: self.socketLock.acquire() while t > self.plannedPackets[0][0]: event = self.plannedPackets.popleft() packet = event[1] while len(packet)>0: sent=self.socket.send(packet) packet = packet[sent:] if len(self.plannedPackets)==0: break self.socketLock.release() sleeptime = 10 if len(self.plannedPackets) > 0: sleeptime = self.plannedPackets[0][0]-t altsleeptime=self.nextPing-t if altsleeptime < sleeptime: sleeptime = altsleeptime if sleeptime > 0: time.sleep(sleeptime) print time.strftime("%a, %d %b %Y %H:%M:%S +0000"),self.threadName,"timed thread going away" class mumbleConnection(threading.Thread): def __init__(self,host=None,nickname=None,channel=None,mimic=False,mimicPrefix=None,mimicChannel=None,relayDelay=None,password=None,verbose=False): global threadNumber i = threadNumber threadNumber+=1 self.threadName="Thread " + str(i) threading.Thread.__init__(self) self.plannedPackets=collections.deque() tcpSock=socket.socket(type=socket.SOCK_STREAM) self.socketLock=thread.allocate_lock() self.socket=ssl.wrap_socket(tcpSock,ssl_version=ssl.PROTOCOL_TLSv1) self.socket.setsockopt(socket.SOL_TCP,socket.TCP_NODELAY,1) self.host=host self.nickname=nickname self.channel=channel self.mimic=mimic self.inChannel=False self.session=None self.channelId=None self.victimSession=None self.userList={} self.mimicList={} self.readyToClose=False self.timedWatcher = None self.mimicPrefix=mimicPrefix self.mimicChannel=mimicChannel self.relayDelay=relayDelay self.password=password self.verbose=verbose def decodePDSInt(self,m,si=0): v = ord(m[si]) if ((v & 0x80) == 0x00): return ((v & 0x7F),1) elif ((v & 0xC0) == 0x80): return ((v & 0x4F) << 8 | ord(m[si+1]),2) elif ((v & 0xF0) == 0xF0): if ((v & 0xFC) == 0xF0): return (ord(m[si+1]) << 24 | ord(m[si+2]) << 16 | ord(m[si+3]) << 8 | ord(m[si+4]),5) elif ((v & 0xFC) == 0xF4): return (ord(m[si+1]) << 56 | ord(m[si+2]) << 48 | ord(m[si+3]) << 40 | ord(m[si+4]) << 32 | ord(m[si+5]) << 24 | ord(m[si+6]) << 16 | ord(m[si+7]) << 8 | ord(m[si+8]),9) elif ((v & 0xFC) == 0xF8): result,length=decodePDSInt(m,si+1) return(-result,length+1) elif ((v & 0xFC) == 0xFC): return (-(v & 0x03),1) else: print time.strftime("%a, %d %b %Y %H:%M:%S +0000"),"Help Help, out of cheese :(" sys.exit(1) elif ((v & 0xF0) == 0xE0): return ((v & 0x0F) << 24 | ord(m[si+1]) << 16 | ord(m[si+2]) << 8 | ord(m[si+3]),4) elif ((v & 0xE0) == 0xC0): return ((v & 0x1F) << 16 | ord(m[si+1]) << 8 | ord(m[si+2]),3) else: print time.strftime("%a, %d %b %Y %H:%M:%S +0000"),"out of cheese?" sys.exit(1) def packageMessageForSending(self,msgType,stringMessage): length=len(stringMessage) return struct.pack(headerFormat,msgType,length)+stringMessage def sendTotally(self,message): self.socketLock.acquire() while len(message)>0: sent=self.socket.send(message) if sent < 0: print time.strftime("%a, %d %b %Y %H:%M:%S +0000"),self.threadName,"Server socket error while trying to write, immediate abort" self.socketLock.release() return False message=message[sent:] self.socketLock.release() return True def readTotally(self,size): message="" while len(message)0: print warning o, arguments = p.parse_args() if len(warning)>0: sys.exit(1) if o.relay_to==None or o.eavesdrop_in==None: p.print_help() print "\nYou MUST include both an eavesdrop channel to listen to, and a relay channel to relay to" sys.exit(1) host=(o.server,o.port) if o.eavesdrop_in=="Root": p.print_help() print "\nEavesdrop channel cannot be root (or it would briefly attempt to mimic everyone who joined - including mimics)" sys.exit(1) eavesdropper = mumbleConnection(host,o.nick,o.eavesdrop_in,mimicPrefix=o.mimic_prefix,mimicChannel=o.relay_to,relayDelay=o.delay,password=o.password,verbose=o.verbose) pp=eavesdropper.plannedPackets eavesdropper.start() #Need to keep main thread alive to receive shutdown signal while eavesdropper.isAlive(): time.sleep(1) #Edge case - if Eve is kicked and mimics are still speaking, they won't leave until they have nothing to say #In that case, the main thread will have already died notAllDead=True while notAllDead: notAllDead=False for session in eavesdropper.mimicList: if eavesdropper.mimicList[session]["thread"].isAlive(): notAllDead=True time.sleep(0.1) if __name__ == '__main__': main()