Mon, 28 Jan 2013 17:37:18 +0100
Correct socket error reporting improvement with IPv6 portable code,
after helpful recommendation by Saúl Ibarra Corretgé on OSips devlist.
1 /*
2 ** openpkg.c -- OpenPKG Frontend
3 ** Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
4 **
5 ** This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
6 ** All rights reserved. Licenses which grant limited permission to use,
7 ** copy, modify and distribute this software are available from the
8 ** OpenPKG GmbH.
9 **
10 ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
11 ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
12 ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
13 ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
14 ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
15 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
16 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
17 ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
18 ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
19 ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
20 ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
21 ** SUCH DAMAGE.
22 */
24 /* program information */
25 #define LICENSE \
26 "Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>\n" \
27 "\n" \
28 "This software is property of the OpenPKG GmbH, DE MUC HRB 160208.\n" \
29 "All rights reserved. Licenses which grant limited permission to use,\n" \
30 "copy, modify and distribute this software are available from the\n" \
31 "OpenPKG GmbH.\n" \
32 "\n" \
33 "THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY EXPRESSED OR IMPLIED\n" \
34 "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n" \
35 "MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" \
36 "IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR\n" \
37 "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" \
38 "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" \
39 "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\n" \
40 "USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n" \
41 "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n" \
42 "OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\n" \
43 "OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n" \
44 "SUCH DAMAGE.\n"
46 /* system includes */
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <stdarg.h>
50 #include <string.h>
51 #include <sys/types.h>
52 #include <sys/param.h>
53 #include <sys/stat.h>
54 #include <pwd.h>
55 #include <grp.h>
56 #include <unistd.h>
57 #include <errno.h>
59 /* sanity check compilation */
60 #ifndef OPENPKG_PREFIX
61 #error OpenPKG instance prefix not defined
62 #endif
63 #ifndef OPENPKG_SUSR
64 #error OpenPKG super user not defined
65 #endif
66 #ifndef OPENPKG_MUSR
67 #error OpenPKG management user not defined
68 #endif
70 /* platform specifics */
71 #if defined(OPENPKG_PLATFORM_FREEBSD) || \
72 defined(OPENPKG_PLATFORM_NETBSD) || \
73 defined(OPENPKG_PLATFORM_OPENBSD) || \
74 defined(OPENPKG_PLATFORM_SUNOS) || \
75 defined(OPENPKG_PLATFORM_LINUX) || \
76 defined(OPENPKG_PLATFORM_DARWIN) || \
77 defined(OPENPKG_PLATFORM_AIX) || \
78 defined(OPENPKG_PLATFORM_IRIX) || \
79 defined(OPENPKG_PLATFORM_HPUX)
80 #define HAVE_INITGROUPS
81 #endif
83 /* global debug enable flag */
84 static int debug_enable = 0;
86 /* helper function: emulate (still less portable) setenv(3) via (more portable) putenv(3) */
87 static int my_setenv(const char *name, const char *value, int overwrite)
88 {
89 char *pair;
91 if (overwrite == 0 && getenv(name) != NULL)
92 return 0;
93 if ((pair = malloc(strlen(name) + 1 + strlen(value) + 1)) == NULL)
94 return -1;
95 strcpy(pair, name);
96 strcat(pair, "=");
97 strcat(pair, value);
98 putenv(pair);
99 return 0;
100 }
102 /* helper function for printing a warning message */
103 static void warn(const char *fmt, ...)
104 {
105 va_list ap;
107 va_start(ap, fmt);
108 fprintf(stderr, "openpkg:WARNING: ");
109 vfprintf(stderr, fmt, ap);
110 fprintf(stderr, "\n");
111 va_end(ap);
112 return;
113 }
115 /* helper function for printing a debug message */
116 static void debug(const char *fmt, ...)
117 {
118 va_list ap;
120 va_start(ap, fmt);
121 if (debug_enable) {
122 fprintf(stderr, "openpkg:DEBUG: ");
123 vfprintf(stderr, fmt, ap);
124 fprintf(stderr, "\n");
125 }
126 va_end(ap);
127 return;
128 }
130 /* helper function for printing a fatal message and exit */
131 static void fatal(const char *fmt, ...)
132 {
133 va_list ap;
135 va_start(ap, fmt);
136 fprintf(stderr, "openpkg:ERROR: ");
137 vfprintf(stderr, fmt, ap);
138 fprintf(stderr, "\n");
139 va_end(ap);
140 exit(1);
141 return;
142 }
144 /* adjust process privileges */
145 static void adjust_privileges(uid_t uid, gid_t gid, int login)
146 {
147 struct passwd *pw;
148 char cwd[MAXPATHLEN];
150 /* optionally emulate a more complete login */
151 if (login) {
152 /* determine information about user id */
153 if ((pw = getpwuid(uid)) == NULL)
154 fatal("unable to resolve user id \"%d\": %s", uid, strerror(errno));
156 /* reset some essential environment variables */
157 my_setenv("LOGNAME", pw->pw_name, 1);
158 my_setenv("USER", pw->pw_name, 1);
159 my_setenv("SHELL", pw->pw_shell, 1);
160 my_setenv("HOME", pw->pw_dir, 1);
162 #ifdef HAVE_INITGROUPS
163 /* initialize complete group access list */
164 if (initgroups(pw->pw_name, pw->pw_gid) == -1)
165 fatal("failed to initialize access group list via initgroups(3): %s", strerror(errno));
166 #endif
167 }
169 /* switch to group id (first) */
170 if (setgid(gid) == -1)
171 fatal("failed to set group id via setgid(2): %s", strerror(errno));
173 /* switch to user id (second) */
174 if (setuid(uid) == -1)
175 fatal("failed to set user id via setuid(2): %s", strerror(errno));
177 /* in case the current working directory is NO LONGER accessible,
178 try to switch to the home directory of the target identity to
179 prevent failures in subsequently called programs (e.g. GNU bash) */
180 if (login) {
181 if (getcwd(cwd, sizeof(cwd)) == NULL) {
182 warn("current working directory is no longer accessible -- switching to \"%s\"", pw->pw_dir);
183 if (chdir(pw->pw_dir) == -1)
184 fatal("unable to chdir(2) to \"%s\": %s", pw->pw_dir, strerror(errno));
185 }
186 }
188 return;
189 }
191 /* check whether caller is an explictly configured management user */
192 static int check_whether_is_manager(uid_t my_uid, gid_t my_gid, uid_t m_uid, gid_t m_gid)
193 {
194 char buf[1024];
195 char *username;
196 char *groupname;
197 char *filename;
198 struct stat sb;
199 char *cp;
200 FILE *fp;
201 struct passwd *pw;
202 struct group *gr;
203 int i;
204 int ok_uid;
205 int ok_gid;
206 int is_manager;
208 is_manager = 0;
210 /* path to the managers configuration file */
211 filename = OPENPKG_PREFIX "/etc/openpkg/managers";
213 /* check permissions of file */
214 if (stat(filename, &sb) == -1) {
215 warn("unable to determine information about configuration"
216 " file \"%s\": %s -- ignoring file", filename, strerror(errno));
217 return 0;
218 }
219 if (sb.st_uid != m_uid) {
220 warn("invalid owner user id %d (expected %d) on configuration"
221 " file \"%s\" -- ignoring file", sb.st_uid, m_uid, filename);
222 return 0;
223 }
224 if (sb.st_gid != m_gid) {
225 warn("invalid owner group id %d (expected %d) on configuration"
226 " file \"%s\" -- ignoring file", sb.st_gid, m_gid, filename);
227 return 0;
228 }
229 if (sb.st_mode != (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)) {
230 warn("invalid permissions on configuration"
231 " file \"%s\" -- ignoring file", filename);
232 return 0;
233 }
235 /* parse configuration file */
236 if ((fp = fopen(filename, "r")) == NULL) {
237 warn("unable to open configuration file \"%s\": %s -- ignoring file", filename, strerror(errno));
238 return 0;
239 }
240 while ((cp = fgets(buf, sizeof(buf), fp)) != NULL) {
241 /* parse entry as "<username>[:<groupname>]" where both
242 <username> and <groupname> can be set and default to "*"
243 to indicate an arbitrary user or group */
244 if ((i = strlen(buf)) == 0) {
245 warn("unexpected empty buffer during parsing of configuration file \"%\"", filename);
246 break;
247 }
248 if (i >= sizeof(buf)) {
249 warn("unexpected buffer overflow during parsing of configuration file \"%\"", filename);
250 break;
251 }
252 if (buf[i-1] != '\r' && buf[i-1] != '\n') {
253 warn("unexpected non-newline-terminated line found during parsing of configuration file \"%\"", filename);
254 break;
255 }
256 username = buf + strspn(buf, " \t");
257 cp = username + strcspn(username, " \t#\r\n");
258 *cp = '\0';
259 if (username[0] == '#' || username[0] == '\r' || username[0] == '\n' || username[0] == '\0')
260 continue;
261 groupname = "*";
262 if ((cp = strchr(username, ':')) != NULL) {
263 *cp++ = '\0';
264 groupname = cp;
265 }
266 debug("parsing result: username=\"%s\" groupname=\"%s\"", username, groupname);
268 /* check whether UID is ok */
269 ok_uid = 0;
270 if (strcmp(username, "*") == 0)
271 ok_uid = 1;
272 else {
273 if ((pw = getpwnam(username)) == NULL) {
274 warn("invalid username \"%s\" in \"%s\"", username, filename);
275 continue;
276 }
277 if (pw->pw_uid == my_uid)
278 ok_uid = 1;
279 }
281 /* check whether GID is ok */
282 ok_gid = 0;
283 if (strcmp(groupname, "*") == 0)
284 ok_gid = 1;
285 else {
286 if ((gr = getgrnam(groupname)) == NULL) {
287 warn("invalid groupname \"%s\" in \"%s\"", groupname, filename);
288 continue;
289 }
290 if (gr->gr_gid == my_gid)
291 ok_gid = 1;
292 }
294 /* if both UID and GID are ok, user is manager */
295 debug("matching: username ok = %s, groupname ok = %s", ok_uid ? "yes" : "no", ok_gid ? "yes" : "no");
296 if (ok_uid && ok_gid) {
297 is_manager = 1;
298 break;
299 }
300 }
301 fclose(fp);
302 return is_manager;
303 }
305 /* check whether command requires super-user privileges */
306 static int check_whether_require_superuser(int argc, char *argv[])
307 {
308 int require_superuser;
309 int i, j;
311 require_superuser = 0;
312 if (argc > 1 && strcmp(argv[1], "rpm") == 0) {
313 for (i = 2; i < argc; i++) {
314 if (strcmp(argv[i], "--") == 0)
315 break;
316 else if ( strcmp(argv[i], "--erase") == 0
317 || strcmp(argv[i], "--freshen") == 0
318 || strcmp(argv[i], "--install") == 0
319 || strcmp(argv[i], "--upgrade") == 0
320 || strcmp(argv[i], "--import") == 0
321 || strcmp(argv[i], "--initdb") == 0
322 || strcmp(argv[i], "--rebuilddb") == 0
323 || strcmp(argv[i], "--db-build") == 0
324 || strcmp(argv[i], "--db-rebuild") == 0
325 || strcmp(argv[i], "--db-cleanup") == 0
326 || strcmp(argv[i], "--db-fixate") == 0
327 || strcmp(argv[i], "--setperms") == 0
328 || strcmp(argv[i], "--setugids") == 0) {
329 require_superuser = 1;
330 break;
331 }
332 else if ( argv[i][0] == '-'
333 && argv[i][1] != '-'
334 && argv[i][1] != 'b'
335 && argv[i][1] != 't'
336 ) {
337 for (j = 1; argv[i][j] != '\0'; j++) {
338 if ( ( argv[i][j] == 'q'
339 || argv[i][j] == 'K')
340 && argv[i][j+1] != '\0') {
341 j++;
342 continue;
343 }
344 else if ( argv[i][j] == 'i'
345 || argv[i][j] == 'U'
346 || argv[i][j] == 'F'
347 || argv[i][j] == 'V'
348 || argv[i][j] == 'e') {
349 require_superuser = 1;
350 break;
351 }
352 }
353 if (require_superuser)
354 break;
355 }
356 }
357 }
358 else if (argc > 1 && strcmp(argv[1], "rc") == 0) {
359 require_superuser = 1;
360 for (i = 2; i < argc; i++) {
361 if (strcmp(argv[i], "--") == 0)
362 break;
363 else if ( strcmp(argv[i], "-p") == 0
364 || strcmp(argv[i], "--print") == 0
365 || strcmp(argv[i], "-e") == 0
366 || strcmp(argv[i], "--eval") == 0
367 || strcmp(argv[i], "-q") == 0
368 || strcmp(argv[i], "--query") == 0
369 || strcmp(argv[i], "-c") == 0
370 || strcmp(argv[i], "--config") == 0) {
371 require_superuser = 0;
372 break;
373 }
374 }
375 }
376 return require_superuser;
377 }
379 /* main program */
380 int main(int argc, char **argv, char **envp)
381 {
382 int keep_original_privileges;
383 int is_manager;
384 int require_superuser;
385 uid_t my_uid, my_euid;
386 gid_t my_gid, my_egid;
387 uid_t m_uid;
388 gid_t m_gid;
389 uid_t s_uid;
390 gid_t s_gid;
391 struct passwd *pw;
392 struct group *gr;
393 int dry_run;
394 int license;
396 /* parse command line options */
397 license = 0;
398 dry_run = 0;
399 keep_original_privileges = 0;
400 if (argc <= 0)
401 abort();
402 argv++; argc--;
403 while (argc > 0) {
404 if (argv[0][0] != '-')
405 break;
406 else if (strcmp(argv[0], "--") == 0) {
407 argv++; argc--;
408 break;
409 }
410 else if (strcmp(argv[0], "--license") == 0) {
411 license = 1;
412 argv++; argc--;
413 continue;
414 }
415 else if (strcmp(argv[0], "--debug") == 0) {
416 debug_enable = 1;
417 argv++; argc--;
418 continue;
419 }
420 else if (strcmp(argv[0], "--dry-run") == 0) {
421 dry_run = 1;
422 argv++; argc--;
423 continue;
424 }
425 else if (strcmp(argv[0], "--keep-privileges") == 0) {
426 keep_original_privileges = 1;
427 argv++; argc--;
428 continue;
429 }
430 break;
431 }
432 argv--; argc++;
434 /* display stand-alone license information */
435 if (license) {
436 fprintf(stdout, "%s", LICENSE);
437 exit(0);
438 }
440 /* determine our current real and effective user/group ids */
441 my_uid = getuid();
442 my_gid = getgid();
443 my_euid = geteuid();
444 my_egid = getegid();
445 if ((pw = getpwuid(my_uid)) == NULL)
446 fatal("unable to resolve current user id %d: %s", my_uid, strerror(errno));
447 if ((gr = getgrgid(my_gid)) == NULL)
448 fatal("unable to resolve current group id %d: %s", my_gid, strerror(errno));
449 debug("current-user: usr=%s uid=%d euid=%d grp=%s gid=%d egid=%d",
450 pw->pw_name, my_uid, my_euid, gr->gr_name, my_gid, my_egid);
452 /* determine super user/group id */
453 if ((pw = getpwnam(OPENPKG_SUSR)) == NULL)
454 fatal("unable to resolve OpenPKG superuser username \"%s\": %s", OPENPKG_SUSR, strerror(errno));
455 s_uid = pw->pw_uid;
456 s_gid = pw->pw_gid;
457 debug("super-user: s_usr=%s s_uid=%d s_gid=%d", OPENPKG_SUSR, s_uid, s_gid);
459 /* determine management user/group id */
460 if ((pw = getpwnam(OPENPKG_MUSR)) == NULL)
461 fatal("unable to resolve OpenPKG management username \"%s\": %s", OPENPKG_MUSR, strerror(errno));
462 m_uid = pw->pw_uid;
463 m_gid = pw->pw_gid;
464 debug("management-user: m_grp=%s m_uid=%d m_gid=%d", OPENPKG_MUSR, m_uid, m_gid);
466 /* determine whether caller is explicitly configured as a management user */
467 is_manager = 0;
468 if (!keep_original_privileges)
469 is_manager = check_whether_is_manager(my_uid, my_gid, m_uid, m_gid);
470 debug("current user is manager: %s", is_manager ? "yes" : "no");
472 /* determine whether command requires super-user privileges */
473 require_superuser = check_whether_require_superuser(argc, argv);
474 debug("current command requires super user privileges: %s", require_superuser ? "yes" : "no");
476 /* adjust privileges according to determined information */
477 if (!keep_original_privileges && require_superuser && is_manager && my_euid == 0) {
478 /* increase privileges to super user */
479 debug("increase privileges to super user");
480 adjust_privileges(s_uid, s_gid, 1);
481 }
482 else if (!keep_original_privileges && !require_superuser && is_manager && my_euid == 0) {
483 /* decrease privileges to management user */
484 debug("decrease privileges to management user");
485 adjust_privileges(m_uid, m_gid, 1);
486 }
487 else /* keep_original_privileges || !is_manager */ {
488 /* drop effective privileges for current user*/
489 debug("drop effective privileges for current user");
490 adjust_privileges(my_uid, my_gid, 0);
491 }
493 /* pass-through control to real Execution Frontend (shell script) */
494 argv[0] = OPENPKG_PREFIX "/lib/openpkg/openpkg";
495 debug("execute \"%s\"", argv[0]);
496 if (!dry_run) {
497 if (execve(argv[0], argv, envp) == -1)
498 fatal("failed to execute \"%s\": %s", argv[0], strerror(errno));
499 /* NOT REACHED */
500 fatal("INTERNAL ERROR");
501 }
502 return 0;
503 }