| /* |
| * |
| * Copyright (c) 2016 Nest Labs, Inc. |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #if HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE 1 |
| |
| #include "assert-macros.h" |
| |
| #include <stdio.h> |
| #include "socket-utils.h" |
| #include "string-utils.h" |
| #include <ctype.h> |
| #include <syslog.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <termios.h> |
| #include <fcntl.h> |
| #include <arpa/inet.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <sys/ioctl.h> |
| #include <ctype.h> |
| #include <signal.h> |
| |
| #if HAVE_SYS_UN_H |
| #include <sys/un.h> |
| #endif |
| |
| #if HAVE_SYS_WAIT_H |
| #include <sys/wait.h> |
| #endif |
| |
| #if HAVE_PTY_H |
| #include <pty.h> |
| #endif |
| |
| #if HAVE_UTIL_H |
| #include <util.h> |
| #endif |
| |
| #if HAVE_SYS_PRCTL_H |
| #include <sys/prctl.h> |
| #endif |
| |
| |
| #if !defined(HAVE_PTSNAME) && __APPLE__ |
| #define HAVE_PTSNAME 1 |
| #endif |
| |
| #if !HAVE_GETDTABLESIZE |
| #define getdtablesize() (int)sysconf(_SC_OPEN_MAX) |
| #endif |
| |
| #ifndef O_NONBLOCK |
| #define O_NONBLOCK O_NDELAY |
| #endif |
| |
| #include <poll.h> |
| |
| static struct { |
| int fd; |
| pid_t pid; |
| } gSystemSocketTable[5]; |
| |
| static void |
| system_socket_table_close_alarm_(int sig) |
| { |
| static const char message[] = "\nclose_super_socket: Unable to terminate child in a timely manner, watchdog fired\n"; |
| |
| // Can't use syslog here, write to stderr instead. |
| (void)write(STDERR_FILENO, message, sizeof(message) - 1); |
| |
| _exit(EXIT_FAILURE); |
| } |
| |
| static void |
| system_socket_table_atexit_(void) |
| { |
| int i; |
| |
| for (i = 0; i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0]); i++) { |
| if (gSystemSocketTable[i].pid != 0) { |
| kill(gSystemSocketTable[i].pid, SIGTERM); |
| } |
| } |
| } |
| |
| static void |
| system_socket_table_add_(int fd, pid_t pid) |
| { |
| static bool did_init = false; |
| int i; |
| |
| if (!did_init) { |
| atexit(&system_socket_table_atexit_); |
| did_init = true; |
| } |
| |
| for (i = 0; i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0]); i++) { |
| if (gSystemSocketTable[i].pid == 0) { |
| break; |
| } |
| } |
| |
| // If we have more than 5 of these types of sockets |
| // open, then there is likely a serious problem going |
| // on that we should immediately deal with. |
| assert(i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0])); |
| |
| if (i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0])) { |
| gSystemSocketTable[i].fd = fd; |
| gSystemSocketTable[i].pid = pid; |
| } |
| } |
| |
| int |
| close_super_socket(int fd) |
| { |
| int i; |
| int ret; |
| for (i = 0; i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0]); i++) { |
| if ( gSystemSocketTable[i].fd == fd |
| && gSystemSocketTable[i].pid != 0 |
| ) { |
| break; |
| } |
| } |
| |
| ret = close(fd); |
| |
| if (i < sizeof(gSystemSocketTable)/sizeof(gSystemSocketTable[0])) { |
| pid_t pid = gSystemSocketTable[i].pid; |
| int x = 0, j = 0; |
| |
| kill(gSystemSocketTable[i].pid, SIGHUP); |
| |
| for (j = 0; j < 100; ++j) { |
| pid = waitpid(gSystemSocketTable[i].pid, &x, WNOHANG); |
| if (pid > 0) { |
| break; |
| } |
| usleep(100000); |
| } |
| |
| if (pid <= 0) { |
| // Five second watchdog. |
| void (*prev_alarm_handler)(int) = signal(SIGALRM, &system_socket_table_close_alarm_); |
| unsigned int prev_alarm_remaining = alarm(5); |
| |
| syslog(LOG_WARNING, "close_super_socket: PID %d didn't respond to SIGHUP, trying SIGTERM", gSystemSocketTable[i].pid); |
| |
| kill(gSystemSocketTable[i].pid, SIGTERM); |
| |
| do { |
| errno = 0; |
| pid = waitpid(gSystemSocketTable[i].pid, &x, 0); |
| } while ((pid == -1) && (errno == EINTR)); |
| |
| // Disarm watchdog. |
| alarm(prev_alarm_remaining); |
| signal(SIGALRM, prev_alarm_handler); |
| } |
| |
| if (pid == -1) { |
| perror(strerror(errno)); |
| } |
| |
| |
| gSystemSocketTable[i].pid = 0; |
| gSystemSocketTable[i].fd = -1; |
| } |
| |
| return ret; |
| } |
| |
| int |
| checkpoll(int fd, int poll_flags) |
| { |
| if (fd >= 0) { |
| struct pollfd pollfd = { fd, (short)poll_flags, 0 }; |
| IGNORE_RETURN_VALUE( poll(&pollfd, 1, 0) ); |
| return pollfd.revents; |
| } |
| |
| return -1; |
| } |
| |
| int |
| fd_has_error(int fd) |
| { |
| const int flags = (POLLPRI|POLLRDBAND|POLLERR|POLLHUP|POLLNVAL); |
| struct pollfd pollfd = { fd, flags, 0 }; |
| int count = poll(&pollfd, 1, 0); |
| |
| if (count<0) { |
| return -errno; |
| } else if (count > 0) { |
| if (pollfd.revents&POLLHUP) { |
| return -EPIPE; |
| } |
| |
| if (pollfd.revents&(POLLRDBAND|POLLPRI)) { |
| return -EPIPE; |
| } |
| |
| if (pollfd.revents&POLLNVAL) { |
| return -EINVAL; |
| } |
| |
| if (pollfd.revents&POLLERR) { |
| return -EIO; |
| } |
| } |
| return 0; |
| } |
| |
| int gSocketWrapperBaud = 115200; |
| |
| static bool |
| socket_name_is_system_command(const char* socket_name) |
| { |
| return strhasprefix(socket_name, SOCKET_SYSTEM_COMMAND_PREFIX) |
| || strhasprefix(socket_name, SOCKET_SYSTEM_FORKPTY_COMMAND_PREFIX) |
| || strhasprefix(socket_name, SOCKET_SYSTEM_SOCKETPAIR_COMMAND_PREFIX) |
| ; |
| } |
| |
| static bool |
| socket_name_is_port(const char* socket_name) |
| { |
| // It's a port if the string is just a number. |
| while (*socket_name) { |
| if (!isdigit(*socket_name++)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool |
| socket_name_is_inet(const char* socket_name) |
| { |
| if (*socket_name == 0) { |
| return false; |
| } |
| // It's an inet address if it Contains no slashes or starts with a '['. |
| if (*socket_name == '[') { |
| return true; |
| } |
| do { |
| if (*socket_name == '/') { |
| return false; |
| } |
| } while (*++socket_name); |
| return !socket_name_is_port(socket_name) && !socket_name_is_system_command(socket_name); |
| } |
| |
| bool |
| socket_name_is_device(const char* socket_name) |
| { |
| return !socket_name_is_system_command(socket_name) && !socket_name_is_inet(socket_name); |
| } |
| |
| int |
| lookup_sockaddr_from_host_and_port( |
| struct sockaddr_in6* outaddr, const char* host, const char* port |
| ) |
| { |
| int ret = 0; |
| struct addrinfo hint = { |
| }; |
| |
| hint.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_ALL; |
| hint.ai_family = AF_INET6; |
| |
| struct addrinfo *results = NULL; |
| struct addrinfo *iter = NULL; |
| |
| if (!port) { |
| port = "4951"; |
| } |
| |
| if (!host) { |
| host = "::1"; |
| } |
| |
| syslog(LOG_INFO, "Looking up [%s]:%s", host, port); |
| |
| if (isdigit(port[0]) && strcmp(host, "::1") == 0) { |
| // Special case to avoid calling getaddrinfo() when |
| // we really don't need to. |
| memset(outaddr, 0, sizeof(struct sockaddr_in6)); |
| outaddr->sin6_family = AF_INET6; |
| outaddr->sin6_addr.s6_addr[15] = 1; |
| outaddr->sin6_port = htons(atoi(port)); |
| } else if (isdigit(port[0]) && inet_addr(host) != 0) { |
| in_addr_t v4addr = inet_addr(host); |
| memset(outaddr, 0, sizeof(struct sockaddr_in6)); |
| outaddr->sin6_family = AF_INET6; |
| outaddr->sin6_addr.s6_addr[10] = 0xFF; |
| outaddr->sin6_addr.s6_addr[11] = 0xFF; |
| outaddr->sin6_port = htons(atoi(port)); |
| memcpy(outaddr->sin6_addr.s6_addr + 12, &v4addr, sizeof(v4addr)); |
| outaddr->sin6_port = htons(atoi(port)); |
| } else { |
| int error = getaddrinfo(host, port, &hint, &results); |
| |
| require_action_string( |
| !error, |
| bail, |
| ret = -1, |
| gai_strerror(error) |
| ); |
| |
| for (iter = results; |
| iter && (iter->ai_family != AF_INET6); |
| iter = iter->ai_next) ; |
| |
| require_action(NULL != iter, bail, ret = -1); |
| |
| memcpy(outaddr, iter->ai_addr, iter->ai_addrlen); |
| } |
| |
| bail: |
| if (results) |
| freeaddrinfo(results); |
| |
| return ret; |
| } |
| |
| #if HAVE_FORKPTY |
| |
| // Declare function to avoid compile warning: implicit declaration of function ‘posix_openpt’ |
| extern int posix_openpt(int flags); |
| |
| static int |
| diagnose_forkpty_problem() |
| { |
| int ret = -1; |
| // COM-S-7530: Do some more diagnostics on the situation, because |
| // sort of failure is weird. We step through some of the same sorts of |
| // things that forkpty would do so that we can see where we are failing. |
| int pty_master_fd = -1; |
| int pty_slave_fd = -1; |
| do { |
| if( access( "/dev/ptmx", F_OK ) < 0 ) { |
| syslog(LOG_WARNING, "Call to access(\"/dev/ptmx\",F_OK) failed: %s (%d)", strerror(errno), errno); |
| perror("access(\"/dev/ptmx\",F_OK)"); |
| } |
| |
| if( access( "/dev/ptmx", R_OK|W_OK ) < 0 ) { |
| syslog(LOG_WARNING, "Call to access(\"/dev/ptmx\",R_OK|W_OK) failed: %s (%d)", strerror(errno), errno); |
| perror("access(\"/dev/ptmx\",R_OK|W_OK)"); |
| } |
| |
| pty_master_fd = posix_openpt(O_NOCTTY | O_RDWR); |
| |
| if (pty_master_fd < 0) { |
| syslog(LOG_CRIT, "Call to posix_openpt() failed: %s (%d)", strerror(errno), errno); |
| perror("posix_openpt(O_NOCTTY | O_RDWR)"); |
| break; |
| } |
| |
| if (grantpt(pty_master_fd) < 0) { |
| syslog(LOG_CRIT, "Call to grantpt() failed: %s (%d)", strerror(errno), errno); |
| perror("grantpt"); |
| } |
| |
| if (unlockpt(pty_master_fd) < 0) { |
| syslog(LOG_CRIT, "Call to unlockpt() failed: %s (%d)", strerror(errno), errno); |
| perror("unlockpt"); |
| } |
| |
| #if HAVE_PTSNAME |
| if (NULL == ptsname(pty_master_fd)) { |
| syslog(LOG_CRIT, "Call to ptsname() failed: %s (%d)", strerror(errno), errno); |
| perror("ptsname"); |
| break; |
| } |
| |
| pty_slave_fd = open(ptsname(pty_master_fd), O_RDWR | O_NOCTTY); |
| |
| if (pty_slave_fd < 0) { |
| syslog(LOG_CRIT, "Call to open(\"%s\",O_RDWR|O_NOCTTY) failed: %s (%d)", ptsname(pty_master_fd), strerror(errno), errno); |
| perror("open(ptsname(pty_master_fd),O_RDWR|O_NOCTTY)"); |
| break; |
| } |
| #endif |
| |
| ret = 0; |
| } while(0); |
| close(pty_master_fd); |
| close(pty_slave_fd); |
| return ret; |
| } |
| |
| static int |
| open_system_socket_forkpty(const char* command) |
| { |
| int ret_fd = -1; |
| int stderr_copy_fd; |
| pid_t pid; |
| struct termios tios = { .c_cflag = CS8|HUPCL|CREAD|CLOCAL }; |
| |
| cfmakeraw(&tios); |
| |
| // Duplicate stderr so that we can hook it back up in the forked process. |
| stderr_copy_fd = dup(STDERR_FILENO); |
| if (stderr_copy_fd < 0) { |
| syslog(LOG_ERR, "Call to dup() failed: %s (%d)", strerror(errno), errno); |
| goto cleanup_and_fail; |
| } |
| |
| pid = forkpty(&ret_fd, NULL, &tios, NULL); |
| if (pid < 0) { |
| syslog(LOG_ERR, "Call to forkpty() failed: %s (%d)", strerror(errno), errno); |
| |
| if (0 == diagnose_forkpty_problem()) { |
| syslog(LOG_CRIT, "FORKPTY() FAILED BUT NOTHING WAS OBVIOUSLY WRONG!!!"); |
| } |
| |
| goto cleanup_and_fail; |
| } |
| |
| // Check to see if we are the forked process or not. |
| if (0 == pid) { |
| // We are the forked process. |
| const int dtablesize = getdtablesize(); |
| int i; |
| |
| #if defined(_LINUX_PRCTL_H) |
| prctl(PR_SET_PDEATHSIG, SIGHUP); |
| #endif |
| |
| // Re-instate our original stderr. |
| dup2(stderr_copy_fd, STDERR_FILENO); |
| |
| syslog(LOG_DEBUG, "Forked!"); |
| |
| // Re-instate our original stderr (clobbered by login_tty) |
| dup2(stderr_copy_fd, STDERR_FILENO); |
| |
| // Set the shell environment variable if it isn't set already. |
| setenv("SHELL", SOCKET_UTILS_DEFAULT_SHELL, 0); |
| |
| // Close all file descriptors larger than STDERR_FILENO. |
| for (i = (STDERR_FILENO + 1); i < dtablesize; i++) { |
| close(i); |
| } |
| |
| syslog(LOG_NOTICE,"About to exec \"%s\"",command); |
| |
| execl(getenv("SHELL"),getenv("SHELL"),"-c",command,NULL); |
| |
| syslog(LOG_ERR,"Failed for fork and exec of \"%s\": %s (%d)", command, strerror(errno), errno); |
| |
| _exit(errno); |
| } |
| |
| // Clean up our copy of stderr |
| close(stderr_copy_fd); |
| |
| #if HAVE_PTSNAME |
| // See http://stackoverflow.com/questions/3486491/ |
| close(open(ptsname(ret_fd), O_RDWR | O_NOCTTY)); |
| #endif |
| |
| system_socket_table_add_(ret_fd, pid); |
| |
| return ret_fd; |
| |
| cleanup_and_fail: |
| { |
| int prevErrno = errno; |
| |
| close(ret_fd); |
| close(stderr_copy_fd); |
| |
| errno = prevErrno; |
| } |
| return -1; |
| } |
| #endif // HAVE_FORKPTY |
| |
| #ifdef PF_UNIX |
| |
| int |
| fork_unixdomain_socket(int* fd_pointer) |
| { |
| int fd[2] = { -1, -1 }; |
| int i; |
| pid_t pid = -1; |
| |
| if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0) { |
| syslog(LOG_ERR, "Call to socketpair() failed: %s (%d)", strerror(errno), errno); |
| goto bail; |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| syslog(LOG_ERR, "Call to fork() failed: %s (%d)", strerror(errno), errno); |
| goto bail; |
| } |
| |
| // Check to see if we are the forked process or not. |
| if (0 == pid) { |
| const int dtablesize = getdtablesize(); |
| // We are the forked process. |
| |
| #if defined(_LINUX_PRCTL_H) |
| prctl(PR_SET_PDEATHSIG, SIGHUP); |
| #endif |
| |
| close(fd[0]); |
| |
| dup2(fd[1], STDIN_FILENO); |
| dup2(fd[1], STDOUT_FILENO); |
| |
| syslog(LOG_DEBUG, "Forked!"); |
| |
| // Close all file descriptors larger than STDERR_FILENO. |
| for (i = (STDERR_FILENO + 1); i < dtablesize; i++) { |
| close(i); |
| } |
| |
| *fd_pointer = STDIN_FILENO; |
| } else { |
| close(fd[1]); |
| *fd_pointer = fd[0]; |
| } |
| |
| fd[0] = -1; |
| fd[1] = -1; |
| |
| bail: |
| |
| { |
| int prevErrno = errno; |
| if (fd[0] >= 0) { |
| close(fd[0]); |
| } |
| if (fd[1] >= 0) { |
| close(fd[1]); |
| } |
| errno = prevErrno; |
| } |
| |
| return pid; |
| } |
| |
| static int |
| open_system_socket_unix_domain(const char* command) |
| { |
| int fd = -1; |
| pid_t pid = -1; |
| |
| pid = fork_unixdomain_socket(&fd); |
| |
| if (pid < 0) { |
| syslog(LOG_ERR, "Call to fork() failed: %s (%d)", strerror(errno), errno); |
| goto cleanup_and_fail; |
| } |
| |
| // Check to see if we are the forked process or not. |
| if (0 == pid) { |
| // Set the shell environment variable if it isn't set already. |
| setenv("SHELL","/bin/sh",0); |
| |
| syslog(LOG_NOTICE, "About to exec \"%s\"", command); |
| |
| execl(getenv("SHELL"), getenv("SHELL"),"-c", command, NULL); |
| |
| syslog(LOG_ERR, "Failed for fork and exec of \"%s\": %s (%d)", command, strerror(errno), errno); |
| |
| _exit(EXIT_FAILURE); |
| } |
| |
| system_socket_table_add_(fd, pid); |
| |
| return fd; |
| |
| cleanup_and_fail: |
| |
| if (fd >= 0) { |
| int prevErrno = errno; |
| |
| close(fd); |
| |
| errno = prevErrno; |
| } |
| |
| return -1; |
| } |
| #endif // PF_UNIX |
| |
| static int |
| open_system_socket(const char* command) |
| { |
| int ret_fd = -1; |
| |
| #if HAVE_FORKPTY |
| ret_fd = open_system_socket_forkpty(command); |
| #endif |
| |
| #if defined(PF_UNIX) |
| // Fall back to unix-domain socket-based mechanism: |
| if (ret_fd < 0) { |
| ret_fd = open_system_socket_unix_domain(command); |
| } |
| #endif |
| |
| #if !HAVE_FORKPTY && !defined(PF_UNIX) |
| assert_printf("%s","Process pipe sockets are not supported in current configuration"); |
| errno = ENOTSUP; |
| #endif |
| |
| return ret_fd; |
| } |
| |
| int |
| get_super_socket_type_from_path(const char* socket_name) |
| { |
| int socket_type = SUPER_SOCKET_TYPE_UNKNOWN; |
| |
| if (strcasehasprefix(socket_name, SOCKET_SYSTEM_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_SYSTEM; |
| } else if (strcasehasprefix(socket_name, SOCKET_SYSTEM_FORKPTY_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_SYSTEM_FORKPTY; |
| } else if (strcasehasprefix(socket_name, SOCKET_SYSTEM_SOCKETPAIR_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_SYSTEM_SOCKETPAIR; |
| } else if (strcasehasprefix(socket_name, SOCKET_FD_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_FD; |
| } else if (strcasehasprefix(socket_name, SOCKET_FILE_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_DEVICE; |
| } else if (strcasehasprefix(socket_name, SOCKET_SERIAL_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_DEVICE; |
| } else if (strcasehasprefix(socket_name, SOCKET_TCP_COMMAND_PREFIX)) { |
| socket_type = SUPER_SOCKET_TYPE_TCP; |
| } else if (socket_name_is_inet(socket_name) || socket_name_is_port(socket_name)) { |
| socket_type = SUPER_SOCKET_TYPE_TCP; |
| } else if (socket_name_is_device(socket_name)) { |
| socket_type = SUPER_SOCKET_TYPE_DEVICE; |
| } |
| return socket_type; |
| } |
| |
| int |
| baud_rate_to_termios_constant(int baud) |
| { |
| int ret; |
| // Standard "TERMIOS" uses constants to get and set |
| // the baud rate, but we use the actual baud rate. |
| // This function converts from the actual baud rate |
| // to the constant supported by this platform. It |
| // returns zero if the baud rate is unsupported. |
| switch(baud) { |
| case 9600: |
| ret = B9600; |
| break; |
| case 19200: |
| ret = B19200; |
| break; |
| case 38400: |
| ret = B38400; |
| break; |
| case 57600: |
| ret = B57600; |
| break; |
| case 115200: |
| ret = B115200; |
| break; |
| #ifdef B230400 |
| case 230400: |
| ret = B230400; |
| break; |
| #endif |
| #ifdef B460800 |
| case 460800: |
| ret = B460800; |
| break; |
| #endif |
| #ifdef B500000 |
| case 500000: |
| ret = B500000; |
| break; |
| #endif |
| #ifdef B576000 |
| case 576000: |
| ret = B576000; |
| break; |
| #endif |
| #ifdef B921600 |
| case 921600: |
| ret = B921600; |
| break; |
| #endif |
| #ifdef B1000000 |
| case 1000000: |
| ret = B1000000; |
| break; |
| #endif |
| default: |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| int |
| open_super_socket(const char* socket_name) |
| { |
| int fd = -1; |
| char* host = NULL; // Needs to be freed if set |
| const char* port = NULL; |
| char* options = strchr(socket_name, ','); |
| char* filename = strchr(socket_name, ':'); |
| bool socket_name_is_well_formed = true; // True if socket has type name and options |
| int socket_type = get_super_socket_type_from_path(socket_name); |
| |
| // Move past the colon, if there was one. |
| if (NULL != filename && socket_name[0] != '[') { |
| filename++; |
| } else { |
| filename = ""; |
| } |
| |
| if (socket_type == SUPER_SOCKET_TYPE_DEVICE) { |
| socket_name_is_well_formed = |
| (strcasehasprefix(socket_name, SOCKET_SERIAL_COMMAND_PREFIX)) |
| || (strcasehasprefix(socket_name, SOCKET_FILE_COMMAND_PREFIX)); |
| } |
| |
| if (socket_type == SUPER_SOCKET_TYPE_TCP) { |
| socket_name_is_well_formed = |
| (strcasehasprefix(socket_name, SOCKET_TCP_COMMAND_PREFIX)); |
| if (!socket_name_is_well_formed) { |
| filename = (char*)socket_name; |
| } |
| } |
| |
| if (!socket_name_is_well_formed) { |
| filename = (char*)socket_name; |
| if (socket_type == SUPER_SOCKET_TYPE_DEVICE) { |
| options = ",default"; |
| } else { |
| options = NULL; |
| } |
| } |
| |
| filename = strdup(filename); |
| |
| if (NULL == filename) { |
| perror("strdup"); |
| syslog(LOG_ERR, "strdup failed. \"%s\" (%d)", strerror(errno), errno); |
| goto bail; |
| } |
| |
| // Make sure we zero terminate the file name before |
| // any options. (this is why we needed to strdup) |
| if (socket_name_is_well_formed && (NULL != options)) { |
| const char* ptr = strchr(socket_name, ':'); |
| if (NULL == ptr) { |
| ptr = socket_name; |
| } else { |
| ptr++; |
| } |
| filename[options-ptr] = 0; |
| } |
| |
| if (SUPER_SOCKET_TYPE_SYSTEM == socket_type) { |
| fd = open_system_socket(filename); |
| #if HAVE_FORKPTY |
| } else if (SUPER_SOCKET_TYPE_SYSTEM_FORKPTY == socket_type) { |
| fd = open_system_socket_forkpty(filename); |
| #endif |
| } else if (SUPER_SOCKET_TYPE_SYSTEM_SOCKETPAIR == socket_type) { |
| fd = open_system_socket_unix_domain(filename); |
| } else if (SUPER_SOCKET_TYPE_FD == socket_type) { |
| errno = 0; |
| fd = strtol(filename, NULL, 0); |
| if (fd == 0 && errno != 0) { |
| perror("strtol"); |
| syslog(LOG_ERR, "strtol failed. \"%s\" (%d)", strerror(errno), errno); |
| fd = -1; |
| goto bail; |
| } |
| #if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| switch(fd) { |
| case STDERR_FILENO: |
| case STDIN_FILENO: |
| case STDOUT_FILENO: |
| // Don't mess up the fuzzer's standard input/output streams. |
| fd = -1; |
| goto bail; |
| default: |
| break; |
| } |
| #endif |
| fd = dup(fd); |
| } else if (SUPER_SOCKET_TYPE_DEVICE == socket_type) { |
| #if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| // Don't let the fuzzer go crazy on files. |
| free(filename); |
| filename = strdup("/dev/null"); |
| #endif |
| fd = open(filename, O_RDWR | O_NOCTTY | O_NONBLOCK); |
| |
| if (fd >= 0) { |
| fcntl(fd, F_SETFL, O_NONBLOCK); |
| tcflush(fd, TCIOFLUSH); |
| } |
| } else if (SUPER_SOCKET_TYPE_TCP == socket_type) { |
| struct sockaddr_in6 addr; |
| |
| if (socket_name_is_port(socket_name)) { |
| port = socket_name; |
| } else { |
| ssize_t i; |
| if (filename[0] == '[') { |
| host = strdup(filename + 1); |
| } else { |
| host = strdup(filename); |
| } |
| for (i = strlen(host) - 1; i >= 0; --i) { |
| if (host[i] == ':' && port == NULL) { |
| host[i] = 0; |
| port = host + i + 1; |
| continue; |
| } |
| if (host[i] == ']') { |
| host[i] = 0; |
| break; |
| } |
| if (!isdigit(host[i])) |
| break; |
| } |
| } |
| |
| if (0 != lookup_sockaddr_from_host_and_port(&addr, host, port)) { |
| syslog(LOG_ERR, "Unable to lookup \"%s\"", socket_name); |
| goto bail; |
| } |
| |
| fd = socket(AF_INET6, SOCK_STREAM, 0); |
| |
| if (fd < 0) { |
| syslog(LOG_ERR, "Unable to open socket. \"%s\" (%d)", strerror(errno), errno); |
| goto bail; |
| } |
| |
| if (0 != connect(fd, (struct sockaddr*)&addr, sizeof(addr))) { |
| syslog(LOG_ERR, "Call to connect() failed. \"%s\" (%d)", strerror(errno), errno); |
| close(fd); |
| fd = -1; |
| goto bail; |
| } |
| } else { |
| syslog(LOG_ERR, "I don't know how to open \"%s\" (socket type %d)", socket_name, (int)socket_type); |
| } |
| |
| if (fd < 0) { |
| syslog(LOG_ERR, "Unable to open socket. \"%s\" (%d) (filename = %s, type = %d)", strerror(errno), errno, filename, (int)socket_type); |
| goto bail; |
| } |
| |
| // Configure the socket. |
| { |
| int flags = fcntl(fd, F_GETFL); |
| int i; |
| struct termios tios; |
| fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
| |
| #ifdef SO_NOSIGPIPE |
| int set = 0; |
| setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); |
| #endif |
| |
| #define FETCH_TERMIOS() do { if(0 != tcgetattr(fd, &tios)) { \ |
| syslog(LOG_DEBUG, "tcgetattr() failed. \"%s\" (%d)", strerror(errno), errno); \ |
| } } while(0) |
| |
| #define COMMIT_TERMIOS() do { if(0 != tcsetattr(fd, TCSANOW, &tios)) { \ |
| syslog(LOG_DEBUG, "tcsetattr() failed. \"%s\" (%d)", strerror(errno), errno); \ |
| } } while(0) |
| |
| // Parse the options, if any |
| for (; NULL != options; options = strchr(options+1, ',')) { |
| if (strcasehasprefix(options, ",b") && isdigit(options[2])) { |
| // Change Baud rate |
| const int baud = (int)strtol(options+2,NULL,10); |
| const int baud_opt = baud_rate_to_termios_constant(baud); |
| if(baud_opt <= 0) { |
| syslog(LOG_INFO, "Unknown baud rate %d - using default", baud); |
| } else { |
| syslog(LOG_DEBUG, "Setting baud rate to %d", baud); |
| } |
| FETCH_TERMIOS(); |
| cfsetspeed(&tios, baud_opt); |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",default")) { |
| FETCH_TERMIOS(); |
| for (i=0; i < NCCS; i++) { |
| tios.c_cc[i] = _POSIX_VDISABLE; |
| } |
| |
| tios.c_cflag = (CS8|HUPCL|CREAD|CLOCAL); |
| tios.c_iflag = 0; |
| tios.c_oflag = 0; |
| tios.c_lflag = 0; |
| |
| const int baud_opt = baud_rate_to_termios_constant(gSocketWrapperBaud); |
| if(baud_opt <= 0) { |
| syslog(LOG_INFO, "Unknown baud rate %d - using default", gSocketWrapperBaud); |
| } else { |
| syslog(LOG_DEBUG, "Setting baud rate to %d", gSocketWrapperBaud); |
| } |
| |
| cfmakeraw(&tios); |
| cfsetspeed(&tios, baud_opt); |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",raw")) { |
| // Raw mode |
| FETCH_TERMIOS(); |
| cfmakeraw(&tios); |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",clocal=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| if (options[1] == '1') { |
| tios.c_cflag |= CLOCAL; |
| } else if (options[1] == '0') { |
| tios.c_cflag &= ~CLOCAL; |
| } |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",ixoff=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| if (options[1] == '1') { |
| tios.c_iflag |= IXOFF; |
| } else if (options[1] == '0') { |
| tios.c_iflag &= ~IXOFF; |
| } |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",ixon=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| if (options[1] == '1') { |
| tios.c_iflag |= IXON; |
| } else if (options[1] == '0') { |
| tios.c_iflag &= ~IXON; |
| } |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",ixany=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| if (options[1] == '1') { |
| tios.c_iflag |= IXANY; |
| } else if (options[1] == '0') { |
| tios.c_iflag &= ~IXANY; |
| } |
| COMMIT_TERMIOS(); |
| } else if (strcasehasprefix(options, ",crtscts=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| // Hardware flow control |
| if (options[1] == '1') { |
| syslog(LOG_DEBUG, "Using hardware flow control for serial socket."); |
| tios.c_cflag |= CRTSCTS; |
| } else if (options[1] == '0') { |
| tios.c_cflag &= ~CRTSCTS; |
| } |
| COMMIT_TERMIOS(); |
| |
| #ifdef CCTS_OFLOW |
| } else if (strcasehasprefix(options, ",ccts_oflow=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| // Hardware output flow control |
| if (options[1] == '1') { |
| syslog(LOG_DEBUG, "Using hardware output flow control for serial socket."); |
| tios.c_cflag |= CCTS_OFLOW; |
| } else if (options[1] == '0') { |
| tios.c_cflag &= ~CCTS_OFLOW; |
| } |
| COMMIT_TERMIOS(); |
| #endif |
| #ifdef CRTS_IFLOW |
| } else if (strcasehasprefix(options, ",crts_iflow=")) { |
| FETCH_TERMIOS(); |
| options = strchr(options,'='); |
| // Hardware input flow control |
| if (options[1] == '1') { |
| syslog(LOG_DEBUG, "Using hardware input flow control for serial socket."); |
| tios.c_cflag |= CRTS_IFLOW; |
| } else if (options[1] == '0') { |
| tios.c_cflag &= ~CRTS_IFLOW; |
| } |
| COMMIT_TERMIOS(); |
| #endif |
| } else { |
| syslog(LOG_ERR, "Unknown option (%s)", options); |
| } |
| } |
| } |
| bail: |
| free(filename); |
| free(host); |
| return fd; |
| } |