/* * Copyright conserver.com, 2000 * * Maintainer/Enhancer: Bryan Stansell (bryan@conserver.com) */ #include #include #include #include #if USE_IPV6 # include #endif #if HAVE_SYS_SOCKIO_H # include #endif #if HAVE_OPENSSL # include #endif int fVerbose = 0, fErrorPrinted = 0; int isMultiProc = 0; char *progname = "conserver package"; pid_t thepid = 0; int fDebug = 0; STRING *allStrings = (STRING *)0; int stringCount = 0; /* count of allStrings list */ #if !USE_IPV6 struct in_addr *myAddrs = (struct in_addr *)0; #endif char myHostname[MAXHOSTNAME]; /* staff.cc.purdue.edu */ fd_set rinit; fd_set winit; int maxfd = 0; int debugLineNo = 0; char *debugFileName = (char *)0; int isMaster = 1; /* in the routines below (the init code) we can bomb if malloc fails (ksb) */ void OutOfMem(void) { static char acNoMem[] = ": out of memory\n"; write(2, progname, strlen(progname)); write(2, acNoMem, sizeof(acNoMem) - 1); exit(EX_UNAVAILABLE); } /* do a general cleanup and exit */ void Bye(int status) { DestroyDataStructures(); #if HAVE_OPENSSL # if OPENSSL_VERSION_NUMBER < 0x10100000L ERR_free_strings(); # endif #endif exit(status); } /* This returns a string with the current time in ascii form. * (same as ctime() but without the \n) * optionally returns the time in time_t form (pass in NULL if you don't care). * It's overwritten each time, so use it and forget it. */ const char * StrTime(time_t *ltime) { static char curtime[40]; /* just in case ctime() varies */ time_t tyme; tyme = time((time_t *)0); StrCpy(curtime, ctime(&tyme), sizeof(curtime)); curtime[24] = '\000'; /* might need to adjust this at some point */ if (ltime != NULL) *ltime = tyme; return (const char *)curtime; } #define STRING_ALLOC_SIZE 64 char * BuildStringChar(const char ch, STRING *msg) { if (msg->used + 1 >= msg->allocated) { if (0 == msg->allocated) { msg->allocated = STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)calloc(1, msg->allocated); } else { msg->allocated += STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)realloc(msg->string, msg->allocated); } CONDDEBUG((3, "BuildStringChar(): 0x%lx tried allocating %lu bytes", (void *)msg, msg->allocated)); if (msg->string == (char *)0) OutOfMem(); } if (msg->used) { msg->string[msg->used - 1] = ch; /* overwrite NULL and */ msg->string[msg->used++] = '\000'; /* increment by one */ CONDDEBUG((3, "BuildStringChar(): 0x%lx added 1 char (%d/%d now)", (void *)msg, msg->used, msg->allocated)); } else { msg->string[msg->used++] = ch; /* no NULL, so store stuff */ msg->string[msg->used++] = '\000'; /* and increment by two */ CONDDEBUG((3, "BuildStringChar(): 0x%lx added 2 chars (%d/%d now)", (void *)msg, msg->used, msg->allocated)); } return msg->string; } char * BuildString(const char *str, STRING *msg) { int len; if ((char *)0 == str) { msg->used = 0; if (msg->string != (char *)0) msg->string[0] = '\000'; CONDDEBUG((3, "BuildString(): 0x%lx reset", (void *)msg)); return msg->string; } if (msg->used) /* string or string + null? */ len = strlen(str); else len = strlen(str) + 1; if (msg->used + len >= msg->allocated) { if (0 == msg->allocated) { msg->allocated = (len / STRING_ALLOC_SIZE + 1) * STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)calloc(1, msg->allocated); } else { msg->allocated += ((msg->used + len - msg->allocated) / STRING_ALLOC_SIZE + 1) * STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)realloc(msg->string, msg->allocated); } CONDDEBUG((3, "BuildString(): 0x%lx tried allocating %lu bytes", (void *)msg, msg->allocated)); if (msg->string == (char *)0) OutOfMem(); } /* if msg->used, then len = strlen(), so we need to copy len + 1 to * get the NULL which we overwrote with the copy */ #if HAVE_MEMCPY if (msg->used) memcpy(msg->string + msg->used - 1, str, len + 1); else memcpy(msg->string, str, len); #else if (msg->used) bcopy(str, msg->string + msg->used - 1, len + 1); else bcopy(str, msg->string, len); #endif msg->used += len; CONDDEBUG((3, "BuildString(): 0x%lx added %d chars (%d/%d now)", (void *)msg, len, msg->used, msg->allocated)); return msg->string; } char * BuildStringN(const char *str, int n, STRING *msg) { int len; if ((char *)0 == str) { msg->used = 0; if (msg->string != (char *)0) msg->string[0] = '\000'; CONDDEBUG((3, "BuildStringN(): 0x%lx reset", (void *)msg)); return msg->string; } if (n <= 0) return msg->string; if (msg->used) len = n; else len = n + 1; if (msg->used + len >= msg->allocated) { if (0 == msg->allocated) { msg->allocated = (len / STRING_ALLOC_SIZE + 1) * STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)calloc(1, msg->allocated); } else { msg->allocated += ((msg->used + len - msg->allocated) / STRING_ALLOC_SIZE + 1) * STRING_ALLOC_SIZE * sizeof(char); msg->string = (char *)realloc(msg->string, msg->allocated); } CONDDEBUG((3, "BuildStringN(): 0x%lx tried allocating %lu bytes", (void *)msg, msg->allocated)); if (msg->string == (char *)0) OutOfMem(); } #if HAVE_MEMCPY memcpy(msg->string + (msg->used ? msg->used - 1 : 0), str, n); #else bcopy(str, msg->string + (msg->used ? msg->used - 1 : 0), n); #endif /* add a NULL */ msg->string[(msg->used ? msg->used - 1 : 0) + n] = '\000'; msg->used += len; CONDDEBUG((3, "BuildStringN(): 0x%lx added %d chars (%d/%d now)", (void *)msg, len, msg->used, msg->allocated)); return msg->string; } void * MemMove(void *dest, void *src, size_t n) { #if HAVE_MEMMOVE return memmove(dest, src, n); #else char *s = src; char *d = dest; if (s < d) { /* Moving from low mem to hi mem; start at end. */ for (s += n, d += n; n > 0; --n) *--d = *--s; } else if (s != d) { /* Moving from hi mem to low mem; start at beginning. */ for (; n > 0; --n) *d++ = *s++; } return dest; #endif } char * ShiftString(STRING *msg, int n) { if (msg == (STRING *)0 || n <= 0 || n > msg->used - 1) return (char *)0; MemMove(msg->string, msg->string + n, msg->used - n); msg->used -= n; return msg->string; } void InitString(STRING *msg) { msg->string = (char *)0; msg->used = msg->allocated = 0; } void DestroyString(STRING *msg) { if (msg->prev == (STRING *)0 && msg->next == (STRING *)0 && allStrings != msg) { CONDDEBUG((1, "DestroyString(): 0x%lx non-pooled string destroyed", (void *)msg, stringCount)); } else { if (msg->prev != (STRING *)0) msg->prev->next = msg->next; if (msg->next != (STRING *)0) msg->next->prev = msg->prev; if (msg == allStrings) { allStrings = msg->next; } stringCount--; CONDDEBUG((1, "DestroyString(): 0x%lx string destroyed (count==%d)", (void *)msg, stringCount)); } if (msg->allocated) free(msg->string); free(msg); } STRING * AllocString(void) { STRING *s; if ((s = (STRING *)calloc(1, sizeof(STRING))) == (STRING *)0) OutOfMem(); if (allStrings != (STRING *)0) { allStrings->prev = s; s->next = allStrings; } allStrings = s; InitString(s); stringCount++; CONDDEBUG((1, "AllocString(): 0x%lx created string #%d", (void *)s, stringCount)); return s; } void DestroyStrings(void) { while (allStrings != (STRING *)0) { DestroyString(allStrings); } } static STRING *mymsg = (STRING *)0; char * BuildTmpString(const char *str) { if (mymsg == (STRING *)0) mymsg = AllocString(); return BuildString(str, mymsg); } char * BuildTmpStringChar(const char c) { if (mymsg == (STRING *)0) mymsg = AllocString(); return BuildStringChar(c, mymsg); } char * ReadLine(FILE *fp, STRING *save, int *iLine) { static char buf[1024]; char *wholeline = (char *)0; char *ret = (char *)0; int i, buflen, peek, commentCheck = 1, comment = 0; static STRING *bufstr = (STRING *)0; static STRING *wholestr = (STRING *)0; if (bufstr == (STRING *)0) bufstr = AllocString(); if (wholestr == (STRING *)0) wholestr = AllocString(); peek = 0; wholeline = (char *)0; BuildString((char *)0, bufstr); BuildString((char *)0, wholestr); while (save->used || ((ret = fgets(buf, sizeof(buf), fp)) != (char *)0) || peek) { /* If we have a previously saved line, use it instead */ if (save->used) { StrCpy(buf, save->string, sizeof(buf)); BuildString((char *)0, save); } if (peek) { /* End of file? Never mind. */ if (ret == (char *)0) break; /* If we don't have a line continuation and we've seen * some worthy data */ if (!isspace((int)buf[0]) && (wholeline != (char *)0)) { BuildString((char *)0, save); BuildString(buf, save); break; } peek = 0; } if (commentCheck) { for (i = 0; buf[i] != '\000'; i++) if (!isspace((int)buf[i])) break; if (buf[i] == '#') { comment = 1; commentCheck = 0; } else if (buf[i] != '\000') { commentCheck = 0; } } /* Check for EOL */ buflen = strlen(buf); if ((buflen >= 1) && (buf[buflen - 1] == '\n')) { (*iLine)++; /* Finally have a whole line */ if (comment == 0 && commentCheck == 0) { /* Finish off the chunk without the \n */ buf[buflen - 1] = '\000'; BuildString(buf, bufstr); wholeline = BuildString(bufstr->string, wholestr); } peek = 1; comment = 0; commentCheck = 1; BuildString((char *)0, bufstr); } else { /* Save off the partial chunk */ BuildString(buf, bufstr); } } /* If we hit the EOF and weren't peeking ahead * and it's not a comment */ if (!peek && (ret == (char *)0) && (comment == 0) && (commentCheck == 0)) { (*iLine)++; wholeline = BuildString(bufstr->string, wholestr); } CONDDEBUG((1, "ReadLine(): returning <%s>", (wholeline != (char *)0) ? wholeline : "")); return wholeline; } /* show a character as a string so the user cannot mistake it for (ksb) * another */ char * FmtCtl(int ci, STRING *pcIn) { unsigned char c; BuildString((char *)0, pcIn); c = ci & 0xff; if (c > 127) { c -= 128; BuildString("M-", pcIn); } if (c < ' ' || c == '\177') { BuildStringChar('^', pcIn); BuildStringChar(c ^ 0100, pcIn); } else if (c == ' ') { BuildString("", pcIn); } else if (c == '^') { BuildString("", pcIn); } else if (c == '\\') { BuildString("", pcIn); } else { BuildStringChar(c, pcIn); } return pcIn->string; } void FmtCtlStr(char *pcIn, int len, STRING *pcOut) { unsigned char c; BuildString((char *)0, pcOut); if (pcIn == (char *)0) return; if (len < 0) len = strlen(pcIn); for (; len; len--, pcIn++) { c = *pcIn & 0xff; if (c > 127) { c -= 128; BuildString("M-", pcOut); } if (c < ' ' || c == '\177') { BuildStringChar('^', pcOut); BuildStringChar(c ^ 0100, pcOut); } else { BuildStringChar(c, pcOut); } } } void Debug(int level, char *fmt, ...) { va_list ap; if (fDebug < level) return; va_start(ap, fmt); if (isMultiProc) fprintf(stderr, "[%s] %s (%lu): DEBUG: [%s:%d] ", StrTime((time_t *)0), progname, (unsigned long)thepid, debugFileName, debugLineNo); else fprintf(stderr, "%s: DEBUG: [%s:%d] ", progname, debugFileName, debugLineNo); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } void Error(char *fmt, ...) { va_list ap; va_start(ap, fmt); if (isMultiProc) fprintf(stderr, "[%s] %s (%lu): ERROR: ", StrTime((time_t *)0), progname, (unsigned long)thepid); else fprintf(stderr, "%s: ", progname); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); fErrorPrinted = 1; } void Msg(char *fmt, ...) { va_list ap; va_start(ap, fmt); if (isMultiProc) fprintf(stdout, "[%s] %s (%lu): ", StrTime((time_t *)0), progname, (unsigned long)thepid); else fprintf(stdout, "%s: ", progname); vfprintf(stdout, fmt, ap); fprintf(stdout, "\n"); va_end(ap); } void Verbose(char *fmt, ...) { va_list ap; if (!fVerbose) return; va_start(ap, fmt); if (isMultiProc) fprintf(stdout, "[%s] %s (%lu): INFO: ", StrTime((time_t *)0), progname, (unsigned long)thepid); else fprintf(stdout, "%s: ", progname); vfprintf(stdout, fmt, ap); fprintf(stdout, "\n"); va_end(ap); } void SimpleSignal(int sig, RETSIGTYPE(*disp) (int)) { #if HAVE_SIGACTION struct sigaction sa; sa.sa_handler = disp; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(sig, &sa, NULL); #else signal(sig, disp); #endif } int GetMaxFiles(void) { int mf; #if HAVE_SYSCONF mf = sysconf(_SC_OPEN_MAX); #else # if HAVE_GETRLIMIT struct rlimit rl; getrlimit(RLIMIT_NOFILE, &rl); mf = rl.rlim_cur; # else # if HAVE_GETDTABLESIZE mf = getdtablesize(); # else # ifndef OPEN_MAX # define OPEN_MAX 64 # endif /* !OPEN_MAX */ mf = OPEN_MAX; # endif /* HAVE_GETDTABLESIZE */ # endif/* HAVE_GETRLIMIT */ #endif /* HAVE_SYSCONF */ #ifdef FD_SETSIZE if (FD_SETSIZE <= mf) { mf = (FD_SETSIZE - 1); } #endif CONDDEBUG((1, "GetMaxFiles(): maxfiles=%d", mf)); return mf; } /* Routines for the generic I/O stuff for conserver. This will handle * all open(), close(), read(), and write() calls. */ /* This encapsulates a regular file descriptor in a CONSFILE * object. Returns a CONSFILE pointer to that object. */ CONSFILE * FileOpenFD(int fd, enum consFileType type) { CONSFILE *cfp; if ((cfp = (CONSFILE *)calloc(1, sizeof(CONSFILE))) == (CONSFILE *)0) OutOfMem(); cfp->ftype = type; cfp->fd = fd; cfp->wbuf = AllocString(); #if HAVE_OPENSSL cfp->ssl = (SSL *)0; cfp->waitForRead = cfp->waitForWrite = FLAGFALSE; #endif #if DEBUG_CONSFILE_IO { char buf[1024]; sprintf(buf, "CONSFILE-%s-%lu-%d.w", progname, (unsigned long)thepid, fd); if ((cfp->debugwfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugwfd, buf, strlen(buf)); } sprintf(buf, "CONSFILE-%s-%lu-%d.r", progname, (unsigned long)thepid, fd); if ((cfp->debugrfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugrfd, buf, strlen(buf)); } } #endif CONDDEBUG((2, "FileOpenFD(): encapsulated fd %d type %d", fd, type)); return cfp; } /* This encapsulates a pipe pair in a CONSFILE * object. Returns a CONSFILE pointer to that object. */ CONSFILE * FileOpenPipe(int fd, int fdout) { CONSFILE *cfp; if ((cfp = (CONSFILE *)calloc(1, sizeof(CONSFILE))) == (CONSFILE *)0) OutOfMem(); cfp->ftype = simplePipe; cfp->fd = fd; cfp->fdout = fdout; cfp->wbuf = AllocString(); #if HAVE_OPENSSL cfp->ssl = (SSL *)0; cfp->waitForRead = cfp->waitForWrite = FLAGFALSE; #endif #if DEBUG_CONSFILE_IO { char buf[1024]; sprintf(buf, "CONSFILE-%s-%lu-%d.w", progname, (unsigned long)thepid, fdout); if ((cfp->debugwfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugwfd, buf, strlen(buf)); } sprintf(buf, "CONSFILE-%s-%lu-%d.r", progname, (unsigned long)thepid, fd); if ((cfp->debugrfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugrfd, buf, strlen(buf)); } } #endif CONDDEBUG((2, "FileOpenPipe(): encapsulated pipe pair fd %d and fd %d", fd, fdout)); return cfp; } /* This is to "unencapsulate" the file descriptor */ int FileUnopen(CONSFILE *cfp) { int retval = 0; if (cfp == (CONSFILE *)0) return 0; switch (cfp->ftype) { case simpleFile: retval = cfp->fd; break; case simplePipe: retval = cfp->fd; break; case simpleSocket: retval = cfp->fd; break; #if HAVE_OPENSSL case SSLSocket: retval = -1; break; #endif default: retval = -1; break; } CONDDEBUG((2, "FileUnopen(): unopened fd %d", cfp->fd)); DestroyString(cfp->wbuf); #if DEBUG_CONSFILE_IO if (cfp->debugwfd != -1) close(cfp->debugwfd); if (cfp->debugrfd != -1) close(cfp->debugrfd); #endif free(cfp); return retval; } /* This opens a file like open(2). Returns a CONSFILE pointer * or a (CONSFILE *)0 on error */ CONSFILE * FileOpen(const char *path, int flag, int mode) { CONSFILE *cfp; int fd; if (-1 == (fd = open(path, flag, mode))) { CONDDEBUG((2, "FileOpen(): failed to open `%s'", path)); return (CONSFILE *)0; } if ((cfp = (CONSFILE *)calloc(1, sizeof(CONSFILE))) == (CONSFILE *)0) OutOfMem(); cfp->ftype = simpleFile; cfp->fd = fd; cfp->wbuf = AllocString(); #if HAVE_OPENSSL cfp->ssl = (SSL *)0; cfp->waitForRead = cfp->waitForWrite = FLAGFALSE; #endif #if DEBUG_CONSFILE_IO { char buf[1024]; sprintf(buf, "CONSFILE-%s-%lu-%d.w", progname, (unsigned long)thepid, fd); if ((cfp->debugwfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugwfd, buf, strlen(buf)); } sprintf(buf, "CONSFILE-%s-%lu-%d.r", progname, (unsigned long)thepid, fd); if ((cfp->debugrfd = open(buf, O_WRONLY | O_CREAT | O_APPEND, 0644)) != -1) { sprintf(buf, "[---- STARTED - %s ----]\n", StrTime((time_t *)0)); write(cfp->debugrfd, buf, strlen(buf)); } } #endif CONDDEBUG((2, "FileOpen(): opened `%s' as fd %d", path, fd)); return cfp; } /* Unless otherwise stated, returns the same values as close(2). * The CONSFILE object passed in *CANNOT* be used once calling * this function - even if there was an error. */ int FileClose(CONSFILE **pcfp) { CONSFILE *cfp; int retval = 0; #if defined(__CYGWIN__) struct linger lingeropt; #endif cfp = *pcfp; if (cfp == (CONSFILE *)0) return 0; switch (cfp->ftype) { case simpleFile: do { retval = close(cfp->fd); } while (retval == -1 && errno == EINTR); break; case simplePipe: do { retval = close(cfp->fd); } while (retval == -1 && errno == EINTR); do { retval = close(cfp->fdout); } while (retval == -1 && errno == EINTR); break; case simpleSocket: #if defined(__CYGWIN__) /* flush out the client socket - set it to blocking, * then write to it */ SetFlags(cfp->fd, 0, O_NONBLOCK); /* sent it a byte - guaranteed to block - ensure delivery * of prior data yeah - this is a bit paranoid - try * without this at first */ /* write(cfp->fd, "\n", 1); */ /* this is the guts of the workaround for Winsock close bug */ shutdown(cfp->fd, 1); /* enable lingering */ lingeropt.l_onoff = 1; lingeropt.l_linger = 15; setsockopt(cfp->fd, SOL_SOCKET, SO_LINGER, &lingeropt, sizeof(lingeropt)); #endif do { retval = close(cfp->fd); } while (retval == -1 && errno == EINTR); break; #if HAVE_OPENSSL case SSLSocket: CONDDEBUG((2, "FileClose(): performing a SSL_shutdown() on fd %d", cfp->fd)); SSL_shutdown(cfp->ssl); CONDDEBUG((2, "FileClose(): performing a SSL_free() on fd %d", cfp->fd)); SSL_free(cfp->ssl); /* set the sucker back to a simpleSocket and recall so we * do all that special stuff we oh so love...and make sure * we return so we don't try and free(0). -bryan */ cfp->ftype = simpleSocket; return FileClose(pcfp); #endif default: retval = -1; break; } if (cfp->ftype == simplePipe) { CONDDEBUG((2, "FileClose(): closed fd %d/%d", cfp->fd, cfp->fdout)); } else { CONDDEBUG((2, "FileClose(): closed fd %d", cfp->fd)); } DestroyString(cfp->wbuf); #if DEBUG_CONSFILE_IO if (cfp->debugwfd != -1) close(cfp->debugwfd); if (cfp->debugrfd != -1) close(cfp->debugrfd); #endif free(cfp); *pcfp = (CONSFILE *)0; return retval; } /* returns: -1 on error or eof, >= 0 for valid reads */ int FileRead(CONSFILE *cfp, void *buf, int len) { int retval = -1; if (cfp->errored == FLAGTRUE) return -1; switch (cfp->ftype) { case simpleFile: case simplePipe: case simpleSocket: while (retval < 0) { if ((retval = read(cfp->fd, buf, len)) <= 0) { if (retval == 0) { retval = -1; break; } if (errno == EINTR) continue; if (errno == EAGAIN) { /* must be non-blocking - so stop */ retval = 0; break; } Error("FileRead(): fd %d: %s", cfp->fd, strerror(errno)); retval = -1; break; } #if DEBUG_CONSFILE_IO if (cfp->debugrfd != -1) write(cfp->debugrfd, buf, retval); #endif } break; #if HAVE_OPENSSL case SSLSocket: if (cfp->waitForWrite == FLAGTRUE) { cfp->waitForWrite = FLAGFALSE; if (cfp->wbuf->used <= 1) FD_CLR(cfp->fd, &winit); } retval = SSL_read(cfp->ssl, buf, len); switch (SSL_get_error(cfp->ssl, retval)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: retval = 0; break; case SSL_ERROR_WANT_WRITE: cfp->waitForWrite = FLAGTRUE; FD_SET(cfp->fd, &winit); retval = 0; break; default: Error("FileRead(): SSL error on fd %d", cfp->fd); /* fall through */ case SSL_ERROR_ZERO_RETURN: retval = -1; CONDDEBUG((2, "FileRead(): performing a SSL_shutdown() on fd %d", cfp->fd)); SSL_shutdown(cfp->ssl); CONDDEBUG((2, "FileRead(): performing a SSL_free() on fd %d", cfp->fd)); SSL_free(cfp->ssl); cfp->ssl = (SSL *)0; cfp->ftype = simpleSocket; break; } # if DEBUG_CONSFILE_IO if (cfp->debugrfd != -1) write(cfp->debugrfd, buf, retval); # endif break; #endif default: retval = -1; break; } if (retval >= 0) { CONDDEBUG((2, "FileRead(): read %d byte%s from fd %d", retval, (retval == 1) ? "" : "s", cfp->fd)); if (fDebug && buf != (char *)0) { static STRING *tmpString = (STRING *)0; if (tmpString == (STRING *)0) tmpString = AllocString(); BuildString((char *)0, tmpString); if (retval > 30) { FmtCtlStr(buf, 30, tmpString); CONDDEBUG((2, "FileRead(): read `%s'... from fd %d", tmpString->string, cfp->fd)); } else { FmtCtlStr(buf, retval, tmpString); CONDDEBUG((2, "FileRead(): read `%s' from fd %d", tmpString->string, cfp->fd)); } } } else { CONDDEBUG((2, "FileRead(): failed attempted read of %d byte%s from fd %d", len, (len == 1) ? "" : "s", cfp->fd)); } if (retval < 0) cfp->errored = FLAGTRUE; return retval; } /* returns: -1 on error or eof, >= 0 for valid reads */ int FileWrite(CONSFILE *cfp, FLAG bufferonly, char *buf, int len) { int len_orig = len; int len_out = 0; int retval = 0; int fdout = 0; if (cfp->ftype == simplePipe) fdout = cfp->fdout; else fdout = cfp->fd; if (cfp->errored == FLAGTRUE) { if (cfp->wbuf->used > 1) BuildString((char *)0, cfp->wbuf); FD_CLR(fdout, &winit); return -1; } if (len < 0 && buf != (char *)0) len = strlen(buf); if (fDebug && len > 0 && buf != (char *)0) { static STRING *tmpString = (STRING *)0; if (tmpString == (STRING *)0) tmpString = AllocString(); BuildString((char *)0, tmpString); if (len > 30) { FmtCtlStr(buf, 30, tmpString); CONDDEBUG((2, "FileWrite(): sending `%s'... to fd %d", tmpString->string, fdout)); } else { FmtCtlStr(buf, len, tmpString); CONDDEBUG((2, "FileWrite(): sending `%s' to fd %d", tmpString->string, fdout)); } } /* save the data */ if (len > 0 && buf != (char *)0) { if (cfp->quoteiac == FLAGTRUE) { int l, o; for (o = l = 0; l < len; l++) { if (buf[l] == (char)OB_IAC) { BuildStringN(buf + o, l + 1 - o, cfp->wbuf); BuildStringChar((char)OB_IAC, cfp->wbuf); o = l + 1; } } if (o < len) BuildStringN(buf + o, len - o, cfp->wbuf); } else BuildStringN(buf, len, cfp->wbuf); } if (bufferonly == FLAGTRUE) return 0; /* point at the local data */ buf = cfp->wbuf->string; len = cfp->wbuf->used - 1; /* if we don't have any, forget it */ if (buf == (char *)0 || len <= 0) return 0; /* so, we could be blocking or non-blocking. since we may be able * to block, we'll just keep trying to write while we have data and * stop when we hit an error or flush all the data. */ switch (cfp->ftype) { case simplePipe: case simpleFile: case simpleSocket: while (len > 0) { if ((retval = write(fdout, buf, len)) < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) { /* must be non-blocking - so stop */ retval = 0; break; } retval = -1; /* i believe, as of 8.0.8, we need to just ignore * this and actually produce the error message * below. perhaps we'll have a lot of extra * FileWrite() errors, perhaps not. things shouldn't * just close down and cause errors in normal cases, * right?!? -bryan * maybe not right now, actually. i'm going to check * the return code of FileWrite() on the "important" * things and let the others silently fail and have * the FileRead() catch problems - like it has been * doing. i really should be checking all the return * codes...and i'm sure i'll get there eventually. */ if (errno == EPIPE) break; Error("FileWrite(): fd %d: %s", fdout, strerror(errno)); break; } #if DEBUG_CONSFILE_IO if (cfp->debugwfd != -1) write(cfp->debugwfd, buf, retval); #endif buf += retval; len -= retval; len_out += retval; } break; #if HAVE_OPENSSL case SSLSocket: if (cfp->waitForRead == FLAGTRUE) cfp->waitForRead = FLAGFALSE; while (len > 0) { /* in theory, SSL_write always returns 'len' on success * so the while() loop is a noop. but, just in case i * read something wrong, we treat SSL_write like write(). */ retval = SSL_write(cfp->ssl, buf, len); switch (SSL_get_error(cfp->ssl, retval)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: cfp->waitForRead = FLAGTRUE; retval = len_out = 0; break; case SSL_ERROR_WANT_WRITE: retval = len_out = 0; break; default: Error("FileWrite(): SSL error on fd %d", cfp->fd); /* fall through */ case SSL_ERROR_ZERO_RETURN: retval = -1; CONDDEBUG((2, "FileWrite(): performing a SSL_shutdown() on fd %d", cfp->fd)); SSL_shutdown(cfp->ssl); CONDDEBUG((2, "FileWrite(): performing a SSL_free() on fd %d", cfp->fd)); SSL_free(cfp->ssl); cfp->ssl = (SSL *)0; cfp->ftype = simpleSocket; break; } if (retval <= 0) break; # if DEBUG_CONSFILE_IO if (cfp->debugwfd != -1) write(cfp->debugwfd, buf, retval); # endif buf += retval; len -= retval; len_out += retval; } break; #endif default: retval = -1; break; } /* so, if we saw an error, just bail...all is done anyway */ if (retval >= 0) { if (len_out > 0) { /* save the rest for later */ if (len > 0) { ShiftString(cfp->wbuf, len_out); } else { BuildString((char *)0, cfp->wbuf); } } retval = len_out; } if (retval < 0) { if (cfp->wbuf->used > 1) BuildString((char *)0, cfp->wbuf); cfp->errored = FLAGTRUE; } if (cfp->wbuf->used <= 1) FD_CLR(fdout, &winit); else { FD_SET(fdout, &winit); CONDDEBUG((2, "FileWrite(): buffered %d byte%s for fd %d", (cfp->wbuf->used <= 1) ? 0 : cfp->wbuf->used, (cfp->wbuf->used <= 1) ? "" : "s", fdout)); } if (retval >= 0) { CONDDEBUG((2, "FileWrite(): wrote %d byte%s to fd %d", retval, (retval == 1) ? "" : "s", fdout)); } else { CONDDEBUG((2, "FileWrite(): write of %d byte%s to fd %d: %s", len_orig, (retval == -1) ? "" : "s", fdout, strerror(errno))); } return retval; } int FileCanRead(CONSFILE *cfp, fd_set *prfd, fd_set *pwfd) { #if HAVE_OPENSSL int fdout; #endif if (cfp == (CONSFILE *)0) return 0; #if HAVE_OPENSSL if (cfp->ftype == simplePipe) fdout = cfp->fdout; else fdout = cfp->fd; #endif return ((FD_ISSET(cfp->fd, prfd) #if HAVE_OPENSSL && cfp->waitForRead != FLAGTRUE) || (fdout >= 0 && FD_ISSET(fdout, pwfd) && cfp->waitForWrite == FLAGTRUE #endif )); } int FileCanWrite(CONSFILE *cfp, fd_set *prfd, fd_set *pwfd) { int fdout; if (cfp == (CONSFILE *)0) return 0; if (cfp->ftype == simplePipe) fdout = cfp->fdout; else fdout = cfp->fd; return ((FD_ISSET(fdout, pwfd) #if HAVE_OPENSSL && cfp->waitForWrite != FLAGTRUE) || (FD_ISSET(cfp->fd, prfd) && cfp->waitForRead == FLAGTRUE #endif )); } int FileBufEmpty(CONSFILE *cfp) { if (cfp == (CONSFILE *)0) return 1; return (cfp->wbuf->used <= 1); } void VWrite(CONSFILE *cfp, FLAG bufferonly, STRING *str, char *fmt, va_list ap) { int s, l, e; char c; int fmtlen = 0; int fmtpre = 0; short padzero = 0; short sawdot = 0; static STRING *msg = (STRING *)0; static STRING *output = (STRING *)0; short flong = 0, fneg = 0, fminus = 0; if (fmt == (char *)0 || (cfp == (CONSFILE *)0 && str == (STRING *)0)) return; if (msg == (STRING *)0) msg = AllocString(); if (output == (STRING *)0) output = AllocString(); BuildString((char *)0, output); for (e = s = l = 0; (c = fmt[s + l]) != '\000'; l++) { if (e == 0 && c == '%') { e = 1; BuildStringN(fmt + s, l, output); s += l; l = 0; continue; } if (e) { unsigned long i; int u; char *p; char cc; if (c >= '0' && c <= '9') { if (sawdot == 0) { if (c == '0' && fmtlen == 0) padzero = 1; fmtlen = fmtlen * 10 + (c - '0'); } else { fmtpre = fmtpre * 10 + (c - '0'); } } else { switch (c) { case '.': sawdot = 1; continue; case '-': fminus = 1; continue; case 'h': /* noop since shorts are promoted to int in va_arg */ continue; case 'l': flong = 1; continue; case '%': BuildStringChar('%', output); break; case 'c': cc = (char)va_arg(ap, int); BuildStringChar(cc, output); break; case 's': p = va_arg(ap, char *); if (p == (char *)0) p = "(null)"; { int l = strlen(p); int c; if (fmtpre > 0 && fmtpre < l) l = fmtpre; if (fminus != 0) BuildStringN(p, l, output); for (c = l; c < fmtlen; c++) BuildStringChar(' ', output); if (fminus == 0) BuildStringN(p, l, output); } break; case 'd': i = (flong ? va_arg(ap, long) : (long) va_arg(ap, int)); if ((long)i < 0) { fneg = 1; i = -i; } goto number; case 'u': i = (flong ? va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int)); number: BuildString((char *)0, msg); while (i >= 10) { BuildStringChar((i % 10) + '0', msg); i /= 10; } BuildStringChar(i + '0', msg); if (fneg) BuildStringChar('-', msg); if (fmtpre > 0) { padzero = 0; if (fmtpre > fmtlen) fmtlen = fmtpre; while (msg->used - 1 < fmtpre) BuildStringChar('0', msg); } /* reverse the text to put it in forward order */ u = msg->used - 1; for (i = 0; i < u / 2; i++) { char temp; temp = msg->string[i]; msg->string[i] = msg->string[u - i - 1]; msg->string[u - i - 1] = temp; } { int l = msg->used - 1; if (fminus != 0) BuildString(msg->string, output); for (; l < fmtlen; l++) { if (padzero == 0 || fminus != 0) BuildStringChar(' ', output); else BuildStringChar('0', output); } if (fminus == 0) BuildString(msg->string, output); } break; case 'X': case 'x': i = (flong ? va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int)); BuildString((char *)0, msg); while (i >= 16) { if (i % 16 >= 10) BuildStringChar((i % 16) - 10 + (c == 'x' ? 'a' : 'A'), msg); else BuildStringChar((i % 16) + '0', msg); i /= 16; } if (i >= 10) BuildStringChar(i - 10 + (c == 'x' ? 'a' : 'A'), msg); else BuildStringChar(i + '0', msg); if (fmtpre > 0) { padzero = 0; if (fmtpre > fmtlen) fmtlen = fmtpre; while (msg->used - 1 < fmtpre) BuildStringChar('0', msg); } /* reverse the text to put it in forward order */ u = msg->used - 1; for (i = 0; i < u / 2; i++) { char temp; temp = msg->string[i]; msg->string[i] = msg->string[u - i - 1]; msg->string[u - i - 1] = temp; } { int l = msg->used - 1; if (fminus != 0) BuildString(msg->string, output); for (; l < fmtlen; l++) { if (padzero == 0 || fminus != 0) BuildStringChar(' ', output); else BuildStringChar('0', output); } if (fminus == 0) BuildString(msg->string, output); } break; default: Error ("VWrite(): unknown conversion character `%c' in `%s'", c, fmt); break; } s += l + 1; l = -1; e = flong = fneg = fminus = 0; fmtlen = fmtpre = sawdot = padzero = 0; } } } if (l) BuildStringN(fmt + s, l, output); if (str != (STRING *)0) BuildString((char *)0, str); if (output->used > 1) { if (str != (STRING *)0) BuildStringN(output->string, output->used - 1, str); if (cfp != (CONSFILE *)0) FileWrite(cfp, bufferonly, output->string, output->used - 1); } } char * BuildStringPrint(STRING *str, char *fmt, ...) { va_list ap; va_start(ap, fmt); VWrite((CONSFILE *)0, FLAGFALSE, str, fmt, ap); va_end(ap); if (str == (STRING *)0) return (char *)0; else return str->string; } char * BuildTmpStringPrint(char *fmt, ...) { va_list ap; va_start(ap, fmt); if (mymsg == (STRING *)0) mymsg = AllocString(); VWrite((CONSFILE *)0, FLAGFALSE, mymsg, fmt, ap); va_end(ap); return mymsg->string; } void FileVWrite(CONSFILE *cfp, FLAG bufferonly, char *fmt, va_list ap) { VWrite(cfp, bufferonly, (STRING *)0, fmt, ap); } void FilePrint(CONSFILE *cfp, FLAG bufferonly, char *fmt, ...) { va_list ap; va_start(ap, fmt); FileVWrite(cfp, bufferonly, fmt, ap); va_end(ap); } /* Unless otherwise stated, returns the same values as fstat(2) */ int FileStat(CONSFILE *cfp, struct stat *buf) { int retval = 0; if (cfp->errored == FLAGTRUE) return -1; switch (cfp->ftype) { case simpleFile: retval = fstat(cfp->fd, buf); break; case simplePipe: retval = -1; break; case simpleSocket: retval = fstat(cfp->fd, buf); break; #if HAVE_OPENSSL case SSLSocket: retval = -1; break; #endif default: retval = -1; break; } if (retval < 0) cfp->errored = FLAGTRUE; return retval; } /* Unless otherwise stated, returns the same values as lseek(2) */ int FileSeek(CONSFILE *cfp, off_t offset, int whence) { int retval = 0; if (cfp->errored == FLAGTRUE) return -1; switch (cfp->ftype) { case simpleFile: retval = lseek(cfp->fd, offset, whence); break; case simplePipe: retval = -1; break; case simpleSocket: retval = lseek(cfp->fd, offset, whence); break; #if HAVE_OPENSSL case SSLSocket: retval = -1; break; #endif default: retval = -1; break; } if (retval < 0) cfp->errored = FLAGTRUE; return retval; } /* Returns the file descriptor number of the underlying file */ int FileFDNum(CONSFILE *cfp) { int retval = 0; if (cfp == (CONSFILE *)0) return -1; switch (cfp->ftype) { case simpleFile: retval = cfp->fd; break; case simplePipe: retval = cfp->fd; break; case simpleSocket: retval = cfp->fd; break; #if HAVE_OPENSSL case SSLSocket: retval = cfp->fd; break; #endif default: retval = cfp->fd; break; } return retval; } /* Returns the file descriptor number of the underlying file */ int FileFDOutNum(CONSFILE *cfp) { if (cfp == (CONSFILE *)0 || cfp->ftype != simplePipe) return -1; return cfp->fdout; } /* Returns the file type */ enum consFileType FileGetType(CONSFILE *cfp) { switch (cfp->ftype) { case simpleFile: return simpleFile; case simplePipe: return simplePipe; case simpleSocket: return simpleSocket; #if HAVE_OPENSSL case SSLSocket: return SSLSocket; #endif default: return nothing; } } /* Sets the file type */ void FileSetType(CONSFILE *cfp, enum consFileType type) { cfp->ftype = type; } /* Sets the file quoting method */ void FileSetQuoteIAC(CONSFILE *cfp, FLAG flag) { cfp->quoteiac = flag; } FLAG FileSawQuoteSusp(CONSFILE *cfp) { FLAG r = cfp->sawiacsusp; cfp->sawiacsusp = FLAGFALSE; return r; } FLAG FileSawQuoteExec(CONSFILE *cfp) { FLAG r = cfp->sawiacexec; cfp->sawiacexec = FLAGFALSE; return r; } FLAG FileSawQuoteAbrt(CONSFILE *cfp) { FLAG r = cfp->sawiacabrt; cfp->sawiacabrt = FLAGFALSE; return r; } FLAG FileSawQuoteGoto(CONSFILE *cfp) { FLAG r = cfp->sawiacgoto; cfp->sawiacgoto = FLAGFALSE; return r; } #if HAVE_OPENSSL /* Get the SSL instance */ SSL * FileGetSSL(CONSFILE *cfp) { return cfp->ssl; } /* Sets the SSL instance */ void FileSetSSL(CONSFILE *cfp, SSL *ssl) { cfp->ssl = ssl; } /* return -1 on error, 0 for "wait" state, 1 for success */ int FileCanSSLAccept(CONSFILE *cfp, fd_set *prfd, fd_set *pwfd) { if (cfp == (CONSFILE *)0) return 0; return ((FD_ISSET(cfp->fd, prfd) && cfp->waitForRead == FLAGTRUE) || (FD_ISSET(cfp->fd, pwfd) && cfp->waitForWrite == FLAGTRUE) || (cfp->waitForRead != FLAGTRUE && cfp->waitForWrite != FLAGTRUE)); } /* return -1 on error, 0 for "wait" state, 1 for success */ int FileSSLAccept(CONSFILE *cfp) { int retval; if (cfp->waitForWrite == FLAGTRUE) { cfp->waitForWrite = FLAGFALSE; if (cfp->wbuf->used <= 1) FD_CLR(cfp->fd, &winit); } cfp->waitForRead = FLAGFALSE; CONDDEBUG((1, "FileSSLAccept(): about to SSL_accept() for fd %d", cfp->fd)); retval = SSL_accept(cfp->ssl); switch (SSL_get_error(cfp->ssl, retval)) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: cfp->waitForRead = FLAGTRUE; return 0; case SSL_ERROR_WANT_WRITE: cfp->waitForWrite = FLAGTRUE; FD_SET(cfp->fd, &winit); return 0; default: Error("FileSSLAccept(): SSL error on fd %d", cfp->fd); /* fall through */ case SSL_ERROR_ZERO_RETURN: SSL_free(cfp->ssl); cfp->ssl = (SSL *)0; cfp->ftype = simpleSocket; return -1; } cfp->ftype = SSLSocket; CONDDEBUG((1, "FileSSLAccept(): SSL Connection: %s :: %s", SSL_get_cipher_version(cfp->ssl), SSL_get_cipher_name(cfp->ssl))); return 1; } #endif /* Unless otherwise stated, returns the same values as send(2) */ int FileSend(CONSFILE *cfp, const void *msg, size_t len, int flags) { int retval = 0; int fdout; if (cfp->ftype == simplePipe) fdout = cfp->fdout; else fdout = cfp->fd; if (cfp->errored == FLAGTRUE) { FD_CLR(fdout, &winit); return -1; } switch (cfp->ftype) { case simpleFile: retval = send(fdout, msg, len, flags); break; case simplePipe: retval = send(fdout, msg, len, flags); break; case simpleSocket: retval = send(fdout, msg, len, flags); break; #if HAVE_OPENSSL case SSLSocket: retval = send(fdout, msg, len, flags); break; #endif default: retval = -1; break; } if (retval < 0) { cfp->errored = FLAGTRUE; FD_CLR(fdout, &winit); } return retval; } /* replace trailing space with '\000' in a string and return * a pointer to the start of the non-space part */ char * PruneSpace(char *string) { char *p; char *head = (char *)0; char *tail = (char *)0; /* Don't do much if it's crap */ if (string == (char *)0 || *string == '\000') return string; /* Now for the tricky part - search the string */ for (p = string; *p != '\000'; p++) { if (isspace((int)(*p))) { if (tail == (char *)0) tail = p; /* possible end of string */ } else { if (head == (char *)0) head = p; /* found the start */ tail = (char *)0; /* reset tail */ } } if (tail != (char *)0) *tail = '\000'; if (head != (char *)0) return head; else return string; } #if !USE_IPV6 /* fills the myAddrs array with host interface addresses */ void ProbeInterfaces(in_addr_t bindAddr) { # ifdef SIOCGIFCONF struct ifconf ifc; struct ifreq *ifr; # ifdef SIOCGIFFLAGS struct ifreq ifrcopy; # endif # ifdef SIOCGIFNUM int nifr; # endif int sock; int r = 0, m = 0; int bufsize = 2048; int count = 0; /* if we use -M, just fill the array with that interface */ if (bindAddr != INADDR_ANY) { myAddrs = (struct in_addr *)calloc(2, sizeof(struct in_addr)); if (myAddrs == (struct in_addr *)0) OutOfMem(); # if HAVE_MEMCPY memcpy(&(myAddrs[0].s_addr), &bindAddr, sizeof(in_addr_t)); # else bcopy(&bindAddr, &(myAddrs[0].s_addr), sizeof(in_addr_t)); # endif Verbose("interface address %s (-M option)", inet_ntoa(myAddrs[0])); return; } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { Error("ProbeInterfaces(): socket(): %s", strerror(errno)); Bye(EX_OSERR); } # ifdef SIOCGIFNUM if (ioctl(sock, SIOCGIFNUM, &nifr) == 0) bufsize = nifr * sizeof(struct ifreq) + 512; # endif while (bufsize) { ifc.ifc_len = bufsize; ifc.ifc_req = (struct ifreq *)malloc(ifc.ifc_len); if (ifc.ifc_req == (struct ifreq *)0) OutOfMem(); if (ioctl(sock, SIOCGIFCONF, &ifc) != 0 && errno != EINVAL) { free(ifc.ifc_req); close(sock); Error("ProbeInterfaces(): ioctl(SIOCGIFCONF): %s", strerror(errno)); Bye(EX_OSERR); } /* if the return size plus a 512 byte "buffer zone" is less than * the buffer we passed in (bufsize), we're done. otherwise * allocate a bigger buffer and try again. with a too-small * buffer, some implementations (freebsd) will fill the buffer * best it can (leaving a gap - returning <=bufsize) and others * (linux) will return a buffer length the same size as passed * in (==bufsize). so, we'll assume a 512 byte gap would have * been big enough to put one more record and as long as we have * that "buffer zone", we should have all the interfaces. * so, solaris returns EINVAL if it's too small, so we catch that * above and since if_len is bufsize, it'll loop again. */ if (ifc.ifc_len + 512 < bufsize) break; free(ifc.ifc_req); bufsize += 2048; } /* this is probably way overkill, but better to kill a few bytes * than loop through looking for valid interfaces that are up * twice, huh? */ count = ifc.ifc_len / sizeof(*ifr); CONDDEBUG((1, "ProbeInterfaces(): ifc_len==%d max_count==%d", ifc.ifc_len, count)); /* set up myAddrs array */ if (myAddrs != (struct in_addr *)0) free(myAddrs); myAddrs = (struct in_addr *)0; if (count == 0) { free(ifc.ifc_req); close(sock); return; } myAddrs = (struct in_addr *)calloc(count + 1, sizeof(struct in_addr)); if (myAddrs == (struct in_addr *)0) OutOfMem(); for (m = r = 0; r < ifc.ifc_len;) { struct sockaddr *sa; ifr = (struct ifreq *)&ifc.ifc_buf[r]; sa = (struct sockaddr *)&ifr->ifr_addr; /* don't use less than a ifreq sized chunk */ if ((ifc.ifc_len - r) < sizeof(*ifr)) break; # ifdef HAVE_SA_LEN if (sa->sa_len > sizeof(ifr->ifr_addr)) r += sizeof(ifr->ifr_name) + sa->sa_len; else # endif r += sizeof(*ifr); if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; /* make sure the address isn't 0.0.0.0, which is how we * signal the end of our list */ if ( # if HAVE_MEMCMP memcmp(&(myAddrs[m]), &(sin->sin_addr), sizeof(struct in_addr)) # else bcmp(&(myAddrs[m]), &(sin->sin_addr), sizeof(struct in_addr)) # endif == 0) continue; # ifdef SIOCGIFFLAGS /* make sure the interface is up */ ifrcopy = *ifr; if ((ioctl(sock, SIOCGIFFLAGS, &ifrcopy) == 0) && ((ifrcopy.ifr_flags & IFF_UP) == 0)) continue; # endif CONDDEBUG((1, "ProbeInterfaces(): name=%s addr=%s", ifr->ifr_name, inet_ntoa(sin->sin_addr))); # if HAVE_MEMCPY memcpy(&myAddrs[m], &(sin->sin_addr), sizeof(struct in_addr)); # else bcopy(&(sin->sin_addr), &myAddrs[m], sizeof(struct in_addr)); # endif Verbose("interface address %s (%s)", inet_ntoa(myAddrs[m]), ifr->ifr_name); m++; } } if (m == 0) { free(myAddrs); myAddrs = (struct in_addr *)0; } close(sock); free(ifc.ifc_req); # else/* use the hostname like the old code did (but use all addresses!) */ int count; struct hostent *he; /* if we use -M, just fill the array with that interface */ if (bindAddr != INADDR_ANY) { myAddrs = (struct in_addr *)calloc(2, sizeof(struct in_addr)); if (myAddrs == (struct in_addr *)0) OutOfMem(); # if HAVE_MEMCPY memcpy(&(myAddrs[0].s_addr), &bindAddr, sizeof(in_addr_t)); # else bcopy(&bindAddr, &(myAddrs[0].s_addr), sizeof(in_addr_t)); # endif Verbose("interface address %s (-M option)", inet_ntoa(myAddrs[0])); return; } Verbose("using hostname for interface addresses"); if ((struct hostent *)0 == (he = gethostbyname(myHostname))) { Error("ProbeInterfaces(): gethostbyname(%s): %s", myHostname, hstrerror(h_errno)); return; } if (4 != he->h_length || AF_INET != he->h_addrtype) { Error ("ProbeInterfaces(): gethostbyname(%s): wrong address size (4 != %d) or address family (%d != %d)", myHostname, he->h_length, AF_INET, he->h_addrtype); return; } for (count = 0; he->h_addr_list[count] != (char *)0; count++); if (myAddrs != (struct in_addr *)0) free(myAddrs); myAddrs = (struct in_addr *)0; if (count == 0) return; myAddrs = (struct in_addr *)calloc(count + 1, sizeof(struct in_addr)); if (myAddrs == (struct in_addr *)0) OutOfMem(); for (count--; count >= 0; count--) { # if HAVE_MEMCPY memcpy(&(myAddrs[count].s_addr), he->h_addr_list[count], he->h_length); # else bcopy(he->h_addr_list[count], &(myAddrs[count].s_addr), he->h_length); # endif Verbose("interface address %s (hostname address)", inet_ntoa(myAddrs[count])); } # endif } #endif /* USE_IPV6 */ int IsMe(char *id) { #if USE_IPV6 int ret = 0; int error; struct addrinfo hints; struct addrinfo *res, *rp; struct ifaddrs *myAddrs, *ifa; void *a, *b; size_t len; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; /* get IP based on hostname */ error = getaddrinfo(id, NULL, &hints, &res); if (error) { perror(gai_strerror(error)); return 0; } /* get list of all addresses on system */ error = getifaddrs(&myAddrs); if (error) { perror("getifaddrs failed"); return 0; } /* try to find a match */ for (ifa = myAddrs; ifa != NULL; ifa = ifa->ifa_next) { /* skip interfaces without address or in down state */ if (ifa->ifa_addr == NULL || !(ifa->ifa_flags & IFF_UP)) continue; for (rp = res; rp != NULL; rp = rp->ai_next) { if (ifa->ifa_addr->sa_family == rp->ai_addr->sa_family) { /* I really don't like to hardcode it but we have to */ if (ifa->ifa_addr->sa_family == AF_INET) { /* IPv4 */ a = &(((struct sockaddr_in *)ifa->ifa_addr)->sin_addr); b = &(((struct sockaddr_in *)rp->ai_addr)->sin_addr); len = sizeof(struct in_addr); } else { /* IPv6 */ a = &(((struct sockaddr_in6 *)ifa-> ifa_addr)->sin6_addr); b = &(((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr); len = sizeof(struct in6_addr); } if ( # if HAVE_MEMCMP memcmp(a, b, len) # else bcmp(a, b, len) # endif == 0) { ret = 1; goto done; } } } } done: freeaddrinfo(res); freeifaddrs(myAddrs); CONDDEBUG((1, "IsMe: ret %d id %s", ret, id)); return ret; #else int j, i; struct hostent *he; in_addr_t addr; # if HAVE_INET_ATON struct in_addr inetaddr; # endif /* check for ip address match */ # if HAVE_INET_ATON if (inet_aton(id, &inetaddr) != 0) { addr = inetaddr.s_addr; # else addr = inet_addr(id); if (addr != (in_addr_t) (-1)) { # endif for (i = 0; myAddrs != (struct in_addr *)0 && myAddrs[i].s_addr != (in_addr_t) 0; i++) { if ( # if HAVE_MEMCMP memcmp(&(myAddrs[i].s_addr), &addr, sizeof(addr)) # else bcmp(&(myAddrs[i].s_addr), &addr, sizeof(addr)) # endif == 0) return 1; } return 0; } /* check for ip match of hostname */ if ((struct hostent *)0 == (he = gethostbyname(id))) { Error("IsMe(): gethostbyname(%s): %s", id, hstrerror(h_errno)); return 0; } if (4 != he->h_length || AF_INET != he->h_addrtype) { Error ("IsMe(): gethostbyname(%s): wrong address size (4 != %d) or address family (%d != %d)", id, he->h_length, AF_INET, he->h_addrtype); return 0; } for (j = 0; he->h_addr_list[j] != (char *)0; j++) { for (i = 0; myAddrs != (struct in_addr *)0 && myAddrs[i].s_addr != (in_addr_t) 0; i++) { if ( # if HAVE_MEMCMP memcmp(&(myAddrs[i].s_addr), he->h_addr_list[j], he->h_length) # else bcmp(&(myAddrs[i].s_addr), he->h_addr_list[j], he->h_length) # endif == 0) return 1; } } return 0; #endif /* USE_IPV6 */ } #if HAVE_OPENSSL /* Unless otherwise stated, returns the same values as send(2) */ int SSLVerifyCallback(int ok, X509_STORE_CTX *store) { char data[256]; if (ok) { if (fDebug) { X509 *cert = X509_STORE_CTX_get_current_cert(store); int depth = X509_STORE_CTX_get_error_depth(store); CONDDEBUG((1, "SSLVerifyCallback(): info of certificate at depth: %d", depth)); X509_NAME_oneline(X509_get_issuer_name(cert), data, 256); CONDDEBUG((1, "SSLVerifyCallback(): issuer = %s", data)); X509_NAME_oneline(X509_get_subject_name(cert), data, 256); CONDDEBUG((1, "SSLVerifyCallback(): subject = %s", data)); } } else { X509 *cert = X509_STORE_CTX_get_current_cert(store); int depth = X509_STORE_CTX_get_error_depth(store); int err = X509_STORE_CTX_get_error(store); Error("SSLVerifyCallback(): error with certificate at depth: %d", depth); X509_NAME_oneline(X509_get_issuer_name(cert), data, 256); Error("SSLVerifyCallback(): issuer = %s", data); X509_NAME_oneline(X509_get_subject_name(cert), data, 256); Error("SSLVerifyCallback(): subject = %s", data); Error("SSLVerifyCallback(): error #%d: %s", err, X509_verify_cert_error_string(err)); } return ok; } #endif int SetFlags(int fd, int s, int c) { int flags; if ((flags = fcntl(fd, F_GETFL)) >= 0) { flags |= s; flags &= ~c; if (fcntl(fd, F_SETFL, flags) < 0) { Error("SetFlags(): fcntl(%u,F_SETFL): %s", fd, strerror(errno)); return 0; } } else { Error("SetFlags(): fcntl(%u,F_GETFL): %s", fd, strerror(errno)); return 0; } return 1; } char * StrDup(const char *msg) { int len; char *buf; if (msg == (char *)0) return (char *)0; len = strlen(msg) + 1; buf = malloc(len); if (buf == (char *)0) return (char *)0; #if HAVE_MEMCPY memcpy(buf, msg, len); #else bcopy(msg, buf, len); #endif return buf; } char * StringChar(STRING *msg, int offset, char c) { int o; if (msg == (STRING *)0 || msg->used <= 1 || offset < 0 || offset > msg->used) return (char *)0; for (o = offset; o != msg->used; o++) { if (msg->string[o] == c) return &(msg->string[o]); } return (char *)0; } /* this takes a buffer, and returns the number of characters to use, * which goes up to the first OB_IAC character sequence (that isn't * OB_IAC/OB_IAC). if it is an OB_IAC sequence, it sets the flag and * returns zero. if it's invalid args, we return -1. * so <0 == no data, 0 == check flags, >0 number of chars to use * this *WILL* modify the buffer (OB_IAC sequences get extracted/shrunk) */ int ParseIACBuf(CONSFILE *cfp, void *msg, int *len) { int l = 0; unsigned char *b = msg; if (*len <= 0) return -1; if (cfp->quoteiac != FLAGTRUE) return *len; /* split OB_IAC/char pair OR OB_IAC at start */ if (cfp->sawiac == FLAGTRUE || b[0] == OB_IAC) { int i = 1; if (cfp->sawiac == FLAGTRUE) { i = 0; cfp->sawiac = FLAGFALSE; } if (i == *len) { /* only thing is OB_IAC */ cfp->sawiac = FLAGTRUE; return -1; } if (b[i] == OB_SUSP) cfp->sawiacsusp = FLAGTRUE; else if (b[i] == OB_EXEC) cfp->sawiacexec = FLAGTRUE; else if (b[i] == OB_ABRT) cfp->sawiacabrt = FLAGTRUE; else if (b[i] == OB_GOTO) cfp->sawiacgoto = FLAGTRUE; else { if (b[i] != OB_IAC) Error ("ParseIACBuf(): fd %d: unrecognized quoted-OB_IAC char", cfp->fd, strerror(errno)); l = 1; } *len = *len - i - 1 + l; MemMove(b, b + i + 1 - l, *len); if (l == 0) return 0; } for (; l < *len; l++) { if (b[l] == OB_IAC) { if (l + 1 == *len) return l; if (b[l + 1] == OB_IAC) { --(*len); MemMove(b + l, b + l + 1, *len - l); } else { return l; } } } return l; } /* the format of the file should be as follows * *
[section name] { * [item value]; * . * . * } * * whitespace gets retained in [section name], and [item value] * values. for example, * * users bryan todd ; * * will give users the value of 'bryan todd'. the leading and * trailing whitespace is nuked, but the middle stuff isn't. * * a little note about the 'state' var... * START = before
* NAME = before [section name] * LEFTB = before left curly brace * KEY = before * VALUE = before [item value] * SEMI = before semi-colon */ typedef enum states { START, NAME, LEFTB, KEY, VALUE, SEMI } STATES; typedef enum tokens { DONE, LEFTBRACE, RIGHTBRACE, SEMICOLON, WORD, INCLUDE } TOKEN; int line = 1; /* current line number */ char *file = (char *)0; TOKEN GetWord(FILE *fp, int *line, short spaceok, STRING *word) { int c; short backslash = 0; short quote = 0; short comment = 0; short sawQuote = 0; short quotedBackslash = 0; char *include = "include"; short checkInc = -1; /* checkInc == -3, saw #include * == -2, saw nothin' * == -1, saw \n or start of file * == 0, saw "\n#" */ BuildString((char *)0, word); while ((c = fgetc(fp)) != EOF) { if (c == '\n') { (*line)++; if (checkInc == -2) checkInc = -1; } if (comment) { if (c == '\n') comment = 0; if (checkInc >= 0) { if (include[checkInc] == '\000') { if (isspace(c)) checkInc = -3; } else if (c == include[checkInc]) checkInc++; else checkInc = -2; } else if (checkInc == -3) { static STRING *fname = (STRING *)0; if (fname == (STRING *)0) fname = AllocString(); if (fname->used != 0 || !isspace(c)) { if (c == '\n') { if (fname->used > 0) { while (fname->used > 1 && isspace((int) (fname-> string [fname-> used - 2]))) fname->used--; if (fname->used > 0) fname->string[fname->used - 1] = '\000'; } checkInc = -2; if (fname->used > 0) { BuildString((char *)0, word); BuildString(fname->string, word); BuildString((char *)0, fname); return INCLUDE; } } else BuildStringChar(c, fname); } } continue; } if (backslash) { BuildStringChar(c, word); backslash = 0; continue; } if (quote) { if (c == '"') { if (quotedBackslash) { BuildStringChar(c, word); quotedBackslash = 0; } else quote = 0; } else { if (quotedBackslash) { BuildStringChar('\\', word); quotedBackslash = 0; } if (c == '\\') quotedBackslash = 1; else BuildStringChar(c, word); } continue; } if (c == '\\') { backslash = 1; } else if (c == '#') { comment = 1; if (checkInc == -1) checkInc = 0; } else if (c == '"') { quote = 1; sawQuote = 1; } else if (isspace(c)) { if (word->used <= 1) continue; if (spaceok) { BuildStringChar(c, word); continue; } gotword: while (word->used > 1 && isspace((int)(word->string[word->used - 2]))) word->used--; if (word->used > 0) word->string[word->used - 1] = '\000'; return WORD; } else if (c == '{') { if (word->used <= 1 && !sawQuote) { BuildStringChar(c, word); return LEFTBRACE; } else { ungetc(c, fp); goto gotword; } } else if (c == '}') { if (word->used <= 1 && !sawQuote) { BuildStringChar(c, word); return RIGHTBRACE; } else { ungetc(c, fp); goto gotword; } } else if (c == ';') { if (word->used <= 1 && !sawQuote) { BuildStringChar(c, word); return SEMICOLON; } else { ungetc(c, fp); goto gotword; } } else { BuildStringChar(c, word); } } /* this should only happen in rare cases */ if (quotedBackslash) { BuildStringChar('\\', word); quotedBackslash = 0; } /* if we saw "valid" data, it's a word */ if (word->used > 1 || sawQuote) goto gotword; return DONE; } void ParseFile(char *filename, FILE *fp, int level) { /* things that should be used between recursions */ static STATES state = START; static STRING *word = (STRING *)0; static short spaceok = 0; static int secIndex = 0; static int keyIndex = 0; /* other stuff that's local to each recursion */ char *p; TOKEN token = DONE; int nextline = 1; /* "next" line number */ if (level >= 10) { if (isMaster) Error("ParseFile(): nesting too deep, not parsing `%s'", filename); return; } /* set some globals */ line = 1; file = filename; /* if we're parsing the base file, set static vars */ if (level == 0) { state = START; spaceok = 0; secIndex = 0; keyIndex = 0; } /* initialize local things */ if (word == (STRING *)0) word = AllocString(); while ((token = GetWord(fp, &nextline, spaceok, word)) != DONE) { if (token == INCLUDE) { FILE *lfp; if ((FILE *)0 == (lfp = fopen(word->string, "r"))) { if (isMaster) Error("ParseFile(): fopen(%s): %s", word->string, strerror(errno)); } else { char *fname; /* word gets destroyed, so save the name */ fname = StrDup(word->string); ParseFile(fname, lfp, level + 1); fclose(lfp); free(fname); } } else { switch (state) { case START: switch (token) { case WORD: for (secIndex = 0; (p = sections[secIndex].id) != (char *)0; secIndex++) { if (strcasecmp(word->string, p) == 0) { CONDDEBUG((1, "ReadCfg(): got keyword '%s' [%s:%d]", word->string, file, line)); state = NAME; break; } } if (state == START) { if (isMaster) Error("invalid keyword '%s' [%s:%d]", word->string, file, line); } break; case LEFTBRACE: case RIGHTBRACE: case SEMICOLON: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; case NAME: switch (token) { case WORD: (*sections[secIndex].begin) (word->string); state = LEFTB; break; case RIGHTBRACE: if (isMaster) Error("premature token '%s' [%s:%d]", word->string, file, line); state = START; break; case LEFTBRACE: case SEMICOLON: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; case LEFTB: switch (token) { case LEFTBRACE: state = KEY; break; case RIGHTBRACE: if (isMaster) Error("premature token '%s' [%s:%d]", word->string, file, line); (*sections[secIndex].abort) (); state = START; break; case SEMICOLON: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case WORD: if (isMaster) Error("invalid word '%s' [%s:%d]", word->string, file, line); break; case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; case KEY: switch (token) { case WORD: for (keyIndex = 0; (p = sections[secIndex].items[keyIndex].id) != (char *)0; keyIndex++) { if (strcasecmp(word->string, p) == 0) { CONDDEBUG((1, "got keyword '%s' [%s:%d]", word->string, file, line)); state = VALUE; break; } } if (state == KEY) { if (isMaster) Error("invalid keyword '%s' [%s:%d]", word->string, file, line); } break; case RIGHTBRACE: (*sections[secIndex].end) (); state = START; break; case LEFTBRACE: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case SEMICOLON: if (isMaster) Error("premature token '%s' [%s:%d]", word->string, file, line); case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; case VALUE: switch (token) { case WORD: (*sections[secIndex]. items[keyIndex].reg) (word->string); state = SEMI; break; case SEMICOLON: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); state = KEY; break; case RIGHTBRACE: if (isMaster) Error("premature token '%s' [%s:%d]", word->string, file, line); (*sections[secIndex].abort) (); state = START; break; case LEFTBRACE: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; case SEMI: switch (token) { case SEMICOLON: state = KEY; break; case RIGHTBRACE: if (isMaster) Error("premature token '%s' [%s:%d]", word->string, file, line); (*sections[secIndex].abort) (); state = START; break; case LEFTBRACE: if (isMaster) Error("invalid token '%s' [%s:%d]", word->string, file, line); break; case WORD: if (isMaster) Error("invalid word '%s' [%s:%d]", word->string, file, line); break; case DONE: /* just shutting up gcc */ case INCLUDE: /* just shutting up gcc */ break; } break; } switch (state) { case NAME: case VALUE: spaceok = 1; break; case KEY: case LEFTB: case START: case SEMI: spaceok = 0; break; } } line = nextline; } if (level == 0) { int i; /* check for proper ending of file and do any cleanup */ switch (state) { case START: break; case KEY: case LEFTB: case VALUE: case SEMI: (*sections[secIndex].abort) (); /* fall through */ case NAME: if (isMaster) Error("premature EOF seen [%s:%d]", file, line); break; } /* now clean up all the temporary space used */ for (i = 0; sections[i].id != (char *)0; i++) { (*sections[i].destroy) (); } } } void ProcessSubst(SUBST *s, char **repl, char **str, char *name, char *id) { /* * (CONSENT *pCE) and (char **repl) are used when a replacement is to * actually happen...repl is the string to munch, pCE holds the data. * * (char **str) is used to store a copy of (char *id), if it passes * the format check. * * the idea is that this is first called when the config file is read, * putting the result in (char **str). then we call it again, near * the end, permuting (char **repl) with values from (CONSENT *pCE) with * the saved string now coming in as (char *id). got it? * * you could pass all arguments in...then both types of actions occur. */ char *p; char *repfmt[256]; unsigned short repnum; int i; enum repstate { REP_BEGIN, REP_LTR, REP_EQ, REP_INT, REP_END } state; if (s == (SUBST *)0) { Error("ProcessSubst(): WTF? No substitute support structure?!?!"); Bye(EX_SOFTWARE); } if (str != (char **)0) { if (*str != (char *)0) { free(*str); *str = (char *)0; } } if ((id == (char *)0) || (*id == '\000')) return; repnum = 0; state = REP_BEGIN; for (i = 0; i < 256; i++) repfmt[i] = (char *)0; for (p = id; *p != '\000'; p++) { switch (state) { case REP_BEGIN: /* must be printable */ if (*p == ',' || !isgraph((int)(*p))) goto subst_err; /* make sure we haven't seen this replacement char yet */ repnum = (unsigned short)(*p); if (repfmt[repnum] != (char *)0) { if (isMaster) Error ("substitution characters of `%s' option are the same [%s:%d]", name, file, line); return; } state = REP_LTR; break; case REP_LTR: if (*p != '=') goto subst_err; state = REP_EQ; break; case REP_EQ: repfmt[repnum] = p; if (s->token(*(repfmt[repnum])) != ISNOTHING) state = REP_INT; else goto subst_err; break; case REP_INT: if (*p == 'd' || *p == 'x' || *p == 'X' || *p == 'a' || *p == 'A') { if (s->token(*(repfmt[repnum])) != ISNUMBER) goto subst_err; state = REP_END; } else if (*p == 's') { if (s->token(*(repfmt[repnum])) != ISSTRING) goto subst_err; state = REP_END; } else if (!isdigit((int)(*p))) goto subst_err; break; case REP_END: if (*p != ',') goto subst_err; state = REP_BEGIN; break; } } if (state != REP_END) { subst_err: if (isMaster) Error ("invalid `%s' specification `%s' (char #%d: `%c') [%s:%d]", name, id, (p - id) + 1, *p, file, line); return; } if (str != (char **)0) { if ((*str = StrDup(id)) == (char *)0) OutOfMem(); } if (s != (SUBST *)0 && repl != (char **)0 && *repl != (char *)0) { static STRING *result = (STRING *)0; if (result == (STRING *)0) result = AllocString(); BuildString((char *)0, result); for (p = *repl; *p != '\000'; p++) { if (repfmt[(unsigned short)(*p)] != (char *)0) { char *r = repfmt[(unsigned short)(*p)]; int plen = 0; char *c = (char *)0; int o = 0; if (s->token(*r) == ISSTRING) { /* check the pattern for a length */ if (isdigit((int)(*(r + 1)))) plen = atoi(r + 1); /* this should never return zero, but just in case */ if ((*s->value) (*r, &c, (int *)0) == 0) c = ""; plen -= strlen(c); /* pad it out, if necessary */ for (i = 0; i < plen; i++) BuildStringChar(' ', result); /* throw in the string */ BuildString(c, result); } else { int i = 0; unsigned short port = 0; unsigned short base = 0; int padzero = 0; static STRING *num = (STRING *)0; if (num == (STRING *)0) num = AllocString(); BuildString((char *)0, num); /* this should never return zero, but just in case */ if ((*s->value) (*r, (char **)0, &i) == 0) port = 0; else port = (unsigned short)i; /* check the pattern for a length and padding */ for (c = r + 1; *c != '\000'; c++) if (!isdigit((int)(*c))) break; if (c != r + 1) { plen = atoi(r + 1); padzero = (r[1] == '0'); } /* check for base */ switch (*c) { case 'd': base = 10; break; case 'x': case 'X': base = 16; break; case 'a': case 'A': base = 36; break; default: return; } while (port >= base) { if (port % base >= 10) BuildStringChar((port % base) - 10 + ((*c == 'x' || *c == 'a') ? 'a' : 'A'), num); else BuildStringChar((port % base) + '0', num); port /= base; } if (port >= 10) BuildStringChar(port - 10 + ((*c == 'x' || *c == 'a') ? 'a' : 'A'), num); else BuildStringChar(port + '0', num); /* if we're supposed to be a certain length, pad it */ while (num->used - 1 < plen) { if (padzero == 0) BuildStringChar(' ', num); else BuildStringChar('0', num); } /* reverse the text to put it in forward order */ o = num->used - 1; for (i = 0; i < o / 2; i++) { char temp; temp = num->string[i]; num->string[i] = num->string[o - i - 1]; num->string[o - i - 1] = temp; } BuildStringN(num->string, o, result); } } else BuildStringChar(*p, result); } free(*repl); if ((*repl = StrDup(result->string)) == (char *)0) OutOfMem(); } return; } char * MyVersion(void) { static STRING *version = (STRING *)0; if (version != (STRING *)0) return version->string; version = AllocString(); BuildStringPrint(version, "%s %d.%d.%d", VERSION_TEXT, VERSION_MAJOR, VERSION_MINOR, VERSION_REV); return version->string; } unsigned int AtoU(char *str) { unsigned int v; int i; v = 0; for (i = 0; isdigit((int)str[i]); i++) { v *= 10; v += str[i] - '0'; } return v; } void StrCpy(char *dst, const char *src, unsigned int size) { #ifdef HAVE_STRLCPY strlcpy(dst, src, size); #else strcpy(dst, src); #endif }