/* * ttyd.c - Daemon for dialing and hanging up on btty devices * * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: ttyd.c,v 1.2 2004/04/08 05:59:00 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ldisc.h" #include "ttyd.h" #include "version.h" const char CONTROL_PATH[]="/tmp/.bab_control"; char configFile[MAXPATHLEN]; const char CONFIG_FILE[]="/etc/babylon/bttyd.conf"; int ctrl_fd; extern struct linedef ttyconfig[MAXCONFIG]; extern struct optiondef options; struct processdef processes[MAXPORTNUM]; char *arg0; int chatpid; int GotSignal; int do_reset=1; int do_scan=1; /* Options we support (see the man page) */ static char shrt_options[] = "fhvrsc:"; static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "no-reset", no_argument, NULL, 'r' }, { "no-scan", no_argument, NULL, 's' }, { "config-file",required_argument,NULL,'c'} }; void bscan(); int read_config(); int yyparse(); int check_lock(char *lck_file,pid_t *pid); void print_usage() { fprintf(stderr, "Usage: %s [options]\n", arg0); fprintf(stderr, " -h, --help display this info\n"); fprintf(stderr, " -c, --config-file use file instead of %s\n",CONFIG_FILE); fprintf(stderr, " -s, --no-scan don't force babylond to rescan for channels\n"); fprintf(stderr, " -r, --no-reset don't reset all tty channels on start\n"); exit(1); } /* * Our signal handlers */ void dial_handler(int signal) { int status; if (signal==SIGCHLD) { /* Child process died. Wait for funeral */ wait(&status); } else { if (chatpid>0) { kill(chatpid,signal); chatpid=0; } syslog(LOG_NOTICE,"Caught signal %d!",signal); GotSignal=1; } } /* * Loop signal handler - rereads config file */ void loop_handler(int signal) { if (signal==SIGHUP) { if (read_config()==0) { syslog(LOG_NOTICE,"Re-read config file"); } else { syslog(LOG_NOTICE,"Error re-reading config file"); } } } /* * Routine to hangup a port - we do this by sending a SIGHUP to the process in * charge of the port which is generally a child of bttyd or is battach */ void hangup (int port) { pid_t pid; if (processes[port].pid>0) { kill(processes[port].pid,SIGHUP); pid=processes[port].pid; processes[port].pid=0; syslog(LOG_NOTICE,"Hangup complete port %d (pid %d)\n", port,pid); } else { syslog(LOG_NOTICE,"Hangup request possible idle port (%d). Forced idle.\n", port); } /* Force the port to idle - we ignore any failures*/ ioctl(ctrl_fd, BIOCF_TTY_STAT_IDLE, port); } /* * Routine to dial. It configures the serial port, and then forks a process to dial * and camp on the line */ void dial(int portnum,char *num) { int ret,ldisc,failed; struct slinedef port; char connect[256],tmp[256],*p; failed=0; chatpid=0; /* Find a port to use */ if ((port.confignum=findserial(portnum))==-1) { /* We failed. Tell bdial*/ ioctl(ctrl_fd, BIOCF_TTY_CONN_FAILED, portnum); syslog(LOG_NOTICE,"Dial attempt failed. No serial ports available."); return; } /* Find the phone number and build up a string with it in it */ strcpy(tmp,ttyconfig[port.confignum].connect); p=strstr(tmp,"%n"); *p=(char)0; p+=2; sprintf(connect,"%s%s%s",tmp,num,p); signal(SIGCHLD,dial_handler); /* * Fork off a process to open the port, to chat to the modem, make a connection, * and camp on the line until we need to hangup */ #ifndef NOFORK ret = fork(); if (0 == ret) { #endif sprintf(arg0,"bttyd [dialing port btty%d]",portnum); /* Catch the HUP and TERM signals */ signal(SIGHUP,dial_handler); signal(SIGTERM,dial_handler); signal(SIGCHLD,dial_handler); /* Open a port */ if (open_port(&port)>0) { if (ioctl(ctrl_fd, BIOCF_TTY_CONN_FAILED, portnum) != 0) { syslog(LOG_NOTICE,"Failed to indicated connection failed on %s.",ttyconfig[port.confignum].linename); } return; } /*processes[portnum].fd=port.fd;*/ /* Setup the port */ if (init_port(&port)>0) { close(port.fd); if (ioctl(ctrl_fd, BIOCF_TTY_CONN_FAILED, portnum) != 0) { syslog(LOG_NOTICE,"Failed to indicated connection failed on %s.",ttyconfig[port.confignum].linename); } return; } if (do_chat(&port,connect)==0) { ldisc=N_BAB; /* Set the line discipline to Babylon */ sprintf(arg0,"bttyd [btty%d: Setting line disc]",portnum); if (ioctl(port.fd, TIOCSETD, &ldisc)==0) { sprintf(arg0,"bttyd [btty%d: Attaching port to babylon]",portnum); /* Tell babylon to attach to the tty */ if (ioctl(port.fd, BIOC_TTY_ATTACH, portnum)==0) { sprintf(arg0,"bttyd [connected port btty%d]",portnum); syslog(LOG_NOTICE,"Line connected (btty%d)",portnum); GotSignal=0; /* Loop until we get told to hangup or the port status */ /* changes to idle indicating we got hungup on */ while (!GotSignal) { /* Check the port status - if this returns CS_IDLE or an error */ /* the line got dropped without us! */ if (ioctl(port.fd, BIOC_TTY_GETSTATUS, portnum)<1) { syslog(LOG_NOTICE,"Down by remote? (btty%d) (result:%s)",portnum, strerror(errno)); break; } sleep(1); /* Sleep for one second */ } syslog(LOG_NOTICE,"Line dropped (btty%d)",portnum); } else { syslog(LOG_NOTICE,"Can't attached to Babylon on line %s.",ttyconfig[port.confignum].linename); failed=1; } } else { syslog(LOG_NOTICE,"Can't set line disc on line %s.",ttyconfig[port.confignum].linename); failed=1; } } else { failed=1; } /* Set the line discipline to normal */ ldisc=0; if (ioctl(port.fd, TIOCSETD, &ldisc)<0) { syslog(LOG_NOTICE,"Can't reset line disc on line %s.",ttyconfig[port.confignum].linename); } close(port.fd); /* If we failed, tell the user */ if (failed) { if (ioctl(ctrl_fd, BIOCF_TTY_CONN_FAILED, portnum) != 0) { syslog(LOG_NOTICE,"Failed to indicated connection failed on %s.",ttyconfig[port.confignum].linename); } } unlock_port(&port); port.fd = 0; #ifndef NOFORK exit(0); } else if (ret < 0) { syslog(LOG_NOTICE,"Dial attempt failed. Unable to fork process to dial."); /* Tell babylon this port is idle */ if (ioctl(ctrl_fd, BIOCF_TTY_CONN_FAILED, portnum) != 0) { syslog(LOG_NOTICE,"Failed to indicated connection failed on %s.",ttyconfig[port.confignum].linename); } close(port.fd); unlock_port(&port); port.fd = 0; exit(1); } else { /* Successful fork - save the PID and file handle for hangup */ processes[portnum].pid=ret; /* Close the port in the parent process */ close(port.fd); port.fd = 0; } #endif } /* * Routine to grab the port num and pid from battach and record it for hangup */ void inport(int portnum,pid_t pid) { if (pid>0) { #ifdef DEBUG syslog(LOG_NOTICE,"inport: Set port btty%d with pid %d",portnum,pid); #endif if (processes[portnum].pid==0) { processes[portnum].pid=pid; processes[portnum].confignum=0; processes[portnum].fd=0; } else { syslog(LOG_NOTICE,"Inport error: Port %d already in use",portnum); } } else { #ifdef DEBUG syslog(LOG_NOTICE,"inport: Cleared port btty%d",portnum,pid); #endif processes[portnum].pid=0; processes[portnum].confignum=0; processes[portnum].fd=0; } } /* * Routine to read the config file */ int read_config() { extern int ttynum; extern int modemnum; extern int lineno; extern FILE *yyin; int ret; /* Initialize configuration space */ memset(&ttyconfig,(sizeof(struct linedef)*MAXCONFIG),(char)0); memset(&options,sizeof(struct optiondef),(char)0); ttynum=-1; modemnum=-1; lineno=0; /* Set our global filename item */ if ((yyin=fopen(CONFIG_FILE,"r"))==NULL) { syslog(LOG_NOTICE,"Cannot open %s: %s\n",CONFIG_FILE,strerror(errno)); exit(1); } /* Parse the config file */ if (yyparse()>0) { ret=1; } else { ret=0; } fclose(yyin); return(ret); } /* * Routine to do the main processing. It's forks to a separate process by main */ int loop() { int ret,i; Req msg; fd_set rfds; signal(SIGHUP,loop_handler); /* Open up our control file */ if ((ctrl_fd = open("/proc/bttyctrl", O_RDONLY)) < 0) { syslog(LOG_NOTICE,"Cannot open /proc/bttyctrl: %s",strerror(errno)); return(2); } if (do_reset) { if (ioctl(ctrl_fd,BIOCF_TTY_RESET,0)!=0) { syslog(LOG_NOTICE,"Driver reset failed: %s",strerror(errno)); return(2); } } /* Create ports for each serial line defined in the config file */ for (i=0;((i 0) { switch(msg.cmd) { case REQ_DIAL: dial(msg.port,msg.phone_num); break; case REQ_HANGUP: hangup(msg.port); break; case REQ_INPORT: inport(msg.port,msg.pid); break; } } } return(0); } /* * Routine to create our pid file in /var/run and barf if we are already * running. Returns 0 on success and 1 one error */ int pid_file() { int pid_fd; pid_t pid; char pid_str[12]; memset(pid_str, '\0', 12); while(1) { if ((pid_fd = open(PID_FILE, O_RDWR | O_EXCL | O_CREAT, 0644))<0) { if(errno == EEXIST) { if (check_lock(PID_FILE,&pid)) { printf("\nbttyd already running as process %d\n\n",pid); return(1); } else { /* Unlink the old lock */ unlink(PID_FILE); continue; } } else { perror(PID_FILE); return(1); } } /* Write our lock info */ pid = getpid(); sprintf(pid_str, "%010d\n", pid); write(pid_fd, pid_str, 11); close(pid_fd); return(0); } } /* * Routine to force babylond to rescan the channel list. We do this to allow * the tty driver to work even if btty crashes, the driver is reload, whatever */ void bscan() { int fd; const char scan[]="bchan rescan\n"; struct sockaddr_un addr; char buf[128]; fd_set rfds; if ((fd = socket(AF_UNIX, SOCK_STREAM, 0))==-1) { syslog(LOG_NOTICE,"Cannot create socket: %s",strerror(errno)); exit(1); } addr.sun_family = AF_UNIX; strcpy(addr.sun_path, CONTROL_PATH); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr.sun_family)+strlen(addr.sun_path))==-1) { syslog(LOG_NOTICE,"Cannot open %s (babylond running?): %s",CONTROL_PATH,strerror(errno)); exit(1); } write(fd,scan,strlen(scan)); /* Wait until babylond sets a response, and then ignore it */ FD_ZERO(&rfds); FD_SET(fd,&rfds); while (select(fd+1,&rfds,NULL,NULL,NULL)!=-1) { if (FD_ISSET(fd, &rfds)) { read(fd,buf,127); break; } } close(fd); } int main(int argc,char *argv[]) { int ret; int do_reset=1; int opt,opt_indx; if (getuid()!=0) { puts("\nThis application must be run as root\n"); exit(2); } /* Setup the config file */ strcpy(configFile,CONFIG_FILE); arg0=argv[0]; /* get out command line options */ while((opt = getopt_long(argc, argv, shrt_options, long_options, &opt_indx)) != EOF) { switch(opt) { case 'v': printf("bttyd Version %s\n",version); exit(1); case 'r': do_reset=0; break; case 's': do_scan=0; break; default: print_usage(); } } /* Check for the tmp/.babcontrol thing */ if (do_scan && (access(CONTROL_PATH,F_OK)!=0)) { syslog(LOG_NOTICE,"%s does not exist. babylond running?",CONTROL_PATH); printf("%s does not exist. babylond running?\n",CONTROL_PATH); } /* Wipe out our processes table */ memset(&processes,(sizeof(struct processdef)*MAXPORTNUM),(char)0); memset(&options,sizeof(struct optiondef),(char)0); memset(&ttyconfig,(sizeof(struct linedef)*MAXCONFIG),(char)0); chatpid=0; #ifndef NOFORK /* fork out as a daemon */ ret = fork(); if (0 == ret) { #endif /* Create our pid file and abort if we are already running */ if (pid_file()==1) { exit(2); } strcpy(arg0,"bttyd [parent process]"); /* Open syslog */ openlog("bttyd",LOG_PID|LOG_CONS,LOG_DAEMON); /* Read the config file */ if (read_config()==0) { syslog(LOG_NOTICE,"bttyd %s loaded",version); if ((ret=loop())==0) { syslog(LOG_NOTICE,"bttyd exited normally"); } else { syslog(LOG_NOTICE,"bttyd exited on error"); } closelog(); exit(ret); } else { closelog(); exit(1); } #ifndef NOFORK } else if (ret<0) { syslog(LOG_NOTICE,"ttyd daemon fork failed: %s",strerror(errno)); perror("ttyd daemon fork failed"); } #endif exit(0); }