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