#include #include #include #include #include #include #include #include #include #include #include #include #include #include "modem.h" #include "lock.h" #include "exec.h" #include "main.h" #include "msntab.h" #include "dle.h" /* Baudrate for the TTY. Should be greater the 64000. */ #define BAUD_RATE B115200 #define RESET_IDLE_TIME 2 #define INIT_TIMEOUT 5 #define ACCEPT_TIMEOUT 5 #define BASIC_TIMEOUT 5 #define INIT_AT_COMMANDS 12 static const char* const init_at_commands[INIT_AT_COMMANDS*2] = { "\nAT&F\n", "OK\r\n", "ATI\n", "Linux ISDN\r\nOK\r\n", "AT&B16\n", "OK\r\n", "AT+FCLASS=8\n", "OK\r\n", "AT+VSM=6\n", "OK\r\n", "ATS18=1\n", "OK\r\n", "ATS14=4\n", "OK\r\n", "ATS13.4=0\n", "OK\r\n", "ATS13.0=1\n", "OK\r\n", "ATS23=1\n", "OK\r\n", "ATS0=0\n", "OK\r\n", "AT&L%s\n", "OK\r\n", }; static void modem_output_empty_cb(struct buffio *b, void *user); static void modem_input_ready_cb(struct buffio *b, void *user); static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user); static void modem_init_cycle(struct modem *m); static void modem_timeout(struct modem *m, int t); struct modem *modem_open(const char *dev) { struct modem *m; int fd = -1, n; struct termios ts, ts2; assert(dev); if (device_lock(dev) != 0) return NULL; if ((fd = open(dev, O_RDWR|O_NDELAY)) < 0) { daemon_log(LOG_ERR, "Failed to open device <%s>: %s", dev, strerror(errno)); goto fail; } if ((n = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, n & ~O_NDELAY) < 0) { daemon_log(LOG_ERR, "Failed to remove O_NDELAY flag from device: %s", strerror(errno)); goto fail; } memset(&ts, 0, sizeof ts); ts.c_cflag = CRTSCTS | IGNPAR | HUPCL | CS8; ts.c_iflag = IGNPAR; ts.c_oflag = 0; ts.c_lflag = 0; ts.c_cc[VMIN] = 1; ts.c_cc[VTIME] = 0; cfsetospeed(&ts, BAUD_RATE); cfsetispeed(&ts, BAUD_RATE); tcflush(fd, TCIFLUSH); if (tcsetattr(fd, TCSANOW, &ts) < 0) { daemon_log(LOG_ERR, "Failed to set TTY attributes: %s", strerror(errno)); goto fail; } if (tcgetattr(fd, &ts2) < 0 || memcmp(&ts, &ts2, sizeof(ts)) != 0) { daemon_log(LOG_ERR, "Failed to set TTY attributes"); /*goto fail;*/ /* DON'T FORGET TO REMOVE THE COMMENT MARKS */ } m = malloc(sizeof(struct modem)); assert(m); memset(m, 0, sizeof(struct modem)); m->dev = strdup(dev); assert(m->dev); m->buffio = buffio_new(fd, fd); assert(m->buffio); m->buffio->output_empty_cb = modem_output_empty_cb; m->buffio->input_ready_cb = modem_input_ready_cb; m->buffio->user = m; m->child_pid = -1; m->local_msn = "46"; m->tabentry = NULL; modem_init_cycle(m); return m; fail: if (fd >= 0) close(fd); device_unlock(dev); return NULL; } void modem_close(struct modem *m) { assert(m); modem_timeout(m, 0); if (m->tabentry) msntab_unref(m->tabentry); if (m->child_pid != -1) child_process_kill(m->child_pid); buffio_free(m->buffio); device_unlock(m->dev); free(m->dev); free(m->caller_number); free(m->ring_number); free(m); } static void modem_timeout(struct modem *m, int t) { assert(m); assert(event_source && event_source->on_time); event_source->cancel_time(event_source, m->timeout, oop_timeout_cb, m); if (t > 0) { gettimeofday(&m->timeout, NULL); m->timeout.tv_sec += t; assert(event_source && event_source->on_time); event_source->on_time(event_source, m->timeout, oop_timeout_cb, m); } } static void modem_next_command(struct modem *m) { const char *p; char tmp[64]; assert(m && m->buffio && m->state == MODEM_STATE_INIT); assert(m->command_index < INIT_AT_COMMANDS); p = init_at_commands[m->command_index*2]; if (m->command_index == 11) { snprintf(tmp, sizeof(tmp), p, m->local_msn); p = tmp; } buffio_command(m->buffio, p); } static void modem_init_cycle(struct modem *m) { assert(m); daemon_log(LOG_INFO, "Initializing modem."); m->state = MODEM_STATE_INIT; m->command_index = 0; modem_timeout(m, INIT_TIMEOUT); modem_next_command(m); } void modem_force_hangup(struct modem *m) { struct termios pts; assert(m && m->buffio); tcgetattr(m->buffio->ifd, &pts); cfsetospeed(&pts, B0); cfsetispeed(&pts, B0); tcsetattr(m->buffio->ifd, TCSANOW, &pts); tcgetattr(m->buffio->ifd, &pts); cfsetospeed(&pts, BAUD_RATE); cfsetispeed(&pts, BAUD_RATE); tcsetattr(m->buffio->ifd, TCSANOW, &pts); } static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user) { struct modem *m = (struct modem*) user; assert(source && user); switch (m->state) { case MODEM_STATE_INIT: case MODEM_STATE_CALLER_NUMBER: case MODEM_STATE_RING_EXPECT: case MODEM_STATE_RING: daemon_log(LOG_ERR, "Timeout reached for device <%s>", m->dev); return OOP_HALT; case MODEM_STATE_ANSWER: case MODEM_STATE_VTXVRX: daemon_log(LOG_ERR, "Connection failed for device <%s>", m->dev); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); return OOP_CONTINUE; case MODEM_STATE_IDLE: modem_init_cycle(m); return OOP_CONTINUE; default: assert(0); } } static void modem_input_ready_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_INIT: { const char *p; assert(m->command_index < INIT_AT_COMMANDS); p = init_at_commands[m->command_index*2+1]; if (buffio_find_input(m->buffio, p)) { m->command_index++; if (m->command_index >= INIT_AT_COMMANDS) { daemon_log(LOG_INFO, "Modem successfully initialised, waiting for calls."); m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_timeout(m, 0); modem_input_ready_cb(b, user); } else modem_next_command(m); } break; } case MODEM_STATE_CALLER_NUMBER_EXPECT: if (buffio_find_input(m->buffio, "CALLER NUMBER: ")) { m->state = MODEM_STATE_CALLER_NUMBER; modem_timeout(m, BASIC_TIMEOUT); modem_input_ready_cb(b, user); } break; case MODEM_STATE_CALLER_NUMBER: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { c[strcspn(c, "\n\r")] = 0; free(m->caller_number); m->caller_number = strdup(c); assert(m->caller_number); m->state = MODEM_STATE_RING_EXPECT; modem_input_ready_cb(b, user); } break; } case MODEM_STATE_RING_EXPECT: if (buffio_find_input(m->buffio, "RING/")) { m->state = MODEM_STATE_RING; modem_input_ready_cb(b, user); } break; case MODEM_STATE_RING: { char c[64]; if (buffio_read_line(m->buffio, c, sizeof(c))) { struct tabentry *t; c[strcspn(c, "\n\r")] = 0; free(m->ring_number); m->ring_number = strdup(c); assert(m->ring_number); modem_timeout(m, 0); daemon_log(LOG_INFO, "Incoming call from [%s] to [%s]", m->caller_number, m->ring_number); if ((t = msntab_check_call(m->ring_number, m->caller_number)) && (t->action == CALL_ACTION_ACCEPT || t->action == CALL_ACTION_HANGUP)) { if (m->tabentry) msntab_unref(m->tabentry); m->tabentry = t; daemon_log(LOG_INFO, "Accepting call."); m->state = MODEM_STATE_ANSWER; modem_timeout(m, ACCEPT_TIMEOUT); buffio_command(m->buffio, "ATA\n"); } else { if (t) msntab_unref(t); daemon_log(LOG_INFO, "Ignoring call."); m->state = MODEM_STATE_CALLER_NUMBER_EXPECT; modem_input_ready_cb(b, user); } } break; } case MODEM_STATE_ANSWER: if (buffio_find_input(m->buffio, "VCON\r\n")) { assert(m->tabentry); if (m->tabentry->action == CALL_ACTION_ACCEPT) { daemon_log(LOG_INFO, "Call accepted, changing to voice mode."); buffio_command(m->buffio, "AT+VTX+VRX"); m->state = MODEM_STATE_VTXVRX; } else if (m->tabentry->action == CALL_ACTION_HANGUP) { modem_timeout(m, 0); daemon_log(LOG_INFO, "Call accepted for hang up."); m->state = MODEM_STATE_ATH; modem_input_ready_cb(b, user); } else assert(0); } else if (buffio_find_input(m->buffio, "NO CARRIER\n\n")) { daemon_log(LOG_INFO, "Failed to accept call, carrier lost"); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); } break; case MODEM_STATE_VTXVRX: if (buffio_find_input(m->buffio, "CONNECT\r\n\r\nCONNECT\r\n")) { modem_timeout(m, 0); m->state = MODEM_STATE_CONNECTION; daemon_log(LOG_INFO, "Voice connection established."); modem_input_ready_cb(b, user); } else if (buffio_find_input(m->buffio, "NO CARRIER\n\n")) { daemon_log(LOG_INFO, "Failed to accept call, carrier lost"); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); } break; case MODEM_STATE_CONNECTION: m->state = MODEM_STATE_HANGUP; modem_input_ready_cb(b, user); break; case MODEM_STATE_HANGUP: { static const char hup[] = { DLE, DC4, DLE, ETX, 0 }; buffio_command(m->buffio, hup); /* pass over ... */ } case MODEM_STATE_ATH: { daemon_log(LOG_INFO, "Hanging up."); buffio_command(m->buffio, "ATH\n"); m->state = MODEM_STATE_FORCE_HANGUP; break; } default: break; } } static void modem_output_empty_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { case MODEM_STATE_FORCE_HANGUP: modem_force_hangup(m); daemon_log(LOG_INFO, "Hang up successful, reinitializing in %i seconds.", RESET_IDLE_TIME); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); break; default: break; } }