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
50extern int kdesuDebugArea();
51
52namespace KDESu {
53
54using 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*/
62int 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*/
81bool 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
102int 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
123class PtyProcess::PtyProcessPrivate
124{
125public:
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
137PtyProcess::PtyProcess()
138 :d(new PtyProcessPrivate)
139{
140 m_bTerminal = false;
141 m_bErase = false;
142}
143
144
145int 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
159PtyProcess::~PtyProcess()
160{
161 delete d;
162}
163
164/** Set additional environment variables. */
165void PtyProcess::setEnvironment( const QList<QByteArray> &env )
166{
167 d->env = env;
168}
169
170int PtyProcess::fd() const
171{
172 return d->m_pPTY ? d->m_pPTY->masterFd() : -1;
173}
174
175int PtyProcess::pid() const
176{
177 return m_Pid;
178}
179
180/** Returns the additional environment variables set by setEnvironment() */
181QList<QByteArray> PtyProcess::environment() const
182{
183 return d->env;
184}
185
186
187QByteArray 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
239QByteArray 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
264void 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
273void 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
282void 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
291int 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
377int 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
406int PtyProcess::enableLocalEcho(bool enable)
407{
408 return d->m_pPTY->setEcho(enable) ? 0 : -1;
409}
410
411
412void PtyProcess::setTerminal(bool terminal)
413{
414 m_bTerminal = terminal;
415}
416
417void 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
430int 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
515int 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
554void PtyProcess::virtual_hook( int, void* )
555{ /*BASE::virtual_hook( id, data );*/ }
556
557}
558