mirror of
https://github.com/weechat/weechat.git
synced 2026-07-01 07:16:37 +02:00
weercd.py: use argparse module to parse command line arguments, remove config file
The configuration file weercd.conf has been removed. Instead, default options can be set in an environment variable called "WEERCD_OPTIONS". A file with options can be used, then name must be given as option with a leading "@", for example: python weercd.py @args.txt The option "action" has been removed. Default behavior is still to flood the client. Actions "user" and "file" have been merged into a single option -f/--file, which accepts a file, or special value "-" to read stdin. The script now requires python >= 2.7 (because the argparse module is not available in python 2.6 and older versions).
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
#
|
||||
# weercd configuration
|
||||
#
|
||||
host=
|
||||
port=7777
|
||||
debug=off
|
||||
action=flood
|
||||
wait=0
|
||||
sleep=0
|
||||
nickused=0
|
||||
maxchans=5
|
||||
maxnicks=100
|
||||
usernotices=on
|
||||
channotices=on
|
||||
+150
-191
@@ -20,13 +20,15 @@
|
||||
#
|
||||
|
||||
#
|
||||
# weercd - the WeeChat IRC server for testing purposes
|
||||
# weercd - the WeeChat IRC testing server
|
||||
#
|
||||
# weercd is an IRC server that is designed to test client resistance and memory
|
||||
# usage (quickly detect memory leaks, for example with client scripts).
|
||||
# Various IRC commands are sent in a short time (privmsg, notice, join/quit, ..)
|
||||
# It can be used with any IRC client (not only WeeChat).
|
||||
#
|
||||
# This script works with Python 2.x and 3.x.
|
||||
# In the "flood" mode, various IRC commands are sent in a short time (privmsg,
|
||||
# notice, join/quit, ..) to test client resistance and memory usage (to quickly
|
||||
# detect memory leaks, for example with client scripts).
|
||||
#
|
||||
# This script works with Python 2.x (>= 2.7) and 3.x.
|
||||
#
|
||||
# It is *STRONGLY RECOMMENDED* to connect this server with a client in a test
|
||||
# environment:
|
||||
@@ -41,7 +43,7 @@
|
||||
# python weercd.py
|
||||
# 2. open another terminal and run WeeChat with home in /tmp:
|
||||
# weechat --dir /tmp/weechat
|
||||
# 3. optional: install script(s) in /tmp/weechat/<language>/autoload/
|
||||
# 3. optional: install script(s) (/script install ...)
|
||||
# 4. add server and connect to it:
|
||||
# /server add weercd 127.0.0.1/7777
|
||||
# /connect weercd
|
||||
@@ -50,127 +52,53 @@
|
||||
# Yeah, it's stable \o/
|
||||
#
|
||||
|
||||
import sys, socket, select, time, random, string, re
|
||||
import argparse
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import select
|
||||
import shlex
|
||||
import socket
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
NAME = 'weercd'
|
||||
VERSION = '0.6'
|
||||
NAME = 'weercd'
|
||||
VERSION = '0.7'
|
||||
DESCRIPTION = 'The WeeChat IRC testing server.'
|
||||
|
||||
options = {
|
||||
'host' : ['', 'Host for socket bind'],
|
||||
'port' : ['7777', 'Port for socket bind'],
|
||||
'debug' : ['off', 'Debug (on/off)'],
|
||||
'action' : ['flood', 'Action of server: "flood" = flood client, "user" = type messages to send to client, "file" = messages sent from a file'],
|
||||
'wait' : ['0', 'Time to wait before flooding client (float, in seconds)'],
|
||||
'sleep' : ['0', 'Sleep for select, delay between 2 messages sent to client (float, in seconds)'],
|
||||
'nickused' : ['0', 'Send 433 (nickname already in use) this number of times before accepting nick'],
|
||||
'maxchans' : ['5', 'Max channels to join'],
|
||||
'maxnicks' : ['100', 'Max nicks per channel'],
|
||||
'usernotices': ['on', 'Send notices to user (on/off)'],
|
||||
'channotices': ['on', 'Send notices to channels (on/off)'],
|
||||
'file' : ['', 'Filename used for sending messages to client (only for action "file")'],
|
||||
}
|
||||
|
||||
def usage():
|
||||
"""Display usage."""
|
||||
global options
|
||||
print('\nUsage: %s [option=value [option=value ...]] | [-h | --help]\n' % sys.argv[0])
|
||||
print(' option=value option with value (see table below)')
|
||||
print(' -h, --help display this help\n')
|
||||
print('Options (name, default value, description):\n')
|
||||
for key in options:
|
||||
v = '"%s"' % options[key][0]
|
||||
print(' %-12s %-10s %s' % (key, v, options[key][1]))
|
||||
print('\nOptions can be written in %s.conf, with format for each line: option=value\n' % NAME)
|
||||
sys.exit(0)
|
||||
|
||||
def setoption(string):
|
||||
"""Set option with string using format "option=value"."""
|
||||
global options
|
||||
items = string.strip().split('=', 1)
|
||||
if len(items) == 2:
|
||||
key = items[0].strip()
|
||||
if not key.startswith('#'):
|
||||
value = items[1].strip()
|
||||
if key in options:
|
||||
options[key][0] = value
|
||||
else:
|
||||
print('WARNING: unknown option "%s"' % key)
|
||||
|
||||
def getoption(option):
|
||||
"""Get value of an option."""
|
||||
global options
|
||||
if option in options:
|
||||
return options[option][0]
|
||||
return None
|
||||
|
||||
def getdictoptions():
|
||||
"""Get dict with options and values."""
|
||||
global options
|
||||
d = {}
|
||||
for key in options:
|
||||
d[key] = options[key][0]
|
||||
return d
|
||||
|
||||
def readconfig(filename):
|
||||
"""Read configuration file."""
|
||||
try:
|
||||
lines = open(filename, 'rb').readlines()
|
||||
for line in lines:
|
||||
setoption(str(line.decode('utf-8')))
|
||||
except:
|
||||
pass
|
||||
|
||||
def strrand(minlength=1, maxlength=50, spaces=False):
|
||||
"""Return string with random lenght and content."""
|
||||
length = random.randint(minlength, maxlength)
|
||||
strspace = ''
|
||||
if spaces:
|
||||
strspace = ' '
|
||||
return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + strspace) for x in range(length))
|
||||
|
||||
class Client:
|
||||
def __init__(self, sock, addr, **kwargs):
|
||||
def __init__(self, sock, addr, args, **kwargs):
|
||||
self.sock, self.addr = sock, addr
|
||||
self.action = getoption('action')
|
||||
self.args = args
|
||||
self.nick = ''
|
||||
self.nicknumber = 0
|
||||
self.channels = {}
|
||||
self.lastbuf = ''
|
||||
self.incount, self.outcount, self.inbytes, self.outbytes = 0, 0, 0, 0
|
||||
self.quit, self.endmsg, self.endexcept = False, '', None
|
||||
self.sleep = float(getoption('sleep'))
|
||||
self.nickused = int(getoption('nickused'))
|
||||
self.maxchans = int(getoption('maxchans'))
|
||||
self.maxnicks = int(getoption('maxnicks'))
|
||||
self.usernotices = (getoption('usernotices') == 'on')
|
||||
self.channotices = (getoption('channotices') == 'on')
|
||||
self.starttime = time.time()
|
||||
self.file = None
|
||||
if self.action == 'file':
|
||||
self.filename = getoption('file')
|
||||
if not self.filename:
|
||||
print('Error: please specify file name with option "file=..."')
|
||||
return
|
||||
try:
|
||||
self.file = open(self.filename, 'r')
|
||||
except IOError:
|
||||
print('Error: unable to open file "%s"' % self.filename)
|
||||
return
|
||||
self.connect()
|
||||
if not self.quit:
|
||||
if self.action == 'flood':
|
||||
self.action_flood()
|
||||
elif self.action == 'user':
|
||||
self.action_user()
|
||||
elif self.action == 'file':
|
||||
self.action_file()
|
||||
if self.args.file:
|
||||
self.send_from_file()
|
||||
else:
|
||||
print('Unknown action: "%s"' % self.action)
|
||||
return
|
||||
self.flood()
|
||||
|
||||
def strrand(self, minlength=1, maxlength=50, spaces=False):
|
||||
"""Return string with random length and content."""
|
||||
length = random.randint(minlength, maxlength)
|
||||
strspace = ''
|
||||
if spaces:
|
||||
strspace = ' '
|
||||
return ''.join(random.choice(string.ascii_uppercase +
|
||||
string.ascii_lowercase +
|
||||
string.digits + strspace) for x in range(length))
|
||||
|
||||
def send(self, data):
|
||||
"""Send one message to client."""
|
||||
if getoption('debug') == 'on':
|
||||
if self.args.debug:
|
||||
print('<-- %s' % data)
|
||||
msg = '%s\r\n' % data
|
||||
self.outbytes += len(msg)
|
||||
@@ -179,7 +107,7 @@ class Client:
|
||||
|
||||
def recv(self, data):
|
||||
"""Read one message from client."""
|
||||
if getoption('debug') == 'on':
|
||||
if self.args.debug:
|
||||
print('--> %s' % data)
|
||||
if data.startswith('PING '):
|
||||
args = data[5:]
|
||||
@@ -207,18 +135,18 @@ class Client:
|
||||
data = data.decode('UTF-8')
|
||||
self.inbytes += len(data)
|
||||
data = self.lastbuf + data
|
||||
while 1:
|
||||
while True:
|
||||
pos = data.find('\r\n')
|
||||
if pos < 0:
|
||||
break
|
||||
self.recv(data[0:pos])
|
||||
data = data[pos+2:]
|
||||
data = data[pos + 2:]
|
||||
self.lastbuf = data
|
||||
|
||||
def connect(self):
|
||||
"""Tell client that connection is ok."""
|
||||
try:
|
||||
count = self.nickused
|
||||
count = self.args.nickused
|
||||
while self.nick == '':
|
||||
self.read(0.1)
|
||||
if self.nick and count > 0:
|
||||
@@ -239,54 +167,56 @@ class Client:
|
||||
return None
|
||||
rnick = self.nick
|
||||
while rnick == self.nick:
|
||||
rnick = self.channels[channel][random.randint(0, len(self.channels[channel])-1)]
|
||||
rnick = self.channels[channel][random.randint(0, len(self.channels[channel]) - 1)]
|
||||
return rnick
|
||||
|
||||
def action_flood(self):
|
||||
def flood(self):
|
||||
"""Yay, funny stuff here! Flood client!"""
|
||||
wait = int(getoption('wait'))
|
||||
if wait > 0:
|
||||
print('Wait %d seconds' % wait)
|
||||
time.sleep(wait)
|
||||
if self.args.wait > 0:
|
||||
print('Wait %f seconds' % self.args.wait)
|
||||
time.sleep(self.args.wait)
|
||||
sys.stdout.write('Flooding client..')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
while not self.quit:
|
||||
self.read(self.sleep)
|
||||
self.read(self.args.sleep)
|
||||
# global actions
|
||||
action = random.randint(1, 2)
|
||||
if action == 1:
|
||||
# join
|
||||
if len(self.channels) < self.maxchans:
|
||||
channel = '#%s' % strrand(1, 25)
|
||||
if len(self.channels) < self.args.maxchans:
|
||||
channel = '#%s' % self.strrand(1, 25)
|
||||
if not channel in self.channels:
|
||||
self.send(':%s!%s JOIN :%s' % (self.nick, self.addr[0], channel))
|
||||
self.send(':%s 353 %s = %s :@%s' % (NAME, self.nick, channel, self.nick))
|
||||
self.send(':%s 366 %s %s :End of /NAMES list.' % (NAME, self.nick, channel))
|
||||
self.channels[channel] = [self.nick]
|
||||
elif action == 2 and self.usernotices:
|
||||
# notice
|
||||
self.send(':%s!%s@%s NOTICE %s :%s' % (
|
||||
strrand(1, 10), strrand(1, 10), strrand(1, 10), self.nick, strrand(1, 400, True)))
|
||||
elif action == 2 and 'user' in self.args.notice:
|
||||
# notice for user
|
||||
self.send(':%s!%s@%s NOTICE %s :%s' % (self.strrand(1, 10), self.strrand(1, 10),
|
||||
self.strrand(1, 10), self.nick,
|
||||
self.strrand(1, 400, True)))
|
||||
# actions for each channel
|
||||
for channel in self.channels:
|
||||
action = random.randint(1, 50)
|
||||
if action >= 1 and action <= 10:
|
||||
# join
|
||||
if len(self.channels[channel]) < self.maxnicks:
|
||||
if len(self.channels[channel]) < self.args.maxnicks:
|
||||
self.nicknumber += 1
|
||||
newnick = '%s%d' % (strrand(1, 5), self.nicknumber)
|
||||
self.send(':%s!%s@%s JOIN :%s' % (newnick, strrand(1, 10), strrand(1, 10), channel))
|
||||
newnick = '%s%d' % (self.strrand(1, 5), self.nicknumber)
|
||||
self.send(':%s!%s@%s JOIN :%s' % (newnick, self.strrand(1, 10),
|
||||
self.strrand(1, 10), channel))
|
||||
self.channels[channel].append(newnick)
|
||||
elif action == 11:
|
||||
# part/quit
|
||||
if len(self.channels[channel]) > 0:
|
||||
rnick = self.chan_randnick(channel)
|
||||
if rnick:
|
||||
command = 'QUIT :%s' % strrand(1, 30)
|
||||
command = 'QUIT :%s' % self.strrand(1, 30)
|
||||
if random.randint(1, 2) == 1:
|
||||
command = 'PART %s' % channel
|
||||
self.send(':%s!%s@%s %s' % (rnick, strrand(1, 10), strrand(1, 10), command))
|
||||
self.send(':%s!%s@%s %s' % (rnick, self.strrand(1, 10),
|
||||
self.strrand(1, 10), command))
|
||||
self.channels[channel].remove(rnick)
|
||||
elif action == 12:
|
||||
# kick
|
||||
@@ -294,29 +224,33 @@ class Client:
|
||||
rnick1 = self.chan_randnick(channel)
|
||||
rnick2 = self.chan_randnick(channel)
|
||||
if rnick1 and rnick2 and rnick1 != rnick2:
|
||||
self.send(':%s!%s@%s KICK %s %s :%s' % (rnick1, strrand(1, 10), strrand(1, 10), channel, rnick2, strrand (1, 50)))
|
||||
self.send(':%s!%s@%s KICK %s %s :%s' % (rnick1, self.strrand(1, 10),
|
||||
self.strrand(1, 10), channel, rnick2,
|
||||
self.strrand(1, 50)))
|
||||
self.channels[channel].remove(rnick2)
|
||||
else:
|
||||
# message
|
||||
if len(self.channels[channel]) > 0:
|
||||
rnick = self.chan_randnick(channel)
|
||||
if rnick:
|
||||
msg = strrand(1, 400, True)
|
||||
if self.channotices and random.randint(1,100) == 100:
|
||||
# notice
|
||||
self.send(':%s!%s@%s NOTICE %s :%s' % (rnick, strrand(1, 10), strrand (1, 10), channel, msg))
|
||||
msg = self.strrand(1, 400, True)
|
||||
if 'channel' in self.args.notice and random.randint(1, 100) == 100:
|
||||
# notice for channel
|
||||
self.send(':%s!%s@%s NOTICE %s :%s' % (rnick, self.strrand(1, 10),
|
||||
self.strrand(1, 10), channel, msg))
|
||||
else:
|
||||
# add random highlight
|
||||
if random.randint(1, 100) == 100:
|
||||
msg = '%s: %s' % (self.nick, msg)
|
||||
action2 = random.randint(1,50)
|
||||
action2 = random.randint(1, 50)
|
||||
if action2 == 1:
|
||||
# action (/me)
|
||||
msg = '\x01ACTION %s\x01' % msg
|
||||
elif action2 == 2:
|
||||
# version
|
||||
msg = '\x01VERSION\x01'
|
||||
self.send(':%s!%s@%s PRIVMSG %s :%s' % (rnick, strrand(1, 10), strrand(1, 10), channel, msg))
|
||||
self.send(':%s!%s@%s PRIVMSG %s :%s' % (rnick, self.strrand(1, 10),
|
||||
self.strrand(1, 10), channel, msg))
|
||||
# display progress
|
||||
if self.outcount % 1000 == 0:
|
||||
sys.stdout.write('.')
|
||||
@@ -335,37 +269,29 @@ class Client:
|
||||
self.endmsg = 'quit received'
|
||||
return
|
||||
|
||||
def action_user(self):
|
||||
"""User enters messages to send to client."""
|
||||
try:
|
||||
while 1:
|
||||
sys.stdout.write('Message to send to client: ')
|
||||
sys.stdout.flush()
|
||||
message = sys.stdin.readline()
|
||||
self.send(message)
|
||||
except Exception as e:
|
||||
self.endmsg = 'connection lost'
|
||||
self.endexcept = e
|
||||
return
|
||||
except KeyboardInterrupt:
|
||||
self.endmsg = 'interrupted'
|
||||
return
|
||||
|
||||
def action_file(self):
|
||||
def send_from_file(self):
|
||||
"""Send messages from a file to client."""
|
||||
stdin = self.args.file == sys.stdin
|
||||
count = 0
|
||||
try:
|
||||
count = 0
|
||||
for line in self.file:
|
||||
if not line.startswith('//'):
|
||||
self.read(self.sleep)
|
||||
self.send(line.replace('${nick}', self.nick))
|
||||
while True:
|
||||
if stdin:
|
||||
sys.stdout.write('Message to send to client: ')
|
||||
sys.stdout.flush()
|
||||
message = self.args.file.readline()
|
||||
if not message:
|
||||
break
|
||||
message = message.rstrip('\n')
|
||||
if sys.version_info < (3,):
|
||||
message = message.decode('UTF-8')
|
||||
if not message.startswith('//'):
|
||||
if not stdin:
|
||||
# sleep, only if commands come from a file
|
||||
self.read(self.args.sleep)
|
||||
self.send(message.replace('${nick}', self.nick))
|
||||
count += 1
|
||||
self.file.close()
|
||||
sys.stdout.write('%d messages sent, press Enter to restart server' % count)
|
||||
sys.stdout.flush()
|
||||
sys.stdin.readline()
|
||||
except IOError as e:
|
||||
self.endmsg = 'unable to open file "%s"' % self.filename
|
||||
self.endmsg = 'unable to read file %s' % self.args.file
|
||||
self.endexcept = e
|
||||
return
|
||||
except Exception as e:
|
||||
@@ -375,6 +301,15 @@ class Client:
|
||||
except KeyboardInterrupt:
|
||||
self.endmsg = 'interrupted'
|
||||
return
|
||||
finally:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.write('%d messages sent from %s, press Enter to exit'
|
||||
% (count, 'stdin' if stdin else 'file'))
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
sys.stdin.readline()
|
||||
except:
|
||||
pass
|
||||
|
||||
def stats(self):
|
||||
msgexcept = ''
|
||||
@@ -393,30 +328,54 @@ class Client:
|
||||
print('Closing connection with %s' % str(self.addr))
|
||||
self.sock.close()
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'):
|
||||
usage()
|
||||
readconfig('%s.conf' % NAME)
|
||||
for arg in sys.argv:
|
||||
setoption(arg)
|
||||
print('%s %s - WeeChat IRC server' % (NAME, VERSION))
|
||||
while 1:
|
||||
print('Options: %s' % getdictoptions())
|
||||
print('Listening on port %s (ctrl-C to exit)' % getoption('port'))
|
||||
servsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
servsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
servsock.bind((getoption('host'), int(getoption('port'))))
|
||||
servsock.listen(1)
|
||||
clientsock = None
|
||||
addr = None
|
||||
try:
|
||||
clientsock, addr = servsock.accept()
|
||||
except KeyboardInterrupt:
|
||||
servsock.close()
|
||||
return
|
||||
print('Connection from %s' % str(addr))
|
||||
client = Client(clientsock, addr)
|
||||
del client
|
||||
# parse command line arguments
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
fromfile_prefix_chars='@',
|
||||
description=DESCRIPTION,
|
||||
epilog='Note: the environment variable "WEERCD_OPTIONS" can be '
|
||||
'set with some default options, and argument "@file.txt" can be '
|
||||
'used to read some default options in a file.')
|
||||
parser.add_argument('-H', '--host', help='host for socket bind')
|
||||
parser.add_argument('-p', '--port', type=int, default=7777, help='port for socket bind')
|
||||
parser.add_argument('-f', '--file', type=argparse.FileType('r'),
|
||||
help='send messages from file, instead of flooding the client (use "-" for stdin)')
|
||||
parser.add_argument('-c', '--maxchans', type=int, default=5, help='max number of channels to join')
|
||||
parser.add_argument('-n', '--maxnicks', type=int, default=100, help='max number of nicks per channel')
|
||||
parser.add_argument('-u', '--nickused', type=int, default=0,
|
||||
help='send 433 (nickname already in use) this number of times before accepting nick')
|
||||
parser.add_argument('-N', '--notice', metavar='NOTICE_TYPE', choices=['user', 'channel'],
|
||||
default=['user', 'channel'], nargs='*',
|
||||
help='notices to send: "user" (to user), "channel" (to channel)')
|
||||
parser.add_argument('-s', '--sleep', type=float, default=0,
|
||||
help='sleep for select: delay between 2 messages sent to client (float, in seconds)')
|
||||
parser.add_argument('-w', '--wait', type=float, default=0,
|
||||
help='time to wait before flooding client (float, in seconds)')
|
||||
parser.add_argument('-d', '--debug', action='store_true', help='debug output')
|
||||
parser.add_argument('-v', '--version', action='version', version=VERSION)
|
||||
args = parser.parse_args(shlex.split(os.getenv('WEERCD_OPTIONS') or '') + sys.argv[1:])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print('%s %s - WeeChat IRC testing server' % (NAME, VERSION))
|
||||
print('Options: %s' % vars(args))
|
||||
|
||||
while True:
|
||||
servsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
servsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
servsock.bind((args.host or '', args.port))
|
||||
servsock.listen(1)
|
||||
except Exception as e:
|
||||
print('Socket error: %s' % e)
|
||||
sys.exit(1)
|
||||
print('Listening on port %s (ctrl-C to exit)' % args.port)
|
||||
clientsock = None
|
||||
addr = None
|
||||
try:
|
||||
clientsock, addr = servsock.accept()
|
||||
except KeyboardInterrupt:
|
||||
servsock.close()
|
||||
sys.exit(0)
|
||||
print('Connection from %s' % str(addr))
|
||||
client = Client(clientsock, addr, args)
|
||||
del client
|
||||
if args.file:
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user