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