#define _ALL_SOURCE /* required by Interix */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif // HAVE_MMAP
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h> // TCP_NODELAY
#include <netinet/in.h> // 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 <CR> to <CRLF>
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);
}
syntax highlighted by Code2HTML, v. 0.9.1