/* * Copyright (c) 2002-2005 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ /* XXX HACK XXX */ #define XP_UNIX 1 #include "sm/generic.h" SM_RCSID("@(#)$Id: smi-net.c,v 1.15 2005/07/12 23:23:36 ca Exp $") #include "sm/assert.h" #include "sm/error.h" #include "sm/memops.h" #include "sm/fcntl.h" #include #include #include "smi-net.h" static bool net_initialized = false; bool net_is_ip_addr(const char * str) { int i = 0, oct = 1; SM_ASSERT(str); if (strlen(str) > 15) /* Too many characters */ return false; for (i = 0; str[i] != '\0'; i++) { if (! (isdigit(str[i]) || str[i] == '.')) return false; if (str[i] == '.') { if (i > 0 && str[i-1] == '.') return false; else oct += 1; } } if (oct != 4) return false; if (! isdigit(str[i-1])) return false; return true; } sm_ret_T net_startup() { #ifdef XP_NT WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD( 2, 0 ); if (WSAStartup(wVersionRequested, &wsaData) != 0) return FAILURE; #endif /* XP_NT */ #ifdef XP_UNIX /* If a client closes a connection, we definitly don't want to die */ signal(SIGPIPE, SIG_IGN); #endif /* XP_UNIX */ net_initialized = true; return SM_SUCCESS; } void net_shutdown() { #ifdef XP_NT WSACleanup(); #endif /* XP_NT */ net_initialized = false; } int netserverlistenaddr(sockaddr_T *my_addr, int addrlen, int backlog) { int listenfd; int sockopt; SM_ASSERT(my_addr != NULL); SM_ASSERT(backlog > 0); sockopt = 1; listenfd = INVALID_SOCKET; SM_ASSERT(net_initialized); /* Open listen port */ if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return sm_err_perm(errno); if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *) &sockopt, sizeof(sockopt)) == -1) { closesocket(listenfd); return sm_err_perm(errno); } if (bind(listenfd, (struct sockaddr *) my_addr, addrlen) == -1) { closesocket(listenfd); return sm_err_perm(errno); } if (listen(listenfd, backlog) == -1) { closesocket(listenfd); return sm_err_perm(errno); } return listenfd; } int netserverlisten(char *ip, int port, int backlog) { struct sockaddr_in servaddr; SM_ASSERT(port > 0); sm_memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; if (ip == NULL || *ip == '\0') ip = "127.0.0.1"; servaddr.sin_addr.s_addr = inet_addr(ip); servaddr.sin_port = htons(port); return netserverlistenaddr((sockaddr_T *)&servaddr, sizeof(servaddr), backlog); } int net_server_accept(int listenfd, struct sockaddr *addr, sockaddr_len_T *addrlen) { int connfd; int sockopt; SM_ASSERT(listenfd >= 0); SM_ASSERT(addr != NULL); SM_ASSERT(addrlen != NULL); sockopt = 1; if ((connfd = accept(listenfd, addr, addrlen)) == -1) { if (errno != EAGAIN) return sm_err_perm(errno); } else { if (setsockopt(connfd, SOL_SOCKET, SO_KEEPALIVE, (void *) &sockopt, sizeof(sockopt)) == -1) return sm_err_perm(errno); } return connfd; } int net_socket_create(int fd) { int sock; #ifdef XP_UNIX int flags; #endif /* XP_UNIX */ #ifdef XP_NT ulong ioctlarg = 1; #endif /* XP_NT */ SM_ASSERT(fd >= 0); sock = fd; #ifdef XP_UNIX if ((flags = fcntl(fd, F_GETFL, 0)) == -1) return sm_err_perm(errno); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return sm_err_perm(errno); #endif /* XP_UNIX */ #ifdef XP_NT if (ioctlsocket(fd, FIONBIO, &ioctlarg) == SOCKET_ERROR) return sm_err_perm(errno); #endif /* XP_NT */ return sock; } void net_socket_close(int sock) { closesocket(sock); } #if 0 /* ** net_daemons_detach borrows from Stevens Networking Vol.1 pp 336 ** (Chapter 12, Daemon Processes and the inetd Superserver), ** and from the MTA source main.c:disconnect() */ retcode net_daemon_detach() { #ifdef XP_UNIX int fd; pid_t pid; /* First fork off and terminate the parent */ pid = fork(); if (pid == -1) return sm_err_perm(errno); else if (pid > 0) { /* We're the parent, exit */ exit (EX_OK); } /* Become session leader */ if (setsid() == -1) return sm_err_perm(errno); /* ** Ignore SIGHUP and fork again. From Steven's page 336: ** "The purpose of this second fork is to guarantee that the daemon ** cannot automatically acquire a controlling terminal should it ** open a terminal device in the future. Under SVR4, when a session ** leader without a controlling terminal opens a terminal device ** (that is not currently some other session's controlling terminal), ** the terminal becomes the controlling terminal of the session ** leader. But by calling fork a second time we guarantee that the ** second child is no longer a session leader, so it cannot acquire ** a controlling terminal. We must ignore SIGHUP because when the ** session leader terminates (the first child), all processes in the ** session (our second child) are sent the SIGHUP signal." */ signal(SIGHUP, SIG_IGN); pid = fork(); if (pid == -1) return sm_err_perm(errno); else if (pid > 0) { /* We're the parent (err, 1st child), exit */ exit (EX_OK); } /* ** chdir to root filesystem so that cores are in a known location ** and so that we don't prevent any filesystems from being unmounted */ if (chdir("/") == -1) return sm_err_perm(errno); /* Clear our umask */ umask(0); /* Reopen stdin, stdout, and stderr to /dev/null */ freopen("/dev/null", "r", stdin); fd = open("/dev/null", O_WRONLY, 0666); (void) fflush(stdout); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); #endif /* XP_UNIX */ return SM_SUCCESS; } #endif /* 0 */ int netclientconnect(char *ip, int port) { int sockfd; struct sockaddr_in servaddr; ulong hostaddr; SM_ASSERT(ip != NULL); SM_ASSERT(port > 0); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; sm_memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); if ((hostaddr = inet_addr(ip)) == INADDR_NONE) return -1; sm_memcpy(&servaddr.sin_addr, &hostaddr, sizeof(hostaddr)); if ((connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) == -1) return -1; return sockfd; } int net_client_connect_hostname(char *host, int port) { int sockfd; struct sockaddr_in servaddr; struct hostent * hostptr; SM_ASSERT(host != NULL); SM_ASSERT(port > 0); if ((hostptr = gethostbyname(host)) == NULL) return sm_err_perm(errno); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return sm_err_perm(errno); while (*(hostptr->h_addr_list) && hostptr->h_addrtype != AF_INET) ++hostptr->h_addr_list; if (*(hostptr->h_addr_list) == NULL) return sm_err_perm(errno); sm_memset(&servaddr, '\0', sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); sm_memcpy(&servaddr.sin_addr, *(hostptr->h_addr_list), hostptr->h_length); if ((connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) == -1) return -1; return sockfd; } #if 0 /* ** NT does send so that it blocks until all data is away, Unix doesn't, so ** we have to check the return on Unix. */ ssize_t net_out(int outfd, const uchar *src, size_t len) { ssize_t l, ret; SM_ASSERT(outfd >= 0); SM_ASSERT(src != NULL); l = 0; #ifdef XP_UNIX /* ** Unix optimization: ** Since Unix properly notices sends to closed sockets (vs NT ** which blocks forever on a closed socket). ** For Unix send it, notice if it's block, and make sure it all ** went (this cuts the code down by 75% on 75% of the calls). */ ret = send(outfd, (char *) src, len, 0); if (ret == -1 && errno == EPIPE) { /* Connection is closed */ return -1; } if (ret >= 0) l += ret; #endif /* XP_UNIX */ while (l < len) { /* XXX use write_wait() */ /* Use select to wait for the socket to open up */ fd_set writefds; SM_ASSERT(outfd < FD_SETSIZE); FD_ZERO(&writefds); FD_SET(outfd, &writefds); /* use poll if outfd too large?? */ if (select(outfd + 1, NULL, &writefds, NULL, NULL) != 1) return sm_err_perm(errno); /* It should now be clear to write some more */ ret = send(outfd, (char *) (src + l), len - l, 0); if (ret == -1) return sm_err_perm(errno); if (ret > 0) l += ret; } SM_ASSERT(l != -1); return l; } /* ** net_voutf is "good enough" for now but needs to be replaced in the future */ static size_t net_voutf(outfd, format, ap) int outfd; const char *format; va_list ap; { size_t len; /* XXX */ char buf[1024]; SM_ASSERT(outfd >= 0); SM_ASSERT(format != NULL); if ((len = vsnprintf(buf, sizeof(buf), format, ap)) >= sizeof(buf)) return sm_err_perm(EINVAL); return net_out(outfd, buf, len); } size_t #ifdef __STDC__ net_outf(int outfd, const char *format, ...) #else /* __STDC__ */ net_outf(outfd, format, va_alist) int outfd; const char *format; va_dcl #endif /* __STDC__ */ { size_t len; va_list ap; #ifdef __STDC__ va_start(ap, format); #else /* __STDC__ */ va_start(va); #endif /* __STDC__ */ len = net_voutf(outfd, format, ap); va_end(ap); return len; } size_t net_socket_out(sock, src, len) NetSocket sock; const char *src; size_t len; { return net_out(sock->fd, src, len); } /* ** net_socket_voutf is "good enough" for now but needs to be ** replaced in the future */ static size_t net_socket_voutf(sock, format, ap) NetSocket sock; const char *format; va_list ap; { size_t len; /* XXX */ char buf[1024]; SM_ASSERT(sock != NULL); SM_ASSERT(format != NULL); if ((len = vsnprintf(buf, sizeof(buf), format, ap)) >= sizeof(buf)) return sm_err_perm(EINVAL); return net_socket_out(sock, buf, len); } size_t #ifdef __STDC__ net_socket_outf(NetSocket sock, const char *format, ...) #else /* __STDC__ */ net_socket_outf(sock, format, va_alist) NetSocket sock; const char *format; va_dcl #endif /* __STDC__ */ { size_t len; va_list ap; #ifdef __STDC__ va_start(ap, format); #else /* __STDC__ */ va_start(va); #endif /* __STDC__ */ len = net_socket_voutf(sock, format, ap); va_end(ap); return len; } #endif /* 0 */