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