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 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 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 * 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 * 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 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 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 * 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 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 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 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