/* (uu)getty - sets up speed, various terminal flags, line discipline, * and waits for new prospective user to enter name, before * calling "login". */ #ident "$Revision: 1.80 $" #ifndef UUGETTY #define USAGE "usage: getty [-hN] [-t time] [-s oline] line [speed [terminal [ldisc]]]\n" #endif /* -h says don't hangup by dropping carrier during the * initialization phase. Normally carrier is dropped to * make the dataswitch release the line. * -N honors /etc/nologin * -t says timeout after the number of seconds in "time" have * elapsed even if nothing is typed. This is useful * for making sure dialup lines release if someone calls * in and then doesn't actually login in. * -L de overwrites the environment variable LANG * "line" is the device in "/dev". * "speed" is a pointer into the "/etc/getty_defs" where the * definition for the speeds and other associated flags are * to be found. * "term" is the name of the terminal type. * "ldisc" is the name of the line discipline. */ #ifdef UUGETTY #define USAGE "usage: uugetty [-hNDr] [-t time] [-i dial1[,dial2] [-d secs] line [speed [terminal [ldisc]]]\n" #endif /* -D requests debugging * -r requires that a character other than EOT be received before * continuing. This cannot be used if two chat scripts * are used with -i. * -i dialer says to initialize the line with the labeled entry in * /etc/uucp/Dialers * -i dialer1,dialer2 says to initialize with the first Dialers entry * and to use the second to wait for "CONNECT". * -d delays a number of seconds after the first character before * believing anything. */ /* Usage: getty -c gettydefs_like_file * * The "-c" flag is used to have "getty" check a gettydefs file. * "getty" parses the entire file and prints out its findings so * that the user can make sure that the file contains the proper * information. */ #ifndef UUGETTY #define uugetty_ck(line) #define delock(line) #define UUCPUID 0 int Debug; #ifdef sgi #include #define AUX_LANG /* LANG handling on */ #endif /* sgi */ #define TRUE 1 #define FALSE 0 #define SUCCESS 0 #endif /* !UUGETTY */ #define FAILURE (-1) #define ID 1 #define IFLAGS 2 #define FFLAGS 3 #define MESSAGE 4 #define NEXTID 5 #define ACTIVE 1 #define FINISHED 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef UUGETTY #include "uucp.h" #undef DEBUG static void uugetty_ck(char *); #endif struct Gdef { char *g_id; /* identification for modes & speeds */ struct termio g_iflags; /* initial terminal flags */ struct termio g_fflags; /* final terminal flags */ char *g_message; /* login message */ char *g_nextid; /* next id if this speed is wrong */ }; static struct Gdef *find_def(char *id); static void parse(char *, char **, int); static void badcall(char*, char*, int, int); static char *getvar(char*, int *); #define GOODNAME 1 #define NONAME 0 #define BADSPEED (-1) #define MAXIDLENGTH 15 /* Maximum length the "g_id" and "g_nextid" * strings can take. Longer ones will be * truncated. */ #define OMAXMESS 79 /* old max message length */ #define MAXMESSAGE 512 /* Maximum length the "g_message" string * can be. Longer ones are truncated. */ /* Maximum length of line in /etc/gettydefs file and the maximum */ /* length of the user response to the "login" message. */ #define OMAXLINE 255 /* original MAXLINE */ #define MAXLINE OMAXLINE-OMAXMESS+MAXMESSAGE /* 688 */ #define MAXARGS 64 /* Maximum number of arguments that can be * passed to "login" */ struct Symbols { char *s_symbol; /* Name of symbol */ unsigned s_value ; /* Value of symbol */ }; /* The following four symbols define the "SANE" state. */ #define ISANE (BRKINT|IGNPAR|ISTRIP|ICRNL|IXON) #define OSANE (OPOST|ONLCR) #define CSANE (CS8|CREAD) #ifdef IEXTEN #define LSANE (ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN) #else #define LSANE (ISIG|ICANON|ECHO|ECHOE|ECHOK) #endif /* Modes set with the TCSETAW ioctl command. */ static struct Symbols imodes[] = { "IGNBRK", IGNBRK, "BRKINT", BRKINT, "IGNPAR", IGNPAR, "PARMRK", PARMRK, "INPCK", INPCK, "ISTRIP", ISTRIP, "INLCR", INLCR, "IGNCR", IGNCR, "ICRNL", ICRNL, "IUCLC", IUCLC, "IXON", IXON, "IXANY", IXANY, "IXOFF", IXOFF, NULL, 0 }; static struct Symbols omodes[] = { "OPOST", OPOST, "OLCUC", OLCUC, "ONLCR", ONLCR, "OCRNL", OCRNL, "ONOCR", ONOCR, "ONLRET", ONLRET, "OFILL", OFILL, "OFDEL", OFDEL, "NLDLY", NLDLY, "NL0", NL0, "NL1", NL1, "CRDLY", CRDLY, "CR0", CR0, "CR1", CR1, "CR2", CR2, "CR3", CR3, "TABDLY", TABDLY, "TAB0", TAB0, "TAB1", TAB1, "TAB2", TAB2, "TAB3", TAB3, "BSDLY", BSDLY, "BS0", BS0, "BS1", BS1, "VTDLY", VTDLY, "VT0", VT0, "VT1", VT1, "FFDLY", FFDLY, "FF0", FF0, "FF1", FF1, NULL, 0 }; static struct Symbols cmodes[] = { "CS5", CS5, "CS6", CS6, "CS7", CS7, "CS8", CS8, "CSTOPB", CSTOPB, "CREAD", CREAD, "PARENB", PARENB, "PARODD", PARODD, "HUPCL", HUPCL, "CLOCAL", CLOCAL, NULL, 0 }; /* * Speed keywords. Words of the form B[0-9]+ (B9600, etc.) are compared * in the code. We don't list them here because there's approximately * 2^32 possible values. */ static struct Symbols smodes[] = { "EXTA", EXTA, "EXTB", EXTB, NULL, 0 }; static struct Symbols lmodes[] = { "ISIG", ISIG, "ICANON", ICANON, "XCASE", XCASE, "ECHO", ECHO, "ECHOE", ECHOE, "ECHOK", ECHOK, "ECHONL", ECHONL, "NOFLSH", NOFLSH, NULL, 0 }; /* Terminal types set with the LDSETT ioctl command. */ static struct Symbols terminals[] = { "none", TERM_NONE, #ifdef TERM_V10 "vt100", TERM_V10, #endif #ifdef TERM_H45 "hp45", TERM_H45, #endif #ifdef TERM_C10 "c100", TERM_C100, #endif #ifdef TERM_TEX "tektronix", TERM_TEX, "tek", TERM_TEX, #endif #ifdef TERM_D40 "ds40-1", TERM_D40, #endif #ifdef TERM_V61 "vt61", TERM_V61, #endif #ifdef TERM_TEC "tec", TERM_TEC, #endif NULL, 0 }; static struct Symbols linedisc[] = { "LDISC0", LDISC0, "LDISC1", LDISC1, NULL, 0 }; /* If the /etc/gettydefs file can't be opened, the following */ /* default is used. */ static struct Gdef default_gdef = { "default", { ICRNL, 0, CREAD+HUPCL, 0, B9600, B9600, LDISC1}, { ICRNL, OPOST+ONLCR+NLDLY+TAB3, CS7+CREAD+HUPCL, ISIG+ICANON+ECHO+ECHOE+ECHOK, B9600, B9600, LDISC1}, "LOGIN: ", "default", }; #define ISSUE_FILE _PATH_ISSUE #define GETTY_DEFS "/etc/gettydefs" static int check; static char *checkgdfile; /* Name of gettydefs file during check mode. */ static int nologin; /* wait for /etc/nologin to disappear */ #ifdef AUX_LANG static char envlang[NL_LANGMAX]; /* LANG environment variable */ #endif /* AUX_LANG */ int wait_read; /* wait for read before output "login" */ char *chat1_str = 0; /* chat-up the modem before starting */ static char *chat2_str = 0; /* jabber after "RING" until "CONNECT" */ #define CHAT_MAX 50 char *chat_args[CHAT_MAX+1]; static int dead_time = 0; /* delay this many seconds after 1st char */ static char *line_locked = 0; /* locked device */ /* "quoted" takes a quoted character, starting at the quote */ /* character, and returns a single character plus the size of */ /* the quote string. "quoted" recognizes the following as */ /* special, \n,\r,\v,\t,\b,\f as well as the \nnn notation. */ static char quoted(char *ptr, int *qsize) { register char c,*rptr; register int i; rptr = ptr; switch(*++rptr) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 'v': c = '\013'; break; case 'b': c = '\b'; break; case 't': c = '\t'; break; case 'f': c = '\f'; break; default: /* If this is a numeric string, take up to three characters of */ /* it as the value of the quoted character. */ if (*rptr >= '0' && *rptr <= '7') { for (i=0,c=0; i < 3;i++) { c = c*8 + (*rptr - '0'); if (*++rptr < '0' || *rptr > '7') break; } rptr--; /* If the character following the '\\' is a NULL, back up the */ /* ptr so that the NULL won't be missed. The sequence */ /* backslash null is essentually illegal. */ } else if (*rptr == '\0') { c = '\0'; rptr--; /* In all other cases the quoting does nothing. */ } else c = *rptr; break; } /* Compute the size of the quoted character. */ (*qsize) = rptr - ptr + 1; return(c); } static char * getword(register char *ptr, int *size) { register char *optr,c; static char word[MAXIDLENGTH+1]; int qsize; /* Skip over all white spaces including quoted spaces and tabs. */ for (*size=0; isspace(*ptr) || *ptr == '\\';) { if (*ptr == '\\') { c = quoted(ptr,&qsize); (*size) += qsize; ptr += qsize+1; /* If this quoted character is not a space or a tab or a newline */ /* then break. */ if (isspace(c) == 0) break; } else { (*size)++; ptr++; } } /* Put all characters from here to next white space or '#' or '\0' */ /* into the word, up to the size of the word. */ for (optr= word,*optr='\0'; isspace(*ptr) == 0 && *ptr != '\0' && *ptr != '#'; ptr++,(*size)++) { /* If the character is quoted, analyze it. */ if (*ptr == '\\') { c = quoted(ptr,&qsize); (*size) += qsize; ptr += qsize; } else c = *ptr; /* If there is room, add this character to the word. */ if (optr < &word[MAXIDLENGTH+1] ) *optr++ = c; } /* Make sure the line is null terminated. */ *optr++ = '\0'; return(word); } /* "search" scans through a table of Symbols trying to find a */ /* match for the supplied string. If it does, it returns the */ /* pointer to the Symbols structure, otherwise it returns NULL. */ static struct Symbols * search(register char *target, register struct Symbols *symbols) { /* Each symbol array terminates with a null pointer for an */ /* "s_symbol". Scan until a match is found, or the null pointer */ /* is reached. */ for (;symbols->s_symbol != NULL; symbols++) if (strcmp(target,symbols->s_symbol) == 0) return(symbols); return(NULL); } /* "fields" picks up the words in the next field and converts all */ /* recognized words into the proper mask and puts it in the target */ /* field. */ static char * fields(register char *ptr, struct termio *termio) { extern struct Symbols imodes[],omodes[],cmodes[],lmodes[]; register struct Symbols *symbol; char *word; int size; extern int check; termio->c_iflag = 0; termio->c_oflag = 0; termio->c_cflag = 0; termio->c_ospeed = 0; termio->c_lflag = 0; while (*ptr != '#' && *ptr != '\0') { /* Pick up the next word in the sequence. */ word = getword(ptr,&size); /* If there is a word, scan the two mode tables for it. */ if (*word != '\0') { /* If the word is the special word "SANE", put in all the flags */ /* that are needed for SANE tty behavior. */ if (strcmp(word,"SANE") == 0) { termio->c_iflag |= ISANE; termio->c_oflag |= OSANE; termio->c_cflag |= CSANE; termio->c_lflag |= LSANE; } else if ((symbol = search(word,imodes)) != NULL) termio->c_iflag |= symbol->s_value; else if ((symbol = search(word,omodes)) != NULL) termio->c_oflag |= symbol->s_value; else if ((symbol = search(word,cmodes)) != NULL) termio->c_cflag |= symbol->s_value; else if ((symbol = search(word,lmodes)) != NULL) termio->c_lflag |= symbol->s_value; else if ((symbol = search(word,smodes)) != NULL) termio->c_ospeed = symbol->s_value; else if ((word[0] == 'B') && (strspn(word+1,"0123456789") == strlen(word+1))) termio->c_ospeed = (speed_t)strtoul(word+1,0,0); else if (check) fprintf(stdout,"Undefined: %s\n",word); } /* Advance pointer to after the word. */ ptr += size; } /* If we didn't end on a '#', return NULL, otherwise return the */ /* updated pointer. */ return(*ptr != '#' ? NULL : ptr); } /* pause to reflect after SIGHUP */ static void huphand(void) { if (Debug) syslog(LOG_DEBUG, "quiting after SIGHUP"); (void)close(0); (void)close(1); (void)close(2); sginap(5*HZ); exit(1); } static void setupline(register struct Gdef *speedef, int termtype, int lined) { struct termio termio; struct termcb termcb; /* Set the terminal type to "none", which will clear all old */ /* special flags, if a terminal type was set from before. */ termcb.st_flgs = 0; termcb.st_termt = TERM_NONE; termcb.st_vrow = 0; (void)ioctl(0,LDSETT,&termcb); termcb.st_termt = termtype; (void)ioctl(0,LDSETT,&termcb); /* Get the current state of the modes and such for the terminal. */ if (0 > ioctl(0,TCGETA,&termio)) badcall("ioctl(TCGETA%s)%d: %m", "",__LINE__, 0); if (termtype != TERM_NONE) { /* If there is a terminal type, take away settings so that */ /* terminal is "raw" and "no echo". Also take away the orginal */ /* speed setting. */ termio.c_iflag = 0; termio.c_cflag &= ~(CSIZE|PARENB); termio.c_cflag |= CS8|CREAD|HUPCL; termio.c_lflag &= ~(ISIG|ICANON|ECHO|ECHOE|ECHOK); /* Add in the speed. */ termio.c_ospeed = (speedef->g_iflags.c_ospeed); } else { termio.c_iflag = speedef->g_iflags.c_iflag; termio.c_oflag = speedef->g_iflags.c_oflag; #ifdef CNEW_RTSCTS /* preserve CNEW_RTSCTS so flow control will still work * if line is a ttyf port */ termio.c_cflag = (termio.c_cflag & CNEW_RTSCTS) | speedef->g_iflags.c_cflag; #else termio.c_cflag = speedef->g_iflags.c_cflag; #endif termio.c_ospeed = speedef->g_iflags.c_ospeed; termio.c_lflag = speedef->g_iflags.c_lflag; } /* Make sure that raw reads are 1 character at a time with no */ /* timeout. */ termio.c_cc[VMIN] = 1; termio.c_cc[VTIME] = 0; /* Add the line discipline. */ termio.c_line = lined; if (0 > ioctl(0,TCSETAF,&termio)) badcall("ioctl(TCSETAF%s)%d: %m", "",__LINE__, 0); /* get rid of any cruft on both input and output */ sginap(HZ/2); if (0 > ioctl(0,TCFLSH,2)) badcall("ioctl(TCFLSH%s)%d: %m", "",__LINE__, 0); } static void openline(register char *line, register struct Gdef *speedef, int termtype, int lined, int hangup) { #ifdef UUGETTY int i; #endif struct stat statb; register FILE *f; register int lfd; struct termio lterm; cap_t ocap; cap_value_t cap_fowner = CAP_FOWNER; cap_value_t cap_device_mgt = CAP_DEVICE_MGT; /* we want the line to be our controlling tty */ lfd = open("/dev/tty",0); if (lfd >= 0) { (void)ioctl(lfd, TIOCNOTTY, 0); (void)close(lfd); } setpgrp(); /* just in case, for debugging, ... */ (void)signal(SIGHUP,SIG_IGN); /* do not worry about carrier yet */ uugetty_ck(line); /* quit if UUCP is using line */ errno = 0; lfd = open(line,O_RDWR|O_NDELAY); if (lfd > 0) { if (Debug != 0) badcall("failed to open \"%s\" as FD 0: fd=%d", line,lfd, 0); (void)close(lfd); closelog(); /* in case FD 0,1, or 2 */ errno = 0; lfd = open(line,O_RDWR|O_NDELAY); } if (lfd != 0) badcall("cannot open \"%s\" as FD 0: %m, fd=%d", line,lfd, 1); /* Change the ownership of the terminal line to uucp or root and set */ /* the protections to only allow him to read the line. */ stat(line,&statb); ocap = cap_acquire(1, &cap_fowner); chown(line,UUCPUID,statb.st_gid); chmod(line,0622); cap_surrender(ocap); if (hangup) { if (speedef->g_fflags.c_cflag & HUPCL) { if (0 > ioctl(0,TCGETA,<erm)) badcall("ioctl(TCGETA,\"%s\"): %m", line,0, 0); lterm.c_ospeed = B0; lterm.c_lflag &= ~(ECHO|ECHOE|ECHOK); if (0 > ioctl(0,TCSETAF,<erm) && errno != ENXIO) badcall("ioctl(TCSETAF,\"%s\"): %m", line,0, 0); sginap(HZ*2); /* let the modem catch up */ } ocap = cap_acquire(1, &cap_device_mgt); if (0 > vhangup() && Debug != 0) { cap_surrender(ocap); badcall("vhangup(%s): %m", line,0, 1); } cap_surrender(ocap); if (0 > close(0)) badcall("close(\"%s\"): %m", line,0, 0); #ifdef UUGETTY lfd = open(line,O_RDWR|O_NDELAY); #else lfd = open(line,O_RDWR); #endif /* UUGETTY */ } f = fdopen(lfd, "r+"); if (lfd != 0 || f != stdin) badcall("cannot fdopen \"%s\" as FD 0: %m fd=%d", line,lfd,1); if (0 > fcntl(0, F_SETFL, fcntl(0, F_GETFL, -1) & ~O_NDELAY)) { badcall("fcntl(\"%s\"): %m",line,0, 1); } (void)signal(SIGHUP,huphand); /* now quit if line does */ /* do not echo anything until we really have the line and it is * healthy. */ setupline(speedef,termtype,lined); if (0 > ioctl(0,TCGETA,<erm)) badcall("ioctl(TCGETA2,\"%s\"): %m", line,0, 1); lterm.c_lflag &= ~(ECHO|ECHOE|ECHOK); if (0 > ioctl(0,TCSETAF,<erm)) badcall("ioctl(TCSETAF2, \"%s\"): %m", line,0, 1); #ifdef UUGETTY /* chat with the modem if necessary */ if (chat1_str) { if (Debug) syslog(LOG_DEBUG, "1st chat with modem on %s", line); setservice("uugetty"); i = gdial(chat1_str,chat_args,CHAT_MAX); if (0 == i) badcall("unknown Dialer script \"%s\"",chat1_str,0, 1); if (i > 2) { if (0 != chat(i-2, chat_args+2, 0, 0,0)) badcall("modem initialization failed on %s", line,0, 1); /* forget any stray modem cruft */ sginap(HZ); if (0 > ioctl(0,TCFLSH,2)) badcall("ioctl(TCFLSH,\"%s\"): %m", line,0, 1); } } delock(line_locked); line_locked = 0; /* wait until there is some input, and then try to relock the line */ i = 0; for (;;) { fd_set readfds; FD_ZERO(&readfds); FD_SET(0,&readfds); /* quit if the line becomes sick */ if (0 > select(0+1,&readfds,0,0,0)) { if (errno == EAGAIN || errno == EINTR) continue; badcall((i == 0 || Debug) ? "select(\"%s\"): %m" : 0, line,0, 1); } if (nologin && !access(_PATH_NOLOGIN, F_OK)) { (void)close(0); syslog(LOG_DEBUG, "\"%s\" existed upon awakening for %s", _PATH_NOLOGIN, line); do { sginap(HZ); } while (!access(_PATH_NOLOGIN, F_OK)); exit(1); } if (FD_ISSET(0, &readfds)) { uugetty_ck(line); /* lock the line */ break; } } /* restore modes in case UUCP changed them while we waited * flush any modem cruft unless we will chat the modem */ if (0 > ioctl(0, chat2_str ? TCSETA : TCSETAF, <erm)) badcall("ioctl(TCSETAF3, \"%s\"): %m", line,0, 1); if (dead_time != 0) { /* wait for modem cruft */ /* be sure modem cruft is not flow controlled. */ if (0 > ioctl(0,TCFLSH,2)) badcall("ioctl(TCFLSH, \"%s\"): %m", line,0, 1); sginap(dead_time*HZ); if (0 > ioctl(0,TCFLSH,2)) badcall("ioctl(TCFLSH, \"%s\"): %m", line,0, 1); } /* wait to read the first character */ if (wait_read) { char buffer; /* * Check for read failure or EOT. * (EOT may come from a cu on the other side.) * This code is to prevent the situation of * "login" program getting started here while * a uugetty is running on the other end of the line. * NOTE: Cu on a direct line when ~. is encountered will * send EOTs to the other side. EOT=\004 */ i = read(0, &buffer, 1); if (i != 1 || buffer == '\004') { char *msg = 0; if (i < 0) { msg = "-r read: %m"; } else if (Debug) { if (i == 0) msg = "-r read produced %d"; else msg = "-r read receive EOT"; } badcall(msg, 0,0, 1); } } /* look for CONNECT. If we dont get it, then quit quietly. */ if (chat2_str) { if (Debug) syslog(LOG_DEBUG, "2nd chat with modem on %s", line); setservice("uugetty"); i = gdial(chat2_str,chat_args,CHAT_MAX); if (0 == i) badcall("unknown Dialer script \"%s\"",chat2_str,0, 1); if (i > 2) { if (0 != chat(i-2, chat_args+2, 0, 0,0)) { if (Debug != 0) badcall("2nd chat failed on \"%s\"", line,0, 0); exit(1); } } /* forget any stray modem cruft */ sginap(HZ/2); if (0 > ioctl(0,TCFLSH,2)) badcall("ioctl(TCFLSH, \"%s\"): %m", line,0, 1); } #endif /* UUGETTY */ /* check for /etc/nologin in case it was created while we were * waiting for a carrier. */ if (nologin && !access(_PATH_NOLOGIN, F_OK)) { (void)close(0); syslog(LOG_DEBUG, "\"%s\" existed upon awakening for %s", _PATH_NOLOGIN, line); do { sginap(HZ); } while (!access(_PATH_NOLOGIN, F_OK)); exit(1); } ocap = cap_acquire(1, &cap_fowner); chown(line,0,statb.st_gid); /* we now own the line */ chmod(line,0622); cap_surrender(ocap); closelog(); /* in case FD 0,1, or 2 */ fclose(stdout); fclose(stderr); if (0 > dup2(0,1) || 0 == fdopen(1, "r+")) badcall("failed to open \"%s\" as STDOUT: %m", line,0, 1); if (0 > dup2(0,2) || 0 == fdopen(2, "r+")) badcall("failed to open \"%s\" as STDERR: %m", line,0, 1); setbuf(stdin,NULL); setbuf(stdout,NULL); setbuf(stderr,NULL); /* Set the terminal type and line discipline, after we know we own it. */ setupline(speedef,termtype,lined); } static void account(char *line) { register int ownpid; register int ppid; register struct utmp *u; register int fd; /* Look in "utmp" file for our own entry and change it to LOGIN. */ ownpid = getpid(); ppid = getppid(); while ((u = getutent()) != NULL) { /* Is this our own entry? */ if (u->ut_type == INIT_PROCESS && (u->ut_pid == ownpid || u->ut_pid == ppid)) { strncpy(u->ut_line,line,sizeof(u->ut_line)); strncpy(u->ut_user,"LOGIN",sizeof(u->ut_user)); u->ut_type = LOGIN_PROCESS; u->ut_pid = ownpid; /* Write out the updated entry. */ pututline(u); break; } } /* If we were successful in finding an entry for ourself in the */ /* utmp file, then attempt to append to the end of the wtmp file. */ if (u != NULL && (fd = open(WTMP_FILE, O_WRONLY|O_APPEND)) >= 0) { cap_value_t cap_dac_write = CAP_DAC_WRITE; cap_t ocap = cap_acquire(1, &cap_dac_write); write(fd, (char *)u, sizeof(*u)); cap_surrender(ocap); close(fd); } /* Close the utmp file. */ endutent(); } /* * Look for /etc/autologin.TTYLINE and use its contents as the login name if * it exists. */ static int check_autologin(char *line, char *name) { char *autopath; register FILE *autol; int len; len = sizeof ("/etc/autologin") + strlen (line)+1 + sizeof (".on"); if ((autopath = malloc (len)) == NULL) { return (NONAME); } if (sprintf (autopath, "%s.%s.on", "/etc/autologin", line) < 0) { free (autopath); return (NONAME); } if (access (autopath, 0) == 0) { unlink (autopath); autopath[strlen(autopath)-3] = NULL; if ((autol = fopen(autopath, "r")) != NULL) { if (fscanf (autol, "%s", name) == EOF) { free (autopath); return (NONAME); } putc('\n',stdout); free (autopath); return(GOODNAME); } } free (autopath); return (NONAME); } /* Pick up the user's name from the standard input or * /etc/autologin.TTYLINE if it exists. * If it sees a line terminated with a , it sets ICRNL. */ static int getname(char *user, char *line, struct termio *termio) { register char *ptr,c; register int rawc; /* Get the previous modes, erase, and kill characters and speeds. */ if (0 > ioctl(0,TCGETA,termio)) badcall("ioctl(TCGETA%s)%d: %m", "",__LINE__, 0); termio->c_iflag &= ICRNL; termio->c_oflag = 0; #ifdef CNEW_RTSCTS termio->c_cflag &= CNEW_RTSCTS; #else termio->c_cflag = 0; #endif termio->c_lflag &= ECHO; termio->c_cc[VEOF] = CEOF; /* fix damage from RAW mode, but */ termio->c_cc[VEOL] = 0; /* assume no other changes to c_cc[] */ if (check_autologin (line, user) == GOODNAME) { return (GOODNAME); } ptr = user; do { /* If it isn't possible to read line, exit. */ if ((rawc = getc(stdin)) == EOF) huphand(); /* If a null character was typed, try another speed. */ if ((c = (rawc & 0177)) == '\0') return(BADSPEED); if (c == '\b' || c == '#') { /* If there is anything to erase, erase a character. */ if (ptr > user) { --ptr; if (*ptr >= ' ' && *ptr < 127) printf("\b \b"); } /* If the character is a kill line, start the line over */ } else if (c == '@' || c == CDEL || c == CKILL || c == CTRL('X')) { while (ptr > user) { ptr--; if (*ptr >= ' ' && *ptr < 127) printf("\b \b"); } } else { if ((termio->c_lflag&ECHO) == 0) putc(rawc,stdout); *ptr++ = c; } /* Continue the above loop until a line terminator is found or */ /* until user name array is full. */ } while (c != '\n' && c != '\r' && ptr < (user + MAXLINE)); /* Remove the last character from name. */ *--ptr = '\0'; if (ptr == user) return(NONAME); /* If the line terminated with a , put ICRNL and ONLCR into */ /* into the modes. */ if (c == '\r') { putc('\n',stdout); termio->c_iflag |= ICRNL; termio->c_oflag |= ONLCR; /* When line ends with a , then add the . */ } else putc('\r',stdout); return(GOODNAME); } main(argc,argv) int argc; char **argv; { int i; char *errmsg; char *line; #ifdef AUX_LANG char *lang = 0; #endif /* AUX_LANG */ register struct Gdef *speedef; char oldspeed[MAXIDLENGTH+1],newspeed[MAXIDLENGTH+1]; int termtype,lined; int hangup,timeout; extern struct Symbols terminals[],linedisc[]; extern void timedout(); register struct Symbols *answer; char user[MAXLINE],*largs[MAXARGS],*ptr,buffer[MAXLINE]; FILE *fp; struct termio termio; #define ESC 27 static char clrscreen[] = {ESC,'H',ESC,'J'}; signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); /* no reason to drop a core */ #ifdef UUGETTY openlog("uugetty", LOG_PID|LOG_CONS|LOG_ODELAY, LOG_AUTH); #else openlog("getty", LOG_PID|LOG_CONS|LOG_ODELAY, LOG_AUTH); #endif hangup = TRUE; timeout = 0; errmsg = USAGE; opterr = 0; while ((i = getopt(argc, argv, "hDNri:d:t:c:L:")) != -1) { switch (i) { case 'h': hangup = FALSE; break; /* Turn on some debugging */ case 'D': Debug = 9; break; /* wait for /etc/nologin to go away */ case 'N': nologin =1; break; /* wait to receive something from the line before proceeding */ case 'r': wait_read = TRUE; break; /* remember to chat with the modem before starting */ case 'i': chat1_str = optarg; chat2_str = strchr(optarg,','); if (0 != chat2_str) *chat2_str++ = '\0'; break; /* delay after initial character */ case 'd': dead_time = strtol(optarg,&ptr,0); if (dead_time <= 0 || dead_time > 100 || *ptr != '\0') { syslog(LOG_ERR,"invalid delay \"%s\"",optarg); errmsg = 0;; } break; case 't': timeout = strtol(optarg,&ptr,0); if (timeout <= 0 || timeout > 100 || *ptr != '\0') { syslog(LOG_ERR,"invalid timeout \"%s\"",optarg); errmsg = 0;; } break; /* Check a "gettydefs" file mode. */ case 'c': check = TRUE; checkgdfile = optarg; signal(SIGINT,SIG_DFL); fp = fopen(checkgdfile,"r"); if ((fp = fopen(checkgdfile,"r")) == NULL) { fprintf(stderr,"Cannot open %s\n",checkgdfile); exit(1); } fclose(fp); /* Call "find_def" to check the check file. * With the "check" flag set, it will parse the * entire file, printing out the results. */ find_def(NULL); exit(0); #ifdef AUX_LANG case 'L' : lang = optarg; break; #endif /* AUX_LANG */ default: syslog(LOG_ERR,errmsg); errmsg = 0; break; } } /* There must be at least one operand. If there isn't, complain. */ argc -= optind; argv += optind; if (argc <= 0 || **argv == '-' || errmsg == 0) badcall(errmsg,0,0, 1); line = *argv; /* point the UUCP chat code at the system log */ (void)fclose(stdin); (void)close(0); (void)fflush(stdout); (void)close(1); (void)fflush(stderr); (void)close(2); #if !defined(UUGETTY) && defined(AUX_LANG) if(lang) { (void)strcpy(envlang, "LANG="); (void)strcat(envlang, lang); (void)putenv(envlang); } #endif /* !defined(UUGETTY) && defined(AUX_LANG) */ #ifdef UUGETTY if (Debug) { static char pidstr0[] = "uugetty[%d]"; static char pidstr[sizeof(pidstr0)+8]; int fildes[2]; pid_t pid; int serror; char *es; if (0 > pipe(fildes)) { pid = -1; es = "pipe() failed for log"; } else { pid = fork(); es = "fork() failed for log"; } if (pid == 0) { sprintf(pidstr,pidstr0,getppid()); (void)alarm(0); (void)signal(SIGHUP,SIG_IGN); (void)signal(SIGINT,SIG_IGN); (void)signal(SIGUSR1,SIG_IGN); (void)signal(SIGUSR2,SIG_IGN); (void)signal(SIGTERM,SIG_IGN); if (0 <= dup2(fildes[0],0)) { for (i = getdtablehi(); --i > 0; ) (void)close(i); (void)execlp("logger", "logger", "-t", pidstr, "-p", "daemon.debug", 0); } syslog(LOG_ERR, "failed to start logger: %m"); exit(-1); } else { (void)close(fildes[0]); es = "unable to dup2() log"; if (0 > dup2(fildes[1],1) || 0 > dup2(fildes[1],2)) pid = -1; if (fildes[1] != 1 && fildes[1] != 2) (void)close(fildes[1]); } if (pid < 0) { serror = errno; (void)freopen("/dev/log", "a", stderr); errno = serror; badcall(es, 0,0, 1); } setlinebuf(stdout); setlinebuf(stderr); } #endif /* UUGETTY */ /* If a "speed" was provided, search for it in the */ /* "getty_defs" file. If none was provided, take the first entry */ /* of the "getty_defs" file as the initial settings. */ if (--argc > 0 ) { if ((speedef = find_def(*++argv)) == NULL) { syslog(LOG_ERR,"unable to find \"%s\" in %s for %s", *argv,GETTY_DEFS,line); /* Use the default value instead. */ speedef = find_def(NULL); } } else speedef = find_def(NULL); /* If a terminal type was supplied, try to find it in list. */ termtype = TERM_NONE; if (--argc > 0) { answer = search(*++argv,terminals); if (0 != answer) termtype = answer->s_value; else syslog(LOG_ERR,"%s is an undefined terminal type.", *argv); } /* If a line discipline was supplied, try to find it in list. */ lined = LDISC1; if (--argc > 0) { answer = search(*++argv,linedisc); if (answer != 0) lined = answer->s_value; else syslog(LOG_ERR,"%s is an undefined line discipline.", *argv); } chdir("/dev"); account(line); /* Perform "utmp" accounting. */ /* wait until the system is on */ if (nologin && !access(_PATH_NOLOGIN, F_OK)) { syslog(LOG_DEBUG, "\"%s\" existed when starting %s", _PATH_NOLOGIN, line); do { sginap(HZ); } while (!access(_PATH_NOLOGIN, F_OK)); exit(0); /* quit since syslog() stole FD 0 */ } /* Attempt to open standard input, output, and error on specified line. */ openline(line,speedef,termtype,lined,hangup); /* Loop until user is successful in requesting login. */ for (;;) { /* If there is no terminal type, just advance a line. */ if (termtype == TERM_NONE) { /* A bug in the stdio package requires that the first output on */ /* the newly reopened stderr stream be a putc rather than an */ /* fprintf. */ putc('\r',stderr); putc('\n',stderr); /* If there is a terminal type, clear the screen with the common */ /* crt language. Note that the characters have to be written in */ /* one write, and hence can't go through standard io, which is */ /* currently unbuffered. */ } else write(fileno(stderr),clrscreen,sizeof(clrscreen)); /* If getty is supposed to die if no one logs in after a */ /* predetermined amount of time, set the timer. */ if(timeout) { signal(SIGALRM,timedout); alarm(timeout); } /* Print out the issue file. */ if ((fp = fopen(ISSUE_FILE,"r")) != NULL) { while (fgets(buffer,sizeof(buffer),fp) != NULL) { fputs(buffer,stderr); putc('\r',stderr); } fclose(fp); } /* Print the login message. */ fprintf(stderr,"%s",speedef->g_message); /* Get the user's typed response and respond appropriately. */ switch(getname(user,line,&termio)) { case GOODNAME: if (timeout) alarm(0); /* If a terminal type was specified, keep only those parts of */ /* the gettydef final settings which were not explicitely turned */ /* on when the terminal type was set. */ if (termtype != TERM_NONE) { termio.c_iflag |= (ISTRIP|ICRNL|IXON|IXANY) | (speedef->g_fflags.c_iflag & ~(ISTRIP|ICRNL|IXON|IXANY)); termio.c_oflag |= (OPOST|ONLCR) | (speedef->g_fflags.c_oflag & ~(OPOST|ONLCR)); #ifdef CNEW_RTSCTS /* preserve CNEW_RTSCTS so flow control * will still work if line is a ttyf port */ termio.c_cflag = (termio.c_cflag & CNEW_RTSCTS) | speedef->g_fflags.c_cflag; #else termio.c_cflag = speedef->g_fflags.c_cflag; #endif termio.c_lflag = (ISIG|ICANON|ECHO|ECHOE|ECHOK) | (speedef->g_fflags.c_lflag & ~(ISIG|ICANON|ECHO|ECHOE|ECHOK)); } else { termio.c_iflag |= speedef->g_fflags.c_iflag; termio.c_oflag |= speedef->g_fflags.c_oflag; termio.c_cflag |= speedef->g_fflags.c_cflag; termio.c_lflag |= speedef->g_fflags.c_lflag; } termio.c_ospeed = speedef->g_fflags.c_ospeed; termio.c_line = lined; if (0 > ioctl(0,TCSETAW,&termio)) badcall("ioctl(TCSETAW,\"%s\")%d: %m", line,__LINE__, 0); /* Parse the input line from the user, breaking it at white */ /* spaces. */ largs[0] = "login"; parse(user,&largs[1],MAXARGS-1); /* silently destroy attempts to pass tricky args to login. */ if (largs[1] != 0 && largs[1][0] == '-') largs[1] = 0; closelog(); /* avoid propagating stray FDs */ execv(_PATH_SCHEME,largs); syslog(LOG_ERR, "failed to exec %s: %m", _PATH_SCHEME); closelog(); execv("/bin/login",largs); syslog(LOG_ERR, "failed to exec /bin/login: %m"); closelog(); execv("/etc/login",largs); syslog(LOG_ERR, "failed to exec /etc/login: %m"); exit(1); /* If the speed supplied was bad, try the next speed in the list. */ case BADSPEED: /* Save the name of the old speed definition incase new one is */ /* bad. Copy the new speed out of the static so that "find_def" */ /* won't overwrite it in the process of looking for new entry. */ strcpy(oldspeed,speedef->g_id); strcpy(newspeed,speedef->g_nextid); if ((speedef = find_def(newspeed)) == NULL) { syslog(LOG_ERR,"pointer to next speed in entry %s is bad.", oldspeed); /* In case of error, go back to the original entry. */ if ((speedef = find_def(oldspeed)) == NULL) { /* If the old entry has disappeared, then quit and let next "getty" try. */ badcall("unable to find %s again", oldspeed,0, 1); } } /* Setup the terminal for the new information. */ setupline(speedef,termtype,lined); break; /* If no name was supplied, not nothing, but try again. */ case NONAME: break; } } } /* complain about the line and quit */ static void badcall(char *fmt, char* sarg, int iarg, int quit) { int i; if (quit) signal(SIGINT,SIG_DFL); if (0 != fmt) syslog(LOG_ERR,fmt,sarg,iarg); if (quit) { if (line_locked) { delock(line_locked); line_locked = 0; } for (i = getdtablehi(); --i > 0; ) (void)close(i); sginap(30*HZ); exit(1); } } #ifdef UUGETTY /* share a line with uucico/cu/ct * wait for open, * When success, check to see if LCK..line exists * If it does, that means that uucico/cu/ct is using the line, * so wait and exit when the LCK..line file goes away */ /* dummies for using uucp .o routines * This seems like a royal kludge, but it is what the original uugetty * did. */ void cleanup(int code) { if (Debug) syslog(LOG_DEBUG, "exiting with %d", code); exit(code); } /* dummies for uucp routines */ void assert(s1, s2, i1, file, line) char *s1; char *s2; int i1; char *file; int line; { syslog(LOG_ERR, "%s %s (%d) [FILE: %s, LINE: %d]", s1, s2, i1, file, line); } void logent(text, status) char *text; char *status; { if (!Debug) return; syslog(LOG_DEBUG, "%s (%s)", status, text); } jmp_buf Sjbuf; /* see if the line is available for uugetty * Do not return if it is not */ static void uugetty_ck(line) char *line; { char lckname[MAXNAMLEN]; /* lock file name LCK..line */ if (mlock(line)) { /* There is a lock file already */ (void)signal(SIGHUP,SIG_IGN); /* stop worrying about carrier */ if (Debug) syslog(LOG_DEBUG, "\"%s\" is busy", line); (void)close(0); /* release the device */ (void)sprintf(lckname, "%s.%s", LOCKPRE, line); for (;;) { /* wait for LCK..line to go away */ if (checkLock(lckname) == 0) break; sginap(5*HZ); } sginap(1*HZ); if (Debug) syslog(LOG_DEBUG, "finished waiting for lock to go away"); exit(0); } line_locked = line; } #endif /* UUGETTY */ void timedout() { if (Debug) syslog(LOG_DEBUG, "after timeout"); exit(1); } /* "find_def" scans "/etc/gettydefs" for a string with the */ /* requested "id". If the "id" is NULL, then the first entry is */ /* taken, hence the first entry must be the default entry. */ /* If a match for the "id" is found, then the line is parsed and */ /* the Gdef structure filled. Errors in parsing generate error */ /* messages on the system console. */ static struct Gdef * find_def(char *id) { register struct Gdef *gptr; register char *ptr,c; FILE *fp; int i,input,state,size,rawc,field; char oldc,*optr,*gdfile; char line[MAXLINE+1]; static struct Gdef def; static char d_id[MAXIDLENGTH+1],d_nextid[MAXIDLENGTH+1]; static char d_message[MAXMESSAGE+1]; extern int check; static char *states[] = { "","id","initial flags","final flags","message","next id" }; /* Decide whether to read the real /etc/gettydefs or the supplied */ /* check file. */ if (check) gdfile = checkgdfile; else gdfile = GETTY_DEFS; /* Open the "/etc/gettydefs" file. Be persistent. */ for (i=0; i < 3; i++) { if ((fp = fopen(gdfile,"r")) != NULL) break; sginap(3*HZ); /* Wait a little and then try again. */ } /* If unable to open, complain and then use the built in default. */ if (fp == NULL) { if (check) { fprintf(stderr, "can't open \"%s\"\n", gdfile); } else { syslog(LOG_ERR, "can't open \"%s\"", gdfile); } return(&default_gdef); } /* Start searching for the line with the proper "id". */ input = ACTIVE; do { for(ptr= line,oldc='\0'; ptr < &line[sizeof(line)] && (rawc = getc(fp)) != EOF; ptr++,oldc = c) { c = *ptr = rawc; /* Search for two \n's in a row. */ if (c == '\n' && oldc == '\n') break; } /* If we didn't end with a '\n' or EOF, then the line is too long. */ /* Skip over the remainder of the stuff in the line so that we */ /* start correctly on next line. */ if (rawc != EOF && c != '\n') { for (oldc='\0'; (rawc = getc(fp)) != EOF;oldc=c) { c = rawc; if (c == '\n' && oldc != '\n') break; } if (check) fprintf(stdout,"Entry too long.\n"); } /* If we ended at the end of the file, then if there is no */ /* input, break out immediately otherwise set the "input" */ /* flag to FINISHED so that the "do" loop will terminate. */ if (rawc == EOF) { if (ptr == line) break; else input = FINISHED; } /* If the last character stored was an EOF or '\n', replace it */ /* with a '\0'. */ if (*ptr == (EOF & 0377) || *ptr == '\n') *ptr = '\0'; /* If the buffer is full, then make sure there is a null after the */ /* last character stored. */ else *++ptr == '\0'; if (check) fprintf(stdout,"\n**** Next Entry ****\n%s\n",line); /* If line starts with #, treat as comment */ if(line[0] == '#') continue; /* Initialize "def" and "gptr". */ gptr = &def; gptr->g_id = (char*)NULL; gptr->g_iflags.c_iflag = 0; gptr->g_iflags.c_oflag = 0; gptr->g_iflags.c_cflag = 0; gptr->g_iflags.c_ospeed = 0; gptr->g_iflags.c_lflag = 0; gptr->g_fflags.c_iflag = 0; gptr->g_fflags.c_oflag = 0; gptr->g_fflags.c_cflag = 0; gptr->g_fflags.c_lflag = 0; gptr->g_fflags.c_ospeed = 0; gptr->g_message = (char*)NULL; gptr->g_nextid = (char*)NULL; /* Now that we have the complete line, scan if for the various */ /* fields. Advance to new field at each unquoted '#'. */ for (state=ID,ptr= line; state != FAILURE && state != SUCCESS;) { switch(state) { case ID: /* Find word in ID field and move it to "d_id" array. */ strncpy(d_id,getword(ptr,&size),MAXIDLENGTH); gptr->g_id = d_id; /* Move to the next field. If there is anything but white space */ /* following the id up until the '#', then set state to FAILURE. */ ptr += size; while (isspace(*ptr)) ptr++; if (*ptr != '#') { field = state; state = FAILURE; } else { ptr++; /* Skip the '#' */ state = IFLAGS; } break; /* Extract the "g_iflags" */ case IFLAGS: if ((ptr = fields(ptr,&gptr->g_iflags)) == NULL) { field = state; state = FAILURE; } else { gptr->g_iflags.c_iflag &= (ICRNL|IUCLC); if((gptr->g_iflags.c_cflag & CSIZE) == 0) gptr->g_iflags.c_cflag |= CS8; gptr->g_iflags.c_cflag |= CREAD|HUPCL; gptr->g_iflags.c_lflag &= ~(ISIG|ICANON |XCASE|ECHOE|ECHOK); ptr++; state = FFLAGS; } break; /* Extract the "g_fflags". */ case FFLAGS: if ((ptr = fields(ptr,&gptr->g_fflags)) == NULL) { field = state; state = FAILURE; } else { /* Force the CREAD mode in regardless of what the user specified. */ gptr->g_fflags.c_cflag |= CREAD; ptr++; state = MESSAGE; } break; /* Take the entire next field as the "login" message. */ /* Follow usual quoting procedures for control characters. */ case MESSAGE: for (optr= d_message; (c = *ptr) != '\0' && c != '#';ptr++) { /* If the next character is a backslash, then get the quoted */ /* character as one item. */ if (c == '\\') { c = quoted(ptr,&size); /* -1 accounts for ++ that takes place later. */ ptr += size - 1; } /* If the next character is a dollar sign, get the variable name */ /* following it and interpolate its value into d_message */ if (c == '$') { char *value; register int length; value = getvar(ptr, &size); ptr += size; length = strlen(value); if (optr + length < &d_message[MAXMESSAGE]) { bcopy(value, optr, length); optr += length; } } else /* If there is room, store the next character in d_message. */ if (optr < &d_message[MAXMESSAGE]) { *optr++ = c; } } /* If we ended on a '#', then all is okay. Move state to NEXTID. */ /* If we didn't, then set state to FAILURE. */ if (c == '#') { gptr->g_message = d_message; state = NEXTID; /* Make sure message is null terminated. */ *optr++ = '\0'; ptr++; } else { field = state; state = FAILURE; } break; /* Finally get the "g_nextid" field. If this is successful, then */ /* the line parsed okay. */ case NEXTID: /* Find the first word in the field and save it as the next id. */ strncpy(d_nextid,getword(ptr,&size),MAXIDLENGTH); gptr->g_nextid = d_nextid; /* There should be nothing else on the line. Starting after the */ /* word found, scan to end of line. If anything beside white */ /* space, set state to FAILURE. */ ptr += size; while (isspace(*ptr)) ptr++; if (*ptr != '\0') { field = state; state = FAILURE; } else state = SUCCESS; break; } } /* If a line was successfully picked up and parsed, compare the */ /* "g_id" field with the "id" we are looking for. */ if (state == SUCCESS) { /* If there is an "id", compare them. */ if (id != NULL) { if (strcmp(id,gptr->g_id) == 0) { fclose(fp); return(gptr); } /* If there is no "id", then return this first successfully */ /* parsed line outright. */ } else if (check == FALSE) { fclose(fp); return(gptr); /* In check mode print out the results of the parsing. */ } else { fprintf(stdout,"id: %s\n",gptr->g_id); fprintf(stdout,"initial flags:\niflag- %o oflag- %o cflag- %o lflag- %o ospeed- %d\n", gptr->g_iflags.c_iflag, gptr->g_iflags.c_oflag, gptr->g_iflags.c_cflag, gptr->g_iflags.c_lflag, gptr->g_iflags.c_ospeed); fprintf(stdout,"final flags:\niflag- %o oflag- %o cflag- %o lflag- %o ospeed- %d\n", gptr->g_fflags.c_iflag, gptr->g_fflags.c_oflag, gptr->g_fflags.c_cflag, gptr->g_fflags.c_lflag, gptr->g_iflags.c_ospeed); fprintf(stdout,"message: %s\n",gptr->g_message); fprintf(stdout,"next id: %s\n",gptr->g_nextid); } /* If parsing failed in check mode, complain, otherwise ignore */ /* the bad line. */ } else if (check) { *++ptr = '\0'; fprintf(stdout,"Parsing failure in the \"%s\" field\n\ %s<--error detected here\n", states[field],line); } } while (input == ACTIVE); /* If no match was found, then return NULL. */ fclose(fp); return(NULL); } /* * Get the variable name which follows the '$' pointed at by cp. * Return a pointer to its value directly. Return the variable name * length indirectly through sizep. */ static char * getvar(char *cp, int *sizep) { register char *vp; static char hostname[MAXHOSTNAMELEN]; vp = getword(cp + 1, sizep); if (strcmp(vp, "HOSTNAME") == 0) { if (hostname[0] == '\0') { gethostname(hostname, MAXHOSTNAMELEN); } return hostname; } else { if (check) fprintf(stderr, "getty: undefined variable %.*s\n", *sizep, cp + 1); else syslog(LOG_ERR,"undefined variable %.*s", *sizep, cp + 1); return "?"; } } /* "parse" breaks up the user's response into seperate arguments */ /* and fills the supplied array with those arguments. Quoting */ /* with the backspace is allowed. */ static void parse(char *string, char **args, int cnt) { register char *ptrin,*ptrout; register int i; int qsize; for (i=0; i < cnt; i++) args[i] = (char *)NULL; for (ptrin = ptrout = string,i=0; *ptrin != '\0' && i < cnt; i++) { /* Skip excess white spaces between arguments. */ while(*ptrin == ' ' || *ptrin == '\t') { ptrin++; ptrout++; } /* Save the address of the argument if there is something there. */ if (*ptrin == '\0') break; else args[i] = ptrout; /* Span the argument itself. The '\' character causes quoting */ /* of the next character to take place (except for '\0'). */ while (*ptrin != '\0') { /* Is this the quote character? */ if (*ptrin == '\\') { *ptrout++ = quoted(ptrin,&qsize); ptrin += qsize; /* Is this the end of the argument? If so quit loop. */ } else if (*ptrin == ' ' || *ptrin == '\t') { ptrin++; break; /* If this is a normal letter of the argument, save it, advancing */ /* the pointers at the same time. */ } else *ptrout++ = *ptrin++; } /* Null terminate the string. */ *ptrout++ = '\0'; } }