move SUID test code to util
[oweals/gnunet.git] / src / util / os_installation.c
1 /*
2      This file is part of GNUnet.
3      (C) 2006 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file src/util/os_installation.c
23  * @brief get paths used by the program
24  * @author Milan
25  */
26 #include <sys/stat.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include "platform.h"
32 #include "gnunet_common.h"
33 #include "gnunet_configuration_lib.h"
34 #include "gnunet_disk_lib.h"
35 #include "gnunet_os_lib.h"
36 #if DARWIN
37 #include <mach-o/ldsyms.h>
38 #include <mach-o/dyld.h>
39 #endif
40
41 #if LINUX
42 /**
43  * Try to determine path by reading /proc/PID/exe
44  */
45 static char *
46 get_path_from_proc_maps ()
47 {
48   char fn[64];
49   char line[1024];
50   char dir[1024];
51   FILE *f;
52   char *lgu;
53
54   GNUNET_snprintf (fn,
55                    sizeof(fn), 
56                    "/proc/%u/maps", 
57                    getpid ());
58   f = fopen (fn, "r");
59   if (f == NULL)
60     return NULL;
61   while (NULL != fgets (line, sizeof(line), f))
62     {
63       if ((1 == sscanf (line,
64                         "%*x-%*x %*c%*c%*c%*c %*x %*2u:%*2u %*u%*[ ]%s",
65                         dir)) &&
66           (NULL != (lgu = strstr (dir, "libgnunetutil"))))
67         {
68           lgu[0] = '\0';
69           fclose (f);
70           return GNUNET_strdup (dir);
71         }
72     }
73   fclose (f);
74   return NULL;
75 }
76
77 /**
78  * Try to determine path by reading /proc/PID/exe
79  */
80 static char *
81 get_path_from_proc_exe ()
82 {
83   char fn[64];
84   char lnk[1024];
85   ssize_t size;
86
87   GNUNET_snprintf (fn, 
88                    sizeof(fn), "/proc/%u/exe", getpid ());
89   size = readlink (fn, lnk, sizeof (lnk)-1);
90   if (size <= 0)
91     {
92       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
93       return NULL;
94     }
95   GNUNET_assert (size < sizeof (lnk));
96   lnk[size] = '\0';
97   while ((lnk[size] != '/') && (size > 0))
98     size--;
99   if ((size < 4) || (lnk[size - 4] != '/'))
100     {
101       /* not installed in "/bin/" -- binary path probably useless */
102       return NULL;
103     }
104   lnk[size] = '\0';
105   return GNUNET_strdup (lnk);
106 }
107 #endif
108
109 #if WINDOWS
110 /**
111  * Try to determine path with win32-specific function
112  */
113 static char *
114 get_path_from_module_filename ()
115 {
116   char path[4097];
117   char *idx;
118
119   GetModuleFileName (NULL, path, sizeof(path)-1);
120   idx = path + strlen (path);
121   while ((idx > path) && (*idx != '\\') && (*idx != '/'))
122     idx--;
123   *idx = '\0';
124   return GNUNET_strdup (path);
125 }
126 #endif
127
128 #if DARWIN
129 typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t * bufsize);
130
131 static char *
132 get_path_from_NSGetExecutablePath ()
133 {
134   static char zero = '\0';
135   char *path;
136   size_t len;
137   MyNSGetExecutablePathProto func;
138   int ret;
139
140   path = NULL;
141   func =
142     (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT, "_NSGetExecutablePath");
143   if (!func)
144     return NULL;
145   path = &zero;
146   len = 0;
147   /* get the path len, including the trailing \0 */
148   func (path, &len);
149   if (len == 0)
150     return NULL;
151   path = GNUNET_malloc (len);
152   ret = func (path, &len);
153   if (ret != 0)
154     {
155       GNUNET_free (path);
156       return NULL;
157     }
158   len = strlen (path);
159   while ((path[len] != '/') && (len > 0))
160     len--;
161   path[len] = '\0';
162   return path;
163 }
164
165 static char *
166 get_path_from_dyld_image ()
167 {
168   const char *path;
169   char *p, *s;
170   int i;
171   int c;
172
173   p = NULL;
174   c = _dyld_image_count ();
175   for (i = 0; i < c; i++)
176     {
177       if (_dyld_get_image_header (i) == &_mh_dylib_header)
178         {
179           path = _dyld_get_image_name (i);
180           if (path != NULL && strlen (path) > 0)
181             {
182               p = strdup (path);
183               s = p + strlen (p);
184               while ((s > p) && (*s != '/'))
185                 s--;
186               s++;
187               *s = '\0';
188             }
189           break;
190         }
191     }
192   return p;
193 }
194 #endif
195
196 /**
197  * Return the actual path to a file found in the current
198  * PATH environment variable.
199  *
200  * @param binary the name of the file to find
201  * @return path to binary, NULL if not found
202  */
203 static char *
204 get_path_from_PATH (const char *binary)
205 {
206   char *path;
207   char *pos;
208   char *end;
209   char *buf;
210   const char *p;
211
212   p = getenv ("PATH");
213   if (p == NULL)
214     return NULL;
215   path = GNUNET_strdup (p);     /* because we write on it */
216   buf = GNUNET_malloc (strlen (path) + 20);
217   pos = path;
218
219   while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
220     {
221       *end = '\0';
222       sprintf (buf, "%s/%s", pos, binary);
223       if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
224         {
225           pos = GNUNET_strdup (pos);
226           GNUNET_free (buf);
227           GNUNET_free (path);
228           return pos;
229         }
230       pos = end + 1;
231     }
232   sprintf (buf, "%s/%s", pos, binary);
233   if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
234     {
235       pos = GNUNET_strdup (pos);
236       GNUNET_free (buf);
237       GNUNET_free (path);
238       return pos;
239     }
240   GNUNET_free (buf);
241   GNUNET_free (path);
242   return NULL;
243 }
244
245 static char *
246 get_path_from_GNUNET_PREFIX ()
247 {
248   const char *p;
249
250   p = getenv ("GNUNET_PREFIX");
251   if (p != NULL)
252     return GNUNET_strdup (p);
253   return NULL;
254 }
255
256 /*
257  * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
258  * @author Milan
259  *
260  * @return a pointer to the executable path, or NULL on error
261  */
262 static char *
263 os_get_gnunet_path ()
264 {
265   char *ret;
266
267   ret = get_path_from_GNUNET_PREFIX ();
268   if (ret != NULL)
269     return ret;
270 #if LINUX
271   ret = get_path_from_proc_maps ();
272   if (ret != NULL)
273     return ret;
274   ret = get_path_from_proc_exe ();
275   if (ret != NULL)
276     return ret;
277 #endif
278 #if WINDOWS
279   ret = get_path_from_module_filename ();
280   if (ret != NULL)
281     return ret;
282 #endif
283 #if DARWIN
284   ret = get_path_from_dyld_image ();
285   if (ret != NULL)
286     return ret;
287   ret = get_path_from_NSGetExecutablePath ();
288   if (ret != NULL)
289     return ret;
290 #endif
291   ret = get_path_from_PATH ("gnunet-arm");
292   if (ret != NULL)
293     return ret;
294   /* other attempts here */
295   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
296               _
297               ("Could not determine installation path for %s.  Set `%s' environment variable.\n"),
298               "GNUnet",
299               "GNUNET_PREFIX");
300   return NULL;
301 }
302
303 /*
304  * @brief get the path to current app's bin/
305  * @author Milan
306  *
307  * @return a pointer to the executable path, or NULL on error
308  */
309 static char *
310 os_get_exec_path ()
311 {
312   char *ret;
313
314   ret = NULL;
315 #if LINUX
316   ret = get_path_from_proc_exe ();
317   if (ret != NULL)
318     return ret;
319 #endif
320 #if WINDOWS
321   ret = get_path_from_module_filename ();
322   if (ret != NULL)
323     return ret;
324 #endif
325 #if DARWIN
326   ret = get_path_from_NSGetExecutablePath ();
327   if (ret != NULL)
328     return ret;
329 #endif
330   /* other attempts here */
331   return ret;
332 }
333
334
335
336 /**
337  * @brief get the path to a specific GNUnet installation directory or,
338  * with GNUNET_IPK_SELF_PREFIX, the current running apps installation directory
339  * @author Milan
340  * @return a pointer to the dir path (to be freed by the caller)
341  */
342 char *
343 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
344 {
345   size_t n;
346   const char *dirname;
347   char *execpath = NULL;
348   char *tmp;
349   int isbasedir;
350
351   /* if wanted, try to get the current app's bin/ */
352   if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
353     execpath = os_get_exec_path ();
354
355   /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
356    * guess for the current app */
357   if (execpath == NULL)
358     execpath = os_get_gnunet_path ();
359
360   if (execpath == NULL)
361     return NULL;
362
363   n = strlen (execpath);
364   if (n == 0)
365     {
366       /* should never happen, but better safe than sorry */
367       GNUNET_free (execpath);
368       return NULL;
369     }
370   /* remove filename itself */
371   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
372     execpath[--n] = '\0';
373
374   isbasedir = 1;
375   if ((n > 5) &&
376       ((0 == strcasecmp (&execpath[n - 5], "lib32")) ||
377        (0 == strcasecmp (&execpath[n - 5], "lib64"))))
378     {
379       if (dirkind != GNUNET_OS_IPK_LIBDIR)
380         {
381           /* strip '/lib32' or '/lib64' */
382           execpath[n - 5] = '\0';
383           n -= 5;
384         }
385       else
386         isbasedir = 0;
387     }
388   else if ((n > 3) &&
389            ((0 == strcasecmp (&execpath[n - 3], "bin")) ||
390             (0 == strcasecmp (&execpath[n - 3], "lib"))))
391     {
392       /* strip '/bin' or '/lib' */
393       execpath[n - 3] = '\0';
394       n -= 3;
395     }
396   /* in case this was a directory named foo-bin, remove "foo-" */
397   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
398     execpath[--n] = '\0';
399   switch (dirkind)
400     {
401     case GNUNET_OS_IPK_PREFIX:
402     case GNUNET_OS_IPK_SELF_PREFIX:
403       dirname = DIR_SEPARATOR_STR;
404       break;
405     case GNUNET_OS_IPK_BINDIR:
406       dirname = DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR;
407       break;
408     case GNUNET_OS_IPK_LIBDIR:
409       if (isbasedir)
410         dirname =
411           DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR "gnunet"
412           DIR_SEPARATOR_STR;
413       else
414         dirname = DIR_SEPARATOR_STR "gnunet" DIR_SEPARATOR_STR;
415       break;
416     case GNUNET_OS_IPK_DATADIR:
417       dirname =
418         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "gnunet"
419         DIR_SEPARATOR_STR;
420       break;
421     case GNUNET_OS_IPK_LOCALEDIR:
422       dirname =
423         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale"
424         DIR_SEPARATOR_STR;
425       break;
426     case GNUNET_OS_IPK_ICONDIR:
427       dirname =
428         DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "icons" DIR_SEPARATOR_STR;
429       break;
430     default:
431       GNUNET_free (execpath);
432       return NULL;
433     }
434   tmp = GNUNET_malloc (strlen (execpath) + strlen (dirname) + 1);
435   sprintf (tmp, "%s%s", execpath, dirname);
436   GNUNET_free (execpath);
437   return tmp;
438 }
439
440
441 /**
442  * Check whether the suid bit is set on a file.
443  * Attempts to find the file using the current
444  * PATH environment variable as a search path.
445  *
446  * @param binary the name of the file to check
447  * @return GNUNET_YES if the file is SUID, 
448  *         GNUNET_NO if not, 
449  *         GNUNET_SYSERR on error
450  */
451 int
452 GNUNET_OS_check_helper_binary (const char *binary)
453 {
454   struct stat statbuf;
455   char *p;
456 #ifdef MINGW
457   SOCKET rawsock;
458   char *binaryexe;
459
460   GNUNET_asprintf (&binaryexe, "%s.exe", binary);
461   p = get_path_from_PATH (binaryexe);
462   free (binaryexe);
463 #else
464   p = get_path_from_PATH (binary);
465 #endif
466   if (p == NULL)
467     {
468       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
469                        "tcp",
470                        _("Could not find binary `%s' in PATH!\n"),
471                        binary);
472       return GNUNET_NO;
473     }
474   if (0 != STAT (p, &statbuf))
475     {
476       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
477                   _("stat (%s) failed: %s\n"), 
478                   p,
479                   STRERROR (errno));
480       GNUNET_free (p);
481       return GNUNET_SYSERR;
482     }
483   GNUNET_free (p);
484 #ifndef MINGW
485   if ( (0 != (statbuf.st_mode & S_ISUID)) &&
486        (statbuf.st_uid == 0) )
487     return GNUNET_YES;
488   return GNUNET_NO;
489 #else
490   rawsock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
491   if (INVALID_SOCKET == rawsock)
492     {
493       DWORD err = GetLastError ();
494       GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, 
495                        "tcp",
496                        "socket (AF_INET, SOCK_RAW, IPPROTO_ICMP) failed! GLE = %d\n", err);
497       return GNUNET_NO; /* not running as administrator */
498     }
499   closesocket (rawsock);
500   return GNUNET_YES;
501 #endif
502 }
503
504
505 /* end of os_installation.c */