📄 vty.c
字号:
/*
* "Copyright (c) 2008 The Regents of the University of California.
* All rights reserved."
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice, the following
* two paragraphs and the author appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
*
*/
/*
* @author Stephen Dawson-Haggerty <stevedh@eecs.berkeley.edu>
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdarg.h>
#include "vty.h"
#include "../logging.h"
#define max(X,Y) (((X) > (Y)) ? (X) : (Y))
typedef enum {
FALSE = 0,
TRUE = 1,
} bool;
enum {
VTY_REMOVE_PENDING = 0x1,
};
struct vty_client {
int flags;
int readfd;
int writefd;
struct sockaddr_in6 ep;
struct vty_client *next;
int buf_off;
unsigned char buf[2][1024];
int argc;
char *argv[N_ARGS];
};
static int sock = -1;
static struct vty_client *conns = NULL;
static struct vty_cmd_table *cmd_tab;
static char prompt_str[40];
int vty_init(struct vty_cmd_table * cmds, short port) {
struct sockaddr_in6 si_me = {
.sin6_family = AF_INET6,
.sin6_port = htons(port),
.sin6_addr = IN6ADDR_ANY_INIT,
};
struct vty_client *tty_client;
int len, yes = 1;
conns = NULL;
cmd_tab = cmds;
strncpy(prompt_str, "blip:", 5);
gethostname(prompt_str+5, sizeof(prompt_str)- 5);
len=strlen(prompt_str+5);
prompt_str[len+5]='>';
prompt_str[len+6]=' ';
prompt_str[len+7]='\0';
if (isatty(fileno(stdin))) {
setbuf(stdin, NULL);
tty_client = (struct vty_client *)malloc(sizeof(struct vty_client));
memset(tty_client, 0, sizeof(struct vty_client));
tty_client->flags = 0;
tty_client->readfd = fileno(stdin);
tty_client->writefd = fileno(stdout);
tty_client->next = NULL;
conns = tty_client;
}
sock = socket(PF_INET6, SOCK_STREAM, 0);
if (sock < 0) {
log_fatal_perror("vty: socket");
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
log_fatal_perror("vty: setsockopt");
goto abort;
}
if (bind(sock, (struct sockaddr *)&si_me, sizeof(si_me)) < 0) {
log_fatal_perror("vty: bind");
goto abort;
}
if (listen(sock, 2) < 0) {
log_fatal_perror("vty: listen");
goto abort;
}
return 0;
abort:
close(sock);
sock = -1;
return -1;
}
int vty_add_fds(fd_set *fds) {
int maxfd;
struct vty_client *cur;
if (sock >= 0) FD_SET(sock, fds);
maxfd = sock;
for (cur = conns; cur != NULL; cur = cur->next) {
if (cur->flags & VTY_REMOVE_PENDING) continue;
FD_SET(cur->readfd, fds);
maxfd = max(maxfd, cur->readfd);
}
return maxfd;
}
static void vty_print_string(struct vty_client *c, const char *fmt, ...) {
char buf[1024];
int len;
va_list ap;
va_start(ap, fmt);
len = vsnprintf(buf, 1024, fmt, ap);
len = write(c->writefd, buf, len);
}
static void prompt(struct vty_client *c) {
vty_print_string(c, prompt_str);
}
static void vty_new_connection() {
char addr_buf[512];
struct vty_client * c = malloc(sizeof(struct vty_client));
socklen_t len;
if (c == NULL) return;
len = sizeof(struct sockaddr_in6);
c->readfd = c->writefd = accept(sock, (struct sockaddr *)&c->ep, &len);
if (c->readfd < 0) {
error("Accept failed!\n");
log_fatal_perror(0);
free(c);
return;
}
c->buf_off = 0;
memset(c->buf, 0, sizeof(c->buf));
inet_ntop(AF_INET6, c->ep.sin6_addr.s6_addr, addr_buf, 512);
info("VTY: new connection accepted from %s\n", addr_buf);
vty_print_string(c, "Welcome to the blip console!\r\n");
vty_print_string(c, " type 'help' to print the command options\r\n");
prompt(c);
c->flags = 0;
c->next = conns;
conns = c;
}
void vty_close_connection(struct vty_client *c) {
close(c->readfd);
if (c->readfd != c->writefd) close(c->writefd);
c->flags |= VTY_REMOVE_PENDING;
}
void vty_dispatch_command(struct vty_client *c) {
int i;
if (c->argc > 0) {
for (i = 0; i < cmd_tab->n; i++) {
if (strncmp(c->argv[0], cmd_tab->table[i].name,
strlen(cmd_tab->table[i].name) + 1) == 0) {
cmd_tab->table[i].fn(c->writefd, c->argc, c->argv);
break;
}
if (strncmp(c->argv[0], "quit", 4) == 0) {
vty_close_connection(c);
return;
}
}
if (i == cmd_tab->n) {
vty_print_string(c, "vty: %s: command not found\r\n", c->argv[0]);
}
}
prompt(c);
}
void vty_handle_data(struct vty_client *c) {
int len, i;
char addr_buf[512];
bool prompt_pending = FALSE;
len = read(c->readfd, c->buf[0] + c->buf_off, sizeof(c->buf) - c->buf_off);
if (len <= 0) {
inet_ntop(AF_INET6, c->ep.sin6_addr.s6_addr, addr_buf, 512);
warn("Invalid read on connection from %s: closing\n", addr_buf);
vty_close_connection(c);
}
c->buf_off += len;
// try to scan the whole line we're building up and remove/process
// any telnet escapes in there
for (i = 0; i < c->buf_off; i++) {
int escape_len;
if (c->buf[0][i] == 255) {
escape_len = 2;
// process and remove a command;
// the command code is in buf[i+1]
switch (c->buf[0][i+1]) {
case TELNET_INTERRUPT:
// ignore the command buffer we've accumulated
memmove(&c->buf[0][0], &c->buf[0][i+2], c->buf_off - i - 2);
c->buf_off -= i + 2;
i = -1;
prompt_pending = TRUE;
continue;
}
if (c->buf[0][i+1] >= 250) {
unsigned char response[3];
// we don't do __anything__
response[0] = 255;
response[1] = TELNET_WONT;
response[2] = c->buf[0][i+2];
len = write(c->writefd, response, 3);
escape_len++;
}
// this isn't like the most efficient parser ever, but since we
// don't ask for anything and reply to everyone with DONT it seems okay.
memmove(&c->buf[0][i], &c->buf[0][i+escape_len],
c->buf_off - (i + escape_len));
c->buf_off -= escape_len;
// restart processing at the same place
i--;
} else if (c->buf[0][i] == 4) {
// EOT : make C-d work
vty_close_connection(c);
}
// technically, clients are supposed to send \r\n as a newline.
// however, the client in busybox (an important one) doesn't
// escape the terminal input and so only sends \n.
if (/* i > 0 && c->buf[i-1] == '\r' && */ c->buf[0][i] == '\n') {
if (!(i <= 1 && (c->buf[0][i] == '\n' || c->buf[0][i] == '\r'))) {
c->buf[0][i] = '\0';
memcpy(c->buf[1], c->buf[0], i + 1);
init_argv((char *)c->buf[1], i + 1, c->argv, &c->argc);
}
prompt_pending = FALSE;
vty_dispatch_command(c);
// start a new command at the head of the buffer
memmove(&c->buf[0][0], &c->buf[0][i+1], c->buf_off - i - 1);
c->buf_off -= i + 1;
i = -1;
}
}
if (prompt_pending) {
vty_print_string(c, "\r\n");
prompt(c);
prompt_pending = FALSE;
}
}
int vty_process(fd_set *fds) {
struct vty_client *prev, *cur, *next;
if (sock >= 0 && FD_ISSET(sock, fds)) {
vty_new_connection();
}
for (cur = conns; cur != NULL; cur = cur->next) {
if (FD_ISSET(cur->readfd, fds)) {
vty_handle_data(cur);
}
}
prev = NULL;
cur = conns;
while (cur != NULL) {
next = cur->next;
if (cur->flags & VTY_REMOVE_PENDING) {
char addr_buf[512];
inet_ntop(AF_INET6, cur->ep.sin6_addr.s6_addr, addr_buf, 512);
info("VTY: removing connection with endpoint %s\n", addr_buf);
free(cur);
if (cur == conns) conns = next;
if (prev != NULL) prev->next = next;
} else {
prev = cur;
}
cur = next;
}
return 0;
}
void vty_shutdown() {
struct vty_client *cur = conns, *next;
if (sock >= 0) close(sock);
while (cur != NULL) {
next = cur->next;
if (!(cur->flags & VTY_REMOVE_PENDING)) {
close(cur->readfd);
if (cur->readfd != cur->writefd) close(cur->writefd);
}
free(cur);
cur = next;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -