/*! \file beeplisten.c * \brief Implementation of the (BEEP) listener Object. * * Actually, this module implements all the listeners. The initial * release did only care about BEEP, but starting with 0.6.0 additional * listeners are supported. In 0.6.0 at least a standard UDP listener * has been added. Other listeners (eventually not implemented by the * time you read this) are the simple syslog over TCP (aka "SELP") and * the local /var/log "listener" on *nix. * * Please see \ref architecture.c for more details. * * \author Rainer Gerhards * \date 2003-08-13 * File initially created. * \date 2003-09-26 * Changed the logical meaning of this module. It is no longer * the BEEP listener, only. It is now *the* listener for * all supported protocols. This allows us to keep all * listeners on a single thread. * * Copyright 2002-2003 * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Adiscon GmbH or Rainer Gerhards * nor the names of its contributors may be used to * endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "liblogging-config.h" #include "liblogging.h" #include "sockets.h" #include "beepsession.h" #include "beepchannel.h" #include "beepframe.h" #include "beeplisten.h" #include "namevaluetree.h" #include "stringbuf.h" #include "syslogmessage.h" /* ################################################################# * * private members * * ################################################################# */ #if FEATURE_UNIX_DOMAIN_SOCKETS == 1 /** * Process an incoming Unix Domain Socket message. */ static srRetVal sbLstnRecvUXDOMSOCK(sbLstnObj *pThis) { srRetVal iRet; int iLenBuf; int iLenRcvd; char *pszFromHost; srSLMGObj *pSLMG; char szMsgBuf[BEEPFRAMEMAX]; sbLstnCHECKVALIDOBJECT(pThis); iLenBuf = sizeof(szMsgBuf) / sizeof(char); if((iLenRcvd = sbSockReceive(pThis->pSockUXDOMSOCKListening, szMsgBuf, iLenBuf)) < 1) return SR_RET_OK /* for now, this seems to be good enough... */; /* We got the message - pass it to the "API" layer */ if((iRet = srSLMGConstruct(&pSLMG)) != SR_RET_OK) { return iRet; } pSLMG->iSource = srSLMG_Source_UX_DFLT_DOMSOCK; if((iRet = srSLMGSetRawMsg(pSLMG, szMsgBuf, TRUE)) != SR_RET_OK) { srSLMGDestroy(pSLMG); return iRet; } if((iRet = sbSock_gethostname((char**) &pszFromHost)) != SR_RET_OK) { srSLMGDestroy(pSLMG); return iRet; } if((iRet = srSLMGSetRemoteHostIP(pSLMG, pszFromHost, FALSE)) != SR_RET_OK) { srSLMGDestroy(pSLMG); free(pszFromHost); return iRet; } if((iRet = srSLMGParseMesg(pSLMG)) != SR_RET_OK) { srSLMGDestroy(pSLMG); return iRet; } pThis->pAPI->OnSyslogMessageRcvd(pThis->pAPI, pSLMG); srSLMGDestroy(pSLMG); return SR_RET_OK; } #endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ #if FEATURE_UDP == 1 /** * Process an incoming UDP message. * * Please note that in UDP, we do not have any profiles. As such, we can * call the API handler immediately (we know what to do in our processing). */ static srRetVal sbLstnRecvUDP(sbLstnObj *pThis) { srRetVal iRet; int iLenBuf; char *pszFromHost; srSLMGObj *pSLMG; char szMsgBuf[BEEPFRAMEMAX]; sbLstnCHECKVALIDOBJECT(pThis); iLenBuf = sizeof(szMsgBuf) / sizeof(char); if((iRet = sbSockRecvFrom(pThis->pSockUDPListening, szMsgBuf, &iLenBuf, &pszFromHost)) != SR_RET_OK) return iRet; /* We got the message - pass it to the "API" layer */ if((iRet = srSLMGConstruct(&pSLMG)) != SR_RET_OK) { return iRet; } pSLMG->iSource = srSLMG_Source_UDP; if((iRet = srSLMGSetRawMsg(pSLMG, szMsgBuf, TRUE)) != SR_RET_OK) { srSLMGDestroy(pSLMG); return iRet; } if((iRet = srSLMGSetRemoteHostIP(pSLMG, pszFromHost, FALSE)) != SR_RET_OK) { srSLMGDestroy(pSLMG); free(pszFromHost); return iRet; } if((iRet = srSLMGParseMesg(pSLMG)) != SR_RET_OK) { srSLMGDestroy(pSLMG); free(pszFromHost); return iRet; } pThis->pAPI->OnSyslogMessageRcvd(pThis->pAPI, pSLMG); srSLMGDestroy(pSLMG); free(pszFromHost); return SR_RET_OK; } #endif /* FEATURE_UDP */ /** * Lowest level event handler when a new frame arrived. * This handler must first verify if it is a good packet * (checks like seqno sequence and thus) and if so, find the * profile that it should send data to. SEQ frames are directly * processed and channel 0 protocol is mapped to fixed handlers * in this object itself. * * Please note: the message object created here is passed on * to the upper layers. However, these upper layers * MUST NOT destroy the Mesg Object - this will be * done once this method finishes! * * \param pSess Pointer to the current session object. * \param pFram Pointer to the just received frame. */ srRetVal sbLstnOnFramRcvd(sbLstnObj* pThis, int *pbAbort, sbSessObj* pSess, sbFramObj *pFram) { srRetVal iRet; sbProfObj *pProf; sbChanObj *pChan; sbMesgObj *pMesg; sbLstnCHECKVALIDOBJECT(pThis); sbSessCHECKVALIDOBJECT(pSess); sbFramCHECKVALIDOBJECT(pFram); assert(pbAbort != NULL); /* As data arrived, the "need data" indicator can be reset. We do not * care if the right data arrived - if it didn't, its not a big deal, * at most we do one unncessary call to the send method. This * will now enable sending data if it was disabled on the session. */ pSess->bNeedData = FALSE; /* find the associated channel & profile */ if((pChan = sbSessRetrChanObj(pSess, pFram->uChannel)) == NULL) { sbFramDestroy(pFram); /* we now no longer need this */ return SR_RET_CHAN_DOESNT_EXIST; } /* we now need to assemble a message out of the frame */ pMesg = sbMesgConstrFromFrame(pFram); sbFramDestroy(pFram); /* we now no longer need this */ if(pMesg == NULL) return SR_RET_ERR; /* pass control to upper layer */ pProf = pChan->pProf; sbProfCHECKVALIDOBJECT(pProf); /* OK, we got the profile, so let's call the event handler */ if(pProf->OnMesgRecv == NULL) { sbChanSendErrResponse(pChan, 451, "local profile error: OnMesgRecv handler is missing - contact software vendor"); return SR_RET_ERR_EVENT_HANDLER_MISSING; } if((iRet = (pProf->OnMesgRecv)(pProf, pbAbort, pSess, pChan, pMesg)) != SR_RET_OK) return iRet; sbMesgDestroy(pMesg); return SR_RET_OK; } /** * This method build a frame by processing one character at a * time. It operates on a continuous stream of data. It is * a state machine. * * As soon as a full frame is detected, the frame handler is called * which then should pass the data to his upper layer - or whatever * he intends to do with it. This is not the business of this * method. For each new frame, new memory is allocated, so * the frame handler (or its upper layers) are responsible * for deleting the frame. * * This method calls itself recursively if it needed to peek * at a character and now needs to do the actual processing. */ srRetVal sbLstnBuildFrame(sbLstnObj* pThis, sbSessObj* pSess, char c, int *pbAbort) { srRetVal iRet; sbFramObj *pFram; char* pBuf; sbLstnCHECKVALIDOBJECT(pThis); sbSessCHECKVALIDOBJECT(pSess); assert(pbAbort != NULL); if((pFram = pSess->pRecvFrame) == NULL) { /* OK, we are ready for the next one, so * let's allocate it ;) */ if((iRet = sbFramConstruct(&pFram)) != SR_RET_OK) return iRet; pFram->iState = sbFRAMSTATE_WAITING_HDR1; pSess->pRecvFrame = pFram; } switch(pFram->iState) { case sbFRAMSTATE_WAITING_HDR1: /* waiting for the first HDR character */ if((pFram->pStrBuf = sbStrBConstruct()) == NULL) return SR_RET_OUT_OF_MEMORY; if(c != 'A' && c != 'E' && c != 'M' && c != 'N' && c != 'R' && c != 'S') return SR_RET_INVALID_HDRCMD; if((iRet = sbStrBAppendChar(pFram->pStrBuf, c)) != SR_RET_OK) return iRet; pFram->iState = sbFRAMSTATE_WAITING_HDR2; break; case sbFRAMSTATE_WAITING_HDR2: /* waiting for the second HDR character */ if(c != 'N' && c != 'R' && c != 'S' && c != 'U' && c != 'P' && c != 'E') return SR_RET_INVALID_HDRCMD; if((iRet = sbStrBAppendChar(pFram->pStrBuf, c)) != SR_RET_OK) return iRet; pFram->iState = sbFRAMSTATE_WAITING_HDR3; break; case sbFRAMSTATE_WAITING_HDR3: /* waiting for the third HDR character */ if((iRet = sbStrBAppendChar(pFram->pStrBuf, c)) != SR_RET_OK) return iRet; pBuf = sbStrBFinish(pFram->pStrBuf); pFram->pStrBuf = NULL; if((pFram->idHdr = sbFramHdrID(pBuf)) == BEEPHDR_UNKNOWN) return SR_RET_INVALID_HDRCMD; free(pBuf); pFram->iState = sbFRAMSTATE_WAITING_SP_CHAN; break; case sbFRAMSTATE_WAITING_SP_CHAN:/* waiting for the SP before channo */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_CHAN; pFram->uChannel = 0; pFram->iState = sbFRAMSTATE_IN_CHAN; break; case sbFRAMSTATE_IN_CHAN: /* reading channo */ if(isdigit(c)) pFram->uChannel = pFram->uChannel * 10 + (c - '0'); else { pFram->iState = (pFram->idHdr == BEEPHDR_SEQ) ? sbFRAMSTATE_WAITING_SP_ACKNO : sbFRAMSTATE_WAITING_SP_MSGNO; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_ACKNO:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_ACKNO; pFram->uAckno = 0; pFram->iState = sbFRAMSTATE_IN_ACKNO; break; case sbFRAMSTATE_IN_ACKNO: /* and the next (char) header */ if(isdigit(c)) pFram->uAckno = pFram->uAckno * 10 + (c - '0'); else { pFram->iState = sbFRAMSTATE_WAITING_SP_WINDOW; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_WINDOW:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_WINDOW; pFram->uWindow = 0; pFram->iState = sbFRAMSTATE_IN_WINDOW; break; case sbFRAMSTATE_IN_WINDOW: /* and the next (numeric) header */ if(isdigit(c)) pFram->uWindow = pFram->uWindow * 10 + (c - '0'); else { pFram->iState = sbFRAMSTATE_WAITING_HDRCR; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_MSGNO:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_MSGNO; pFram->uMsgno = 0; pFram->iState = sbFRAMSTATE_IN_MSGNO; break; case sbFRAMSTATE_IN_MSGNO: /* and the next (numeric) header */ if(isdigit(c)) pFram->uMsgno = pFram->uMsgno * 10 + (c - '0'); else { pFram->iState = sbFRAMSTATE_WAITING_SP_MORE; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_MORE:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_MORE; pFram->iState = sbFRAMSTATE_IN_MORE; break; case sbFRAMSTATE_IN_MORE: /* and the next (char) header */ if(c != '.' && c != '*') return SR_RET_INVALID_IN_MORE; pFram->cMore = c; pFram->iState = sbFRAMSTATE_WAITING_SP_SEQNO; break; case sbFRAMSTATE_WAITING_SP_SEQNO:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_SEQNO; pFram->uSeqno = 0; pFram->iState = sbFRAMSTATE_IN_SEQNO; break; case sbFRAMSTATE_IN_SEQNO: /* and the next (numeric) header */ if(isdigit(c)) pFram->uSeqno = pFram->uSeqno * 10 + (c - '0'); else { pFram->iState = sbFRAMSTATE_WAITING_SP_SIZE; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_SIZE:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_SIZE; pFram->uSize = 0; pFram->iState = sbFRAMSTATE_IN_SIZE; break; case sbFRAMSTATE_IN_SIZE: /* and the next (numeric) header */ if(isdigit(c)) pFram->uSize = pFram->uSize * 10 + (c - '0'); else { /* We need to guard against oversized frames here. * To do it fully right, we would need to check the * remaining bytes in the window and compare this against * uSize. However, we right now do it somewhat simpler and * just compare against the max window size. Later releases * can than add the more complex checks. This one here is * at least good enough against malicous frames... ) */ /** \todo improve! */ if(pFram->uSize > BEEPFRAMEMAX) return SR_RET_OVERSIZED_FRAME; pFram->iState = (pFram->idHdr == BEEPHDR_ANS) ? sbFRAMSTATE_WAITING_SP_ANSNO : sbFRAMSTATE_WAITING_HDRCR; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_SP_ANSNO:/* now the space before the next header */ if(c != ' ') return SR_RET_INVALID_WAITING_SP_ANSNO; pFram->uAnsno = 0; pFram->iState = sbFRAMSTATE_IN_ANSNO; break; case sbFRAMSTATE_IN_ANSNO: /* and the next (numeric) header */ if(isdigit(c)) pFram->uAnsno = pFram->uAnsno * 10 + (c - '0'); else { pFram->iState = sbFRAMSTATE_WAITING_HDRCR; return sbLstnBuildFrame(pThis, pSess, c, pbAbort); } break; case sbFRAMSTATE_WAITING_HDRCR: /* awaiting the HDR's CR */ if(c != 0x0d) return SR_RET_INVALID_WAITING_HDRCR; pFram->iState = sbFRAMSTATE_WAITING_HDRLF; break; case sbFRAMSTATE_WAITING_HDRLF: /* awaiting the HDR's LF */ if(c != 0x0a) return SR_RET_INVALID_WAITING_HDRLF; if(pFram->idHdr == BEEPHDR_SEQ) { /* frame is fully received and ready for processing*/ /* call the profile event! */ pSess->pRecvFrame = NULL; /* We are done, so let's move to the next one ;) */ return sbLstnOnFramRcvd(pThis, pbAbort, pSess, pFram); } else { if((pFram->pStrBuf = sbStrBConstruct()) == NULL) return SR_RET_OUT_OF_MEMORY; pFram->uToReceive = pFram->uSize; pFram->iState = (pFram->uToReceive > 0) ? sbFRAMSTATE_IN_PAYLOAD : sbFRAMSTATE_WAITING_END1; } break; case sbFRAMSTATE_IN_PAYLOAD: /* reading payload area */ if((iRet = sbStrBAppendChar(pFram->pStrBuf, c)) != SR_RET_OK) return iRet; if(--(pFram->uToReceive) == 0) pFram->iState = sbFRAMSTATE_WAITING_END1; break; case sbFRAMSTATE_WAITING_END1: /* waiting for the 1st HDR character */ /* we first need to save the payload area */ pFram->szRawBuf = sbStrBFinish(pFram->pStrBuf); pFram->pStrBuf = NULL; pFram->iFrameLen = pFram->uSize; /* now on with "normal" checking */ if(c != 'E') return SR_RET_INVALID_WAITING_END1; pFram->iState = sbFRAMSTATE_WAITING_END2; break; case sbFRAMSTATE_WAITING_END2: /* waiting for the 2nd HDR character */ if(c != 'N') return SR_RET_INVALID_WAITING_END2; pFram->iState = sbFRAMSTATE_WAITING_END3; break; case sbFRAMSTATE_WAITING_END3: /* waiting for the 3rd HDR character */ if(c != 'D') return SR_RET_INVALID_WAITING_END3; pFram->iState = sbFRAMSTATE_WAITING_END4; break; case sbFRAMSTATE_WAITING_END4: /* waiting for the 4th HDR character */ if(c != 0x0d) return SR_RET_INVALID_WAITING_END4; pFram->iState = sbFRAMSTATE_WAITING_END5; break; case sbFRAMSTATE_WAITING_END5: /* waiting for the 5th HDR character */ if(c != 0x0a) return SR_RET_INVALID_WAITING_END5; /* frame is fully received and ready for processing*/ /* call the profile event! */ pSess->pRecvFrame = NULL; /* We are done, so let's move to the next one ;) */ return sbLstnOnFramRcvd(pThis, pbAbort, pSess, pFram); break; default: return SR_RET_ERR; } return SR_RET_OK; } /** * Receive all incoming data that is available. * This method reads all available incoming data and processes * it. It will call receive state machine to form new frames. * That machine, in turn, will fire off Receive events as * full frames are completely received. Please note that it is possible * for a single run of this method to receive no, one or more full * frames. */ srRetVal sbLstnDoIncomingData(sbLstnObj* pThis, sbSessObj* pSess) { int iBytesRcvd; /**< actual number of bytes received */ char* pBuf; srRetVal iRet; int bAbort; /**< set by lower layer methods if a fatal error ** occured. in this case, the session should be ** aborted and such the full buffer needs not to ** be processed. **/ char szRcvBuf[1600]; /**< receive buffer - this could be any size, we * have selected this size so that an ethernet frame * fits. Feel free to tune. */ sbLstnCHECKVALIDOBJECT(pThis); sbSessCHECKVALIDOBJECT(pSess); if((iBytesRcvd = sbSockReceive(pSess->pSock, szRcvBuf, sizeof(szRcvBuf))) == 0) return SR_RET_CONNECTION_CLOSED; if(iBytesRcvd == SOCKET_ERROR) if(pSess->pSock->dwLastError != SBSOCK_EWOULDBLOCK) return SR_RET_SOCKET_ERR; bAbort = FALSE; pBuf = szRcvBuf; while(iBytesRcvd--) if((iRet = sbLstnBuildFrame(pThis, pSess, *pBuf++, &bAbort)) != SR_RET_OK) if(bAbort == TRUE) return iRet; return SR_RET_OK; } /** * This method is used to "free" a session. If there is still * a valid session pointer in the structure when the tree is * deleted, that means something went wrong and we need to get * rid of the leftovers. As such, any sessions found are aborted. */ static void sbLstnSessFreeLinkedListDummy(sbSessObj *pSess) { int bShouldNeverBeCalled = 0; sbSessCHECKVALIDOBJECT(pSess); sbSessAbort(pSess); } /** * Add a session to the list of active sessions. */ srRetVal sbSessAddActiveSession(sbLstnObj* pThis, sbSessObj *pSess) { sbNVTEObj* pEntry; sbLstnCHECKVALIDOBJECT(pThis); sbSessCHECKVALIDOBJECT(pSess); if((pEntry = sbNVTAddEntry(pThis->pRootSessions)) == NULL) return SR_RET_OUT_OF_MEMORY; sbNVTESetUsrPtr(pEntry, pSess, (void*) sbLstnSessFreeLinkedListDummy); return SR_RET_OK; } /** * This method is called when a new session is initiated * by a client. A new session object will be generated and * put into the linked list of existing session objects. * Then, a channel 0 greeting will be issued. */ static srRetVal sbLstnNewSess(sbLstnObj* pThis) { srRetVal iRet; sbSockObj *pNewSock; sbSessObj *pSess; /* Accept the connection, make room for next clients ;) */ if((iRet = sbSockAcceptConnection(pThis->pSockListening, &pNewSock) != SR_RET_OK)) return iRet; if((iRet = sbSockSetNonblocking(pNewSock) != SR_RET_OK)) { sbSockExit(pNewSock); /* best we can do */ return iRet; } /* begin constructing the profile */ if((iRet = sbSessRemoteOpen(&pSess, pNewSock, pThis->pProfsSupported)) != SR_RET_OK) { sbSockExit(pNewSock); /* best we can do */ return iRet; } /* The channel is ready, now we need to add the profile */ if((iRet = sbSessAddActiveSession(pThis, pSess)) != SR_RET_OK) { sbSessDestroy(pSess); sbSockExit(pNewSock); return iRet; } return sbSessSendGreeting(pSess, pThis->pProfsSupported); } /** * Actually send a frame over the socket. If the method would * block, we could only send a partial frame. * * \todo We need to upgrade either this method (or create another one * so that it will send multiple frames if multiple frames are * in the send queue. Right now, this is done by the select() loop * anyhow, but it may be more efficient to do it that way (or it may * be not, this needs some more thinking...). * * IMPORTANT: When sending SEQ frames, we do not * use the "normal" window mechanism. Essentially, these * SEQs are acknowledgements, so they are not to be * considered as part of the data window. If we would * count them, we would stall the process as the peer * will abviously never acknowledge acknowledgements... ;) */ srRetVal sbLstnSendFram(sbLstnObj* pThis, sbSessObj *pSess) { int iBytes2Send; int iBytesSent; char* pBuf; sbFramObj *pFram; sbLstnCHECKVALIDOBJECT(pThis); sbSessCHECKVALIDOBJECT(pSess); if(pSess->pSendQue->pFirst == NULL) return SR_RET_OK; pFram = (sbFramObj*) pSess->pSendQue->pFirst->pUsr; sbFramCHECKVALIDOBJECT(pFram); /* We now need to check if this is a full frame that * we need to begin to send. If so, we must first * check if we are within the current window size. * If not, we must wait for the next SEQ frame on this * channel and thus can not send immediately, even if * the socket would be ready. */ if(pFram->iState == sbFRAMSTATE_READY_TO_SEND) { /** \todo We need to see if race conditions can occur * where pChan becomes invalid but is still stored * with the message. It may be a better idea to use * the "normal" channel lookup methods, though they * are definitely slower... * For now, we use an assert and will test this well. */ sbChanCHECKVALIDOBJECT(pFram->pChan); if( (pFram->idHdr != BEEPHDR_SEQ) && ((unsigned) pFram->iFrameLen > pFram->pChan->uTXWinLeft)) { pSess->bNeedData = TRUE; return SR_RET_REMAIN_WIN_TOO_SMALL; } } iBytes2Send = pFram->iFrameLen - pFram->uBytesSend; assert(iBytes2Send > 0); pBuf = pFram->szRawBuf + pFram->uBytesSend; iBytesSent = sbSockSend(pSess->pSock, pBuf, iBytes2Send); if((iBytesSent > iBytes2Send) || (iBytesSent < 0)) return SR_RET_SOCKET_ERR; pFram->uBytesSend += iBytesSent; /* now check if all is send and we can remove the frame */ if((pFram->uBytesSend == pFram->iFrameLen)) { pFram->iState = sbFRAMSTATE_SENT; /** \todo again, referencing the channel object as done below ** is a little dangerous... */ if(pFram->idHdr != BEEPHDR_SEQ) pFram->pChan->uTXWinLeft -= pFram->iFrameLen; sbNVTRRemoveFirst(pSess->pSendQue); } else pFram->iState = sbFRAMSTATE_SENDING; return SR_RET_OK; } /** * This is the main server/IO handler. This loop is executed * until termination is flagged. This is the ONLY method * that is responsible for scheduling IO. * * The loop has three phases: * - check if data is to be sent and, if so, try to send it * - wait for socket to become ready for read or write * - process pending socket reads * * We have some OS dependant code in here. The reason is * that "original" BSD sockets need to have passed the * highest numbered file descriptor. We can not properly * emulate this in the socket layer. Even if we could, * that would potentially cause a considerable performance * hit. Thus, we implement it in this module and pass it * on to the lower layer. If we run into a non BSD-sockets * OS other than Windows, we might be not so happy with this * decision, but lets try it... * * Please note that all send operations are only done in phase 1. */ srRetVal sbLstnServerLoop(sbLstnObj* pThis) { srRetVal iRet; srSock_fd_set fdsetRD; /* for select() */ srSock_fd_set fdsetWR; /* for select() */ sbNVTEObj *pEntrySess; /* active session from session linked list */ sbNVTEObj *pEntrySessToDel; sbSessObj *pSess; sbFramObj *pFram; # ifndef SROS_WIN32 int iHighestDesc; # endif int iReturnSock; sbLstnCHECKVALIDOBJECT(pThis); while(pThis->bRun == TRUE) { /* phase 1: send data (if any) * We loop through all sessions to see if there is something * pending. */ pEntrySess = NULL; while((pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, pEntrySess, NULL)) != NULL) { pSess = ((sbSessObj*) pEntrySess->pUsr); if(pSess->pSendQue != NULL) if(pSess->pSendQue->pFirst != NULL) /* ok, quick hack, but this is FAST... */ { pFram = (sbFramObj*) pSess->pSendQue->pFirst->pUsr; if(pFram->iState == sbFRAMSTATE_READY_TO_SEND) /* we eventually have something to send */ sbLstnSendFram(pThis, pSess); } } /* assemble list of all active sockets & figure out & remove closed ones */ sbSockFD_ZERO(&fdsetWR); sbSockFD_ZERO(&fdsetRD); /* add BEEP listener (can not be turned off so far) */ # ifndef SROS_WIN32 iHighestDesc = pThis->pSockListening->sock; # endif sbSockFD_SET(pThis->pSockListening->sock, &fdsetRD); /* add UDP listener (if configured) */ if(pThis->bLstnUDP == TRUE) { # ifndef SROS_WIN32 if(pThis->pSockUDPListening->sock > iHighestDesc) iHighestDesc = pThis->pSockUDPListening->sock; # endif sbSockFD_SET(pThis->pSockUDPListening->sock, &fdsetRD); } /* UNIX Domain Sockets (if configured) */ # if FEATURE_UNIX_DOMAIN_SOCKETS if(pThis->bLstnUXDOMSOCK == TRUE) { if(pThis->pSockUXDOMSOCKListening->sock > iHighestDesc) iHighestDesc = pThis->pSockUXDOMSOCKListening->sock; sbSockFD_SET(pThis->pSockUXDOMSOCKListening->sock, &fdsetRD); } # endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ /* add active BEEP sessions */ pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, NULL, NULL); while(pEntrySess != NULL) { /* all are added into the read fdset, those with outstanding messages in the * write fdset, too. */ pSess = ((sbSessObj*) pEntrySess->pUsr); if(pSess->iState == sbSESSSTATE_CLOSED) { /** \todo think a little about the performance implications of the below - * we may think about either doubly linking the list or having * the previous element at hand (e.g. in the linked list object...). */ pEntrySessToDel = pEntrySess; pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, pEntrySess, NULL); /* debug: printf("shutdown session %8.8x, pUsr %8.8x\n", pEntrySessToDel, pSess); */ sbNVTRRemovEntryWithpUsr(pThis->pRootSessions, pSess); } else { sbSockFD_SET(pSess->pSock->sock, &fdsetRD); # ifndef SROS_WIN32 /* This also takes care of the write descriptor, as * we do this anyway (it is the same descriptor!). */ if(iHighestDesc < pSess->pSock->sock) iHighestDesc = pSess->pSock->sock; # endif if(pSess->pSendQue->pFirst != NULL) sbSockFD_SET(pSess->pSock->sock, &fdsetWR); pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, pEntrySess, NULL); } } /* we are ready now and can wait on the sockets */ # ifdef SROS_WIN32 iReturnSock = sbSockSelectMulti(&fdsetRD, &fdsetWR, 10, 0); # else iReturnSock = sbSockSelectMulti(&fdsetRD, &fdsetWR, 10, 0, iHighestDesc); # endif if(iReturnSock == -1) continue; /* First let's see if we have any incoming UDP data (if UDP is configured). * Please note: the ORDER of conditions is vitally important! */ if((pThis->bLstnUDP == TRUE) && sbSockFD_ISSET(pThis->pSockUDPListening->sock, &fdsetRD)) { if((iRet = sbLstnRecvUDP(pThis)) != SR_RET_OK) printf("UDP error %d!\n", iRet); } /* now check UNIX Domain Sockets (if configured) */ # if FEATURE_UNIX_DOMAIN_SOCKETS if(pThis->bLstnUXDOMSOCK == TRUE) { if(sbSockFD_ISSET(pThis->pSockUXDOMSOCKListening->sock, &fdsetRD)) { if((iRet = sbLstnRecvUXDOMSOCK(pThis)) != SR_RET_OK) printf("UX DOM SOCK error %d!\n", iRet); } } # endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ /* Let's see if we have any new BEEP connection requests. There * is only very limited space in the connection buffers, so these * should be served ASAP. */ if(sbSockFD_ISSET(pThis->pSockListening->sock, &fdsetRD)) { if((iRet = sbLstnNewSess(pThis)) != SR_RET_OK) /* empty by intention */; /** \todo log this once we have a logging subsystem */ /* let me elaborate: if we broke the loop in this case, we * would have won nothing - we would have shut down our server. * This is definitely not what we would like to see just because * a single accept fails (would be a nice way to force DoS ;)). * So we continue processing. If it was an unrecoverable error * (e.g. out of memory), other functions may also fail, but why * care about it before it happens? After all, if some sessions * are drop, memory becomes available and so the server could * continue to operate... (at least in this sample case). */ } /* We then go through all sessions and check which ones * need attention. */ pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, NULL, NULL); while(pEntrySess != NULL) { pSess = ((sbSessObj*) pEntrySess->pUsr); /* check read first... */ if(sbSockFD_ISSET(pSess->pSock->sock, &fdsetRD)) { /* OK, we now have received data. We will pass whatever we received off * to the receive state machine. It must handle it. */ iRet = sbLstnDoIncomingData(pThis, pSess); /* there are some error codes which are not actually errors, but warnings. If they * occur, we should NOT shut down the session as whole. They have taken care of their * respective reaction themselfs (hopefully ;)). */ if( iRet != SR_RET_OK && iRet != SR_RET_ERR_EVENT_HANDLER_MISSING) { /* close this connection */ /* debug: printf("Error %d - closing session.\n", iRet); */ /* read next entry as we are going to destroy the current one */ pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, pEntrySess, NULL); /** \todo think a little about the performance implications of the below - * we may think about either doubly linking the list or having * the previous element at hand (e.g. in the linked list object...). */ sbNVTRRemovEntryWithpUsr(pThis->pRootSessions, pSess); continue; } } /* ... then write */ if(pSess != NULL && sbSockFD_ISSET(pSess->pSock->sock, &fdsetWR)) { sbLstnSendFram(pThis, pSess); /** \todo check & act on return value!!!! (close session? most probably...) */ } /* Read next entry. Please note that this was already done if we needed to * destroy a session (see above). In that case, however, a "continue" is used and * control won't reach this point here. */ pEntrySess = sbNVTSearchKeySZ(pThis->pRootSessions, pEntrySess, NULL); } } return SR_RET_OK; } /* ################################################################# * * public members * * ################################################################# */ sbLstnObj* sbLstnConstruct(void) { sbLstnObj* pThis; if((pThis = calloc(1, sizeof(sbLstnObj))) == NULL) return NULL; if((pThis->pProfsSupported = sbNVTRConstruct()) == NULL) { SRFREEOBJ(pThis); return NULL; } pThis->OID = OIDsbLstn; pThis->pRootSessions = NULL; pThis->pSockListening = NULL; pThis->szListenAddr = NULL; pThis->uListenPort = 601; /* IANA default for RFC 3195 */ pThis->bLstnBEEP = TRUE; pThis->pAPI = NULL; # if FEATURE_UNIX_DOMAIN_SOCKETS == 1 pThis->bLstnUXDOMSOCK = TRUE; pThis->pSockUXDOMSOCKListening = NULL; pThis->pSockName = NULL; # endif # if FEATURE_UDP == 1 pThis->bLstnUDP = FALSE; pThis->uUDPLstnPort = 0; pThis->pSockUDPListening = NULL; # endif if((pThis->pRootSessions = sbNVTRConstruct()) == NULL) { SRFREEOBJ(pThis); return NULL; } return pThis; } void sbLstnDestroy(sbLstnObj* pThis) { sbLstnCHECKVALIDOBJECT(pThis); if(pThis->pRootSessions != NULL) sbNVTRDestroy(pThis->pRootSessions); if(pThis->pProfsSupported != NULL) sbNVTRDestroy(pThis->pProfsSupported); if(pThis->pSockListening != NULL) sbSockExit(pThis->pSockListening); # if FEATURE_UDP == 1 if(pThis->pSockUDPListening != NULL) sbSockExit(pThis->pSockUDPListening); # endif # if FEATURE_UNIX_DOMAIN_SOCKETS == 1 if(pThis->pSockUXDOMSOCKListening != NULL) sbSockExit(pThis->pSockUXDOMSOCKListening); # endif SRFREEOBJ(pThis); } srRetVal sbLstnInit(sbLstnObj* pThis) { srRetVal iRet; sbLstnCHECKVALIDOBJECT(pThis); /* Init BEEP */ if(pThis->bLstnBEEP == TRUE) { if((pThis->pSockListening = sbSockInitListenSock(&iRet, SOCK_STREAM, pThis->szListenAddr, pThis->uListenPort)) == NULL) { sbLstnDestroy(pThis); return iRet; } } /* Init UDP */ # if FEATURE_UDP == 1 if(pThis->bLstnUDP == TRUE) { if(pThis->uUDPLstnPort == 0) /* if the port is not set, we use the IANA assigned port. OK, we * could also do a services lookup, but as of now we prefer to have * the caller provide us correct information. But this may be * reconsidered later. 2003-10-02 RGerhards */ pThis->uUDPLstnPort = 514; printf("port: %d\n", pThis->uUDPLstnPort); if((pThis->pSockUDPListening = sbSockInitListenSock(&iRet, SOCK_DGRAM, pThis->szListenAddr, pThis->uUDPLstnPort)) == NULL) { sbLstnDestroy(pThis); return iRet; } } # endif /* FEATURE_UDP */ # if FEATURE_UNIX_DOMAIN_SOCKETS == 1 /* Unix Domain Sockets */ if(pThis->bLstnUXDOMSOCK == TRUE) { char *pActualSockName; pActualSockName = (pThis->pSockName == NULL) ? "/dev/log" : pThis->pSockName; printf("listeing to %s (config was %s)\n", pActualSockName, pThis->pSockName); if((iRet = sbSock_InitUXDOMSOCK(&(pThis->pSockUXDOMSOCKListening), pActualSockName, /*SOCK_DGRAM*/ SOCK_STREAM)) != SR_RET_OK) { sbLstnDestroy(pThis); return iRet; } } # endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ return(SR_RET_OK); } srRetVal sbLstnRun(sbLstnObj* pThis) { srRetVal iRet; sbLstnCHECKVALIDOBJECT(pThis); pThis->bRun = TRUE; /* start listening */ /* BEEP */ if(pThis->bLstnBEEP == TRUE) { if((iRet = sbSockListen(pThis->pSockListening) != SR_RET_OK)) return iRet; if((iRet = sbSockSetNonblocking(pThis->pSockListening) != SR_RET_OK)) { sbSockExit(pThis->pSockListening); /* best we can do */ return iRet; } } # if FEATURE_UDP == 1 /* UDP * In this case, we just need to make it asynchronous */ if(pThis->bLstnUDP == TRUE) if((iRet = sbSockSetNonblocking(pThis->pSockUDPListening) != SR_RET_OK)) { sbSockExit(pThis->pSockUDPListening); /* best we can do */ return iRet; } # endif # if FEATURE_UNIX_DOMAIN_SOCKETS == 1 /* Unix Domain Sockets */ if(pThis->bLstnUXDOMSOCK == TRUE) if((iRet = sbSockSetNonblocking(pThis->pSockUXDOMSOCKListening) != SR_RET_OK)) { sbSockExit(pThis->pSockUXDOMSOCKListening); /* best we can do */ return iRet; } # endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ /* done init - now run the server */ if((iRet = sbLstnServerLoop(pThis)) != SR_RET_OK) { sbSockExit(pThis->pSockListening); /* best we can do */ return iRet; } /* shutdown listening sockets */ /** \todo think of a smart way to handle iRet down here... For ** now, we just make sure BEEP is closed last, as this has the ** highest potential for errors. **/ # if FEATURE_UDP == 1 /* UDP */ if(pThis->bLstnUDP == TRUE) { iRet = sbSockExit(pThis->pSockUDPListening); pThis->pSockUDPListening = NULL; } # endif # if FEATURE_UNIX_DOMAIN_SOCKETS == 1 /* Unix Domain Sockets */ if(pThis->bLstnUXDOMSOCK == TRUE) { iRet = sbSockExit(pThis->pSockUXDOMSOCKListening); pThis->pSockUXDOMSOCKListening = NULL; } # endif /* FEATURE_UNIX_DOMAIN_SOCKETS */ /* BEEP */ if(pThis->bLstnBEEP == TRUE) { iRet = sbSockExit(pThis->pSockListening); pThis->pSockListening = NULL; } return iRet; } srRetVal sbLstnExit(sbLstnObj *pThis) { sbLstnCHECKVALIDOBJECT(pThis); sbLstnDestroy(pThis); return SR_RET_OK; } /** * Method the free the profile object. This is a helper * so that the sbNVTESetUsrPtr() interface can be used. * It will actually be called when the listener is * destroyed. */ static void sbLstnFreeProf(void *pUsr) { sbProfDestroy((sbProfObj*) pUsr); } srRetVal sbLstnAddProfile(sbLstnObj *pThis, sbProfObj *pProf) { sbNVTEObj *pEntry; sbLstnCHECKVALIDOBJECT(pThis); sbProfCHECKVALIDOBJECT(pProf); sbNVTRCHECKVALIDOBJECT(pThis->pProfsSupported); if((pEntry = sbNVTAddEntry(pThis->pProfsSupported)) == NULL) return SR_RET_OUT_OF_MEMORY; sbNVTESetKeySZ(pEntry, pProf->pszProfileURI, TRUE); sbNVTESetUsrPtr(pEntry, pProf, sbLstnFreeProf); return SR_RET_OK; }