1 /* 2 * bot.c version 0.333 3 * all versions previous to 0.333 are completely public domain 4 * all contributed code falls under the same BSD style license as 5 * noted below unless the contributing author places a copyright 6 * notice in their file/s. 7 */ 8 9 10 /* 11 * Copyright (c) 2001 David T. Stiles 12 * All rights reserved. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 3. All advertising materials mentioning features or use of this software 23 * must display the following acknowledgement: 24 * This product includes software developed by David T. Stiles 25 * 4. The name David T. Stiles may not be used to endorse or promote 26 * products derived from this software without specific prior written 27 * permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY DAVID T. STILES `AS IS'' AND ANY 30 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 32 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 39 * SUCH DAMAGE. 40 */ 41 42 /* this code would not be possible without the patience and intelligence 43 * provided by many of the people from #c/efnet. I thank all of you sincerely. 44 */ 45 46 /* 47 * a client/bot irc thingy. 1 dec 98 48 * This #c project is officially named "Sahib" on 3 February 1999 49 * serious modification/cleaning/extending proceeding on 1 September 2000 50 * merging Aegis' code starting sometime in October of 2000. 51 * ripped out the database and turned it into a separate server 13 Oct. 2000 52 * checking comments on 31 jan 2001 53 * radical restructuring of all source code starting on 4 june 2001 54 */ 55 56 #include "bot.h" 57 #include "users.h" 58 #include "calcdb.h" 59 #include "dcalc.h" 60 #include "rpn.h" 61 62 63 /* yeah, i know that globals are considered evil. byte me. */ 64 65 /* config file variables. declared in the order that they are read, to facilitate 66 * config file re/creation. PORT and MAXCALCS have defaults that are 67 * overwritten by the contents of the config file 68 */ 69 70 static char SERVER[MAXDATASIZE]; /* name of the irc server to connect with */ 71 static int PORT = 6667; /* default to the standard irc server port */ 72 static char NICK1[MAXDATASIZE]; /* preferred nickname to use */ 73 static char NICK2[MAXDATASIZE]; /* alternate nickname */ 74 static char USER[MAXDATASIZE]; /* the USER irc protocol message */ 75 static int MAXCALCS = 5000; /* make the default max database size conservative */ 76 static char CALCDB[MAXDATASIZE]; /* name of, and possibly path to, the calc database file */ 77 78 79 80 /* other misc local globals that are needed. i fail to see any non-hacked way 81 * way to get rid of these. 82 */ 83 84 static int sockfd; /* used for the socket file descriptor */ 85 static char BOTNAME[MAXDATASIZE]; /* holds the actual nickname in use by the bot */ 86 static struct message cur_msg; /* defined in bot.h, holds a parsed irc message */ 87 88 89 /* this variable is extern'ed to ALL modules needing to send their own irc messages */ 90 91 char MSGTO[MAXDATASIZE]; /* set to nick/channel on each incoming message */ 92 93 94 /******************************----BEGIN CODE----*********************************/ 95 96 97 98 /* return if there are network difficulties, otherwise, this is where the main purpose 99 * of this program really begins. an endless loop around select(). 100 * select on stdin and the socket. 101 */ 102 103 void main_loop( void ) 104 { 105 int whatever; 106 fd_set fdgroup; 107 struct timeval tv; 108 109 for( ;; ) 110 { 111 FD_ZERO(&fdgroup); 112 FD_SET(STDIN_FILENO, &fdgroup); 113 FD_SET(sockfd, &fdgroup); 114 tv.tv_sec = 360; /* if no data for 6 minutes, something is wrong. */ 115 tv.tv_usec = 0; 116 117 whatever = select( (sockfd + 1) , &fdgroup, NULL, NULL, &tv); 118 119 if( !whatever ) break; /* we must not be connected anymore, return. */ 120 if( FD_ISSET( sockfd, &fdgroup ) ) if( process_in( ) ) break; 121 if( FD_ISSET( STDIN_FILENO, &fdgroup ) ) if( process_out( ) ) break; 122 123 } 124 125 return; 126 } 127 128 129 130 /* you can pretend you have a console through this code. '/' will allow raw irc protocol out. 131 * btw, there are no real sanity checks on this function. the person at the console is supposed 132 * to be not malicious. fgets should prevent accidental buffer overruns though. :) 133 */ 134 135 int process_out( void ) 136 { 137 char ray[MAXDATASIZE], tmp[MAXDATASIZE]; 138 139 fgets( tmp, MAXDATASIZE - 1, stdin ); 140 if( tmp[0] == '/' ) strncpy( ray, (tmp + 1), MAXDATASIZE ); 141 else snprintf( ray, MAXDATASIZE, "PRIVMSG %s :%s", DEF_CHAN, tmp ); 142 send_irc_message( ray ); 143 144 return 0; 145 } 146 147 148 149 /* ugh. this one is impossible to comment on. what it does is make sure that only 150 * complete lines are processed. It uses a static buffer to hold the incomplete 151 * information until the next packet comes in. X is the placeholder index. 152 * marked High Priority for recoding. this code sucks badly... even though it works 153 * this hurts my eyes every time i see it. 154 */ 155 156 int process_in( void ) 157 { 158 char buf[MAXDATASIZE], *ptr; 159 int numbytes = 0; 160 int z = 0; 161 int y = 0; 162 static int x = 0; 163 static char tmpbuf[MAXDATASIZE]; 164 165 if( (numbytes = recv( sockfd, buf, MAXDATASIZE, 0)) == -1) 166 { 167 perror("recv"); 168 return 1; 169 } 170 171 if( !numbytes ) return 1; 172 if( numbytes < MAXDATASIZE ) buf[numbytes] = '\0'; 173 buf[MAXDATASIZE - 1] = '\0'; 174 175 ptr = buf; 176 177 for( y = 0; y < numbytes; y++) 178 { 179 while( *(ptr + y) != '\n' ) 180 { 181 tmpbuf[x] = *( ptr + y ); 182 x+=1; y+=1; 183 if( x == MAXDATASIZE ) x--; 184 if( *(ptr + y) == '\0' ) break; 185 } 186 tmpbuf[x] = '\0'; 187 if( *(ptr + y) == '\n' ) 188 { 189 x = 0; 190 parse_incoming( tmpbuf ); 191 for( z = 0; z < MAXDATASIZE; z++ ) tmpbuf[z] = '\0'; 192 } 193 } 194 195 196 return 0; 197 } 198 199 200 201 /* full ircd messages come through here for parsing. 202 * this form of parsing is based around the "typical" irc message where someone 203 * actually says something. if it is some other type of message, the parser tries 204 * to copy into msgarg1 etc but will fall through when the arguments don't fall 205 * neatly into place. 206 */ 207 208 void parse_incoming( char *ptr ) 209 { 210 int position = 0; 211 212 clean_message( ptr ); 213 214 if( ptr[0] == 'P' ) { /* this is will catch server PINGs */ 215 reply_ping( ptr ); 216 return; /* no need to continue parsing */ 217 } 218 219 memset( &cur_msg, 0, sizeof(cur_msg) ); /* clear the buffer... i want a safe default */ 220 221 /* do an initial breakup of the message based on whitespace */ 222 223 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.userline, 1, ' ' ); /* 1 to skip the leading colon */ 224 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.msgtype, position, ' ' ); 225 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.msgto, position, ' ' ); 226 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.fulltext, position + 1, '\n' ); /* + 1 to skip the colon */ 227 228 /*break down the host/nick type stuff */ 229 230 position = chop( ptr, cur_msg.nick, 1, '!' ); 231 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.logname, position, '@' ); 232 if( *(ptr + position) != '\0' ) position = chop( ptr, cur_msg.hostname, position, ' ' ); 233 234 /* now that the protocol crap is gone, let's parse what the person said. */ 235 if( (cur_msg.msgtype[0] == 'P') || (cur_msg.msgtype[0] == 'N') ) { /*PRIVMSG or NOTICE, NICK triggers too though */ 236 position = chop( cur_msg.fulltext, cur_msg.msgarg1, 0, ' ' ); 237 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg2, position, ' ' ); 238 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg3, position, ' ' ); 239 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg4, position, ' ' ); 240 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg5, position, ' ' ); 241 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg6, position, ' ' ); 242 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg7, position, ' ' ); 243 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg8, position, ' ' ); 244 if( *(ptr + position) != '\0' ) position = chop( cur_msg.fulltext, cur_msg.msgarg9, position, ' ' ); 245 } 246 247 /* console output with simple formatting */ 248 if( !strncasecmp( cur_msg.msgtype, "PRIVMSG", MAXDATASIZE ) ) printf( "|%s| %s: %s\n", cur_msg.msgto, cur_msg.nick, cur_msg.fulltext ); 249 else puts( ptr ); 250 make_a_decision(); 251 return; 252 } 253 254 255 256 /* after the message from the ircd has been parsed, i need to decide what course of 257 * action to take based upon that input. this is where events are triggered. 258 */ 259 260 void make_a_decision( void ) 261 { 262 263 /* don't let it talk to itself, so make sure this is first in the list. */ 264 if( !strncasecmp( BOTNAME, cur_msg.nick, MAXDATASIZE ) ) return; 265 266 /* this sets msgto to the msg sender's nick if it was a privmsg to the bot itself.*/ 267 /* otherwise it makes it possible to talk to the channel that sent the message. */ 268 if( !strcmp( cur_msg.msgto, BOTNAME ) ) strncpy( MSGTO, cur_msg.nick, MAXDATASIZE ); 269 else strncpy( MSGTO, cur_msg.msgto, MAXDATASIZE ); 270 271 /* switch on the first character of the first "word" in the message */ 272 switch( tolower(cur_msg.msgarg1[0]) ) { 273 case 1: 274 if( !strncasecmp( BOTNAME, cur_msg.msgto, MAXDATASIZE ) ) { do_ctcp(); return; } 275 case 'c': 276 if( !strncasecmp( "chpass", cur_msg.msgarg1, MAXDATASIZE ) ) { chpass_stub(); return; } 277 if( !strncasecmp( "calc", cur_msg.msgarg1, MAXDATASIZE ) ) { docalc_stub(); return; } 278 if( !strncasecmp( "clac", cur_msg.msgarg1, MAXDATASIZE ) ) { docalc_stub(); return; } 279 case 'o': 280 if( !strncasecmp( "op", cur_msg.msgarg1, MAXDATASIZE ) ) { oppeople_stub(); return; } 281 if( !strncasecmp( "owncalc", cur_msg.msgarg1, MAXDATASIZE ) ) { owncalc_stub(); return; } 282 case 'w': 283 if( !strncasecmp( "whois", cur_msg.msgarg1, MAXDATASIZE ) ) { whois_stub(); return; } 284 case 'a': 285 if( !strncasecmp( "adduser", cur_msg.msgarg1, MAXDATASIZE ) ) { adduser_stub(); return; } 286 case 'h': 287 if( !strncasecmp( "help", cur_msg.msgarg1, MAXDATASIZE ) ) { help(); return; } 288 case 'r': 289 if( !strncasecmp( "rmuser", cur_msg.msgarg1, MAXDATASIZE ) ) { rmuser_stub(); return; } 290 if( !strncasecmp( "rmcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { rmcalc_stub(); return; } 291 if( !strncasecmp( "rawirc", cur_msg.msgarg1, MAXDATASIZE ) ) { rawirc(); return; } 292 if( !strncasecmp( "rcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { rpn_stub(); return; } 293 case 'm': 294 if( !strncasecmp( "mkcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { mkcalc_stub(); return; } 295 case 'l': 296 if( !strncasecmp( "login", cur_msg.msgarg1, MAXDATASIZE ) ) { help(); return; } 297 if( !strncasecmp( "listcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { listcalc_stub(); return; } 298 case 'x': 299 if( !strncasecmp( "xpln", cur_msg.msgarg1, MAXDATASIZE ) ) { docalc_stub(); return; } 300 case 'd': 301 if( !strncasecmp( "dcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { dcalc_stub(); return; } 302 case 's': 303 if( !strncasecmp( "searchcalc", cur_msg.msgarg1, MAXDATASIZE ) ) { searchcalc_stub(); return; } 304 default: 305 break; 306 } 307 308 return; 309 } 310 311 312 313 /* how to comment? this function is almost pointless as is. its purpose will become 314 * clear later... after it is fully implemented. all it does now is ensure that a 315 * newline char is sent after each message and provide a wrapper for the network I/O. 316 * eventually, the bot will monitor its own output here. 317 */ 318 319 void send_irc_message( char *sndmsg ) 320 { 321 char newline = '\n'; 322 323 if( send (sockfd, sndmsg, strlen( sndmsg ), 0) == -1) perror("send"); 324 if( send (sockfd, &newline, 1, 0) == -1) perror("send"); 325 sleep( 1 ); /* pause for a second to help keep things from getting too crazy. */ 326 return; 327 } 328 329 330 /*******************************-----begin stubs-----************************************/ 331 332 333 /* chpass stub finished */ 334 335 void chpass_stub() 336 { 337 chpass( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4 ); 338 return; 339 } 340 341 342 /* docalc stub finished */ 343 344 void docalc_stub() 345 { 346 docalc( cur_msg.msgarg2 ); 347 return; 348 } 349 350 351 /* op_people stub finished */ 352 353 void oppeople_stub() 354 { 355 /* making getting ops easier for other than the default channel */ 356 if( (cur_msg.msgarg2[0] == '#') || (cur_msg.msgarg2[0] == '&') ) { 357 if( cur_msg.msgarg4[0] ) oppeople( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4, cur_msg.nick ); 358 else oppeople( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.nick, cur_msg.nick ); 359 return; 360 } 361 /* op only in the default channel */ 362 if( cur_msg.msgarg3[0] ) oppeople( DEF_CHAN, cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.nick ); 363 else oppeople( DEF_CHAN, cur_msg.msgarg2, cur_msg.nick, cur_msg.nick ); 364 return; 365 } 366 367 368 /* owncalc stub finished */ 369 370 void owncalc_stub() 371 { 372 owncalc( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.nick ); 373 return; 374 } 375 376 377 /* whois stub finished */ 378 379 void whois_stub() 380 { 381 whois( cur_msg.msgarg2 ); 382 return; 383 } 384 385 386 /* adduser stub finished */ 387 388 void adduser_stub() 389 { 390 adduser( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4, cur_msg.msgarg5 ); 391 return; 392 } 393 394 395 /* rmuser stub finished */ 396 397 void rmuser_stub() 398 { 399 rmuser( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4 ); 400 return; 401 } 402 403 404 /* rmcalc stub finished */ 405 406 void rmcalc_stub() 407 { 408 rmcalc( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4 ); 409 return; 410 } 411 412 413 /* 414 * i parse cur_msg.fulltext to step over command arguments and login info. 415 * the actual calc data can obviously have spaces, so the original 416 * parsing of the irc message is insufficient. note that 'newcalctext' gets 417 * overwritten by each call to chop(). after the last call, it holds the calc data. 418 */ 419 420 void mkcalc_stub() 421 { 422 int y; 423 char newcalctext[MAXDATASIZE]; 424 425 y = chop( cur_msg.fulltext, newcalctext, 0, ' ' ); 426 y = chop( cur_msg.fulltext, newcalctext, y, ' ' ); 427 y = chop( cur_msg.fulltext, newcalctext, y, ' ' ); 428 y = chop( cur_msg.fulltext, newcalctext, y, ' ' ); 429 y = chop( cur_msg.fulltext, newcalctext, y, '\0' ); 430 431 mkcalc( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.msgarg4, newcalctext ); 432 return; 433 } 434 435 436 /* listcalc stub finished */ 437 438 void listcalc_stub() 439 { 440 listcalc( cur_msg.msgarg2, cur_msg.msgarg3, cur_msg.nick ); 441 return; 442 } 443 444 445 /* searchcalc stub */ 446 447 void searchcalc_stub() 448 { 449 searchcalc( cur_msg.msgarg2, cur_msg.msgarg3 ); 450 return; 451 } 452 453 454 /*stub for couts RPN calculator */ 455 456 int rpn_stub( void ) 457 { 458 int x = 0; 459 char tmpray[MAXDATASIZE], answer[MAXDATASIZE]; 460 461 rpn_calc( cur_msg.fulltext + (strlen( cur_msg.msgarg1 ) + 1), answer, (MAXDATASIZE - 2) ); 462 463 if( x != RPN_OK ) snprintf( tmpray, MAXDATASIZE, "privmsg %s :error: %s", MSGTO, answer ); 464 else snprintf( tmpray, MAXDATASIZE, "privmsg %s :cout says: %s", MSGTO, answer ); 465 466 send_irc_message( tmpray ); 467 468 return 0; 469 } 470 471 472 473 /* stub for demoncrat's "normal" calculator code */ 474 475 int dcalc_stub( void ) 476 { 477 char tmpray[MAXDATASIZE]; 478 Value v; 479 const char *plaint; 480 481 482 plaint = dcalc(&v, cur_msg.fulltext + (strlen( cur_msg.msgarg1 ) + 1) ); 483 484 if( plaint ) snprintf( tmpray, MAXDATASIZE, "privmsg %s :answer: %s", MSGTO, plaint); 485 else snprintf( tmpray, MAXDATASIZE, "privmsg %s :answer: %.16g", MSGTO, v); 486 487 send_irc_message( tmpray ); 488 489 return 0; 490 } 491 492 493 /********************************-----end stubs-----*************************************/ 494 495 496 497 /* repsond only to private requests for help. the actual messages are #defined in bot.h 498 * i should make these loadable from a file rather than compiled in. 499 * erm... why don't i make this a switch() like i did in make_a_decision()? 500 */ 501 502 void help( void ) 503 { 504 char tmpray[MAXDATASIZE]; 505 506 if( strncasecmp( cur_msg.msgto, BOTNAME, MAXDATASIZE ) ) return; 507 508 if( !strncasecmp( cur_msg.msgarg2, "commands", MAXDATASIZE ) ) { 509 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, COMMANDS ); 510 send_irc_message( tmpray ); 511 return; 512 } 513 if( !strncasecmp( cur_msg.msgarg2, "syntax", MAXDATASIZE ) ) { 514 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, SYNTAX ); 515 send_irc_message( tmpray ); 516 return; 517 } 518 if( !strncasecmp( cur_msg.msgarg2, "adduser", MAXDATASIZE ) ) { 519 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, ADDUSER ); 520 send_irc_message( tmpray ); 521 return; 522 } 523 if( !strncasecmp( cur_msg.msgarg2, "rmuser", MAXDATASIZE ) ) { 524 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, RMUSER ); 525 send_irc_message( tmpray ); 526 return; 527 } 528 if( !strncasecmp( cur_msg.msgarg2, "op", MAXDATASIZE ) ) { 529 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, OP ); 530 send_irc_message( tmpray ); 531 return; 532 } 533 if( !strncasecmp( cur_msg.msgarg2, "chpass", MAXDATASIZE ) ) { 534 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, CHPASS ); 535 send_irc_message( tmpray ); 536 return; 537 } 538 if( !strncasecmp( cur_msg.msgarg2, "rmcalc", MAXDATASIZE ) ) { 539 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, RMCALC ); 540 send_irc_message( tmpray ); 541 return; 542 } 543 if( !strncasecmp( cur_msg.msgarg2, "owncalc", MAXDATASIZE ) ) { 544 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, OWNCALC ); 545 send_irc_message( tmpray ); 546 return; 547 } 548 if( !strncasecmp( cur_msg.msgarg2, "mkcalc", MAXDATASIZE ) ) { 549 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, MKCALC ); 550 send_irc_message( tmpray ); 551 return; 552 } 553 if( !strncasecmp( cur_msg.msgarg2, "rawirc", MAXDATASIZE ) ) { 554 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, RAWIRC ); 555 send_irc_message( tmpray ); 556 return; 557 } 558 if( !strncasecmp( cur_msg.msgarg2, "whois", MAXDATASIZE ) ) { 559 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, WHOIS ); 560 send_irc_message( tmpray ); 561 return; 562 } 563 if( !strncasecmp( cur_msg.msgarg2, "listcalc", MAXDATASIZE ) ) { 564 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, LISTCALC ); 565 send_irc_message( tmpray ); 566 return; 567 } 568 if( !strncasecmp( cur_msg.msgarg2, "searchcalc", MAXDATASIZE ) ) { 569 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, SEARCHCALC ); 570 send_irc_message( tmpray ); 571 return; 572 } 573 if( !strncasecmp( cur_msg.msgarg2, "chattr", MAXDATASIZE ) ) { 574 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, CHATTR ); 575 send_irc_message( tmpray ); 576 return; 577 } 578 579 580 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :%s", cur_msg.nick, HELPHELP ); 581 send_irc_message( tmpray ); 582 583 return; 584 } 585 586 587 588 /* CTCPs are accomplished through 'NOTICE' messages. the first letter of each 589 * important CTCP is unique so i merely switch on that value. the default is 590 * to merely echo back the same type of NOTICE but without arguments. (this covers ping) 591 * VERSION is the only CTCP that i care about currently. 592 */ 593 594 void do_ctcp( void ) 595 { 596 char ray[MAXDATASIZE]; 597 598 switch( cur_msg.msgarg1[1] ) { 599 case 'V': 600 snprintf( ray, MAXDATASIZE, "NOTICE %s :\001VERSION ircII.5 not quite all there yet.\001", cur_msg.nick ); 601 break; 602 default: 603 snprintf( ray, MAXDATASIZE, "NOTICE %s :%s\001", cur_msg.nick, cur_msg.msgarg1 ); 604 break; 605 } 606 607 send_irc_message( ray ); 608 return; 609 } 610 611 612 613 /* this function validates user/pass and then sends the rest of the text to the server. 614 * the code is trivial enough to remain inside of bot.c 615 */ 616 617 void rawirc( void ) 618 { 619 int y; 620 char tmpray[MAXDATASIZE]; 621 622 if( !valid_login( cur_msg.msgarg3, cur_msg.msgarg2 ) ){ 623 snprintf( tmpray, MAXDATASIZE, "PRIVMSG %s :failed login", cur_msg.nick ); 624 send_irc_message( tmpray ); 625 return; 626 } 627 628 /* this is the same parsing technique described in mkcalc_stub() */ 629 y = chop( cur_msg.fulltext, tmpray, 0, ' ' ); 630 y = chop( cur_msg.fulltext, tmpray, y, ' ' ); 631 y = chop( cur_msg.fulltext, tmpray, y, ' ' ); 632 y = chop( cur_msg.fulltext, tmpray, y, '\0' ); 633 634 send_irc_message( tmpray ); 635 636 return; 637 } 638 639 640 641 /* let the ^A chars stay, but no other nonprintable chars... walking through the array. 642 * i really like pointer arithmetic, i apologise to those who find this unreadable. 643 */ 644 645 void clean_message( char *msg ) 646 { 647 register int x; 648 for( x = 0; x < strlen(msg); x++ ) { 649 if( *(msg+x) == 1 ) continue; 650 if( !isprint( *(msg+x) ) ) { 651 if( *(msg+x) == '\r' ) { *(msg+x) = '\0'; continue; } 652 if( *(msg+x) == '\n' ) { *(msg+x) = '\0'; continue; } 653 *(msg+x) = ' '; 654 } 655 } 656 return; 657 } 658 659 660 661 /* the first array is the array you wish to break up nondestructively 662 * the second array will hold the section that is "chopped off". 663 * 'position' is the location in the array you want it to start looking for 'separator'. 664 * the separator character is the token you wish to chop on. e.g. a space character. 665 * this function is intentionally dangerous. it assumes that only proper NULL terminated 666 * arrays will be passed to it. 667 */ 668 669 int chop( char *in, char *out, int position, char separator ) 670 { 671 int x = 0; 672 673 while( *(in + position) != separator ) 674 { 675 *(out + x) = *(in + position); 676 x+=1; position+=1; 677 if( *(in + position) == '\0' ) break; 678 if( *(in + position) == '\r' ) break; 679 if( *(in + position) == '\n' ) break; 680 } 681 682 *(out + x) = '\0'; 683 return (*(in + position)) ? position+1 : position; /* thanks for the simpler 'if' construct Xgc */ 684 } 685 686 687 688 /* the name of the function explains it fairly well. it merely replies to any 689 * PING requests from IRC servers. 690 * ptr holds "PING irc.home.com" so i start copying from the end of "PING " 691 * onwards, which is 5 chars, to get the irc server name for the PONG response. 692 */ 693 694 void reply_ping( char *ptr ) 695 { 696 char str[MAXDATASIZE]; 697 698 snprintf( str, MAXDATASIZE - 5, "PONG %s", (ptr + 5) ); 699 send_irc_message( str ); 700 701 return; 702 } 703 704 705 706 /* hide most of the network stuff here. 707 * pass in a host name, port, and a file descriptor and this function 708 * will take care of the network details. the value it returns should 709 * be assigned to a file descriptor or evaluated for errors. 710 */ 711 712 int host_connect( char *exthost, int extport, int extsockfd ) 713 { 714 struct hostent *he; 715 struct sockaddr_in their_addr; 716 717 if ((he=gethostbyname( exthost )) == NULL) 718 { 719 perror("gethostbyname"); 720 return -1; 721 } 722 723 if ((extsockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 724 { 725 perror("socket"); 726 close( extsockfd ); 727 return -1; 728 } 729 730 their_addr.sin_family = AF_INET; 731 their_addr.sin_port = htons(extport); 732 their_addr.sin_addr = *((struct in_addr *)he->h_addr); 733 memset( &their_addr.sin_zero, '\0', 8 ); 734 735 if (connect(extsockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) 736 { 737 perror("connect"); 738 close( extsockfd ); 739 return -1; 740 } 741 742 743 return extsockfd; 744 745 } 746 747 748 749 /* connect to the irc server and register. 750 * ugly ugly ugly 751 */ 752 753 int irc_connect( void ) 754 { 755 char ray[MAXDATASIZE]; 756 int numbytes = 0; 757 758 sockfd = 0; 759 760 if( (sockfd = host_connect(SERVER, PORT, sockfd)) < 1) return 1; 761 762 strncpy( BOTNAME, NICK1, MAXDATASIZE ); 763 send_irc_message( USER ); 764 765 memset( ray, '\0', sizeof(ray) ); 766 snprintf( ray, MAXDATASIZE, "nick %s", BOTNAME ); 767 send_irc_message( ray ); 768 769 sleep( 3 ); 770 771 numbytes = recv( sockfd, ray, MAXDATASIZE, 0); 772 if( numbytes == -1 ) 773 { 774 perror("recv"); 775 close( sockfd ); 776 return 1; 777 } 778 779 if( strstr( ray, "PING" ) ) { reply_ping( ray ); } 780 if( strstr( ray, "Nickname is already in use" ) ) 781 { 782 strncpy( BOTNAME, NICK2, MAXDATASIZE ); 783 memset( ray, '\0', sizeof(ray) ); 784 snprintf( ray, MAXDATASIZE, "nick %s", BOTNAME ); 785 send_irc_message( ray ); 786 787 if( (numbytes = recv( sockfd, ray, MAXDATASIZE, 0)) == -1) 788 { 789 perror("recv"); 790 close( sockfd ); 791 return 1; 792 } 793 if( strstr( ray, "Nickname is already in use" ) ) { close( sockfd ); return 1; } 794 } 795 796 memset( ray, '\0', sizeof(ray) ); 797 snprintf( ray, MAXDATASIZE, "mode %s +i", BOTNAME ); 798 send_irc_message( ray ); 799 800 memset( ray, '\0', sizeof(ray) ); 801 snprintf( ray, MAXDATASIZE, "join %s", DEF_CHAN ); 802 send_irc_message( ray ); 803 804 send_irc_message( "join #code-poets" ); 805 /* send_irc_message( "join #code-cafe" );*/ 806 return 0; 807 } 808 809 810 811 /* this function loads variables from bot.cfg 812 * into global variables. 813 */ 814 815 int load_cfg( void ) 816 { 817 FILE *fp; 818 char tmp[MAXDATASIZE]; 819 820 fp = fopen( "bot.cfg", "r" ); 821 if( !fp ) return 1; 822 823 puts( "\n--------------- bot.cfg data ---------------\n" ); 824 825 fgets( SERVER, MAXDATASIZE, fp ); 826 if( !SERVER[0] ) { puts( "failed loading server name" ); return 1; } 827 clean_message( SERVER ); 828 printf( "irc server: %s\n", SERVER ); 829 830 fgets( tmp, MAXDATASIZE, fp ); 831 if( !tmp[0] ) { puts( "failed loading PORT line" ); return 1; } 832 PORT = atol( tmp );; 833 printf( "port number: %i\n", PORT ); 834 835 fgets( NICK1, MAXDATASIZE, fp ); 836 if( !NICK1[0] ) { puts( "failed loading nickname" ); return 1; } 837 clean_message( NICK1 ); 838 printf( "primary nickname: %s\n", NICK1 ); 839 840 fgets( NICK2, MAXDATASIZE, fp ); 841 if( !NICK2[0] ) { puts( "failed loading alternate nick" ); return 1; } 842 clean_message( NICK2 ); 843 printf( "alternate nickname: %s\n", NICK2 ); 844 845 fgets( USER, MAXDATASIZE, fp ); 846 if( !USER[0] ) { puts( "failed loading USER line" ); return 1; } 847 clean_message( USER ); 848 printf( "USER dataline: %s\n", USER ); 849 850 fgets( tmp, MAXDATASIZE, fp ); 851 if( !tmp[0] ) { puts( "failed loading MAXCALCS line" ); return 1; } 852 MAXCALCS = atol( tmp );; 853 printf( "maximum db entries: %i\n", MAXCALCS ); 854 855 fgets( CALCDB, MAXDATASIZE, fp ); 856 if( !CALCDB[0] ) { puts( "failed loading CALCDB line" ); return 1; } 857 clean_message( CALCDB ); 858 printf( "calcdb filename: %s\n", CALCDB ); 859 860 puts( "\n--------------- data loaded ---------------\n" ); 861 fclose( fp ); 862 863 return 0; 864 } 865 866 867 868 /* this is only performed once during the entire execution of the program 869 * any failures here are critical and will stop program execution. 870 */ 871 872 int prep( void ) 873 { 874 signal(SIGPIPE, SIG_IGN); 875 signal(SIGFPE, SIG_IGN); 876 srand( time( NULL ) ); 877 if( load_cfg() ) { puts( "failed at end of load_cfg()"); return 10; } 878 if( loadusers( "user.list" ) ) { puts( "failed loading the user.list " ); return 15; } 879 if( loaddb( CALCDB, MAXCALCS ) ) { puts( "failed loading the calc database." ); return 20; } 880 return 0; 881 } 882 883 884 885 /* Only exit if the config files are not found, else endlessly cycle through reconnects */ 886 887 int main(int argc, char *argv[]) 888 { 889 int x = 0; 890 891 892 if( argc ) printf( "%s is loading, please wait...\n\n", argv[0] ); 893 894 if( prep() ) { 895 puts( "preparations failed. do you have a bot.cfg, calcdb.data, and user.list file?" ); 896 return 10; 897 } 898 899 for( ;; ) { 900 printf( "\n\nattempting to connect to %s, please wait...\n\n", SERVER ); 901 x = irc_connect(); 902 sleep( 3 ); /* 3 seconds to keep from hitting the server too much with reconnects */ 903 if( x ) { 904 puts("hm. i could not connect dude."); 905 continue; 906 } 907 main_loop(); 908 close( sockfd ); 909 puts( "disconnected... retrying" ); 910 } 911 } 912 913 914 915 /*************************************----end code----*************************************/ |