This patch enables logging of ip/nickname of clients that created a company and that played for a company. These information can be retrieved by nettool with new command 'companies'.
Any comments and suggestions how to proceed with these stuff ?
Next steps could be unlocking and removing of companies via nettool.
Excellent. Those sound like sensible further steps - maybe also option to liquidate?
Looks good! Options to unlock (or, indeed, lock e.g. change password), remove/liquidate and rename companies would be very useful.
Index: simversion.h
===================================================================
--- simversion.h (revision 6126)
+++ simversion.h (working copy)
@@ -18,7 +18,7 @@
// Beware: SAVEGAME minor is often ahead of version minor when there were patches.
// ==> These have no direct connection at all!
#define SIM_SAVE_MINOR 0
-#define SIM_SERVER_MINOR 0
+#define SIM_SERVER_MINOR 1
#define MAKEOBJ_VERSION "55"
Index: simworld.cc
===================================================================
--- simworld.cc (revision 6126)
+++ simworld.cc (working copy)
@@ -2454,6 +2454,10 @@
if ( (param != spieler_t::HUMAN && !public_player_unlocked) || param >= spieler_t::MAX_AI) {
return false;
}
+ // range check, player already existent?
+ if ( player_nr >= PLAYER_UNOWNED || get_spieler(player_nr) ) {
+ return false;
+ }
if (exec) {
new_spieler( player_nr, param );
// activate/deactivate AI immediately
Index: tpl/vector_tpl.h
===================================================================
--- tpl/vector_tpl.h (revision 6126)
+++ tpl/vector_tpl.h (working copy)
@@ -160,15 +160,18 @@
while( high - low>1 ) {
const sint32 mid = ((uint32) (low + high)) >> 1;
T &mid_elem = data[mid];
- if( elem==mid_elem ) {
- return &mid_elem;
- }
- else if( comp(elem, mid_elem) ) {
+ if( comp(elem, mid_elem) ) {
+ // elem < mid_elem
high = mid;
}
- else {
+ else if( comp(mid_elem, elem) ) {
+ // mid_elem < elem
low = mid;
}
+ else {
+ // mid_elem == elem
+ return &mid_elem;
+ }
}
insert_at(high, elem);
return NULL;
Index: dataobj/network_socket_list.cc
===================================================================
--- dataobj/network_socket_list.cc (revision 6126)
+++ dataobj/network_socket_list.cc (working copy)
@@ -8,6 +8,16 @@
#endif
+bool connection_info_t::compare(connection_info_t const* a, connection_info_t const* b)
+{
+ sint64 diff = (sint64)a->address.get_ip() - (sint64)b->address.get_ip();
+ if (diff == 0) {
+ diff = strcmp(a->nickname.c_str(), b->nickname.c_str());
+ }
+ return diff < 0;
+}
+
+
void socket_info_t::reset()
{
if (packet) {
@@ -212,11 +222,11 @@
#ifndef NETTOOL
// set server nickname
if (!umgebung_t::nickname.empty()) {
- list[i]->nickname = umgebung_t::nickname;
+ list[i]->nickname = umgebung_t::nickname.c_str();
}
else {
list[i]->nickname = "Server#0";
- umgebung_t::nickname = list[i]->nickname;
+ umgebung_t::nickname = list[i]->nickname.c_str();
}
#endif //NETTOOL
}
Index: dataobj/network_cmd_ingame.h
===================================================================
--- dataobj/network_cmd_ingame.h (revision 6126)
+++ dataobj/network_cmd_ingame.h (working copy)
@@ -9,6 +9,7 @@
#include "koord3d.h"
class memory_rw_t;
+class connection_info_t;
class packet_t;
class spieler_t;
class werkzeug_t;
@@ -305,6 +306,15 @@
uint8 cmd;
uint8 player_nr;
uint16 param;
+
+ /// store information about client that tries to create a company
+ static connection_info_t* pending_company_creator[PLAYER_UNOWNED];
+
+ /// store information about client that created a company
+ static connection_info_t* company_creator[PLAYER_UNOWNED];
+
+ /// store information about clients that played with a company
+ static vector_tpl<connection_info_t*> company_active_clients[PLAYER_UNOWNED];
};
/**
Index: dataobj/network_address.cc
===================================================================
--- dataobj/network_address.cc (revision 6126)
+++ dataobj/network_address.cc (working copy)
@@ -34,10 +34,12 @@
}
-void net_address_t::rdwr(packet_t *packet)
+net_address_t::net_address_t(const net_address_t &other)
{
- packet->rdwr_long(ip);
- packet->rdwr_long(mask);
+ ip = other.ip;
+ mask = other.mask;
+ ipstr[0] = '\0';
+ init_ipstr();
}
Index: dataobj/network_cmd.cc
===================================================================
--- dataobj/network_cmd.cc (revision 6126)
+++ dataobj/network_cmd.cc (working copy)
@@ -121,6 +121,7 @@
case SRVC_BAN_IP:
case SRVC_UNBAN_IP:
case SRVC_ADMIN_MSG:
+ case SRVC_GET_COMPANY_LIST:
packet->rdwr_str(text);
break;
Index: dataobj/network_cmd_ingame.cc
===================================================================
--- dataobj/network_cmd_ingame.cc (revision 6126)
+++ dataobj/network_cmd_ingame.cc (working copy)
@@ -843,6 +843,27 @@
}
socket_info_t const& info = socket_list_t::get_client(our_client_id); //.is_player_unlocked(player_nr)
+ if (!welt->change_player_tool(cmd, player_nr, param, info.is_player_unlocked(1), false)) {
+ return NULL;
+ }
+
+ // only one company creation per client IP allowed
+ if (cmd == karte_t::new_player && player_nr < lengthof(pending_company_creator)) {
+ if ( our_client_id != 0) {
+ for(uint8 i = 2; i<lengthof(company_creator); i++) {
+ if (company_creator[i] && info.address == company_creator[i]->address) {
+ // this client already created a company
+ //return NULL;
+ }
+ }
+ }
+ // temporary store address of client
+ delete pending_company_creator[player_nr];
+
+ dbg->warning("nwc_chg_player_t::clone", "pending_company_creator for %d is set to %s/%s", player_nr, info.address.get_str(), info.nickname.c_str());
+ pending_company_creator[player_nr] = new connection_info_t( info );
+ }
+
if (welt->change_player_tool(cmd, player_nr, param, info.is_player_unlocked(1), false)) {
return new nwc_chg_player_t(get_sync_step(), get_map_counter(), cmd, player_nr, param);
}
@@ -850,10 +871,30 @@
}
+connection_info_t* nwc_chg_player_t::pending_company_creator[PLAYER_UNOWNED] = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+connection_info_t* nwc_chg_player_t::company_creator[PLAYER_UNOWNED] = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+vector_tpl<connection_info_t*> nwc_chg_player_t::company_active_clients[PLAYER_UNOWNED];
+
+
void nwc_chg_player_t::do_command(karte_t *welt)
{
welt->change_player_tool(cmd, player_nr, param, true, true);
+ // store IP of client who created this company
+ if (umgebung_t::server && cmd == karte_t::new_player && player_nr < lengthof(pending_company_creator)) {
+ company_creator[player_nr] = pending_company_creator[player_nr];
+
+ if (pending_company_creator[player_nr]) {
+ dbg->warning("nwc_chg_player_t::clone", "company_creator for %d is set to %s/%s", player_nr,
+ pending_company_creator[player_nr]->address.get_str(), pending_company_creator[player_nr]->nickname.c_str());
+ }
+ pending_company_creator[player_nr] = NULL;
+ }
+
// update the window
ki_kontroll_t* playerwin = (ki_kontroll_t*)win_get_magic(magic_ki_kontroll_t);
if (playerwin) {
@@ -987,7 +1028,8 @@
player_nr = 1;
default: ;
}
- if ( player_nr < PLAYER_UNOWNED && !socket_list_t::get_client(our_client_id).is_player_unlocked(player_nr) ) {
+ socket_info_t const& info = socket_list_t::get_client(our_client_id);
+ if ( player_nr < PLAYER_UNOWNED && !info.is_player_unlocked(player_nr) ) {
if (wkz_id == (WKZ_ADD_MESSAGE_TOOL|SIMPLE_TOOL)) {
player_nr = PLAYER_UNOWNED;
}
@@ -996,6 +1038,14 @@
return NULL; // indicate failure
}
}
+ // log that this client acted as this player
+ if (player_nr < PLAYER_UNOWNED) {
+ connection_info_t *cinfo = new connection_info_t(info);
+ if(nwc_chg_player_t::company_active_clients[player_nr].insert_unique_ordered(cinfo, connection_info_t::compare) != NULL) {
+ delete cinfo; // entry exists already
+ }
+ }
+
// do scenario checks here, send error message back
scenario_t *scen = welt->get_scenario();
if ( scen->is_scripted() ) {
@@ -1326,6 +1376,41 @@
break;
}
+ case SRVC_GET_COMPANY_LIST: {
+ cbuffer_t buf;
+ for (uint8 i=0; i<PLAYER_UNOWNED; i++) {
+ if (spieler_t *sp = welt->get_spieler(i)) {
+ buf.printf("Company #%d: %s\n", i, sp->get_name());
+ buf.printf(" Password: %sset\n", sp->access_password_hash().empty() ? "NOT " :"");
+ // print creator information
+ if (i < lengthof(nwc_chg_player_t::company_creator)) {
+ if (connection_info_t const* creator = nwc_chg_player_t::company_creator[i]) {
+ buf.printf(" founded by %s at %s\n", creator->nickname.c_str(), creator->address.get_str());
+ }
+ }
+ // print clients who have this player unlocked
+ for(uint32 j = 0; j < socket_list_t::get_count(); j++) {
+ socket_info_t const& info = socket_list_t::get_client(j);
+ if (info.is_active() && info.is_player_unlocked(i)) {
+ buf.printf(" unlocked by [%d] %s at %s\n", j, info.nickname.c_str(), info.address.get_str());
+ }
+ }
+ // print clients who played for this company
+ for(uint32 j = 0; j < nwc_chg_player_t::company_active_clients[i].get_count(); j++) {
+ connection_info_t const* info = nwc_chg_player_t::company_active_clients[i][j];
+ if (info) {
+ buf.printf(" played by [%d] %s at %s\n", j, info->nickname.c_str(), info->address.get_str());
+ }
+ }
+ }
+ }
+
+ nwc_service_t nws;
+ nws.flag = SRVC_GET_COMPANY_LIST;
+ nws.text = strdup(buf);
+ nws.send(packet->get_sender());
+ }
+
default: ;
}
return true; // to delete
Index: dataobj/network_socket_list.h
===================================================================
--- dataobj/network_socket_list.h (revision 6126)
+++ dataobj/network_socket_list.h (working copy)
@@ -5,12 +5,38 @@
#include "network_address.h"
#include "../tpl/slist_tpl.h"
#include "../tpl/vector_tpl.h"
-#include <string>
+#include "../utils/plainstring.h"
class network_command_t;
class packet_t;
-class socket_info_t {
+
+/**
+ * Class to store pairs of (address, nickname) for logging and admin purposes.
+ */
+class connection_info_t {
+public:
+ /// address of connection
+ net_address_t address;
+
+ /// client nickname
+ plainstring nickname;
+
+ connection_info_t() : address(), nickname() {}
+
+ connection_info_t(const connection_info_t& other) : address(other.address), nickname(other.nickname) {}
+
+ template<class F> void rdwr(F *packet)
+ {
+ address.rdwr(packet);
+ packet->rdwr_str(nickname);
+ }
+
+ static bool compare(connection_info_t const* a, connection_info_t const* b);
+};
+
+
+class socket_info_t : public connection_info_t {
private:
packet_t *packet;
slist_tpl<packet_t *> send_queue;
@@ -29,12 +55,8 @@
SOCKET socket;
- net_address_t address;
+ socket_info_t() : connection_info_t(), packet(0), send_queue(), state(inactive), socket(INVALID_SOCKET), player_unlocked(0) {}
- std::string nickname;
-
- socket_info_t() : packet(0), send_queue(), state(inactive), socket(INVALID_SOCKET), address(), player_unlocked(0) {}
-
~socket_info_t();
/**
Index: dataobj/network_address.h
===================================================================
--- dataobj/network_address.h (revision 6126)
+++ dataobj/network_address.h (working copy)
@@ -24,11 +24,21 @@
net_address_t(const char *);
+ net_address_t(const net_address_t&);
+
bool matches(const net_address_t &other) const {
return (other.ip & mask)==(ip & mask);
}
- void rdwr(packet_t *packet);
+ template<class F> void rdwr(F *packet)
+ {
+ packet->rdwr_long(ip);
+ packet->rdwr_long(mask);
+ if (packet->is_loading()) {
+ ipstr[0] = '\0';
+ init_ipstr();
+ }
+ }
/**
* Return human readable representation of this IP address
@@ -36,9 +46,11 @@
*/
const char* get_str () const;
- bool operator==(const net_address_t& other) {
+ bool operator==(const net_address_t& other) const {
return ip==other.ip && mask == other.mask;
}
+
+ uint32 get_ip() const { return ip; }
};
class address_list_t : public vector_tpl<net_address_t> {
Index: dataobj/network_cmd.h
===================================================================
--- dataobj/network_cmd.h (revision 6126)
+++ dataobj/network_cmd.h (working copy)
@@ -104,6 +104,7 @@
SRVC_ADMIN_MSG = 8,
SRVC_SHUTDOWN = 9,
SRVC_FORCE_SYNC = 10,
+ SRVC_GET_COMPANY_LIST = 11,
SRVC_MAX
};
Index: nettools/nettool.cc
===================================================================
--- nettools/nettool.cc (revision 6126)
+++ nettools/nettool.cc (working copy)
@@ -101,6 +101,33 @@
return 0;
}
+// Simple command to send command ID to server and receive and print a text buffer
+int simple_gettext_command(SOCKET socket, uint32 command_id, int, char **) {
+ nwc_service_t nwcs;
+ nwcs.flag = command_id;
+ if (!nwcs.send(socket)) {
+ fprintf(stderr, "Could not send request!\n");
+ return 2;
+ }
+ nwc_service_t *nws = (nwc_service_t*)network_receive_command(NWC_SERVICE);
+ if (nws==NULL) {
+ return 3;
+ }
+ if (nws->flag != command_id) {
+ delete nws;
+ return 3;
+ }
+
+ if (nws->text) {
+ printf(nws->text);
+ }
+ else {
+ printf("Nothing received.\n");
+ }
+ delete nws;
+ return 0;
+}
+
int get_client_list(SOCKET socket, uint32 command_id, int, char **) {
nwc_service_t nwcs;
nwcs.flag = command_id;
@@ -291,6 +318,9 @@
" clients\n"
" Receive list of playing clients from server\n"
"\n"
+ " companies\n"
+ " Receive list of running companies from server\n"
+ "\n"
" kick-client <client number>\n"
" ban-client <client number>\n"
" Kick / ban client (use clients command to get client number)\n"
@@ -403,6 +433,7 @@
command_t commands[] = {
{"announce", false, nwc_service_t::SRVC_ANNOUNCE_SERVER, 0, &simple_command},
{"clients", true, nwc_service_t::SRVC_GET_CLIENT_LIST, 0, &get_client_list},
+ {"companies", true, nwc_service_t::SRVC_GET_COMPANY_LIST, 0, &simple_gettext_command},
{"kick-client", true, nwc_service_t::SRVC_KICK_CLIENT, 1, &kick_client},
{"ban-client", true, nwc_service_t::SRVC_BAN_CLIENT, 1, &ban_client},
{"blacklist", true, nwc_service_t::SRVC_GET_BLACK_LIST, 0, &get_blacklist},
It that all in r6127?
no
update: new functionality to nettool:
companies
Receive list of running companies from server
info-company <company number>
Show detailed info for company
unlock-company <company number>
Clears password of company, effectively unlocking it for all clients
remove-company <company number>
Immediately remove company and all its belongings
I thing this is very useful and should be committed.
This looks great. I'd like to ask for one more addition, being able to set a company's password using nettool would be useful too (e.g. for setting a password on the public service account).
(Actually being able to change a company's name would be quite useful also...)
Update. Implemented features:
companies
Receive list of running companies from server
info-company <company number>
Show detailed info for company
lock-company <company number> <new password>
lock-company <company number> -F <filename>
Set password, read from file if specified (use '-' to read from stdin)
unlock-company <company number>
Clears password of company, effectively unlocking it for all clients
remove-company <company number>
Immediately remove company and all its belongings
Implementing renaming of companies is a lot more complicated, I am afraid.
All these IP / nickname information is not saved, they will be lost after server crash / restart. Is a save/load routine important?
Otherwise, this patch is kind of feature-complete and commit-ready.
This looks good, a save/load routine for this data would be ideal given that server crashes/restarts are still not unheard of. But could do without if it's really hard to do.
this is now in r6159.