Files
s390-tools/iucvterm/src/iucvtty.c
Hendrik Brueckner 0c771423b4 iucvterm/iucvtty: Ensure PTY and server fd's are closed at exec
Check the return code of fcntl() calls to ensure the close-on-exec
setting succeeded.  Otherwise, the PTY and server file descriptors
would become available to the forked client process.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-07-22 17:45:06 +02:00

336 lines
8.3 KiB
C

/*
* iucvtty - Application that provides a full-screen terminal for iucvconn
*
* Core application
*
* Copyright IBM Corp. 2008, 2017
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <errno.h>
#include <fcntl.h>
#include <pty.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syslog.h>
#include <unistd.h>
#include <utmp.h>
#include "lib/util_base.h"
#include "af_iucv.h"
#include "iucvterm/config.h"
#include "iucvterm/functions.h"
#include "iucvterm/gettext.h"
#define SYSLOG_IDENT "iucvtty"
#define PRG_COMPONENT SYSLOG_IDENT
#define TERM_DEFAULT "linux"
static volatile sig_atomic_t sig_shutdown;
/**
* sig_handler() - Signal handler
* @sig: Signal number.
*/
static void sig_handler(int sig)
{
sig_shutdown = sig;
}
/**
* exec_login_prog() - execute a login program
* @cmd: Path to the (login) program executable
*/
static int exec_login_prog(char *cmd[])
{
int rc;
if (cmd != NULL)
rc = execv(cmd[0], cmd);
else
rc = execl("/bin/login", "/bin/login", (char *) NULL);
return rc;
}
/**
* receive_term_env() - receive terminal environment with timeout
* @term: Terminal environment variable
* @len: Maximum characters for the terminal environment variable
*/
static void receive_term_env(int client, char *term, size_t len)
{
struct timeval tv = { .tv_sec = 0, .tv_usec = 0};
/* Set a temporary timeout of 10s to time out a stalling
* client connection. Use SO_RCVTIMEO to set the timeout.
* Using those hard coded values directly is safe as
* SO_RCVTIMEO is not being used in other parts of iucvtty.
*/
tv.tv_sec = 10;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
memset(term, 0, len);
if (iucvtty_rx_termenv(client, term, len))
snprintf(term, len, "%s", TERM_DEFAULT);
tv.tv_sec = 0;
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (!is_term_valid(term, len)) {
print_error("Ignoring received TERM env due to invalid character(s)");
snprintf(term, len, "%s", TERM_DEFAULT);
}
}
/**
* iucvtty_worker() - Handle an incoming client connection
* @client: Client file descriptor
* @master: PTY master file descriptor
* @slave: PTY slave file descriptor
* @cfg: IUCV TTY configuration structure.
*
* Unlike the HVC IUCV terminal device driver, iucvtty expects to
* receive the TERM environment first. This is necessary to correctly
* set up the login program.
*/
static int iucvtty_worker(int client, int master, int slave,
const struct iucvterm_cfg *cfg)
{
int rc;
struct iucvtty_msg *msg;
pid_t child;
fd_set set;
size_t chunk;
char term_env[MAX_TERM_SIZE];
/* flush pending terminal data */
tcflush(master, TCIOFLUSH);
/* read and validate terminal parameters from client */
receive_term_env(client, term_env, sizeof(term_env));
/* start login program */
child = fork();
if (child == -1) {
print_error("Creating a new process to run the "
"login program failed");
iucvtty_tx_error(client, ERR_FORK);
return 1; /* return from worker */
}
if (child == 0) { /* child process */
closelog(); /* close syslog */
/* setup terminal */
if (login_tty(slave)) {
print_error("Setting up a terminal for user login failed");
iucvtty_tx_error(client, ERR_SETUP_LOGIN_TTY);
exit(2);
}
setenv("TERM", term_env, 1);
if (exec_login_prog(cfg->cmd_parms)) {
print_error("Running the login program failed");
iucvtty_tx_error(client, ERR_CANNOT_EXEC_LOGIN);
}
exit(3); /* we only reach here if exec has failed */
}
/* Close slave fd to properly handle SIGHUP for the child process */
close(slave);
/* setup buffers */
msg = malloc(MSG_BUFFER_SIZE);
if (msg == NULL) {
print_error("Allocating memory for the data buffer failed");
rc = 2;
goto out_kill_login;
}
/* multiplex i/o between login program and socket. */
rc = 0;
chunk = 0;
while (!sig_shutdown) {
FD_ZERO(&set);
FD_SET(client, &set);
FD_SET(master, &set);
if (select(MAX(master, client) + 1, &set,
NULL, NULL, NULL) == -1) {
if (errno == EINTR)
continue;
break;
}
if (FD_ISSET(client, &set)) {
if (iucvtty_read_msg(client, msg,
MSG_BUFFER_SIZE, &chunk))
break;
switch (msg->type) {
case MSG_TYPE_DATA:
iucvtty_copy_data(master, msg);
break;
case MSG_TYPE_WINSIZE:
if (msg->datalen != sizeof(struct winsize))
break;
if (ioctl(master, TIOCSWINSZ,
(struct winsize *) msg->data))
print_error("Resizing the terminal "
"window failed");
break;
case MSG_TYPE_TERMIOS: /* ignored */
break;
case MSG_TYPE_ERROR:
iucvtty_error(msg);
break;
}
}
if (FD_ISSET(master, &set))
if (iucvtty_tx_data(client, master,
msg, MSG_BUFFER_SIZE))
break;
}
free(msg);
out_kill_login:
/* ensure the chld is terminated before calling waitpid:
* - in case a sigterm has been received,
* - or a sigchld from other than the chld
*/
kill(child, SIGKILL); /* cause a sigchld */
waitpid(child, NULL, 0);
return rc;
}
/**
* main() - IUCV TTY program startup
*/
int main(int argc, char *argv[])
{
struct iucvterm_cfg conf; /* program configuration */
struct sockaddr_iucv saddr, caddr; /* IUCV socket address info */
char client_host[9]; /* client guest name */
int server, client; /* socket file descriptors */
int master, slave; /* pre-allocated PTY fds */
struct sigaction sigact; /* signal handler */
int rc;
socklen_t len;
/* gettext initialization */
gettext_init();
/* parse command line arguments */
parse_options(PROG_IUCV_TTY, &conf, argc, argv);
/* create server socket... */
server = iucvtty_socket(&saddr, NULL, conf.service);
if (server == -1) {
print_error((errno == EAFNOSUPPORT)
? N_("The AF_IUCV address family is not available")
: N_("Creating the AF_IUCV socket failed"));
return 1;
}
if (bind(server, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) {
print_error("Binding the AF_IUCV socket failed");
close(server);
return 1;
}
if (listen(server, 1) == -1) {
print_error("Listening for incoming connections failed");
close(server);
return 1;
}
/* pre-allocate PTY master/slave file descriptors */
if (openpty(&master, &slave, NULL, NULL, NULL)) {
print_error("Opening a new PTY master/slave device pair failed");
close(server);
return 1;
}
/* set close-on-exec for file descriptors */
if (fcntl(master, F_SETFD, FD_CLOEXEC) ||
fcntl(server, F_SETFD, FD_CLOEXEC)) {
print_error("Setting file controls failed");
close(server);
rc = 1;
goto exit_on_error;
}
/* syslog */
openlog(SYSLOG_IDENT, LOG_PID, LOG_AUTHPRIV);
syslog(LOG_INFO, "Listening on terminal ID: %s, using pts device: %s",
conf.service, ttyname(slave));
rc = 0;
len = sizeof(struct sockaddr_iucv);
/* accept a new client connection */
client = accept(server, (struct sockaddr *) &caddr, &len);
if (client == -1) {
print_error("An incoming connection could not be accepted");
close(server);
rc = 2;
goto exit_on_error;
}
/* check if client is allowed to connect */
userid_cpy(client_host, caddr.siucv_user_id);
if (is_client_allowed(client_host, &conf)) {
iucvtty_tx_error(client, ERR_NOT_AUTHORIZED);
syslog(LOG_WARNING, "Rejected client connection from %s; "
"Client is not allowed to connect.",
client_host);
rc = 3;
} else { /* client is allowed to connect */
syslog(LOG_INFO, "Accepted client connection from %s",
client_host);
/* close server socket */
close(server);
/* set close-on-exec for client socket */
if (fcntl(client, F_SETFD, FD_CLOEXEC)) {
print_error("Setting file controls failed");
rc = 4;
goto exit_on_error;
}
/* setup signal handler to notify shutdown signal */
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART;
sigact.sa_handler = sig_handler;
if (sigaction(SIGCHLD, &sigact, NULL)
|| sigaction(SIGTERM, &sigact, NULL)
|| sigaction(SIGINT, &sigact, NULL)
|| sigaction(SIGPIPE, &sigact, NULL)) {
print_error("Registering a signal handler failed");
rc = 4;
goto exit_on_error;
}
/* handle client terminal connection */
rc = iucvtty_worker(client, master, slave, &conf);
}
close(client);
exit_on_error:
close(slave);
close(master);
closelog();
return rc;
}