/* ** isc_tcp.c Routines to handle TCP ISC sessions ** ** Copyright (C) 1992, 1998-1999 by Peter Eriksson and ** Per Cederqvist of the Lysator Academic Computer Association. ** ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Library General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This library 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 ** Library General Public License for more details. ** ** You should have received a copy of the GNU Library General Public ** License along with this library; if not, write to the Free ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ** ** history: ** 920209 pen code extracted from isc_session.c ** (See ChangeLog for recent history) */ #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STDDEF_H # include #endif #ifdef HAVE_STDARG_H # include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_STRING_H # include #endif #include #ifndef NULL # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #include "isc.h" #include "intern.h" #include "unused.h" static IscSession * isc_tcp_accept_fn(IscHandlerList *UNUSED(hl), IscSession *scb, IscMessage *UNUSED(msg)) { IscSession *new_scb; int fd; if ((fd = accept(scb->fd, (struct sockaddr *) NULL, (int *) NULL)) < 0) return NULL; new_scb = isc_createtcp(scb->cfg, fd); if (!new_scb) return NULL; /* Fill in the session info structure */ new_scb->state = ISC_STATE_RUNNING; new_scb->mode = O_RDWR; /* FIXME: isc_getraddress may return NULL. Should we fail then? */ new_scb->info.tcp.raddr = isc_getraddress(new_scb); new_scb->info.tcp.laddr = isc_getladdress(new_scb); return new_scb; } static void isc_tcp_destroy_fn(IscHandlerList *UNUSED(hl), IscSession *scb) { if (scb->info.tcp.raddr) { isc_freeaddress(scb->info.tcp.raddr); scb->info.tcp.raddr = NULL; } if (scb->info.tcp.laddr) { isc_freeaddress(scb->info.tcp.laddr); scb->info.tcp.laddr = NULL; } } /* ** TCP I/O functions structure */ static IscHandler isc_tcp_funs = { &isc_default_read_fn, &isc_default_write_fn, &isc_default_close_fn, NULL, &isc_tcp_accept_fn, &isc_tcp_destroy_fn, NULL }; /* ** Create a TCP Session Address */ static IscAddress * isc_mktcpaddress(const char *address, const char *service) { struct sockaddr_in addr; struct hostent *hp; struct servent *sp; memset(&addr, 0, sizeof(addr)); /* Any local address? */ if (address == NULL) { addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); } else if (isdigit((int)(unsigned char)address[0])) { addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(address); if (addr.sin_addr.s_addr == -1) return NULL; } else if ((hp = gethostbyname(address)) == NULL) return NULL; else { addr.sin_family = hp->h_addrtype; memcpy(&addr.sin_addr, hp->h_addr, sizeof(addr.sin_addr)); } if (isdigit((int)(unsigned char)service[0])) addr.sin_port = htons(atoi(service)); else if ((sp = getservbyname(service, "tcp")) == NULL) return NULL; else addr.sin_port = sp->s_port; return isc_mkipaddress((struct sockaddr *)&addr); } /* ** Create a TCP session. ** Will close fd and return NULL if an error occurs. */ IscSession * isc_createtcp(IscSessionConfig *cfg, int fd) { IscSession *scb; int res; int flag; struct linger ling; if (fd == -1) if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return NULL; fd = isc_relocate_fd(fd, cfg->fd_relocate); /* Set non blocking write mode */ if ((res = fcntl(fd, F_GETFL, 0)) == -1) { close(fd); return NULL; } /* If compilation fails on the next line, please report it as a bug to ceder@lysator.liu.se. I'd like to talk to you so that you can test an autoconf solution to this problem. As a workaround, you can change "O_NONBLOCK" to "FNDELAY". */ if (fcntl(fd, F_SETFL, res | O_NONBLOCK) == -1) { close(fd); return NULL; } #if 0 /* The setsockopt calls below used to look like this, but this style is apparently now obsolete. It doesn't work on some Linux boxes. */ setsockopt(fd, SOL_SOCKET, SO_DONTLINGER, 0, 0); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 0, 0); #else /* This is the modern way to turn off linger and turn on reuseaddr. */ ling.l_onoff = 0; ling.l_linger = 0; setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); flag = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); #endif scb = isc_create(cfg, &isc_tcp_funs); if (!scb) { close(fd); return NULL; } scb->type = ISC_TYPE_TCP; scb->fd = fd; scb->info.tcp.raddr = NULL; scb->info.tcp.laddr = NULL; return scb; } /* ** Bind a TCP session to a local port and address */ int isc_bindtcp(IscSession *scb, const char *address, const char *service) { IscAddress *ia; if (scb->type != ISC_TYPE_TCP) return -1; ia = isc_mktcpaddress(address, service); if (!ia) return -1; if (bind(scb->fd, &ia->ip.saddr, sizeof(ia->ip.saddr)) < 0) return -1; scb->info.tcp.laddr = ia; return 0; } /* ** Connect a TCP session to a remote service. */ int isc_connecttcp(IscSession *scb, const char *address, const char *service) { IscAddress *ia; int res; if (scb->type != ISC_TYPE_TCP) return -1; /* Local connection? */ if (address == NULL) address = "localhost"; ia = isc_mktcpaddress(address, service); if (!ia) return -1; res = connect(scb->fd, &ia->ip.saddr, sizeof(ia->ip.saddr)); if (res < 0 && errno != EINPROGRESS) return -1; scb->state = (res < 0 ? ISC_STATE_CONNECTING : ISC_STATE_RUNNING); scb->info.tcp.raddr = ia; if (scb->info.tcp.laddr == NULL) scb->info.tcp.laddr = isc_getladdress(scb); return 0; } /* ** Establish a session with a remote TCP service, then insert it ** into a master control structure- */ IscSession * isc_opentcp(IscMaster *mcb, const char *address, const char *service) { IscSession *scb; scb = isc_createtcp(&mcb->scfg, -1); if (!scb) return NULL; if (isc_connecttcp(scb, address, service) < 0) { isc_destroy(NULL, scb); return NULL; } (void) isc_insert(mcb, scb); return scb; } /* ** Establish a port to listen at for new TCP connections */ IscSession * isc_listentcp(IscMaster *mcb, const char *address, const char *service) { IscSession *scb; int retries; int errcode; scb = isc_createtcp(&mcb->scfg, -1); if (!scb) return NULL; for (retries = 0; retries < scb->cfg->max.openretries; sleep(1), retries++) { errno = 0; if (isc_bindtcp(scb, address, service) >= 0 || errno != EADDRINUSE) break; } if (retries >= scb->cfg->max.openretries || errno != 0) { errcode = errno; isc_destroy(NULL, scb); errno = errcode; return NULL; } if (listen(scb->fd, scb->cfg->max.backlog) < 0) { errcode = errno; isc_destroy(NULL, scb); errno = errcode; return NULL; } scb->state = ISC_STATE_LISTENING; (void) isc_insert(mcb, scb); return scb; }