added macro DISABLE_COPY_CTOR; minor refactoring
[public/netxms.git] / src / libnetxms / net.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
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
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
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 */
40 SocketConnection *SocketConnection::createTCPConnection(const TCHAR *hostName, WORD port, UINT32 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
51 /**
52 * Default constructor
53 */
54 SocketConnection::SocketConnection()
55 {
56 m_dataPos = 0;
57 m_data[0] = 0;
58 m_socket = INVALID_SOCKET;
59 }
60
61 /**
62 * Destructor
63 */
64 SocketConnection::~SocketConnection()
65 {
66 if (m_socket != INVALID_SOCKET)
67 closesocket(m_socket);
68 }
69
70 /**
71 * Establish TCP connection to given host and port
72 *
73 * @param hostName host name or IP address
74 * @param port port number
75 * @param timeout connection timeout in milliseconds
76 * @return true if connection attempt was successful
77 */
78 bool SocketConnection::connectTCP(const TCHAR *hostName, WORD port, UINT32 timeout)
79 {
80 InetAddress addr = InetAddress::resolveHostName(hostName);
81 if (!addr.isValidUnicast())
82 return false;
83 return connectTCP(addr, port, timeout);
84 }
85
86 /**
87 * Establish TCP connection to given host and port
88 *
89 * @param hostName host name or IP address in host byte order
90 * @param port port number in host byte order
91 * @param timeout connection timeout in milliseconds
92 * @return true if connection attempt was successful
93 */
94 bool SocketConnection::connectTCP(const InetAddress& ip, WORD port, UINT32 timeout)
95 {
96 m_socket = socket(ip.getFamily(), SOCK_STREAM, 0);
97 if (m_socket != INVALID_SOCKET)
98 {
99 SockAddrBuffer sa;
100 ip.fillSockAddr(&sa, port);
101 if (ConnectEx(m_socket, (struct sockaddr *)&sa, SA_LEN((struct sockaddr *)&sa), (timeout != 0) ? timeout : 30000) < 0)
102 {
103 closesocket(m_socket);
104 m_socket = INVALID_SOCKET;
105 }
106 SetSocketNonBlocking(m_socket);
107 }
108
109 return m_socket != INVALID_SOCKET;
110 }
111
112 /**
113 * Check if given socket can be read
114 */
115 bool SocketConnection::canRead(UINT32 timeout)
116 {
117 SocketPoller p;
118 p.add(m_socket);
119 return p.poll(timeout) > 0;
120 }
121
122 /**
123 * Read data from socket
124 */
125 int SocketConnection::read(char *pBuff, int nSize, UINT32 timeout)
126 {
127 return RecvEx(m_socket, pBuff, nSize, 0, timeout);
128 }
129
130 /**
131 * Write data to socket
132 */
133 int SocketConnection::write(const char *pBuff, int nSize)
134 {
135 return SendEx(m_socket, pBuff, nSize, 0, NULL);
136 }
137
138 /**
139 * Write line to socket (send CR/LF pair after data block
140 */
141 bool SocketConnection::writeLine(const char *line)
142 {
143 if (write(line, (int)strlen(line)) <= 0)
144 return false;
145 return write("\r\n", 2) > 0;
146 }
147
148 /**
149 * Close socket
150 */
151 void SocketConnection::disconnect()
152 {
153 shutdown(m_socket, SHUT_RDWR);
154 closesocket(m_socket);
155 m_socket = INVALID_SOCKET;
156 }
157
158 /**
159 * Wait for specific text in input stream. All data up to given text are discarded.
160 */
161 bool SocketConnection::waitForText(const char *text, int timeout)
162 {
163 int textLen = (int)strlen(text);
164 int bufLen = (int)strlen(m_data);
165
166 char *p = strstr(m_data, text);
167 if (p != NULL)
168 {
169 int index = (int)(p - m_data);
170 m_dataPos = bufLen - (index + textLen);
171 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
172 return true;
173 }
174
175 m_dataPos = std::min(bufLen, textLen - 1);
176 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
177
178 while(1)
179 {
180 if (!canRead(timeout))
181 {
182 return false;
183 }
184
185 int size = read(&m_data[m_dataPos], 4095 - m_dataPos);
186 if ((size <= 0) && (WSAGetLastError() != WSAEWOULDBLOCK) && (WSAGetLastError() != WSAEINPROGRESS))
187 {
188 return false;
189 }
190
191 m_data[size + m_dataPos] = 0;
192 bufLen = (int)strlen(m_data);
193
194 p = strstr(m_data, text);
195 if (p != NULL)
196 {
197 int index = (int)(p - m_data);
198 m_dataPos = bufLen - (index + textLen);
199 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
200 return true;
201 }
202
203 m_dataPos = std::min(bufLen, textLen - 1);
204 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos);
205 }
206 }
207
208 /*
209 * Establish TCP connection to given host and port
210 *
211 * @param hostName host name or IP address
212 * @param port port number in host byte order
213 * @param timeout connection timeout in milliseconds
214 * @return true if connection attempt was successful
215 */
216 bool TelnetConnection::connect(const TCHAR *hostName, WORD port, UINT32 timeout)
217 {
218 InetAddress addr = InetAddress::resolveHostName(hostName);
219 if (!addr.isValidUnicast())
220 return false;
221 return connect(addr, port, timeout);
222 }
223
224 /**
225 * Establish TCP connection to given host and port
226 *
227 * @param hostName host name or IP address in host byte order
228 * @param port port number in host byte order
229 * @param timeout connection timeout in milliseconds
230 * @return true if connection attempt was successful
231 */
232 bool TelnetConnection::connect(const InetAddress& ip, WORD port, UINT32 timeout)
233 {
234 bool ret = SocketConnection::connectTCP(ip, port, timeout);
235
236 if (ret)
237 {
238 // disable echo
239 unsigned char out[3];
240 out[0] = TELNET_IAC;
241 out[1] = TELNET_WONT;
242 out[2] = 0x01; // echo
243 write((char *)out, 3);
244 }
245
246 return ret;
247 }
248
249 /**
250 * Read data from socket
251 */
252 int TelnetConnection::read(char *pBuff, int nSize, UINT32 timeout)
253 {
254 retry:
255 int bytesRead = RecvEx(m_socket, pBuff, nSize, 0, timeout);
256 if (bytesRead > 0)
257 {
258 // process telnet control sequences
259 for (int i = 0; i < bytesRead - 1; i++)
260 {
261 int skip = 0;
262 switch ((unsigned char)pBuff[i])
263 {
264 case TELNET_IAC: // "Interpret as Command"
265 {
266 unsigned char cmd = (unsigned char)pBuff[i + 1];
267
268 switch (cmd)
269 {
270 case TELNET_IAC:
271 // Duplicate IAC - data byte 0xFF, just deduplicate
272 skip = 1;
273 break;
274 case TELNET_WILL:
275 case TELNET_DO:
276 case TELNET_DONT:
277 case TELNET_WONT:
278 if ((i + 1) < bytesRead)
279 {
280 skip = 3;
281 if ((unsigned char)pBuff[i + 2] == TELNET_GA)
282 {
283 pBuff[i + 1] = cmd == TELNET_DO ? TELNET_WILL : TELNET_DO;
284 }
285 else
286 {
287 pBuff[i + 1] = cmd == TELNET_DO ? TELNET_WONT : TELNET_DONT;
288 }
289 write(pBuff + i, 3);
290 }
291 break;
292 default:
293 skip = 2; // skip IAC + unhandled command
294 }
295 }
296 break;
297 case 0:
298 // "No Operation", skip
299 skip = 1;
300 }
301
302 if (skip > 0)
303 {
304 memmove(pBuff + i, pBuff + i + skip, bytesRead - i - 1);
305 bytesRead -= skip;
306 i--;
307 }
308 }
309
310 // Only control sequence was received, read more data
311 if (bytesRead == 0)
312 goto retry;
313 }
314
315 return bytesRead;
316 }
317
318
319 /**
320 * Read line from socket
321 */
322 int TelnetConnection::readLine(char *buffer, int size, UINT32 timeout)
323 {
324 int numOfChars = 0;
325 int bytesRead = 0;
326 while (true) {
327 bytesRead = read(buffer + numOfChars, 1, timeout);
328 if (bytesRead <= 0) {
329 break;
330 }
331
332 if (buffer[numOfChars] == 0x0d || buffer[numOfChars] == 0x0a)
333 {
334 if (numOfChars == 0) {
335 // ignore leading new line characters
336 }
337 else
338 {
339 // got complete string, return
340 break;
341 }
342 }
343 else
344 {
345 numOfChars++;
346 }
347 };
348
349 buffer[numOfChars] = 0;
350 return numOfChars;
351 }
352
353 /**
354 * Helper fuction to create connected TelnetConnection object.
355 *
356 * @param hostName host name or IP address
357 * @param port port number
358 * @param timeout connection timeout in milliseconds
359 * @return connected TelnetConnection object or NULL on connection failure
360 */
361 TelnetConnection *TelnetConnection::createConnection(const TCHAR *hostName, WORD port, UINT32 timeout)
362 {
363 TelnetConnection *tc = new TelnetConnection();
364 if (!tc->connect(hostName, port, timeout))
365 {
366 delete tc;
367 tc = NULL;
368 }
369
370 return tc;
371 }
372
373 /**
374 * Helper fuction to create connected TelnetConnection object.
375 *
376 * @param ip IP address
377 * @param port port number
378 * @param timeout connection timeout in milliseconds
379 * @return connected TelnetConnection object or NULL on connection failure
380 */
381 TelnetConnection *TelnetConnection::createConnection(const InetAddress& ip, WORD port, UINT32 timeout)
382 {
383 TelnetConnection *tc = new TelnetConnection();
384 if (!tc->connect(ip, port, timeout))
385 {
386 delete tc;
387 tc = NULL;
388 }
389
390 return tc;
391 }