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