1 | /* vi: ts=8 sts=4 sw=4 |
2 | * |
3 | * This file is part of the KDE project, module kdesu. |
4 | * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> |
5 | * |
6 | * This file contains code from TEShell.C of the KDE konsole. |
7 | * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> |
8 | * |
9 | * This is free software; you can use this library under the GNU Library |
10 | * General Public License, version 2. See the file "COPYING.LIB" for the |
11 | * exact licensing terms. |
12 | * |
13 | * process.cpp: Functionality to build a front end to password asking |
14 | * terminal programs. |
15 | */ |
16 | |
17 | #include "process.h" |
18 | #include "kcookie.h" |
19 | |
20 | #include <config.h> |
21 | |
22 | #include <stdio.h> |
23 | #include <stdlib.h> |
24 | #include <unistd.h> |
25 | #include <fcntl.h> |
26 | #include <signal.h> |
27 | #include <errno.h> |
28 | #include <string.h> |
29 | #include <termios.h> |
30 | |
31 | #include <sys/types.h> |
32 | #include <sys/wait.h> |
33 | #include <sys/stat.h> |
34 | #include <sys/time.h> |
35 | #include <sys/resource.h> |
36 | |
37 | #ifdef HAVE_SYS_SELECT_H |
38 | #include <sys/select.h> // Needed on some systems. |
39 | #endif |
40 | |
41 | #include <QtCore/QBool> |
42 | #include <QtCore/QFile> |
43 | |
44 | #include <ksharedconfig.h> |
45 | #include <kconfiggroup.h> |
46 | #include <kdebug.h> |
47 | #include <kstandarddirs.h> |
48 | #include <kde_file.h> |
49 | |
50 | extern int kdesuDebugArea(); |
51 | |
52 | namespace KDESu { |
53 | |
54 | using namespace KDESuPrivate; |
55 | |
56 | /* |
57 | ** Wait for @p ms miliseconds |
58 | ** @param fd file descriptor |
59 | ** @param ms time to wait in miliseconds |
60 | ** @return |
61 | */ |
62 | int PtyProcess::waitMS(int fd,int ms) |
63 | { |
64 | struct timeval tv; |
65 | tv.tv_sec = 0; |
66 | tv.tv_usec = 1000*ms; |
67 | |
68 | fd_set fds; |
69 | FD_ZERO(&fds); |
70 | FD_SET(fd,&fds); |
71 | return select(fd+1, &fds, 0L, 0L, &tv); |
72 | } |
73 | |
74 | // XXX this function is nonsense: |
75 | // - for our child, we could use waitpid(). |
76 | // - the configurability at this place it *complete* braindamage |
77 | /* |
78 | ** Basic check for the existence of @p pid. |
79 | ** Returns true iff @p pid is an extant process. |
80 | */ |
81 | bool PtyProcess::checkPid(pid_t pid) |
82 | { |
83 | KSharedConfig::Ptr config = KGlobal::config(); |
84 | KConfigGroup cg(config, "super-user-command" ); |
85 | QString superUserCommand = cg.readEntry("super-user-command" , "sudo" ); |
86 | //sudo does not accept signals from user so we except it |
87 | if (superUserCommand == "sudo" ) { |
88 | return true; |
89 | } else { |
90 | return kill(pid, 0) == 0; |
91 | } |
92 | } |
93 | |
94 | /* |
95 | ** Check process exit status for process @p pid. |
96 | ** On error (no child, no exit), return Error (-1). |
97 | ** If child @p pid has exited, return its exit status, |
98 | ** (which may be zero). |
99 | ** If child @p has not exited, return NotExited (-2). |
100 | */ |
101 | |
102 | int PtyProcess::checkPidExited(pid_t pid) |
103 | { |
104 | int state, ret; |
105 | ret = waitpid(pid, &state, WNOHANG); |
106 | |
107 | if (ret < 0) |
108 | { |
109 | kError(kdesuDebugArea()) << k_lineinfo << "waitpid():" << perror; |
110 | return Error; |
111 | } |
112 | if (ret == pid) |
113 | { |
114 | if (WIFEXITED(state)) |
115 | return WEXITSTATUS(state); |
116 | return Killed; |
117 | } |
118 | |
119 | return NotExited; |
120 | } |
121 | |
122 | |
123 | class PtyProcess::PtyProcessPrivate |
124 | { |
125 | public: |
126 | PtyProcessPrivate() : m_pPTY(0L) {} |
127 | ~PtyProcessPrivate() |
128 | { |
129 | delete m_pPTY; |
130 | } |
131 | QList<QByteArray> env; |
132 | KPty *m_pPTY; |
133 | QByteArray m_Inbuf; |
134 | }; |
135 | |
136 | |
137 | PtyProcess::PtyProcess() |
138 | :d(new PtyProcessPrivate) |
139 | { |
140 | m_bTerminal = false; |
141 | m_bErase = false; |
142 | } |
143 | |
144 | |
145 | int PtyProcess::init() |
146 | { |
147 | delete d->m_pPTY; |
148 | d->m_pPTY = new KPty(); |
149 | if (!d->m_pPTY->open()) |
150 | { |
151 | kError(kdesuDebugArea()) << k_lineinfo << "Failed to open PTY." ; |
152 | return -1; |
153 | } |
154 | d->m_Inbuf.resize(0); |
155 | return 0; |
156 | } |
157 | |
158 | |
159 | PtyProcess::~PtyProcess() |
160 | { |
161 | delete d; |
162 | } |
163 | |
164 | /** Set additional environment variables. */ |
165 | void PtyProcess::setEnvironment( const QList<QByteArray> &env ) |
166 | { |
167 | d->env = env; |
168 | } |
169 | |
170 | int PtyProcess::fd() const |
171 | { |
172 | return d->m_pPTY ? d->m_pPTY->masterFd() : -1; |
173 | } |
174 | |
175 | int PtyProcess::pid() const |
176 | { |
177 | return m_Pid; |
178 | } |
179 | |
180 | /** Returns the additional environment variables set by setEnvironment() */ |
181 | QList<QByteArray> PtyProcess::environment() const |
182 | { |
183 | return d->env; |
184 | } |
185 | |
186 | |
187 | QByteArray PtyProcess::readAll(bool block) |
188 | { |
189 | QByteArray ret; |
190 | if (!d->m_Inbuf.isEmpty()) |
191 | { |
192 | // if there is still something in the buffer, we need not block. |
193 | // we should still try to read any further output, from the fd, though. |
194 | block = false; |
195 | ret = d->m_Inbuf; |
196 | d->m_Inbuf.resize(0); |
197 | } |
198 | |
199 | int flags = fcntl(fd(), F_GETFL); |
200 | if (flags < 0) |
201 | { |
202 | kError(kdesuDebugArea()) << k_lineinfo << "fcntl(F_GETFL):" << perror; |
203 | return ret; |
204 | } |
205 | int oflags = flags; |
206 | if (block) |
207 | flags &= ~O_NONBLOCK; |
208 | else |
209 | flags |= O_NONBLOCK; |
210 | |
211 | if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0)) |
212 | { |
213 | // We get an error here when the child process has closed |
214 | // the file descriptor already. |
215 | return ret; |
216 | } |
217 | |
218 | while (1) |
219 | { |
220 | ret.reserve(ret.size() + 0x8000); |
221 | int nbytes = read(fd(), ret.data() + ret.size(), 0x8000); |
222 | if (nbytes == -1) |
223 | { |
224 | if (errno == EINTR) |
225 | continue; |
226 | else break; |
227 | } |
228 | if (nbytes == 0) |
229 | break; // nothing available / eof |
230 | |
231 | ret.resize(ret.size() + nbytes); |
232 | break; |
233 | } |
234 | |
235 | return ret; |
236 | } |
237 | |
238 | |
239 | QByteArray PtyProcess::readLine(bool block) |
240 | { |
241 | d->m_Inbuf = readAll(block); |
242 | |
243 | int pos; |
244 | QByteArray ret; |
245 | if (!d->m_Inbuf.isEmpty()) |
246 | { |
247 | pos = d->m_Inbuf.indexOf('\n'); |
248 | if (pos == -1) |
249 | { |
250 | // NOTE: this means we return something even if there in no full line! |
251 | ret = d->m_Inbuf; |
252 | d->m_Inbuf.resize(0); |
253 | } else |
254 | { |
255 | ret = d->m_Inbuf.left(pos); |
256 | d->m_Inbuf.remove(0, pos+1); |
257 | } |
258 | } |
259 | |
260 | return ret; |
261 | } |
262 | |
263 | |
264 | void PtyProcess::writeLine(const QByteArray &line, bool addnl) |
265 | { |
266 | if (!line.isEmpty()) |
267 | write(fd(), line, line.length()); |
268 | if (addnl) |
269 | write(fd(), "\n" , 1); |
270 | } |
271 | |
272 | |
273 | void PtyProcess::unreadLine(const QByteArray &line, bool addnl) |
274 | { |
275 | QByteArray tmp = line; |
276 | if (addnl) |
277 | tmp += '\n'; |
278 | if (!tmp.isEmpty()) |
279 | d->m_Inbuf.prepend(tmp); |
280 | } |
281 | |
282 | void PtyProcess::setExitString(const QByteArray &exit) |
283 | { |
284 | m_Exit = exit; |
285 | } |
286 | |
287 | /* |
288 | * Fork and execute the command. This returns in the parent. |
289 | */ |
290 | |
291 | int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args) |
292 | { |
293 | kDebug(kdesuDebugArea()) << k_lineinfo << "Running" << command; |
294 | int i; |
295 | |
296 | if (init() < 0) |
297 | return -1; |
298 | |
299 | if ((m_Pid = fork()) == -1) |
300 | { |
301 | kError(kdesuDebugArea()) << k_lineinfo << "fork():" << perror; |
302 | return -1; |
303 | } |
304 | |
305 | // Parent |
306 | if (m_Pid) |
307 | { |
308 | d->m_pPTY->closeSlave(); |
309 | return 0; |
310 | } |
311 | |
312 | // Child |
313 | if (setupTTY() < 0) |
314 | _exit(1); |
315 | |
316 | for (i = 0; i < d->env.count(); ++i) |
317 | { |
318 | putenv(const_cast<char *>(d->env.at(i).constData())); |
319 | } |
320 | unsetenv("KDE_FULL_SESSION" ); |
321 | // for : Qt: Session management error |
322 | unsetenv("SESSION_MANAGER" ); |
323 | // QMutex::lock , deadlocks without that. |
324 | // <thiago> you cannot connect to the user's session bus from another UID |
325 | unsetenv("DBUS_SESSION_BUS_ADDRESS" ); |
326 | |
327 | // set temporarily LC_ALL to C, for su (to be able to parse "Password:") |
328 | const QByteArray old_lc_all = qgetenv( "LC_ALL" ); |
329 | if( !old_lc_all.isEmpty() ) |
330 | qputenv( "KDESU_LC_ALL" , old_lc_all ); |
331 | else |
332 | unsetenv( "KDESU_LC_ALL" ); |
333 | qputenv("LC_ALL" , "C" ); |
334 | |
335 | // From now on, terminal output goes through the tty. |
336 | |
337 | QByteArray path; |
338 | if (command.contains('/')) |
339 | path = command; |
340 | else |
341 | { |
342 | QString file = KStandardDirs::findExe(command); |
343 | if (file.isEmpty()) |
344 | { |
345 | kError(kdesuDebugArea()) << k_lineinfo << command << "not found." ; |
346 | _exit(1); |
347 | } |
348 | path = QFile::encodeName(file); |
349 | } |
350 | |
351 | const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *)); |
352 | |
353 | i = 0; |
354 | argp[i++] = path; |
355 | for (QList<QByteArray>::ConstIterator it=args.begin(); it!=args.end(); ++it, ++i) |
356 | argp[i] = *it; |
357 | |
358 | argp[i] = NULL; |
359 | |
360 | execv(path, const_cast<char **>(argp)); |
361 | kError(kdesuDebugArea()) << k_lineinfo << "execv(" << path << "):" << perror; |
362 | _exit(1); |
363 | return -1; // Shut up compiler. Never reached. |
364 | } |
365 | |
366 | |
367 | /* |
368 | * Wait until the terminal is set into no echo mode. At least one su |
369 | * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password: |
370 | * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly |
371 | * taking the password with it. So we wait until no echo mode is set |
372 | * before writing the password. |
373 | * Note that this is done on the slave fd. While Linux allows tcgetattr() on |
374 | * the master side, Solaris doesn't. |
375 | */ |
376 | |
377 | int PtyProcess::WaitSlave() |
378 | { |
379 | kDebug(kdesuDebugArea()) << k_lineinfo << "Child pid" << m_Pid; |
380 | |
381 | struct termios tio; |
382 | while (1) |
383 | { |
384 | if (!checkPid(m_Pid)) |
385 | { |
386 | kError(kdesuDebugArea()) << "process has exited while waiting for password." ; |
387 | return -1; |
388 | } |
389 | if (!d->m_pPTY->tcGetAttr(&tio)) |
390 | { |
391 | kError(kdesuDebugArea()) << k_lineinfo << "tcgetattr():" << perror; |
392 | return -1; |
393 | } |
394 | if (tio.c_lflag & ECHO) |
395 | { |
396 | kDebug(kdesuDebugArea()) << k_lineinfo << "Echo mode still on." ; |
397 | usleep(10000); |
398 | continue; |
399 | } |
400 | break; |
401 | } |
402 | return 0; |
403 | } |
404 | |
405 | |
406 | int PtyProcess::enableLocalEcho(bool enable) |
407 | { |
408 | return d->m_pPTY->setEcho(enable) ? 0 : -1; |
409 | } |
410 | |
411 | |
412 | void PtyProcess::setTerminal(bool terminal) |
413 | { |
414 | m_bTerminal = terminal; |
415 | } |
416 | |
417 | void PtyProcess::setErase(bool erase) |
418 | { |
419 | m_bErase = erase; |
420 | } |
421 | |
422 | /* |
423 | * Copy output to stdout until the child process exits, or a line of output |
424 | * matches `m_Exit'. |
425 | * We have to use waitpid() to test for exit. Merely waiting for EOF on the |
426 | * pty does not work, because the target process may have children still |
427 | * attached to the terminal. |
428 | */ |
429 | |
430 | int PtyProcess::waitForChild() |
431 | { |
432 | fd_set fds; |
433 | FD_ZERO(&fds); |
434 | QByteArray remainder; |
435 | |
436 | while (1) |
437 | { |
438 | FD_SET(fd(), &fds); |
439 | |
440 | // specify timeout to make sure select() does not block, even if the |
441 | // process is dead / non-responsive. It does not matter if we abort too |
442 | // early. In that case 0 is returned, and we'll try again in the next |
443 | // iteration. (As long as we don't consitently time out in each iteration) |
444 | timeval timeout; |
445 | timeout.tv_sec = 0; |
446 | timeout.tv_usec = 100000; |
447 | int ret = select(fd()+1, &fds, 0L, 0L, &timeout); |
448 | if (ret == -1) |
449 | { |
450 | if (errno != EINTR) |
451 | { |
452 | kError(kdesuDebugArea()) << k_lineinfo << "select():" << perror; |
453 | return -1; |
454 | } |
455 | ret = 0; |
456 | } |
457 | |
458 | if (ret) |
459 | { |
460 | forever { |
461 | QByteArray output = readAll(false); |
462 | if (output.isEmpty()) |
463 | break; |
464 | if (m_bTerminal) |
465 | { |
466 | fwrite(output.constData(), output.size(), 1, stdout); |
467 | fflush(stdout); |
468 | } |
469 | if (!m_Exit.isEmpty()) |
470 | { |
471 | // match exit string only at line starts |
472 | remainder += output; |
473 | while (remainder.length() >= m_Exit.length()) { |
474 | if (remainder.startsWith(m_Exit)) { |
475 | kill(m_Pid, SIGTERM); |
476 | remainder.remove(0, m_Exit.length()); |
477 | } |
478 | int off = remainder.indexOf('\n'); |
479 | if (off < 0) |
480 | break; |
481 | remainder.remove(0, off + 1); |
482 | } |
483 | } |
484 | } |
485 | } |
486 | |
487 | ret = checkPidExited(m_Pid); |
488 | if (ret == Error) |
489 | { |
490 | if (errno == ECHILD) return 0; |
491 | else return 1; |
492 | } |
493 | else if (ret == Killed) |
494 | { |
495 | return 0; |
496 | } |
497 | else if (ret == NotExited) |
498 | { |
499 | // keep checking |
500 | } |
501 | else |
502 | { |
503 | return ret; |
504 | } |
505 | } |
506 | } |
507 | |
508 | /* |
509 | * SetupTTY: Creates a new session. The filedescriptor "fd" should be |
510 | * connected to the tty. It is closed after the tty is reopened to make it |
511 | * our controlling terminal. This way the tty is always opened at least once |
512 | * so we'll never get EIO when reading from it. |
513 | */ |
514 | |
515 | int PtyProcess::setupTTY() |
516 | { |
517 | // Reset signal handlers |
518 | for (int sig = 1; sig < NSIG; sig++) |
519 | KDE_signal(sig, SIG_DFL); |
520 | KDE_signal(SIGHUP, SIG_IGN); |
521 | |
522 | d->m_pPTY->setCTty(); |
523 | |
524 | // Connect stdin, stdout and stderr |
525 | int slave = d->m_pPTY->slaveFd(); |
526 | dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); |
527 | |
528 | // Close all file handles |
529 | // XXX this caused problems in KProcess - not sure why anymore. -- ??? |
530 | // Because it will close the start notification pipe. -- ossi |
531 | struct rlimit rlp; |
532 | getrlimit(RLIMIT_NOFILE, &rlp); |
533 | for (int i = 3; i < (int)rlp.rlim_cur; i++) |
534 | close(i); |
535 | |
536 | // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) |
537 | // translated to '\r\n'. |
538 | struct ::termios tio; |
539 | if (tcgetattr(0, &tio) < 0) |
540 | { |
541 | kError(kdesuDebugArea()) << k_lineinfo << "tcgetattr():" << perror; |
542 | return -1; |
543 | } |
544 | tio.c_oflag &= ~OPOST; |
545 | if (tcsetattr(0, TCSANOW, &tio) < 0) |
546 | { |
547 | kError(kdesuDebugArea()) << k_lineinfo << "tcsetattr():" << perror; |
548 | return -1; |
549 | } |
550 | |
551 | return 0; |
552 | } |
553 | |
554 | void PtyProcess::virtual_hook( int, void* ) |
555 | { /*BASE::virtual_hook( id, data );*/ } |
556 | |
557 | } |
558 | |