1 /*****************************************************************************
2 * PokerTH - The open source texas holdem engine *
3 * Copyright (C) 2006-2013 Felix Hammer, Florian Thauer, Lothar May *
4 * *
5 * This program is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU Affero General Public License as *
7 * published by the Free Software Foundation, either version 3 of the *
8 * License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU Affero General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Affero General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 * *
18 * *
19 * Additional permission under GNU AGPL version 3 section 7 *
20 * *
21 * If you modify this program, or any covered work, by linking or *
22 * combining it with the OpenSSL project's OpenSSL library (or a *
23 * modified version of that library), containing parts covered by the *
24 * terms of the OpenSSL or SSLeay licenses, the authors of PokerTH *
25 * (Felix Hammer, Florian Thauer, Lothar May) grant you additional *
26 * permission to convey the resulting work. *
27 * Corresponding Source for a non-source form of such a combination *
28 * shall include the source code for the parts of OpenSSL used as well *
29 * as that of the covered work. *
30 *****************************************************************************/
31
32 #include <net/chatcleanermanager.h>
33 #include <net/asiosendbuffer.h>
34 #include <boost/bind.hpp>
35 #include <core/loghelper.h>
36 #include <third_party/protobuf/chatcleaner.pb.h>
37
38 #include <sstream>
39
40 using namespace std;
41 using boost::asio::ip::tcp;
42
43
ChatCleanerManager(ChatCleanerCallback & cb,boost::shared_ptr<boost::asio::io_service> ioService)44 ChatCleanerManager::ChatCleanerManager(ChatCleanerCallback &cb, boost::shared_ptr<boost::asio::io_service> ioService)
45 : m_callback(cb), m_ioService(ioService), m_connected(false), m_curRequestId(0), m_serverPort(0), m_useIpv6(false),
46 m_recvBufUsed(0)
47 {
48 m_recvBuf[0] = 0;
49 m_resolver.reset(
50 new boost::asio::ip::tcp::resolver(*m_ioService));
51 m_sendManager.reset(
52 new AsioSendBuffer);
53 }
54
~ChatCleanerManager()55 ChatCleanerManager::~ChatCleanerManager()
56 {
57 }
58
59 void
Init(const string & serverAddr,int port,bool ipv6,const string & clientSecret,const string & serverSecret)60 ChatCleanerManager::Init(const string &serverAddr, int port, bool ipv6,
61 const string &clientSecret, const string &serverSecret)
62 {
63 m_serverAddr = serverAddr;
64 m_serverPort = port;
65 m_useIpv6 = ipv6;
66 m_clientSecret = clientSecret;
67 m_serverSecret = serverSecret;
68 ReInit();
69 }
70
71 void
ReInit()72 ChatCleanerManager::ReInit()
73 {
74 if (m_useIpv6)
75 m_socket.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v6()));
76 else
77 m_socket.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v4()));
78
79 ostringstream portStr;
80 portStr << m_serverPort;
81 boost::asio::ip::tcp::resolver::query q(m_serverAddr, portStr.str());
82
83 m_resolver->async_resolve(
84 q,
85 boost::bind(&ChatCleanerManager::HandleResolve,
86 shared_from_this(),
87 boost::asio::placeholders::error,
88 boost::asio::placeholders::iterator));
89 }
90
91 void
HandleLobbyChatText(unsigned playerId,const std::string & name,const std::string & text)92 ChatCleanerManager::HandleLobbyChatText(unsigned playerId, const std::string &name, const std::string &text)
93 {
94 HandleGameChatText(0, playerId, name, text);
95 }
96
97 void
HandleGameChatText(unsigned gameId,unsigned playerId,const std::string & name,const std::string & text)98 ChatCleanerManager::HandleGameChatText(unsigned gameId, unsigned playerId, const std::string &name, const std::string &text)
99 {
100 if (m_connected) {
101 boost::shared_ptr<ChatCleanerMessage> tmpChat(ChatCleanerMessage::default_instance().New());
102 tmpChat->set_messagetype(ChatCleanerMessage::Type_CleanerChatRequestMessage);
103 CleanerChatRequestMessage *netRequest = tmpChat->mutable_cleanerchatrequestmessage();
104 netRequest->set_requestid(GetNextRequestId());
105 if (gameId) {
106 netRequest->set_cleanerchattype(cleanerChatTypeGame);
107 netRequest->set_gameid(gameId);
108 } else {
109 netRequest->set_cleanerchattype(cleanerChatTypeLobby);
110 }
111 netRequest->set_playerid(playerId);
112 netRequest->set_playername(name);
113 netRequest->set_chatmessage(text);
114 SendMessageToServer(*tmpChat);
115 }
116 }
117
118 void
HandleResolve(const boost::system::error_code & ec,boost::asio::ip::tcp::resolver::iterator endpoint_iterator)119 ChatCleanerManager::HandleResolve(const boost::system::error_code& ec,
120 boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
121 {
122 if (!ec) {
123 boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
124 m_socket->async_connect(
125 endpoint,
126 boost::bind(&ChatCleanerManager::HandleConnect,
127 shared_from_this(),
128 boost::asio::placeholders::error,
129 ++endpoint_iterator));
130 } else if (ec != boost::asio::error::operation_aborted) {
131 LOG_ERROR("Could not resolve chat cleaner server.");
132 }
133 }
134
135 void
HandleConnect(const boost::system::error_code & ec,boost::asio::ip::tcp::resolver::iterator endpoint_iterator)136 ChatCleanerManager::HandleConnect(const boost::system::error_code& ec,
137 boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
138 {
139 if (!ec) {
140 boost::shared_ptr<ChatCleanerMessage> tmpInit(ChatCleanerMessage::default_instance().New());
141 tmpInit->set_messagetype(ChatCleanerMessage::Type_CleanerInitMessage);
142 CleanerInitMessage *netInit = tmpInit->mutable_cleanerinitmessage();
143 netInit->set_requestedversion(CLEANER_PROTOCOL_VERSION);
144 netInit->set_clientsecret(m_clientSecret);
145 SendMessageToServer(*tmpInit);
146 m_socket->async_read_some(
147 boost::asio::buffer(m_recvBuf, sizeof(m_recvBuf)),
148 boost::bind(
149 &ChatCleanerManager::HandleRead,
150 shared_from_this(),
151 boost::asio::placeholders::error,
152 boost::asio::placeholders::bytes_transferred));
153 } else if (ec != boost::asio::error::operation_aborted) {
154 if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator()) {
155 // Try next resolve entry.
156 boost::system::error_code ec;
157 m_socket->close(ec);
158 boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
159 m_socket->async_connect(
160 endpoint,
161 boost::bind(&ChatCleanerManager::HandleConnect,
162 shared_from_this(),
163 boost::asio::placeholders::error,
164 ++endpoint_iterator));
165 } else
166 LOG_ERROR("Could not connect to chat cleaner server.");
167 }
168 }
169
170 void
HandleRead(const boost::system::error_code & ec,size_t bytesRead)171 ChatCleanerManager::HandleRead(const boost::system::error_code &ec, size_t bytesRead)
172 {
173 if (!ec) {
174 bool error = false;
175 m_recvBufUsed += bytesRead;
176
177 bool valid;
178 do {
179 valid = false;
180 if (m_recvBufUsed >= CLEANER_NET_HEADER_SIZE) {
181 // Read the size of the packet (first 4 bytes in network byte order).
182 uint32_t nativeVal;
183 memcpy(&nativeVal, &m_recvBuf[0], sizeof(uint32_t));
184 size_t packetSize = ntohl(nativeVal);
185 if (packetSize > MAX_CLEANER_PACKET_SIZE) {
186 m_recvBufUsed = 0;
187 LOG_ERROR("Invalid packet size: " << packetSize);
188 } else if (m_recvBufUsed >= packetSize + CLEANER_NET_HEADER_SIZE) {
189 try {
190 // Try to decode the packet.
191 boost::shared_ptr<ChatCleanerMessage> recvMsg(ChatCleanerMessage::default_instance().New());
192 if (recvMsg->ParseFromArray(&m_recvBuf[CLEANER_NET_HEADER_SIZE], static_cast<int>(packetSize))) {
193 m_recvBufUsed -= (packetSize + CLEANER_NET_HEADER_SIZE);
194 if (m_recvBufUsed) {
195 memmove(m_recvBuf, m_recvBuf + packetSize + CLEANER_NET_HEADER_SIZE, m_recvBufUsed);
196 }
197 }
198 // Handle the packet.
199 error = HandleMessage(*recvMsg);
200 valid = true;
201 } catch (const exception &e) {
202 // Reset buffer on error.
203 m_recvBufUsed = 0;
204 LOG_ERROR("Exception while decoding packet: " << e.what());
205 }
206 }
207 }
208 } while (valid && !error);
209
210 if (!error) {
211 m_socket->async_read_some(
212 boost::asio::buffer(m_recvBuf + m_recvBufUsed, sizeof(m_recvBuf) - m_recvBufUsed),
213 boost::bind(
214 &ChatCleanerManager::HandleRead,
215 shared_from_this(),
216 boost::asio::placeholders::error,
217 boost::asio::placeholders::bytes_transferred));
218 } else {
219 boost::system::error_code ec;
220 m_socket->close(ec);
221 m_connected = false;
222 }
223 } else if (ec != boost::asio::error::operation_aborted) {
224 LOG_ERROR("Error receiving data from chat cleaner.");
225 bool wasConnected = m_connected;
226 boost::system::error_code ec;
227 m_socket->close(ec);
228 m_connected = false;
229 if (wasConnected)
230 ReInit(); // Try to reconnect once if disconnected.
231 }
232 }
233
234 bool
HandleMessage(ChatCleanerMessage & msg)235 ChatCleanerManager::HandleMessage(ChatCleanerMessage &msg)
236 {
237 bool error = true;
238 if (msg.messagetype() == ChatCleanerMessage::Type_CleanerInitAckMessage) {
239 const CleanerInitAckMessage &netAck = msg.cleanerinitackmessage();
240 if (netAck.serverversion() == CLEANER_PROTOCOL_VERSION) {
241 if (m_serverSecret == netAck.serversecret()) {
242 m_connected = true;
243 error = false;
244 LOG_MSG("Successfully connected to chat cleaner.");
245 }
246 }
247 if (!m_connected)
248 LOG_ERROR("Chat cleaner handshake failed.");
249 } else if (msg.messagetype() == ChatCleanerMessage::Type_CleanerChatReplyMessage) {
250 const CleanerChatReplyMessage &netReply = msg.cleanerchatreplymessage();
251 if (!netReply.cleanertext().empty()) {
252 if (netReply.cleanerchattype() == cleanerChatTypeLobby) {
253 m_callback.SignalChatBotMessage(netReply.cleanertext());
254 } else if (netReply.cleanerchattype() == cleanerChatTypeGame) {
255 m_callback.SignalChatBotMessage(netReply.gameid(), netReply.cleanertext());
256 }
257 }
258 if (netReply.cleaneractiontype() == CleanerChatReplyMessage_CleanerActionType_cleanerActionKick)
259 m_callback.SignalKickPlayer(netReply.playerid());
260 else if (netReply.cleaneractiontype() == CleanerChatReplyMessage_CleanerActionType_cleanerActionBan)
261 m_callback.SignalBanPlayer(netReply.playerid());
262 else if (netReply.cleaneractiontype() == CleanerChatReplyMessage_CleanerActionType_cleanerActionMute)
263 m_callback.SignalMutePlayer(netReply.playerid());
264 error = false;
265 }
266 return error;
267 }
268
269 void
SendMessageToServer(ChatCleanerMessage & msg)270 ChatCleanerManager::SendMessageToServer(ChatCleanerMessage &msg)
271 {
272 uint32_t packetSize = msg.ByteSize();
273 google::protobuf::uint8 *buf = new google::protobuf::uint8[packetSize + CLEANER_NET_HEADER_SIZE];
274 *((uint32_t *)buf) = htonl(packetSize);
275 msg.SerializeWithCachedSizesToArray(&buf[CLEANER_NET_HEADER_SIZE]);
276 m_sendManager->EncodeToBuf(buf, packetSize + CLEANER_NET_HEADER_SIZE);
277 delete[] buf;
278
279 m_sendManager->AsyncSendNextPacket(m_socket);
280 }
281
282 unsigned
GetNextRequestId()283 ChatCleanerManager::GetNextRequestId()
284 {
285 m_curRequestId++;
286 if (m_curRequestId == 0) // 0 is an invalid id.
287 m_curRequestId++;
288
289 return m_curRequestId;
290 }
291
292