1/* -*-Mode: C;-*- */ 2 3/*********************************************************************** 4* 5* wrapper.c 6* 7* C wrapper designed to run SUID root for controlling PPPoE connections. 8* 9* Copyright (C) 2005 by Roaring Penguin Software Inc. 10* 11* LIC: GPL 12* 13* This program may be distributed under the terms of the GNU General 14* Public License, Version 2, or (at your option) any later version. 15***********************************************************************/ 16 17#define _SVID_SOURCE 1 /* For putenv */ 18#define _POSIX_SOURCE 1 /* For fileno */ 19#define _BSD_SOURCE 1 /* For setreuid */ 20#include <stdlib.h> 21#include <string.h> 22#include <stdio.h> 23#include <errno.h> 24#include <sys/stat.h> 25#include <unistd.h> 26 27#define CONN_NAME_LEN 64 28#define LINELEN 512 29 30static char const *pppoe_start = PPPOE_START_PATH; 31static char const *pppoe_stop = PPPOE_STOP_PATH; 32static char const *pppoe_status = PPPOE_STATUS_PATH; 33 34/********************************************************************** 35 *%FUNCTION: PathOK 36 *%ARGUMENTS: 37 * fname -- a file name. 38 *%RETURNS: 39 * 1 if path to fname is secure; 0 otherwise. 40 *%DESCRIPTION: 41 * Makes sure ownership/permissions of file and parent directories 42 * are safe. 43 **********************************************************************/ 44static int 45PathOK(char const *fname) 46{ 47 char path[LINELEN]; 48 struct stat buf; 49 char const *slash; 50 51 if (strlen(fname) > LINELEN) { 52 fprintf(stderr, "Pathname '%s' too long\n", fname); 53 return 0; 54 } 55 56 /* Must be absolute path */ 57 if (*fname != '/') { 58 fprintf(stderr, "Unsafe path '%s' not absolute\n", fname); 59 return 0; 60 } 61 62 /* Check root directory */ 63 if (stat("/", &buf) < 0) { 64 perror("stat"); 65 return 0; 66 } 67 if (buf.st_uid) { 68 fprintf(stderr, "SECURITY ALERT: Root directory (/) not owned by root\n"); 69 return 0; 70 } 71 if (buf.st_mode & (S_IWGRP | S_IWOTH)) { 72 fprintf(stderr, "SECURITY ALERT: Root directory (/) writable by group or other\n"); 73 return 0; 74 } 75 76 /* Check each component */ 77 slash = fname; 78 79 while(*slash) { 80 slash = strchr(slash+1, '/'); 81 if (!slash) { 82 slash = fname + strlen(fname); 83 } 84 memcpy(path, fname, slash-fname); 85 path[slash-fname] = 0; 86 if (stat(path, &buf) < 0) { 87 perror("stat"); 88 return 0; 89 } 90 if (buf.st_uid) { 91 fprintf(stderr, "SECURITY ALERT: '%s' not owned by root\n", path); 92 return 0; 93 } 94 95 if (buf.st_mode & (S_IWGRP | S_IWOTH)) { 96 fprintf(stderr, "SECURITY ALERT: '%s' writable by group or other\n", 97 path); 98 return 0; 99 } 100 } 101 return 1; 102} 103 104/********************************************************************** 105 *%FUNCTION: CleanEnvironment 106 *%ARGUMENTS: 107 * envp -- environment passed to main 108 *%RETURNS: 109 * Nothing 110 *%DESCRIPTION: 111 * Deletes all environment variables; makes safe environment 112 **********************************************************************/ 113static void 114CleanEnvironment(char *envp[]) 115{ 116 envp[0] = NULL; 117 putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin"); 118} 119 120/********************************************************************** 121 *%FUNCTION: main 122 *%ARGUMENTS: 123 * argc, argv -- usual suspects 124 * Usage: pppoe-wrapper {start|stop|status} {connection_name} 125 *%RETURNS: 126 * Whatever pppoe-start, pppoe-stop or pppoe-status returns. 127 *%DESCRIPTION: 128 * Runs pppoe-start, pppoe-stop or pppoe-status on given connection if 129 * non-root users are allowed to do it. 130 **********************************************************************/ 131int 132main(int argc, char *argv[]) 133{ 134 int amRoot; 135 char *cp; 136 char fname[64+CONN_NAME_LEN]; 137 char line[LINELEN+1]; 138 int allowed = 0; 139 140 FILE *fp; 141 142 extern char **environ; 143 144 /* Clean out environment */ 145 CleanEnvironment(environ); 146 147 /* Are we root? */ 148 amRoot = (getuid() == 0); 149 150 /* Validate arguments */ 151 if (argc != 3) { 152 fprintf(stderr, "Usage: %s {start|stop|status} connection_name\n", 153 argv[0]); 154 exit(1); 155 } 156 157 if (strcmp(argv[1], "start") && 158 strcmp(argv[1], "stop") && 159 strcmp(argv[1], "status")) { 160 fprintf(stderr, "Usage: %s {start|stop|status} connection_name\n", 161 argv[0]); 162 exit(1); 163 } 164 165 /* Connection name can be at most CONN_NAME_LEN chars; alpha, num, underscore */ 166 if (strlen(argv[2]) > CONN_NAME_LEN) { 167 fprintf(stderr, "%s: Connection name '%s' too long.\n", 168 argv[0], argv[2]); 169 exit(1); 170 } 171 172 for (cp = argv[2]; *cp; cp++) { 173 if (!strchr("abcdefghijklmnopqrstuvwxyz" 174 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 175 "0123456789_-", *cp)) { 176 fprintf(stderr, "%s: Connection name '%s' contains illegal character '%c'\n", argv[0], argv[2], *cp); 177 exit(1); 178 } 179 } 180 181 /* Open the connection file */ 182 sprintf(fname, "/etc/ppp/rp-pppoe-gui/conf.%s", argv[2]); 183 /* Check path sanity */ 184 if (!PathOK(fname)) { 185 exit(1); 186 } 187 188 fp = fopen(fname, "r"); 189 if (!fp) { 190 fprintf(stderr, "%s: Could not open '%s': %s\n", 191 argv[0], fname, strerror(errno)); 192 exit(1); 193 } 194 195 /* Check if non-root users can control it */ 196 if (amRoot) { 197 allowed = 1; 198 } else { 199 while (!feof(fp)) { 200 if (!fgets(line, LINELEN, fp)) { 201 break; 202 } 203 if (!strcmp(line, "NONROOT=OK\n")) { 204 allowed = 1; 205 break; 206 } 207 } 208 } 209 fclose(fp); 210 211 if (!allowed) { 212 fprintf(stderr, "%s: Non-root users are not permitted to control connection '%s'\n", argv[0], argv[2]); 213 exit(1); 214 } 215 216 /* Become root with setuid() to defeat is-root checks in shell scripts */ 217 if (setreuid(0, 0) < 0) { 218 perror("setreuid"); 219 exit(1); 220 } 221 222 /* It's OK -- do it. */ 223 if (!strcmp(argv[1], "start")) { 224 if (!PathOK(pppoe_start)) exit(1); 225 execl(pppoe_start, "pppoe-start", fname, NULL); 226 } else if (!strcmp(argv[1], "stop")) { 227 if (!PathOK(pppoe_stop)) exit(1); 228 execl(pppoe_stop, "pppoe-stop", fname, NULL); 229 } else { 230 if (!PathOK(pppoe_status)) exit(1); 231 execl(pppoe_status, "pppoe-status", fname, NULL); 232 } 233 fprintf(stderr, "%s: execl: %s\n", argv[0], strerror(errno)); 234 exit(1); 235} 236