/* * ------------------------------------------------------- * Copyright (C) 2003-2007 Tommi Saviranta * (C) 2002 Lee Hardy * (C) 1998-2002 Sebastian Kienzl * ------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifdef HAVE_CONFIG_H #include #endif /* ifdef HAVE_CONFIG_H */ #include "client.h" #include "conntype.h" #include "error.h" #include "messages.h" #include "miau.h" #include "irc.h" #include "qlog.h" #include "tools.h" #include "channels.h" #include "dcc.h" #include "privlog.h" #include "commands.h" #include "chanlog.h" #include "log.h" #include "common.h" #include client_info i_client; clientlist_type c_clients; void client_drop_real(connection_type *client, char *reason, const int echo, const int dying); /* * "work" must be global so when miau_command is called, "work" can be freed * if users want to quit miau. */ static char *work = NULL; /* Temporary copy of input. */ /* * Drop client from bouncer. * * If client == NULL, drop all clients. */ void client_drop(connection_type *client, char *reason, const int msgtype, const int echo, const char *awaymsg) { if (client == NULL) { /* Drop all clients. */ while (c_clients.clients->head != NULL) { client_drop_real((connection_type *) c_clients.clients->head->data, reason, echo, msgtype == DISCONNECT_DYING); } } else { client_drop_real(client, reason, echo, msgtype == DISCONNECT_DYING); } switch (msgtype) { case DISCONNECT_ERROR: error("%s", reason); break; case DISCONNECT_DYING: report("%s%s", CLNT_DIE, reason); break; case DISCONNECT_REPORT: report("%s", reason); break; } /* Reporting number of clients is irrevelant if only one is allowed. */ if (cfg.maxclients != 1) { report(CLNT_CLIENTS, c_clients.connected); } if (c_clients.connected == 0) { clients_left(cfg.usequitmsg ? awaymsg : NULL); } else if (cfg.autoaway == 1) { if (cfg.usequitmsg == 1 && awaymsg != NULL){ set_away(awaymsg); } else { set_away(cfg.awaymsg); } } } /* void client_drop(connection_type *, char *, const int, const int, const char *) */ /* * Drop client from bouncer. */ void client_drop_real(connection_type *client, char *reason, const int echo, const int dying) { llist_node *node; /* We could skip testing reason, but lets play safe. */ if (echo && reason != NULL) { /* Set back to blocking. */ sock_setblock(c_server.socket); if (reason[0] == ':') { irc_write(client, "ERROR %s", reason); } else { irc_write(client, dying ? MIAU_USERKILLED : MIAU_CLOSINGLINK, reason); } } sock_close(client); node = llist_find((void *) client, c_clients.clients); xfree(node->data); llist_delete(node, c_clients.clients); c_clients.connected--; } /* void client_drop_real(connection_type *, char *, const int, const int) */ static void cmd_part_action(channel_type *chan, char *reason) { /* * ":" is always there after channel name so this works even with * mIRC when connected to Undernet. */ irc_mwrite(&c_clients, ":%s!%s@%s PART %s :%s", status.nickname, i_client.username, i_client.hostname, chan->name, reason); channel_rem(chan, LIST_PASSIVE); } static void cmd_part(char *par0, char *par1) { /* User wants to leave list of channels. */ channel_type *chan; char *name; name = strtok(par0, ":"); /* Set EOS for channels. */ name = strtok(par0, ","); while (name != NULL) { chan = channel_find(name, LIST_PASSIVE); if (chan == NULL) { irc_mwrite(&c_clients, ":miau %d %s %s :%s", ERR_NOSUCHCHANNEL, status.nickname, name, IRC_NOSUCHCHAN); } else { cmd_part_action(chan, par1 + 1); } name = strtok(NULL, ","); } } /* static void cmd_part(char *par0, char *par1) */ static void cmd_join(char *par0, char *par1) { /* user wants to join a list of channels */ char *chan; /* channel name */ char *key; /* channel key */ char *chanseek; char *keyseek; if (par0 != NULL && xstrcmp(par0, "0") == 0) { /* user want to leave all channels */ LLIST_WALK_H(passive_channels.head, channel_type *); cmd_part_action(data, ""); LLIST_WALK_F; return; } chan = par0; key = par1; keyseek = NULL; while (chan != NULL && *chan != '\0') { chanseek = strchr(chan, ','); if (chanseek != NULL) { *chanseek = '\0'; chanseek++; } if (key != NULL && *key != '\0') { keyseek = strchr(key, ','); if (keyseek != NULL) { *keyseek = '\0'; keyseek++; } } irc_mnotice(&c_clients, status.nickname, MIAU_JOIN_QUEUE, chan); channel_add(chan, key, LIST_PASSIVE); chan = chanseek; key = keyseek; } } /* static void cmd_join(char *par0, char *par1) */ #ifdef DCCBOUNCE static int cmd_privmsg_ctcp(char *par0, char *par1) { char dcct[IRC_MSGLEN]; char *ret; if (cfg.dccbounce == 0) { return 1; } upcase(par1); if (xstrncmp(par1, ":\1DCC", 5) != 0) { return 1; } strncpy(dcct, par1 + 1, IRC_MSGLEN); ret = dcc_initiate(dcct, IRC_MSGLEN, 1); if (ret != NULL) { irc_write(&c_server, "PRIVMSG %s %s", par0, dcct); return 0; } else { return 1; } } /* static int cmd_privmsg_dcc(char *par0, char *par1) */ #endif /* ifdef DCCBOUNCE */ #ifdef NEED_LOGGING static int cmd_privmsg(char *par0, char *par1) { int is_chan; if (par0 == NULL) { return 1; } is_chan = channel_is_name(par0); #ifdef PRIVLOG if (is_chan == 0) { /* * We could say * if ((c_clients.connected > 0 && (cfg.privlog & 0x02)) || (c_clients.connected == 0 && cfg.privlog == PRIVLOG_DETACHED)) { * * ...but do disconnected clients really talk?-) * * Therefore we say the following instead: */ if (cfg.privlog != 0) { privlog_write(par0, PRIVLOG_OUT, CMD_PRIVMSG + MINCOMMANDVALUE, par1 + 1); } } #endif /* PRIVLOG */ #ifdef CHANLOG if (is_chan != 0) { channel_type *chan; chan = channel_find(par0, LIST_ACTIVE); if (chan != NULL && chanlog_has_log(chan, LOG_MESSAGE)) { char *t; t = log_prepare_entry(status.nickname, par1 + 1); if (t == NULL) { chanlog_write_entry(chan, LOGM_MESSAGE, get_short_localtime(), status.nickname, par1 + 1); } else { chanlog_write_entry(chan, "%s", t); } } } #endif /* CHANLOG */ return 1; } /* static int cmd_privmsg(char *par0, char *par1) */ #endif /* ifdef NEED_LOGGING */ static int cmd_away(const char *orig, char *par0, char *par1) { if (par0 == NULL) { FREE(status.awaymsg); /* Not away / no custom message. */ status.awaystate &= ~AWAY & ~CUSTOM; } else { /* This should be safe by now. */ char *t = strchr(orig, (int) ' ') + 2; #ifdef EMPTYAWAY xfree(status.awaymsg); status.awaymsg = xstrdup(t); /* Away / custom message. */ status.awaystate |= AWAY | CUSTOM; #else /* EMPTYAWAY */ FREE(status.awaymsg); if (*t != '\0') { status.awaymsg = xstrdup(t); /* Away / custom message. */ status.awaystate |= AWAY | CUSTOM; } else { /* Not away / no custom message. */ status.awaystate &= ~AWAY & ~CUSTOM; } #endif /* EMPTYAWAY */ } return 1; } /* static int cmd_away(char *par0, char *par1) */ static void pass_cmd(connection_type *client, char *cmd, char *par0, char *par1, const char *bufbu) { const char *buf; /* * Use buffer from client unless bufbu is set. Having bufbu set * means client is invalid and should not be used. */ if (bufbu != NULL) { buf = bufbu; } else { buf = client->buffer; } if (i_server.connected == 2) { int msg; /* Pass the message to server. */ irc_write(&c_server, "%s", buf); msg = ((xstrcmp(cmd, "PRIVMSG") == 0) || (xstrcmp(cmd, "NOTICE") == 0)) ? 1 : 0; /* Echo the meesage to other clients. */ if (msg == 1) { char tmp[IRC_MSGLEN + 1]; const char *cl_out = NULL; llist_node *iter; if (cfg.privmsg_fmt != NULL && channel_is_name(par0) == 0) { int i; /* if the message is too long to fit the buffer * then it's a shame. sorry. */ i = snprintf(tmp, IRC_MSGLEN, cfg.privmsg_fmt, status.nickname); tmp[i] = tmp[IRC_MSGLEN] = '\0'; strncat(tmp, par1 + 1, 510 - i); cl_out = tmp; } for (iter = c_clients.clients->head; iter != NULL; iter = iter->next) { connection_type *conn = (connection_type *) iter->data; if (iter->data == client) { continue; } if (cl_out == NULL) { irc_write(conn, ":%s!%s %s", status.nickname, status.idhostname, buf); } else { irc_write(conn, ":%s!%s %s %s %s", par0, "dummy", cmd, par0, cl_out); } } } #ifdef QUICKLOG /* Also put it in quicklog. If necessary, or course. */ if (cfg.flushqlog == 0 && par0 != NULL && msg == 1) { qlog_write(0, ":%s!%s@%s %s", status.nickname, i_client.username, i_client.hostname, buf); } #endif /* QUICKLOG */ } else { irc_notice(client, status.nickname, CLNT_NOTCONNECT); } } /* static void pass_cmd(connection_type *client, char *cmd, char *par0, char *par1, const char *bufbu) */ int client_read(connection_type *client) { int c_status; char *command, *param1, *param2; char *orig; int pass; c_status = irc_read(client); if (c_status <= 0) { /* Something went unexpected. */ return c_status; } /* Ok, got something. */ if (client->buffer[0] == '\0') { /* Darn, got nothing after all. */ return 0; } work = xstrdup(client->buffer); orig = NULL; command = strtok(work, " "); if (command == NULL) { xfree(work); work = NULL; return 0; } param1 = strtok(NULL, " "); param2 = strtok(NULL, "\0"); if (param1 != NULL && *param1 == ':') { param1++; } upcase(command); /* Commands from client when not connected to IRC-server. */ if (i_server.connected != 2) { /* Willing to leave channels. */ if (xstrcmp(command, "PART") == 0) { cmd_part(param1, param2); } /* Willing to join or leave channels. */ else if (xstrcmp(command, "JOIN") == 0) { cmd_join(param1, param2); } } pass = 1; /* pass by default */ if (xstrcmp(command, "PRIVMSG") == 0) { if (param2 == NULL) { #ifdef ENDUSERDEBUG enduserdebug("client_read(): PRIVMSG, param2 = NULL"); #endif /* ifdef ENDUSERDEBUG */ } #ifdef DCCBOUNCE else if (param2[0] == ':' && param2[1] == '\1') { pass = cmd_privmsg_ctcp(param1, param2); } #endif /* ifdef DCCBOUNCE */ #ifdef NEED_LOGGING else { pass = cmd_privmsg(param1, param2); } #endif /* ifdef NEED_LOGGING */ } else if (xstrcmp(command, "PONG") == 0) { pass = 0; /* Munch munch munch. */ } else if (xstrcmp(command, "PING") == 0) { irc_write(client, ":%s PONG %s :%s", i_server.realname, param2 != NULL ? param2 : i_server.realname, param1 != NULL ? param1 : status.nickname); pass = 0; } else if (xstrcmp(command, "MIAU") == 0) { miau_commands(param1, param2, client); pass = 0; } else if (xstrcmp(command, "QUIT") == 0) { const char *reason; orig = xstrdup(client->buffer); if (param1 != NULL) { reason = orig + (param1 - work); } else { reason = NULL; } /* (single client, message, report, echo) */ client_drop(client, CLNT_LEFT, DISCONNECT_REPORT, 0, reason); pass = 0; } else if (xstrcmp(command, "AWAY") == 0) { pass = cmd_away(client->buffer, param1, param2); } else if (cfg.no_identify_capab == 1 && xstrcmp(command, "CAPAB") == 0) { if (xstrncasecmp(param1, "IDENTIFY-", 9) == 0 && strlen(param1) >= 9) { /* * freenode responds with an empty 290 if capability is * not supported. I suppose I can do the same. */ irc_write(client, ":%s 290 %s :", i_server.realname, status.nickname); pass = 0; } } else if (i_server.connected != 2) { pass = 0; } if (pass == 1) { /* orig is set only if client was detached */ if (orig != NULL) { pass_cmd(client, command, param1, param2, orig); } else { pass_cmd(client, command, param1, param2, client->buffer); } } xfree(orig); xfree(work); work = NULL; /* All ok. */ return 0; } /* int client_read(connection_type *) */ /* * Free copy of user's command. * * This function is a bit ugly and could be considered useless. When * client_read() is called, a copy of user's command is made, and call to * miau_command() may be made. If command was "DIE", this memory would leave * unfreed before quitting miau. So, this function is needed so that miau.c * can ask this memory to be freed without returning to client_read(). */ void client_free(void) { if (work != NULL) { xfree(work); work = NULL; } } /* void_client_free(void) */