]> granicus.if.org Git - postgresql/blob - src/port/exec.c
839bc73f00514859f02f13dcc9725a0e839fc64f
[postgresql] / src / port / exec.c
1 /*-------------------------------------------------------------------------
2  *
3  * exec.c
4  *
5  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
6  * Portions Copyright (c) 1994, Regents of the University of California
7  *
8  *
9  * IDENTIFICATION
10  *        $PostgreSQL: pgsql/src/port/exec.c,v 1.31 2004/11/06 01:16:22 tgl Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14
15 #ifndef FRONTEND
16 #include "postgres.h"
17 #else
18 #include "postgres_fe.h"
19 #endif
20
21 #include <grp.h>
22 #include <pwd.h>
23 #include <sys/stat.h>
24 #include <sys/wait.h>
25 #ifndef WIN32_CLIENT_ONLY
26 #include <unistd.h>
27 #endif
28
29 #define _(x) gettext(x)
30
31 #ifndef S_IRUSR                                 /* XXX [TRH] should be in a header */
32 #define S_IRUSR          S_IREAD
33 #define S_IWUSR          S_IWRITE
34 #define S_IXUSR          S_IEXEC
35 #define S_IRGRP          ((S_IRUSR)>>3)
36 #define S_IWGRP          ((S_IWUSR)>>3)
37 #define S_IXGRP          ((S_IXUSR)>>3)
38 #define S_IROTH          ((S_IRUSR)>>6)
39 #define S_IWOTH          ((S_IWUSR)>>6)
40 #define S_IXOTH          ((S_IXUSR)>>6)
41 #endif
42
43 #ifndef FRONTEND
44 /* We use only 3-parameter elog calls in this file, for simplicity */
45 #define log_error(str, param)   elog(LOG, str, param)
46 #else
47 #define log_error(str, param)   (fprintf(stderr, str, param), fputc('\n', stderr))
48 #endif
49
50
51 /*
52  * validate_exec -- validate "path" as an executable file
53  *
54  * returns 0 if the file is found and no error is encountered.
55  *                -1 if the regular file "path" does not exist or cannot be executed.
56  *                -2 if the file is otherwise valid but cannot be read.
57  */
58 static int
59 validate_exec(const char *path)
60 {
61         struct stat buf;
62
63 #ifndef WIN32
64         uid_t           euid;
65         struct group *gp;
66         struct passwd *pwp;
67         int                     i;
68         int                     in_grp = 0;
69
70 #else
71         char            path_exe[MAXPGPATH + sizeof(".exe") - 1];
72 #endif
73         int                     is_r = 0;
74         int                     is_x = 0;
75
76 #ifdef WIN32
77         /* Win32 requires a .exe suffix for stat() */
78         if (strlen(path) >= strlen(".exe") &&
79                 pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
80         {
81                 strcpy(path_exe, path);
82                 strcat(path_exe, ".exe");
83                 path = path_exe;
84         }
85 #endif
86
87         /*
88          * Ensure that the file exists and is a regular file.
89          *
90          * XXX if you have a broken system where stat() looks at the symlink
91          * instead of the underlying file, you lose.
92          */
93         if (stat(path, &buf) < 0)
94                 return -1;
95
96         if ((buf.st_mode & S_IFMT) != S_IFREG)
97                 return -1;
98
99         /*
100          * Ensure that we are using an authorized executable.
101          */
102
103         /*
104          * Ensure that the file is both executable and readable (required for
105          * dynamic loading).
106          */
107 #ifdef WIN32
108         is_r = buf.st_mode & S_IRUSR;
109         is_x = buf.st_mode & S_IXUSR;
110         return is_x ? (is_r ? 0 : -2) : -1;
111 #else
112         euid = geteuid();
113
114         /* If owned by us, just check owner bits */
115         if (euid == buf.st_uid)
116         {
117                 is_r = buf.st_mode & S_IRUSR;
118                 is_x = buf.st_mode & S_IXUSR;
119                 return is_x ? (is_r ? 0 : -2) : -1;
120         }
121
122         /* OK, check group bits */
123
124         pwp = getpwuid(euid);           /* not thread-safe */
125         if (pwp)
126         {
127                 if (pwp->pw_gid == buf.st_gid)  /* my primary group? */
128                         ++in_grp;
129                 else if (pwp->pw_name &&
130                                  (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */
131                                  gp->gr_mem != NULL)
132                 {                                               /* try list of member groups */
133                         for (i = 0; gp->gr_mem[i]; ++i)
134                         {
135                                 if (!strcmp(gp->gr_mem[i], pwp->pw_name))
136                                 {
137                                         ++in_grp;
138                                         break;
139                                 }
140                         }
141                 }
142                 if (in_grp)
143                 {
144                         is_r = buf.st_mode & S_IRGRP;
145                         is_x = buf.st_mode & S_IXGRP;
146                         return is_x ? (is_r ? 0 : -2) : -1;
147                 }
148         }
149
150         /* Check "other" bits */
151         is_r = buf.st_mode & S_IROTH;
152         is_x = buf.st_mode & S_IXOTH;
153         return is_x ? (is_r ? 0 : -2) : -1;
154 #endif
155 }
156
157 /*
158  * find_my_exec -- find an absolute path to a valid executable
159  *
160  * The reason we have to work so hard to find an absolute path is that
161  * on some platforms we can't do dynamic loading unless we know the
162  * executable's location.  Also, we need a full path not a relative
163  * path because we will later change working directory.
164  *
165  * This function is not thread-safe because it calls validate_exec(),
166  * which calls getgrgid().      This function should be used only in
167  * non-threaded binaries, not in library routines.
168  */
169 int
170 find_my_exec(const char *argv0, char *retpath)
171 {
172         char            cwd[MAXPGPATH],
173                                 test_path[MAXPGPATH];
174         char       *path;
175
176 #ifndef WIN32_CLIENT_ONLY
177         if (!getcwd(cwd, MAXPGPATH))
178                 strcpy(cwd, ".");               /* cheesy, but better than nothing */
179 #else
180         if (!GetCurrentDirectory(MAXPGPATH, cwd))
181                 strcpy(cwd, ".");               /* cheesy, but better than nothing */
182 #endif
183
184         /*
185          * If argv0 contains a separator, then PATH wasn't used.
186          */
187         if (first_dir_separator(argv0) != NULL)
188         {
189                 if (is_absolute_path(argv0))
190                         StrNCpy(retpath, argv0, MAXPGPATH);
191                 else
192                         join_path_components(retpath, cwd, argv0);
193                 canonicalize_path(retpath);
194
195                 if (validate_exec(retpath) == 0)
196                         return 0;
197
198                 log_error("invalid binary \"%s\"", retpath);
199                 return -1;
200         }
201
202 #ifdef WIN32
203         /* Win32 checks the current directory first for names without slashes */
204         join_path_components(retpath, cwd, argv0);
205         if (validate_exec(retpath) == 0)
206                 return 0;
207 #endif
208
209         /*
210          * Since no explicit path was supplied, the user must have
211          * been relying on PATH.  We'll search the same PATH.
212          */
213         if ((path = getenv("PATH")) && *path)
214         {
215                 char       *startp = NULL,
216                                    *endp = NULL;
217
218                 do
219                 {
220                         if (!startp)
221                                 startp = path;
222                         else
223                                 startp = endp + 1;
224
225                         endp = first_path_separator(startp);
226                         if (!endp)
227                                 endp = startp + strlen(startp); /* point to end */
228
229                         StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
230
231                         if (is_absolute_path(test_path))
232                                 join_path_components(retpath, test_path, argv0);
233                         else
234                         {
235                                 join_path_components(retpath, cwd, test_path);
236                                 join_path_components(retpath, retpath, argv0);
237                         }
238                         canonicalize_path(retpath);
239
240                         switch (validate_exec(retpath))
241                         {
242                                 case 0:                 /* found ok */
243                                         return 0;
244                                 case -1:                /* wasn't even a candidate, keep looking */
245                                         break;
246                                 case -2:                /* found but disqualified */
247                                         log_error("could not read binary \"%s\"", retpath);
248                                         break;
249                         }
250                 } while (*endp);
251         }
252
253         log_error("could not find a \"%s\" to execute", argv0);
254         return -1;
255 }
256
257 /*
258  * The runtime library's popen() on win32 does not work when being
259  * called from a service when running on windows <= 2000, because
260  * there is no stdin/stdout/stderr.
261  *
262  * Executing a command in a pipe and reading the first line from it
263  * is all we need.
264  */
265
266 static char *
267 pipe_read_line(char *cmd, char *line, int maxsize)
268 {
269 #ifndef WIN32
270         FILE       *pgver;
271
272         /* flush output buffers in case popen does not... */
273         fflush(stdout);
274         fflush(stderr);
275
276         if ((pgver = popen(cmd, "r")) == NULL)
277                 return NULL;
278
279         if (fgets(line, maxsize, pgver) == NULL)
280         {
281                 perror("fgets failure");
282                 return NULL;
283         }
284
285         if (pclose_check(pgver))
286                 return NULL;
287
288         return line;
289 #else
290         /* Win32 */
291         SECURITY_ATTRIBUTES sattr;
292         HANDLE          childstdoutrd,
293                                 childstdoutwr,
294                                 childstdoutrddup;
295         PROCESS_INFORMATION pi;
296         STARTUPINFO si;
297         char       *retval = NULL;
298
299         sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
300         sattr.bInheritHandle = TRUE;
301         sattr.lpSecurityDescriptor = NULL;
302
303         if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
304                 return NULL;
305
306         if (!DuplicateHandle(GetCurrentProcess(),
307                                                  childstdoutrd,
308                                                  GetCurrentProcess(),
309                                                  &childstdoutrddup,
310                                                  0,
311                                                  FALSE,
312                                                  DUPLICATE_SAME_ACCESS))
313         {
314                 CloseHandle(childstdoutrd);
315                 CloseHandle(childstdoutwr);
316                 return NULL;
317         }
318
319         CloseHandle(childstdoutrd);
320
321         ZeroMemory(&pi, sizeof(pi));
322         ZeroMemory(&si, sizeof(si));
323         si.cb = sizeof(si);
324         si.dwFlags = STARTF_USESTDHANDLES;
325         si.hStdError = childstdoutwr;
326         si.hStdOutput = childstdoutwr;
327         si.hStdInput = INVALID_HANDLE_VALUE;
328
329         if (CreateProcess(NULL,
330                                           cmd,
331                                           NULL,
332                                           NULL,
333                                           TRUE,
334                                           0,
335                                           NULL,
336                                           NULL,
337                                           &si,
338                                           &pi))
339         {
340                 DWORD           bytesread = 0;
341
342                 /* Successfully started the process */
343
344                 ZeroMemory(line, maxsize);
345
346                 /* Let's see if we can read */
347                 if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0)
348                 {
349                         /* Got timeout */
350                         CloseHandle(pi.hProcess);
351                         CloseHandle(pi.hThread);
352                         CloseHandle(childstdoutwr);
353                         CloseHandle(childstdoutrddup);
354                         return NULL;
355                 }
356
357                 /* We try just once */
358                 if (ReadFile(childstdoutrddup, line, maxsize, &bytesread, NULL) &&
359                         bytesread > 0)
360                 {
361                         /* So we read some data */
362                         int                     len = strlen(line);
363                         retval = line;
364
365                         /*
366                          * If EOL is \r\n, convert to just \n. Because stdout is a
367                          * text-mode stream, the \n output by the child process is
368                          * received as \r\n, so we convert it to \n.  The server
369                          * main.c sets setvbuf(stdout, NULL, _IONBF, 0) which has the
370                          * effect of disabling \n to \r\n expansion for stdout.
371                          */
372                         if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
373                         {
374                                 line[len - 2] = '\n';
375                                 line[len - 1] = '\0';
376                                 len--;
377                         }
378
379                         /*
380                          * We emulate fgets() behaviour. So if there is no newline at
381                          * the end, we add one...
382                          */
383                         if (len == 0 || line[len - 1] != '\n')
384                                 strcat(line, "\n");
385                 }
386
387                 CloseHandle(pi.hProcess);
388                 CloseHandle(pi.hThread);
389         }
390
391         CloseHandle(childstdoutwr);
392         CloseHandle(childstdoutrddup);
393
394         return retval;
395 #endif
396 }
397
398
399 /*
400  * Find another program in our binary's directory,
401  * then make sure it is the proper version.
402  */
403 int
404 find_other_exec(const char *argv0, const char *target,
405                                 const char *versionstr, char *retpath)
406 {
407         char            cmd[MAXPGPATH];
408         char            line[100];
409
410         if (find_my_exec(argv0, retpath) < 0)
411                 return -1;
412
413         /* Trim off program name and keep just directory */
414         *last_dir_separator(retpath) = '\0';
415         canonicalize_path(retpath);
416
417         /* Now append the other program's name */
418         snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
419                          "/%s%s", target, EXE);
420
421         if (validate_exec(retpath))
422                 return -1;
423
424         snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);
425
426         if (!pipe_read_line(cmd, line, sizeof(line)))
427                 return -1;
428
429         if (strcmp(line, versionstr) != 0)
430                 return -2;
431
432         return 0;
433 }
434
435
436 /*
437  * pclose() plus useful error reporting
438  * Is this necessary?  bjm 2004-05-11
439  * It is better here because pipe.c has win32 backend linkage.
440  */
441 int
442 pclose_check(FILE *stream)
443 {
444         int                     exitstatus;
445
446         exitstatus = pclose(stream);
447
448         if (exitstatus == 0)
449                 return 0;                               /* all is well */
450
451         if (exitstatus == -1)
452         {
453                 /* pclose() itself failed, and hopefully set errno */
454                 perror("pclose failed");
455         }
456         else if (WIFEXITED(exitstatus))
457         {
458                 log_error(_("child process exited with exit code %d"),
459                                   WEXITSTATUS(exitstatus));
460         }
461         else if (WIFSIGNALED(exitstatus))
462         {
463                 log_error(_("child process was terminated by signal %d"),
464                                   WTERMSIG(exitstatus));
465         }
466         else
467         {
468                 log_error(_("child process exited with unrecognized status %d"),
469                                   exitstatus);
470         }
471
472         return -1;
473 }