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