#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# python: 3.1
from pyjabber import jabber
import threading, time, sys, os, select, socket
import base64, queue
class SNode(object):
def __init__(self, socket, parent):
self._opt = {'enc': 'CP1251',\
'status': 'auth',\
'limit': '1024',\
'info': '',\
'nick': '',\
'sup': [],\
'secr': 'secret',\
'first': time.time(),\
'last': time.time(),\
'jb_user': '',\
'jb_host': 'falstelo.net.ru',\
'jb_port': '5222',\
'jb_addr': '10.12.17.73',
'jb_room': 'nul@conference.falstelo.net.ru'}
self._parent = parent
self._socket = socket
self._opt_l = threading.Lock()
self._last = b'' # последнее незавершонное сообщение
self._jc = None
# -
self._j_u = []
self._j_m = []
# -
self._active = True
self._thread = threading.Thread(None, self._loop)
self._thread.start()
self._tab_rep = ['000', '005', '036', '096', '124', '126']
# ut
# /%DCN000%/, /%DCN005%/, /%DCN036%/, /%DCN096%/, /%DCN124%/, /%DCN126%/
def _dc_enc(self, line):
for q in self._tab_rep:
line.replace(chr(int(q)).encode(), b'/%DCN' + q.encode() + b'%/')
return line
def _dc_dec(self, line, enc=None):
for q in self._tab_rep:
line.replace(b'/%DCN' + q.encode() + b'%/', chr(int(q)).encode())
try:
if enc: return line.decode(enc)
except: pass
return line
# control
def send(self, data):
if self._socket.fileno() == -1: return 0
s = b''
if type(data) not in (tuple, list): data = [data]
for q in data:
if type(q) == str or type(q) != bytes:
try: q = str(q).encode(self.get('enc'))
except: q = b'can\'t translate utf-8 string'
if len(q) and not q[-1] == b'|': q += b'|'
s += q
if self._socket.fileno() != -1:
try: return self._socket.send(s)
except IOError: self.close(None)
return 0
def recv(self):
try:
if self._socket and not self._socket.fileno() == -1:
b = b''
while select.select([self._socket], [], [], 0)[0]:
a = self._socket.recv(1024)
if not a: break
b += a
if not b: raise Exception('')
return b
except:
self.close(None)
return b''
def get(self, key):
self._opt_l.acquire()
try:
r = None
if type(key) in (list, tuple):
r = []
for q in key:
r.append( q in self._opt and self._opt[q] or None)
else: r = key in self._opt and self._opt[key] or None
return r
finally: self._opt_l.release()
def set(self, key, val):
self._opt_l.acquire()
try:
if type(key) in (list, tuple):
if len(key) == len(val):
for q in range(0, len(key)):
if key[q] in self._opt:
self._opt[key[q]] = val[q]
else:
if key in self._opt: self._opt[key] = val
finally: self._opt_l.release()
def status(self):
return self.get('status')
def close(self, msg='system shutdown'):
if msg: self.send('<#sys> %s' %msg)
self._active = False
# internal
def _load(self, nick=''):
fn = self._parent.opt_get('Home') + '/' + nick and nick or self.get('nick')
if not os.path.exists(fn):
fn = os.path.exists(self._parent.opt_get('Home') + '~~(O,o)~~.d4jm')
if not os.path.exists(fn):
return
# TODO: pass
pass
# - jabber hndl
def __jb_hndl_discon(self, c = None):
self.send('<#sys> jabber\'s connection annihilate')
try: self._socket.close()
except: pass
#if self._jc:
# try:
# if not c: self._jc.disconnect()
# except: pass
# try: self._jc = None
# except: pass
def __jb_hndl_message(self, c, msg):
# self.send('<###> %s' %msg)
b = msg.getBody()
nn = msg.getFrom().getResource()
nick = self.get('nick')
if self._parent.opt_get('MSG_FAST'):
if nick == str(nn): return
room = self.get('jb_room')
if str(msg.getFrom().getStripped()) != room:
m = jabber.Message(msg.getFrom(),\
'please send this message to %s, not %s'\
%(room + '/' + nick, msg.getTo()))
return
if msg.getType() == 'chat':
if b:
self.send('$To: %s From: %s $<%s> %s' %(nick, nn, nn, b))
else:
if not b:
if nn: nn = '* ' + nn
self.send('%s set subject to: %s' %(nn, msg.getSubject()))
elif not nn: self.send('* %s' %(b))
else:
try:
self.send('%s <%s> %s' %(self.getTag('x').getAttr('stamp'), nn, b))
except:
self.send('<%s> %s' %(nn, b))
def __jb_hndl_presence(self, c, msg):
# self.send('<###> %s' %msg)
room, sup = self.get(['jb_room', 'sup'])
fr = msg.getFrom()
nn = fr.getResource()
if str(fr.getStripped()) == str(room):
if msg.getType() == 'error':
self.send('<#sys> error while join: %s' %(msg.getTag('text')))
#self.close()
if msg.getType() == 'unavailable':
if nn in self._j_u: self._j_u.remove(nn)
if nn in self._j_m: self._j_m.remove(nn)
self.send('$Quit %s' %nn)
if nn == self.get('nick'):
self.close('<#sys> kicked: %s' %(msg.getStatus()))
else: # пользователь подключил или апдейтиццо
st = msg.getStatus()
if st and st.find('JDCI:') != -1:
ip = st[st.find('JDCI:')+5:st.find('MIF:')]
try:
mif = st[st.find('MIF:') + 4:st.find('|')].encode()
mif = base64.b64decode(mif)
self.send(b'$MyINFO ' + mif)
except: self.send(str(sys.exc_info()))
self.send('$UserIP %s %s' %(nn, ip))
else:
t = msg.getTags('x')
self.send('$MyINFO $ALL %s ~~(O,o)~~$ $0%s$?$0$' %(nn, chr(1)))
self.send('$UserIP %s 127.0.0.1' %nn)
try:
t = msg.getTags('x')
for q in t:
if q.getTag('item'):
t = q.getTag('item')
if t.getAttr('role') == 'moderator':
self._j_m.append(nn)
if nn in self._j_u: self._j_u.remove(nn)
self.send('$OpList %s$$' %nn)
if t.getAttr('jid'):
self.send('$MyINFO $ALL %s ~~(O,o)~~$ $0%s$%s$0$' %(nn, chr(1),t.getAttr('jid')))
except: raise
# FIXME: если чтото не так - то пользователь будет висеть в обоих спиках ><
if not nn in self._j_m: self._j_u.append(nn)
def __jb_hndl_iq(self, c, msg):
# self.send('<###> %s' %msg)
if str(msg.getFrom().getStripped()) == self.get('jb_room'):
nn = msg.getFrom().getResource()
tt = msg.getTag('dc4jmuc')
if tt:
cmd, arg = tt.getAttr('type'), tt.getAttr('val')
# self.send('<#sys> iq recv: %s: %s' %(cmd, arg))
if cmd == 'RevConnectToMe':
self.send('$%s %s %s' %(cmd, nn, self.get('nick')))
if cmd == 'ConnectToMe':
self.send('$%s %s %s' %(cmd, self.get('nick'), arg))
if cmd == 'SR':
self.send('$%s %s' %(cmd, arg))
# - dc hndl
def __dc_hndl_command(self, cmd, arg):
# cmd - команда, arg - неразмеченые аргументы (в bytes)
if cmd == 'ValidateNick':
self.set('nick', self._dc_dec(arg, self.get('enc')))
self.send('$GetPass')
elif cmd == 'MyPass':
nick, login, host = self.get(['nick', 'jb_user', 'jb_host'])
secr = self._dc_dec(arg)
if not login:
login = nick
self.set('jb_user', nick)
for q in range(0, 2):
self.send('<#sys> try login as %s@%s' %(login, host))
if not self._jc.auth(login, secr, 'DC4JMUC_#%s#%s' %(nick, time.time())):
self.send('<#sys> can\'t login, try create')
time.sleep(0.7)
self._jc.requestRegInfo()
self._jc.setRegInfo('username', login)
self._jc.setRegInfo('password', secr)
time.sleep(1.3)
if not self._jc.sendRegInfo():
self.close('cry now!')
break
else:
self.send('<#sys> login ok, send query ro muc (%s)' %(self.get('jb_room')))
#if not 'NoHello' in self.get('sup'):
self.send('$Hello %s' %nick)
self.send('$Version 1.0091')
m = jabber.Presence(self.get('jb_room') + '/' + nick)
mif = self.get('info')
if mif:
m.setStatus('JDCI:%sMIF:%s|' %(self._socket.getpeername()[0], mif))
self._jc.send(m)
break
elif cmd == 'GetNickList':
pass
elif cmd == 'Supports':
self.set('sup', self._dc_dec(arg, self.get('enc')).split(' '))
elif cmd == 'Kick':
pass
elif cmd == 'Quit':
self.close()
elif cmd == 'MyINFO':
mif = base64.b64encode(arg).decode()
self.set('info', mif)
m = jabber.Presence(self.get('jb_room'))
m.setStatus('JDCI:%sMIF:%s|' %(self._socket.getpeername()[0], mif))
# m.insertTag('dc4jmuc', {'type': 'MyINFO', 'val': mif} )
self._jc.send(m)
elif cmd == 'Search':
# поисковые запросы выполняем внутри сегмента
arg = self._dc_dec(arg, self.get('enc'))
self._parent.q_msg.put((None, self.get('nick'), '$%s %s' %(cmd, arg)))
elif cmd == 'SR': # searh result
# ответ на поиск делаем так же внутри сегмента
arg = self._dc_dec(arg, self.get('enc'))
nn = arg.split('\x05')[3]
arg = arg[:(len(nn)+ 1)*-1]
self._parent.q_msg.put((nn, self.get('nick'), '$SR %s' %arg))
elif cmd == 'RevConnectToMe':
# запрос на подключение можно разсылать за пределы сегмента
nn = self._dc_dec(arg, self.get('enc')).split(' ', 1)[1]
if nn:
m = jabber.Iq(self.get('jb_room') + '/' + nn, 'query')
m.insertTag('dc4jmuc', {'type': cmd, 'val': ''} )
self._jc.send(m)
elif cmd == 'ConnectToMe':
# так же рассылаем за предел сегмента
nn = self._dc_dec(arg, self.get('enc')).split(' ', 1)
if 1:
# FIXME: плохо непроверерять обратный адрес -
# тк можно указать что угодно(те сделать небольшой ддос) ><
#if nn[0] and self._socket.getpeername()[0] == nn[1].split(':')[0]:
m = jabber.Iq(self.get('jb_room') + '/' + nn[0], 'query')
m.insertTag('dc4jmuc', {'type': cmd, 'val': nn[1]})
self._jc.send(m)
elif cmd == 'MultiConnectToMe':
self.send('$%s not implemented')
def __dc_hndl_message(self, to, msg):
# self.send('>>> %s %s' %(to, msg))
# если to == None, значит сообщение в общак
sto = ''
if not to: sto = self.get('jb_room')
else: sto = self.get('jb_room') + '/' + str(to)
m = jabber.Message(sto, str(msg))
m.setType(to and 'chat' or 'groupchat')
self._jc.send(m)
if self._parent.opt_get('MSG_FAST'):
if not to:
self.send('<%s> %s' %(self.get('nick'), msg))
else:pass
# n = self.get('nick')
# self.send('$To: %s From: %s $<%s> %s' %(to, n, n, msg))
# self.send(str(m))
#
def __loop(self):
# self.send('<$process> %s' %self._jc)
self._jc.process(0.5)
# self.send('<$select> %s' %self._socket)
if select.select([self._socket], [], [], 0.5)[0]:
ll = self.recv().split(b'|')
if self._last and len(ll) > 0:
ll[1] = self._last + ll[1]
self._last = b''
try:
if ll[-1][-1] != b'|':
self._last = ll[-1]
ll = ll[:-1]
except: pass
if len(self._last) > int(self.get('limit')):
self._last = b''
for q in ll:
try:
# self.send('>>> %s' %q)
q = q.split(b' ', 1)
if not len(q[0]): continue
if q[0][0] == b'$'[0]:
if q[0] == b'$To:':
q[1] = q[1].split(b' ', 4)
# self.send('>>> %s' %q)
self.__dc_hndl_message(self._dc_dec(q[1][0], self.get('enc')), self._dc_dec(q[1][4], self.get('enc')))
else:
self.__dc_hndl_command(q[0][1:].decode(self.get('enc')), len(q) > 1 and q[1] or b'')
elif q[0][0] == b'<'[0] and len(q) > 1:
self.__dc_hndl_message(None, self._dc_dec(q[1], self.get('enc')))
except: raise
def _loop(self):
th_m = threading.enumerate()[0]
# self.send('<$begin>')
if not self._jc:
# self.send('<$create>')
host, port, addr = self.get(['jb_host', 'jb_port', 'jb_addr'])
if addr:
self._jc = jabber.Client(host, int(port), hostIP=addr)
else: self._jc = jabber.Client(host, int(port))
self.send('<#sys> make connect... (%s:%s (%s))' %(host, port, addr))
try:
self._jc.connect()
self._jc.setDisconnectHandler(self.__jb_hndl_discon)
self._jc.registerHandler('message', self.__jb_hndl_message)
self._jc.registerHandler('presence', self.__jb_hndl_presence)
self._jc.registerHandler('iq', self.__jb_hndl_iq)
self.send('<#sys> jabber ok')
# отправяляем всякую хрень
self.send(['$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=py_dc4jmuc',\
'$HubName %s' %(self._parent.opt_get('HubName'))])
except:
self.close(str( __import__('sys').exc_info() ))
self._jc = None
while th_m.isAlive() and self._socket.fileno() != -1 and self._active:
st_t = time.time()
try:
try:
try: self.__loop()
except IOError: pass
except KeyboardInterrupt: pass
except:
exc = __import__('sys').exc_info()
if self._socket.fileno() != -1:
self.send('<#sys> Exception: %s\n%s: %s'\
%('\n'.join(__import__('traceback').format_tb(exc[2])),\
str(exc[1]), exc[0].__name__))
else: print(exc)
st_t = 1.0 - (time.time() - st_t)
if st_t > 0:
print('<$sleep> %s' %st_t)
time.sleep(st_t)
print('<$end>')
if self._socket: self._socket.close()
try: self._jc.disconnect()
except: pass
class Spirit(object):
def __init__(self):
self.port = 411
self.addr = '0.0.0.0'
self._socket = None
self._cli = []
self.q_msg = queue.Queue()
self._opts_l = threading.Lock()
self._opts = {'HubName': 'Njalternative - Welcome to little hope [(^^^)]',
'MSG_FAST': True,
'Home': './home'}
def opt_get(self, key, default=None):
self._opts_l.acquire()
try:
if key in self._opts:
return self._opts[key]
finally: self._opts_l.release()
return default
def opt_load(self, fn):
pass
def serve(self):
if not self._socket or self._socket.fileno() == -1:
try:
f = open('/var/run/py_dcj.pid', 'w')
f.write(str(os.getpid()))
f.close()
except: pass
while 1:
try:
self._socket = socket.socket()
self._socket.bind((self.addr, self.port))
self._socket.listen(10)
except: continue
break
print('start')
def stop(self):
if self._socket: self._socket.close()
for q in self._cli: q.close()
self._cli = []
def process(self, tm=1):
if self._socket and self._socket.fileno() != -1:
if select.select([self._socket], [], [], tm)[0]:
self._cli.append(SNode(self._socket.accept()[0], self))
while not self.q_msg.empty():
to, fr, msg = self.q_msg.get()
if not to:
for q in self._cli:
if q.get('nick') != fr:
q.send(msg)
else:
for q in self._cli:
if q.get('nick') == to:
q.send(msg)
break
if __name__ == '__main__':
s = Spirit()
s.serve()
# if 1:
try:
while 1: s.process()
finally: s.stop()