#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmdsock.h" #define DBGALL 1 struct SOCK_INFO{ int handle; // Handle (not only sockets) struct { int set; // Max delay without activity time_t last;// Last time there was activity }idle; bool is_active; // Any activity on this handle ? }; void logevent(const char *fmt, ...) { va_list list; va_start (list,fmt); char buf[3000]; vsprintf (buf,fmt,list); syslog (LOG_ERR,buf); va_end (list); } void logdebug (int level, const char *fmt, ...) { #if 0 va_list list; va_start (list,fmt); char buf[3000]; int len = sprintf (buf,"Debug level %d : ",level); vsprintf (buf+len,fmt,list); syslog (LOG_DEBUG,buf); va_end (list); #endif } /* Get the port number from /etc/service If the service is a number, it is used directly. */ static int cmdsock_getport(const char *service) { int ret = -1; struct servent *serv = getservbyname (service,"tcp"); if (serv == NULL){ const char *pt = service; while (isdigit(*pt)) pt++; if (isdigit(*service) && *pt == '\0'){ ret = atoi(service); }else{ logevent ("No service %s in /etc/service",service); } }else{ ret = ntohs(serv->s_port); } return ret; } PROTECTED void CMDSOCK::baseinit() { listen_handle = -1; nbcli = 0; inf = (SOCK_INFO*)malloc(100*sizeof(SOCK_INFO)); maxcli = 100; } PRIVATE void CMDSOCK::init( const char *bindaddr, int port, int reuseadr) // set SO_REUSEADDR { baseinit(); if (port != -1){ /* Il faut creer le socket original pour la connection */ struct hostent *h = NULL; if (bindaddr != NULL){ h = (struct hostent *) gethostbyname(bindaddr); } if (h == NULL){ syslog (LOG_ERR,"gethostbyname(%s) failed",bindaddr); }else{ struct sockaddr_in sin; sin.sin_family = AF_INET; if (h != NULL){ memcpy (&sin.sin_addr,h->h_addr, h->h_length); }else{ memset (&sin.sin_addr,0,sizeof(sin.sin_addr)); } sin.sin_port = htons(port); int i; for (i=0; i<5; i++){ listen_handle = socket (AF_INET, SOCK_STREAM, 0); if (bindaddr == NULL) sin.sin_addr.s_addr = INADDR_ANY; int opt = 1; if (listen_handle == -1){ logdebug (DBGALL,"listen_handle %d(%s)\n" ,errno ,strerror(errno)); }else if (reuseadr && setsockopt(listen_handle,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))==-1){ fprintf (stderr,"Can't set socket option SO_REUSEADDR (%s)\n" ,strerror (errno)); }else if (bind (listen_handle,(struct sockaddr *) &sin, sizeof (sin)) == -1){ logdebug (DBGALL,"bind %d(%s)\n",errno ,strerror(errno)); }else if (::listen (listen_handle,35) == -1){ logdebug (DBGALL,"listen %d(%s)\n",errno ,strerror(errno)); break; }else{ logdebug (DBGALL,"bind ok\n"); break; } close (listen_handle); listen_handle = -1; if (i < 5) sleep (i*5); } } }else{ /* #Specification: CMDSOCK / via inetd A server using CMDSOCK can be started with inetd. We specify -1 as the port number CMDSOCK will then use the handle 0 to wait for connections. */ listen_handle = 0; } } PROTECTED CMDSOCK::CMDSOCK() { } PRIVATE void CMDSOCK_UNIX::initunix(const char *sockn) { baseinit(); unlink (sockn); int fd = socket (AF_UNIX,SOCK_STREAM,0); if (fd == -1){ perror("socket server"); }else{ struct sockaddr_un un; un.sun_family = AF_UNIX; strcpy (un.sun_path,sockn); if (bind(fd,(struct sockaddr*)&un,sizeof(un))==-1){ perror("bind"); }else{ chmod (sockn,0600); int code = ::listen (fd,10); if (code == -1){ perror ("listen"); }else{ listen_handle = fd; } } } } /* Ouverture d'un socket en mode listen pour serveur. */ PUBLIC CMDSOCK::CMDSOCK(const char *portname, int reuseaddr) { init (NULL,cmdsock_getport(portname),reuseaddr); } /* Ouverture d'un socket en mode listen pour serveur. */ PUBLIC CMDSOCK::CMDSOCK(const char *bindaddr, const char *portname, int reuseaddr) { init (bindaddr,cmdsock_getport(portname),reuseaddr); } /* Ouverture d'un socket en mode listen pour serveur. */ PUBLIC CMDSOCK::CMDSOCK(int port, int reuseaddr) { init (NULL,port,reuseaddr); } /* Ouverture d'un socket en mode listen pour serveur. */ PUBLIC CMDSOCK::CMDSOCK(const char *bindaddr, int port, int reuseaddr) { init (bindaddr,port,reuseaddr); } /* Ouverture d'un socket unix en mode listen pour serveur. */ PUBLIC CMDSOCK_UNIX::CMDSOCK_UNIX(const char *sockpath) { initunix (sockpath); } PUBLIC CMDSOCK::~CMDSOCK() { if (listen_handle != -1){ SOCK_INFO *pt = inf; for (int i=0; ihandle); close (listen_handle); } free (inf); } /* Retourne != 0 si le serveur est correctement initialise */ PUBLIC int CMDSOCK::is_ok() { return listen_handle != -1; } /* Forget a handle (without closing it) */ PUBLIC void CMDSOCK::forgetcli(int fd) { int dst = 0; SOCK_INFO *pt = inf; for (int i=0; ihandle != fd){ inf[dst++] = inf[i]; } } nbcli = dst; } /* Close the connection with a client */ PUBLIC void CMDSOCK::closecli(int fd) { close (fd); forgetcli(fd); } /* Add a handle to manage. Does also timeout (idle time) management. The timeout is expressed in seconds. The caller has accepted the connection. This function is usually called by an application using CMDSOCK to provide a service and also acting as a client to other servers. By adding handle here, we have a central place to monitor activity. */ PUBLIC void CMDSOCK::addcli (int fd, int timeout) { if (fd >= 0){ if (nbcli == maxcli){ maxcli += 100; inf = (SOCK_INFO*)realloc(inf,maxcli*sizeof(SOCK_INFO)); if (inf == NULL){ close (fd); syslog (LOG_CRIT,"Out of memory SOCK_INFO[]"); return; } } SOCK_INFO *pt = inf + nbcli++; pt->handle = fd; pt->idle.set = timeout; pt->idle.last = time(NULL); pt->is_active = false; } } /* Set a maximum idle time on a connection */ PUBLIC void CMDSOCK::set_timeout (int fd, int timeout) { SOCK_INFO *pt = inf; for (int i=0; ihandle == fd){ pt->idle.set = timeout; break; } } } /* Ajoutte un handle de plus au serveur. */ PUBLIC void CMDSOCK::addcli (int fd) { addcli (fd,0); } /* Insert the various handle on which it is listening in the 3 sets. Return the largest handle inserted (or maxhandle if none were larger) CMDSOCK::process_select() is generally called after the select() */ PUBLIC int CMDSOCK::setup_select ( fd_set &set, // set for input int max_handle) // Largest handle in the sets so far { SOCK_INFO *pt = inf; //syslog (LOG_DEBUG,"::listen nbcli %d",nbcli); for (int i=0; ihandle; if (handle > 200){ syslog (LOG_CRIT,"handle = %d",handle); }else{ pt->is_active = false; FD_SET (handle,&set); if (handle > max_handle) max_handle = handle; } } if (listen_handle != -1){ FD_SET (listen_handle,&set); if (listen_handle > max_handle) max_handle = listen_handle; } return max_handle; } /* Attend de l'activite sur un handle parmi plusieurs ou une connection sur un socket. Return -1 if any error. Return 0 if the timeout was met. Return > 0 if something is available (or simply a new connexion). In that case, readnext() should be called to get some work. */ PUBLIC int CMDSOCK::listen ( long timeout, // seconds // -1 = no timeout // 0 = pollmode int &newclient) { fd_set set; FD_ZERO(&set); int maxhd = setup_select (set,0); struct timeval timeo; timeo.tv_sec = timeout; timeo.tv_usec = 0; fd_set spcset; spcset = set; struct timeval *pttimeo = NULL; if (timeout != -1) pttimeo = & timeo; int sel = select (maxhd+1,&set,NULL,&spcset,pttimeo); return process_select (sel,set,newclient,timeout); } /* Must be called after a select(), even if it is a timeout Then readnext() is called repeatedly to process each socket as needed. */ PUBLIC int CMDSOCK::process_select ( int sel, // Value returned by select() fd_set &set, int &newclient, long timeout) { newclient = -1; active = 0; int ret = 0; if (sel > 0){ time_t now = time(NULL); if (listen_handle != -1 && FD_ISSET(listen_handle,&set) != 0){ char sacc[100]; unsigned int size=100; int fd = accept (listen_handle,(struct sockaddr *)sacc ,&size); addcli (fd); newclient = fd; } SOCK_INFO *pt = inf; for (int i=0; ihandle; //if (FD_ISSET(handle,&spcset)!=0){ // logdebug (DBGALL,"client %d dans spcset\n",handle); //} if (FD_ISSET(handle,&set)){ pt->is_active = true; pt->idle.last = now; } } ret = 1; } return ret; } /* Attend de l'activite sur un handle parmi plusieurs ou une connection sur un socket. Return -1 if any error. Return 0 if the timeout was met. Return > 0 if something is available (or simply a new connexion). In that case, readnext() should be called to get some work. */ PUBLIC int CMDSOCK::listen ( long timeout) // seconds // -1 = no timeout // 0 = pollmode { int fd; return listen (timeout,fd); } /* Retourne le nombre de connexion courramment monitoré. Cette fonction est généralement utilisée pour décider si on doit terminer le service */ PUBLIC int CMDSOCK::getnbcli() { return nbcli; } /* Read one packet from the next client. Returns 0 if the client has closed the connection. Returns -1 if there is no more client to process. Returns the number of bytes written into buf. cli will contain the file handle of the client, which may be used to talk back. */ PUBLIC int CMDSOCK::readnext(void *buf, int size, int &cli, bool &is_timeout) { int ret = -1; SOCK_INFO *pt = inf + active; time_t now = time(NULL); is_timeout = false; for ( ; active < nbcli; active++, pt++){ if (pt->is_active){ cli = pt->handle; logdebug (DBGALL,"Transaction du client %d\n",cli); int nb = read (cli,buf,size); if (nb > 0){ ret = nb; active++; }else{ logdebug (DBGALL,"Client %d a ferme la connexion\n",cli); closecli (cli); ret = 0; } break; }else if (pt->idle.set > 0){ if ((now - pt->idle.last) > pt->idle.set){ is_timeout = true; cli = pt->handle; ret = 0; closecli (cli); syslog (LOG_INFO,"Stale connexion %d, closing",cli); break; } } } return ret; } /* Returns the number of clients with pending requests */ PUBLIC int CMDSOCK::getnbpending() { int ret = 0; SOCK_INFO *pt = inf + active; for (int i=active; i < nbcli; i++, pt++){ if (pt->is_active) ret++; } return ret; } static void fsig (int) { // fprintf (stderr,"signal\n"); } /* Install the signal handler and set the alarm */ static void cmdsock_setsig(int time_out) { static struct sigaction siga; siga.sa_handler = fsig; sigaction (SIGALRM,&siga,NULL); alarm (time_out); } /* Remove the signal handler and reset the alarm */ static void cmdsock_resetsig () { static struct sigaction siga; siga.sa_handler = SIG_DFL; sigaction (SIGALRM,&siga,NULL); alarm (0); } static int cmdsock_sinconnect (struct sockaddr_in &sin, int nbretry) { int ret = -1; for (int i=0; i= 0) { logdebug (DBGALL,"avantconnect %d\n",s); if (connect (s, (struct sockaddr *) &sin, sizeof (sin)) == -1){ if (i==0){ logdebug (DBGALL,"Can't connect (%s)\n" ,strerror(errno)); } close (s); if (errno == EINTR) break; sleep(1); }else{ ret = s; break; } }else{ logdebug (DBGALL,"socket"); } } return ret; } /* Establish a TCP connection. Return -1 if any error or time out. */ EXPORT int cmdsock_connect ( const char *servname, int port, int time_out, // Time_out (seconds) to establish connection int nbretry) { int ret = -1; cmdsock_setsig (time_out); struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(port); if (ipnum_validip (servname,true)){ long addr = htonl(ipnum_aip2l (servname)); memcpy (&sin.sin_addr,&addr, sizeof(addr)); ret = cmdsock_sinconnect (sin,nbretry); }else{ struct hostent *h = (struct hostent *) gethostbyname(servname); if (h == NULL){ logevent ("No server \"%s\" defined",servname); }else{ memcpy (&sin.sin_addr,h->h_addr, h->h_length); ret = cmdsock_sinconnect (sin,nbretry); } } cmdsock_resetsig(); return ret; } /* Establish a TCP or Unix domain connection. Retourne -1 si erreur. */ EXPORT int cmdsock_connect ( const char *servname, // host name or "unix:" const char *portname, // TCP port number or name, or unix socket int time_out, // Time_out (seconds) to establish connection int nbretry) { int ret = -1; if (strcmp(servname,"unix:")==0){ int fd = socket (AF_UNIX,SOCK_STREAM,0); if (fd == -1){ logevent ("Can't create socket\n"); }else{ struct sockaddr_un un; un.sun_family = AF_UNIX; strcpy (un.sun_path,portname); int s = connect(fd,(struct sockaddr*)&un,sizeof(un)); if (s == -1){ close (fd); }else{ ret = fd; } } }else{ cmdsock_setsig (time_out); int port = cmdsock_getport(portname); if (port != -1){ ret = cmdsock_connect (servname,port,time_out,nbretry); } cmdsock_resetsig(); } return ret; } /* Retourne le nombre de handle prêt pour une lecture (data disponible) Retourne 0 si le timeout est écoulé. Retourne -1 s'il y a un erreur. */ int cmdsock_wait (int nbfds, int fds[],int ready[], long timeout) { fd_set set; FD_ZERO (&set); int maxfd = 0; int i; for (i=0; i maxfd) maxfd = fd; } struct timeval timeo; timeo.tv_sec = timeout; timeo.tv_usec = 0; int ret = select (maxfd+1,&set,NULL,NULL ,timeout == -1 ? (struct timeval*)NULL : &timeo); if (ret > 0){ ret = 0; for (i=0; i",buf); write (cli,repond,nb); } } } } } }else if (strncmp(argv[1],"cli",3)==0){ while (1){ char buf[200]; printf ("Entrer n'importe quoi "); fflush (stdout); fgets(buf,sizeof(buf)-1,stdin); char retbuf[200]; if (cmdsock_sendmsg (retbuf,sizeof(retbuf),"%s",buf)==-1){ fprintf (stderr,"Ne peut transmettre au serveur\n"); }else{ printf ("Recoit :%s:\n",retbuf); } } }else{ usage_test(); } }else{ usage_test(); } return 0; } #endif