/*
 * Copyright (c) 2002 Sergey Lyubka
 * Copyright (c) 2003 Maxim Tretjyakov
 *
 * IRC bot, for FreeBSD x86
 * Compilation: 
 * m4 bot.s | as -o bot.o && ld bot.o -o bot
 * Note: can be easily ported to x86 Linux, by changing PUSHPARAMS and
 * SYSCALL macros.
 */

define(`PUSHPARAMS', `ifelse($#, 0, ,$#, 1, `pushl	$1',
`pushl	$1
	PUSHPARAMS(shift($@))')')

define(`REVERSE', `ifelse($#, 0, ,$#, 1, $1, `REVERSE(shift($@)), $1')')

define(`SYSCALL',
`PUSHPARAMS(REVERSE(shift($@)))
	movl	$1, %eax
	pushl	%eax
	int	`$'0x80
	addl	`$'eval(4 * ($#)),%esp')

.globl _start

.data
sock:		.long	0
logfd:		.long	0
analyzfd:	.long	0
usagestr:	.asciz	"usage:\nasmbot [server_ip [port]]\n"
fatalstr:	.asciz	"fatal error occured\n"
logfile:	.space	255
analyzed:	.asciz	"analyzed.txt"
delim:		.asciz	"\n**********\n"
newline:	.byte	0x0a
tcp_nodelay:	.long	1

/* the next few var is the sockaddr */
sockaddr_in:
sin_len:	.byte	16		/* sizeof(sockaddr_in) */
sin_family:	.byte	2		/* AF_INET */
sin_port:	.short	0x0c1a		/* htons(6667) */
sin_addr:	.long	0x411036c3	/* htonl(172.16.3.21) */	

/* timespec for delay */
tv_sec:		.long	0
tv_nsec:	.long	500000000

/* irc commands */
cmd_user:	.asciz	"user female_asmbot female_asmbot asmbot asmbot\r\n"
cmd_nick:	.ascii	"nick "
nickname:	.asciz  "female_asmbot\r\n"
cmd_join:	.ascii	"join "
channel:	.space	64
cmd_privmsg:	.asciz	"PRIVMSG #asm :Action detected!\r\n"

/* stuff for testing code */
ping_reaction:	.asciz	"Ping? Pong!\n"
queue_:		.long	0
qname:		.asciz	"queue"
status_str:	.asciz	"Status!!!\n"
adduser_str:	.asciz	"Add user!!!\n"
deluser_str:	.asciz	"Del user!!!\n"
exit_str:	.asciz	"Exit!!!\n"

/* table of handlers of IRC commands */
/* row structure: <asciiz command name> <entry point of handler> */
irc_reactor_table:
.asciz	"PING"
.long	ping
.asciz	"PRIVMSG"
.long	privmsg
.long   0		/* terminates reactors chain */

/* table of handlers of bot commands */
/* row structure: <asciiz command name> <entry point of handler> */
bot_reactor_table:
.asciz	"status"
.long	status
.asciz	"adduser"
.long	adduser
.asciz	"deluser"
.long	deluser
.asciz	"disconnect"
.long	exit
.asciz	"say"
.long	say
.long   0		/* terminates reactors chain */

/* input-output buffers */
buflen:	.long	0
offset:	.long	0
.comm	ibuf,	2048
.comm	obuf,   2048
.comm	pbuf,	512

/* extracted from message */
.comm	        server,  256
.comm	        nick,    256
.comm	        command, 32
params:	       .long     0
reactor_table: .long     0

/* message queue. max - 200 nodes */
.comm	queue,	1600
nodes_count:	.long	0
/* structure of queue node:
    struct QUEUE_NODE {
     int key_len; /* 32-bit integer number. Containts length of key. If it is
                     equal to zero then this is special node, `end marker'*//*
     char * key;  /* Pointer to ASCII string. Contains key *//*
*/

/* need to read configuration */
conf_file:	.asciz	"mybot.conf"
conffd:		.long	0
conf_len:	.long	0
conf_mem:	.long	0

dns_query:
/* DNS header */
ID:		.short 0	# query identifier. useful if a lot of queries
Flags:		.short 0	# see lower
QDCOUNT:	.short 0	# number of queries
ANCOUNT:	.short 0	#
NSCOUNT:	.short 0
ARCOUNT:	.short 0

/*
   Flags:
  0                             1  1  1  1  1  1
  0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Description:
QR: message type
    0 - message is query
    1 - message is response
Opcode: kind of query (same in response on this query)
    0 - standart query (QUERY)
    1 - inverse query (IQUERY)
    2 - a server status request (STATUS)
    3-15 - reserved
AA: valid in response.
    0 - responding name server is not an authority for the domain name in
        question section
    1 - responding name server is an authority for the domain name in question
        section
TC: truncation
    0 - message was not truncated due to length greater than that permitted on
        the transmission channel
    1 - message was truncated due to length greater than that permitted on the
        transmission channel
RD: recursion desired
    0 - pursuing query recursively is not desired
    1 - pursuing query recursively is desired
RA: recursion available
    0 - recursive pursuing not available
    1 - recursive pursuing available
Z: reserved for future use. Set to zero
RCODE: responce code
    0 - no error
    1 - format error - name server was unavailable to interpret the query
    2 - server failure
    3 - name error - meaningful only for authoritative responses - domain name
        referenced in the query does not exist
    4 - not implemented
    5 - refused to perform specified operation for policy reasons
    6-15 - reserved
*/

/* DNS question section */
query: .space 512
/*
  format:
  QNAME:  ASCIIZ domain name
  QTYPE:  2 octets - type of query
   1  A     a host address
   2  NS    an authoritative name server
   3  MD    a mail destination (obsolete)
   4  MF    a mail forwarder (obsolete)
   5  CNAME the canonical name for the alias
   6  SOA   marks the start of a zone of authority
   7  MB    mailbox domain (experimental)
   8  MG    a mail group member (experimental)
   9  MR    a mail rename domain name (experimental)
   10 NULL  a null RR (exprimental)
   11 WKS   well known service description
   12 PTR   a domain name pointer
   13 HINFO host information
   14 MINFO mailbox or mail list information
   15 MX    mail exchange
   16 TXT   text strings
  QCLASS: 2 octets - class of query
   1   IN Internet
   2   CS CSNET
   3   CH CHAOS
   4   HS Hesiod
   255 *  anyclass
*/

dns_server: .long   0x020110ac           # 172.16.1.2
port:       .short  0x3500               # 53
dns_sock:   .long   0

/*
 pointer to users table.
   row structure:
   size description
    1    len of row
    ?    ASCIIZ user name (IRC nickname)
    1    user's level
 ends with zero byte
*/
.comm users_table, 1024

.code32

/*
  PN:  get_user_level
  IN:  nick - ASCIIZ nickname for which return level
  OUT: eax  - users level
  ACT: searches given nickname in users table and returns its level, or 0
*/
get_user_level:
	xorl	%eax, %eax
	movl	$users_table, %ebx
	movl	%ebx, %esi

get_level_loop:
	movl	$nick, %edi
	lodsb
	movl	%eax, %ecx
	repz cmpsb
	cmpb	$0x02, %cl
	jz	user_found
	addl	%eax, %ebx
	movl	%ebx, %esi
	cmpb	$0x00, (%esi)
	jz	user_not_found
	jmp	get_level_loop
	
user_found:
	lodsb
	jmp	get_level_loop_end

user_not_found:
	xorb	%al, %al

get_level_loop_end:
	ret

/*
  PN:  status
  IN:  nick - user's ident: nick!ident@hostname
  OUT: sends mode string or silently terminates
  ACT: checks sender's level in user table and gives appropriate status (op,
       voice), if user was found, if not - drops
*/
status:
	call	get_user_level
	orb	%al, %al
	jz	status_undefined
	cmpb	$0x01, %al
	jz	give_voice
	movb	$'o', %bl
	jmp	send_mode_string

give_voice:
	movb	$'v', %bl
	
send_mode_string:
	movl	$obuf, %edi
	movl	$0x45444f4d, %eax   /* MODE */
	stosl
	movb	$0x20, %al
	stosb
	pushl	$channel
	call	strlen
	orl	%eax, %eax
	jz	status_undefined
	decl	%eax
	decl	%eax
	movl	%eax, %ecx
	movl	$channel, %esi
	rep movsb
	
	movl	$0x2020002b, %eax
	movb	%bl, %ah
	roll	$8, %eax
	stosl
	pushl	$nick
	call	strlen
	orl	%eax, %eax
	jz	status_undefined
	movl	%eax, %ecx
	movl	$nick, %esi

next_nickname_char:
	lodsb
	cmpb	$'!', %al
	jz	got_nickname
	orb	%al, %al
	jz	got_nickname
	stosb
	jmp	next_nickname_char

got_nickname:
	movl	$0x00000a0d, %eax
	stosl
	pushl	$obuf
	call	strlen
	SYSCALL( $4, sock, $obuf, %eax )

	movl	$status_str, %ebx
	pushl	%ebx
	call	strlen
	SYSCALL( $4, $1, %ebx, %eax )

status_undefined:
	ret

/*
  PN:  say
  IN:  nick   - user's nick
       params - this PRIVMSG params
  OUT: says in channel specified phrase or not
  ACT: check's user's level, if it is enough than say to channel specified
       phrase, or if no such user, or level is not appropriate than drop
*/
say:
	call	get_user_level
	cmpb	$0x03, %al
	jb	he_cant_say

	movl	$obuf, %edi
	movl	$0x56495250, %eax
	stosl
	movl	$0x2047534d, %eax
	stosl
	movl	$channel, %esi
	
next_chan_say_byte:
	lodsb
	cmpb	$0x0d, %al
	jz	say_chan_ready
	stosb
	jmp	next_chan_say_byte
	
say_chan_ready:
	pushl	%edi
	movl	params, %edi
	movl	$500, %ecx
	movb	$0x20, %al
	repz scasb
	repnz scasb
	repz scasb
	repnz scasb
	decl	%edi
	
	movl	%edi, %esi
	popl	%edi
	movw	$0x3a20, %ax
	stosw
	
next_say_byte:
	lodsb
	stosb
	cmpb	$0x0a, %al
	jz	say_ready
	jmp	next_say_byte
	
say_ready:
	xorb	%al, %al
	stosb
	
	pushl	$obuf
	call	strlen
	SYSCALL( $4, sock, $obuf, %eax )
	
he_cant_say:
	ret

adduser:
	call	get_user_level
	cmpb	$0x05, %al
	jb	he_cant_adduser

	movl	$users_table, %esi
	xorl	%eax, %eax
	
next_user:
	movb	(%esi), %al
	orb	%al, %al
	jz	get_riched_end_of_table
	addl	%eax, %esi
	jmp	next_user
	
get_riched_end_of_table:
	/* esi points to the end of table */
	movl	%esi, %ebx
	movl	params, %edi
	movl	$500, %ecx
	movb	$0x20, %al
	cmpb	(%edi), %al

/*
Message: :botmaster PRIVMSG asmbot adduser chattr 3
                           |+--params
   skip:
   - spaces
   - word    asmbot
   - spaces
   - word    adduser
   - spaces
*/
	repz scasb
	repnz scasb
	repz scasb
	repnz scasb
	repz scasb
	decl	%edi		/* edi -> username */
				/* esi -> end of table */
	xchg	%edi, %esi
	incl	%edi
	xorl	%ecx, %ecx
	movb	%cl, %dl

au_next_nickname_char:
	lodsb
	incb	%dl
	cmpb	$0x20, %al
	jz	au_got_nickname
	decb	%dl
	cmpb	$0x0d, %al
	jz	au_got_nickname
	orb	%al, %al
	jz	au_got_nickname
	stosb
	incl	%ecx
	jmp	au_next_nickname_char

au_got_nickname:
	addl	$3, %ecx
	movb	%cl, (%ebx)
	orb	%dl, %dl
	jz	get_user_default_level
	lodsb
	cmpb	$0x30, %al
	jb	get_user_default_level
	cmp	$0x39, %al
	ja	get_user_default_level
	subb	$0x30, %al
	jmp	store_user_level
	
get_user_default_level:
	movb	$1, %al
	
store_user_level:
	and	$0x000000ff, %eax
	xchg	%ah, %al
	stosw
	
he_cant_adduser:
	movl	$adduser_str, %ebx
	pushl	%ebx
	call	strlen
	SYSCALL( $4, $1, %ebx, %eax )
	ret

deluser:
	call	get_user_level
	cmpb	$0x05, %al
	jb	cant_delete_users
	
	movl	params, %edi
	movb	$0x20, %al
	movl	$500, %ecx
	repz scasb
	repnz scasb
	repz scasb
	repnz scasb
	repz scasb
	jecxz	cant_delete_users
	decl	%edi		/* edi -> username */
	
	movl	$users_table, %ebx
	movl	%ebx, %esi
	xorl	%eax, %eax
	movl	%eax, %ecx
	movl	%eax, %edx
	
deluser_check_next_user:
	lodsb
	orb	%al, %al
	jz	cant_delete_users
	addl	%eax, %edx
	movb	%al, %cl
	pushl	%edi
	repz cmpsb
	popl	%edi
	movl	%ebx, %ebp
	cmpb	$0x02, %cl
	jnz	nicks_not_similar
	movb	-1(%edi), %cl

nicks_not_similar:
	movb	(%ebx), %al
	addl	%eax, %ebx
	movl	%ebx, %esi
	cmpb	$0x20, %cl
	jz	get_table_size
	cmpb	$0x0d, %cl
	jz	get_table_size
	jmp	deluser_check_next_user

get_table_size:
	lodsb
	orb	%al, %al
	jz	got_table_size
	addl	%eax, %edx
	addl	%eax, %ebx
	movl	%ebx, %esi
	jmp	get_table_size

got_table_size:
	movb	(%ebp), %al
	movl	%ebp, %esi
	movl	%ebp, %edi
	addl	%eax, %esi
	cmpb	$0x00, (%esi)
	jnz	delete_not_first
	
	movb	$0x00, (%ebp)
	jmp	cant_delete_users	

delete_not_first:
	movl	%esi, %ebx
	subl	$users_table, %ebx
	subl	%ebx, %edx
	movl	%edx, %ecx
	rep movsb
	xorb	%al, %al
	stosb

cant_delete_users:
	movl	$status_str, %ebx
	pushl	%ebx
	call	strlen
	SYSCALL( $4, $1, %ebx, %eax )
	ret

exit:
	call	get_user_level
	cmpb	$0x05, %al
	jb	cant_exit

	SYSCALL( $1, $0 );

cant_exit:
	movl	$exit_str, %ebx
	pushl	%ebx
	call	strlen
	SYSCALL( $4, $1, %ebx, %eax )
	ret

/*
 PN:  privmsg
 IN:  params - tagets of message and its content
 OUT: obuf   - dummy message
 ACT: create dummy message and sends it
*/
privmsg:
	movl	params, %edi
	movb	$0x20, %al
	movw	$500, %cx
	repz scasb
	decl	%edi
	movl	$nickname, %esi

to_whom_message:
	cmpb	$0x0d, (%edi)
	jz	privmsg_end
	cmpb	$0x20, (%edi)
	jnz	next_target_char
	cmpb	$0x0d, (%esi)
	jz	privmsg_to_bot

next_target_char:
	cmpsb
	jnz	privmsg_end
	jmp	to_whom_message

privmsg_to_bot:
	pushl	reactor_table
	movl	$bot_reactor_table, reactor_table
	
	movb	$0x20, %al
	repz scasb
	decl	%edi
	cmpb	$':', (%edi)
	jnz	single_word_message
	incl	%edi
	
single_word_message:
	movl	%edi, %esi
	movl	$command, %edi
	movl	%edi, %ebx
 	call	get_word
	pushl	%edi
	pushl	%ebx
	call	strlen
	addl	%eax, %ebx
	cmpb	$0x0d, -1(%ebx)
	jnz	1f
	movb	$0x00, -1(%ebx)

1:
	call	get_reactor
	popl	%edi
	orl	%eax, %eax
	jz	no_bot_reactor
	call	*%eax			/* edi - parameters */

no_bot_reactor:
	popl	reactor_table

privmsg_end:
	ret

/*
  PN:  gethostbyname
  IN:  esi - ASCIIZ domain name
  OUT: eax - IP address of domain name, or zero if given domain name not exists
  ACT: resolves domain name
*/
gethostbyname:
	pushl	%esi
	incl	%esi

next_hostname_byte:
	lodsb
	cmpb	$'.', %al
	jz	end_of_label
	orb	%al, %al
	jz	ending_label
	jmp	next_hostname_byte

end_of_label:
	popl	%eax
	movl	%eax, %edi
	subl	%esi, %eax
	neg	%eax
	decb	%al
	decb	%al
	movb	%al, (%edi)
	decl	%esi
	pushl	%esi
	incl	%esi
	jmp	next_hostname_byte

ending_label:
	popl	%eax
	movl	%eax, %edi
	subl	%esi, %eax
	neg	%eax
	decb	%al
	decb	%al
	movb	%al, (%edi)

	/* socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ) */
	SYSCALL( $97, $2, $2, $0 )
	cmpl	$-1, %eax
	jz	programm_end
	movl	%eax, dns_sock
	
	/* fill out sockaddr_in fields */
	movb	$16, sin_len
	movb	$2, sin_family
	movw	port, %ax
	movw	%ax, sin_port
	movl	dns_server, %eax
	movl	%eax, sin_addr

	/* connect( dns_sock, sockaddr_in, 16 ) */
	SYSCALL( $98, dns_sock, $sockaddr_in, $16 )
	orl	%eax, %eax
	jnz	dns_error
	
	/* fill out DNS query fields */
	movw	$0x0100, ID
	movw	$0x0001, Flags
	movw	$0x0100, QDCOUNT             # number of queries
	movw	$0x0000, ANCOUNT             # number of answers
	movw	$0x0000, NSCOUNT             #
	movw	$0x0000, ARCOUNT             #
	movl	$ibuf, %esi
	movl	$query, %edi
	pushl	%esi
	call	strlen
	incl	%eax
	movl	%eax, %ecx
	rep movsb
	movl	$0x01000100, %eax       # QTYPE ( = A ) and QCLASS ( = IN )
	stosl
	subl	$dns_query, %edi
	
	/* write( dns_sock, dns_query, edi ) */
	SYSCALL( $4, dns_sock, $dns_query, %edi )
	cmpl	%eax, %edi
	jnz	dns_error
	
	/* recv( dns_sock, dns_query, 512, 0, 0, 0 ) */
	SYSCALL( $29, dns_sock, $dns_query, $512, $0, $0, $0 )
	cmpl	$-1, %eax
	jz	dns_error
	
	xorl	%ecx, %ecx
	movl	%ecx, %eax
	movw	QDCOUNT, %cx
	xchg	%cl, %ch
	movl	$query, %esi
	jecxz	no_queries

next_label:
	lodsb
	orb	%al, %al
	jz	next_query
	addl	%eax, %esi
	jmp	next_label

next_query:
	addl	$4, %esi
	loop	next_label
	
no_queries:
	/* assume that name is the same	or this ip has more than one names */
	movw	ANCOUNT, %cx
	xchg	%cl, %ch
	jecxz	dns_error
	lodsw
	test	$0xc0, %al
	jnz	compressed_name

	decl	%esi
	decl	%esi

next_label_answer:
	lodsb
	orb	%al, %al
	jz	compressed_name
	test	$0xc0, %al
	jnz	compressed_name_pre
	addl	%eax, %esi
	jmp	next_label_answer

compressed_name_pre:
	incl	%esi

compressed_name:
	lodsw
	cmpw	$0x0100, %ax	/* type - A */
	jnz	dns_error
	lodsw
	cmpw	$0x0100, %ax    /* class - IN */
	jnz	dns_error

	addl	$6, %esi
	lodsl
	jmp	close_socket

dns_error:
	xorl	%eax, %eax

close_socket:
	/* close( dns_sock ) */
	SYSCALL( $6, dns_sock )

programm_end:
	ret

/*
 PN:  get_word
 IN:  esi - pointer to word
      edi - where store word
 OUT: none
 ACT: copies bytes from string, pointed by esi to memory, pointed by edi, until
      newline or space. words are null-terminated
*/
get_word:
	cmpb	$0x0a, (%esi)
	jz	get_word_end
	cmpb	$0x20, (%esi)
	jz	get_word_end
	movsb
	jmp	get_word
	
get_word_end:
	movb	$0x00, (%edi)
	ret

/*
 PN:  skip_spaces
 IN:  edi - pointer to <name> in string, formmatted <name>=<value>
 OUT: edi - pointer to <value>
 ACT: skips space
*/
skip_spaces:
	movb	$'=', %al
	repnz scasb
	movb	$0x20, %al
	cmpb	%al, (%edi)
	jnz	skip_spaces_end
	repz scasb
	
skip_spaces_end:
	ret

/*
 PN:  read_config
 IN:  file `mybot.conf'
 OUT: sin_addr     - address of irc server
      sin_port     - port of irc server
      channel_name - name of channel to join
      logfd        - descriptor of logfile
      users_table  - table of users
 ACT: reads parameters from configuration file and validates them
*/
read_config:
	/* open(conf_file, O_RDONLY, 0644) */
	SYSCALL($5, $conf_file, $0x000, $0644)
	cmpl	$-1, %eax
	jz	no_config_file
	movl	%eax, conffd
	
	/* get file size, and map file to memory to read it */
	/* fstat( eax, ibuf ) */
	SYSCALL( $189, %eax, $ibuf )
	orl	%eax, %eax
	jnz	close_config
	movl	(ibuf+48), %eax		/* stat.st_size - size in bytes */
	movl	%eax, conf_len
	movl	%eax, %ecx

	/* mmap( 0, conf_len, PROT_READ, 0, conffd, 0 ) */
	SYSCALL( $197, $0, conf_len, $1, $0, conffd, $0, $0, $0 )
	cmpl	$-1, %eax
	jz	close_config
	
	movl	%eax, conf_mem
	movl	%eax, %esi

next_conf_str:
	lodsb
	/* here read common config strings */
	cmpb	$'S', %al
	jz	store_server
	cmpb	$'P', %al
	jz	store_port
	cmpb	$'C', %al
	jz	store_channel
	cmpb	$'L', %al
	jz	store_log

	cmpb	$'[', %al
	jz	read_user_table
	cmpb	$'.', %al
	jz	unmap_conf_mem

skip_line:
	movb	$0x0a, %al
	repnz	scasb
	jecxz	unmap_conf_mem
	movl	%edi, %esi
	jmp	next_conf_str
	
store_server:
	movl	%esi, %edi
	call	skip_spaces
	movl	%edi, %esi
	movl	$server+1, %edi
	call	get_word
	movl	%esi, %edi
	
	pushal
	movl	$server, %esi
	call	gethostbyname
	orl	%eax, %eax
	jnz	got_server
	movl	$0x411036c3, %eax       /* 195.54.16.65 - irc.chelyabinsk.ru */
	
got_server:
	movl	%eax, sin_addr
	popal
	
	jmp	skip_line
	
store_port:
	movl	%esi, %edi
	call	skip_spaces
	movl	%edi, %esi
	movl	$ibuf, %edi
	call	get_word

	pushl	%esi
	movl	$ibuf, %esi
	xorl	%eax, %eax
	movl	%eax, %ebx

get_port:
	lodsb
	orb	%al, %al
	jz	got_port
	sub	$0x30, %al	/* convert ASCII code of digit to digit */
	xchg	%eax, %ebx
	movw	$10, %dx
	mul	%dx
	addl	%eax, %ebx
	xorl	%eax, %eax
	jmp	get_port

got_port:
	xchg	%bh, %bl
	movw	%bx, sin_port
	
	popl	%esi
	movl	%esi, %edi
	jmp	skip_line

store_channel:
	movl	%esi, %edi
	call	skip_spaces
	movl	%edi, %esi
	movl	$channel, %edi
	call	get_word
	movw	$0x0a0d, %ax
	stosw
	movl	%esi, %edi
	jmp	skip_line

store_log:
	movl	%esi, %edi
	call	skip_spaces
	movl	%edi, %esi
	movl	$logfile, %edi
	call	get_word
	movl	%esi, %edi
	jmp	skip_line

read_user_table:
	movl	$users_table, %ebx
	movb	$0x0a, %al
	repnz scasb
	movl	%edi, %esi
	movl	%ebx, %edi

read_next_user:
	incl	%edi
	call	get_word
	incl	%edi
	movl	%edi, %edx
	subl	%ebx, %edx
	incb	%dl
	movb	%dl, (%ebx)
	incl	%esi
	lodsb
	subb	$0x30, %al
	stosb
	movl	%edi, %ebx
	incl	%esi
	cmpb	$'.', (%esi)
	jnz	read_next_user
	xorb	%al, %al
	stosb
	
unmap_conf_mem:
	/* munmap( conf_mem, conf_len ) */
	SYSCALL( $73, conf_mem, conf_len )
	
close_config:
	/* close( conf_file ) */
	SYSCALL( $6, conf_file )
	jmp	read_config_end

no_config_file:
	jmp	fatal
	
read_config_end:
	ret

/*
 PN:  process_queue
 IN:  queue - message queue
      nodes_count - queue length
 OUT: queue - clean queue
      nodes_count=0
 ACT: extracts messages from queue, extracts commands from queue, selects
      reactor and calls it, if command hasn't reactor silently drops
*/
process_queue:
	movl	$irc_reactor_table, reactor_table
	/* check if queue is empty */
	movl	nodes_count, %ecx
	or	%ecx, %ecx
	jz	empty_queue
	movl	%ecx, %ebx
	
process_queue_loop:
	/* store loop counter and number of nodes in queue */
	pushl	%ecx
	pushl	%ebx
	
	/* copy node from queue to processing buffer */
	movl	$pbuf, %edi
	movl	queue+4, %esi /* pointer */
	movl	queue, %ecx   /* length */
	rep movsb
	
	/* free memory, which were allocated for queue key */
	SYSCALL( $73, queue+4, queue ) /* unmap memory */
	
	/* check if command can be processed. If it can not be processed it
	   is being silently dropped */
	call	get_prefix_and_command_name
	call	get_reactor
	or	%eax, %eax
	jz	no_reactor
	call	*%eax
	
no_reactor:
	/* delete processed node from queue */
	movl	$queue+8, %esi
	movl	$queue, %edi
	popl	%ebx
	decl	%ebx
	movl	%ebx, %ecx
	shll	$1, %ecx
	rep movsd

	/* check if queue not empty */
	popl	%ecx
	loop	process_queue_loop

	/* now in ebx is zero - so it will increase perfomance ;-) */
	movl	%ebx, nodes_count

empty_queue:
	ret

/*
 PN:  create_queue
 IN:  ibuf   - input buffer
      buflen - size of data in buffer
 OUT: queue  - modified queue
      offset - offset in buffer of 
 ACT: adds new messages from ibuf to queue to proceed and sets off
*/
create_queue:
	/* open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644) */
	SYSCALL($5, $qname, $(0x1 | 0x8 | 0x200), $0644)
	movl	%eax, queue_

	/* prepare to process frame */
	movl	buflen, %ecx
	movl	$ibuf, %edi
	or	%ecx, %ecx
	jz	2f

1:
	/* scan bytes until the end of buffer or end of message */
	movb	$0x0d, %al
	mov	%edi, %esi
	repnz scasb

	cmpb	$0x0a, (%edi)
	jz	4f		/* if last char 0x0a */
	jecxz	3f		/* if all buffer scanned */
	/* ok, esi points to start of message, edi to last char of it.
	   calculating message length, allocating mem for message,
	   creating node, filling length of message and pointer to it there.
	   after all actions edi will point to next char in buffer after
	   this message
	*/

4:	/* calculating */
	movl	%edi, %eax
	subl	%esi, %eax
	incl	%eax

	/* log to file */
	pushl	%eax
	SYSCALL( $4, queue_, %esi, %eax )
	popl	%eax
	
	/* allocating */
	/* mmap( 0x00000000, eax, PROT_READ|PROT_WRITE, MAP_ANON, -1, 0 ) */
	movl	%eax, %edx
	SYSCALL( $197, $0, %eax, $0x03, $0x1000, $-1, $0 )
	cmpl	$-1, %eax
	jz	2f
	
	/* add node to queue:
	   - store pointer and length of key
	   - copy message to allocated memory
	*/
	movl	nodes_count, %ebx
	movl	%eax, queue+4(, %ebx, 8) /* pointer */
	movl	%edx, queue(, %ebx, 8) /* length */
	incl	%ebx
	movl	%ebx, nodes_count
	
	pushl	%ecx
	pushl	%edi
	pushl	%esi
	
	movl	%edx, %ecx
	movl	%eax, %edi
	rep movsb
	
	popl	%esi
	popl	%edi
	popl	%ecx

	/* move to next char in frame and check if we at the end of frame */
	incl	%edi
	decl	%ecx
	jecxz	2f
	
	jmp	1b
	
3:	/* if last message was not recieved entirely copy recieved part to
           start of buffer and remember length of recieved part */
	movl	%edi, %ecx
	subl	%esi, %ecx
	pushl	%ecx
	
	movl	$ibuf, %edi
	rep movsb
	
	popl	%ecx

2:
	movl	%ecx, offset
	SYSCALL( $6, queue_ )	/* close queue log file */
	ret

/*
 PN:  get_reactor
 IN:  command - asciiz string with command
      reactor_table - chain of reactors on commands
 OUT: eax - reactor (servicing procedure)
 ACT: returns entry point of reactor of specified command or zero in eax
*/
get_reactor:
	movl	reactor_table, %edi

	xorl	%eax, %eax

1:	/* restore pointer to command */
	movl	$command, %esi

2:	/* check if next command has reactor */
	cmpsb
	jnz	3f			/* try next command */
	cmpb	$0, -1(%esi)
	jz	5f
	jmp	2b
	
3:	/* if strings (command name and next command in table) are not equal
 	   move pointer to next command */
    	scasb
	jz	4f
	jmp	3b

4:	/* if last command checked */
	cmpl	$0, 4(%edi)
	jz	6f
	addl	$4, %edi
	jmp	1b	

5:	
	mov	(%edi), %eax
	
6:
	ret

/*
 PN:  ping
 IN:  params - pointer to server name
 OUT: obuf   - pong message
 ACT: create pong message - echo to ping from server and sends it
*/
ping:
	/* show `Ping? Pong!' message */
	pushl	$ping_reaction
	call	strlen
	SYSCALL($4, $1, $ping_reaction, %eax)

	/* send pong to server */
	movl	$obuf, %edi
	mov	%edi, %ebx
	movl	$0x474e4f50, %eax
	stosl
	movl	params, %esi

next_server_char_to_pong:
	lodsb
	stosb
	cmpb	$0x0a, %al
	jnz	next_server_char_to_pong
	xorb	%al, %al
	stosb
	
	pushl	%ebx
	call	strlen
	SYSCALL($4, sock, %ebx, %eax);
	ret

/*
 PN:  get_prefix_and_command_name
 IN:  esi         - pointer to message
 OUT: server      - server name
      nick        - nickname, possible with ident and hostname
      irc_command - command name
 ACT: determines kind of prefix: server or clinet, then stores prefix content
      in appropriate memory area (server or nick). then extracts command name.
      extracted strings are null-terminated
*/
get_prefix_and_command_name:
	/* does this message have prefix? */
	mov	$pbuf, %esi
	cmpb	$':', (%esi)
	jnz	no_prefix
	incl	%esi
	
	movb	$0, %al
	movb	%al, server
	movb	%al, nick
	
/* determine type of prefix: server (first will be '.', not '!') or nickname
   (first will be '!', not '.') */
	movl	%esi, %ebx	/* store current position in message */

1:
	movb	(%esi), %al
	cmpb	$'!', %al
	jz	prefix_nick
	cmpb	$'.', %al
	jz	prefix_server
	cmpb	$0x20, %al
	jz	no_prefix
	cmpb	$0x0d, %al
	jz	error_ignore
	incl	%esi
	jmp	1b

	/* put to edi address where store chars until SPACE
	   - if prefix is server then in buffer, named 'server'
	   - if prefix is nickname then in buffer, names 'nick'
	*/
prefix_nick:
	movl	$nick, %edi
	movb	$0, server
	jmp	2f
	
prefix_server:
	movl	$server, %edi
	movb	$0, nick

2:
	mov	%ebx, %esi	/* restore current position in message - start
	                           of prefix */
3:
	movsb
	cmpb	$0x20, (%esi)
	jz	get_command
	jmp	3b
	
	/* null-terminate string */
get_command:
	movb	$0, (%edi)

	/* as in RFC 1459 after prefix always is command name, store it in
	   buffer, named 'command'
	*/
no_prefix:
	cmpb	$0x20, (%esi)
	jnz	4f
	incl	%esi
	jmp	no_prefix
	
4:
	mov	$command, %edi

5:
	movb	(%esi), %al	/* some checks on validity of message */
	cmpb	$0x20, %al
	jz	got_command
	cmpb	$0x0d, %al
	jz	error_ignore
	movsb
	jmp	5b
	
got_command:
	movb	$0, (%edi)
	movl	%esi, params

	/* log prefix (if available) and command */
	/* open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644) */
	SYSCALL($5, $analyzed, $(0x1 | 0x8 | 0x200), $0644)
	movl	%eax, analyzfd
	cmpb	$0, server
	jz	6f
	movl	$server, %edi
	jmp	7f
	
6:
	movl	$nick, %edi

7:	/* show command name */
	pushl	%edi
	call	strlen
	SYSCALL($4, analyzfd, %edi, %eax)
	SYSCALL($4, analyzfd, $newline, $1)

	SYSCALL($6, analyzfd)	/* close log file */

error_ignore:
	ret

strlen:			/* return string len. string ptr is pushed to stack */
	pushl	%edi
	movl	8(%esp), %edi
	xorl	%eax, %eax
1:
	cmpb	$0, (%edi, %eax)
	jz	2f
	incl	%eax
	jmp	1b
2:
	popl	%edi
	ret	$4
	
fatal:
	SYSCALL($6, $logfd)
	pushl	%edx
	movl	8(%esp), %edx
	pushl	%edx	
	call	strlen
	SYSCALL($4, $1, %edx, %eax)
	SYSCALL($1, $1)	
	popl	%edx
	ret
	

/*
 * create socket, and connect it to a server. socket stored in the
 * global variable sock
 */
mksocket:
	SYSCALL($97, $2, $1, $0)	/* socket(PF_INET, SOCK_STREAM, 0) */
	movl	%eax, sock
	/* setsockopt(TCP_NODELAY) */
	SYSCALL($105, sock, $0xffff, $0x1, $tcp_nodelay, $4)
	SYSCALL($98, sock, $sin_len, $16)	/* connect */
	ret

initlog:
	/* open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644) */
	SYSCALL($5, $logfile, $(0x1 | 0x8 | 0x200), $0644)
	movl	%eax, logfd
	ret

delay:
	/* nanosleep() */
	SYSCALL($240, $tv_sec, $0x0)
	ret

login:
	pushl	$cmd_user
	call	strlen
	SYSCALL($4, sock, $cmd_user, %eax);
	call	delay
	pushl	$cmd_nick
	call	strlen
	SYSCALL($4, sock, $cmd_nick, %eax);
	call	delay
	pushl	$cmd_join
	call	strlen
	SYSCALL($4, sock, $cmd_join, %eax);
	call	delay
	ret

loop:
2:
	/* here remember that last message from previous recieved frame might
	   not be fit there entirely. So we copied recieved part to start and
	   stored its length, now recieve next portion of message */
	movl	offset, %eax
	movl	$2048, %ebx
	subl	%eax, %ebx
	addl	$ibuf, %eax
	/* recv(sock, ibuf, 20480, 0x00, 0x00, 0x00) */
	SYSCALL($29, sock, %eax, %ebx, $0x00, $0x00, $0x00)
	cmpl	$0, %eax
	jg	1f
	pushl	$fatalstr
	call	fatal
	jmp	2b

/* analyze inconig data */
1:
	/* show packet contents */	
#	pushl	%eax
#	SYSCALL( $4, $1, $delim, $12 ); /* delimit */
#	popl	%eax
#
	pushl	%eax
	SYSCALL($4, logfd, $ibuf, %eax)	/* write to logfile */
	popl	%eax

	pushl	%eax
	SYSCALL( $4, logfd, $delim, $12 ); /* delimit */
	popl	%eax

#	pushl	%eax
#	SYSCALL( $4, $1, $ibuf, %eax ) /* write to stdin */
#	popl	%eax

	addl	offset, %eax
	movl	%eax, buflen
	call	create_queue		/* create queue from recieved data */
	call	process_queue		/* process it */
#	/* is it a server PING ? */
#	cmpl	$0x474e4950, ibuf
#	jne	3f
#	/* yes, send a response */
#	pushl	$cmd_pong
#	call	strlen
#	SYSCALL($4, sock, $cmd_pong, %eax);
	jmp	2b

_start:
	popl	%eax
	popl	%eax
	decl	%eax		/* argc == 1 ? */
	jz	1f
	
	/* TODO: handle command-line arguments */
	
1:
	call	read_config
	call	initlog
	call	mksocket
	call	login
	call	loop

