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 | |
67 | static const char description[] = |
68 | I18N_NOOP("A dialer and front-end to pppd" ); |
69 | |
70 | |
71 | KPPPWidget* p_kppp; |
72 | |
73 | // for testing purposes |
74 | bool TESTING=0; |
75 | |
76 | // initial effective user id before possible suid status is dropped |
77 | uid_t euid; |
78 | // helper process' pid |
79 | pid_t helperPid = -1; |
80 | |
81 | QString local_ip_address; |
82 | QString remote_ip_address; |
83 | QString pidfile; |
84 | |
85 | #if 0 |
86 | extern "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 | |
126 | int 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 | |
321 | pid_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. |
354 | pid_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 | |
406 | bool 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 | |
421 | void 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 | |
434 | void 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 | |