1#include "econ.h"
2
3#include "netban.h"
4
5#include <base/dbg.h>
6#include <base/log.h>
7#include <base/net.h>
8#include <base/str.h>
9#include <base/time.h>
10
11#include <engine/console.h>
12#include <engine/shared/config.h>
13
14CEcon::CEcon() :
15 m_Ready(false)
16{
17}
18
19int CEcon::NewClientCallback(int ClientId, void *pUser)
20{
21 CEcon *pThis = (CEcon *)pUser;
22
23 log_info("econ", "Client accepted. client_id=%d addr=<{%s}>", ClientId, pThis->m_NetConsole.ClientAddrString(ClientId).data());
24
25 pThis->m_aClients[ClientId].m_State = CClient::STATE_CONNECTED;
26 pThis->m_aClients[ClientId].m_TimeConnected = time_get();
27 pThis->m_aClients[ClientId].m_AuthTries = 0;
28
29 pThis->m_NetConsole.Send(ClientId, pLine: "Enter password:");
30 return 0;
31}
32
33int CEcon::DelClientCallback(int ClientId, const char *pReason, void *pUser)
34{
35 CEcon *pThis = (CEcon *)pUser;
36
37 log_info("econ", "Client dropped. client_id=%d addr=<{%s}> reason='%s'", ClientId, pThis->m_NetConsole.ClientAddrString(ClientId).data(), pReason);
38
39 pThis->m_aClients[ClientId].m_State = CClient::STATE_EMPTY;
40 return 0;
41}
42
43void CEcon::ConLogout(IConsole::IResult *pResult, void *pUserData)
44{
45 CEcon *pThis = static_cast<CEcon *>(pUserData);
46
47 if(pThis->m_UserClientId >= 0 && pThis->m_UserClientId < NET_MAX_CONSOLE_CLIENTS && pThis->m_aClients[pThis->m_UserClientId].m_State != CClient::STATE_EMPTY)
48 pThis->m_NetConsole.Drop(ClientId: pThis->m_UserClientId, pReason: "Logout");
49}
50
51void CEcon::Init(CConfig *pConfig, IConsole *pConsole, CNetBan *pNetBan)
52{
53 m_pConfig = pConfig;
54 m_pConsole = pConsole;
55
56 for(auto &Client : m_aClients)
57 Client.m_State = CClient::STATE_EMPTY;
58
59 m_Ready = false;
60 m_UserClientId = -1;
61
62 if(g_Config.m_EcPort == 0)
63 return;
64
65 if(g_Config.m_EcPassword[0] == 0)
66 {
67 log_error("econ", "Setting ec_password is required for econ to be enabled.");
68 return;
69 }
70
71 NETADDR BindAddr;
72 if(g_Config.m_EcBindaddr[0] && net_host_lookup(hostname: g_Config.m_EcBindaddr, addr: &BindAddr, types: NETTYPE_ALL) == 0)
73 {
74 // got bindaddr
75 BindAddr.port = g_Config.m_EcPort;
76 }
77 else
78 {
79 log_error("econ", "The configured bindaddr '%s' cannot be resolved.", g_Config.m_EcBindaddr);
80 return;
81 }
82
83 if(m_NetConsole.Open(BindAddr, pNetBan))
84 {
85 m_NetConsole.SetCallbacks(pfnNewClient: NewClientCallback, pfnDelClient: DelClientCallback, pUser: this);
86 m_Ready = true;
87 log_info("econ", "Bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort);
88 Console()->Register(pName: "logout", pParams: "", Flags: CFGFLAG_ECON, pfnFunc: ConLogout, pUser: this, pHelp: "Logout of econ");
89 }
90 else
91 {
92 log_error("econ", "Couldn't open socket. Port %d might already be in use.", g_Config.m_EcPort);
93 }
94}
95
96void CEcon::Update()
97{
98 if(!m_Ready)
99 return;
100
101 m_NetConsole.Update();
102
103 char aBuf[NET_MAX_PACKETSIZE];
104 int ClientId;
105
106 while(m_NetConsole.Recv(pLine: aBuf, MaxLength: (int)(sizeof(aBuf)) - 1, pClientId: &ClientId))
107 {
108 dbg_assert(m_aClients[ClientId].m_State != CClient::STATE_EMPTY, "got message from empty slot");
109 if(m_aClients[ClientId].m_State == CClient::STATE_CONNECTED)
110 {
111 if(str_comp(a: aBuf, b: g_Config.m_EcPassword) == 0)
112 {
113 m_aClients[ClientId].m_State = CClient::STATE_AUTHED;
114 m_NetConsole.Send(ClientId, pLine: "Authentication successful. External console access granted.");
115 log_info("econ", "Client authed. client_id=%d addr=<{%s}>", ClientId, m_NetConsole.ClientAddrString(ClientId).data());
116 }
117 else
118 {
119 m_aClients[ClientId].m_AuthTries++;
120 char aMsg[64];
121 str_format(buffer: aMsg, buffer_size: sizeof(aMsg), format: "Wrong password %d/%d.", m_aClients[ClientId].m_AuthTries, MAX_AUTH_TRIES);
122 m_NetConsole.Send(ClientId, pLine: aMsg);
123 if(m_aClients[ClientId].m_AuthTries >= MAX_AUTH_TRIES)
124 {
125 if(!g_Config.m_EcBantime)
126 m_NetConsole.Drop(ClientId, pReason: "Too many authentication tries");
127 else
128 m_NetConsole.NetBan()->BanAddr(pAddr: m_NetConsole.ClientAddr(ClientId), Seconds: g_Config.m_EcBantime * 60, pReason: "Too many authentication tries", VerbatimReason: false);
129 }
130 }
131 }
132 else if(m_aClients[ClientId].m_State == CClient::STATE_AUTHED)
133 {
134 log_info("econ", "client_id=%d addr=<{%s}> command='%s'", ClientId, m_NetConsole.ClientAddrString(ClientId).data(), aBuf);
135 m_UserClientId = ClientId;
136 Console()->ExecuteLine(pStr: aBuf, ClientId: IConsole::CLIENT_ID_UNSPECIFIED);
137 m_UserClientId = -1;
138 }
139 }
140
141 for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; ++i)
142 {
143 if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
144 time_get() > m_aClients[i].m_TimeConnected + g_Config.m_EcAuthTimeout * time_freq())
145 {
146 m_NetConsole.Drop(ClientId: i, pReason: "Authentication timeout");
147 }
148 }
149}
150
151void CEcon::Send(int ClientId, const char *pLine)
152{
153 if(!m_Ready)
154 return;
155
156 if(ClientId == -1)
157 {
158 for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
159 {
160 if(m_aClients[i].m_State == CClient::STATE_AUTHED)
161 m_NetConsole.Send(ClientId: i, pLine);
162 }
163 }
164 else if(ClientId >= 0 && ClientId < NET_MAX_CONSOLE_CLIENTS && m_aClients[ClientId].m_State == CClient::STATE_AUTHED)
165 m_NetConsole.Send(ClientId, pLine);
166}
167
168void CEcon::Shutdown()
169{
170 if(!m_Ready)
171 return;
172
173 m_NetConsole.Close();
174}
175