/* * iucvtty / iucvconn - IUCV Terminal Applications * * Common functions * * 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 #include #include #include #include #include #include #include #include #include #include #include "af_iucv.h" #include "iucvterm/config.h" #include "iucvterm/functions.h" #include "iucvterm/gettext.h" #include "iucvterm/proto.h" /* Global program component for iucv terminal tools */ #define PRG_COMPONENT "iucvterm" /** * __write() - Write data * @fd: File descriptor * @buf: Pointer to data buffer * @len: Buffer length * * Write @len number of bytes from the buffer @buf to the file * descriptor @fd. The routine handles EINTR and partial writes. * Returns the error code from the underlying write(2) syscall. */ ssize_t __write(int fd, const void *buf, size_t len) { ssize_t rc; size_t written = 0; while (written < len) { rc = write(fd, buf + written, len - written); if (rc == -1 && errno == EINTR) continue; if (rc <= 0) return rc; written += rc; } return written; } /** * __read() - Read data * @fd: File descriptor * @buf: Pointer to data buffer * @len: Buffer size * * Read up to @len number of bytes from file descriptor @fd and stores them * in the buffer to which @buf points. * The routine handles EINTR and partial reads and returns the error code * from the underlying read(2) syscall. */ ssize_t __read(int fd, void *buf, size_t len) { ssize_t rc; size_t count = 0; while (count < len) { rc = read(fd, buf + count, len - count); if (rc == -1 && errno == EINTR) continue; if (rc <= 0) return rc; count += rc; } return count; } #ifdef __DEBUG__ static void __dump_msg(int fd, const struct iucvtty_msg *m, char dir) { write_session_info("%c: (fd=%d) MSG: ver=%02x type=%02x datalen=%u\n", dir, fd, m->version, m->type, (uint16_t) m->datalen); } #endif /** * iucvtty_socket() - Creates and return an IUCV socket * @sai: AF_IUCV socket address structure * @host: z/VM guest name * @service: Terminal name passed as additional data to @host after connect * * This function sets up the struct sockaddr_iucv with the specified * VM guest virtual machine and terminal information. * Finally, it returns an AF_IUCV socket. */ int iucvtty_socket(struct sockaddr_iucv *sai, const char *host, const char *service) { char temp[9]; memset(sai, 0, sizeof(struct sockaddr_iucv)); sai->siucv_family = AF_IUCV; if (host != NULL) { snprintf(temp, 9, "%-8s", host); memcpy(sai->siucv_user_id, temp, 8); } else memset(sai->siucv_user_id, ' ', 8); if (service != NULL) { snprintf(temp, 9, "%-8s", service); memcpy(sai->siucv_name, temp, 8); } else memset(sai->siucv_name, ' ', 8); return socket(PF_IUCV, SOCK_STREAM, 0); } /** * iucvtty_tx_termenv() - Send terminal environment variable * @dest: File descriptor to output data * @dflt: TERM environment string ('\0' terminated) * * Copy terminal environment variable to destination @dest. */ int iucvtty_tx_termenv(int dest, char *dflt) { struct iucvtty_msg *msg; char *term = getenv("TERM"); size_t len; int rc; if (term == NULL && dflt != NULL) term = dflt; len = 0; if (term != NULL) len = MIN(1 + strlen(term), (size_t)MAX_TERM_SIZE); /* Note: The server console tool waits for terminal environment * information: the message is sent even if it is empty */ msg = msg_alloc(MSG_TYPE_TERMENV, len); if (msg == NULL) return -1; msg->datalen = len; if (msg->datalen) snprintf((char *)msg->data, msg->datalen, "%s", term); rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_rx_termenv() - Receive terminal environment variable * @fd: File descriptor to read data from * @buf: Buffer to store the terminal environment variable * @len: Size of buffer @buf */ int iucvtty_rx_termenv(int fd, void *buf, size_t len) { int rc; size_t skip; struct iucvtty_msg *msg = msg_alloc(MSG_TYPE_TERMENV, len); if (msg == NULL) return -1; skip = 0; rc = iucvtty_read_msg(fd, msg, msg_size(msg), &skip); iucvtty_skip_msg_chunk(fd, &skip); if (!rc) { if (msg->datalen == 0) { memset(buf, 0, MIN(1u, len)); } else { msg_cpy_to(msg, buf, len); memset(buf + MIN(msg->datalen, len - 1), 0, 1); } } msg_free(msg); return rc; } /** * iucvtty_tx_data() - Send terminal data * @from: File descriptor to read data from * @msg: Pointer to iucv tty message buffer * @len: Size of message buffer * * This routine reads data from file descriptor @from and stores them in * a data array of the specified iucv tty message @msg. It reads up to * @len - MSG_DATA_OFFSET bytes from fd @from. */ int iucvtty_read_data(int from, struct iucvtty_msg *msg, size_t len) { ssize_t r; r = read(from, msg->data, len - MSG_DATA_OFFSET); if (r == -1 && errno == EINTR) /* REVIEW: loop if EINTR ? */ r = read(from, msg->data, len - MSG_DATA_OFFSET); if (r <= 0) return -1; msg->version = MSG_VERSION; msg->type = MSG_TYPE_DATA; msg->datalen = (uint16_t) r; return 0; } /** * iucvtty_tx_data() - Send terminal data * @dest: File descriptor to send data to * @from: File descriptor to read data from * @msg: Pointer to iucv tty message buffer * @len: Size of message buffer * * This routine reads data from file descriptor @from and stores them in * a data array of the specified iucv tty message @msg. It reads up to * @len - MSG_DATA_OFFSET bytes from fd @from. * Finally, the iucv tty message written to file descriptor @dest. */ int iucvtty_tx_data(int dest, int from, struct iucvtty_msg *msg, size_t len) { if (iucvtty_read_data(from, msg, len)) return -1; if (iucvtty_write_msg(dest, msg)) return -1; return 0; } /** * iucvtty_tx_winsize() - Send terminal window size information. * @dest: Destination * @from: Terminal file descriptor to request winsize * * Sends the terminal window size from terminal file descriptor * @from to the destination @dest. * If the window size is not retrieved, the routine will not fail. * The routine fails if there is a problem sending the window size * to @dest. The return codes are specified by iucvtty_write_msg(). */ int iucvtty_tx_winsize(int dest, int from) { int rc; struct iucvtty_msg *msg = msg_alloc(MSG_TYPE_WINSIZE, sizeof(struct winsize)); if (msg == NULL) return -1; rc = 0; if (ioctl(from, TIOCGWINSZ, msg->data) > -1) rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_tx_error() - Send an error code * @dest: Destination * @errCode: Error code */ int iucvtty_tx_error(int dest, uint32_t errCode) { struct iucvtty_msg *msg; int rc; msg = msg_alloc(MSG_TYPE_ERROR, sizeof(errCode)); if (msg == NULL) return -1; msg_cpy_from(msg, &errCode, sizeof(errCode)); rc = iucvtty_write_msg(dest, msg); msg_free(msg); return rc; } /** * iucvtty_copy_data() - Copy IUCV message data * @dest: Destination to copy data to * @msg: IUCV message */ int iucvtty_copy_data(int dest, struct iucvtty_msg *msg) { if (__write(dest, msg->data, msg->datalen) <= 0) return -1; return 0; } /** * iucvtty_skip_msg_chunk() - Skip (receive & forget) count number of bytes * @fd: File descriptor * @chunk: Remaining chunk from a previous iucvtty_read_msg() call * * See iucvtty_read_msg() for an explanation when to use this routine. * Note: The @chunk parameter must not be NULL. */ void iucvtty_skip_msg_chunk(int fd, size_t *chunk) { int r; char buf[256]; if (*chunk <= 0) return; while (*chunk) { r = __read(fd, buf, MIN(sizeof(buf), *chunk)); if (r <= 0) break; *chunk -= r; } *chunk = 0; } /** * iucvtty_read_msg_chunk() - Read IUCV message chunk * @fd: File descriptor to read from * @msg: Pointer to IUCV message buffer * @msglen: IUCV message buffer size (including message header) * @chunk: Size of chunk data to read * * Stores new message data and calculates next chunk size if not all data * could be read. Returns zero on success, non-zero otherwise. */ static int iucvtty_read_msg_chunk(int fd, struct iucvtty_msg *msg, size_t msglen, size_t *chunk) { ssize_t r; size_t datalen; /* Calculate message data length to read */ datalen = MIN(msglen - MSG_DATA_OFFSET, *chunk); for (;;) { r = read(fd, msg->data, datalen); if (r == -1 && errno == EINTR) continue; if (r <= 0) return -1; break; } /* Update message and re-calculate next chunk */ msg->datalen = r; *chunk -= r; return 0; } /** * validate_msg() - Perform sanity checks on a received message * @msg: IUCV message buffer * * Returns zero if the message is valid; otherwise non-zero */ static int validate_msg(struct iucvtty_msg *msg) { switch (msg->type) { case MSG_TYPE_DATA: /* The datalen ranges from 0 to its maximum of 0xffff * which is the maximum of the type definition of uint16_t. * * Consider the datalen value as valid. */ break; case MSG_TYPE_ERROR: if (msg->datalen != sizeof(uint32_t)) return 1; break; case MSG_TYPE_TERMENV: if (msg->datalen > MAX_TERM_SIZE) return 1; break; case MSG_TYPE_TERMIOS: /* ignored */ break; case MSG_TYPE_WINSIZE: if (msg->datalen != sizeof(struct winsize)) return 1; break; default: /* Invalid message type */ return 1; } return 0; } /** * iucvtty_read_msg() - Read/Receive an IUCV message * @fd: File descriptor to read from * @msg: Pointer to IUCV message buffer * @msglen: IUCV message buffer size (MUST BE > MSG_DATA_OFFSET) * @chunk: Size of remaining data; must be passed on next call * * The function reads up to @len bytes from file descriptor @fd. If the * received message is larger than @len bytes, the @chunk value is set to * the number of remaining bytes. The function shall then be re-called to * create a new message and receive the outstanding data. Alternatively, * call iucvtty_skip_msg_chunk() to discard remaining data. * * NOTE: The @len parameter must be greater than MSG_DATA_OFFSET! * The @chunk parameter must not be NULL. */ int iucvtty_read_msg(int fd, struct iucvtty_msg *msg, size_t msglen, size_t *chunk) { int rc; ssize_t r; /* number of bytes read from fd */ /* Ensure message buffer size can hold data and can be processed */ assert(msglen > MSG_DATA_OFFSET); assert(chunk != NULL); /* Read pending message data */ if (*chunk) return iucvtty_read_msg_chunk(fd, msg, msglen, chunk); /* Read message header of new message */ r = __read(fd, msg, MSG_DATA_OFFSET); if (r <= 0) { rc = -1; goto out_read_error; } #ifdef __DEBUG__ __dump_msg(fd, msg, 'R'); #endif /* Check message header */ if (msg->version != MSG_VERSION) { fprintf(stderr, _("%s: %s\n"), PRG_COMPONENT, _("The version of the received data " "message is not supported")); fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n", msg->version, msg->type, msg->datalen); rc = -2; goto out_read_error; } /* Check for an empty message */ if (!msg->datalen) return 0; /* Check message type and data length */ if (validate_msg(msg)) { fprintf(stderr, _("%s: %s\n"), PRG_COMPONENT, _("The received message is invalid")); fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n", msg->version, msg->type, msg->datalen); rc = -3; goto out_read_error; } /* Process the new message as a one chunk */ *chunk = msg->datalen; msg->datalen = 0; rc = iucvtty_read_msg_chunk(fd, msg, msglen, chunk); out_read_error: return rc; } /** * iucvtty_write_msg() - Write/Send IUCV message * @fd: File descriptor * @msg: Pointer to IUCV message */ int iucvtty_write_msg(int fd, struct iucvtty_msg *msg) { msg->version = MSG_VERSION; if (__write(fd, msg, msg_size(msg)) <= 0) return -1; #ifdef __DEBUG__ __dump_msg(fd, msg, 'S'); #endif return 0; } /** * iucv_msg_error() - Reports an IUCV message error * @comp: Program component * @errnum: IUCV message error code */ void iucv_msg_error(const char *comp, uint32_t errnum) { const char *translated; switch (errnum) { case ERR_FORK: translated = _("Creating a new process to run the " "login program failed"); break; case ERR_CANNOT_EXEC_LOGIN: translated = _("Running the login program failed"); break; case ERR_SETUP_LOGIN_TTY: translated = _("Setting up a terminal for user login failed"); break; case ERR_NOT_AUTHORIZED: translated = _("The z/VM guest virtual machine is not " "permitted to connect"); break; default: translated = _("The specified error code is not known"); break; } fprintf(stderr, "%s: %s (%s=%" PRIu32 ")\r\n", comp, translated, _("error code"), errnum); } /** * program_error() - Report an program/syscall error. * @comp: Program component name * @d: Error message, subject to gettext translation */ void program_error(const char *comp, const char *d) { fprintf(stderr, _("%s: %s: %s\n"), comp, _(d), strerror(errno)); } /** * __regerror - Report an error from a previous regex api call * @error: Error code * @re: Reference to the used regular expression */ static inline void __regerror(int error, const regex_t *re) { char errbuf[81]; regerror(error, re, errbuf, 81); fprintf(stderr, _("The regular expression has an error: %s\n"), errbuf); return; } /** * is_regex_valid() - Check if the specified regex is syntactically correct. * @re: String representation of the regular expression * * Returns zero on success, otherwise -1. */ int is_regex_valid(const char *re) { regex_t regex; int rc; if (re == NULL) return -1; rc = regcomp(®ex, re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if (rc) { __regerror(rc, ®ex); rc = -1; } regfree(®ex); return rc; } /** * strmatch() - Match a string using a regular expression * @str: String to match * @re: Regular expression * * Returns zero on success, -1 on error or if @str is NULL; and * 1 if the regular expression did not match the string. */ int strmatch(const char *str, const char *re) { regex_t regex; regmatch_t pmatch[1]; size_t nmatch = 0; int rc; if (re == NULL) return -1; rc = regcomp(®ex, re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if (rc) { __regerror(rc, ®ex); regfree(®ex); return -1; } rc = regexec(®ex, str, nmatch, pmatch, 0); if (rc == REG_NOMATCH) rc = 1; regfree(®ex); return rc; } /** * is_client_allowed() - Check if the client is allowed to connect. * @client: Client name * @cfg: Pointer to the IUCV terminal configuration structure * * The return code is identical to strmatch(). If client checking is * disabled, the function returns zero. */ int is_client_allowed(const char *client, const struct iucvterm_cfg *cfg) { if (!CFG_CHKCLNT(cfg)) return 0; return strmatch(client, cfg->client_re); } /** * is_term_valid() - Validate TERM environment value * @term: Terminal environment name to validate * @len: Maximum number of characters to validate */ int is_term_valid(const char *term, size_t len) { const char *c; if (term == NULL || *term == '\0') return 0; for (c = term; *c != '\0'; c++) { if ((size_t)(c - term) >= len) return 0; if (!isalnum((unsigned char)*c) && *c != '.' && *c != '_' && *c != '-') return 0; } return 1; } /** * userid_cpy() - Copy z/VM user ID and skip trailing spaces. * @dest: Destination buffer * @userid: z/VM user ID */ void userid_cpy(char dest[9], const char userid[8]) { ssize_t pos; /* find pos of last character (pos 0..7) or -1 if user ID is empty */ for (pos = 7; pos >= 0; pos--) if (userid[pos] != ' ') break; if (pos >= 0) memcpy(dest, userid, pos + 1); dest[pos + 1] = '\0'; }