mirror of
https://github.com/lxsang/antd-tunnel-publishers
synced 2024-11-14 17:18:21 +01:00
526 lines
16 KiB
C
526 lines
16 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <termios.h>
|
|
#include <sys/select.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <antd/list.h>
|
|
#include <antd/bst.h>
|
|
#include <antd/utils.h>
|
|
#include <sys/time.h>
|
|
#include "../tunnel.h"
|
|
|
|
#define MODULE_NAME "vterm"
|
|
|
|
typedef struct{
|
|
int fdm;
|
|
pid_t pid;
|
|
int cid;
|
|
} vterm_proc_t;
|
|
|
|
static bst_node_t* processes = NULL;
|
|
|
|
static volatile int running = 1;
|
|
|
|
static void int_handler(int dummy) {
|
|
(void) dummy;
|
|
running = 0;
|
|
}
|
|
|
|
static vterm_proc_t* terminal_new(const char* user)
|
|
{
|
|
int fdm, fds, rc;
|
|
pid_t pid;
|
|
vterm_proc_t* proc = NULL;
|
|
char cmd[64];
|
|
(void)memset(cmd, 0, sizeof(cmd));
|
|
if(user && strlen(user) > 0)
|
|
{
|
|
snprintf(cmd, sizeof(cmd),"TERM=linux sudo -iu %s", user);
|
|
}
|
|
else
|
|
{
|
|
snprintf(cmd, sizeof(cmd),"TERM=linux login");
|
|
}
|
|
// Check arguments
|
|
fdm = posix_openpt(O_RDWR);
|
|
if (fdm < 0)
|
|
{
|
|
M_LOG(MODULE_NAME, "Error on posix_openpt(): %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
rc = grantpt(fdm);
|
|
if (rc != 0)
|
|
{
|
|
M_LOG(MODULE_NAME, "Error on grantpt(): %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
rc = unlockpt(fdm);
|
|
if (rc != 0)
|
|
{
|
|
M_LOG(MODULE_NAME, "Error on unlockpt(): %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
// Open the slave side ot the PTY
|
|
fds = open(ptsname(fdm), O_RDWR);
|
|
|
|
// Create the child process
|
|
pid = fork();
|
|
if (pid)
|
|
{
|
|
// parent
|
|
proc = (vterm_proc_t*)malloc(sizeof(vterm_proc_t));
|
|
proc->fdm = fdm;
|
|
proc->pid = pid;
|
|
return proc;
|
|
}
|
|
else
|
|
{
|
|
//struct termios slave_orig_term_settings; // Saved terminal settings
|
|
//struct termios new_term_settings; // Current terminal settings
|
|
|
|
// CHILD
|
|
|
|
// Close the master side of the PTY
|
|
close(fdm);
|
|
|
|
// Save the defaults parameters of the slave side of the PTY
|
|
//rc = tcgetattr(fds, &slave_orig_term_settings);
|
|
|
|
// Set RAW mode on slave side of PTY
|
|
//new_term_settings = slave_orig_term_settings;
|
|
//cfmakeraw (&new_term_settings);
|
|
//tcsetattr (fds, TCSANOW, &new_term_settings);
|
|
|
|
// The slave side of the PTY becomes the standard input and outputs of the child process
|
|
// we use cook mode here
|
|
close(0); // Close standard input (current terminal)
|
|
close(1); // Close standard output (current terminal)
|
|
close(2); // Close standard error (current terminal)
|
|
|
|
rc = dup(fds); // PTY becomes standard input (0)
|
|
rc = dup(fds); // PTY becomes standard output (1)
|
|
rc = dup(fds); // PTY becomes standard error (2)
|
|
|
|
// Now the original file descriptor is useless
|
|
close(fds);
|
|
|
|
// Make the current process a new session leader
|
|
setsid();
|
|
|
|
// As the child is a session leader, set the controlling terminal to be the slave side of the PTY
|
|
// (Mandatory for programs like the shell to make them manage correctly their outputs)
|
|
ioctl(0, TIOCSCTTY, 1);
|
|
|
|
//system("/bin/bash");
|
|
rc = system(cmd);
|
|
//M_LOG("%s\n","Terminal exit");
|
|
_exit(1);
|
|
}
|
|
}
|
|
|
|
static void terminal_kill(int client_id, int should_delete)
|
|
{
|
|
// find the proc
|
|
bst_node_t* node = bst_find(processes, client_id);
|
|
vterm_proc_t* proc;
|
|
if(node != NULL)
|
|
{
|
|
proc = (vterm_proc_t*)node->data;
|
|
if(proc != NULL)
|
|
{
|
|
(void) close(proc->fdm);
|
|
M_LOG(MODULE_NAME, "Kill the process %d", proc->pid);
|
|
if(kill(proc->pid, SIGKILL) == - 1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to kill process %d: %s", proc->pid, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
(void)waitpid(proc->pid, NULL, 0);
|
|
}
|
|
free(node->data);
|
|
if(should_delete)
|
|
processes = bst_delete(processes, node->key);
|
|
// wait child
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static int terminal_write(tunnel_msg_t* msg)
|
|
{
|
|
// TODO: control frame e.g. for window resize
|
|
bst_node_t* node = bst_find(processes, msg->header.client_id);
|
|
vterm_proc_t* proc;
|
|
if(node != NULL)
|
|
{
|
|
proc = (vterm_proc_t*)node->data;
|
|
if(proc != NULL)
|
|
{
|
|
if(write(proc->fdm, msg->data, msg->header.size) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to write data to the terminal corresponding to client %d", msg->header.client_id);
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to find the process linked to client %d", msg->header.client_id);
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to find the process from processes list for %d", msg->header.client_id);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void unsubscribe(bst_node_t* node, void** args, int argc)
|
|
{
|
|
(void) argc;
|
|
tunnel_msg_t msg;
|
|
int* ufd = (int*) args[0];
|
|
vterm_proc_t* proc = (vterm_proc_t*) node->data;
|
|
if(proc != NULL)
|
|
{
|
|
msg.header.type = CHANNEL_UNSUBSCRIBE;
|
|
msg.header.client_id = proc->cid;
|
|
msg.header.size = 0;
|
|
terminal_kill(proc->cid, 0);
|
|
if(msg_write(*ufd, &msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to request unsubscribe to client %d", proc->cid);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void set_sock_fd(bst_node_t* node, void** args, int argc)
|
|
{
|
|
(void) argc;
|
|
tunnel_msg_t msg;
|
|
pid_t wpid;
|
|
fd_set* fd_in = (fd_set*) args[1];
|
|
int* max_fd = (int*)args[2];
|
|
list_t* list_p = (list_t*) args[3];
|
|
int* ufd = (int*) args[0];
|
|
|
|
vterm_proc_t* proc = (vterm_proc_t*) node->data;
|
|
|
|
if(proc != NULL)
|
|
{
|
|
// monitor the pid
|
|
wpid = waitpid(proc->pid, NULL, WNOHANG);
|
|
if(wpid == -1 || wpid > 0)
|
|
{
|
|
// child exits
|
|
M_LOG(MODULE_NAME, "Terminal linked to client %d exits\n", proc->cid);
|
|
unsubscribe(node, args, argc);
|
|
list_put_ptr(list_p, node);
|
|
}
|
|
else
|
|
{
|
|
FD_SET(proc->fdm, fd_in);
|
|
if(*max_fd < proc->fdm)
|
|
{
|
|
*max_fd = proc->fdm;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void terminal_monitor(bst_node_t* node, void** args, int argc)
|
|
{
|
|
(void) argc;
|
|
int* ufd = (int*) args[0];
|
|
fd_set* fd_in = (fd_set*) args[1];
|
|
list_t* list = (list_t*) args[3];
|
|
char buff[BUFFLEN];
|
|
tunnel_msg_t msg;
|
|
int rc;
|
|
vterm_proc_t* proc = (vterm_proc_t*) node->data;
|
|
|
|
if(proc != NULL && FD_ISSET(proc->fdm, fd_in))
|
|
{
|
|
if ((rc = read(proc->fdm, buff, BUFFLEN)) > 0)
|
|
{
|
|
// Send data to client
|
|
msg.header.client_id = node->key;
|
|
msg.header.type = CHANNEL_DATA;
|
|
msg.header.size = rc;
|
|
msg.data = buff;
|
|
if(msg_write(*ufd, &msg) == -1)
|
|
{
|
|
terminal_kill(node->key, 0);
|
|
M_ERROR(MODULE_NAME,"Unable to send data to client %d", msg.header.client_id);
|
|
list_put_ptr(list, node);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rc < 0)
|
|
{
|
|
M_LOG(MODULE_NAME, "Error on read standard input: %s\n", strerror(errno));
|
|
terminal_kill(node->key, 0);
|
|
list_put_ptr(list, node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void terminal_resize(int cid, int col, int row)
|
|
{
|
|
struct winsize win = {0, 0, 0, 0};
|
|
bst_node_t* node = bst_find(processes, cid);
|
|
vterm_proc_t* proc;
|
|
if(node != NULL)
|
|
{
|
|
proc = (vterm_proc_t*) node->data;
|
|
if (ioctl(proc->fdm, TIOCGWINSZ, &win) != 0)
|
|
{
|
|
if (errno != EINVAL)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to get terminal winsize setting: %s", strerror(errno));
|
|
return;
|
|
}
|
|
memset(&win, 0, sizeof(win));
|
|
}
|
|
//printf("Setting winsize\n");
|
|
if (row >= 0)
|
|
win.ws_row = (unsigned short)row;
|
|
if (col >= 0)
|
|
win.ws_col = (unsigned short)col;
|
|
|
|
if (ioctl(proc->fdm, TIOCSWINSZ, (char *)&win) != 0)
|
|
M_ERROR(MODULE_NAME, "Unable to set terminal window size process linked to client %d: %s", cid, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to find the terminal process linked to client %d", cid);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int fd;
|
|
tunnel_msg_t msg;
|
|
fd_set fd_in;
|
|
int status, maxfd;
|
|
struct timeval timeout;
|
|
char buff[MAX_CHANNEL_NAME+1];
|
|
void *args[4];
|
|
list_t list;
|
|
item_t item;
|
|
int ncol, nrow;
|
|
|
|
LOG_INIT(MODULE_NAME);
|
|
if(argc != 2)
|
|
{
|
|
printf("Usage: %s path/to/hotline/socket\n", argv[0]);
|
|
return -1;
|
|
}
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGABRT, SIG_IGN);
|
|
signal(SIGINT, int_handler);
|
|
M_LOG(MODULE_NAME, "Hotline is: %s", argv[1]);
|
|
// now try to request new channel from hotline
|
|
fd = open_unix_socket(argv[1]);
|
|
if(fd == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to open the hotline: %s", argv[1]);
|
|
return -1;
|
|
}
|
|
msg.header.type = CHANNEL_OPEN;
|
|
msg.header.channel_id = 0;
|
|
msg.header.client_id = 0;
|
|
M_LOG(MODULE_NAME, "Request to open the channel %s", MODULE_NAME);
|
|
(void)strncpy(buff, MODULE_NAME,MAX_CHANNEL_NAME);
|
|
msg.header.size = strlen(buff);
|
|
msg.data = (uint8_t*) buff;
|
|
if(msg_write(fd, &msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to write message to hotline");
|
|
(void) close(fd);
|
|
return -1;
|
|
}
|
|
M_LOG(MODULE_NAME, "Wait for comfirm creation of %s", MODULE_NAME);
|
|
// now wait for message
|
|
if(msg_read(fd, &msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to read message from hotline");
|
|
(void) close(fd);
|
|
return -1;
|
|
}
|
|
if(msg.header.type == CHANNEL_OK)
|
|
{
|
|
M_LOG(MODULE_NAME, "Channel created: %s", MODULE_NAME);
|
|
if(msg.data)
|
|
free(msg.data);
|
|
}
|
|
else
|
|
{
|
|
M_ERROR(MODULE_NAME, "Channel is not created: %s. Tunnel service responds with msg of type %d", MODULE_NAME, msg.header.type);
|
|
if(msg.data)
|
|
free(msg.data);
|
|
running = 0;
|
|
}
|
|
|
|
// now read data
|
|
while(running)
|
|
{
|
|
FD_ZERO(&fd_in);
|
|
FD_SET(fd, &fd_in);
|
|
maxfd = fd;
|
|
|
|
// monitor processes
|
|
list = list_init();
|
|
args[1] = (void*) &fd_in;
|
|
args[2] = (void*) &maxfd;
|
|
args[3] = (void*) &list;
|
|
args[0] = (void*) &fd;
|
|
bst_for_each(processes, set_sock_fd, args, 4);
|
|
list_for_each(item, list)
|
|
{
|
|
processes = bst_delete(processes, ((bst_node_t*)(item->value.ptr))->key);
|
|
item->value.ptr = NULL;
|
|
}
|
|
list_free(&list);
|
|
|
|
status = select(maxfd + 1, &fd_in, NULL, NULL, NULL);
|
|
|
|
switch (status)
|
|
{
|
|
case -1:
|
|
M_LOG(MODULE_NAME, "Error %d on select()\n", errno);
|
|
running = 0;
|
|
break;
|
|
case 0:
|
|
break;
|
|
// we have data
|
|
default:
|
|
if (FD_ISSET(fd, &fd_in))
|
|
{
|
|
if(msg_read(fd, &msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to read message from channel. quit");
|
|
running = 0;
|
|
}
|
|
else
|
|
{
|
|
switch (msg.header.type)
|
|
{
|
|
case CHANNEL_SUBSCRIBE:
|
|
M_LOG(MODULE_NAME, "Client %d subscribes to the chanel with user [%s]", msg.header.client_id, msg.data);
|
|
// create new process
|
|
vterm_proc_t* proc = terminal_new(msg.data);
|
|
if(proc == NULL)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to create new terminal for client %d", msg.header.client_id);
|
|
// unsubscribe client
|
|
msg.header.type = CHANNEL_UNSUBSCRIBE;
|
|
msg.header.size = 0;
|
|
if(msg_write(fd, &msg) == -1)
|
|
{
|
|
M_LOG(MODULE_NAME,"Unable to request unsubscribe client %d", msg.header.client_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
proc->cid = msg.header.client_id;
|
|
// insert new terminal to the list
|
|
processes = bst_insert(processes, msg.header.client_id, proc);
|
|
}
|
|
break;
|
|
|
|
case CHANNEL_UNSUBSCRIBE:
|
|
M_LOG(MODULE_NAME, "Client %d unsubscribes to the chanel", msg.header.client_id);
|
|
terminal_kill(msg.header.client_id, 1);
|
|
break;
|
|
|
|
case CHANNEL_CTRL:
|
|
if(msg.header.size == 8)
|
|
{
|
|
(void)memcpy(&ncol, msg.data, sizeof(ncol));
|
|
(void)memcpy(&nrow, msg.data + sizeof(ncol), sizeof(nrow));
|
|
M_LOG(MODULE_NAME, "Client %d request terminal window resize of (%d,%d)", msg.header.client_id, ncol, nrow);
|
|
terminal_resize(msg.header.client_id, ncol, nrow);
|
|
|
|
}
|
|
else
|
|
{
|
|
M_ERROR(MODULE_NAME, "Invalid control message size: %d from client %d, expected 8", msg.header.size, msg.header.client_id);
|
|
}
|
|
|
|
break;
|
|
|
|
case CHANNEL_DATA:
|
|
if(terminal_write(&msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to write data to terminal corresponding to client %d", msg.header.client_id);
|
|
terminal_kill(msg.header.client_id, 1);
|
|
msg.header.type = CHANNEL_UNSUBSCRIBE;
|
|
msg.header.size = 0;
|
|
if(msg_write(fd, &msg) == -1)
|
|
{
|
|
M_LOG(MODULE_NAME,"Unable to request unsubscribe client %d", msg.header.client_id);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
M_LOG(MODULE_NAME, "Client %d send message of type %d",
|
|
msg.header.client_id, msg.header.type);
|
|
break;
|
|
}
|
|
if(msg.data)
|
|
{
|
|
free(msg.data);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// on the processes side
|
|
list = list_init();
|
|
bst_for_each(processes, terminal_monitor, args, 4);
|
|
list_for_each(item, list)
|
|
{
|
|
processes = bst_delete(processes, ((bst_node_t*)(item->value.ptr))->key);
|
|
item->value.ptr = NULL;
|
|
}
|
|
list_free(&list);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// unsubscribe all clients
|
|
args[0] = (void*) &fd;
|
|
bst_for_each(processes, unsubscribe, args, 1);
|
|
(void)bst_free(processes);
|
|
// close the channel
|
|
M_LOG(MODULE_NAME, "Close the channel %s (%d)", MODULE_NAME, fd);
|
|
msg.header.type = CHANNEL_CLOSE;
|
|
msg.header.size = 0;
|
|
msg.data = NULL;
|
|
if( msg_write(fd, &msg) == -1)
|
|
{
|
|
M_ERROR(MODULE_NAME, "Unable to request channel close");
|
|
}
|
|
// close all opened terminal
|
|
|
|
(void)msg_read(fd, &msg);
|
|
(void) close(fd);
|
|
return 0;
|
|
} |