void handler(struct con *cp) { int end = 0; int n; if (cp->r) { n = read(cp->fd, cp->ip, cp->il); if (n == 0) closecon(cp); else if (n == -1) { if (debug > 0) perror("read()"); closecon(cp); } else { cp->ip[n] = '\0'; if (cp->rend[0]) if (strpbrk(cp->ip, cp->rend)) end = 1; cp->ip += n; cp->il -= n; } } if (end || cp->il == 0) { while (cp->ip > cp->ibuf && (cp->ip[-1] == '\r' || cp->ip[-1] == '\n')) cp->ip--; *cp->ip = '\0'; cp->r = 0; nextstate(cp); } }
void handlew(struct con *cp, int one) { int n; /* kill stutter on greylisted connections after initial delay */ if (cp->stutter && greylist && cp->blacklists == NULL && (t - cp->s) > grey_stutter) cp->stutter=0; if (cp->w) { if (*cp->op == '\n' && !cp->sr) { /* insert \r before \n */ n = write(cp->fd, "\r", 1); if (n == 0) { closecon(cp); goto handled; } else if (n == -1) { if (debug > 0 && errno != EPIPE) perror("write()"); closecon(cp); goto handled; } } if (*cp->op == '\r') cp->sr = 1; else cp->sr = 0; n = write(cp->fd, cp->op, (one && cp->stutter) ? 1 : cp->ol); if (n == 0) closecon(cp); else if (n == -1) { if (debug > 0 && errno != EPIPE) perror("write()"); closecon(cp); } else { cp->op += n; cp->ol -= n; } } handled: cp->w = t + cp->stutter; if (cp->ol == 0) { cp->w = 0; nextstate(cp); } }
/* * handlewrite - deal with a connection that we want to write stuff * to. assumes the caller has checked that cp->sd is writeable * by using select(). once we write everything out, change the * state of the connection to the reading state. */ void handlewrite(struct con *cp) { ssize_t i; /* * assuming before we are called, cp->sd was put into an fd_set * and checked for writeability by select, we know that we can * do one write() and write something. We are *NOT* guaranteed * how much we can write. So while we will be able to write bytes * we don't know if we will get a whole line, or even how much * we will get - so we do *exactly* one write. and keep track * of where we are. If we don't want to block, we can't do * multiple writes to write everything out without calling * select() again between writes. */ i = write(cp->sd, cp->bp, cp->bl); if (i == -1) { if (errno != EAGAIN) { /* the write failed */ closecon(cp, 0); } /* * note if EAGAIN, we just return, and let our caller * decide to call us again when socket is writable */ return; } /* otherwise, something ok happened */ cp->bp += i; /* move where we are */ cp->bl -= i; /* decrease how much we have left to write */ if (cp->bl == 0) { /* we wrote it all out, hooray, so go back to reading */ cp->state = STATE_READING; cp->bl = cp->bs; /* we can read up to this much */ cp->bp = cp->buf; /* we'll start at the beginning */ } }
void nextstate(struct con *cp) { if (match(cp->ibuf, "QUIT") && cp->state < 99) { snprintf(cp->obuf, cp->osize, "221 %s\r\n", hostname); cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->w = t + cp->stutter; cp->laststate = cp->state; cp->state = 99; return; } if (match(cp->ibuf, "RSET") && cp->state > 2 && cp->state < 50) { snprintf(cp->obuf, cp->osize, "250 Ok to start over.\r\n"); cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->w = t + cp->stutter; cp->laststate = cp->state; cp->state = 2; return; } switch (cp->state) { case 0: /* banner sent; wait for input */ cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->laststate = cp->state; cp->state = 1; cp->r = t; break; case 1: /* received input: parse, and select next state */ if (match(cp->ibuf, "HELO") || match(cp->ibuf, "EHLO")) { int nextstate = 2; cp->helo[0] = '\0'; gethelo(cp->helo, sizeof cp->helo, cp->ibuf); if (cp->helo[0] == '\0') { nextstate = 0; snprintf(cp->obuf, cp->osize, "501 helo requires domain name.\r\n"); } else { snprintf(cp->obuf, cp->osize, "250 Hello, spam sender. " "Pleased to be wasting your time.\r\n"); } cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->laststate = cp->state; cp->state = nextstate; cp->w = t + cp->stutter; break; } goto mail; case 2: /* sent 250 Hello, wait for input */ cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->laststate = cp->state; cp->state = 3; cp->r = t; break; case 3: mail: if (match(cp->ibuf, "MAIL")) { setlog(cp->mail, sizeof cp->mail, cp->ibuf); snprintf(cp->obuf, cp->osize, "250 You are about to try to deliver spam. " "Your time will be spent, for nothing.\r\n"); cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->laststate = cp->state; cp->state = 4; cp->w = t + cp->stutter; break; } goto rcpt; case 4: /* sent 250 Sender ok */ cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->laststate = cp->state; cp->state = 5; cp->r = t; break; case 5: rcpt: if (match(cp->ibuf, "RCPT")) { setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf); snprintf(cp->obuf, cp->osize, "250 This is hurting you more than it is " "hurting me.\r\n"); cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->laststate = cp->state; cp->state = 6; cp->w = t + cp->stutter; if (cp->mail[0] && cp->rcpt[0]) { if (verbose) syslog_r(LOG_INFO, &sdata, "(%s) %s: %s -> %s", cp->blacklists ? "BLACK" : "GREY", cp->addr, cp->mail, cp->rcpt); if (debug) fprintf(stderr, "(%s) %s: %s -> %s\n", cp->blacklists ? "BLACK" : "GREY", cp->addr, cp->mail, cp->rcpt); if (greylist && cp->blacklists == NULL) { /* send this info to the greylister */ getcaddr(cp); fprintf(grey, "CO:%s\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n", cp->caddr, cp->helo, cp->addr, cp->mail, cp->rcpt); fflush(grey); } } break; } goto spam; case 6: /* sent 250 blah */ cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->laststate = cp->state; cp->state = 5; cp->r = t; break; case 50: spam: if (match(cp->ibuf, "DATA")) { snprintf(cp->obuf, cp->osize, "354 Enter spam, end with \".\" on a line by " "itself\r\n"); cp->state = 60; if (window && setsockopt(cp->fd, SOL_SOCKET, SO_RCVBUF, &window, sizeof(window)) == -1) { syslog_r(LOG_DEBUG, &sdata,"setsockopt: %m"); /* don't fail if this doesn't work. */ } cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->w = t + cp->stutter; if (greylist && cp->blacklists == NULL) { cp->laststate = cp->state; cp->state = 98; goto done; } } else { if (match(cp->ibuf, "NOOP")) snprintf(cp->obuf, cp->osize, "250 2.0.0 OK I did nothing\r\n"); else { snprintf(cp->obuf, cp->osize, "500 5.5.1 Command unrecognized\r\n"); cp->badcmd++; if (cp->badcmd > 20) { cp->laststate = cp->state; cp->state = 98; goto done; } } cp->state = cp->laststate; cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->w = t + cp->stutter; } break; case 60: /* sent 354 blah */ cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->laststate = cp->state; cp->state = 70; cp->r = t; break; case 70: { char *p, *q; for (p = q = cp->ibuf; q <= cp->ip; ++q) if (*q == '\n' || q == cp->ip) { *q = 0; if (q > p && q[-1] == '\r') q[-1] = 0; if (!strcmp(p, ".") || (cp->data_body && ++cp->data_lines >= 10)) { cp->laststate = cp->state; cp->state = 98; goto done; } if (!cp->data_body && !*p) cp->data_body = 1; if (verbose && cp->data_body && *p) syslog_r(LOG_DEBUG, &sdata, "%s: " "Body: %s", cp->addr, p); else if (verbose && (match(p, "FROM:") || match(p, "TO:") || match(p, "SUBJECT:"))) syslog_r(LOG_INFO, &sdata, "%s: %s", cp->addr, p); p = ++q; } cp->ip = cp->ibuf; cp->il = sizeof(cp->ibuf) - 1; cp->r = t; break; } case 98: done: doreply(cp); cp->op = cp->obuf; cp->ol = strlen(cp->op); cp->w = t + cp->stutter; cp->laststate = cp->state; cp->state = 99; break; case 99: closecon(cp); break; default: errx(1, "illegal state %d", cp->state); break; } }
int main(int argc, char *argv[]) { #ifdef __FreeBSD__ extern char *__progname; FILE *fpid = NULL; struct stat dbstat; int pt, ge, we; /* make build on amd64/sparc happy */ #endif fd_set *fdsr = NULL, *fdsw = NULL; struct sockaddr_in sin; struct sockaddr_in lin; int ch, s, s2, conflisten = 0, syncfd = 0, i, omax = 0, one = 1; socklen_t sinlen; u_short port; struct servent *ent; struct rlimit rlp; char *bind_address = NULL; const char *errstr; char *sync_iface = NULL; char *sync_baddr = NULL; tzset(); openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); if ((ent = getservbyname("spamd", "tcp")) == NULL) errx(1, "Can't find service \"spamd\" in /etc/services"); port = ntohs(ent->s_port); if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL) errx(1, "Can't find service \"spamd-cfg\" in /etc/services"); cfg_port = ntohs(ent->s_port); if ((ent = getservbyname("spamd-sync", "udp")) == NULL) errx(1, "Can't find service \"spamd-sync\" in /etc/services"); sync_port = ntohs(ent->s_port); if (gethostname(hostname, sizeof hostname) == -1) err(1, "gethostname"); maxfiles = get_maxfiles(); if (maxcon > maxfiles) maxcon = maxfiles; if (maxblack > maxfiles) maxblack = maxfiles; while ((ch = #ifndef __FreeBSD__ getopt(argc, argv, "45l:c:B:p:bdG:h:s:S:M:n:vw:y:Y:")) != -1) { #else getopt(argc, argv, "45l:c:B:p:bdG:h:s:S:M:n:vw:y:Y:t:m:")) != -1) { #endif switch (ch) { case '4': nreply = "450"; break; case '5': nreply = "550"; break; case 'l': bind_address = optarg; break; case 'B': i = atoi(optarg); maxblack = i; break; case 'c': i = atoi(optarg); if (i > maxfiles) { fprintf(stderr, "%d > system max of %d connections\n", i, maxfiles); usage(); } maxcon = i; break; case 'p': i = atoi(optarg); port = i; break; case 'd': debug = 1; break; case 'b': greylist = 0; break; case 'G': if (sscanf(optarg, "%d:%d:%d", &pt, &ge, &we) != 3) usage(); /* convert to seconds from minutes */ passtime = pt * 60; /* convert to seconds from hours */ whiteexp = we * (60 * 60); /* convert to seconds from hours */ greyexp = ge * (60 * 60); break; case 'h': bzero(&hostname, sizeof(hostname)); if (strlcpy(hostname, optarg, sizeof(hostname)) >= sizeof(hostname)) errx(1, "-h arg too long"); break; case 's': i = strtonum(optarg, 0, 10, &errstr); if (errstr) usage(); stutter = i; break; case 'S': i = strtonum(optarg, 0, 90, &errstr); if (errstr) usage(); grey_stutter = i; break; case 'M': low_prio_mx_ip = optarg; break; case 'n': spamd = optarg; break; case 'v': verbose = 1; break; case 'w': window = atoi(optarg); if (window <= 0) usage(); break; case 'Y': if (sync_addhost(optarg, sync_port) != 0) sync_iface = optarg; syncsend++; break; case 'y': sync_baddr = optarg; syncrecv++; break; #ifdef __FreeBSD__ case 't': ipfw_tabno = atoi(optarg); break; case 'm': if (strcmp(optarg, "ipfw") == 0) use_pf=0; break; #endif default: usage(); break; } } #ifdef __FreeBSD__ /* check if PATH_SPAMD_DB is a regular file */ if (lstat(PATH_SPAMD_DB, &dbstat) == 0 && !S_ISREG(dbstat.st_mode)) { syslog(LOG_ERR, "error %s (Not a regular file)", PATH_SPAMD_DB); errx(1, "exit \"%s\" : Not a regular file", PATH_SPAMD_DB); } #endif setproctitle("[priv]%s%s", greylist ? " (greylist)" : "", (syncrecv || syncsend) ? " (sync)" : ""); if (!greylist) maxblack = maxcon; else if (maxblack > maxcon) usage(); rlp.rlim_cur = rlp.rlim_max = maxcon + 15; if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) err(1, "setrlimit"); con = calloc(maxcon, sizeof(*con)); if (con == NULL) err(1, "calloc"); con->obuf = malloc(8192); if (con->obuf == NULL) err(1, "malloc"); con->osize = 8192; for (i = 0; i < maxcon; i++) con[i].fd = -1; signal(SIGPIPE, SIG_IGN); s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) err(1, "socket"); if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) return (-1); conflisten = socket(AF_INET, SOCK_STREAM, 0); if (conflisten == -1) err(1, "socket"); if (setsockopt(conflisten, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) return (-1); memset(&sin, 0, sizeof sin); sin.sin_len = sizeof(sin); if (bind_address) { if (inet_pton(AF_INET, bind_address, &sin.sin_addr) != 1) err(1, "inet_pton"); } else sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_family = AF_INET; sin.sin_port = htons(port); if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1) err(1, "bind"); memset(&lin, 0, sizeof sin); lin.sin_len = sizeof(sin); lin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); lin.sin_family = AF_INET; lin.sin_port = htons(cfg_port); if (bind(conflisten, (struct sockaddr *)&lin, sizeof lin) == -1) err(1, "bind local"); if (syncsend || syncrecv) { syncfd = sync_init(sync_iface, sync_baddr, sync_port); if (syncfd == -1) err(1, "sync init"); } if ((pw = getpwnam("_spamd")) == NULL) errx(1, "no such user _spamd"); #ifdef __FreeBSD__ /* open the pid file just before daemon */ fpid = fopen(pid_file, "w"); if (fpid == NULL) { syslog(LOG_ERR, "error can't create pid file %s (%m)", pid_file); err(1, "can't create pid file \"%s\"", pid_file); } #endif if (debug == 0) { if (daemon(1, 1) == -1) err(1, "daemon"); } if (greylist) { #ifdef __FreeBSD__ if(use_pf){ #endif pfdev = open("/dev/pf", O_RDWR); if (pfdev == -1) { syslog_r(LOG_ERR, &sdata, "open /dev/pf: %m"); exit(1); } #ifdef __FreeBSD__ } #endif maxblack = (maxblack >= maxcon) ? maxcon - 100 : maxblack; if (maxblack < 0) maxblack = 0; /* open pipe to talk to greylister */ if (pipe(greypipe) == -1) { syslog(LOG_ERR, "pipe (%m)"); exit(1); } /* open pipe to recieve spamtrap configs */ if (pipe(trappipe) == -1) { syslog(LOG_ERR, "pipe (%m)"); exit(1); } jail_pid = fork(); switch (jail_pid) { case -1: syslog(LOG_ERR, "fork (%m)"); exit(1); case 0: /* child - continue */ signal(SIGPIPE, SIG_IGN); grey = fdopen(greypipe[1], "w"); if (grey == NULL) { syslog(LOG_ERR, "fdopen (%m)"); _exit(1); } close(greypipe[0]); trapfd = trappipe[0]; trapcfg = fdopen(trappipe[0], "r"); if (trapcfg == NULL) { syslog(LOG_ERR, "fdopen (%m)"); _exit(1); } close(trappipe[1]); goto jail; } /* parent - run greylister */ grey = fdopen(greypipe[0], "r"); if (grey == NULL) { syslog(LOG_ERR, "fdopen (%m)"); exit(1); } close(greypipe[1]); trapcfg = fdopen(trappipe[1], "w"); if (trapcfg == NULL) { syslog(LOG_ERR, "fdopen (%m)"); exit(1); } close(trappipe[0]); return (greywatcher()); /* NOTREACHED */ } jail: #ifdef __FreeBSD__ /* after switch user and daemon write and close the pid file */ if (fpid) { fprintf(fpid, "%ld\n", (long) getpid()); if (fclose(fpid) == EOF) { syslog(LOG_ERR, "error can't close pid file %s (%m)", pid_file); exit(1); } } #endif if (chroot("/var/empty") == -1 || chdir("/") == -1) { syslog(LOG_ERR, "cannot chdir to /var/empty."); exit(1); } if (pw) if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) err(1, "failed to drop privs"); if (listen(s, 10) == -1) err(1, "listen"); if (listen(conflisten, 10) == -1) err(1, "listen"); if (debug != 0) printf("listening for incoming connections.\n"); syslog_r(LOG_WARNING, &sdata, "listening for incoming connections."); while (1) { struct timeval tv, *tvp; int max, n; int writers; max = MAX(s, conflisten); if (syncrecv) max = MAX(max, syncfd); max = MAX(max, conffd); max = MAX(max, trapfd); time(&t); for (i = 0; i < maxcon; i++) if (con[i].fd != -1) max = MAX(max, con[i].fd); if (max > omax) { free(fdsr); fdsr = NULL; free(fdsw); fdsw = NULL; fdsr = (fd_set *)calloc(howmany(max+1, NFDBITS), sizeof(fd_mask)); if (fdsr == NULL) err(1, "calloc"); fdsw = (fd_set *)calloc(howmany(max+1, NFDBITS), sizeof(fd_mask)); if (fdsw == NULL) err(1, "calloc"); omax = max; } else { memset(fdsr, 0, howmany(max+1, NFDBITS) * sizeof(fd_mask)); memset(fdsw, 0, howmany(max+1, NFDBITS) * sizeof(fd_mask)); } writers = 0; for (i = 0; i < maxcon; i++) { if (con[i].fd != -1 && con[i].r) { if (con[i].r + MAXTIME <= t) { closecon(&con[i]); continue; } FD_SET(con[i].fd, fdsr); } if (con[i].fd != -1 && con[i].w) { if (con[i].w + MAXTIME <= t) { closecon(&con[i]); continue; } if (con[i].w <= t) FD_SET(con[i].fd, fdsw); writers = 1; } } FD_SET(s, fdsr); /* only one active config conn at a time */ if (conffd == -1) FD_SET(conflisten, fdsr); else FD_SET(conffd, fdsr); if (trapfd != -1) FD_SET(trapfd, fdsr); if (syncrecv) FD_SET(syncfd, fdsr); if (writers == 0) { tvp = NULL; } else { tv.tv_sec = 1; tv.tv_usec = 0; tvp = &tv; } n = select(max+1, fdsr, fdsw, NULL, tvp); if (n == -1) { if (errno != EINTR) err(1, "select"); continue; } if (n == 0) continue; for (i = 0; i < maxcon; i++) { if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsr)) handler(&con[i]); if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsw)) handlew(&con[i], clients + 5 < maxcon); } if (FD_ISSET(s, fdsr)) { sinlen = sizeof(sin); s2 = accept(s, (struct sockaddr *)&sin, &sinlen); if (s2 == -1) /* accept failed, they may try again */ continue; for (i = 0; i < maxcon; i++) if (con[i].fd == -1) break; if (i == maxcon) close(s2); else { initcon(&con[i], s2, (struct sockaddr *)&sin); syslog_r(LOG_INFO, &sdata, "%s: connected (%d/%d)%s%s", con[i].addr, clients, blackcount, ((con[i].lists == NULL) ? "" : ", lists:"), ((con[i].lists == NULL) ? "": con[i].lists)); } } if (FD_ISSET(conflisten, fdsr)) { sinlen = sizeof(lin); conffd = accept(conflisten, (struct sockaddr *)&lin, &sinlen); if (conffd == -1) /* accept failed, they may try again */ continue; else if (ntohs(lin.sin_port) >= IPPORT_RESERVED) { close(conffd); conffd = -1; } } if (conffd != -1 && FD_ISSET(conffd, fdsr)) do_config(); if (trapfd != -1 && FD_ISSET(trapfd, fdsr)) read_configline(trapcfg); if (syncrecv && FD_ISSET(syncfd, fdsr)) sync_recv(); } exit(1); }
int main(int argc, char *argv[]) { struct sockaddr_in sockname; int max = -1, omax; /* the biggest value sd. for select */ int sd; /* our listen socket */ fd_set *readable = NULL , *writable = NULL; /* fd_sets for select */ u_short port; u_long p; char *ep; int i; /* * first, figure out what port we will listen on - it should * be our first parameter. */ if (argc != 2) usage(); errno = 0; p = strtoul(argv[1], &ep, 10); if (*argv[1] == '\0' || *ep != '\0') { /* parameter wasn't a number, or was empty */ fprintf(stderr, "%s - not a number\n", argv[1]); usage(); } if ((errno == ERANGE && p == ULONG_MAX) || (p > USHRT_MAX)) { /* It's a number, but it either can't fit in an unsigned * long, or is too big for an unsigned short */ fprintf(stderr, "%s - value out of range\n", argv[1]); usage(); } /* now safe to do this */ port = p; /* now before we get going, decide if we want to daemonize, that * is, run in the background like a real system process */ #ifndef DEBUG /* don't daemonize if we compile with -DDEBUG */ if (daemon(1, 0) == -1) err(1, "daemon() failed"); #endif /* now off to the races - let's set up our listening socket */ memset(&sockname, 0, sizeof(sockname)); sockname.sin_family = AF_INET; sockname.sin_port = htons(port); sockname.sin_addr.s_addr = htonl(INADDR_ANY); sd=socket(AF_INET,SOCK_STREAM,0); if ( sd == -1) err(1, "socket failed"); if (bind(sd, (struct sockaddr *) &sockname, sizeof(sockname)) == -1) err(1, "bind failed"); if (listen(sd,3) == -1) err(1, "listen failed"); /* * We're now bound, and listening for connections on "sd". * Each call to "accept" will return us a descriptor talking to * a connected client. */ /* * finally - the main loop. accept connections and deal with 'em */ #ifndef DEBUG /* * since we'll be running as a daemon if we're not compiled with * -DDEBUG, we better not be using printf - since stdout will be * unusable */ printf("Server up and listening for connections on port %u\n", port); #endif /* initialize all our connection structures */ for (i = 0; i < MAXCONN; i++) closecon(&connections[i], 1); for(;;) { int i; int maxfd = -1; /* the biggest value sd we are interested in.*/ /* * first we have to initialize the fd_sets to keep * track of readable and writable sockets. we have * to make sure we have fd_sets that are big enough * to hold our largest valued socket descriptor. * so first, we find the max value by iterating through * all the connections, and then we allocate fd sets * that are big enough, if they aren't already. */ omax = max; max = sd; /* the listen socket */ for (i = 0; i < MAXCONN; i++) { if (connections[i].sd > max) max = connections[i].sd; } if (max > omax) { /* we need bigger fd_sets allocated */ /* free the old ones - does nothing if they are NULL */ free(readable); free(writable); /* * this is how to allocate fd_sets for select */ readable = (fd_set *)calloc(howmany(max + 1, NFDBITS), sizeof(fd_mask)); if (readable == NULL) err(1, "out of memory"); writable = (fd_set *)calloc(howmany(max + 1, NFDBITS), sizeof(fd_mask)); if (writable == NULL) err(1, "out of memory"); omax = max; /* * note that calloc always returns 0'ed memory, * (unlike malloc) so these sets are all set to 0 * and ready to go */ } else { /* * our allocated sets are big enough, just make * sure they are cleared to 0. */ memset(readable, 0, howmany(max+1, NFDBITS) * sizeof(fd_mask)); memset(writable, 0, howmany(max+1, NFDBITS) * sizeof(fd_mask)); } /* * Now, we decide which sockets we are interested * in reading and writing, by setting the corresponding * bit in the readable and writable fd_sets. */ /* * we are always interesting in reading from the * listening socket. so put it in the read set. */ FD_SET(sd, readable); if (maxfd < sd) maxfd = sd; /* * now go through the list of connections, and if we * are interested in reading from, or writing to, the * connection's socket, put it in the readable, or * writable fd_set - in preparation to call select * to tell us which ones we can read and write to. */ for (i = 0; i<MAXCONN; i++) { if (connections[i].state == STATE_READING) { FD_SET(connections[i].sd, readable); if (maxfd < connections[i].sd) maxfd = connections[i].sd; } if (connections[i].state == STATE_WRITING) { FD_SET(connections[i].sd, writable); if (maxfd < connections[i].sd) maxfd = connections[i].sd; } } /* * finally, we can call select. we have filled in "readable" * and "writable" with everything we are interested in, and * when select returns, it will indicate in each fd_set * which sockets are readable and writable */ i = select(maxfd + 1, readable, writable, NULL,NULL); if (i == -1 && errno != EINTR) err(1, "select failed"); if (i > 0) { /* something is readable or writable... */ /* * First things first. check the listen socket. * If it was readable - we have a new connection * to accept. */ if (FD_ISSET(sd, readable)) { struct con *cp; int newsd; socklen_t slen; struct sockaddr_in sa; slen = sizeof(sa); newsd = accept(sd, (struct sockaddr *)&sa, &slen); if (newsd == -1) err(1, "accept failed"); cp = get_free_conn(); if (cp == NULL) { /* * we have no connection structures * so we close connection to our * client to not leave him hanging * because we are too busy to * service his request */ close(newsd); } else { /* * ok, if this worked, we now have a * new connection. set him up to be * READING so we do something with him */ cp->state = STATE_READING; cp->sd = newsd; cp->slen = slen; memcpy(&cp->sa, &sa, sizeof(sa)); } } /* * now, iterate through all of our connections, * check to see if they are readble or writable, * and if so, do a read or write accordingly */ for (i = 0; i<MAXCONN; i++) { if ((connections[i].state == STATE_READING) && FD_ISSET(connections[i].sd, readable)) handleread(&connections[i]); if ((connections[i].state == STATE_WRITING) && FD_ISSET(connections[i].sd, writable)) handlewrite(&connections[i]); } } } }
/* * handleread - deal with a connection that we want to read stuff * from. assumes the caller has checked that cp->sd is writeable * by using select(). If a newline is seen at the end of what we * are reading, change the state of this connection to the writing * state. */ void handleread(struct con *cp) { ssize_t i; /* * first, let's make sure we have enough room to do a * decent sized read. */ if (cp->bl < 10) { char *tmp; tmp = realloc(cp->buf, (cp->bs + BUF_ASIZE) * sizeof(char)); if (tmp == NULL) { /* we're out of memory */ closecon(cp, 0); return; } cp->buf = tmp; cp->bs += BUF_ASIZE; cp->bl += BUF_ASIZE; cp->bp = cp->buf + (cp->bs - cp->bl); } /* * assuming before we are called, cp->sd was put into an fd_set * and checked for readability by select, we know that we can * do one read() and get something. We are *NOT* guaranteed * how much we can get. So while we will be able to read bytes * we don't know if we will get a whole line, or even how much * we will get - so we do *exactly* one read. and keep track * of where we are. If we don't want to block, we can't do * multiple reads to read in a whole line without calling * select() to check for readability between each read. */ i = read(cp->sd, cp->bp, cp->bl); if (i == 0) { /* 0 byte read means the connection got closed */ closecon(cp, 0); return; } if (i == -1) { if (errno != EAGAIN) { /* read failed */ err(1, "read failed! sd %d\n", cp->sd); closecon(cp, 0); } /* * note if EAGAIN, we just return, and let our caller * decide to call us again when socket is readable */ return; } /* * ok we really got something read. chage where we're * pointing */ cp->bp += i; cp->bl -= i; /* * now check to see if we should change state - i.e. we got * a newline on the end of the buffer */ if (*(cp->bp - 1) == '\n') { cp->state = STATE_WRITING; cp->bl = cp->bp - cp->buf; /* how much will we write */ cp->bp = cp->buf; /* and we'll start from here */ } }