1/*
2 * kPPP: A pppd front end for the KDE project
3 *
4 * Copyright (C) 1997 Bernd Johannes Wuebben
5 * wuebben@math.cornell.edu
6 * Copyright (C) 1998-2002 Harri Porten <porten@kde.org>
7 *
8 * based on EzPPP:
9 * Copyright (C) 1997 Jay Painter
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
20 *
21 * You should have received a copy of the GNU Library General Public
22 * License along with this program; if not, write to the Free
23 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 */
25
26#include <config-kppp.h>
27
28#include "main.h"
29
30#include <unistd.h>
31#include <stdio.h>
32#include <sys/stat.h>
33#include <sys/socket.h>
34#include <locale.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <stdlib.h>
38
39#ifdef _XPG4_2
40#define __xnet_connect connect
41#endif
42
43#ifdef HAVE_SYS_PARAM_H
44#include <sys/param.h>
45#endif
46
47#include <kaboutdata.h>
48#include <kapplication.h>
49#include <kcmdlineargs.h>
50#include <kdebug.h>
51#include <kglobal.h>
52#include <klocale.h>
53#include <kmessagebox.h>
54#include <kstandarddirs.h>
55
56#include "kpppwidget.h"
57#include "opener.h"
58#include "pppdata.h"
59#include "providerdb.h"
60#include "version.h"
61#include "requester.h"
62
63//#include <X11/Xlib.h>
64//Added by qt3to4:
65#include <QEvent>
66
67static const char description[] =
68 I18N_NOOP("A dialer and front-end to pppd");
69
70
71KPPPWidget* p_kppp;
72
73// for testing purposes
74bool TESTING=0;
75
76// initial effective user id before possible suid status is dropped
77uid_t euid;
78// helper process' pid
79pid_t helperPid = -1;
80
81QString local_ip_address;
82QString remote_ip_address;
83QString pidfile;
84
85#if 0
86extern "C" {
87 static int kppp_x_errhandler( Display *dpy, XErrorEvent *err ) {
88 char errstr[256]; // safe
89
90 /*
91 if(gpppdata.pppdpid() >= 0) {
92 kill(gpppdata.pppdpid(), SIGTERM);
93 }
94
95 p_kppp->stopAccounting();
96 removedns();
97 unlockdevice();*/
98
99 XGetErrorText( dpy, err->error_code, errstr, 256 );
100 kFatal() << "X Error: " << errstr;
101 kFatal() << "Major opcode: " << err->request_code;
102 exit(256);
103 return 0;
104 }
105
106
107 static int kppp_xio_errhandler( Display * ) {
108 if(gpppdata.get_xserver_exit_disconnect()) {
109 fprintf(stderr, "X11 Error!\n");
110 if(gpppdata.pppdRunning())
111 Requester::rq->killPPPDaemon();
112
113 p_kppp->stopAccounting();
114 removedns();
115 Modem::modem->unlockdevice();
116 return 0;
117 } else{
118 kFatal() << "Fatal IO error: client killed";
119 exit(256);
120 return 0;
121 }
122 }
123} /* extern "C" */
124#endif
125
126int main( int argc, char **argv ) {
127 // make sure that open/fopen and so on NEVER return 1 or 2 (stdout and stderr)
128 // Expl: if stdout/stderr were closed on program start (by parent), open()
129 // would return a FD of 1, 2 (or even 0 if stdin was closed too)
130 if(fcntl(0, F_GETFL) == -1)
131 (void)open("/dev/null", O_RDONLY);
132
133 if(fcntl(1, F_GETFL) == -1)
134 (void)open("/dev/null", O_WRONLY);
135
136 if(fcntl(2, F_GETFL) == -1)
137 (void)open("/dev/null", O_WRONLY);
138
139 // Don't insert anything above this line unless you really know what
140 // you're doing. We're most likely running setuid root here,
141 // until we drop this status a few lines below.
142 int sockets[2];
143 if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) != 0) {
144 fprintf(stderr, "error creating socketpair !\n");
145 return 1;
146 }
147
148 switch(helperPid = fork()) {
149 case 0:
150 // child process
151 // make process leader of new group
152 setsid();
153 umask(0);
154 close(sockets[0]);
155 signal(SIGHUP, SIG_IGN);
156 (void) new Opener(sockets[1]);
157 // we should never get here
158 _exit(1);
159
160 case -1:
161 perror("fork() failed");
162 exit(1);
163 }
164
165 // parent process
166 close(sockets[1]);
167
168 // drop setuid status
169 euid = geteuid();
170 if (setgid(getgid()) < 0 && errno != EPERM) {
171 perror("setgid() failed");
172 exit(1);
173 }
174 setuid(getuid());
175 if (geteuid() != getuid()) {
176 perror("setuid() failed");
177 exit(1);
178 }
179
180 //
181 // end of setuid-dropping block.
182 //
183
184 // install exit handler that will kill the helper process
185 atexit(myShutDown);
186
187 // not needed anymore, just causes problems with broken setup
188 // if(getHomeDir() != 0)
189 // setenv("HOME", getHomeDir(), 1);
190
191 (void) new Requester(sockets[0]);
192
193 KAboutData aboutData("kppp", 0, ki18n("KPPP"),
194 KPPPVERSION, ki18n(description), KAboutData::License_GPL,
195 ki18n("(c) 1999-2002, The KPPP Developers"));
196 aboutData.addAuthor(ki18n("Harri Porten"), ki18n("Current maintainer"), "porten@kde.org");
197 aboutData.addAuthor(ki18n("Bernd Wuebben"), ki18n("Original author"), "wuebben@kde.org");
198 aboutData.addAuthor(ki18n("Mario Weilguni"));
199
200 KCmdLineArgs::init( argc, argv, &aboutData );
201
202 KCmdLineOptions options;
203 options.add("c <account_name>", ki18n("Connect using 'account_name'"));
204 options.add("m <modem_name>", ki18n("Connect using 'modem_name'"));
205 options.add("k", ki18n("Terminate an existing connection"));
206 options.add("q", ki18n("Quit after end of connection"));
207 options.add("r <rule_file>", ki18n("Check syntax of rule_file"));
208 options.add("T", ki18n("Enable test-mode"));
209 options.add("dev <device_name>", ki18n("Use the specified device"));
210 KCmdLineArgs::addCmdLineOptions( options );
211
212
213
214 KApplication a;
215
216 // set portable locale for decimal point
217 setlocale(LC_NUMERIC ,"C");
218
219 // open configuration file
220 gpppdata.open();
221
222 kDebug(5002) << "helperPid: " << (int) helperPid;
223
224 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
225
226 bool terminate_connection = args->isSet("k");
227 if (args->isSet("r"))
228 return RuleSet::checkRuleFile(args->getOption("r"));
229
230 TESTING = args->isSet("T");
231
232 // make sure that nobody can read the password from the
233 // config file
234 QString configFile = KGlobal::dirs()->saveLocation("config")
235 + kapp->objectName() + "rc";
236 if(access(QFile::encodeName(configFile), F_OK) == 0)
237 chmod(QFile::encodeName(configFile), S_IRUSR | S_IWUSR);
238
239 // do we really need to generate an empty directory structure here ?
240 KGlobal::dirs()->saveLocation("appdata", "Rules");
241
242 int pid = create_pidfile();
243 QString err_msg = i18n("kppp can not create or read from\n%1.", pidfile);
244
245 if(pid < 0) {
246 KMessageBox::error(0L, err_msg);
247 return 1;
248 }
249
250 if (terminate_connection) {
251 setgid(getgid());
252 setuid(getuid());
253 if (pid > 0)
254 kill(pid, SIGINT);
255 else
256 remove_pidfile();
257 return 0;
258 }
259
260 // Mario: testing
261 if(TESTING) {
262 gpppdata.open();
263 gpppdata.setAccountByIndex(0);
264
265 QString s = argv[2];
266 urlEncode(s);
267 kDebug(5002) << s;
268
269 remove_pidfile();
270 return 0;
271 }
272
273 if (pid > 0) {
274 QString msg = i18n("kppp has detected a %1 file.\n"
275 "Another instance of kppp seems to be "
276 "running under process-ID %2.\n"
277 "Please click Exit, make sure that you are "
278 "not running another kppp, delete the pid "
279 "file, and restart kppp.\n"
280 "Alternatively, if you have determined that "
281 "there is no other kppp running, please "
282 "click Continue to begin.",
283 pidfile, pid);
284 int button = KMessageBox::warningYesNo(0, msg, i18n("Error"),
285 KGuiItem(i18n("Exit")), KStandardGuiItem::cont());
286 if (button == KMessageBox::Yes) /* exit */
287 return 1;
288
289 remove_pidfile();
290 pid = create_pidfile();
291 if(pid) {
292 KMessageBox::error(0L, err_msg);
293 return 1;
294 }
295 }
296
297 KPPPWidget kppp;
298 p_kppp = &kppp;
299
300 (void)new DockWidget(p_kppp->con_win, "dockw", p_kppp->stats);
301
302 // we really don't want to die accidentally, since that would leave the
303 // modem connected. If you really really want to kill me you must send
304 // me a SIGKILL.
305 signal(SIGINT, sighandler);
306 signal(SIGCHLD, sighandler);
307 signal(SIGUSR1, sighandler);
308 signal(SIGTERM, SIG_IGN);
309
310 // XSetErrorHandler( kppp_x_errhandler );
311 // XSetIOErrorHandler( kppp_xio_errhandler );
312
313 int ret = a.exec();
314
315 remove_pidfile();
316
317 return ret;
318}
319
320
321pid_t execute_command (const QString & cmd) {
322 QByteArray command = QFile::encodeName(cmd);
323 if (command.isEmpty() || command.length() > COMMAND_SIZE)
324 return (pid_t) -1;
325
326 pid_t id;
327
328 kDebug(5002) << "Executing command: " << command;
329
330 QApplication::flush();
331 if((id = fork()) == 0) {
332 // don't bother dieppp()
333 signal(SIGCHLD, SIG_IGN);
334
335 // close file descriptors
336 const int open_max = sysconf( _SC_OPEN_MAX );
337 for (int fd = 3; fd < open_max; ++fd)
338 close(fd);
339
340 // drop privileges if running setuid root
341 setgid(getgid());
342 setuid(getuid());
343
344 system(command);
345 _exit(0);
346 }
347
348 return id;
349}
350
351
352// Create a file containing the current pid. Returns 0 on success,
353// -1 on failure or the pid of an already running kppp process.
354pid_t create_pidfile() {
355 int fd = -1;
356 char pidstr[40]; // safe
357
358 pidfile = KGlobal::dirs()->saveLocation("appdata") + "kppp.pid";
359
360 if(access(QFile::encodeName(pidfile), F_OK) == 0) {
361
362 if((access(QFile::encodeName(pidfile), R_OK) < 0) ||
363 (fd = open(QFile::encodeName(pidfile), O_RDONLY)) < 0)
364 return -1;
365
366 int sz = read(fd, &pidstr, 32);
367 close (fd);
368 if (sz < 0)
369 return -1;
370 pidstr[sz] = '\0';
371
372 kDebug(5002) << "found kppp.pid containing: " << pidstr;
373
374 // non-empty file ?
375 if (sz > 0) {
376 int oldpid;
377 int match = sscanf(pidstr, "%d", &oldpid);
378
379 // found a pid in pidfile ?
380 if (match < 1 || oldpid <= 0)
381 return -1;
382
383 // check if process exists
384 if (kill((pid_t)oldpid, 0) == 0 || errno != ESRCH)
385 return oldpid;
386 }
387
388 kDebug(5002) << "pidfile is stale\n";
389 remove_pidfile();
390 }
391
392 if((fd = open(QFile::encodeName(pidfile), O_WRONLY | O_CREAT | O_EXCL,
393 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
394 return -1;
395
396 fchown(fd, getuid(), getgid());
397
398 sprintf(pidstr, "%d\n", getpid());
399 write(fd, pidstr, strlen(pidstr));
400 close(fd);
401
402 return 0;
403}
404
405
406bool remove_pidfile() {
407 struct stat st;
408
409 // only remove regular files with user write permissions
410 if(stat(QFile::encodeName(pidfile), &st) == 0 )
411 if(S_ISREG(st.st_mode) && (access(QFile::encodeName(pidfile), W_OK) == 0)) {
412 unlink(QFile::encodeName(pidfile));
413 return true;
414 }
415
416 fprintf(stderr, "error removing pidfile.\n");
417 return false;
418}
419
420
421void myShutDown() {
422 pid_t pid;
423 // don't bother about SIGCHLDs anymore
424 signal(SIGCHLD, SIG_IGN);
425 // fprintf(stderr, "myShutDown(%i)\n", status);
426 pid = helperPid;
427 if(pid > 0) {
428 helperPid = -1;
429 // fprintf(stderr, "killing child process %i", pid);
430 kill(pid, SIGKILL);
431 }
432}
433
434void sighandler(int sig) {
435 QEvent *e = 0L;
436 if(sig == SIGCHLD) {
437 pid_t id = wait(0L);
438 if(id >= 0 && id == helperPid) // helper process died
439 e = new SignalEvent(sig);
440 } else if(sig == SIGINT || sig == SIGUSR1)
441 e = new SignalEvent(sig);
442
443 // let eventFilter() deal with this when we're back in the loop
444 if (e)
445 QApplication::postEvent(p_kppp, e);
446
447 signal(sig, sighandler); // reinstall signal handler
448}
449
450