#define _ALL_SOURCE /* required by Interix */ #include #include #include #include #include #include #include #include #ifdef HAVE_MMAP #include #endif // HAVE_MMAP #include #include #include #include #include #include // TCP_NODELAY #include // sockaddr_in // compile with options -lm for math library // example: gcc quoted.c -lm -DHAVE_MMAP -o quoted #define QOTD_DEFAULT_PORT 17 #define LISTEN_BACKLOG 4 /* up to 4 pending connections */ #ifndef NULL #define NULL ((void *)0) #endif // NULL const char *pszAppName = NULL; // assigned by 'main()' int bKillFlag = 0; // set to '1' if the a daemon should shut down extern char *optarg; // for getopt extern int optind; extern int optopt; extern int opterr; extern int optreset; #ifndef FALSE #define FALSE 0 #endif // FALSE #ifndef TRUE #define TRUE !FALSE #endif // TRUE int usage(); int DoQuote(int hOut, int nQuote, struct sockaddr_in *pSA); int Daemonize(int bDaemon, int iPort, long nQuote); void SignalProc(int iSig); #ifndef MAX #define MAX(X,Y) ((X) >= (Y) ? (X) : (Y)) #endif // MAX int main(int argc, char *argv[], char *envp[]) { int iRval, iPort = QOTD_DEFAULT_PORT; long nQuote = -1; int nOpt, bDaemon = FALSE, bDebug = FALSE; pszAppName = argv[0]; // always capture the application name // step 1: get options while((nOpt = getopt(argc, argv, "dDp:h")) != -1) { switch(nOpt) { case 'd': // daemonize bDaemon = TRUE; break; case 'D': // 'debug' daemonize bDebug = TRUE; // like daemonize, but no daemon break; case 'p': // port to listen on (only if 'daemonize' is in effect if(!optarg) iPort = -1; else if(*optarg = ':') // normally this will happen iPort = atoi(optarg + 1); else iPort = atoi(optarg); if(iPort < 1 || iPort > 65535) { fprintf(stderr, "Error: illegal port specified (%d)\n", iPort); return(usage()); } break; case 'h': return(usage()); default: fprintf(stderr, "Illegal or unrecognized option\n"); return(usage()); } } argc -= optind; argv += optind; // updates them so that the next arg is after the switches if(argc > 0) // anything left? { // did we specify a quote number? nQuote = atoi(argv[0]); if(nQuote <= 0) { return(usage()); } } iRval = 1; // set non-blocking mode ioctl(0, FIONBIO, &iRval); // should help UDP if(!bDaemon && !bDebug) { int iOpt, nOptLen; // see if I'm a UDP socket, and if I am, I'll need to do a 'recvfrom' // and pass that information to 'DoQuote' nOptLen = sizeof(iOpt); if(!getsockopt(0, SOL_SOCKET, SO_TYPE, &iOpt, &nOptLen) && iOpt == SOCK_DGRAM) { struct sockaddr_in saR; bzero(&saR, sizeof(saR)); nOptLen = sizeof(saR); if((iRval = recvfrom(0, &iOpt, 1, MSG_PEEK, (struct sockaddr *)&saR, &nOptLen)) >= 0) { if(iRval > 0) { // eat all incoming packets (or inetd will have trouble with it) while(recv(0, &iOpt, 1, MSG_PEEK) > 0) recv(0, &iOpt, 1, 0); } iRval = DoQuote(1, nQuote, &saR); } else iRval = -1; } else iRval = DoQuote(1, nQuote, NULL); return(iRval); } // daemonize signal(SIGHUP, SignalProc); signal(SIGABRT, SignalProc); signal(SIGTERM, SignalProc); signal(SIGKILL, SignalProc); signal(SIGCHLD, SignalProc); // signal(SIGIO, SignalProc); // TODO: daemonize iRval = Daemonize(bDaemon, iPort, nQuote); // restore signal handlers signal(SIGHUP, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGKILL, SIG_DFL); signal(SIGCHLD, SIG_DFL); // signal(SIGIO, SIG_DFL); return(iRval); // exit } int usage() { fprintf(stderr, "USAGE: quoted [-h][-[d|D] [-p:port]] [n]\n" " where\n" " 'n' is a 1-based integer specifying the quote number\n" " and\n" " '-d' instructs 'quoted' to run as a daemon\n" " and\n" " '-D' is like '-d' but the process does not detach (for test purposes)\n" " and\n" " '-p:port' indicates which port to listen on (default is 17)\n" " and\n" " '-h' prints this message\n" "\n" "When 'quoted' runs as a daemon it will listen on both tcp and udp sockets.\n" "\n" "To run 'quoted' as a service for inetd, you can add the following to inetd.conf:\n" "qotd stream tcp nowait NULL /usr/local/bin/quoted quoted\n" "qotd dgram udp wait NULL /usr/local/bin/quoted quoted\n" "\n"); return(1); } void SignalProc(int iSig) { int i1; pid_t pid1; // signal handling is required for daemons switch(iSig) { case SIGCHLD: // child process just did something... if((pid1 = waitpid((pid_t)-1, &i1, WNOHANG)) > 0) { // TODO: log process ID pid1 returns code i1 } break; case SIGABRT: case SIGTERM: case SIGKILL: case SIGHUP: bKillFlag = TRUE; break; case SIGIO: // TODO: something? break; } } void WriteStr(int hOut, const char *szString, struct sockaddr_in *pSA) { if(pSA) sendto(hOut, szString, strlen(szString), 0, (struct sockaddr *)pSA, sizeof(*pSA)); else write(hOut, szString, strlen(szString)); } int DoQuote(int hOut, int nQuote, struct sockaddr_in *pSA) { int hQuotes = -1; // handle to 'quotes' file const char *pBuf = NULL, *p1, *p2, *pEnd = NULL; long nBytes, nPercents; hQuotes = open(QUOTES_FILE_PATH,O_RDONLY); // this file is not locked in any way if(hQuotes == -1) // error - try /usr/local/etc hQuotes = open("/usr/local/etc/quotes", O_RDONLY); if(hQuotes == -1) // error - try /etc hQuotes = open("/etc/quotes", O_RDONLY); if(hQuotes == -1) { WriteStr(hOut, "Quotes file missing\n", pSA); return(2); } nBytes = lseek(hQuotes, 0, SEEK_END); // get file size lseek(hQuotes, 0, SEEK_SET); // back to beginning // memory map the file so that I can get a list of '%' and // randomly select the one I want. This will be way faster. #ifdef HAVE_MMAP pBuf = (const char *)mmap(NULL, nBytes, PROT_READ, MAP_PRIVATE, hQuotes, 0); #else // HAVE_MMAP pBuf = (const char *)malloc(nBytes); #endif // HAVE_MMAP if(!pBuf) { close(hQuotes); WriteStr(hOut, "Insufficient memory to process request\r\n", pSA); return(2); } #ifndef HAVE_MMAP if(read(hQuotes, (char *)pBuf, nBytes) != nBytes) { close(hQuotes); free((void *)pBuf); WriteStr(hOut, "Read error on 'quotes' file, unable to process request\n", pSA); return(3); } #endif // HAVE_MMAP pEnd = pBuf + nBytes; // now, find out how many '%' symbols there are. If the file doesn't // end in a '%', assume it does and add an extra... nPercents = 0; p1 = pBuf; while(p1 < pEnd) { // strip any blank lines while(p1 < pEnd && *p1 <= ' ') { const char *p2 = p1; while(p1 < pEnd && *p1 != '\n') { if(*p1 > ' ') // not white space break; } if(p1 >= pEnd || *p1 != '\n') { p1 = p2; // restore it break; } p1++; // it must be a newline, so skip to next char } if(p1 >= pEnd) break; if(*p1 == '%') // percent found, so skip this entire line { while(p1 < pEnd && *p1 != '\n') p1++; if(p1 < pEnd && *p1 == '\n') p1++; } nPercents ++; // increment this anyway if(p1 >= pEnd) // end of file? break; // yes, so that's all she wrote // now look for an ending '%' while(p1 < pEnd && (*p1 != '%' || (p1 > pBuf && *(p1 - 1) != '\n'))) { p1++; // look for '%' as first character on a line } if(p1 >= pEnd) { nPercents ++; // increment as though one WERE there break; } } // printf("temporary: number of quotes found is %d\n", nPercents - 1); // at this point 'nPercents' is 1 + the number of entries. So I // can use that as my random number calculation thingy, from which I // shall truly get a truly random quote. if(nQuote <= 0) { nQuote = time(NULL); // first get time nQuote = ((nQuote >> 16) & 0xffffL) | ((nQuote << 16) & 0xffff0000L); // swap words srandom(nQuote ^ time(NULL)); // really randomize the random function nQuote = 1 + (int)floor((double)(random() & LONG_MAX) * (nPercents - 1) / LONG_MAX ); } if(nQuote < 1 || nQuote >= nPercents) nQuote = 1; // default it, so nothing breaks p1 = pBuf; nPercents = 0; // reset to zero for this next part while(p1 < pEnd) { // strip any blank lines while(p1 < pEnd && *p1 <= ' ') { p2 = p1; while(p1 < pEnd && *p1 != '\n') { if(*p1 > ' ') // not white space break; } if(p1 >= pEnd || *p1 != '\n') { p1 = p2; // restore it break; } p1++; // it must be a newline, so skip to next char } if(p1 >= pEnd) { WriteStr(hOut, "Error: quote number out of range - sorry\n", pSA); break; } if(*p1 == '%') // percent found, so skip this entire line { while(p1 < pEnd && *p1 != '\n') p1++; if(p1 < pEnd && *p1 == '\n') p1++; } nPercents ++; // increment this anyway if(p1 >= pEnd) // end of file? break; // yes, so that's all she wrote p2 = p1; // remember where it is... // now look for an ending '%' while(p1 < pEnd && (*p1 != '%' || (p1 > pBuf && *(p1 - 1) != '\n'))) { p1++; // look for '%' as first character on a line } if(nPercents == nQuote) // the quote that I want { if(p1 > p2) { char *pCur, *pBuf = (char *)malloc((p1 - p2) * 2 + 1); pCur = pBuf; if(pBuf) *pBuf = 0; // write output, and translate to while(p2 < p1) { const char *p2a = p2; while(p2a < p1 && (*p2a != '\n' || (p2a > p2 && *(p2a - 1) == '\r'))) { p2a++; } if(p2a > p2) { if(pBuf) { memcpy(pCur, p2, p2a - p2); pCur += p2a - p2; *pCur = 0; // mark end of string } else if(pSA) sendto(hOut, p2, p2a - p2, 0, (struct sockaddr *)pSA, sizeof(*pSA)); else write(hOut, p2, p2a - p2); } if(p2a < p1 && *p2a == '\n') { if(pBuf) { *(pCur++) = '\r'; *(pCur++) = '\n'; *pCur = 0; // mark end of string } else if(pSA) sendto(hOut, "\r\n", 2, 0, (struct sockaddr *)pSA, sizeof(*pSA)); else write(hOut, "\r\n", 2); // send a CRLF p2a++; } p2 = p2a; } if(pBuf) { WriteStr(hOut, pBuf, pSA); free(pBuf); } } else WriteStr(hOut, "The 'quotes' file has a blank line for this entry\r\n", pSA); break; } } // cleanup #ifndef HAVE_MMAP free((void *)pBuf); #else // HAVE_MMAP munmap((void *)pBuf, nBytes); #endif // HAVE_MAP close(hQuotes); return(0); } int Daemonize(int bDaemon, int iPort, long nQuote) { int sListen, sAccept, sUDP, i1, i2, iChild; struct sockaddr_in sa, saAccept; struct timeval tmvWait; fd_set fds1, fds2, fds3; FD_ZERO(&fds1); // because I'm a glutton for punishment, this one forks #ifdef WIN32 if ((sListen = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) #else // WIN32 if ((sListen = socket(PF_INET, SOCK_STREAM, 0)) < 0) #endif // WIN32 { fprintf(stderr, "Unable to create tcp socket, errno = %d\n", errno); return(1); } #ifdef WIN32 if ((sUDP = socket(PF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) #else // WIN32 if ((sUDP = socket(PF_INET, SOCK_DGRAM, 0)) < 0) #endif // WIN32 { fprintf(stderr, "Unable to create udp socket, errno = %d\n", errno); #ifdef WIN32 closesocket(sListen); #else // WIN32 close(sListen); #endif // WIN32 return(1); } i1 = 1; if(setsockopt(sListen, SOL_SOCKET, TCP_NODELAY, &i1, sizeof(int))) // disable NAGLE algorithm { fprintf(stderr, "Warning: 'setsockopt(TCP_NODELAY)' failed, errno = %d\n", errno); } bzero(&sa, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(iPort); if(INADDR_ANY) // if not zero... (implementation dependent) sa.sin_addr.s_addr = htonl(INADDR_ANY); i1 = bind(sListen, (struct sockaddr *)&sa, sizeof sa); bzero(&sa, sizeof sa); // I have to re-do this, 'bind' could have modified it sa.sin_family = AF_INET; sa.sin_port = htons(iPort); if(INADDR_ANY) // if not zero... (implementation dependent) sa.sin_addr.s_addr = htonl(INADDR_ANY); if(i1 < 0 || bind(sUDP, (struct sockaddr *)&sa, sizeof sa) < 0) { fprintf(stderr, "Unable to bind socket(s), errno = %d\n", errno); #ifdef WIN32 closesocket(sListen); closesocket(sUDP); #else // WIN32 close(sListen); close(sUDP); #endif // WIN32 return(2); } // listen on TCP socket if(listen(sListen, LISTEN_BACKLOG) < 0) { fprintf(stderr, "Call to 'listen()' failed, errno = %d\n", errno); #ifdef WIN32 closesocket(sListen); #else // WIN32 close(sListen); #endif // WIN32 return(3); } // make the sockets asynchronous i1 = 1; // set non-blocking mode if(ioctl(sListen, FIONBIO, &i1) < 0) { fprintf(stderr, "Warning: 'ioctl(FIONBIO)' failed, errno = %d\n", errno); } i1 = 1; // set non-blocking mode (again) if(ioctl(sUDP, FIONBIO, &i1) < 0) { fprintf(stderr, "Warning: 'ioctl(FIONBIO)' failed, errno = %d\n", errno); } // daemonize if(bDaemon && daemon(0,0)) // daemon failed? { fprintf(stderr, "Unable to daemonize, errno = %d\n", errno); return(-1); } while(!bKillFlag) { FD_ZERO(&fds1); FD_SET(sListen, &fds1); FD_SET(sUDP, &fds1); tmvWait.tv_sec = 0; tmvWait.tv_usec = 25000; // up to 25 millisecs // the first parameter of 'select' is poorly documented. In the BSD realm, // it is normally a very large number. The 'select' library function actually // uses it as the MAXIMUM index for the 'fd_set', and as it turns out, it's // really just a bitmask. To optimize this, I use the maximum of the socket // values, plus 1. This minimizes the looping inside of 'select' while also // making it work. The min value of '2' is for compatibility with what appears // to be how it's documented, though as I mentioned, it's documented poorly. i1 = select(MAX(2,MAX(sUDP + 1, sListen + 1)), &fds1, NULL, NULL, &tmvWait); sAccept = -1; if(i1 < 0) { // this can happen when a socket gets closed so ignore it } else if(i1 == 0) { // nothing came back, idling... } else // if(i1 > 0) { if(FD_ISSET(sUDP, &fds1)) { // fprintf(stderr, "temporary, got something for UDP\n"); struct sockaddr_in saR; bzero(&saR, sizeof(saR)); i2 = sizeof(saR); if(recvfrom(sUDP, &i1, 1, 0, (struct sockaddr *)&saR, &i2) >= 0) // something out there? { DoQuote(sUDP, nQuote, &saR); } } // this never seems to happen for some reason // but according to the docs, it's supposed to work if(FD_ISSET(sListen, &fds1)) { // fprintf(stderr, "temporary, got something for TCP\n"); bzero(&saAccept, sizeof(saAccept)); i1 = sizeof(saAccept); sAccept = accept(sListen, (struct sockaddr *)&saAccept, &i1); #ifdef WIN32 if(sAccept == INVALID_SOCKET) #else // WIN32 if(sAccept < 0) #endif // WIN32 { // TODO: error log?? } } } if(sAccept >= 0) { iChild = fork(); if(iChild == -1) // error { #ifdef WIN32 closesocket(sAccept); #else // WIN32 close(sAccept); #endif // WIN32 // TODO: report error? } else if(iChild != 0) // main process, returns child process ID { #ifdef WIN32 closesocket(sAccept); // must close the socket #else // WIN32 close(sAccept); #endif // WIN32 } else { // this is the child process. serve up the return value // and then exit. // close the (duplicated) socket that I'm listening on #ifdef WIN32 closesocket(sListen); closesocket(sUDP); #else // WIN32 close(sListen); close(sUDP); #endif // WIN32 // create FILE * for stdin/stdout equivalents // TODO: streams really don't associate with sockets in WIN32 land i1 = DoQuote(sAccept, nQuote, NULL); #ifdef WIN32 closesocket(sAccept); #else // WIN32 close(sAccept); #endif // WIN32 return(i1); } } } // exit point for daemon only #ifdef WIN32 closesocket(sListen); closesocket(sUDP); #else // WIN32 close(sListen); close(sUDP); #endif // WIN32 return(0); }