/* point-to-point daemon utility code */ #ident "$Revision: 1.48 $" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pputil.h" #if !defined(PPP) && !defined(SLIP) #error "must be compiled for either SLIP or PPP" #endif #ifdef PPP extern void proxy_unarp(void); #endif #if defined(PPP_IRIX_62) || defined(PPP_IRIX_53) extern void setservice(char*); extern void stlock(char*); extern int conn_trycalls; extern char lock_pid[SIZEOFPID+2]; /* account for \n and \0 */ extern int Nlocks; extern char *AbortStr[]; extern int AbortSeen, Aborts; extern int conn(char*); #endif static void stray_ctty(void); static pid_t kludged; #define USEC_PER_SEC 1000000 /* Update the nondecreasing clock that is not affected by time changes, * as well as the system time-of-day clock. Use a value that is the * number of microseconds since the system was started. * Using only times() produces a clock that is too coarse, so combine * the kernel's time-of-day clock with times()'s number of ticks since * the system started. */ time_t /* return seconds */ get_clk(void) { # define FLOAT_TIME(t) (t.tv_sec*1.0 + t.tv_usec*1.0/USEC_PER_SEC) static double sstart_d, prev_clk_d; double d, cur_date_d; clock_t ticks; /* HZ ticks since system started. */ struct tms tms; ticks = times(&tms); gettimeofday(&cur_date); /* Notice changes in the apparent time-of-day when the system was * started, and infer an adjustment in the system clock. * Use that to keep the daemon's nondecreasing clock straight. */ cur_date_d = FLOAT_TIME(cur_date); d = cur_date_d - ticks*1.0/HZ; /* From the start of a tick until its end, the appearant system * start time will appear to increase. At the start of the next * tick, it will decrease by 1.0/HZ seconds. Consecutive * nondecreasing clock values could jump forward or backwards * by 1.0/HZ seconds as a result if the computed system start * time were accept uncritically. */ if (fabs(d-sstart_d) > 1.0/HZ) { if (d < sstart_d) { /* do not let the clock jump backwards */ sstart_d = MIN(cur_date_d-prev_clk_d+1.0/USEC_PER_SEC, d+1.0/HZ); } else { sstart_d = d-1.0/HZ; } } prev_clk_d = cur_date_d-sstart_d; clk.tv_sec = (long)floor(prev_clk_d); clk.tv_usec = (long)((prev_clk_d-clk.tv_sec)*USEC_PER_SEC); return clk.tv_sec; } void timevalsub(struct timeval *t1, struct timeval *t2, struct timeval *t3) { register time_t sec, usec; sec = t2->tv_sec - t3->tv_sec; usec = t2->tv_usec - t3->tv_usec; while (usec < 0) { sec--; usec += USEC_PER_SEC; } while (usec >= USEC_PER_SEC) { sec++; usec -= USEC_PER_SEC; } t1->tv_sec = sec; t1->tv_usec = usec; } /* Arrange to make stderr point to something useful for the UUCP * code, and otherwise start the error logging. */ void kludge_stderr(void) { static char pidstr[16+1+16+1+1]; int fildes[2]; pid_t pid; int i; int serror; char *es; pid = getpid(); if (stderrfd < 0 || (kludged > 0 && kludged != pid)) { kludged = pid; (void)sprintf(pidstr,"%0.16s[%d]",pgmname,kludged); stderrfd = -1; if (0 > pipe(fildes)) { stderrpid = -1; es = "pipe() failed for log"; } else { stderrpid = fork(); es = "fork() failed for log"; } if (stderrpid == 0) { /* in the child */ no_signals(SIG_IGN); if (0 <= dup2(fildes[0],0)) { /* no controlly tty */ ctty = 0; stray_ctty(); /* stderr is open, ensure it is file ID 2 */ if (stderrttyfd >= 0 && stderrttyfd != 2 && 0 <= dup2(stderrttyfd,2)) stderrttyfd = 2; for (i = getdtablehi(); --i > 0; ) { if (i != stderrttyfd) (void)close(i); } (void)execlp("logger", "logger", (stderrttyfd == 2) ? "-st" : "-t", pidstr, "-p", "daemon.err", 0); } syslog(LOG_ERR, "failed to start logger: %m"); exit(-1); } else if (stderrpid > 0) { if (stderrttyfd == 2) { stderrttyfd = dup(stderrttyfd); (void)close(2); } (void)close(fildes[0]); if (0 > dup2(fildes[1], 2)) { es = "dup2()"; stderrpid = -1; } else { (void)dup2(2,1); stderrfd = 2; } if (fildes[1] != 2) (void)close(fildes[1]); } if (stderrpid < 0) { /* fall back to the kludge if the fork fails */ serror = errno; if (!freopen("/dev/log", "a", stderr)) return; /* huh? */ stderrfd = fileno(stderr); errno = serror; log_errno(es,""); } setlinebuf(stderr); } } void call_system(char *link_name, char *cmd_name, char *cmd_base, int note_exit) { #define CMD_NORM "1>/dev/null " #define CMD_VERBOSE "set -x; 1>&2 " int i; char *cmd; if (debug != 0) { cmd = malloc(strlen(cmd_base) + sizeof(CMD_VERBOSE)); strcpy(cmd, CMD_VERBOSE); } else { cmd = malloc(strlen(cmd_base) + sizeof(CMD_NORM)); strcpy(cmd, CMD_NORM); } strcat(cmd, cmd_base); kludge_stderr(); i = system(cmd); if (0 > i) { log_errno(cmd_name, cmd_base); } else if (note_exit && (i = WEXITSTATUS(i)) != 0) { log_complain(link_name,"%s`%s` exit=%d", cmd_name, cmd_base, i); } free(cmd); #undef CMD_STDIO #undef CMD_VERBOSE } void init_rand(void) { extern unsigned sysid(unsigned char id[16]); srandom(sysid(0) ^ time(0) ^ gethostid()); srand48(random()); srand(random()); } /* lock the device after answering the phone */ void grab_dev(const char *hello_msg, struct dev *dp) { FILE *lf; pid_t pid; char *p; int i; dp->devfd = 0; if (!set_tty_modes(dp)) /* set async tty modes */ cleanup(1); p = ttyname(dp->devfd); if (!p) { log_complain("","unable to discover device name (fd=%d)", dp->devfd); cleanup(1); } i = strlen(p); if (i > sizeof(dp->nodename)-sizeof("/dev/") || i < sizeof("/dev/")) { log_complain("","bogus device name \"%s\" (fd=%d)", p,dp->devfd); cleanup(1); } (void)strcpy(dp->nodename,p+sizeof("/dev/")-1); (void)strcpy(dp->line,p); if (!lockname(dp->nodename,dp->lockfile, sizeof(dp->lockfile))) { log_complain("","unable to compute lock file name for %s", dp->nodename); } if (mmlock(dp->nodename)) { /* Try to lock the tty */ lf = fopen(dp->lockfile,"r+"); if (!lf) { log_complain("","unable to open lockfile for %s", dp->line); cleanup(1); } if (1 != fscanf(lf, "%ld", &pid)) { log_complain("","unable to parse lockfile for %s", dp->line); cleanup(1); } if (pid != getpid()) { log_debug(1, "","strange PID %d in lockfile for %s" " (my PPID=%d);" " incoming/outgoing call collision?", pid, dp->line, getppid()); /* Since things are confused, let the other guy know * and then quit. */ (void)kill(pid, SIGINT); cleanup(1); } (void)fclose(lf); stlock(dp->lockfile); } if (dp->sync == SYNC_OFF) { if (0 > write(dp->devfd, hello_msg, strlen(hello_msg))) log_errno("write(hello)",""); if (0 > tcdrain(dp->devfd) && errno != EINTR) log_errno("hello tcdrain",""); (void)sginap(HZ/5); } } /* Relay between a socket and a pty for TCP testing. * This wants a uucp/Devices entry like: * PPPTCP - - Any TCP login * and a uucp/Systems entry like: * tcpgar Never PPPTCP,t Any garnet "" \0lnam\0rnam\0ppp/38400\0\c * assword~2 tcpppp PPP~2 * PPP needs * map_char_num=0xff */ static void pty_relay(struct dev *dp, int soc, /* socket being connected to pty */ int mpty) /* master side of pty */ { int i, j; char buf[2048]; fd_set rfds; /* kludge to keep the PTY open while closing everything else */ dp->devfd = soc; rendnode = mpty; stderrfd = -1; stderrpid = -1; status_poke_fd = -1; closefds(); modfd = -1; rendnode = -1; no_signals(SIG_IGN); (void)signal(SIGPIPE, killed); FD_ZERO(&rfds); for (;;) { FD_SET(mpty, &rfds); FD_SET(soc, &rfds); i = select(MAX(mpty,soc)+1, &rfds, 0, 0, 0); if (i < 0) { if (errno == EINTR || errno == EAGAIN) continue; bad_errno("pty_relay() select()",""); } if (FD_ISSET(mpty, &rfds)) { i = read(mpty, buf, sizeof(buf)); if (i <= 0) { if (i == 0) log_debug(4,"","pty_relay mpty EOF"); else bad_errno("pty_relay read(mpty)", ""); exit(0); } j = write(soc, buf, i); if (i != j) { if (j < 0) { if (errno == EBADF || debug != 0) bad_errno("pty_relay write(soc)", ""); exit(0); } else { log_complain("", "pty_relay write(soc)=%d" " not %d", i, j); } } } if (FD_ISSET(soc, &rfds)) { i = read(soc, buf, sizeof(buf)); if (i <= 0) { if (i == 0) log_debug(4,"","pty_relay soc EOF"); else bad_errno("pty_relay read(soc)", ""); exit(0); } j = write(mpty, buf, i); if (i != j) { if (j < 0) { if (errno == EBADF || debug != 0) bad_errno("pty_relay write(mpty)", ""); exit(0); } else { log_complain("", "pty_relay write(mpty)=%d" " not %d", i, j); } } } } } int /* 0=failed */ set_tty_modes(struct dev *dp) { char lname[FMNAMESZ+1]; char *spty_name; int soc, spty, mpty = -1; int fl; int val, val_len; FILE *lf; /* if we are talking to a socket, then assume this is a test * over TCP and insert a pty in series. * Just muddle through if we fail to make the pty. */ if (!strcmp(dp->nodename, "-")) { soc = dp->devfd; val_len = sizeof(val); if (0 > getsockopt(soc,SOL_SOCKET,SO_SNDBUF, &val, &val_len)) { log_errno("getsockopt(soc, SO_SNDBUF)",""); } else if (val < 60*1024) { val = 60*1024; if (0 > setsockopt(soc,SOL_SOCKET,SO_SNDBUF, &val, sizeof(val))) log_errno("setsockopt(soc, SO_SNDBUF)",""); } val = 1; if (0 > setsockopt(soc, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val))) { log_errno("setsockopt(soc, TCP_NODELAY)",""); return 0; } if (0 == (spty_name = _getpty(&mpty,O_RDWR|O_NOCTTY,0600,1))) { log_complain("", "_getpty() failed"); return 0; } if (0 > (spty = open(spty_name, O_RDWR|O_NOCTTY))) { log_errno("spty open() ", spty_name); (void)close(mpty); return 0; } /* switch to the pty */ (void)strcpy(dp->line, spty_name); (void)strcpy(dp->nodename, &spty_name[!strncmp(spty_name,"/dev/",5) ? 5:0]); if (!lockname(dp->nodename,dp->lockfile, sizeof(dp->lockfile))) { log_complain("","unable to make lock file name for %s", dp->nodename); return 0; } lf = fopen(dp->lockfile,"w"); if (!lf) { log_errno("fopen(pty) lock file ", dp->lockfile); return 0; } (void)fprintf(lf, LOCKPID_PAT, getpid()); stlock(dp->lockfile); (void)fclose(lf); dp->devfd = spty; dp->sync = SYNC_OFF; } else { if (0 > fstat(dp->devfd, &dp->dev_sbuf)) { dp->dev_sbuf.st_ino = 0; log_errno("fstat(dev)",""); return 0; } /* default line type */ #if defined(SPLI_MAJOR) || defined(WSTY_MAJOR) if (dp->sync == SYNC_DEFAULT && (major(dp->dev_sbuf.st_rdev) == SPLI_MAJOR #ifdef WSTY_MAJOR || major(dp->dev_sbuf.st_rdev) == WSTY_MAJOR #endif )) dp->sync = SYNC_ON; #endif if (dp->sync == SYNC_DEFAULT) { if (0 > ioctl(dp->devfd,I_LOOK,lname)) { if (errno != EINVAL) { log_errno("ioctl(I_LOOK)",""); return 0; } /* assume EINVAL indicates a MUX and that * all MUXes are for sync devices. sigh. */ dp->sync = SYNC_ON; } else if (strcmp("stty_ld", lname)) { log_debug(1,"", "no line discipline STREAMS module" "--assume sync"); dp->sync = SYNC_ON; } else { dp->sync = SYNC_OFF; } } } if (dp->sync == SYNC_OFF) { if (0 > ioctl(dp->devfd, TCGETA, (char*)&dp->devtio)) { log_errno("set_tty_modes TCGETA",""); return 0; } #if defined(PPP_IRIX_62) || defined(PPP_IRIX_53) #ifdef CNEW_RTSCTS dp->devtio.c_cflag &= (CBAUD | CNEW_RTSCTS); #else dp->devtio.c_cflag &= CBAUD; #endif #else #ifdef CNEW_RTSCTS dp->devtio.c_cflag &= CNEW_RTSCTS; #else dp->devtio.c_cflag = 0; #endif #endif dp->devtio.c_cflag |= CS8|CREAD|HUPCL; if (dp->xon_xoff) dp->devtio.c_iflag = IGNPAR|IGNBRK | IXON|IXOFF; else dp->devtio.c_iflag = IGNPAR|IGNBRK; dp->devtio.c_oflag = 0; dp->devtio.c_lflag = 0; dp->devtio.c_cc[VMIN] = 0; dp->devtio.c_cc[VTIME] = 0; if (0 > ioctl(dp->devfd, TCSETAW, (char*)&dp->devtio)) { log_errno("TCSETAW",""); return 0; } /* after the pty is ready, start using it */ if (mpty >= 0) { dp->pty_pid = fork(); if (0 > dp->pty_pid) { log_errno("pty fork()",""); return 0; } if (dp->pty_pid == 0) pty_relay(dp, soc, mpty); log_debug(1,"","pty pid=%d", dp->pty_pid); dp->dev_sbuf.st_ino = 0; (void)close(soc); (void)close(mpty); } } else { /* Use non-blocking I/O on sync channels, since it is * better to drop output packets than to wait forever for * the ISDN daemon to work. Blocking forever in putmsg() * causes lots of havoc. */ fl = fcntl(dp->devfd, F_GETFL, 0); if (fl == -1) { log_errno("fcntl(F_GETFL)",""); } else if (0 > fcntl(dp->devfd, F_SETFL, fl | FNDELAY)) { log_errno("fcntl(F_SETFL, | FNDELAY)",""); } } return 1; } /* get a tty ready */ int /* 0=failed */ rdy_tty_dev(struct dev *dp) { char lname[FMNAMESZ+1]; int i; if (!set_tty_modes(dp)) return 0; if (dp->dev_sbuf.st_ino != 0) { if (dp->dev_sbuf.st_uid != 0 && 0 > chown(dp->line, 0,0)) log_errno("chown(dev) ", dp->line); if (0 > chmod(dp->line, S_IREAD|S_IWRITE)) log_errno("chmod(dev) ", dp->line); } /* strip line discipline */ if (dp->sync == SYNC_OFF) { if (0 > ioctl(dp->devfd,I_LOOK,lname)) { log_errno("ioctl(I_LOOK) ",""); strcpy(lname,"???"); } if (0 > ioctl(dp->devfd,I_POP,0)) log_errno("ioctl(I_POP) ",lname); if (0 > ioctl(dp->devfd, TCFLSH, 2)) log_errno("TCFLSH-2",""); } /* install custom STREAMS modules */ for (i = 0; i < num_smods; i++) { if (0 > ioctl(dp->devfd, I_PUSH, smods[i])) bad_errno("ioctl(I_PUSH) ", smods[i]); log_debug(6,"","ioctl(I_PUSH,%s) ok", smods[i]); } return 1; } /* set IP parameters */ void set_ip(void) { int soc; struct ifreq ifr; soc = socket(AF_INET, SOCK_DGRAM, 0); if (soc < 0) bad_errno("set_ip() socket()",""); if (netmask.sin_addr.s_addr == 0) { if (IN_CLASSA(remhost.sin_addr.s_addr)) netmask.sin_addr.s_addr = htonl(IN_CLASSA_NET); else if (IN_CLASSB(remhost.sin_addr.s_addr)) netmask.sin_addr.s_addr = htonl(IN_CLASSB_NET); else netmask.sin_addr.s_addr = htonl(IN_CLASSC_NET); } (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_addr = *(struct sockaddr *)&netmask; #ifdef _HAVE_SIN_LEN netmask.sin_len = _SIN_ADDR_SIZE; #endif netmask.sin_family = AF_INET; if (0 > ioctl(soc, SIOCSIFNETMASK, (caddr_t)&ifr)) bad_errno("SIOCSIFNETMASK",""); if (metric != 0) { (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_metric = metric; if (0 > ioctl(soc, SIOCSIFMETRIC, (char*)&ifr)) bad_errno("SIOCSIFMETRIC",""); } (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_addr = *(struct sockaddr *)&remhost; if (0 > ioctl(soc, SIOCSIFDSTADDR, (char*)&ifr)) bad_errno("SIOCSIFDSTADDR",""); (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); ifr.ifr_addr = *(struct sockaddr *)&lochost; if (0 > ioctl(soc, SIOCSIFADDR, (char*)&ifr)) bad_errno("SIOCSIFADDR",""); (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (0 > ioctl(soc, SIOCGIFFLAGS, (char*)&ifr)) bad_errno("SIOCGIFFLAGS",""); if (debug > 2) { ifr.ifr_flags |= IFF_DEBUG; } else { ifr.ifr_flags &= ~IFF_DEBUG; } ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); (void)strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (0 > ioctl(soc, SIOCSIFFLAGS, (char*)&ifr)) bad_errno("SIOCSIFFLAGS",""); (void)close(soc); } /* create directory for rendezvous */ static int /* 0=failed */ rend_mkdir(void) { char rm_cmd[sizeof("/bin/rm -rf ")+RENDPATH_LEN]; struct stat sbuf; for (;;) { if (0 > lstat(RENDDIR, &sbuf)) { if (errno != ENOENT) bad_errno("lstat() ", RENDDIR); if (0 > mkdir(RENDDIR, 0755) && errno != EEXIST) { log_errno("mkdir() ", RENDDIR); return 0; } continue; } if (S_ISDIR(sbuf.st_mode) && sbuf.st_uid == 0 && (sbuf.st_mode & (S_IWGRP | S_IWOTH)) == 0) break; log_complain("","stray %s", RENDDIR); (void)sprintf(rm_cmd, "/bin/rm -rf %s", RENDDIR); (void)system(rm_cmd); if (0 <= lstat(RENDDIR, &sbuf)) { log_complain("", "failed to `%s`", rm_cmd); return 0; } } return 1; } /* define another rendezvous */ int /* 0=too many names */ add_rend_name(char *prefix, /* "" or "ep-" */ char *str) /* hostname, MAC address, etc */ { int i; struct rend *rp; char path[RENDPATH_LEN]; if (!rend_mkdir()) return 0; (void)sprintf(path, RENDPATH_PAT, prefix, str); for (rp = rends, i = 0; i < MAX_RENDPATHS; i++, rp++) { /* add the path to the list if it is new */ if (rp->path[0] == '\0') { (void)strcpy(rp->path, path); rp->made = 0; rp->st.st_uid = -1; num_rends++; return 1; } /* quit if the path is already known */ if (!strcmp(path,rp->path)) return 1; } log_complain("","too many rendezvous names with \"%s\"", str); return 0; } /* mark all rendezvous names removed so that they will not be removed. */ void unmade_rend(void) { int i; struct rend *rp; for (rp = rends, i = 0; i < MAX_RENDPATHS; i++, rp++) { rp->made = 0; rp->st.st_uid = -1; } status_poke_path[0] = '\0'; status_path[0] = '\0'; } /* remove one or all rendezvous file names */ void rm_rend(char *prefix, /* "" or "ep-" */ char *str) /* hostname, MAC address, etc */ { int i; struct rend *rp; char path[RENDPATH_LEN]; if (str) (void)sprintf(path, RENDPATH_PAT, prefix, str); for (rp = rends, i = 0; i < MAX_RENDPATHS; i++, rp++) { if (rp->path[0] == '\0') continue; if (rp->made && (!str || !strcmp(path, rp->path))) { (void)unlink(rp->path); rp->made = 0; rp->st.st_uid = -1; num_rends--; if (str) { rp->path[0] = '\0'; return; } rp->path[0] = '\0'; } } if (str) { (void)unlink(path); return; } if (status_poke_path[0] != '\0') { (void)unlink(status_poke_path); status_poke_path[0] = '\0'; } if (status_path[0] != '\0') { (void)unlink(status_poke_path); status_path[0] = '\0'; } } /* Create the rendezvous FIFO, if it does not already exist. * Must only be called after link_fifo() so that rp->st.st_uid is valid. */ static void make_fifo(int tgt) /* this name */ { struct rend *rp = &rends[tgt]; if (rp->st.st_uid == 0) { if (0 <= utime(rp->path, 0)) return; if (errno != ENOENT) bad_errno("lstat() ", rp->path); log_complain("","%s disappeared",rp->path); } (void)unlink(rp->path); rp->st.st_uid = -1; if (!rend_mkdir()) return; if (0 > mknod(rp->path, S_IFIFO|S_IRUSR|S_IWUSR,0)) { log_errno("mknod() ", rp->path); rendnode = -1; } else { rp->made = 1; } } /* Check that the rendezvous FIFO exits * Close its file descriptor if it is wrong. * Create its links if ok. */ static int /* return good name index */ link_fifo(int mode) /* 0=create nothing, * 1=create only if rendnode ok * 2=create all */ { static nlink_t complain_num_rends; struct rend *rp, *good_rp; int i, good, bad; char *p1, *p2; struct stat sfd; /* Check all of the names to see that they are the same inode. * If not, remove the oldest. */ good_rp = &rends[good = 0]; for (rp = rends, i = 0; i < MAX_RENDPATHS; i++, rp++) { if (rp->path[0] == '\0') continue; if (0 > lstat(rp->path, &rp->st)) { /* bad node if non-existent or cannot stat() it */ if (errno != ENOENT) bad_errno("lstat() ", rp->path); if (rp->made) log_complain("","%s disappeared",rp->path); rp->made = 0; rp->st.st_uid = -1; continue; } if (!S_ISFIFO(rp->st.st_mode) || rp->st.st_uid != 0) { /* Bad node if not one of our FIFOs. */ log_complain("","stray %s", rp->path); (void)unlink(rp->path); rp->made = 0; rp->st.st_uid = -1; continue; } /* note the first good name */ while (good < i && rends[good].st.st_uid != 0) good_rp = &rends[++good]; /* bad if not the same as the other FIFOs */ if (good != i && (good_rp->st.st_dev != rp->st.st_dev || good_rp->st.st_ino != rp->st.st_ino)) { p1 = good_rp->path; p2 = rp->path; if (good_rp->st.st_mtime >= rp->st.st_mtime) { bad = i; } else { bad = good; good_rp = &rends[good = i]; } if (mode != 0) log_complain("","conflicting %s and %s;" " unlink %s", p1, p2, rends[bad].path); rends[bad].made = 0; rends[bad].st.st_uid = -1; } } /* If no good path exists, then start creating with the first one. */ if (good_rp->st.st_uid != 0) { good_rp = &rends[good = 0]; (void)close(rendnode); rendnode = -1; } else if (rendnode >= 0) { if (0 > fstat(rendnode,&sfd)) { log_errno("rendezvous fstat() ","rendnode"); (void)close(rendnode); rendnode = -1; } else if (good_rp->st.st_dev != sfd.st_dev || good_rp->st.st_ino != sfd.st_ino) { log_complain("","rendezvous %s replaced", good_rp->path); (void)close(rendnode); rendnode = -1; } } /* if only checking (and so do not have the lock), quit now. */ if (mode == 0 || (mode == 1 && rendnode < 0)) return (good_rp->st.st_uid != 0) ? -1 : good; /* create the node and the links */ make_fifo(good); for (rp = rends, i = 0; i < MAX_RENDPATHS; i++, rp++) { if (rp->path[0] == '\0') continue; rp->made = 1; if (i != good && rp->st.st_uid != 0) { (void)unlink(rp->path); if (0 > link(good_rp->path,rp->path)) { log_errno("link() ", rp->path); rendnode = -1; } } } /* check for stray links */ if (0 > stat(good_rp->path, &good_rp->st)) { log_errno("stat() ", good_rp->path); rendnode = -1; } if (good_rp->st.st_nlink != num_rends && good_rp->st.st_nlink != complain_num_rends) { log_debug(1,"","%s has %d instead of %d links", good_rp->path, good_rp->st.st_nlink, num_rends); complain_num_rends = good_rp->st.st_nlink; } return good; } /* create the rendezvous FIFO if necessary * The IP address of the remote host must be known. */ int /* 1=need to pass on FD */ make_rend(int make_nodes) /* 1=names are known so make nodes */ { # define REND_LOCK "pputil" # define REND_LOCK2 (LOCKPRE "." REND_LOCK) int good, i; struct rend *good_rp; if (num_rends == 0) { log_debug(6,"","peer name and address unknown for rendezvous"); return 0; } /* If the FIFO already exists, then just check to see that it has * not been deleted by an overeager /tmp cleaner. */ if (rendnode >= 0) { (void)link_fifo(1); if (rendnode >= 0) return 0; } /* We do not have the FIFO open. Check to see if it exists */ good = link_fifo(0); /* If we are not sure of the right names, do not create any nodes. */ if (!make_nodes) { if (good < 0) return 0; good_rp = &rends[good]; /* if this succeeds, then a PPP process is already * in charge of the link. */ rendnode = open(good_rp->path, O_WRONLY|O_NDELAY, 0); return (rendnode >= 0); } /* Lock to mostly fix race during creation of the pipe. * Try only for a while, and then just give up and hope * for the best. */ for (i = 0; mmlock(REND_LOCK) && i < 5*4; i++) { log_debug(2,"","waiting to make rendezvous node"); sginap(HZ/4); } /* Now that we have the lock, see if some other process * was quicker and created the FIFO */ good = link_fifo(0); if (good < 0) good = 0; good_rp = &rends[good]; /* Create a FIFO for this connection. * If FIFO already exists, check to see the connection is * already active. If so, poke the existing daemon and quit. * Open an existing name if there is one to avoid creating * links that the existing daemon might object to. */ make_fifo(good); rendnode = open(good_rp->path, O_WRONLY|O_NDELAY, 0); if (rendnode >= 0) { /* No error means some other daemon is in charge. * Give away the line or quit. */ rmlock(REND_LOCK2); return 1; } if (errno != ENXIO) bad_errno("open(O_WRONLY) ",good_rp->path); /* ENXIO means we are in charge. * * Re-open the FIFO for reading and writing while keeping it open. */ i = rendnode; rendnode = open(good_rp->path, O_RDWR | O_NDELAY, 0); if (rendnode < 0) bad_errno("open(O_RDWR) ", good_rp->path); (void)close(i); rmlock(REND_LOCK2); /* check again, to resolve the race getting the lock. * While we were waiting for the lock */ (void)link_fifo(2); if (rendnode < 0) { log_complain("", "failed to reliably create rendezvous"); unmade_rend(); cleanup(1); } return 0; } /* common error exit */ void cleanup(int code) { struct dev *dp; pid_t pid; int st; for (dp = dp0; dp != 0; dp = dp->next) rel_dev(dp); rm_rend(0,0); if (rendnode >= 0) { (void)close(rendnode); rendnode = -1; } if (modfd >= 0) { (void)close(modfd); modfd = -1; } /* delete added route */ if (del_route != 0) call_system("", "del_route: ", del_route, 1); /* optional ending script */ if (end_cmd != 0) call_system("", "end_cmd: ", end_cmd, 1); #ifdef PPP /* Delete proxy-ARP table entry. */ proxy_unarp(); #endif /* PPP */ /* wait for children */ if ((pid = add_pid) > 0) { add_pid = -1; log_debug(7,"","kill pid %d", pid); (void)kill(pid, SIGTERM); (void)waitpid(pid,&st,0); } /* tell about any attempted calls */ if (dp0->acct.attempts && add_pid != 0) ck_acct(dp0,1); log_debug(1,"","exiting with %d", code); stderrfd = -1; stderrttyfd = -1; closefds(); if (stderrpid > 0) { (void)waitpid(stderrpid,&st,0); stderrpid = -1; } exit(code); } /* get rid of signals */ void no_signals(__sigret_t (*ksig)()) { (void)alarm(0); (void)signal(SIGHUP, SIG_IGN); (void)signal(SIGINT, ksig); (void)signal(SIGTERM, ksig); (void)signal(SIGPIPE, ksig); (void)signal(SIGUSR1, SIG_IGN); (void)signal(SIGUSR2, SIG_IGN); (void)signal(SIGCHLD, SIG_DFL); } /* (probably) killed by resident daemon */ void killed(int code) { log_debug(1,"","killed by signal %d", code); exit(code); } /* clear accounting * get_clk() must have been called recently. */ void clr_acct(struct dev *dp) { dp->acct.last_date = cur_date.tv_sec; dp->acct.last_secs = clk.tv_sec; dp->acct.call_start = 0; dp->acct.calls = 0; dp->acct.attempts = 0; dp->acct.failures = 0; dp->acct.answered = 0; dp->acct.min_setup = MAXINT; dp->acct.max_setup = 0; dp->acct.setup = 0; if (dp->acct.sconn == 0) dp->acct.sconn = clk.tv_sec; dp->acct.orig_conn = 0; dp->acct.orig_idle = 0; dp->acct.ans_conn = 0; dp->acct.ans_idle = 0; } static char* hms(int secs) { # define NUM_BUFS 10 static int n; static struct { char c[11]; /* hhhh:mm:ss */ } bufs[NUM_BUFS]; char *p; p = bufs[n].c; if (++n >= NUM_BUFS) n = 0; if (secs < 0) secs = 0; sprintf(p, "%d:%02d:%02d", (secs/(60*60)) % 10000, (secs/60) % 60, secs % 60); return p; } /* account for phone line use */ void ck_acct(struct dev *dp, int force) /* 1=do it anyway */ { static char tbuf[] = "dayweek month dd hh:mm:ss "; static char tpat[] = "%a %b %e %T"; struct tm lt; if (!demand_dial && (!force || !camping)) return; get_clk(); /* Report for the log once a day, after midnight and before 3 am. * This minimizes problems with daylight savings time. */ if (!force) { if (cur_date.tv_sec < dp->acct.next_date) return; lt = *localtime(&cur_date.tv_sec); if (lt.tm_hour >= 3) return; } syslog(LOG_INFO, "%s: %d answered, %d links originated," " %d calls dialed, %d gave up link", remote, dp->acct.answered, dp->acct.calls, dp->acct.attempts, dp->acct.failures); if (dp->acct.calls != 0) syslog(LOG_INFO, "%s: %d/%d/%d min/avg/max seconds call setup", remote, dp->acct.min_setup, dp->acct.setup/dp->acct.calls, dp->acct.max_setup); (void)cftime(tbuf,tpat,&dp->acct.last_date); syslog(LOG_INFO,"%s: calling %s,idle %s; answering %s,idle %s;" " disconncted %s since %s", remote, hms(dp->acct.orig_conn), hms(dp->acct.orig_idle), hms(dp->acct.ans_conn), hms(dp->acct.ans_idle), hms(dp->acct.sconn - dp->acct.last_secs - dp->acct.orig_conn-dp->acct.ans_conn), tbuf); /* if not forcing things, reset the counters */ if (!force) { clr_acct(dp); lt.tm_sec = 1; lt.tm_min = 1; lt.tm_hour = 0; dp->acct.next_date = mktime(<) + 24*60*60; } } /* get rid of stray controlling TTY * We do not want control characters to give us signals. */ static void stray_ctty(void) { register int i; if (ctty <= 0) { i = open("/dev/tty", O_RDWR|O_NDELAY); if (i >= 0) { ioctl(i, TIOCNOTTY, (char *)0); (void)close(i); } } } /* stop being interactive */ void no_interact(void) { interact = 0; stderrfd = -1; stderrttyfd = -1; /* If not interactive, avoid controlling tty to avoid stray * signals from the keyboard. */ ctty = 0; stray_ctty(); (void)setsid(); (void)chdir("/"); } /* see if a file descriptor is important */ static int /* 1=save it */ savefd(int fd) { struct dev *dp; if (fd < 0) return 1; if (fd == stderrfd || fd == stderrttyfd || fd == modfd || fd == rendnode || fd == status_poke_fd) return 1; for (dp = dp0; dp != 0; dp = dp->next) { if (fd == dp->devfd || fd == dp->devfd_save) return 1; } return 0; } /* garbage collect stray file descriptors from UUCP and friends */ void closefds(void) { extern void _yp_unbind_all(void); extern void _res_close(void); int i; stray_ctty(); /* Close all stray files and tell YP and DNS. * The UUCP code likes to leave things open. */ for (i = getdtablehi(); --i > 2; ) { if (!savefd(i)) (void)close(i); } _yp_unbind_all(); /* tell NIS its FD is dead */ _res_close(); /* tell resolver its FD is dead */ closelog(); i = open(_PATH_DEVNULL, O_RDWR, 0); if (i >= 0) { if (!savefd(0)) (void)dup2(i, 0); if (!savefd(1)) (void)dup2(i, 1); if (!savefd(2)) (void)dup2(i, 2); if (i > 2) (void)close(i); } openlog(pgmname, LOG_PID | LOG_ODELAY | LOG_NOWAIT, LOG_DAEMON); } char * ascii_str(u_char *s, int len) { static char buf[200]; char *p; u_char *s2, c; for (p = buf; len > 0 && p < &buf[sizeof(buf)-1]; len--) { c = *s++; if (c == '\0') { for (s2 = s+1; s2 < &s[len]; s2++) { if (*s2 != '\0') break; } if (s2 >= &s[len]) break; } if (c >= ' ' && c < 0x7f && c != '\\') { *p++ = c; continue; } *p++ = '\\'; switch (c) { case '\\': *p++ = '\\'; break; case '\n': *p++= 'n'; break; case '\r': *p++= 'r'; break; case '\t': *p++ = 't'; break; case '\b': *p++ = 'b'; break; default: p += sprintf(p,"%o",c); break; } } *p = '\0'; return buf; } void log_errno(char *s1, char *s2) { int serrno = errno; kludge_stderr(); (void)fprintf(stderr, "%s: %s%s: %s\n", remote, s1,s2, strerror(serrno)); (void)fflush(stderr); errno = serrno; } void bad_errno(char *s1, char *s2) { log_errno(s1,s2); #ifdef DEBUG (void)fflush(stderr); abort(); #endif cleanup(1); } void log_debug(int lvl, char* name, char *p, ...) { va_list args; if (debug < lvl) return; kludge_stderr(); va_start(args, p); (void)fprintf(stderr, "%s%s: ", remote,name); (void)vfprintf(stderr, p, args); (void)putc('\n',stderr); (void)fflush(stderr); va_end(args); } void log_cd(int sw, int lvl, char* name, char *p, ...) { va_list args; if (!sw && debug < lvl) return; va_start(args, p); kludge_stderr(); (void)fprintf(stderr, "%s%s: ", remote,name); (void)vfprintf(stderr, p, args); (void)putc('\n',stderr); (void)fflush(stderr); va_end(args); } void log_complain(char* name, char *p, ...) { va_list args; va_start(args, p); kludge_stderr(); (void)fprintf(stderr, "%s%s: ", remote,name); (void)vfprintf(stderr, p, args); (void)putc('\n',stderr); (void)fflush(stderr); va_end(args); } /* Touch the devices to make them appear active to `w`. */ void act_devs(int weak, /* 0=TCP/IP active, 1=IP active, 2=? */ int touch) /* 1=set the mtime on the device */ { struct dev *dp; time_t t0, t, b, d; t0 = clk.tv_sec + ((weak >= 1) ? sactime : lactime); for (dp = dp0; dp != 0; dp = dp->next) { if (dp->line[0] == '\0') continue; t = t0; b = dp->acct.toll_bound; if (b > 0 && dp->acct.call_start != 0) { /* Round up connect-time to close to a multiple * of the boundary. Do not get too close to * the boundary to avoid paying for an extra * minute. */ d = b - ((t0 - dp->acct.call_start) % b); if (d > 1 && d < b-1) t += d; } if (weak <= 1) dp->active = t; else if (dp->active < t) dp->active = t; if (touch && dp->touched+HEARTBEAT < get_clk()) { if (0 > utime(dp->line, 0)) log_errno("touch() ", dp->line); dp->touched = clk.tv_sec; } } } /* finish with the device */ void rel_dev(struct dev *dp) { int st; if (modfd >= 0 && dp->dev_index != -1) { if (0 > ioctl(modfd,I_UNLINK,dp->dev_index)) /* unlink MUX */ log_errno("I_UNLINK",""); dp->devfd = dp->devfd_save; } dp->dev_index = -1; dp->devfd_save = -1; dp->devfd = -1; /* restore the owner and permissions of the device */ if (dp->dev_sbuf.st_ino != 0) { if (dp->dev_sbuf.st_uid != 0 && 0 > chown(dp->line, dp->dev_sbuf.st_uid, dp->dev_sbuf.st_gid)) log_errno("chown(restore) ", dp->line); if (0 > chmod(dp->line, dp->dev_sbuf.st_mode)) log_errno("chmod(restore) ", dp->line); dp->dev_sbuf.st_ino = 0; } dp->line[0] = '\0'; dp->nodename[0] = '\0'; closefds(); dologout(dp); if (dp->lockfile[0] != '\0') { rmlock(dp->lockfile); dp->lockfile[0] = '\0'; } /* get rid of rendezvousing process */ if (dp->rendpid > 0) { if (0 > kill(dp->rendpid, SIGINT) && (errno != ESRCH || debug >= 1)) log_errno("kill(rel_dev rendezvous)",""); (void)waitpid(dp->rendpid,&st,0); } dp->rendpid = -1; if (dp->pty_pid > 0) { if (0 > kill(dp->pty_pid, SIGPIPE) && errno != ESRCH) log_errno("kill(rel_dev pty_pid)",""); (void)waitpid(dp->pty_pid,&st,0); dp->pty_pid = -1; } } /* connect to the other side */ int /* 0=failed, 1=done, 2=rendezvous */ get_conn(struct dev *dp, int rend_ok) /* 1=try to rendezvous */ { char ebuf[40+2+80+1]; fd_set in; int i, tries, delay; struct timeval timer; time_t start; /* Try not to keep the modem busy in case the other end * is trying to call us. */ conn_trycalls = 1; /* reset the locking mechanisms */ lock_pid[0] = '\0'; Nlocks = 0; get_clk(); start = clk.tv_sec; if (Debug) kludge_stderr(); tries = 0; delay = 0; for (;;) { tries++; dp->acct.attempts++; /* Use the UUCP dialing code. */ dp->sync = dp->conf_sync; setservice(pgmname); dp->acct.call_start = clk.tv_sec; dp->devfd = conn(dp->uucp_nam); alarm(0); /* in case UUCP forgets */ (void)signal(SIGALRM, SIG_IGN); if (dp->devfd != FAIL) { (void)strncpy(dp->nodename,Dc,sizeof(dp->nodename)); (void)sprintf(dp->line,"/dev/%s",dp->nodename); if (!lockname(dp->nodename,dp->lockfile, sizeof(dp->lockfile))) { log_complain("", "unable to make lock file name" " for %s", dp->nodename); } closefds(); get_clk(); dp->acct.sconn = clk.tv_sec; i = dp->acct.sconn - start; dp->acct.setup += i; if (i < dp->acct.min_setup || dp->acct.min_setup == 0) dp->acct.min_setup = i; if (i > dp->acct.max_setup) dp->acct.max_setup = i; dp->acct.calls++; return 1; } dp->devfd = -1; rel_dev(dp); sprintf(ebuf, ((Uerror == SS_CHAT_FAILED && AbortSeen < Aborts) ? "%.40s--%.80s" : "%.40s"), UERRORTEXT, AbortStr[AbortSeen]); /* Give up if not trying hard and not interested in retrying, */ if (!camping && !demand_dial) { log_complain("","fatal error %s", ebuf); dp->acct.failures++; return 0; } /* or if the cause of failure is strange. */ if (Uerror != SS_DIAL_FAILED && Uerror != SS_LOGIN_FAILED && Uerror != SS_CANT_ACCESS_DEVICE && Uerror != SS_CHAT_FAILED && Uerror != SS_LOCKED_DEVICE) { log_complain("","fatal error %s on try #%d", ebuf, tries); dp->acct.failures++; if (dp->modpause > 0) sginap(dp->modpause*HZ); return 0; } /* Random backoff in case the other machine * is trying to call us at the same time. * Quick delay if the modem is busy, to let getty * either answer a call or finish initializing the modem, * but exponentially increasing delay otherwise. */ timer.tv_sec = ((dp->modwait > 0) ? dp->modwait : DEFAULT_ASYNC_MODWAIT); timer.tv_usec = 0; if (Uerror != SS_LOCKED_DEVICE) { i = rint(++delay * drand48()); timer.tv_sec <<= i; } log_debug(demand_dial ? 1 : 0, "","%s on try #%d, back off %d seconds", ebuf,tries,timer.tv_sec); if (rendnode >= 0) { FD_ZERO(&in); FD_SET(rendnode, &in); switch (select(rendnode+1, &in,0,0,&timer)) { case 0: break; case 1: if (!rend_ok) return 2; if (rendezvous(1)) return 2; break; default: if (errno != EINTR && errno != EAGAIN) bad_errno("select(rendnode) " "in get_conn()",""); break; } } else { sginap(timer.tv_sec*HZ); } if (tries > dp->modtries) { dp->acct.failures++; if (dp->modpause > 0) sginap(dp->modpause*HZ); return 0; } } }