1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 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 General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 #include "comms.h"
22 #include "telnet.h"
23 #include "log.h"
24 
25 static char	prompt_char = '\0';
26 
telnet_waitsocket(ZBX_SOCKET socket_fd,int mode)27 static int	telnet_waitsocket(ZBX_SOCKET socket_fd, int mode)
28 {
29 	const char	*__function_name = "telnet_waitsocket";
30 	struct timeval	tv;
31 	int		rc;
32 	fd_set		fd, *readfd = NULL, *writefd = NULL;
33 
34 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
35 
36 	tv.tv_sec = 0;
37 	tv.tv_usec = 100000;	/* 1/10 sec */
38 
39 	FD_ZERO(&fd);
40 	FD_SET(socket_fd, &fd);
41 
42 	if (WAIT_READ == mode)
43 		readfd = &fd;
44 	else
45 		writefd = &fd;
46 
47 	rc = select(ZBX_SOCKET_TO_INT(socket_fd) + 1, readfd, writefd, NULL, &tv);
48 
49 	if (ZBX_PROTO_ERROR == rc)
50 	{
51 		zabbix_log(LOG_LEVEL_DEBUG, "%s() rc:%d errno:%d error:[%s]", __function_name, rc,
52 				zbx_socket_last_error(), strerror_from_system(zbx_socket_last_error()));
53 	}
54 
55 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%d", __function_name, rc);
56 
57 	return rc;
58 }
59 
telnet_socket_read(ZBX_SOCKET socket_fd,void * buf,size_t count)60 static ssize_t	telnet_socket_read(ZBX_SOCKET socket_fd, void *buf, size_t count)
61 {
62 	const char	*__function_name = "telnet_socket_read";
63 	ssize_t		rc;
64 	int		error;
65 
66 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
67 
68 	while (ZBX_PROTO_ERROR == (rc = ZBX_TCP_READ(socket_fd, buf, count)))
69 	{
70 		error = zbx_socket_last_error();	/* zabbix_log() resets the error code */
71 		zabbix_log(LOG_LEVEL_DEBUG, "%s() rc:%ld errno:%d error:[%s]",
72 				__function_name, (long int)rc, error, strerror_from_system(error));
73 #ifdef _WINDOWS
74 		if (WSAEWOULDBLOCK == error)
75 #else
76 		if (EAGAIN == error)
77 #endif
78 		{
79 			/* wait and if there is still an error or no input available */
80 			/* we assume the other side has nothing more to say */
81 			if (1 > (rc = telnet_waitsocket(socket_fd, WAIT_READ)))
82 				goto ret;
83 
84 			continue;
85 		}
86 
87 		break;
88 	}
89 
90 	/* when ZBX_TCP_READ returns 0, it means EOF - let's consider it a permanent error */
91 	/* note that if telnet_waitsocket() is zero, it is not a permanent condition */
92 	if (0 == rc)
93 		rc = ZBX_PROTO_ERROR;
94 ret:
95 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%ld", __function_name, (long int)rc);
96 
97 	return rc;
98 }
99 
telnet_socket_write(ZBX_SOCKET socket_fd,const void * buf,size_t count)100 static ssize_t	telnet_socket_write(ZBX_SOCKET socket_fd, const void *buf, size_t count)
101 {
102 	const char	*__function_name = "telnet_socket_write";
103 	ssize_t		rc;
104 	int		error;
105 
106 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
107 
108 	while (ZBX_PROTO_ERROR == (rc = ZBX_TCP_WRITE(socket_fd, buf, count)))
109 	{
110 		error = zbx_socket_last_error();	/* zabbix_log() resets the error code */
111 		zabbix_log(LOG_LEVEL_DEBUG, "%s() rc:%ld errno:%d error:[%s]",
112 				__function_name, (long int)rc, error, strerror_from_system(error));
113 #ifdef _WINDOWS
114 		if (WSAEWOULDBLOCK == error)
115 #else
116 		if (EAGAIN == error)
117 #endif
118 		{
119 			telnet_waitsocket(socket_fd, WAIT_WRITE);
120 			continue;
121 		}
122 
123 		break;
124 	}
125 
126 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%ld", __function_name, (long int)rc);
127 
128 	return rc;
129 }
130 
telnet_read(ZBX_SOCKET socket_fd,char * buf,size_t * buf_left,size_t * buf_offset)131 static ssize_t	telnet_read(ZBX_SOCKET socket_fd, char *buf, size_t *buf_left, size_t *buf_offset)
132 {
133 	const char	*__function_name = "telnet_read";
134 	unsigned char	c, c1, c2, c3;
135 	ssize_t		rc = ZBX_PROTO_ERROR;
136 
137 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
138 
139 	for (;;)
140 	{
141 		if (1 > (rc = telnet_socket_read(socket_fd, &c1, 1)))
142 			break;
143 
144 		zabbix_log(LOG_LEVEL_DEBUG, "%s() c1:[%x=%c]", __function_name, c1, isprint(c1) ? c1 : ' ');
145 
146 		switch (c1)
147 		{
148 			case CMD_IAC:
149 				while (0 == (rc = telnet_socket_read(socket_fd, &c2, 1)))
150 					;
151 
152 				if (ZBX_PROTO_ERROR == rc)
153 					goto end;
154 
155 				zabbix_log(LOG_LEVEL_DEBUG, "%s() c2:%x", __function_name, c2);
156 
157 				switch (c2)
158 				{
159 					case CMD_IAC: 	/* only IAC needs to be doubled to be sent as data */
160 						if (0 < *buf_left)
161 						{
162 							buf[(*buf_offset)++] = (char)c2;
163 							(*buf_left)--;
164 						}
165 						break;
166 					case CMD_WILL:
167 					case CMD_WONT:
168 					case CMD_DO:
169 					case CMD_DONT:
170 						while (0 == (rc = telnet_socket_read(socket_fd, &c3, 1)))
171 							;
172 
173 						if (ZBX_PROTO_ERROR == rc)
174 							goto end;
175 
176 						zabbix_log(LOG_LEVEL_DEBUG, "%s() c3:%x", __function_name, c3);
177 
178 						/* reply to all options with "WONT" or "DONT", */
179 						/* unless it is Suppress Go Ahead (SGA)        */
180 
181 						c = CMD_IAC;
182 						telnet_socket_write(socket_fd, &c, 1);
183 
184 						if (CMD_WONT == c2)
185 							c = CMD_DONT;	/* the only valid response */
186 						else if (CMD_DONT == c2)
187 							c = CMD_WONT;	/* the only valid response */
188 						else if (OPT_SGA == c3)
189 							c = (c2 == CMD_DO ? CMD_WILL : CMD_DO);
190 						else
191 							c = (c2 == CMD_DO ? CMD_WONT : CMD_DONT);
192 
193 						telnet_socket_write(socket_fd, &c, 1);
194 						telnet_socket_write(socket_fd, &c3, 1);
195 						break;
196 					default:
197 						break;
198 				}
199 				break;
200 			default:
201 				if (0 < *buf_left)
202 				{
203 					buf[(*buf_offset)++] = (char)c1;
204 					(*buf_left)--;
205 				}
206 				break;
207 		}
208 	}
209 end:
210 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%d", __function_name, (int)rc);
211 
212 	return rc;
213 }
214 
215 /******************************************************************************
216  *                                                                            *
217  * Comments: converts CR+LF to Unix LF and clears CR+NUL                      *
218  *                                                                            *
219  ******************************************************************************/
convert_telnet_to_unix_eol(char * buf,size_t * offset)220 static void	convert_telnet_to_unix_eol(char *buf, size_t *offset)
221 {
222 	size_t	i, sz = *offset, new_offset;
223 
224 	new_offset = 0;
225 
226 	for (i = 0; i < sz; i++)
227 	{
228 		if (i + 1 < sz && '\r' == buf[i] && '\n' == buf[i + 1])		/* CR+LF (Windows) */
229 		{
230 			buf[new_offset++] = '\n';
231 			i++;
232 		}
233 		else if (i + 1 < sz && '\r' == buf[i] && '\0' == buf[i + 1])	/* CR+NUL */
234 		{
235 			i++;
236 		}
237 		else if (i + 1 < sz && '\n' == buf[i] && '\r' == buf[i + 1])	/* LF+CR */
238 		{
239 			buf[new_offset++] = '\n';
240 			i++;
241 		}
242 		else if ('\r' == buf[i])					/* CR */
243 		{
244 			buf[new_offset++] = '\n';
245 		}
246 		else
247 			buf[new_offset++] = buf[i];
248 	}
249 
250 	*offset = new_offset;
251 }
252 
convert_unix_to_telnet_eol(const char * buf,size_t offset,char * out_buf,size_t * out_offset)253 static void	convert_unix_to_telnet_eol(const char *buf, size_t offset, char *out_buf, size_t *out_offset)
254 {
255 	size_t	i;
256 
257 	*out_offset = 0;
258 
259 	for (i = 0; i < offset; i++)
260 	{
261 		if ('\n' != buf[i])
262 		{
263 			out_buf[(*out_offset)++] = buf[i];
264 		}
265 		else
266 		{
267 			out_buf[(*out_offset)++] = '\r';
268 			out_buf[(*out_offset)++] = '\n';
269 		}
270 	}
271 }
272 
telnet_lastchar(const char * buf,size_t offset)273 static char	telnet_lastchar(const char *buf, size_t offset)
274 {
275 	while (0 < offset)
276 	{
277 		offset--;
278 		if (' ' != buf[offset])
279 			return buf[offset];
280 	}
281 
282 	return '\0';
283 }
284 
telnet_rm_echo(char * buf,size_t * offset,const char * echo,size_t len)285 static int	telnet_rm_echo(char *buf, size_t *offset, const char *echo, size_t len)
286 {
287 	if (0 == memcmp(buf, echo, len))
288 	{
289 		*offset -= len;
290 		memmove(&buf[0], &buf[len], *offset * sizeof(char));
291 
292 		return SUCCEED;
293 	}
294 
295 	return FAIL;
296 }
297 
telnet_rm_prompt(const char * buf,size_t * offset)298 static void	telnet_rm_prompt(const char *buf, size_t *offset)
299 {
300 	unsigned char	state = 0;	/* 0 - init, 1 - prompt */
301 
302 	while (0 < *offset)
303 	{
304 		(*offset)--;
305 		if (0 == state && buf[*offset] == prompt_char)
306 			state = 1;
307 		if (1 == state && buf[*offset] == '\n')
308 			break;
309 	}
310 }
311 
telnet_test_login(ZBX_SOCKET socket_fd)312 int	telnet_test_login(ZBX_SOCKET socket_fd)
313 {
314 	const char	*__function_name = "telnet_test_login";
315 	char		buf[MAX_BUFFER_LEN];
316 	size_t		sz, offset;
317 	int		rc, ret = FAIL;
318 
319 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
320 
321 	sz = sizeof(buf);
322 	offset = 0;
323 	while (ZBX_PROTO_ERROR != (rc = telnet_read(socket_fd, buf, &sz, &offset)))
324 	{
325 		if (':' == telnet_lastchar(buf, offset))
326 			break;
327 	}
328 
329 	convert_telnet_to_unix_eol(buf, &offset);
330 	zabbix_log(LOG_LEVEL_DEBUG, "%s() login prompt:'%.*s'", __function_name, (int)offset, buf);
331 
332 	if (ZBX_PROTO_ERROR != rc)
333 		ret = SUCCEED;
334 
335 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
336 
337 	return ret;
338 }
339 
telnet_login(ZBX_SOCKET socket_fd,const char * username,const char * password,AGENT_RESULT * result)340 int	telnet_login(ZBX_SOCKET socket_fd, const char *username, const char *password, AGENT_RESULT *result)
341 {
342 	const char	*__function_name = "telnet_login";
343 	char		buf[MAX_BUFFER_LEN], c;
344 	size_t		sz, offset;
345 	int		rc, ret = FAIL;
346 
347 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
348 
349 	sz = sizeof(buf);
350 	offset = 0;
351 	while (ZBX_PROTO_ERROR != (rc = telnet_read(socket_fd, buf, &sz, &offset)))
352 	{
353 		if (':' == telnet_lastchar(buf, offset))
354 			break;
355 	}
356 
357 	convert_telnet_to_unix_eol(buf, &offset);
358 	zabbix_log(LOG_LEVEL_DEBUG, "%s() login prompt:'%.*s'", __function_name, (int)offset, buf);
359 
360 	if (ZBX_PROTO_ERROR == rc)
361 	{
362 		SET_MSG_RESULT(result, zbx_strdup(NULL, "No login prompt."));
363 		goto fail;
364 	}
365 
366 	telnet_socket_write(socket_fd, username, strlen(username));
367 	telnet_socket_write(socket_fd, "\r\n", 2);
368 
369 	sz = sizeof(buf);
370 	offset = 0;
371 	while (ZBX_PROTO_ERROR != (rc = telnet_read(socket_fd, buf, &sz, &offset)))
372 	{
373 		if (':' == telnet_lastchar(buf, offset))
374 			break;
375 	}
376 
377 	convert_telnet_to_unix_eol(buf, &offset);
378 	zabbix_log(LOG_LEVEL_DEBUG, "%s() password prompt:'%.*s'", __function_name, (int)offset, buf);
379 
380 	if (ZBX_PROTO_ERROR == rc)
381 	{
382 		SET_MSG_RESULT(result, zbx_strdup(NULL, "No password prompt."));
383 		goto fail;
384 	}
385 
386 	telnet_socket_write(socket_fd, password, strlen(password));
387 	telnet_socket_write(socket_fd, "\r\n", 2);
388 
389 	sz = sizeof(buf);
390 	offset = 0;
391 	while (ZBX_PROTO_ERROR != (rc = telnet_read(socket_fd, buf, &sz, &offset)))
392 	{
393 		if ('$' == (c = telnet_lastchar(buf, offset)) || '#' == c || '>' == c || '%' == c)
394 		{
395 			prompt_char = c;
396 			break;
397 		}
398 	}
399 
400 	convert_telnet_to_unix_eol(buf, &offset);
401 	zabbix_log(LOG_LEVEL_DEBUG, "%s() prompt:'%.*s'", __function_name, (int)offset, buf);
402 
403 	if (ZBX_PROTO_ERROR == rc)
404 	{
405 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Login failed."));
406 		goto fail;
407 	}
408 
409 	ret = SUCCEED;
410 fail:
411 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
412 
413 	return ret;
414 }
415 
telnet_execute(ZBX_SOCKET socket_fd,const char * command,AGENT_RESULT * result,const char * encoding)416 int	telnet_execute(ZBX_SOCKET socket_fd, const char *command, AGENT_RESULT *result, const char *encoding)
417 {
418 	const char	*__function_name = "telnet_execute";
419 	char		buf[MAX_BUFFER_LEN];
420 	size_t		sz, offset;
421 	int		rc, ret = FAIL;
422 	char		*command_lf = NULL, *command_crlf = NULL;
423 	size_t		i, offset_lf, offset_crlf;
424 
425 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
426 
427 	/* `command' with multiple lines may contain CR+LF from the browser;	*/
428 	/* it should be converted to plain LF to remove echo later on properly	*/
429 	offset_lf = strlen(command);
430 	command_lf = (char *)zbx_malloc(command_lf, offset_lf + 1);
431 	zbx_strlcpy(command_lf, command, offset_lf + 1);
432 	convert_telnet_to_unix_eol(command_lf, &offset_lf);
433 
434 	/* telnet protocol requires that end-of-line is transferred as CR+LF	*/
435 	command_crlf = (char *)zbx_malloc(command_crlf, offset_lf * 2 + 1);
436 	convert_unix_to_telnet_eol(command_lf, offset_lf, command_crlf, &offset_crlf);
437 
438 	telnet_socket_write(socket_fd, command_crlf, offset_crlf);
439 	telnet_socket_write(socket_fd, "\r\n", 2);
440 
441 	sz = sizeof(buf);
442 	offset = 0;
443 	while (ZBX_PROTO_ERROR != (rc = telnet_read(socket_fd, buf, &sz, &offset)))
444 	{
445 		if (prompt_char == telnet_lastchar(buf, offset))
446 			break;
447 	}
448 
449 	convert_telnet_to_unix_eol(buf, &offset);
450 	zabbix_log(LOG_LEVEL_DEBUG, "%s() command output:'%.*s'", __function_name, (int)offset, buf);
451 
452 	if (ZBX_PROTO_ERROR == rc)
453 	{
454 		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot find prompt after command execution: %s",
455 				strerror_from_system(zbx_socket_last_error())));
456 		goto fail;
457 	}
458 
459 	telnet_rm_echo(buf, &offset, command_lf, offset_lf);
460 
461 	/* multi-line commands may have returned additional prompts;	*/
462 	/* this is not a perfect solution, because in case of multiple	*/
463 	/* multi-line shell statements these prompts might appear in	*/
464 	/* the middle of the output, but we still try to be helpful by	*/
465 	/* removing additional prompts at least from the beginning	*/
466 	for (i = 0; i < offset_lf; i++)
467 	{
468 		if ('\n' == command_lf[i])
469 		{
470 			if (SUCCEED != telnet_rm_echo(buf, &offset, "$ ", 2) &&
471 				SUCCEED != telnet_rm_echo(buf, &offset, "# ", 2) &&
472 				SUCCEED != telnet_rm_echo(buf, &offset, "> ", 2) &&
473 				SUCCEED != telnet_rm_echo(buf, &offset, "% ", 2))
474 			{
475 				break;
476 			}
477 		}
478 	}
479 
480 	telnet_rm_echo(buf, &offset, "\n", 1);
481 	telnet_rm_prompt(buf, &offset);
482 
483 	zabbix_log(LOG_LEVEL_DEBUG, "%s() stripped command output:'%.*s'", __function_name, (int)offset, buf);
484 
485 	if (MAX_BUFFER_LEN == offset)
486 		offset--;
487 	buf[offset] = '\0';
488 
489 	SET_STR_RESULT(result, convert_to_utf8(buf, offset, encoding));
490 	ret = SUCCEED;
491 fail:
492 	zbx_free(command_lf);
493 	zbx_free(command_crlf);
494 
495 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
496 
497 	return ret;
498 }
499