ref: 34ba346d0d82fa4e34e953f872d62bfc5c375df0
dir: /ircd.py/
#!/usr/bin/env python
# a basic ircd
# JOIN, PART
import socket
import time
from select import select
from enum import Enum
defaults = {}
defaults['hostname'] = 'localhost'
defaults['pingtime'] = 250
defaults['name'] = 'ircd.py'
defaults['version'] = '1.0'
defaults['host'] = '' # aka 0.0.0.0
defaults['port'] = 6667
defaults['motd'] = 'Hi.'
defaults['encoding'] = 'utf-8'
# codes
class C(Enum):
WELCOME = 1
MOTD = 372
MOTDSTART = 375
ENDOFMOTD = 376
# errors
NOSUCHNICK = 401
UNKNOWNCOMMAND = 421
ERRONEUSNICKNAME = 431
NONICKNAMEGIVEN = 432
NICKNAMEINUSE = 433
NOTONCHANNEL = 442
#NOTREGISTERED = 451
NEEDMOREPARAMS = 461
ALREADYREGISTERED = 462
Cmsg = {}
Cmsg[C.WELCOME] = 'Welcome.'
Cmsg[C.MOTDSTART] = 'Message of the day is:'
Cmsg[C.ENDOFMOTD] = 'End of the /MOTD'
Cmsg[C.NOSUCHNICK] = 'No such nickname or channel'
Cmsg[C.UNKNOWNCOMMAND] = 'Unknown command'
Cmsg[C.ERRONEUSNICKNAME] = 'Invalid nick'
Cmsg[C.NONICKNAMEGIVEN] = 'No nickname were given'
Cmsg[C.NICKNAMEINUSE] = 'Nickname is already in use'
Cmsg[C.NOTONCHANNEL] = 'No such channel'
Cmsg[C.NEEDMOREPARAMS] = 'Not enough parameters'
Cmsg[C.ALREADYREGISTERED] = 'Client is already registered'
class Client(object):
def __init__(self, server, sock, addr):
self.sock = sock
self.addr = addr
self.server = server
self.ip = self.addr[0]
self.port = self.addr[1]
self.nick = ''
self.user = ''
self.real = ''
self.registered = False
self.ping = 0
self.sendbuf = []
self.recvbuf = []
print('New client', self.ip, self.port, 'has connected')
def send(self, msg):
self.sendbuf.append(msg)
def _reply(self, cmd, msg):
self.send(':{} {} {} {}\r\n'.format(self.server.hostname, str(cmd).rjust(3, '0'), self.nick, msg))
def reply(self, cmd):
self._reply(cmd.value, Cmsg[cmd])
def register(self):
if(self.nick == '' or self.user == '' or self.registered != False):
return False
self.registered = True
print('Client', self.ip, 'has registed with nick', self.nick, 'and user', self.user)
self.reply(C.WELCOME)
self.cmd_motd([])
return True
def cmd_handler(self, cmdl):
print('cmdl:', cmdl)
if(len(cmdl) != 0 and cmdl[-1] == '\r'):
cmdl = cmdl[:-1]
cmdl = cmdl.split(' ')
cmd = cmdl[0]
if(len(cmdl) > 0):
args = cmdl[1:]
else:
args = ''
# fast exit
if(cmd == '\r' or cmd == '\n' or cmd == '\r\n' or cmd.isspace() or cmd == ''):
return
print('cmd:', cmd)
print('args:', args)
match(cmd.upper()):
case 'NICK':
self.cmd_nick(args)
case 'USER':
self.cmd_user(args)
case 'PING':
self.cmd_ping(args)
case 'PRIVMSG':
self.cmd_privmsg(args)
case 'MOTD':
self.cmd_motd(args)
case 'QUIT':
self.cmd_quit(args)
case 'JOIN':
self.cmd_join(args)
case 'PART':
self.cmd_part(args)
case _:
self._reply(C.UNKNOWNCOMMAND.value, Cmsg[C.UNKNOWNCOMMAND] + ' ' + cmd)
def validnick(self, nick):
return nick.isalpha()
def cmd_nick(self, nick):
if(len(nick) < 1 or nick[0] == ''):
self.reply(C.NONICKNAMEGIVEN)
elif(self.validnick(nick[0]) == False):
self.reply(C.ERRONEUSNICKNAME)
elif(self.server.findnick(nick[0]) != None):
self.reply(C.NICKNAMEINUSE)
else:
self.nick = nick[0]
self.register()
def cmd_user(self, user):
if(self.user != ''):
self.reply(C.ALREADYREGISTERED)
else:
self.user = user[0]
self.register()
def cmd_ping(self, cmd):
id = cmd[0]
self.send('PONG {}\n'.format(id))
def cmd_quit(self, cmd):
# XXX self.broadcast()
self.sock.send(bytes('ERROR :closing link\r\n', 'utf-8'))
self.sock.close()
self.server.clients.remove(self)
def cmd_privmsg(self, cmd):
if(len(cmd) < 2):
self.reply(C.NEEDMOREPARAMS)
dst = cmd[0]
msg = cmd[1:]
tmsg = ''
for i in msg:
tmsg += (' ' + i)
u = self.server.findnick(dst)
ch = self.server.findchan(dst)
if(dst[0] == '#'):
if(ch == None):
self.reply(C.NOSUCHNICK)
else:
ch.bcast(':%s!%s@%s PRIVMSG %s%s\r\n' % (self.nick, self.user, self.ip, dst, tmsg))
else:
if(u == None):
self.reply(C.NOSUCHNICK)
else:
u.send(':%s!%s@%s PRIVMSG %s%s\r\n' % (self.nick, self.user, self.ip, dst, tmsg))
#u.send(':%s PRIVMSG %s%s\r\n' % (self.nick, nick, tmsg))
print('privmsg(): ', ':%s!%s@%s PRIVMSG %s%s\r\n' % (self.nick, self.user, self.ip, dst, tmsg))
def cmd_motd(self, cmd):
self.reply(C.MOTDSTART)
self._reply(C.MOTD.value, self.server.motd)
self.reply(C.ENDOFMOTD)
def cmd_join(self, cmd):
if(len(cmd) < 1):
self.reply(C.NEEDMOREPARAMS)
chan = cmd[0]
channel = self.server.findchan(chan)
if(channel == None):
channel = self.server.chans.append(Chan(chan, self))
else:
channel.clients.append(self)
def cmd_part(self, cmd):
if(len(cmd) < 1):
self.reply(C.NEEDMOREPARAMS)
chan = cmd[0]
channel = self.server.findchan(chan)
if(not self in channel.clients):
self.reply(C.NOTONCHANNEL)
else:
channel.clients.remove(self)
def fileno(self):
return self.sock.fileno()
class Chan(object):
def __init__(self, name, client):
self.name = name
self.clients = [client]
self.topic = ''
self.ctime = int(time.time())
def __repr__(self):
return '<Channel ' + self.name + '>'
def bcast(self, msg):
for c in self.clients:
c.sendbuf.append(msg)
def send(self, client, msg):
for c in self.clients:
if(c == client):
return
c.sendbuf.append(msg)
class Server(socket.socket):
def __init__(self, cfg):
super(Server, self).__init__(socket.AF_INET, socket.SOCK_STREAM)
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.chans = []
self.clients = []
self.name = cfg['name']
self.motd = cfg['motd']
self.hostname = cfg['hostname']
self.pingtime = cfg['pingtime']
self.host = cfg['host']
self.port = cfg['port']
self.encoding = cfg['encoding']
def findnick(self, nick):
for i in self.clients:
if(i.nick == nick):
return i
return None
def findchan(self, chan):
for i in self.chans:
if(i.name == chan):
return i
return None
def main(self):
self.bind((self.host, self.port))
self.listen()
while True:
sendu = [c for c in self.clients if len(c.sendbuf) > 0]
read, write, err = select([self] + self.clients, sendu, self.clients) # why self + self.clients
# new clients
if self in read:
sock, address = self.accept()
self.clients.append(Client(self, sock, address))
continue
for c in read:
recv = c.sock.recv(4096)
recv = recv.decode()
recv = recv.strip('\r')
print('client:', c.ip, 'recv:', recv)
for i in recv.split('\n'):
c.recvbuf.append(i)
print('c.recvbuf:', c.recvbuf)
while len(c.recvbuf) > 0:
c.cmd_handler(c.recvbuf.pop(0))
# send messages to each user
for c in write:
# send unsent parts of message each time
while len(c.sendbuf) > 0:
msg = bytes(c.sendbuf.pop(0), self.encoding)
c.sock.send(msg)
def main():
srv = Server(defaults)
srv.main()
if __name__ == '__main__':
main()