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* Sudo support added by Jonathan Riddell <jriddell@ ubuntu.com>
7* Copyright (C) 2005 Canonical Ltd // krazy:exclude=copyright (no email)
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* su.cpp: Execute a program as another user with "class SuProcess".
14*/
15
16#include "su.h"
17#include "kcookie.h"
18
19#include <config.h>
20#include <config-prefix.h> // for LIBEXEC_INSTALL_DIR
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <unistd.h>
25#include <fcntl.h>
26#include <errno.h>
27#include <string.h>
28#include <ctype.h>
29#include <signal.h>
30
31#include <sys/types.h>
32#include <sys/stat.h>
33
34#include <QtCore/QFile>
35
36#include <kconfig.h>
37#include <kconfiggroup.h>
38#include <kdebug.h>
39#include <klocale.h>
40#include <kstandarddirs.h>
41#include <kuser.h>
42
43int kdesuDebugArea()
44{
45 static int s_area = KDebug::registerArea("kdesu (kdelibs)");
46 return s_area;
47}
48
49#ifndef __PATH_SU
50#define __PATH_SU "false"
51#endif
52
53#ifndef __PATH_SUDO
54#define __PATH_SUDO "false"
55#endif
56
57#ifdef KDESU_USE_SUDO_DEFAULT
58# define DEFAULT_SUPER_USER_COMMAND "sudo"
59#else
60# define DEFAULT_SUPER_USER_COMMAND "su"
61#endif
62
63namespace KDESu {
64using namespace KDESuPrivate;
65
66class SuProcess::SuProcessPrivate
67{
68public:
69 QString m_superUserCommand;
70};
71
72SuProcess::SuProcess(const QByteArray &user, const QByteArray &command)
73 : d( new SuProcessPrivate )
74{
75 m_User = user;
76 m_Command = command;
77
78 KSharedConfig::Ptr config = KGlobal::config();
79 KConfigGroup group(config, "super-user-command");
80 d->m_superUserCommand = group.readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND);
81
82 if ( d->m_superUserCommand != "sudo" && d->m_superUserCommand != "su" ) {
83 kWarning() << "unknown super user command.";
84 d->m_superUserCommand = DEFAULT_SUPER_USER_COMMAND;
85 }
86}
87
88
89SuProcess::~SuProcess()
90{
91 delete d;
92}
93
94QString SuProcess::superUserCommand()
95{
96 return d->m_superUserCommand;
97}
98
99bool SuProcess::useUsersOwnPassword()
100{
101 if (superUserCommand() == "sudo" && m_User == "root") {
102 return true;
103 }
104
105 KUser user;
106 return user.loginName() == m_User;
107}
108
109int SuProcess::checkInstall(const char *password)
110{
111 return exec(password, Install);
112}
113
114int SuProcess::checkNeedPassword()
115{
116 return exec(0L, NeedPassword);
117}
118
119/*
120* Execute a command with su(1).
121*/
122
123int SuProcess::exec(const char *password, int check)
124{
125 if (check)
126 setTerminal(true);
127
128 // since user may change after constructor (due to setUser())
129 // we need to override sudo with su for non-root here
130 if (m_User != "root") {
131 d->m_superUserCommand = "su";
132 }
133
134 QList<QByteArray> args;
135 if (d->m_superUserCommand == "sudo") {
136 args += "-u";
137 }
138
139 if ((m_Scheduler != SchedNormal) || (m_Priority > 50))
140 args += "root";
141 else
142 args += m_User;
143
144 if (d->m_superUserCommand == "su") {
145 args += "-c";
146 }
147 args += QByteArray(LIBEXEC_INSTALL_DIR) + "/kdesu_stub";
148 args += "-"; // krazy:exclude=doublequote_chars (QList, not QString)
149
150 QByteArray command;
151 if (d->m_superUserCommand == "sudo") {
152 command = __PATH_SUDO;
153 } else {
154 command = __PATH_SU;
155 }
156
157 if (::access(command, X_OK) != 0)
158 {
159 command = QFile::encodeName( KGlobal::dirs()->findExe(d->m_superUserCommand.toLatin1()) );
160 if (command.isEmpty())
161 return check ? SuNotFound : -1;
162 }
163
164 // kDebug(kdesuDebugArea()) << k_lineinfo << "Call StubProcess::exec()";
165 if (StubProcess::exec(command, args) < 0)
166 {
167 return check ? SuNotFound : -1;
168 }
169 // kDebug(kdesuDebugArea()) << k_lineinfo << "Done StubProcess::exec()";
170
171 SuErrors ret = (SuErrors) ConverseSU(password);
172 // kDebug(kdesuDebugArea()) << k_lineinfo << "Conversation returned" << ret;
173
174 if (ret == error)
175 {
176 if (!check)
177 kError(kdesuDebugArea()) << k_lineinfo << "Conversation with su failed.";
178 return ret;
179 }
180 if (check == NeedPassword)
181 {
182 if (ret == killme)
183 {
184 if ( d->m_superUserCommand == "sudo" ) {
185 // sudo can not be killed, just return
186 return ret;
187 }
188 if (kill(m_Pid, SIGKILL) < 0) {
189 kDebug() << "kill < 0";
190 //FIXME SIGKILL doesn't work for sudo,
191 //why is this different from su?
192 //A: because sudo runs as root. Perhaps we could write a Ctrl+C to its stdin, instead?
193 ret=error;
194 }
195 else
196 {
197 int iret = waitForChild();
198 if (iret < 0) ret=error;
199 else /* nothing */ {} ;
200 }
201 }
202 return ret;
203 }
204
205 if (m_bErase && password)
206 memset(const_cast<char *>(password), 0, qstrlen(password));
207
208 if (ret != ok)
209 {
210 kill(m_Pid, SIGKILL);
211 if (d->m_superUserCommand != "sudo") {
212 waitForChild();
213 }
214 return SuIncorrectPassword;
215 }
216
217 int iret = ConverseStub(check);
218 if (iret < 0)
219 {
220 if (!check)
221 kError(kdesuDebugArea()) << k_lineinfo << "Conversation with kdesu_stub failed.";
222 return iret;
223 }
224 else if (iret == 1)
225 {
226 kill(m_Pid, SIGKILL);
227 waitForChild();
228 return SuIncorrectPassword;
229 }
230
231 if (check == Install)
232 {
233 waitForChild();
234 return 0;
235 }
236
237 iret = waitForChild();
238 return iret;
239}
240
241/*
242* Conversation with su: feed the password.
243* Return values: -1 = error, 0 = ok, 1 = kill me, 2 not authorized
244*/
245
246int SuProcess::ConverseSU(const char *password)
247{
248 enum { WaitForPrompt, CheckStar, HandleStub } state = WaitForPrompt;
249 int colon;
250 unsigned i, j;
251 // kDebug(kdesuDebugArea()) << k_lineinfo << "ConverseSU starting.";
252
253 QByteArray line;
254 while (true)
255 {
256 line = readLine();
257 if (line.isNull())
258 return ( state == HandleStub ? notauthorized : error);
259 kDebug(kdesuDebugArea()) << k_lineinfo << "Read line" << line;
260
261 if (line == "kdesu_stub")
262 {
263 unreadLine(line);
264 return ok;
265 }
266
267 switch (state)
268 {
269 //////////////////////////////////////////////////////////////////////////
270 case WaitForPrompt:
271 {
272 if (waitMS(fd(),100)>0)
273 {
274 // There is more output available, so this line
275 // couldn't have been a password prompt (the definition
276 // of prompt being that there's a line of output followed
277 // by a colon, and then the process waits).
278 continue;
279 }
280
281 // Match "Password: " with the regex ^[^:]+:[\w]*$.
282 const uint len = line.length();
283 for (i=0,j=0,colon=0; i<len; ++i)
284 {
285 if (line[i] == ':')
286 {
287 j = i; colon++;
288 continue;
289 }
290 if (!isspace(line[i]))
291 j++;
292 }
293 if ((colon == 1) && (line[j] == ':'))
294 {
295 if (password == 0L)
296 return killme;
297 if (WaitSlave())
298 return error;
299 write(fd(), password, strlen(password));
300 write(fd(), "\n", 1);
301 state = CheckStar;
302 }
303 break;
304 }
305 //////////////////////////////////////////////////////////////////////////
306 case CheckStar:
307 {
308 QByteArray s = line.trimmed();
309 if (s.isEmpty())
310 {
311 state=HandleStub;
312 break;
313 }
314 const uint len = line.length();
315 for (i=0; i< len; ++i)
316 {
317 if (s[i] != '*')
318 return error;
319 }
320 state=HandleStub;
321 break;
322 }
323 //////////////////////////////////////////////////////////////////////////
324 case HandleStub:
325 break;
326 //////////////////////////////////////////////////////////////////////////
327 } // end switch
328 } // end while (true)
329 return ok;
330}
331
332void SuProcess::virtual_hook( int id, void* data )
333{ StubProcess::virtual_hook( id, data ); }
334
335}
336