8dacd431d4b741ae700da9afa9f5daff961d6fc8
[oweals/gnunet.git] / src / util / os_installation.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2006-2018 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21 /**
22  * @file src/util/os_installation.c
23  * @brief get paths used by the program
24  * @author Milan
25  * @author Christian Fuchs
26  * @author Christian Grothoff
27  * @author Matthias Wachs
28  * @author Heikki Lindholm
29  * @author LRN
30  */
31 #include <sys/stat.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <unistr.h> /* for u16_to_u8 */
36
37 #include "platform.h"
38 #include "gnunet_util_lib.h"
39 #if DARWIN
40 #include <mach-o/ldsyms.h>
41 #include <mach-o/dyld.h>
42 #elif WINDOWS
43 #include <windows.h>
44 #endif
45
46
47 #define LOG(kind, ...) \
48   GNUNET_log_from (kind, "util-os-installation", __VA_ARGS__)
49
50 #define LOG_STRERROR_FILE(kind, syscall, filename)       \
51   GNUNET_log_from_strerror_file (kind,                   \
52                                  "util-os-installation", \
53                                  syscall,                \
54                                  filename)
55
56
57 /**
58  * Default project data used for installation path detection
59  * for GNUnet (core).
60  */
61 static const struct GNUNET_OS_ProjectData default_pd = {
62   .libname = "libgnunetutil",
63   .project_dirname = "gnunet",
64   .binary_name = "gnunet-arm",
65   .version = PACKAGE_VERSION " " VCS_VERSION,
66   .env_varname = "GNUNET_PREFIX",
67   .base_config_varname = "GNUNET_BASE_CONFIG",
68   .bug_email = "gnunet-developers@gnu.org",
69   .homepage = "http://www.gnu.org/s/gnunet/",
70   .config_file = "gnunet.conf",
71   .user_config_file = "~/.config/gnunet.conf",
72   .is_gnu = 1,
73   .gettext_domain = PACKAGE,
74   .gettext_path = NULL,
75 };
76
77 /**
78  * Which project data do we currently use for installation
79  * path detection? Never NULL.
80  */
81 static const struct GNUNET_OS_ProjectData *current_pd = &default_pd;
82
83 /**
84  * Wether or not gettext has been initialized for the library.
85  * Note that the gettext initialization done within
86  * GNUNET_PROGRAM_run2 is for the specific application.
87  */
88 static int gettextinit = 0;
89
90 /**
91  * Return default project data used by 'libgnunetutil' for GNUnet.
92  */
93 const struct GNUNET_OS_ProjectData *
94 GNUNET_OS_project_data_default (void)
95 {
96   return &default_pd;
97 }
98
99
100 /**
101  * @return current project data.
102  */
103 const struct GNUNET_OS_ProjectData *
104 GNUNET_OS_project_data_get ()
105 {
106   if (0 == gettextinit)
107   {
108     char *path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
109     if (NULL != path)
110       BINDTEXTDOMAIN (PACKAGE, path);
111     GNUNET_free (path);
112     gettextinit = 1;
113   }
114   return current_pd;
115 }
116
117
118 /**
119  * Setup OS subsystem with project data.
120  *
121  * @param pd project data used to determine paths
122  */
123 void
124 GNUNET_OS_init (const struct GNUNET_OS_ProjectData *pd)
125 {
126   if (0 == gettextinit)
127   {
128     char *path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
129     if (NULL != path)
130       BINDTEXTDOMAIN (PACKAGE, path);
131     GNUNET_free (path);
132     gettextinit = 1;
133   }
134   GNUNET_assert (NULL != pd);
135   current_pd = pd;
136 }
137
138
139 #if LINUX
140 /**
141  * Try to determine path by reading /proc/PID/exe
142  *
143  * @return NULL on error
144  */
145 static char *
146 get_path_from_proc_maps ()
147 {
148   char fn[64];
149   char line[1024];
150   char dir[1024];
151   FILE *f;
152   char *lgu;
153
154   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/maps", getpid ());
155   if (NULL == (f = FOPEN (fn, "r")))
156     return NULL;
157   while (NULL != fgets (line, sizeof (line), f))
158   {
159     if ((1 == SSCANF (line,
160                       "%*x-%*x %*c%*c%*c%*c %*x %*2x:%*2x %*u%*[ ]%1023s",
161                       dir)) &&
162         (NULL != (lgu = strstr (dir, current_pd->libname))))
163     {
164       lgu[0] = '\0';
165       FCLOSE (f);
166       return GNUNET_strdup (dir);
167     }
168   }
169   FCLOSE (f);
170   return NULL;
171 }
172
173
174 /**
175  * Try to determine path by reading /proc/PID/exe
176  *
177  * @return NULL on error
178  */
179 static char *
180 get_path_from_proc_exe ()
181 {
182   char fn[64];
183   char lnk[1024];
184   ssize_t size;
185   char *lep;
186
187   GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/exe", getpid ());
188   size = readlink (fn, lnk, sizeof (lnk) - 1);
189   if (size <= 0)
190   {
191     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "readlink", fn);
192     return NULL;
193   }
194   GNUNET_assert (((size_t) size) < sizeof (lnk));
195   lnk[size] = '\0';
196   while ((lnk[size] != '/') && (size > 0))
197     size--;
198   GNUNET_asprintf (&lep, "/%s/libexec/", current_pd->project_dirname);
199   /* test for being in lib/gnunet/libexec/ or lib/MULTIARCH/gnunet/libexec */
200   if ((((size_t) size) > strlen (lep)) &&
201       (0 == strcmp (lep, &lnk[size - strlen (lep)])))
202     size -= strlen (lep) - 1;
203   GNUNET_free (lep);
204   if ((size < 4) || (lnk[size - 4] != '/'))
205   {
206     /* not installed in "/bin/" -- binary path probably useless */
207     return NULL;
208   }
209   lnk[size] = '\0';
210   return GNUNET_strdup (lnk);
211 }
212 #endif
213
214
215 #if WINDOWS
216 static HINSTANCE dll_instance;
217
218
219 /**
220  * GNUNET_util_cl_init() in common_logging.c is preferred.
221  * This function is only for thread-local storage (not used in GNUnet)
222  * and hInstance saving.
223  */
224 BOOL WINAPI
225 DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
226 {
227   switch (fdwReason)
228   {
229   case DLL_PROCESS_ATTACH:
230     dll_instance = hinstDLL;
231     break;
232   case DLL_THREAD_ATTACH:
233     break;
234   case DLL_THREAD_DETACH:
235     break;
236   case DLL_PROCESS_DETACH:
237     break;
238   }
239   return TRUE;
240 }
241
242
243 /**
244  * Try to determine path with win32-specific function
245  *
246  * @return NULL on error
247  */
248 static char *
249 get_path_from_module_filename ()
250 {
251   size_t pathlen = 512;
252   DWORD real_pathlen;
253   wchar_t *idx;
254   wchar_t *modulepath = NULL;
255   char *upath;
256   uint8_t *u8_string;
257   size_t u8_string_length;
258
259   /* This braindead function won't tell us how much space it needs, so
260    * we start at 1024 and double the space up if it doesn't fit, until
261    * it fits, or we exceed the threshold.
262    */
263   do
264   {
265     pathlen = pathlen * 2;
266     modulepath = GNUNET_realloc (modulepath, pathlen * sizeof (wchar_t));
267     SetLastError (0);
268     real_pathlen =
269       GetModuleFileNameW (dll_instance, modulepath, pathlen * sizeof (wchar_t));
270   } while (real_pathlen >= pathlen && pathlen < 16 * 1024);
271   if (real_pathlen >= pathlen)
272     GNUNET_assert (0);
273   /* To be safe */
274   modulepath[real_pathlen] = '\0';
275
276   idx = modulepath + real_pathlen;
277   while ((idx > modulepath) && (*idx != L'\\') && (*idx != L'/'))
278     idx--;
279   *idx = L'\0';
280
281   /* Now modulepath holds full path to the directory where libgnunetutil is.
282    * This directory should look like <GNUNET_PREFIX>/bin or <GNUNET_PREFIX>.
283    */
284   if (wcschr (modulepath, L'/') || wcschr (modulepath, L'\\'))
285   {
286     /* At least one directory component (i.e. we're not in a root directory) */
287     wchar_t *dirname = idx;
288     while ((dirname > modulepath) && (*dirname != L'\\') && (*dirname != L'/'))
289       dirname--;
290     *dirname = L'\0';
291     if (dirname > modulepath)
292     {
293       dirname++;
294       /* Now modulepath holds full path to the parent directory of the directory
295        * where libgnunetutil is.
296        * dirname holds the name of the directory where libgnunetutil is.
297        */
298       if (wcsicmp (dirname, L"bin") == 0)
299       {
300         /* pass */
301       }
302       else
303       {
304         /* Roll back our changes to modulepath */
305         dirname--;
306         *dirname = L'/';
307       }
308     }
309   }
310
311   /* modulepath is GNUNET_PREFIX */
312   u8_string =
313     u16_to_u8 (modulepath, wcslen (modulepath), NULL, &u8_string_length);
314   if (NULL == u8_string)
315     GNUNET_assert (0);
316
317   upath = GNUNET_malloc (u8_string_length + 1);
318   GNUNET_memcpy (upath, u8_string, u8_string_length);
319   upath[u8_string_length] = '\0';
320
321   free (u8_string);
322   GNUNET_free (modulepath);
323
324   return upath;
325 }
326 #endif
327
328
329 #if DARWIN
330 /**
331  * Signature of the '_NSGetExecutablePath" function.
332  *
333  * @param buf where to write the path
334  * @param number of bytes available in @a buf
335  * @return 0 on success, otherwise desired number of bytes is stored in 'bufsize'
336  */
337 typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t *bufsize);
338
339
340 /**
341  * Try to obtain the path of our executable using '_NSGetExecutablePath'.
342  *
343  * @return NULL on error
344  */
345 static char *
346 get_path_from_NSGetExecutablePath ()
347 {
348   static char zero = '\0';
349   char *path;
350   size_t len;
351   MyNSGetExecutablePathProto func;
352
353   path = NULL;
354   if (NULL ==
355       (func = (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT,
356                                                   "_NSGetExecutablePath")))
357     return NULL;
358   path = &zero;
359   len = 0;
360   /* get the path len, including the trailing \0 */
361   (void) func (path, &len);
362   if (0 == len)
363     return NULL;
364   path = GNUNET_malloc (len);
365   if (0 != func (path, &len))
366   {
367     GNUNET_free (path);
368     return NULL;
369   }
370   len = strlen (path);
371   while ((path[len] != '/') && (len > 0))
372     len--;
373   path[len] = '\0';
374   return path;
375 }
376
377
378 /**
379  * Try to obtain the path of our executable using '_dyld_image' API.
380  *
381  * @return NULL on error
382  */
383 static char *
384 get_path_from_dyld_image ()
385 {
386   const char *path;
387   char *p;
388   char *s;
389   unsigned int i;
390   int c;
391
392   c = _dyld_image_count ();
393   for (i = 0; i < c; i++)
394   {
395     if (((const void *) _dyld_get_image_header (i)) !=
396         ((const void *) &_mh_dylib_header))
397       continue;
398     path = _dyld_get_image_name (i);
399     if ((NULL == path) || (0 == strlen (path)))
400       continue;
401     p = GNUNET_strdup (path);
402     s = p + strlen (p);
403     while ((s > p) && ('/' != *s))
404       s--;
405     s++;
406     *s = '\0';
407     return p;
408   }
409   return NULL;
410 }
411 #endif
412
413
414 /**
415  * Return the actual path to a file found in the current
416  * PATH environment variable.
417  *
418  * @param binary the name of the file to find
419  * @return path to binary, NULL if not found
420  */
421 static char *
422 get_path_from_PATH (const char *binary)
423 {
424   char *path;
425   char *pos;
426   char *end;
427   char *buf;
428   const char *p;
429
430   if (NULL == (p = getenv ("PATH")))
431     return NULL;
432 #if WINDOWS
433   /* On W32 look in CWD first. */
434   GNUNET_asprintf (&path, ".%c%s", PATH_SEPARATOR, p);
435 #else
436   path = GNUNET_strdup (p); /* because we write on it */
437 #endif
438   buf = GNUNET_malloc (strlen (path) + strlen (binary) + 1 + 1);
439   pos = path;
440   while (NULL != (end = strchr (pos, PATH_SEPARATOR)))
441   {
442     *end = '\0';
443     sprintf (buf, "%s/%s", pos, binary);
444     if (GNUNET_DISK_file_test (buf) == GNUNET_YES)
445     {
446       pos = GNUNET_strdup (pos);
447       GNUNET_free (buf);
448       GNUNET_free (path);
449       return pos;
450     }
451     pos = end + 1;
452   }
453   sprintf (buf, "%s/%s", pos, binary);
454   if (GNUNET_YES == GNUNET_DISK_file_test (buf))
455   {
456     pos = GNUNET_strdup (pos);
457     GNUNET_free (buf);
458     GNUNET_free (path);
459     return pos;
460   }
461   GNUNET_free (buf);
462   GNUNET_free (path);
463   return NULL;
464 }
465
466
467 /**
468  * Try to obtain the installation path using the "GNUNET_PREFIX" environment
469  * variable.
470  *
471  * @return NULL on error (environment variable not set)
472  */
473 static char *
474 get_path_from_GNUNET_PREFIX ()
475 {
476   const char *p;
477
478   if ((NULL != current_pd->env_varname) &&
479       (NULL != (p = getenv (current_pd->env_varname))))
480     return GNUNET_strdup (p);
481   if ((NULL != current_pd->env_varname_alt) &&
482       (NULL != (p = getenv (current_pd->env_varname_alt))))
483     return GNUNET_strdup (p);
484   return NULL;
485 }
486
487
488 /**
489  * @brief get the path to GNUnet bin/ or lib/, prefering the lib/ path
490  * @author Milan
491  *
492  * @return a pointer to the executable path, or NULL on error
493  */
494 static char *
495 os_get_gnunet_path ()
496 {
497   char *ret;
498
499   if (NULL != (ret = get_path_from_GNUNET_PREFIX ()))
500     return ret;
501 #if LINUX
502   if (NULL != (ret = get_path_from_proc_maps ()))
503     return ret;
504   /* try path *first*, before /proc/exe, as /proc/exe can be wrong */
505   if ((NULL != current_pd->binary_name) &&
506       (NULL != (ret = get_path_from_PATH (current_pd->binary_name))))
507     return ret;
508   if (NULL != (ret = get_path_from_proc_exe ()))
509     return ret;
510 #endif
511 #if WINDOWS
512   if (NULL != (ret = get_path_from_module_filename ()))
513     return ret;
514 #endif
515 #if DARWIN
516   if (NULL != (ret = get_path_from_dyld_image ()))
517     return ret;
518   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
519     return ret;
520 #endif
521   if ((NULL != current_pd->binary_name) &&
522       (NULL != (ret = get_path_from_PATH (current_pd->binary_name))))
523     return ret;
524   /* other attempts here */
525   LOG (GNUNET_ERROR_TYPE_ERROR,
526        _ (
527          "Could not determine installation path for %s.  Set `%s' environment variable.\n"),
528        current_pd->project_dirname,
529        current_pd->env_varname);
530   return NULL;
531 }
532
533
534 /**
535  * @brief get the path to current app's bin/
536  * @return a pointer to the executable path, or NULL on error
537  */
538 static char *
539 os_get_exec_path ()
540 {
541   char *ret = NULL;
542
543 #if LINUX
544   if (NULL != (ret = get_path_from_proc_exe ()))
545     return ret;
546 #endif
547 #if WINDOWS
548   if (NULL != (ret = get_path_from_module_filename ()))
549     return ret;
550 #endif
551 #if DARWIN
552   if (NULL != (ret = get_path_from_NSGetExecutablePath ()))
553     return ret;
554 #endif
555   /* other attempts here */
556   return ret;
557 }
558
559
560 /**
561  * @brief get the path to a specific GNUnet installation directory or,
562  * with #GNUNET_OS_IPK_SELF_PREFIX, the current running apps installation directory
563  * @return a pointer to the dir path (to be freed by the caller)
564  */
565 char *
566 GNUNET_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind)
567 {
568   size_t n;
569   char *dirname;
570   char *execpath = NULL;
571   char *tmp;
572   char *multiarch;
573   char *libdir;
574   int isbasedir;
575
576   /* if wanted, try to get the current app's bin/ */
577   if (dirkind == GNUNET_OS_IPK_SELF_PREFIX)
578     execpath = os_get_exec_path ();
579
580   /* try to get GNUnet's bin/ or lib/, or if previous was unsuccessful some
581    * guess for the current app */
582   if (NULL == execpath)
583     execpath = os_get_gnunet_path ();
584
585   if (NULL == execpath)
586     return NULL;
587
588   n = strlen (execpath);
589   if (0 == n)
590   {
591     /* should never happen, but better safe than sorry */
592     GNUNET_free (execpath);
593     return NULL;
594   }
595   /* remove filename itself */
596   while ((n > 1) && (DIR_SEPARATOR == execpath[n - 1]))
597     execpath[--n] = '\0';
598
599   isbasedir = 1;
600   if ((n > 6) && ((0 == strcasecmp (&execpath[n - 6], "/lib32")) ||
601                   (0 == strcasecmp (&execpath[n - 6], "/lib64"))))
602   {
603     if ((GNUNET_OS_IPK_LIBDIR != dirkind) &&
604         (GNUNET_OS_IPK_LIBEXECDIR != dirkind))
605     {
606       /* strip '/lib32' or '/lib64' */
607       execpath[n - 6] = '\0';
608       n -= 6;
609     }
610     else
611       isbasedir = 0;
612   }
613   else if ((n > 4) && ((0 == strcasecmp (&execpath[n - 4], "/bin")) ||
614                        (0 == strcasecmp (&execpath[n - 4], "/lib"))))
615   {
616     /* strip '/bin' or '/lib' */
617     execpath[n - 4] = '\0';
618     n -= 4;
619   }
620   multiarch = NULL;
621   if (NULL != (libdir = strstr (execpath, "/lib/")))
622   {
623     /* test for multi-arch path of the form "PREFIX/lib/MULTIARCH/";
624        here we need to re-add 'multiarch' to lib and libexec paths later! */
625     multiarch = &libdir[5];
626     if (NULL == strchr (multiarch, '/'))
627       libdir[0] =
628         '\0'; /* Debian multiarch format, cut of from 'execpath' but preserve in multicarch */
629     else
630       multiarch =
631         NULL; /* maybe not, multiarch still has a '/', which is not OK */
632   }
633   /* in case this was a directory named foo-bin, remove "foo-" */
634   while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR))
635     execpath[--n] = '\0';
636   switch (dirkind)
637   {
638   case GNUNET_OS_IPK_PREFIX:
639   case GNUNET_OS_IPK_SELF_PREFIX:
640     dirname = GNUNET_strdup (DIR_SEPARATOR_STR);
641     break;
642   case GNUNET_OS_IPK_BINDIR:
643     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR);
644     break;
645   case GNUNET_OS_IPK_LIBDIR:
646     if (isbasedir)
647     {
648       GNUNET_asprintf (&tmp,
649                        "%s%s%s%s%s%s%s",
650                        execpath,
651                        DIR_SEPARATOR_STR "lib",
652                        (NULL != multiarch) ? DIR_SEPARATOR_STR : "",
653                        (NULL != multiarch) ? multiarch : "",
654                        DIR_SEPARATOR_STR,
655                        current_pd->project_dirname,
656                        DIR_SEPARATOR_STR);
657       if (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES))
658       {
659         GNUNET_free (execpath);
660         return tmp;
661       }
662       GNUNET_free (tmp);
663       tmp = NULL;
664       dirname = NULL;
665       if (4 == sizeof (void *))
666       {
667         GNUNET_asprintf (&dirname,
668                          DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR
669                          "%s" DIR_SEPARATOR_STR,
670                          current_pd->project_dirname);
671         GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
672       }
673       if (8 == sizeof (void *))
674       {
675         GNUNET_asprintf (&dirname,
676                          DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR
677                          "%s" DIR_SEPARATOR_STR,
678                          current_pd->project_dirname);
679         GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
680       }
681
682       if ((NULL != tmp) &&
683           (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES)))
684       {
685         GNUNET_free (execpath);
686         GNUNET_free_non_null (dirname);
687         return tmp;
688       }
689       GNUNET_free (tmp);
690       GNUNET_free_non_null (dirname);
691     }
692     GNUNET_asprintf (&dirname,
693                      DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR,
694                      current_pd->project_dirname);
695     break;
696   case GNUNET_OS_IPK_DATADIR:
697     GNUNET_asprintf (&dirname,
698                      DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
699                      "%s" DIR_SEPARATOR_STR,
700                      current_pd->project_dirname);
701     break;
702   case GNUNET_OS_IPK_LOCALEDIR:
703     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
704                              "locale" DIR_SEPARATOR_STR);
705     break;
706   case GNUNET_OS_IPK_ICONDIR:
707     dirname = GNUNET_strdup (DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
708                              "icons" DIR_SEPARATOR_STR);
709     break;
710   case GNUNET_OS_IPK_DOCDIR:
711     GNUNET_asprintf (&dirname,
712                      DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR
713                      "doc" DIR_SEPARATOR_STR
714                      "%s" DIR_SEPARATOR_STR,
715                      current_pd->project_dirname);
716     break;
717   case GNUNET_OS_IPK_LIBEXECDIR:
718     if (isbasedir)
719     {
720       GNUNET_asprintf (&dirname,
721                        DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR
722                        "libexec" DIR_SEPARATOR_STR,
723                        current_pd->project_dirname);
724       GNUNET_asprintf (&tmp,
725                        "%s%s%s%s",
726                        execpath,
727                        DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR,
728                        (NULL != multiarch) ? multiarch : "",
729                        dirname);
730       if (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES))
731       {
732         GNUNET_free (execpath);
733         GNUNET_free (dirname);
734         return tmp;
735       }
736       GNUNET_free (tmp);
737       tmp = NULL;
738       dirname = NULL;
739       if (4 == sizeof (void *))
740       {
741         GNUNET_asprintf (&dirname,
742                          DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR
743                          "%s" DIR_SEPARATOR_STR
744                          "libexec" DIR_SEPARATOR_STR,
745                          current_pd->project_dirname);
746         GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
747       }
748       if (8 == sizeof (void *))
749       {
750         GNUNET_asprintf (&dirname,
751                          DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR
752                          "%s" DIR_SEPARATOR_STR
753                          "libexec" DIR_SEPARATOR_STR,
754                          current_pd->project_dirname);
755         GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
756       }
757       if ((NULL != tmp) &&
758           (GNUNET_YES == GNUNET_DISK_directory_test (tmp, GNUNET_YES)))
759       {
760         GNUNET_free (execpath);
761         GNUNET_free_non_null (dirname);
762         return tmp;
763       }
764       GNUNET_free (tmp);
765       GNUNET_free_non_null (dirname);
766     }
767     GNUNET_asprintf (&dirname,
768                      DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR
769                      "libexec" DIR_SEPARATOR_STR,
770                      current_pd->project_dirname);
771     break;
772   default:
773     GNUNET_free (execpath);
774     return NULL;
775   }
776   GNUNET_asprintf (&tmp, "%s%s", execpath, dirname);
777   GNUNET_free (dirname);
778   GNUNET_free (execpath);
779   return tmp;
780 }
781
782
783 /**
784  * Given the name of a gnunet-helper, gnunet-service or gnunet-daemon
785  * binary, try to prefix it with the libexec/-directory to get the
786  * full path.
787  *
788  * @param progname name of the binary
789  * @return full path to the binary, if possible, otherwise copy of 'progname'
790  */
791 char *
792 GNUNET_OS_get_libexec_binary_path (const char *progname)
793 {
794   static char *cache;
795   char *libexecdir;
796   char *binary;
797
798   if ((DIR_SEPARATOR == progname[0]) ||
799       (GNUNET_YES ==
800        GNUNET_STRINGS_path_is_absolute (progname, GNUNET_NO, NULL, NULL)))
801     return GNUNET_strdup (progname);
802   if (NULL != cache)
803     libexecdir = cache;
804   else
805     libexecdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
806   if (NULL == libexecdir)
807     return GNUNET_strdup (progname);
808   GNUNET_asprintf (&binary, "%s%s", libexecdir, progname);
809   cache = libexecdir;
810   return binary;
811 }
812
813
814 /**
815  * Given the name of a helper, service or daemon binary construct the full
816  * path to the binary using the SUID_BINARY_PATH in the PATHS section of the
817  * configuration. If that option is not present, fall back to
818  * GNUNET_OS_get_libexec_binary_path. If @a progname is an absolute path, a
819  * copy of this path is returned.
820  *
821  * @param cfg configuration to inspect
822  * @param progname name of the binary
823  * @return full path to the binary, if possible, a copy of @a progname
824  *         otherwise
825  */
826 char *
827 GNUNET_OS_get_suid_binary_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
828                                 const char *progname)
829 {
830   static char *cache;
831   char *binary = NULL;
832   char *path = NULL;
833   size_t path_len;
834
835   if (GNUNET_YES ==
836       GNUNET_STRINGS_path_is_absolute (progname, GNUNET_NO, NULL, NULL))
837   {
838     return GNUNET_strdup (progname);
839   }
840   if (NULL != cache)
841     path = cache;
842   else
843     GNUNET_CONFIGURATION_get_value_string (cfg,
844                                            "PATHS",
845                                            "SUID_BINARY_PATH",
846                                            &path);
847   if ((NULL == path)||(0 == strlen (path)))
848     return GNUNET_OS_get_libexec_binary_path (progname);
849   path_len = strlen (path);
850   GNUNET_asprintf (&binary,
851                    "%s%s%s",
852                    path,
853                    (path[path_len - 1] == DIR_SEPARATOR) ? ""
854                    : DIR_SEPARATOR_STR,
855                    progname);
856   cache = path;
857   return binary;
858 }
859
860
861 /**
862  * Check whether an executable exists and possibly if the suid bit is
863  * set on the file.  Attempts to find the file using the current PATH
864  * environment variable as a search path.
865  *
866  * @param binary the name of the file to check.
867  *        W32: must not have an .exe suffix.
868  * @param check_suid input true if the binary should be checked for SUID (*nix)
869  *        W32: checks if the program has sufficient privileges by executing this
870  *             binary with the -d flag. -d omits a programs main loop and only
871  *             executes all privileged operations in an binary.
872  * @param params parameters used for w32 privilege checking (can be NULL for != w32 )
873  * @return #GNUNET_YES if the file is SUID (*nix) or can be executed with current privileges (W32),
874  *         #GNUNET_NO if not SUID (but binary exists),
875  *         #GNUNET_SYSERR on error (no such binary or not executable)
876  */
877 int
878 GNUNET_OS_check_helper_binary (const char *binary,
879                                int check_suid,
880                                const char *params)
881 {
882   struct stat statbuf;
883   char *p;
884   char *pf;
885 #ifdef MINGW
886   char *binaryexe;
887
888   GNUNET_asprintf (&binaryexe, "%s.exe", binary);
889   if ((GNUNET_YES ==
890        GNUNET_STRINGS_path_is_absolute (binaryexe, GNUNET_NO, NULL, NULL)) ||
891       (0 == strncmp (binary, "./", 2)))
892     p = GNUNET_strdup (binaryexe);
893   else
894   {
895     p = get_path_from_PATH (binaryexe);
896     if (NULL != p)
897     {
898       GNUNET_asprintf (&pf, "%s/%s", p, binaryexe);
899       GNUNET_free (p);
900       p = pf;
901     }
902   }
903   GNUNET_free (binaryexe);
904 #else
905   if ((GNUNET_YES ==
906        GNUNET_STRINGS_path_is_absolute (binary, GNUNET_NO, NULL, NULL)) ||
907       (0 == strncmp (binary, "./", 2)))
908   {
909     p = GNUNET_strdup (binary);
910   }
911   else
912   {
913     p = get_path_from_PATH (binary);
914     if (NULL != p)
915     {
916       GNUNET_asprintf (&pf, "%s/%s", p, binary);
917       GNUNET_free (p);
918       p = pf;
919     }
920   }
921 #endif
922   if (NULL == p)
923   {
924     LOG (GNUNET_ERROR_TYPE_INFO,
925          _ ("Could not find binary `%s' in PATH!\n"),
926          binary);
927     return GNUNET_SYSERR;
928   }
929   if (0 != ACCESS (p, X_OK))
930   {
931     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "access", p);
932     GNUNET_free (p);
933     return GNUNET_SYSERR;
934   }
935 #ifndef MINGW
936   if (0 == getuid ())
937   {
938     /* as we run as root, we don't insist on SUID */
939     GNUNET_free (p);
940     return GNUNET_YES;
941   }
942 #endif
943   if (0 != STAT (p, &statbuf))
944   {
945     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "stat", p);
946     GNUNET_free (p);
947     return GNUNET_SYSERR;
948   }
949   if (check_suid)
950   {
951 #ifndef MINGW
952     (void) params;
953     if ((0 != (statbuf.st_mode & S_ISUID)) && (0 == statbuf.st_uid))
954     {
955       GNUNET_free (p);
956       return GNUNET_YES;
957     }
958     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
959                 _ ("Binary `%s' exists, but is not SUID\n"),
960                 p);
961     /* binary exists, but not SUID */
962 #else
963     STARTUPINFO start;
964     char parameters[512];
965     PROCESS_INFORMATION proc;
966     DWORD exit_value;
967
968     GNUNET_snprintf (parameters, sizeof (parameters), "-d %s", params);
969     memset (&start, 0, sizeof (start));
970     start.cb = sizeof (start);
971     memset (&proc, 0, sizeof (proc));
972
973
974     // Start the child process.
975     if (! (CreateProcess (
976              p, // current windows (2k3 and up can handle / instead of \ in paths))
977              parameters, // execute dryrun/priviliege checking mode
978              NULL, // Process handle not inheritable
979              NULL, // Thread handle not inheritable
980              FALSE, // Set handle inheritance to FALSE
981              CREATE_DEFAULT_ERROR_MODE, // No creation flags
982              NULL, // Use parent's environment block
983              NULL, // Use parent's starting directory
984              &start, // Pointer to STARTUPINFO structure
985              &proc) // Pointer to PROCESS_INFORMATION structure
986            ))
987     {
988       LOG (GNUNET_ERROR_TYPE_ERROR,
989            _ ("CreateProcess failed for binary %s (%d).\n"),
990            p,
991            GetLastError ());
992       return GNUNET_SYSERR;
993     }
994
995     // Wait until child process exits.
996     WaitForSingleObject (proc.hProcess, INFINITE);
997
998     if (! GetExitCodeProcess (proc.hProcess, &exit_value))
999     {
1000       LOG (GNUNET_ERROR_TYPE_ERROR,
1001            _ ("GetExitCodeProcess failed for binary %s (%d).\n"),
1002            p,
1003            GetLastError ());
1004       return GNUNET_SYSERR;
1005     }
1006     // Close process and thread handles.
1007     CloseHandle (proc.hProcess);
1008     CloseHandle (proc.hThread);
1009
1010     if (! exit_value)
1011       return GNUNET_YES;
1012 #endif
1013   }
1014   GNUNET_free (p);
1015   return GNUNET_NO;
1016 }
1017
1018
1019 /* end of os_installation.c */