| Uutiset | Koodikirjasto | Wiki | Keskustelut | FAQ | Info |
IrkkibottiEnec 26.02.08 17:55 Vähän tällänen ikuisuusprojekti, joka nyt kuitenkin on käytettävässä kunnossa. Listaus komennoista selityksineen:http://cene.ath.cx/scb/commands.html --- Uusin edit: pieni bugifix ainakin unrealircd:lle
/* SCB - Simple C Bot * * An IRC Bot written in C * * Copyright (c) 2007 - 2008, Joeli Hokkanen <cene@fix.fi> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * * Should build on all Linux and BSD systems with GCC. * *********************************************************** * * * REQUIRES LIBCONFUSE FROM http://www.nongnu.org/confuse/ * * Build with gcc -lconfuse * * * *********************************************************** * */ #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <netdb.h> #include <stdlib.h> #include <string.h> #include <malloc.h> #include <stdarg.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> #include <time.h> #include <confuse.h> #define BUFFER_SIZE 1024 #define MAX_ARGS 256 #define MAXLEN 100 #define ERR_FILECREATED 74 #define ERR_NOFILE 73 #define ERR_PARSE 78 #define ERR_CONN 75 #define ERR_SOCKET 70 #define CONFFILE "scb.cfg" #define VERSION 1.1 /* The error message we'll send when someone tried to do something (s)he wasn't allowed to. */ #define NOAUTH sendl(sock, "NOTICE %s :Sorry, you lack the sufficient access to do that or tried to access a command that is available only via private message from a channel.", who); /* Default arguments to send to a IRC command. * These must be the same to all of the commands that * are executed through a ommand in IRC, because they * will all be given the same arguments * through the execcmd function. */ #define CARGS int sock, char *from, char **args, int arg_count /* If something goes wrong or needs attention, these will do it in a standardized format. */ #define derr(msg) fprintf(stderr, "=> Error: %s\n", msg); #define dwarn(msg) fprintf(stderr, "=> Warning: %s\n", msg); #ifdef DEBUG /* Only for debug use, to give additional information to (surprisingly!) help debugging. */ #define dinfo(msg) printf("=> (DEBUG) %s\n", msg); #endif /* Introducing most of our functions.. */ void cmd_quit(CARGS), cmd_nick(CARGS), cmd_abt(CARGS), cmd_catcher(int sig), inslist(char *name, char *desc, void *execcmd), execcmd(char *name, CARGS), cmd_list(CARGS), cmd_kick(CARGS), cmd_op(CARGS), cmd_deop(CARGS), cmd_voice(CARGS), cmd_devoice(CARGS), cmd_mode(CARGS), cmd_topic(CARGS), cmd_join(CARGS), cmd_part(CARGS), cmd_auth(int sock, char* nick), addop(CARGS), addvoice(CARGS), deluser(CARGS), inslist_usr(int lvl, char *name), rehash(CARGS), listusers(CARGS); int conn(int sock, const char *remotehost, unsigned short remote_port), sendl(int sock, const char *format, ...), recvln(int sock, char *line, unsigned int line_size, FILE *log), parse(void); char *strsave(char *word); int i, sock, /* Declares the socket */ own = 0, /* Switch to see if the line came from an owner */ voiced = 0, /* Switch to see if the line came from a voiced person */ tolog = 1, /* Shall we take logs? Turned off via -l command line switch */ tofork = 1, /* Shall we fork? turned off via -f command line switch or defining DEBUG on build */ authed = 0, /* Are we authed yet? */ AOP = 0, /* Shall owners be auto-opped? Turned on from the config file */ AVOICE = 0, /* Shall voices be auto-opped? Turned on from the config file */ IRC_PORT = 0, /* Port of the irc server to be connected to. Changed via the config file or -p switch */ react = 0; /* Shall we react on the line? For security reasons channel operator functions like listops and such * are disabled if we didn't get the order to do it via private message. */ char *IRC_SERVER = NULL, /* These all should be pretty self-explanatory. */ *IRC_NICK = NULL, /* All these values (except for the last one) */ *IRC_REALNAME = NULL, /* Are turned on via the config file. */ *IRC_CHAN = NULL, *AUTH_COMMAND = NULL, *LOGFILE = NULL, *who = NULL; /* This is a variable where we will store the person's nick who said * the given line. */ const char IRC_CMD_TRIGGER = '-'; /* Command trigger. All lines that the bot will react to must * begin with this character. */ FILE *fp, /* Few file switches that are easier to be left global */ *gg; /* Than given to each of those functions that want them. */ /* All the commands are stored in this linked list, * and will be added to it via the * inslist function. */ struct irc_cmd { const char *cmd_name_p, *desc_p; void (*exec_cmd)(CARGS); struct irc_cmd *next; /* Linked list */ }; /* All users will be stored in this one. * New users are added with the * inslist_usr function. */ struct users { char *mask; int lvl; /* user level. 1 = op, 2 = voice */ struct users *next; }; typedef struct users *UPTR; UPTR user = NULL; typedef struct irc_cmd *LISTPTR; LISTPTR cmd = NULL; LISTPTR lalloc (void); /* Lalloc and olalloc will allocate the needed space */ UPTR olalloc(void); /* For inslist() and inslist_usr() */ void execcmd(char *name, CARGS) { /* This is the function that executes the commands * from the irc_cmd linked list. * The functionality is pretty self-explanatory. First we'll * create a new list pointer and scan with it if any * command name matches. If it does we'll execute the command. */ LISTPTR run = cmd; while(run) { if(!strcmp(name, run->cmd_name_p)) { /* Got match */ run->exec_cmd(sock, from, args, arg_count); } run = run->next; } } void inslist (char *name, char *desc, void *execcmd) { /* Inserting new values to the irc_cmd list. * first we'll create yet another list pointer, * then allocate the required space, * save the command name and description * with strsave() and the pointer to the function itself. * Then we'll fix the list to adjust to the newly added stuff. */ LISTPTR new; new = lalloc(); new->cmd_name_p = strsave (name); new->desc_p = strsave (desc); new->exec_cmd = execcmd; new->next = NULL; /* Not necessary, since lalloc does it already */ if(cmd) { LISTPTR run = cmd; while(run->next) run = run->next; run->next = new; } else cmd = new; } void inslist_usr(int lvl, char *name) { /* For explanation look above to the inslist() function. * This does exactly the same, but with different values and to a * different list (the owners and voices). */ UPTR new; new = olalloc(); new->mask = strsave(name); new->lvl = lvl; new->next = NULL; if(user) { UPTR urun = user; while(urun->next) urun = urun->next; urun->next = new; } else user = new; } /* These are the functions to allocate space for new entries in the linked * lists. */ LISTPTR lalloc() { return (LISTPTR) calloc(sizeof(struct irc_cmd), 1); } UPTR olalloc(void) { return (UPTR) calloc(sizeof(struct users), 1); } /* Function to duplicate the given string. */ char *strsave (char *word) { return strdup(word); } int parse(void) { /* Inside this function is the place we need libConfuse and the reason * you need it. * libConfuse is the easiest and most flexible way to scan a config file. */ FILE *qq; if ((gg = fopen(CONFFILE, "r")) == NULL) { /* If we couldn't open the conf file for reading, it means * it probably doesn't exist. */ derr("No config file found!"); derr("Creating " CONFFILE " and exiting. Edit the file to fit your needs and then re-run the bot."); if ((qq = fopen(CONFFILE, "w+")) == NULL) { /* If opening the file for writing failed too, * then there's something wrong that the user must find out first. * Maybe permission denied? */ derr("Cannot create file named " CONFFILE); return ERR_NOFILE; } /* If we could get it open for writing, we'll print a base * for the configuration file * and tell the user to edit it. */ fprintf(qq, "# Configuration file for scb\n" "# All files beginning with # are comments,\n" "# And will be ignored.\n\n" "# IRC server and port to conn to.\n" "server = \"irc.quakenet.org\"\n" "port = 6667\n\n" "# Real name and nick of the bot\n" "nick = \"scb\"\n" "realname = \"Simple C Bot\"\n\n" "# Channel to join to\n" "chan = \"#simplecbot\"\n\n" "# Owner(s) of the bot (A part of the\n" "# hostmasks of the users who should be able to\n" "# execute any command with the bot.\n" "owners = { \"foo!bar@Foo.users.quakenet.org\", \"bar!foo@bar.users.quakenet.org\" }\n\n" "# Voices. Users with limited access to commands (voice, kick, devoice mainly)\n" "# and will be automatically voiced on join.\n" "voices = { \"some!simple@guy\", \"Just!another@user\" }\n\n" "# Should we automatically op owners and voice those on the voices group?\n" "autoop = 1\n" "autovoice = 1\n\n" "# Log file to log to. \n" "logfile = \"scb.log\"\n\n" "# Auth command. Comment to disable.\n" "# The example below provides a correct syntax for\n" "# Quakenet authing, and should be used as a base for any\n" "# Other network too.\n" "authcmd = \"PRIVMSG Q@CServe.quakenet.org :AUTH acc pass\"\n\n\n"); fclose(qq); return ERR_FILECREATED; } fclose(gg); /* We can close the config file for now. */ cfg_opt_t opts[] = { /* Here we'll declare all the * values that are to be parsed from the * config file. */ CFG_SIMPLE_INT("port",&IRC_PORT), CFG_SIMPLE_INT("autoop", &AOP), CFG_SIMPLE_INT("autovoice", &AVOICE), CFG_SIMPLE_STR("server", &IRC_SERVER), CFG_SIMPLE_STR("realname", &IRC_REALNAME), CFG_SIMPLE_STR("nick", &IRC_NICK), CFG_SIMPLE_STR("chan", &IRC_CHAN), CFG_STR_LIST("owners", 0, CFGF_NODEFAULT), CFG_STR_LIST("voices", 0, CFGF_NODEFAULT), CFG_SIMPLE_STR("logfile", &LOGFILE), CFG_SIMPLE_STR("authcmd", &AUTH_COMMAND), CFG_END() }; #ifdef DEBUG dinfo("set cfg_opt_t opts[]"); #endif cfg_t *cfg; cfg = cfg_init(opts, CFGF_NONE); #ifdef DEBUG dinfo("Starting cfg_parse()"); #endif /* This is where the actual parsing and inserting the values * to variables happen. */ if (cfg_parse(cfg, ("%s", CONFFILE)) == CFG_PARSE_ERROR) { /* ("%s", CONFFILE) is required here, otherwise strange * things will happen. Ignore the warning gcc gives with -Wall. */ derr("Config parse error!"); /* Hmm, something went wrong. I haven't faced a single situation which * yielded CFG_PARSE_ERROR though. But according to the manual of * libConfuse it can happen. Maybe a syntax error in the config file? */ return ERR_PARSE; } #ifdef DEBUG /* I had some trouble with this part and rehash(). * I still do. * Doesn't hurt to check things out. */ dinfo("Parsing owners"); dinfo("======================"); dinfo("testing cfg_size()"); printf("%i\n", cfg_size(cfg, "owners")); dinfo("done"); dinfo("testin cfg_getnstr()"); printf("%s\n", cfg_getnstr(cfg, "owners", 0)); dinfo("All done"); #endif /* Here we'll insert the owners to the linked list. * cfg_size gives the total amount of owners in the list, * and cfg_getnstr gives i:th the owner from the list */ for(i = 0; i < cfg_size(cfg, "owners"); i++) inslist_usr(1, (cfg_getnstr(cfg, "owners", i))); #ifdef DEBUG dinfo("Parsing voices"); #endif /* Same thing for voices. * Look up for explanation. */ for (i = 0; i < cfg_size(cfg, "voices"); i++) inslist_usr(2, (cfg_getnstr(cfg, "voices", i))); #ifdef DEBUG dinfo("All done, running cfg_free()"); #endif /* Finally we're freeing anything that isn't needed anymore. */ cfg_free(cfg); return 0; } int main(int argc, char **argv) { int argcount, fd; /* Inserting the commands, their names and their descriptions to the * irc_cmd list. */ inslist("about", "Info about the bot", &cmd_abt); inslist("addop", "Add channel operator", &addop); inslist("addvoice", "Add voiced user", &addvoice); inslist("rehash", "Rehash the config file", &rehash); inslist("listusers", "List all owners", &listusers); inslist("deluser", "Remove user from the owners or voices list", &deluser); inslist("join", "Join the given channel", &cmd_join); inslist("part", "Part the given channel", &cmd_part); inslist("kick", "Kick an user", &cmd_kick); inslist("k","Kick an user", &cmd_kick); inslist("op","Op an user", &cmd_op); inslist("deop", "Deop an user",& cmd_deop); inslist("voice", "Voice an user", &cmd_voice); inslist("devoice", "Devoice an user", &cmd_devoice); inslist("mode", "Change channel modes", &cmd_mode); inslist("topic", "Change channel topic", &cmd_topic); inslist("quit", "Make me quit", &cmd_quit); inslist("nick", "Make me change my nick", &cmd_nick); inslist("list", "List all commands", &cmd_list); inslist("ls", "List all commands", &cmd_list); #ifdef DEBUG dinfo("inslist done, starting cfg parsing"); #endif /* This is where we'll parse the config and quit if it doesn't return 0. */ int c; if((c = parse()) != 0) return c; /* Getting the command line options. The reason it's made this far is that options * given from here would override the ones parsed from the * config file. */ while ((i = getopt(argc, argv, "hfls:c:n:r:")) != -1) switch (i) { case 'h': fprintf(stderr, "%s - Simple IRC Bot written in C\n\n" "Version %f\n\n" "Usage: %s [-hfl] [-s irc.server.net] [-c channel]\n" " [-n nick] [-r \"real name\"]\n\n" "-h : Show this help.\n" "-l : Turn off logging.\n" "-f : Don't fork to background, run in console instead.\n" "-s : Server to connect to.\n" "-c : Channel to join to.\n" "-n : Nick to be used by the bot.\n" "-r : Real name.\n" " All values entered here will override the ones set on config file.\n", argv[0], VERSION, argv[0]); exit(0); case 'l': tolog = 0; break; case 'f': tofork = 0; break; case 's': IRC_SERVER = optarg; break; case 'c': IRC_CHAN = optarg; break; case 'n': IRC_NICK = optarg; break; case 'r': IRC_REALNAME = optarg; break; } #ifdef DEBUG tofork = 0; /* We will want to stay on the console if we enabled DEBUG. */ #endif /* Printing the 'cool' start-up message. First thing an user will see unless (s)he specified * the -h flag. */ printf("===============================================\n" " Simple C Bot starting up . . .\n" "===============================================\n\n" "=> Nick set to `%s'\n" "=> Server set to `%s' on port %i\n" "=> Channel set to `%s'\n" "=> Real name set to `%s'\n", IRC_NICK, IRC_SERVER, IRC_PORT, IRC_CHAN, IRC_REALNAME); if (tolog == 1) { printf("=> Opening logfile `%s'\n", LOGFILE); fp = fopen(LOGFILE, "a+"); } char buffer[512] = {0}, from[56] = {0}, nick[32] = {0}, *argument[MAX_ARGS], *token; /* Creating the socket */ if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { derr("Oh shit! socket() failed!"); return(ERR_SOCKET); } printf("=> Created socket\n"); /* Setting signals to the catcher to allow graceful stopping of the bot. */ signal(SIGHUP, cmd_catcher); signal(SIGINT, cmd_catcher); signal(SIGTERM, cmd_catcher); /* Then connecting to the specified server. * This calls the conn() function that is below the main. */ if (conn(sock, IRC_SERVER, IRC_PORT) != 0 ) { derr(("Failed to connect to %s", IRC_SERVER)); return ERR_CONN; } printf("=> Connected to %s:%i\n", IRC_SERVER, IRC_PORT); /* Then we're forking unless the user specified (s)he didn't want it via the * -f flag or defining DEBUG. */ if (tofork == 1) { printf("=> Forking to background\n\n"); pid_t pid; pid = fork(); if (pid < 0) exit(1); if (pid != 0) exit(0); setsid(); signal (SIGHUP, SIG_IGN); pid = fork(); if (pid < 0) exit(1); if (pid != 0) exit(0); umask(0); for (i = 0; i < 3; ++i) close(i); fd = open("/dev/null", O_RDWR); dup(fd); dup(fd); } snprintf(nick, sizeof(nick), "%s", IRC_NICK); /* We don't want too long nicks */ sendl(sock, "NICK %s", nick); /* Telling the server our nick */ sendl(sock, "USER scb foo bar :%s", IRC_REALNAME); /* And logging in */ /* This is the loop that will run while the connection is active. * Everything is handled from here. */ while(1) { /* Clearing the buffer from previous lines*/ memset(buffer, 0, sizeof(buffer)); /* Waiting until we receive a new line and insert it to buffer. */ if (recvln(sock, buffer, sizeof(buffer), fp) == 1) break; /* Then we'll split the buffer to pieces * to make it easier to handle. Also counting the total number of arguments. */ token = strtok(buffer, " "); argcount = 0; while (token != NULL) { argument[argcount] = token; token = strtok(NULL, " "); argcount++; } /* Two arguments means there were two entries seperated with spaces in the input. */ if (argcount > 2) { if (strcmp(argument[1], "001") == 0) { /* 001 is the answer from IRC server announcing * that everything went fine. */ if (authed == 0) cmd_auth(sock, nick); sendl(sock, "JOIN %s", IRC_CHAN); /* so we're joining to the channel. */ continue; } else if (strcmp(argument[1], "433") == 0) { /* 433 means that the nick is already in use. */ strncat(nick, "^", 1); /* so we're adding ^ to the end of the nick */ sendl(sock, "NICK %s", nick); /* And telling the server we did so */ if (authed == 0) cmd_auth(sock, nick); continue; } } if (argcount == 2) { if (strcmp(argument[0], "PING") == 0) { /* IRC servers requires a PONG response * for every PING it sends or we'll get disconnected * with a reason "ping timeout". Usually this means that * the connection between the server and the user is broken, so * the server didn't get PONG in time. */ sendl(sock, "PONG %s", argument[1]); continue; } } /* Here we're going through the users list to see if argument[0], which contains the hostmask * of the user that said something, is listed on the owners or voices list. * If it is, we'll set own or voiced variable to true, so that commands * that require ownership or to be voiced can be executed. */ UPTR scan = user; while(scan) { if ((strstr(argument[0], scan->mask)) != NULL) { #ifdef DEBUG dinfo(("Checking whether %s is on the users list", scan->mask)); #endif if (scan->lvl == 1) { #ifdef DEBUG dinfo("op"); #endif own = 1; } else if (scan->lvl == 2) { #ifdef DEBUG dinfo("voice"); #endif voiced = 1; } } scan = scan->next; } who = strtok((strsave(argument[0])), ":!"); /* wtf strtok sucks, have to use strsave to keep * the original argument[0] intact. * * The who variable holds the nick of the user who said * the current line. This is used by many commands which will * send a NOTICE to the user. */ /* Three arguments most likely mean a join, part, quit or similiar. */ if (argcount == 3) if (strcmp(argument[1], "JOIN") == 0) { /* so if someone's joining.. */ if ((strncmp(argument[2], ":", 1)) == 0) { /* Fix for unrealircd */ char *fix = strtok(strdup(argument[2]), ":"); argument[2] = fix; } if ((own == 1) && (AOP == 1)) /* .. and if the user is a owner and auto-op is set to yes.. */ sendl(sock, "MODE %s +o %s", argument[2], who); /* We'll op him/her. */ else if ((voiced == 1) && (AVOICE == 1)) /* Or if it was a voiced user and autovoice is enabled */ sendl(sock, "MODE %s +v %s", argument[2], who); /* We'll voice him/her. */ } /* If the target of the message was the bot, meaning a private * message, we can assume that it is safe to react to it. */ if (strcmp(argument[2], nick) == 0) react = 1; /* Four or more arguments are most likely a cause of somebody saying something. */ if (argcount >= 4) { if (strcmp(argument[1], "PRIVMSG") == 0) { /* This confirms if someone indeed said something. */ snprintf(from, sizeof(from), "%s", argument[2]); /* And then gets the channel the line was said in. */ if (argument[3][1] == IRC_CMD_TRIGGER) { /* Here we'll check if the first letter of the first word that * the user said was the IRC_CMD_TRIGGER, '-' by default. * * If it was, we'll connect all the arguments to * one string to make it easier for the * command that was called to do anything * with them. */ *argument += (strlen(argument[0]) + 1) + (strlen(argument[1]) + 1) + (strlen(argument[2]) + 3); execcmd(argument[0], sock, from, argument, argcount - 3); /* And then we'll call execcmd which will scan through the list of irc_cmd * and execute the command that was given to it. */ } } } continue; } /* The loop closed here, which means that the connection was closed. We'll start the * shutdown prodecure then. */ if (tolog == 1) { /* First we'll flush and close the log file. * flush isn't necessary, as it's most likely already done, * but better to be sure. */ printf("=> Closing log file `%s'...", LOGFILE); fflush(fp); fclose(fp); printf("Done!\n"); } printf("=> Disconnected\n"); return 0; } /* connect function */ int conn(int sock, const char *remotehost, unsigned short remote_port) { printf("=> Resolving %s... ", remotehost); struct sockaddr_in s_in; struct hostent *hostent; /* First we need to perform DNS query to get the IP of the server */ if (!(hostent = gethostbyname(remotehost))) return 0; printf("Success!\n"); memset(&s_in, 0, sizeof(s_in)); s_in.sin_family = PF_INET; /* IPv4 */ s_in.sin_addr.s_addr = *(long *) hostent->h_addr; /* Taking the returned IP */ s_in.sin_port = htons(remote_port); /* Converting remote_port to network order byte */ printf("=> Resolved %s to %s\n", remotehost, inet_ntoa(s_in.sin_addr)); /* Here we'll use the socket to connect to the * IP we resolved moments ago. */ if (connect(sock, (struct sockaddr *) &s_in, sizeof(s_in)) == -1) return 1; return 0; } /* This is the function that will handle the sending * of all data to IRC server. */ int sendl(int sock, const char *format, ...) { va_list args; /* va_list is used when there is a variable amount of arguments for a function */ char buffer[512] = {0}; va_start(args, format); /* Here we'll append every variable given to the function to buffer. */ vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); strncat(buffer, "\r\n", (sizeof(buffer) - strlen(buffer))); /* Adding CRLF to the end of the string, * so the IRCd correctly recognizes it as a end of line. */ /* No need to see PONGs, so we'll skip over the printing of the line if it is a PONG response. */ if (strstr(buffer,"PONG :") == NULL) printf(">> %s", buffer); /* This is where we'll actually send the line to the server. * The send() returns int, so we know if something went wrong with the * return value. */ return(send(sock, buffer, strlen(buffer), 0)); } /* Receive line function */ int recvln(int sock, char *line, unsigned int line_size, FILE *log) { char byte = 0; react = 0; own = 0; /* We'll reset the react, own and voiced values from the previous line. */ voiced = 0; while (byte != '\n' && strlen(line) < line_size) { /* Getting one byte at a time until newline */ if (!recv(sock, (char *) &byte, 1, 0)) return 1; if (byte != '\r' && byte != '\n' && byte != '\0') strncat(line, (char *) &byte, 1); } if (tolog == 1) { /* If we're taking logs and the line is either a PRIVMSG to the channel, a quit message, * a join message or a part message we'll write it to the log together * with the current UNIX time. * This log format gives us full compatibility with the oer log format * for pisg to generate stats from the log if you want to. */ if ((strstr(line,"PRIVMSG") != NULL && strstr(line,IRC_CHAN) != NULL) || strstr(line,"QUIT :") != NULL || strstr(line,"JOIN") != NULL || strstr(line,"PART") != NULL) { fprintf(log, "%ld %s\n", (long)time(NULL), line); fflush(log); } } /* We don't want to see ping and pong, it's enough that they happen. */ if (strstr(line,"PING :") == NULL) printf("<< %s\n", line); return 0; } /* Function to change nick */ void cmd_nick(CARGS) { if (own == 1 && react == 1) { sendl(sock, "NICK %s", args[4]); IRC_NICK = args[4]; } else NOAUTH; } /* Quit function */ void cmd_quit(CARGS) { if (own == 1 && react == 1) if (((strcmp(args[4], "-y")) == 0) || (strcmp(args[4], "-yes")) == 0) /* If confirmed the quit with -y or -yes */ sendl(sock, "QUIT :Oh god how did this get in here i am not good with computers"); else sendl(sock, "NOTICE %s :Are you sure you want me to quit? :( -quit -yes to confirm.", who); else NOAUTH; } /* Auth function */ void cmd_auth (int sock, char* nick) { authed = 1; sendl(sock, AUTH_COMMAND); sendl(sock, "MODE %s +x", nick); sleep(0.5); /* We better sleep a moment for mode +x to take effect before joining.. */ } /* About function. Probably going to disappear as soon as I * have time to implement CTCP. */ void cmd_abt (CARGS) { sendl(sock, "PRIVMSG %s :Simple C Bot, version %f (built at %s %s)", from, VERSION, __DATE__, __TIME__); sendl(sock, "PRIVMSG %s :Send bugs, suggestions etc to cene@fix.fi", from); } /* Joining to channels */ void cmd_join (CARGS) { if (own == 1 && react == 1) sendl(sock, "JOIN %s", args[4]); else NOAUTH; } /* Function to part channels */ void cmd_part (CARGS) { if (own == 1 && react == 1) { if (args[4]) /* If the user specified a channel to part */ sendl(sock, "PART %s", args[4]); else /* Else we'll part the current one */ sendl(sock, "PART %s", from); } else NOAUTH; } /* Kick function */ void cmd_kick(CARGS) { if (own == 1) { if (arg_count >= 3) { /* If there is more than three * arguments, it means that the user who issued * the kick command specified a kick reason. * We'll append the reason to one string "mtmp" and * use it as a kick reason. */ char *mtmp = NULL; int len = 0; for(i = 5; i < arg_count + 4; i++) len += (strlen(args[i]) + 2); mtmp = malloc(len * sizeof(char) + 1); mtmp[0] = (char)0; for (i = 5; i < arg_count + 4; i++) { strcat(mtmp, args[i]); strcat(mtmp, " "); } sendl(sock, "KICK %s %s :%s", from, args[4], mtmp); } /* If there were only two arguments, there is no reason specified * so we don't need to look for it either. */ else sendl(sock, "KICK %s %s", from, args[4]); } else NOAUTH; } /* Op function */ void cmd_op(CARGS) { if (own == 1) sendl(sock, "MODE %s +o %s", from, args[4]); else NOAUTH; } /* Deop function */ void cmd_deop(CARGS) { if (own == 1) sendl(sock, "MODE %s -o %s", from, args[4]); else NOAUTH; } /* Voice function */ void cmd_voice(CARGS) { if (own == 1) sendl(sock, "MODE %s +v %s", from, args[4]); else NOAUTH; } /* Devoice function */ void cmd_devoice(CARGS) { if (own == 1) sendl(sock, "MODE %s -v %s", from, args[4]); else NOAUTH; } /* Modes */ void cmd_mode(CARGS) { if (own == 1) { if (arg_count > 2) { /* If there were more than one args[] * that include the modes to modify, * we need to append them to a single * string first. */ char *mtmp = NULL; int len = 0; for (i = 4; i < arg_count + 3; i++) len += (strlen(args[i]) + 2); mtmp = malloc(len * sizeof(char) + 1); mtmp[0] = (char)0; for (i = 4; i < arg_count + 3; i++) { strcat(mtmp, args[i]); strcat(mtmp, " "); } sendl(sock, "MODE %s %s", from, mtmp); free(mtmp); } else sendl(sock, "MODE %s %s", from, args[4]); } else NOAUTH; } /* Topic */ void cmd_topic(CARGS) { if (own == 1) { /* Appending anything past args[4] to * a single string and then setting it as a topic. * Nothing special. */ char *tmp = NULL; int len = 0; for(i = 4; i < arg_count + 3; i++) len += (strlen(args[i]) + 2); tmp = malloc(len * sizeof(char) + 1); tmp[0] = (char)0; for(i = 4; i < arg_count + 3; i++) { strcat(tmp, args[i]); strcat(tmp, " "); } #ifdef DEBUG dinfo(tmp); #endif sendl(sock, "TOPIC %s :%s", from, tmp); free(tmp); } else NOAUTH; } void deluser(CARGS) { if (own == 1 && react == 1) { int rm = 0; /* How many users we've deleted. Will be used later on */ UPTR scan = user; UPTR tmp = NULL; for (scan = user; scan != NULL; tmp = scan, scan = scan->next) { /* Scanning through the list of users looking for a matching string. */ if ((strstr(scan->mask, args[4])) != NULL) { if(scan->lvl == 1) { sendl(sock, "NOTICE %s :Regex '%s' matches '%s', removing owner status from '%s'", who, args[4], scan->mask, scan->mask); } else if(scan->lvl == 2) { sendl(sock, "NOTICE %s :Regex '%s' matches '%s', removing voiced status from '%s'", who, args[4], scan->mask, scan->mask); } if (tmp == NULL) user = scan->next; else tmp->next = scan->next; free(scan); rm++; } } if (rm != 0) { /* If anyone was removed, printing the reminder that these changes are not permanent. */ sendl(sock, "NOTICE %s :Remember, these changes are not (yet) permanent.", who); sendl(sock, "NOTICE %s :To make these changes permanent you must remove the demoted user(s) from the config file after stopping the bot.", who); } else { sendl(sock, "NOTICE %s :Sorry, no hits with your search term '%s'", who, args[4]); } #ifdef DEBUG UPTR sscan = user; while(sscan) { dinfo(sscan->mask); sscan = sscan->next; } #endif } else NOAUTH; } void addop(CARGS) { if (own == 1 && react == 1) { /* Adding new owners. -perm adds them permanently (aka prints the hostmask * to the config file too), * and -temp adds temporarily, lasting only until the bot is shut down. */ int ok = 0; /* Switch to check if the syntax was correct. */ if (arg_count != 3) { /* Exactly three arguments are required. */ sendl(sock, "NOTICE %s :Syntax: %caddop (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER); } else { /* If it was for temporary use only, we can switch ok on and just continue. */ if ((strcmp(args[4], "-temp")) == 0) ok = 1; if ((strcmp(args[4], "-perm")) == 0) { /* If (s)he was added permanently, we need to open the config file for appending, * and write the new owner there. * Then we can continue. */ gg = fopen(CONFFILE, "a+"); fprintf(gg, "owners += { \"%s\" }\n", args[5]); ok = 1; fflush(gg); fclose(gg); } if (ok == 1) { inslist_usr(1, args[5]); /* Here we'll insert the hostmask to users. 1 means it will be added as a owner. */ sendl(sock, "NOTICE %s :Added hostmask '%s' to owners.", who, args[5]); } else { sendl(sock, "NOTICE %s :Syntax: %caddop (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER); } #ifdef DEBUG UPTR scan = user; while(scan) { dinfo(scan->mask); scan = scan->next; } #endif } } else NOAUTH; } void addvoice(CARGS) { if (own == 1 && react == 1) { /* Same function as addop, but just for voices. See the comments there * if you need something explained. */ int ok = 0; if (arg_count != 3) { sendl(sock, "NOTICE %s :Syntax: %caddvoice (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER); } else { if ((strcmp(args[4], "-temp")) == 0) { ok = 1; } if ((strcmp(args[4], "-perm")) == 0) { gg = fopen(CONFFILE, "a+"); fprintf(gg, "voices += { \"%s\" }\n", args[5]); fflush(gg); fclose(gg); ok = 1; } if (ok == 1) { sendl(sock, "NOTICE %s :Added hostmask '%s' to voices list.", who, args[5]); inslist_usr(2, args[5]); } else { sendl(sock, "NOTICE %s :Syntax: %caddvoice (-perm|-temp) hostmask", who, IRC_CMD_TRIGGER); } } } else NOAUTH; } /* Listing all commands */ void cmd_list(CARGS) { if (((strcmp(args[4], "-y")) == 0) || (strcmp(args[4], "-yes") == 0)) { /* If the listing was confirmed with the -yes trigger, then * we'll print all the commands. */ LISTPTR prun = cmd; while(prun) { sendl(sock, "NOTICE %s :%s - %s", who, prun->cmd_name_p, prun->desc_p); prun = prun->next; sleep(1); /* We will want to sleep for a moment between each sent line to avoid getting removed * from the network because of excessive spam. */ } } else { LISTPTR run = cmd; int g = 0; while(run) { g++; /* Checking how many commands there are. */ run = run->next; } sendl(sock, "NOTICE %s :There are %i different commands currently available.", who, g); sendl(sock, "NOTICE %s :If you want to see all of them, type -list -yes", who); sendl(sock, "NOTICE %s :All the commands are also explained here to avoid spam: http://cene.ath.cx/scb/commands.html", who); } } /* Listing all users. */ void listusers(CARGS) { if (own == 1 && react == 1) { UPTR q = user; while(q) { if (q->lvl == 1) sendl(sock, "NOTICE %s :User level 1 (op) - %s", who, q->mask); else if (q->lvl == 2) sendl(sock, "NOTICE %s :User level 2 (voice) - %s", who, q->mask); q = q->next; } } else NOAUTH; } /* Rehashing, * aka reloading the config file for changes on * variables and/or owners. * * Currently crashes if variables are changed, and feels * a bit unrealiable otherwise too. Cleaning of the users list is a bit ugly too. * * Any ideas on how to make this thing work better are welcome. */ void rehash(CARGS) { dwarn("This really sucks. Don't be surprised if the bot crashes. Any ideas on how to make it better are welcome."); if (own == 1 && react == 1) { sendl(sock, "NOTICE %s :Rehashing.. ", who); /* Clearing the lists so they can be reassigned */ UPTR tmp = user, L = user; int i = 0; while(L != NULL) { L = L->next; tmp->next = NULL; if (i != 0) free(tmp); tmp = L; i++; } #ifdef DEBUG UPTR dscan = user; while(dscan) { dinfo(dscan->mask); dscan = dscan->next; } #endif int c; if ((c = parse |