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