1 /*-
2  * Copyright (c) 1997 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  *	$Id: pppctl.c,v 1.18 2005/07/11 22:32:28 brad Exp $
27  */
28 
29 #include <sys/types.h>
30 
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <sys/un.h>
35 #include <netdb.h>
36 
37 #include <sys/time.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <histedit.h>
41 #ifdef __FreeBSD__
42 #include <libutil.h>
43 #endif
44 #include <setjmp.h>
45 #include <signal.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <time.h>
50 #include <poll.h>
51 #include <unistd.h>
52 
53 #define LINELEN 2048
54 static char Buffer[LINELEN], Command[LINELEN];
55 
56 static int
usage()57 usage()
58 {
59     fprintf(stderr, "usage: pppctl [-v] [-p passwd] [-t n] "
60             "Port|LocalSock [command[;command]...]\n");
61     fprintf(stderr, "              -p passwd specifies your password\n");
62     fprintf(stderr, "              -t n specifies a timeout of n"
63             " seconds when connecting (default 2)\n");
64     fprintf(stderr, "              -v tells pppctl to output all"
65             " conversation\n");
66     exit(1);
67 }
68 
69 static int TimedOut = 0;
70 static void
Timeout(int Sig)71 Timeout(int Sig)
72 {
73     TimedOut = 1;
74 }
75 
76 #define REC_PASSWD  (1)
77 #define REC_SHOW    (2)
78 #define REC_VERBOSE (4)
79 
80 static char *passwd;
81 static char *prompt;
82 
83 static char *
GetPrompt(EditLine * e)84 GetPrompt(EditLine *e)
85 {
86     if (prompt == NULL)
87         prompt = "";
88     return prompt;
89 }
90 
91 static int
Receive(int fd,int display)92 Receive(int fd, int display)
93 {
94     int Result;
95     int len;
96     char *last;
97 
98     prompt = Buffer;
99     len = 0;
100     while (Result = read(fd, Buffer+len, sizeof(Buffer)-len-1), Result != -1) {
101         if (Result == 0 && errno != EINTR) {
102           Result = -1;
103           break;
104         }
105         len += Result;
106         Buffer[len] = '\0';
107         if (len > 2 && !strcmp(Buffer+len-2, "> ")) {
108             prompt = strrchr(Buffer, '\n');
109             if (display & (REC_SHOW|REC_VERBOSE)) {
110                 if (display & REC_VERBOSE)
111                     last = Buffer+len-1;
112                 else
113                     last = prompt;
114                 if (last) {
115                     last++;
116                     write(1, Buffer, last-Buffer);
117                 }
118             }
119             prompt = prompt == NULL ? Buffer : prompt+1;
120             for (last = Buffer+len-2; last > Buffer && *last != ' '; last--)
121                 ;
122             if (last > Buffer+3 && !strncmp(last-3, " on", 3)) {
123                  /* a password is required! */
124                  if (display & REC_PASSWD) {
125                     /* password time */
126                     if (!passwd)
127                         if ((passwd = getpass("Password: ")) == NULL)
128 				err(1, "getpass");
129                     snprintf(Buffer, sizeof Buffer, "passwd %s\n", passwd);
130                     memset(passwd, '\0', strlen(passwd));
131                     if (display & REC_VERBOSE)
132                         write(1, Buffer, strlen(Buffer));
133                     write(fd, Buffer, strlen(Buffer));
134                     memset(Buffer, '\0', strlen(Buffer));
135                     return Receive(fd, display & ~REC_PASSWD);
136                 }
137                 Result = 1;
138             } else
139                 Result = 0;
140             break;
141         }
142         if (len == sizeof Buffer - 1) {
143             int flush;
144             if ((last = strrchr(Buffer, '\n')) == NULL)
145                 /* Yeuch - this is one mother of a line ! */
146                 flush = sizeof Buffer / 2;
147             else
148                 flush = last - Buffer + 1;
149             write(1, Buffer, flush);
150             strlcpy(Buffer, Buffer + flush, sizeof Buffer);
151             len -= flush;
152         }
153     }
154 
155     return Result;
156 }
157 
158 static int data = -1;
159 static jmp_buf pppdead;
160 
161 static void
check_fd(int sig)162 check_fd(int sig)
163 {
164   if (data != -1) {
165     struct pollfd pfd[1];
166     static char buf[LINELEN];
167     int len;
168 
169     pfd[0].fd = data;
170     pfd[0].events = POLLIN;
171     if (poll(pfd, 1, 0) > 0) {
172       len = read(data, buf, sizeof buf);
173       if (len > 0)
174         write(1, buf, len);
175       else
176         longjmp(pppdead, -1);
177     }
178   }
179 }
180 
181 static const char *
smartgets(EditLine * e,int * count,int fd)182 smartgets(EditLine *e, int *count, int fd)
183 {
184   const char *result;
185 
186   data = fd;
187   signal(SIGALRM, check_fd);
188   ualarm(500000, 500000);
189   result = setjmp(pppdead) ? NULL : el_gets(e, count);
190   ualarm(0,0);
191   signal(SIGALRM, SIG_DFL);
192   data = -1;
193 
194   return result;
195 }
196 
197 static const char *
sockaddr_ntop(const struct sockaddr * sa)198 sockaddr_ntop(const struct sockaddr *sa)
199 {
200     const void *addr;
201     static char addrbuf[INET6_ADDRSTRLEN];
202 
203     switch (sa->sa_family) {
204     case AF_INET:
205 	addr = &((const struct sockaddr_in *)sa)->sin_addr;
206 	break;
207     case AF_UNIX:
208 	addr = &((const struct sockaddr_un *)sa)->sun_path;
209 	break;
210     case AF_INET6:
211 	addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
212 	break;
213     default:
214 	return NULL;
215     }
216     inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf));
217     return addrbuf;
218 }
219 
220 /*
221  * Connect to ppp using either a local domain socket or a tcp socket.
222  */
223 int
main(int argc,char ** argv)224 main(int argc, char **argv)
225 {
226     struct sockaddr_un ifsun;
227     int n, arg, fd = -1, len, verbose, save_errno, hide1, hide1off, hide2;
228     unsigned TimeoutVal;
229     char *DoneWord = "x", *next, *start;
230     struct sigaction act, oact;
231 
232     verbose = 0;
233     TimeoutVal = 2;
234     hide1 = hide1off = hide2 = 0;
235 
236     for (arg = 1; arg < argc; arg++)
237         if (*argv[arg] == '-') {
238             for (start = argv[arg] + 1; *start; start++)
239                 switch (*start) {
240                     case 't':
241                         TimeoutVal = (unsigned)atoi
242                             (start[1] ? start + 1 : argv[++arg]);
243                         start = DoneWord;
244                         break;
245 
246                     case 'v':
247                         verbose = REC_VERBOSE;
248                         break;
249 
250                     case 'p':
251                         if (start[1]) {
252                           hide1 = arg;
253                           hide1off = start - argv[arg];
254                           passwd = start + 1;
255                         } else {
256                           hide1 = arg;
257                           hide1off = start - argv[arg];
258                           passwd = argv[++arg];
259                           hide2 = arg;
260                         }
261                         start = DoneWord;
262                         break;
263 
264                     default:
265                         usage();
266                 }
267         }
268         else
269             break;
270 
271 
272     if (argc < arg + 1)
273         usage();
274 
275     if (hide1) {
276       char title[1024];
277       int pos, harg;
278 
279       for (harg = pos = 0; harg < argc; harg++)
280         if (harg == 0 || harg != hide2) {
281           if (harg == 0 || harg != hide1)
282             n = snprintf(title + pos, sizeof title - pos, "%s%s",
283                             harg ? " " : "", argv[harg]);
284           else if (hide1off > 1)
285             n = snprintf(title + pos, sizeof title - pos, " %.*s",
286                             hide1off, argv[harg]);
287           else
288             n = 0;
289           if (n < 0 || n >= sizeof title - pos)
290             break;
291           pos += n;
292         }
293 #ifdef __FreeBSD__
294       setproctitle("-%s", title);
295 #else
296       setproctitle("%s", title);
297 #endif
298     }
299 
300     if (*argv[arg] == '/') {
301         memset(&ifsun, '\0', sizeof ifsun);
302         ifsun.sun_len = strlen(argv[arg]);
303         if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
304             warnx("%s: path too long", argv[arg]);
305             return 1;
306         }
307         ifsun.sun_family = AF_LOCAL;
308         strlcpy(ifsun.sun_path, argv[arg], sizeof ifsun.sun_path);
309 
310         if (fd = socket(AF_LOCAL, SOCK_STREAM, 0), fd < 0) {
311             warnx("cannot create local domain socket");
312             return 2;
313         }
314 	if (connect(fd, (struct sockaddr *)&ifsun, sizeof(ifsun)) < 0) {
315 	    if (errno)
316 		warn("cannot connect to socket %s", argv[arg]);
317 	    else
318 		warnx("cannot connect to socket %s", argv[arg]);
319 	    close(fd);
320 	    return 3;
321 	}
322     } else {
323 	char *addr, *p, *port;
324 	const char *caddr;
325 	struct addrinfo hints, *res, *pai;
326 	int gai;
327 	char local[] = "localhost";
328 
329 	addr = argv[arg];
330 	if (addr[strspn(addr, "0123456789")] == '\0') {
331 	    /* port on local machine */
332 	    port = addr;
333 	    addr = local;
334 	} else if (*addr == '[') {
335 	    /* [addr]:port */
336 	    if ((p = strchr(addr, ']')) == NULL) {
337 		warnx("%s: mismatched '['", addr);
338 		return 1;
339 	    }
340 	    addr++;
341 	    *p++ = '\0';
342 	    if (*p != ':') {
343 		warnx("%s: missing port", addr);
344 		return 1;
345 	    }
346 	    port = ++p;
347 	} else {
348 	    /* addr:port */
349 	    p = addr + strcspn(addr, ":");
350 	    if (*p != ':') {
351 		warnx("%s: missing port", addr);
352 		return 1;
353 	    }
354 	    *p++ = '\0';
355 	    port = p;
356 	}
357 	memset(&hints, 0, sizeof(hints));
358 	hints.ai_socktype = SOCK_STREAM;
359 	gai = getaddrinfo(addr, port, &hints, &res);
360 	if (gai != 0) {
361 	    warnx("%s: %s", addr, gai_strerror(gai));
362 	    return 1;
363 	}
364 	for (pai = res; pai != NULL; pai = pai->ai_next) {
365 	    if (fd = socket(pai->ai_family, pai->ai_socktype,
366 		pai->ai_protocol), fd < 0) {
367 		warnx("cannot create socket");
368 		continue;
369 	    }
370 	    TimedOut = 0;
371 	    if (TimeoutVal) {
372 		act.sa_handler = Timeout;
373 		sigemptyset(&act.sa_mask);
374 		act.sa_flags = 0;
375 		sigaction(SIGALRM, &act, &oact);
376 		alarm(TimeoutVal);
377 	    }
378 	    if (connect(fd, pai->ai_addr, pai->ai_addrlen) == 0)
379 		break;
380 	    if (TimeoutVal) {
381 		save_errno = errno;
382 		alarm(0);
383 		sigaction(SIGALRM, &oact, 0);
384 		errno = save_errno;
385 	    }
386 	    caddr = sockaddr_ntop(pai->ai_addr);
387 	    if (caddr == NULL)
388 		caddr = argv[arg];
389 	    if (TimedOut)
390 		warnx("timeout: cannot connect to %s", caddr);
391 	    else {
392 		if (errno)
393 		    warn("cannot connect to %s", caddr);
394 		else
395 		    warnx("cannot connect to %s", caddr);
396 	    }
397 	    close(fd);
398 	}
399 	freeaddrinfo(res);
400 	if (pai == NULL)
401 	    return 1;
402 	if (TimeoutVal) {
403 	    alarm(0);
404 	    sigaction(SIGALRM, &oact, 0);
405 	}
406     }
407 
408     len = 0;
409     Command[sizeof(Command)-1] = '\0';
410     for (arg++; arg < argc; arg++) {
411         if (len && len < sizeof(Command)-1) {
412             strlcpy(Command+len, " ", sizeof Command - len);
413 	    len++;
414 	}
415         strncpy(Command+len, argv[arg], sizeof(Command)-len-1);
416         len += strlen(Command+len);
417     }
418 
419     switch (Receive(fd, verbose | REC_PASSWD))
420     {
421         case 1:
422             fprintf(stderr, "Password incorrect\n");
423             break;
424 
425         case 0:
426             if (len == 0) {
427                 EditLine *edit;
428                 History *hist;
429                 const char *l, *env;
430                 int size;
431                 HistEvent hev = { 0, "" };
432 
433                 hist = history_init();
434                 if ((env = getenv("EL_SIZE"))) {
435                     size = atoi(env);
436                     if (size < 0)
437                       size = 20;
438                 } else
439                     size = 20;
440                 history(hist, &hev, H_SETSIZE, size);
441                 edit = el_init("pppctl", stdin, stdout, stderr);
442                 el_source(edit, NULL);
443                 el_set(edit, EL_PROMPT, GetPrompt);
444                 if ((env = getenv("EL_EDITOR"))) {
445                     if (!strcmp(env, "vi"))
446                         el_set(edit, EL_EDITOR, "vi");
447                     else if (!strcmp(env, "emacs"))
448                         el_set(edit, EL_EDITOR, "emacs");
449                 }
450                 el_set(edit, EL_SIGNAL, 1);
451                 el_set(edit, EL_HIST, history, (const char *)hist);
452                 while ((l = smartgets(edit, &len, fd))) {
453                     if (len > 1)
454                         history(hist, &hev, H_ENTER, l);
455                     write(fd, l, len);
456                     if (Receive(fd, REC_SHOW) != 0)
457                         break;
458                 }
459                 fprintf(stderr, "Connection closed\n");
460                 el_end(edit);
461                 history_end(hist);
462             } else {
463                 start = Command;
464                 do {
465                     next = strchr(start, ';');
466                     while (*start == ' ' || *start == '\t')
467                         start++;
468                     if (next)
469                         *next = '\0';
470                     strlcpy(Buffer, start, sizeof Buffer);
471                     Buffer[sizeof(Buffer)-2] = '\0';
472                     strlcat(Buffer, "\n", sizeof Buffer);
473                     if (verbose)
474                         write(1, Buffer, strlen(Buffer));
475                     write(fd, Buffer, strlen(Buffer));
476                     if (Receive(fd, verbose | REC_SHOW) != 0) {
477                         fprintf(stderr, "Connection closed\n");
478                         break;
479                     }
480                     if (next)
481                         start = ++next;
482                 } while (next && *next);
483                 if (verbose)
484                     puts("");
485             }
486             break;
487 
488         default:
489             warnx("ppp is not responding");
490             break;
491     }
492 
493     close(fd);
494 
495     return 0;
496 }
497