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 | |
43 | int 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 | |
63 | namespace KDESu { |
64 | using namespace KDESuPrivate; |
65 | |
66 | class SuProcess::SuProcessPrivate |
67 | { |
68 | public: |
69 | QString m_superUserCommand; |
70 | }; |
71 | |
72 | SuProcess::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 | |
89 | SuProcess::~SuProcess() |
90 | { |
91 | delete d; |
92 | } |
93 | |
94 | QString SuProcess::superUserCommand() |
95 | { |
96 | return d->m_superUserCommand; |
97 | } |
98 | |
99 | bool 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 | |
109 | int SuProcess::checkInstall(const char *password) |
110 | { |
111 | return exec(password, Install); |
112 | } |
113 | |
114 | int SuProcess::checkNeedPassword() |
115 | { |
116 | return exec(0L, NeedPassword); |
117 | } |
118 | |
119 | /* |
120 | * Execute a command with su(1). |
121 | */ |
122 | |
123 | int 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 | |
246 | int 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 | |
332 | void SuProcess::virtual_hook( int id, void* data ) |
333 | { StubProcess::virtual_hook( id, data ); } |
334 | |
335 | } |
336 | |