summaryrefslogtreecommitdiff
path: root/release/src/router/pptp-client/pptp.c
blob: 192f1630dfb929796c6b7ae777e99ddb02fdb77e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/* pptp.c ... client shell to launch call managers, data handlers, and
 *            the pppd from the command line.
 *            C. Scott Ananian <cananian@alumni.princeton.edu>
 *
 * $Id: pptp.c,v 1.2 2002/08/20 07:11:40 honor Exp $
 */

#include <sys/types.h>
#include <sys/socket.h>
#if defined(__FreeBSD__)
#include <libutil.h>
#elif defined(__NetBSD__)
#include <util.h>
#else
#include <pty.h>
#endif
//#ifdef USER_PPP
#include <fcntl.h>
//#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <errno.h>
#include <sys/wait.h>
#include <getopt.h>
#include <limits.h>
#include "config.h"
#include "pptp_callmgr.h"
#include "pptp_gre.h"
#include "version.h"
#include "inststr.h"
#include "util.h"
#include "pptp_quirks.h"

#ifndef PPPD_BINARY
#define PPPD_BINARY "pppd"
#endif

struct in_addr get_ip_address(char *name);
int open_callmgr(struct in_addr inetaddr, char *phonenr, int argc,char **argv,char **envp);
void launch_callmgr(struct in_addr inetaddr, char *phonenr, int argc,char **argv,char **envp);
int get_call_id(int sock, pid_t gre, pid_t pppd, 
		 u_int16_t *call_id, u_int16_t *peer_call_id);
void launch_pppd(char *ttydev, int argc, char **argv);


/* TODO: redesign to avoid longjmp/setjmp.  Several variables here
   have a volatile qualifier to silence warnings from gcc < 3.0.
   Remove the volatile qualifiers when longjmp/setjmp are removed. */
/* Create pseudo tty master slave pair and set terminal attributes
   according to TERMP and WINP.  Return handles for both ends in
   AMASTER and ASLAVE, and return the name of the slave end in NAME.  */

/* from uClibc 0.9.9 openpty.c */
int
my_openpty (int *amaster, int *aslave, char *name, struct termios *termp,
         struct winsize *winp)
{
#if 0
#ifdef PATH_MAX
  char _buf[PATH_MAX];
#else
  char _buf[512];
#endif
  char *buf = _buf;
#else
#ifdef PATH_MAX
  char buf[PATH_MAX];
#else
  char buf[512];
#endif
#endif
  int master, slave;

  master = getpt ();
  if (master == -1)
    return -1;

  if (grantpt (master))
    goto fail;

  if (unlockpt (master))
    goto fail;

#if 0
  if (pts_name (master, &buf, sizeof (_buf)))
#else
  if (ptsname_r (master, buf, sizeof buf))
#endif
    goto fail;

  slave = open (buf, O_RDWR | O_NOCTTY);
  if (slave == -1)
    {
#if 0
      if (buf != _buf)
        free (buf);
#endif
      goto fail;
    }

  /* XXX Should we ignore errors here?  */
  if(termp)
    tcsetattr (slave, TCSAFLUSH, termp);
  if (winp)
    ioctl (slave, TIOCSWINSZ, winp);

  *amaster = master;
  *aslave = slave;
  if (name != NULL)
    strcpy (name, buf);

#if 0
  if (buf != _buf)
    free (buf);
#endif
  return 0;

 fail:
  close (master);
  return -1;
}

void usage(char *progname) {
  fprintf(stderr,
	  "%s\n"
	  "Usage:\n"
	  " %s hostname [[--phone <phone number>] [--quirks ISP_NAME] -- ][ pppd options]\n"
	  "\nOr using pppd option pty: \n"
	  " pty \" %s hostname --nolaunchpppd [--phone <phone number>] [--quirks ISP_NAME]\"\n"
	  "Currently recognized ISP_NAMEs for quirks are BEZEQ_ISRAEL\n",
	  version, progname, progname);
  log("%s called with wrong arguments, program not started.", progname);
  
  exit(1);
}

static int signaled = 0;

void do_nothing(int sig) { 
    /* do nothing signal handler. Better than SIG_IGN. */
    signaled = 1;
}

sigjmp_buf env;
void sighandler(int sig) {
  siglongjmp(env, 1);
}

/* TODO: redesign to avoid longjmp/setjmp.  Several variables here
   have a volatile qualifier to silence warnings from gcc < 3.0.
   Remove the volatile qualifiers when longjmp/setjmp are removed. */

int main(int argc, char **argv, char **envp) {
  struct in_addr inetaddr;
  volatile int callmgr_sock = -1;
  char ttydev[PATH_MAX];
  int pty_fd, tty_fd, rc;
  volatile pid_t parent_pid, child_pid;
  u_int16_t call_id, peer_call_id;
  int pppdargc;
  char **pppdargv;
  char phonenrbuf[65]; /* maximum length of field plus one for the trailing
                        * '\0' */
  char * volatile phonenr = NULL;
  volatile int launchpppd = 1;
  if (argc < 2)
    usage(argv[0]);

  /* Step 1a: Get IP address for the hostname in argv[1] */
  inetaddr = get_ip_address(argv[1]);

  /* step 1b: Find the ppp options, extract phone number */
  argc--;
  argv++;
  while(1){ 
      /* structure with all recognised options for pptp */
      static struct option long_options[] = {
          {"phone", 1, 0, 0},  
          {"nolaunchpppd", 0, 0, 0},  
	  {"quirks", 1, 0, 0},
          {0, 0, 0, 0}
      };
      int option_index = 0;
      int c;
      opterr=0; /* suppress "unrecognised option" message, here
                 * we assume that it is a pppd option */
      c = getopt_long (argc, argv, "", long_options, &option_index);
      if( c==-1) break;  /* no more options */
      switch (c) {
        case 0: 
            if(option_index == 0) { /* --phone specified */
                /* copy it to a buffer, as the argv's will be overwritten by 
                 * inststr() */
                strncpy(phonenrbuf,optarg,sizeof(phonenrbuf));
                phonenrbuf[sizeof(phonenrbuf)-1]='\0';
                phonenr=phonenrbuf;
            }else if(option_index == 1) {/* --nolaunchpppd specified */
                  launchpppd=0;
            }else if(option_index == 2) {/* --quirks specified */
                if (set_quirk_index(find_quirk(optarg)))
                    usage(argv[0]);
            }
            /* other pptp options come here */
            break;
        case '?': /* unrecognised option, treat it as the first pppd option */
            /* fall through */
        default:
            c = -1;
            break;
      }
      if( c==-1) break;  /* no more options for pptp */
    }
  pppdargc = argc - optind;
  pppdargv = argv + optind;
 
  /* Step 3: Find an open pty/tty pair. */
  if(launchpppd){
      rc = my_openpty (&pty_fd, &tty_fd, ttydev, NULL, NULL);
      if (rc < 0) { 
          close(callmgr_sock); 
          fatal("Could not find free pty.");
      }
  
      /* Step 4: fork and wait. */
      signal(SIGUSR1, do_nothing); /* don't die */
      parent_pid = getpid();
      switch (child_pid = fork()) {
      case -1:
        fatal("Could not fork pppd process");

      case 0: /* I'm the child! */
        close (tty_fd);
        signal(SIGUSR1, SIG_DFL);
        child_pid = getpid();
        break;
      default: /* parent */
        close (pty_fd);
        /*
         * There is still a very small race condition here.  If a signal
         * occurs after signaled is checked but before pause is called,
         * things will hang.
         */
        if (!signaled) {
            pause(); /* wait for the signal */
        }
        launch_pppd(ttydev, pppdargc, pppdargv); /* launch pppd */
        perror("Error");
        fatal("Could not launch pppd");
      }
  } else { /* ! launchpppd */
      pty_fd=tty_fd=0;
      child_pid=getpid();
      parent_pid=0; /* don't kill pppd */
  }

  do {
    /*
     * Step 2: Open connection to call manager
     *         (Launch call manager if necessary.)
     */
    callmgr_sock = open_callmgr(inetaddr, phonenr, argc, argv, envp);

  /* Step 5: Exchange PIDs, get call ID */
  } while (get_call_id(callmgr_sock, parent_pid, child_pid, 
	               &call_id, &peer_call_id) < 0);

  /* Step 5b: Send signal to wake up pppd task */
  if(launchpppd){
      kill(parent_pid, SIGUSR1);
      sleep(2);
  }
 
  {
    char buf[128];
    snprintf(buf, sizeof(buf), "pptp: GRE-to-PPP gateway on %s", ttyname(tty_fd));
    inststr(argc,argv,envp, buf);
  }

  if (sigsetjmp(env, 1)!=0) goto shutdown;
  signal(SIGINT,  sighandler);
  signal(SIGTERM, sighandler);
  signal(SIGKILL, sighandler);
 
  /* Step 6: Do GRE copy until close. */
  pptp_gre_copy(call_id, peer_call_id, pty_fd, inetaddr);

shutdown:
  /* on close, kill all. */
  if(launchpppd)
      kill(parent_pid, SIGTERM);
  close(pty_fd);
  close(callmgr_sock);
  sleep(3);     /* give ctrl manager a chance to exit */
  exit(0);
}

struct in_addr get_ip_address(char *name) {
  struct in_addr retval;
  struct hostent *host = gethostbyname(name);
  if (host==NULL) {
    if (h_errno == HOST_NOT_FOUND)
      fatal("gethostbyname: HOST NOT FOUND");
    else if (h_errno == NO_ADDRESS)
      fatal("gethostbyname: NO IP ADDRESS");
    else
      fatal("gethostbyname: name server error");
  }
  
  if (host->h_addrtype != AF_INET)
    fatal("Host has non-internet address");
  
  memcpy(&retval.s_addr, host->h_addr, sizeof(retval.s_addr));
  return retval;
}

int open_callmgr(struct in_addr inetaddr, char *phonenr, int argc, char **argv, char **envp)
{
  /* Try to open unix domain socket to call manager. */
  struct sockaddr_un where;
  const int NUM_TRIES = 3;
  int i, fd;
  pid_t pid;
  int status;

  /* Open socket */
  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    fatal("Could not create unix domain socket: %s", strerror(errno));
  }

  /* Make address */
  where.sun_family = AF_UNIX;
  snprintf(where.sun_path, sizeof(where.sun_path), 
	   PPTP_SOCKET_PREFIX "%s", inet_ntoa(inetaddr));

  for (i=0; i<NUM_TRIES; i++) {
    if (connect(fd, (struct sockaddr *) &where, sizeof(where)) < 0) {
      /* couldn't connect.  We'll have to launch this guy. */

      unlink (where.sun_path);	

      /* fork and launch call manager process */
      switch (pid=fork()) {
      case -1: /* failure */
	  fatal("fork() to launch call manager failed.");
      case 0: /* child */
	  {
	      close (fd);
	      launch_callmgr(inetaddr, phonenr, argc,argv,envp);
	  }
      default: /* parent */
	  waitpid(pid, &status, 0);
	  if (status!=0)
	      fatal("Call manager exited with error %d", status);
	  break;
      }
      sleep(1);
    }
    else return fd;
  }
  close(fd);
  fatal("Could not launch call manager after %d tries.", i);
  return -1;   /* make gcc happy */
}

void launch_callmgr(struct in_addr inetaddr, char *phonenr, int argc,
        char**argv,char**envp) 
{
      int callmgr_main(int argc, char**argv, char**envp);
      char *my_argv[3] = { argv[0], inet_ntoa(inetaddr), phonenr };
      char buf[128];
      snprintf(buf, sizeof(buf), "pptp: call manager for %s", my_argv[1]);
      inststr(argc,argv,envp,buf);
      exit(callmgr_main(3, my_argv, envp));
      /*
      const char *callmgr = PPTP_CALLMGR_BINARY;
      execlp(callmgr, callmgr, inet_ntoa(inetaddr), NULL);
      fatal("execlp() of call manager [%s] failed: %s", 
	  callmgr, strerror(errno));
      */
}

/* XXX need better error checking XXX */
int get_call_id(int sock, pid_t gre, pid_t pppd, 
		 u_int16_t *call_id, u_int16_t *peer_call_id)
{
  u_int16_t m_call_id, m_peer_call_id;
  /* write pid's to socket */
  /* don't bother with network byte order, because pid's are meaningless
   * outside the local host.
   */
  int rc;
  rc = write(sock, &gre, sizeof(gre));
  if (rc != sizeof(gre))
      return -1;
  rc = write(sock, &pppd, sizeof(pppd));
  if (rc != sizeof(pppd))
      return -1;
  rc = read(sock,  &m_call_id, sizeof(m_call_id));
  if (rc != sizeof(m_call_id))
      return -1;
  rc = read(sock,  &m_peer_call_id, sizeof(m_peer_call_id));
  if (rc != sizeof(m_peer_call_id))
      return -1;
  /*
   * XXX FIX ME ... DO ERROR CHECKING & TIME-OUTS XXX
   * (Rhialto: I am assuming for now that timeouts are not relevant
   * here, because the read and write calls would return -1 (fail) when
   * the peer goes away during the process. We know it is (or was)
   * running because the connect() call succeeded.)
   */
  *call_id = m_call_id;
  *peer_call_id = m_peer_call_id;

  return 0;
}

void launch_pppd(char *ttydev, int argc, char **argv) {
  char *new_argv[argc+4]; /* XXX if not using GCC, hard code a limit here. */
  int i = 0, j;

  new_argv[i++] = PPPD_BINARY;
#ifdef USER_PPP
  new_argv[i++] = "-direct";
  /* ppp expects to have stdin connected to ttydev */
  if ((j = open(ttydev, O_RDWR)) == -1)
    fatal("Cannot open %s: %s", ttydev, strerror(errno));
  if (dup2(j, 0) == -1)
    fatal("dup2 failed: %s", strerror(errno));
  close(j);
#else
  new_argv[i++] = ttydev;
  new_argv[i++] = "38400";
#endif
  for (j=0; j<argc; j++)
    new_argv[i++] = argv[j];
  new_argv[i] = NULL;
  execvp(new_argv[0], new_argv);
}

/*************** COMPILE call manager into same binary *********/
#define main       callmgr_main
#define sighandler callmgr_sighandler
#define do_nothing callmgr_do_nothing
#define env        callmgr_env
#include "pptp_callmgr.c"