#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mconsole.m" #include #include #include #include using namespace std; static map baudlk; static void mconsole_initbaud() { baudlk[50] = B50; baudlk[75] = B75; baudlk[110] = B110; baudlk[134] = B134; baudlk[150] = B150; baudlk[200] = B200; baudlk[300] = B300; baudlk[600] = B600; baudlk[1200] = B1200; baudlk[1800] = B1800; baudlk[2400] = B2400; baudlk[4800] = B4800; baudlk[9600] = B9600; baudlk[19200] = B19200; baudlk[38400] = B38400; baudlk[57600] = B57600; baudlk[115200] = B115200; baudlk[230400] = B230400; } class HANDLE_INFO: public ARRAY_OBJ{ public: string name; bool serial; // This is a serial port (or actually a backend, not a client) FILE *log; int fd; // Handle of this connection or serial port int linked; // Handle of the serial port linked to this // connection. // If -1, the connection is in control mode. After the "connect" verb // it becomes linked to a back-end string host; // Used to reconnect (some devices hangup when doing a logout) int port; // idem string cmd; STREAMP_BUF buf; HANDLE_INFO(){ serial = false; log = NULL; fd = -1; linked = -1; port = 0; } ~HANDLE_INFO(){ if (log != NULL) fclose (log); } }; typedef ARRAY_OBJS HANDLE_INFOS; static const char *house_keeping_name = "house keeping"; static int house_keeping (int fd) { int ret = -1; if (write (fd,"a",1)==1) ret = 0; return ret; } /* A backend is a way to connect to something using either a socket (host,port) or a command. Once connected, several clients may be hooked to the connection. */ class BACKEND: public ARRAY_OBJ{ public: string name; string host; int port; string cmd; // Command to execute to connect int errors; // How many connection errors BACKEND(const char *_name, const char *_host, int _port){ name = _name; host = _host; port = _port; errors = 0; } BACKEND(const char *_name, const char *_cmd){ name = _name; cmd = _cmd; port = 0; errors = 0; } }; typedef ARRAY_OBJS BACKENDS; /* Open a log file for one tty. Produces some error messages. */ static FILE *mconsole_openlog (const char *dir, const char *file) { SSTRING tmp; tmp.setfromf ("%s/%s.log",dir,file); FILE *ret = fopen64 (tmp.get(),"a+"); if (ret == NULL){ tlmp_error (MSG_U(E_OPENLOG,"Can't open log file %s (%s)") ,tmp.get(),strerror(errno)); }else{ fchmod (fileno(ret),0600); } return ret; } static void mconsole_sendend (FILE *fin, int fd) { char buf[10000]; int n; while ((n=fread(buf,1,sizeof(buf),fin))>0) write (fd,buf,n); } /* Copy the last 10k of logs to a file handle */ static void mconsole_copylast ( FILE *log, // Already opened log file, potentially renamed const char *dir, const char *file, int fd) { struct stat st; if (fstat(fileno(log),&st)==-1){ tlmp_error (MSG_U(E_FSTAT,"Can't fstat a log file (%s/%s.log), odd (%s)") ,dir,file,strerror(errno)); }else{ int mustsend = 10000; if (st.st_size < 10000){ // We have to send the .last file as well // See comment later in the reopenlog function to learn more // about this SSTRING tmp; tmp.setfromf ("%s/%s.last",dir,file); FILE *fin = fopen (tmp.get(),"r"); mustsend = st.st_size; if (fin != NULL){ fseek (fin,st.st_size - 10000,SEEK_END); mconsole_sendend (fin,fd); fclose (fin); } } fseek (log,-mustsend,SEEK_END); mconsole_sendend (log,fd); } } /* Close the log file, and reopen This is usually requested in logrotate. The logs are renamed (while they are opened) and then the openlog request is performed. */ static void mconsole_reopenlog ( HANDLE_INFOS &infos, const char *logdir) { for (int i=0; iserial){ // We first copy the last 10k in the .last file // so even if we have many logrotate operation // we can still keep the last 10k const char *name = n->name.c_str(); SSTRING last; last.setfromf ("%s/%s.last",logdir,name); SSTRING newlast; newlast.setfromf ("%s.new",last.get()); FILE *fout = fopen (newlast.get(),"w"); if (fout == NULL){ tlmp_error (MSG_U(E_OPENLASTNEW,"Can't open temp file %s (%s)") ,newlast.get(),strerror(errno)); }else{ mconsole_copylast (n->log,logdir,name,fileno(fout)); fclose (fout); if (rename (newlast.get(),last.get())==-1){ tlmp_error (MSG_U(E_RENAME,"Can't rename %s -> %s (%s)") ,newlast.get(),last.get(),strerror(errno)); } } if (n->log != NULL) fclose (n->log); n->log = mconsole_openlog (logdir,n->name.c_str()); } } } static int mconsole_cnvbaud (const char *pt) { int ret = 9600; if (pt == NULL){ tlmp_error (MSG_U(E_MISSBAUD ,"Missing -b or --baudrate value, using 9600")); }else if (!isdigit(pt[0])){ tlmp_error (MSG_U(E_IVLDBAUDS ,"Invalid serial speed %s, exiting") ,pt); exit (-1); }else{ ret = atoi(pt); if (baudlk.count(ret)!=1){ tlmp_error (MSG_R(E_IVLDBAUDS),pt); exit(-1); } } return ret; } static int mconsole_exec (const char *cmd, int fd) { int ret = -1; pid_t pid = fork(); if (pid == (pid_t)0){ dup2 (fd,0); dup2 (fd,1); dup2 (fd,2); for (int i=3; i<1024; i++) close (i); system (cmd); _exit (0); }else if (pid == (pid_t)-1){ tlmp_error (MSG_U(E_CANTFORK,"Can't fork a new process (%s)\n"),strerror(errno)); }else{ ret = 0; } return ret; } int main (int argc, char *argv[]) { glocal const char *sockpath = "unix:/var/run/mconsole.sock"; glocal const char *logpath = "/var/log/mconsole"; glocal bool daemon = false; glocal vector comtrols; glocal vector serials; glocal vector ipmis; glocal const char *pidfile = NULL; int ret = (argc,argv,"mconsole"); setproginfo ("mconsole",PACKAGE_REV ,MSG_U(I_MCONSOLE,"Serial console manager")); setarg ('c',"comtrol",MSG_U(I_COMTROL,"Comtrol configuration"),glocal.comtrols,false); setarg ('i',"ipmi",MSG_U(I_IPMI,"IPMI devices configuration"),glocal.ipmis,false); setarg ('S',"serial",MSG_U(I_SERIAL,"Serial devices configuration"),glocal.serials,false); setgrouparg(""); setarg ('d',"daemon",MSG_U(I_DAEMON,"Run in background"),glocal.daemon,false); setarg ('l',"logdir",MSG_U(I_LOGDIR,"Log file directory"),glocal.logpath,false); setarg ('p',"pidfile",MSG_U(I_PIDFILE,"Write the process ID in file"),glocal.pidfile,false); setarg ('s',"socket",MSG_R(I_SOCKET),glocal.sockpath,false); fprintf (stderr,"%s\n",msg); syslog (LOG_ERR,"%s",msg); mconsole_initbaud(); glocal HANDLE_INFOS infos; glocal BACKENDS backends; glocal int housefd; // Use to talk to ourself to trigger house keeping glocal.infos.neverdelete(); (glocal.sockpath,1); HANDLE_INFO *v = new HANDLE_INFO; v->fd = no; v->name = "--control--"; info.data = v; glocal.infos.add (v); debug_printf("Newclient %d\n",no); setrawmode (false); // Connecting client are line oriented until they // establish a connection with a back-end. HANDLE_INFO *v = (HANDLE_INFO*)info.data; debug_printf ("endclient %d name=%s serial=%d\n",no,v->name.c_str(),v->serial); if (v->serial){ // Close de various client connected to this back-end/serial for (int i=0; ilinked == no){ sendtof (n->fd,MSG_U(I_LOSTBACKEND,"Back-end %s has disconnected\r\nEnding\r\n"),v->name.c_str()); debug_printf ("Closing client %d connected to host %s port %d\n" ,n->fd,v->host.c_str(),v->port); closeclient(n->fd); glocal.infos.remove (n); } } if (v->host.size() > 0){ debug_printf ("Lost connection to backend %s, try to reconnect: host=%s port=%d\n" ,v->name.c_str(),v->host.c_str(),v->port); // Try to reconnect glocal.backends.add(new BACKEND (v->name.c_str(),v->host.c_str(),v->port)); house_keeping (glocal.housefd); }else if (v->cmd.size() > 0){ debug_printf ("Lost connection to ipmi backend %s, try to reconnect\n" ,v->name.c_str()); // Try to reconnect glocal.backends.add(new BACKEND (v->name.c_str(),v->cmd.c_str())); house_keeping (glocal.housefd); } } glocal.infos.remove (v); glocal HANDLE_INFO *v = (HANDLE_INFO*)info.data; //debug_printf ("Receive %d %d %s\n",no,info.linelen,line); //debug_printf ("v->serial %d v->log %p v->linked %d\n",glocal.v->serial,glocal.v->log,glocal.v->linked); if (glocal.v->serial){ (glocal.v->buf,line,info.linelen); // Check for escape int lensend = len; int used = len; int state = -1; const char *line = (const char *)buf; for (int i=0; i= 0 && (line[i] == badseq1[state] || line[i] == badseq2[state])){ state++; if (state == lenseq){ used = i+1; debug_printf ("badseq found\n"); break; } }else{ state = -1; lensend = len; used = len; } } if (lensend > 0){ if (glocal.v->log != NULL){ fwrite (line,1,lensend,glocal.v->log); fflush (glocal.v->log); } int no = glocal.v->fd; // Send to the various listener for (int i=0; ilinked == no){ glocal.TCPSERVER.sendto (n->fd,line,lensend); } } } return used; }else if (glocal.v->linked != -1){ // Check break for (int i=0; ilinked,1000); }else{ sendto (glocal.v->linked,line+i,1); } } }else if (strcmp(glocal.v->name.c_str(),house_keeping_name)==0){ // debug_printf ("House keeping\n"); set failhosts; // List of host failing to connect // comtrol have many ports on the same IP // so if a comtrol is down, no need to try to connect // to all ports (they will fail one by one). for (int i=0; iname.c_str(); if (b->cmd.size() > 0){ int tb[2]; if (socketpair(AF_UNIX,SOCK_STREAM,PF_UNIX,tb) == -1){ tlmp_error (MSG_U(E_SOCKETPAIR,"Can't setup socketpair for ipmi %s (%s)\n"),name,strerror(errno)); }else{ if (mconsole_exec(b->cmd.c_str(),tb[1])==-1){ close (tb[0]); close (tb[1]); }else{ close (tb[1]); HANDLE_INFO *info = new HANDLE_INFO; info->serial = true; info->log = mconsole_openlog (glocal.logpath,name); info->name = name; info->fd = tb[0]; info->cmd = b->cmd; glocal.infos.add (info); inject (tb[0],info); glocal.backends.remove_del(b); i--; } } }else{ const char *host = b->host.c_str(); if (failhosts.count(host) == 0){ int port = b->port; int fd = cmdsock_connect (host,port,1,1); debug_printf ("Connecting backend %s, host=%s port=%d, fd=%d\n",name,host,port,fd); if (fd == -1){ if (b->errors == 0){ tlmp_error (MSG_U(E_COMTROLCONNECT,"Can't connect to %s:%d (server %s)") ,host,port,name); } b->errors++; failhosts.insert (host); }else{ HANDLE_INFO *info = new HANDLE_INFO; info->serial = true; info->log = mconsole_openlog (glocal.logpath,name); info->name = name; info->fd = fd; info->host = host; info->port = port; glocal.infos.add (info); inject (fd,info); glocal.backends.remove_del(b); i--; } } } } }else{ if (strncmp(line,"openlog",7)==0){ send ("Re-opening log\n"); mconsole_reopenlog (glocal.infos,glocal.logpath); endclient = true; }else if (strncmp(line,"connect ",8)==0){ SSTRING tmp(line); tmp.strip_end(); const char *name = tmp.get()+8; debug_printf ("name :%s:\n",name); for (int i=0; iname == name){ setrawmode (true); glocal.v->linked = n->fd; glocal.v->name = name; debug_printf("Found handle %d\n",n->fd); mconsole_copylast (n->log,glocal.logpath,n->name.c_str(),no); break; } } if (glocal.v->linked == -1){ send (MSG_U(I_IVLDCONNAME,"Invalid/unavailable connection name\r\n")); endclient = true; } }else if (strncmp(line,"dump",4)==0){ for (int i=0; iname.c_str(),n->serial,n->linked ,n->host.c_str(),n->port); } for (int i=0; iname.c_str() ,n->host.c_str(),n->port); } endclient = true; }else{ debug_printf ("Invalid command length=%d: %s\n",info.linelen,line); send (MSG_U(I_IVLDCMD,"Invalid command\r\n")); endclient = true; } } signal (SIGCHLD,SIG_IGN); signal (SIGPIPE,SIG_IGN); serv.setrawmode (true); glocal TCPSERVER_V1 *serv = &serv; if (glocal.serials.size()==0 && glocal.comtrols.size()==0 && glocal.ipmis.size()==0){ tlmp_error (MSG_U(E_MISSOPT,"At least one option --serial, --comtrol or --ipmi must be used\n")); usage(); }else{ for (unsigned i=0; i(glocal.fname,true); SSTRINGS words; if (strncmp(line,"baud=",5)==0){ glocal.baud = atoi(line+5); if (baudlk.count(glocal.baud)!= 1){ tlmp_error (MSG_U(E_IVLDBAUD,"File %s: Invalid serial speed %s, exiting") ,glocal.fname,line); exit (-1); } }else if (line[0] != '#' && str_splitline (line,' ',words)==2){ const char *device = words.getitem(1)->c_str(); const char *name = words.getitem(0)->c_str(); int fd = open (device,O_NDELAY|O_RDWR); if (fd == -1){ tlmp_error (MSG_U(E_OPENDEV,"Can't open serial device %s (%s)") ,device,strerror(errno)); }else{ struct termios term; tcgetattr (fd,&term); speed_t sp = baudlk[glocal.baud]; cfsetispeed (&term,sp); cfsetospeed (&term,sp); cfmakeraw (&term); tcsetattr (fd,TCSANOW,&term); HANDLE_INFO *info = new HANDLE_INFO; info->serial = true; info->log = mconsole_openlog (glocal.logpath,name); info->name = name; info->fd = fd; glocal.infos.add (info); glocal.serv->inject (fd,info); } } return 0; } for (unsigned i=0; i(fname,true); vector words; if (strncmp(line,"host=",5)==0){ glocal.host = line+5; }else if (strncmp(line,"port=",5)==0){ glocal.port = atoi(line+5); }else if (line[0] != '#' && str_splitline (line,' ',words)==2){ const char *name = words[0].c_str(); int port = glocal.port + atoi(words[1].c_str()); glocal.backends.add(new BACKEND (name,glocal.host.c_str(),port)); } return 0; } for (unsigned i=0; i(fname,true); string name; const char *pt = str_copyword (name,line); pt = str_skip(pt); if (*pt != '\0'){ glocal.backends.add(new BACKEND (name.c_str(),pt)); } return 0; } if (glocal.daemon){ daemon_init (glocal.pidfile,NULL); } { // Setup a sub-process that trigger an alarm to do house keeping (open backend connections) int tb[2]; if (pipe(tb)==-1){ tlmp_error (MSG_U(E_NOPIPE,"Can't setup the pipe for house keeping (%s)\nAborting\n"),strerror(errno)); exit (-1); } glocal.housefd = tb[1]; HANDLE_INFO *v = new HANDLE_INFO; v->name = house_keeping_name; debug_printf ("Inject house keeping %d\n",tb[0]); serv.inject (tb[0],v); pid_t pid = fork(); if (pid == (pid_t)-1){ tlmp_error (MSG_U(E_FORKHOUSE,"Can't fork for house keeping (%s)\n"),strerror(errno)); }else if (pid == (pid_t)0){ close (tb[0]); house_keeping (glocal.housefd); while (1){ sleep (5); if (house_keeping (glocal.housefd)==-1) break; } _exit (0); } } serv.loop(); debug_printf ("No more work, ending\n"); } return 0; return ret; }