1 /*
2 * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7 #define _POSIX_C_SOURCE 201108L
8 #define _XOPEN_SOURCE 600
9
10 #include <assert.h>
11 #include <ctype.h>
12 #include <fcntl.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <termios.h>
17 #include <unistd.h>
18
19 #include <glib.h>
20 #include <dbus/dbus-glib.h>
21
22 GIOChannel* ioc;
23 int masterfd;
24
25 typedef struct {
26 GRegex *command;
27 char *reply; // generic text
28 char *responsetext; // ERROR, +CMS ERROR, etc.
29 } Pattern;
30
31 typedef struct _FakeModem {
32 GObject parent;
33 gboolean echo;
34 gboolean verbose;
35 GPtrArray *patterns;
36 } FakeModem;
37
38 typedef struct _FakeModemClass
39 {
40 GObjectClass parent_class;
41 } FakeModemClass;
42
43 GType fakemodem_get_type (void) G_GNUC_CONST;
44
45 #define FAKEMODEM_TYPE (fake_modem_get_type ())
46 #define FAKEMODEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FAKEMODEM_TYPE, FakeModem))
47
G_DEFINE_TYPE(FakeModem,fake_modem,G_TYPE_OBJECT)48 G_DEFINE_TYPE (FakeModem, fake_modem, G_TYPE_OBJECT)
49
50 static void
51 fake_modem_init (FakeModem* self)
52 {
53
54 self->echo = TRUE;
55 self->verbose = TRUE;
56 self->patterns = NULL;
57 }
58
59 static void
fake_modem_class_init(FakeModemClass * self)60 fake_modem_class_init (FakeModemClass* self)
61 {
62 }
63
64 static gboolean master_read (GIOChannel *source, GIOCondition condition,
65 gpointer data);
66
67 static const gchar *handle_cmd (FakeModem *fakemodem, const gchar *cmd);
68
69 static gboolean send_unsolicited (FakeModem* fakemodem, const gchar* text);
70 static gboolean set_response (FakeModem* fakemodem, const gchar* command,
71 const gchar* reply, const gchar* response);
72 static gboolean remove_response (FakeModem* fakemodem, const gchar* command);
73
74 #include "fakemodem-dbus.h"
75
76 GPtrArray *
parse_pattern_files(char ** pattern_files,GError ** error)77 parse_pattern_files(char **pattern_files, GError **error)
78 {
79 gint linenum;
80 GRegex *skip, *parts;
81 GPtrArray *patterns;
82 int i;
83
84 patterns = g_ptr_array_new();
85
86 skip = g_regex_new ("^\\s*(#.*)?$", 0, 0, error);
87 if (skip == NULL)
88 return NULL;
89 parts = g_regex_new ("^(\\S+)\\s*(\"([^\"]*)\")?\\s*(.*)$", 0, 0, error);
90 if (parts == NULL)
91 return NULL;
92
93 for (i = 0 ; pattern_files[i] != NULL; i++) {
94 GIOChannel *pf;
95 gchar *pattern_file;
96 gchar *line;
97 gsize len, term;
98
99 pattern_file = pattern_files[i];
100
101 pf = g_io_channel_new_file (pattern_file, "r", error);
102 if (pf == NULL)
103 return NULL;
104
105 linenum = 0;
106 while (g_io_channel_read_line (pf, &line, &len, &term, error) ==
107 G_IO_STATUS_NORMAL) {
108 /* Don't need the terminator */
109 line[term] = '\0';
110 linenum++;
111
112 if (!g_regex_match (skip, line, 0, NULL)) {
113 GMatchInfo *info;
114 gboolean ret;
115 gchar *command, *responsetext;
116 ret = g_regex_match (parts, line, 0, &info);
117 if (ret) {
118 Pattern *pat;
119 pat = g_malloc (sizeof (*pat));
120 command = g_match_info_fetch (info, 1);
121 pat->command = g_regex_new (command,
122 G_REGEX_ANCHORED |
123 G_REGEX_CASELESS |
124 G_REGEX_RAW |
125 G_REGEX_OPTIMIZE,
126 0,
127 error);
128 g_free (command);
129 if (pat->command == NULL) {
130 printf ("error: %s\n", (*error)->message);
131 g_error_free (*error);
132 *error = NULL;
133 }
134 responsetext = g_match_info_fetch (info, 3);
135 if (strlen (responsetext) == 0) {
136 g_free (responsetext);
137 responsetext = NULL;
138 }
139 pat->responsetext = responsetext;
140 pat->reply = g_match_info_fetch (info, 4);
141 while (pat->reply[strlen (pat->reply) - 1] == '\\') {
142 gchar *origstr;
143 pat->reply[strlen (pat->reply) - 1] = '\0';
144 g_free (line); /* probably invalidates fields in 'info' */
145 g_io_channel_read_line (pf, &line, &len, &term, error);
146 line[term] = '\0';
147 linenum++;
148 origstr = pat->reply;
149 pat->reply = g_strjoin ("\r\n", origstr, line, NULL);
150 g_free (origstr);
151 }
152 g_ptr_array_add (patterns, pat);
153 } else {
154 printf (" Line %d '%s' was not parsed"
155 " as a command-response pattern\n",
156 linenum, line);
157 }
158 g_match_info_free (info);
159 }
160 g_free (line);
161 }
162 g_io_channel_shutdown (pf, TRUE, NULL);
163 }
164
165 g_regex_unref (skip);
166 g_regex_unref (parts);
167
168 return patterns;
169 }
170
171 #define FM_DBUS_SERVICE "org.chromium.FakeModem"
172
173 static DBusGProxy *
create_dbus_proxy(DBusGConnection * bus)174 create_dbus_proxy (DBusGConnection *bus)
175 {
176 DBusGProxy *proxy;
177 GError *err = NULL;
178 int request_name_result;
179
180 proxy = dbus_g_proxy_new_for_name (bus,
181 "org.freedesktop.DBus",
182 "/org/freedesktop/DBus",
183 "org.freedesktop.DBus");
184
185 if (!dbus_g_proxy_call (proxy, "RequestName", &err,
186 G_TYPE_STRING, FM_DBUS_SERVICE,
187 G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
188 G_TYPE_INVALID,
189 G_TYPE_UINT, &request_name_result,
190 G_TYPE_INVALID)) {
191 g_print ("Could not acquire the %s service.\n"
192 " Message: '%s'\n", FM_DBUS_SERVICE, err->message);
193
194 g_error_free (err);
195 g_object_unref (proxy);
196 proxy = NULL;
197 } else if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
198 g_print ("Could not acquire the " FM_DBUS_SERVICE
199 " service as it is already taken. Return: %d\n",
200 request_name_result);
201
202 g_object_unref (proxy);
203 proxy = NULL;
204 } else {
205 dbus_g_proxy_add_signal (proxy, "NameOwnerChanged",
206 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
207 G_TYPE_INVALID);
208 }
209
210 return proxy;
211 }
212
213 int
main(int argc,char * argv[])214 main (int argc, char *argv[])
215 {
216 DBusGConnection *bus;
217 DBusGProxy *proxy;
218 GMainLoop* loop;
219 const char *slavedevice;
220 struct termios t;
221 FakeModem *fakemodem;
222 GOptionContext *opt_ctx;
223 char **pattern_files = NULL;
224 gboolean session = FALSE;
225 GError *err = NULL;
226
227 GOptionEntry entries[] = {
228 { "patternfile", 0, 0, G_OPTION_ARG_STRING_ARRAY, &pattern_files,
229 "Path to pattern file", NULL},
230 { "session", 0, 0, G_OPTION_ARG_NONE, &session,
231 "Bind to session bus", NULL},
232 { "system", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &session,
233 "Bind to system bus (default)", NULL},
234 { NULL }
235 };
236
237 #if !GLIB_CHECK_VERSION(2,35,0)
238 g_type_init ();
239 #endif
240
241 opt_ctx = g_option_context_new (NULL);
242 g_option_context_set_summary (opt_ctx,
243 "Emulate a modem with a set of "
244 "regexp-programmed responses.");
245 g_option_context_add_main_entries (opt_ctx, entries, NULL);
246 if (!g_option_context_parse (opt_ctx, &argc, &argv, &err)) {
247 g_warning ("%s\n", err->message);
248 g_error_free (err);
249 exit (1);
250 }
251
252 g_option_context_free (opt_ctx);
253
254 fakemodem = g_object_new (FAKEMODEM_TYPE, NULL);
255 if (pattern_files) {
256 fakemodem->patterns = parse_pattern_files (pattern_files, &err);
257 if (fakemodem->patterns == NULL) {
258 g_warning ("%s\n", err->message);
259 g_error_free (err);
260 exit (1);
261 }
262 } else
263 fakemodem->patterns = g_ptr_array_sized_new (0);
264
265 loop = g_main_loop_new (NULL, FALSE);
266
267 dbus_g_object_type_install_info (FAKEMODEM_TYPE,
268 &dbus_glib_fakemodem_object_info);
269
270 err = NULL;
271 if (session)
272 bus = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
273 else
274 bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
275
276 if (bus == NULL) {
277 g_warning ("%s\n", err->message);
278 g_error_free (err);
279 exit (1);
280 }
281
282 proxy = create_dbus_proxy (bus);
283 if (!proxy)
284 exit (1);
285
286 dbus_g_connection_register_g_object (bus,
287 "/",
288 G_OBJECT (fakemodem));
289
290 masterfd = posix_openpt (O_RDWR | O_NOCTTY);
291
292 if (masterfd == -1
293 || grantpt (masterfd) == -1
294 || unlockpt (masterfd) == -1
295 || (slavedevice = ptsname (masterfd)) == NULL)
296 exit (1);
297
298 printf ("%s\n", slavedevice);
299 fflush (stdout);
300
301 /* Echo is actively harmful here */
302 tcgetattr (masterfd, &t);
303 t.c_lflag &= ~ECHO;
304 tcsetattr (masterfd, TCSANOW, &t);
305
306 ioc = g_io_channel_unix_new (masterfd);
307 g_io_channel_set_encoding (ioc, NULL, NULL);
308 g_io_channel_set_line_term (ioc, "\r", 1);
309 g_io_add_watch (ioc, G_IO_IN, master_read, fakemodem);
310
311 g_main_loop_run (loop);
312
313 g_main_loop_unref (loop);
314
315 g_object_unref (fakemodem);
316 return 0;
317 }
318
319
320 /*
321 * &?[A-CE-RT-Z][0-9]*
322 * S[0-9]+?
323 * S[0-9]+=(([0-9A-F]+|"[^"]*")?,)+
324 */
325
326 /*
327 * action +[A-Z][A-Z0-9%-./:_]{0,15}
328 * test +[A-Z][A-Z0-9%-./:_]{0,15}=?
329 * get +[A-Z][A-Z0-9%-./:_]{0,15}?
330 * set +[A-Z][A-Z0-9%-./:_]{0,15}=(([0-9A-F]+|"[^"]*")?,)+
331 */
332
333
334 #define VALUE "([0-9A-F]+|\"[^\"]*\")"
335 #define CVALUE VALUE "?(," VALUE "?)*"
336 static char *command_patterns[] =
337 {"\\s*(&?[A-CE-RT-Z][0-9]*)",
338 "\\s*(S[0-9]+\\?)",
339 "\\s*(S[0-9]+=" CVALUE ")",
340 /* ATD... (dial string) handling is missing */
341 "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=\\?)",
342 "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}=" CVALUE ")",
343 "\\s*;?\\s*([+*%&][A-Z][A-Z0-9%-./:_]{0,15}(\\?)?)",
344 };
345
346 #undef VALUE
347 #undef CVALUE
348
master_read(GIOChannel * source,GIOCondition condition,gpointer data)349 static gboolean master_read (GIOChannel *source, GIOCondition condition,
350 gpointer data)
351 {
352 FakeModem *fakemodem = data;
353 gchar *line, *next;
354 const gchar *response;
355 gsize term;
356 GError *error = NULL;
357 GIOStatus status;
358 int i, rval;
359
360 static GPtrArray *commands;
361
362 if (commands == NULL) {
363 int n;
364 n = sizeof (command_patterns) / sizeof (command_patterns[0]);
365 commands = g_ptr_array_sized_new (n);
366 for (i = 0 ; i < n ; i++) {
367 GRegex *re = g_regex_new (command_patterns[i],
368 G_REGEX_CASELESS |
369 G_REGEX_ANCHORED |
370 G_REGEX_RAW |
371 G_REGEX_OPTIMIZE,
372 0,
373 &error);
374 if (re == NULL) {
375 g_warning ("Couldn't generate command regex: %s\n", error->message);
376 g_error_free (error);
377 exit (1);
378 }
379 g_ptr_array_add (commands, re);
380 }
381 }
382
383 status = g_io_channel_read_line (source, &line, NULL, &term, &error);
384 if (status == G_IO_STATUS_ERROR)
385 return FALSE;
386 line[term] = '\0';
387
388 printf ("Line: '%s'\n", line);
389
390 if (fakemodem->echo) {
391 rval = write (masterfd, line, term);
392 assert(term == rval);
393 rval = write (masterfd, "\r\n", 2);
394 assert(2 == rval);
395 }
396
397 if (g_ascii_strncasecmp (line, "AT", 2) != 0) {
398 if (line[0] == '\0')
399 goto out;
400 response = "ERROR";
401 goto done;
402 }
403
404 response = NULL;
405 next = line + 2;
406
407 while (!response && *next) {
408 for (i = 0 ; i < commands->len; i++) {
409 GMatchInfo *info;
410 if (g_regex_match (g_ptr_array_index (commands, i), next, 0, &info)) {
411 gint start, end;
412 gchar *cmd;
413 g_match_info_fetch_pos (info, 1, &start, &end);
414 cmd = g_strndup (next + start, end - start);
415 response = handle_cmd (fakemodem, cmd);
416 g_free (cmd);
417 g_match_info_free (info);
418 next += end;
419 break;
420 }
421 g_match_info_free (info);
422 }
423 if (i == commands->len) {
424 response = "ERROR";
425 break;
426 }
427 }
428
429
430 done:
431 if (fakemodem->verbose) {
432 gchar *rstr;
433 if (response == NULL)
434 response = "OK";
435 rstr = g_strdup_printf("\r\n%s\r\n", response);
436 rval = write (masterfd, rstr, strlen (rstr));
437 assert(strlen(rstr) == rval);
438 g_free (rstr);
439 } else {
440 gchar *rstr;
441 rstr = g_strdup_printf("%s\n", response);
442 rval = write (masterfd, rstr, strlen (rstr));
443 assert(strlen(rstr) == rval);
444 g_free (rstr);
445 }
446
447 out:
448 g_free (line);
449 return TRUE;
450 }
451
452 static const gchar *
handle_cmd(FakeModem * fakemodem,const gchar * cmd)453 handle_cmd(FakeModem *fakemodem, const gchar *cmd)
454 {
455 guint i;
456 Pattern *pat = NULL;
457
458 printf (" Cmd: '%s'\n", cmd);
459
460 if (toupper (cmd[0]) >= 'A' && toupper (cmd[0]) <= 'Z') {
461 switch (toupper (cmd[0])) {
462 case 'E':
463 if (cmd[1] == '0')
464 fakemodem->echo = FALSE;
465 else if (cmd[1] == '1')
466 fakemodem->echo = TRUE;
467 else
468 return "ERROR";
469 return "OK";
470 case 'V':
471 if (cmd[1] == '0')
472 fakemodem->verbose = FALSE;
473 else if (cmd[1] == '1')
474 fakemodem->verbose = TRUE;
475 else
476 return "ERROR";
477 return "OK";
478 case 'Z':
479 fakemodem->echo = TRUE;
480 fakemodem->verbose = TRUE;
481 return "OK";
482 }
483 }
484
485 for (i = 0 ; i < fakemodem->patterns->len; i++) {
486 pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
487 if (g_regex_match (pat->command, cmd, 0, NULL)) {
488 break;
489 }
490 }
491
492 if (i == fakemodem->patterns->len)
493 return "ERROR";
494
495 if (pat->reply && pat->reply[0]) {
496 int rval;
497 printf (" Reply: '%s'\n", pat->reply);
498 rval = write (masterfd, pat->reply, strlen (pat->reply));
499 assert(strlen(pat->reply) == rval);
500 rval = write (masterfd, "\r\n", 2);
501 assert(2 == rval);
502 }
503
504 return pat->responsetext; /* NULL implies "OK" and keep processing */
505 }
506
507
508 static gboolean
send_unsolicited(FakeModem * fakemodem,const gchar * text)509 send_unsolicited (FakeModem *fakemodem, const gchar* text)
510 {
511 int rval;
512
513 rval = write (masterfd, "\r\n", 2);
514 rval = write (masterfd, text, strlen (text));
515 assert(strlen(text) == rval);
516 rval = write (masterfd, "\r\n", 2);
517 assert(2 == rval);
518
519 return TRUE;
520 }
521
522 static gboolean
set_response(FakeModem * fakemodem,const gchar * command,const gchar * reply,const gchar * response)523 set_response (FakeModem *fakemodem,
524 const gchar* command,
525 const gchar* reply,
526 const gchar* response)
527 {
528 int i;
529 Pattern *pat;
530
531 if (strlen (response) == 0)
532 response = "OK";
533
534 for (i = 0 ; i < fakemodem->patterns->len; i++) {
535 pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
536 if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
537 g_free (pat->reply);
538 pat->reply = g_strdup (reply);
539 g_free (pat->responsetext);
540 pat->responsetext = g_strdup (response);
541 break;
542 }
543 }
544
545 if (i == fakemodem->patterns->len) {
546 GError *error = NULL;
547 pat = g_malloc (sizeof (*pat));
548 pat->command = g_regex_new (command,
549 G_REGEX_ANCHORED |
550 G_REGEX_CASELESS |
551 G_REGEX_RAW |
552 G_REGEX_OPTIMIZE,
553 0,
554 &error);
555 if (pat->command == NULL) {
556 printf ("error: %s\n", error->message);
557 g_free (pat);
558 return FALSE;
559 }
560 pat->responsetext = g_strdup (response);
561 pat->reply = g_strdup (reply);
562 g_ptr_array_add (fakemodem->patterns, pat);
563 }
564
565 return TRUE;
566 }
567
568 static gboolean
remove_response(FakeModem * fakemodem,const gchar * command)569 remove_response (FakeModem* fakemodem, const gchar* command)
570 {
571 int i;
572 gboolean found;
573 Pattern *pat;
574
575 found = FALSE;
576 for (i = 0 ; i < fakemodem->patterns->len; i++) {
577 pat = (Pattern *)g_ptr_array_index (fakemodem->patterns, i);
578 if (strcmp (g_regex_get_pattern (pat->command), command) == 0) {
579 g_ptr_array_remove_index (fakemodem->patterns, i);
580 found = TRUE;
581 break;
582 }
583 }
584
585 return found;
586 }
587