401 lines
9.8 KiB
C
401 lines
9.8 KiB
C
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <loadables.h>
|
|
#include <netinet/in.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
|
|
int sherver_bind_builtin(WORD_LIST *args) {
|
|
uint quiet = 0;
|
|
char *host = "127.0.0.1";
|
|
uint port = 0;
|
|
char *var_name = "SHERVER_FD";
|
|
uint arg = 0;
|
|
|
|
reset_internal_getopt();
|
|
|
|
while((arg = internal_getopt(args, "qh:p:v:")) != -1) {
|
|
if(arg == 'q') {
|
|
quiet = 1;
|
|
} else if(arg == 'h') {
|
|
host = list_optarg;
|
|
} else if(arg == 'p') {
|
|
char *end = NULL;
|
|
unsigned long res = strtoul(list_optarg, &end, 10);
|
|
|
|
if(*end != '\0' || res > 65535) {
|
|
builtin_usage();
|
|
return EX_USAGE;
|
|
}
|
|
|
|
port = res;
|
|
} else if(arg == 'v') {
|
|
var_name = list_optarg;
|
|
} else {
|
|
builtin_usage();
|
|
return EX_USAGE;
|
|
}
|
|
}
|
|
|
|
unbind_variable(var_name);
|
|
int server_sock = 0;
|
|
int on = 1;
|
|
|
|
if((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot open server socket: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
} else if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot configure server socket: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
}
|
|
|
|
int flags = fcntl(server_sock, F_GETFL, 0);
|
|
|
|
if(flags < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot obtain information on socket: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
} else if(fcntl(server_sock, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot make server socket non-blocking: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
}
|
|
|
|
struct sockaddr_in server;
|
|
memset(&server, 0, sizeof(server));
|
|
inet_pton(AF_INET, host, &server.sin_addr);
|
|
server.sin_family = AF_INET;
|
|
server.sin_port = htons(port);
|
|
|
|
if(bind(server_sock, (struct sockaddr *) &server, sizeof(server)) < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot bind to host: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
}
|
|
|
|
if(listen(server_sock, 0) < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("cannot start listening on host: %s", strerror(errno));
|
|
}
|
|
|
|
return errno;
|
|
}
|
|
|
|
char ibuf[INT_STRLEN_BOUND(int) + 1] = { '\0' };
|
|
char *sock_str = fmtulong(server_sock, 10, ibuf, sizeof(ibuf), 0);
|
|
bind_global_variable(var_name, sock_str, 0);
|
|
return EXECUTION_SUCCESS;
|
|
}
|
|
|
|
char *sherver_bind_doc[] = {
|
|
"Open a socket on a network interface for accepting connections.",
|
|
"",
|
|
"Binds a socket to a specified interface and port for accepting incoming",
|
|
"connections. If NAME is supplied (via -v), then the server fd is stored in",
|
|
"a variable with that name. Otherwise, the name SHERVER_FD is used.",
|
|
"",
|
|
"Options:",
|
|
" -q enable quiet mode. Do not report error messages.",
|
|
" -h HOST the HOST to bind to for incoming connections.",
|
|
" -p PORT the PORT to listen for incoming connections.",
|
|
" -v NAME the NAME to bind the server fd to.",
|
|
"",
|
|
"Exit Status:",
|
|
"On a successful binding, sherver-bind exits with a status of 0.",
|
|
"Otherwise, if an intermediate network operation fails, it returns with",
|
|
"the error code from that failure. SHERVER_FD (or the variable name",
|
|
"supplied with -v) is unset in the event of a failure.",
|
|
(char *) NULL
|
|
};
|
|
|
|
struct builtin sherver_bind_struct = {
|
|
"sherver-bind",
|
|
sherver_bind_builtin,
|
|
BUILTIN_ENABLED,
|
|
sherver_bind_doc,
|
|
"sherver-bind [-q] [-h HOST] [-p PORT] [-v NAME]",
|
|
0
|
|
};
|
|
|
|
int sherver_select_builtin(WORD_LIST *args) {
|
|
uint quiet = 0;
|
|
uint ignore_timeout = 0;
|
|
time_t timeout_secs = 5;
|
|
char *var_name = "READY_FDS";
|
|
uint arg = 0;
|
|
|
|
reset_internal_getopt();
|
|
|
|
while((arg = internal_getopt(args, "qit:v:")) != -1) {
|
|
if(arg == 'q') {
|
|
quiet = 1;
|
|
} else if(arg == 'i') {
|
|
ignore_timeout = 1;
|
|
} else if(arg == 'v') {
|
|
var_name = list_optarg;
|
|
} else if(arg == 't') {
|
|
char *end = NULL;
|
|
unsigned long res = strtoul(list_optarg, &end, 10);
|
|
|
|
if(*end != '\0') {
|
|
builtin_usage();
|
|
return EX_USAGE;
|
|
}
|
|
|
|
timeout_secs = res;
|
|
} else {
|
|
builtin_usage();
|
|
return EX_USAGE;
|
|
}
|
|
}
|
|
|
|
int max_fd = 0;
|
|
fd_set read_set, reads;
|
|
FD_ZERO(&read_set);
|
|
|
|
for(args = loptend; args; args = args->next) {
|
|
char *end = NULL;
|
|
uint fd = strtoul(args->word->word, &end, 10);
|
|
|
|
if(*end != '\0' || fd >= FD_SETSIZE) {
|
|
if(!quiet) {
|
|
builtin_error("can only monitor file descriptors less than %lu", fd);
|
|
}
|
|
|
|
continue;
|
|
} else if(fd > max_fd) {
|
|
max_fd = fd;
|
|
}
|
|
|
|
FD_SET(fd, &read_set);
|
|
}
|
|
|
|
SHELL_VAR *var = find_or_make_array_variable(var_name, 1);
|
|
|
|
if(var == 0 || readonly_p(var) || noassign_p(var)) {
|
|
if(!quiet) {
|
|
builtin_error("unable to create array variable %s", var_name);
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
} else if(array_p(var) == 0) {
|
|
if(!quiet) {
|
|
builtin_error("%s is not an indexed array", var_name);
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
}
|
|
|
|
if(invisible_p(var)) {
|
|
VUNSETATTR(var, att_invisible);
|
|
}
|
|
|
|
array_flush(array_cell(var));
|
|
|
|
while(1) {
|
|
struct timeval timeout = {
|
|
.tv_sec = timeout_secs, .tv_usec = 0
|
|
};
|
|
|
|
memcpy(&reads, &read_set, sizeof(read_set));
|
|
int result = select(max_fd + 1, &reads, NULL, NULL, &timeout);
|
|
|
|
if(!ignore_timeout && result == 0) {
|
|
return EAGAIN;
|
|
} else if(result < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("unable to poll file descriptors: %s", strerror(errno));
|
|
}
|
|
|
|
return cached;
|
|
} else if(result > 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
int fd = 0;
|
|
int idx = 0;
|
|
|
|
for(; fd < max_fd + 1; fd += 1) {
|
|
if(FD_ISSET(fd, &reads)) {
|
|
char fbuf[FD_SETSIZE] = { '\0' };
|
|
char *fd_str = fmtulong(fd, 10, fbuf, sizeof(fbuf), 0);
|
|
bind_array_element(var, idx, fd_str, 0);
|
|
idx += 1;
|
|
}
|
|
}
|
|
|
|
return EXECUTION_SUCCESS;
|
|
}
|
|
|
|
char *sherver_select_doc[] = {
|
|
"Monitor a list of file descriptors for reading activity.",
|
|
"",
|
|
"Takes a list of 0 or more file descriptors to monitor for reading activity.",
|
|
"`sherver-select` will monitor for up to SECONDS seconds (a default of 5)",
|
|
"unless -i is provided, in which case the timeout is ignored. Any file",
|
|
"descriptors that are ready for reading are copied into the array named NAME",
|
|
"(which defaults to READY_FDS).",
|
|
"",
|
|
"Options:",
|
|
" -q enable quiet mode. Do not report error messages.",
|
|
" -i ignore the timeout and try again (effectively disables -t).",
|
|
" -t SECONDS the number of SECONDS to monitor for.",
|
|
" -v NAME the NAME to bind the server fd to.",
|
|
"",
|
|
"Exit Status:",
|
|
"If successful, sherver-select returns 0. Otherwise, it returns the error",
|
|
"code of select(2).",
|
|
(char *) NULL
|
|
};
|
|
|
|
struct builtin sherver_select_struct = {
|
|
"sherver-select",
|
|
sherver_select_builtin,
|
|
BUILTIN_ENABLED,
|
|
sherver_select_doc,
|
|
"sherver-select [-q] [-i] [-t SECONDS] [-v NAME] [FD ...]",
|
|
0
|
|
};
|
|
|
|
int sherver_accept_builtin(WORD_LIST *args) {
|
|
uint quiet = 0;
|
|
char *var_name = "CLIENT_INFO";
|
|
uint arg = 0;
|
|
|
|
reset_internal_getopt();
|
|
|
|
while((arg = internal_getopt(args, "qv:")) != -1) {
|
|
if(arg == 'q') {
|
|
quiet = 1;
|
|
} else if(arg == 'v') {
|
|
var_name = list_optarg;
|
|
} else {
|
|
builtin_usage();
|
|
return EX_USAGE;
|
|
}
|
|
}
|
|
|
|
args = loptend;
|
|
|
|
if(!args) {
|
|
if(!quiet) {
|
|
builtin_error("no server file descriptor given.");
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
}
|
|
|
|
char *end = NULL;
|
|
unsigned long server_fd = strtoul(args->word->word, &end, 10);
|
|
|
|
if(*end != '\0') {
|
|
if(!quiet) {
|
|
builtin_error("'%s' is not a valid file descriptor.", args->word->word);
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
}
|
|
|
|
struct sockaddr_in peer;
|
|
socklen_t peer_size = sizeof(peer);
|
|
int client_sock = accept(server_fd, (struct sockaddr *) &peer, &peer_size);
|
|
|
|
if(client_sock < 0) {
|
|
int cached = errno;
|
|
|
|
if(!quiet) {
|
|
builtin_error("could not accept connection: %s", strerror(errno));
|
|
}
|
|
|
|
return cached;
|
|
}
|
|
|
|
char ip[INET_ADDRSTRLEN] = { '\0' };
|
|
unsigned short port = ntohs(peer.sin_port);
|
|
inet_ntop(AF_INET, &peer.sin_addr, ip, INET_ADDRSTRLEN);
|
|
SHELL_VAR *var = find_or_make_array_variable(var_name, 1);
|
|
char sbuf[FD_SETSIZE] = { '\0' };
|
|
char pbuf[6] = { '\0' };
|
|
char *sock_str = fmtulong(client_sock, 10, sbuf, sizeof(sbuf), 0);
|
|
char *port_str = fmtulong(port, 10, pbuf, sizeof(pbuf), 0);
|
|
|
|
if(var == 0 || readonly_p(var) || noassign_p(var)) {
|
|
if(!quiet) {
|
|
builtin_error("unable to create array variable %s", var_name);
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
} else if(array_p(var) == 0) {
|
|
if(!quiet) {
|
|
builtin_error("%s is not an indexed array", var_name);
|
|
}
|
|
|
|
return EXECUTION_FAILURE;
|
|
}
|
|
|
|
if(invisible_p(var)) {
|
|
VUNSETATTR(var, att_invisible);
|
|
}
|
|
|
|
array_flush(array_cell(var));
|
|
bind_array_element(var, 0, sock_str, 0);
|
|
bind_array_element(var, 1, ip, 0);
|
|
bind_array_element(var, 2, port_str, 0);
|
|
return EXECUTION_SUCCESS;
|
|
}
|
|
|
|
char *sherver_accept_doc[] = {
|
|
"Accept a connection from a server socket file descriptor.",
|
|
"",
|
|
"Accepts a connection from a server file descriptor. The client file",
|
|
"descriptor, client ip address, and client port are stored in the array",
|
|
"named NAME (default CLIENT_INFO) at indexes 0, 1, and 2 respectively.",
|
|
"",
|
|
"Options:",
|
|
" -q enable quiet mode. Do not report error messages.",
|
|
" -v NAME the NAME to bind the server fd to.",
|
|
"",
|
|
"Exit Status:",
|
|
"If successful, sherver-accept returns 0. Otherwise, it returns the error",
|
|
"code of accept(2).",
|
|
(char *) NULL
|
|
};
|
|
|
|
struct builtin sherver_accept_struct = {
|
|
"sherver-accept",
|
|
sherver_accept_builtin,
|
|
BUILTIN_ENABLED,
|
|
sherver_accept_doc,
|
|
"sherver-accept [-q] [-v NAME] [SERVER FD]",
|
|
0
|
|
};
|