Update changelog
[manu/suphp.git] / src / API_Linux.cpp
1 /*
2     suPHP - (c)2002-2008 Sebastian Marsching <sebastian@marsching.com>
3
4     This file is part of suPHP.
5
6     suPHP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     suPHP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with suPHP; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include <string>
22 #include <iostream>
23
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #include "Environment.hpp"
34 #include "UserInfo.hpp"
35 #include "GroupInfo.hpp"
36 #include "API_Linux_Logger.hpp"
37 #include "Util.hpp"
38
39 #include "API_Linux.hpp"
40
41 extern char **environ;
42
43 using namespace suPHP;
44
45 SmartPtr<API_Linux_Logger> suPHP::API_Linux::logger;
46
47 bool suPHP::API_Linux::isSymlink(const std::string path) const
48     throw (SystemException) {
49     struct stat temp;
50     if (lstat(path.c_str(), &temp) == -1) {
51         throw SystemException(std::string("Could not stat \"")
52                               + path + "\": "
53                               + ::strerror(errno), __FILE__, __LINE__);
54     }
55     if ((temp.st_mode & S_IFLNK) == S_IFLNK) {
56         return true;
57     } else {
58         return false;
59     }
60 }
61 std::string suPHP::API_Linux::readSymlink(const std::string path) const
62     throw (SystemException) {
63     char buf[1024] = {0};
64     if (::readlink(path.c_str(), buf, 1023) == -1) {
65         throw SystemException(std::string("Could not read symlink \"")
66                               + path + "\": "
67                               + ::strerror(errno), __FILE__, __LINE__);
68     }
69     
70     if (buf[0] == '/') {
71         return std::string(buf);
72     } else {
73         if (path.rfind('/') == std::string::npos)
74             return std::string(buf);
75         return path.substr(0, path.rfind('/') + 1) + std::string(buf);
76     }
77 }
78
79 Environment suPHP::API_Linux::getProcessEnvironment() {
80     Environment env;
81     char **entry = ::environ;
82     while (*entry != NULL) {
83         std::string estr = std::string(*entry);
84         int eqpos = estr.find("=");
85         std::string name = estr.substr(0, eqpos);
86         std::string content = estr.substr(eqpos + 1);
87         env.putVar(name, content);
88         entry++;
89     }
90     return env;
91 }
92
93 UserInfo suPHP::API_Linux::getUserInfo(const std::string username)
94     throw (LookupException) {
95     struct passwd *tmpuser = ::getpwnam(username.c_str());
96     if (tmpuser == NULL) {
97         throw LookupException(std::string("Could not lookup username \"") 
98                               + username + "\"", __FILE__, __LINE__);
99     }
100     return UserInfo(tmpuser->pw_uid);
101 }
102
103 UserInfo suPHP::API_Linux::getUserInfo(const int uid) {
104     return UserInfo(uid);
105 }
106
107 GroupInfo suPHP::API_Linux::getGroupInfo(const std::string groupname)
108     throw (LookupException) {
109     struct group *tmpgroup = ::getgrnam(groupname.c_str());
110     if (tmpgroup == NULL) {
111         throw LookupException(std::string("Could not lookup groupname \"") 
112                               + groupname + "\"", __FILE__, __LINE__);
113     }
114     return GroupInfo(tmpgroup->gr_gid);
115 }
116
117 GroupInfo suPHP::API_Linux::getGroupInfo(const int gid) {
118     return GroupInfo(gid);
119 }
120
121 UserInfo suPHP::API_Linux::getEffectiveProcessUser() {
122     return UserInfo(::geteuid());
123 }
124
125
126 UserInfo suPHP::API_Linux::getRealProcessUser() {
127     return UserInfo(getuid());
128 }
129
130
131 GroupInfo suPHP::API_Linux::getEffectiveProcessGroup() {
132     return GroupInfo(getegid());
133 }
134
135
136 GroupInfo suPHP::API_Linux::getRealProcessGroup() {
137     return GroupInfo(getgid());
138 }
139
140
141 Logger& suPHP::API_Linux::getSystemLogger() {
142     if (suPHP::API_Linux::logger.get() == NULL) {
143         suPHP::API_Linux::logger.reset(new API_Linux_Logger());
144     }
145     return *(suPHP::API_Linux::logger);
146 }
147
148
149 void suPHP::API_Linux::setProcessUser(const UserInfo& user) const
150     throw (SystemException) {
151     // Reset supplementary groups
152     if (::setgroups(0, NULL) == -1) {
153         throw SystemException(std::string("setgroups() failed: ")
154                               + ::strerror(errno), __FILE__, __LINE__);
155     }
156     
157     try {
158         if (::initgroups(user.getUsername().c_str(), 
159                          user.getGroupInfo().getGid()) 
160             == -1) {
161             throw SystemException(std::string("initgroups() failed: ")
162                                   + ::strerror(errno), __FILE__, __LINE__);
163         }
164     } catch (LookupException &e) {
165         // Ignore this exception
166         // If we have a UID, which does not exist in /etc/passwd
167         // we simply cannot use supplementary groups
168     }
169
170     if (::setuid(user.getUid()) == -1) {
171         throw SystemException(std::string("setuid() failed: ") 
172                               + ::strerror(errno), __FILE__, __LINE__);
173     }
174 }
175
176
177 void suPHP::API_Linux::setProcessGroup(const GroupInfo& group) const
178     throw (SystemException) {
179     if (::setgid(group.getGid()) == -1) {
180         throw SystemException(std::string("setgid() failed: ") 
181                               + ::strerror(errno), __FILE__, __LINE__);
182     }
183 }
184
185 std::string suPHP::API_Linux::UserInfo_getUsername(const UserInfo& uinfo) const
186     throw (LookupException) {
187     struct passwd *tmpuser = ::getpwuid(uinfo.getUid());
188     if (tmpuser == NULL) {
189         throw LookupException(std::string("Could not lookup UID ")
190                               + Util::intToStr(uinfo.getUid()),
191                               __FILE__, __LINE__);
192     }
193     return std::string(tmpuser->pw_name);
194 }
195
196 GroupInfo suPHP::API_Linux::UserInfo_getGroupInfo(const UserInfo& uinfo) const
197     throw (LookupException) {
198     struct passwd *tmpuser = NULL;
199     tmpuser = getpwuid(uinfo.getUid());
200     if (tmpuser == NULL) {
201         throw LookupException(std::string("Could not lookup UID ") 
202                               + Util::intToStr(uinfo.getUid()), 
203                               __FILE__, __LINE__);
204     }
205     return GroupInfo(tmpuser->pw_gid);
206 }
207
208 std::string suPHP::API_Linux::UserInfo_getHomeDirectory(const UserInfo& uinfo) const
209     throw (LookupException) {
210     struct passwd *tmpuser = NULL;
211     tmpuser = getpwuid(uinfo.getUid());
212     if (tmpuser == NULL) {
213         throw LookupException(std::string("Could not lookup UID ") 
214                               + Util::intToStr(uinfo.getUid()), 
215                               __FILE__, __LINE__);
216     }
217     return tmpuser->pw_dir;
218 }
219
220 bool suPHP::API_Linux::UserInfo_isSuperUser(const UserInfo& uinfo) const {
221     if (uinfo.getUid() == 0)
222         return true;
223     else
224         return false;
225 }
226
227 std::string suPHP::API_Linux::GroupInfo_getGroupname(const GroupInfo& ginfo) 
228     const throw (LookupException) {
229     struct group *tmpgroup = ::getgrgid(ginfo.getGid());
230     if (tmpgroup == NULL) {
231         throw LookupException(std::string("Could not lookup GID ") 
232                                + Util::intToStr(ginfo.getGid()),
233                               __FILE__, __LINE__);
234     }
235     return std::string(tmpgroup->gr_name);
236 }
237
238 bool suPHP::API_Linux::File_exists(const File& file) const {
239     struct stat dummy;
240     if (::lstat(file.getPath().c_str(), &dummy) == 0)
241         return true;
242     else
243         return false;
244 }
245
246 std::string suPHP::API_Linux::File_getRealPath(const File& file) const
247     throw (SystemException) {
248     std::string currentpath = file.getPath();
249     std::string resolvedpath = "";
250     bool failed = true;
251
252     if ((currentpath.size() == 0) || (currentpath.at(0) != '/')) {
253         currentpath = this->getCwd() + std::string("/") + currentpath;
254     }
255     
256     // Limit iterations to avoid infinite symlink loops
257     for (int i=0; i<512; i++) {
258         // If nothing is left, we have finished
259         if (currentpath.size() == 0) {
260             resolvedpath = ("/" + resolvedpath);
261             failed = false;
262             break;
263         }
264         
265         if (this->isSymlink(currentpath)) {
266             currentpath = this->readSymlink(currentpath);
267         } else {
268             // We know last part is not a symlink, so it is resolved
269             std::string part1 = 
270                 currentpath.substr(0, currentpath.rfind('/'));
271             std::string part2 = 
272                 currentpath.substr(currentpath.rfind('/')+1);
273             currentpath = part1;
274             if (resolvedpath.size() == 0)
275                 resolvedpath = part2;
276             else
277                 resolvedpath = part2 + "/" + resolvedpath;
278         }
279     } 
280     
281     if (failed) {
282         throw SystemException("Could not resolve path \"" + 
283                               file.getPath() + "\"", __FILE__, __LINE__);
284     }
285
286     while (resolvedpath.find("/./") != std::string::npos) {
287         int pos = resolvedpath.find("/./");
288         resolvedpath = resolvedpath.substr(0, pos)
289             + resolvedpath.substr(pos + 2);
290     }
291     
292     while (resolvedpath.find("/../") != std::string::npos) {
293         int pos = resolvedpath.find("/../");
294         int pos2 = resolvedpath.rfind('/', pos-1);
295         resolvedpath = resolvedpath.substr(0, pos2)
296             + resolvedpath.substr(pos + 3);
297     }
298     
299     if (resolvedpath.find("/..", resolvedpath.size() - 3) 
300         != std::string::npos) {
301         resolvedpath = resolvedpath.substr(0, resolvedpath.size() - 3);
302         resolvedpath = resolvedpath.substr(0, resolvedpath.rfind('/'));
303     }
304
305     if (resolvedpath.find("/.", resolvedpath.size() - 2) 
306         != std::string::npos) {
307         resolvedpath = resolvedpath.substr(0, resolvedpath.size() - 2);
308     }
309     
310     if (resolvedpath.size() == 0)
311         resolvedpath = "/";
312
313     return resolvedpath;
314 }   
315
316 bool suPHP::API_Linux::File_hasPermissionBit(const File& file, FileMode perm) 
317     const throw (SystemException) {
318     struct stat temp;
319     if (lstat(file.getPath().c_str(), &temp) == -1) {
320         throw SystemException(std::string("Could not stat \"")
321                               + file.getPath() + "\": "
322                               + ::strerror(errno), __FILE__, __LINE__);
323     }
324     switch (perm) {
325     case FILEMODE_USER_READ:
326         if ((temp.st_mode & S_IRUSR) == S_IRUSR)
327             return true;
328         break;
329
330     case FILEMODE_USER_WRITE:
331         if ((temp.st_mode & S_IWUSR) == S_IWUSR)
332             return true;
333         break;
334
335     case FILEMODE_USER_EXEC:
336         if ((temp.st_mode & S_IXUSR) == S_IXUSR)
337             return true;
338         break;
339
340     case FILEMODE_GROUP_READ:
341         if ((temp.st_mode & S_IRGRP) == S_IRGRP)
342             return true;
343         break;
344
345     case FILEMODE_GROUP_WRITE:
346         if ((temp.st_mode & S_IWGRP) == S_IWGRP)
347             return true;
348         break;
349
350     case FILEMODE_GROUP_EXEC:
351         if ((temp.st_mode & S_IXGRP) == S_IXGRP)
352             return true;
353         break;
354
355     case FILEMODE_OTHERS_READ:
356         if ((temp.st_mode & S_IROTH) == S_IROTH)
357             return true;
358         break;
359
360     case FILEMODE_OTHERS_WRITE:
361         if ((temp.st_mode & S_IWOTH) == S_IWOTH)
362             return true;
363         break;
364         
365     case FILEMODE_OTHERS_EXEC:
366         if ((temp.st_mode & S_IXOTH) == S_IXOTH)
367             return true;
368         break;
369     }
370
371     return false;
372 }
373
374 UserInfo suPHP::API_Linux::File_getUser(const File& file) const
375     throw (SystemException) {
376     struct stat temp;
377     if (lstat(file.getPath().c_str(), &temp) == -1) {
378         throw SystemException(std::string("Could not stat \"")
379                               + file.getPath() + "\": "
380                               + ::strerror(errno), __FILE__, __LINE__);
381     }
382     return UserInfo(temp.st_uid);
383 }
384
385 GroupInfo suPHP::API_Linux::File_getGroup(const File& file) const
386     throw (SystemException) {
387     struct stat temp;
388     if (lstat(file.getPath().c_str(), &temp) == -1) {
389         throw SystemException(std::string("Could not stat \"")
390                               + file.getPath() + "\": "
391                               + ::strerror(errno), __FILE__, __LINE__);
392     }
393     return GroupInfo(temp.st_gid);
394 }
395
396
397 bool suPHP::API_Linux::File_isSymlink(const File& file) const throw (SystemException) {
398     return this->isSymlink(file.getPath());
399 }
400
401
402 void suPHP::API_Linux::execute(std::string program, const CommandLine& cline,
403                                const Environment& env) const
404     throw (SystemException) {
405     char **sysCline = NULL;
406     char **sysEnv = NULL;
407     char **p = NULL;
408     char *sysProgram = NULL;
409     std::map<std::string, std::string> map;
410     int i;
411
412
413     
414     // Construct commandline
415     sysCline = new char*[cline.size() + 1];
416     for (i=0; i<cline.size(); i++) {
417         std::string arg = cline.getArgument(i);
418         sysCline[i] = new char[arg.size()+1];
419         ::strncpy(sysCline[i], arg.c_str(), arg.size()+1);
420     }
421     sysCline[cline.size()] = NULL;
422     
423     // Construct environment
424     map = env.getBackendMap();
425     sysEnv = new char*[map.size() + 1];
426     p = sysEnv;
427     for (std::map<std::string, std::string>::iterator pos = map.begin(); 
428          pos != map.end(); 
429          pos++) {
430         std::string var;
431         var = pos->first + "=" + pos->second;
432         *p = new char[var.size()+1];
433         ::strncpy(*p, var.c_str(), var.size()+1);
434         p++;
435     }
436     *p = NULL;
437
438     // Make sure target program name is on heap
439     sysProgram = new char[program.size() + 1];
440     ::strncpy(sysProgram, program.c_str(), program.size()+1);
441     if (execve(sysProgram, sysCline, sysEnv) == -1) {
442         throw SystemException("execve() for program \"" + program 
443                               + "\" failed: " + ::strerror(errno),
444                               __FILE__, __LINE__);
445     }
446     
447     // We are still here? This cannot be good..
448     throw SystemException("execve() for program \"" + program 
449                           + "\" failed because of unknown reason", 
450                           __FILE__, __LINE__);
451 }
452
453 std::string suPHP::API_Linux::getCwd() const throw (SystemException) {
454     char buf[4096] = {0};
455     if (::getcwd(buf, 4095) == NULL)
456         throw SystemException(std::string("getcwd() failed: ")
457                               + ::strerror(errno), __FILE__, __LINE__);
458     return std::string(buf);
459 }
460
461 void suPHP::API_Linux::setCwd(const std::string& dir) const
462     throw (SystemException) {
463     if(::chdir(dir.c_str())) {
464         throw SystemException(std::string("chdir() failed: ")
465                               + ::strerror(errno), __FILE__, __LINE__);
466     }
467 }
468
469 void suPHP::API_Linux::setUmask(int mode) const throw (SystemException) {
470     ::umask(mode);
471 }
472
473 void suPHP::API_Linux::chroot(const std::string& dir) const
474     throw (SystemException) {
475     if (::chroot(dir.c_str())) {
476         throw SystemException(std::string("chroot() failed: ")
477                               + ::strerror(errno), __FILE__, __LINE__);
478     }
479 }