/* * 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: */ irc_reactor_table: .asciz "PING" .long ping .asciz "PRIVMSG" .long privmsg .long 0 /* terminates reactors chain */ /* table of handlers of bot commands */ /* row structure: */ 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 in string, formmatted = OUT: edi - pointer to 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