/* -*-Mode: C;-*- */ /*********************************************************************** * * wrapper.c * * C wrapper designed to run SUID root for controlling PPPoE connections. * * Copyright (C) 2001 by Roaring Penguin Software Inc. * ***********************************************************************/ static char const RCSID[] = "$Id$"; #define _SVID_SOURCE 1 /* For putenv */ #define _POSIX_SOURCE 1 /* For fileno */ #define _BSD_SOURCE 1 /* For setreuid */ #include #include #include #include #include #include #define CONN_NAME_LEN 64 #define LINELEN 512 static char const *adsl_start = ADSL_START_PATH; static char const *adsl_stop = ADSL_STOP_PATH; static char const *adsl_status = ADSL_STATUS_PATH; /********************************************************************** *%FUNCTION: PathOK *%ARGUMENTS: * fname -- a file name. *%RETURNS: * 1 if path to fname is secure; 0 otherwise. *%DESCRIPTION: * Makes sure ownership/permissions of file and parent directories * are safe. **********************************************************************/ static int PathOK(char const *fname) { char path[LINELEN]; struct stat buf; char const *slash; if (strlen(fname) > LINELEN) { fprintf(stderr, "Pathname '%s' too long\n", fname); return 0; } /* Must be absolute path */ if (*fname != '/') { fprintf(stderr, "Unsafe path '%s' not absolute\n", fname); return 0; } /* Check root directory */ if (stat("/", &buf) < 0) { perror("stat"); return 0; } if (buf.st_uid) { fprintf(stderr, "SECURITY ALERT: Root directory (/) not owned by root\n"); return 0; } if (buf.st_mode & (S_IWGRP | S_IWOTH)) { fprintf(stderr, "SECURITY ALERT: Root directory (/) writable by group or other\n"); return 0; } /* Check each component */ slash = fname; while(*slash) { slash = strchr(slash+1, '/'); if (!slash) { slash = fname + strlen(fname); } memcpy(path, fname, slash-fname); path[slash-fname] = 0; if (stat(path, &buf) < 0) { perror("stat"); return 0; } if (buf.st_uid) { fprintf(stderr, "SECURITY ALERT: '%s' not owned by root\n", path); return 0; } if (buf.st_mode & (S_IWGRP | S_IWOTH)) { fprintf(stderr, "SECURITY ALERT: '%s' writable by group or other\n", path); return 0; } } return 1; } /********************************************************************** *%FUNCTION: CleanEnvironment *%ARGUMENTS: * envp -- environment passed to main *%RETURNS: * Nothing *%DESCRIPTION: * Deletes all environment variables; makes safe environment **********************************************************************/ static void CleanEnvironment(char *envp[]) { envp[0] = NULL; putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin"); } /********************************************************************** *%FUNCTION: main *%ARGUMENTS: * argc, argv -- usual suspects * Usage: pppoe-wrapper {start|stop|status} {connection_name} *%RETURNS: * Whatever adsl-start, adsl-stop or adsl-status returns. *%DESCRIPTION: * Runs adsl-start, adsl-stop or adsl-status on given connection if * non-root users are allowed to do it. **********************************************************************/ int main(int argc, char *argv[]) { int amRoot; char *cp; char fname[64+CONN_NAME_LEN]; char line[LINELEN+1]; int allowed = 0; FILE *fp; extern char **environ; /* Clean out environment */ CleanEnvironment(environ); /* Are we root? */ amRoot = (getuid() == 0); /* Validate arguments */ if (argc != 3) { fprintf(stderr, "Usage: %s {start|stop|status} connection_name\n", argv[0]); exit(1); } if (strcmp(argv[1], "start") && strcmp(argv[1], "stop") && strcmp(argv[1], "status")) { fprintf(stderr, "Usage: %s {start|stop|status} connection_name\n", argv[0]); exit(1); } /* Connection name can be at most CONN_NAME_LEN chars; alpha, num, underscore */ if (strlen(argv[2]) > CONN_NAME_LEN) { fprintf(stderr, "%s: Connection name '%s' too long.\n", argv[0], argv[2]); exit(1); } for (cp = argv[2]; *cp; cp++) { if (!strchr("abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_-", *cp)) { fprintf(stderr, "%s: Connection name '%s' contains illegal character '%c'\n", argv[0], argv[2], *cp); exit(1); } } /* Open the connection file */ sprintf(fname, "/etc/ppp/rp-pppoe-gui/conf.%s", argv[2]); /* Check path sanity */ if (!PathOK(fname)) { exit(1); } fp = fopen(fname, "r"); if (!fp) { fprintf(stderr, "%s: Could not open '%s': %s\n", argv[0], fname, strerror(errno)); exit(1); } /* Check if non-root users can control it */ if (amRoot) { allowed = 1; } else { while (!feof(fp)) { if (!fgets(line, LINELEN, fp)) { break; } if (!strcmp(line, "NONROOT=OK\n")) { allowed = 1; break; } } } fclose(fp); if (!allowed) { fprintf(stderr, "%s: Non-root users are not permitted to control connection '%s'\n", argv[0], argv[2]); exit(1); } /* Become root with setuid() to defeat is-root checks in shell scripts */ if (setreuid(0, 0) < 0) { perror("setreuid"); exit(1); } /* It's OK -- do it. */ if (!strcmp(argv[1], "start")) { if (!PathOK(adsl_start)) exit(1); execl(adsl_start, "adsl-start", fname, NULL); } else if (!strcmp(argv[1], "stop")) { if (!PathOK(adsl_stop)) exit(1); execl(adsl_stop, "adsl-stop", fname, NULL); } else { if (!PathOK(adsl_status)) exit(1); execl(adsl_status, "adsl-status", fname, NULL); } fprintf(stderr, "%s: execl: %s\n", argv[0], strerror(errno)); exit(1); }