diff --git a/configure.in b/configure.in index 045d7350e..6a6d3a951 100644 --- a/configure.in +++ b/configure.in @@ -7,6 +7,7 @@ dnl checks for programs AC_PROG_MAKE_SET AC_PROG_CC AC_PROG_INSTALL +AM_PROG_LEX dnl checks for libraries diff --git a/include/openbsc/Makefile.am b/include/openbsc/Makefile.am index c1c3dd642..f1f163b2a 100644 --- a/include/openbsc/Makefile.am +++ b/include/openbsc/Makefile.am @@ -1,3 +1,3 @@ noinst_HEADERS = abis_nm.h abis_rsl.h debug.h db.h gsm_04_08.h gsm_data.h \ gsm_subscriber.h linuxlist.h msgb.h select.h tlv.h gsm_04_11.h \ - timer.h misdn.h chan_alloc.h + timer.h misdn.h chan_alloc.h telnet_interface.h diff --git a/include/openbsc/telnet_interface.h b/include/openbsc/telnet_interface.h new file mode 100644 index 000000000..885cd648f --- /dev/null +++ b/include/openbsc/telnet_interface.h @@ -0,0 +1,45 @@ +/* minimalistic telnet/network interface it might turn into a wire interface */ +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef TELNET_INTERFACE_H +#define TELNET_INTERFACE_H + +#include "gsm_data.h" +#include "linuxlist.h" +#include "select.h" + +#define TELNET_COMMAND_48 1 +#define TELNET_COMMAND_11 2 + +struct telnet_connection { + struct llist_head entry; + struct gsm_network *network; + struct bsc_fd fd; + + int command; + char *imsi; + char commands[1024]; + int read; +}; + + +void telnet_init(struct gsm_network *network, int port); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 62afff2e4..b0e29ee21 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,5 +5,5 @@ sbin_PROGRAMS = bsc_hack bsc_hack_SOURCES = bsc_hack.c misdn.c abis_rsl.c abis_nm.c gsm_04_08.c gsm_data.c \ gsm_subscriber.c msgb.c select.c chan_alloc.c timer.c debug.c db.c \ - gsm_04_11.c + gsm_04_11.c telnet_interface.c telnet_parser.l bsc_hack_LDADD = -ldl -ldbi diff --git a/src/bsc_hack.c b/src/bsc_hack.c index c59024a90..a3aefc664 100644 --- a/src/bsc_hack.c +++ b/src/bsc_hack.c @@ -1,6 +1,7 @@ /* A hackish minimal BSC (+MSC +HLR) implementation */ /* (C) 2008 by Harald Welte + * (C) 2009 by Holger Hans Peter Freyther * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -42,6 +43,7 @@ #include #include #include +#include /* global pointer to the gsm network data structure */ static struct gsm_network *gsmnet; @@ -685,6 +687,7 @@ static int bootstrap_network(void) gsmnet->call_state_changed = bsc_hack_call_state_changed; #endif + telnet_init(gsmnet, 4242); if (mi_setup(bts, 0, mi_cb) < 0) return -EIO; diff --git a/src/telnet_interface.c b/src/telnet_interface.c new file mode 100644 index 000000000..b0a8e4334 --- /dev/null +++ b/src/telnet_interface.c @@ -0,0 +1,193 @@ +/* minimalistic telnet/network interface it might turn into a wire interface */ +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +extern void telnet_parse(struct telnet_connection *connection, char *line); + +/* per connection data */ +LLIST_HEAD(active_connections); + +/* per network data */ +static int telnet_new_connection(struct bsc_fd *fd, unsigned int what); +static struct bsc_fd server_socket = { + .when = BSC_FD_READ, + .cb = telnet_new_connection, + .priv_nr = 0, +}; + +void telnet_init(struct gsm_network *network, int port) { + struct sockaddr_in sock_addr; + int fd; + + fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (fd < 0) { + perror("Telnet interface socket creation failed"); + return; + } + + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_port = htons(port); + sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0) { + perror("Telnet interface failed to bind"); + return; + } + + if (listen(fd, 0) < 0) { + perror("Telnet interface failed to listen"); + return; + } + + server_socket.data = network; + server_socket.fd = fd; + bsc_register_fd(&server_socket); +} + +void telnet_write_help(int fd) { + int ret; + static char *msg = + "Help for the ad-hoc telnet command line interface\n" + "The generic pattern is CMD LEN DATA\\n or just CMD\n" + "where CMD is one of the following:\n" + "help\n" + "page IMSI (type)\n" + "call IMSI (number)\n" + "get_channel IMSI Add use count on an active channel\n" + "put_channel IMSI Remove use count on an active channel\n" + "48 IMSI 0xAB 0xEF...Send GSM 04.08\n" + "11 IMSI 0xAB 0xEF...Send GSM 04.11\n"; + + ret = write(fd, msg, strlen(msg)); +} + +static void print_welcome(int fd) { + int ret; + static char *msg = + "Welcome to the OpenBSC Control interface\n" + "Copyright (C) 2008, 2009 Harald Welte\n" + "Contributions by Daniel Willmann, Jan Lübbe, " + "Stefan Schmidt, Holger Freyther\n\n" + "License GPLv2+: GNU GPL version 2 or later " + "\n" + "This is free software: you are free to change " + "and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted " + "by law.\nType \"help\" to get a short introduction.\n"; + + ret = write(fd, msg, strlen(msg)); +} + +int telnet_close_client(struct bsc_fd *fd) { + struct telnet_connection *conn = (struct telnet_connection*)fd->data; + + close(fd->fd); + bsc_unregister_fd(fd); + llist_del(&conn->entry); + free(conn); + return 0; +} + +void telnet_error_client(int fd) { + int ret; + static char *msg = "Something went wrong. Please try again.\n"; + + printf("Error\n"); + ret = write(fd, msg, strlen(msg)); +} + +void telnet_page(struct telnet_connection *connection, const char *imsi, int page) { + printf("going to page: '%s' %d\n", imsi, page); +} + +void telnet_put_channel(struct telnet_connection *connection, const char *imsi) { + printf("put_channel: '%s'\n", imsi); +} + +void telnet_get_channel(struct telnet_connection *connection, const char *imsi) { + printf("get_channel: '%s'\n", imsi); +} + +void telnet_call(struct telnet_connection *connection, const char* imsi, + const char *origin) { + printf("calling: '%s' from: '%s'\n", imsi, origin); +} + +void telnet_send_gsm_48(struct telnet_connection *connection) { + printf("sending gsm04.08 message\n"); +} + +void telnet_send_gsm_11(struct telnet_connection *connection) { + printf("sending gsm04.11 message\n"); +} + +static int client_data(struct bsc_fd *fd, unsigned int what) { + char buf[4096]; + int ret; + + ret = read(fd->fd, buf, sizeof(buf)-1); + buf[ret] = '\0'; + + /* connection is gone */ + if (ret <= 0) + return telnet_close_client(fd); + + /* time to parse. This code assumes that the input is line based */ + telnet_parse((struct telnet_connection*)fd->data, buf); + + return 0; +} + +static int telnet_new_connection(struct bsc_fd *fd, unsigned int what) { + struct telnet_connection *connection; + struct sockaddr_in sockaddr; + socklen_t len = sizeof(sockaddr); + int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len); + + if (new_connection < 0) { + perror("telnet accept failed"); + return -1; + } + + + connection = (struct telnet_connection*)malloc(sizeof(*connection)); + memset(connection, 0, sizeof(*connection)); + connection->network = (struct gsm_network*)fd->data; + connection->fd.data = connection; + connection->fd.fd = new_connection; + connection->fd.when = BSC_FD_READ; + connection->fd.cb = client_data; + bsc_register_fd(&connection->fd); + llist_add_tail(&connection->entry, &active_connections); + + print_welcome(new_connection); + + return 0; +} diff --git a/src/telnet_parser.l b/src/telnet_parser.l new file mode 100644 index 000000000..3af0d5818 --- /dev/null +++ b/src/telnet_parser.l @@ -0,0 +1,180 @@ +/* (C) 2009 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +/* + * I'm lazy and will not introduce lemon to this game. Our telnet + * interface is matching line based so we can have a pattern that + * is matching a line and everyone will be happy. + */ + +%option never-interactive +%option noyywrap +%option reentrant + +%{ +#include +#include + +extern char *strndup(const char *s, size_t n); +extern void telnet_write_help(int); +extern void telnet_close_client(struct bsc_fd*); +extern void telnet_error_client(int fd); +extern void telnet_page(struct telnet_connection *con, const char *imsi, int page); +extern void telnet_call(struct telnet_connection *con, const char *imsi, + const char* origin); +extern void telnet_put_channel(struct telnet_connection*, const char *imsi); +extern void telnet_get_channel(struct telnet_connection*, const char *imsi); +extern void telnet_send_gsm_48(struct telnet_connection*); +extern void telnet_send_gsm_11(struct telnet_connection*); + +static const int PAGE_LEN = 5; /* "page " */ +static const int CALL_LEN = 5; /* "call " */ +static const int PUT_LEN = 12; /* "put_channel " */ +static const int GET_LEN = 12; /* "get_channel " */ +static const int NET_LEN = 3; /* "48 " "11 " */ + +#define YY_EXTRA_TYPE struct telnet_connection* + +/* the string is null terminated */ +static int parse_hex(char *hex) +{ + int byte; + sscanf(hex, "%x", &byte); + return byte; +} + +#define PREPARE_STRING(len) \ + yytext[yyleng-1] = '\0'; \ + char *str = yytext + len; \ + char *pag = strstr(str, "\r"); \ + if (pag) pag[0] = '\0'; \ + pag = strstr(str, "\n"); \ + if (pag) pag[0] = '\0'; + +%} + +CMD_HELP "help" +CMD_EXIT "exit" +CMD_CLOSE "close" +CMD_PAGE "page" +CMD_GET_CHANNEL "get_channel" +CMD_PUT_CHANNEL "put_channel" +CMD_CALL "call" +CMD_48 "48" +CMD_11 "11" + +LINE_END \n|\r\n +HEX [0][x][0-9a-zA-Z][0-9a-zA-Z] + +%s READ_HEX_BYTES + +%% +{CMD_HELP}{LINE_END} {telnet_write_help(yyextra->fd.fd); yyterminate();} +{CMD_EXIT}{LINE_END} {telnet_close_client(&yyextra->fd); yyterminate();} +{CMD_CLOSE}{LINE_END} {telnet_close_client(&yyextra->fd); yyterminate();} +{CMD_PAGE}[ ][0-9]+{LINE_END} { + PREPARE_STRING(PAGE_LEN) + telnet_page(yyextra, str, 0); + yyterminate(); + } +{CMD_PAGE}[ ][0-9]+[ ][0-2]{LINE_END} { + PREPARE_STRING(PAGE_LEN) + char *sp = strstr(str, " "); + sp[0] = '\0'; + telnet_page(yyextra, str, atoi(sp+1)); + yyterminate(); + } +{CMD_PUT_CHANNEL}[ ][0-9]+{LINE_END} { + PREPARE_STRING(PUT_LEN) + telnet_put_channel(yyextra, str); + yyterminate(); + } +{CMD_GET_CHANNEL}[ ][0-9]+{LINE_END} { + PREPARE_STRING(GET_LEN) + telnet_get_channel(yyextra, str); + yyterminate(); + } +{CMD_CALL}[ ][0-9]+[ ][0-9]+{LINE_END} { + PREPARE_STRING(CALL_LEN) + char *sp = strstr(str, " "); + sp[0] = '\0'; + telnet_call(yyextra, str, sp+1); + yyterminate(); + } +{CMD_CALL}[ ][0-9]+{LINE_END} { + PREPARE_STRING(CALL_LEN) + telnet_call(yyextra, str, NULL); + yyterminate(); + } +{HEX} { + if (yyextra->read >= sizeof(yyextra->commands)) { + yyterminate(); + } + yytext[yyleng] = '\0'; + yyextra->commands[yyextra->read++] = parse_hex(yytext+2); + } +{LINE_END} { + if (yyextra->command == TELNET_COMMAND_11) { + telnet_send_gsm_11(yyextra); + } else if (yyextra->command == TELNET_COMMAND_48) { + telnet_send_gsm_48(yyextra); + } + + if (yyextra->imsi) { + free(yyextra->imsi); + yyextra->imsi = NULL; + } + yyterminate(); + } +{CMD_48}[ ][0-9]+ { + BEGIN READ_HEX_BYTES; + yyextra->read = 0; + yyextra->command = TELNET_COMMAND_48; + yytext[yyleng-1] = '\0'; + yyextra->imsi = strdup(yytext); + } + +{CMD_11}[ ][0-9]+ { + BEGIN READ_HEX_BYTES; + yyextra->read = 0; + yyextra->command = TELNET_COMMAND_11; + yytext[yyleng-1] = '\0'; + yyextra->imsi = strdup(yytext); + } + + + +[ \t\r\n] /* Ommit */ +. { telnet_error_client(yyextra->fd.fd); yyterminate(); } + +%% + +void telnet_parse(struct telnet_connection *conn, char *buf) +{ + yyscan_t scanner; + yylex_init(&scanner); + yyset_extra(conn, scanner); + yy_scan_string(buf, scanner); + yylex(scanner); + yylex_destroy(scanner); + + if (conn->imsi) { + free(conn->imsi); + conn->imsi = NULL; + } +}