Fix: after pause a download, it shows 100% progress. This is a regression of commit...
[kdenetwork.git] / kppp / opener.cpp
blob611699cad3edc357a707d738ec3c6ec81738c874
2 /*
3 * kPPP: A pppd Front End for the KDE project
5 * $Id$
7 * Copyright (C) 1997,98 Bernd Johannes Wuebben,
8 * Mario Weilguni
9 * Copyright (C) 1998-2002 Harri Porten <porten@kde.org>
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.
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.
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.
27 /* A note to developers:
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.
36 #include <kdefakes.h>
37 #include <config-kppp.h>
39 #if defined(__osf__) || defined(__svr4__)
40 #define _POSIX_PII_SOCKET
41 extern "C" int sethostname(char *name, int name_len);
42 #if !defined(__osf__)
43 extern "C" int _Psendmsg(int, void*, int);
44 extern "C" int _Precvmsg(int, void*, int);
45 #endif
46 #endif
48 #include "kpppconfig.h"
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>
60 #include <netinet/in.h>
62 #ifdef __FreeBSD__
63 # include <sys/linker.h> // for kldload
64 #endif
66 #ifndef HAVE_NET_IF_PPP_H
67 # ifdef HAVE_LINUX_IF_PPP_H
68 # include <linux/if_ppp.h>
69 # endif
70 #else
71 # include <net/ppp_defs.h>
72 # include <net/if.h>
73 # include <net/if_ppp.h>
74 #endif
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <regex.h>
79 #include <signal.h>
80 #include <stdio.h>
81 #include <stdlib.h>
82 #include <string.h>
83 #include <unistd.h>
84 #include <termios.h>
86 #include "opener.h"
87 #include "devices.h"
89 #ifdef HAVE_RESOLV_H
90 # include <arpa/nameser.h>
91 # include <resolv.h>
92 #endif
94 #ifndef _PATH_RESCONF
95 #define _PATH_RESCONF "/etc/resolv.conf"
96 #endif
98 #ifdef _XPG4_2
99 extern "C" {
100 ssize_t recvmsg(int, struct msghdr *, int);
101 ssize_t sendmsg(int, const struct msghdr *, int);
103 #endif
105 #define MY_ASSERT(x) if (!(x)) { \
106 fprintf(stderr, "ASSERT: \"%s\" in %s (%d)\n",#x,__FILE__,__LINE__); \
107 exit(1); }
109 #define MY_DEBUG
110 #ifndef MY_DEBUG
111 #define Debug(s) ((void)0);
112 #define Debug2(s, i) ((void)0);
113 #else
114 #define Debug(s) fprintf(stderr, (s "\n"));
115 #define Debug2(s, i) fprintf(stderr, (s), (i));
116 #endif
118 static void sighandler_child(int);
119 static pid_t pppdPid = -1;
120 static int pppdExitStatus = -1;
121 static int checkForInterface();
123 // processing will stop at first file that could be opened successfully
124 const char * const kppp_syslog[] = { "/var/log/syslog.ppp",
125 "/var/log/syslog",
126 "/var/log/messages",
127 0 };
129 Opener::Opener(int s) : socket(s), ttyfd(-1) {
130 lockfile[0] = '\0';
131 signal(SIGUSR1, SIG_IGN);
132 signal(SIGTERM, SIG_IGN);
133 signal(SIGINT, SIG_IGN);
134 signal(SIGCHLD, sighandler_child);
135 mainLoop();
138 void Opener::mainLoop() {
140 int len;
141 int fd = -1;
142 int flags, mode;
143 const char *device, * const *logFile;
144 union AllRequests request;
145 struct ResponseHeader response;
146 struct msghdr msg;
147 struct iovec iov;
149 iov.iov_base = IOV_BASE_CAST &request;
150 iov.iov_len = sizeof(request);
152 msg.msg_name = 0L;
153 msg.msg_namelen = 0;
154 msg.msg_iov = &iov;
155 msg.msg_iovlen = 1;
156 msg.msg_control = 0L;
157 msg.msg_controllen = 0;
159 // loop forever
160 while(1) {
161 len = recvmsg(socket, &msg, 0);
162 if(len < 0) {
163 switch(errno) {
164 case EINTR:
165 Debug("Opener: interrupted system call, continuing");
166 break;
167 default:
168 perror("Opener: error reading from socket");
169 _exit(1);
171 } else {
172 switch(request.header.type) {
174 case OpenDevice:
175 Debug("Opener: received OpenDevice");
176 MY_ASSERT(len == sizeof(struct OpenModemRequest));
177 close(ttyfd);
178 device = deviceByIndex(request.modem.deviceNum);
179 response.status = 0;
180 if ((ttyfd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) == -1) {
181 Debug("error opening modem device !");
182 fd = open(DEVNULL, O_RDONLY);
183 response.status = -errno;
184 sendFD(fd, &response);
185 close(fd);
186 } else
187 sendFD(ttyfd, &response);
188 break;
190 case OpenLock:
191 Debug("Opener: received OpenLock\n");
192 MY_ASSERT(len == sizeof(struct OpenLockRequest));
193 flags = request.lock.flags;
194 MY_ASSERT(flags == O_RDONLY || flags == O_WRONLY|O_TRUNC|O_CREAT);
195 if(flags == O_WRONLY|O_TRUNC|O_CREAT)
196 mode = 0644;
197 else
198 mode = 0;
200 device = deviceByIndex(request.lock.deviceNum);
201 MY_ASSERT(strlen(LOCK_DIR)+strlen(device) < MaxPathLen);
202 strlcpy(lockfile, LOCK_DIR"/LCK..", MaxPathLen);
203 strlcat(lockfile, strrchr(device, '/') + 1, MaxPathLen );
204 response.status = 0;
205 // TODO:
206 // struct stat st;
207 // if(stat(lockfile.data(), &st) == -1) {
208 // if(errno == EBADF)
209 // return -1;
210 // } else {
211 // // make sure that this is a regular file
212 // if(!S_ISREG(st.st_mode))
213 // return -1;
214 // }
215 if ((fd = open(lockfile, flags, mode)) == -1) {
216 Debug("error opening lockfile!");
217 lockfile[0] = '\0';
218 fd = open(DEVNULL, O_RDONLY);
219 response.status = -errno;
220 } else
221 fchown(fd, 0, 0);
222 sendFD(fd, &response);
223 close(fd);
224 break;
226 case RemoveLock:
227 Debug("Opener: received RemoveLock");
228 MY_ASSERT(len == sizeof(struct RemoveLockRequest));
229 close(ttyfd);
230 ttyfd = -1;
231 response.status = unlink(lockfile);
232 lockfile[0] = '\0';
233 sendResponse(&response);
234 break;
236 case OpenResolv:
237 Debug("Opener: received OpenResolv");
238 MY_ASSERT(len == sizeof(struct OpenResolvRequest));
239 flags = request.resolv.flags;
240 response.status = 0;
241 if ((fd = open(_PATH_RESCONF, flags)) == -1) {
242 Debug("error opening resolv.conf!");
243 fd = open(DEVNULL, O_RDONLY);
244 response.status = -errno;
246 sendFD(fd, &response);
247 close(fd);
248 break;
250 case OpenSysLog:
251 Debug("Opener: received OpenSysLog");
252 MY_ASSERT(len == sizeof(struct OpenLogRequest));
253 response.status = 0;
254 logFile = &kppp_syslog[0];
255 while (*logFile) {
256 if ((fd = open(*logFile, O_RDONLY)) >= 0)
257 break;
258 logFile++;
260 if (!*logFile) {
261 Debug("No success opening a syslog file !");
262 fd = open(DEVNULL, O_RDONLY);
263 response.status = -errno;
265 sendFD(fd, &response);
266 close(fd);
267 break;
269 case SetSecret:
270 Debug("Opener: received SetSecret");
271 MY_ASSERT(len == sizeof(struct SetSecretRequest));
272 response.status = !createAuthFile(request.secret.method,
273 request.secret.username,
274 request.secret.password);
275 sendResponse(&response);
276 break;
278 case RemoveSecret:
279 Debug("Opener: received RemoveSecret");
280 MY_ASSERT(len == sizeof(struct RemoveSecretRequest));
281 response.status = !removeAuthFile(request.remove.method);
282 sendResponse(&response);
283 break;
285 case SetHostname:
286 Debug("Opener: received SetHostname");
287 MY_ASSERT(len == sizeof(struct SetHostnameRequest));
288 response.status = 0;
289 if(sethostname(request.host.name, strlen(request.host.name)))
290 response.status = -errno;
291 sendResponse(&response);
292 break;
294 case ExecPPPDaemon:
295 Debug("Opener: received ExecPPPDaemon");
296 MY_ASSERT(len == sizeof(struct ExecDaemonRequest));
297 response.status = execpppd(request.daemon.arguments);
298 sendResponse(&response);
299 break;
301 case KillPPPDaemon:
302 Debug("Opener: received KillPPPDaemon");
303 MY_ASSERT(len == sizeof(struct KillDaemonRequest));
304 response.status = killpppd();
305 sendResponse(&response);
306 break;
308 case PPPDExitStatus:
309 Debug("Opener: received PPPDExitStatus");
310 MY_ASSERT(len == sizeof(struct PPPDExitStatusRequest));
311 response.status = pppdExitStatus;
312 sendResponse(&response);
313 break;
315 case Stop:
316 Debug("Opener: received STOP command");
317 _exit(0);
318 break;
320 default:
321 Debug("Opener: unknown command type. Exiting ...");
322 _exit(1);
324 } // else
330 // Send an open fd over a UNIX socket pair
332 int Opener::sendFD(int fd, struct ResponseHeader *response) {
334 struct { struct cmsghdr cmsg; int fd; } control;
335 struct msghdr msg;
336 struct iovec iov;
338 msg.msg_name = 0L;
339 msg.msg_namelen = 0;
340 msg.msg_iov = &iov;
341 msg.msg_iovlen = 1;
343 // Send data
344 iov.iov_base = IOV_BASE_CAST response;
345 iov.iov_len = sizeof(struct ResponseHeader);
347 // Send a (duplicate of) the file descriptor
348 control.cmsg.cmsg_len = sizeof(struct cmsghdr) + sizeof(int);
349 control.cmsg.cmsg_level = SOL_SOCKET;
350 control.cmsg.cmsg_type = MY_SCM_RIGHTS;
352 msg.msg_control = (char *) &control;
353 msg.msg_controllen = control.cmsg.cmsg_len;
355 #ifdef CMSG_DATA
356 *((int *)CMSG_DATA(&control.cmsg)) = fd;
357 #else
358 *((int *) &control.cmsg.cmsg_data) = fd;
359 #endif
361 if (sendmsg(socket, &msg, 0) < 0) {
362 perror("unable to send file descriptors");
363 return -1;
366 return 0;
369 int Opener::sendResponse(struct ResponseHeader *response) {
371 struct msghdr msg;
372 struct iovec iov;
374 msg.msg_name = 0L;
375 msg.msg_namelen = 0;
376 msg.msg_iov = &iov;
377 msg.msg_iovlen = 1;
378 msg.msg_control = 0L;
379 msg.msg_controllen = 0;
381 // Send data
382 iov.iov_base = IOV_BASE_CAST response;
383 iov.iov_len = sizeof(struct ResponseHeader);
385 if (sendmsg(socket, &msg, 0) < 0) {
386 perror("unable to send response");
387 return -1;
390 return 0;
393 const char* Opener::deviceByIndex(int idx) {
395 const char *device = 0L;
397 for(int i = 0; devices[i]; i++)
398 if(i == idx)
399 device = devices[i];
400 MY_ASSERT(device);
401 return device;
404 bool Opener::createAuthFile(Auth method, char *username, char *password) {
405 const char *authfile, *oldName, *newName;
406 char line[100];
407 char regexp[2*MaxStrLen+30];
408 regex_t preg;
410 if(!(authfile = authFile(method)))
411 return false;
413 if(!(newName = authFile(method, New)))
414 return false;
416 // look for username, "username" or 'username'
417 // if you modify this RE you have to adapt regexp's size above
418 snprintf(regexp, sizeof(regexp), "^[ \t]*%s[ \t]\\|^[ \t]*[\"\']%s[\"\']",
419 username,username);
420 MY_ASSERT(regcomp(&preg, regexp, 0) == 0);
422 // copy to new file pap- or chap-secrets
423 int old_umask = umask(0077);
424 FILE *fout = fopen(newName, "w");
425 if(fout) {
426 // copy old file
427 FILE *fin = fopen(authfile, "r");
428 if(fin) {
429 while(fgets(line, sizeof(line), fin)) {
430 if(regexec(&preg, line, 0, 0L, 0) == 0)
431 continue;
432 fputs(line, fout);
434 fclose(fin);
437 // append user/pass pair
438 fprintf(fout, "\"%s\"\t*\t\"%s\"\n", username, password);
439 fclose(fout);
442 // restore umask
443 umask(old_umask);
445 // free memory allocated by regcomp
446 regfree(&preg);
448 if(!(oldName = authFile(method, Old)))
449 return false;
451 // delete old file if any
452 unlink(oldName);
454 rename(authfile, oldName);
455 rename(newName, authfile);
457 return true;
461 bool Opener::removeAuthFile(Auth method) {
462 const char *authfile, *oldName;
464 if(!(authfile = authFile(method)))
465 return false;
466 if(!(oldName = authFile(method, Old)))
467 return false;
469 if(access(oldName, F_OK) == 0) {
470 unlink(authfile);
471 return (rename(oldName, authfile) == 0);
472 } else
473 return false;
477 const char* Opener::authFile(Auth method, int version) {
478 switch(method|version) {
479 case PAP|Original:
480 return PAP_AUTH_FILE;
481 break;
482 case PAP|New:
483 return PAP_AUTH_FILE".new";
484 break;
485 case PAP|Old:
486 return PAP_AUTH_FILE".old";
487 break;
488 case CHAP|Original:
489 return CHAP_AUTH_FILE;
490 break;
491 case CHAP|New:
492 return CHAP_AUTH_FILE".new";
493 break;
494 case CHAP|Old:
495 return CHAP_AUTH_FILE".old";
496 break;
497 default:
498 return 0L;
503 bool Opener::execpppd(const char *arguments) {
504 char buf[MAX_CMDLEN];
505 char *args[MaxArgs];
506 pid_t pgrpid;
508 if(ttyfd<0)
509 return false;
511 pppdExitStatus = -1;
513 switch(pppdPid = fork())
515 case -1:
516 fprintf(stderr,"In parent: fork() failed\n");
517 return false;
518 break;
520 case 0:
521 // let's parse the arguments the user supplied into UNIX suitable form
522 // that is a list of pointers each pointing to exactly one word
523 strlcpy(buf, arguments, sizeof(buf));
524 parseargs(buf, args);
525 // become a session leader and let /dev/ttySx
526 // be the controlling terminal.
527 pgrpid = setsid();
528 #ifdef TIOCSCTTY
529 if(ioctl(ttyfd, TIOCSCTTY, 0)<0)
530 fprintf(stderr, "ioctl() failed.\n");
531 #elif defined (TIOCSPGRP)
532 if(ioctl(ttyfd, TIOCSPGRP, &pgrpid)<0)
533 fprintf(stderr, "ioctl() failed.\n");
534 #endif
535 if(tcsetpgrp(ttyfd, pgrpid)<0)
536 fprintf(stderr, "tcsetpgrp() failed.\n");
538 dup2(ttyfd, 0);
539 dup2(ttyfd, 1);
541 switch (checkForInterface()) {
542 case 1:
543 fprintf(stderr, "Cannot determine if kernel supports ppp.\n");
544 break;
545 case -1:
546 fprintf(stderr, "Kernel does not support ppp, oops.\n");
547 break;
548 case 0:
549 fprintf(stderr, "Kernel supports ppp alright.\n");
550 break;
553 execve(pppdPath(), args, 0L);
554 _exit(0);
555 break;
557 default:
558 Debug2("In parent: pppd pid %d\n",pppdPid);
559 close(ttyfd);
560 ttyfd = -1;
561 return true;
562 break;
567 bool Opener::killpppd()const {
568 if(pppdPid > 0) {
569 Debug2("In killpppd(): Sending SIGTERM to %d\n", pppdPid);
570 if(kill(pppdPid, SIGTERM) < 0) {
571 Debug2("Error terminating %d. Sending SIGKILL\n", pppdPid);
572 if(kill(pppdPid, SIGKILL) < 0) {
573 Debug2("Error killing %d\n", pppdPid);
574 return false;
578 return true;
582 void Opener::parseargs(char* buf, char** args) {
583 int nargs = 0;
584 int quotes;
586 while(nargs < MaxArgs-1 && *buf != '\0') {
588 quotes = 0;
590 // Strip whitespace. Use nulls, so that the previous argument is
591 // terminated automatically.
593 while ((*buf == ' ' ) || (*buf == '\t' ) || (*buf == '\n' ) )
594 *buf++ = '\0';
596 // detect begin of quoted argument
597 if (*buf == '"' || *buf == '\'') {
598 quotes = *buf;
599 *buf++ = '\0';
602 // save the argument
603 if(*buf != '\0') {
604 *args++ = buf;
605 nargs++;
608 if (!quotes)
609 while ((*buf != '\0') && (*buf != '\n') &&
610 (*buf != '\t') && (*buf != ' '))
611 buf++;
612 else {
613 while ((*buf != '\0') && (*buf != quotes))
614 buf++;
615 *buf++ = '\0';
619 *args = 0L;
623 const char* pppdPath() {
624 // wasting a few bytes
625 static char buffer[sizeof(PPPDSEARCHPATH)+sizeof(PPPDNAME)];
626 static char *pppdPath = 0L;
627 char *p;
629 if(pppdPath == 0L) {
630 const char *c = PPPDSEARCHPATH;
631 while(*c != '\0') {
632 while(*c == ':')
633 c++;
634 p = buffer;
635 while(*c != '\0' && *c != ':')
636 *p++ = *c++;
637 *p = '\0';
638 strcat(p, "/");
639 strcat(p, PPPDNAME);
640 if(access(buffer, F_OK) == 0)
641 return (pppdPath = buffer);
645 return pppdPath;
648 int checkForInterface()
650 // I don't know if Linux needs more initialization to get the ioctl to
651 // work, pppd seems to hint it does. But BSD doesn't, and the following
652 // code should compile.
653 #if (defined(HAVE_NET_IF_PPP_H) || defined(HAVE_LINUX_IF_PPP_H)) && !defined(__svr4__)
654 int s, ok;
655 struct ifreq ifr;
656 // extern char *no_ppp_msg;
658 if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
659 return 1; /* can't tell */
661 strlcpy(ifr.ifr_name, "ppp0", sizeof (ifr.ifr_name));
662 ok = ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) >= 0;
663 close(s);
665 if (ok == -1) {
666 // This is ifdef'd FreeBSD, because FreeBSD is the only BSD that supports
667 // KLDs, the old LKM interface couldn't handle loading devices
668 // dynamically, and thus can't load ppp support on the fly
669 #ifdef __FreeBSD__
670 // If we failed to load ppp support and don't have it already.
671 if (kldload("if_ppp") == -1) {
672 return -1;
674 return 0;
675 #else
676 return -1;
677 #endif
679 return 0;
680 #else
681 // We attempt to use the SunOS/SysVr4 method and stat /dev/ppp
682 struct stat buf;
684 memset(&buf, 0, sizeof(buf));
685 return stat("/dev/ppp", &buf);
686 #endif
690 void sighandler_child(int) {
691 pid_t pid;
692 int status;
694 signal(SIGCHLD, sighandler_child);
695 if(pppdPid>0) {
696 pid = waitpid(pppdPid, &status, WNOHANG);
697 if(pid != pppdPid) {
698 fprintf(stderr, "received SIGCHLD from unknown origin.\n");
699 } else {
700 Debug("It was pppd that died");
701 pppdPid = -1;
702 if((WIFEXITED(status))) {
703 pppdExitStatus = (WEXITSTATUS(status));
704 Debug2("pppd exited with return value %d\n", pppdExitStatus);
705 } else {
706 pppdExitStatus = 99;
707 Debug("pppd exited abnormally.");
709 Debug2("Sending %i a SIGUSR1\n", getppid());
710 kill(getppid(), SIGUSR1);
712 } else
713 fprintf(stderr, "received unexpected SIGCHLD.\n");