| /* |
| <:copyright-BRCM:2016:DUAL/GPL:standard |
| |
| Broadcom Proprietary and Confidential.(c) 2016 Broadcom |
| All Rights Reserved |
| |
| Unless you and Broadcom execute a separate written software license |
| agreement governing use of this software, this software is licensed |
| to you under the terms of the GNU General Public License version 2 |
| (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, |
| with the following added to such license: |
| |
| As a special exception, the copyright holders of this software give |
| you permission to link this software with independent modules, and |
| to copy and distribute the resulting executable under terms of your |
| choice, provided that you also meet, for each linked independent |
| module, the terms and conditions of the license of that module. |
| An independent module is a module which is not derived from this |
| software. The special exception does not apply to any modifications |
| of the software. |
| |
| Not withstanding the above, under no circumstances may you combine |
| this software in any way with any other Broadcom software provided |
| under a license other than the GPL, without Broadcom's express prior |
| written consent. |
| |
| :> |
| */ |
| |
| |
| /******************************************************************* |
| * bcmcli_server.c |
| * |
| * CLI engine - remote shell support |
| * |
| * This module is a back-end of remote shell support. |
| * - multiple servers |
| * - domain and TCP-based connections |
| * - session access level - per server |
| *******************************************************************/ |
| |
| #include <bcmcli_server.h> |
| |
| typedef struct bcmclis_server bcmclis_server_t; |
| |
| /* Server connection |
| */ |
| typedef struct bcmclis_conn |
| { |
| struct bcmclis_conn *next; |
| bcmclis_server_t *server; |
| const char *address; /* client address */ |
| int sock; /* transport socket */ |
| bdmf_task rx_thread; |
| bcmcli_session *session; |
| uint32_t bytes_sent; |
| uint32_t bytes_received; |
| bdmf_task conn_thread; |
| } bcmclis_conn_t; |
| |
| /* Server control bdmfock |
| */ |
| struct bcmclis_server |
| { |
| bcmclis_server_t *next; |
| bcmclis_conn_t *conn_list; |
| int sock; /* listening socket */ |
| bcmclis_parm_t parms; |
| int id; |
| int nconns; |
| bcmos_fastlock lock; |
| bdmf_task listen_thread; |
| }; |
| |
| /* socaddr variants */ |
| typedef union |
| { |
| struct sockaddr sa; |
| struct sockaddr_un domain_sa; |
| struct sockaddr_in tcp_sa; |
| } sockaddr_any; |
| |
| static bcmclis_server_t *bcmclis_servers; |
| static int bcmclis_server_id; |
| |
| static bcmclis_server_t *bcmclis_id_to_server(int hs, bcmclis_server_t **prev) |
| { |
| bcmclis_server_t *s=bcmclis_servers; |
| if (prev) |
| *prev = NULL; |
| while(s) |
| { |
| if (s->id == hs) |
| break; |
| if (prev) |
| *prev = s; |
| s = s->next; |
| } |
| return s; |
| } |
| |
| /* Parse address helper */ |
| static int bcmclis_parse_address(const bcmclis_parm_t *parms, int *protocol, sockaddr_any *sa, int *len) |
| { |
| switch(parms->transport) |
| { |
| case BCMCLI_TRANSPORT_DOMAIN_SOCKET: |
| { |
| *protocol = AF_UNIX; |
| sa->domain_sa.sun_family = AF_UNIX; /* local is declared before socket() ^ */ |
| strcpy(sa->domain_sa.sun_path, parms->address); |
| *len = strlen(sa->domain_sa.sun_path) + sizeof(sa->domain_sa.sun_family); |
| break; |
| } |
| case BCMCLI_TRANSPORT_TCP_SOCKET: |
| { |
| *protocol = AF_INET; |
| sa->tcp_sa.sin_family = AF_INET; |
| sa->tcp_sa.sin_port = htons(atoi(parms->address)); |
| sa->tcp_sa.sin_addr.s_addr = INADDR_ANY; |
| *len = sizeof(sa->tcp_sa); |
| break; |
| } |
| default: |
| return BCM_ERR_PARM; |
| } |
| return 0; |
| } |
| |
| |
| /* disconnect client and clear resources */ |
| static void bcmclis_disconnect(bcmclis_conn_t *conn) |
| { |
| bcmclis_server_t *s=conn->server; |
| bcmclis_conn_t *c=s->conn_list, *prev=NULL; |
| |
| bcmos_fastlock_lock(&s->lock); |
| while(c && c!=conn) |
| { |
| prev = c; |
| c = c->next; |
| } |
| BUG_ON(!c); |
| if (prev) |
| prev->next = c->next; |
| else |
| s->conn_list = c->next; |
| --s->nconns; |
| bcmos_fastlock_unlock(&s->lock); |
| bcmcli_session_close(c->session); |
| close(c->sock); |
| bdmf_task_destroy(c->rx_thread); |
| bcmos_free(c); |
| } |
| |
| /* |
| * Session callbacks |
| */ |
| |
| /** Session's output function. |
| * returns the number of bytes written or <0 if error |
| */ |
| static int bcmclis_cb_sess_write(void *user_priv, const void *buf, uint32_t size) |
| { |
| bcmclis_conn_t *c=user_priv; |
| int rc; |
| |
| rc = send(c->sock, buf, size, 0); |
| /* disconnect if IO error */ |
| if (rc < size) |
| bcmclis_disconnect(c); |
| else |
| c->bytes_sent += rc; |
| return rc; |
| } |
| |
| #define CHAR_EOT 0x04 |
| |
| /** Session's input function. |
| * returns the number of bytes read or <0 if error |
| */ |
| static char *bcmclis_read_line(bcmclis_conn_t *c, char *buf, uint32_t size) |
| { |
| int i; |
| int rc; |
| int len=0; |
| |
| for(i=0; i<size-1; i++) |
| { |
| char ch; |
| rc = recv(c->sock, &ch, 1, MSG_WAITALL); |
| if (rc <= 0) |
| break; |
| if (ch == '\r') |
| continue; |
| if (ch == CHAR_EOT) |
| break; |
| buf[len++] = ch; |
| if (ch == '\n') |
| break; |
| } |
| c->bytes_received += i; |
| buf[len] = 0; |
| return (len ? buf : NULL); |
| } |
| |
| /* Receive handler */ |
| static int bcmclis_rx_thread_handler(void *arg) |
| { |
| char buf[512]; |
| bcmclis_conn_t *c=arg; |
| |
| while(!bcmcli_is_stopped(c->session) && |
| bcmclis_read_line(c, buf, sizeof(buf))) |
| { |
| bcmcli_parse(c->session, buf); |
| } |
| bcmclis_disconnect(c); |
| return 0; |
| } |
| |
| /* New client connection indication */ |
| static void bcmclis_connect(bcmclis_server_t *s, char *addr, int sock) |
| { |
| bcmclis_conn_t *c; |
| bcmcli_session_parm sess_parm; |
| int rc; |
| |
| if (s->parms.max_clients && s->nconns >= s->parms.max_clients) |
| { |
| bcmos_printf("bdmfmons: server %s: refused connection because max number has been reached\n", s->parms.address); |
| close(sock); |
| return; |
| } |
| |
| c = bcmos_calloc(sizeof(*c) + strlen(addr) + 1); |
| if (!c) |
| goto cleanup; |
| c->address = (char *)c + sizeof(*c); |
| strcpy((char *)c->address, addr); |
| c->server = s; |
| c->sock = sock; |
| |
| /* create new management session */ |
| memset(&sess_parm, 0, sizeof(sess_parm)); |
| sess_parm.access_right = s->parms.access; |
| sess_parm.write = bcmclis_cb_sess_write; |
| sess_parm.user_priv = c; |
| rc = bcmcli_session_open(&sess_parm, &c->session); |
| if (rc) |
| goto cleanup; |
| |
| /* wait for receive in a separate thread */ |
| rc = bdmf_task_create("bcmclis_rx", |
| BDMFSYS_DEFAULT_TASK_PRIORITY, |
| BDMFSYS_DEFAULT_TASK_STACK, |
| bcmclis_rx_thread_handler, c, |
| &c->rx_thread); |
| if (rc) |
| goto cleanup; |
| |
| bcmos_fastlock_lock(&s->lock); |
| c->next = s->conn_list; |
| s->conn_list = c; |
| ++s->nconns; |
| bcmos_fastlock_unlock(&s->lock); |
| |
| return; |
| |
| cleanup: |
| close(sock); |
| if (c->session) |
| bcmcli_session_close(c->session); |
| if (c) |
| bcmos_free(c); |
| } |
| |
| /* Receive handler */ |
| static int bcmclis_listen_thread_handler(void *arg) |
| { |
| bcmclis_server_t *s=arg; |
| sockaddr_any addr; |
| socklen_t len; |
| int sock; |
| |
| while(1) |
| { |
| char caddr[64]; |
| len = sizeof(addr); |
| sock = accept(s->sock, &addr.sa, &len); |
| if (sock < 0) |
| { |
| perror("accept"); |
| break; |
| } |
| if (s->parms.transport==BCMCLI_TRANSPORT_DOMAIN_SOCKET) |
| strncpy(caddr, s->parms.address, sizeof(caddr)-1); |
| else |
| { |
| snprintf(caddr, sizeof(caddr)-1, "%s:%d", |
| inet_ntoa(addr.tcp_sa.sin_addr), ntohs(addr.tcp_sa.sin_port)); |
| } |
| bcmclis_connect(s, caddr, sock); |
| } |
| return 0; |
| } |
| |
| /* |
| * External API |
| */ |
| |
| /** Create shell server. |
| * Immediately after creation server is ready to accept client connections |
| * \param[in] parms Server parameters |
| * \param[out] hs Server handle |
| * \return 0 - OK\n |
| * <0 - error code |
| */ |
| bcmos_errno bcmclis_server_create(const bcmclis_parm_t *parms, int *hs) |
| { |
| bcmclis_server_t *s; |
| int protocol; |
| sockaddr_any sa; |
| int len; |
| int rc; |
| |
| if (!parms || !hs || !parms->address) |
| return BCM_ERR_PARM; |
| |
| /* parse address */ |
| if (bcmclis_parse_address(parms, &protocol, &sa, &len)) |
| return BCM_ERR_PARM; |
| |
| /* allocate server structure */ |
| s = bcmos_calloc(sizeof(bcmclis_server_t)+strlen(parms->address)+1); |
| if (!s) |
| return BCM_ERR_NOMEM; |
| s->parms = *parms; |
| s->parms.address = (char *)s + sizeof(*s); |
| strcpy(s->parms.address, parms->address); |
| s->id = ++bcmclis_server_id; |
| bcmos_fastlock_init(&s->lock); |
| |
| /* create socket and start listening */ |
| s->sock = socket(protocol, SOCK_STREAM, 0); |
| if ((s->sock < 0) || |
| (bind(s->sock, &sa.sa, len) < 0) || |
| (listen(s->sock, 1) < 0)) |
| { |
| perror("socket/bind/listen"); |
| close(s->sock); |
| bcmos_free(s); |
| return BCM_ERR_PARM; |
| } |
| |
| /* wait for connection(s) in a separate thread */ |
| rc = bdmf_task_create("bcmclis_listen", |
| BDMFSYS_DEFAULT_TASK_PRIORITY, |
| BDMFSYS_DEFAULT_TASK_STACK, |
| bcmclis_listen_thread_handler, s, |
| &s->listen_thread); |
| if (rc) |
| { |
| close(s->sock); |
| bcmos_free(s); |
| return rc; |
| } |
| |
| /* all good */ |
| s->next = bcmclis_servers; |
| bcmclis_servers = s; |
| *hs = s->id; |
| |
| return 0; |
| } |
| |
| /** Destroy shell server. |
| * All client connections if any are closed |
| * \param[in] hs Server handle |
| * \return 0 - OK\n |
| * <0 - error code |
| */ |
| bcmos_errno bcmclis_server_destroy(int hs) |
| { |
| bcmclis_server_t *prev; |
| bcmclis_server_t *s = bcmclis_id_to_server(hs, &prev); |
| bcmclis_conn_t *c; |
| if (!s) |
| return BCM_ERR_NOENT; |
| |
| bdmf_task_destroy(s->listen_thread); |
| close(s->sock); |
| |
| /* disconnect all clients */ |
| while((c = s->conn_list)) |
| bcmclis_disconnect(c); |
| |
| /* destroy server */ |
| bcmos_fastlock_lock(&s->lock); |
| if (prev) |
| prev->next = s->next; |
| else |
| bcmclis_servers = s->next; |
| bcmos_fastlock_unlock(&s->lock); |
| |
| bcmos_free(s); |
| return 0; |
| } |
| |
| /* |
| * Shell command handlers |
| */ |
| |
| static bcmcli_enum_val transport_type_enum_tabdmfe[] = { |
| { .name="domain_socket", .val=BCMCLI_TRANSPORT_DOMAIN_SOCKET}, |
| { .name="tcp_socket", .val=BCMCLI_TRANSPORT_TCP_SOCKET}, |
| BCMCLI_ENUM_LAST |
| }; |
| |
| static bcmcli_enum_val access_type_enum_tabdmfe[] = { |
| { .name="guest", .val=BCMCLI_ACCESS_GUEST}, |
| { .name="admin", .val=BCMCLI_ACCESS_ADMIN}, |
| { .name="debug", .val=BCMCLI_ACCESS_DEBUG}, |
| BCMCLI_ENUM_LAST |
| }; |
| |
| /* Create remote shell server |
| BCMCLI_MAKE_PARM_ENUM("transport", "Transport type", transport_type_enum_tabdmfe, 0), |
| BCMCLI_MAKE_PARM("address", "Bind address", BCMCLI_PARM_STRING, 0), |
| BCMCLI_MAKE_PARM_ENUM("access", "Access level", access_type_enum_tabdmfe, 0), |
| BCMCLI_MAKE_PARM_DEFVAL("max_clients", "Max clients. 0=default", BCMCLI_PARM_NUMBER, 0, 0), |
| */ |
| static int bcmclis_mon_create(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms) |
| { |
| bcmclis_transport_type_t transport = (bcmclis_transport_type_t)parm[0].value.number; |
| char *address = (char *)parm[1].value.number; |
| bcmcli_access_right access = (bcmcli_access_right)parm[2].value.number; |
| int max_clients = (int)parm[3].value.number; |
| bcmclis_parm_t parms; |
| int hs; |
| int rc; |
| |
| memset(&parms, 0, sizeof(parms)); |
| parms.transport = transport; |
| parms.access = access; |
| parms.address = address; |
| parms.max_clients = max_clients; |
| rc = bcmclis_server_create(&parms, &hs); |
| if (rc) |
| bcmcli_session_print(session, "bcmclis_server_create() failed with rc=%d - %s\n", |
| rc, bcmos_strerror(rc)); |
| else |
| bcmcli_session_print(session, "Remote shell server created. Server id %d\n", hs); |
| return rc; |
| } |
| |
| /* Destroy remote shell server |
| BCMCLI_MAKE_PARM("server_id", "Server id", BCMCLI_PARM_NUMBER, 0), |
| */ |
| static int bcmclis_mon_destroy(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms) |
| { |
| int hs = (int)parm[0].value.number; |
| int rc; |
| rc = bcmclis_server_destroy(hs); |
| bcmcli_session_print(session, "Remote shell server %d destroyed. rc=%d - %s\n", |
| hs, rc, bcmos_strerror(rc)); |
| return rc; |
| } |
| |
| /* Show remote shell servers |
| */ |
| static int bcmclis_mon_show(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms) |
| { |
| bcmclis_server_t *s=bcmclis_servers; |
| bcmclis_conn_t *c; |
| while(s) |
| { |
| bcmcli_session_print(session, "Remote server %d at %s\n", s->id, s->parms.address); |
| c = s->conn_list; |
| while(c) |
| { |
| bcmcli_session_print(session, "\t - %s. bytes sent:%d received:%d\n", |
| c->address, c->bytes_sent, c->bytes_received); |
| c = c->next; |
| } |
| s = s->next; |
| } |
| return 0; |
| } |
| |
| /* Create shell_server directory in root_dir |
| Returns the "shell_server" directory handle |
| */ |
| bcmcli_entry *bcmclis_server_mon_init(bcmcli_entry *root_dir) |
| { |
| bcmcli_entry *shell_dir; |
| |
| if ((shell_dir=bcmcli_dir_find(NULL, "shell_server"))!=NULL) |
| return NULL; |
| |
| shell_dir = bcmcli_dir_add(root_dir, "shell_server", |
| "Remote Shell", |
| BCMCLI_ACCESS_GUEST, NULL); |
| |
| { |
| static bcmcli_cmd_parm parms[]={ |
| BCMCLI_MAKE_PARM_ENUM("transport", "Transport type", transport_type_enum_tabdmfe, 0), |
| BCMCLI_MAKE_PARM("address", "Bind address: domain_socket address or TCP port", BCMCLI_PARM_STRING, 0), |
| BCMCLI_MAKE_PARM_ENUM("access", "Access level", access_type_enum_tabdmfe, 0), |
| BCMCLI_MAKE_PARM_DEFVAL("max_clients", "Max clients. 0=default", BCMCLI_PARM_NUMBER, 0, 0), |
| BCMCLI_PARM_LIST_TERMINATOR |
| }; |
| bcmcli_cmd_add(shell_dir, "create", bcmclis_mon_create, |
| "Create remote shell server", |
| BCMCLI_ACCESS_ADMIN, NULL, parms); |
| } |
| |
| { |
| static bcmcli_cmd_parm parms[]={ |
| BCMCLI_MAKE_PARM("server_id", "Server id", BCMCLI_PARM_NUMBER, 0), |
| BCMCLI_PARM_LIST_TERMINATOR |
| }; |
| bcmcli_cmd_add(shell_dir, "destroy", bcmclis_mon_destroy, |
| "Destroy remote shell server", |
| BCMCLI_ACCESS_ADMIN, NULL, parms); |
| } |
| |
| { |
| bcmcli_cmd_add(shell_dir, "show", bcmclis_mon_show, |
| "Show remote shell servers", |
| BCMCLI_ACCESS_GUEST, NULL, NULL); |
| } |
| |
| return shell_dir; |
| } |
| |
| /* Destroy shell_server directory |
| */ |
| void bcmclis_server_mon_destroy(void) |
| { |
| bcmcli_entry *shell_dir; |
| shell_dir=bcmcli_dir_find(NULL, "shell_server"); |
| if (shell_dir) |
| bcmcli_token_destroy(shell_dir); |
| } |
| |