wm: pyircd

Download patch

ref: 34ba346d0d82fa4e34e953f872d62bfc5c375df0
author: mkf <mkf@cloud9p.org>
date: Sun Dec 17 09:23:11 EST 2023

import sources

--- /dev/null
+++ b/ircd.py
@@ -1,0 +1,325 @@
+#!/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()