/* * kPPP: A pppd Front End for the KDE project * * $Id$ * * Copyright (C) 1997,98 Bernd Johannes Wuebben, * Mario Weilguni * Copyright (C) 1998-2002 Harri Porten <porten@kde.org> * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* A note to developers: * * Apart from the first dozen lines in main() the following code represents * the setuid root part of kppp. So please be careful ! * o restrain from using X, TQt or KDE library calls * o check for possible buffer overflows * o handle requests from the parent process with care. They might be forged. * o be paranoid and think twice about everything you change. */ #include <config.h> #if defined(__osf__) || defined(__svr4__) #define _POSIX_PII_SOCKET extern "C" int sethostname(char *name, int name_len); #if !defined(__osf__) extern "C" int _Psendmsg(int, void*, int); extern "C" int _Precvmsg(int, void*, int); #endif #endif #include "kpppconfig.h" #include <sys/types.h> #include <sys/uio.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/un.h> #include <sys/wait.h> #include <sys/param.h> #include <netinet/in.h> #ifdef __FreeBSD__ # include <sys/linker.h> // for kldload #endif #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifndef HAVE_NET_IF_PPP_H # if defined(__DragonFly__) # include <net/ppp_layer/ppp_defs.h> # include <net/if.h> # include <net/ppp/if_ppp.h> # elif defined HAVE_LINUX_IF_PPP_H # ifndef aligned_u64 # define aligned_u64 unsigned long long __attribute__((aligned(8))) # endif # include <linux/if_ppp.h> # endif #else # include <net/ppp_defs.h> # include <net/if.h> # include <net/if_ppp.h> #endif #include <errno.h> #include <fcntl.h> #include <regex.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <termios.h> #include "opener.h" #include "devices.h" #ifdef HAVE_RESOLV_H # include <arpa/nameser.h> # include <resolv.h> #endif #ifndef _PATH_RESCONF #define _PATH_RESCONF "/etc/resolv.conf" #endif #ifdef _XPG4_2 extern "C" { ssize_t recvmsg(int, struct msghdr *, int); ssize_t sendmsg(int, const struct msghdr *, int); } #endif #define MY_ASSERT(x) if (!(x)) { \ fprintf(stderr, "ASSERT: \"%s\" in %s (%d)\n",#x,__FILE__,__LINE__); \ exit(1); } #define MY_DEBUG #ifndef MY_DEBUG #define Debug(s) ((void)0); #define Debug2(s, i) ((void)0); #else #define Debug(s) fprintf(stderr, (s "\n")); #define Debug2(s, i) fprintf(stderr, (s), (i)); #endif static void sighandler_child(int); static pid_t pppdPid = -1; static int pppdExitStatus = -1; static int checkForInterface(); // processing will stop at first file that could be opened successfully const char * const kppp_syslog[] = { "/var/log/syslog.ppp", "/var/log/syslog", "/var/log/messages", 0 }; Opener::Opener(int s) : socket(s), ttyfd(-1) { lockfile[0] = '\0'; signal(SIGUSR1, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGCHLD, sighandler_child); mainLoop(); } void Opener::mainLoop() { int len; int fd = -1; int flags, mode; const char *device, * const *logFile; union AllRequests request; struct ResponseHeader response; struct msghdr msg; struct iovec iov; iov.iov_base = IOV_BASE_CAST &request; iov.iov_len = sizeof(request); msg.msg_name = 0L; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = 0L; msg.msg_controllen = 0; // loop forever while(1) { len = recvmsg(socket, &msg, 0); if(len < 0) { switch(errno) { case EINTR: Debug("Opener: interrupted system call, continuing"); break; default: perror("Opener: error reading from socket"); _exit(1); } } else { switch(request.header.type) { case OpenDevice: Debug("Opener: received OpenDevice"); MY_ASSERT(len == sizeof(struct OpenModemRequest)); close(ttyfd); device = deviceByIndex(request.modem.deviceNum); response.status = 0; if ((ttyfd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) == -1) { Debug("error opening modem device !"); fd = open(DEVNULL, O_RDONLY); response.status = -errno; sendFD(fd, &response); close(fd); } else sendFD(ttyfd, &response); break; case OpenLock: Debug("Opener: received OpenLock\n"); MY_ASSERT(len == sizeof(struct OpenLockRequest)); flags = request.lock.flags; MY_ASSERT(flags == O_RDONLY || flags == O_WRONLY|O_TRUNC|O_CREAT); if(flags == O_WRONLY|O_TRUNC|O_CREAT) mode = 0644; else mode = 0; device = deviceByIndex(request.lock.deviceNum); MY_ASSERT(strlen(LOCK_DIR)+strlen(device) < MaxPathLen); strlcpy(lockfile, LOCK_DIR"/LCK..", MaxPathLen); strlcat(lockfile, strrchr(device, '/') + 1, MaxPathLen ); response.status = 0; // TODO: // struct stat st; // if(stat(lockfile.data(), &st) == -1) { // if(errno == EBADF) // return -1; // } else { // // make sure that this is a regular file // if(!S_ISREG(st.st_mode)) // return -1; // } if ((fd = open(lockfile, flags, mode)) == -1) { Debug("error opening lockfile!"); lockfile[0] = '\0'; fd = open(DEVNULL, O_RDONLY); response.status = -errno; } else fchown(fd, 0, 0); sendFD(fd, &response); close(fd); break; case RemoveLock: Debug("Opener: received RemoveLock"); MY_ASSERT(len == sizeof(struct RemoveLockRequest)); close(ttyfd); ttyfd = -1; response.status = unlink(lockfile); lockfile[0] = '\0'; sendResponse(&response); break; case OpenResolv: Debug("Opener: received OpenResolv"); MY_ASSERT(len == sizeof(struct OpenResolvRequest)); flags = request.resolv.flags; response.status = 0; if ((fd = open(_PATH_RESCONF, flags)) == -1) { Debug("error opening resolv.conf!"); fd = open(DEVNULL, O_RDONLY); response.status = -errno; } sendFD(fd, &response); close(fd); break; case OpenSysLog: Debug("Opener: received OpenSysLog"); MY_ASSERT(len == sizeof(struct OpenLogRequest)); response.status = 0; logFile = &kppp_syslog[0]; while (*logFile) { if ((fd = open(*logFile, O_RDONLY)) >= 0) break; logFile++; } if (!*logFile) { Debug("No success opening a syslog file !"); fd = open(DEVNULL, O_RDONLY); response.status = -errno; } sendFD(fd, &response); close(fd); break; case SetSecret: Debug("Opener: received SetSecret"); MY_ASSERT(len == sizeof(struct SetSecretRequest)); response.status = !createAuthFile(request.secret.method, request.secret.username, request.secret.password); sendResponse(&response); break; case RemoveSecret: Debug("Opener: received RemoveSecret"); MY_ASSERT(len == sizeof(struct RemoveSecretRequest)); response.status = !removeAuthFile(request.remove.method); sendResponse(&response); break; case SetHostname: Debug("Opener: received SetHostname"); MY_ASSERT(len == sizeof(struct SetHostnameRequest)); response.status = 0; if(sethostname(request.host.name, strlen(request.host.name))) response.status = -errno; sendResponse(&response); break; case ExecPPPDaemon: Debug("Opener: received ExecPPPDaemon"); MY_ASSERT(len == sizeof(struct ExecDaemonRequest)); response.status = execpppd(request.daemon.arguments); sendResponse(&response); break; case KillPPPDaemon: Debug("Opener: received KillPPPDaemon"); MY_ASSERT(len == sizeof(struct KillDaemonRequest)); response.status = killpppd(); sendResponse(&response); break; case PPPDExitStatus: Debug("Opener: received PPPDExitStatus"); MY_ASSERT(len == sizeof(struct PPPDExitStatusRequest)); response.status = pppdExitStatus; sendResponse(&response); break; case Stop: Debug("Opener: received STOP command"); _exit(0); break; default: Debug("Opener: unknown command type. Exiting ..."); _exit(1); } } // else } } // // Send an open fd over a UNIX socket pair // int Opener::sendFD(int fd, struct ResponseHeader *response) { struct { struct cmsghdr cmsg; int fd; } control; struct msghdr msg; struct iovec iov; msg.msg_name = 0L; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; // Send data iov.iov_base = IOV_BASE_CAST response; iov.iov_len = sizeof(struct ResponseHeader); // Send a (duplicate of) the file descriptor control.cmsg.cmsg_len = sizeof(struct cmsghdr) + sizeof(int); control.cmsg.cmsg_level = SOL_SOCKET; control.cmsg.cmsg_type = MY_SCM_RIGHTS; msg.msg_control = (char *) &control; msg.msg_controllen = control.cmsg.cmsg_len; #ifdef CMSG_DATA *((int *)CMSG_DATA(&control.cmsg)) = fd; #else *((int *) &control.cmsg.cmsg_data) = fd; #endif if (sendmsg(socket, &msg, 0) < 0) { perror("unable to send file descriptors"); return -1; } return 0; } int Opener::sendResponse(struct ResponseHeader *response) { struct msghdr msg; struct iovec iov; msg.msg_name = 0L; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = 0L; msg.msg_controllen = 0; // Send data iov.iov_base = IOV_BASE_CAST response; iov.iov_len = sizeof(struct ResponseHeader); if (sendmsg(socket, &msg, 0) < 0) { perror("unable to send response"); return -1; } return 0; } const char* Opener::deviceByIndex(int idx) { const char *device = 0L; for(int i = 0; devices[i]; i++) if(i == idx) device = devices[i]; MY_ASSERT(device); return device; } bool Opener::createAuthFile(Auth method, char *username, char *password) { const char *authfile, *oldName, *newName; char line[100]; char regexp[2*MaxStrLen+30]; regex_t preg; if(!(authfile = authFile(method))) return false; if(!(newName = authFile(method, New))) return false; // look for username, "username" or 'username' // if you modify this RE you have to adapt regexp's size above snprintf(regexp, sizeof(regexp), "^[ \t]*%s[ \t]\\|^[ \t]*[\"\']%s[\"\']", username,username); MY_ASSERT(regcomp(&preg, regexp, 0) == 0); // copy to new file pap- or chap-secrets int old_umask = umask(0077); FILE *fout = fopen(newName, "w"); if(fout) { // copy old file FILE *fin = fopen(authfile, "r"); if(fin) { while(fgets(line, sizeof(line), fin)) { if(regexec(&preg, line, 0, 0L, 0) == 0) continue; fputs(line, fout); } fclose(fin); } // append user/pass pair fprintf(fout, "\"%s\"\t*\t\"%s\"\n", username, password); fclose(fout); } // restore umask umask(old_umask); // free memory allocated by regcomp regfree(&preg); if(!(oldName = authFile(method, Old))) return false; // delete old file if any unlink(oldName); rename(authfile, oldName); rename(newName, authfile); return true; } bool Opener::removeAuthFile(Auth method) { const char *authfile, *oldName; if(!(authfile = authFile(method))) return false; if(!(oldName = authFile(method, Old))) return false; if(access(oldName, F_OK) == 0) { unlink(authfile); return (rename(oldName, authfile) == 0); } else return false; } const char* Opener::authFile(Auth method, int version) { switch(method|version) { case PAP|Original: return PAP_AUTH_FILE; break; case PAP|New: return PAP_AUTH_FILE".new"; break; case PAP|Old: return PAP_AUTH_FILE".old"; break; case CHAP|Original: return CHAP_AUTH_FILE; break; case CHAP|New: return CHAP_AUTH_FILE".new"; break; case CHAP|Old: return CHAP_AUTH_FILE".old"; break; default: return 0L; } } bool Opener::execpppd(const char *arguments) { char buf[MAX_CMDLEN]; char *args[MaxArgs]; pid_t pgrpid; if(ttyfd<0) return false; pppdExitStatus = -1; switch(pppdPid = fork()) { case -1: fprintf(stderr,"In parent: fork() failed\n"); return false; break; case 0: // let's parse the arguments the user supplied into UNIX suitable form // that is a list of pointers each pointing to exactly one word strlcpy(buf, arguments, sizeof(buf)); parseargs(buf, args); // become a session leader and let /dev/ttySx // be the controlling terminal. pgrpid = setsid(); #ifdef TIOCSCTTY if(ioctl(ttyfd, TIOCSCTTY, 0)<0) fprintf(stderr, "ioctl() failed.\n"); #elif defined (TIOCSPGRP) if(ioctl(ttyfd, TIOCSPGRP, &pgrpid)<0) fprintf(stderr, "ioctl() failed.\n"); #endif if(tcsetpgrp(ttyfd, pgrpid)<0) fprintf(stderr, "tcsetpgrp() failed.\n"); dup2(ttyfd, 0); dup2(ttyfd, 1); switch (checkForInterface()) { case 1: fprintf(stderr, "Cannot determine if kernel supports ppp.\n"); break; case -1: fprintf(stderr, "Kernel does not support ppp, oops.\n"); break; case 0: fprintf(stderr, "Kernel supports ppp alright.\n"); break; } execve(pppdPath(), args, 0L); _exit(0); break; default: Debug2("In parent: pppd pid %d\n",pppdPid); close(ttyfd); ttyfd = -1; return true; break; } } bool Opener::killpppd()const { if(pppdPid > 0) { Debug2("In killpppd(): Sending SIGTERM to %d\n", pppdPid); if(kill(pppdPid, SIGTERM) < 0) { Debug2("Error terminating %d. Sending SIGKILL\n", pppdPid); if(kill(pppdPid, SIGKILL) < 0) { Debug2("Error killing %d\n", pppdPid); return false; } } } return true; } void Opener::parseargs(char* buf, char** args) { int nargs = 0; int quotes; while(nargs < MaxArgs-1 && *buf != '\0') { quotes = 0; // Strip whitespace. Use nulls, so that the previous argument is // terminated automatically. while ((*buf == ' ' ) || (*buf == '\t' ) || (*buf == '\n' ) ) *buf++ = '\0'; // detect begin of quoted argument if (*buf == '"' || *buf == '\'') { quotes = *buf; *buf++ = '\0'; } // save the argument if(*buf != '\0') { *args++ = buf; nargs++; } if (!quotes) while ((*buf != '\0') && (*buf != '\n') && (*buf != '\t') && (*buf != ' ')) buf++; else { while ((*buf != '\0') && (*buf != quotes)) buf++; *buf++ = '\0'; } } *args = 0L; } const char* pppdPath() { // wasting a few bytes static char buffer[sizeof(PPPDSEARCHPATH)+sizeof(PPPDNAME)]; static char *pppdPath = 0L; char *p; if(pppdPath == 0L) { const char *c = PPPDSEARCHPATH; while(*c != '\0') { while(*c == ':') c++; p = buffer; while(*c != '\0' && *c != ':') *p++ = *c++; *p = '\0'; strcat(p, "/"); strcat(p, PPPDNAME); if(access(buffer, F_OK) == 0) return (pppdPath = buffer); } } return pppdPath; } int checkForInterface() { // I don't know if Linux needs more initialization to get the ioctl to // work, pppd seems to hint it does. But BSD doesn't, and the following // code should compile. #if (defined(HAVE_NET_IF_PPP_H) || defined(HAVE_LINUX_IF_PPP_H)) && !defined(__svr4__) int s, ok; struct ifreq ifr; // extern char *no_ppp_msg; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return 1; /* can't tell */ strlcpy(ifr.ifr_name, "ppp0", sizeof (ifr.ifr_name)); ok = ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) >= 0; close(s); if (ok == -1) { // This is ifdef'd FreeBSD, because FreeBSD is the only BSD that supports // KLDs, the old LKM interface couldn't handle loading devices // dynamically, and thus can't load ppp support on the fly #ifdef __FreeBSD__ // If we failed to load ppp support and don't have it already. if (kldload("if_ppp") == -1) { return -1; } return 0; #else return -1; #endif } return 0; #else // We attempt to use the SunOS/SysVr4 method and stat /dev/ppp struct stat buf; memset(&buf, 0, sizeof(buf)); return stat("/dev/ppp", &buf); #endif } void sighandler_child(int) { pid_t pid; int status; signal(SIGCHLD, sighandler_child); if(pppdPid>0) { pid = waitpid(pppdPid, &status, WNOHANG); if(pid != pppdPid) { fprintf(stderr, "received SIGCHLD from unknown origin.\n"); } else { Debug("It was pppd that died"); pppdPid = -1; if((WIFEXITED(status))) { pppdExitStatus = (WEXITSTATUS(status)); Debug2("pppd exited with return value %d\n", pppdExitStatus); } else { pppdExitStatus = 99; Debug("pppd exited abnormally."); } Debug2("Sending %i a SIGUSR1\n", getppid()); kill(getppid(), SIGUSR1); } } else fprintf(stderr, "received unexpected SIGCHLD.\n"); }