/*************************************************************************** CServerSocket.c Network component (c) 2003-2004 Daniel Campos Fernández This is the implementation of the SocketServer class This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ***************************************************************************/ #define __CSERVERSOCKET_C #include #include #include #include #include #include #include #include #include "main.h" #include "tools.h" #include "CServerSocket.h" #include "CSocket.h" #define UNIXPATHMAX 108 DECLARE_EVENT (Connection); DECLARE_EVENT (CSERVERSOCKET_Error); void srvsock_post_error(CSERVERSOCKET* mythis) { GB.Raise((void*)mythis,CSERVERSOCKET_Error,0); GB.Unref(POINTER(&mythis)); } // BM: Fix bug in allocations void CServerSocket_NewChild(CSERVERSOCKET *mythis,void *cli_obj) { if (mythis->nchildren++) GB.Realloc ( POINTER(&mythis->children),mythis->nchildren * sizeof(*mythis->children)); else GB.Alloc ( POINTER(&mythis->children),mythis->nchildren * sizeof(*mythis->children)); mythis->children[mythis->nchildren-1]=cli_obj; } void CServerSocket_DeleteChild(CSERVERSOCKET *mythis,void *cli_obj) { int myloop; int myloop2; if (!mythis->nchildren) return; for (myloop=0;myloopnchildren;myloop++) { if (mythis->children[myloop]==cli_obj) { for (myloop2=myloop;myloop2<(mythis->nchildren-1);myloop2++) mythis->children[myloop2]=mythis->children[myloop2+1]; if ( --mythis->nchildren) { GB.Realloc ( POINTER(&mythis->children),mythis->nchildren * sizeof(*mythis->children)); } else { GB.Free (POINTER(&mythis->children)); mythis->children=NULL; } return; } } } void CServerSocket_OnClose(void *sck) { CSOCKET *chd=(CSOCKET*)sck; CSERVERSOCKET *mythis; if (!chd) return; if (!chd->c_parent) return; CServerSocket_DeleteChild(chd->c_parent,sck); mythis=(CSERVERSOCKET*)chd->c_parent; mythis->iCurConn--; } void CServerSocket_CallBack(int fd,int type,long lParam) { int okval=0; char *rem_ip_buf; unsigned int ClientLen; CSERVERSOCKET *mythis; mythis=(CSERVERSOCKET*)lParam; if ( mythis->iStatus != 1) return; mythis->iStatus=2; ClientLen=sizeof(struct sockaddr_in); mythis->Client=accept(mythis->ServerSocket,(struct sockaddr*)&mythis->so_client.in,&ClientLen); if (mythis->Client == -1) { close(mythis->Client); mythis->iStatus=1; return; } rem_ip_buf=inet_ntoa(mythis->so_client.in.sin_addr); if ( (!mythis->iMaxConn) || (mythis->iCurConn < mythis->iMaxConn) ) okval=1; if ( (!mythis->iPause) && (okval) ) GB.Raise(mythis,Connection,1,GB_T_STRING,rem_ip_buf,0); if ( mythis->iStatus == 2) close(mythis->Client); mythis->iStatus=1; } void CServerSocket_CallBackUnix(int fd,int type,long lParam) { //int position=0; int okval=0; unsigned int ClientLen; CSERVERSOCKET *mythis; mythis=(CSERVERSOCKET*)lParam; if ( mythis->iStatus != 1) return; mythis->iStatus=2; ClientLen=sizeof(struct sockaddr_un); mythis->Client=accept(mythis->ServerSocket,(struct sockaddr*)&mythis->so_client.un,&ClientLen); if (mythis->Client == -1) { close(mythis->Client); mythis->iStatus=1; return; } if ( (!mythis->iMaxConn) || (mythis->iCurConn < mythis->iMaxConn) ) okval=1; if ( (!mythis->iPause) && (okval) ) GB.Raise(mythis,Connection,1,GB_T_STRING,NULL,0); if ( mythis->iStatus == 2) close(mythis->Client); mythis->iStatus=1; } /*************************************************** This property reflects current status of the socket (closed, listening...) ***************************************************/ BEGIN_PROPERTY ( CSERVERSOCKET_MaxPathLength ) GB.ReturnInteger(UNIXPATHMAX); END_PROPERTY /*************************************************** This property reflects current status of the socket (closed, listening...) ***************************************************/ BEGIN_PROPERTY ( CSERVERSOCKET_Status ) GB.ReturnInteger(THIS->iStatus); END_PROPERTY /****************************************************************** This property gets/sets the port to listen to (TCP or UDP sockets) ******************************************************************/ BEGIN_PROPERTY ( CSERVERSOCKET_Port ) if (READ_PROPERTY) { GB.ReturnInteger(THIS->iPort); return; } if (THIS->iStatus>0) { GB.Error("Port value can not be changed when socket is active"); return; } if ( (VPROP(GB_INTEGER)<1) || (VPROP(GB_INTEGER)>65535) ) { GB.Error("Invalid Port Value"); return; } THIS->iPort=VPROP(GB_INTEGER); END_PROPERTY /*************************************************************** This property gets/sets the address to listen to (UNIX socket) ***************************************************************/ BEGIN_PROPERTY ( CSERVERSOCKET_Path ) char *tmpstr=NULL; if (READ_PROPERTY) { GB.ReturnString(THIS->sPath); return; } if (THIS->iStatus>0) { GB.Error("Path value can not be changed when socket is active"); return; } tmpstr=GB.ToZeroString ( PROP(GB_STRING) ); if ( (strlen(tmpstr)<1) || (strlen(tmpstr)>UNIXPATHMAX) ) { GB.Error ("Invalid path length"); return; } GB.StoreString(PROP(GB_STRING), &THIS->sPath); END_PROPERTY /*************************************************************** This property gets/sets the socket type (0 -> TCP, 1 -> UNIX) ***************************************************************/ BEGIN_PROPERTY ( CSERVERSOCKET_Type ) if (READ_PROPERTY) { GB.ReturnInteger(THIS->iSockType); return; } if (THIS->iStatus>0) { GB.Error("Socket Type can not be changed when socket is active"); return; } if ( (VPROP(GB_INTEGER)<0) || (VPROP(GB_INTEGER)>1) ) { GB.Error("Invalid Socket Type Value"); return; } THIS->iSockType=VPROP(GB_INTEGER); END_PROPERTY /*********************************************** Gambas object "Constructor" ***********************************************/ BEGIN_METHOD(CSERVERSOCKET_new,GB_STRING sPath;GB_INTEGER iMaxConn;) int retval; char *buf=NULL; int nport=0; int iMax=0; THIS->iPort=0; THIS->iStatus=0; THIS->sPath=NULL; THIS->iPause=0; THIS->iMaxConn=0; THIS->iCurConn=0; THIS->iSockType=1; THIS->children=NULL; THIS->nchildren=0; if (MISSING(sPath)) return; if (!STRING(sPath)) return; if (!MISSING(iMaxConn)) iMax=VARG(iMaxConn); retval=IsHostPath(STRING(sPath),&buf,&nport); if (!retval) { GB.Error("Invalid Host / Path string"); return; } if (retval==2) { THIS->iSockType=0; buf=GB.ToZeroString ( (GB_STRING*)STRING(sPath) ); if ( (strlen(buf)<1) || (strlen(buf)>UNIXPATHMAX) ) { GB.Error ("Invalid path length"); return; } GB.StoreString((GB_STRING*)STRING(sPath),&THIS->sPath); return; } else { if (buf) { GB.Free(POINTER(&buf)); GB.Error("Invalid Host String"); return; } if (nport<1) { GB.Error("Invalid Port value"); return; } THIS->iSockType=1; THIS->iPort=nport; } switch(srvsock_listen(THIS,iMax)) { case 1: GB.Error("Socket is already listening"); return; case 7: GB.Error ("You must define Path"); return; case 8: GB.Error ("Error. You must define port"); return; case 13: GB.Error ("Invalid maximun connections value"); return; } END_METHOD void close_server(CSERVERSOCKET *mythis) { CSOCKET *chd; if (mythis->iStatus <= 0) return; GB.Watch (mythis->ServerSocket , GB_WATCH_NONE , (void *)CServerSocket_CallBack,0); close(mythis->ServerSocket); mythis->iStatus=0; if (!mythis->nchildren) return; while(mythis->nchildren) { chd=(CSOCKET*)mythis->children[0]; if (chd->stream.desc) CSocket_stream_close(&chd->stream); CServerSocket_DeleteChild(mythis,(void*)chd); } } /************************************************* Gambas object "Destructor" *************************************************/ BEGIN_METHOD_VOID(CSERVERSOCKET_free) close_server(THIS); GB.FreeString(&THIS->sPath); END_METHOD /******************************************************** Stops listening (TCP/UDP/UNIX), and closes all client sockets associated to this server *********************************************************/ BEGIN_METHOD_VOID(CSERVERSOCKET_Close) close_server(THIS); END_METHOD /******************************************************** Do not accept more connections until Resume is used *********************************************************/ BEGIN_METHOD_VOID(CSERVERSOCKET_Pause) THIS->iPause=1; END_METHOD /******************************************************** Accept connections again *********************************************************/ BEGIN_METHOD_VOID(CSERVERSOCKET_Resume) THIS->iPause=0; END_METHOD /********************************************************* Starts listening (TCP/UDP/UNIX) **********************************************************/ int srvsock_listen(CSERVERSOCKET* mythis,int mymax) { int NoBlock=1; int retval; int auth = 1; if ( (!mythis->iPort) && (mythis->iSockType) ) return 8; if ( mythis->iStatus >0 ) return 1; if (mymax<0) return 13; if ( (!mythis->iSockType) && (!mythis->sPath) ) return 7; if (mythis->iSockType) { mythis->so_server.in.sin_family=AF_INET; mythis->so_server.in.sin_addr.s_addr=INADDR_ANY; mythis->so_server.in.sin_port=htons(mythis->iPort); mythis->ServerSocket=socket(PF_INET,SOCK_STREAM,0); } else { unlink(mythis->sPath); mythis->so_server.un.sun_family=AF_UNIX; strcpy(mythis->so_server.un.sun_path,mythis->sPath); mythis->ServerSocket=socket(AF_UNIX,SOCK_STREAM,0); } if ( mythis->ServerSocket==-1 ) { mythis->iStatus=-2; GB.Ref(mythis); GB.Post(srvsock_post_error,(long)mythis); return 2; } // thanks to Benoit : this option allows non-root users to reuse the // port after closing it and reopening it in a short interval of time. // However, If you are porting this component to other O.S., be careful, // as this is not a standard unix option setsockopt(mythis->ServerSocket, SOL_SOCKET, SO_REUSEADDR, &auth, sizeof(int)); // if (mythis->iSockType) retval=bind(mythis->ServerSocket,(struct sockaddr*)&mythis->so_server.in, \ sizeof(struct sockaddr_in)); else retval=bind(mythis->ServerSocket,(struct sockaddr*)&mythis->so_server.un, \ sizeof(struct sockaddr_un)); if (retval==-1) { close(mythis->ServerSocket); mythis->iStatus=-10; GB.Ref(mythis); GB.Post(srvsock_post_error,(long)mythis); return 10; } ioctl(mythis->ServerSocket,FIONBIO,&NoBlock); if ( listen(mythis->ServerSocket,mymax) == -1 ) { close(mythis->ServerSocket); mythis->iStatus=-14; GB.Ref(mythis); GB.Post(srvsock_post_error,(long)mythis); return 14; } mythis->iCurConn=0; mythis->iMaxConn=mymax; mythis->iStatus=1; //CServerSocket_AssignCallBack((long)mythis,mythis->ServerSocket); if (mythis->iSockType) GB.Watch (mythis->ServerSocket , GB_WATCH_READ , (void *)CServerSocket_CallBack,(long)mythis); else GB.Watch (mythis->ServerSocket , GB_WATCH_READ , (void *)CServerSocket_CallBackUnix,(long)mythis); return 0; } BEGIN_METHOD(CSERVERSOCKET_Listen,GB_INTEGER MaxConn;) int retval; int mymax=0; if (!MISSING(MaxConn)) mymax=VARG(MaxConn); retval=srvsock_listen(THIS,mymax); switch(retval) { case 1: GB.Error("Socket is already listening"); return; case 7: GB.Error ("You must define Path"); return; case 8: GB.Error ("Error. You must define port"); return; case 13: GB.Error ("Invalid maximun connections value"); return; } END_METHOD /****************************************************************** To accept a pending connection and delegate it to a Socket object *******************************************************************/ BEGIN_METHOD_VOID(CSERVERSOCKET_Accept) CSOCKET *cli_obj; struct sockaddr_in myhost; unsigned int mylen; if ( THIS->iStatus != 2){ GB.Error("No connection to accept");return; } GB.New(POINTER(&cli_obj),GB.FindClass("Socket"),"Socket",NULL); cli_obj->Socket=THIS->Client; cli_obj->iStatus=7; cli_obj->c_parent=(void*)THIS; cli_obj->OnClose=CServerSocket_OnClose; THIS->iCurConn++; GB.FreeString ( &cli_obj->sRemoteHostIP); GB.FreeString ( &cli_obj->sLocalHostIP); GB.FreeString ( &cli_obj->sPath); cli_obj->iLocalPort=0; cli_obj->iPort=0; cli_obj->conn_type=0; if (THIS->iSockType) { GB.NewString ( &cli_obj->sRemoteHostIP , inet_ntoa(THIS->so_client.in.sin_addr) ,0); GB.NewString ( &cli_obj->Host, inet_ntoa(THIS->so_client.in.sin_addr) ,0); mylen=sizeof(struct sockaddr); getsockname (cli_obj->Socket,(struct sockaddr*)&myhost,&mylen); GB.NewString ( &cli_obj->sLocalHostIP , inet_ntoa(myhost.sin_addr) ,0); cli_obj->iLocalPort=ntohs(myhost.sin_port); cli_obj->iPort=ntohs(THIS->so_client.in.sin_port); cli_obj->iUsePort=ntohs(THIS->so_client.in.sin_port); } else { cli_obj->conn_type=1; GB.NewString ( &cli_obj->sPath,THIS->sPath,0); GB.NewString ( &cli_obj->Path,THIS->sPath,0); } cli_obj->stream.desc=&SocketStream; cli_obj->stream._free[0]=(long)cli_obj; GB.Watch (cli_obj->Socket, GB_WATCH_READ, (void *)CSocket_CallBack,(long)cli_obj); CServerSocket_NewChild(THIS,cli_obj); GB.Ref(cli_obj); GB.Post(CSocket_post_connected,(long)cli_obj); THIS->iStatus=3; GB.ReturnObject((void*)cli_obj); END_METHOD // BM: Enumeration of child sockets BEGIN_METHOD_VOID(CSERVERSOCKET_next) long *index = (long *)GB.GetEnum(); if (*index >= THIS->nchildren) GB.StopEnum(); else { GB.ReturnObject(THIS->children[*index]); (*index)++; } END_METHOD BEGIN_PROPERTY(CSERVERSOCKET_count) GB.ReturnInteger(THIS->nchildren); END_PROPERTY /**************************************************************** Here we declare public structure of the ServerSocket class *****************************************************************/ GB_DESC CServerSocketDesc[] = { GB_DECLARE("ServerSocket", sizeof(CSERVERSOCKET)), GB_EVENT("Connection", NULL, "(RemoteHostIP)s", &Connection), GB_EVENT("Error", NULL,NULL, &CSERVERSOCKET_Error), GB_METHOD("_new", NULL, CSERVERSOCKET_new,"[(Path)s(MaxConn)i]"), GB_METHOD("_free", NULL, CSERVERSOCKET_free, NULL), GB_METHOD("Listen",NULL, CSERVERSOCKET_Listen, "[(MaxConn)i]"), GB_METHOD("Pause", NULL, CSERVERSOCKET_Pause, NULL), GB_METHOD("Resume", NULL, CSERVERSOCKET_Resume, NULL), GB_METHOD("Accept","Socket",CSERVERSOCKET_Accept,NULL), GB_METHOD("Close",NULL,CSERVERSOCKET_Close,NULL), GB_PROPERTY("Type","i",CSERVERSOCKET_Type), GB_PROPERTY("Path","s",CSERVERSOCKET_Path), GB_PROPERTY("Port", "i", CSERVERSOCKET_Port), GB_PROPERTY_READ("Status","i",CSERVERSOCKET_Status), GB_STATIC_PROPERTY_READ("MaxPathLength","i",CSERVERSOCKET_MaxPathLength), GB_METHOD("_next", "Socket", CSERVERSOCKET_next, NULL), GB_PROPERTY_READ("Count", "i", CSERVERSOCKET_count), GB_CONSTANT("_Properties", "s", "Type=0,Path,Port"), GB_CONSTANT("_DefaultEvent", "s", "Connection"), GB_END_DECLARE };