/* $Cambridge: hermes/src/prayer/accountd/os_linux.c,v 1.4 2004/10/01 13:25:51 dpc22 Exp $ */ /************************************************ * Prayer - a Webmail Interface * ************************************************/ /* Copyright (c) University of Cambridge 2000 - 2002 */ /* See the file NOTICE for conditions of use and distribution. */ #include "accountd.h" #include #include #include #include #include #include #include #include #include #include #ifdef USE_SSL #include #endif #ifdef USE_BSD_PTY /* Use BSD terminal stuff: simpler */ #include /* for openpty and forkpty */ #include /* for login_tty */ #endif /* ====================================================================== */ BOOL os_socketpair(int *sockfd) { int rc; do { rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd); } while ((rc < 0) && (errno == EINTR)); return ((rc == 0) ? T : NIL); } /* ====================================================================== */ int os_connect_unix_socket(char *name) { struct sockaddr_un serv_addr; int sockfd, servlen; /* Open the socket */ if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return (-1); bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, name); servlen = sizeof(serv_addr); if (connect(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0) { close(sockfd); return (-1); } return (sockfd); } int os_connect_inet_socket(char *host, unsigned long port) { struct hostent *hostent; struct sockaddr_in serv_addr; int sockfd; /* Open the socket */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return (-1); /* Set up the socket */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); if ((hostent = gethostbyname(host)) == NIL) { close(sockfd); return (-1); } bcopy(hostent->h_addr, (char *) &serv_addr.sin_addr, hostent->h_length); if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { close(sockfd); return (-1); } return (sockfd); } /* ====================================================================== */ int os_bind_unix_socket(char *name) { struct sockaddr_un serv_addr; int sockfd, servlen; int i; /* Generate well known connect address for frontend servers */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, name); servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family); unlink(serv_addr.sun_path); /* Open the socket */ if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { log_panic("[os_bind_unix_socket()] socket() failed, %s", strerror(errno)); return (-1); } /* Set socket reuseaddr, otherwise bind will fail after fast stop/start */ i = 1; if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &i, sizeof(int))) { log_panic(("[os_bind_unix_socket()] setsockopt() failed: " "couldn't set SO_REUSEADDR on init port, %s"), strerror(errno)); close(sockfd); return (-1); } /* bind() as UNIX domain socket */ if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { log_panic("[os_bind_unix_socket()] bind() failed: %s", strerror(errno)); close(sockfd); return (-1); } /* Requests should queue on sockfd until we are ready to serve them */ if (listen(sockfd, 10) < 0) { log_panic("[os_bind_unix_socket()] listen() failed, %s", strerror(errno)); close(sockfd); return (-1); } return (sockfd); } /* ====================================================================== */ int os_bind_inet_socket(unsigned long port) { int i, sockfd; struct sockaddr_in serv_addr; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { log_panic("[os_bind_unix_socket()] socket() failed, %s", strerror(errno)); return (-1); } /* Set Reuseaddr on this socket */ i = 1; if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &i, sizeof(int))) { log_panic(("[os_bind_inet_socket()] setsockopt() failed: " "couldn't set SO_REUSEADDR on init port, %s"), strerror(errno)); close(sockfd); return (-1); } /* bind() as Internet domain socket */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { log_panic("[os_bind_inet_socket()] bind() failed: %s", strerror(errno)); close(sockfd); return (-1); } if (listen(sockfd, 10) < 0) { log_panic("[os_bind_inet_socket()] listen() failed, %s", strerror(errno)); close(sockfd); return (-1); } return (sockfd); } /* ====================================================================== */ int os_accept_unix(int sockfd) { struct sockaddr_un addr; socklen_t len = (socklen_t) sizeof(struct sockaddr_un); int newsockfd; do { newsockfd = accept(sockfd, (struct sockaddr *) &addr, &len); } while ((newsockfd < 0) && (errno == EINTR)); if (newsockfd < 0) { log_panic("[os_accept_unix()] accept() failed: %s", strerror(errno)); close(newsockfd); return (-1); } /* Set close on exec so subprocesses can't interfere */ if (fcntl(newsockfd, F_SETFD, FD_CLOEXEC) < 0) { log_panic("[os_accept_unix()] fcntl() (close-on-exec) failed: %s", strerror(errno)); close(newsockfd); return (-1); } return (newsockfd); } int os_accept_inet(int sockfd, struct ipaddr *ipaddr) { struct sockaddr_in addr; socklen_t len = (socklen_t) sizeof(struct sockaddr_in); int newsockfd; do { newsockfd = accept(sockfd, (struct sockaddr *) &addr, &len); } while ((newsockfd < 0) && (errno == EINTR)); if (newsockfd < 0) { log_panic("[os_accept_inet()] accept() failed: %s", strerror(errno)); close(newsockfd); return (-1); } if (ipaddr) ipaddr_set(ipaddr, 4, (unsigned char *) &addr.sin_addr); /* Set close on exec so subprocesses can't interfere */ if (fcntl(newsockfd, F_SETFD, FD_CLOEXEC) < 0) { log_panic("[os_accept_inet()] fcntl() (close-on-exec) failed: %s", strerror(errno)); close(newsockfd); return (-1); } return (newsockfd); } /* ====================================================================== */ /* Run child outside PTY */ BOOL os_run(char *cmdline, int *fdp, int *childpidp) { int fd[2]; int pid; if (!os_socketpair(fd)) return(NIL); if ((pid = fork()) < 0) return (NIL); if (pid == 0) { dup2(fd[1], 0); dup2(fd[1], 1); dup2(fd[1], 2); close(fd[0]); close(fd[1]); process_exec(cmdline); /* NOTREACHED */ exit(1); } /* Parent process */ close(fd[1]); *fdp = fd[0]; *childpidp = pid; return (T); } #ifdef USE_BSD_PTY /* BSD Psuedo-Terminal support */ BOOL os_run_pty(char *cmdline, int *fdp, int *childpidp) { int fd; int pid; if ((pid = forkpty(&fd, NIL, NIL, NIL)) < 0) return (NIL); if (pid == 0) { process_exec(cmdline); /* NOTREACHED */ exit(1); } /* Parent process */ *fdp = fd; *childpidp = pid; return (T); } #else /* System V Pseudo-Terminal support */ /* PTY handling code inferrred from OpenSSH and Solaris 8 in.telnetd code * Conclusion: PTY stuff is awful! */ BOOL os_run_pty(char *cmdline, int *fdp, int *childpidp) { extern int grantpt(); /* Provide prototypes for pty fns */ extern int unlockpt(); /* Linux hdrs don't define these properly */ extern char *ptsname(); char *slavename; int masterfd, slavefd; int pid; *fdp = (-1); *childpidp = 0; masterfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); if (masterfd < 0) { close(masterfd); log_misc("/dev/ptmx: %s", strerror(errno)); return (NIL); } if (grantpt(masterfd) < 0) { close(masterfd); log_misc("grantpt: %s", strerror(errno)); return (NIL); } if (unlockpt(masterfd) < 0) { close(masterfd); log_misc("unlockpt: %s", strerror(errno)); return (NIL); } if ((pid = fork()) < 0) log_fatal("fork() failed"); if (pid == 0) { /* Child process */ slavename = ptsname(masterfd); if (slavename == NIL) { close(masterfd); log_misc("Slave pty side name could not be obtained."); return (NIL); } /* Put child process in its own session group */ /* Removes controlling terminal */ (void) setsid(); /* Open slave side of pty and dup to become stdin, stdout, stderr */ /* slavefd becomes new controlling terminal for child process */ if ((slavefd = open(slavename, O_RDWR)) < 0) log_fatal("Couldn't open slave"); close(masterfd); if (isastream(slavefd)) { if (ioctl(slavefd, I_PUSH, "ptem") < 0) log_fatal("ioctl I_PUSH ptem: %s", strerror(errno)); if (ioctl(slavefd, I_PUSH, "ldterm") < 0) log_fatal("ioctl I_PUSH ldterm: %s", strerror(errno)); if (ioctl(slavefd, I_PUSH, "ttcompat") < 0) log_fatal("ioctl I_PUSH ttcompat: %s", strerror(errno)); } #if 0 /* XXX Testing terminal control: something funny going on here... */ { struct termios attr; if (tcgetattr(slavefd, &attr) != 0) log_fatal("tcgetattr() failed"); attr.c_oflag &= ~(OPOST | IXOFF | IXON); attr.c_lflag &= ~(ISIG | ECHO | XCASE); /* ICANON? */ attr.c_lflag |= ECHONL; attr.c_cflag &= ~(CSIZE | PARENB); attr.c_cflag |= CS8; attr.c_cc[VMIN] = 1; attr.c_cc[VTIME] = 1; if (tcsetattr(slavefd, TCSANOW, &attr) != 0) log_fatal("tcgetattr() failed"); } #endif dup2(slavefd, 0); dup2(slavefd, 1); dup2(slavefd, 2); close(slavefd); process_exec(cmdline); /* NOTREACHED */ exit(1); } *fdp = masterfd; *childpidp = pid; return (T); } #endif /* ====================================================================== */ /* Trivial SIG_CLD handler to prevent zombies from hanging around */ void os_child_reaper() { int status; pid_t child; do { child = waitpid(0, &status, WNOHANG); } while (child > 0); } pid_t os_waitpid_nohang() { int status; return (waitpid(0, &status, WNOHANG)); } BOOL os_signal_child_init(void (*fn) ()) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_handler = fn; act.sa_flags = 0; if (sigaction(SIGCHLD, &act, &oact) == 0) return (T); log_panic("[os_signal_child_init()] sigaction() failed: %s", strerror(errno)); return (NIL); } BOOL os_signal_child_clear(void) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_handler = SIG_DFL; act.sa_flags = 0; if (sigaction(SIGCHLD, &act, &oact) == 0) return (T); log_panic("[os_signal_child_clear()] sigaction() failed: %s", strerror(errno)); return (NIL); } /* ====================================================================== */ BOOL os_signal_alarm_init(void (*fn) ()) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_handler = fn; act.sa_flags = 0; if (sigaction(SIGALRM, &act, &oact) == 0) return (T); log_panic("[os_signal_alarm_init()] sigaction() failed: %s", strerror(errno)); return (NIL); } BOOL os_signal_alarm_clear(void) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_handler = SIG_DFL; act.sa_flags = 0; if (sigaction(SIGALRM, &act, &oact) == 0) return (T); log_panic("[os_signal_alarm_clear()] sigaction() failed: %s", strerror(errno)); return (NIL); } /* ====================================================================== */ BOOL os_lock_exclusive(int fd) { int rc; do { rc = flock(fd, LOCK_EX); } while ((rc < 0) && (errno == EINTR)); return ((rc >= 0) ? T : NIL); } BOOL os_lock_shared(int fd) { int rc; do { rc = flock(fd, LOCK_SH); } while ((rc < 0) && (errno == EINTR)); return ((rc >= 0) ? T : NIL); } BOOL os_lock_release(int fd) { int rc; do { rc = flock(fd, LOCK_UN); } while ((rc < 0) && (errno == EINTR)); return ((rc >= 0) ? T : NIL); } BOOL os_lock_exclusive_allow_break(int fd) { return ((flock(fd, LOCK_EX) >= 0) ? T : NIL); } BOOL os_lock_shared_allow_break(int fd) { return ((flock(fd, LOCK_SH) >= 0) ? T : NIL); } BOOL os_lock_release_allow_break(int fd) { return ((flock(fd, LOCK_UN) >= 0) ? T : NIL); } /* ====================================================================== */ static int os_read(int fd, char *buf, unsigned long count) { int rc; while (count > 0) { rc = read(fd, buf, count); if (rc > 0) { buf += rc; count -= rc; } else if (rc == 0) /* EOF */ break; else if (errno != EINTR) /* read() failed */ break; } return ((count == 0) ? T : NIL); } BOOL os_random(struct config * config, void *buffer, unsigned long count) { int fd; if (config->egd_socket) { if ((fd = os_connect_unix_socket(config->egd_socket)) < 0) return (NIL); while (count > 0) { unsigned char buf[2]; unsigned long bytes = (count > 255) ? (count % 256) : count; buf[0] = 0x02; buf[1] = (unsigned char) bytes; if (!((write(fd, buf, 2) == 2) && os_read(fd, buffer, bytes))) { close(fd); return (NIL); } count -= bytes; } close(fd); return (T); } if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return (NIL); if (!os_read(fd, buffer, count)) { close(fd); return (NIL); } close(fd); return (T); }