diff options
author | DukePyrolator <DukePyrolator@anope.org> | 2013-10-26 18:31:50 +0200 |
---|---|---|
committer | DukePyrolator <DukePyrolator@anope.org> | 2013-10-26 18:42:58 +0200 |
commit | b5af310f8aacd168ba4b9bee97374558e11c57fd (patch) | |
tree | 10be038b2b6fd717ae59b8cdb5106d76b896ef72 | |
parent | 2cfc97053faba7582cd57701900de78ebacb12ce (diff) |
added the irc2sql gateway module
-rw-r--r-- | data/example.conf | 13 | ||||
-rw-r--r-- | data/irc2sql.example.conf | 106 | ||||
-rw-r--r-- | data/stats.standalone.example.conf | 522 | ||||
-rw-r--r-- | modules/stats/irc2sql/CMakeLists.txt | 2 | ||||
-rw-r--r-- | modules/stats/irc2sql/irc2sql.cpp | 239 | ||||
-rw-r--r-- | modules/stats/irc2sql/irc2sql.h | 74 | ||||
-rw-r--r-- | modules/stats/irc2sql/tables.cpp | 311 | ||||
-rw-r--r-- | modules/stats/irc2sql/utils.cpp | 61 | ||||
-rw-r--r-- | src/tools/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/tools/geoipupdate.sh | 78 |
10 files changed, 1409 insertions, 0 deletions
diff --git a/data/example.conf b/data/example.conf index 68a3a6583..6d73eb021 100644 --- a/data/example.conf +++ b/data/example.conf @@ -1275,3 +1275,16 @@ include type = "file" name = "chanstats.example.conf" } + +/* + * IRC2SQL Gateway + * This module collects data about users, channels and servers. It doesn't build stats + * itself, however, it gives you the database, it's up to you how you use it. + * + * Requires a MySQL Database and MySQL version 5.5 or higher + */ +#include +{ + type = "file" + name = "irc2sql.example.conf" +} diff --git a/data/irc2sql.example.conf b/data/irc2sql.example.conf new file mode 100644 index 000000000..8872b8817 --- /dev/null +++ b/data/irc2sql.example.conf @@ -0,0 +1,106 @@ +/* + * Example configuration file for the irc2sql gateway + * + */ + +module +{ + name = "irc2sql" + + /* + * The name of this engine. + * This must match with the name in the mysql{ } block + */ + engine ="mysql/main" + + /* + * An optional prefix to prepended to the name of each created table. + * Do not use the same prefix for other programs. + */ + + prefix = "anope_" + + /* + * GeoIP - Automagically add a users geoip to the user table. + * Tables are created by irc2sql, you have to run the + * geoipupdate script after you started anope to download + * and import the actual geoip database + * + */ + + /* + * Enable GeoIP Lookup + */ + GeoIPLookup = yes + + /* + * Chose between the smaller 'country' or the bigger 'city' database. + * + */ + GeoIPDatabase = "country" + + /* + * The name of the client that should send the CTCP VERSION requests. + * It must already exist or must be defined in the following service block. + */ + client = "StatServ" + + /* + * Get the CTCP version from users + * The users connecting to the network will receive a CTCP VERSION + * request from the above configured services client + */ + ctcpuser = "yes" + + /* + * Send out CTCP VERSION requests to users during burst. + * Disable this if you restart Anope often and don't want to + * annoy your users. + */ + ctcpeob = "yes" +} + +service +{ + /* + * The name of the StatServ client. + */ + nick = "StatServ" + + /* + * The username of the StatServ client. + */ + user = "StatServ" + + /* + * The hostname of the StatServ client. + */ + host = "services.host" + + /* + * The realname of the StatServ client. + */ + gecos = "Statistical Service" + + /* + * The modes this client should use. + * Do not modify this unless you know what you are doing. + * + * These modes are very IRCd specific. If left commented, sane defaults + * are used based on what protocol module you have loaded. + * + * Note that setting this option incorrectly could potentially BREAK some, if + * not all, usefulness of the client. We will not support you if this client is + * unable to do certain things if this option is enabled. + */ + #modes = "+o" + + /* + * An optional comma separated list of channels this service should join. Outside + * of log channels this is not very useful, as the service will just idle in the + * specified channels, and will not accept any types of commands. + * + * Prefixes may be given to the channels in the form of mode characters or prefix symbols. + */ + #channels = "@#services,#mychan" +} diff --git a/data/stats.standalone.example.conf b/data/stats.standalone.example.conf new file mode 100644 index 000000000..45526f389 --- /dev/null +++ b/data/stats.standalone.example.conf @@ -0,0 +1,522 @@ +/* + * Example configuration file for Services. After making the appropriate + * changes to this file, place it in the Services conf directory (as + * specified in the "configure" script, default /home/username/services/conf) + * under the name "services.conf". + * + * The format of this file is fairly simple: three types of comments are supported: + * - All text after a '#' on a line is ignored, as in shell scripting + * - All text after '//' on a line is ignored, as in C++ + * - A block of text like this one is ignored, as in C + * + * Outside of comments, there are three structures: blocks, keys, and values. + * + * A block is a named container, which contains a number of key to value pairs + * - you may think of this as an array. + * + * A block is created like so: + * foobar + * { + * moo = "cow" + * foo = bar + * } + * + * Note that nameless blocks are allowed and are often used with comments to allow + * easily commenting an entire block, for example: + * #foobar + * { + * moo = "cow" + * foo = bar + * } + * is an entirely commented block. + * + * Keys are case insensitive. Values depend on what key - generally, information is + * given in the key comment. The quoting of values (and most other syntax) is quite + * flexible, however, please do not forget to quote your strings: + * + * "This is a parameter string with spaces in it" + * + * If you need to include a double quote inside a quoted string, precede it + * by a backslash: + * + * "This string has \"double quotes\" in it" + * + * Time parameters can be specified either as an integer representing a + * number of seconds (e.g. "3600" = 1 hour), or as an integer with a unit + * specifier: "s" = seconds, "m" = minutes, "h" = hours, "d" = days. + * Combinations (such as "1h30m") are not permitted. Examples (all of which + * represent the same length of time, one day): + * + * "86400", "86400s", "1440m", "24h", "1d" + * + * In the documentation for each directive, one of the following will be + * included to indicate whether an option is required: + * + * [REQUIRED] + * Indicates a directive which must be given. Without it, Services will + * not start. + * + * [RECOMMENDED] + * Indicates a directive which may be omitted, but omitting it may cause + * undesirable side effects. + * + * [OPTIONAL] + * Indicates a directive which is optional. If not given, the feature + * will typically be disabled. If this is not the case, more + * information will be given in the documentation. + * + * [DISCOURAGED] + * Indicates a directive which may cause undesirable side effects if + * specified. + * + * [DEPRECATED] + * Indicates a directive which will disappear in a future version of + * Services, usually because its functionality has been either + * superseded by that of other directives or incorporated into the main + * program. + */ + +/* + * [OPTIONAL] Defines + * + * You can define values to other values, which can be used to easily change + * every value in the configuration. For example, use: + * + * define + * { + * name = "ChanServ" + * value = "ChannelServ" + * } + * + * To replace every occurrence of ChanServ with ChannelServ in the configuration file, + * and in every included configuration file (such as chanserv.example.conf). + */ + +/* + * The services.host define is used in multiple different locations throughout the + * configuration for services clients hostnames. + */ +define +{ + name = "services.host" + value = "services.localhost.net" +} + +/* + * [OPTIONAL] Additional Includes + * + * You can include additional configuration files here. + * You may also include executable files, which will be executed and + * the output from it will be included into your configuration. + */ + +#include +{ + type = "file" + name = "some.conf" +} + +#include +{ + type = "executable" + name = "/usr/bin/wget -q -O - http://some.misconfigured.network.com/services.conf" +} + +/* + * [REQUIRED] IRCd Config + * + * This section is used to set up Anope to connect to your IRC network. + * This section can be included multiple times, and Anope will attempt to + * connect to each server until it finally connects. + * + * Each uplink IRCd should have a corresponding configuration to allow Services + * to link to it. + * + * An example configuration for InspIRCd that is compatible with the below uplink + * and serverinfo configuration would look like: + * + * <link name="services.localhost.net" + * ipaddr="127.0.0.1" + * port="7000" + * sendpass="mypassword" + * recvpass="mypassword"> + * <uline server="services.localhost.net" silent="yes"> + * <bind address="127.0.0.1" port="7000" type="servers"> + * + * An example configuration for UnrealIRCd that is compatible with the below uplink + * and serverinfo configuration would look like: + * + * link services.localhost.net + * { + * username *; + * hostname *; + * bind-ip "127.0.0.1"; + * port 7000; + * hub *; + * password-connect "mypassword"; + * password-receive "mypassword"; + * class servers; + * }; + * ulines { services.localhost.net; }; + * listen 127.0.0.1:7000; + */ +uplink +{ + /* + * The IP or hostname of the IRC server you wish to connect Services to. + * Usually, you will want to connect Services over 127.0.0.1 (aka localhost). + * + * NOTE: On some shell providers, this will not be an option. + */ + host = "127.0.0.1" + + /* + * Enable if Services should connect using IPv6. + */ + ipv6 = no + + /* + * Enable if Services should connect using SSL. + * You must have m_ssl loaded for this to work. + */ + ssl = no + + /* + * The port to connect to. + * The IRCd *MUST* be configured to listen on this port, and to accept + * server connections. + * + * Refer to your IRCd documentation for how this is to be done. + */ + port = 7000 + + /* + * The password to send to the IRC server for authentication. + * This must match the link block on your IRCd. + * + * Refer to your IRCd documentation for more information on link blocks. + */ + password = "mypassword" +} + +/* + * [REQUIRED] Server Information + * + * This section contains information about the Services server. + */ +serverinfo +{ + /* + * The hostname that Services will be seen as, it must have no conflicts with any + * other server names on the rest of your IRC network. Note that it does not have + * to be an existing hostname, just one that isn't on your network already. + */ + name = "stats.localhost.net" + + /* + * The text which should appear as the server's information in /whois and similar + * queries. + */ + description = "Stats for IRC Networks" + + /* + * The local address that Services will bind to before connecting to the remote + * server. This may be useful for multihomed hosts. If omitted, Services will let + * the Operating System choose the local address. This directive is optional. + * + * If you don't know what this means or don't need to use it, just leave this + * directive commented out. + */ + #localhost = "nowhere." + + /* + * What Server ID to use for this connection? + * Note: This should *ONLY* be used for TS6/P10 IRCds. Refer to your IRCd documentation + * to see if this is needed. + */ + #id = "00A" + + /* + * The filename containing the Services process ID. The path is relative to the + * services root directory. + */ + pid = "data/services.pid" + + /* + * The filename containing the Message of the Day. The path is relative to the + * services root directory. + */ + motd = "conf/services.motd" +} + +/* + * [REQUIRED] Protocol module + * + * This directive tells Anope which IRCd Protocol to speak when connecting. + * You MUST modify this to match the IRCd you run. + * + * Supported: + * - bahamut + * - charybdis + * - hybrid + * - inspircd12 + * - inspircd20 + * - ngircd + * - plexus + * - ratbox + * - unreal + */ +module +{ + name = "inspircd20" +} + +/* + * [REQUIRED] Network Information + * + * This section contains information about the IRC network that Services will be + * connecting to. + */ +networkinfo +{ + /* + * This is the name of the network that Services will be running on. + */ + networkname = "LocalNet" + + /* + * Set this to the maximum allowed nick length on your network. + * Be sure to set this correctly, as setting this wrong can result in + * Services being disconnected from the network. + */ + nicklen = 31 + + /* Set this to the maximum allowed ident length on your network. + * Be sure to set this correctly, as setting this wrong can result in + * Services being disconnected from the network. + */ + userlen = 10 + + /* Set this to the maximum allowed hostname length on your network. + * Be sure to set this correctly, as setting this wrong can result in + * Services being disconnected from the network. + */ + hostlen = 64 + + /* Set this to the maximum allowed channel length on your network. + */ + chanlen = 32 + + /* The maximum number of list modes settable on a channel (such as b, e, I). + * Comment out or set to 0 to disable. + */ + modelistsize = 100 + + /* + * The characters allowed in hostnames. This is used for validating hostnames given + * to services, such as BotServ bot hostnames and user vhosts. Changing this is not + * recommended unless you know for sure your IRCd supports whatever characters you are + * wanting to use. Telling services to set a vHost containing characters your IRCd + * disallows could potentially break the IRCd and/or Services. + * + * It is recommended you DON'T change this. + */ + vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-" + + /* + * If set to true, allows vHosts to not contain dots (.). + * Newer IRCds generally do not have a problem with this, but the same warning as + * vhost_chars applies. + * + * It is recommended you DON'T change this. + */ + allow_undotted_vhosts = false + + /* + * The characters that are not allowed to be at the very beginning or very ending + * of a vHost. The same warning as vhost_chars applies. + * + * It is recommended you DON'T change this. + */ + disallow_start_or_end = ".-" +} + +/* + * [REQUIRED] Services Options + * + * This section contains various options which determine how Services will operate. + */ +options +{ + /* + * On Linux/UNIX systems Anope can setuid and setgid to this user and group + * after starting up. This is useful if Anope has to bind to privileged ports + */ + #user = "anope" + #group = "anope" + + /* + * The case mapping used by services. This must be set to a valid locale name + * installed on your machine. Services use this case map to compare, with + * case insensitivity, things such as nick names, channel names, etc. + * + * We provide two special casemaps shipped with Anope, ascii and rfc1459. + * + * This value should be set to what your IRCd uses, which is probably rfc1459, + * however Anope has always used ascii for comparison, so the default is ascii. + * + * Changing this value once set is not recommended. + */ + casemap = "ascii" + + /* + * Sets the timeout period for reading from the uplink. + */ + readtimeout = 5s + + /* + * Sets the interval between sending warning messages for program errors via + * WALLOPS/GLOBOPS. + */ + warningtimeout = 4h + + /* + * If set, Services will only show /stats o to IRC Operators. This directive + * is optional. + */ + #hidestatso = yes + + /* + * A space-separated list of ulined servers on your network, it is assumed that + * the servers in this list are allowed to set channel modes and Services will + * not attempt to reverse their mode changes. + * + * WARNING: Do NOT put your normal IRC user servers in this directive. + * + * This directive is optional. + */ + #ulineservers = "services.your.network" + + /* + * How long to wait between connection retries with the uplink(s). + */ + retrywait = 60s +} + +/* + * [RECOMMENDED] Logging Configuration + * + * This section is used for configuring what is logged and where it is logged to. + * You may have multiple log blocks if you wish. Remember to properly secure any + * channels you choose to have Anope log to! + */ +log +{ + /* + * Target(s) to log to, which may be one of the following: + * - a channel name + * - a filename + * - globops + */ + target = "stats.log" + + /* Log to both services.log and the channel #services + * + * Note that some older IRCds, such as Ratbox, require services to be in the + * log channel to be able to message it. To do this, configure service:channels to + * join your logging channel. + */ + #target = "stats.log #services" + + /* + * The source(s) to only accept log messages from. Leave commented to allow all sources. + * This can be a users name, a channel name, one of our clients (eg, OperServ), or a server name. + */ + #source = "" + + /* + * The bot used to log generic messages which have no predefined sender if there + * is a channel in the target directive. + */ + bot = "Global" + + /* + * The number of days to keep logfiles, only useful if you are logging to a file. + * Set to 0 to never delete old logfiles. + * + * Note that Anope must run 24 hours a day for this feature to work correctly. + */ + logage = 7 + + /* + * What types of log messages should be logged by this block. There are nine general categories: + * + * servers - Server actions, linking, squitting, etc. + * channels - Actions in channels such as joins, parts, kicks, etc. + * users - User actions such as connecting, disconnecting, changing name, etc. + * other - All other messages without a category. + * rawio - Logs raw input and output from services + * debug - Debug messages (log files can become VERY large from this). + * + * These options determine what messages from the categories should be logged. Wildcards are accepted, and + * you can also negate values with a ~. For example, "~operserv/akill operserv/*" would log all operserv + * messages except for operserv/akill. Note that processing stops at the first matching option, which + * means "* ~operserv/*" would log everything because * matches everything. + * + * Valid server options are: + * connect, quit, sync, squit + * + * Valid channel options are: + * create, destroy, join, part, kick, leave, mode + * + * Valid user options are: + * connect, disconnect, quit, nick, ident, host, mode, maxusers, oper + * + * Rawio and debug are simple yes/no answers, there are no types for them. + * + * Note that modules may add their own values to these options. + */ + servers = "*" + #channels = "~mode *" + users = "connect disconnect nick" + other = "*" + rawio = no + debug = no +} + +/* + * [REQUIRED] MySQL Database configuration. + * + * m_mysql + * + * This module allows other modules to use MySQL. + */ +module +{ + name = "m_mysql" + + mysql + { + /* The name of this service. */ + name = "mysql/main" + database = "anope" + server = "127.0.0.1" + username = "anope" + password = "mypassword" + port = 3306 + } +} + +/* + * IRC2SQL Gateway + * This module collects data about users, channels and servers. It doesn't build stats + * itself, however, it gives you the database, it's up to you how you use it. + * + * Requires a MySQL Database and MySQL version 5.5 or higher + */ +include +{ + type = "file" + name = "irc2sql.example.conf" +} + diff --git a/modules/stats/irc2sql/CMakeLists.txt b/modules/stats/irc2sql/CMakeLists.txt new file mode 100644 index 000000000..e68a5b63e --- /dev/null +++ b/modules/stats/irc2sql/CMakeLists.txt @@ -0,0 +1,2 @@ +#build_subdir(${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/modules/stats/irc2sql/irc2sql.cpp b/modules/stats/irc2sql/irc2sql.cpp new file mode 100644 index 000000000..e47abf34a --- /dev/null +++ b/modules/stats/irc2sql/irc2sql.cpp @@ -0,0 +1,239 @@ +#include "irc2sql.h" + +void IRC2SQL::OnShutdown() anope_override +{ + // TODO: test if we really have to use blocking query here + // (sometimes m_mysql get unloaded before the other thread executed all queries) + SQL::Result r = this->sql->RunQuery(SQL::Query("CALL " + prefix + "OnShutdown()")); + quitting = true; +} + +void IRC2SQL::OnReload(Configuration::Conf *conf) anope_override +{ + Configuration::Block *block = Config->GetModule(this); + prefix = block->Get<const Anope::string>("prefix", "anope_"); + UseGeoIP = block->Get<bool>("GeoIPLookup", "no"); + GeoIPDB = block->Get<const Anope::string>("GeoIPDatabase", "country"); + ctcpuser = block->Get<bool>("ctcpuser", "no"); + ctcpeob = block->Get<bool>("ctcpeob", "yes"); + Anope::string engine = block->Get<const Anope::string>("engine"); + this->sql = ServiceReference<SQL::Provider>("SQL::Provider", engine); + if (sql) + this->CheckTables(); + else + Log() << "IRC2SQL: no database connection to " << engine; + + const Anope::string &snick = block->Get<const Anope::string>("client"); + if (snick.empty()) + throw ConfigException(Module::name + ": <client> must be defined"); + StatServ = BotInfo::Find(snick, true); + if (!StatServ) + throw ConfigException(Module::name + ": no bot named " + snick); +} + +void IRC2SQL::OnNewServer(Server *server) anope_override +{ + query = "INSERT DELAYED INTO `" + prefix + "server` (name, hops, comment, link_time, online, ulined) " + "VALUES (@name@, @hops@, @comment@, now(), 'Y', @ulined@) " + "ON DUPLICATE KEY UPDATE name=VALUES(name), hops=VALUES(hops), comment=VALUES(comment), " + "link_time=VALUES(link_time), online=VALUES(online), ulined=(ulined)"; + query.SetValue("name", server->GetName()); + query.SetValue("hops", server->GetHops()); + query.SetValue("comment", server->GetDescription()); + query.SetValue("ulined", server->IsULined() ? "Y" : "N"); + this->RunQuery(query); +} + +void IRC2SQL::OnServerQuit(Server *server) anope_override +{ + if (quitting) + return; + + query = "UPDATE `" + prefix + "server` " + "SET currentusers = 0, online = 'N', split_time = now()"; + query.SetValue("name", server->GetName()); + this->RunQuery(query); +} + +void IRC2SQL::OnUserConnect(User *u, bool &exempt) anope_override +{ + if (!introduced_myself) + { + this->OnNewServer(Me); + introduced_myself = true; + } + + query = "CALL " + prefix + "UserConnect(@nick@,@host@,@vhost@,@chost@,@realname@,@ip@,@ident@,@vident@," + "@account@,@secure@,@fingerprint@,@signon@,@server@,@uuid@,@modes@,@oper@)"; + query.SetValue("nick", u->nick); + query.SetValue("host", u->host); + query.SetValue("vhost", u->vhost); + query.SetValue("chost", u->chost); + query.SetValue("realname", u->realname); + query.SetValue("ip", u->ip); + query.SetValue("ident", u->GetIdent()); + query.SetValue("vident", u->GetVIdent()); + query.SetValue("secure", u->HasMode("SSL") || u->HasExt("SSL") ? "Y" : "N"); + query.SetValue("account", u->Account() ? u->Account()->display : ""); + query.SetValue("fingerprint", u->fingerprint); + query.SetValue("signon", u->signon); + query.SetValue("server", u->server->GetName()); + query.SetValue("uuid", u->GetUID()); + query.SetValue("modes", u->GetModes()); + query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N"); + this->RunQuery(query); + + if (ctcpuser && (Me->IsSynced() || ctcpeob)) + IRCD->SendPrivmsg(StatServ, u->GetUID(), "\1VERSION\1"); + +} + +void IRC2SQL::OnUserQuit(User *u, const Anope::string &msg) anope_override +{ + if (quitting) + return; + + query = "CALL " + prefix + "UserQuit(@nick@)"; + query.SetValue("nick", u->nick); + this->RunQuery(query); +} + +void IRC2SQL::OnUserNickChange(User *u, const Anope::string &oldnick) anope_override +{ + query = "UPDATE `" + prefix + "user` SET nick=@newnick@ WHERE nick=@oldnick@"; + query.SetValue("newnick", u->nick); + query.SetValue("oldnick", oldnick); + this->RunQuery(query); +} + +void IRC2SQL::OnFingerprint(User *u) anope_override +{ + query = "UPDATE `" + prefix + "user` SET secure=@secure@, fingerprint=@fingerprint@ WHERE nick=@nick@"; + query.SetValue("secure", u->HasMode("SSL") || u->HasExt("SSL") ? "Y" : "N"); + query.SetValue("fingerprint", u->fingerprint); + query.SetValue("nick", u->nick); + this->RunQuery(query); +} + +void IRC2SQL::OnUserModeSet(User *u, const Anope::string &mname) anope_override +{ + query = "UPDATE `" + prefix + "user` SET modes=@modes@, oper=@oper@ WHERE nick=@nick@"; + query.SetValue("nick", u->nick); + query.SetValue("modes", u->GetModes()); + query.SetValue("oper", u->HasMode("OPER") ? "Y" : "N"); + this->RunQuery(query); +} + +void IRC2SQL::OnUserModeUnset(User *u, const Anope::string &mname) anope_override +{ + this->OnUserModeSet(u, mname); +} + +void IRC2SQL::OnUserLogin(User *u) +{ + query = "UPDATE `" + prefix + "user` SET account=@account@ WHERE nick=@nick@"; + query.SetValue("nick", u->nick); + query.SetValue("account", u->Account() ? u->Account()->display : ""); + this->RunQuery(query); +} + +void IRC2SQL::OnNickLogout(User *u) anope_override +{ + this->OnUserLogin(u); +} + +void IRC2SQL::OnSetDisplayedHost(User *u) anope_override +{ + query = "UPDATE `" + prefix + "user` " + "SET vhost=@vhost@ " + "WHERE nick=@nick@"; + query.SetValue("vhost", u->GetDisplayedHost()); + query.SetValue("nick", u->nick); + this->RunQuery(query); +} + +void IRC2SQL::OnChannelCreate(Channel *c) anope_override +{ + query = "INSERT INTO `" + prefix + "chan` (channel, topic, topicauthor, topictime, modes) " + "VALUES (@channel@,@topic@,@topicauthor@,@topictime@,@modes@) " + "ON DUPLICATE KEY UPDATE channel=VALUES(channel), topic=VALUES(topic)," + "topicauthor=VALUES(topicauthor), topictime=VALUES(topictime), modes=VALUES(modes)"; + query.SetValue("channel", c->name); + query.SetValue("topic", c->topic); + query.SetValue("topicauthor", c->topic_setter); + query.SetValue("topictime", c->topic_ts); + query.SetValue("modes", c->GetModes(true,true)); + this->RunQuery(query); +} + +void IRC2SQL::OnChannelDelete(Channel *c) anope_override +{ + query = "DELETE FROM `" + prefix + "chan` WHERE channel=@channel@"; + query.SetValue("channel", c->name); + this->RunQuery(query); +} + +void IRC2SQL::OnJoinChannel(User *u, Channel *c) anope_override +{ + Anope::string modes; + ChanUserContainer *cu = u->FindChannel(c); + if (cu) + modes = cu->status.Modes(); + + query = "CALL " + prefix + "JoinUser(@nick@,@channel@,@modes@)"; + query.SetValue("nick", u->nick); + query.SetValue("channel", c->name); + query.SetValue("modes", modes); + this->RunQuery(query); +} + +void IRC2SQL::OnLeaveChannel(User *u, Channel *c) anope_override +{ + if (quitting) + return; + + query = "CALL " + prefix + "PartUser(@nick@,@channel@)"; + query.SetValue("nick", u->nick); + query.SetValue("channel", c->name); + this->RunQuery(query); +} + +void IRC2SQL::OnTopicUpdated(Channel *c, const Anope::string &user, const Anope::string &topic) anope_override +{ + query = "UPDATE `" + prefix + "chan` " + "SET topic=@topic@, topicauthor=@author@, topictime=FROM_UNIXTIME(@time@) " + "WHERE channel=@channel@"; + query.SetValue("topic", c->topic); + query.SetValue("author", c->topic_setter); + query.SetValue("time", c->topic_ts); + query.SetValue("channel", c->name); + this->RunQuery(query); +} + +void IRC2SQL::OnBotNotice(User *u, BotInfo *bi, Anope::string &message) anope_override +{ + Anope::string versionstr; + if (bi != StatServ) + return; + if (message[0] == '\1' && message[message.length() - 1] == '\1') + { + if (message.substr(0, 9).equals_ci("\1VERSION ")) + { + if (u->HasExt("CTCPVERSION")) + return; + u->Extend<bool>("CTCPVERSION"); + + versionstr = Anope::NormalizeBuffer(message.substr(9, message.length() - 10)); + if (versionstr.empty()) + return; + query = "UPDATE `" + prefix + "user` " + "SET version=@version@ " + "WHERE nick=@nick@"; + query.SetValue("version", versionstr); + query.SetValue("nick", u->nick); + this->RunQuery(query); + } + } +} + +MODULE_INIT(IRC2SQL) diff --git a/modules/stats/irc2sql/irc2sql.h b/modules/stats/irc2sql/irc2sql.h new file mode 100644 index 000000000..d2fcd7aca --- /dev/null +++ b/modules/stats/irc2sql/irc2sql.h @@ -0,0 +1,74 @@ +#include "module.h" +#include "modules/sql.h" + +class MySQLInterface : public SQL::Interface +{ + public: + MySQLInterface(Module *o) : SQL::Interface(o) { } + + void OnResult(const SQL::Result &r) anope_override + { + } + + void OnError(const SQL::Result &r) anope_override + { + if (!r.GetQuery().query.empty()) + Log(LOG_DEBUG) << "m_irc2sql: Error executing query " << r.finished_query << ": " << r.GetError(); + else + Log(LOG_DEBUG) << "m_irc2sql: Error executing query: " << r.GetError(); + } +}; + +class IRC2SQL : public Module +{ + ServiceReference<SQL::Provider> sql; + MySQLInterface sqlinterface; + SQL::Query query; + std::vector<Anope::string> TableList, ProcedureList, EventList; + Anope::string prefix, GeoIPDB; + bool quitting, introduced_myself, UseGeoIP, ctcpuser, ctcpeob; + BotInfo *StatServ; + PrimitiveExtensibleItem<bool> versionreply; + + void RunQuery(const SQL::Query &q); + void GetTables(); + + bool HasTable(const Anope::string &table); + bool HasProcedure(const Anope::string &table); + bool HasEvent(const Anope::string &table); + + void CheckTables(); + + public: + IRC2SQL(const Anope::string &modname, const Anope::string &creator) : + Module(modname, creator, EXTRA | THIRD), sql("", ""), sqlinterface(this), versionreply(this, "CTCPVERSION") + { + quitting = false; + introduced_myself = false; + } + + void OnShutdown() anope_override; + void OnReload(Configuration::Conf *config) anope_override; + void OnNewServer(Server *server) anope_override; + void OnServerQuit(Server *server) anope_override; + void OnUserConnect(User *u, bool &exempt) anope_override; + void OnUserQuit(User *u, const Anope::string &msg) anope_override; + void OnUserNickChange(User *u, const Anope::string &oldnick) anope_override; + void OnFingerprint(User *u) anope_override; + void OnUserModeSet(User *u, const Anope::string &mname) anope_override; + void OnUserModeUnset(User *u, const Anope::string &mname) anope_override; + void OnUserLogin(User *u) anope_override; + void OnNickLogout(User *u) anope_override; + void OnSetDisplayedHost(User *u) anope_override; + + void OnChannelCreate(Channel *c) anope_override; + void OnChannelDelete(Channel *c) anope_override; + void OnLeaveChannel(User *u, Channel *c) anope_override; + void OnJoinChannel(User *u, Channel *c) anope_override; + + void OnTopicUpdated(Channel *c, const Anope::string &user, const Anope::string &topic) anope_override; + + void OnBotNotice(User *u, BotInfo *bi, Anope::string &message) anope_override; +}; + + diff --git a/modules/stats/irc2sql/tables.cpp b/modules/stats/irc2sql/tables.cpp new file mode 100644 index 000000000..8b339f406 --- /dev/null +++ b/modules/stats/irc2sql/tables.cpp @@ -0,0 +1,311 @@ +#include "irc2sql.h" + +void IRC2SQL::CheckTables() +{ + Anope::string geoquery(""); + + /* TODO: remove the DropTable commands when the table layout is done + * perhaps we should drop/recreate some tables by default in case anope crashed + * and was unable to clear the content (ison) + * TRUNCATE could perform better for this? + */ + SQL::Result r; + r = this->sql->RunQuery(SQL::Query("DROP TABLE " + prefix + "user")); + r = this->sql->RunQuery(SQL::Query("DROP TABLE " + prefix + "chan")); + r = this->sql->RunQuery(SQL::Query("DROP TABLE " + prefix + "ison")); + + this->GetTables(); + + if (UseGeoIP && GeoIPDB.equals_ci("country") && !this->HasTable(prefix + "geoip_country")) + { + query = "CREATE TABLE `" + prefix + "geoip_country` (" + "`start` INT UNSIGNED NOT NULL," + "`end` INT UNSIGNED NOT NULL," + "`countrycode` varchar(2)," + "`countryname` varchar(50)," + "PRIMARY KEY `end` (`end`)," + "KEY `start` (`start`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_blocks")) + { + query = "CREATE TABLE `" + prefix + "geoip_city_blocks` (" + "`start` INT UNSIGNED NOT NULL," + "`end` INT UNSIGNED NOT NULL," + "`locId` INT UNSIGNED NOT NULL," + "PRIMARY KEY `end` (`end`)," + "KEY `start` (`start`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + + } + if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_location")) + { + query = "CREATE TABLE `" + prefix + "geoip_city_location` (" + "`locId` INT UNSIGNED NOT NULL," + "`country` CHAR(2) NOT NULL," + "`region` CHAR(2) NOT NULL," + "`city` VARCHAR(50)," + "`latitude` FLOAT," + "`longitude` FLOAT," + "`areaCode` INT," + "PRIMARY KEY (`locId`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (UseGeoIP && GeoIPDB.equals_ci("city") && !this->HasTable(prefix + "geoip_city_region")) + { query = "CREATE TABLE `" + prefix + "geoip_city_region` (" + "`country` CHAR(2) NOT NULL," + "`region` CHAR(2) NOT NULL," + "`regionname` VARCHAR(100) NOT NULL," + "PRIMARY KEY (`country`,`region`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (!this->HasTable(prefix + "server")) + { + query = "CREATE TABLE `" + prefix + "server` (" + "`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT," + "`name` varchar(64) NOT NULL," + "`hops` tinyint(3) NOT NULL," + "`comment` varchar(255) NOT NULL," + "`link_time` datetime DEFAULT NULL," + "`split_time` datetime DEFAULT NULL," + "`version` varchar(127) NOT NULL," + "`currentusers` int(15) NOT NULL," + "`online` enum('Y','N') NOT NULL DEFAULT 'Y'," + "`ulined` enum('Y','N') NOT NULL DEFAULT 'N'," + "PRIMARY KEY (`id`)," + "UNIQUE KEY `name` (`name`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (!this->HasTable(prefix + "chan")) + { + query = "CREATE TABLE `" + prefix + "chan` (" + "`chanid` int(11) UNSIGNED NOT NULL AUTO_INCREMENT," + "`channel` varchar(255) NOT NULL," + "`currentusers` int(15) NOT NULL DEFAULT 0," + "`topic` varchar(255) DEFAULT NULL," + "`topicauthor` varchar(255) DEFAULT NULL," + "`topictime` datetime DEFAULT NULL," + "`modes` varchar(512) DEFAULT NULL," + "PRIMARY KEY (`chanid`)," + "UNIQUE KEY `channel`(`channel`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (!this->HasTable(prefix + "user")) + { + query = "CREATE TABLE `" + prefix + "user` (" + "`nickid` int(11) UNSIGNED NOT NULL AUTO_INCREMENT," + "`nick` varchar(255) NOT NULL DEFAULT ''," + "`host` varchar(255) NOT NULL DEFAULT ''," + "`vhost` varchar(255) NOT NULL DEFAULT ''," + "`chost` varchar(255) NOT NULL DEFAULT ''," + "`realname` varchar(255) NOT NULL DEFAULT ''," + "`ip` varchar(255) NOT NULL DEFAULT ''," + "`ident` varchar(32) NOT NULL DEFAULT ''," + "`vident` varchar(32) NOT NULL DEFAULT ''," + "`modes` varchar(255) NOT NULL DEFAULT ''," + "`account` varchar(255) NOT NULL DEFAULT ''," + "`secure` enum('Y','N') NOT NULL DEFAULT 'N'," + "`fingerprint` varchar(128) NOT NULL DEFAULT ''," + "`signon` datetime DEFAULT NULL," + "`server` varchar(255) NOT NULL DEFAULT ''," + "`servid` int(11) UNSIGNED NOT NULL DEFAULT '0'," + "`uuid` varchar(32) NOT NULL DEFAULT ''," + "`oper` enum('Y','N') NOT NULL DEFAULT 'N'," + "`away` enum('Y','N') NOT NULL DEFAULT 'N'," + "`awaymsg` varchar(255) NOT NULL DEFAULT ''," + "`version` varchar(255) NOT NULL DEFAULT ''," + "`geocode` varchar(16) NOT NULL DEFAULT ''," + "`geocountry` varchar(64) NOT NULL DEFAULT ''," + "`georegion` varchar(100) NOT NULL DEFAULT ''," + "`geocity` varchar(128) NOT NULL DEFAULT ''," + "`locId` INT UNSIGNED," + "PRIMARY KEY (`nickid`)," + "UNIQUE KEY `nick` (`nick`)," + "KEY `servid` (`servid`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (!this->HasTable(prefix + "ison")) + { + query = "CREATE TABLE `" + prefix + "ison` (" + "`nickid` int(11) unsigned NOT NULL default '0'," + "`chanid` int(11) unsigned NOT NULL default '0'," + "`modes` varchar(255) NOT NULL default ''," + "PRIMARY KEY (`nickid`,`chanid`)," + "KEY `modes` (`modes`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (!this->HasTable(prefix + "maxusers")) + { + query = "CREATE TABLE `" + prefix + "maxusers` (" + "`name` VARCHAR(255) NOT NULL," + "`maxusers` INT(15) NOT NULL," + "`maxtime` DATETIME NOT NULL," + "`lastused` DATETIME NOT NULL," + "UNIQUE KEY `name` (`name`)" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;"; + this->RunQuery(query); + } + if (this->HasProcedure(prefix + "UserConnect")) + this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "UserConnect")); + + if (UseGeoIP) + { + if (GeoIPDB.equals_ci("country")) + geoquery = "UPDATE `" + prefix + "user` AS u " + "JOIN ( SELECT `countrycode`, `countryname` " + "FROM `" + prefix + "geoip_country` " + "WHERE INET_ATON(ip_) <= `end` " + "AND `start` <= INET_ATON(ip_) " + "ORDER BY `end` ASC LIMIT 1 ) as c " + "SET u.geocode = c.countrycode, u.geocountry = c.countryname " + "WHERE u.nick = nick_; "; + else if (GeoIPDB.equals_ci("city")) + geoquery = "UPDATE `" + prefix + "user` as u " + "JOIN ( SELECT * FROM `" + prefix + "geoip_city_location` " + "WHERE `locID` = ( SELECT `locID` " + "FROM `" + prefix + "geoip_city_blocks` " + "WHERE INET_ATON(ip_) <= `end` " + "AND `start` <= INET_ATON(ip_) " + "ORDER BY `end` ASC LIMIT 1 ) " + ") as l " + "SET u.geocode = l.country, " + "u.geocity = l.city, " + "u.locID = l.locID, " + "u.georegion = ( SELECT `regionname` " + "FROM `" + prefix + "geoip_city_region` " + "WHERE `country` = l.country " + "AND `region` = l.region )" + "WHERE u.nick = nick_;"; + } + query = "CREATE PROCEDURE `" + prefix + "UserConnect`" + "(nick_ varchar(255), host_ varchar(255), vhost_ varchar(255), " + "chost_ varchar(255), realname_ varchar(255), ip_ varchar(255), " + "ident_ varchar(255), vident_ varchar(255), account_ varchar(255), " + "secure_ enum('Y','N'), fingerprint_ varchar(255), signon_ int(15), " + "server_ varchar(255), uuid_ varchar(32), modes_ varchar(255), " + "oper_ enum('Y','N')) " + "BEGIN " + "DECLARE cur int(15);" + "DECLARE max int(15);" + "INSERT INTO `" + prefix + "user` " + "(nick, host, vhost, chost, realname, ip, ident, vident, account, " + "secure, fingerprint, signon, server, uuid, modes, oper) " + "VALUES (nick_, host_, vhost_, chost_, realname_, ip_, ident_, vident_, " + "account_, secure_, fingerprint_, FROM_UNIXTIME(signon_), server_, " + "uuid_, modes_, oper_) " + "ON DUPLICATE KEY UPDATE host=VALUES(host), vhost=VALUES(vhost), " + "chost=VALUES(chost), realname=VALUES(realname), ip=VALUES(ip), " + "ident=VALUES(ident), vident=VALUES(vident), account=VALUES(account), " + "secure=VALUES(secure), fingerprint=VALUES(fingerprint), signon=VALUES(signon), " + "server=VALUES(server), uuid=VALUES(uuid), modes=VALUES(modes), " + "oper=VALUES(oper);" + "UPDATE `" + prefix + "user` as `u`, `" + prefix + "server` as `s`" + "SET u.servid = s.id, " + "s.currentusers = s.currentusers + 1 " + "WHERE s.name = server_ AND u.nick = nick_;" + "SELECT `currentusers` INTO cur FROM `" + prefix + "server` WHERE name=server_;" + "SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=server_;" + "IF found_rows() AND cur <= max THEN " + "UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=server_;" + "ELSE " + "INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) " + "VALUES ( server_, cur, now(), now() ) " + "ON DUPLICATE KEY UPDATE " + "name=VALUES(name), maxusers=VALUES(maxusers)," + "maxtime=VALUES(maxtime), lastused=VALUES(lastused);" + "END IF;" + + geoquery + + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "UserQuit")) + this->RunQuery(SQL::Query("DROP PROCEDURE " +prefix + "UserQuit")); + query = "CREATE PROCEDURE `" + prefix + "UserQuit`" + "(nick_ varchar(255)) " + "BEGIN " + /* decrease usercount on the server where the user was on */ + "UPDATE `" + prefix + "user` AS `u`, `" + prefix + "server` AS `s` " + "SET s.currentusers = s.currentusers - 1 " + "WHERE u.nick=nick_ AND u.servid = s.id; " + /* decrease the usercount on all channels where the user was on */ + "UPDATE `" + prefix + "user` AS u, `" + prefix + "ison` AS i, " + "`" + prefix + "chan` AS c " + "SET c.currentusers = c.currentusers - 1 " + "WHERE u.nick=nick_ AND u.nickid = i.nickid " + "AND i.chanid = c.chanid; " + /* remove from all channels where the user was on */ + "DELETE i FROM `" + prefix + "ison` AS i " + "INNER JOIN `" + prefix + "user` as u " + "INNER JOIN `" + prefix + "chan` as c " + "WHERE u.nick = nick_ " + "AND i.nickid = u.nickid " + "AND i.chanid = c.chanid;" + /* remove the user from the user table */ + "DELETE FROM `" + prefix + "user` WHERE nick = nick_; " + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "ShutDown")) + this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "ShutDown")); + query = "CREATE PROCEDURE `" + prefix + "ShutDown`()" + "BEGIN " + "UPDATE `" + prefix + "server` " + "SET currentusers=0, online='N', split_time=now();" + "TRUNCATE TABLE `" + prefix + "user`;" + "TRUNCATE TABLE `" + prefix + "chan`;" + "TRUNCATE TABLE `" + prefix + "ison`;" + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "JoinUser")) + this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "JoinUser")); + query = "CREATE PROCEDURE `"+ prefix + "JoinUser`" + "(nick_ varchar(255), channel_ varchar(255), modes_ varchar(255)) " + "BEGIN " + "DECLARE cur int(15);" + "DECLARE max int(15);" + "INSERT INTO `" + prefix + "ison` (nickid, chanid, modes) " + "SELECT u.nickid, c.chanid, modes_ " + "FROM " + prefix + "user AS u, " + prefix + "chan AS c " + "WHERE u.nick=nick_ AND c.channel=channel_;" + "UPDATE `" + prefix + "chan` SET currentusers=currentusers+1 " + "WHERE channel=channel_;" + "SELECT `currentusers` INTO cur FROM `" + prefix + "chan` WHERE channel=channel_;" + "SELECT `maxusers` INTO max FROM `" + prefix + "maxusers` WHERE name=channel_;" + "IF found_rows() AND cur <= max THEN " + "UPDATE `" + prefix + "maxusers` SET lastused=now() WHERE name=channel_;" + "ELSE " + "INSERT INTO `" + prefix + "maxusers` (name, maxusers, maxtime, lastused) " + "VALUES ( channel_, cur, now(), now() ) " + "ON DUPLICATE KEY UPDATE " + "name=VALUES(name), maxusers=VALUES(maxusers)," + "maxtime=VALUES(maxtime), lastused=VALUES(lastused);" + "END IF;" + "END"; + this->RunQuery(query); + + if (this->HasProcedure(prefix + "PartUser")) + this->RunQuery(SQL::Query("DROP PROCEDURE " + prefix + "PartUser")); + query = "CREATE PROCEDURE `" + prefix + "PartUser`" + "(nick_ varchar(255), channel_ varchar(255)) " + "BEGIN " + "DELETE FROM `" + prefix + "ison` " + "USING `" + prefix + "ison`, `" + prefix + "user` , `" + + prefix + "chan` " + "WHERE " + prefix + "ison.nickid = " + prefix + "user.nickid " + "AND " + prefix + "user.nick = nick_ " + "AND " + prefix + "ison.chanid = " + prefix + "chan.chanid " + "AND " + prefix + "chan.channel = channel_; " + "UPDATE `" + prefix + "chan` SET currentusers=currentusers-1 " + "WHERE channel=channel_;" + "END"; + this->RunQuery(query); +} diff --git a/modules/stats/irc2sql/utils.cpp b/modules/stats/irc2sql/utils.cpp new file mode 100644 index 000000000..0c35b13fa --- /dev/null +++ b/modules/stats/irc2sql/utils.cpp @@ -0,0 +1,61 @@ +#include "irc2sql.h" + +void IRC2SQL::RunQuery(const SQL::Query &q) +{ + if (sql) + sql->Run(&sqlinterface, q); +} + +void IRC2SQL::GetTables() +{ + TableList.clear(); + ProcedureList.clear(); + EventList.clear(); + if (!sql) + return; + + SQL::Result r = this->sql->RunQuery(this->sql->GetTables(prefix)); + for (int i = 0; i < r.Rows(); ++i) + { + const std::map<Anope::string, Anope::string> &map = r.Row(i); + for (std::map<Anope::string, Anope::string>::const_iterator it = map.begin(); it != map.end(); ++it) + TableList.push_back(it->second); + } + query = "SHOW PROCEDURE STATUS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + ProcedureList.push_back(r.Get(i, "Name")); + } + query = "SHOW EVENTS WHERE `Db` = Database();"; + r = this->sql->RunQuery(query); + for (int i = 0; i < r.Rows(); ++i) + { + EventList.push_back(r.Get(i, "Name")); + } +} + +bool IRC2SQL::HasTable(const Anope::string &table) +{ + for (std::vector<Anope::string>::const_iterator it = TableList.begin(); it != TableList.end(); ++it) + if (*it == table) + return true; + return false; +} + +bool IRC2SQL::HasProcedure(const Anope::string &table) +{ + for (std::vector<Anope::string>::const_iterator it = ProcedureList.begin(); it != ProcedureList.end(); ++it) + if (*it == table) + return true; + return false; +} + +bool IRC2SQL::HasEvent(const Anope::string &table) +{ + for (std::vector<Anope::string>::const_iterator it = EventList.begin(); it != EventList.end(); ++it) + if (*it == table) + return true; + return false; +} + diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 8d66e577f..200aa2bf3 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -41,6 +41,9 @@ if(NOT WIN32) install (PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/anoperc DESTINATION ${BIN_DIR} ) + install (PROGRAMS geoipupdate.sh + DESTINATION ${BIN_DIR} + ) endif(NOT WIN32) # On non-Windows platforms, if RUNGROUP is set, change the permissions of the tools directory diff --git a/src/tools/geoipupdate.sh b/src/tools/geoipupdate.sh new file mode 100644 index 000000000..827860a16 --- /dev/null +++ b/src/tools/geoipupdate.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# This script is a helper script for the irc2sql module. +# It downloads the configured geoip databases and inserts +# them into existing mysql tables. The tables are created +# by the irc2sql module on the first load. + +############################ +# Config +############################ + + +geoip_database="country" # available options: "country" and "city" +mysql_host="localhost" +mysql_user="anope" +mysql_password="anope" +mysql_database="anope" +prefix="anope_" +die="no" + +########################### + +# The GeoIP data is created by MaxMind, available from www.maxmind.com. +geoip_country_source="http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip" +geoip_city_source="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity_CSV/GeoLiteCity-latest.zip" +geoip_region_source="http://dev.maxmind.com/static/csv/codes/maxmind/region.csv" + +########################### +LOGIN="--host=$mysql_host --user=$mysql_user --password=$mysql_password $mysql_database" +PARAMS="--delete --local --fields-terminated-by=, --fields-enclosed-by=\" --lines-terminated-by=\n $LOGIN" + +download() { + local url=$1 + local desc=$2 + echo -n " $desc " + wget --progress=dot $url 2>&1 | grep --line-buffered "%" | sed -u -e "s,\.,,g" | awk '{printf("\b\b\b\b%4s", $2)}' + echo -ne " Done\n" +} + + +if test $die = "yes"; then + echo "You have to edit and configure this script first." + exit +fi + + +if test $geoip_database = "country"; then + echo "Downloading..." + download "$geoip_country_source" "Country Database:" + echo "Unpacking..." + unzip -jo GeoIPCountryCSV.zip + rm GeoIPCountryCSV.zip + echo "Converting to UFT-8..." + iconv -f ISO-8859-1 -t UTF-8 GeoIPCountryWhois.csv -o $prefix"geoip_country.csv" + rm GeoIPCountryWhois.csv + echo "Importing..." + mysqlimport --columns=@x,@x,start,end,countrycode,countryname $PARAMS $prefix"geoip_country.csv" + rm $prefix"geoip_country.csv" $prefix"geoip_country6.csv" + echo "Done..." +elif test $geoip_database = "city"; then + echo "Downloading..." + download "$geoip_city_source" "City Database:" + download "$geoip_region_source" "Region Database:" + echo "Unpacking..." + unzip -jo GeoLiteCity-latest.zip + rm GeoLiteCity-latest.zip + echo "Converting to utf-8..." + iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Blocks.csv -o $prefix"geoip_city_blocks.csv" + iconv -f ISO-8859-1 -t UTF-8 GeoLiteCity-Location.csv -o $prefix"geoip_city_location.csv" + iconv -f ISO-8859-1 -t UTF-8 region.csv -o $prefix"geoip_city_region.csv" + rm GeoLiteCity-Blocks.csv GeoLiteCity-Location.csv region.csv + echo "Importing..." + mysqlimport --columns=start,end,locID --ignore-lines=2 $PARAMS $prefix"geoip_city_blocks.csv" + mysqlimport --columns=locID,country,region,city,@x,latitude,longitude,@x,areaCode --ignore-lines=2 $PARAMS $prefix"geoip_city_location.csv" + mysqlimport --columns=country,region,regionname $PARAMS $prefix"geoip_city_region.csv" + rm $prefix"geoip_city_blocks.csv" $prefix"geoip_city_location.csv" $prefix"geoip_city_region.csv" $prefix"geoip_country6.csv" + echo "Done..." +fi |