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 "log.h"
22 
23 #include "zbxmedia.h"
24 
25 #include <termios.h>
26 
write_gsm(int fd,const char * str,char * error,int max_error_len)27 static int	write_gsm(int fd, const char *str, char *error, int max_error_len)
28 {
29 	int	i, wlen, len, ret = SUCCEED;
30 
31 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() str:'%s'", __func__, str);
32 
33 	len = strlen(str);
34 
35 	for (wlen = 0; wlen < len; wlen += i)
36 	{
37 		if (-1 == (i = write(fd, str + wlen, len - wlen)))
38 		{
39 			i = 0;
40 
41 			if (EAGAIN == errno)
42 				continue;
43 
44 			zabbix_log(LOG_LEVEL_DEBUG, "error writing to GSM modem: %s", zbx_strerror(errno));
45 			if (NULL != error)
46 				zbx_snprintf(error, max_error_len, "error writing to GSM modem: %s", zbx_strerror(errno));
47 
48 			ret = FAIL;
49 			break;
50 		}
51 	}
52 
53 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
54 
55 	return ret;
56 }
57 
check_modem_result(char * buffer,char ** ebuf,char ** sbuf,const char * expect,char * error,int max_error_len)58 static int	check_modem_result(char *buffer, char **ebuf, char **sbuf, const char *expect, char *error,
59 		int max_error_len)
60 {
61 	char	rcv[0xff];
62 	int	i, len, ret = SUCCEED;
63 
64 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
65 
66 	zbx_strlcpy(rcv, *sbuf, sizeof(rcv));
67 
68 	do
69 	{
70 		len = *ebuf - *sbuf;
71 		for (i = 0; i < len && (*sbuf)[i] != '\n' && (*sbuf)[i] != '\r'; i++)
72 			; /* find first '\r' & '\n' */
73 
74 		if (i < len)
75 			(*sbuf)[i++] = '\0';
76 
77 		ret = (NULL == strstr(*sbuf, expect)) ? FAIL : SUCCEED;
78 
79 		*sbuf += i;
80 
81 		if (*sbuf != buffer)
82 		{
83 			memmove(buffer, *sbuf, *ebuf - *sbuf + 1); /* +1 for '\0' */
84 			*ebuf -= *sbuf - buffer;
85 			*sbuf = buffer;
86 		}
87 	}
88 	while (*sbuf < *ebuf && FAIL == ret);
89 
90 	if (FAIL == ret && NULL != error)
91 		zbx_snprintf(error, max_error_len, "Expected [%s] received [%s]", expect, rcv);
92 
93 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
94 
95 	return ret;
96 }
97 
98 #define MAX_ATTEMPTS	3
99 
read_gsm(int fd,const char * expect,char * error,int max_error_len,int timeout_sec)100 static int	read_gsm(int fd, const char *expect, char *error, int max_error_len, int timeout_sec)
101 {
102 	static char	buffer[0xff], *ebuf = buffer, *sbuf = buffer;
103 	fd_set		fdset;
104 	struct timeval  tv;
105 	int		i, nbytes, nbytes_total, rc, ret = SUCCEED;
106 
107 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() [%s] [%s] [%s] [%s]", __func__, expect,
108 			ebuf != buffer ? buffer : "NULL", ebuf != buffer ? ebuf : "NULL", ebuf != buffer ? sbuf : "NULL");
109 
110 	if ('\0' != *expect && ebuf != buffer &&
111 			SUCCEED == check_modem_result(buffer, &ebuf, &sbuf, expect, error, max_error_len))
112 	{
113 		goto out;
114 	}
115 
116 	/* make attempts to read until there is a printable character, which would indicate a result of the command */
117 
118 	for (i = 0; i < MAX_ATTEMPTS; i++)
119 	{
120 		tv.tv_sec = timeout_sec / MAX_ATTEMPTS;
121 		tv.tv_usec = (timeout_sec % MAX_ATTEMPTS) * 1000000 / MAX_ATTEMPTS;
122 
123 		/* wait for response from modem */
124 
125 		FD_ZERO(&fdset);
126 		FD_SET(fd, &fdset);
127 
128 		while (1)
129 		{
130 			rc = select(fd + 1, &fdset, NULL, NULL, &tv);
131 
132 			if (-1 == rc)
133 			{
134 				if (EINTR == errno)
135 					continue;
136 
137 				zabbix_log(LOG_LEVEL_DEBUG, "error select() for GSM modem: %s", zbx_strerror(errno));
138 
139 				if (NULL != error)
140 				{
141 					zbx_snprintf(error, max_error_len, "error select() for GSM modem: %s",
142 							zbx_strerror(errno));
143 				}
144 
145 				ret = FAIL;
146 				goto out;
147 			}
148 			else if (0 == rc)
149 			{
150 				/* timeout exceeded */
151 
152 				zabbix_log(LOG_LEVEL_DEBUG, "error during wait for GSM modem");
153 				if (NULL != error)
154 					zbx_snprintf(error, max_error_len, "error during wait for GSM modem");
155 
156 				goto check_result;
157 			}
158 			else
159 				break;
160 		}
161 
162 		/* read characters into our string buffer */
163 
164 		nbytes_total = 0;
165 
166 		while (0 < (nbytes = read(fd, ebuf, buffer + sizeof(buffer) - 1 - ebuf)))
167 		{
168 			ebuf += nbytes;
169 			*ebuf = '\0';
170 
171 			nbytes_total += nbytes;
172 
173 			zabbix_log(LOG_LEVEL_DEBUG, "Read attempt #%d from GSM modem [%s]", i, ebuf - nbytes);
174 		}
175 
176 		while (0 < nbytes_total)
177 		{
178 			if (0 == isspace(ebuf[-nbytes_total]))
179 				goto check_result;
180 
181 			nbytes_total--;
182 		}
183 	}
184 
185 	/* nul terminate the string and see if we got an OK response */
186 check_result:
187 	*ebuf = '\0';
188 
189 	zabbix_log(LOG_LEVEL_DEBUG, "Read from GSM modem [%s]", sbuf);
190 
191 	if ('\0' == *expect) /* empty */
192 	{
193 		sbuf = ebuf = buffer;
194 		*ebuf = '\0';
195 		goto out;
196 	}
197 
198 	ret = check_modem_result(buffer, &ebuf, &sbuf, expect, error, max_error_len);
199 out:
200 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
201 
202 	return ret;
203 }
204 
205 typedef struct
206 {
207 	const char	*message;
208 	const char	*result;
209 	int		timeout_sec;
210 }
211 zbx_sms_scenario;
212 
send_sms(const char * device,const char * number,const char * message,char * error,int max_error_len)213 int	send_sms(const char *device, const char *number, const char *message, char *error, int max_error_len)
214 {
215 #define	ZBX_AT_ESC	"\x1B"
216 #define ZBX_AT_CTRL_Z	"\x1A"
217 
218 	zbx_sms_scenario scenario[] =
219 	{
220 		{ZBX_AT_ESC	, NULL		, 0},	/* Send <ESC> */
221 		{"AT+CMEE=2\r"	, ""/*"OK"*/	, 5},	/* verbose error values */
222 		{"ATE0\r"	, "OK"		, 5},	/* Turn off echo */
223 		{"AT\r"		, "OK"		, 5},	/* Init modem */
224 		{"AT+CMGF=1\r"	, "OK"		, 5},	/* Switch to text mode */
225 		{"AT+CMGS=\""	, NULL		, 0},	/* Set phone number */
226 		{number		, NULL		, 0},	/* Write phone number */
227 		{"\"\r"		, "> "		, 5},	/* Set phone number */
228 		{message	, NULL		, 0},	/* Write message */
229 		{ZBX_AT_CTRL_Z	, "+CMGS: "	, 40},	/* Send message */
230 		{NULL		, "OK"		, 1},	/* ^Z */
231 		{NULL		, NULL		, 0}
232 	};
233 
234 	zbx_sms_scenario	*step;
235 	struct termios		options, old_options;
236 	int			f, ret = SUCCEED;
237 
238 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
239 
240 	if (-1 == (f = open(device, O_RDWR | O_NOCTTY | O_NDELAY)))
241 	{
242 		zabbix_log(LOG_LEVEL_DEBUG, "error in open(%s): %s", device, zbx_strerror(errno));
243 		if (NULL != error)
244 			zbx_snprintf(error, max_error_len, "error in open(%s): %s", device, zbx_strerror(errno));
245 		return FAIL;
246 	}
247 
248 	if (-1 == fcntl(f, F_SETFL, 0))
249 	{
250 		zabbix_log(LOG_LEVEL_DEBUG, "error in setting the status flag to 0 (for %s): %s", device,
251 				zbx_strerror(errno));
252 
253 		if (NULL != error)
254 		{
255 			zbx_snprintf(error, max_error_len, "error in setting the status flag to 0 (for %s): %s", device,
256 					zbx_strerror(errno));
257 		}
258 	}
259 
260 	/* set ta parameters */
261 	tcgetattr(f, &old_options);
262 
263 	memset(&options, 0, sizeof(options));
264 
265 	options.c_iflag = IGNCR | INLCR | ICRNL;
266 #ifdef ONOCR
267 	options.c_oflag = ONOCR;
268 #endif
269 	options.c_cflag = old_options.c_cflag | CRTSCTS | CS8 | CLOCAL | CREAD;
270 	options.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
271 	options.c_cc[VMIN] = 0;
272 	options.c_cc[VTIME] = 1;
273 
274 	tcsetattr(f, TCSANOW, &options);
275 
276 	for (step = scenario; NULL != step->message || NULL != step->result; step++)
277 	{
278 		if (NULL != step->message)
279 		{
280 			if (message == step->message)
281 			{
282 				char	*tmp;
283 
284 				tmp = zbx_strdup(NULL, message);
285 				zbx_remove_chars(tmp, "\r");
286 
287 				ret = write_gsm(f, tmp, error, max_error_len);
288 
289 				zbx_free(tmp);
290 			}
291 			else
292 				ret = write_gsm(f, step->message, error, max_error_len);
293 
294 			if (FAIL == ret)
295 				break;
296 		}
297 
298 		if (NULL != step->result)
299 		{
300 			if (FAIL == (ret = read_gsm(f, step->result, error, max_error_len, step->timeout_sec)))
301 				break;
302 		}
303 	}
304 
305 	if (FAIL == ret)
306 	{
307 		write_gsm(f, "\r" ZBX_AT_ESC ZBX_AT_CTRL_Z, NULL, 0); /* cancel all */
308 		read_gsm(f, "", NULL, 0, 0); /* clear buffer */
309 	}
310 
311 	tcsetattr(f, TCSANOW, &old_options);
312 	close(f);
313 
314 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
315 
316 	return ret;
317 }
318