1
2/*
3 * kPPP: A pppd Front End for the KDE project
4 *
5 * $Id$
6 *
7 * Copyright (C) 1997,98 Bernd Johannes Wuebben,
8 * Mario Weilguni
9 * Copyright (C) 1998-2002 Harri Porten <porten@kde.org>
10 *
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with this program; if not, write to the Free
24 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27/* A note to developers:
28 *
29 * Apart from the first dozen lines in main() the following code represents
30 * the setuid root part of kppp. So please be careful !
31 * o restrain from using X, Qt or KDE library calls
32 * o check for possible buffer overflows
33 * o handle requests from the parent process with care. They might be forged.
34 * o be paranoid and think twice about everything you change.
35 */
36#include <kdefakes.h>
37#include <config-kppp.h>
38
39#if defined(__osf__) || defined(__SVR4)
40#define _POSIX_PII_SOCKET
41extern "C" int sethostname(char *name, int name_len);
42#if !defined(__osf__)
43extern "C" int _Psendmsg(int, void*, int);
44extern "C" int _Precvmsg(int, void*, int);
45#endif
46#endif
47
48#include "kpppconfig.h"
49
50#include <sys/types.h>
51#include <sys/uio.h>
52#include <sys/stat.h>
53#include <sys/socket.h>
54#include <sys/ioctl.h>
55#include <sys/un.h>
56#include <sys/wait.h>
57#include <sys/param.h>
58
59
60#include <netinet/in.h>
61
62#ifdef __FreeBSD__
63# include <sys/linker.h> // for kldload
64#endif
65
66#ifndef HAVE_NET_IF_PPP_H
67# if defined(__DragonFly__)
68# include <net/ppp_layer/ppp_defs.h>
69# include <net/if.h>
70# include <net/ppp/if_ppp.h>
71# elif defined HAVE_LINUX_IF_PPP_H
72# include <linux/if_ppp.h>
73# endif
74#else
75# include <net/ppp_defs.h>
76# include <net/if.h>
77# include <net/if_ppp.h>
78#endif
79
80#include <errno.h>
81#include <fcntl.h>
82#include <regex.h>
83#include <signal.h>
84#include <stdio.h>
85#include <stdlib.h>
86#include <string.h>
87#include <unistd.h>
88#include <termios.h>
89
90#include "opener.h"
91#include "devices.h"
92
93#ifdef HAVE_RESOLV_H
94# include <arpa/nameser.h>
95# include <resolv.h>
96#endif
97
98#ifndef _PATH_RESCONF
99#define _PATH_RESCONF "/etc/resolv.conf"
100#endif
101
102#ifdef _XPG4_2
103extern "C" {
104 ssize_t recvmsg(int, struct msghdr *, int);
105 ssize_t sendmsg(int, const struct msghdr *, int);
106}
107#endif
108
109#define MY_ASSERT(x) if (!(x)) { \
110 fprintf(stderr, "ASSERT: \"%s\" in %s (%d)\n",#x,__FILE__,__LINE__); \
111 exit(1); }
112
113#define MY_DEBUG
114#ifndef MY_DEBUG
115#define Debug(s) ((void)0);
116#define Debug2(s, i) ((void)0);
117#else
118#define Debug(s) fprintf(stderr, (s "\n"));
119#define Debug2(s, i) fprintf(stderr, (s), (i));
120#endif
121
122static void sighandler_child(int);
123static pid_t pppdPid = -1;
124static int pppdExitStatus = -1;
125static int checkForInterface();
126
127// processing will stop at first file that could be opened successfully
128const char * const kppp_syslog[] = { "/var/log/syslog.ppp",
129 "/var/log/syslog",
130 "/var/log/messages",
131 0 };
132
133Opener::Opener(int s) : socket(s), ttyfd(-1) {
134 lockfile[0] = '\0';
135 signal(SIGUSR1, SIG_IGN);
136 signal(SIGTERM, SIG_IGN);
137 signal(SIGINT, SIG_IGN);
138 signal(SIGCHLD, sighandler_child);
139 mainLoop();
140}
141
142void Opener::mainLoop() {
143
144 int len;
145 int fd = -1;
146 int flags, mode;
147 const char *device, * const *logFile;
148 union AllRequests request;
149 struct ResponseHeader response;
150 struct msghdr msg;
151 struct iovec iov;
152
153 iov.iov_base = IOV_BASE_CAST &request;
154 iov.iov_len = sizeof(request);
155
156 msg.msg_name = 0L;
157 msg.msg_namelen = 0;
158 msg.msg_iov = &iov;
159 msg.msg_iovlen = 1;
160 msg.msg_control = 0L;
161 msg.msg_controllen = 0;
162
163 // loop forever
164 while(1) {
165 len = recvmsg(socket, &msg, 0);
166 if(len < 0) {
167 switch(errno) {
168 case EINTR:
169 Debug("Opener: interrupted system call, continuing");
170 break;
171 default:
172 perror("Opener: error reading from socket");
173 _exit(1);
174 }
175 } else {
176 switch(request.header.type) {
177
178 case OpenDevice:
179 Debug("Opener: received OpenDevice");
180 MY_ASSERT(len == sizeof(struct OpenModemRequest));
181 close(ttyfd);
182 device = deviceByIndex(request.modem.deviceNum);
183 response.status = 0;
184 if ((ttyfd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) == -1) {
185 Debug("error opening modem device !");
186 fd = open(DEVNULL, O_RDONLY);
187 response.status = -errno;
188 sendFD(fd, &response);
189 close(fd);
190 } else
191 sendFD(ttyfd, &response);
192 break;
193
194 case OpenLock:
195 Debug("Opener: received OpenLock\n");
196 MY_ASSERT(len == sizeof(struct OpenLockRequest));
197 flags = request.lock.flags;
198 MY_ASSERT(flags == O_RDONLY || flags == (O_WRONLY|O_TRUNC|O_CREAT));
199 if(flags == (O_WRONLY|O_TRUNC|O_CREAT))
200 mode = 0644;
201 else
202 mode = 0;
203
204 device = deviceByIndex(request.lock.deviceNum);
205 MY_ASSERT(strlen(LOCK_DIR)+strlen(device) < MaxPathLen);
206 strlcpy(lockfile, LOCK_DIR"/LCK..", MaxPathLen);
207 strlcat(lockfile, strrchr(device, '/') + 1, MaxPathLen );
208 response.status = 0;
209 // TODO:
210 // struct stat st;
211 // if(stat(lockfile.data(), &st) == -1) {
212 // if(errno == EBADF)
213 // return -1;
214 // } else {
215 // // make sure that this is a regular file
216 // if(!S_ISREG(st.st_mode))
217 // return -1;
218 // }
219 if ((fd = open(lockfile, flags, mode)) == -1) {
220 Debug("error opening lockfile!");
221 lockfile[0] = '\0';
222 fd = open(DEVNULL, O_RDONLY);
223 response.status = -errno;
224 } else
225 fchown(fd, 0, 0);
226 sendFD(fd, &response);
227 close(fd);
228 break;
229
230 case RemoveLock:
231 Debug("Opener: received RemoveLock");
232 MY_ASSERT(len == sizeof(struct RemoveLockRequest));
233 close(ttyfd);
234 ttyfd = -1;
235 response.status = unlink(lockfile);
236 lockfile[0] = '\0';
237 sendResponse(&response);
238 break;
239
240 case OpenResolv:
241 Debug("Opener: received OpenResolv");
242 MY_ASSERT(len == sizeof(struct OpenResolvRequest));
243 flags = request.resolv.flags;
244 response.status = 0;
245 if ((fd = open(_PATH_RESCONF, flags)) == -1) {
246 Debug("error opening resolv.conf!");
247 fd = open(DEVNULL, O_RDONLY);
248 response.status = -errno;
249 }
250 sendFD(fd, &response);
251 close(fd);
252 break;
253
254 case OpenSysLog:
255 Debug("Opener: received OpenSysLog");
256 MY_ASSERT(len == sizeof(struct OpenLogRequest));
257 response.status = 0;
258 logFile = &kppp_syslog[0];
259 while (*logFile) {
260 if ((fd = open(*logFile, O_RDONLY)) >= 0)
261 break;
262 logFile++;
263 }
264 if (!*logFile) {
265 Debug("No success opening a syslog file !");
266 fd = open(DEVNULL, O_RDONLY);
267 response.status = -errno;
268 }
269 sendFD(fd, &response);
270 close(fd);
271 break;
272
273 case SetSecret:
274 Debug("Opener: received SetSecret");
275 MY_ASSERT(len == sizeof(struct SetSecretRequest));
276 response.status = !createAuthFile(request.secret.method,
277 request.secret.username,
278 request.secret.password);
279 sendResponse(&response);
280 break;
281
282 case RemoveSecret:
283 Debug("Opener: received RemoveSecret");
284 MY_ASSERT(len == sizeof(struct RemoveSecretRequest));
285 response.status = !removeAuthFile(request.remove.method);
286 sendResponse(&response);
287 break;
288
289 case SetHostname:
290 Debug("Opener: received SetHostname");
291 MY_ASSERT(len == sizeof(struct SetHostnameRequest));
292 response.status = 0;
293 if(sethostname(request.host.name, strlen(request.host.name)))
294 response.status = -errno;
295 sendResponse(&response);
296 break;
297
298 case ExecPPPDaemon:
299 Debug("Opener: received ExecPPPDaemon");
300 MY_ASSERT(len == sizeof(struct ExecDaemonRequest));
301 response.status = execpppd(request.daemon.arguments);
302 sendResponse(&response);
303 break;
304
305 case KillPPPDaemon:
306 Debug("Opener: received KillPPPDaemon");
307 MY_ASSERT(len == sizeof(struct KillDaemonRequest));
308 response.status = killpppd();
309 sendResponse(&response);
310 break;
311
312 case PPPDExitStatus:
313 Debug("Opener: received PPPDExitStatus");
314 MY_ASSERT(len == sizeof(struct PPPDExitStatusRequest));
315 response.status = pppdExitStatus;
316 sendResponse(&response);
317 break;
318
319 case Stop:
320 Debug("Opener: received STOP command");
321 _exit(0);
322 break;
323
324 default:
325 Debug("Opener: unknown command type. Exiting ...");
326 _exit(1);
327 }
328 } // else
329 }
330}
331
332
333//
334// Send an open fd over a UNIX socket pair
335//
336int Opener::sendFD(int fd, struct ResponseHeader *response) {
337
338 struct { struct cmsghdr cmsg; int fd; } control;
339 struct msghdr msg;
340 struct iovec iov;
341
342 msg.msg_name = 0L;
343 msg.msg_namelen = 0;
344 msg.msg_iov = &iov;
345 msg.msg_iovlen = 1;
346
347 // Send data
348 iov.iov_base = IOV_BASE_CAST response;
349 iov.iov_len = sizeof(struct ResponseHeader);
350
351 // Send a (duplicate of) the file descriptor
352 control.cmsg.cmsg_len = sizeof(struct cmsghdr) + sizeof(int);
353 control.cmsg.cmsg_level = SOL_SOCKET;
354 control.cmsg.cmsg_type = MY_SCM_RIGHTS;
355
356 msg.msg_control = (char *) &control;
357 msg.msg_controllen = control.cmsg.cmsg_len;
358
359#ifdef CMSG_DATA
360 *((int *)CMSG_DATA(&control.cmsg)) = fd;
361#else
362 *((int *) &control.cmsg.cmsg_data) = fd;
363#endif
364
365 if (sendmsg(socket, &msg, 0) < 0) {
366 perror("unable to send file descriptors");
367 return -1;
368 }
369
370 return 0;
371}
372
373int Opener::sendResponse(struct ResponseHeader *response) {
374
375 struct msghdr msg;
376 struct iovec iov;
377
378 msg.msg_name = 0L;
379 msg.msg_namelen = 0;
380 msg.msg_iov = &iov;
381 msg.msg_iovlen = 1;
382 msg.msg_control = 0L;
383 msg.msg_controllen = 0;
384
385 // Send data
386 iov.iov_base = IOV_BASE_CAST response;
387 iov.iov_len = sizeof(struct ResponseHeader);
388
389 if (sendmsg(socket, &msg, 0) < 0) {
390 perror("unable to send response");
391 return -1;
392 }
393
394 return 0;
395}
396
397const char* Opener::deviceByIndex(int idx) {
398
399 const char *device = 0L;
400
401 for(int i = 0; devices[i]; i++)
402 if(i == idx)
403 device = devices[i];
404 MY_ASSERT(device);
405 return device;
406}
407
408bool Opener::createAuthFile(Auth method, char *username, char *password) {
409 const char *authfile, *oldName, *newName;
410 char line[100];
411 char regexp[2*MaxStrLen+30];
412 regex_t preg;
413
414 if(!(authfile = authFile(method)))
415 return false;
416
417 if(!(newName = authFile(method, New)))
418 return false;
419
420 // look for username, "username" or 'username'
421 // if you modify this RE you have to adapt regexp's size above
422 snprintf(regexp, sizeof(regexp), "^[ \t]*%s[ \t]\\|^[ \t]*[\"\']%s[\"\']",
423 username,username);
424 MY_ASSERT(regcomp(&preg, regexp, 0) == 0);
425
426 // copy to new file pap- or chap-secrets
427 int old_umask = umask(0077);
428 FILE *fout = fopen(newName, "w");
429 if(fout) {
430 // copy old file
431 FILE *fin = fopen(authfile, "r");
432 if(fin) {
433 while(fgets(line, sizeof(line), fin)) {
434 if(regexec(&preg, line, 0, 0L, 0) == 0)
435 continue;
436 fputs(line, fout);
437 }
438 fclose(fin);
439 }
440
441 // append user/pass pair
442 fprintf(fout, "\"%s\"\t*\t\"%s\"\n", username, password);
443 fclose(fout);
444 }
445
446 // restore umask
447 umask(old_umask);
448
449 // free memory allocated by regcomp
450 regfree(&preg);
451
452 if(!(oldName = authFile(method, Old)))
453 return false;
454
455 // delete old file if any
456 unlink(oldName);
457
458 rename(authfile, oldName);
459 rename(newName, authfile);
460
461 return true;
462}
463
464
465bool Opener::removeAuthFile(Auth method) {
466 const char *authfile, *oldName;
467
468 if(!(authfile = authFile(method)))
469 return false;
470 if(!(oldName = authFile(method, Old)))
471 return false;
472
473 if(access(oldName, F_OK) == 0) {
474 unlink(authfile);
475 return (rename(oldName, authfile) == 0);
476 } else
477 return false;
478}
479
480
481const char* Opener::authFile(Auth method, int version) {
482 switch(method|version) {
483 case PAP|Original:
484 return PAP_AUTH_FILE;
485 break;
486 case PAP|New:
487 return PAP_AUTH_FILE".new";
488 break;
489 case PAP|Old:
490 return PAP_AUTH_FILE".old";
491 break;
492 case CHAP|Original:
493 return CHAP_AUTH_FILE;
494 break;
495 case CHAP|New:
496 return CHAP_AUTH_FILE".new";
497 break;
498 case CHAP|Old:
499 return CHAP_AUTH_FILE".old";
500 break;
501 default:
502 return 0L;
503 }
504}
505
506
507bool Opener::execpppd(const char *arguments) {
508 char buf[MAX_CMDLEN];
509 char *args[MaxArgs];
510 pid_t pgrpid;
511
512 if(ttyfd<0)
513 return false;
514
515 pppdExitStatus = -1;
516
517 switch(pppdPid = fork())
518 {
519 case -1:
520 fprintf(stderr,"In parent: fork() failed\n");
521 return false;
522 break;
523
524 case 0:
525 // let's parse the arguments the user supplied into UNIX suitable form
526 // that is a list of pointers each pointing to exactly one word
527 strlcpy(buf, arguments, sizeof(buf));
528 parseargs(buf, args);
529 // become a session leader and let /dev/ttySx
530 // be the controlling terminal.
531 pgrpid = setsid();
532#ifdef TIOCSCTTY
533 if(ioctl(ttyfd, TIOCSCTTY, 0)<0)
534 fprintf(stderr, "ioctl() failed.\n");
535#elif defined (TIOCSPGRP)
536 if(ioctl(ttyfd, TIOCSPGRP, &pgrpid)<0)
537 fprintf(stderr, "ioctl() failed.\n");
538#endif
539 if(tcsetpgrp(ttyfd, pgrpid)<0)
540 fprintf(stderr, "tcsetpgrp() failed.\n");
541
542 dup2(ttyfd, 0);
543 dup2(ttyfd, 1);
544
545 switch (checkForInterface()) {
546 case 1:
547 fprintf(stderr, "Cannot determine if kernel supports ppp.\n");
548 break;
549 case -1:
550 fprintf(stderr, "Kernel does not support ppp, oops.\n");
551 break;
552 case 0:
553 fprintf(stderr, "Kernel supports ppp alright.\n");
554 break;
555 }
556
557 execve(pppdPath(), args, 0L);
558 _exit(0);
559 break;
560
561 default:
562 Debug2("In parent: pppd pid %d\n",pppdPid);
563 close(ttyfd);
564 ttyfd = -1;
565 return true;
566 break;
567 }
568}
569
570
571bool Opener::killpppd()const {
572 if(pppdPid > 0) {
573 Debug2("In killpppd(): Sending SIGTERM to %d\n", pppdPid);
574 if(kill(pppdPid, SIGTERM) < 0) {
575 Debug2("Error terminating %d. Sending SIGKILL\n", pppdPid);
576 if(kill(pppdPid, SIGKILL) < 0) {
577 Debug2("Error killing %d\n", pppdPid);
578 return false;
579 }
580 }
581 }
582 return true;
583}
584
585
586void Opener::parseargs(char* buf, char** args) {
587 int nargs = 0;
588 int quotes;
589
590 while(nargs < MaxArgs-1 && *buf != '\0') {
591
592 quotes = 0;
593
594 // Strip whitespace. Use nulls, so that the previous argument is
595 // terminated automatically.
596
597 while ((*buf == ' ' ) || (*buf == '\t' ) || (*buf == '\n' ) )
598 *buf++ = '\0';
599
600 // detect begin of quoted argument
601 if (*buf == '"' || *buf == '\'') {
602 quotes = *buf;
603 *buf++ = '\0';
604 }
605
606 // save the argument
607 if(*buf != '\0') {
608 *args++ = buf;
609 nargs++;
610 }
611
612 if (!quotes)
613 while ((*buf != '\0') && (*buf != '\n') &&
614 (*buf != '\t') && (*buf != ' '))
615 buf++;
616 else {
617 while ((*buf != '\0') && (*buf != quotes))
618 buf++;
619 *buf++ = '\0';
620 }
621 }
622
623 *args = 0L;
624}
625
626
627const char* pppdPath() {
628 // wasting a few bytes
629 static char buffer[sizeof(PPPDSEARCHPATH)+sizeof(PPPDNAME)];
630 static char *pppdPath = 0L;
631 char *p;
632
633 if(pppdPath == 0L) {
634 const char *c = PPPDSEARCHPATH;
635 while(*c != '\0') {
636 while(*c == ':')
637 c++;
638 p = buffer;
639 while(*c != '\0' && *c != ':')
640 *p++ = *c++;
641 *p = '\0';
642 strcat(p, "/");
643 strcat(p, PPPDNAME);
644 if(access(buffer, F_OK) == 0)
645 return (pppdPath = buffer);
646 }
647 }
648
649 return pppdPath;
650}
651
652int checkForInterface()
653{
654// I don't know if Linux needs more initialization to get the ioctl to
655// work, pppd seems to hint it does. But BSD doesn't, and the following
656// code should compile.
657#if (defined(HAVE_NET_IF_PPP_H) || defined(HAVE_LINUX_IF_PPP_H)) && !defined(__SVR4)
658 int s, ok;
659 struct ifreq ifr;
660 // extern char *no_ppp_msg;
661
662 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
663 return 1; /* can't tell */
664
665 strlcpy(ifr.ifr_name, "ppp0", sizeof (ifr.ifr_name));
666 ok = ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) >= 0;
667 close(s);
668
669 if (ok == -1) {
670// This is ifdef'd FreeBSD, because FreeBSD is the only BSD that supports
671// KLDs, the old LKM interface couldn't handle loading devices
672// dynamically, and thus can't load ppp support on the fly
673#ifdef __FreeBSD__
674 // If we failed to load ppp support and don't have it already.
675 if (kldload("if_ppp") == -1) {
676 return -1;
677 }
678 return 0;
679#else
680 return -1;
681#endif
682 }
683 return 0;
684#else
685// We attempt to use the SunOS/SysVr4 method and stat /dev/ppp
686 struct stat buf;
687
688 memset(&buf, 0, sizeof(buf));
689 return stat("/dev/ppp", &buf);
690#endif
691}
692
693
694void sighandler_child(int) {
695 pid_t pid;
696 int status;
697
698 signal(SIGCHLD, sighandler_child);
699 if(pppdPid>0) {
700 pid = waitpid(pppdPid, &status, WNOHANG);
701 if(pid != pppdPid) {
702 fprintf(stderr, "received SIGCHLD from unknown origin.\n");
703 } else {
704 Debug("It was pppd that died");
705 pppdPid = -1;
706 if((WIFEXITED(status))) {
707 pppdExitStatus = (WEXITSTATUS(status));
708 Debug2("pppd exited with return value %d\n", pppdExitStatus);
709 } else {
710 pppdExitStatus = 99;
711 Debug("pppd exited abnormally.");
712 }
713 Debug2("Sending %i a SIGUSR1\n", getppid());
714 kill(getppid(), SIGUSR1);
715 }
716 } else
717 fprintf(stderr, "received unexpected SIGCHLD.\n");
718}
719