1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
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 * $FreeBSD: stable/11/usr.sbin/ppp/prompt.c 330449 2018-03-05 07:26:05Z eadler $
29 */
30
31#include <sys/param.h>
32#include <netinet/in.h>
33#include <netinet/in_systm.h>
34#include <netinet/ip.h>
35#include <sys/socket.h>
36#include <sys/un.h>
37
38#include <errno.h>
39#include <stdarg.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <sys/fcntl.h>
44#include <termios.h>
45#include <unistd.h>
46
47#include "layer.h"
48#include "defs.h"
49#include "timer.h"
50#include "command.h"
51#include "log.h"
52#include "descriptor.h"
53#include "prompt.h"
54#include "fsm.h"
55#include "auth.h"
56#include "iplist.h"
57#include "throughput.h"
58#include "slcompress.h"
59#include "mbuf.h"
60#include "lqr.h"
61#include "hdlc.h"
62#include "lcp.h"
63#include "ncpaddr.h"
64#include "ipcp.h"
65#include "filter.h"
66#include "async.h"
67#include "ccp.h"
68#include "link.h"
69#include "physical.h"
70#include "mp.h"
71#ifndef NORADIUS
72#include "radius.h"
73#endif
74#include "ipv6cp.h"
75#include "ncp.h"
76#include "bundle.h"
77#include "chat.h"
78#include "chap.h"
79#include "cbcp.h"
80#include "datalink.h"
81#include "server.h"
82#include "main.h"
83
84static void
85prompt_Display(struct prompt *p)
86{
87  /* XXX: See Index2Nam() - should we only figure this out once ? */
88  static char shostname[MAXHOSTNAMELEN];
89  const char *pconnect, *pauth;
90
91  if (p->TermMode || !p->needprompt)
92    return;
93
94  p->needprompt = 0;
95
96  if (p->nonewline)
97    p->nonewline = 0;
98  else
99    fprintf(p->Term, "\n");
100
101  if (p->auth == LOCAL_AUTH)
102    pauth = " ON ";
103  else
104    pauth = " on ";
105
106  if (p->bundle->ncp.ipcp.fsm.state == ST_OPENED)
107    pconnect = "PPP";
108#ifndef NOINET6
109  else if (!Enabled(p->bundle, OPT_IPCP) &&
110	   p->bundle->ncp.ipv6cp.fsm.state == ST_OPENED)
111    pconnect = "PPP";
112#endif
113  else if (bundle_Phase(p->bundle) == PHASE_NETWORK)
114    pconnect = "PPp";
115  else if (bundle_Phase(p->bundle) == PHASE_AUTHENTICATE)
116    pconnect = "Ppp";
117  else
118    pconnect = "ppp";
119
120  if (*shostname == '\0') {
121    char *dot;
122
123    if (gethostname(shostname, sizeof shostname) || *shostname == '\0')
124      strcpy(shostname, "localhost");
125    else if ((dot = strchr(shostname, '.')))
126      *dot = '\0';
127  }
128
129  fprintf(p->Term, "%s%s%s> ", pconnect, pauth, shostname);
130  fflush(p->Term);
131}
132
133static int
134prompt_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
135		 fd_set *e, int *n)
136{
137  struct prompt *p = descriptor2prompt(d);
138  int sets;
139
140  sets = 0;
141
142  if (!p->active)
143    return sets;
144
145  if (p->fd_in >= 0) {
146    if (r) {
147      FD_SET(p->fd_in, r);
148      log_Printf(LogTIMER, "prompt %s: fdset(r) %d\n", p->src.from, p->fd_in);
149      sets++;
150    }
151    if (e) {
152      FD_SET(p->fd_in, e);
153      log_Printf(LogTIMER, "prompt %s: fdset(e) %d\n", p->src.from, p->fd_in);
154      sets++;
155    }
156    if (sets && *n < p->fd_in + 1)
157      *n = p->fd_in + 1;
158  }
159
160  prompt_Display(p);
161
162  return sets;
163}
164
165static int
166prompt_IsSet(struct fdescriptor *d, const fd_set *fdset)
167{
168  struct prompt *p = descriptor2prompt(d);
169  return p->fd_in >= 0 && FD_ISSET(p->fd_in, fdset);
170}
171
172
173static void
174prompt_ShowHelp(struct prompt *p)
175{
176  prompt_Printf(p, "The following commands are available:\n");
177  prompt_Printf(p, " ~p\tEnter Packet mode\n");
178  prompt_Printf(p, " ~t\tShow timers\n");
179  prompt_Printf(p, " ~m\tShow memory map\n");
180  prompt_Printf(p, " ~.\tTerminate program\n");
181  prompt_Printf(p, " ~?\tThis help\n");
182}
183
184static void
185prompt_Read(struct fdescriptor *d, struct bundle *bundle,
186	    const fd_set *fdset __unused)
187{
188  struct prompt *p = descriptor2prompt(d);
189  struct prompt *op;
190  int n;
191  char ch;
192  char linebuff[LINE_LEN];
193
194  if (p->TermMode == NULL) {
195    n = read(p->fd_in, linebuff, sizeof linebuff - 1);
196    if (n > 0) {
197      if (linebuff[n-1] == '\n')
198        linebuff[--n] = '\0';
199      else
200        linebuff[n] = '\0';
201      p->nonewline = 1;		/* Maybe command_Decode does a prompt */
202      prompt_Required(p);
203      if (n) {
204        if ((op = log_PromptContext) == NULL)
205          log_PromptContext = p;
206        if (!command_Decode(bundle, linebuff, n, p, p->src.from))
207          prompt_Printf(p, "Syntax error\n");
208        log_PromptContext = op;
209      }
210    } else if (n <= 0) {
211      log_Printf(LogPHASE, "%s: Client connection closed.\n", p->src.from);
212      if (!p->owner)
213        Cleanup();
214      prompt_Destroy(p, 0);
215    }
216    return;
217  }
218
219  switch (p->TermMode->state) {
220    case DATALINK_CLOSED:
221      prompt_Printf(p, "Link lost, terminal mode.\n");
222      prompt_TtyCommandMode(p);
223      p->nonewline = 0;
224      prompt_Required(p);
225      return;
226
227    case DATALINK_READY:
228      break;
229
230    case DATALINK_OPEN:
231      prompt_Printf(p, "\nPacket mode detected.\n");
232      prompt_TtyCommandMode(p);
233      p->nonewline = 0;
234      /* We'll get a prompt because of our status change */
235      /* FALLTHROUGH */
236
237    default:
238      /* Wait 'till we're in a state we care about */
239      return;
240  }
241
242  /*
243   * We are in terminal mode, decode special sequences
244   */
245  n = read(p->fd_in, &ch, 1);
246  log_Printf(LogDEBUG, "Got %d bytes (reading from the terminal)\n", n);
247
248  if (n > 0) {
249    switch (p->readtilde) {
250    case 0:
251      if (ch == '~')
252        p->readtilde = 1;
253      else
254	if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
255	  log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
256          prompt_TtyCommandMode(p);
257        }
258      break;
259    case 1:
260      switch (ch) {
261      case '?':
262	prompt_ShowHelp(p);
263	break;
264      case 'p':
265        datalink_Up(p->TermMode, 0, 1);
266        prompt_Printf(p, "\nPacket mode.\n");
267	prompt_TtyCommandMode(p);
268        break;
269      case '.':
270	prompt_TtyCommandMode(p);
271        p->nonewline = 0;
272        prompt_Required(p);
273	break;
274      case 't':
275	timer_Show(0, p);
276	break;
277      case 'm':
278        {
279          struct cmdargs arg;
280
281          arg.cmdtab = NULL;
282          arg.cmd = NULL;
283          arg.argc = 0;
284          arg.argn = 0;
285          arg.argv = NULL;
286          arg.bundle = bundle;
287          arg.cx = p->TermMode;
288          arg.prompt = p;
289
290	  mbuf_Show(&arg);
291        }
292	break;
293      default:
294	if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
295	  log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
296          prompt_TtyCommandMode(p);
297        }
298	break;
299      }
300      p->readtilde = 0;
301      break;
302    }
303  }
304}
305
306static int
307prompt_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
308	     const fd_set *fdset __unused)
309{
310  /* We never want to write here ! */
311  log_Printf(LogALERT, "prompt_Write: Internal error: Bad call !\n");
312  return 0;
313}
314
315struct prompt *
316prompt_Create(struct server *s, struct bundle *bundle, int fd)
317{
318  struct prompt *p = (struct prompt *)malloc(sizeof(struct prompt));
319
320  if (p != NULL) {
321    p->desc.type = PROMPT_DESCRIPTOR;
322    p->desc.UpdateSet = prompt_UpdateSet;
323    p->desc.IsSet = prompt_IsSet;
324    p->desc.Read = prompt_Read;
325    p->desc.Write = prompt_Write;
326
327    if (fd == PROMPT_STD) {
328      char *tty = ttyname(STDIN_FILENO);
329
330      if (!tty) {
331        free(p);
332        return NULL;
333      }
334      p->fd_in = STDIN_FILENO;
335      p->fd_out = STDOUT_FILENO;
336      p->Term = stdout;
337      p->owner = NULL;
338      p->auth = LOCAL_AUTH;
339      p->src.type = "Controller";
340      strncpy(p->src.from, tty, sizeof p->src.from - 1);
341      p->src.from[sizeof p->src.from - 1] = '\0';
342      tcgetattr(p->fd_in, &p->oldtio);	/* Save original tty mode */
343    } else {
344      p->fd_in = p->fd_out = fd;
345      p->Term = fdopen(fd, "a+");
346      p->owner = s;
347      p->auth = *s->cfg.passwd ? LOCAL_NO_AUTH : LOCAL_AUTH;
348      p->src.type = "unknown";
349      *p->src.from = '\0';
350    }
351    p->TermMode = NULL;
352    p->nonewline = 1;
353    p->needprompt = 1;
354    p->readtilde = 0;
355    p->bundle = bundle;
356    log_RegisterPrompt(p);
357  }
358
359  return p;
360}
361
362void
363prompt_Destroy(struct prompt *p, int verbose)
364{
365  if (p) {
366    if (p->Term != stdout) {
367      fclose(p->Term);
368      close(p->fd_in);
369      if (p->fd_out != p->fd_in)
370        close(p->fd_out);
371      if (verbose)
372        log_Printf(LogPHASE, "%s: Client connection dropped.\n", p->src.from);
373    } else
374      prompt_TtyOldMode(p);
375
376    log_UnRegisterPrompt(p);
377    free(p);
378  }
379}
380
381void
382prompt_Printf(struct prompt *p, const char *fmt,...)
383{
384  if (p && p->active) {
385    va_list ap;
386
387    va_start(ap, fmt);
388    prompt_vPrintf(p, fmt, ap);
389    va_end(ap);
390  }
391}
392
393void
394prompt_vPrintf(struct prompt *p, const char *fmt, va_list ap)
395{
396  if (p && p->active) {
397    char nfmt[LINE_LEN];
398    const char *pfmt;
399
400    if (p->TermMode) {
401      /* Stuff '\r' in front of '\n' 'cos we're in raw mode */
402      size_t len = strlen(fmt);
403
404      if (len && len < sizeof nfmt - 1 && fmt[len-1] == '\n' &&
405          (len == 1 || fmt[len-2] != '\r')) {
406        strcpy(nfmt, fmt);
407        strcpy(nfmt + len - 1, "\r\n");
408        pfmt = nfmt;
409      } else
410        pfmt = fmt;
411    } else
412      pfmt = fmt;
413    vfprintf(p->Term, pfmt, ap);
414    fflush(p->Term);
415    p->nonewline = 1;
416  }
417}
418
419void
420prompt_TtyInit(struct prompt *p)
421{
422  int stat, fd = p ? p->fd_in : STDIN_FILENO;
423  struct termios newtio;
424
425  stat = fcntl(fd, F_GETFL, 0);
426  if (stat > 0) {
427    stat |= O_NONBLOCK;
428    fcntl(fd, F_SETFL, stat);
429  }
430
431  if (p)
432    newtio = p->oldtio;
433  else
434    tcgetattr(fd, &newtio);
435
436  newtio.c_lflag &= ~(ECHO | ISIG | ICANON);
437  newtio.c_iflag = 0;
438  newtio.c_oflag &= ~OPOST;
439  if (!p)
440    newtio.c_cc[VINTR] = _POSIX_VDISABLE;
441  newtio.c_cc[VMIN] = 1;
442  newtio.c_cc[VTIME] = 0;
443  newtio.c_cflag |= CS8;
444  tcsetattr(fd, TCSANOW, &newtio);
445  if (p)
446    p->comtio = newtio;
447}
448
449/*
450 *  Set tty into command mode. We allow canonical input and echo processing.
451 */
452void
453prompt_TtyCommandMode(struct prompt *p)
454{
455  struct termios newtio;
456  int stat;
457
458  tcgetattr(p->fd_in, &newtio);
459  newtio.c_lflag |= (ECHO | ISIG | ICANON);
460  newtio.c_iflag = p->oldtio.c_iflag;
461  newtio.c_oflag |= OPOST;
462  tcsetattr(p->fd_in, TCSADRAIN, &newtio);
463
464  stat = fcntl(p->fd_in, F_GETFL, 0);
465  if (stat > 0) {
466    stat |= O_NONBLOCK;
467    fcntl(p->fd_in, F_SETFL, stat);
468  }
469
470  p->TermMode = NULL;
471}
472
473/*
474 * Set tty into terminal mode which is used while we invoke term command.
475 */
476void
477prompt_TtyTermMode(struct prompt *p, struct datalink *dl)
478{
479  int stat;
480
481  if (p->Term == stdout)
482    tcsetattr(p->fd_in, TCSADRAIN, &p->comtio);
483
484  stat = fcntl(p->fd_in, F_GETFL, 0);
485  if (stat > 0) {
486    stat &= ~O_NONBLOCK;
487    fcntl(p->fd_in, F_SETFL, stat);
488  }
489  p->TermMode = dl;
490}
491
492void
493prompt_TtyOldMode(struct prompt *p)
494{
495  int stat;
496
497  stat = fcntl(p->fd_in, F_GETFL, 0);
498  if (stat > 0) {
499    stat &= ~O_NONBLOCK;
500    fcntl(p->fd_in, F_SETFL, stat);
501  }
502
503  if (p->Term == stdout)
504    tcsetattr(p->fd_in, TCSADRAIN, &p->oldtio);
505}
506
507pid_t
508prompt_pgrp(struct prompt *p)
509{
510  return tcgetpgrp(p->fd_in);
511}
512
513int
514PasswdCommand(struct cmdargs const *arg)
515{
516  const char *pass;
517
518  if (!arg->prompt) {
519    log_Printf(LogWARN, "passwd: Cannot specify without a prompt\n");
520    return 0;
521  }
522
523  if (arg->prompt->owner == NULL) {
524    log_Printf(LogWARN, "passwd: Not required\n");
525    return 0;
526  }
527
528  if (arg->argc == arg->argn)
529    pass = "";
530  else if (arg->argc > arg->argn+1)
531    return -1;
532  else
533    pass = arg->argv[arg->argn];
534
535  if (!strcmp(arg->prompt->owner->cfg.passwd, pass))
536    arg->prompt->auth = LOCAL_AUTH;
537  else
538    arg->prompt->auth = LOCAL_NO_AUTH;
539
540  return 0;
541}
542
543static struct pppTimer bgtimer;
544
545static void
546prompt_TimedContinue(void *v)
547{
548  prompt_Continue((struct prompt *)v);
549}
550
551void
552prompt_Continue(struct prompt *p)
553{
554  timer_Stop(&bgtimer);
555  if (getpgrp() == prompt_pgrp(p)) {
556    prompt_TtyCommandMode(p);
557    p->nonewline = 1;
558    prompt_Required(p);
559    log_ActivatePrompt(p);
560  } else if (!p->owner) {
561    bgtimer.func = prompt_TimedContinue;
562    bgtimer.name = "prompt bg";
563    bgtimer.load = SECTICKS;
564    bgtimer.arg = p;
565    timer_Start(&bgtimer);
566  }
567}
568
569void
570prompt_Suspend(struct prompt *p)
571{
572  if (getpgrp() == prompt_pgrp(p)) {
573    prompt_TtyOldMode(p);
574    log_DeactivatePrompt(p);
575  }
576}
577