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()