UnixUtil.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <kopano/ECLogger.h>
  19. #include <kopano/UnixUtil.h>
  20. #include <unistd.h>
  21. #include <fcntl.h>
  22. #include <sys/types.h>
  23. #include <pwd.h>
  24. #include <grp.h>
  25. #include <cerrno>
  26. #include <cstring>
  27. #include <cstdlib>
  28. #include <csignal>
  29. #include <sys/file.h>
  30. #include <sys/resource.h>
  31. #include <string>
  32. using namespace std;
  33. namespace KC {
  34. static int unix_runpath(ECConfig *conf)
  35. {
  36. const char *path = conf->GetSetting("running_path");
  37. int ret;
  38. if (path != NULL) {
  39. ret = chdir(path);
  40. if (ret != 0)
  41. ec_log_err("Unable to run in given path \"%s\": %s", path, strerror(errno));
  42. }
  43. if (path == NULL || ret != 0) {
  44. ret = chdir("/");
  45. if (ret != 0)
  46. ec_log_err("chdir /: %s\n", strerror(errno));
  47. }
  48. return ret;
  49. }
  50. int unix_runas(ECConfig *lpConfig)
  51. {
  52. const char *group = lpConfig->GetSetting("run_as_group");
  53. const char *user = lpConfig->GetSetting("run_as_user");
  54. int ret;
  55. ret = unix_runpath(lpConfig);
  56. if (ret != 0)
  57. return ret;
  58. if (getgroups(0, NULL) != 0 && setgroups(0, NULL) < 0)
  59. ec_log_warn("setgroups(0): %s", strerror(errno));
  60. if (group != NULL && *group != '\0') {
  61. const struct group *gr = getgrnam(group);
  62. if (!gr) {
  63. ec_log_err("Looking up group \"%s\" failed: %s", group, strerror(errno));
  64. return -1;
  65. }
  66. if (getgid() != gr->gr_gid && setgid(gr->gr_gid) != 0) {
  67. ec_log_crit("Changing to group \"%s\" failed: %s", gr->gr_name, strerror(errno));
  68. return -1;
  69. }
  70. }
  71. if (user != NULL && *user != '\0') {
  72. const struct passwd *pw = getpwnam(user);
  73. if (!pw) {
  74. ec_log_err("Looking up user \"%s\" failed: %s", user, strerror(errno));
  75. return -1;
  76. }
  77. if (getuid() != pw->pw_uid && setuid(pw->pw_uid) != 0) {
  78. ec_log_crit("Changing to user \"%s\" failed: %s", pw->pw_name, strerror(errno));
  79. return -1;
  80. }
  81. }
  82. return 0;
  83. }
  84. int unix_chown(const char *filename, const char *username, const char *groupname) {
  85. const struct group *gr = NULL;
  86. const struct passwd *pw = NULL;
  87. uid_t uid;
  88. gid_t gid;
  89. gid = getgid();
  90. if (groupname && strcmp(groupname,"")) {
  91. gr = getgrnam(groupname);
  92. if (gr)
  93. gid = gr->gr_gid;
  94. }
  95. uid = getuid();
  96. if (username && strcmp(username,"")) {
  97. pw = getpwnam(username);
  98. if (pw)
  99. uid = pw->pw_uid;
  100. }
  101. return chown(filename, uid, gid);
  102. }
  103. void unix_coredump_enable(void)
  104. {
  105. struct rlimit limit;
  106. limit.rlim_cur = RLIM_INFINITY;
  107. limit.rlim_max = RLIM_INFINITY;
  108. if (setrlimit(RLIMIT_CORE, &limit) < 0)
  109. ec_log_err("Unable to raise coredump filesize limit: %s", strerror(errno));
  110. }
  111. int unix_create_pidfile(const char *argv0, ECConfig *lpConfig, bool bForce)
  112. {
  113. string pidfilename = "/var/run/kopano/" + string(argv0) + ".pid";
  114. FILE *pidfile;
  115. int oldpid;
  116. char tmp[256];
  117. bool running = false;
  118. if (strcmp(lpConfig->GetSetting("pid_file"), "")) {
  119. pidfilename = lpConfig->GetSetting("pid_file");
  120. }
  121. // test for existing and running process
  122. pidfile = fopen(pidfilename.c_str(), "r");
  123. if (pidfile) {
  124. if (fscanf(pidfile, "%d", &oldpid) < 1)
  125. oldpid = -1;
  126. fclose(pidfile);
  127. snprintf(tmp, 255, "/proc/%d/cmdline", oldpid);
  128. pidfile = fopen(tmp, "r");
  129. if (pidfile) {
  130. memset(tmp, '\0', sizeof(tmp));
  131. if (fscanf(pidfile, "%255s", tmp) < 1)
  132. /* nothing */;
  133. fclose(pidfile);
  134. if (strlen(tmp) < strlen(argv0)) {
  135. if (strstr(argv0, tmp))
  136. running = true;
  137. } else if (strstr(tmp, argv0)) {
  138. running = true;
  139. }
  140. if (running) {
  141. ec_log_crit("Warning: Process %s is probably already running.", argv0);
  142. if (!bForce) {
  143. ec_log_crit("If you are sure the process is stopped, please remove pidfile %s", pidfilename.c_str());
  144. return -1;
  145. }
  146. }
  147. }
  148. }
  149. pidfile = fopen(pidfilename.c_str(), "w");
  150. if (!pidfile) {
  151. ec_log_crit("Unable to open pidfile '%s'", pidfilename.c_str());
  152. return 1;
  153. }
  154. fprintf(pidfile, "%d\n", getpid());
  155. fclose(pidfile);
  156. return 0;
  157. }
  158. int unix_daemonize(ECConfig *lpConfig)
  159. {
  160. int ret;
  161. // make sure we daemonize in an always existing directory
  162. ret = unix_runpath(lpConfig);
  163. if (ret != 0)
  164. return ret;
  165. ret = fork();
  166. if (ret == -1) {
  167. ec_log_crit("Daemonizing failed on 1st step");
  168. return -1;
  169. }
  170. if (ret)
  171. _exit(0); // close parent process
  172. setsid(); // start new session
  173. ret = fork();
  174. if (ret == -1) {
  175. ec_log_crit("Daemonizing failed on 2nd step");
  176. return -1;
  177. }
  178. if (ret)
  179. _exit(0); // close parent process
  180. // close output to console. a logger which logged to the console is now diverted to /dev/null
  181. fclose(stdin);
  182. freopen("/dev/null", "a+", stdout);
  183. freopen("/dev/null", "a+", stderr);
  184. return 0;
  185. }
  186. /**
  187. * Starts a new Unix process and calls the given function. Optionally
  188. * closes some given file descriptors. The child process does not
  189. * return from this function.
  190. *
  191. * @note the child process calls exit(0) at exit, not _exit(0), so
  192. * atexit() and on_exit() callbacks from the parent are called and
  193. * tmpfile() created files are removed from either parent and child.
  194. * This is wanted behaviour for us since we don't use any exit
  195. * callbacks and tmpfiles, but we do want coverage output from gcov,
  196. * which seems to use an exit callback to write the usage info.
  197. *
  198. * @param[in] func Pointer to a function with one void* parameter and returning a void* that should run in the child process.
  199. * @param[in] param Parameter to pass to the func function.
  200. * @param[in] nCloseFDs Number of file descriptors in pCloseFDs.
  201. * @param[in] pCloseFDs Array of file descriptors to close in the child process.
  202. * @retval processid of the started child, or a negative value on error.
  203. */
  204. int unix_fork_function(void*(func)(void*), void *param, int nCloseFDs, int *pCloseFDs)
  205. {
  206. int pid;
  207. if (!func)
  208. return -1;
  209. pid = fork();
  210. if (pid < 0)
  211. return pid;
  212. if (pid == 0) {
  213. // reset the SIGHUP signal to default, not to trigger the config/logfile reload signal too often on 'killall <process>'
  214. signal(SIGHUP, SIG_DFL);
  215. // close filedescriptors
  216. for (int n = 0; n < nCloseFDs && pCloseFDs != NULL; ++n)
  217. if (pCloseFDs[n] >= 0)
  218. close(pCloseFDs[n]);
  219. func(param);
  220. // call normal cleanup exit
  221. exit(0);
  222. }
  223. return pid;
  224. }
  225. /**
  226. * Starts a new process with a read and write channel. Optionally sets
  227. * resource limites and environment variables.
  228. *
  229. * @param lpLogger[in] Logger object where error messages during the function may be sent to. Cannot be NULL.
  230. * @param lpszCommand[in] The command to execute in the new subprocess.
  231. * @param lpulIn[out] The filedescriptor to read data of the command from.
  232. * @param lpulOut[out] The filedescriptor to write data to the command to.
  233. * @param lpLimits[in] Optional resource limits to set for the new subprocess.
  234. * @param env[in] Optional environment variables to set in the new subprocess.
  235. * @param bNonBlocking[in] Make the in and out pipes non-blocking on read and write calls.
  236. * @param bStdErr[in] Add STDERR output to *lpulOut
  237. *
  238. * @return new process pid, or -1 on failure.
  239. */
  240. static pid_t unix_popen_rw(const char *lpszCommand, int *lpulIn, int *lpulOut,
  241. popen_rlimit_array *lpLimits, const char **env, bool bNonBlocking,
  242. bool bStdErr)
  243. {
  244. int ulIn[2];
  245. int ulOut[2];
  246. pid_t pid;
  247. if (!lpszCommand || !lpulIn || !lpulOut)
  248. return -1;
  249. if (pipe(ulIn) || pipe(ulOut))
  250. return -1;
  251. if (bNonBlocking) {
  252. if (fcntl(ulIn[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(ulIn[1], F_SETFL, O_NONBLOCK) < 0 ||
  253. fcntl(ulOut[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(ulOut[1], F_SETFL, O_NONBLOCK) < 0)
  254. return -1;
  255. }
  256. pid = vfork();
  257. if (pid < 0)
  258. return pid;
  259. if (pid == 0) {
  260. /* Close pipes we aren't going to use */
  261. close(ulIn[STDOUT_FILENO]);
  262. dup2(ulIn[STDIN_FILENO], STDIN_FILENO);
  263. close(ulOut[STDIN_FILENO]);
  264. dup2(ulOut[STDOUT_FILENO], STDOUT_FILENO);
  265. if (bStdErr)
  266. dup2(ulOut[STDOUT_FILENO], STDERR_FILENO);
  267. // give the process a new group id, so we can easely kill all sub processes of this child too when needed.
  268. setsid();
  269. /* If provided set rlimit settings */
  270. if (lpLimits != NULL)
  271. for (unsigned int i = 0; i < lpLimits->cValues; ++i)
  272. if (setrlimit(lpLimits->sLimit[i].resource, &lpLimits->sLimit[i].limit) != 0)
  273. ec_log_err("Unable to set rlimit for popen - resource %d, errno %d",
  274. lpLimits->sLimit[i].resource, errno);
  275. if (execle("/bin/sh", "sh", "-c", lpszCommand, NULL, env) == 0)
  276. _exit(EXIT_SUCCESS);
  277. else
  278. _exit(EXIT_FAILURE);
  279. return 0;
  280. }
  281. *lpulIn = ulIn[STDOUT_FILENO];
  282. close(ulIn[STDIN_FILENO]);
  283. *lpulOut = ulOut[STDIN_FILENO];
  284. close(ulOut[STDOUT_FILENO]);
  285. return pid;
  286. }
  287. /**
  288. * Start an external process
  289. *
  290. * This function is used to start an external process that requires no STDIN input. The output will be
  291. * logged via lpLogger if provided.
  292. *
  293. * @param lpLogger[in] NULL or pointer to logger object to log to (will be logged via EC_LOGLEVEL_INFO)
  294. * @param lsszLogName[in] Name to show in the log. Will show NAME[pid]: DATA
  295. * @param lpszCommand[in] String to command to be started, which will be executed with /bin/sh -c "string"
  296. * @param env[in] NULL-terminated array of strings with environment settings in the form ENVNAME=VALUE, see
  297. * execlp(3) for details
  298. *
  299. * @return Returns TRUE on success, FALSE on failure
  300. */
  301. bool unix_system(const char *lpszLogName, const char *lpszCommand, const char **env)
  302. {
  303. int fdin = 0, fdout = 0;
  304. int pid = unix_popen_rw(lpszCommand, &fdin, &fdout, NULL, env, false, true);
  305. char buffer[1024];
  306. int status = 0;
  307. bool rv = true;
  308. FILE *fp = fdopen(fdout, "rb");
  309. close(fdin);
  310. while (fgets(buffer, sizeof(buffer), fp)) {
  311. buffer[strlen(buffer) - 1] = '\0'; // strip enter
  312. ec_log_debug("%s[%d]: %s", lpszLogName, pid, buffer);
  313. }
  314. fclose(fp);
  315. waitpid(pid, &status, 0);
  316. if (status == -1) {
  317. ec_log_err(string("System call \"system\" failed: ") + strerror(errno));
  318. return false;
  319. }
  320. #ifdef WEXITSTATUS
  321. if (WIFEXITED(status)) { /* Child exited by itself */
  322. if (WEXITSTATUS(status)) {
  323. ec_log_err("Command `%s` exited with non-zero status %d", lpszCommand, WEXITSTATUS(status));
  324. rv = false;
  325. }
  326. else
  327. ec_log_info("Command `%s` ran successfully", lpszCommand);
  328. } else if (WIFSIGNALED(status)) { /* Child was killed by a signal */
  329. ec_log_err("Command `%s` was killed by signal %d", lpszCommand, WTERMSIG(status));
  330. rv = false;
  331. } else { /* Something strange happened */
  332. ec_log_err(string("Command `") + lpszCommand + "` terminated abnormally");
  333. rv = false;
  334. }
  335. #else
  336. if (status)
  337. ec_log_err("Command `%s` exited with status %d", lpszCommand, status);
  338. else
  339. ec_log_info("Command `%s` ran successfully", lpszCommand);
  340. #endif
  341. return rv;
  342. }
  343. } /* namespace */