TelnetConnection added
[public/netxms.git] / src / libnetxms / net.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2012 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, 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
51 /**
52 * Default constructor
53 */
54 SocketConnection::SocketConnection()
55 {
56 m_dataPos = 0;
57 m_data[0] = 0;
58 }
59
60 /**
61 * Destructor
62 */
63 SocketConnection::~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
75 * @return true if connection attempt was successful
76 */
77 bool SocketConnection::connectTCP(const TCHAR *hostName, WORD port, DWORD timeout)
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 */
100 bool 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 */
122 int SocketConnection::read(char *pBuff, int nSize, DWORD timeout)
123 {
124 return RecvEx(m_socket, pBuff, nSize, 0, timeout);
125 }
126
127 /**
128 * Write data to socket
129 */
130 int 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 */
138 bool SocketConnection::writeLine(const char *line)
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 */
148 void 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 */
158 bool 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);
168 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
169 return true;
170 }
171
172 m_dataPos = min(bufLen, textLen - 1);
173 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
174
175 while(1)
176 {
177 if (!canRead(timeout))
178 {
179 return false;
180 }
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);
191 memmove(m_data, &m_data[bufLen - m_dataPos], m_dataPos + 1);
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 }
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 */
209 bool 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 */
229 int 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 */
298 TelnetConnection *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 }