TelnetConnection added
[public/netxms.git] / src / libnetxms / net.cpp
CommitLineData
58ee2f43
VK
1/*
2** NetXMS - Network Management System
b08a37b8 3** Copyright (C) 2003-2012 Victor Kirhenshtein
58ee2f43
VK
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU Lesser General Public License as published by
7** the Free Software Foundation; either version 3 of the License, or
8** (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 General Public License for more details.
14**
15** You should have received a copy of the GNU Lesser General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19** File: net.cpp
20**
21**/
22
23#include "libnetxms.h"
24
def5b792
AK
25#define TELNET_WILL 0xFB
26#define TELNET_WONT 0xFC
27#define TELNET_DO 0xFD
28#define TELNET_DONT 0xFE
29#define TELNET_IAC 0xFF
30#define TELNET_GA 0xF9
1671f85d
VK
31
32/**
33 * Helper fuction to create connected SocketConnection object.
34 *
35 * @param hostName host name or IP address
36 * @param port port number
37 * @param timeout connection timeout in milliseconds
38 * @return connected SocketConnection object or NULL on connection failure
39 */
40SocketConnection *SocketConnection::createTCPConnection(const TCHAR *hostName, WORD port, DWORD timeout)
41{
42 SocketConnection *s = new SocketConnection;
43 if (!s->connectTCP(hostName, port, timeout))
44 {
45 delete s;
46 s = NULL;
47 }
48 return s;
49}
50
58ee2f43
VK
51/**
52 * Default constructor
53 */
54SocketConnection::SocketConnection()
55{
56 m_dataPos = 0;
57 m_data[0] = 0;
58}
59
60/**
61 * Destructor
62 */
63SocketConnection::~SocketConnection()
64{
65 if (m_socket != INVALID_SOCKET)
66 closesocket(m_socket);
67}
68
69/**
70 * Establish TCP connection to given host and port
71 *
72 * @param hostName host name or IP address
73 * @param port port number
74 * @param timeout connection timeout in milliseconds
1671f85d 75 * @return true if connection attempt was successful
58ee2f43 76 */
da73681c 77bool SocketConnection::connectTCP(const TCHAR *hostName, WORD port, DWORD timeout)
58ee2f43
VK
78{
79 m_socket = socket(AF_INET, SOCK_STREAM, 0);
80 if (m_socket != INVALID_SOCKET)
81 {
82 struct sockaddr_in sa;
83 sa.sin_family = AF_INET;
84 sa.sin_port = htons(port);
85 sa.sin_addr.s_addr = ResolveHostName(hostName);
86
87 if (ConnectEx(m_socket, (struct sockaddr*)&sa, sizeof(sa), (timeout != 0) ? timeout : 30000) < 0)
88 {
89 closesocket(m_socket);
90 m_socket = INVALID_SOCKET;
91 }
92 }
93
94 return m_socket != INVALID_SOCKET;
95}
96
97/**
98 * Check if given socket can be read
99 */
100bool SocketConnection::canRead(DWORD timeout)
101{
102 bool ret = false;
103 struct timeval tv;
104 fd_set readFdSet;
105
106 FD_ZERO(&readFdSet);
107 FD_SET(m_socket, &readFdSet);
108 tv.tv_sec = timeout / 1000;
109 tv.tv_usec = (timeout % 1000) * 1000;
110
111 if (select(SELECT_NFDS(m_socket + 1), &readFdSet, NULL, NULL, &tv) > 0)
112 {
113 ret = true;
114 }
115
116 return ret;
117}
118
119/**
120 * Read data from socket
121 */
37a83e60 122int SocketConnection::read(char *pBuff, int nSize, DWORD timeout)
58ee2f43 123{
37a83e60 124 return RecvEx(m_socket, pBuff, nSize, 0, timeout);
58ee2f43
VK
125}
126
127/**
128 * Write data to socket
129 */
130int SocketConnection::write(const char *pBuff, int nSize)
131{
132 return SendEx(m_socket, pBuff, nSize, 0, NULL);
133}
134
135/**
136 * Write line to socket (send CR/LF pair after data block
137 */
1671f85d 138bool SocketConnection::writeLine(const char *line)
58ee2f43
VK
139{
140 if (write(line, (int)strlen(line)) <= 0)
141 return false;
142 return write("\r\n", 2) > 0;
143}
144
145/**
146 * Close socket
147 */
148void SocketConnection::disconnect()
149{
150 shutdown(m_socket, SHUT_RDWR);
151 closesocket(m_socket);
152 m_socket = INVALID_SOCKET;
153}
154
155/**
156 * Wait for specific text in input stream. All data up to given text are discarded.
157 */
158bool SocketConnection::waitForText(const char *text, int timeout)
159{
160 int textLen = (int)strlen(text);
161 int bufLen = (int)strlen(m_data);
162
163 char *p = strstr(m_data, text);
164 if (p != NULL)
165 {
166 int index = (int)(p - m_data);
167 m_dataPos = bufLen - (index + textLen);
c090edaa 168 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
58ee2f43
VK
169 return true;
170 }
171
172 m_dataPos = min(bufLen, textLen - 1);
c090edaa 173 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
58ee2f43
VK
174
175 while(1)
176 {
177 if (!canRead(timeout))
b08a37b8 178 {
58ee2f43 179 return false;
b08a37b8 180 }
58ee2f43
VK
181
182 int size = read(&m_data[m_dataPos], 4095 - m_dataPos);
183 m_data[size + m_dataPos] = 0;
184 bufLen = (int)strlen(m_data);
185
186 p = strstr(m_data, text);
187 if (p != NULL)
188 {
189 int index = (int)(p - m_data);
190 m_dataPos = bufLen - (index + textLen);
b08a37b8 191 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
58ee2f43
VK
192 return true;
193 }
194
195 m_dataPos = min(bufLen, textLen - 1);
196 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos);
197 }
198}
def5b792
AK
199
200
201/**
202 * Establish TCP connection to given host and port
203 *
204 * @param hostName host name or IP address
205 * @param port port number
206 * @param timeout connection timeout in milliseconds
207 * @return true if connection attempt was successful
208 */
209bool TelnetConnection::connect(const TCHAR *hostName, WORD port, DWORD timeout)
210{
211 bool ret = SocketConnection::connectTCP(hostName, port, timeout);
212
213 if (ret)
214 {
215 // disable echo
216 unsigned char out[3];
217 out[0] = TELNET_IAC;
218 out[1] = TELNET_WILL;
219 out[2] = 0x01; // echo
220 write((char *)out, 3);
221 }
222
223 return ret;
224}
225
226/**
227 * Read data from socket
228 */
229int TelnetConnection::read(char *pBuff, int nSize, DWORD timeout)
230{
231 int bytesRead = RecvEx(m_socket, pBuff, nSize, 0, timeout);
232 if (bytesRead > 0)
233 {
234 // process telnet control sequences
235 for (int i = 0; i < bytesRead - 1; i++)
236 {
237 int skip = 0;
238 switch ((unsigned char)pBuff[i])
239 {
240 case TELNET_IAC: // "Interpret as Command"
241 {
242 unsigned char cmd = (unsigned char)pBuff[i + 1];
243
244 switch (cmd)
245 {
246 case TELNET_IAC:
247 // Duplicate IAC - data byte 0xFF, just deduplicate
248 skip = 1;
249 break;
250 case TELNET_WILL:
251 case TELNET_DO:
252 case TELNET_DONT:
253 case TELNET_WONT:
254 if ((i + 1) < bytesRead)
255 {
256 skip = 3;
257 if ((unsigned char)pBuff[i + 2] == TELNET_GA)
258 {
259 pBuff[i + 1] = cmd == TELNET_DO ? TELNET_WILL : TELNET_DO;
260 }
261 else
262 {
263 pBuff[i + 1] = cmd == TELNET_DO ? TELNET_WONT : TELNET_DONT;
264 }
265 write(pBuff + i, 3);
266 }
267 break;
268 default:
269 skip = 2; // skip IAC + unhandled command
270 }
271 }
272 break;
273 case 0:
274 // "No Operation", skip
275 skip = 1;
276 }
277
278 if (skip > 0)
279 {
280 memmove(pBuff + i, pBuff + i + skip, bytesRead - i - 1);
281 bytesRead -= skip;
282 i--;
283 }
284 }
285 }
286
287 return bytesRead;
288}
289
290/**
291 * Helper fuction to create connected TelnetConnection object.
292 *
293 * @param hostName host name or IP address
294 * @param port port number
295 * @param timeout connection timeout in milliseconds
296 * @return connected TelnetConnection object or NULL on connection failure
297 */
298TelnetConnection *TelnetConnection::createConnection(const TCHAR *hostName, WORD port, DWORD timeout)
299{
300 TelnetConnection *tc = new TelnetConnection();
301 if (!tc->connect(hostName, port, timeout))
302 {
303 delete tc;
304 tc = NULL;
305 }
306
307 return tc;
308}