LogSQL/logmysql.cpp

1342 lines
53 KiB
C++
Executable File

//
// logmysql.cpp
// LogSQL
//
// Created by Mr. Gecko on 2/10/12.
// Copyright (c) 2012 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
//
// 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.
//
#include <znc/Chan.h>
#include <znc/User.h>
#include <znc/Client.h>
#include <znc/IRCNetwork.h>
#include <znc/Modules.h>
#include <time.h>
#import <mysql.h>
class CLogMySQL : public CModule {
public:
MODCONSTRUCTOR(CLogMySQL) {
AddHelpCommand();
AddCommand("Host", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::HostCommand), "[a-z0-9.]+", "MySQL host.");
AddCommand("Port", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::PortCommand), "[0-9]+", "MySQL port.");
AddCommand("Username", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::UsernameCommand), "[A-Za-z0-9]+", "MySQL username.");
AddCommand("Password", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::PasswordCommand), "[A-Za-z0-9]+", "MySQL password.");
AddCommand("DatabaseName", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::DatabaseNameCommand), "[a-z0-9]+", "MySQL database name.");
AddCommand("Connect", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::ConnectCommand), "", "Reconnect to the MySQL database.");
AddCommand("Replay", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::ReplayCommand), "[0-9]*", "Play back the messages received.");
AddCommand("ReplayAll", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::ReplayAllCommand), "[1|0]", "Replay all messages stored.");
AddCommand("AutoReplay", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::AutoReplayCommand), "[1|0]", "Replay on connect.");
AddCommand("LogLimit", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::LogLimitCommand), "[0-9]+", "Limit the amount of items to store into the log.");
AddCommand("LogLevel", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::LogLevelCommand), "[0-4]", "Log level.");
AddCommand("AddIgnore", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::AddIgnoreCommand), "Type[nick|chan] Target", "Add to ignore list.");
AddCommand("RemoveIgnore", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::RemoveIgnoreCommand), "Type[nick|chan] Target", "Remove from ignore list.");
AddCommand("IgnoreList", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::IgnoreListCommand), "", "View what is currently ignored.");
AddCommand("DoNotTrack", static_cast<CModCommand::ModCmdFunc>(&CLogMySQL::DoNotTrack), "", "Adds the current session to the do not track disconnect list.");
}
void HostCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("The MySQL host. Can be an IP Address or Hostname. 127.0.0.1 or localhost is for the current server.");
} else if (!sArgs.empty()) {
SetNV("host",sArgs);
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("Host is "+now+"set to: "+GetNV("host"));
}
void PortCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("The MySQL port. Usually will be 3306.");
} else if (!sArgs.empty()) {
CString result;
unsigned int port = strtoul(sArgs.c_str(), NULL, 10);
char portStr[20];
snprintf(portStr, sizeof(portStr), "%u", port);
result = portStr;
SetNV("port",result);
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("Port is "+now+"set to: "+GetNV("port"));
}
void UsernameCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("Your MySQL user username.");
} else if (!sArgs.empty()) {
SetNV("username",sArgs);
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("Username is "+now+"set to: "+GetNV("username"));
}
void PasswordCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("Your MySQL user password.");
} else if (!sArgs.empty()) {
SetNV("password",sArgs);
}
CString status = (GetNV("password").empty() ? "blank" : "set");
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("Password is "+now+status);
}
void DatabaseNameCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("The MySQL database name which contains the messages and settings tables.");
} else if (!sArgs.empty()) {
SetNV("databaseName",sArgs);
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("Database Name is "+now+"set to: "+GetNV("databaseName"));
}
void ConnectCommand(const CString &sLine) {
MySQLConnect();
if (databaseConnected)
PutModule("Database is now connected");
else
PutModule("Unable to connect to database. Check configuration.");
}
void ReplayCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
if (!sArgs.empty()) {
Replay(sArgs.ToInt());
} else {
Replay();
}
PutModule("Replayed");
}
void ReplayAllCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("On: All logs stored will be replayed.");
PutModule("Off: Only logs since the last time you connected will be replayed.");
} else if (!sArgs.empty()) {
replayAll = sArgs.ToBool();
SetSetting("replayAll", (replayAll ? "1" : "0"));
}
CString status = (replayAll ? "On" : "Off");
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("ReplayAll is "+now+"set to: "+status);
}
void AutoReplayCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
if (help) {
PutModule("On: Replay on connect.");
PutModule("Off: Require replay command to replay.");
} else if (!sArgs.empty()) {
autoReplay = sArgs.ToBool();
SetSetting("autoReplay", (autoReplay ? "1" : "0"));
}
CString status = (autoReplay ? "On" : "Off");
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("AutoReplay is "+now+"set to: "+status);
}
void LogLimitCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
CString setting;
if (help) {
PutModule("0: Everything will be kept in database.");
PutModule("1: Everything will be kept in database until replayed.");
PutModule("2+: Limit logs stored in database to limit set.");
} else if (!sArgs.empty()) {
logLimit = strtoul(sArgs.c_str(), NULL, 10);
char limitStr[20];
snprintf(limitStr, sizeof(limitStr), "%lu", logLimit);
setting = limitStr;
SetSetting("logLimit", setting);
}
if (setting.empty()) {
char limitStr[20];
snprintf(limitStr, sizeof(limitStr), "%lu", logLimit);
setting = limitStr;
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("LogLimit is "+now+"set to: "+setting);
}
void LogLevelCommand(const CString &sLine) {
CString sArgs = sLine.Token(1, true);
bool help = sArgs.Equals("HELP");
CString setting;
if (help) {
PutModule("0: Mentions and messages to you.");
PutModule("1: All messages.");
PutModule("2: Actions, Joins/Parts, and Notices.");
PutModule("3: Server wide messages.");
PutModule("4: All messages, actions, joins/parts, and noticies sent by you.");
} else if (!sArgs.empty()) {
logLevel = atoi(sArgs.c_str());
char levelStr[20];
snprintf(levelStr, sizeof(levelStr), "%d", logLevel);
setting = levelStr;
SetSetting("logLevel", setting);
}
if (setting.empty()) {
char levelStr[20];
snprintf(levelStr, sizeof(levelStr), "%d", logLevel);
setting = levelStr;
}
CString now = (sArgs.empty() || help ? "" : "now ");
PutModule("LogLevel is "+now+"set to: "+setting);
}
void AddIgnoreCommand(const CString &sLine) {
CString type = sLine.Token(1);
CString target = sLine.Token(2);
bool help = sLine.Equals("HELP");
if (help) {
PutModule("Inorder to add an ignore, you must choose what type of ignore it is which can ether be a nick or a chan.");
PutModule("Nicks are matched with wildcards against the full mask.");
PutModule("Channels are matched by #channel which can contain wildcards.");
} else if (!type.empty() && !target.empty()) {
if (type.Equals("nick"))
type = "nick";
else if (type.Equals("chan") || type.Equals("channel"))
type = "chan";
else
type = "";
if (type.empty()) {
PutModule("Unknown type. If you need help, type \"AddIgnore help\".");
return;
}
if (AddIgnore(type, target))
PutModule("Successfully added \""+target+"\" to the ignore list.");
else
PutModule("Failed, maybe it already existed?");
} else {
PutModule("If you need help, type \"AddIgnore help\".");
}
}
void RemoveIgnoreCommand(const CString &sLine) {
CString type = sLine.Token(1);
CString target = sLine.Token(2);
bool help = sLine.Equals("HELP");
if (help) {
PutModule("Inorder to remove an ignore, you must specify the type and the exact pattren used to add it. If you need to find what currently exists, type \"IgnoreList\".");
} else if (!type.empty() && !target.empty()) {
if (type.Equals("nick"))
type = "nick";
else if (type.Equals("chan") || type.Equals("channel"))
type = "chan";
else
type = "";
if (type.empty()) {
PutModule("Unknown type. If you need help, type \"RemoveIgnore help\".");
return;
}
if (RemoveIgnore(type, target))
PutModule("Successfully removed \""+target+"\" from the ignore list.");
else
PutModule("Failed, maybe it does not exist?");
} else {
PutModule("If you need help, type \"RemoveIgnore help\".");
}
}
void IgnoreListCommand(const CString &sLine) {
if (nickIgnoreList.size()==0) {
PutModule("The nick ignore list is currently empty.");
} else {
PutModule("Nick ignore list contains:");
for (std::vector<CString>::iterator it=nickIgnoreList.begin(); it<nickIgnoreList.end(); it++) {
PutModule(*it);
}
}
PutModule("---");
if (chanIgnoreList.size()==0) {
PutModule("The channel ignore list is currently empty.");
} else {
PutModule("Channel ignore list contains:");
for (std::vector<CString>::iterator it=chanIgnoreList.begin(); it<chanIgnoreList.end(); it++) {
PutModule(*it);
}
}
}
void DoNotTrack(const CString &sLine) {
bool tracking = true;
for (std::vector<CClient *>::iterator it=doNotTrackClient.begin(); it<doNotTrackClient.end(); it++) {
if (*it==m_pClient) {
tracking = false;
break;
}
}
if (tracking) {
doNotTrackClient.push_back(m_pClient);
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Added session to do not track list.");
} else {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Already added session to do not track list.");
}
}
virtual bool OnLoad(const CString& sArgs, CString& sMessage) {
connected = true;
databaseConnected = false;
MySQLConnect();
return true;
}
virtual ~CLogMySQL() {
if (databaseConnected) {
databaseConnected = false;
mysql_close(database);
database = NULL;
}
}
void MySQLConnect() {
CString host = GetNV("host");
CString port = GetNV("port");
if (port.empty()) {
port = "3306";
SetNV("port",port);
}
CString username = GetNV("username");
CString password = GetNV("password");
CString databaseName = GetNV("databaseName");
if (!host.empty() && !username.empty() && !databaseName.empty()) {
if (databaseConnected) {
databaseConnected = false;
mysql_close(database);
database = NULL;
}
database = mysql_init(NULL);
if (database!=NULL) {
void *theRet = mysql_real_connect(database, host.c_str(), username.c_str(), password.c_str(), databaseName.c_str(), (unsigned int)strtoul(port.c_str(), NULL, 10), MYSQL_UNIX_ADDR, CLIENT_COMPRESS);
databaseConnected = (theRet==database);
if (databaseConnected) {
std::cout << "LogMySQL: Database connected.\n";
MYSQL_RES *settings = mysql_list_tables(database, "settings");
if (mysql_num_rows(settings)==0) {
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "CREATE TABLE `settings` (`name` text,`value` text)", 50);
if (status==0) {
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
SetSetting("replayAll","0");
SetSetting("autoReplay","1");
SetSetting("logLimit","1");
SetSetting("logLevel","1");
SetSetting("version","2");
}
}
if (settings!=NULL)
mysql_free_result(settings);
MYSQL_RES *messages = mysql_list_tables(database, "messages");
if (mysql_num_rows(messages)==0) {
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "CREATE TABLE `messages` (`rowid` int UNSIGNED AUTO_INCREMENT,`target` text,`nick` text,`type` text,`message` longblob,`time` decimal(20,5) UNSIGNED,PRIMARY KEY (`rowid`))", 170);
if (status==0) {
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
}
}
if (messages!=NULL)
mysql_free_result(messages);
MYSQL_RES *ignorelist = mysql_list_tables(database, "ignorelist");
if (mysql_num_rows(ignorelist)==0) {
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "CREATE TABLE `ignorelist` (`rowid` int UNSIGNED AUTO_INCREMENT,`type` text,`target` text,PRIMARY KEY (`rowid`))", 111);
if (status==0) {
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
}
}
if (ignorelist!=NULL)
mysql_free_result(ignorelist);
unsigned long version = strtoul(GetSetting("version").c_str(), NULL, 10);
if (version==0) {
SetSetting("version","1");
version = 1;
}
if (version==1) {
SetSetting("autoReplay","1");
SetSetting("version","2");
version = 2;
}
replayAll = atoi(GetSetting("replayAll").c_str());
autoReplay = atoi(GetSetting("autoReplay").c_str());
logLimit = strtoul(GetSetting("logLimit").c_str(), NULL, 10);
logLevel = atoi(GetSetting("logLevel").c_str());
} else {
std::cout << "LogMySQL: Database unable to connect.\n";
}
}
}
}
CString GetUNIXTime() {
struct timeval time;
gettimeofday(&time, NULL);
double microtime = (double)(time.tv_sec + (time.tv_usec/1000000.00));
char timeStr[25];
snprintf(timeStr, sizeof(timeStr), "%f", microtime);
return timeStr;
}
void AddMessage(const CString& target, const CString& nick, const CString& type, const CString& message) {
if (!databaseConnected)
return;
if (IsIgnored("nick",nick) || (target.Left(1).Equals("#") && IsIgnored("chan",target)))
return;
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "INSERT INTO `messages` (`target`, `nick`, `type`, `message`, `time`) VALUES (?,?,?,?,?)", 87);
if (status!=0)
return;
MYSQL_BIND bind[5];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)target.c_str();
bind[0].buffer_length = target.length();
bind[0].is_null = (my_bool *)0;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void*)nick.c_str();
bind[1].buffer_length = nick.length();
bind[1].is_null = (my_bool *)0;
bind[2].buffer_type = MYSQL_TYPE_STRING;
bind[2].buffer = (void*)type.c_str();
bind[2].buffer_length = type.length();
bind[2].is_null = (my_bool *)0;
bind[3].buffer_type = MYSQL_TYPE_STRING;
bind[3].buffer = (void*)message.c_str();
bind[3].buffer_length = message.length();
bind[3].is_null = (my_bool *)0;
CString time = GetUNIXTime();
bind[4].buffer_type = MYSQL_TYPE_STRING;
bind[4].buffer = (void*)time.c_str();
bind[4].buffer_length = time.length();
bind[4].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return;
}
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
if (logLimit>=2) {
statement = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement, "SELECT COUNT(*) FROM `messages`", 31);
if (status!=0)
return;
status = mysql_stmt_execute(statement);
if (status!=0) {
mysql_stmt_close(statement);
return;
}
MYSQL_RES *result = mysql_stmt_result_metadata(statement);
unsigned int dataCount = mysql_num_fields(result);
if (dataCount!=1) {
mysql_free_result(result);
mysql_stmt_close(statement);
std::cout << "LogMySQL: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n";
return;
}
MYSQL_FIELD *fields = mysql_fetch_fields(result);
unsigned long length;
char countData[fields[0].length];
MYSQL_BIND results[1];
memset(results, 0, sizeof(results));
results[0].buffer_type = MYSQL_TYPE_STRING;
results[0].buffer = (void *)countData;
results[0].buffer_length = fields[0].length;
results[0].length = &length;
status = mysql_stmt_bind_result(statement, results);
if (status!=0) {
mysql_free_result(result);
mysql_stmt_close(statement);
return;
}
status = mysql_stmt_fetch(statement);
if (status!=0) {
mysql_free_result(result);
mysql_stmt_close(statement);
return;
}
CString countString = CString(countData, length);
unsigned long count = strtoul(countString.c_str(), NULL, 10);
if (count<=logLimit)
count = 0;
else
count = count-logLimit;
mysql_free_result(result);
mysql_stmt_close(statement);
if (count!=0) {
statement = mysql_stmt_init(database);
char *queryStr = (char *)malloc(73);
snprintf(queryStr, 73, "SELECT `rowid` FROM `messages` ORDER BY `time` LIMIT %lu", count);
status = mysql_stmt_prepare(statement, queryStr, strlen(queryStr));
free(queryStr);
if (status!=0)
return;
status = mysql_stmt_execute(statement);
if (status!=0) {
mysql_stmt_close(statement);
return;
}
result = mysql_stmt_result_metadata(statement);
dataCount = mysql_num_fields(result);
if (dataCount!=1) {
mysql_free_result(result);
mysql_stmt_close(statement);
std::cout << "LogMySQL: We are only suppose to receive 1 field. We received the count of " << dataCount << " fields.\n";
return;
}
fields = mysql_fetch_fields(result);
char rowidData[fields[0].length];
memset(results, 0, sizeof(results));
results[0].buffer_type = MYSQL_TYPE_STRING;
results[0].buffer = (void *)rowidData;
results[0].buffer_length = fields[0].length;
results[0].length = &length;
status = mysql_stmt_bind_result(statement, results);
if (status!=0) {
mysql_free_result(result);
mysql_stmt_close(statement);
return;
}
while (true) {
status = mysql_stmt_fetch(statement);
if (status!=0)
break;
CString rowid = CString(rowidData, length);
MYSQL_STMT *statement2 = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement2, "DELETE FROM `messages` WHERE `rowid`=?", 38);
if (status!=0)
continue;
MYSQL_BIND bind2[1];
memset(bind, 0, sizeof(bind));
bind2[0].buffer_type = MYSQL_TYPE_STRING;
bind2[0].buffer = (void*)rowid.c_str();
bind2[0].buffer_length = rowid.length();
bind2[0].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement2, bind2);
if (status!=0) {
mysql_stmt_close(statement2);
continue;
}
mysql_stmt_execute(statement2);
mysql_stmt_close(statement2);
}
mysql_free_result(result);
mysql_stmt_close(statement);
}
}
}
void SetSetting(const CString& name, const CString& value) {
if (!databaseConnected)
return;
bool exists = false;
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "SELECT `value` FROM `settings` WHERE `name`=?", 45);
if (status==0) {
MYSQL_BIND bind[1];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)name.c_str();
bind[0].buffer_length = name.length();
bind[0].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status==0) {
mysql_stmt_execute(statement);
status = mysql_stmt_fetch(statement);
if (status==0) {
exists = true;
}
}
mysql_stmt_close(statement);
}
if (exists) {
statement = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement, "UPDATE `settings` SET `value`=? WHERE `name`=?", 46);
if (status!=0)
return;
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)value.c_str();
bind[0].buffer_length = value.length();
bind[0].is_null = (my_bool *)0;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void*)name.c_str();
bind[1].buffer_length = name.length();
bind[1].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return;
}
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
} else {
statement = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement, "INSERT INTO `settings` (`name`,`value`) VALUES (?,?)", 52);
if (status!=0)
return;
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)name.c_str();
bind[0].buffer_length = name.length();
bind[0].is_null = (my_bool *)0;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void*)value.c_str();
bind[1].buffer_length = value.length();
bind[1].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return;
}
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
}
}
CString GetSetting(const CString& name) {
CString stringValue;
if (!databaseConnected)
return stringValue;
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "SELECT `value` FROM `settings` WHERE `name`=?", 45);
if (status!=0)
return stringValue;
MYSQL_BIND bind[1];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)name.c_str();
bind[0].buffer_length = name.length();
bind[0].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return stringValue;
}
status = mysql_stmt_execute(statement);
if (status!=0) {
mysql_stmt_close(statement);
return stringValue;
}
MYSQL_RES *result = mysql_stmt_result_metadata(statement);
unsigned int dataCount = mysql_num_fields(result);
if (dataCount!=1) {
mysql_free_result(result);
mysql_stmt_close(statement);
std::cout << "LogMySQL: Settings are only suppose to return 1 as the field count. We received the count of " << dataCount << " fields.\n";
return stringValue;
}
MYSQL_FIELD *fields = mysql_fetch_fields(result);
unsigned long length;
char valueData[fields[0].length];
MYSQL_BIND results[1];
memset(results, 0, sizeof(results));
results[0].buffer_type = MYSQL_TYPE_STRING;
results[0].buffer = (void *)valueData;
results[0].buffer_length = fields[0].length;
results[0].length = &length;
status = mysql_stmt_bind_result(statement, results);
if (status!=0) {
mysql_free_result(result);
mysql_stmt_close(statement);
return stringValue;
}
while (true) {
status = mysql_stmt_fetch(statement);
if (status!=0)
break;
stringValue = CString(valueData, length);
break;
}
mysql_free_result(result);
mysql_stmt_close(statement);
return stringValue;
}
void UpdateIgnoreLists() {
if (!databaseConnected)
return;
nickIgnoreList.clear();
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "SELECT `target` FROM `ignorelist` WHERE `type`='nick'", 53);
if (status==0) {
status = mysql_stmt_execute(statement);
if (status==0) {
MYSQL_RES *result = mysql_stmt_result_metadata(statement);
unsigned int dataCount = mysql_num_fields(result);
if (dataCount==1) {
MYSQL_FIELD *fields = mysql_fetch_fields(result);
unsigned long length;
char targetData[fields[0].length];
MYSQL_BIND results[1];
memset(results, 0, sizeof(results));
results[0].buffer_type = MYSQL_TYPE_STRING;
results[0].buffer = (void *)targetData;
results[0].buffer_length = fields[0].length;
results[0].length = &length;
status = mysql_stmt_bind_result(statement, results);
if (status==0) {
while (true) {
status = mysql_stmt_fetch(statement);
if (status!=0)
break;
CString ignore = CString(targetData, length);
nickIgnoreList.push_back(ignore);
break;
}
}
}
mysql_free_result(result);
}
mysql_stmt_close(statement);
}
chanIgnoreList.clear();
statement = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement, "SELECT `target` FROM `ignorelist` WHERE `type`='chan'", 53);
if (status==0) {
status = mysql_stmt_execute(statement);
if (status==0) {
MYSQL_RES *result = mysql_stmt_result_metadata(statement);
unsigned int dataCount = mysql_num_fields(result);
if (dataCount==1) {
MYSQL_FIELD *fields = mysql_fetch_fields(result);
unsigned long length;
char targetData[fields[0].length];
MYSQL_BIND results[1];
memset(results, 0, sizeof(results));
results[0].buffer_type = MYSQL_TYPE_STRING;
results[0].buffer = (void *)targetData;
results[0].buffer_length = fields[0].length;
results[0].length = &length;
status = mysql_stmt_bind_result(statement, results);
if (status==0) {
while (true) {
status = mysql_stmt_fetch(statement);
if (status!=0)
break;
CString ignore = CString(targetData, length);
chanIgnoreList.push_back(ignore);
break;
}
}
}
mysql_free_result(result);
}
mysql_stmt_close(statement);
}
}
bool AddIgnore(const CString& type, const CString& target) {
if (!IgnoreExists(type, target)) {
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "INSERT INTO `ignorelist` (`type`, `target`) VALUES (?,?)", 56);
if (status!=0)
return false;
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)type.c_str();
bind[0].buffer_length = type.length();
bind[0].is_null = (my_bool *)0;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void*)target.c_str();
bind[1].buffer_length = target.length();
bind[1].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return false;
}
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
if (type.Equals("nick"))
nickIgnoreList.push_back(target);
else if (type.Equals("chan"))
chanIgnoreList.push_back(target);
return true;
}
return false;
}
bool RemoveIgnore(const CString& type, const CString& target) {
if (IgnoreExists(type, target)) {
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = mysql_stmt_prepare(statement, "DELETE FROM `ignorelist` WHERE `type`=? AND `target`=?", 54);
if (status!=0)
return false;
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)type.c_str();
bind[0].buffer_length = type.length();
bind[0].is_null = (my_bool *)0;
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void*)target.c_str();
bind[1].buffer_length = target.length();
bind[1].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
mysql_stmt_close(statement);
return false;
}
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
UpdateIgnoreLists();
return true;
}
return false;
}
bool IgnoreExists(const CString& type, const CString& target) {
if (type.Equals("nick")) {
for (std::vector<CString>::iterator it=nickIgnoreList.begin(); it<nickIgnoreList.end(); it++) {
if (target.Equals(*it))
return true;
}
} else if (type.Equals("chan")) {
for (std::vector<CString>::iterator it=chanIgnoreList.begin(); it<chanIgnoreList.end(); it++) {
if (target.Equals(*it))
return true;
}
}
return false;
}
bool IsIgnored(const CString& type, const CString& target) {
if (type.Equals("nick")) {
for (std::vector<CString>::iterator it=nickIgnoreList.begin(); it<nickIgnoreList.end(); it++) {
if (target.WildCmp(*it))
return true;
}
} else if (type.Equals("chan")) {
for (std::vector<CString>::iterator it=chanIgnoreList.begin(); it<chanIgnoreList.end(); it++) {
if (target.WildCmp(*it))
return true;
}
}
return false;
}
//Server stuff
virtual void OnIRCDisconnected() {
if (connected) {
connected = false;
if (logLevel>=3) {
AddMessage("","","DISCONNECT","");
}
}
}
virtual void OnIRCConnected() {
if (!connected) {
connected = true;
if (logLevel>=3) {
AddMessage("","","CONNECT","");
}
}
}
//User stuff
virtual EModRet OnUserAction(CString& sTarget, CString& sMessage) {
if (logLevel>=4) {
AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"ACTION",sMessage);
}
return CONTINUE;
}
virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage) {
if (logLevel>=4) {
AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"PRIVMSG",sMessage);
}
return CONTINUE;
}
virtual EModRet OnUserNotice(CString& sTarget, CString& sMessage) {
if (logLevel>=4) {
AddMessage(sTarget,m_pNetwork->GetIRCNick().GetNickMask(),"NOTICE",sMessage);
}
return CONTINUE;
}
virtual EModRet OnUserJoin(CString& sChannel, CString& sKey) {
if (logLevel>=4) {
AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"JOIN","");
}
return CONTINUE;
}
virtual EModRet OnUserPart(CString& sChannel, CString& sMessage) {
if (logLevel>=4) {
AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"PART",sMessage);
}
return CONTINUE;
}
virtual EModRet OnUserTopic(CString& sChannel, CString& sTopic) {
if (logLevel>=4) {
AddMessage(sChannel,m_pNetwork->GetIRCNick().GetNickMask(),"TOPIC",sTopic);
}
return CONTINUE;
}
//Other stuff
virtual void OnRawMode(const CNick& OpNick, CChan& Channel, const CString& sModes, const CString& sArgs) {
if (logLevel>=3) {
AddMessage(Channel.GetName(),OpNick.GetNickMask(),"MODE",sModes+" "+sArgs);
}
}
virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) {
if (logLevel>=2) {
AddMessage(Channel.GetName(),OpNick.GetNickMask(),"KICK",sKickedNick+" "+sMessage);
}
}
virtual void OnQuit(const CNick& Nick, const CString& sMessage, const std::vector<CChan*>& vChans) {
if (logLevel>=2) {
std::vector<CChan*>::const_iterator it;
for (it=vChans.begin(); it!=vChans.end(); it++) {
CChan& channel = **it;
AddMessage(channel.GetName(),Nick.GetNickMask(),"QUIT",sMessage);
}
}
}
virtual void OnJoin(const CNick& Nick, CChan& Channel) {
if (logLevel>=2) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"JOIN","");
}
}
virtual void OnPart(const CNick& Nick, CChan& Channel, const CString& sMessage) {
if (logLevel>=2) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"PART",sMessage);
}
}
virtual void OnNick(const CNick& OldNick, const CString& sNewNick, const std::vector<CChan*>& vChans) {
if (logLevel>=2) {
std::vector<CChan*>::const_iterator it;
for (it=vChans.begin(); it!=vChans.end(); it++) {
CChan& channel = **it;
AddMessage(channel.GetName(),OldNick.GetNickMask(),"NICK",sNewNick);
}
}
}
virtual EModRet OnPrivAction(CNick& Nick, CString& sMessage) {
if (logLevel>=0) {
AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"ACTION",sMessage);
}
return CONTINUE;
}
virtual EModRet OnChanAction(CNick& Nick, CChan& Channel, CString& sMessage) {
if (logLevel==0) {
if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str()))
AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage);
} else if (logLevel>=1) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"ACTION",sMessage);
}
return CONTINUE;
}
virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage) {
if (logLevel>=0) {
AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"PRIVMSG",sMessage);
}
return CONTINUE;
}
virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) {
if (logLevel==0) {
if (strcasestr(sMessage.c_str(),m_pNetwork->GetCurNick().c_str()))
AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage);
} else if (logLevel>=1) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"PRIVMSG",sMessage);
}
return CONTINUE;
}
virtual EModRet OnPrivNotice(CNick& Nick, CString& sMessage) {
if (logLevel>=2) {
AddMessage(m_pNetwork->GetCurNick(),Nick.GetNickMask(),"NOTICE",sMessage);
}
return CONTINUE;
}
virtual EModRet OnChanNotice(CNick& Nick, CChan& Channel, CString& sMessage) {
if (logLevel>=2) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"NOTICE",sMessage);
}
return CONTINUE;
}
virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) {
if (logLevel>=2) {
AddMessage(Channel.GetName(),Nick.GetNickMask(),"TOPIC",sTopic);
}
return CONTINUE;
}
virtual EModRet OnRaw(CString& sLine) {
if (logLevel>=3) {
CString sCmd = sLine.Token(1);
if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) {
unsigned int uRaw = sCmd.ToUInt();
if (uRaw!=10 && uRaw!=305 && uRaw!=306 && uRaw!=324 && uRaw!=329 && uRaw<331 && uRaw>333 && uRaw!=352 && uRaw!=353 && uRaw!=366 && uRaw!=432 && uRaw!=433 && uRaw!=437 && uRaw!=451 && uRaw!=670) {
AddMessage("",sLine.Token(0),sCmd,sLine.Token(2, true));
}
}
}
return CONTINUE;
}
//Client Connection
virtual void OnClientLogin() {
SetSetting("clientConnected",GetUNIXTime());
if (autoReplay)
Replay();
}
virtual void OnClientDisconnect() {
bool track = true;
for (std::vector<CClient *>::iterator it=doNotTrackClient.begin(); it<doNotTrackClient.end(); it++) {
if (*it==m_pClient) {
doNotTrackClient.erase(it);
track = false;
break;
}
}
if (track)
SetSetting("clientDisconnected",GetUNIXTime());
}
void Replay() {
Replay(0);
}
void Replay(int replayCount) {
if (!databaseConnected)
return;
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Buffer Playback...");
CString lastOnline = GetSetting("clientDisconnected");
MYSQL_STMT *statement = mysql_stmt_init(database);
int status = 0;
if (replayCount!=0) {
status = mysql_stmt_prepare(statement, "SELECT * FROM (SELECT * FROM `messages` ORDER BY `time` DESC LIMIT ?) ORDER BY `time`", 85);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
return;
}
MYSQL_BIND bind[1];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (void*)&replayCount;
bind[0].buffer_length = 0;
bind[0].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
mysql_stmt_close(statement);
return;
}
} else if (!replayAll && !lastOnline.empty()) {
status = mysql_stmt_prepare(statement, "SELECT * FROM `messages` WHERE `time`>? ORDER BY `time`", 55);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
return;
}
MYSQL_BIND bind[1];
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void*)lastOnline.c_str();
bind[0].buffer_length = lastOnline.length();
bind[0].is_null = (my_bool *)0;
status = mysql_stmt_bind_param(statement, bind);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
mysql_stmt_close(statement);
return;
}
} else {
status = mysql_stmt_prepare(statement, "SELECT * FROM `messages` ORDER BY `time`", 40);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
return;
}
}
status = mysql_stmt_execute(statement);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
mysql_stmt_close(statement);
return;
}
MYSQL_RES *result = mysql_stmt_result_metadata(statement);
unsigned int columnCount = mysql_num_fields(result);
MYSQL_FIELD *fields = mysql_fetch_fields(result);
MYSQL_BIND results[columnCount];
memset(results, 0, sizeof(results));
std::map<unsigned int,char *> columnData;
std::map<unsigned int,unsigned long> columnLength;
std::map<unsigned int,bool> columnNull;
std::map<unsigned int,CString> columns;
for (unsigned int i=0; i<columnCount; i++) {
columns[i] = CString(fields[i].name);
if (columns[i].empty()) {
char count[20];
snprintf(count, sizeof(count), "%u", i);
columns[i] = count;
}
columnData[i] = (char *)malloc(fields[i].length);
results[i].buffer_type = MYSQL_TYPE_STRING;
results[i].buffer = (void *)columnData[i];
results[i].buffer_length = fields[i].length;
results[i].length = &columnLength[i];
results[i].is_null = (my_bool *)&columnNull[i];
}
status = mysql_stmt_bind_result(statement, results);
if (status!=0) {
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback failed due to sql problem.");
mysql_free_result(result);
mysql_stmt_close(statement);
return;
}
time_t now = time(NULL);
while (true) {
status = mysql_stmt_fetch(statement);
if (status!=0)
break;
std::map<CString,CString> data;
for (unsigned int i=0; i<columnCount; i++) {
if (columnNull[i]) {
data[columns[i]] = "";
} else {
data[columns[i]] = CString(columnData[i], columnLength[i]);
}
}
time_t unixTime = (time_t)strtol(data["time"].c_str(),NULL,10);
CString timeString;
CString prefixString;
if (m_pClient->HasServerTime()) {
char timeStr[20];
snprintf(timeStr, sizeof(timeStr), "%lu", unixTime);
prefixString = "@t=";
prefixString += timeStr;
prefixString += " ";
} else {
struct tm *timeinfo = localtime(&unixTime);
char timeStr[20];
if (((long)now-86000)<(long)time)
strftime(timeStr, sizeof(timeStr), "%I:%M:%S %p", timeinfo);
else
strftime(timeStr, sizeof(timeStr), "%m/%d/%y %I:%M:%S %p", timeinfo);
timeString = "[";
timeString += timeStr;
timeString += "] ";
}
if (data["type"].Equals("DISCONNECT")) {
PutUser(prefixString+":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :"+timeString+"Server Disconnected");
} else if (data["type"].Equals("CONNECT")) {
PutUser(prefixString+":*LogSQLite!LogSQLite@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :"+timeString+"Server Connected");
} else if (data["type"].Equals("JOIN")) {
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"joined");
} else if (data["type"].Equals("PART")) {
if (data["message"].Equals(""))
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"parted");
else
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"parted: "+data["message"]);
} else if (data["type"].Equals("TOPIC")) {
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"changed topic: "+data["message"]);
} else if (data["type"].Equals("QUIT")) {
if (data["message"].Equals(""))
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"quit");
else
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"quit: "+data["message"]);
} else if (data["type"].Equals("MODE")) {
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"changed mode: "+data["message"]);
} else if (data["type"].Equals("NICK")) {
PutUser(prefixString+":"+data["nick"]+" NOTICE "+data["target"]+" :"+timeString+"changed nick: "+data["message"]);
} else if (data["type"].Equals("ACTION")) {
PutUser(prefixString+":"+data["nick"]+" PRIVMSG "+data["target"]+" :\001ACTION "+timeString+data["message"]+"\001");
} else {
PutUser(prefixString+":"+data["nick"]+" "+data["type"]+" "+data["target"]+" :"+timeString+data["message"]);
}
}
for (unsigned int i=0; i<columnCount; i++) {
free(columnData[i]);
}
mysql_free_result(result);
mysql_stmt_close(statement);
if (logLimit==1) {
statement = mysql_stmt_init(database);
status = mysql_stmt_prepare(statement, "DELETE FROM `messages`", 22);
if (status==0) {
mysql_stmt_execute(statement);
mysql_stmt_close(statement);
}
}
PutUser(":*LogMySQL!LogMySQL@znc.in NOTICE "+m_pNetwork->GetIRCNick().GetNickMask()+" :Playback Complete.");
}
private:
MYSQL *database;
bool databaseConnected;
bool connected;
bool replayAll;
bool autoReplay;
unsigned long logLimit;
int logLevel;
std::vector<CString> nickIgnoreList;
std::vector<CString> chanIgnoreList;
std::vector<CClient *> doNotTrackClient;
};
template<> void TModInfo<CLogMySQL>(CModInfo& Info) {
Info.SetWikiPage("logmysql");
}
NETWORKMODULEDEFS(CLogMySQL, "Add logging to MySQL")