Initial import from FreeBSD RELENG_4:
[dragonfly.git] / crypto / kerberosIV / appl / bsd / rlogin.c
1 /*
2  * Copyright (c) 1983, 1990, 1993
3  *      The Regents of the University of California.  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  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 /*
35  * rlogin - remote login
36  */
37 #include "bsd_locl.h"
38
39 RCSID("$Id: rlogin.c,v 1.67.2.2 2000/10/10 12:54:26 assar Exp $");
40
41 CREDENTIALS cred;
42 Key_schedule schedule;
43 int use_kerberos = 1, doencrypt;
44 char dst_realm_buf[REALM_SZ], *dest_realm = NULL;
45
46 #ifndef CCEQ
47 #define c2uc(x) ((unsigned char) x)
48 #define CCEQ__(val, c)  (c == val ? val != c2uc(_POSIX_VDISABLE) : 0)
49 #define CCEQ(val, c) CCEQ__(c2uc(val), c2uc(c))
50 #endif
51
52 int eight, rem;
53 struct termios deftty;
54
55 int noescape;
56 char escapechar = '~';
57
58 struct  winsize winsize;
59
60 int parent, rcvcnt;
61 char rcvbuf[8 * 1024];
62
63 int child;
64
65 static void
66 echo(char c)
67 {
68         char *p;
69         char buf[8];
70
71         p = buf;
72         c &= 0177;
73         *p++ = escapechar;
74         if (c < ' ') {
75                 *p++ = '^';
76                 *p++ = c + '@';
77         } else if (c == 0177) {
78                 *p++ = '^';
79                 *p++ = '?';
80         } else
81                 *p++ = c;
82         *p++ = '\r';
83         *p++ = '\n';
84         write(STDOUT_FILENO, buf, p - buf);
85 }
86
87 static void
88 mode(int f)
89 {
90         struct termios tty;
91
92         switch (f) {
93         case 0:
94                 tcsetattr(0, TCSANOW, &deftty);
95                 break;
96         case 1:
97                 tcgetattr(0, &deftty);
98                 tty = deftty;
99                 /* This is loosely derived from sys/compat/tty_compat.c. */
100                 tty.c_lflag &= ~(ECHO|ICANON|ISIG|IEXTEN);
101                 tty.c_iflag &= ~ICRNL;
102                 tty.c_oflag &= ~OPOST;
103                 tty.c_cc[VMIN] = 1;
104                 tty.c_cc[VTIME] = 0;
105                 if (eight) {
106                         tty.c_iflag &= IXOFF;
107                         tty.c_cflag &= ~(CSIZE|PARENB);
108                         tty.c_cflag |= CS8;
109                 }
110                 tcsetattr(0, TCSANOW, &tty);
111                 break;
112         default:
113                 return;
114         }
115 }
116
117 static void
118 done(int status)
119 {
120         int w, wstatus;
121
122         mode(0);
123         if (child > 0) {
124                 /* make sure catch_child does not snap it up */
125                 signal(SIGCHLD, SIG_DFL);
126                 if (kill(child, SIGKILL) >= 0)
127                         while ((w = wait(&wstatus)) > 0 && w != child);
128         }
129         exit(status);
130 }
131
132 static
133 RETSIGTYPE
134 catch_child(int foo)
135 {
136         int status;
137         int pid;
138
139         for (;;) {
140                 pid = waitpid(-1, &status, WNOHANG|WUNTRACED);
141                 if (pid == 0)
142                         return;
143                 /* if the child (reader) dies, just quit */
144                 if (pid < 0 || (pid == child && !WIFSTOPPED(status)))
145                         done(WTERMSIG(status) | WEXITSTATUS(status));
146         }
147         /* NOTREACHED */
148 }
149
150 /*
151  * There is a race in the SunOS5 rlogind. If the slave end has not yet
152  * been opened by the child when setting tty size the size is reset to
153  * zero when the child opens it. Therefore we send the window update
154  * twice.
155  */
156
157 static int tty_kludge = 1;
158
159 /* Return the number of OOB bytes processed. */
160 static int
161 oob_real(void)
162 {
163         struct termios tty;
164         int atmark, n, out, rcvd;
165         char waste[BUFSIZ], mark;
166
167         out = O_RDWR;
168         rcvd = 0;
169         if (recv(rem, &mark, 1, MSG_OOB) < 0) {
170                 return -1;
171         }
172         if (mark & TIOCPKT_WINDOW) {
173                 /* Let server know about window size changes */
174                 kill(parent, SIGUSR1);
175         } else if (tty_kludge) {
176                 /* Let server know about window size changes */
177                 kill(parent, SIGUSR1);
178                 tty_kludge = 0;
179         }
180         if (!eight && (mark & TIOCPKT_NOSTOP)) {
181                 tcgetattr(0, &tty);
182                 tty.c_iflag &= ~IXON;
183                 tcsetattr(0, TCSANOW, &tty);
184         }
185         if (!eight && (mark & TIOCPKT_DOSTOP)) {
186                 tcgetattr(0, &tty);
187                 tty.c_iflag |= (deftty.c_iflag & IXON);
188                 tcsetattr(0, TCSANOW, &tty);
189         }
190         if (mark & TIOCPKT_FLUSHWRITE) {
191 #ifdef TCOFLUSH
192                 tcflush(1, TCOFLUSH);
193 #else
194                 ioctl(1, TIOCFLUSH, (char *)&out);
195 #endif
196                 for (;;) {
197                         if (ioctl(rem, SIOCATMARK, &atmark) < 0) {
198                             warn("ioctl");
199                             break;
200                         }
201                         if (atmark)
202                                 break;
203                         n = read(rem, waste, sizeof (waste));
204                         if (n <= 0)
205                                 break;
206                 }
207                 /*
208                  * Don't want any pending data to be output, so clear the recv
209                  * buffer.  If we were hanging on a write when interrupted,
210                  * don't want it to restart.  If we were reading, restart
211                  * anyway.
212                  */
213                 rcvcnt = 0;
214         }
215
216         /* oob does not do FLUSHREAD (alas!) */
217         return 1;
218 }
219
220 /* reader: read from remote: line -> 1 */
221 static int
222 reader(void)
223 {
224         int n, remaining;
225         char *bufp;
226         int kludgep = 1;
227
228         bufp = rcvbuf;
229         for (;;) {
230                 fd_set readfds, exceptfds;
231                 while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) {
232                         n = write(STDOUT_FILENO, bufp, remaining);
233                         if (n < 0) {
234                                 if (errno != EINTR)
235                                         return (-1);
236                                 continue;
237                         }
238                         bufp += n;
239                 }
240                 bufp = rcvbuf;
241                 rcvcnt = 0;
242
243                 FD_ZERO (&readfds);
244                 if (rem >= FD_SETSIZE)
245                     errx (1, "fd too large");
246                 FD_SET (rem, &readfds);
247                 FD_ZERO (&exceptfds);
248                 if (kludgep)
249                   FD_SET (rem, &exceptfds);
250                 if (select(rem+1, &readfds, 0, &exceptfds, 0) == -1) {
251                     if (errno == EINTR)
252                         continue; /* Got signal */
253                     else
254                         errx(1, "select failed mysteriously");
255                 }
256
257                 if (!FD_ISSET(rem, &exceptfds) && !FD_ISSET(rem, &readfds)) {
258                     warnx("select: nothing to read?");
259                     continue;
260                   }
261
262                 if (FD_ISSET(rem, &exceptfds)) {
263                   int foo = oob_real ();
264                   if (foo >= 1)
265                     continue;   /* First check if there is more OOB data. */
266                   else if (foo < 0)
267                     kludgep = 0;
268                 }
269
270                 if (!FD_ISSET(rem, &readfds))
271                     continue;   /* Nothing to read. */
272
273                 kludgep = 1;
274 #ifndef NOENCRYPTION
275                 if (doencrypt)
276                         rcvcnt = des_enc_read(rem, rcvbuf,
277                                               sizeof(rcvbuf),
278                                               schedule, &cred.session);
279                 else
280 #endif
281                         rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf));
282                 if (rcvcnt == 0)
283                         return (0);
284                 if (rcvcnt < 0) {
285                         if (errno == EINTR)
286                                 continue;
287                         warn("read");
288                         return (-1);
289                 }
290         }
291 }
292
293 /*
294  * Send the window size to the server via the magic escape
295  */
296 static void
297 sendwindow(void)
298 {
299         char obuf[4 + 4 * sizeof (u_int16_t)];
300         unsigned short *p;
301
302         p = (u_int16_t *)(obuf + 4);
303         obuf[0] = 0377;
304         obuf[1] = 0377;
305         obuf[2] = 's';
306         obuf[3] = 's';
307         *p++ = htons(winsize.ws_row);
308         *p++ = htons(winsize.ws_col);
309 #ifdef HAVE_WS_XPIXEL
310         *p++ = htons(winsize.ws_xpixel);
311 #else
312         *p++ = htons(0);
313 #endif  
314 #ifdef HAVE_WS_YPIXEL
315         *p++ = htons(winsize.ws_ypixel);
316 #else
317         *p++ = htons(0);
318 #endif  
319
320 #ifndef NOENCRYPTION
321         if(doencrypt)
322                 des_enc_write(rem, obuf, sizeof(obuf), schedule,
323                               &cred.session);
324         else
325 #endif
326                 write(rem, obuf, sizeof(obuf));
327 }
328
329 static
330 RETSIGTYPE
331 sigwinch(int foo)
332 {
333         struct winsize ws;
334
335         if (get_window_size(0, &ws) == 0 &&
336             memcmp(&ws, &winsize, sizeof(ws))) {
337                 winsize = ws;
338                 sendwindow();
339         }
340 }
341
342 static void
343 stop(int all)
344 {
345         mode(0);
346         signal(SIGCHLD, SIG_IGN);
347         kill(all ? 0 : getpid(), SIGTSTP);
348         signal(SIGCHLD, catch_child);
349         mode(1);
350 #ifdef SIGWINCH
351         kill(SIGWINCH, getpid()); /* check for size changes, if caught */
352 #endif
353 }
354
355 /*
356  * writer: write to remote: 0 -> line.
357  * ~.                           terminate
358  * ~^Z                          suspend rlogin process.
359  * ~<delayed-suspend char>      suspend rlogin process, but leave reader alone.
360  */
361 static void
362 writer(void)
363 {
364         int bol, local, n;
365         char c;
366
367         bol = 1;                        /* beginning of line */
368         local = 0;
369         for (;;) {
370                 n = read(STDIN_FILENO, &c, 1);
371                 if (n <= 0) {
372                         if (n < 0 && errno == EINTR)
373                                 continue;
374                         break;
375                 }
376                 /*
377                  * If we're at the beginning of the line and recognize a
378                  * command character, then we echo locally.  Otherwise,
379                  * characters are echo'd remotely.  If the command character
380                  * is doubled, this acts as a force and local echo is
381                  * suppressed.
382                  */
383                 if (bol) {
384                         bol = 0;
385                         if (!noescape && c == escapechar) {
386                                 local = 1;
387                                 continue;
388                         }
389                 } else if (local) {
390                         local = 0;
391                         if (c == '.' || CCEQ(deftty.c_cc[VEOF], c)) {
392                                 echo(c);
393                                 break;
394                         }
395                         if (CCEQ(deftty.c_cc[VSUSP], c)) {
396                                 bol = 1;
397                                 echo(c);
398                                 stop(1);
399                                 continue;
400                         }
401 #ifdef VDSUSP
402                         /* Is VDSUSP called something else on Linux?
403                          * Perhaps VDELAY is a better thing? */         
404                         if (CCEQ(deftty.c_cc[VDSUSP], c)) {
405                                 bol = 1;
406                                 echo(c);
407                                 stop(0);
408                                 continue;
409                         }
410 #endif /* VDSUSP */
411                         if (c != escapechar) {
412 #ifndef NOENCRYPTION
413                                 if (doencrypt)
414                                         des_enc_write(rem, &escapechar,1, schedule, &cred.session);
415                                 else
416 #endif
417                                         write(rem, &escapechar, 1);
418                         }
419                 }
420
421                 if (doencrypt) {
422 #ifdef NOENCRYPTION
423                         if (write(rem, &c, 1) == 0) {
424 #else
425                         if (des_enc_write(rem, &c, 1, schedule, &cred.session) == 0) {
426 #endif
427                                 warnx("line gone");
428                                 break;
429                         }
430                 } else
431                         if (write(rem, &c, 1) == 0) {
432                                 warnx("line gone");
433                                 break;
434                         }
435                 bol = CCEQ(deftty.c_cc[VKILL], c) ||
436                     CCEQ(deftty.c_cc[VEOF], c) ||
437                     CCEQ(deftty.c_cc[VINTR], c) ||
438                     CCEQ(deftty.c_cc[VSUSP], c) ||
439                     c == '\r' || c == '\n';
440         }
441 }
442
443 static
444 RETSIGTYPE
445 lostpeer(int foo)
446 {
447         signal(SIGPIPE, SIG_IGN);
448         warnx("\aconnection closed.\r");
449         done(1);
450 }
451
452 /*
453  * This is called in the parent when the reader process gets the
454  * out-of-band (urgent) request to turn on the window-changing
455  * protocol. It is signalled from the child(reader).
456  */
457 static
458 RETSIGTYPE
459 sigusr1(int foo)
460 {
461         /*
462          * Now we now daemon supports winsize hack,
463          */
464         sendwindow();
465 #ifdef SIGWINCH
466         signal(SIGWINCH, sigwinch); /* so we start to support it */
467 #endif
468         SIGRETURN(0);
469 }
470
471 static void
472 doit(void)
473 {
474         signal(SIGINT, SIG_IGN);
475         signal(SIGHUP, SIG_IGN);
476         signal(SIGQUIT, SIG_IGN);
477
478         signal(SIGCHLD, catch_child);
479
480         /*
481          * Child sends parent this signal for window size hack.
482          */
483         signal(SIGUSR1, sigusr1);
484
485         signal(SIGPIPE, lostpeer);
486
487         mode(1);
488         parent = getpid();
489         child = fork();
490         if (child == -1) {
491             warn("fork");
492             done(1);
493         }
494         if (child == 0) {
495                 signal(SIGCHLD, SIG_IGN);
496                 signal(SIGTTOU, SIG_IGN);
497                 if (reader() == 0)
498                     errx(1, "connection closed.\r");
499                 sleep(1);
500                 errx(1, "\aconnection closed.\r");
501         }
502
503         writer();
504         warnx("closed connection.\r");
505         done(0);
506 }
507
508 static void
509 usage(void)
510 {
511     fprintf(stderr,
512             "usage: rlogin [ -%s]%s[-e char] [ -l username ] host\n",
513             "8DEKLdx", " [-k realm] ");
514     exit(1);
515 }
516
517 static u_int
518 getescape(char *p)
519 {
520         long val;
521         int len;
522
523         if ((len = strlen(p)) == 1)     /* use any single char, including '\' */
524                 return ((u_int)*p);
525                                         /* otherwise, \nnn */
526         if (*p == '\\' && len >= 2 && len <= 4) {
527                 val = strtol(++p, NULL, 8);
528                 for (;;) {
529                         if (!*++p)
530                                 return ((u_int)val);
531                         if (*p < '0' || *p > '8')
532                                 break;
533                 }
534         }
535         warnx("illegal option value -- e");
536         usage();
537         return 0;
538 }
539
540 int
541 main(int argc, char **argv)
542 {
543         struct passwd *pw;
544         int sv_port, user_port = 0;
545         int argoff, ch, dflag, Dflag, one, uid;
546         char *host, *user, term[1024];
547
548         argoff = dflag = Dflag = 0;
549         one = 1;
550         host = user = NULL;
551
552         set_progname(argv[0]);
553
554         /* handle "rlogin host flags" */
555         if (argc > 2 && argv[1][0] != '-') {
556             host = argv[1];
557             argoff = 1;
558         }
559
560 #define OPTIONS "8DEKLde:k:l:xp:"
561         while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != -1)
562                 switch(ch) {
563                 case '8':
564                         eight = 1;
565                         break;
566                 case 'D':
567                         Dflag = 1;
568                         break;
569                 case 'E':
570                         noescape = 1;
571                         break;
572                 case 'K':
573                         use_kerberos = 0;
574                         break;
575                 case 'd':
576                         dflag = 1;
577                         break;
578                 case 'e':
579                         noescape = 0;
580                         escapechar = getescape(optarg);
581                         break;
582                 case 'k':
583                         dest_realm = dst_realm_buf;
584                         strlcpy(dest_realm, optarg, REALM_SZ);
585                         break;
586                 case 'l':
587                         user = optarg;
588                         break;
589                 case 'x':
590                         doencrypt = 1;
591                         break;
592                 case 'p': {
593                     char *endptr;
594
595                     user_port = strtol (optarg, &endptr, 0);
596                     if (user_port == 0 && optarg == endptr)
597                         errx (1, "Bad port `%s'", optarg);
598                     user_port = htons(user_port);
599                     break;
600                 }
601                 case '?':
602                 default:
603                         usage();
604                 }
605         optind += argoff;
606
607         /* if haven't gotten a host yet, do so */
608         if (!host && !(host = argv[optind++]))
609                 usage();
610
611         if (argv[optind])
612                 usage();
613
614         if (!(pw = k_getpwuid(uid = getuid())))
615             errx(1, "unknown user id.");
616         if (!user)
617             user = pw->pw_name;
618
619         if (user_port)
620                 sv_port = user_port;
621         else
622                 sv_port = get_login_port(use_kerberos, doencrypt);
623
624         {
625             char *p = getenv("TERM");
626             struct termios tty;
627             int i;
628
629             if (p == NULL)
630                 p = "network";
631
632             if (tcgetattr(0, &tty) == 0
633                 && (i = speed_t2int (cfgetospeed(&tty))) > 0)
634                 snprintf (term, sizeof(term),
635                           "%s/%d",
636                           p, i);
637             else
638                 snprintf (term, sizeof(term),
639                           "%s",
640                           p);
641         }
642
643         get_window_size(0, &winsize);
644
645         if (use_kerberos) {
646                 paranoid_setuid(getuid());
647                 rem = KSUCCESS;
648                 errno = 0;
649                 if (dest_realm == NULL)
650                     dest_realm = krb_realmofhost(host);
651
652                 if (doencrypt)
653                     rem = krcmd_mutual(&host, sv_port, user, term, 0,
654                                        dest_realm, &cred, schedule);
655                 else
656                     rem = krcmd(&host, sv_port, user, term, 0,
657                                 dest_realm);
658                 if (rem < 0) {
659                     int i;
660                     char **newargv;
661
662                     if (errno == ECONNREFUSED)
663                         warning("remote host doesn't support Kerberos");
664                     if (errno == ENOENT)
665                         warning("can't provide Kerberos auth data");
666                     newargv = malloc((argc + 2) * sizeof(*newargv));
667                     if (newargv == NULL)
668                         err(1, "malloc");
669                     newargv[0] = argv[0];
670                     newargv[1] = "-K";
671                     for(i = 1; i < argc; ++i)
672                         newargv[i + 1] = argv[i];
673                     newargv[argc + 1] = NULL;
674                     execv(_PATH_RLOGIN, newargv);
675                 }
676         } else {
677                 if (doencrypt)
678                     errx(1, "the -x flag requires Kerberos authentication.");
679                 if (geteuid() != 0)
680                     errx(1, "not installed setuid root, "
681                          "only root may use non kerberized rlogin");
682                 rem = rcmd(&host, sv_port, pw->pw_name, user, term, 0);
683         }
684         
685         if (rem < 0)
686                 exit(1);
687
688 #ifdef HAVE_SETSOCKOPT
689 #ifdef SO_DEBUG
690         if (dflag &&
691             setsockopt(rem, SOL_SOCKET, SO_DEBUG, (void *)&one,
692                        sizeof(one)) < 0)
693             warn("setsockopt");
694 #endif
695 #ifdef TCP_NODELAY
696         if (Dflag &&
697             setsockopt(rem, IPPROTO_TCP, TCP_NODELAY, (void *)&one,
698                        sizeof(one)) < 0)
699             warn("setsockopt(TCP_NODELAY)");
700 #endif
701 #ifdef IP_TOS
702         one = IPTOS_LOWDELAY;
703         if (setsockopt(rem, IPPROTO_IP, IP_TOS, (void *)&one, sizeof(int)) < 0)
704             warn("setsockopt(IP_TOS)");
705 #endif /* IP_TOS */
706 #endif /* HAVE_SETSOCKOPT */
707
708         paranoid_setuid(uid);
709         doit();
710         return 0;
711 }