tdenetwork/kppp/opener.cpp

726 lines
17 KiB

/*
* 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");
}