/**************************************************************************
* Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, *
* Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. *
* *
* Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael *
* Chastain, Michael Quan, and Mitchell Tse. *
* *
* In order to use any part of this Merc Diku Mud, you must comply with *
* both the original Diku license in 'license.doc' as well the Merc *
* license in 'license.txt'. In particular, you may not remove either of *
* these copyright notices. *
* *
* Much time and thought has gone into this software and you are *
* benefiting. We hope that you share your changes too. What goes *
* around, comes around. *
***************************************************************************
* ROM 2.4 is copyright 1993-1998 Russ Taylor *
* ROM has been brought to you by the ROM consortium *
* Russ Taylor (rtaylor@hypercube.org) *
* Gabrielle Taylor (gtaylor@hypercube.org) *
* Brian Moore (zump@rom.org) *
* By using this code, you have agreed to follow the terms of the *
* ROM license, in the file Rom24/doc/rom.license *
***************************************************************************
* The Dawn of Time v1.69q (c)1997-2002 Michael Garratt *
* >> A number of people have contributed to the Dawn codebase, with the *
* majority of code written by Michael Garratt - www.dawnoftime.org *
* >> To use this source code, you must fully comply with the dawn license *
* in licenses.txt... In particular, you may not remove this copyright *
* notice. *
***************************************************************************
* 1stMud ROM Derivative (c) 2001-2004 by Markanth *
* http://www.firstmud.com/ <markanth@firstmud.com> *
* By using this code you have agreed to follow the term of *
* the 1stMud license in ../doc/1stMud/LICENSE *
***************************************************************************/
// About: The code within this module sends to firstmud.com statistical
// information. Using this information we are able to
// get an idea of the number of 1stmud based muds running and
// hopefully the number of players.
//
// The statistical information submitted to firstmud.com is a
// summary of mudstats, combined with a few other things specific to
// your mud environment (such as the name of the mud). You are free
// to remove/add info as you please, but please leave the unique_id,
// name, and version if possible.
//
// By default the mud after about 10 minutes of running will
// send the stats to http://www.firstmud.com/cgi-bin/1stmud/post.cgi
// This does not lag the mud in anyway, (unless your dns resolver
// is broken - use sockets to determine this), if the dns resolver
// is broken there may be a one off small delay (ordinarily less
// than 5 seconds) while the mud resolves the ip address of
// firstmud.com in order to know where to send the
// stats to.
//
#include "merc.h"
#include "recycle.h"
#include "tables.h"
#include "olc.h"
#ifndef DISABLE_SENDSTAT
// #define SENDSTAT_LOG_PROGRESS 1
#define SENDSTAT_SUBMIT_DOMAIN "firstmud.com"
#define SENDSTAT_SUBMIT_URL "/scripts/post.php"
void
sendstat_logf (const char *fmt, ...)
{
char buf[MSL];
va_list args;
#ifndef SENDSTAT_LOG_PROGRESS
return;
#endif
if (NullStr (fmt))
return;
va_start (args, fmt);
vsnprintf (buf, MSL, fmt, args);
va_end (args);
logf ("sendstat: %s", buf);
}
const char *url_encode_table[] = {
"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
"%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
"%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
"+", "!", "%22", "%23", "$", "%25", "%26", "%27",
"(", ")", "*", "%2B", ",", "-", ".", "%2F",
"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
"%40", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W",
"X", "Y", "Z", "%5B", "%5C", "%5D", "%5E", "_",
"%60", "a", "b", "c", "d", "e", "f", "g",
"h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w",
"x", "y", "z", "%7B", "%7C", "%7D", "%7E", "%7F",
"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
"%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
"%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
"%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
"%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
"%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
"%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
"%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
"%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
"%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
"%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
"%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
"%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
"%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
"%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
};
char *
url_encode_post_data (const char *postdata)
{
static char *result;
unsigned char *s;
const char *t;
char *d;
if (result)
{
free_mem (result);
}
alloc_mem (result, char, strlen (postdata) * 3 + 1);
d = result;
for (s = (unsigned char *) postdata; *s; s++)
{
t = url_encode_table[*s];
while (*t)
{
*d++ = *t++;
}
}
*d = '\0';
return result;
}
char *
sendstat_generate_statistics_text ()
{
static char result[45000];
char stats[45000];
char *encoded;
int len;
stats[0] = NUL;
#define ENCODE_INT(field) strcat(stats, FORMATF("&" # field"=%d", field))
#define ENCODE_INTH(field, header) strcat(stats, FORMATF("&%s=%d", header, field))
#define ENCODE_BOOL(field) strcat(stats, FORMATF("&" # field"=%s", !field ? "false" : "true"))
#define ENCODE_LONG(field) strcat(stats, FORMATF("&" # field"=%ld", field))
#define ENCODE_LONGH(field, header) strcat(stats, FORMATF("&%s=%ld", header, field))
#define ENCODE_LONGHD(field, header) if(field > 0) strcat(stats, FORMATF("&%s=%ld", header, field))
#define ENCODE_STR(field) strcat(stats, FORMATF("&" # field"=%s", url_encode_post_data(field)))
#define ENCODE_STRH(field, header) strcat(stats, FORMATF("&%s=%s", header, url_encode_post_data(field)))
#define ENCODE_TIME(field) strcat(stats, FORMATF("&" # field"=%s", url_encode_post_data(str_time(field, -1, NULL))))
#define ENCODE_TIMEH(field, header) strcat(stats, FORMATF("&%s=%s", header, url_encode_post_data(str_time(field, -1, NULL))))
ENCODE_INTH (mud_info.unique_id, "unique_id");
ENCODE_STRH (mud_info.name, "name");
ENCODE_STRH (MUDVERSION, "version");
ENCODE_STRH (mud_info.bind_ip_address, "bind_ip");
ENCODE_INTH (mud_info.default_port, "default_port");
ENCODE_INT (mainport);
#ifndef DISABLE_WEBSRV
ENCODE_INT (webport);
#endif
if (mud_info.disabled_signals)
ENCODE_STRH (flag_string (signal_flags, mud_info.disabled_signals),
"disabled_signals");
ENCODE_INTH (mud_info.min_save_lvl, "min_save_lvl");
ENCODE_INTH (mud_info.group_lvl_limit, "group_lvl_range");
ENCODE_INTH (mud_info.pcdam, "pcdam_mod");
ENCODE_INTH (mud_info.mobdam, "mobdam_mod");
ENCODE_INTH (mud_info.max_points, "creation_point_mod");
if (top_descriptor)
ENCODE_INTH (top_descriptor, "connections");
ENCODE_INT (MAX_STATS);
ENCODE_INTH (top_area, "areas");
ENCODE_INTH (top_room_index, "rooms");
ENCODE_INTH (top_shop, "shops");
ENCODE_INTH (top_explored, "explorable_rooms");
ENCODE_INTH (top_reset, "resets");
ENCODE_INTH (top_exit, "exits");
ENCODE_INTH (top_ed, "extra_descriptions");
ENCODE_INTH (top_affect, "affects");
ENCODE_INTH (top_char_index, "mobiles");
ENCODE_INT (mobile_count);
ENCODE_INTH (top_obj_index, "objects");
ENCODE_INTH (top_obj, "object_count");
if (top_mprog > 0)
ENCODE_INTH (top_mprog, "mob_programs");
if (top_oprog > 0)
ENCODE_INTH (top_oprog, "obj_programs");
if (top_rprog > 0)
ENCODE_INTH (top_rprog, "room_programs");
ENCODE_INTH (top_help, "helps");
ENCODE_INTH (nAllocString, "str_count");
ENCODE_INTH (sAllocString, "str_size");
ENCODE_INTH (mud_info.share_value, "share_value");
ENCODE_INTH (mud_info.stats.online, "max_online");
ENCODE_INTH (mud_info.pulsepersec, "pulsepersec");
ENCODE_LONGHD (mud_info.stats.logins, "logins");
ENCODE_LONGHD (mud_info.stats.quests, "quests");
ENCODE_LONGHD (mud_info.stats.qcomplete, "qcomplete");
ENCODE_LONGHD (mud_info.stats.levels, "levels");
ENCODE_LONGHD (mud_info.stats.newbies, "newbies");
ENCODE_LONGHD (mud_info.stats.deletions, "deletions");
ENCODE_LONGHD (mud_info.stats.mobdeaths, "mobdeaths");
ENCODE_LONGHD (mud_info.stats.auctions, "auctions");
ENCODE_LONGHD (mud_info.stats.aucsold, "aucsold");
ENCODE_LONGHD (mud_info.stats.pdied, "pdied");
ENCODE_LONGHD (mud_info.stats.pkill, "pkill");
ENCODE_LONGHD (mud_info.stats.notes, "notes");
ENCODE_LONGHD (mud_info.stats.remorts, "remorts");
ENCODE_LONGHD (mud_info.stats.wars, "wars");
ENCODE_LONGHD (mud_info.stats.gquests, "gquests");
ENCODE_LONGHD (mud_info.stats.connections, "connections");
ENCODE_LONGHD (mud_info.stats.boot_connects, "boot_connects");
if (mud_info.last_copyover > 0)
ENCODE_STRH (timestr (current_time - mud_info.last_copyover, false),
"last_copyover");
if (mud_info.longest_uptime > 0)
ENCODE_STRH (timestr (mud_info.longest_uptime, false), "longest_uptime");
ENCODE_LONGHD (mud_info.stats.web_requests, "web_requests");
ENCODE_LONGHD (mud_info.stats.chan_msgs, "chan_msgs");
ENCODE_TIMEH (mud_info.stats.lastupdate, "stats_since");
ENCODE_INTH (mud_info.stats.version, "stats_version");
ENCODE_LONG (top_vnum_room);
ENCODE_LONG (top_vnum_obj);
ENCODE_LONG (top_vnum_mob);
ENCODE_INT (LEVEL_HERO);
ENCODE_INT (LEVEL_IMMORTAL);
ENCODE_INT (MAX_LEVEL);
ENCODE_TIME (current_time);
ENCODE_INTH (top_race, "races");
ENCODE_INTH (top_class, "classes");
ENCODE_INTH (top_skill, "skills");
ENCODE_INTH (top_group, "groups");
ENCODE_INTH (top_social, "socials");
ENCODE_INTH (top_clan, "clans");
ENCODE_INTH (top_cmd, "commands");
ENCODE_INTH (top_deity, "deities");
ENCODE_INTH (top_song, "songs");
ENCODE_STR (HOSTNAME);
ENCODE_STR (UNAME);
ENCODE_STR (CWDIR);
ENCODE_STR (EXE_FILE);
ENCODE_INTH (top_channel, "channels");
ENCODE_INTH (pfiles.count, "Pfiles");
ENCODE_STRH (flag_string (mud_flags, mud_info.mud_flags), "mudflags");
ENCODE_TIME (boot_time);
#ifdef __cplusplus
ENCODE_STRH ("yes", "Cplus");
#endif
#ifndef DISABLE_MCCP
ENCODE_STRH ("yes", "MCCP");
#endif
ENCODE_INT (MAX_KEY_HASH);
ENCODE_INT (MAX_STRING_LENGTH);
ENCODE_INT (MAX_INPUT_LENGTH);
if (top_ban > 0)
ENCODE_INTH (top_ban, "Bans");
if (top_disabled > 0)
ENCODE_INTH (top_disabled, "disabled_cmds");
if (top_mbr > 0)
ENCODE_INTH (top_mbr, "clan_members");
if (top_msp > 0)
ENCODE_INTH (top_msp, "msp_sounds");
#ifdef __DATE__
ENCODE_STRH (__DATE__, "compiled_date");
#endif
#ifdef __TIME__
ENCODE_STRH (__TIME__, "compiled_time");
#endif
#ifdef __CYGWIN__
ENCODE_STRH ("cygwin", "compiled_platform");
#elif defined WIN32
ENCODE_STRH ("Win32", "compiled_platform");
#elif defined unix
#ifdef linux
ENCODE_STRH ("linux", "compiled_platform");
#elif defined __OpenBSD__
ENCODE_STRH ("OpenBSD", "compiled_platform");
#elif defined __FreeBSD__
ENCODE_STRH ("FreeBSD", "compiled_platform");
#elif defined __NetBSD__
ENCODE_STRH ("NetBSD", "compiled_platform");
#elif defined BSD
ENCODE_STRH ("BSD", "compiled_platform");
#else
ENCODE_STRH ("unix", "compiled_platform");
#endif
#else
ENCODE_STRH ("unknown", "compiled_platform");
#endif
#ifdef __VERSION__
ENCODE_STRH (__VERSION__, "compiler_version");
#endif
ENCODE_STRH (get_platform_info (), "platform_info");
encoded = &stats[1];
len = strlen (encoded);
sprintf (result,
"POST " SENDSTAT_SUBMIT_URL " HTTP/1.1\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Host: " SENDSTAT_SUBMIT_DOMAIN "\r\n"
"User-Agent: Mozilla/4.0 (compatible; " MUDNAME
"SendStat/1.0;)\r\n" "Content-Length: %d\r\n"
"Cache-Control: no-cache\r\n" "\r\n%s", len, encoded);
return result;
}
char *sendstat_stattext_to_post;
typedef enum
{
SENDSTATSTAGE_WAIT,
SENDSTATSTAGE_DOMAIN_RESOLVED,
SENDSTATSTAGE_CONNECT_IN_PROGRESS,
SENDSTATSTAGE_GENERATE_STATS,
SENDSTATSTAGE_POSTING,
SENDSTATSTAGE_CLOSE_CONNECT,
SENDSTATSTAGE_COMPLETED,
SENDSTATSTAGE_ABORTED
}
sendstat_stages;
sendstat_stages sendstat_stage = SENDSTATSTAGE_WAIT;
time_t sendstat_connect_timeout = 0;
struct in_addr sendstat_address;
static SOCKET sendstat_socket;
void
sendstat_resolve_domain ()
{
if (!inet_aton (SENDSTAT_SUBMIT_DOMAIN, &sendstat_address))
{
if (sendstat_address.s_addr == 0)
{
struct hostent *hostp = gethostbyname (SENDSTAT_SUBMIT_DOMAIN);
if (!hostp)
{
sendstat_logf ("failed to resolve '%s.'",
SENDSTAT_SUBMIT_DOMAIN);
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
memcpy (&sendstat_address, hostp->h_addr, hostp->h_length);
}
}
sendstat_logf ("resolved '%s' as %s",
SENDSTAT_SUBMIT_DOMAIN, inet_ntoa (sendstat_address));
sendstat_stage = SENDSTATSTAGE_DOMAIN_RESOLVED;
}
struct sockaddr_in sockaddress;
void
sendstat_initiate_connection ()
{
SOCKET nRet;
sendstat_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sendstat_socket == INVALID_SOCKET)
{
sendstat_logf ("Error creating connection socket");
return;
}
sockaddress.sin_family = AF_INET;
sockaddress.sin_addr.s_addr = sendstat_address.s_addr;
sockaddress.sin_port = htons (80);
if (!socket_cntl (sendstat_socket))
{
sendstat_logf ("error setting new socket to nonblocking");
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
nRet = connect (sendstat_socket, (SOCKADDR *) & sockaddress,
sizeof (sockaddress));
if (nRet == 0)
{ // successful connection, jump straight to generating the stats to post
sendstat_stage = SENDSTATSTAGE_GENERATE_STATS;
return;
}
#ifdef WIN32
if (WSAGetLastError () != WSAEWOULDBLOCK)
{
sendstat_logf
("sendstat_initiate_connection(): connect() error %d",
WSAGetLastError ());
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
#else
if (nRet < 0)
{
if (errno != EINPROGRESS && errno != EALREADY)
{
sendstat_logf
("sendstat_initiate_connection(): connect() error %d", errno);
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
}
#endif
sendstat_connect_timeout = current_time + 200;
sendstat_logf ("connection initiation successful");
sendstat_stage = SENDSTATSTAGE_CONNECT_IN_PROGRESS;
return;
}
void
sendstat_process_connect ()
{
SOCKET nRet;
if (sendstat_connect_timeout < current_time)
{
sendstat_logf
("sendstat_process_connect(): pending connection timed out.");
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
sendstat_logf ("processing connection %s:80", inet_ntoa (sendstat_address));
#ifdef WIN32
{
struct timeval select_timeout;
fd_set fdWrite;
fd_set fdExcept;
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 0;
FD_ZERO (&fdWrite);
FD_SET (sendstat_socket, &fdWrite);
FD_ZERO (&fdExcept);
FD_SET (sendstat_socket, &fdExcept);
nRet = select (sendstat_socket + 1, NULL, &fdWrite, &fdExcept,
&select_timeout);
if (nRet < 1)
{
if (nRet)
{
sendstat_logf
("sendstat_process_connect(): select() returned error");
sendstat_stage = SENDSTATSTAGE_ABORTED;
}
return;
}
if (FD_ISSET (sendstat_socket, &fdWrite))
{
sendstat_logf ("sendstat_process_connect(): connection established.");
sendstat_stage = SENDSTATSTAGE_GENERATE_STATS;
return;
}
if (FD_ISSET (sendstat_socket, &fdExcept))
{
sendstat_logf ("sendstat_process_connect(): connection failed.");
}
else
{
sendstat_logf
("sendstat_process_connect(): don't know how we got here!");
}
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
#else
nRet = connect (sendstat_socket, (SOCKADDR *) & sockaddress,
sizeof (sockaddress));
if (nRet == 0)
{
sendstat_stage = SENDSTATSTAGE_GENERATE_STATS;
return;
}
if (nRet < 0)
{
if (errno != EINPROGRESS && errno != EALREADY)
{
sendstat_logf ("sendstat_process_connect(): connect() error %d",
errno);
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
}
#endif
return;
}
void
sendstat_generate_stats ()
{
sendstat_stattext_to_post = sendstat_generate_statistics_text ();
if (NullStr (sendstat_stattext_to_post))
{
sendstat_logf
("sendstat_generate_stats(): An error occured generating statistics.");
sendstat_stage = SENDSTATSTAGE_ABORTED;
}
else
sendstat_stage = SENDSTATSTAGE_POSTING;
}
void
sendstat_post ()
{
char *msg = sendstat_stattext_to_post;
int written;
int msglen = strlen (msg);
written = write_to_socket (sendstat_socket, msg, msglen);
if (written < 0)
{
sendstat_logf ("sendstat_post(): An error occured posting statistics.");
sendstat_stage = SENDSTATSTAGE_ABORTED;
return;
}
if (written < msglen)
{
sendstat_logf
("Incomplete write, sent %d bytes of %d, write rest later",
written, msglen);
sendstat_stattext_to_post += written;
return;
}
sendstat_logf ("Submitted %d bytes", written);
sendstat_stage = SENDSTATSTAGE_CLOSE_CONNECT;
}
#endif
Do_Fun (do_sendstat)
{
#ifdef DISABLE_SENDSTAT
chprintlnf (ch,
"Posting %s statistics to http://" SENDSTAT_SUBMIT_DOMAIN
"/muds/%d.php is disabled.", mud_info.name, mud_info.unique_id);
#else
while (sendstat_stage != SENDSTATSTAGE_ABORTED
&& sendstat_stage != SENDSTATSTAGE_COMPLETED)
{
sendstat_resolve_domain ();
sendstat_initiate_connection ();
sendstat_process_connect ();
sendstat_generate_stats ();
sendstat_post ();
closesocket (sendstat_socket);
sendstat_stage = SENDSTATSTAGE_COMPLETED;
}
if (sendstat_stage == SENDSTATSTAGE_COMPLETED)
chprintlnf (ch,
"%s statistics posted on http://" SENDSTAT_SUBMIT_DOMAIN
"/muds/%d.php", mud_info.name, mud_info.unique_id);
else
chprintlnf (ch,
"There was an error posting %s statistics on http://"
SENDSTAT_SUBMIT_DOMAIN "/muds/%d.php", mud_info.name,
mud_info.unique_id);
#endif
}
void
sendstat_update (void)
{
#ifndef DISABLE_SENDSTAT
static time_t wait_until = 0;
if ((int) sendstat_stage < (int) SENDSTATSTAGE_COMPLETED)
{
if (!wait_until || sendstat_stage != SENDSTATSTAGE_WAIT)
{
sendstat_logf ("sendstat_update(%d)", (int) sendstat_stage);
}
}
switch (sendstat_stage)
{
case SENDSTATSTAGE_WAIT:
if (wait_until == 0)
{
wait_until = current_time + MINUTE * 30;
}
else if (wait_until < current_time)
{
wait_until = 0;
sendstat_logf ("moving on to resolving stage.");
sendstat_resolve_domain ();
}
break;
case SENDSTATSTAGE_DOMAIN_RESOLVED:
sendstat_logf ("initiating connection to '%s:80'",
inet_ntoa (sendstat_address));
sendstat_initiate_connection ();
break;
case SENDSTATSTAGE_CONNECT_IN_PROGRESS:
sendstat_logf ("processing connection.");
sendstat_process_connect ();
break;
case SENDSTATSTAGE_GENERATE_STATS:
sendstat_logf ("generating statistics.");
sendstat_generate_stats ();
break;
case SENDSTATSTAGE_POSTING:
sendstat_logf ("posting statistics.");
sendstat_post ();
break;
case SENDSTATSTAGE_CLOSE_CONNECT:
sendstat_logf ("closing socket.");
closesocket (sendstat_socket);
sendstat_stage = SENDSTATSTAGE_COMPLETED;
break;
case SENDSTATSTAGE_COMPLETED:
case SENDSTATSTAGE_ABORTED:
{
static time_t redo_in = 0;
if (redo_in)
{
if (redo_in < current_time)
{
sendstat_logf ("restarting sendstats");
sendstat_stage = SENDSTATSTAGE_WAIT;
redo_in = 0;
}
}
else
{
redo_in = current_time + (24 * HOUR);
}
}
default:
break;
};
#endif
}