1 /*-
2  * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
3  *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
4  *                           Internet Initiative Japan, Inc (IIJ)
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $OpenBSD: systems.c,v 1.20 2008/03/02 19:31:43 deraadt Exp $
29  */
30 
31 #include <sys/param.h>
32 
33 #include <ctype.h>
34 #include <pwd.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <termios.h>
39 
40 #include "defs.h"
41 #include "command.h"
42 #include "log.h"
43 #include "id.h"
44 #include "systems.h"
45 
46 #define issep(ch) ((ch) == ' ' || (ch) == '\t')
47 
48 FILE *
OpenSecret(const char * file)49 OpenSecret(const char *file)
50 {
51   FILE *fp;
52   char line[100];
53 
54   snprintf(line, sizeof line, "%s/%s", PPP_CONFDIR, file);
55   fp = ID0fopen(line, "r");
56   if (fp == NULL)
57     log_Printf(LogWARN, "OpenSecret: Can't open %s.\n", line);
58   return (fp);
59 }
60 
61 void
CloseSecret(FILE * fp)62 CloseSecret(FILE *fp)
63 {
64   fclose(fp);
65 }
66 
67 /*
68  * Move string from ``from'' to ``to'', interpreting ``~'' and $....
69  * Returns NULL if string expansion failed due to lack of buffer space.
70  */
71 const char *
InterpretArg(const char * from,char * to,size_t tosiz)72 InterpretArg(const char *from, char *to, size_t tosiz)
73 {
74   char *ptr, *startto, *endto;
75   struct passwd *pwd;
76   int len, instring;
77   const char *env;
78 
79   instring = 0;
80   startto = to;
81   endto = to + tosiz - 1;
82 
83   while(issep(*from))
84     from++;
85 
86   while (*from != '\0') {
87     if (to >= endto)
88       return NULL;
89     switch (*from) {
90       case '"':
91         instring = !instring;
92         *to++ = *from++;
93         break;
94       case '\\':
95         switch (*++from) {
96           case '$':
97           case '~':
98             break;		/* Swallow the escapes */
99 
100           default:
101             *to++ = '\\';	/* Pass the escapes on, maybe skipping \# */
102             break;
103         }
104         if (to >= endto)
105           return NULL;
106         *to++ = *from++;
107         break;
108       case '$':
109         if (from[1] == '$') {
110           *to = '\0';	/* For an empty var name below */
111           from += 2;
112         } else if (from[1] == '{') {
113           ptr = strchr(from+2, '}');
114           if (ptr) {
115             len = ptr - from - 2;
116             if (endto - to < len)
117               return NULL;
118             if (len) {
119               strncpy(to, from+2, len);
120               to[len] = '\0';
121               from = ptr+1;
122             } else {
123               *to++ = *from++;
124               continue;
125             }
126           } else {
127             *to++ = *from++;
128             continue;
129           }
130         } else {
131           ptr = to;
132           for (from++; (isalnum(*from) || *from == '_') && ptr < endto; from++)
133             *ptr++ = *from;
134           *ptr = '\0';
135         }
136         if (to >= endto)
137           return NULL;
138         if (*to == '\0')
139           *to++ = '$';
140         else if ((env = getenv(to)) != NULL) {
141           if (endto - to < strlen(env))
142             return NULL;
143           strncpy(to, env, endto - to);
144           *endto = '\0';
145           to += strlen(to);
146         }
147         break;
148 
149       case '~':
150         ptr = strchr(++from, '/');
151         len = ptr ? ptr - from : strlen(from);
152         if (len == 0)
153           pwd = getpwuid(ID0realuid());
154         else {
155           if (endto - to < len)
156             return NULL;
157           strncpy(to, from, len);
158           to[len] = '\0';
159           pwd = getpwnam(to);
160         }
161         if (to >= endto)
162           return NULL;
163         if (pwd == NULL)
164           *to++ = '~';
165         else {
166           if (endto - to < strlen(pwd->pw_dir))
167             return NULL;
168           strncpy(to, pwd->pw_dir, endto - to);
169           *endto = '\0';
170           to += strlen(to);
171           from += len;
172         }
173         break;
174 
175       default:
176         *to++ = *from++;
177         break;
178     }
179   }
180 
181   while (to > startto) {
182     to--;
183     if (!issep(*to)) {
184       to++;
185       break;
186     }
187   }
188   *to = '\0';
189 
190   return from;
191 }
192 
193 #define CTRL_UNKNOWN (0)
194 #define CTRL_INCLUDE (1)
195 
196 static int
DecodeCtrlCommand(char * line,char * arg,size_t argsiz)197 DecodeCtrlCommand(char *line, char *arg, size_t argsiz)
198 {
199   const char *end;
200 
201   if (!strncasecmp(line, "include", 7) && issep(line[7])) {
202     end = InterpretArg(line+8, arg, argsiz);
203     if (end == NULL) {
204       log_Printf(LogWARN, "Failed to expand command '%s': too long for the destination buffer\n", line);
205       return CTRL_UNKNOWN;
206     }
207     if (*end && *end != '#')
208       log_Printf(LogWARN, "usage: !include filename\n");
209     else
210       return CTRL_INCLUDE;
211   }
212   return CTRL_UNKNOWN;
213 }
214 
215 /*
216  * Initialised in system_IsValid(), set in ReadSystem(),
217  * used by system_IsValid()
218  */
219 static int modeok;
220 static int userok;
221 static int modereq;
222 
223 int
AllowUsers(struct cmdargs const * arg)224 AllowUsers(struct cmdargs const *arg)
225 {
226   /* arg->bundle may be NULL (see system_IsValid()) ! */
227   int f;
228   struct passwd *pwd;
229 
230   if (userok == -1)
231     userok = 0;
232 
233   pwd = getpwuid(ID0realuid());
234   if (pwd != NULL)
235     for (f = arg->argn; f < arg->argc; f++)
236       if (!strcmp("*", arg->argv[f]) || !strcmp(pwd->pw_name, arg->argv[f])) {
237         userok = 1;
238         break;
239       }
240 
241   return 0;
242 }
243 
244 int
AllowModes(struct cmdargs const * arg)245 AllowModes(struct cmdargs const *arg)
246 {
247   /* arg->bundle may be NULL (see system_IsValid()) ! */
248   int f, mode, allowed;
249 
250   allowed = 0;
251   for (f = arg->argn; f < arg->argc; f++) {
252     mode = Nam2mode(arg->argv[f]);
253     if (mode == PHYS_NONE || mode == PHYS_ALL)
254       log_Printf(LogWARN, "allow modes: %s: Invalid mode\n", arg->argv[f]);
255     else
256       allowed |= mode;
257   }
258 
259   modeok = modereq & allowed ? 1 : 0;
260   return 0;
261 }
262 
263 static char *
strip(char * line)264 strip(char *line)
265 {
266   int len;
267 
268   len = strlen(line);
269   while (len && (line[len-1] == '\n' || line[len-1] == '\r' ||
270                  issep(line[len-1])))
271     line[--len] = '\0';
272 
273   while (issep(*line))
274     line++;
275 
276   if (*line == '#')
277     *line = '\0';
278 
279   return line;
280 }
281 
282 static int
xgets(char * buf,int buflen,FILE * fp)283 xgets(char *buf, int buflen, FILE *fp)
284 {
285   int len, n;
286 
287   n = 0;
288   while (fgets(buf, buflen-1, fp)) {
289     n++;
290     buf[buflen-1] = '\0';
291     len = strlen(buf);
292     while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
293       buf[--len] = '\0';
294     if (len && buf[len-1] == '\\') {
295       buf += len - 1;
296       buflen -= len - 1;
297       if (!buflen)        /* No buffer space */
298         break;
299     } else
300       break;
301   }
302   return n;
303 }
304 
305 /* Values for ``how'' in ReadSystem */
306 #define SYSTEM_EXISTS	1
307 #define SYSTEM_VALIDATE	2
308 #define SYSTEM_EXEC	3
309 
310 static char *
GetLabel(char * line,const char * filename,int linenum)311 GetLabel(char *line, const char *filename, int linenum)
312 {
313   char *argv[MAXARGS];
314   int argc, len;
315 
316   argc = MakeArgs(line, argv, MAXARGS, PARSE_REDUCE);
317 
318   if (argc == 2 && !strcmp(argv[1], ":"))
319     return argv[0];
320 
321   if (argc != 1 || (len = strlen(argv[0])) < 2 || argv[0][len-1] != ':') {
322       log_Printf(LogWARN, "Bad label in %s (line %d) - missing colon\n",
323                  filename, linenum);
324       return NULL;
325   }
326   argv[0][len-1] = '\0';	/* Lose the ':' */
327 
328   return argv[0];
329 }
330 
331 /* Returns -2 for ``file not found'' and -1 for ``label not found'' */
332 
333 static int
ReadSystem(struct bundle * bundle,const char * name,const char * file,struct prompt * prompt,struct datalink * cx,int how)334 ReadSystem(struct bundle *bundle, const char *name, const char *file,
335            struct prompt *prompt, struct datalink *cx, int how)
336 {
337   FILE *fp;
338   char *cp;
339   int n, len;
340   char line[LINE_LEN];
341   char filename[PATH_MAX];
342   int linenum;
343   int argc;
344   char *argv[MAXARGS];
345   int allowcmd;
346   int indent;
347   char arg[LINE_LEN];
348   struct prompt *op;
349 
350   if (*file == '/')
351     snprintf(filename, sizeof filename, "%s", file);
352   else
353     snprintf(filename, sizeof filename, "%s/%s", PPP_CONFDIR, file);
354   fp = ID0fopen(filename, "r");
355   if (fp == NULL) {
356     log_Printf(LogDEBUG, "ReadSystem: Can't open %s.\n", filename);
357     return -2;
358   }
359   log_Printf(LogDEBUG, "ReadSystem: Checking %s (%s).\n", name, filename);
360 
361   linenum = 0;
362   while ((n = xgets(line, sizeof line, fp))) {
363     linenum += n;
364     if (issep(*line))
365       continue;
366 
367     cp = strip(line);
368 
369     switch (*cp) {
370     case '\0':			/* empty/comment */
371       break;
372 
373     case '!':
374       switch (DecodeCtrlCommand(cp+1, arg, LINE_LEN)) {
375       case CTRL_INCLUDE:
376         log_Printf(LogCOMMAND, "%s: Including \"%s\"\n", filename, arg);
377         n = ReadSystem(bundle, name, arg, prompt, cx, how);
378         log_Printf(LogCOMMAND, "%s: Done include of \"%s\"\n", filename, arg);
379         if (!n) {
380           fclose(fp);
381           return 0;	/* got it */
382         }
383         break;
384       default:
385         log_Printf(LogWARN, "%s: %s: Invalid command\n", filename, cp);
386         break;
387       }
388       break;
389 
390     default:
391       if ((cp = GetLabel(cp, filename, linenum)) == NULL)
392         continue;
393 
394       if (strcmp(cp, name) == 0) {
395         /* We're in business */
396         if (how == SYSTEM_EXISTS) {
397           fclose(fp);
398 	  return 0;
399 	}
400 	while ((n = xgets(line, sizeof line, fp))) {
401           linenum += n;
402           indent = issep(*line);
403           cp = strip(line);
404 
405           if (*cp == '\0')			/* empty / comment */
406             continue;
407 
408           if (!indent) {			/* start of next section */
409             if (*cp != '!' && how == SYSTEM_EXEC)
410               cp = GetLabel(cp, filename, linenum);
411             break;
412           }
413 
414           len = strlen(cp);
415           if ((argc = command_Expand_Interpret(cp, len, argv, cp - line)) < 0)
416             log_Printf(LogWARN, "%s: %d: Syntax error\n", filename, linenum);
417           else {
418             allowcmd = argc > 0 && !strcasecmp(argv[0], "allow");
419             if ((how != SYSTEM_EXEC && allowcmd) ||
420                 (how == SYSTEM_EXEC && !allowcmd)) {
421               /*
422                * Disable any context so that warnings are given to everyone,
423                * including syslog.
424                */
425               op = log_PromptContext;
426               log_PromptContext = NULL;
427 	      command_Run(bundle, argc, (char const *const *)argv, prompt,
428                           name, cx);
429               log_PromptContext = op;
430             }
431           }
432         }
433 
434 	fclose(fp);  /* everything read - get out */
435 	return 0;
436       }
437       break;
438     }
439   }
440   fclose(fp);
441   return -1;
442 }
443 
444 const char *
system_IsValid(const char * name,struct prompt * prompt,int mode)445 system_IsValid(const char *name, struct prompt *prompt, int mode)
446 {
447   /*
448    * Note:  The ReadSystem() calls only result in calls to the Allow*
449    * functions.  arg->bundle will be set to NULL for these commands !
450    */
451   int def, how, rs;
452   int defuserok;
453 
454   def = !strcmp(name, "default");
455   how = ID0realuid() == 0 ? SYSTEM_EXISTS : SYSTEM_VALIDATE;
456   userok = -1;
457   modeok = 1;
458   modereq = mode;
459 
460   rs = ReadSystem(NULL, "default", CONFFILE, prompt, NULL, how);
461 
462   defuserok = userok;
463   userok = -1;
464 
465   if (!def) {
466     if (rs == -1)
467       rs = 0;		/* we don't care that ``default'' doesn't exist */
468 
469     if (rs == 0)
470       rs = ReadSystem(NULL, name, CONFFILE, prompt, NULL, how);
471 
472     if (rs == -1)
473       return "Configuration label not found";
474 
475     if (rs == -2)
476       return PPP_CONFDIR "/" CONFFILE " : File not found";
477   }
478 
479   if (userok == -1)
480     userok = defuserok;
481 
482   if (how == SYSTEM_EXISTS)
483     userok = modeok = 1;
484 
485   if (!userok)
486     return "User access denied";
487 
488   if (!modeok)
489     return "Mode denied for this label";
490 
491   return NULL;
492 }
493 
494 int
system_Select(struct bundle * bundle,const char * name,const char * file,struct prompt * prompt,struct datalink * cx)495 system_Select(struct bundle *bundle, const char *name, const char *file,
496              struct prompt *prompt, struct datalink *cx)
497 {
498   userok = modeok = 1;
499   modereq = PHYS_ALL;
500   return ReadSystem(bundle, name, file, prompt, cx, SYSTEM_EXEC);
501 }
502