diff options
Diffstat (limited to 'libexec')
265 files changed, 56454 insertions, 0 deletions
diff --git a/libexec/Makefile b/libexec/Makefile new file mode 100644 index 0000000..7d1c1f8 --- /dev/null +++ b/libexec/Makefile @@ -0,0 +1,95 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +.include <src.opts.mk> + +SUBDIR= ${_atf} \ + ${_atrun} \ + bootpd \ + ${_casper} \ + ${_comsat} \ + ${_dma} \ + ${_dma-mbox-create} \ + fingerd \ + ftpd \ + getty \ + ${_mail.local} \ + ${_mknetid} \ + ${_pppoed} \ + rbootd \ + revnetgroup \ + ${_rlogind} \ + rpc.rquotad \ + rpc.rstatd \ + rpc.rusersd \ + rpc.rwalld \ + rpc.sprayd \ + ${_rshd} \ + ${_rtld-elf} \ + save-entropy \ + ${_smrsh} \ + talkd \ + tcpd \ + ${_telnetd} \ + ${_tests} \ + tftpd \ + ${_tftp-proxy} \ + ulog-helper \ + ${_ypxfr} + +.if ${MK_AT} != "no" +_atrun= atrun +.endif + +.if ${MK_CASPER} != "no" +_casper= casper +.endif + +.if ${MK_MAIL} != "no" +_comsat= comsat +.endif + +.if ${MK_DMAGENT} != "no" +_dma= dma +_dma-mbox-create= dma-mbox-create +.endif + +.if ${MK_NIS} != "no" +_mknetid= mknetid +_ypxfr= ypxfr +.endif + +.if ${MK_NETGRAPH} != "no" +_pppoed= pppoed +.endif + +.if ${MK_PF} != "no" +_tftp-proxy= tftp-proxy +.endif + +.if !defined(NO_PIC) && !defined(NO_RTLD) +_rtld-elf= rtld-elf +.endif + +.if ${MK_RCMDS} != "no" +_rlogind= rlogind +_rshd= rshd +.endif + +.if ${MK_SENDMAIL} != "no" +_mail.local= mail.local +_smrsh= smrsh +.endif + +.if ${MK_TELNET} != "no" +_telnetd= telnetd +.endif + +.if ${MK_TESTS} != "no" +_atf= atf +_tests= tests +.endif + +.include <bsd.arch.inc.mk> + +.include <bsd.subdir.mk> diff --git a/libexec/Makefile.amd64 b/libexec/Makefile.amd64 new file mode 100644 index 0000000..1092a29 --- /dev/null +++ b/libexec/Makefile.amd64 @@ -0,0 +1,5 @@ +# $FreeBSD$ + +.if ${MK_HYPERV} != "no" +SUBDIR+= hyperv +.endif diff --git a/libexec/Makefile.i386 b/libexec/Makefile.i386 new file mode 100644 index 0000000..1092a29 --- /dev/null +++ b/libexec/Makefile.i386 @@ -0,0 +1,5 @@ +# $FreeBSD$ + +.if ${MK_HYPERV} != "no" +SUBDIR+= hyperv +.endif diff --git a/libexec/Makefile.inc b/libexec/Makefile.inc new file mode 100644 index 0000000..7b6a65f --- /dev/null +++ b/libexec/Makefile.inc @@ -0,0 +1,7 @@ +# @(#)Makefile.inc 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +BINDIR?= /usr/libexec + +WARNS?= 6 +WFORMAT?= 1 diff --git a/libexec/Makefile.pc98 b/libexec/Makefile.pc98 new file mode 100644 index 0000000..a755298 --- /dev/null +++ b/libexec/Makefile.pc98 @@ -0,0 +1,4 @@ +# $FreeBSD$ + +# Because i386 adds extra stuff we don't need or want for PC98 we need +# an empty file so it doesn't get added. diff --git a/libexec/atf/Makefile b/libexec/atf/Makefile new file mode 100644 index 0000000..db7554d --- /dev/null +++ b/libexec/atf/Makefile @@ -0,0 +1,30 @@ +#- +# Copyright (c) 2011 Google, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +SUBDIR= atf-check atf-sh tests + +.include <bsd.subdir.mk> diff --git a/libexec/atf/Makefile.inc b/libexec/atf/Makefile.inc new file mode 100644 index 0000000..2521be6 --- /dev/null +++ b/libexec/atf/Makefile.inc @@ -0,0 +1,32 @@ +#- +# Copyright (c) 2011 Google, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +CFLAGS+= -DHAVE_CONFIG_H + +WARNS?= 3 + +.include "../Makefile.inc" diff --git a/libexec/atf/atf-check/Makefile b/libexec/atf/atf-check/Makefile new file mode 100644 index 0000000..2a9851e --- /dev/null +++ b/libexec/atf/atf-check/Makefile @@ -0,0 +1,49 @@ +#- +# Copyright (c) 2011 Google, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +.include <src.opts.mk> +.include <bsd.init.mk> + +ATF= ${.CURDIR:H:H:H}/contrib/atf +.PATH: ${ATF}/atf-sh + +PROG_CXX= atf-check +SRCS= atf-check.cpp +MAN= atf-check.1 + +CFLAGS+= -I${ATF} +CFLAGS+= -DATF_SHELL='"/bin/sh"' + +DPADD+= ${LIBATF_CXX} ${LIBATF_C} +LDADD+= ${LDATF_CXX} ${LDATF_C} +USEPRIVATELIB= atf-c++ atf-c + +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif + +.include <bsd.prog.mk> diff --git a/libexec/atf/atf-check/Makefile.inc b/libexec/atf/atf-check/Makefile.inc new file mode 100644 index 0000000..265f86d --- /dev/null +++ b/libexec/atf/atf-check/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +.include "../Makefile.inc" diff --git a/libexec/atf/atf-check/tests/Makefile b/libexec/atf/atf-check/tests/Makefile new file mode 100644 index 0000000..43a7498 --- /dev/null +++ b/libexec/atf/atf-check/tests/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +.include <bsd.init.mk> + +TESTSDIR= ${TESTSBASE}/libexec/atf/atf-check + +ATF= ${.CURDIR:H:H:H:H}/contrib/atf +.PATH: ${ATF}/atf-sh + +ATF_TESTS_SH= atf-check_test + +.include <bsd.test.mk> diff --git a/libexec/atf/atf-sh/Makefile b/libexec/atf/atf-sh/Makefile new file mode 100644 index 0000000..4e14643 --- /dev/null +++ b/libexec/atf/atf-sh/Makefile @@ -0,0 +1,59 @@ +#- +# Copyright (c) 2011 Google, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +.include <src.opts.mk> +.include <bsd.init.mk> + +ATF= ${.CURDIR:H:H:H}/contrib/atf +.PATH: ${ATF}/atf-sh + +PROG_CXX= atf-sh +SRCS= atf-sh.cpp +MAN= atf-sh.1 atf-sh.3 +MLINKS+= atf-sh.3 atf-sh-api.3 # Backwards compatibility. + +CFLAGS+= -DHAVE_CONFIG_H +CFLAGS+= -DATF_LIBEXECDIR='"${LIBEXECDIR}"' +CFLAGS+= -DATF_PKGDATADIR='"${SHAREDIR}/atf"' +CFLAGS+= -DATF_SHELL='"/bin/sh"' +CFLAGS+= -I${ATF} + +DPADD+= ${LIBATF_C} ${LIBATF_CXX} +LDADD+= ${LDATF_C} ${LDATF_CXX} +USEPRIVATELIB= atf-c++ atf-c + +FILESGROUPS= SUBR + +SUBRDIR= ${SHAREDIR}/atf +SUBR= libatf-sh.subr + +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif + +.include "../../../lib/atf/common.mk" +.include <bsd.prog.mk> diff --git a/libexec/atf/atf-sh/tests/Makefile b/libexec/atf/atf-sh/tests/Makefile new file mode 100644 index 0000000..3360b97 --- /dev/null +++ b/libexec/atf/atf-sh/tests/Makefile @@ -0,0 +1,30 @@ +# $FreeBSD$ + +.include <bsd.init.mk> + +TESTSDIR= ${TESTSBASE}/libexec/atf/atf-sh + +ATF= ${.CURDIR:H:H:H:H}/contrib/atf +.PATH: ${ATF}/atf-sh + +ATF_TESTS_SH+= atf_check_test +ATF_TESTS_SH+= config_test +ATF_TESTS_SH+= integration_test +ATF_TESTS_SH+= normalize_test +ATF_TESTS_SH+= tc_test +ATF_TESTS_SH+= tp_test + +integration_test: Makefile +ATF_TESTS_SH_SED_integration_test= \ + -e 's,__ATF_SH__,/usr/libexec/atf-sh,g' + +SCRIPTS+= misc_helpers +SCRIPTSDIR_misc_helpers=${TESTSDIR} +CLEANFILES+= misc_helpers misc_helpers.tmp +misc_helpers: misc_helpers.sh + echo '#! /usr/libexec/atf-sh' >${.TARGET}.tmp + cat ${.ALLSRC} >>${.TARGET}.tmp + chmod +x ${.TARGET}.tmp + mv ${.TARGET}.tmp ${.TARGET} + +.include <bsd.test.mk> diff --git a/libexec/atf/tests/Makefile b/libexec/atf/tests/Makefile new file mode 100644 index 0000000..7aa9601 --- /dev/null +++ b/libexec/atf/tests/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +TESTSDIR= ${TESTSBASE}/libexec/atf + +.PATH: ${.CURDIR:H:H:H}/tests +KYUAFILE= yes + +.include <bsd.test.mk> diff --git a/libexec/atrun/LEGAL b/libexec/atrun/LEGAL new file mode 100644 index 0000000..af8be57 --- /dev/null +++ b/libexec/atrun/LEGAL @@ -0,0 +1,31 @@ +# $FreeBSD$ + +-----BEGIN PGP SIGNED MESSAGE----- + +Sorry for the long wait, but there still were a few things to +be ironed out in at, which I've finally done :-) + +The FreeBSD team does have my permission to use at, version 2.9, +under the BSD license. + +You'll find it on sunsite.unc.edu's Incoming, hopefully; the +md5 checksum is + +3ba2ca3c0e87e1a04feae2c6c1376b0d at-2.9.tgz + +Best regards + Thomas +- -- +Thomas Koenig, Thomas.Koenig@ciw.uni-karlsruhe.de, ig25@dkauni2.bitnet. +The joy of engineering is to find a straight line on a double +logarithmic diagram. + +-----BEGIN PGP SIGNATURE----- +Version: 2.6.2i + +iQCVAwUBMCjVrPBu+cbJcKCVAQFNiQP/dpWP57s/E8plVGUD3zfgOXDmKUvg8U7a +VwRzJrIMuSgnSJs0wkpvcomc3NLicipfX7hhWLh/xatPM2YbF7O5HZoNdvWvexD2 +1Y67zJ+0HFb1mPnSBOrS5RFiQAe3KqmGec6E14Rih/qNoFQZBVRFXZ4xxuwP+0Rs +e2U+TVTUz6A= +=TvyW +-----END PGP SIGNATURE----- diff --git a/libexec/atrun/Makefile b/libexec/atrun/Makefile new file mode 100644 index 0000000..2730559 --- /dev/null +++ b/libexec/atrun/Makefile @@ -0,0 +1,32 @@ +# $FreeBSD$ + +MAINSRC=${.CURDIR}/../../usr.bin/at + +.include "${MAINSRC}/Makefile.inc" + +PROG= atrun +SRCS= atrun.c gloadavg.c +MAN= atrun.8 + +BINDIR= ${ATLIB_DIR} +CLEANFILES= ${MAN} + +CFLAGS+=-I${MAINSRC} -I${.CURDIR} +CFLAGS+=-DLOGIN_CAP -DPAM + +WARNS?= 2 +WFORMAT=0 + +DPADD= ${LIBPAM} ${LIBUTIL} +LDADD= ${MINUSLPAM} -lutil + +atrun.8: atrun.man + @${ECHO} Making ${.TARGET:T} from ${.ALLSRC:T}; \ + sed -e \ + "s@_ATSPOOL_DIR@$(ATSPOOL_DIR)@g; \ + s@_ATJOB_DIR@$(ATJOB_DIR)@g; \ + s@_ATLIB_DIR@$(ATLIB_DIR)@g; \ + s@_LOADAVG_MX@$(LOADAVG_MX)@g;" \ + < ${.ALLSRC} > ${.TARGET} + +.include <bsd.prog.mk> diff --git a/libexec/atrun/atrun.c b/libexec/atrun/atrun.c new file mode 100644 index 0000000..1e25766 --- /dev/null +++ b/libexec/atrun/atrun.c @@ -0,0 +1,589 @@ +/* + * atrun.c - run jobs queued by at; run with root privileges. + * Copyright (C) 1993, 1994 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* System Headers */ + +#include <sys/fcntl.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef __FreeBSD__ +#include <sys/sysctl.h> +#endif +#include <sys/wait.h> +#include <sys/param.h> +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#ifdef __FreeBSD__ +#include <paths.h> +#else +#include <getopt.h> +#endif +#ifdef LOGIN_CAP +#include <login_cap.h> +#endif +#ifdef PAM +#include <security/pam_appl.h> +#include <security/openpam.h> +#endif + +/* Local headers */ + +#include "gloadavg.h" +#define MAIN +#include "privs.h" + +/* Macros */ + +#ifndef ATJOB_DIR +#define ATJOB_DIR "/usr/spool/atjobs/" +#endif + +#ifndef ATSPOOL_DIR +#define ATSPOOL_DIR "/usr/spool/atspool/" +#endif + +#ifndef LOADAVG_MX +#define LOADAVG_MX 1.5 +#endif + +/* File scope variables */ + +static const char * const atrun = "atrun"; /* service name for syslog etc. */ +static int debug = 0; + +void perr(const char *fmt, ...); +void perrx(const char *fmt, ...); +static void usage(void); + +/* Local functions */ +static int +write_string(int fd, const char* a) +{ + return write(fd, a, strlen(a)); +} + +#undef DEBUG_FORK +#ifdef DEBUG_FORK +static pid_t +myfork(void) +{ + pid_t res; + res = fork(); + if (res == 0) + kill(getpid(),SIGSTOP); + return res; +} + +#define fork myfork +#endif + +static void +run_file(const char *filename, uid_t uid, gid_t gid) +{ +/* Run a file by spawning off a process which redirects I/O, + * spawns a subshell, then waits for it to complete and sends + * mail to the user. + */ + pid_t pid; + int fd_out, fd_in; + int queue; + char mailbuf[MAXLOGNAME], fmt[64]; + char *mailname = NULL; + FILE *stream; + int send_mail = 0; + struct stat buf, lbuf; + off_t size; + struct passwd *pentry; + int fflags; + long nuid; + long ngid; +#ifdef PAM + pam_handle_t *pamh = NULL; + int pam_err; + struct pam_conv pamc = { + .conv = openpam_nullconv, + .appdata_ptr = NULL + }; +#endif + + PRIV_START + + if (chmod(filename, S_IRUSR) != 0) + { + perr("cannot change file permissions"); + } + + PRIV_END + + pid = fork(); + if (pid == -1) + perr("cannot fork"); + + else if (pid != 0) + return; + + /* Let's see who we mail to. Hopefully, we can read it from + * the command file; if not, send it to the owner, or, failing that, + * to root. + */ + + pentry = getpwuid(uid); + if (pentry == NULL) + perrx("Userid %lu not found - aborting job %s", + (unsigned long) uid, filename); + +#ifdef PAM + PRIV_START + + pam_err = pam_start(atrun, pentry->pw_name, &pamc, &pamh); + if (pam_err != PAM_SUCCESS) + perrx("cannot start PAM: %s", pam_strerror(pamh, pam_err)); + + pam_err = pam_acct_mgmt(pamh, PAM_SILENT); + /* Expired password shouldn't prevent the job from running. */ + if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) + perrx("Account %s (userid %lu) unavailable for job %s: %s", + pentry->pw_name, (unsigned long)uid, + filename, pam_strerror(pamh, pam_err)); + + pam_end(pamh, pam_err); + + PRIV_END +#endif /* PAM */ + + PRIV_START + + stream=fopen(filename, "r"); + + PRIV_END + + if (stream == NULL) + perr("cannot open input file %s", filename); + + if ((fd_in = dup(fileno(stream))) <0) + perr("error duplicating input file descriptor"); + + if (fstat(fd_in, &buf) == -1) + perr("error in fstat of input file descriptor"); + + if (lstat(filename, &lbuf) == -1) + perr("error in fstat of input file"); + + if (S_ISLNK(lbuf.st_mode)) + perrx("Symbolic link encountered in job %s - aborting", filename); + + if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) || + (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) || + (lbuf.st_size!=buf.st_size)) + perrx("Somebody changed files from under us for job %s - aborting", + filename); + + if (buf.st_nlink > 1) + perrx("Somebody is trying to run a linked script for job %s", filename); + + if ((fflags = fcntl(fd_in, F_GETFD)) <0) + perr("error in fcntl"); + + fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); + + snprintf(fmt, sizeof(fmt), + "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d", + MAXLOGNAME - 1); + + if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) + perrx("File %s is in wrong format - aborting", filename); + + if (mailbuf[0] == '-') + perrx("Illegal mail name %s in %s", mailbuf, filename); + + mailname = mailbuf; + + if (nuid != uid) + perrx("Job %s - userid %ld does not match file uid %lu", + filename, nuid, (unsigned long)uid); + + if (ngid != gid) + perrx("Job %s - groupid %ld does not match file gid %lu", + filename, ngid, (unsigned long)gid); + + fclose(stream); + + if (chdir(ATSPOOL_DIR) < 0) + perr("cannot chdir to %s", ATSPOOL_DIR); + + /* Create a file to hold the output of the job we are about to run. + * Write the mail header. + */ + if((fd_out=open(filename, + O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) + perr("cannot create output file"); + + write_string(fd_out, "Subject: Output from your job "); + write_string(fd_out, filename); + write_string(fd_out, "\n\n"); + fstat(fd_out, &buf); + size = buf.st_size; + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + pid = fork(); + if (pid < 0) + perr("error in fork"); + + else if (pid == 0) + { + char *nul = NULL; + char **nenvp = &nul; + + /* Set up things for the child; we want standard input from the input file, + * and standard output and error sent to our output file. + */ + + if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) + perr("error in lseek"); + + if (dup(fd_in) != STDIN_FILENO) + perr("error in I/O redirection"); + + if (dup(fd_out) != STDOUT_FILENO) + perr("error in I/O redirection"); + + if (dup(fd_out) != STDERR_FILENO) + perr("error in I/O redirection"); + + close(fd_in); + close(fd_out); + if (chdir(ATJOB_DIR) < 0) + perr("cannot chdir to %s", ATJOB_DIR); + + queue = *filename; + + PRIV_START + + nice(tolower(queue) - 'a'); + +#ifdef LOGIN_CAP + /* + * For simplicity and safety, set all aspects of the user context + * except for a selected subset: Don't set priority, which was + * set based on the queue file name according to the tradition. + * Don't bother to set environment, including path vars, either + * because it will be discarded anyway. Although the job file + * should set umask, preset it here just in case. + */ + if (setusercontext(NULL, pentry, uid, LOGIN_SETALL & + ~(LOGIN_SETPRIORITY | LOGIN_SETPATH | LOGIN_SETENV)) != 0) + exit(EXIT_FAILURE); /* setusercontext() logged the error */ +#else /* LOGIN_CAP */ + if (initgroups(pentry->pw_name,pentry->pw_gid)) + perr("cannot init group access list"); + + if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) + perr("cannot change group"); + + if (setlogin(pentry->pw_name)) + perr("cannot set login name"); + + if (setuid(uid) < 0 || seteuid(uid) < 0) + perr("cannot set user id"); +#endif /* LOGIN_CAP */ + + if (chdir(pentry->pw_dir)) + chdir("/"); + + if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0) + perr("exec failed for /bin/sh"); + + PRIV_END + } + /* We're the parent. Let's wait. + */ + close(fd_in); + close(fd_out); + waitpid(pid, (int *) NULL, 0); + + /* Send mail. Unlink the output file first, so it is deleted after + * the run. + */ + stat(filename, &buf); + if (open(filename, O_RDONLY) != STDIN_FILENO) + perr("open of jobfile failed"); + + unlink(filename); + if ((buf.st_size != size) || send_mail) + { + PRIV_START + +#ifdef LOGIN_CAP + /* + * This time set full context to run the mailer. + */ + if (setusercontext(NULL, pentry, uid, LOGIN_SETALL) != 0) + exit(EXIT_FAILURE); /* setusercontext() logged the error */ +#else /* LOGIN_CAP */ + if (initgroups(pentry->pw_name,pentry->pw_gid)) + perr("cannot init group access list"); + + if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) + perr("cannot change group"); + + if (setlogin(pentry->pw_name)) + perr("cannot set login name"); + + if (setuid(uid) < 0 || seteuid(uid) < 0) + perr("cannot set user id"); +#endif /* LOGIN_CAP */ + + if (chdir(pentry->pw_dir)) + chdir("/"); + +#ifdef __FreeBSD__ + execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", + "-odi", "-oem", + mailname, (char *) NULL); +#else + execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL); +#endif + perr("exec failed for mail command"); + + PRIV_END + } + exit(EXIT_SUCCESS); +} + +/* Global functions */ + +/* Needed in gloadavg.c */ +void +perr(const char *fmt, ...) +{ + const char * const fmtadd = ": %m"; + char nfmt[strlen(fmt) + strlen(fmtadd) + 1]; + va_list ap; + + va_start(ap, fmt); + if (debug) + { + vwarn(fmt, ap); + } + else + { + snprintf(nfmt, sizeof(nfmt), "%s%s", fmt, fmtadd); + vsyslog(LOG_ERR, nfmt, ap); + } + va_end(ap); + + exit(EXIT_FAILURE); +} + +void +perrx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (debug) + vwarnx(fmt, ap); + else + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ +/* Browse through ATJOB_DIR, checking all the jobfiles wether they should + * be executed and or deleted. The queue is coded into the first byte of + * the job filename, the date (in minutes since Eon) as a hex number in the + * following eight bytes, followed by a dot and a serial number. A file + * which has not been executed yet is denoted by its execute - bit set. + * For those files which are to be executed, run_file() is called, which forks + * off a child which takes care of I/O redirection, forks off another child + * for execution and yet another one, optionally, for sending mail. + * Files which already have run are removed during the next invocation. + */ + DIR *spool; + struct dirent *dirent; + struct stat buf; + unsigned long ctm; + unsigned long jobno; + char queue; + time_t now, run_time; + char batch_name[] = "Z2345678901234"; + uid_t batch_uid; + gid_t batch_gid; + int c; + int run_batch; +#ifdef __FreeBSD__ + size_t ncpu, ncpusz; + double load_avg = -1; +#else + double load_avg = LOADAVG_MX; +#endif + +/* We don't need root privileges all the time; running under uid and gid daemon + * is fine. + */ + + RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID) + + openlog(atrun, LOG_PID, LOG_CRON); + + opterr = 0; + while((c=getopt(argc, argv, "dl:"))!= -1) + { + switch (c) + { + case 'l': + if (sscanf(optarg, "%lf", &load_avg) != 1) + perr("garbled option -l"); +#ifndef __FreeBSD__ + if (load_avg <= 0.) + load_avg = LOADAVG_MX; +#endif + break; + + case 'd': + debug ++; + break; + + case '?': + default: + usage(); + } + } + + if (chdir(ATJOB_DIR) != 0) + perr("cannot change to %s", ATJOB_DIR); + +#ifdef __FreeBSD__ + if (load_avg <= 0.) { + ncpusz = sizeof(size_t); + if (sysctlbyname("hw.ncpu", &ncpu, &ncpusz, NULL, 0) < 0) + ncpu = 1; + load_avg = LOADAVG_MX * ncpu; + } +#endif + + /* Main loop. Open spool directory for reading and look over all the + * files in there. If the filename indicates that the job should be run + * and the x bit is set, fork off a child which sets its user and group + * id to that of the files and exec a /bin/sh which executes the shell + * script. Unlink older files if they should no longer be run. For + * deletion, their r bit has to be turned on. + * + * Also, pick the oldest batch job to run, at most one per invocation of + * atrun. + */ + if ((spool = opendir(".")) == NULL) + perr("cannot read %s", ATJOB_DIR); + + if (flock(dirfd(spool), LOCK_EX) == -1) + perr("cannot lock %s", ATJOB_DIR); + + now = time(NULL); + run_batch = 0; + batch_uid = (uid_t) -1; + batch_gid = (gid_t) -1; + + while ((dirent = readdir(spool)) != NULL) { + if (stat(dirent->d_name,&buf) != 0) + perr("cannot stat in %s", ATJOB_DIR); + + /* We don't want directories + */ + if (!S_ISREG(buf.st_mode)) + continue; + + if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3) + continue; + + run_time = (time_t) ctm*60; + + if ((S_IXUSR & buf.st_mode) && (run_time <=now)) { + if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) { + run_batch = 1; + strlcpy(batch_name, dirent->d_name, sizeof(batch_name)); + batch_uid = buf.st_uid; + batch_gid = buf.st_gid; + } + + /* The file is executable and old enough + */ + if (islower(queue)) + run_file(dirent->d_name, buf.st_uid, buf.st_gid); + } + /* Delete older files + */ + if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode)) + unlink(dirent->d_name); + } + /* run the single batch file, if any + */ + if (run_batch && (gloadavg() < load_avg)) + run_file(batch_name, batch_uid, batch_gid); + + if (flock(dirfd(spool), LOCK_UN) == -1) + perr("cannot unlock %s", ATJOB_DIR); + + if (closedir(spool) == -1) + perr("cannot closedir %s", ATJOB_DIR); + + closelog(); + exit(EXIT_SUCCESS); +} + +static void +usage(void) +{ + if (debug) + fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n"); + else + syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); + + exit(EXIT_FAILURE); +} diff --git a/libexec/atrun/atrun.man b/libexec/atrun/atrun.man new file mode 100644 index 0000000..cea322f --- /dev/null +++ b/libexec/atrun/atrun.man @@ -0,0 +1,82 @@ +.\" $FreeBSD$ +.Dd October 30, 2012 +.Dt ATRUN 8 +.Os +.Sh NAME +.Nm atrun +.Nd run jobs queued for later execution +.Sh SYNOPSIS +.Nm atrun +.Op Fl l Ar load_avg +.Op Fl d +.Sh DESCRIPTION +.Nm Atrun +runs jobs queued by +.Xr at 1 . +.Pp +Root's +.Xr crontab 5 +file +.Pa /etc/crontab +has to contain the line +.Bd -literal +*/5 * * * * root /usr/libexec/atrun +.Ed +.Pp +so that +.Nm +gets invoked every five minutes. +.Pp +At every invocation, +.Nm +will start all the jobs in the lowercase queues whose start +time has elapsed. +In addition, if the load average over the last minute was less than +the specified limit then a maximum of one batch job (denoted by the +uppercase queues) is started. +.Pp +Before starting a job, +.Nm +will check the status of its owner's account with +.Xr pam 3 +and refuse to run the job if the account is unavailable, +e.g., locked out or expired. +.Sh OPTIONS +.Bl -tag -width indent +.It Fl l Ar load_avg +Specify a limiting load factor, over which batch jobs should +not be run, instead of the default of 1.5 * number of active CPUs. +.It Fl d +Debug; print error messages to standard error instead of using +.Xr syslog 3 . +.El +.Sh WARNINGS +For +.Nm +to work, you have to start up a +.Xr cron 8 +daemon. +.Sh FILES +.Bl -tag -width /etc/pam.d/atrun -compact +.It Pa /etc/pam.d/atrun +.Xr pam.conf 5 +configuration file for +.Nm +.It Pa /var/at/jobs +Directory containing job files +.It Pa /var/at/spool +Directory containing output spool files +.El +.Sh SEE ALSO +.Xr at 1 , +.Xr crontab 1 , +.Xr pam 3 , +.Xr syslog 3 , +.Xr crontab 5 , +.Xr pam.conf 5 , +.Xr cron 8 +.Sh BUGS +The functionality of +.Nm +should be merged into +.Xr cron 8 . diff --git a/libexec/atrun/gloadavg.c b/libexec/atrun/gloadavg.c new file mode 100644 index 0000000..f75c333 --- /dev/null +++ b/libexec/atrun/gloadavg.c @@ -0,0 +1,72 @@ +/* + * gloadavg.c - get load average for Linux + * Copyright (C) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#ifndef __FreeBSD__ +#define _POSIX_SOURCE 1 + +/* System Headers */ + +#include <stdio.h> +#else +#include <stdlib.h> +#endif + +/* Local headers */ + +#include "gloadavg.h" + +/* Global functions */ + +void perr(const char *fmt, ...); + +double +gloadavg(void) +/* return the current load average as a floating point number, or <0 for + * error + */ +{ + double result; +#ifndef __FreeBSD__ + FILE *fp; + + if((fp=fopen(PROC_DIR "loadavg","r")) == NULL) + result = -1.0; + else + { + if(fscanf(fp,"%lf",&result) != 1) + result = -1.0; + fclose(fp); + } +#else + if (getloadavg(&result, 1) != 1) + perr("error in getloadavg"); +#endif + return result; +} diff --git a/libexec/atrun/gloadavg.h b/libexec/atrun/gloadavg.h new file mode 100644 index 0000000..48890ce --- /dev/null +++ b/libexec/atrun/gloadavg.h @@ -0,0 +1,29 @@ +/* + * gloadavg.h - header for atrun(8) + * Copyright (C) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +double gloadavg(void); +#if 0 +static char atrun_h_rcsid[] = "$FreeBSD$"; +#endif diff --git a/libexec/bootpd/Announce b/libexec/bootpd/Announce new file mode 100644 index 0000000..60838d5 --- /dev/null +++ b/libexec/bootpd/Announce @@ -0,0 +1,65 @@ +# $FreeBSD$ + +This is an enhanced version of the CMU BOOTP server which was derived +from the original BOOTP server created by Bill Croft at Stanford. +This version merges most of the enhancements and bug-fixes from the +NetBSD, Columbia, and other versions. + +New features in version 2.4 include: + + Added a simple BOOTP gateway program: bootpgw + Allow host name anywhere IP address is expected. + Automatically lookup the IP address when the name of a + bootptab entry is a valid hostname. + (Dummy entries names should start with '.') + Merged changes from NetBSD and Columbia versions. + Merged changes for Solaris-2.X and SVR4 systems. + Combined bootptest into the bootp release. + Merged tag 18 support (:ef=...:) from Jason Zions. + Use :ef=extension_file_name: and make the + extension files for all clients using bootpef. + Merged HP compatibility (:ra=...:) from David R Linn. + Allows you to override the reply address. + (i.e. send the reply to a broadcast address) + Add /etc/ethers support for NetBSD. + More systems support getether (Ultrix, OSF, NetBSD) + Added RFC 1533 tags 40,41,42 + :yd=<NIS domain>:ys=<NIS server>:nt=<NTP server>: + ConvOldTab.sh to convert old (1.1) bootptab to new format. + Permits extended-length replies with more option data. + +Problems fixed in this version: + + Fixed references to free host structures. + (used to cause core dump on Solaris) + Remove change that added null terminator to string options. + (this annoyed some clients...) + Add missing symbols to dump routine, fix order. + Works (again) with no -DSYSLOGD defined. + Fixed several more NULL references in readfile. + Added proper length checks to option insertions. + Fixed bootptest IP address printing. + Cleaned-up signed/unsigned and byteorder bugs. + Added SVR4/Streams support to getif and getether + Removed extra newlines in syslog messages. + Specify facility code when calling syslog(3) + When lookup_hwa fails, assume numeric HW address. + +Systems on which I have seen this code work: + NetBSD-1.0 (BSD-4.4 derivative) + SunOS 4.X (Solaris 1.X) + SunOS 5.X (Solaris 2.X) + System V/386 Rel. 4.0 + +Systems on which others say this code works: + CDC EP/IX (1.4.3, 2.1.1) + DEC Ultrix (4.2, 4.3) + Linux 1.1.81 + OSF/1 (DEC Alpha CPU) + +Please direct questions, comments, and bug reports to: + <bootp@andrew.cmu.edu> + +Gordon W. Ross Mercury Computer Systems +gwr@mc.com 199 Riverneck Road +508-256-1300 Chelmsford, MA 01824-2820 diff --git a/libexec/bootpd/Changes b/libexec/bootpd/Changes new file mode 100644 index 0000000..d797ea1 --- /dev/null +++ b/libexec/bootpd/Changes @@ -0,0 +1,294 @@ +# $FreeBSD$ + +Changes, most recent first +Date, <email> Real Name + what... + +--> bootp-2.4.3 + +03/27/96 gwr@mc.com (Gordon W. Ross) + Use LOG_NOTICE in place of LOG_INFO for messages related + to unsatisfied clients [at request of <otto@tukki.jyu.fi>] + Fix the irix Makefile targets, and other misc. + +03/25/95 gwr@mc.com (Gordon W. Ross) + Corrected a bug I introduced into SunOS setarp, where + bad IP address caused "network unreachable" errors. + [Thanks to andrew@ntplx.net (Andrew Lindh) for the fix!] + +--> bootp-2.4.2 + +01/14/95 middelin@polyware.iaf.nl (Pauline Middelink) + Corrected support for the Linux networking code. + Fixed lots of warnings (gcc -Wall) + Added "linux" Makefile target. + +01/02/95 Jukka Ukkonen <ukkonen@csc.fi> + Allow bootptab syntax: ha="0:0:c0:80:e8:a7" + +11/30/94 Tonny van Lankveld <A.L.M.G.v.Lankveld@urc.tue.nl> + Fix reporting of duplicate Ethernet addresses. + +09/06/94 longyear@netcom.com (Al Longyear) + Better setarp for linux, allows non-ether types. + +09/02/94 Robert MacKinnon <rbm@montrouge.mis.slb.com> + Add support for IBM's AIX 3.2.5 + +08/30/94 piercarl@ltd.c-d.com (Piercarlo Grandi) + Fix select calls on linux (modifies timeval arg). + Fix setarp (specify Ethernet type for now). + +08/27/94 drew@drewsun.FEITH.COM (Andrew B. Sudell) + Add support for Wollongong Win-TCP (SysVr4 variant). + +08/24/94 gwr@mc.com (Gordon W. Ross) + Use sigaction() on systems that define SA_NOCLDSTOP + (a symbol required by POSIX) for HP/UX and others. + +--> bootp-2.4.1 + +08/24/94 gwr@mc.com (Gordon W. Ross) + Fix bug in boot file name generation (missing init) + +--> bootp-2.4.0 + +08/20/94 gwr@mc.com (Gordon W. Ross) + Fix code to build bootfile name based on combination of + client requested name and bootfile specifications. + Behave similarly with or without CHECK_FILE_ACCESS. + +07/30/94 Dirk Koeppen <dirk@incom.de> + Add "min wait" option (mw) to cause bootpd to ignore + requests from clients that have not waited long enough. + Add code to honor client requests containing the DHCP + option "Maximum Message Size" and use its value to + determine the size of the reply message. + +--> bootp-2.3.8 + +06/25/94 Christos Zoulas <christos@deshaw.com> + Add "-h" flag to override host name (affects default IP + address provided in reply messages. (Also minor bug fix) + +05/27/94 gwr@mc.com (Gordon W. Ross) + Add code to call "arp -s IPADDR HWADDR" on systems + that do not provide an SIOCSARP ioctl (i.e. NetBSD) + +--> bootp-2.3.7 + +05/05/94 Walter Wong <wcw+@CMU.EDU> + Reduce noize at debug level one, where log messages + are generated only for hosts that are recognized + and replied to by bootpd. (At request of HP folks.) + +04/30/94 gwr@mc.com (Gordon W. Ross) + Use memxxx functions unless USE_BFUNCS is defined. + Added -f <file> option to bootptest (requested file). + +04/29/94 tpaquett@ita.lgc.com (Trevor Paquette) + Remove call to haddr_conv802() in sendreply(). + The setarp should get the non-transformed address. + +04/27/94 gwr@mc.com + Improve logic for building bootfile pathname, so a path + will be put in the reply if either the client or bootpd + specifies a boot file. (Needed for NetBSD diskless boot) + +04/25/94 shamash@boxhill.com (Ari Shamash) + Fix prs_inetaddr() so it allows '_' in hostnames. + +04/16/94 gwr@mc.com (Gordon W. Ross) + Fix setarp for SVR4 (needs to use I_STR ioctl) + Thanks to several people: (all sent the same fix) + Barney Wolff <barney@databus.com>, + bear@upsys.se (Bj|rn Sj|holm), + Michael Kuschke <Michael.Kuschke@Materna.DE>, + +03/25/95 Ulrich Heuer </I=zhhi9/G=Ulrich/S=Heuer/@zhflur.ubs.ubs.ch> + Make option string lengths not include a null terminator. + The trailing null breaks some clients. + +03/15/94 "Edmund J. Sutcliffe" <ejs1@tower.york.ac.uk> + Add support for the "EX" option: Execute a program + before sending a BOOTREPLY to a client. Support for + this option is conditional on YORK_EX_OPTION. + +03/10/94 Nigel Metheringham <nigelm@ohm.york.ac.uk> + Make getether.c work on Linux. + +03/09/94 Koch@Math.Uni-Duisburg.DE (Peter Koch) + Add missing MANDIR definition to Makefile. + +03/08/94 Jeroen.Scheerder@let.ruu.nl + Fix args to report in getether code for Ultrix. + Run install individually for each program. + +--> bootp-2.3.6 +03/07/94 gwr@mc.com + Cleanup for release (run gnu indent, tab-size=4) + +02/24/94 Jeroen.Scheerder@let.ruu.nl + Allow underscore in host names - readfile.c:goodname() + Add ConvOldTab.sh - converts 1.1 bootptab to new format. + +02/20/94 gwr@mc.com (Gordon W. Ross) + Make readfile tolerant of hardware addresses that start + with a letter. (If lookup_hwa() fails, assume numeric.) + Fix whitespace skip before :vm= auto: and avoid lookup. + +02/12/94 walker@zk3.dec.com (Mary Walker) + Added support for 64-bit longs (for the DEC Alpha) + Allow ieee802 hardware address in bit-reversed oreder + +02/07/94 hl@tekla.fi (Harald Lundberg) + Fix conflict with DUMP_FILE in syslog.h on OSF1 + Use int for (struct bootp).bp_xid (for DEC Alpha) + Added Ultrix support to bootptest (getether) + +02/06/94 brezak@ch.hp.com (John Brezak) + Add man-page and install targets to Makefile.NetBSD + Add getether support for NetBSD + +02/05/94 gwr@mc.com (Gordon W. Ross) + Added tags 40,41,42 (NIS domain, NIS server, NTP server) + Add stub to getether for machines not yet supported. + +--> bootp-2.3.5 +01/29/94 gwr@mc.com (Gordon W. Ross) + Make bootpgw put a correct address in "giaddr" when + the client request came via broadcast. + +01/22/94 gwr@mc.com (Gordon W. Ross) + Fix syslog call (missing "facility" code) + Add SVR4/Streams support to getif() and getether() + Fix getif bug (matched when it should not) + Macro-ize lots of similar cases in readfile.c + +12/27/93 brezak@ch.hp.com (John Brezak) + Remove all newlines passed to syslog(3) + Add /etc/ethers support for NetBSD. + +12/18/93 gwr@mc.com (Gordon W. Ross) + Fix bootptest IP address printing. + Fix byte-order bugs in bootpgw and bootptest. + Clean-up signed/unsigned mismatches. + Back out SLIP support changes for now + (code fragment saved in ToDo). + +--> bootp-2.3.4 (beta test release) +12/12/93 gwr@mc.com (Gordon W. Ross) + Fixed several more NULL references in readfile. + Added proper length checks to option insertions. + +--> bootp-2.3.3 (beta test release) +12/09/93 gwr@mc.com (Gordon W. Ross) + Added ASSERT checks to readfile.c:fill_defaults() + +12/08/93 brezak@ch.hp.com (John Brezak) + New Makefile.NetBSD + Added setsid() and #ifdef TIOCNOTTY + (bootpd.c, bootpgw.c) + Moved #include <net/if.h> out of #ifdef SUNOS + Fixed several multiple declaration problems + +12/04/93 gwr@mc.com (Gordon W. Ross) + Re-implemented Extension File support + based on work by Jason Zions <jazz@hal.com> + Added support for Reply-Address-Override to support + HP clients (need reply sent to broadcast address) + from David R. Linn <drl@vuse.vanderbilt.edu> + +--> bootp-2.3.2 (beta test release) +11/27/93 gwr@mc.com (Gordon W. Ross) + Incorporated bootptest into the bootp release. + Added ANSI function prototypes everywhere. + +11/17/93 dpm@depend.com (David P. Maynard) + Added automatic SLIP address determination. + (This is NOT dynamic IP address assignment.) + Cleaned up some type warnings from gcc. + +11/11/93 gwr@mc.com (Gordon W. Ross) + Works (again) with no -DSYSLOGD defined. + Provide a default value for the subnet mask. + More #ifdef's for SunOS specific code (lookup_hwa) + Added a simple BOOTP gateway program: bootpgw + Reorganized for more code sharing (with bootpgw) + +--> bootp-2.3.1 (alpha test release) +11/08/93 gwr@mc.com (Gordon W. Ross) + Back-out changes to honor option structure in request + (this needs to be a per-client option). + Merged changes from NetBSD and Columbia versions. + Allow host name anywhere IP address is expected. + Add null terminators to option strings. + Add missing symbols to dump routine, dump symbols + in alphabetical order, one tag per line. + +--> bootp-2.2.D (posted as patch 2) +10/19/93 gwr@mc.com (Gordon W. Ross) + Fix references to free memory (leads to core dumps). + +--> bootp-2.2.C (posted as patch 1) +10/14/93 gwr@mc.com (Gordon W. Ross) + Fix data access alignment problems on SPARC/Solaris. + +--> bootp-2.2.B (posted to usenet) +10/11/93 gwr@mc.com (Gordon W. Ross) + Allow extended-length BOOTP packets (more vendor options) + Honor option format specified in client requests. + Added Solaris-2.X changes from db@sunbim.be (Danny Backx). + +All history before this point may be inaccurate. Please send +changes if any of the credits are incorrect. -gwr + +--> bootp-2.2+NetBSD released +08/27/93 brezak@ch.hp.com (John Brezak) + Added RFC 1396 support (tags 14-17) + +--> bootp-2.2+NetBSD (version?) +??/??/93 mckim@lerc.nasa.gov (Jim McKim) + Ported to NetBSD (see Makefile.NetBSD) + Set server host name in responses. + Check all interfaces in address match routine. + +--> bootp-2.2+FdC released +01/27/93 <fdc@watsun.cc.columbia.edu> Frank da Cruz + Added RFC 1395 information: Merit dump file, + client domain name, swap server address, root path. + +--> bootp-2.2alpha released +11/14/91 <walt+@cmu.edu> Walter L. Wimer + Add "td" to TFTP directory for "secure" (chroot) TFTP. + Add "sa" tag to set explicit server address. + Automatically determine if child of inetd. + Use RFC 1048 format when request has magic number zero. + Fixed various bugs. Give bootptab a separate man page. + +--> bootp-2.1 released +01/09/89 <walt+@cmu.edu> Walter L. Wimer + Check world read bit on TFTP boot file. + Add support for rfc1085 "bootfile size" tag. + Add generic tags. Fix byte order of rfc1048 data. + Fix various crashing bugs. + +--> bootp-2.0 released +07/15/88 <walt+@cmu.edu> Walter L. Wimer + Added vendor information to conform to RFC1048. + Adopted termcap-like file format to support above. + Added hash table lookup instead of linear search. + Other cleanups. + +--> bootp-1.3(?) released +07/24/87 <ddp@andrew.cmu.edu> Drew D. Perkins + Modified to use syslog instead of Kovar's + routines. Add debugging dumps. Many other fixups. + +--> bootp-1.2(?) released +07/30/86 David Kovar at Carnegie Mellon University + Modified to work at CMU. + +--> bootp-1.1 released +01/22/86 Bill Croft at Stanford University + Original created. diff --git a/libexec/bootpd/ConvOldTab.sh b/libexec/bootpd/ConvOldTab.sh new file mode 100755 index 0000000..00683f0 --- /dev/null +++ b/libexec/bootpd/ConvOldTab.sh @@ -0,0 +1,141 @@ +#!/bin/sh +# convert_bootptab Jeroen.Scheerder@let.ruu.nl 02/25/94 +# This script can be used to convert bootptab files in old format +# to new (termcap-like) bootptab files +# +# The old format - real entries are commented out by '###' +# +# Old-style bootp files consist of two sections. +# The first section has two entries: +# First, a line that specifies the home directory +# (where boot file paths are relative to) + +###/tftpboot + +# The next non-empty non-comment line specifies the default bootfile + +###no-file + +# End of first section - indicated by '%%' at the start of the line + +###%% + +# The remainder of this file contains one line per client +# interface with the information shown by the table headings +# below. The host name is also tried as a suffix for the +# bootfile when searching the home directory (that is, +# bootfile.host) +# +# Note that htype is always 1, indicating the hardware type Ethernet. +# Conversion therefore always yields ':ha=ether:'. +# +# host htype haddr iaddr bootfile +# + +###somehost 1 00:0b:ad:01:de:ad 128.128.128.128 dummy + +# That's all for the description of the old format. +# For the new-and-improved format, see bootptab(5). + +set -u$DX + +case $# +in 2 ) OLDTAB=$1 ; NEWTAB=$2 ;; + * ) echo "Usage: `basename $0` <Input> <Output>" + exit 1 +esac + +if [ ! -r $OLDTAB ] +then + echo "`basename $0`: $OLDTAB does not exist or is unreadable." + exit 1 +fi + +if touch $NEWTAB 2> /dev/null +then + : +else + echo "`basename $0`: cannot write to $NEWTAB." + exit 1 +fi + + +cat << END_OF_HEADER >> $NEWTAB +# /etc/bootptab: database for bootp server (/etc/bootpd) +# This file was generated automagically + +# Blank lines and lines beginning with '#' are ignored. +# +# Legend: (see bootptab.5) +# first field -- hostname (not indented) +# bf -- bootfile +# bs -- bootfile size in 512-octet blocks +# cs -- cookie servers +# df -- dump file name +# dn -- domain name +# ds -- domain name servers +# ef -- extension file +# gw -- gateways +# ha -- hardware address +# hd -- home directory for bootfiles +# hn -- host name set for client +# ht -- hardware type +# im -- impress servers +# ip -- host IP address +# lg -- log servers +# lp -- LPR servers +# ns -- IEN-116 name servers +# ra -- reply address +# rl -- resource location protocol servers +# rp -- root path +# sa -- boot server address +# sm -- subnet mask +# sw -- swap server +# tc -- template host (points to similar host entry) +# td -- TFTP directory +# to -- time offset (seconds) +# ts -- time servers +# vm -- vendor magic number +# Tn -- generic option tag n +# +# Be careful about including backslashes where they're needed. Weird (bad) +# things can happen when a backslash is omitted where one is intended. +# Also, note that generic option data must be either a string or a +# sequence of bytes where each byte is a two-digit hex value. + +# First, we define a global entry which specifies the stuff every host uses. +# (Host name lookups are relative to the domain: your.domain.name) + +END_OF_HEADER + +# Fix up HW addresses in aa:bb:cc:dd:ee:ff and aa-bb-cc-dd-ee-ff style first +# Then awk our stuff together +sed -e 's/[:-]//g' < $OLDTAB | \ +nawk 'BEGIN { PART = 0 ; FIELD=0 ; BOOTPATH="unset" ; BOOTFILE="unset" } + /^%%/ { + PART = 1 + printf ".default:\\\n\t:ht=ether:\\\n\t:hn:\\\n\t:dn=your.domain.name:\\\n\t:ds=your,dns,servers:\\\n\t:sm=255.255.0.0:\\\n\t:hd=%s:\\\n\t:rp=%s:\\\n\t:td=%s:\\\n\t:bf=%s:\\\n\t:to=auto:\n\n", BOOTPATH, BOOTPATH, BOOTPATH, BOOTFILE + next + } + /^$/ { next } + /^#/ { next } + { + if ( PART == 0 && FIELD < 2 ) + { + if ( FIELD == 0 ) BOOTPATH=$1 + if ( FIELD == 1 ) BOOTFILE=$1 + FIELD++ + } + } + { + if ( PART == 1 ) + { + HOST=$1 + HA=$3 + IP=$4 + BF=$5 + printf "%s:\\\n\t:tc=.default:\\\n\t:ha=0x%s:\\\n\t:ip=%s:\\\n\t:bf=%s:\n", HOST, HA, IP, BF + } + }' >> $NEWTAB + +exit 0 diff --git a/libexec/bootpd/Installation b/libexec/bootpd/Installation new file mode 100644 index 0000000..466cabc --- /dev/null +++ b/libexec/bootpd/Installation @@ -0,0 +1,29 @@ + +Installation instructions for SunOS + +Compile the executable: +For SunOS 4.X: + make sunos4 +For SunOS 5.X: (Solaris) + make sunos5 + +Install the executables: + + make install + +Edit (or create) the bootptab: +(See bootptab.sample and bootptab.5 manual entry) + edit /etc/bootptab + +Edit /etc/services to add these two lines: +bootps 67/udp bootp # BOOTP Server +bootpc 68/udp # BOOTP Client + +Edit /etc/inetd.conf to add the line: +bootp dgram udp wait root /usr/etc/bootpd bootpd -i + +If you compiled report.c with LOG_LOCAL2 (defined in the Makefile) +then you may want to capture syslog messages from BOOTP by changing +your syslog.conf file. (See the sample syslog.conf file here). +Test the change with: logger -t test -p local2.info "message" + diff --git a/libexec/bootpd/Makefile b/libexec/bootpd/Makefile new file mode 100644 index 0000000..6f02477 --- /dev/null +++ b/libexec/bootpd/Makefile @@ -0,0 +1,18 @@ +# bootpd/Makefile +# $FreeBSD$ + +PROG= bootpd +CFLAGS+= -DETC_ETHERS +CFLAGS+= -DSYSLOG -DDEBUG -DVEND_CMU + +WARNS?= 2 + +SUBDIR= bootpgw tools + +SRCS= bootpd.c dovend.c readfile.c hash.c dumptab.c \ + lookup.c getif.c hwaddr.c report.c tzone.c rtmsg.c + +MAN= bootptab.5 bootpd.8 +MLINKS= bootpd.8 bootpgw.8 + +.include <bsd.prog.mk> diff --git a/libexec/bootpd/Makefile.UNIX b/libexec/bootpd/Makefile.UNIX new file mode 100644 index 0000000..5b2c186 --- /dev/null +++ b/libexec/bootpd/Makefile.UNIX @@ -0,0 +1,204 @@ +# $FreeBSD$ +# +# Makefile for the BOOTP programs: +# bootpd - BOOTP server daemon +# bootpef - BOOTP extension file builder +# bootpgw - BOOTP gateway daemon +# bootptest - BOOTP tester (client) +# + +# OPTion DEFinitions: +# Remove the -DVEND_CMU if you don't wish to support the "CMU vendor format" +# in addition to the RFC1048 format. Leaving out DEBUG saves little. +OPTDEFS= -DSYSLOG -DVEND_CMU -DDEBUG + +# Uncomment and edit this to choose the facility code used for syslog. +# LOG_FACILITY= "-DLOG_BOOTP=LOG_LOCAL2" + +# SYStem DEFinitions: +# Either uncomment some of the following, or do: +# "make sunos4" (or "make sunos5", etc.) +# SYSDEFS= -DSUNOS -DETC_ETHERS +# SYSDEFS= -DSVR4 +# SYSLIBS= -lsocket -lnsl + +# Uncomment this if your system does not provide streror(3) +# STRERROR=strerror.o + +# FILE DEFinitions: +# The next few lines may be uncommented and changed to alter the default +# filenames bootpd uses for its configuration and dump files. +#CONFFILE= -DCONFIG_FILE=\"/usr/etc/bootptab\" +#DUMPFILE= -DDUMPTAB_FILE=\"/usr/etc/bootpd.dump\" +#FILEDEFS= $(CONFFILE) $(DUMPFILE) + +# MORE DEFinitions (whatever you might want to add) +# One might define NDEBUG (to remove "assert()" checks). +MOREDEFS= + +INSTALL=/usr/bin/install +DESTDIR= +BINDIR=/usr/etc +MANDIR=/usr/local/man + +CFLAGS= $(OPTDEFS) $(SYSDEFS) $(FILEDEFS) $(MOREDEFS) +PROGS= bootpd bootpef bootpgw bootptest +TESTS= trylook trygetif trygetea + +all: $(PROGS) $(TESTS) + +system: install + +install: $(PROGS) + -for f in $(PROGS) ;\ + do \ + $(INSTALL) -c -s $$f $(DESTDIR)$(BINDIR) ;\ + done + +MAN5= bootptab.5 +MAN8= bootpd.8 bootpef.8 bootptest.8 +install.man: $(MAN5) $(MAN8) + -for f in $(MAN5) ;\ + do \ + $(INSTALL) -c -m 644 $$f $(DESTDIR)$(MANDIR)/man5 ;\ + done + -for f in $(MAN8) ;\ + do \ + $(INSTALL) -c -m 644 $$f $(DESTDIR)$(MANDIR)/man8 ;\ + done + +clean: + -rm -f core *.o + -rm -f $(PROGS) $(TESTS) + +distclean: + -rm -f *.BAK *.CKP *~ .emacs* + +# +# Handy targets for systems needing special treatment: +# (Most POSIX systems should work with just "make all") +# + +# DEC/OSF1 on the Alpha +alpha: + $(MAKE) SYSDEFS="-DETC_ETHERS -Dint32=int -D_SOCKADDR_LEN" \ + STRERROR=strerror.o + +# Control Data EP/IX 1.4.3 system, BSD 4.3 mode +epix143: + $(MAKE) CC="cc -systype bsd43" \ + SYSDEFS="-Dconst= -D_SIZE_T -DNO_UNISTD -DUSE_BFUNCS" \ + STRERROR=strerror.o + +# Control Data EP/IX 2.1.1 system, SVR4 mode +epix211: + $(MAKE) CC="cc -systype svr4" \ + SYSDEFS="-DSVR4" \ + SYSLIBS="-lsocket -lnsl" + +# IRIX 5.X (Silicon Graphics) +irix: + $(MAKE) SYSDEFS= SYSLIBS= + +# Linux 1.1.80+ on [34]86 +linux: + $(MAKE) SYSDEFS="-O6 -Wall -fomit-frame-pointer" + +# SunOS 4.X +sunos4: + $(MAKE) SYSDEFS="-DSUNOS -DETC_ETHERS" \ + STRERROR=strerror.o + +# Solaris 2.X (i.e. SunOS 5.X) +sunos5: + $(MAKE) SYSDEFS="-DSVR4 -DETC_ETHERS" \ + SYSLIBS="-lsocket -lnsl" + +# Solaris 2.X (i.e. SunOS 5.X) with GCC. Note that GCC normally +# defines __STDC__=1 which breaks many Solaris header files... +sunos5gcc: + $(MAKE) SYSDEFS="-DSVR4 -DETC_ETHERS -D__STDC__=0" \ + SYSLIBS="-lsocket -lnsl" CC="gcc -Wall" + +# UNIX System V Rel. 3 +svr3: + $(MAKE) SYSDEFS="-DSYSV" + +# UNIX System V Rel. 4 +svr4: + $(MAKE) SYSDEFS="-DSVR4" \ + SYSLIBS="-lsocket -lnsl" + +# AT&T/GIS - Both AT&T StarServer and NCR 3000 +# may work for others using Wollongong's WIN-TCP +wollongong gis : + $(MAKE) SYSDEFS="-DSVR4 -DWIN_TCP" \ + SYSLIBS="-lsocket -lnsl" + +# +# How to build each program: +# + +OBJ_D= bootpd.o dovend.o readfile.o hash.o dumptab.o \ + lookup.o getif.o hwaddr.o tzone.o report.o $(STRERROR) +bootpd: $(OBJ_D) + $(CC) -o $@ $(OBJ_D) $(SYSLIBS) + +OBJ_EF= bootpef.o dovend.o readfile.o hash.o dumptab.o \ + lookup.o hwaddr.o tzone.o report.o $(STRERROR) +bootpef: $(OBJ_EF) + $(CC) -o $@ $(OBJ_EF) $(SYSLIBS) + +OBJ_GW= bootpgw.o getif.o hwaddr.o report.o $(STRERROR) +bootpgw: $(OBJ_GW) + $(CC) -o $@ $(OBJ_GW) $(SYSLIBS) + +OBJ_TEST= bootptest.o print-bootp.o getif.o getether.o \ + report.o $(STRERROR) +bootptest: $(OBJ_TEST) + $(CC) -o $@ $(OBJ_TEST) $(SYSLIBS) + +# This is just for testing the lookup functions. +TRYLOOK= trylook.o lookup.o report.o $(STRERROR) +trylook : $(TRYLOOK) + $(CC) -o $@ $(TRYLOOK) $(SYSLIBS) + +# This is just for testing getif. +TRYGETIF= trygetif.o getif.o report.o $(STRERROR) +trygetif : $(TRYGETIF) + $(CC) -o $@ $(TRYGETIF) $(SYSLIBS) + +# This is just for testing getether. +TRYGETEA= trygetea.o getether.o report.o $(STRERROR) +trygetea : $(TRYGETEA) + $(CC) -o $@ $(TRYGETEA) $(SYSLIBS) + +# This rule just keeps the LOG_BOOTP define localized. +report.o : report.c + $(CC) $(CFLAGS) $(LOG_FACILITY) -c $< + +# Punt SunOS -target noise +.c.o: + $(CC) $(CFLAGS) -c $< + +# +# Header file dependencies: +# + +bootpd.o : bootp.h bptypes.h hash.h hwaddr.h bootpd.h dovend.h +bootpd.o : readfile.h report.h tzone.h patchlevel.h getif.h +bootpef.o : bootp.h bptypes.h hash.h hwaddr.h bootpd.h dovend.h +bootpef.o : readfile.h report.h tzone.h patchlevel.h +bootpgw.o : bootp.h bptypes.h getif.h hwaddr.h report.h patchlevel.h +bootptest.o : bootp.h bptypes.h bootptest.h getif.h patchlevel.h +dovend.o : bootp.h bptypes.h bootpd.h hash.h hwaddr.h report.h dovend.h +dumptab.o : bootp.h bptypes.h hash.h hwaddr.h report.h patchlevel.h bootpd.h +getif.o : getif.h report.h +hash.o : hash.h +hwaddr.o : bptypes.h hwaddr.h report.h +lookup.o : bootp.h bptypes.h lookup.h report.h +print-bootp.o : bootp.h bptypes.h bootptest.h +readfile.o : bootp.h bptypes.h hash.h hwaddr.h lookup.h readfile.h +readfile.o : report.h tzone.h bootpd.h +report.o : report.h +tzone.o : bptypes.h report.h tzone.h diff --git a/libexec/bootpd/Makefile.inc b/libexec/bootpd/Makefile.inc new file mode 100644 index 0000000..899c9b5 --- /dev/null +++ b/libexec/bootpd/Makefile.inc @@ -0,0 +1,5 @@ +# $FreeBSD$ + +BINDIR?= /usr/libexec + +WARNS?= 1 diff --git a/libexec/bootpd/Problems b/libexec/bootpd/Problems new file mode 100644 index 0000000..c7f9951 --- /dev/null +++ b/libexec/bootpd/Problems @@ -0,0 +1,66 @@ +# $FreeBSD$ + +Common problems and ways to work around them: + +Bootpd complains: "bind: Address already in use" and fails to start. + You are already running something that has bound the + BOOTP listening port number. Check /etc/inetd.conf or + the equivalent for a bootp line (or in startup files). + +Bootpd complains that it "can not get IP addr for HOSTNAME" + + If the entry is a "dummy" (not a real host) used only for + reference by other entries, put '.' in front of the name. + + If the entry is for a real client and the IP address for + the client can not be found using gethostbyname(), specify + the IP address for the client using numeric form. + +Bootpd takes a long time to finish parsing the bootptab file: + + Excessive startup time is usually caused by waiting for + timeouts on failed DNS lookup operations. If this is the + problem, find the client names for which DNS lookup fails + and change the bootptab to specify the IP addresses for + those clients using numeric form. + + When bootptab entries do not specify an ip address, bootpd + attempts to lookup the tagname as a host name to find the + IP address. To suppress this default action, either make + the entry a "dummy" or specify its IP numeric address. + + If your DNS lookups work but are just slow, consider either + running bootpd on the same machine as the DNS server or + running a caching DNS server on the host running bootpd. + +My huge bootptab file causes startup time to be so long that clients +give up waiting for a reply. + + Truly huge bootptab files make "inetd" mode impractical. + Start bootpd in "standalone" mode when the server boots. + + Another possibility is to run one bootpd on each network + segment so each one can have a smaller bootptab. Only one + instance of bootpd may run on one server, so you would need + to use a different server for each network segment. + +My bootp clients are given responses with a boot file name that is +not a fully specified path. + + Make sure the TFTP directory or home directory tags are set: + :td=/tftpboot: (or) + :hd=/usr/boot: (for example) + +My PC clients running Sun's PC-NFS Pro v1.1 fail to receive +acceptable responses from the bootp server. + + These clients send a request with the DHCP "message length" + option and the (new) BOOTP "broadcast flag" both set. + The bootp server (on SunOS) will send a fragmented reply + unless you override the length with :ms=1024: (or less). + The "broadcast flag" is not yet supported, but there is + a simple work-around, just add :ra=255.255.255.255: + for any clients that need their reply broadcasted. + You may need to use a differnet broadcast address. + (Thanks to Ivan Auger <ivan.auger@wadsworth.org>) + diff --git a/libexec/bootpd/README b/libexec/bootpd/README new file mode 100644 index 0000000..a88cca5 --- /dev/null +++ b/libexec/bootpd/README @@ -0,0 +1,136 @@ +# $FreeBSD$ + +This is an enhanced version of the CMU BOOTP server which was derived +from the original BOOTP server created by Bill Croft at Stanford. +This version merges all the enhancements and bug-fixes from the +NetBSD, Columbia, and other versions. + +Please direct questions, comments, and bug reports to the list: + <bootp@andrew.cmu.edu> + +You can subscribe to this mailing list by sending mail to: + bootp-request@andrew.cmu.edu +(The body of the message should contain: "Add <your-address>") + +[ From the NetBSD README file: ] + +BOOTPD is a useful adjunct to the nfs diskless boot EPROM code. + +The alternatives for initiating a boot of a kernel across a network +are to use RARP protocol, or BOOTP protocol. BOOTP is more flexible; +it allows additional items of information to be returned to the +booting client; it also supports booting across gateways. + +[ From the CMU README file: ] + +Notes: +1) BOOTP was originally designed and implemented by Bill Croft at Stanford. + Much of the credit for the ideas and the code goes to him. We've added + code to support the vendor specific area of the packet as specified in + RFC1048. We've also improved the host lookup algorithm and added some + extra logging. + +2) The server now uses syslog to do logging. Specifically it uses the 4.3bsd + version. I've #ifdef'd all of these calls. If you are running 4.2 you + should compile without the -DSYSLOG switch. + +3) You must update your /etc/services file to contain the following two lines: + bootps 67/udp bootp # BOOTP Server + bootpc 68/udp # BOOTP Client + +4) Edit the bootptab. It has some explanitory comments, and there + is a manual entry describing its format (bootptab.5) + If you have any questions, just let us know. + +Construction: + [ See the file Installation which is more up-to-date. -gwr ] + + Make sure all of the files exist first. If anything is missing, + please contact either Walt Wimer or Drew Perkins by E-mail or phone. + Addresses and phone numbers are listed below. + + Type 'make'. The options at present are: -DSYSLOG which enables logging + code, -DDEBUG which enables table dumping via signals, and -DVEND_CMU + which enables the CMU extensions for CMU PC/IP. + + Edit the bootptab. The man page and the comments in the file should + explain how to go about doing so. If you have any problems, let me know. + + Type 'make install'. This should put all of the files in the right place. + + Edit your /etc/rc.local or /etc/inetd.conf file to start up bootpd upon + reboot. The following is a sample /etc/inetd.conf entry: + # BOOTP server + bootps dgram udp wait root /usr/etc/bootpd bootpd -i + +Care and feeding: + If you change the interface cards on your host or add new hosts you will + need to update /etc/bootptab. Just edit it as before. Once you write + it back out, bootpd will notice that there is a new copy and will + reread it the next time it gets a request. + + If your bootp clients don't get a response then several things might be + wrong. Most often, the entry for that host is not in the database. + Check the hardware address and then check the entry and make sure + everything is right. Other problems include the server machine crashing, + bad cables, and the like. If your network is very congested you should + try making your bootp clients send additional requests before giving up. + + +November 7, 1988 + + +Walter L. Wimer Drew D. Perkins +ww0n@andrew.cmu.edu ddp@andrew.cmu.edu +(412) 268-6252 (412) 268-8576 + +4910 Forbes Ave +Pittsburgh, PA 15213 + +[ Contents description by file: ] + +Announce* Text of release announcements +Changes Change history, reverse chronological +ConvOldTab.sh Script to convert old (1.x) bootptab files +Installation Instructions for building and installing +Makefile* for "make" +README This file +ToDo Things not yet done +bootp.h The protocol header file +bootpd.8 Manual page for bootpd, boopgw +bootpd.c BOOTP server main module +bootpd.h header for above (and others) +bootpef.8 Manual page for bootpef +bootpef.c BOOTP extension file compiler +bootpgw.c BOOTP gateway main module +bootptab.5 A manual describing the bootptab format +bootptab.cmu A sample database file for the server +bootptab.mcs Another sample from <gwr@mc.com> +bootptest.8 Manual page for bootptest +bootptest.c BOOTP test program (fake client) +bootptest.h header for above +dovend.c Vendor Option builder (for bootpd, bootpef) +dovend.h header for above +dumptab.c Implements debugging dump for bootpd +getether.c For bootptest (not used yet) +getether.h header for above +getif.c Get network interface info. +getif.h header for above +hash.c The hash table module +hash.h header for above +hwaddr.c Hardware address support +hwaddr.h header for above +lookup.c Internet Protocol address lookup +lookup.h header for above +patchlevel.h Holds version numbers +print-bootp.c Prints BOOTP packets (taken from BSD tcpdump) +readfile.c The configuration file-reading routines +readfile.h header for above +report.c Does syslog-style messages +report.h header for above +strerror.c Library errno-to-string (for systems lacking it) +syslog.conf Sample config file for syslogd(8) +syslog.h For systems that lack syslog(3) +try*.c Test programs (for debugging) +tzone.c Get timezone offset +tzone.h header for above diff --git a/libexec/bootpd/ToDo b/libexec/bootpd/ToDo new file mode 100644 index 0000000..261d24c --- /dev/null +++ b/libexec/bootpd/ToDo @@ -0,0 +1,61 @@ +ToDo: -*- text -*- + +---------------------------------------------------------------------- +Memory allocation locality: + +Currently mallocs memory in a very haphazard manner. As such, most of +the program ends up core-resident all the time just to follow all the +stupid pointers around. . . . + +---------------------------------------------------------------------- +Input parser: + +The reader implemented in readfile.c could use improvement. Some sort +of "data-driven" parser should be used so the big switch statements +would have only one case for each data type instead of one case for +every recognized option symbol. Then adding a new tag would involve +only adding a new element to the data table describing known symbols. +Hopefully, this would shrink the code a bit too. -gwr + +---------------------------------------------------------------------- +SLIP Initialization via BOOTP: + +In the function handle_request(), both in bootpd and bootpgw, +we might want to add code like the following just before testing +the client IP address field for zero. (bp->bp_ciaddr == 0) +(David suggests we leave this out for now. -gwr) + +#if 1 /* XXX - Experimental */ + /* + * SLIP initialization support. + * + * If this packet came from a SLIP driver that does + * automatic IP address initialization, then the socket + * will have the IP address and the packet will + * have zeros for both the IP and HW addresses. + * + * Thanks to David P. Maynard <dpm@depend.com> + * for explaining how this works. -gwr + */ + if ((bp->bp_ciaddr.s_addr == 0) && + (bp->bp_htype == 0)) + { + /* Pretend the client knows its address. It will soon. */ + bp->bp_ciaddr = recv_addr.sin_addr; + if (debug) + report(LOG_INFO, "fixed blank request from IP addr %s", + inet_ntoa(recv_addr.sin_addr)); + } +#endif + +---------------------------------------------------------------------- +DHCP Support: + +There is a set of patches from Jeanette Pauline Middelink +<middelin@calvin.polyware.iaf.nl> to add DHCP support. + +Those patches will be integrated into the BOOTP release stream +very soon, but if you can't wait, you can get them from: +nimbus.anu.edu.au:/pub/tridge/samba/contributed/DHCP.patch + +---------------------------------------------------------------------- diff --git a/libexec/bootpd/bootp.h b/libexec/bootpd/bootp.h new file mode 100644 index 0000000..c3a3909 --- /dev/null +++ b/libexec/bootpd/bootp.h @@ -0,0 +1,149 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. +************************************************************************/ + +/* + * Bootstrap Protocol (BOOTP). RFC951 and RFC1395. + * + * $FreeBSD$ + * + * + * This file specifies the "implementation-independent" BOOTP protocol + * information which is common to both client and server. + * + */ + +#include "bptypes.h" /* for int32, u_int32 */ + +#define BP_CHADDR_LEN 16 +#define BP_SNAME_LEN 64 +#define BP_FILE_LEN 128 +#define BP_VEND_LEN 64 +#define BP_MINPKTSZ 300 /* to check sizeof(struct bootp) */ +/* Overhead to fit a bootp message into an Ethernet packet. */ +#define BP_MSG_OVERHEAD (14 + 20 + 8) /* Ethernet + IP + UDP headers */ + +struct bootp { + unsigned char bp_op; /* packet opcode type */ + unsigned char bp_htype; /* hardware addr type */ + unsigned char bp_hlen; /* hardware addr length */ + unsigned char bp_hops; /* gateway hops */ + u_int32 bp_xid; /* transaction ID */ + unsigned short bp_secs; /* seconds since boot began */ + unsigned short bp_flags; /* RFC1532 broadcast, etc. */ + struct in_addr bp_ciaddr; /* client IP address */ + struct in_addr bp_yiaddr; /* 'your' IP address */ + struct in_addr bp_siaddr; /* server IP address */ + struct in_addr bp_giaddr; /* gateway IP address */ + unsigned char bp_chaddr[BP_CHADDR_LEN]; /* client hardware address */ + char bp_sname[BP_SNAME_LEN]; /* server host name */ + char bp_file[BP_FILE_LEN]; /* boot file name */ + unsigned char bp_vend[BP_VEND_LEN]; /* vendor-specific area */ + /* note that bp_vend can be longer, extending to end of packet. */ +}; + +/* + * UDP port numbers, server and client. + */ +#define IPPORT_BOOTPS 67 +#define IPPORT_BOOTPC 68 + +#define BOOTREPLY 2 +#define BOOTREQUEST 1 + +/* + * Hardware types from Assigned Numbers RFC. + */ +#define HTYPE_ETHERNET 1 +#define HTYPE_EXP_ETHERNET 2 +#define HTYPE_AX25 3 +#define HTYPE_PRONET 4 +#define HTYPE_CHAOS 5 +#define HTYPE_IEEE802 6 +#define HTYPE_ARCNET 7 + +/* + * Vendor magic cookie (v_magic) for CMU + */ +#define VM_CMU "CMU" + +/* + * Vendor magic cookie (v_magic) for RFC1048 + */ +#define VM_RFC1048 { 99, 130, 83, 99 } + + + +/* + * Tag values used to specify what information is being supplied in + * the vendor (options) data area of the packet. + */ +/* RFC 1048 */ +#define TAG_END ((unsigned char) 255) +#define TAG_PAD ((unsigned char) 0) +#define TAG_SUBNET_MASK ((unsigned char) 1) +#define TAG_TIME_OFFSET ((unsigned char) 2) +#define TAG_GATEWAY ((unsigned char) 3) +#define TAG_TIME_SERVER ((unsigned char) 4) +#define TAG_NAME_SERVER ((unsigned char) 5) +#define TAG_DOMAIN_SERVER ((unsigned char) 6) +#define TAG_LOG_SERVER ((unsigned char) 7) +#define TAG_COOKIE_SERVER ((unsigned char) 8) +#define TAG_LPR_SERVER ((unsigned char) 9) +#define TAG_IMPRESS_SERVER ((unsigned char) 10) +#define TAG_RLP_SERVER ((unsigned char) 11) +#define TAG_HOST_NAME ((unsigned char) 12) +#define TAG_BOOT_SIZE ((unsigned char) 13) +/* RFC 1395 */ +#define TAG_DUMP_FILE ((unsigned char) 14) +#define TAG_DOMAIN_NAME ((unsigned char) 15) +#define TAG_SWAP_SERVER ((unsigned char) 16) +#define TAG_ROOT_PATH ((unsigned char) 17) +/* RFC 1497 */ +#define TAG_EXTEN_FILE ((unsigned char) 18) +/* RFC 1533 */ +#define TAG_NIS_DOMAIN ((unsigned char) 40) +#define TAG_NIS_SERVER ((unsigned char) 41) +#define TAG_NTP_SERVER ((unsigned char) 42) +/* DHCP maximum message size. */ +#define TAG_MAX_MSGSZ ((unsigned char) 57) + +/* XXX - Add new tags here */ + + +/* + * "vendor" data permitted for CMU bootp clients. + */ + +struct cmu_vend { + char v_magic[4]; /* magic number */ + u_int32 v_flags; /* flags/opcodes, etc. */ + struct in_addr v_smask; /* Subnet mask */ + struct in_addr v_dgate; /* Default gateway */ + struct in_addr v_dns1, v_dns2; /* Domain name servers */ + struct in_addr v_ins1, v_ins2; /* IEN-116 name servers */ + struct in_addr v_ts1, v_ts2; /* Time servers */ + int32 v_unused[6]; /* currently unused */ +}; + + +/* v_flags values */ +#define VF_SMASK 1 /* Subnet mask field contains valid data */ diff --git a/libexec/bootpd/bootpd.8 b/libexec/bootpd/bootpd.8 new file mode 100644 index 0000000..d124800 --- /dev/null +++ b/libexec/bootpd/bootpd.8 @@ -0,0 +1,310 @@ +.\" Copyright (c) 1988, 1989, 1991 Carnegie Mellon University +.\" +.\" $FreeBSD$ +.\" +.Dd February 10, 2004 +.Dt BOOTPD 8 +.Os +.Sh NAME +.Nm bootpd , bootpgw +.Nd Internet Boot Protocol server/gateway +.Sh SYNOPSIS +.Nm +.Op Fl i | s +.Op Fl c Ar chdir-path +.Op Fl d Ar level +.Op Fl h Ar hostname +.Op Fl t Ar timeout +.Oo +.Ar bootptab +.Op Ar dumpfile +.Oc +.Nm bootpgw +.Op Fl i | s +.Op Fl d Ar level +.Op Fl h Ar hostname +.Op Fl t Ar timeout +.Ar server +.Sh DESCRIPTION +The +.Nm +utility +implements an Internet Bootstrap Protocol (BOOTP) server as defined in +RFC951, RFC1532, and RFC1533. +The +.Nm bootpgw +utility implements a simple BOOTP gateway which can be used to forward +requests and responses between clients on one subnet and a +BOOTP server (i.e.\& +.Nm ) +on another subnet. +While either +.Nm +or +.Nm bootpgw +will forward BOOTREPLY packets, only +.Nm bootpgw +will forward BOOTREQUEST packets. +.Pp +One host on each network segment is normally configured to run either +.Nm +or +.Nm bootpgw +from +.Xr inetd 8 +by including one of the following lines in the file +.Pa /etc/inetd.conf : +.Pp +.Dl bootps dgram udp wait root /usr/libexec/bootpd bootpd /etc/bootptab +.Dl bootps dgram udp wait root /usr/libexec/bootpgw bootpgw server +.Pp +This mode of operation is referred to as "inetd mode" and causes +.Nm +(or +.Nm bootpgw ) +to be started only when a boot request arrives. +If it does not +receive another packet within fifteen minutes of the last one +it received, it will exit to conserve system resources. +The +.Fl t +option controls this timeout (see OPTIONS). +.Pp +It is also possible to run +.Nm +(or +.Nm bootpgw ) +in "standalone mode" (without +.Xr inetd 8 ) +by simply invoking it from a shell like any other regular command. +Standalone mode is particularly useful when +.Nm +is used with a large configuration database, where the start up +delay might otherwise prevent timely response to client requests. +(Automatic start up in standalone mode can be done by invoking +.Nm +from within +.Pa /etc/rc.local , +for example.) +Standalone mode is less useful for +.Nm bootpgw +which +has very little start up delay because +it does not read a configuration file. +.Pp +Either program automatically detects whether it was invoked from inetd +or from a shell and automatically selects the appropriate mode. +The +.Fl s +or +.Fl i +option may be used to force standalone or inetd mode respectively +(see OPTIONS). +.Sh OPTIONS +The following options are available: +.Bl -tag -width indent +.It Fl t Ar timeout +Specify the +.Ar timeout +value (in minutes) that a +.Nm +or +.Nm bootpgw +process will wait for a BOOTP packet before exiting. +If no packets are received for +.Ar timeout +minutes, then the program will exit. +A timeout value of zero means "run forever". +In standalone mode, this option is forced to zero. +.It Fl d Ar debug-level +Set the +.Ar debug-level +variable that controls the amount of debugging messages generated. +For example, +.Fl d Ns 4 +or +.Fl d +4 will set the debugging level to 4. +For compatibility with older versions of +.Nm , +omitting the numeric parameter (i.e., just +.Fl d ) +will simply increment the debug level by one. +.It Fl c Ar chdir-path +Set the current directory used by +.Nm +while checking the existence and size of client boot files. +This is +useful when client boot files are specified as relative pathnames, and +.Nm +needs to use the same current directory as the TFTP server +(typically +.Pa /tftpboot ) . +This option is not recognized by +.Nm bootpgw . +.It Fl h Ar hostname +Specify the hostname corresponding to the IP address to listen on. +By default, +.Nm +listens on the IP address corresponding to the machine's hostname, as +returned by +.Xr gethostname 3 . +.It Fl i +Force inetd mode. +This option is obsolete, but remains for +compatibility with older versions of +.Nm . +.It Fl s +Force standalone mode. +This option is obsolete, but remains for +compatibility with older versions of +.Nm . +.It Ar bootptab +Specify the name of the configuration file from which +.Nm +loads its database of known clients and client options +.No ( Nm +only). +.It Ar dumpfile +Specify the name of the file that +.Nm +will dump its internal database into when it receives a +SIGUSR1 signal +.No ( Nm +only). +This option is only recognized if +.Nm +was compiled with the -DDEBUG flag. +.It Ar server +Specify the name of a BOOTP server to which +.Nm bootpgw +will forward all BOOTREQUEST packets it receives +.Pf ( Nm bootpgw +only). +.El +.Sh OPERATION +Both +.Nm +and +.Nm bootpgw +operate similarly in that both listen for any packets sent to the +.Em bootps +port, and both simply forward any BOOTREPLY packets. +They differ in their handling of BOOTREQUEST packets. +.Pp +When +.Nm bootpgw +is started, it determines the address of a BOOTP server +whose name is provided as a command line parameter. +When +.Nm bootpgw +receives a BOOTREQUEST packet, it sets the "gateway address" +and "hop count" fields in the packet and forwards the packet +to the BOOTP server at the address determined earlier. +Requests are forwarded only if they indicate that +the client has been waiting for at least three seconds. +.Pp +When +.Nm +is started it reads a configuration file, (normally +.Pa /etc/bootptab ) +that initializes the internal database of known clients and client +options. +This internal database is reloaded +from the configuration file when +.Nm +receives a hangup signal (SIGHUP) or when it discovers that the +configuration file has changed. +.Pp +When +.Nm +receives a BOOTREQUEST packet, it +.\" checks the modification time of the +.\" configuration file and reloads the database if necessary. Then it +looks for a database entry matching the client request. +If the client is known, +.Nm +composes a BOOTREPLY packet using the database entry found above, +and sends the reply to the client (possibly using a gateway). +If the client is unknown, the request is discarded +(with a notice if debug > 0). +.Pp +If +.Nm +is compiled with the -DDEBUG option, receipt of a SIGUSR1 signal causes +it to dump its internal database to the file +.Pa /tmp/bootpd.dump +or the dumpfile specified as a command line parameter. +.Pp +During initialization, both programs +determine the UDP port numbers to be used by calling +.Xr getservbyname 3 +(which normally uses +.Pa /etc/services ) . +Two service names (and port numbers) are used: +.Pp +.Dl bootps BOOTP Server listening port +.Dl bootpc BOOTP Client destination port +.Pp +If the port numbers cannot be determined using +.Xr getservbyname 3 +then the values default to bootps=67 and bootpc=68. +.Sh FILES +.Bl -tag -width /tmp/bootpd.dump -compact +.It Pa /etc/bootptab +Database file read by +.Nm . +.It Pa /tmp/bootpd.dump +Debugging dump file created by +.Nm . +.It Pa /etc/services +Internet service numbers. +.It Pa /tftpboot +Current directory typically used by the TFTP server and +.Nm . +.El +.Sh "SEE ALSO" +.Xr bootptab 5 , +.Xr inetd 8 , +.Xr tftpd 8 +.Pp +DARPA Internet Request For Comments: +.Bl -tag -width RFC1533 -compact +.It RFC951 +Bootstrap Protocol +.It RFC1532 +Clarifications and Extensions for the Bootstrap Protocol +.It RFC1533 +DHCP Options and BOOTP Vendor Extensions +.El +.Sh AUTHORS +This distribution is currently maintained by +.An Walter L. Wimer Aq Mt walt+@cmu.edu . +.Pp +The original BOOTP server was created by +.An Bill Croft +at Stanford University in January 1986. +.Pp +The current version of +.Nm +is primarily the work of +.An David Kovar , +.An Drew D. Perkins , +and +.An Walter L. Wimer , +at Carnegie Mellon University. +.Pp +Enhancements and bug-fixes have been contributed by: +.Pp +(in alphabetical order) +.Pp +.An -split +.An Danny Backx Aq Mt db@sunbim.be +.An John Brezak Aq Mt brezak@ch.hp.com +.An Frank da Cruz Aq Mt fdc@cc.columbia.edu +.An David R. Linn Aq Mt drl@vuse.vanderbilt.edu +.An Jim McKim Aq Mt mckim@lerc.nasa.gov +.An Gordon W. Ross Aq Mt gwr@mc.com +.An Jason Zions Aq Mt jazz@hal.com . +.Sh BUGS +Individual host entries must not exceed 1024 characters. diff --git a/libexec/bootpd/bootpd.c b/libexec/bootpd/bootpd.c new file mode 100644 index 0000000..fe9cefa --- /dev/null +++ b/libexec/bootpd/bootpd.c @@ -0,0 +1,1408 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ + +/* + * BOOTP (bootstrap protocol) server daemon. + * + * Answers BOOTP request packets from booting client machines. + * See [SRI-NIC]<RFC>RFC951.TXT for a description of the protocol. + * See [SRI-NIC]<RFC>RFC1048.TXT for vendor-information extensions. + * See RFC 1395 for option tags 14-17. + * See accompanying man page -- bootpd.8 + * + * HISTORY + * See ./Changes + * + * BUGS + * See ./ToDo + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#ifndef NO_UNISTD +#include <unistd.h> +#endif + +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <netdb.h> +#include <paths.h> +#include <syslog.h> +#include <assert.h> +#include <inttypes.h> + +#ifdef NO_SETSID +# include <fcntl.h> /* for O_RDONLY, etc */ +#endif + +#ifndef USE_BFUNCS +# include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +# define bcopy(a,b,c) memcpy(b,a,c) +# define bzero(p,l) memset(p,0,l) +# define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "bootp.h" +#include "hash.h" +#include "hwaddr.h" +#include "bootpd.h" +#include "dovend.h" +#include "getif.h" +#include "readfile.h" +#include "report.h" +#include "tzone.h" +#include "patchlevel.h" + +#ifndef CONFIG_FILE +#define CONFIG_FILE "/etc/bootptab" +#endif +#ifndef DUMPTAB_FILE +#define DUMPTAB_FILE "/tmp/bootpd.dump" +#endif + + + +/* + * Externals, forward declarations, and global variables + */ + +extern void dumptab(char *); + +PRIVATE void catcher(int); +PRIVATE int chk_access(char *, int32 *); +#ifdef VEND_CMU +PRIVATE void dovend_cmu(struct bootp *, struct host *); +#endif +PRIVATE void dovend_rfc1048(struct bootp *, struct host *, int32); +PRIVATE void handle_reply(void); +PRIVATE void handle_request(void); +PRIVATE void sendreply(int forward, int32 dest_override); +PRIVATE void usage(void); + +/* + * IP port numbers for client and server obtained from /etc/services + */ + +u_short bootps_port, bootpc_port; + + +/* + * Internet socket and interface config structures + */ + +struct sockaddr_in bind_addr; /* Listening */ +struct sockaddr_in recv_addr; /* Packet source */ +struct sockaddr_in send_addr; /* destination */ + + +/* + * option defaults + */ +int debug = 0; /* Debugging flag (level) */ +struct timeval actualtimeout = +{ /* fifteen minutes */ + 15 * 60L, /* tv_sec */ + 0 /* tv_usec */ +}; + +/* + * General + */ + +int s; /* Socket file descriptor */ +char *pktbuf; /* Receive packet buffer */ +int pktlen; +char *progname; +char *chdir_path; +struct in_addr my_ip_addr; + +static const char *hostname; +static char default_hostname[MAXHOSTNAMELEN]; + +/* Flags set by signal catcher. */ +PRIVATE int do_readtab = 0; +PRIVATE int do_dumptab = 0; + +/* + * Globals below are associated with the bootp database file (bootptab). + */ + +char *bootptab = CONFIG_FILE; +char *bootpd_dump = DUMPTAB_FILE; + + + +/* + * Initialization such as command-line processing is done and then the + * main server loop is started. + */ + +int +main(argc, argv) + int argc; + char **argv; +{ + struct timeval *timeout; + struct bootp *bp; + struct servent *servp; + struct hostent *hep; + char *stmp; + socklen_t ba_len, ra_len; + int n; + int nfound; + fd_set readfds; + int standalone; +#ifdef SA_NOCLDSTOP /* Have POSIX sigaction(2). */ + struct sigaction sa; +#endif + + progname = strrchr(argv[0], '/'); + if (progname) progname++; + else progname = argv[0]; + + /* + * Initialize logging. + */ + report_init(0); /* uses progname */ + + /* + * Log startup + */ + report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL); + + /* Debugging for compilers with struct padding. */ + assert(sizeof(struct bootp) == BP_MINPKTSZ); + + /* Get space for receiving packets and composing replies. */ + pktbuf = malloc(MAX_MSG_SIZE); + if (!pktbuf) { + report(LOG_ERR, "malloc failed"); + exit(1); + } + bp = (struct bootp *) pktbuf; + + /* + * Check to see if a socket was passed to us from inetd. + * + * Use getsockname() to determine if descriptor 0 is indeed a socket + * (and thus we are probably a child of inetd) or if it is instead + * something else and we are running standalone. + */ + s = 0; + ba_len = sizeof(bind_addr); + bzero((char *) &bind_addr, ba_len); + errno = 0; + standalone = TRUE; + if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) { + /* + * Descriptor 0 is a socket. Assume we are a child of inetd. + */ + if (bind_addr.sin_family == AF_INET) { + standalone = FALSE; + bootps_port = ntohs(bind_addr.sin_port); + } else { + /* Some other type of socket? */ + report(LOG_ERR, "getsockname: not an INET socket"); + } + } + + /* + * Set defaults that might be changed by option switches. + */ + stmp = NULL; + timeout = &actualtimeout; + + if (gethostname(default_hostname, sizeof(default_hostname) - 1) < 0) { + report(LOG_ERR, "bootpd: can't get hostname\n"); + exit(1); + } + default_hostname[sizeof(default_hostname) - 1] = '\0'; + hostname = default_hostname; + + /* + * Read switches. + */ + for (argc--, argv++; argc > 0; argc--, argv++) { + if (argv[0][0] != '-') + break; + switch (argv[0][1]) { + + case 'c': /* chdir_path */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (stmp[0] != '/')) { + report(LOG_ERR, + "bootpd: invalid chdir specification\n"); + break; + } + chdir_path = stmp; + break; + + case 'd': /* debug level */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else if (argv[1] && argv[1][0] == '-') { + /* + * Backwards-compatible behavior: + * no parameter, so just increment the debug flag. + */ + debug++; + break; + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + report(LOG_ERR, + "%s: invalid debug level\n", progname); + break; + } + debug = n; + break; + + case 'h': /* override hostname */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp) { + report(LOG_ERR, + "bootpd: missing hostname\n"); + break; + } + hostname = stmp; + break; + + case 'i': /* inetd mode */ + standalone = FALSE; + break; + + case 's': /* standalone mode */ + standalone = TRUE; + break; + + case 't': /* timeout */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + report(LOG_ERR, + "%s: invalid timeout specification\n", progname); + break; + } + actualtimeout.tv_sec = (int32) (60 * n); + /* + * If the actual timeout is zero, pass a NULL pointer + * to select so it blocks indefinitely, otherwise, + * point to the actual timeout value. + */ + timeout = (n > 0) ? &actualtimeout : NULL; + break; + + default: + report(LOG_ERR, "%s: unknown switch: -%c\n", + progname, argv[0][1]); + usage(); + break; + + } /* switch */ + } /* for args */ + + /* + * Override default file names if specified on the command line. + */ + if (argc > 0) + bootptab = argv[0]; + + if (argc > 1) + bootpd_dump = argv[1]; + + /* + * Get my hostname and IP address. + */ + + hep = gethostbyname(hostname); + if (!hep) { + report(LOG_ERR, "Can not get my IP address\n"); + exit(1); + } + bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr)); + + if (standalone) { + /* + * Go into background and disassociate from controlling terminal. + */ + if (debug < 3) { + if (fork()) + exit(0); +#ifdef NO_SETSID + setpgrp(0,0); +#ifdef TIOCNOTTY + n = open(_PATH_TTY, O_RDWR); + if (n >= 0) { + ioctl(n, TIOCNOTTY, (char *) 0); + (void) close(n); + } +#endif /* TIOCNOTTY */ +#else /* SETSID */ + if (setsid() < 0) + perror("setsid"); +#endif /* SETSID */ + } /* if debug < 3 */ + + /* + * Nuke any timeout value + */ + timeout = NULL; + + } /* if standalone (1st) */ + + /* Set the cwd (i.e. to /tftpboot) */ + if (chdir_path) { + if (chdir(chdir_path) < 0) + report(LOG_ERR, "%s: chdir failed", chdir_path); + } + + /* Get the timezone. */ + tzone_init(); + + /* Allocate hash tables. */ + rdtab_init(); + + /* + * Read the bootptab file. + */ + readtab(1); /* force read */ + + if (standalone) { + + /* + * Create a socket. + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + report(LOG_ERR, "socket: %s", get_network_errmsg()); + exit(1); + } + + /* + * Get server's listening port number + */ + servp = getservbyname("bootps", "udp"); + if (servp) { + bootps_port = ntohs((u_short) servp->s_port); + } else { + bootps_port = (u_short) IPPORT_BOOTPS; + report(LOG_ERR, + "bootps/udp: unknown service -- using port %d", + bootps_port); + } + + /* + * Bind socket to BOOTPS port. + */ + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = INADDR_ANY; + bind_addr.sin_port = htons(bootps_port); + if (bind(s, (struct sockaddr *) &bind_addr, + sizeof(bind_addr)) < 0) + { + report(LOG_ERR, "bind: %s", get_network_errmsg()); + exit(1); + } + } /* if standalone (2nd)*/ + + /* + * Get destination port number so we can reply to client + */ + servp = getservbyname("bootpc", "udp"); + if (servp) { + bootpc_port = ntohs(servp->s_port); + } else { + report(LOG_ERR, + "bootpc/udp: unknown service -- using port %d", + IPPORT_BOOTPC); + bootpc_port = (u_short) IPPORT_BOOTPC; + } + + /* + * Set up signals to read or dump the table. + */ +#ifdef SA_NOCLDSTOP /* Have POSIX sigaction(2). */ + sa.sa_handler = catcher; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGHUP, &sa, NULL) < 0) { + report(LOG_ERR, "sigaction: %s", get_errmsg()); + exit(1); + } + if (sigaction(SIGUSR1, &sa, NULL) < 0) { + report(LOG_ERR, "sigaction: %s", get_errmsg()); + exit(1); + } +#else /* SA_NOCLDSTOP */ + /* Old-fashioned UNIX signals */ + if ((int) signal(SIGHUP, catcher) < 0) { + report(LOG_ERR, "signal: %s", get_errmsg()); + exit(1); + } + if ((int) signal(SIGUSR1, catcher) < 0) { + report(LOG_ERR, "signal: %s", get_errmsg()); + exit(1); + } +#endif /* SA_NOCLDSTOP */ + + /* + * Process incoming requests. + */ + FD_ZERO(&readfds); + for (;;) { + struct timeval tv; + + FD_SET(s, &readfds); + if (timeout) + tv = *timeout; + + nfound = select(s + 1, &readfds, NULL, NULL, + (timeout) ? &tv : NULL); + if (nfound < 0) { + if (errno != EINTR) { + report(LOG_ERR, "select: %s", get_errmsg()); + } + /* + * Call readtab() or dumptab() here to avoid the + * dangers of doing I/O from a signal handler. + */ + if (do_readtab) { + do_readtab = 0; + readtab(1); /* force read */ + } + if (do_dumptab) { + do_dumptab = 0; + dumptab(bootpd_dump); + } + continue; + } + if (!FD_ISSET(s, &readfds)) { + if (debug > 1) + report(LOG_INFO, "exiting after %jd minutes of inactivity", + (intmax_t)actualtimeout.tv_sec / 60); + exit(0); + } + ra_len = sizeof(recv_addr); + n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0, + (struct sockaddr *) &recv_addr, &ra_len); + if (n <= 0) { + continue; + } + if (debug > 1) { + report(LOG_INFO, "recvd pkt from IP addr %s", + inet_ntoa(recv_addr.sin_addr)); + } + if (n < sizeof(struct bootp)) { + if (debug) { + report(LOG_NOTICE, "received short packet"); + } + continue; + } + pktlen = n; + + readtab(0); /* maybe re-read bootptab */ + + switch (bp->bp_op) { + case BOOTREQUEST: + handle_request(); + break; + case BOOTREPLY: + handle_reply(); + break; + } + } + return 0; +} + + + + +/* + * Print "usage" message and exit + */ + +PRIVATE void +usage() +{ + fprintf(stderr, + "usage: bootpd [-i | -s] [-c chdir-path] [-d level] [-h hostname] [-t timeout]\n"); + fprintf(stderr, " [bootptab [dumpfile]]\n"); + fprintf(stderr, "\t -c n\tset current directory\n"); + fprintf(stderr, "\t -d n\tset debug level\n"); + fprintf(stderr, "\t -h n\tset the hostname to listen on\n"); + fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n"); + fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n"); + fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n"); + exit(1); +} + +/* Signal catchers */ +PRIVATE void +catcher(sig) + int sig; +{ + if (sig == SIGHUP) + do_readtab = 1; + if (sig == SIGUSR1) + do_dumptab = 1; +#if !defined(SA_NOCLDSTOP) && defined(SYSV) + /* For older "System V" derivatives with no sigaction(). */ + signal(sig, catcher); +#endif +} + + + +/* + * Process BOOTREQUEST packet. + * + * Note: This version of the bootpd.c server never forwards + * a request to another server. That is the job of a gateway + * program such as the "bootpgw" program included here. + * + * (Also this version does not interpret the hostname field of + * the request packet; it COULD do a name->address lookup and + * forward the request there.) + */ +PRIVATE void +handle_request() +{ + struct bootp *bp = (struct bootp *) pktbuf; + struct host *hp = NULL; + struct host dummyhost; + int32 bootsize = 0; + unsigned hlen, hashcode; + int32 dest; + char realpath[1024]; + char *clntpath; + char *homedir, *bootfile; + int n; + + bp->bp_file[sizeof(bp->bp_file)-1] = '\0'; + + /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */ + + /* + * If the servername field is set, compare it against us. + * If we're not being addressed, ignore this request. + * If the server name field is null, throw in our name. + */ + if (strlen(bp->bp_sname)) { + if (strcmp(bp->bp_sname, hostname)) { + if (debug) + report(LOG_INFO, "\ +ignoring request for server %s from client at %s address %s", + bp->bp_sname, netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, bp->bp_hlen)); + /* XXX - Is it correct to ignore such a request? -gwr */ + return; + } + } else { + strcpy(bp->bp_sname, hostname); + } + + /* Convert the request into a reply. */ + bp->bp_op = BOOTREPLY; + if (bp->bp_ciaddr.s_addr == 0) { + /* + * client doesn't know his IP address, + * search by hardware address. + */ + if (debug > 1) { + report(LOG_INFO, "request from %s address %s", + netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, bp->bp_hlen)); + } + hlen = haddrlength(bp->bp_htype); + if (hlen != bp->bp_hlen) { + report(LOG_NOTICE, "bad addr len from %s address %s", + netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, hlen)); + } + dummyhost.htype = bp->bp_htype; + bcopy(bp->bp_chaddr, dummyhost.haddr, hlen); + hashcode = hash_HashFunction(bp->bp_chaddr, hlen); + hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, + &dummyhost); + if (hp == NULL && + bp->bp_htype == HTYPE_IEEE802) + { + /* Try again with address in "canonical" form. */ + haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen); + if (debug > 1) { + report(LOG_INFO, "\ +HW addr type is IEEE 802. convert to %s and check again\n", + haddrtoa(dummyhost.haddr, bp->bp_hlen)); + } + hashcode = hash_HashFunction(dummyhost.haddr, hlen); + hp = (struct host *) hash_Lookup(hwhashtable, hashcode, + hwlookcmp, &dummyhost); + } + if (hp == NULL) { + /* + * XXX - Add dynamic IP address assignment? + */ + if (debug) + report(LOG_NOTICE, "unknown client %s address %s", + netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, bp->bp_hlen)); + return; /* not found */ + } + (bp->bp_yiaddr).s_addr = hp->iaddr.s_addr; + + } else { + + /* + * search by IP address. + */ + if (debug > 1) { + report(LOG_INFO, "request from IP addr %s", + inet_ntoa(bp->bp_ciaddr)); + } + dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr; + hashcode = hash_HashFunction((u_char *) &(bp->bp_ciaddr.s_addr), 4); + hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp, + &dummyhost); + if (hp == NULL) { + if (debug) { + report(LOG_NOTICE, "IP address not found: %s", + inet_ntoa(bp->bp_ciaddr)); + } + return; + } + } + + if (debug) { + report(LOG_INFO, "found %s (%s)", inet_ntoa(hp->iaddr), + hp->hostname->string); + } + + /* + * If there is a response delay threshold, ignore requests + * with a timestamp lower than the threshold. + */ + if (hp->flags.min_wait) { + u_int32 t = (u_int32) ntohs(bp->bp_secs); + if (t < hp->min_wait) { + if (debug > 1) + report(LOG_INFO, + "ignoring request due to timestamp (%d < %d)", + t, hp->min_wait); + return; + } + } + +#ifdef YORK_EX_OPTION + /* + * The need for the "ex" tag arose out of the need to empty + * shared networked drives on diskless PCs. This solution is + * not very clean but it does work fairly well. + * Written by Edmund J. Sutcliffe <edmund@york.ac.uk> + * + * XXX - This could compromise security if a non-trusted user + * managed to write an entry in the bootptab with :ex=trojan: + * so I would leave this turned off unless you need it. -gwr + */ + /* Run a program, passing the client name as a parameter. */ + if (hp->flags.exec_file) { + char tst[100]; + /* XXX - Check string lengths? -gwr */ + strcpy (tst, hp->exec_file->string); + strcat (tst, " "); + strcat (tst, hp->hostname->string); + strcat (tst, " &"); + if (debug) + report(LOG_INFO, "executing %s", tst); + system(tst); /* Hope this finishes soon... */ + } +#endif /* YORK_EX_OPTION */ + + /* + * If a specific TFTP server address was specified in the bootptab file, + * fill it in, otherwise zero it. + * XXX - Rather than zero it, should it be the bootpd address? -gwr + */ + (bp->bp_siaddr).s_addr = (hp->flags.bootserver) ? + hp->bootserver.s_addr : 0L; + +#ifdef STANFORD_PROM_COMPAT + /* + * Stanford bootp PROMs (for a Sun?) have no way to leave + * the boot file name field blank (because the boot file + * name is automatically generated from some index). + * As a work-around, this little hack allows those PROMs to + * specify "sunboot14" with the same effect as a NULL name. + * (The user specifies boot device 14 or some such magic.) + */ + if (strcmp(bp->bp_file, "sunboot14") == 0) + bp->bp_file[0] = '\0'; /* treat it as unspecified */ +#endif + + /* + * Fill in the client's proper bootfile. + * + * If the client specifies an absolute path, try that file with a + * ".host" suffix and then without. If the file cannot be found, no + * reply is made at all. + * + * If the client specifies a null or relative file, use the following + * table to determine the appropriate action: + * + * Homedir Bootfile Client's file + * specified? specified? specification Action + * ------------------------------------------------------------------- + * No No Null Send null filename + * No No Relative Discard request + * No Yes Null Send if absolute else null + * No Yes Relative Discard request *XXX + * Yes No Null Send null filename + * Yes No Relative Lookup with ".host" + * Yes Yes Null Send home/boot or bootfile + * Yes Yes Relative Lookup with ".host" *XXX + * + */ + + /* + * XXX - I don't like the policy of ignoring a client when the + * boot file is not accessible. The TFTP server might not be + * running on the same machine as the BOOTP server, in which + * case checking accessibility of the boot file is pointless. + * + * Therefore, file accessibility is now demanded ONLY if you + * define CHECK_FILE_ACCESS in the Makefile options. -gwr + */ + + /* + * The "real" path is as seen by the BOOTP daemon on this + * machine, while the client path is relative to the TFTP + * daemon chroot directory (i.e. /tftpboot). + */ + if (hp->flags.tftpdir) { + snprintf(realpath, sizeof(realpath), "%s", hp->tftpdir->string); + clntpath = &realpath[strlen(realpath)]; + } else { + realpath[0] = '\0'; + clntpath = realpath; + } + + /* + * Determine client's requested homedir and bootfile. + */ + homedir = NULL; + bootfile = NULL; + if (bp->bp_file[0]) { + homedir = bp->bp_file; + bootfile = strrchr(homedir, '/'); + if (bootfile) { + if (homedir == bootfile) + homedir = NULL; + *bootfile++ = '\0'; + } else { + /* no "/" in the string */ + bootfile = homedir; + homedir = NULL; + } + if (debug > 2) { + report(LOG_INFO, "requested path=\"%s\" file=\"%s\"", + (homedir) ? homedir : "", + (bootfile) ? bootfile : ""); + } + } + + /* + * Specifications in bootptab override client requested values. + */ + if (hp->flags.homedir) + homedir = hp->homedir->string; + if (hp->flags.bootfile) + bootfile = hp->bootfile->string; + + /* + * Construct bootfile path. + */ + if (homedir) { + if (homedir[0] != '/') + strcat(clntpath, "/"); + strcat(clntpath, homedir); + homedir = NULL; + } + if (bootfile) { + if (bootfile[0] != '/') + strcat(clntpath, "/"); + strcat(clntpath, bootfile); + bootfile = NULL; + } + + /* + * First try to find the file with a ".host" suffix + */ + n = strlen(clntpath); + strcat(clntpath, "."); + strcat(clntpath, hp->hostname->string); + if (chk_access(realpath, &bootsize) < 0) { + clntpath[n] = 0; /* Try it without the suffix */ + if (chk_access(realpath, &bootsize) < 0) { + /* neither "file.host" nor "file" was found */ +#ifdef CHECK_FILE_ACCESS + + if (bp->bp_file[0]) { + /* + * Client wanted specific file + * and we didn't have it. + */ + report(LOG_NOTICE, + "requested file not found: \"%s\"", clntpath); + return; + } + /* + * Client didn't ask for a specific file and we couldn't + * access the default file, so just zero-out the bootfile + * field in the packet and continue processing the reply. + */ + bzero(bp->bp_file, sizeof(bp->bp_file)); + goto null_file_name; + +#else /* CHECK_FILE_ACCESS */ + + /* Complain only if boot file size was needed. */ + if (hp->flags.bootsize_auto) { + report(LOG_ERR, "can not determine size of file \"%s\"", + clntpath); + } + +#endif /* CHECK_FILE_ACCESS */ + } + } + strncpy(bp->bp_file, clntpath, BP_FILE_LEN); + if (debug > 2) + report(LOG_INFO, "bootfile=\"%s\"", clntpath); + +#ifdef CHECK_FILE_ACCESS +null_file_name: +#endif /* CHECK_FILE_ACCESS */ + + + /* + * Handle vendor options based on magic number. + */ + + if (debug > 1) { + report(LOG_INFO, "vendor magic field is %d.%d.%d.%d", + (int) ((bp->bp_vend)[0]), + (int) ((bp->bp_vend)[1]), + (int) ((bp->bp_vend)[2]), + (int) ((bp->bp_vend)[3])); + } + /* + * If this host isn't set for automatic vendor info then copy the + * specific cookie into the bootp packet, thus forcing a certain + * reply format. Only force reply format if user specified it. + */ + if (hp->flags.vm_cookie) { + /* Slam in the user specified magic number. */ + bcopy(hp->vm_cookie, bp->bp_vend, 4); + } + /* + * Figure out the format for the vendor-specific info. + * Note that bp->bp_vend may have been set above. + */ + if (!bcmp(bp->bp_vend, vm_rfc1048, 4)) { + /* RFC1048 conformant bootp client */ + dovend_rfc1048(bp, hp, bootsize); + if (debug > 1) { + report(LOG_INFO, "sending reply (with RFC1048 options)"); + } + } +#ifdef VEND_CMU + else if (!bcmp(bp->bp_vend, vm_cmu, 4)) { + dovend_cmu(bp, hp); + if (debug > 1) { + report(LOG_INFO, "sending reply (with CMU options)"); + } + } +#endif + else { + if (debug > 1) { + report(LOG_INFO, "sending reply (with no options)"); + } + } + + dest = (hp->flags.reply_addr) ? + hp->reply_addr.s_addr : 0L; + + /* not forwarded */ + sendreply(0, dest); +} + + +/* + * Process BOOTREPLY packet. + */ +PRIVATE void +handle_reply() +{ + if (debug) { + report(LOG_INFO, "processing boot reply"); + } + /* forwarded, no destination override */ + sendreply(1, 0); +} + + +/* + * Send a reply packet to the client. 'forward' flag is set if we are + * not the originator of this reply packet. + */ +PRIVATE void +sendreply(forward, dst_override) + int forward; + int32 dst_override; +{ + struct bootp *bp = (struct bootp *) pktbuf; + struct in_addr dst; + u_short port = bootpc_port; + unsigned char *ha; + int len, haf; + + /* + * XXX - Should honor bp_flags "broadcast" bit here. + * Temporary workaround: use the :ra=ADDR: option to + * set the reply address to the broadcast address. + */ + + /* + * If the destination address was specified explicitly + * (i.e. the broadcast address for HP compatibility) + * then send the response to that address. Otherwise, + * act in accordance with RFC951: + * If the client IP address is specified, use that + * else if gateway IP address is specified, use that + * else make a temporary arp cache entry for the client's + * NEW IP/hardware address and use that. + */ + if (dst_override) { + dst.s_addr = dst_override; + if (debug > 1) { + report(LOG_INFO, "reply address override: %s", + inet_ntoa(dst)); + } + } else if (bp->bp_ciaddr.s_addr) { + dst = bp->bp_ciaddr; + } else if (bp->bp_giaddr.s_addr && forward == 0) { + dst = bp->bp_giaddr; + port = bootps_port; + if (debug > 1) { + report(LOG_INFO, "sending reply to gateway %s", + inet_ntoa(dst)); + } + } else { + dst = bp->bp_yiaddr; + ha = bp->bp_chaddr; + len = bp->bp_hlen; + if (len > MAXHADDRLEN) + len = MAXHADDRLEN; + haf = (int) bp->bp_htype; + if (haf == 0) + haf = HTYPE_ETHERNET; + + if (debug > 1) + report(LOG_INFO, "setarp %s - %s", + inet_ntoa(dst), haddrtoa(ha, len)); + setarp(s, &dst, haf, ha, len); + } + + if ((forward == 0) && + (bp->bp_siaddr.s_addr == 0)) + { + struct ifreq *ifr; + struct in_addr siaddr; + /* + * If we are originating this reply, we + * need to find our own interface address to + * put in the bp_siaddr field of the reply. + * If this server is multi-homed, pick the + * 'best' interface (the one on the same net + * as the client). Of course, the client may + * be on the other side of a BOOTP gateway... + */ + ifr = getif(s, &dst); + if (ifr) { + struct sockaddr_in *sip; + sip = (struct sockaddr_in *) &(ifr->ifr_addr); + siaddr = sip->sin_addr; + } else { + /* Just use my "official" IP address. */ + siaddr = my_ip_addr; + } + + /* XXX - No need to set bp_giaddr here. */ + + /* Finally, set the server address field. */ + bp->bp_siaddr = siaddr; + } + /* Set up socket address for send. */ + send_addr.sin_family = AF_INET; + send_addr.sin_port = htons(port); + send_addr.sin_addr = dst; + + /* Send reply with same size packet as request used. */ + if (sendto(s, pktbuf, pktlen, 0, + (struct sockaddr *) &send_addr, + sizeof(send_addr)) < 0) + { + report(LOG_ERR, "sendto: %s", get_network_errmsg()); + } +} /* sendreply */ + + +/* nmatch() - now in getif.c */ +/* setarp() - now in hwaddr.c */ + + +/* + * This call checks read access to a file. It returns 0 if the file given + * by "path" exists and is publicly readable. A value of -1 is returned if + * access is not permitted or an error occurs. Successful calls also + * return the file size in bytes using the long pointer "filesize". + * + * The read permission bit for "other" users is checked. This bit must be + * set for tftpd(8) to allow clients to read the file. + */ + +PRIVATE int +chk_access(path, filesize) + char *path; + int32 *filesize; +{ + struct stat st; + + if ((stat(path, &st) == 0) && (st.st_mode & (S_IREAD >> 6))) { + *filesize = (int32) st.st_size; + return 0; + } else { + return -1; + } +} + + +/* + * Now in dumptab.c : + * dumptab() + * dump_host() + * list_ipaddresses() + */ + +#ifdef VEND_CMU + +/* + * Insert the CMU "vendor" data for the host pointed to by "hp" into the + * bootp packet pointed to by "bp". + */ + +PRIVATE void +dovend_cmu(bp, hp) + struct bootp *bp; + struct host *hp; +{ + struct cmu_vend *vendp; + struct in_addr_list *taddr; + + /* + * Initialize the entire vendor field to zeroes. + */ + bzero(bp->bp_vend, sizeof(bp->bp_vend)); + + /* + * Fill in vendor information. Subnet mask, default gateway, + * domain name server, ien name server, time server + */ + vendp = (struct cmu_vend *) bp->bp_vend; + strcpy(vendp->v_magic, (char *)vm_cmu); + if (hp->flags.subnet_mask) { + (vendp->v_smask).s_addr = hp->subnet_mask.s_addr; + (vendp->v_flags) |= VF_SMASK; + if (hp->flags.gateway) { + (vendp->v_dgate).s_addr = hp->gateway->addr->s_addr; + } + } + if (hp->flags.domain_server) { + taddr = hp->domain_server; + if (taddr->addrcount > 0) { + (vendp->v_dns1).s_addr = (taddr->addr)[0].s_addr; + if (taddr->addrcount > 1) { + (vendp->v_dns2).s_addr = (taddr->addr)[1].s_addr; + } + } + } + if (hp->flags.name_server) { + taddr = hp->name_server; + if (taddr->addrcount > 0) { + (vendp->v_ins1).s_addr = (taddr->addr)[0].s_addr; + if (taddr->addrcount > 1) { + (vendp->v_ins2).s_addr = (taddr->addr)[1].s_addr; + } + } + } + if (hp->flags.time_server) { + taddr = hp->time_server; + if (taddr->addrcount > 0) { + (vendp->v_ts1).s_addr = (taddr->addr)[0].s_addr; + if (taddr->addrcount > 1) { + (vendp->v_ts2).s_addr = (taddr->addr)[1].s_addr; + } + } + } + /* Log message now done by caller. */ +} /* dovend_cmu */ + +#endif /* VEND_CMU */ + + + +/* + * Insert the RFC1048 vendor data for the host pointed to by "hp" into the + * bootp packet pointed to by "bp". + */ +#define NEED(LEN, MSG) do \ + if (bytesleft < (LEN)) { \ + report(LOG_NOTICE, noroom, \ + hp->hostname->string, MSG); \ + return; \ + } while (0) +PRIVATE void +dovend_rfc1048(bp, hp, bootsize) + struct bootp *bp; + struct host *hp; + int32 bootsize; +{ + int bytesleft, len; + byte *vp; + + static const char noroom[] = "%s: No room for \"%s\" option"; + + vp = bp->bp_vend; + + if (hp->flags.msg_size) { + pktlen = hp->msg_size; + } else { + /* + * If the request was longer than the official length, build + * a response of that same length where the additional length + * is assumed to be part of the bp_vend (options) area. + */ + if (pktlen > sizeof(*bp)) { + if (debug > 1) + report(LOG_INFO, "request message length=%d", pktlen); + } + /* + * Check whether the request contains the option: + * Maximum DHCP Message Size (RFC1533 sec. 9.8) + * and if so, override the response length with its value. + * This request must lie within the first BP_VEND_LEN + * bytes of the option space. + */ + { + byte *p, *ep; + byte tag, len; + short msgsz = 0; + + p = vp + 4; + ep = p + BP_VEND_LEN - 4; + while (p < ep) { + tag = *p++; + /* Check for tags with no data first. */ + if (tag == TAG_PAD) + continue; + if (tag == TAG_END) + break; + /* Now scan the length byte. */ + len = *p++; + switch (tag) { + case TAG_MAX_MSGSZ: + if (len == 2) { + bcopy(p, (char*)&msgsz, 2); + msgsz = ntohs(msgsz); + } + break; + case TAG_SUBNET_MASK: + /* XXX - Should preserve this if given... */ + break; + } /* swtich */ + p += len; + } + + if (msgsz > sizeof(*bp) + BP_MSG_OVERHEAD) { + if (debug > 1) + report(LOG_INFO, "request has DHCP msglen=%d", msgsz); + pktlen = msgsz - BP_MSG_OVERHEAD; + } + } + } + + if (pktlen < sizeof(*bp)) { + report(LOG_ERR, "invalid response length=%d", pktlen); + pktlen = sizeof(*bp); + } + bytesleft = ((byte*)bp + pktlen) - vp; + if (pktlen > sizeof(*bp)) { + if (debug > 1) + report(LOG_INFO, "extended reply, length=%d, options=%d", + pktlen, bytesleft); + } + + /* Copy in the magic cookie */ + bcopy(vm_rfc1048, vp, 4); + vp += 4; + bytesleft -= 4; + + if (hp->flags.subnet_mask) { + /* always enough room here. */ + *vp++ = TAG_SUBNET_MASK;/* -1 byte */ + *vp++ = 4; /* -1 byte */ + insert_u_long(hp->subnet_mask.s_addr, &vp); /* -4 bytes */ + bytesleft -= 6; /* Fix real count */ + if (hp->flags.gateway) { + (void) insert_ip(TAG_GATEWAY, + hp->gateway, + &vp, &bytesleft); + } + } + if (hp->flags.bootsize) { + /* always enough room here */ + bootsize = (hp->flags.bootsize_auto) ? + ((bootsize + 511) / 512) : (hp->bootsize); /* Round up */ + *vp++ = TAG_BOOT_SIZE; + *vp++ = 2; + *vp++ = (byte) ((bootsize >> 8) & 0xFF); + *vp++ = (byte) (bootsize & 0xFF); + bytesleft -= 4; /* Tag, length, and 16 bit blocksize */ + } + /* + * This one is special: Remaining options go in the ext file. + * Only the subnet_mask, bootsize, and gateway should precede. + */ + if (hp->flags.exten_file) { + /* + * Check for room for exten_file. Add 3 to account for + * TAG_EXTEN_FILE, length, and TAG_END. + */ + len = strlen(hp->exten_file->string); + NEED((len + 3), "ef"); + *vp++ = TAG_EXTEN_FILE; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->exten_file->string, vp, len); + vp += len; + *vp++ = TAG_END; + bytesleft -= len + 3; + return; /* no more options here. */ + } + /* + * The remaining options are inserted by the following + * function (which is shared with bootpef.c). + * Keep back one byte for the TAG_END. + */ + len = dovend_rfc1497(hp, vp, bytesleft - 1); + vp += len; + bytesleft -= len; + + /* There should be at least one byte left. */ + NEED(1, "(end)"); + *vp++ = TAG_END; + bytesleft--; + + /* Log message done by caller. */ + if (bytesleft > 0) { + /* + * Zero out any remaining part of the vendor area. + */ + bzero(vp, bytesleft); + } +} /* dovend_rfc1048 */ +#undef NEED + + +/* + * Now in readfile.c: + * hwlookcmp() + * iplookcmp() + */ + +/* haddrtoa() - now in hwaddr.c */ +/* + * Now in dovend.c: + * insert_ip() + * insert_generic() + * insert_u_long() + */ + +/* get_errmsg() - now in report.c */ + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/bootpd.h b/libexec/bootpd/bootpd.h new file mode 100644 index 0000000..86c1b3d --- /dev/null +++ b/libexec/bootpd/bootpd.h @@ -0,0 +1,213 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. +************************************************************************/ + + +/* + * bootpd.h -- common header file for all the modules of the bootpd program. + * + * $FreeBSD$ + */ + +#include "bptypes.h" +#include "hash.h" +#include "hwaddr.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef PRIVATE +#define PRIVATE static +#endif + +#ifndef SIGUSR1 +#define SIGUSR1 30 /* From 4.3 <signal.h> */ +#endif + +#define MAXSTRINGLEN 80 /* Max string length */ + +/* Local definitions: */ +#define MAX_MSG_SIZE (3*512) /* Maximum packet size */ + + +/* + * Return pointer to static string which gives full network error message. + */ +#define get_network_errmsg get_errmsg + + +/* + * Data structure used to hold an arbitrary-lengthed list of IP addresses. + * The list may be shared among multiple hosts by setting the linkcount + * appropriately. + */ + +struct in_addr_list { + unsigned int linkcount, addrcount; + struct in_addr addr[1]; /* Dynamically extended */ +}; + + +/* + * Data structures used to hold shared strings and shared binary data. + * The linkcount must be set appropriately. + */ + +struct shared_string { + unsigned int linkcount; + char string[1]; /* Dynamically extended */ +}; + +struct shared_bindata { + unsigned int linkcount, length; + byte data[1]; /* Dynamically extended */ +}; + + +/* + * Flag structure which indicates which symbols have been defined for a + * given host. This information is used to determine which data should or + * should not be reported in the bootp packet vendor info field. + */ + +struct flag { + unsigned bootfile :1, + bootserver :1, + bootsize :1, + bootsize_auto :1, + cookie_server :1, + domain_server :1, + gateway :1, + generic :1, + haddr :1, + homedir :1, + htype :1, + impress_server :1, + iaddr :1, + log_server :1, + lpr_server :1, + name_server :1, + name_switch :1, + rlp_server :1, + send_name :1, + subnet_mask :1, + tftpdir :1, + time_offset :1, + time_server :1, + dump_file :1, + domain_name :1, + swap_server :1, + root_path :1, + exten_file :1, + reply_addr :1, + nis_domain :1, + nis_server :1, + ntp_server :1, + exec_file :1, + msg_size :1, + min_wait :1, + /* XXX - Add new tags here */ + vm_cookie :1; +}; + + + +/* + * The flags structure contains TRUE flags for all the fields which + * are considered valid, regardless of whether they were explicitly + * specified or indirectly inferred from another entry. + * + * The gateway and the various server fields all point to a shared list of + * IP addresses. + * + * The hostname, home directory, and bootfile are all shared strings. + * + * The generic data field is a shared binary data structure. It is used to + * hold future RFC1048 vendor data until bootpd is updated to understand it. + * + * The vm_cookie field specifies the four-octet vendor magic cookie to use + * if it is desired to always send the same response to a given host. + * + * Hopefully, the rest is self-explanatory. + */ + +struct host { + unsigned linkcount; /* hash list inserts */ + struct flag flags; /* ALL valid fields */ + struct in_addr_list *cookie_server, + *domain_server, + *gateway, + *impress_server, + *log_server, + *lpr_server, + *name_server, + *rlp_server, + *time_server, + *nis_server, + *ntp_server; + struct shared_string *bootfile, + *hostname, + *domain_name, + *homedir, + *tftpdir, + *dump_file, + *exten_file, + *root_path, + *nis_domain, + *exec_file; + struct shared_bindata *generic; + byte vm_cookie[4], + htype, /* RFC826 says this should be 16-bits but + RFC951 only allocates 1 byte. . . */ + haddr[MAXHADDRLEN]; + int32 time_offset; + u_int32 bootsize, + msg_size, + min_wait; + struct in_addr bootserver, + iaddr, + swap_server, + reply_addr, + subnet_mask; + /* XXX - Add new tags here (or above as appropriate) */ +}; + + + +/* + * Variables shared among modules. + */ + +extern int debug; +extern char *bootptab; +extern char *progname; + +extern u_char vm_cmu[4]; +extern u_char vm_rfc1048[4]; + +extern hash_tbl *hwhashtable; +extern hash_tbl *iphashtable; +extern hash_tbl *nmhashtable; + diff --git a/libexec/bootpd/bootpgw/Makefile b/libexec/bootpd/bootpgw/Makefile new file mode 100644 index 0000000..b7adadf --- /dev/null +++ b/libexec/bootpd/bootpgw/Makefile @@ -0,0 +1,12 @@ +# Makefile +# $FreeBSD$ + +PROG= bootpgw +MAN= +SRCS= bootpgw.c getif.c hwaddr.c report.c rtmsg.c + +SRCDIR= ${.CURDIR}/.. +CFLAGS+=-I${SRCDIR} +.PATH: ${SRCDIR} + +.include <bsd.prog.mk> diff --git a/libexec/bootpd/bootpgw/bootpgw.c b/libexec/bootpd/bootpgw/bootpgw.c new file mode 100644 index 0000000..16bb66b --- /dev/null +++ b/libexec/bootpd/bootpgw/bootpgw.c @@ -0,0 +1,677 @@ +/* + * bootpgw.c - BOOTP GateWay + * This program forwards BOOTP Request packets to a BOOTP server. + */ + +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. +************************************************************************/ + +/* + * BOOTPGW is typically used to forward BOOTP client requests from + * one subnet to a BOOTP server on a different subnet. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#ifndef NO_UNISTD +#include <unistd.h> +#endif + +#include <err.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <netdb.h> +#include <paths.h> +#include <syslog.h> +#include <assert.h> + +#ifdef NO_SETSID +# include <fcntl.h> /* for O_RDONLY, etc */ +#endif + +#ifndef USE_BFUNCS +# include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +# define bcopy(a,b,c) memcpy(b,a,c) +# define bzero(p,l) memset(p,0,l) +# define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "bootp.h" +#include "getif.h" +#include "hwaddr.h" +#include "report.h" +#include "patchlevel.h" + +/* Local definitions: */ +#define MAX_MSG_SIZE (3*512) /* Maximum packet size */ +#define TRUE 1 +#define FALSE 0 +#define get_network_errmsg get_errmsg + + + +/* + * Externals, forward declarations, and global variables + */ + +static void usage(void); +static void handle_reply(void); +static void handle_request(void); + +/* + * IP port numbers for client and server obtained from /etc/services + */ + +u_short bootps_port, bootpc_port; + + +/* + * Internet socket and interface config structures + */ + +struct sockaddr_in bind_addr; /* Listening */ +struct sockaddr_in recv_addr; /* Packet source */ +struct sockaddr_in send_addr; /* destination */ + + +/* + * option defaults + */ +int debug = 0; /* Debugging flag (level) */ +struct timeval actualtimeout = +{ /* fifteen minutes */ + 15 * 60L, /* tv_sec */ + 0 /* tv_usec */ +}; +u_char maxhops = 4; /* Number of hops allowed for requests. */ +u_int minwait = 3; /* Number of seconds client must wait before + its bootrequest packets are forwarded. */ + +/* + * General + */ + +int s; /* Socket file descriptor */ +char *pktbuf; /* Receive packet buffer */ +int pktlen; +char *progname; +char *servername; +int32 server_ipa; /* Real server IP address, network order. */ + +struct in_addr my_ip_addr; + +struct utsname my_uname; +char *hostname; + + + + + +/* + * Initialization such as command-line processing is done and then the + * main server loop is started. + */ + +int +main(argc, argv) + int argc; + char **argv; +{ + struct timeval *timeout; + struct bootp *bp; + struct servent *servp; + struct hostent *hep; + char *stmp; + int n, ba_len, ra_len; + int nfound, readfds; + int standalone; + + progname = strrchr(argv[0], '/'); + if (progname) progname++; + else progname = argv[0]; + + /* + * Initialize logging. + */ + report_init(0); /* uses progname */ + + /* + * Log startup + */ + report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL); + + /* Debugging for compilers with struct padding. */ + assert(sizeof(struct bootp) == BP_MINPKTSZ); + + /* Get space for receiving packets and composing replies. */ + pktbuf = malloc(MAX_MSG_SIZE); + if (!pktbuf) { + report(LOG_ERR, "malloc failed"); + exit(1); + } + bp = (struct bootp *) pktbuf; + + /* + * Check to see if a socket was passed to us from inetd. + * + * Use getsockname() to determine if descriptor 0 is indeed a socket + * (and thus we are probably a child of inetd) or if it is instead + * something else and we are running standalone. + */ + s = 0; + ba_len = sizeof(bind_addr); + bzero((char *) &bind_addr, ba_len); + errno = 0; + standalone = TRUE; + if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) { + /* + * Descriptor 0 is a socket. Assume we are a child of inetd. + */ + if (bind_addr.sin_family == AF_INET) { + standalone = FALSE; + bootps_port = ntohs(bind_addr.sin_port); + } else { + /* Some other type of socket? */ + report(LOG_INFO, "getsockname: not an INET socket"); + } + } + /* + * Set defaults that might be changed by option switches. + */ + stmp = NULL; + timeout = &actualtimeout; + + if (uname(&my_uname) < 0) + errx(1, "can't get hostname"); + hostname = my_uname.nodename; + + hep = gethostbyname(hostname); + if (!hep) { + printf("Can not get my IP address\n"); + exit(1); + } + bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr)); + + /* + * Read switches. + */ + for (argc--, argv++; argc > 0; argc--, argv++) { + if (argv[0][0] != '-') + break; + switch (argv[0][1]) { + + case 'd': /* debug level */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else if (argv[1] && argv[1][0] == '-') { + /* + * Backwards-compatible behavior: + * no parameter, so just increment the debug flag. + */ + debug++; + break; + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + warnx("invalid debug level"); + break; + } + debug = n; + break; + + case 'h': /* hop count limit */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || + (n < 0) || (n > 16)) + { + warnx("invalid hop count limit"); + break; + } + maxhops = (u_char)n; + break; + + case 'i': /* inetd mode */ + standalone = FALSE; + break; + + case 's': /* standalone mode */ + standalone = TRUE; + break; + + case 't': /* timeout */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + warnx("invalid timeout specification"); + break; + } + actualtimeout.tv_sec = (int32) (60 * n); + /* + * If the actual timeout is zero, pass a NULL pointer + * to select so it blocks indefinitely, otherwise, + * point to the actual timeout value. + */ + timeout = (n > 0) ? &actualtimeout : NULL; + break; + + case 'w': /* wait time */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || + (n < 0) || (n > 60)) + { + warnx("invalid wait time"); + break; + } + minwait = (u_int)n; + break; + + default: + warnx("unknown switch: -%c", argv[0][1]); + usage(); + break; + + } /* switch */ + } /* for args */ + + /* Make sure server name argument is suplied. */ + servername = argv[0]; + if (!servername) { + warnx("missing server name"); + usage(); + } + /* + * Get address of real bootp server. + */ + if (isdigit(servername[0])) + server_ipa = inet_addr(servername); + else { + hep = gethostbyname(servername); + if (!hep) + errx(1, "can't get addr for %s", servername); + bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa)); + } + + if (standalone) { + /* + * Go into background and disassociate from controlling terminal. + * XXX - This is not the POSIX way (Should use setsid). -gwr + */ + if (debug < 3) { + if (fork()) + exit(0); +#ifdef NO_SETSID + setpgrp(0,0); +#ifdef TIOCNOTTY + n = open(_PATH_TTY, O_RDWR); + if (n >= 0) { + ioctl(n, TIOCNOTTY, (char *) 0); + (void) close(n); + } +#endif /* TIOCNOTTY */ +#else /* SETSID */ + if (setsid() < 0) + perror("setsid"); +#endif /* SETSID */ + } /* if debug < 3 */ + /* + * Nuke any timeout value + */ + timeout = NULL; + + /* + * Here, bootpd would do: + * chdir + * tzone_init + * rdtab_init + * readtab + */ + + /* + * Create a socket. + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + report(LOG_ERR, "socket: %s", get_network_errmsg()); + exit(1); + } + /* + * Get server's listening port number + */ + servp = getservbyname("bootps", "udp"); + if (servp) { + bootps_port = ntohs((u_short) servp->s_port); + } else { + bootps_port = (u_short) IPPORT_BOOTPS; + report(LOG_ERR, + "bootps/udp: unknown service -- using port %d", + bootps_port); + } + + /* + * Bind socket to BOOTPS port. + */ + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = htons(bootps_port); + bind_addr.sin_addr.s_addr = INADDR_ANY; + if (bind(s, (struct sockaddr *) &bind_addr, + sizeof(bind_addr)) < 0) + { + report(LOG_ERR, "bind: %s", get_network_errmsg()); + exit(1); + } + } /* if standalone */ + /* + * Get destination port number so we can reply to client + */ + servp = getservbyname("bootpc", "udp"); + if (servp) { + bootpc_port = ntohs(servp->s_port); + } else { + report(LOG_ERR, + "bootpc/udp: unknown service -- using port %d", + IPPORT_BOOTPC); + bootpc_port = (u_short) IPPORT_BOOTPC; + } + + /* no signal catchers */ + + /* + * Process incoming requests. + */ + for (;;) { + struct timeval tv; + + readfds = 1 << s; + if (timeout) + tv = *timeout; + + nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, + (timeout) ? &tv : NULL); + if (nfound < 0) { + if (errno != EINTR) { + report(LOG_ERR, "select: %s", get_errmsg()); + } + continue; + } + if (!(readfds & (1 << s))) { + report(LOG_INFO, "exiting after %ld minutes of inactivity", + (long)(actualtimeout.tv_sec / 60)); + exit(0); + } + ra_len = sizeof(recv_addr); + n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0, + (struct sockaddr *) &recv_addr, &ra_len); + if (n <= 0) { + continue; + } + if (debug > 3) { + report(LOG_INFO, "recvd pkt from IP addr %s", + inet_ntoa(recv_addr.sin_addr)); + } + if (n < sizeof(struct bootp)) { + if (debug) { + report(LOG_INFO, "received short packet"); + } + continue; + } + pktlen = n; + + switch (bp->bp_op) { + case BOOTREQUEST: + handle_request(); + break; + case BOOTREPLY: + handle_reply(); + break; + } + } + return 0; +} + + + + +/* + * Print "usage" message and exit + */ + +static void +usage() +{ + fprintf(stderr, + "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n"); + fprintf(stderr, "\t -d n\tset debug level\n"); + fprintf(stderr, "\t -h n\tset max hop count\n"); + fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n"); + fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n"); + fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n"); + fprintf(stderr, "\t -w n\tset min wait time (secs)\n"); + exit(1); +} + + + +/* + * Process BOOTREQUEST packet. + * + * Note, this just forwards the request to a real server. + */ +static void +handle_request() +{ + struct bootp *bp = (struct bootp *) pktbuf; + u_short secs; + u_char hops; + + /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */ + + if (debug) { + report(LOG_INFO, "request from %s", + inet_ntoa(recv_addr.sin_addr)); + } + /* Has the client been waiting long enough? */ + secs = ntohs(bp->bp_secs); + if (secs < minwait) + return; + + /* Has this packet hopped too many times? */ + hops = bp->bp_hops; + if (++hops > maxhops) { + report(LOG_NOTICE, "request from %s reached hop limit", + inet_ntoa(recv_addr.sin_addr)); + return; + } + bp->bp_hops = hops; + + /* + * Here one might discard a request from the same subnet as the + * real server, but we can assume that the real server will send + * a reply to the client before it waits for minwait seconds. + */ + + /* If gateway address is not set, put in local interface addr. */ + if (bp->bp_giaddr.s_addr == 0) { +#if 0 /* BUG */ + struct sockaddr_in *sip; + struct ifreq *ifr; + /* + * XXX - This picks the wrong interface when the receive addr + * is the broadcast address. There is no portable way to + * find out which interface a broadcast was received on. -gwr + * (Thanks to <walker@zk3.dec.com> for finding this bug!) + */ + ifr = getif(s, &recv_addr.sin_addr); + if (!ifr) { + report(LOG_NOTICE, "no interface for request from %s", + inet_ntoa(recv_addr.sin_addr)); + return; + } + sip = (struct sockaddr_in *) &(ifr->ifr_addr); + bp->bp_giaddr = sip->sin_addr; +#else /* BUG */ + /* + * XXX - Just set "giaddr" to our "official" IP address. + * RFC 1532 says giaddr MUST be set to the address of the + * interface on which the request was received. Setting + * it to our "default" IP address is not strictly correct, + * but is good enough to allow the real BOOTP server to + * get the reply back here. Then, before we forward the + * reply to the client, the giaddr field is corrected. + * (In case the client uses giaddr, which it should not.) + * See handle_reply() + */ + bp->bp_giaddr = my_ip_addr; +#endif /* BUG */ + + /* + * XXX - DHCP says to insert a subnet mask option into the + * options area of the request (if vendor magic == std). + */ + } + /* Set up socket address for send. */ + send_addr.sin_family = AF_INET; + send_addr.sin_port = htons(bootps_port); + send_addr.sin_addr.s_addr = server_ipa; + + /* Send reply with same size packet as request used. */ + if (sendto(s, pktbuf, pktlen, 0, + (struct sockaddr *) &send_addr, + sizeof(send_addr)) < 0) + { + report(LOG_ERR, "sendto: %s", get_network_errmsg()); + } +} + + + +/* + * Process BOOTREPLY packet. + */ +static void +handle_reply() +{ + struct bootp *bp = (struct bootp *) pktbuf; + struct ifreq *ifr; + struct sockaddr_in *sip; + unsigned char *ha; + int len, haf; + + if (debug) { + report(LOG_INFO, " reply for %s", + inet_ntoa(bp->bp_yiaddr)); + } + /* Make sure client is directly accessible. */ + ifr = getif(s, &(bp->bp_yiaddr)); + if (!ifr) { + report(LOG_NOTICE, "no interface for reply to %s", + inet_ntoa(bp->bp_yiaddr)); + return; + } +#if 1 /* Experimental (see BUG above) */ +/* #ifdef CATER_TO_OLD_CLIENTS ? */ + /* + * The giaddr field has been set to our "default" IP address + * which might not be on the same interface as the client. + * In case the client looks at giaddr, (which it should not) + * giaddr is now set to the address of the correct interface. + */ + sip = (struct sockaddr_in *) &(ifr->ifr_addr); + bp->bp_giaddr = sip->sin_addr; +#endif + + /* Set up socket address for send to client. */ + send_addr.sin_family = AF_INET; + send_addr.sin_addr = bp->bp_yiaddr; + send_addr.sin_port = htons(bootpc_port); + + /* Create an ARP cache entry for the client. */ + ha = bp->bp_chaddr; + len = bp->bp_hlen; + if (len > MAXHADDRLEN) + len = MAXHADDRLEN; + haf = (int) bp->bp_htype; + if (haf == 0) + haf = HTYPE_ETHERNET; + + if (debug > 1) + report(LOG_INFO, "setarp %s - %s", + inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len)); + setarp(s, &bp->bp_yiaddr, haf, ha, len); + + /* Send reply with same size packet as request used. */ + if (sendto(s, pktbuf, pktlen, 0, + (struct sockaddr *) &send_addr, + sizeof(send_addr)) < 0) + { + report(LOG_ERR, "sendto: %s", get_network_errmsg()); + } +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/bootptab.5 b/libexec/bootpd/bootptab.5 new file mode 100644 index 0000000..0ba2b69 --- /dev/null +++ b/libexec/bootpd/bootptab.5 @@ -0,0 +1,430 @@ +.\" Copyright (c) 1988, 1989, 1991 Carnegie Mellon University +.\" +.\" $FreeBSD$ +.\" +.Dd October 31, 1991 +.Dt BOOTPTAB 5 +.Os +.Sh NAME +.Nm bootptab +.Nd Internet Bootstrap Protocol server database +.Sh DESCRIPTION +The +.Nm +file is the configuration database file for +.Xr bootpd 8 , +the Internet Bootstrap Protocol server. +Its format is similar to that of +.Xr termcap 5 +in which two-character case-sensitive tag symbols are used to +represent host parameters. +These parameter declarations are separated by +colons (:), with a general format of: +.Pp +.Dl "hostname:tg=value. . . :tg=value. . . :tg=value. . . ." +.Pp +where +.Em hostname +is the actual name of a bootp client (or a "dummy entry"), and +.Em tg +is a two-character tag symbol. +Dummy entries have an invalid hostname +(one with a "." as the first character) and are used to provide +default values used by other entries via the +.Em tc=.dummy-entry +mechanism. +Most tags must be followed by an equals-sign +and a value as above. +Some may also appear in a boolean form with no +value (i.e.\& +.Em :tg: ) . +The currently recognized tags are: +.Pp +.Bl -tag -width xxx -compact +.It bf +Bootfile +.It bs +Bootfile size in 512-octet blocks +.It cs +Cookie server address list +.It df +Merit dump file +.It dn +Domain name +.It ds +Domain name server address list +.It ef +Extension file +.It gw +Gateway address list +.It ha +Host hardware address +.It hd +Bootfile home directory +.It hn +Send client's hostname to client +.It ht +Host hardware type (see Assigned Numbers RFC) +.It im +Impress server address list +.It ip +Host IP address +.It lg +Log server address list +.It lp +LPR server address list +.It ns +IEN-116 name server address list +.It nt +NTP (time) Server (RFC 1129) +.It ra +Reply address override +.It rl +Resource location protocol server address list +.It rp +Root path to mount as root +.It sa +TFTP server address client should use +.It sm +Host subnet mask +.It sw +Swap server address +.It tc +Table continuation (points to similar "template" host entry) +.It td +TFTP root directory used by "secure" TFTP servers +.It to +Time offset in seconds from UTC +.It ts +Time server address list +.It vm +Vendor magic cookie selector +.It yd +YP (NIS) domain name +.It ys +YP (NIS) server address +.El +.Pp +There is also a generic tag, +.Pf T Em n , +where +.Em n +is an RFC1084 vendor field tag number. +Thus it is possible to immediately +take advantage of future extensions to RFC1084 without being forced to modify +.Nm bootpd +first. +Generic data may be represented as either a stream of hexadecimal +numbers or as a quoted string of +.Tn ASCII +characters. +The length of the generic +data is automatically determined and inserted into the proper field(s) of the +RFC1084-style bootp reply. +.Pp +The following tags take a whitespace-separated list of IP addresses: +.Em cs , +.Em ds , +.Em gw , +.Em im , +.Em lg , +.Em lp , +.Em ns , +.Em nt , +.Em ra , +.Em rl , +and +.Em ts . +The +.Em ip , +.Em sa , +.Em sw , +.Em sm , +and +.Em ys +tags each take a single IP address. +All IP addresses are specified in standard Internet "dot" notation +and may use decimal, octal, or hexadecimal numbers +(octal numbers begin with 0, hexadecimal numbers begin with '0x' or '0X'). +Any IP addresses may alternatively be specified as a hostname, causing +.Nm bootpd +to lookup the IP address for that host name using +.Xr gethostbyname 3 . +If the +.Em ip +tag is not specified, +.Nm bootpd +will determine the IP address using the entry name as the host name. +(Dummy entries use an invalid host name to avoid automatic IP lookup.) +.Pp +The +.Em ht +tag specifies the hardware type code as either an unsigned decimal, octal, or +hexadecimal integer or one of the following symbolic names: +.Em ethernet +or +.Em ether +for 10Mb Ethernet, +.Em ethernet3 +or +.Em ether3 +for 3Mb experimental Ethernet, +.Em ieee802 , +.Em tr , +or +.Em token-ring +for IEEE 802 networks, +.Em pronet +for Proteon ProNET Token Ring, or +.Em chaos , +.Em arcnet , +or +.Em ax.25 +for Chaos, ARCNET, and AX.25 Amateur Radio networks, respectively. +The +.Em ha +tag takes a hardware address which may be specified as a host name +or in numeric form. +Note that the numeric form +.Em must +be specified in hexadecimal; optional periods and/or a leading '0x' may be +included for readability. +The +.Em ha +tag must be preceded by the +.Em ht +tag (either explicitly or implicitly; see +.Em tc +below). +If the hardware address is not specified and the type is specified +as either "ethernet" or "ieee802", then +.Nm bootpd +will try to determine the hardware address using +.Xr ether_hostton 3 . +.Pp +The hostname, home directory, and bootfile are +.Tn ASCII +strings which may be +optionally surrounded by double quotes ("). +The client's request and the +values of the +.Em hd +and +.Em bf +symbols determine how the server fills in the bootfile field of the bootp +reply packet. +.Pp +If the client provides a file name it is left as is. +Otherwise, if the +.Em bf +option is specified its value is copied into the reply packet. +If the +.Em hd +option is specified as well, its value is prepended to the +boot file copied into the reply packet. +The existence of the boot file is checked only if the +.Em bs Ns =auto +option is used (to determine the boot file size). +A reply may be sent whether or not the boot file exists. +.Pp +Some newer versions of +.Xr tftpd 8 +provide a security feature to change their root directory using +the +.Xr chroot 2 +system call. +The +.Em td +tag may be used to inform +.Nm bootpd +of this special root directory used by +.Nm tftpd . +(One may alternatively use the +.Nm bootpd +.Fl c Ar chdir +option.) +The +.Em hd +tag is actually relative to the root directory specified by the +.Em td +tag. +For example, if the real absolute path to your BOOTP client bootfile is +.Pa /tftpboot/bootfiles/bootimage , +and +.Nm tftpd +uses +.Pa /tftpboot +as its "secure" directory, then specify the following in +.Pa bootptab : +.Pp +.Dl :td=/tftpboot:hd=/bootfiles:bf=bootimage: +.Pp +If your bootfiles are located directly in +.Pa /tftpboot , +use: +.Pp +.Dl :td=/tftpboot:hd=/:bf=bootimage: +.Pp +The +.Em sa +tag may be used to specify the IP address of the particular TFTP server +you wish the client to use. +In the absence of this tag, +.Nm bootpd +will tell the client to perform TFTP to the same machine +.Nm bootpd +is running on. +.Pp +The time offset +.Em to +may be either a signed decimal integer specifying the client's +time zone offset in seconds from UTC, or the keyword +.Em auto +which uses the server's time zone offset. +Specifying the +.Em to +symbol as a boolean has the same effect as specifying +.Em auto +as its value. +.Pp +The bootfile size +.Em bs +may be either a decimal, octal, or hexadecimal integer specifying the size of +the bootfile in 512-octet blocks, or the keyword +.Em auto +which causes the server to automatically calculate the bootfile size at each +request. +As with the time offset, specifying the +.Em bs +symbol as a boolean has the same effect as specifying +.Em auto +as its value. +.Pp +The vendor magic cookie selector (the +.Em vm +tag) may take one of the following keywords: +.Em auto +(indicating that vendor information is determined by the client's request), +.Em rfc1048 +or +.Em rfc1084 +(which always forces an RFC1084-style reply), or +.Em cmu +(which always forces a CMU-style reply). +.Pp +The +.Em hn +tag is strictly a boolean tag; it does not take the usual equals-sign and +value. +Its presence indicates that the hostname should be sent to RFC1084 +clients. +.Nm Bootpd +attempts to send the entire hostname as it is specified in the configuration +file; if this will not fit into the reply packet, the name is shortened to +just the host field (up to the first period, if present) and then tried. +In no case is an arbitrarily-truncated hostname sent (if nothing reasonable +will fit, nothing is sent). +.Pp +Often, many host entries share common values for certain tags (such as name +servers, etc.). +Rather than repeatedly specifying these tags, a full +specification can be listed for one host entry and shared by others via the +.Em tc +(table continuation) mechanism. +Often, the template entry is a dummy host which does not actually exist and +never sends bootp requests. +This feature is similar to the +.Em tc +feature of +.Xr termcap 5 +for similar terminals. +Note that +.Nm bootpd +allows the +.Em tc +tag symbol to appear anywhere in the host entry, unlike +.Pa termcap +which requires it to be the last tag. +Information explicitly specified for a +host always overrides information implied by a +.Em tc +tag symbol, regardless of its location within the entry. +The +value of the +.Em tc +tag may be the hostname or IP address of any host entry +previously listed in the configuration file. +.Pp +Sometimes it is necessary to delete a specific tag after it has been inferred +via +.Em tc . +This can be done using the construction +.Em tag Ns @ +which removes the effect of +.Em tag +as in +.Xr termcap 5 . +For example, to completely undo an IEN-116 name server specification, use +.Em :ns@: +at an appropriate place in the configuration entry. +After removal +with +.Em @ , +a tag is eligible to be set again through the +.Em tc +mechanism. +.Pp +Blank lines and lines beginning with "#" are ignored in the configuration +file. +Host entries are separated from one another by newlines; a single host +entry may be extended over multiple lines if the lines end with a backslash +(\\). +It is also acceptable for lines to be longer than 80 characters. +Tags +may appear in any order, with the following exceptions: the hostname must be +the very first field in an entry, and the hardware type must precede the +hardware address. +.Pp +An example +.Pa /etc/bootptab +file follows: +.Bd -literal -offset indent +# Sample bootptab file (domain=andrew.cmu.edu) + +\&.default:\\ + :hd=/usr/boot:bf=null:\\ + :ds=netserver, lancaster:\\ + :ns=pcs2, pcs1:\\ + :ts=pcs2, pcs1:\\ + :sm=255.255.255.0:\\ + :gw=gw.cs.cmu.edu:\\ + :hn:to=-18000: + +carnegie:ht=6:ha=7FF8100000AF:tc=.default: +baldwin:ht=1:ha=0800200159C3:tc=.default: +wylie:ht=1:ha=00DD00CADF00:tc=.default: +arnold:ht=1:ha=0800200102AD:tc=.default: +bairdford:ht=1:ha=08002B02A2F9:tc=.default: +bakerstown:ht=1:ha=08002B0287C8:tc=.default: + +# Special domain name server and option tags for next host +butlerjct:ha=08002001560D:ds=128.2.13.42:\\ + :T37=0x12345927AD3BCF:\\ + :T99="Special ASCII string":\\ + :tc=.default: + +gastonville:ht=6:ha=7FFF81000A47:tc=.default: +hahntown:ht=6:ha=7FFF81000434:tc=.default: +hickman:ht=6:ha=7FFF810001BA:tc=.default: +lowber:ht=1:ha=00DD00CAF000:tc=.default: +mtoliver:ht=1:ha=00DD00FE1600:tc=.default: +.Ed +.Sh FILES +.Bl -tag -width /etc/bootptab -compact +.It Pa /etc/bootptab +.El +.Sh "SEE ALSO" +.Xr bootpd 8 , +.Xr tftpd 8 +.Pp +DARPA Internet Request For Comments RFC951, RFC1048, RFC1084, Assigned Numbers diff --git a/libexec/bootpd/bootptab.cmu b/libexec/bootpd/bootptab.cmu new file mode 100644 index 0000000..66212d4 --- /dev/null +++ b/libexec/bootpd/bootptab.cmu @@ -0,0 +1,124 @@ +# /etc/bootptab: database for bootp server (/etc/bootpd) +# (I've hacked on this but can't test it... -gwr) + +# Blank lines and lines beginning with '#' are ignored. +# +# Legend: (see bootptab.5) +# first field -- hostname (not indented) +# bf -- bootfile +# bs -- bootfile size in 512-octet blocks +# cs -- cookie servers +# df -- dump file name +# dn -- domain name +# ds -- domain name servers +# ef -- extension file +# gw -- gateways +# ha -- hardware address +# hd -- home directory for bootfiles +# hn -- host name set for client +# ht -- hardware type +# im -- impress servers +# ip -- host IP address +# lg -- log servers +# lp -- LPR servers +# ns -- IEN-116 name servers +# ra -- reply address +# rl -- resource location protocol servers +# rp -- root path +# sa -- boot server address +# sm -- subnet mask +# sw -- swap server +# tc -- template host (points to similar host entry) +# td -- TFTP directory +# to -- time offset (seconds) +# ts -- time servers +# vm -- vendor magic number +# Tn -- generic option tag n +# +# Be careful about including backslashes where they're needed. Weird (bad) +# things can happen when a backslash is omitted where one is intended. +# Also, note that generic option data must be either a string or a +# sequence of bytes where each byte is a two-digit hex value. + +# First, we define a global entry which specifies the stuff every host uses. +# (Host name lookups are relative to the domain: andrew.cmu.edu) +.default:\ + :hn:dn=cmu.edu:\ + :hd=/usr/boot:\ + :ds=netserver, lancaster:\ + :ns=pcs2, pcs1:\ + :ts=pcs2, pcs1:\ + :sm=255.255.0.0:\ + :gw=gw.cs.cmu.edu:\ + to=auto: + + +# Next, we can define different master entries for each subnet. . . +.subnet13 :sm=255.255.255.0:gw=128.2.13.1 :tc=.default: +.subnet19 :sm=255.255.255.0:gw=128.2.19.1 :tc=.default: +.subnet232 :sm=255.255.255.0:gw=128.2.232.1 :tc=.default: + +# +# We should be able to use as many levels of indirection as desired. Use +# your imagination. . . +# + + +# Individual entries (could also have different servers for some/all of these +# hosts, but we don't really use this feature at CMU): + +carnegie:tc=.subnet13:ht=ieee802:ha=7FF8100000AF: +baldwin:tc=.subnet19:ha=0800200159C3: +wylie:tc=.subnet232:ha=00DD00CADF00: +arnold:tc=.subnet19:ha=0800200102AD: +bairdford:tc=.subnet19:ha=08002B02A2F9: +bakerstown:tc=.subnet19:ha=08002B0287C8: +butlerjct:tc=.subnet232:ha=08002001560D: +gastonville:tc=.subnet232:ht=ieee802:ha=7FFF81000A47: +hahntown:tc=.subnet13:ht=ieee802:ha=7FFF81000434: +hickman:tc=.subnet19:ht=ieee802:ha=7FFF810001BA: +lowber:tc=.subnet13:ha=00DD00CAF000: +mtoliver:tc=.subnet19:ha=00DD00FE1600: +osborne:tc=.subnet232:ha=00DD00CAD600: +russelton:tc=.subnet232:ha=080020017FC3: +thornburg:tc=.subnet13:ha=080020012A33: + + +# Hmmm. . . Let's throw in some whitespace for readability. . . . + +andrew: tc=.subnet19:ha=00DD00C88900: +birdville: tc=.subnet19:ha=00DD00FE2D00: +coudersport: tc=.subnet13:ha=00DD00CB1E00: +bridgeville: tc=.subnet232:ha=080020011394: +franklin: tc=.subnet19:ha=08002B02A5D5: +hollidaysburg: tc=.subnet19:ht=ieee802:ha=7FFF810002C8: +honesdale: tc=.subnet19:ha=08002B02F83F: +huntingdon: tc=.subnet19:ha=08002B02E410: +indiana: tc=.subnet13:ha=08002B029BEC: +jimthorpe: tc=.subnet232:ha=08002B02FBBA: +kittanning: tc=.subnet232:ha=08002B0273FC: +lebanon: tc=.subnet232:ha=08002B037F67: +lewisburg: tc=.subnet19:ha=50005A1A0DE4: +middleburg: tc=.subnet232:ha=00DD00FE1200: +aspinwall: tc=.subnet13:ha=08002B03C163: +berlin: tc=.subnet13:ha=00DD000A4400: +norristown: tc=.subnet13:ha=08002001455B: +pottsville: tc=.subnet13:ha=00DD000A3700: +ridgway: tc=.subnet19:ha=08002B029425: +scranton: tc=.subnet232:ha=0800200113A1: +chalfont: tc=.subnet13:ha=08002001124B: +washington: tc=.subnet19:ha=00DD00656E00: +wellsboro: tc=.subnet13:ha=00DD00CB1C00: +bb1: tc=.subnet19:ha=00DD000A1F00: +adamstown: tc=.subnet13:ha=08002B02D0E6: +beta: tc=.subnet19:ha=02070100B197: +carbondale: tc=.subnet232:ha=08002B022A73: +clairton: tc=.subnet19:ha=080020010FD1: +egypt: tc=.subnet13:ha=00DD00847B00: +fairchance: tc=.subnet232:ha=00DD000AB100: +fairhope: tc=.subnet232:ha=00DD00CB0800: +galeton: tc=.subnet232:ha=08002001138C: +imperial: tc=.subnet232:ha=08002001130C: +kingston: tc=.subnet232:ha=080020011382: +knox: tc=.subnet232:ha=50005A1A0D2A: +lakecity: tc=.subnet13:ha=080020011380: diff --git a/libexec/bootpd/bootptab.mcs b/libexec/bootpd/bootptab.mcs new file mode 100644 index 0000000..9bce37e --- /dev/null +++ b/libexec/bootpd/bootptab.mcs @@ -0,0 +1,91 @@ +# /etc/bootptab: database for bootp server (/etc/bootpd) +# Last update: gwr, Sun Dec 12 19:00:00 EDT 1993 +# Blank lines and lines beginning with '#' are ignored. +# +# $FreeBSD$ +# +# Legend: (see bootptab.5) +# first field -- hostname (not indented) +# bf -- bootfile +# bs -- bootfile size in 512-octet blocks +# cs -- cookie servers +# df -- dump file name +# dn -- domain name +# ds -- domain name servers +# ef -- extension file +# gw -- gateways +# ha -- hardware address +# hd -- home directory for bootfiles +# hn -- host name set for client +# ht -- hardware type +# im -- impress servers +# ip -- host IP address +# lg -- log servers +# lp -- LPR servers +# ns -- IEN-116 name servers +# ra -- reply address +# rl -- resource location protocol servers +# rp -- root path +# sa -- boot server address +# sm -- subnet mask +# sw -- swap server +# tc -- template host (points to similar host entry) +# td -- TFTP directory +# to -- time offset (seconds) +# ts -- time servers +# vm -- vendor magic number +# Tn -- generic option tag n +# +# Be careful about including backslashes where they're needed. Weird (bad) +# things can happen when a backslash is omitted where one is intended. +# Also, note that generic option data must be either a string or a +# sequence of bytes where each byte is a two-digit hex value. + +# First, we define a global entry which specifies the stuff every host uses. + +# If you leave "td" empty, run bootpd with the "-c /tftpboot" switch +# so path names (boot files) will be interpreted relative to the same +# directory as tftpd will use when opening files. +.default:\ + :hn:dn="mc.com":\ + :td=/tftpboot:\ + :ds=merlin, jericho:\ + :to=auto: + +# Next, we can define different master entries for each subnet. . . + +.subnet16:\ + :tc=.default:\ + :sm=255.255.255.0:\ + :gw=merlin:\ + :sa=merlin: + +.subnet17:\ + :tc=.default:\ + :sm=255.255.255.0:\ + :gw=merlin-gw:\ + :sa=merlin-gw: + +# +# We should be able to use as many levels of indirection as desired. Use +# your imagination. . . +# + +# Individual entries (could also have different servers for some/all of these +# hosts, but we don't really use this feature at CMU): + +# Emulex terminal server +emulex: tc=.subnet16:ha=00.00.C9.00.42.E0:bf=P4KTL0E: + +# Lantronix eps1 +eps1: tc=.subnet16:ha=00.80.A3.04.1D.78: + +# Tadpole 885 board. +tp885: tc=.subnet17:ha=08.00.4C.00.2F.74:bf=tp885sys2.cfe: + +# MVME147 VxWorks board. +#mvme147:tc=.subnet17:ha=08.00.3e.20.da.47:bf=mv147vxw.st: + +# These are just for testing +bach: tc=.subnet16:ha="08:00:20:04:98:8d":bf=boot.sun4m: +xanadu:tc=.subnet17:ha="00:80:42:42:04:c7":bf=boot.sun4c: diff --git a/libexec/bootpd/bptypes.h b/libexec/bootpd/bptypes.h new file mode 100644 index 0000000..e27cc83 --- /dev/null +++ b/libexec/bootpd/bptypes.h @@ -0,0 +1,23 @@ +/* $FreeBSD$ + */ + +#ifndef BPTYPES_H +#define BPTYPES_H + +#include <sys/types.h> + +/* + * 32 bit integers are different types on various architectures + */ + +#define int32 int32_t +#define u_int32 u_int32_t + +/* + * Nice typedefs. . . + */ + +typedef int boolean; +typedef unsigned char byte; + +#endif /* BPTYPES_H */ diff --git a/libexec/bootpd/dovend.c b/libexec/bootpd/dovend.c new file mode 100644 index 0000000..3b2bd97 --- /dev/null +++ b/libexec/bootpd/dovend.c @@ -0,0 +1,408 @@ +/* + * dovend.c : Inserts all but the first few vendor options. + * + * $FreeBSD$ + */ + +#include <sys/types.h> + +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> + +#ifndef USE_BFUNCS +# include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +# define bcopy(a,b,c) memcpy(b,a,c) +# define bzero(p,l) memset(p,0,l) +# define bcmp(a,b,c) memcmp(a,b,c) +# define index strchr +#endif + +#include "bootp.h" +#include "bootpd.h" +#include "report.h" +#include "dovend.h" + +PRIVATE int insert_generic(struct shared_bindata *, byte **, int *); + +/* + * Insert the 2nd part of the options into an option buffer. + * Return amount of space used. + * + * This inserts everything EXCEPT: + * magic cookie, subnet mask, gateway, bootsize, extension file + * Those are handled separately (in bootpd.c) to allow this function + * to be shared between bootpd and bootpef. + * + * When an "extension file" is in use, the options inserted by + * this function go into the exten_file, not the bootp response. + */ + +int +dovend_rfc1497(hp, buf, len) + struct host *hp; + byte *buf; + int len; +{ + int bytesleft = len; + byte *vp = buf; + + static const char noroom[] = "%s: No room for \"%s\" option"; +#define NEED(LEN, MSG) do \ + if (bytesleft < (LEN)) { \ + report(LOG_NOTICE, noroom, \ + hp->hostname->string, MSG); \ + return (vp - buf); \ + } while (0) + + /* + * Note that the following have already been inserted: + * magic_cookie, subnet_mask, gateway, bootsize + * + * The remaining options are inserted in order of importance. + * (Of course the importance of each is a matter of opinion.) + * The option insertion order should probably be configurable. + * + * This is the order used in the NetBSD version. Can anyone + * explain why the time_offset and swap_server are first? + * Also, why is the hostname so far down the list? -gwr + */ + + if (hp->flags.time_offset) { + NEED(6, "to"); + *vp++ = TAG_TIME_OFFSET;/* -1 byte */ + *vp++ = 4; /* -1 byte */ + insert_u_long(htonl(hp->time_offset), &vp); /* -4 bytes */ + bytesleft -= 6; + } + /* + * swap server, root path, dump path + */ + if (hp->flags.swap_server) { + NEED(6, "sw"); + /* There is just one SWAP_SERVER, so it is not an iplist. */ + *vp++ = TAG_SWAP_SERVER;/* -1 byte */ + *vp++ = 4; /* -1 byte */ + insert_u_long(hp->swap_server.s_addr, &vp); /* -4 bytes */ + bytesleft -= 6; /* Fix real count */ + } + if (hp->flags.root_path) { + /* + * Check for room for root_path. Add 2 to account for + * TAG_ROOT_PATH and length. + */ + len = strlen(hp->root_path->string); + NEED((len + 2), "rp"); + *vp++ = TAG_ROOT_PATH; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->root_path->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + if (hp->flags.dump_file) { + /* + * Check for room for dump_file. Add 2 to account for + * TAG_DUMP_FILE and length. + */ + len = strlen(hp->dump_file->string); + NEED((len + 2), "df"); + *vp++ = TAG_DUMP_FILE; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->dump_file->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + /* + * DNS server and domain + */ + if (hp->flags.domain_server) { + if (insert_ip(TAG_DOMAIN_SERVER, + hp->domain_server, + &vp, &bytesleft)) + NEED(8, "ds"); + } + if (hp->flags.domain_name) { + /* + * Check for room for domain_name. Add 2 to account for + * TAG_DOMAIN_NAME and length. + */ + len = strlen(hp->domain_name->string); + NEED((len + 2), "dn"); + *vp++ = TAG_DOMAIN_NAME; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->domain_name->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + /* + * NIS (YP) server and domain + */ + if (hp->flags.nis_server) { + if (insert_ip(TAG_NIS_SERVER, + hp->nis_server, + &vp, &bytesleft)) + NEED(8, "ds"); + } + if (hp->flags.nis_domain) { + /* + * Check for room for nis_domain. Add 2 to account for + * TAG_NIS_DOMAIN and length. + */ + len = strlen(hp->nis_domain->string); + NEED((len + 2), "dn"); + *vp++ = TAG_NIS_DOMAIN; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->nis_domain->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + /* IEN 116 name server */ + if (hp->flags.name_server) { + if (insert_ip(TAG_NAME_SERVER, + hp->name_server, + &vp, &bytesleft)) + NEED(8, "ns"); + } + if (hp->flags.rlp_server) { + if (insert_ip(TAG_RLP_SERVER, + hp->rlp_server, + &vp, &bytesleft)) + NEED(8, "rl"); + } + /* Time server (RFC 868) */ + if (hp->flags.time_server) { + if (insert_ip(TAG_TIME_SERVER, + hp->time_server, + &vp, &bytesleft)) + NEED(8, "ts"); + } + /* NTP (time) Server (RFC 1129) */ + if (hp->flags.ntp_server) { + if (insert_ip(TAG_NTP_SERVER, + hp->ntp_server, + &vp, &bytesleft)) + NEED(8, "ts"); + } + /* + * I wonder: If the hostname were "promoted" into the BOOTP + * response part, might these "extension" files possibly be + * shared between several clients? + * + * Also, why not just use longer BOOTP packets with all the + * additional length used as option data. This bootpd version + * already supports that feature by replying with the same + * packet length as the client request packet. -gwr + */ + if (hp->flags.name_switch && hp->flags.send_name) { + /* + * Check for room for hostname. Add 2 to account for + * TAG_HOST_NAME and length. + */ + len = strlen(hp->hostname->string); +#if 0 + /* + * XXX - Too much magic. The user can always set the hostname + * to the short version in the bootptab file. -gwr + */ + if ((len + 2) > bytesleft) { + /* + * Not enough room for full (domain-qualified) hostname, try + * stripping it down to just the first field (host). + */ + char *tmpstr = hp->hostname->string; + len = 0; + while (*tmpstr && (*tmpstr != '.')) { + tmpstr++; + len++; + } + } +#endif + NEED((len + 2), "hn"); + *vp++ = TAG_HOST_NAME; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->hostname->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + /* + * The rest of these are less important, so they go last. + */ + if (hp->flags.lpr_server) { + if (insert_ip(TAG_LPR_SERVER, + hp->lpr_server, + &vp, &bytesleft)) + NEED(8, "lp"); + } + if (hp->flags.cookie_server) { + if (insert_ip(TAG_COOKIE_SERVER, + hp->cookie_server, + &vp, &bytesleft)) + NEED(8, "cs"); + } + if (hp->flags.log_server) { + if (insert_ip(TAG_LOG_SERVER, + hp->log_server, + &vp, &bytesleft)) + NEED(8, "lg"); + } + /* + * XXX - Add new tags here (to insert options) + */ + if (hp->flags.generic) { + if (insert_generic(hp->generic, &vp, &bytesleft)) + NEED(64, "(generic)"); + } + /* + * The end marker is inserted by the caller. + */ + return (vp - buf); +#undef NEED +} /* dovend_rfc1497 */ + + + +/* + * Insert a tag value, a length value, and a list of IP addresses into the + * memory buffer indirectly pointed to by "dest". "tag" is the RFC1048 tag + * number to use, "iplist" is a pointer to a list of IP addresses + * (struct in_addr_list), and "bytesleft" points to an integer which + * indicates the size of the "dest" buffer. + * + * Return zero if everything fits. + * + * This is used to fill the vendor-specific area of a bootp packet in + * conformance to RFC1048. + */ + +int +insert_ip(tag, iplist, dest, bytesleft) + byte tag; + struct in_addr_list *iplist; + byte **dest; + int *bytesleft; +{ + struct in_addr *addrptr; + unsigned addrcount = 1; + byte *d; + + if (iplist == NULL) + return (0); + + if (*bytesleft >= 6) { + d = *dest; /* Save pointer for later */ + **dest = tag; + (*dest) += 2; + (*bytesleft) -= 2; /* Account for tag and length */ + addrptr = iplist->addr; + addrcount = iplist->addrcount; + while ((*bytesleft >= 4) && (addrcount > 0)) { + insert_u_long(addrptr->s_addr, dest); + addrptr++; + addrcount--; + (*bytesleft) -= 4; /* Four bytes per address */ + } + d[1] = (byte) ((*dest - d - 2) & 0xFF); + } + return (addrcount); +} + + + +/* + * Insert generic data into a bootp packet. The data is assumed to already + * be in RFC1048 format. It is inserted using a first-fit algorithm which + * attempts to insert as many tags as possible. Tags and data which are + * too large to fit are skipped; any remaining tags are tried until they + * have all been exhausted. + * Return zero if everything fits. + */ + +static int +insert_generic(gendata, buff, bytesleft) + struct shared_bindata *gendata; + byte **buff; + int *bytesleft; +{ + byte *srcptr; + int length, numbytes; + int skipped = 0; + + if (gendata == NULL) + return (0); + + srcptr = gendata->data; + length = gendata->length; + while ((length > 0) && (*bytesleft > 0)) { + switch (*srcptr) { + case TAG_END: + length = 0; /* Force an exit on next iteration */ + break; + case TAG_PAD: + *(*buff)++ = *srcptr++; + (*bytesleft)--; + length--; + break; + default: + numbytes = srcptr[1] + 2; + if (*bytesleft < numbytes) + skipped += numbytes; + else { + bcopy(srcptr, *buff, numbytes); + (*buff) += numbytes; + (*bytesleft) -= numbytes; + } + srcptr += numbytes; + length -= numbytes; + break; + } + } /* while */ + return (skipped); +} + +/* + * Insert the unsigned long "value" into memory starting at the byte + * pointed to by the byte pointer (*dest). (*dest) is updated to + * point to the next available byte. + * + * Since it is desirable to internally store network addresses in network + * byte order (in struct in_addr's), this routine expects longs to be + * passed in network byte order. + * + * However, due to the nature of the main algorithm, the long must be in + * host byte order, thus necessitating the use of ntohl() first. + */ + +void +insert_u_long(value, dest) + u_int32 value; + byte **dest; +{ + byte *temp; + int n; + + value = ntohl(value); /* Must use host byte order here */ + temp = (*dest += 4); + for (n = 4; n > 0; n--) { + *--temp = (byte) (value & 0xFF); + value >>= 8; + } + /* Final result is network byte order */ +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/dovend.h b/libexec/bootpd/dovend.h new file mode 100644 index 0000000..ef3b62d --- /dev/null +++ b/libexec/bootpd/dovend.h @@ -0,0 +1,6 @@ +/* dovend.h */ +/* $FreeBSD$ */ + +extern int dovend_rfc1497(struct host *hp, u_char *buf, int len); +extern int insert_ip(int, struct in_addr_list *, u_char **, int *); +extern void insert_u_long(u_int32, u_char **); diff --git a/libexec/bootpd/dumptab.c b/libexec/bootpd/dumptab.c new file mode 100644 index 0000000..43e94ec --- /dev/null +++ b/libexec/bootpd/dumptab.c @@ -0,0 +1,378 @@ +/* + * dumptab.c - handles dumping the database + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <time.h> + +#ifndef USE_BFUNCS +#include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "bootp.h" +#include "hash.h" +#include "hwaddr.h" +#include "report.h" +#include "patchlevel.h" +#include "bootpd.h" + +#ifdef DEBUG +static void dump_generic(FILE *, struct shared_bindata *); +static void dump_host(FILE *, struct host *); +static void list_ipaddresses(FILE *, struct in_addr_list *); +#endif + +#ifndef DEBUG +void +dumptab(filename) + char *filename; +{ + report(LOG_INFO, "No dumptab support!"); +} + +#else /* DEBUG */ + +/* + * Dump the internal memory database to bootpd_dump. + */ + +void +dumptab(filename) + char *filename; +{ + int n; + struct host *hp; + FILE *fp; + time_t t; + /* Print symbols in alphabetical order for reader's convenience. */ + static char legend[] = "#\n# Legend:\t(see bootptab.5)\n\ +#\tfirst field -- hostname (not indented)\n\ +#\tbf -- bootfile\n\ +#\tbs -- bootfile size in 512-octet blocks\n\ +#\tcs -- cookie servers\n\ +#\tdf -- dump file name\n\ +#\tdn -- domain name\n\ +#\tds -- domain name servers\n\ +#\tef -- extension file\n\ +#\tex -- exec file (YORK_EX_OPTION)\n\ +#\tgw -- gateways\n\ +#\tha -- hardware address\n\ +#\thd -- home directory for bootfiles\n\ +#\thn -- host name set for client\n\ +#\tht -- hardware type\n\ +#\tim -- impress servers\n\ +#\tip -- host IP address\n\ +#\tlg -- log servers\n\ +#\tlp -- LPR servers\n\ +#\tms -- message size\n\ +#\tmw -- min wait (secs)\n\ +#\tns -- IEN-116 name servers\n\ +#\tnt -- NTP servers (RFC 1129)\n\ +#\tra -- reply address override\n\ +#\trl -- resource location protocol servers\n\ +#\trp -- root path\n\ +#\tsa -- boot server address\n\ +#\tsm -- subnet mask\n\ +#\tsw -- swap server\n\ +#\ttc -- template host (points to similar host entry)\n\ +#\ttd -- TFTP directory\n\ +#\tto -- time offset (seconds)\n\ +#\tts -- time servers\n\ +#\tvm -- vendor magic number\n\ +#\tyd -- YP (NIS) domain\n\ +#\tys -- YP (NIS) servers\n\ +#\tTn -- generic option tag n\n\ +\n"; + + /* + * Open bootpd.dump file. + */ + if ((fp = fopen(filename, "w")) == NULL) { + report(LOG_ERR, "error opening \"%s\": %s", + filename, get_errmsg()); + exit(1); + } + t = time(NULL); + fprintf(fp, "\n# %s %s.%d\n", progname, VERSION, PATCHLEVEL); + fprintf(fp, "# %s: dump of bootp server database.\n", filename); + fprintf(fp, "# Dump taken %s", ctime(&t)); + fwrite(legend, 1, sizeof(legend) - 1, fp); + + n = 0; + for (hp = (struct host *) hash_FirstEntry(nmhashtable); hp != NULL; + hp = (struct host *) hash_NextEntry(nmhashtable)) { + dump_host(fp, hp); + fprintf(fp, "\n"); + n++; + } + fclose(fp); + + report(LOG_INFO, "dumped %d entries to \"%s\".", n, filename); +} + + + +/* + * Dump all the available information on the host pointed to by "hp". + * The output is sent to the file pointed to by "fp". + */ + +static void +dump_host(fp, hp) + FILE *fp; + struct host *hp; +{ + /* Print symbols in alphabetical order for reader's convenience. */ + if (hp) { + fprintf(fp, "%s:", (hp->hostname ? + hp->hostname->string : "?")); + if (hp->flags.bootfile) { + fprintf(fp, "\\\n\t:bf=%s:", hp->bootfile->string); + } + if (hp->flags.bootsize) { + fprintf(fp, "\\\n\t:bs="); + if (hp->flags.bootsize_auto) { + fprintf(fp, "auto:"); + } else { + fprintf(fp, "%lu:", (u_long)hp->bootsize); + } + } + if (hp->flags.cookie_server) { + fprintf(fp, "\\\n\t:cs="); + list_ipaddresses(fp, hp->cookie_server); + fprintf(fp, ":"); + } + if (hp->flags.dump_file) { + fprintf(fp, "\\\n\t:df=%s:", hp->dump_file->string); + } + if (hp->flags.domain_name) { + fprintf(fp, "\\\n\t:dn=%s:", hp->domain_name->string); + } + if (hp->flags.domain_server) { + fprintf(fp, "\\\n\t:ds="); + list_ipaddresses(fp, hp->domain_server); + fprintf(fp, ":"); + } + if (hp->flags.exten_file) { + fprintf(fp, "\\\n\t:ef=%s:", hp->exten_file->string); + } + if (hp->flags.exec_file) { + fprintf(fp, "\\\n\t:ex=%s:", hp->exec_file->string); + } + if (hp->flags.gateway) { + fprintf(fp, "\\\n\t:gw="); + list_ipaddresses(fp, hp->gateway); + fprintf(fp, ":"); + } + /* FdC: swap_server (see below) */ + if (hp->flags.homedir) { + fprintf(fp, "\\\n\t:hd=%s:", hp->homedir->string); + } + /* FdC: dump_file (see above) */ + /* FdC: domain_name (see above) */ + /* FdC: root_path (see below) */ + if (hp->flags.name_switch && hp->flags.send_name) { + fprintf(fp, "\\\n\t:hn:"); + } + if (hp->flags.htype) { + int hlen = haddrlength(hp->htype); + fprintf(fp, "\\\n\t:ht=%u:", (unsigned) hp->htype); + if (hp->flags.haddr) { + fprintf(fp, "ha=\"%s\":", + haddrtoa(hp->haddr, hlen)); + } + } + if (hp->flags.impress_server) { + fprintf(fp, "\\\n\t:im="); + list_ipaddresses(fp, hp->impress_server); + fprintf(fp, ":"); + } + /* NetBSD: swap_server (see below) */ + if (hp->flags.iaddr) { + fprintf(fp, "\\\n\t:ip=%s:", inet_ntoa(hp->iaddr)); + } + if (hp->flags.log_server) { + fprintf(fp, "\\\n\t:lg="); + list_ipaddresses(fp, hp->log_server); + fprintf(fp, ":"); + } + if (hp->flags.lpr_server) { + fprintf(fp, "\\\n\t:lp="); + list_ipaddresses(fp, hp->lpr_server); + fprintf(fp, ":"); + } + if (hp->flags.msg_size) { + fprintf(fp, "\\\n\t:ms=%lu:", (u_long)hp->msg_size); + } + if (hp->flags.min_wait) { + fprintf(fp, "\\\n\t:mw=%lu:", (u_long)hp->min_wait); + } + if (hp->flags.name_server) { + fprintf(fp, "\\\n\t:ns="); + list_ipaddresses(fp, hp->name_server); + fprintf(fp, ":"); + } + if (hp->flags.ntp_server) { + fprintf(fp, "\\\n\t:nt="); + list_ipaddresses(fp, hp->ntp_server); + fprintf(fp, ":"); + } + if (hp->flags.reply_addr) { + fprintf(fp, "\\\n\t:ra=%s:", inet_ntoa(hp->reply_addr)); + } + if (hp->flags.rlp_server) { + fprintf(fp, "\\\n\t:rl="); + list_ipaddresses(fp, hp->rlp_server); + fprintf(fp, ":"); + } + if (hp->flags.root_path) { + fprintf(fp, "\\\n\t:rp=%s:", hp->root_path->string); + } + if (hp->flags.bootserver) { + fprintf(fp, "\\\n\t:sa=%s:", inet_ntoa(hp->bootserver)); + } + if (hp->flags.subnet_mask) { + fprintf(fp, "\\\n\t:sm=%s:", inet_ntoa(hp->subnet_mask)); + } + if (hp->flags.swap_server) { + fprintf(fp, "\\\n\t:sw=%s:", inet_ntoa(hp->subnet_mask)); + } + if (hp->flags.tftpdir) { + fprintf(fp, "\\\n\t:td=%s:", hp->tftpdir->string); + } + /* NetBSD: rootpath (see above) */ + /* NetBSD: domainname (see above) */ + /* NetBSD: dumpfile (see above) */ + if (hp->flags.time_offset) { + fprintf(fp, "\\\n\t:to=%ld:", (long)hp->time_offset); + } + if (hp->flags.time_server) { + fprintf(fp, "\\\n\t:ts="); + list_ipaddresses(fp, hp->time_server); + fprintf(fp, ":"); + } + if (hp->flags.vm_cookie) { + fprintf(fp, "\\\n\t:vm="); + if (!bcmp(hp->vm_cookie, vm_rfc1048, 4)) { + fprintf(fp, "rfc1048:"); + } else if (!bcmp(hp->vm_cookie, vm_cmu, 4)) { + fprintf(fp, "cmu:"); + } else { + fprintf(fp, "%d.%d.%d.%d:", + (int) ((hp->vm_cookie)[0]), + (int) ((hp->vm_cookie)[1]), + (int) ((hp->vm_cookie)[2]), + (int) ((hp->vm_cookie)[3])); + } + } + if (hp->flags.nis_domain) { + fprintf(fp, "\\\n\t:yd=%s:", + hp->nis_domain->string); + } + if (hp->flags.nis_server) { + fprintf(fp, "\\\n\t:ys="); + list_ipaddresses(fp, hp->nis_server); + fprintf(fp, ":"); + } + /* + * XXX - Add new tags here (or above, + * so they print in alphabetical order). + */ + + if (hp->flags.generic) { + dump_generic(fp, hp->generic); + } + } +} + + +static void +dump_generic(fp, generic) + FILE *fp; + struct shared_bindata *generic; +{ + u_char *bp = generic->data; + u_char *ep = bp + generic->length; + u_char tag; + int len; + + while (bp < ep) { + tag = *bp++; + if (tag == TAG_PAD) + continue; + if (tag == TAG_END) + return; + len = *bp++; + if (bp + len > ep) { + fprintf(fp, " #junk in generic! :"); + return; + } + fprintf(fp, "\\\n\t:T%d=", tag); + while (len) { + fprintf(fp, "%02X", *bp); + bp++; + len--; + if (len) + fprintf(fp, "."); + } + fprintf(fp, ":"); + } +} + + + +/* + * Dump an entire struct in_addr_list of IP addresses to the indicated file. + * + * The addresses are printed in standard ASCII "dot" notation and separated + * from one another by a single space. A single leading space is also + * printed before the first adddress. + * + * Null lists produce no output (and no error). + */ + +static void +list_ipaddresses(fp, ipptr) + FILE *fp; + struct in_addr_list *ipptr; +{ + unsigned count; + struct in_addr *addrptr; + + if (ipptr) { + count = ipptr->addrcount; + addrptr = ipptr->addr; + while (count > 0) { + fprintf(fp, "%s", inet_ntoa(*addrptr++)); + count--; + if (count) + fprintf(fp, ", "); + } + } +} + +#endif /* DEBUG */ + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/getether.c b/libexec/bootpd/getether.c new file mode 100644 index 0000000..e4dc1b6 --- /dev/null +++ b/libexec/bootpd/getether.c @@ -0,0 +1,389 @@ +/* + * getether.c : get the ethernet address of an interface + * + * All of this code is quite system-specific. As you may well + * guess, it took a good bit of detective work to figure out! + * + * If you figure out how to do this on another system, + * please let me know. <gwr@mc.com> + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#ifndef NO_UNISTD +#include <unistd.h> +#endif + +#include <ctype.h> +#include <paths.h> +#include <string.h> +#include <syslog.h> + +#include "getether.h" +#include "report.h" +#define EALEN 6 + +#if defined(ultrix) || (defined(__osf__) && defined(__alpha)) +/* + * This is really easy on Ultrix! Thanks to + * Harald Lundberg <hl@tekla.fi> for this code. + * + * The code here is not specific to the Alpha, but that was the + * only symbol we could find to identify DEC's version of OSF. + * (Perhaps we should just define DEC in the Makefile... -gwr) + */ + +#include <sys/ioctl.h> +#include <net/if.h> /* struct ifdevea */ + +getether(ifname, eap) + char *ifname, *eap; +{ + int rc = -1; + int fd; + struct ifdevea phys; + bzero(&phys, sizeof(phys)); + strcpy(phys.ifr_name, ifname); + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + report(LOG_ERR, "getether: socket(INET,DGRAM) failed"); + return -1; + } + if (ioctl(fd, SIOCRPHYSADDR, &phys) < 0) { + report(LOG_ERR, "getether: ioctl SIOCRPHYSADDR failed"); + } else { + bcopy(&phys.current_pa[0], eap, EALEN); + rc = 0; + } + close(fd); + return rc; +} + +#define GETETHER +#endif /* ultrix|osf1 */ + + +#ifdef SUNOS + +#include <sys/sockio.h> +#include <sys/time.h> /* needed by net_if.h */ +#include <net/nit_if.h> /* for NIOCBIND */ +#include <net/if.h> /* for struct ifreq */ + +getether(ifname, eap) + char *ifname; /* interface name from ifconfig structure */ + char *eap; /* Ether address (output) */ +{ + int rc = -1; + + struct ifreq ifrnit; + int nit; + + bzero((char *) &ifrnit, sizeof(ifrnit)); + strlcpy(&ifrnit.ifr_name[0], ifname, IFNAMSIZ); + + nit = open("/dev/nit", 0); + if (nit < 0) { + report(LOG_ERR, "getether: open /dev/nit: %s", + get_errmsg()); + return rc; + } + do { + if (ioctl(nit, NIOCBIND, &ifrnit) < 0) { + report(LOG_ERR, "getether: NIOCBIND on nit"); + break; + } + if (ioctl(nit, SIOCGIFADDR, &ifrnit) < 0) { + report(LOG_ERR, "getether: SIOCGIFADDR on nit"); + break; + } + bcopy(&ifrnit.ifr_addr.sa_data[0], eap, EALEN); + rc = 0; + } while (0); + close(nit); + return rc; +} + +#define GETETHER +#endif /* SUNOS */ + + +#if defined(__FreeBSD__) || defined(__NetBSD__) +/* Thanks to John Brezak <brezak@ch.hp.com> for this code. */ +#include <sys/ioctl.h> +#include <sys/time.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +int +getether(ifname, eap) + char *ifname; /* interface name from ifconfig structure */ + char *eap; /* Ether address (output) */ +{ + int fd, rc = -1; + register int n; + struct ifreq ibuf[16]; + struct ifconf ifc; + register struct ifreq *ifrp, *ifend; + + /* Fetch the interface configuration */ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + report(LOG_ERR, "getether: socket %s: %s", ifname, get_errmsg()); + return (fd); + } + ifc.ifc_len = sizeof(ibuf); + ifc.ifc_buf = (caddr_t) ibuf; + if (ioctl(fd, SIOCGIFCONF, (char *) &ifc) < 0 || + ifc.ifc_len < sizeof(struct ifreq)) { + report(LOG_ERR, "getether: SIOCGIFCONF: %s", get_errmsg()); + goto out; + } + /* Search interface configuration list for link layer address. */ + ifrp = ibuf; + ifend = (struct ifreq *) ((char *) ibuf + ifc.ifc_len); + while (ifrp < ifend) { + /* Look for interface */ + if (strcmp(ifname, ifrp->ifr_name) == 0 && + ifrp->ifr_addr.sa_family == AF_LINK && + ((struct sockaddr_dl *) &ifrp->ifr_addr)->sdl_type == IFT_ETHER) { + bcopy(LLADDR((struct sockaddr_dl *) &ifrp->ifr_addr), eap, EALEN); + rc = 0; + break; + } + /* Bump interface config pointer */ + n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name); + if (n < sizeof(*ifrp)) + n = sizeof(*ifrp); + ifrp = (struct ifreq *) ((char *) ifrp + n); + } + + out: + close(fd); + return (rc); +} + +#define GETETHER +#endif /* __NetBSD__ */ + + +#ifdef SVR4 +/* + * This is for "Streams TCP/IP" by Lachman Associates. + * They sure made this cumbersome! -gwr + */ + +#include <sys/sockio.h> +#include <sys/dlpi.h> +#include <stropts.h> +#include <string.h> +#ifndef NULL +#define NULL 0 +#endif + +int +getether(ifname, eap) + char *ifname; /* interface name from ifconfig structure */ + char *eap; /* Ether address (output) */ +{ + int rc = -1; + char devname[32]; + char tmpbuf[sizeof(union DL_primitives) + 16]; + struct strbuf cbuf; + int fd, flags; + union DL_primitives *dlp; + char *enaddr; + int unit = -1; /* which unit to attach */ + + snprintf(devname, sizeof(devname), "%s%s", _PATH_DEV, ifname); + fd = open(devname, 2); + if (fd < 0) { + /* Try without the trailing digit. */ + char *p = devname + 5; + while (isalpha(*p)) + p++; + if (isdigit(*p)) { + unit = *p - '0'; + *p = '\0'; + } + fd = open(devname, 2); + if (fd < 0) { + report(LOG_ERR, "getether: open %s: %s", + devname, get_errmsg()); + return rc; + } + } +#ifdef DL_ATTACH_REQ + /* + * If this is a "Style 2" DLPI, then we must "attach" first + * to tell the driver which unit (board, port) we want. + * For now, decide this based on the device name. + * (Should do "info_req" and check dl_provider_style ...) + */ + if (unit >= 0) { + memset(tmpbuf, 0, sizeof(tmpbuf)); + dlp = (union DL_primitives *) tmpbuf; + dlp->dl_primitive = DL_ATTACH_REQ; + dlp->attach_req.dl_ppa = unit; + cbuf.buf = tmpbuf; + cbuf.len = DL_ATTACH_REQ_SIZE; + if (putmsg(fd, &cbuf, NULL, 0) < 0) { + report(LOG_ERR, "getether: attach: putmsg: %s", get_errmsg()); + goto out; + } + /* Recv the ack. */ + cbuf.buf = tmpbuf; + cbuf.maxlen = sizeof(tmpbuf); + flags = 0; + if (getmsg(fd, &cbuf, NULL, &flags) < 0) { + report(LOG_ERR, "getether: attach: getmsg: %s", get_errmsg()); + goto out; + } + /* + * Check the type, etc. + */ + if (dlp->dl_primitive == DL_ERROR_ACK) { + report(LOG_ERR, "getether: attach: dlpi_errno=%d, unix_errno=%d", + dlp->error_ack.dl_errno, + dlp->error_ack.dl_unix_errno); + goto out; + } + if (dlp->dl_primitive != DL_OK_ACK) { + report(LOG_ERR, "getether: attach: not OK or ERROR"); + goto out; + } + } /* unit >= 0 */ +#endif /* DL_ATTACH_REQ */ + + /* + * Get the Ethernet address the same way the ARP module + * does when it is pushed onto a new stream (bind). + * One should instead be able just do a dl_info_req + * but many drivers do not supply the hardware address + * in the response to dl_info_req (they MUST supply it + * for dl_bind_ack because the ARP module requires it). + */ + memset(tmpbuf, 0, sizeof(tmpbuf)); + dlp = (union DL_primitives *) tmpbuf; + dlp->dl_primitive = DL_BIND_REQ; + dlp->bind_req.dl_sap = 0x8FF; /* XXX - Unused SAP */ + cbuf.buf = tmpbuf; + cbuf.len = DL_BIND_REQ_SIZE; + if (putmsg(fd, &cbuf, NULL, 0) < 0) { + report(LOG_ERR, "getether: bind: putmsg: %s", get_errmsg()); + goto out; + } + /* Recv the ack. */ + cbuf.buf = tmpbuf; + cbuf.maxlen = sizeof(tmpbuf); + flags = 0; + if (getmsg(fd, &cbuf, NULL, &flags) < 0) { + report(LOG_ERR, "getether: bind: getmsg: %s", get_errmsg()); + goto out; + } + /* + * Check the type, etc. + */ + if (dlp->dl_primitive == DL_ERROR_ACK) { + report(LOG_ERR, "getether: bind: dlpi_errno=%d, unix_errno=%d", + dlp->error_ack.dl_errno, + dlp->error_ack.dl_unix_errno); + goto out; + } + if (dlp->dl_primitive != DL_BIND_ACK) { + report(LOG_ERR, "getether: bind: not OK or ERROR"); + goto out; + } + if (dlp->bind_ack.dl_addr_offset == 0) { + report(LOG_ERR, "getether: bind: ack has no address"); + goto out; + } + if (dlp->bind_ack.dl_addr_length < EALEN) { + report(LOG_ERR, "getether: bind: ack address truncated"); + goto out; + } + /* + * Copy the Ethernet address out of the message. + */ + enaddr = tmpbuf + dlp->bind_ack.dl_addr_offset; + memcpy(eap, enaddr, EALEN); + rc = 0; + + out: + close(fd); + return rc; +} + +#define GETETHER +#endif /* SVR4 */ + + +#ifdef __linux__ +/* + * This is really easy on Linux! This version (for linux) + * written by Nigel Metheringham <nigelm@ohm.york.ac.uk> and + * updated by Pauline Middelink <middelin@polyware.iaf.nl> + * + * The code is almost identical to the Ultrix code - however + * the names are different to confuse the innocent :-) + * Most of this code was stolen from the Ultrix bit above. + */ + +#include <memory.h> +#include <sys/ioctl.h> +#include <net/if.h> /* struct ifreq */ +#include <sys/socketio.h> /* Needed for IOCTL defs */ + +int +getether(ifname, eap) + char *ifname, *eap; +{ + int rc = -1; + int fd; + struct ifreq phys; + + memset(&phys, 0, sizeof(phys)); + strcpy(phys.ifr_name, ifname); + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + report(LOG_ERR, "getether: socket(INET,DGRAM) failed"); + return -1; + } + if (ioctl(fd, SIOCGIFHWADDR, &phys) < 0) { + report(LOG_ERR, "getether: ioctl SIOCGIFHWADDR failed"); + } else { + memcpy(eap, &phys.ifr_hwaddr.sa_data, EALEN); + rc = 0; + } + close(fd); + return rc; +} + +#define GETETHER +#endif /* __linux__ */ + + +/* If we don't know how on this system, just return an error. */ +#ifndef GETETHER +int +getether(ifname, eap) + char *ifname, *eap; +{ + return -1; +} + +#endif /* !GETETHER */ + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/getether.h b/libexec/bootpd/getether.h new file mode 100644 index 0000000..026df15 --- /dev/null +++ b/libexec/bootpd/getether.h @@ -0,0 +1,4 @@ +/* getether.h */ +/* $FreeBSD$ */ + +extern int getether(char *ifname, char *eaptr); diff --git a/libexec/bootpd/getif.c b/libexec/bootpd/getif.c new file mode 100644 index 0000000..64e5eda --- /dev/null +++ b/libexec/bootpd/getif.c @@ -0,0 +1,147 @@ +/* + * getif.c : get an interface structure + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#if defined(SUNOS) || defined(SVR4) +#include <sys/sockio.h> +#endif +#ifdef SVR4 +#include <sys/stropts.h> +#endif + +#include <sys/time.h> /* for struct timeval in net/if.h */ +#include <net/if.h> /* for struct ifreq */ +#include <netinet/in.h> + +#ifndef NO_UNISTD +#include <unistd.h> +#endif +#include <syslog.h> +#include <errno.h> +#include <assert.h> + +#include "getif.h" +#include "report.h" + +#ifdef __bsdi__ +#define BSD 43 +#endif + +static struct ifreq ifreq[10]; /* Holds interface configuration */ +static struct ifconf ifconf; /* points to ifreq */ + +static int nmatch(); + +/* Return a pointer to the interface struct for the passed address. */ +struct ifreq * +getif(s, addrp) + int s; /* socket file descriptor */ + struct in_addr *addrp; /* destination address on interface */ +{ + int maxmatch; + int len, m, incr; + struct ifreq *ifrq, *ifrmax; + struct sockaddr_in *sip; + char *p; + + /* If no address was supplied, just return NULL. */ + if (!addrp) + return (struct ifreq *) 0; + + /* Get the interface config if not done already. */ + if (ifconf.ifc_len == 0) { +#ifdef SVR4 + /* + * SysVr4 returns garbage if you do this the obvious way! + * This one took a while to figure out... -gwr + */ + struct strioctl ioc; + ioc.ic_cmd = SIOCGIFCONF; + ioc.ic_timout = 0; + ioc.ic_len = sizeof(ifreq); + ioc.ic_dp = (char *) ifreq; + m = ioctl(s, I_STR, (char *) &ioc); + ifconf.ifc_len = ioc.ic_len; + ifconf.ifc_req = ifreq; +#else /* SVR4 */ + ifconf.ifc_len = sizeof(ifreq); + ifconf.ifc_req = ifreq; + m = ioctl(s, SIOCGIFCONF, (caddr_t) & ifconf); +#endif /* SVR4 */ + if ((m < 0) || (ifconf.ifc_len <= 0)) { + report(LOG_ERR, "ioctl SIOCGIFCONF"); + return (struct ifreq *) 0; + } + } + maxmatch = 7; /* this many bits or less... */ + ifrmax = (struct ifreq *) 0;/* ... is not a valid match */ + p = (char *) ifreq; + len = ifconf.ifc_len; + while (len > 0) { + ifrq = (struct ifreq *) p; + sip = (struct sockaddr_in *) &ifrq->ifr_addr; + m = nmatch((u_char *)addrp, (u_char *)&(sip->sin_addr)); + if (m > maxmatch) { + maxmatch = m; + ifrmax = ifrq; + } +#ifndef IFNAMSIZ + /* BSD not defined or earlier than 4.3 */ + incr = sizeof(*ifrq); +#else + incr = ifrq->ifr_addr.sa_len + IFNAMSIZ; +#endif + + p += incr; + len -= incr; + } + + return ifrmax; +} + +/* + * Return the number of leading bits matching in the + * internet addresses supplied. + */ +static int +nmatch(ca, cb) + u_char *ca, *cb; /* ptrs to IP address, network order */ +{ + u_int m = 0; /* count of matching bits */ + u_int n = 4; /* bytes left, then bitmask */ + + /* Count matching bytes. */ + while (n && (*ca == *cb)) { + ca++; + cb++; + m += 8; + n--; + } + /* Now count matching bits. */ + if (n) { + n = 0x80; + while (n && ((*ca & n) == (*cb & n))) { + m++; + n >>= 1; + } + } + return (m); +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/getif.h b/libexec/bootpd/getif.h new file mode 100644 index 0000000..a2c86cf --- /dev/null +++ b/libexec/bootpd/getif.h @@ -0,0 +1,4 @@ +/* getif.h */ +/* $FreeBSD$ */ + +extern struct ifreq *getif(int, struct in_addr *); diff --git a/libexec/bootpd/hash.c b/libexec/bootpd/hash.c new file mode 100644 index 0000000..146e87e --- /dev/null +++ b/libexec/bootpd/hash.c @@ -0,0 +1,416 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + + $FreeBSD$ + +************************************************************************/ + +/* + * Generalized hash table ADT + * + * Provides multiple, dynamically-allocated, variable-sized hash tables on + * various data and keys. + * + * This package attempts to follow some of the coding conventions suggested + * by Bob Sidebotham and the AFS Clean Code Committee of the + * Information Technology Center at Carnegie Mellon. + */ + + +#include <sys/types.h> +#include <stdlib.h> + +#ifndef USE_BFUNCS +#include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "hash.h" + +#define TRUE 1 +#define FALSE 0 +#ifndef NULL +#define NULL 0 +#endif + +/* + * This can be changed to make internal routines visible to debuggers, etc. + */ +#ifndef PRIVATE +#define PRIVATE static +#endif + +PRIVATE void hashi_FreeMembers(hash_member *, hash_freefp); + + + + +/* + * Hash table initialization routine. + * + * This routine creates and intializes a hash table of size "tablesize" + * entries. Successful calls return a pointer to the hash table (which must + * be passed to other hash routines to identify the hash table). Failed + * calls return NULL. + */ + +hash_tbl * +hash_Init(tablesize) + unsigned tablesize; +{ + register hash_tbl *hashtblptr; + register unsigned totalsize; + + if (tablesize > 0) { + totalsize = sizeof(hash_tbl) + + sizeof(hash_member *) * (tablesize - 1); + hashtblptr = (hash_tbl *) malloc(totalsize); + if (hashtblptr) { + bzero((char *) hashtblptr, totalsize); + hashtblptr->size = tablesize; /* Success! */ + hashtblptr->bucketnum = 0; + hashtblptr->member = (hashtblptr->table)[0]; + } + } else { + hashtblptr = NULL; /* Disallow zero-length tables */ + } + return hashtblptr; /* NULL if failure */ +} + + + +/* + * Frees an entire linked list of bucket members (used in the open + * hashing scheme). Does nothing if the passed pointer is NULL. + */ + +PRIVATE void +hashi_FreeMembers(bucketptr, free_data) + hash_member *bucketptr; + hash_freefp free_data; +{ + hash_member *nextbucket; + while (bucketptr) { + nextbucket = bucketptr->next; + (*free_data) (bucketptr->data); + free((char *) bucketptr); + bucketptr = nextbucket; + } +} + + + + +/* + * This routine re-initializes the hash table. It frees all the allocated + * memory and resets all bucket pointers to NULL. + */ + +void +hash_Reset(hashtable, free_data) + hash_tbl *hashtable; + hash_freefp free_data; +{ + hash_member **bucketptr; + unsigned i; + + bucketptr = hashtable->table; + for (i = 0; i < hashtable->size; i++) { + hashi_FreeMembers(*bucketptr, free_data); + *bucketptr++ = NULL; + } + hashtable->bucketnum = 0; + hashtable->member = (hashtable->table)[0]; +} + + + +/* + * Generic hash function to calculate a hash code from the given string. + * + * For each byte of the string, this function left-shifts the value in an + * accumulator and then adds the byte into the accumulator. The contents of + * the accumulator is returned after the entire string has been processed. + * It is assumed that this result will be used as the "hashcode" parameter in + * calls to other functions in this package. These functions automatically + * adjust the hashcode for the size of each hashtable. + * + * This algorithm probably works best when the hash table size is a prime + * number. + * + * Hopefully, this function is better than the previous one which returned + * the sum of the squares of all the bytes. I'm still open to other + * suggestions for a default hash function. The programmer is more than + * welcome to supply his/her own hash function as that is one of the design + * features of this package. + */ + +unsigned +hash_HashFunction(string, len) + unsigned char *string; + register unsigned len; +{ + register unsigned accum; + + accum = 0; + for (; len > 0; len--) { + accum <<= 1; + accum += (unsigned) (*string++ & 0xFF); + } + return accum; +} + + + +/* + * Returns TRUE if at least one entry for the given key exists; FALSE + * otherwise. + */ + +int +hash_Exists(hashtable, hashcode, compare, key) + hash_tbl *hashtable; + unsigned hashcode; + hash_cmpfp compare; + hash_datum *key; +{ + register hash_member *memberptr; + + memberptr = (hashtable->table)[hashcode % (hashtable->size)]; + while (memberptr) { + if ((*compare) (key, memberptr->data)) { + return TRUE; /* Entry does exist */ + } + memberptr = memberptr->next; + } + return FALSE; /* Entry does not exist */ +} + + + +/* + * Insert the data item "element" into the hash table using "hashcode" + * to determine the bucket number, and "compare" and "key" to determine + * its uniqueness. + * + * If the insertion is successful 0 is returned. If a matching entry + * already exists in the given bucket of the hash table, or some other error + * occurs, -1 is returned and the insertion is not done. + */ + +int +hash_Insert(hashtable, hashcode, compare, key, element) + hash_tbl *hashtable; + unsigned hashcode; + hash_cmpfp compare; + hash_datum *key, *element; +{ + hash_member *temp; + + hashcode %= hashtable->size; + if (hash_Exists(hashtable, hashcode, compare, key)) { + return -1; /* At least one entry already exists */ + } + temp = (hash_member *) malloc(sizeof(hash_member)); + if (!temp) + return -1; /* malloc failed! */ + + temp->data = element; + temp->next = (hashtable->table)[hashcode]; + (hashtable->table)[hashcode] = temp; + return 0; /* Success */ +} + + + +/* + * Delete all data elements which match the given key. If at least one + * element is found and the deletion is successful, 0 is returned. + * If no matching elements can be found in the hash table, -1 is returned. + */ + +int +hash_Delete(hashtable, hashcode, compare, key, free_data) + hash_tbl *hashtable; + unsigned hashcode; + hash_cmpfp compare; + hash_datum *key; + hash_freefp free_data; +{ + hash_member *memberptr, *tempptr; + hash_member *previous = NULL; + int retval; + + retval = -1; + hashcode %= hashtable->size; + + /* + * Delete the first member of the list if it matches. Since this moves + * the second member into the first position we have to keep doing this + * over and over until it no longer matches. + */ + memberptr = (hashtable->table)[hashcode]; + while (memberptr && (*compare) (key, memberptr->data)) { + (hashtable->table)[hashcode] = memberptr->next; + /* + * Stop hashi_FreeMembers() from deleting the whole list! + */ + memberptr->next = NULL; + hashi_FreeMembers(memberptr, free_data); + memberptr = (hashtable->table)[hashcode]; + retval = 0; + } + + /* + * Now traverse the rest of the list + */ + if (memberptr) { + previous = memberptr; + memberptr = memberptr->next; + } + while (memberptr) { + if ((*compare) (key, memberptr->data)) { + tempptr = memberptr; + previous->next = memberptr = memberptr->next; + /* + * Put the brakes on hashi_FreeMembers(). . . . + */ + tempptr->next = NULL; + hashi_FreeMembers(tempptr, free_data); + retval = 0; + } else { + previous = memberptr; + memberptr = memberptr->next; + } + } + return retval; +} + + + +/* + * Locate and return the data entry associated with the given key. + * + * If the data entry is found, a pointer to it is returned. Otherwise, + * NULL is returned. + */ + +hash_datum * +hash_Lookup(hashtable, hashcode, compare, key) + hash_tbl *hashtable; + unsigned hashcode; + hash_cmpfp compare; + hash_datum *key; +{ + hash_member *memberptr; + + memberptr = (hashtable->table)[hashcode % (hashtable->size)]; + while (memberptr) { + if ((*compare) (key, memberptr->data)) { + return (memberptr->data); + } + memberptr = memberptr->next; + } + return NULL; +} + + + +/* + * Return the next available entry in the hashtable for a linear search + */ + +hash_datum * +hash_NextEntry(hashtable) + hash_tbl *hashtable; +{ + register unsigned bucket; + register hash_member *memberptr; + + /* + * First try to pick up where we left off. + */ + memberptr = hashtable->member; + if (memberptr) { + hashtable->member = memberptr->next; /* Set up for next call */ + return memberptr->data; /* Return the data */ + } + /* + * We hit the end of a chain, so look through the array of buckets + * until we find a new chain (non-empty bucket) or run out of buckets. + */ + bucket = hashtable->bucketnum + 1; + while ((bucket < hashtable->size) && + !(memberptr = (hashtable->table)[bucket])) { + bucket++; + } + + /* + * Check to see if we ran out of buckets. + */ + if (bucket >= hashtable->size) { + /* + * Reset to top of table for next call. + */ + hashtable->bucketnum = 0; + hashtable->member = (hashtable->table)[0]; + /* + * But return end-of-table indication to the caller this time. + */ + return NULL; + } + /* + * Must have found a non-empty bucket. + */ + hashtable->bucketnum = bucket; + hashtable->member = memberptr->next; /* Set up for next call */ + return memberptr->data; /* Return the data */ +} + + + +/* + * Return the first entry in a hash table for a linear search + */ + +hash_datum * +hash_FirstEntry(hashtable) + hash_tbl *hashtable; +{ + hashtable->bucketnum = 0; + hashtable->member = (hashtable->table)[0]; + return hash_NextEntry(hashtable); +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/hash.h b/libexec/bootpd/hash.h new file mode 100644 index 0000000..969703d --- /dev/null +++ b/libexec/bootpd/hash.h @@ -0,0 +1,148 @@ +#ifndef HASH_H +#define HASH_H +/* hash.h */ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. +************************************************************************/ + +/* + * Generalized hash table ADT + * $FreeBSD$ + * + * Provides multiple, dynamically-allocated, variable-sized hash tables on + * various data and keys. + * + * This package attempts to follow some of the coding conventions suggested + * by Bob Sidebotham and the AFS Clean Code Committee. + */ + + +/* + * The user must supply the following: + * + * 1. A comparison function which is declared as: + * + * int compare(data1, data2) + * hash_datum *data1, *data2; + * + * This function must compare the desired fields of data1 and + * data2 and return TRUE (1) if the data should be considered + * equivalent (i.e. have the same key value) or FALSE (0) + * otherwise. This function is called through a pointer passed to + * the various hashtable functions (thus pointers to different + * functions may be passed to effect different tests on different + * hash tables). + * + * Internally, all the functions of this package always call the + * compare function with the "key" parameter as the first parameter, + * and a full data element as the second parameter. Thus, the key + * and element arguments to functions such as hash_Lookup() may + * actually be of different types and the programmer may provide a + * compare function which compares the two different object types + * as desired. + * + * Example: + * + * int compare(key, element) + * char *key; + * struct some_complex_structure *element; + * { + * return !strcmp(key, element->name); + * } + * + * key = "John C. Doe" + * element = &some_complex_structure + * hash_Lookup(table, hashcode, compare, key); + * + * 2. A hash function yielding an unsigned integer value to be used + * as the hashcode (index into the hashtable). Thus, the user + * may hash on whatever data is desired and may use several + * different hash functions for various different hash tables. + * The actual hash table index will be the passed hashcode modulo + * the hash table size. + * + * A generalized hash function, hash_HashFunction(), is included + * with this package to make things a little easier. It is not + * guaranteed to use the best hash algorithm in existence. . . . + */ + + + +/* + * Various hash table definitions + */ + + +/* + * Define "hash_datum" as a universal data type + */ +typedef void hash_datum; + +typedef struct hash_memberstruct hash_member; +typedef struct hash_tblstruct hash_tbl; +typedef struct hash_tblstruct_hdr hash_tblhdr; + +struct hash_memberstruct { + hash_member *next; + hash_datum *data; +}; + +struct hash_tblstruct_hdr { + unsigned size, bucketnum; + hash_member *member; +}; + +struct hash_tblstruct { + unsigned size, bucketnum; + hash_member *member; /* Used for linear dump */ + hash_member *table[1]; /* Dynamically extended */ +}; + +/* ANSI function prototypes or empty arg list? */ + +typedef int (*hash_cmpfp)(hash_datum *, hash_datum *); +typedef void (*hash_freefp)(hash_datum *); + +extern hash_tbl *hash_Init(u_int tablesize); + +extern void hash_Reset(hash_tbl *tbl, hash_freefp); + +extern unsigned hash_HashFunction(u_char *str, u_int len); + +extern int hash_Exists(hash_tbl *, u_int code, + hash_cmpfp, hash_datum *key); + +extern int hash_Insert(hash_tbl *, u_int code, + hash_cmpfp, hash_datum *key, + hash_datum *element); + +extern int hash_Delete(hash_tbl *, u_int code, + hash_cmpfp, hash_datum *key, + hash_freefp); + +extern hash_datum *hash_Lookup(hash_tbl *, u_int code, + hash_cmpfp, hash_datum *key); + +extern hash_datum *hash_FirstEntry(hash_tbl *); + +extern hash_datum *hash_NextEntry(hash_tbl *); + +#endif /* HASH_H */ diff --git a/libexec/bootpd/hwaddr.c b/libexec/bootpd/hwaddr.c new file mode 100644 index 0000000..de264cc --- /dev/null +++ b/libexec/bootpd/hwaddr.c @@ -0,0 +1,352 @@ +/* + * hwaddr.c - routines that deal with hardware addresses. + * (i.e. Ethernet) + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +#if defined(SUNOS) || defined(SVR4) +#include <sys/sockio.h> +#endif +#ifdef SVR4 +#include <sys/stream.h> +#include <stropts.h> +#include <fcntl.h> +#endif + +#ifdef _AIX32 +#include <sys/time.h> /* for struct timeval in net/if.h */ +#include <net/if.h> /* for struct ifnet in net/if_arp.h */ +#endif + +#include <net/if_arp.h> +#include <netinet/in.h> + +#ifdef WIN_TCP +#include <netinet/if_ether.h> +#include <sys/dlpi.h> +#endif + +#include <stdio.h> +#ifndef NO_UNISTD +#include <unistd.h> +#endif +#include <syslog.h> + +#ifndef USE_BFUNCS +/* Yes, memcpy is OK here (no overlapped copies). */ +#include <memory.h> +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#ifndef ATF_INUSE /* Not defined on some systems (i.e. Linux) */ +#define ATF_INUSE 0 +#endif + +/* For BSD 4.4, set arp entry by writing to routing socket */ +#if defined(BSD) +#if BSD >= 199306 +extern int bsd_arp_set(struct in_addr *, char *, int); +#endif +#endif + +#include "bptypes.h" +#include "hwaddr.h" +#include "report.h" + +extern int debug; + +/* + * Hardware address lengths (in bytes) and network name based on hardware + * type code. List in order specified by Assigned Numbers RFC; Array index + * is hardware type code. Entries marked as zero are unknown to the author + * at this time. . . . + */ + +struct hwinfo hwinfolist[] = +{ + {0, "Reserved"}, /* Type 0: Reserved (don't use this) */ + {6, "Ethernet"}, /* Type 1: 10Mb Ethernet (48 bits) */ + {1, "3Mb Ethernet"}, /* Type 2: 3Mb Ethernet (8 bits) */ + {0, "AX.25"}, /* Type 3: Amateur Radio AX.25 */ + {1, "ProNET"}, /* Type 4: Proteon ProNET Token Ring */ + {0, "Chaos"}, /* Type 5: Chaos */ + {6, "IEEE 802"}, /* Type 6: IEEE 802 Networks */ + {0, "ARCNET"} /* Type 7: ARCNET */ +}; +int hwinfocnt = sizeof(hwinfolist) / sizeof(hwinfolist[0]); + + +/* + * Setup the arp cache so that IP address 'ia' will be temporarily + * bound to hardware address 'ha' of length 'len'. + */ +void +setarp(s, ia, hafamily, haddr, halen) + int s; /* socket fd */ + struct in_addr *ia; /* protocol address */ + int hafamily; /* HW address family */ + u_char *haddr; /* HW address data */ + int halen; +{ +#ifdef SIOCSARP +#ifdef WIN_TCP + /* This is an SVR4 with different networking code from + * Wollongong WIN-TCP. Not quite like the Lachman code. + * Code from: drew@drewsun.FEITH.COM (Andrew B. Sudell) + */ +#undef SIOCSARP +#define SIOCSARP ARP_ADD + struct arptab arpreq; /* Arp table entry */ + + bzero((caddr_t) &arpreq, sizeof(arpreq)); + arpreq.at_flags = ATF_COM; + + /* Set up IP address */ + arpreq.at_in = ia->s_addr; + + /* Set up Hardware Address */ + bcopy(haddr, arpreq.at_enaddr, halen); + + /* Set the Date Link type. */ + /* XXX - Translate (hafamily) to dltype somehow? */ + arpreq.at_dltype = DL_ETHER; + +#else /* WIN_TCP */ + /* Good old Berkeley way. */ + struct arpreq arpreq; /* Arp request ioctl block */ + struct sockaddr_in *si; + char *p; + + bzero((caddr_t) &arpreq, sizeof(arpreq)); + arpreq.arp_flags = ATF_INUSE | ATF_COM; + + /* Set up the protocol address. */ + arpreq.arp_pa.sa_family = AF_INET; + si = (struct sockaddr_in *) &arpreq.arp_pa; + si->sin_addr = *ia; + + /* Set up the hardware address. */ +#ifdef __linux__ /* XXX - Do others need this? -gwr */ + /* + * Linux requires the sa_family field set. + * longyear@netcom.com (Al Longyear) + */ + arpreq.arp_ha.sa_family = hafamily; +#endif /* linux */ + + /* This variable is just to help catch type mismatches. */ + p = arpreq.arp_ha.sa_data; + bcopy(haddr, p, halen); +#endif /* WIN_TCP */ + +#ifdef SVR4 + /* + * And now the stuff for System V Rel 4.x which does not + * appear to allow SIOCxxx ioctls on a socket descriptor. + * Thanks to several people: (all sent the same fix) + * Barney Wolff <barney@databus.com>, + * bear@upsys.se (Bj|rn Sj|holm), + * Michael Kuschke <Michael.Kuschke@Materna.DE>, + */ + { + int fd; + struct strioctl iocb; + + if ((fd=open("/dev/arp", O_RDWR)) < 0) { + report(LOG_ERR, "open /dev/arp: %s\n", get_errmsg()); + } + iocb.ic_cmd = SIOCSARP; + iocb.ic_timout = 0; + iocb.ic_dp = (char *)&arpreq; + iocb.ic_len = sizeof(arpreq); + if (ioctl(fd, I_STR, (caddr_t)&iocb) < 0) { + report(LOG_ERR, "ioctl I_STR: %s\n", get_errmsg()); + } + close (fd); + } +#else /* SVR4 */ + /* + * On SunOS, the ioctl sometimes returns ENXIO, and it + * appears to happen when the ARP cache entry you tried + * to add is already in the cache. (Sigh...) + * XXX - Should this error simply be ignored? -gwr + */ + if (ioctl(s, SIOCSARP, (caddr_t) &arpreq) < 0) { + report(LOG_ERR, "ioctl SIOCSARP: %s", get_errmsg()); + } +#endif /* SVR4 */ +#else /* SIOCSARP */ +#if defined(BSD) && (BSD >= 199306) + bsd_arp_set(ia, haddr, halen); +#else + /* + * Oh well, SIOCSARP is not defined. Just run arp(8). + * Need to delete partial entry first on some systems. + * XXX - Gag! + */ + int status; + char buf[256]; + char *a; + extern char *inet_ntoa(); + + a = inet_ntoa(*ia); + snprintf(buf, sizeof(buf), "arp -d %s; arp -s %s %s temp", + a, a, haddrtoa(haddr, halen)); + if (debug > 2) + report(LOG_INFO, "%s", buf); + status = system(buf); + if (status) + report(LOG_ERR, "arp failed, exit code=0x%x", status); + return; +#endif /* ! 4.4 BSD */ +#endif /* SIOCSARP */ +} + + +/* + * Convert a hardware address to an ASCII string. + */ +char * +haddrtoa(haddr, hlen) + u_char *haddr; + int hlen; +{ + static char haddrbuf[3 * MAXHADDRLEN + 1]; + char *bufptr; + + if (hlen > MAXHADDRLEN) + hlen = MAXHADDRLEN; + + bufptr = haddrbuf; + while (hlen > 0) { + sprintf(bufptr, "%02X:", (unsigned) (*haddr++ & 0xFF)); + bufptr += 3; + hlen--; + } + bufptr[-1] = 0; + return (haddrbuf); +} + + +/* + * haddr_conv802() + * -------------- + * + * Converts a backwards address to a canonical address and a canonical address + * to a backwards address. + * + * INPUTS: + * adr_in - pointer to six byte string to convert (unsigned char *) + * addr_len - how many bytes to convert + * + * OUTPUTS: + * addr_out - The string is updated to contain the converted address. + * + * CALLER: + * many + * + * DATA: + * Uses conv802table to bit-reverse the address bytes. + */ + +static u_char conv802table[256] = +{ + /* 0x00 */ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, + /* 0x08 */ 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + /* 0x10 */ 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + /* 0x18 */ 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + /* 0x20 */ 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, + /* 0x28 */ 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + /* 0x30 */ 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, + /* 0x38 */ 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + /* 0x40 */ 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + /* 0x48 */ 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + /* 0x50 */ 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, + /* 0x58 */ 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + /* 0x60 */ 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, + /* 0x68 */ 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + /* 0x70 */ 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + /* 0x78 */ 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + /* 0x80 */ 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, + /* 0x88 */ 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + /* 0x90 */ 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, + /* 0x98 */ 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + /* 0xA0 */ 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + /* 0xA8 */ 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + /* 0xB0 */ 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, + /* 0xB8 */ 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + /* 0xC0 */ 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, + /* 0xC8 */ 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + /* 0xD0 */ 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + /* 0xD8 */ 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + /* 0xE0 */ 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, + /* 0xE8 */ 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + /* 0xF0 */ 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, + /* 0xF8 */ 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, +}; + +void +haddr_conv802(addr_in, addr_out, len) + register u_char *addr_in, *addr_out; + int len; +{ + u_char *lim; + + lim = addr_out + len; + while (addr_out < lim) + *addr_out++ = conv802table[*addr_in++]; +} + +#if 0 +/* + * For the record, here is a program to generate the + * bit-reverse table above. + */ +static int +bitrev(n) + int n; +{ + int i, r; + + r = 0; + for (i = 0; i < 8; i++) { + r <<= 1; + r |= (n & 1); + n >>= 1; + } + return r; +} + +main() +{ + int i; + for (i = 0; i <= 0xFF; i++) { + if ((i & 7) == 0) + printf("/* 0x%02X */", i); + printf(" 0x%02X,", bitrev(i)); + if ((i & 7) == 7) + printf("\n"); + } +} + +#endif + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/hwaddr.h b/libexec/bootpd/hwaddr.h new file mode 100644 index 0000000..bd68857 --- /dev/null +++ b/libexec/bootpd/hwaddr.h @@ -0,0 +1,36 @@ +/* + * hwaddr.h + * + * $FreeBSD$ + */ + +#ifndef HWADDR_H +#define HWADDR_H + +#define MAXHADDRLEN 8 /* Max hw address length in bytes */ + +/* + * This structure holds information about a specific network type. The + * length of the network hardware address is stored in "hlen". + * The string pointed to by "name" is the cononical name of the network. + */ +struct hwinfo { + unsigned int hlen; + char *name; +}; + +extern struct hwinfo hwinfolist[]; +extern int hwinfocnt; + +extern void setarp(int, struct in_addr *, int, u_char *, int); +extern char *haddrtoa(u_char *, int); +extern void haddr_conv802(u_char *, u_char *, int); + +/* + * Return the length in bytes of a hardware address of the given type. + * Return the canonical name of the network of the given type. + */ +#define haddrlength(type) ((hwinfolist[(int) (type)]).hlen) +#define netname(type) ((hwinfolist[(int) (type)]).name) + +#endif /* HWADDR_H */ diff --git a/libexec/bootpd/lookup.c b/libexec/bootpd/lookup.c new file mode 100644 index 0000000..54b3f62 --- /dev/null +++ b/libexec/bootpd/lookup.c @@ -0,0 +1,129 @@ +/* + * lookup.c - Lookup IP address, HW address, netmask + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <sys/time.h> /* for struct timeval in net/if.h */ +#include <net/if.h> +#include <netinet/in.h> + +#ifdef ETC_ETHERS +#include <net/ethernet.h> +extern int ether_hostton(); +#endif + +#include <netdb.h> +#include <syslog.h> + +#ifndef USE_BFUNCS +#include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +#define bcopy(a,b,c) memcpy(b,a,c) +#endif + +#include "bootp.h" +#include "lookup.h" +#include "report.h" + +/* + * Lookup an Ethernet address and return it. + * Return NULL if addr not found. + */ +u_char * +lookup_hwa(hostname, htype) + char *hostname; + int htype; +{ + switch (htype) { + + /* XXX - How is this done on other systems? -gwr */ +#ifdef ETC_ETHERS + case HTYPE_ETHERNET: + case HTYPE_IEEE802: + { + static struct ether_addr ea; + /* This does a lookup in /etc/ethers */ + if (ether_hostton(hostname, &ea)) { + report(LOG_ERR, "no HW addr for host \"%s\"", + hostname); + return (u_char *) 0; + } + return (u_char *) & ea; + } +#endif /* ETC_ETHERS */ + + default: + report(LOG_ERR, "no lookup for HW addr type %d", htype); + } /* switch */ + + /* If the system can't do it, just return an error. */ + return (u_char *) 0; +} + + +/* + * Lookup an IP address. + * Return non-zero on failure. + */ +int +lookup_ipa(hostname, result) + char *hostname; + u_int32 *result; +{ + struct hostent *hp; + hp = gethostbyname(hostname); + if (!hp) + return -1; + bcopy(hp->h_addr, result, sizeof(*result)); + return 0; +} + + +/* + * Lookup a netmask + * Return non-zero on failure. + * + * XXX - This is OK as a default, but to really make this automatic, + * we would need to get the subnet mask from the ether interface. + * If this is wrong, specify the correct value in the bootptab. + */ +int +lookup_netmask(addr, result) + u_int32 addr; /* both in network order */ + u_int32 *result; +{ + int32 m, a; + + a = ntohl(addr); + m = 0; + + if (IN_CLASSA(a)) + m = IN_CLASSA_NET; + + if (IN_CLASSB(a)) + m = IN_CLASSB_NET; + + if (IN_CLASSC(a)) + m = IN_CLASSC_NET; + + if (!m) + return -1; + *result = htonl(m); + return 0; +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/lookup.h b/libexec/bootpd/lookup.h new file mode 100644 index 0000000..4940f05 --- /dev/null +++ b/libexec/bootpd/lookup.h @@ -0,0 +1,8 @@ +/* lookup.h */ +/* $FreeBSD$ */ + +#include "bptypes.h" /* for int32, u_int32 */ + +extern u_char *lookup_hwa(char *hostname, int htype); +extern int lookup_ipa(char *hostname, u_int32 *addr); +extern int lookup_netmask(u_int32 addr, u_int32 *mask); diff --git a/libexec/bootpd/patchlevel.h b/libexec/bootpd/patchlevel.h new file mode 100644 index 0000000..fc79f18 --- /dev/null +++ b/libexec/bootpd/patchlevel.h @@ -0,0 +1,8 @@ +/* + * patchlevel.h + * + * $FreeBSD$ + */ + +#define VERSION "2.4" +#define PATCHLEVEL 3 diff --git a/libexec/bootpd/readfile.c b/libexec/bootpd/readfile.c new file mode 100644 index 0000000..ab583d4 --- /dev/null +++ b/libexec/bootpd/readfile.c @@ -0,0 +1,2084 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + + $FreeBSD$ + +************************************************************************/ + +/* + * bootpd configuration file reading code. + * + * The routines in this file deal with reading, interpreting, and storing + * the information found in the bootpd configuration file (usually + * /etc/bootptab). + */ + + +#include <sys/errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/time.h> +#include <netinet/in.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <syslog.h> + +#ifndef USE_BFUNCS +#include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "bootp.h" +#include "hash.h" +#include "hwaddr.h" +#include "lookup.h" +#include "readfile.h" +#include "report.h" +#include "tzone.h" +#include "bootpd.h" + +#define HASHTABLESIZE 257 /* Hash table size (prime) */ + +/* Non-standard hardware address type (see bootp.h) */ +#define HTYPE_DIRECT 0 + +/* Error codes returned by eval_symbol: */ +#define SUCCESS 0 +#define E_END_OF_ENTRY (-1) +#define E_SYNTAX_ERROR (-2) +#define E_UNKNOWN_SYMBOL (-3) +#define E_BAD_IPADDR (-4) +#define E_BAD_HWADDR (-5) +#define E_BAD_LONGWORD (-6) +#define E_BAD_HWATYPE (-7) +#define E_BAD_PATHNAME (-8) +#define E_BAD_VALUE (-9) + +/* Tag idendities. */ +#define SYM_NULL 0 +#define SYM_BOOTFILE 1 +#define SYM_COOKIE_SERVER 2 +#define SYM_DOMAIN_SERVER 3 +#define SYM_GATEWAY 4 +#define SYM_HWADDR 5 +#define SYM_HOMEDIR 6 +#define SYM_HTYPE 7 +#define SYM_IMPRESS_SERVER 8 +#define SYM_IPADDR 9 +#define SYM_LOG_SERVER 10 +#define SYM_LPR_SERVER 11 +#define SYM_NAME_SERVER 12 +#define SYM_RLP_SERVER 13 +#define SYM_SUBNET_MASK 14 +#define SYM_TIME_OFFSET 15 +#define SYM_TIME_SERVER 16 +#define SYM_VENDOR_MAGIC 17 +#define SYM_SIMILAR_ENTRY 18 +#define SYM_NAME_SWITCH 19 +#define SYM_BOOTSIZE 20 +#define SYM_BOOT_SERVER 22 +#define SYM_TFTPDIR 23 +#define SYM_DUMP_FILE 24 +#define SYM_DOMAIN_NAME 25 +#define SYM_SWAP_SERVER 26 +#define SYM_ROOT_PATH 27 +#define SYM_EXTEN_FILE 28 +#define SYM_REPLY_ADDR 29 +#define SYM_NIS_DOMAIN 30 /* RFC 1533 */ +#define SYM_NIS_SERVER 31 /* RFC 1533 */ +#define SYM_NTP_SERVER 32 /* RFC 1533 */ +#define SYM_EXEC_FILE 33 /* YORK_EX_OPTION */ +#define SYM_MSG_SIZE 34 +#define SYM_MIN_WAIT 35 +/* XXX - Add new tags here */ + +#define OP_ADDITION 1 /* Operations on tags */ +#define OP_DELETION 2 +#define OP_BOOLEAN 3 + +#define MAXINADDRS 16 /* Max size of an IP address list */ +#define MAXBUFLEN 256 /* Max temp buffer space */ +#define MAXENTRYLEN 2048 /* Max size of an entire entry */ + + + +/* + * Structure used to map a configuration-file symbol (such as "ds") to a + * unique integer. + */ + +struct symbolmap { + char *symbol; + int symbolcode; +}; + + +struct htypename { + char *name; + byte htype; +}; + + +PRIVATE int nhosts; /* Number of hosts (/w hw or IP address) */ +PRIVATE int nentries; /* Total number of entries */ +PRIVATE int32 modtime = 0; /* Last modification time of bootptab */ +PRIVATE char *current_hostname; /* Name of the current entry. */ +PRIVATE char current_tagname[8]; + +/* + * List of symbolic names used in the bootptab file. The order and actual + * values of the symbol codes (SYM_. . .) are unimportant, but they must + * all be unique. + */ + +PRIVATE struct symbolmap symbol_list[] = { + {"bf", SYM_BOOTFILE}, + {"bs", SYM_BOOTSIZE}, + {"cs", SYM_COOKIE_SERVER}, + {"df", SYM_DUMP_FILE}, + {"dn", SYM_DOMAIN_NAME}, + {"ds", SYM_DOMAIN_SERVER}, + {"ef", SYM_EXTEN_FILE}, + {"ex", SYM_EXEC_FILE}, /* YORK_EX_OPTION */ + {"gw", SYM_GATEWAY}, + {"ha", SYM_HWADDR}, + {"hd", SYM_HOMEDIR}, + {"hn", SYM_NAME_SWITCH}, + {"ht", SYM_HTYPE}, + {"im", SYM_IMPRESS_SERVER}, + {"ip", SYM_IPADDR}, + {"lg", SYM_LOG_SERVER}, + {"lp", SYM_LPR_SERVER}, + {"ms", SYM_MSG_SIZE}, + {"mw", SYM_MIN_WAIT}, + {"ns", SYM_NAME_SERVER}, + {"nt", SYM_NTP_SERVER}, + {"ra", SYM_REPLY_ADDR}, + {"rl", SYM_RLP_SERVER}, + {"rp", SYM_ROOT_PATH}, + {"sa", SYM_BOOT_SERVER}, + {"sm", SYM_SUBNET_MASK}, + {"sw", SYM_SWAP_SERVER}, + {"tc", SYM_SIMILAR_ENTRY}, + {"td", SYM_TFTPDIR}, + {"to", SYM_TIME_OFFSET}, + {"ts", SYM_TIME_SERVER}, + {"vm", SYM_VENDOR_MAGIC}, + {"yd", SYM_NIS_DOMAIN}, + {"ys", SYM_NIS_SERVER}, + /* XXX - Add new tags here */ +}; + + +/* + * List of symbolic names for hardware types. Name translates into + * hardware type code listed with it. Names must begin with a letter + * and must be all lowercase. This is searched linearly, so put + * commonly-used entries near the beginning. + */ + +PRIVATE struct htypename htnamemap[] = { + {"ethernet", HTYPE_ETHERNET}, + {"ethernet3", HTYPE_EXP_ETHERNET}, + {"ether", HTYPE_ETHERNET}, + {"ether3", HTYPE_EXP_ETHERNET}, + {"ieee802", HTYPE_IEEE802}, + {"tr", HTYPE_IEEE802}, + {"token-ring", HTYPE_IEEE802}, + {"pronet", HTYPE_PRONET}, + {"chaos", HTYPE_CHAOS}, + {"arcnet", HTYPE_ARCNET}, + {"ax.25", HTYPE_AX25}, + {"direct", HTYPE_DIRECT}, + {"serial", HTYPE_DIRECT}, + {"slip", HTYPE_DIRECT}, + {"ppp", HTYPE_DIRECT} +}; + + + +/* + * Externals and forward declarations. + */ + +extern boolean iplookcmp(); +boolean nmcmp(hash_datum *, hash_datum *); + +PRIVATE void + adjust(char **); +PRIVATE void + del_string(struct shared_string *); +PRIVATE void + del_bindata(struct shared_bindata *); +PRIVATE void + del_iplist(struct in_addr_list *); +PRIVATE void + eat_whitespace(char **); +PRIVATE int + eval_symbol(char **, struct host *); +PRIVATE void + fill_defaults(struct host *, char **); +PRIVATE void + free_host(hash_datum *); +PRIVATE struct in_addr_list * + get_addresses(char **); +PRIVATE struct shared_string * + get_shared_string(char **); +PRIVATE char * + get_string(char **, char *, u_int *); +PRIVATE u_int32 + get_u_long(char **); +PRIVATE boolean + goodname(char *); +PRIVATE boolean + hwinscmp(hash_datum *, hash_datum *); +PRIVATE int + interp_byte(char **, byte *); +PRIVATE void + makelower(char *); +PRIVATE boolean + nullcmp(hash_datum *, hash_datum *); +PRIVATE int + process_entry(struct host *, char *); +PRIVATE int + process_generic(char **, struct shared_bindata **, u_int); +PRIVATE byte * + prs_haddr(char **, u_int); +PRIVATE int + prs_inetaddr(char **, u_int32 *); +PRIVATE void + read_entry(FILE *, char *, u_int *); +PRIVATE char * + smalloc(u_int); + + +/* + * Vendor magic cookies for CMU and RFC1048 + */ +u_char vm_cmu[4] = VM_CMU; +u_char vm_rfc1048[4] = VM_RFC1048; + +/* + * Main hash tables + */ +hash_tbl *hwhashtable; +hash_tbl *iphashtable; +hash_tbl *nmhashtable; + +/* + * Allocate hash tables for hardware address, ip address, and hostname + * (shared by bootpd and bootpef) + */ +void +rdtab_init() +{ + hwhashtable = hash_Init(HASHTABLESIZE); + iphashtable = hash_Init(HASHTABLESIZE); + nmhashtable = hash_Init(HASHTABLESIZE); + if (!(hwhashtable && iphashtable && nmhashtable)) { + report(LOG_ERR, "Unable to allocate hash tables."); + exit(1); + } +} + + +/* + * Read bootptab database file. Avoid rereading the file if the + * write date hasn't changed since the last time we read it. + */ + +void +readtab(force) + int force; +{ + struct host *hp; + FILE *fp; + struct stat st; + unsigned hashcode, buflen; + static char buffer[MAXENTRYLEN]; + + /* + * Check the last modification time. + */ + if (stat(bootptab, &st) < 0) { + report(LOG_ERR, "stat on \"%s\": %s", + bootptab, get_errmsg()); + return; + } +#ifdef DEBUG + if (debug > 3) { + char timestr[28]; + strcpy(timestr, ctime(&(st.st_mtime))); + /* zap the newline */ + timestr[24] = '\0'; + report(LOG_INFO, "bootptab mtime: %s", + timestr); + } +#endif + if ((force == 0) && + (st.st_mtime == modtime) && + st.st_nlink) { + /* + * hasn't been modified or deleted yet. + */ + return; + } + if (debug) + report(LOG_INFO, "reading %s\"%s\"", + (modtime != 0L) ? "new " : "", + bootptab); + + /* + * Open bootptab file. + */ + if ((fp = fopen(bootptab, "r")) == NULL) { + report(LOG_ERR, "error opening \"%s\": %s", bootptab, get_errmsg()); + return; + } + /* + * Record file modification time. + */ + if (fstat(fileno(fp), &st) < 0) { + report(LOG_ERR, "fstat: %s", get_errmsg()); + fclose(fp); + return; + } + modtime = st.st_mtime; + + /* + * Entirely erase all hash tables. + */ + hash_Reset(hwhashtable, free_host); + hash_Reset(iphashtable, free_host); + hash_Reset(nmhashtable, free_host); + + nhosts = 0; + nentries = 0; + while (TRUE) { + buflen = sizeof(buffer); + read_entry(fp, buffer, &buflen); + if (buflen == 0) { /* More entries? */ + break; + } + hp = (struct host *) smalloc(sizeof(struct host)); + bzero((char *) hp, sizeof(*hp)); + /* the link count it zero */ + + /* + * Get individual info + */ + if (process_entry(hp, buffer) < 0) { + hp->linkcount = 1; + free_host((hash_datum *) hp); + continue; + } + /* + * If this is not a dummy entry, and the IP or HW + * address is not yet set, try to get them here. + * Dummy entries have . as first char of name. + */ + if (goodname(hp->hostname->string)) { + char *hn = hp->hostname->string; + u_int32 value; + if (hp->flags.iaddr == 0) { + if (lookup_ipa(hn, &value)) { + report(LOG_ERR, "can not get IP addr for %s", hn); + report(LOG_ERR, "(dummy names should start with '.')"); + } else { + hp->iaddr.s_addr = value; + hp->flags.iaddr = TRUE; + } + } + /* Set default subnet mask. */ + if (hp->flags.subnet_mask == 0) { + if (lookup_netmask(hp->iaddr.s_addr, &value)) { + report(LOG_ERR, "can not get netmask for %s", hn); + } else { + hp->subnet_mask.s_addr = value; + hp->flags.subnet_mask = TRUE; + } + } + } + if (hp->flags.iaddr) { + nhosts++; + } + /* Register by HW addr if known. */ + if (hp->flags.htype && hp->flags.haddr) { + /* We will either insert it or free it. */ + hp->linkcount++; + hashcode = hash_HashFunction(hp->haddr, haddrlength(hp->htype)); + if (hash_Insert(hwhashtable, hashcode, hwinscmp, hp, hp) < 0) { + report(LOG_NOTICE, "duplicate %s address: %s", + netname(hp->htype), + haddrtoa(hp->haddr, haddrlength(hp->htype))); + free_host((hash_datum *) hp); + continue; + } + } + /* Register by IP addr if known. */ + if (hp->flags.iaddr) { + hashcode = hash_HashFunction((u_char *) & (hp->iaddr.s_addr), 4); + if (hash_Insert(iphashtable, hashcode, nullcmp, hp, hp) < 0) { + report(LOG_ERR, + "hash_Insert() failed on IP address insertion"); + } else { + /* Just inserted the host struct in a new hash list. */ + hp->linkcount++; + } + } + /* Register by Name (always known) */ + hashcode = hash_HashFunction((u_char *) hp->hostname->string, + strlen(hp->hostname->string)); + if (hash_Insert(nmhashtable, hashcode, nullcmp, + hp->hostname->string, hp) < 0) { + report(LOG_ERR, + "hash_Insert() failed on insertion of hostname: \"%s\"", + hp->hostname->string); + } else { + /* Just inserted the host struct in a new hash list. */ + hp->linkcount++; + } + + nentries++; + } + + fclose(fp); + if (debug) + report(LOG_INFO, "read %d entries (%d hosts) from \"%s\"", + nentries, nhosts, bootptab); + return; +} + + + +/* + * Read an entire host entry from the file pointed to by "fp" and insert it + * into the memory pointed to by "buffer". Leading whitespace and comments + * starting with "#" are ignored (removed). Backslashes (\) always quote + * the next character except that newlines preceded by a backslash cause + * line-continuation onto the next line. The entry is terminated by a + * newline character which is not preceded by a backslash. Sequences + * surrounded by double quotes are taken literally (including newlines, but + * not backslashes). + * + * The "bufsiz" parameter points to an unsigned int which specifies the + * maximum permitted buffer size. Upon return, this value will be replaced + * with the actual length of the entry (not including the null terminator). + * + * This code is a little scary. . . . I don't like using gotos in C + * either, but I first wrote this as an FSM diagram and gotos seemed like + * the easiest way to implement it. Maybe later I'll clean it up. + */ + +PRIVATE void +read_entry(fp, buffer, bufsiz) + FILE *fp; + char *buffer; + unsigned *bufsiz; +{ + int c, length; + + length = 0; + + /* + * Eat whitespace, blank lines, and comment lines. + */ + top: + c = fgetc(fp); + if (c < 0) { + goto done; /* Exit if end-of-file */ + } + if (isspace(c)) { + goto top; /* Skip over whitespace */ + } + if (c == '#') { + while (TRUE) { /* Eat comments after # */ + c = fgetc(fp); + if (c < 0) { + goto done; /* Exit if end-of-file */ + } + if (c == '\n') { + goto top; /* Try to read the next line */ + } + } + } + ungetc(c, fp); /* Other character, push it back to reprocess it */ + + + /* + * Now we're actually reading a data entry. Get each character and + * assemble it into the data buffer, processing special characters like + * double quotes (") and backslashes (\). + */ + + mainloop: + c = fgetc(fp); + switch (c) { + case EOF: + case '\n': + goto done; /* Exit on EOF or newline */ + case '\\': + c = fgetc(fp); /* Backslash, read a new character */ + if (c < 0) { + goto done; /* Exit on EOF */ + } + *buffer++ = c; /* Store the literal character */ + length++; + if (length < *bufsiz - 1) { + goto mainloop; + } else { + goto done; + } + case '"': + *buffer++ = '"'; /* Store double-quote */ + length++; + if (length >= *bufsiz - 1) { + goto done; + } + while (TRUE) { /* Special quote processing loop */ + c = fgetc(fp); + switch (c) { + case EOF: + goto done; /* Exit on EOF . . . */ + case '"': + *buffer++ = '"';/* Store matching quote */ + length++; + if (length < *bufsiz - 1) { + goto mainloop; /* And continue main loop */ + } else { + goto done; + } + case '\\': + if ((c = fgetc(fp)) < 0) { /* Backslash */ + goto done; /* EOF. . . .*/ + } + /* FALLTHROUGH */ + default: + *buffer++ = c; /* Other character, store it */ + length++; + if (length >= *bufsiz - 1) { + goto done; + } + } + } + case ':': + *buffer++ = c; /* Store colons */ + length++; + if (length >= *bufsiz - 1) { + goto done; + } + do { /* But remove whitespace after them */ + c = fgetc(fp); + if ((c < 0) || (c == '\n')) { + goto done; + } + } while (isspace(c)); /* Skip whitespace */ + + if (c == '\\') { /* Backslash quotes next character */ + c = fgetc(fp); + if (c < 0) { + goto done; + } + if (c == '\n') { + goto top; /* Backslash-newline continuation */ + } + } + /* FALLTHROUGH if "other" character */ + default: + *buffer++ = c; /* Store other characters */ + length++; + if (length >= *bufsiz - 1) { + goto done; + } + } + goto mainloop; /* Keep going */ + + done: + *buffer = '\0'; /* Terminate string */ + *bufsiz = length; /* Tell the caller its length */ +} + + + +/* + * Parse out all the various tags and parameters in the host entry pointed + * to by "src". Stuff all the data into the appropriate fields of the + * host structure pointed to by "host". If there is any problem with the + * entry, an error message is reported via report(), no further processing + * is done, and -1 is returned. Successful calls return 0. + * + * (Some errors probably shouldn't be so completely fatal. . . .) + */ + +PRIVATE int +process_entry(host, src) + struct host *host; + char *src; +{ + int retval; + char *msg; + + if (!host || *src == '\0') { + return -1; + } + host->hostname = get_shared_string(&src); +#if 0 + /* Be more liberal for the benefit of dummy tag names. */ + if (!goodname(host->hostname->string)) { + report(LOG_ERR, "bad hostname: \"%s\"", host->hostname->string); + del_string(host->hostname); + return -1; + } +#endif + current_hostname = host->hostname->string; + adjust(&src); + while (TRUE) { + retval = eval_symbol(&src, host); + if (retval == SUCCESS) { + adjust(&src); + continue; + } + if (retval == E_END_OF_ENTRY) { + /* The default subnet mask is set in readtab() */ + return 0; + } + /* Some kind of error. */ + switch (retval) { + case E_SYNTAX_ERROR: + msg = "bad syntax"; + break; + case E_UNKNOWN_SYMBOL: + msg = "unknown symbol"; + break; + case E_BAD_IPADDR: + msg = "bad INET address"; + break; + case E_BAD_HWADDR: + msg = "bad hardware address"; + break; + case E_BAD_LONGWORD: + msg = "bad longword value"; + break; + case E_BAD_HWATYPE: + msg = "bad HW address type"; + break; + case E_BAD_PATHNAME: + msg = "bad pathname (need leading '/')"; + break; + case E_BAD_VALUE: + msg = "bad value"; + break; + default: + msg = "unknown error"; + break; + } /* switch */ + report(LOG_ERR, "in entry named \"%s\", symbol \"%s\": %s", + current_hostname, current_tagname, msg); + return -1; + } +} + + +/* + * Macros for use in the function below: + */ + +/* Parse one INET address stored directly in MEMBER. */ +#define PARSE_IA1(MEMBER) do \ +{ \ + if (optype == OP_BOOLEAN) \ + return E_SYNTAX_ERROR; \ + hp->flags.MEMBER = FALSE; \ + if (optype == OP_ADDITION) { \ + if (prs_inetaddr(symbol, &value) < 0) \ + return E_BAD_IPADDR; \ + hp->MEMBER.s_addr = value; \ + hp->flags.MEMBER = TRUE; \ + } \ +} while (0) + +/* Parse a list of INET addresses pointed to by MEMBER */ +#define PARSE_IAL(MEMBER) do \ +{ \ + if (optype == OP_BOOLEAN) \ + return E_SYNTAX_ERROR; \ + if (hp->flags.MEMBER) { \ + hp->flags.MEMBER = FALSE; \ + assert(hp->MEMBER); \ + del_iplist(hp->MEMBER); \ + hp->MEMBER = NULL; \ + } \ + if (optype == OP_ADDITION) { \ + hp->MEMBER = get_addresses(symbol); \ + if (hp->MEMBER == NULL) \ + return E_SYNTAX_ERROR; \ + hp->flags.MEMBER = TRUE; \ + } \ +} while (0) + +/* Parse a shared string pointed to by MEMBER */ +#define PARSE_STR(MEMBER) do \ +{ \ + if (optype == OP_BOOLEAN) \ + return E_SYNTAX_ERROR; \ + if (hp->flags.MEMBER) { \ + hp->flags.MEMBER = FALSE; \ + assert(hp->MEMBER); \ + del_string(hp->MEMBER); \ + hp->MEMBER = NULL; \ + } \ + if (optype == OP_ADDITION) { \ + hp->MEMBER = get_shared_string(symbol); \ + if (hp->MEMBER == NULL) \ + return E_SYNTAX_ERROR; \ + hp->flags.MEMBER = TRUE; \ + } \ +} while (0) + +/* Parse an unsigned integer value for MEMBER */ +#define PARSE_UINT(MEMBER) do \ +{ \ + if (optype == OP_BOOLEAN) \ + return E_SYNTAX_ERROR; \ + hp->flags.MEMBER = FALSE; \ + if (optype == OP_ADDITION) { \ + value = get_u_long(symbol); \ + hp->MEMBER = value; \ + hp->flags.MEMBER = TRUE; \ + } \ +} while (0) + +/* + * Evaluate the two-character tag symbol pointed to by "symbol" and place + * the data in the structure pointed to by "hp". The pointer pointed to + * by "symbol" is updated to point past the source string (but may not + * point to the next tag entry). + * + * Obviously, this need a few more comments. . . . + */ +PRIVATE int +eval_symbol(symbol, hp) + char **symbol; + struct host *hp; +{ + char tmpstr[MAXSTRINGLEN]; + byte *tmphaddr; + struct symbolmap *symbolptr; + u_int32 value; + int32 timeoff; + int i, numsymbols; + unsigned len; + int optype; /* Indicates boolean, addition, or deletion */ + + eat_whitespace(symbol); + + /* Make sure this is set before returning. */ + current_tagname[0] = (*symbol)[0]; + current_tagname[1] = (*symbol)[1]; + current_tagname[2] = 0; + + if ((*symbol)[0] == '\0') { + return E_END_OF_ENTRY; + } + if ((*symbol)[0] == ':') { + return SUCCESS; + } + if ((*symbol)[0] == 'T') { /* generic symbol */ + (*symbol)++; + value = get_u_long(symbol); + snprintf(current_tagname, sizeof(current_tagname), + "T%d", (int)value); + eat_whitespace(symbol); + if ((*symbol)[0] != '=') { + return E_SYNTAX_ERROR; + } + (*symbol)++; + if (!(hp->generic)) { + hp->generic = (struct shared_bindata *) + smalloc(sizeof(struct shared_bindata)); + } + if (process_generic(symbol, &(hp->generic), (byte) (value & 0xFF))) + return E_SYNTAX_ERROR; + hp->flags.generic = TRUE; + return SUCCESS; + } + /* + * Determine the type of operation to be done on this symbol + */ + switch ((*symbol)[2]) { + case '=': + optype = OP_ADDITION; + break; + case '@': + optype = OP_DELETION; + break; + case ':': + case '\0': + optype = OP_BOOLEAN; + break; + default: + return E_SYNTAX_ERROR; + } + + symbolptr = symbol_list; + numsymbols = sizeof(symbol_list) / sizeof(struct symbolmap); + for (i = 0; i < numsymbols; i++) { + if (((symbolptr->symbol)[0] == (*symbol)[0]) && + ((symbolptr->symbol)[1] == (*symbol)[1])) { + break; + } + symbolptr++; + } + if (i >= numsymbols) { + return E_UNKNOWN_SYMBOL; + } + /* + * Skip past the = or @ character (to point to the data) if this + * isn't a boolean operation. For boolean operations, just skip + * over the two-character tag symbol (and nothing else. . . .). + */ + (*symbol) += (optype == OP_BOOLEAN) ? 2 : 3; + + eat_whitespace(symbol); + + /* The cases below are in order by symbolcode value. */ + switch (symbolptr->symbolcode) { + + case SYM_BOOTFILE: + PARSE_STR(bootfile); + break; + + case SYM_COOKIE_SERVER: + PARSE_IAL(cookie_server); + break; + + case SYM_DOMAIN_SERVER: + PARSE_IAL(domain_server); + break; + + case SYM_GATEWAY: + PARSE_IAL(gateway); + break; + + case SYM_HWADDR: + if (optype == OP_BOOLEAN) + return E_SYNTAX_ERROR; + hp->flags.haddr = FALSE; + if (optype == OP_ADDITION) { + /* Default the HW type to Ethernet */ + if (hp->flags.htype == 0) { + hp->flags.htype = TRUE; + hp->htype = HTYPE_ETHERNET; + } + tmphaddr = prs_haddr(symbol, hp->htype); + if (!tmphaddr) + return E_BAD_HWADDR; + bcopy(tmphaddr, hp->haddr, haddrlength(hp->htype)); + hp->flags.haddr = TRUE; + } + break; + + case SYM_HOMEDIR: + PARSE_STR(homedir); + break; + + case SYM_HTYPE: + if (optype == OP_BOOLEAN) + return E_SYNTAX_ERROR; + hp->flags.htype = FALSE; + if (optype == OP_ADDITION) { + value = 0L; /* Assume an illegal value */ + eat_whitespace(symbol); + if (isdigit(**symbol)) { + value = get_u_long(symbol); + } else { + len = sizeof(tmpstr); + (void) get_string(symbol, tmpstr, &len); + makelower(tmpstr); + numsymbols = sizeof(htnamemap) / + sizeof(struct htypename); + for (i = 0; i < numsymbols; i++) { + if (!strcmp(htnamemap[i].name, tmpstr)) { + break; + } + } + if (i < numsymbols) { + value = htnamemap[i].htype; + } + } + if (value >= hwinfocnt) { + return E_BAD_HWATYPE; + } + hp->htype = (byte) (value & 0xFF); + hp->flags.htype = TRUE; + } + break; + + case SYM_IMPRESS_SERVER: + PARSE_IAL(impress_server); + break; + + case SYM_IPADDR: + PARSE_IA1(iaddr); + break; + + case SYM_LOG_SERVER: + PARSE_IAL(log_server); + break; + + case SYM_LPR_SERVER: + PARSE_IAL(lpr_server); + break; + + case SYM_NAME_SERVER: + PARSE_IAL(name_server); + break; + + case SYM_RLP_SERVER: + PARSE_IAL(rlp_server); + break; + + case SYM_SUBNET_MASK: + PARSE_IA1(subnet_mask); + break; + + case SYM_TIME_OFFSET: + if (optype == OP_BOOLEAN) + return E_SYNTAX_ERROR; + hp->flags.time_offset = FALSE; + if (optype == OP_ADDITION) { + len = sizeof(tmpstr); + (void) get_string(symbol, tmpstr, &len); + if (!strncmp(tmpstr, "auto", 4)) { + hp->time_offset = secondswest; + } else { + if (sscanf(tmpstr, "%d", (int*)&timeoff) != 1) + return E_BAD_LONGWORD; + hp->time_offset = timeoff; + } + hp->flags.time_offset = TRUE; + } + break; + + case SYM_TIME_SERVER: + PARSE_IAL(time_server); + break; + + case SYM_VENDOR_MAGIC: + if (optype == OP_BOOLEAN) + return E_SYNTAX_ERROR; + hp->flags.vm_cookie = FALSE; + if (optype == OP_ADDITION) { + if (strncmp(*symbol, "auto", 4)) { + /* The string is not "auto" */ + if (!strncmp(*symbol, "rfc", 3)) { + bcopy(vm_rfc1048, hp->vm_cookie, 4); + } else if (!strncmp(*symbol, "cmu", 3)) { + bcopy(vm_cmu, hp->vm_cookie, 4); + } else { + if (!isdigit(**symbol)) + return E_BAD_IPADDR; + if (prs_inetaddr(symbol, &value) < 0) + return E_BAD_IPADDR; + bcopy(&value, hp->vm_cookie, 4); + } + hp->flags.vm_cookie = TRUE; + } + } + break; + + case SYM_SIMILAR_ENTRY: + switch (optype) { + case OP_ADDITION: + fill_defaults(hp, symbol); + break; + default: + return E_SYNTAX_ERROR; + } + break; + + case SYM_NAME_SWITCH: + switch (optype) { + case OP_ADDITION: + return E_SYNTAX_ERROR; + case OP_DELETION: + hp->flags.send_name = FALSE; + hp->flags.name_switch = FALSE; + break; + case OP_BOOLEAN: + hp->flags.send_name = TRUE; + hp->flags.name_switch = TRUE; + break; + } + break; + + case SYM_BOOTSIZE: + switch (optype) { + case OP_ADDITION: + if (!strncmp(*symbol, "auto", 4)) { + hp->flags.bootsize = TRUE; + hp->flags.bootsize_auto = TRUE; + } else { + hp->bootsize = (unsigned int) get_u_long(symbol); + hp->flags.bootsize = TRUE; + hp->flags.bootsize_auto = FALSE; + } + break; + case OP_DELETION: + hp->flags.bootsize = FALSE; + break; + case OP_BOOLEAN: + hp->flags.bootsize = TRUE; + hp->flags.bootsize_auto = TRUE; + break; + } + break; + + case SYM_BOOT_SERVER: + PARSE_IA1(bootserver); + break; + + case SYM_TFTPDIR: + PARSE_STR(tftpdir); + if ((hp->tftpdir != NULL) && + (hp->tftpdir->string[0] != '/')) + return E_BAD_PATHNAME; + break; + + case SYM_DUMP_FILE: + PARSE_STR(dump_file); + break; + + case SYM_DOMAIN_NAME: + PARSE_STR(domain_name); + break; + + case SYM_SWAP_SERVER: + PARSE_IA1(swap_server); + break; + + case SYM_ROOT_PATH: + PARSE_STR(root_path); + break; + + case SYM_EXTEN_FILE: + PARSE_STR(exten_file); + break; + + case SYM_REPLY_ADDR: + PARSE_IA1(reply_addr); + break; + + case SYM_NIS_DOMAIN: + PARSE_STR(nis_domain); + break; + + case SYM_NIS_SERVER: + PARSE_IAL(nis_server); + break; + + case SYM_NTP_SERVER: + PARSE_IAL(ntp_server); + break; + +#ifdef YORK_EX_OPTION + case SYM_EXEC_FILE: + PARSE_STR(exec_file); + break; +#endif + + case SYM_MSG_SIZE: + PARSE_UINT(msg_size); + if (hp->msg_size < BP_MINPKTSZ || + hp->msg_size > MAX_MSG_SIZE) + return E_BAD_VALUE; + break; + + case SYM_MIN_WAIT: + PARSE_UINT(min_wait); + break; + + /* XXX - Add new tags here */ + + default: + return E_UNKNOWN_SYMBOL; + + } /* switch symbolcode */ + + return SUCCESS; +} +#undef PARSE_IA1 +#undef PARSE_IAL +#undef PARSE_STR + + + + +/* + * Read a string from the buffer indirectly pointed to through "src" and + * move it into the buffer pointed to by "dest". A pointer to the maximum + * allowable length of the string (including null-terminator) is passed as + * "length". The actual length of the string which was read is returned in + * the unsigned integer pointed to by "length". This value is the same as + * that which would be returned by applying the strlen() function on the + * destination string (i.e the terminating null is not counted as a + * character). Trailing whitespace is removed from the string. For + * convenience, the function returns the new value of "dest". + * + * The string is read until the maximum number of characters, an unquoted + * colon (:), or a null character is read. The return string in "dest" is + * null-terminated. + */ + +PRIVATE char * +get_string(src, dest, length) + char **src, *dest; + unsigned *length; +{ + int n, len, quoteflag; + + quoteflag = FALSE; + n = 0; + len = *length - 1; + while ((n < len) && (**src)) { + if (!quoteflag && (**src == ':')) { + break; + } + if (**src == '"') { + (*src)++; + quoteflag = !quoteflag; + continue; + } + if (**src == '\\') { + (*src)++; + if (!**src) { + break; + } + } + *dest++ = *(*src)++; + n++; + } + + /* + * Remove that troublesome trailing whitespace. . . + */ + while ((n > 0) && isspace(dest[-1])) { + dest--; + n--; + } + + *dest = '\0'; + *length = n; + return dest; +} + + + +/* + * Read the string indirectly pointed to by "src", update the caller's + * pointer, and return a pointer to a malloc'ed shared_string structure + * containing the string. + * + * The string is read using the same rules as get_string() above. + */ + +PRIVATE struct shared_string * +get_shared_string(src) + char **src; +{ + char retstring[MAXSTRINGLEN]; + struct shared_string *s; + unsigned length; + + length = sizeof(retstring); + (void) get_string(src, retstring, &length); + + s = (struct shared_string *) smalloc(sizeof(struct shared_string) + + length); + s->linkcount = 1; + strcpy(s->string, retstring); + + return s; +} + + + +/* + * Load RFC1048 generic information directly into a memory buffer. + * + * "src" indirectly points to the ASCII representation of the generic data. + * "dest" points to a string structure which is updated to point to a new + * string with the new data appended to the old string. The old string is + * freed. + * + * The given tag value is inserted with the new data. + * + * The data may be represented as either a stream of hexadecimal numbers + * representing bytes (any or all bytes may optionally start with '0x' and + * be separated with periods ".") or as a quoted string of ASCII + * characters (the quotes are required). + */ + +PRIVATE int +process_generic(src, dest, tagvalue) + char **src; + struct shared_bindata **dest; + u_int tagvalue; +{ + byte tmpbuf[MAXBUFLEN]; + byte *str; + struct shared_bindata *bdata; + u_int newlength, oldlength; + + str = tmpbuf; + *str++ = (tagvalue & 0xFF); /* Store tag value */ + str++; /* Skip over length field */ + if ((*src)[0] == '"') { /* ASCII data */ + newlength = sizeof(tmpbuf) - 2; /* Set maximum allowed length */ + (void) get_string(src, (char *) str, &newlength); + newlength++; /* null terminator */ + } else { /* Numeric data */ + newlength = 0; + while (newlength < sizeof(tmpbuf) - 2) { + if (interp_byte(src, str++) < 0) + break; + newlength++; + if (**src == '.') { + (*src)++; + } + } + } + if ((*src)[0] != ':') + return -1; + + tmpbuf[1] = (newlength & 0xFF); + oldlength = ((*dest)->length); + bdata = (struct shared_bindata *) smalloc(sizeof(struct shared_bindata) + + oldlength + newlength + 1); + if (oldlength > 0) { + bcopy((*dest)->data, bdata->data, oldlength); + } + bcopy(tmpbuf, bdata->data + oldlength, newlength + 2); + bdata->length = oldlength + newlength + 2; + bdata->linkcount = 1; + if (*dest) { + del_bindata(*dest); + } + *dest = bdata; + return 0; +} + + + +/* + * Verify that the given string makes sense as a hostname (according to + * Appendix 1, page 29 of RFC882). + * + * Return TRUE for good names, FALSE otherwise. + */ + +PRIVATE boolean +goodname(hostname) + register char *hostname; +{ + do { + if (!isalpha(*hostname++)) { /* First character must be a letter */ + return FALSE; + } + while (isalnum(*hostname) || + (*hostname == '-') || + (*hostname == '_') ) + { + hostname++; /* Alphanumeric or a hyphen */ + } + if (!isalnum(hostname[-1])) { /* Last must be alphanumeric */ + return FALSE; + } + if (*hostname == '\0') {/* Done? */ + return TRUE; + } + } while (*hostname++ == '.'); /* Dot, loop for next label */ + + return FALSE; /* If it's not a dot, lose */ +} + + + +/* + * Null compare function -- always returns FALSE so an element is always + * inserted into a hash table (i.e. there is never a collision with an + * existing element). + */ + +PRIVATE boolean +nullcmp(d1, d2) + hash_datum *d1, *d2; +{ + return FALSE; +} + + +/* + * Function for comparing a string with the hostname field of a host + * structure. + */ + +boolean +nmcmp(d1, d2) + hash_datum *d1, *d2; +{ + char *name = (char *) d1; /* XXX - OK? */ + struct host *hp = (struct host *) d2; + + return !strcmp(name, hp->hostname->string); +} + + +/* + * Compare function to determine whether two hardware addresses are + * equivalent. Returns TRUE if "host1" and "host2" are equivalent, FALSE + * otherwise. + * + * If the hardware addresses of "host1" and "host2" are identical, but + * they are on different IP subnets, this function returns FALSE. + * + * This function is used when inserting elements into the hardware address + * hash table. + */ + +PRIVATE boolean +hwinscmp(d1, d2) + hash_datum *d1, *d2; +{ + struct host *host1 = (struct host *) d1; + struct host *host2 = (struct host *) d2; + + if (host1->htype != host2->htype) { + return FALSE; + } + if (bcmp(host1->haddr, host2->haddr, haddrlength(host1->htype))) { + return FALSE; + } + /* XXX - Is the subnet_mask field set yet? */ + if ((host1->subnet_mask.s_addr) == (host2->subnet_mask.s_addr)) { + if (((host1->iaddr.s_addr) & (host1->subnet_mask.s_addr)) != + ((host2->iaddr.s_addr) & (host2->subnet_mask.s_addr))) + { + return FALSE; + } + } + return TRUE; +} + + +/* + * Macros for use in the function below: + */ + +#define DUP_COPY(MEMBER) do \ +{ \ + if (!hp->flags.MEMBER) { \ + if ((hp->flags.MEMBER = hp2->flags.MEMBER) != 0) { \ + hp->MEMBER = hp2->MEMBER; \ + } \ + } \ +} while (0) + +#define DUP_LINK(MEMBER) do \ +{ \ + if (!hp->flags.MEMBER) { \ + if ((hp->flags.MEMBER = hp2->flags.MEMBER) != 0) { \ + assert(hp2->MEMBER); \ + hp->MEMBER = hp2->MEMBER; \ + (hp->MEMBER->linkcount)++; \ + } \ + } \ +} while (0) + +/* + * Process the "similar entry" symbol. + * + * The host specified as the value of the "tc" symbol is used as a template + * for the current host entry. Symbol values not explicitly set in the + * current host entry are inferred from the template entry. + */ +PRIVATE void +fill_defaults(hp, src) + struct host *hp; + char **src; +{ + unsigned int tlen, hashcode; + struct host *hp2; + char tstring[MAXSTRINGLEN]; + + tlen = sizeof(tstring); + (void) get_string(src, tstring, &tlen); + hashcode = hash_HashFunction((u_char *) tstring, tlen); + hp2 = (struct host *) hash_Lookup(nmhashtable, hashcode, nmcmp, tstring); + + if (hp2 == NULL) { + report(LOG_ERR, "can't find tc=\"%s\"", tstring); + return; + } + DUP_LINK(bootfile); + DUP_LINK(cookie_server); + DUP_LINK(domain_server); + DUP_LINK(gateway); + /* haddr not copied */ + DUP_LINK(homedir); + DUP_COPY(htype); + + DUP_LINK(impress_server); + /* iaddr not copied */ + DUP_LINK(log_server); + DUP_LINK(lpr_server); + DUP_LINK(name_server); + DUP_LINK(rlp_server); + + DUP_COPY(subnet_mask); + DUP_COPY(time_offset); + DUP_LINK(time_server); + + if (!hp->flags.vm_cookie) { + if ((hp->flags.vm_cookie = hp2->flags.vm_cookie)) { + bcopy(hp2->vm_cookie, hp->vm_cookie, 4); + } + } + if (!hp->flags.name_switch) { + if ((hp->flags.name_switch = hp2->flags.name_switch)) { + hp->flags.send_name = hp2->flags.send_name; + } + } + if (!hp->flags.bootsize) { + if ((hp->flags.bootsize = hp2->flags.bootsize)) { + hp->flags.bootsize_auto = hp2->flags.bootsize_auto; + hp->bootsize = hp2->bootsize; + } + } + DUP_COPY(bootserver); + + DUP_LINK(tftpdir); + DUP_LINK(dump_file); + DUP_LINK(domain_name); + + DUP_COPY(swap_server); + DUP_LINK(root_path); + DUP_LINK(exten_file); + + DUP_COPY(reply_addr); + + DUP_LINK(nis_domain); + DUP_LINK(nis_server); + DUP_LINK(ntp_server); + +#ifdef YORK_EX_OPTION + DUP_LINK(exec_file); +#endif + + DUP_COPY(msg_size); + DUP_COPY(min_wait); + + /* XXX - Add new tags here */ + + DUP_LINK(generic); + +} +#undef DUP_COPY +#undef DUP_LINK + + + +/* + * This function adjusts the caller's pointer to point just past the + * first-encountered colon. If it runs into a null character, it leaves + * the pointer pointing to it. + */ + +PRIVATE void +adjust(s) + char **s; +{ + register char *t; + + t = *s; + while (*t && (*t != ':')) { + t++; + } + if (*t) { + t++; + } + *s = t; +} + + + + +/* + * This function adjusts the caller's pointer to point to the first + * non-whitespace character. If it runs into a null character, it leaves + * the pointer pointing to it. + */ + +PRIVATE void +eat_whitespace(s) + char **s; +{ + register char *t; + + t = *s; + while (*t && isspace(*t)) { + t++; + } + *s = t; +} + + + +/* + * This function converts the given string to all lowercase. + */ + +PRIVATE void +makelower(s) + char *s; +{ + while (*s) { + if (isupper(*s)) { + *s = tolower(*s); + } + s++; + } +} + + + +/* + * + * N O T E : + * + * In many of the functions which follow, a parameter such as "src" or + * "symbol" is passed as a pointer to a pointer to something. This is + * done for the purpose of letting the called function update the + * caller's copy of the parameter (i.e. to effect call-by-reference + * parameter passing). The value of the actual parameter is only used + * to locate the real parameter of interest and then update this indirect + * parameter. + * + * I'm sure somebody out there won't like this. . . . + * (Yea, because it usually makes code slower... -gwr) + * + */ + + + +/* + * "src" points to a character pointer which points to an ASCII string of + * whitespace-separated IP addresses. A pointer to an in_addr_list + * structure containing the list of addresses is returned. NULL is + * returned if no addresses were found at all. The pointer pointed to by + * "src" is updated to point to the first non-address (illegal) character. + */ + +PRIVATE struct in_addr_list * +get_addresses(src) + char **src; +{ + struct in_addr tmpaddrlist[MAXINADDRS]; + struct in_addr *address1, *address2; + struct in_addr_list *result; + unsigned addrcount, totalsize; + + address1 = tmpaddrlist; + for (addrcount = 0; addrcount < MAXINADDRS; addrcount++) { + while (isspace(**src) || (**src == ',')) { + (*src)++; + } + if (!**src) { /* Quit if nothing more */ + break; + } + if (prs_inetaddr(src, &(address1->s_addr)) < 0) { + break; + } + address1++; /* Point to next address slot */ + } + if (addrcount < 1) { + result = NULL; + } else { + totalsize = sizeof(struct in_addr_list) + + (addrcount - 1) * sizeof(struct in_addr); + result = (struct in_addr_list *) smalloc(totalsize); + result->linkcount = 1; + result->addrcount = addrcount; + address1 = tmpaddrlist; + address2 = result->addr; + for (; addrcount > 0; addrcount--) { + address2->s_addr = address1->s_addr; + address1++; + address2++; + } + } + return result; +} + + + +/* + * prs_inetaddr(src, result) + * + * "src" is a value-result parameter; the pointer it points to is updated + * to point to the next data position. "result" points to an unsigned long + * in which an address is returned. + * + * This function parses the IP address string in ASCII "dot notation" pointed + * to by (*src) and places the result (in network byte order) in the unsigned + * long pointed to by "result". For malformed addresses, -1 is returned, + * (*src) points to the first illegal character, and the unsigned long pointed + * to by "result" is unchanged. Successful calls return 0. + */ + +PRIVATE int +prs_inetaddr(src, result) + char **src; + u_int32 *result; +{ + char tmpstr[MAXSTRINGLEN]; + register u_int32 value; + u_int32 parts[4], *pp; + int n; + char *s, *t; + + /* Leading alpha char causes IP addr lookup. */ + if (isalpha(**src)) { + /* Lookup IP address. */ + s = *src; + t = tmpstr; + while ((isalnum(*s) || (*s == '.') || + (*s == '-') || (*s == '_') ) && + (t < &tmpstr[MAXSTRINGLEN - 1]) ) + *t++ = *s++; + *t = '\0'; + *src = s; + + n = lookup_ipa(tmpstr, result); + if (n < 0) + report(LOG_ERR, "can not get IP addr for %s", tmpstr); + return n; + } + + /* + * Parse an address in Internet format: + * a.b.c.d + * a.b.c (with c treated as 16-bits) + * a.b (with b treated as 24 bits) + */ + pp = parts; + loop: + /* If it's not a digit, return error. */ + if (!isdigit(**src)) + return -1; + *pp++ = get_u_long(src); + if (**src == '.') { + if (pp < (parts + 4)) { + (*src)++; + goto loop; + } + return (-1); + } +#if 0 + /* This is handled by the caller. */ + if (**src && !(isspace(**src) || (**src == ':'))) { + return (-1); + } +#endif + + /* + * Construct the address according to + * the number of parts specified. + */ + n = pp - parts; + switch (n) { + case 1: /* a -- 32 bits */ + value = parts[0]; + break; + case 2: /* a.b -- 8.24 bits */ + value = (parts[0] << 24) | (parts[1] & 0xFFFFFF); + break; + case 3: /* a.b.c -- 8.8.16 bits */ + value = (parts[0] << 24) | ((parts[1] & 0xFF) << 16) | + (parts[2] & 0xFFFF); + break; + case 4: /* a.b.c.d -- 8.8.8.8 bits */ + value = (parts[0] << 24) | ((parts[1] & 0xFF) << 16) | + ((parts[2] & 0xFF) << 8) | (parts[3] & 0xFF); + break; + default: + return (-1); + } + *result = htonl(value); + return (0); +} + + + +/* + * "src" points to a pointer which in turn points to a hexadecimal ASCII + * string. This string is interpreted as a hardware address and returned + * as a pointer to the actual hardware address, represented as an array of + * bytes. + * + * The ASCII string must have the proper number of digits for the specified + * hardware type (e.g. twelve digits for a 48-bit Ethernet address). + * Two-digit sequences (bytes) may be separated with periods (.) and/or + * prefixed with '0x' for readability, but this is not required. + * + * For bad addresses, the pointer which "src" points to is updated to point + * to the start of the first two-digit sequence which was bad, and the + * function returns a NULL pointer. + */ + +PRIVATE byte * +prs_haddr(src, htype) + char **src; + u_int htype; +{ + static byte haddr[MAXHADDRLEN]; + byte *hap; + char tmpstr[MAXSTRINGLEN]; + u_int tmplen; + unsigned hal; + char *p; + + hal = haddrlength(htype); /* Get length of this address type */ + if (hal <= 0) { + report(LOG_ERR, "Invalid addr type for HW addr parse"); + return NULL; + } + tmplen = sizeof(tmpstr); + get_string(src, tmpstr, &tmplen); + p = tmpstr; + + /* If it's a valid host name, try to lookup the HW address. */ + if (goodname(p)) { + /* Lookup Hardware Address for hostname. */ + if ((hap = lookup_hwa(p, htype)) != NULL) + return hap; /* success */ + report(LOG_ERR, "Add 0x prefix if hex value starts with A-F"); + /* OK, assume it must be numeric. */ + } + + hap = haddr; + while (hap < haddr + hal) { + if ((*p == '.') || (*p == ':')) + p++; + if (interp_byte(&p, hap++) < 0) { + return NULL; + } + } + return haddr; +} + + + +/* + * "src" is a pointer to a character pointer which in turn points to a + * hexadecimal ASCII representation of a byte. This byte is read, the + * character pointer is updated, and the result is deposited into the + * byte pointed to by "retbyte". + * + * The usual '0x' notation is allowed but not required. The number must be + * a two digit hexadecimal number. If the number is invalid, "src" and + * "retbyte" are left untouched and -1 is returned as the function value. + * Successful calls return 0. + */ + +PRIVATE int +interp_byte(src, retbyte) + char **src; + byte *retbyte; +{ + int v; + + if ((*src)[0] == '0' && + ((*src)[1] == 'x' || + (*src)[1] == 'X')) { + (*src) += 2; /* allow 0x for hex, but don't require it */ + } + if (!isxdigit((*src)[0]) || !isxdigit((*src)[1])) { + return -1; + } + if (sscanf(*src, "%2x", &v) != 1) { + return -1; + } + (*src) += 2; + *retbyte = (byte) (v & 0xFF); + return 0; +} + + + +/* + * The parameter "src" points to a character pointer which points to an + * ASCII string representation of an unsigned number. The number is + * returned as an unsigned long and the character pointer is updated to + * point to the first illegal character. + */ + +PRIVATE u_int32 +get_u_long(src) + char **src; +{ + register u_int32 value, base; + char c; + + /* + * Collect number up to first illegal character. Values are specified + * as for C: 0x=hex, 0=octal, other=decimal. + */ + value = 0; + base = 10; + if (**src == '0') { + base = 8; + (*src)++; + } + if (**src == 'x' || **src == 'X') { + base = 16; + (*src)++; + } + while ((c = **src)) { + if (isdigit(c)) { + value = (value * base) + (c - '0'); + (*src)++; + continue; + } + if (base == 16 && isxdigit(c)) { + value = (value << 4) + ((c & ~32) + 10 - 'A'); + (*src)++; + continue; + } + break; + } + return value; +} + + + +/* + * Routines for deletion of data associated with the main data structure. + */ + + +/* + * Frees the entire host data structure given. Does nothing if the passed + * pointer is NULL. + */ + +PRIVATE void +free_host(hmp) + hash_datum *hmp; +{ + struct host *hostptr = (struct host *) hmp; + if (hostptr == NULL) + return; + assert(hostptr->linkcount > 0); + if (--(hostptr->linkcount)) + return; /* Still has references */ + del_iplist(hostptr->cookie_server); + del_iplist(hostptr->domain_server); + del_iplist(hostptr->gateway); + del_iplist(hostptr->impress_server); + del_iplist(hostptr->log_server); + del_iplist(hostptr->lpr_server); + del_iplist(hostptr->name_server); + del_iplist(hostptr->rlp_server); + del_iplist(hostptr->time_server); + del_iplist(hostptr->nis_server); + del_iplist(hostptr->ntp_server); + + /* + * XXX - Add new tags here + * (if the value is an IP list) + */ + + del_string(hostptr->hostname); + del_string(hostptr->homedir); + del_string(hostptr->bootfile); + del_string(hostptr->tftpdir); + del_string(hostptr->root_path); + del_string(hostptr->domain_name); + del_string(hostptr->dump_file); + del_string(hostptr->exten_file); + del_string(hostptr->nis_domain); + +#ifdef YORK_EX_OPTION + del_string(hostptr->exec_file); +#endif + + /* + * XXX - Add new tags here + * (if it is a shared string) + */ + + del_bindata(hostptr->generic); + free((char *) hostptr); +} + + + +/* + * Decrements the linkcount on the given IP address data structure. If the + * linkcount goes to zero, the memory associated with the data is freed. + */ + +PRIVATE void +del_iplist(iplist) + struct in_addr_list *iplist; +{ + if (iplist) { + if (!(--(iplist->linkcount))) { + free((char *) iplist); + } + } +} + + + +/* + * Decrements the linkcount on a string data structure. If the count + * goes to zero, the memory associated with the string is freed. Does + * nothing if the passed pointer is NULL. + */ + +PRIVATE void +del_string(stringptr) + struct shared_string *stringptr; +{ + if (stringptr) { + if (!(--(stringptr->linkcount))) { + free((char *) stringptr); + } + } +} + + + +/* + * Decrements the linkcount on a shared_bindata data structure. If the + * count goes to zero, the memory associated with the data is freed. Does + * nothing if the passed pointer is NULL. + */ + +PRIVATE void +del_bindata(dataptr) + struct shared_bindata *dataptr; +{ + if (dataptr) { + if (!(--(dataptr->linkcount))) { + free((char *) dataptr); + } + } +} + + + + +/* smalloc() -- safe malloc() + * + * Always returns a valid pointer (if it returns at all). The allocated + * memory is initialized to all zeros. If malloc() returns an error, a + * message is printed using the report() function and the program aborts + * with a status of 1. + */ + +PRIVATE char * +smalloc(nbytes) + unsigned nbytes; +{ + char *retvalue; + + retvalue = malloc(nbytes); + if (!retvalue) { + report(LOG_ERR, "malloc() failure -- exiting"); + exit(1); + } + bzero(retvalue, nbytes); + return retvalue; +} + + +/* + * Compare function to determine whether two hardware addresses are + * equivalent. Returns TRUE if "host1" and "host2" are equivalent, FALSE + * otherwise. + * + * This function is used when retrieving elements from the hardware address + * hash table. + */ + +boolean +hwlookcmp(d1, d2) + hash_datum *d1, *d2; +{ + struct host *host1 = (struct host *) d1; + struct host *host2 = (struct host *) d2; + + if (host1->htype != host2->htype) { + return FALSE; + } + if (bcmp(host1->haddr, host2->haddr, haddrlength(host1->htype))) { + return FALSE; + } + return TRUE; +} + + +/* + * Compare function for doing IP address hash table lookup. + */ + +boolean +iplookcmp(d1, d2) + hash_datum *d1, *d2; +{ + struct host *host1 = (struct host *) d1; + struct host *host2 = (struct host *) d2; + + return (host1->iaddr.s_addr == host2->iaddr.s_addr); +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/readfile.h b/libexec/bootpd/readfile.h new file mode 100644 index 0000000..be9cada --- /dev/null +++ b/libexec/bootpd/readfile.h @@ -0,0 +1,11 @@ +/* readfile.h */ +/* $FreeBSD$ */ + +#include "bptypes.h" +#include "hash.h" + +extern boolean hwlookcmp(hash_datum *, hash_datum *); +extern boolean iplookcmp(hash_datum *, hash_datum *); +extern boolean nmcmp(hash_datum *, hash_datum *); +extern void readtab(int); +extern void rdtab_init(void); diff --git a/libexec/bootpd/report.c b/libexec/bootpd/report.c new file mode 100644 index 0000000..290e19e --- /dev/null +++ b/libexec/bootpd/report.c @@ -0,0 +1,138 @@ +/* $FreeBSD$ */ + +/* + * report() - calls syslog + */ + +#include <stdarg.h> + +#include <stdio.h> +#include <syslog.h> +#include <string.h> +#include <errno.h> + +#include "report.h" + +#ifndef LOG_NDELAY +#define LOG_NDELAY 0 +#endif +#ifndef LOG_DAEMON +#define LOG_DAEMON 0 +#endif +#ifndef LOG_BOOTP +#define LOG_BOOTP LOG_DAEMON +#endif + +extern int debug; +extern char *progname; + +/* + * This is initialized so you get stderr until you call + * report_init() + */ +static int stderr_only = 1; + +void +report_init(nolog) + int nolog; +{ + stderr_only = nolog; +#ifdef SYSLOG + if (!stderr_only) { + openlog(progname, LOG_PID | LOG_NDELAY, LOG_BOOTP); + } +#endif +} + +/* + * This routine reports errors and such via stderr and syslog() if + * appopriate. It just helps avoid a lot of "#ifdef SYSLOG" constructs + * from being scattered throughout the code. + * + * The syntax is identical to syslog(3), but %m is not considered special + * for output to stderr (i.e. you'll see "%m" in the output. . .). Also, + * control strings should normally end with \n since newlines aren't + * automatically generated for stderr output (whereas syslog strips out all + * newlines and adds its own at the end). + */ + +static char *levelnames[] = { +#ifdef LOG_SALERT + "level(0): ", + "alert(1): ", + "alert(2): ", + "emerg(3): ", + "error(4): ", + "crit(5): ", + "warn(6): ", + "note(7): ", + "info(8): ", + "debug(9): ", + "level(?): " +#else + "emerg(0): ", + "alert(1): ", + "crit(2): ", + "error(3): ", + "warn(4): ", + "note(5): ", + "info(6): ", + "debug(7): ", + "level(?): " +#endif +}; +static int numlevels = sizeof(levelnames) / sizeof(levelnames[0]); + + +/* + * Print a log message using syslog(3) and/or stderr. + * The message passed in should not include a newline. + */ +void +report(int priority, const char *fmt,...) +{ + va_list ap; + static char buf[128]; + + if ((priority < 0) || (priority >= numlevels)) { + priority = numlevels - 1; + } + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + /* + * Print the message + */ + if (stderr_only || (debug > 2)) { + fprintf(stderr, "%s: %s %s\n", + progname, levelnames[priority], buf); + } +#ifdef SYSLOG + if (!stderr_only) + syslog((priority | LOG_BOOTP), "%s", buf); +#endif +} + + + +/* + * Return pointer to static string which gives full filesystem error message. + */ +const char * +get_errmsg() +{ + return strerror(errno); +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/report.h b/libexec/bootpd/report.h new file mode 100644 index 0000000..55cec5c --- /dev/null +++ b/libexec/bootpd/report.h @@ -0,0 +1,6 @@ +/* report.h */ +/* $FreeBSD$ */ + +extern void report_init(int nolog); +extern void report(int, const char *, ...) __printflike(2, 3); +extern const char *get_errmsg(void); diff --git a/libexec/bootpd/rtmsg.c b/libexec/bootpd/rtmsg.c new file mode 100644 index 0000000..09886fd --- /dev/null +++ b/libexec/bootpd/rtmsg.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 1984, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1994 + * Geoffrey M. Rehmet, All rights reserved. + * + * This code is derived from software which forms part of the 4.4-Lite + * Berkeley software distribution, which was in derived from software + * contributed to Berkeley by Sun Microsystems, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * from arp.c 8.2 (Berkeley) 1/2/94 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +/* + * Verify that we are at least 4.4 BSD + */ +#if defined(BSD) +#if BSD >= 199306 + +#include <sys/socket.h> +#include <sys/filio.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <arpa/inet.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "report.h" + + +static int rtmsg(int); + +static int s = -1; /* routing socket */ + + +/* + * Open the routing socket + */ +static void getsocket () { + if (s < 0) { + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s < 0) { + report(LOG_ERR, "socket %s", strerror(errno)); + exit(1); + } + } else { + /* + * Drain the socket of any unwanted routing messages. + */ + int n; + char buf[512]; + + ioctl(s, FIONREAD, &n); + while (n > 0) { + read(s, buf, sizeof buf); + ioctl(s, FIONREAD, &n); + } + } +} + +static struct sockaddr_in so_mask = {8, 0, 0, { 0xffffffff}}; +static struct sockaddr_in blank_sin = {sizeof(blank_sin), AF_INET }, sin_m; +static struct sockaddr_dl blank_sdl = {sizeof(blank_sdl), AF_LINK }, sdl_m; +static int expire_time, flags, doing_proxy; +static struct { + struct rt_msghdr m_rtm; + char m_space[512]; +} m_rtmsg; + +/* + * Set an individual arp entry + */ +int bsd_arp_set(ia, eaddr, len) + struct in_addr *ia; + char *eaddr; + int len; +{ + register struct sockaddr_in *sin = &sin_m; + register struct sockaddr_dl *sdl; + register struct rt_msghdr *rtm = &(m_rtmsg.m_rtm); + u_char *ea; + struct timespec tp; + int op = RTM_ADD; + + getsocket(); + sdl_m = blank_sdl; + sin_m = blank_sin; + sin->sin_addr = *ia; + + ea = (u_char *)LLADDR(&sdl_m); + bcopy(eaddr, ea, len); + sdl_m.sdl_alen = len; + doing_proxy = flags = expire_time = 0; + + /* make arp entry temporary */ + clock_gettime(CLOCK_MONOTONIC, &tp); + expire_time = tp.tv_sec + 20 * 60; + +tryagain: + if (rtmsg(RTM_GET) < 0) { + report(LOG_WARNING, "rtmget: %s", strerror(errno)); + return (1); + } + sin = (struct sockaddr_in *)(rtm + 1); + sdl = (struct sockaddr_dl *)(sin->sin_len + (char *)sin); + if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr) { + if (sdl->sdl_family == AF_LINK && + !(rtm->rtm_flags & RTF_GATEWAY)) switch (sdl->sdl_type) { + case IFT_ETHER: case IFT_FDDI: case IFT_ISO88023: + case IFT_ISO88024: case IFT_ISO88025: + op = RTM_CHANGE; + goto overwrite; + } + if (doing_proxy == 0) { + report(LOG_WARNING, "set: can only proxy for %s\n", + inet_ntoa(sin->sin_addr)); + return (1); + } + goto tryagain; + } +overwrite: + if (sdl->sdl_family != AF_LINK) { + report(LOG_WARNING, + "cannot intuit interface index and type for %s\n", + inet_ntoa(sin->sin_addr)); + return (1); + } + sdl_m.sdl_type = sdl->sdl_type; + sdl_m.sdl_index = sdl->sdl_index; + return (rtmsg(op)); +} + + +static int rtmsg(cmd) + int cmd; +{ + static int seq; + int rlen; + register struct rt_msghdr *rtm = &m_rtmsg.m_rtm; + register char *cp = m_rtmsg.m_space; + register int l; + + errno = 0; + bzero((char *)&m_rtmsg, sizeof(m_rtmsg)); + rtm->rtm_flags = flags; + rtm->rtm_version = RTM_VERSION; + + switch (cmd) { + default: + report(LOG_ERR, "set_arp: internal wrong cmd - exiting"); + exit(1); + case RTM_ADD: + case RTM_CHANGE: + rtm->rtm_addrs |= RTA_GATEWAY; + rtm->rtm_rmx.rmx_expire = expire_time; + rtm->rtm_inits = RTV_EXPIRE; + rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA); + if (doing_proxy) { + rtm->rtm_addrs |= RTA_NETMASK; + rtm->rtm_flags &= ~RTF_HOST; + } + /* FALLTHROUGH */ + case RTM_GET: + rtm->rtm_addrs |= RTA_DST; + } +#define NEXTADDR(w, s) \ + if (rtm->rtm_addrs & (w)) { \ + bcopy((char *)&s, cp, sizeof(s)); cp += sizeof(s);} + + NEXTADDR(RTA_DST, sin_m); + NEXTADDR(RTA_GATEWAY, sdl_m); + NEXTADDR(RTA_NETMASK, so_mask); + + rtm->rtm_msglen = cp - (char *)&m_rtmsg; + + l = rtm->rtm_msglen; + rtm->rtm_seq = ++seq; + rtm->rtm_type = cmd; + if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) { + if ((errno != ESRCH) && !(errno == EEXIST && cmd == RTM_ADD)){ + report(LOG_WARNING, "writing to routing socket: %s", + strerror(errno)); + return (-1); + } + } + do { + l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } while (l > 0 && (rtm->rtm_type != cmd || rtm->rtm_seq != seq || rtm->rtm_pid != getpid())); + if (l < 0) + report(LOG_WARNING, "arp: read from routing socket: %s\n", + strerror(errno)); + return (0); +} + +#endif /* BSD */ +#endif /* BSD >= 199306 */ diff --git a/libexec/bootpd/syslog.conf b/libexec/bootpd/syslog.conf new file mode 100644 index 0000000..2c135af --- /dev/null +++ b/libexec/bootpd/syslog.conf @@ -0,0 +1,63 @@ +# +# syslog configuration file for SunOS 4.X +# (modified to do local2 separately) +# +# This file is processed by m4 so be careful to quote (`') names +# that match m4 reserved words. Also, within ifdef's, arguments +# containing commas must be quoted. +# +# Note: Have to exclude user from most lines so that user.alert +# and user.emerg are not included, because old sendmails +# will generate them for debugging information. If you +# have no 4.2BSD based systems doing network logging, you +# can remove all the special cases for "user" logging. + +#*.err;kern.debug;auth.notice;user.none /dev/console +kern.debug;user,mail.crit;auth.notice /dev/console +daemon,syslog,lpr,news,uucp,cron.err /dev/console + +#*.err;kern.debug;daemon,auth.notice;mail.crit;user.none /var/adm/messages +kern.debug;user,mail.crit;auth.notice /var/adm/messages +daemon.notice;syslog,news,uucp,cron.err /var/adm/messages + +lpr.debug /var/adm/lpd-errs + +*.alert;kern.err;daemon.err;user.none operator +*.alert;user.none root + +*.emerg;user.none * + +# for loghost machines, to have authentication messages (su, login, etc.) +# logged to a file, un-comment out the following line and adjust the file name +# as appropriate. +# +# if a non-loghost machine chooses to have such messages +# sent to the loghost machine, un-comment out the following line. +# +#auth.notice ifdef(`LOGHOST', /var/log/authlog, @loghost) + +mail.debug ifdef(`LOGHOST', /var/log/syslog, @loghost) + +# following line for compatibility with old sendmails. they will send +# messages with no facility code, which will be turned into "user" messages +# by the local syslog daemon. only the "loghost" machine needs the following +# line, to cause these old sendmail log messages to be logged in the +# mail syslog file. +# +ifdef(`LOGHOST', +user.alert /var/log/syslog +) +# +# non-loghost machines will use the following lines to cause "user" +# log messages to be logged locally. +# +ifdef(`LOGHOST', , +user.err /dev/console +user.err /var/adm/messages +user.alert `root, operator' +user.emerg * +) + +# Local2: (bootpd, pppd) +local2.debug /dev/console +#local2.debug /var/log/local2 diff --git a/libexec/bootpd/tools/Makefile b/libexec/bootpd/tools/Makefile new file mode 100644 index 0000000..e905bff --- /dev/null +++ b/libexec/bootpd/tools/Makefile @@ -0,0 +1,6 @@ +# Makefile +# $FreeBSD$ + +SUBDIR= bootpef bootptest + +.include <bsd.subdir.mk> diff --git a/libexec/bootpd/tools/Makefile.inc b/libexec/bootpd/tools/Makefile.inc new file mode 100644 index 0000000..f4a306f --- /dev/null +++ b/libexec/bootpd/tools/Makefile.inc @@ -0,0 +1,6 @@ +# Makefile.inc +# $FreeBSD$ + +BINDIR= /usr/sbin + +WARNS?= 1 diff --git a/libexec/bootpd/tools/bootpef/Makefile b/libexec/bootpd/tools/bootpef/Makefile new file mode 100644 index 0000000..9436561 --- /dev/null +++ b/libexec/bootpd/tools/bootpef/Makefile @@ -0,0 +1,13 @@ +# Makefile +# $FreeBSD$ + +PROG= bootpef +MAN= bootpef.8 +SRCS= bootpef.c dovend.c readfile.c hash.c dumptab.c lookup.c \ + hwaddr.c report.c tzone.c rtmsg.c + +SRCDIR= ${.CURDIR}/../.. +CFLAGS+=-I${SRCDIR} +.PATH: ${SRCDIR} + +.include <bsd.prog.mk> diff --git a/libexec/bootpd/tools/bootpef/bootpef.8 b/libexec/bootpd/tools/bootpef/bootpef.8 new file mode 100644 index 0000000..841b01c --- /dev/null +++ b/libexec/bootpd/tools/bootpef/bootpef.8 @@ -0,0 +1,64 @@ +.\" $FreeBSD$ +.\" +.\" bootpef.8 +.Dd December 4, 1993 +.Dt BOOTPEF 8 +.Os +.Sh NAME +.Nm bootpef +.Nd "BOOTP Extension File compiler" +.Sh SYNOPSIS +.Bk -words +.Nm +.Op Fl c Ar chdir\-path +.Op Fl d Ar debug\-level +.Op Fl f Ar config\-file +.Op Ar client\-name ... +.Ek +.Sh DESCRIPTION +The +.Nm +utility builds the +.Em "Extension Path" +files described by +.%T "RFC 1497" +(tag 18). +If any +.Ar client\-name +arguments are specified, then +.Nm +compiles the extension files for only those clients. +.Sh OPTIONS +.Bl -tag -width indent +.It Fl c Ar chdir\-path +Sets the current directory used by +.Nm +while creating extension files. +This is useful when the +extension file names are specified as relative pathnames, and +.Nm +needs to use the same current directory as the TFTP server +(typically +.Pa /tftpboot ) . +.It Fl d Ar debug\-level +Sets the +.Ar debug\-level +variable that controls the amount of debugging messages generated. +For example, +.Fl d Ar 4 +will set the debugging level to 4. +.It Fl f Ar config\-file +Set the name of the config file that specifies the option +data to be sent to each client. +.El +.Sh SEE ALSO +.Xr bootpd 8 , +.Xr tftpd 8 +.Rs +.%O RFC951 +.%T "BOOTSTRAP PROTOCOL (BOOTP)" +.Re +.Rs +.%O RFC1497 +.%T "BOOTP Vendor Information Extensions" +.Re diff --git a/libexec/bootpd/tools/bootpef/bootpef.c b/libexec/bootpd/tools/bootpef/bootpef.c new file mode 100644 index 0000000..04089c8 --- /dev/null +++ b/libexec/bootpd/tools/bootpef/bootpef.c @@ -0,0 +1,331 @@ +/************************************************************************ + Copyright 1988, 1991 by Carnegie Mellon University + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation, and that the name of Carnegie Mellon University not be used +in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + + $FreeBSD$ + +************************************************************************/ + +/* + * bootpef - BOOTP Extension File generator + * Makes an "Extension File" for each host entry that + * defines an and Extension File. (See RFC1497, tag 18.) + * + * HISTORY + * See ./Changes + * + * BUGS + * See ./ToDo + */ + + + +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/time.h> + +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#ifndef NO_UNISTD +#include <unistd.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <syslog.h> + +#ifndef USE_BFUNCS +#include <memory.h> +/* Yes, memcpy is OK here (no overlapped copies). */ +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +#include "bootp.h" +#include "hash.h" +#include "hwaddr.h" +#include "bootpd.h" +#include "dovend.h" +#include "readfile.h" +#include "report.h" +#include "tzone.h" +#include "patchlevel.h" + +#define BUFFERSIZE 0x4000 + +#ifndef CONFIG_FILE +#define CONFIG_FILE "/etc/bootptab" +#endif + + + +/* + * Externals, forward declarations, and global variables + */ + +static void mktagfile(struct host *); +static void usage(void); + +/* + * General + */ + +char *progname; +char *chdir_path; +int debug = 0; /* Debugging flag (level) */ +byte *buffer; + +/* + * Globals below are associated with the bootp database file (bootptab). + */ + +char *bootptab = CONFIG_FILE; + + +/* + * Print "usage" message and exit + */ +static void +usage() +{ + fprintf(stderr, + "usage: $s [ -c chdir ] [-d level] [-f configfile] [host...]\n"); + fprintf(stderr, "\t -c n\tset current directory\n"); + fprintf(stderr, "\t -d n\tset debug level\n"); + fprintf(stderr, "\t -f n\tconfig file name\n"); + exit(1); +} + + +/* + * Initialization such as command-line processing is done and then the + * main server loop is started. + */ +int +main(argc, argv) + int argc; + char **argv; +{ + struct host *hp; + char *stmp; + int n; + + progname = strrchr(argv[0], '/'); + if (progname) progname++; + else progname = argv[0]; + + /* Get work space for making tag 18 files. */ + buffer = (byte *) malloc(BUFFERSIZE); + if (!buffer) { + report(LOG_ERR, "malloc failed"); + exit(1); + } + /* + * Set defaults that might be changed by option switches. + */ + stmp = NULL; + + /* + * Read switches. + */ + for (argc--, argv++; argc > 0; argc--, argv++) { + if (argv[0][0] != '-') + break; + switch (argv[0][1]) { + + case 'c': /* chdir_path */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (stmp[0] != '/')) { + fprintf(stderr, + "bootpd: invalid chdir specification\n"); + break; + } + chdir_path = stmp; + break; + + case 'd': /* debug */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else if (argv[1] && argv[1][0] == '-') { + /* + * Backwards-compatible behavior: + * no parameter, so just increment the debug flag. + */ + debug++; + break; + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + fprintf(stderr, + "bootpd: invalid debug level\n"); + break; + } + debug = n; + break; + + case 'f': /* config file */ + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + bootptab = stmp; + break; + + default: + fprintf(stderr, "bootpd: unknown switch: -%c\n", + argv[0][1]); + usage(); + break; + } + } + + /* Get the timezone. */ + tzone_init(); + + /* Allocate hash tables. */ + rdtab_init(); + + /* + * Read the bootptab file. + */ + readtab(1); /* force read */ + + /* Set the cwd (i.e. to /tftpboot) */ + if (chdir_path) { + if (chdir(chdir_path) < 0) + report(LOG_ERR, "%s: chdir failed", chdir_path); + } + /* If there are host names on the command line, do only those. */ + if (argc > 0) { + unsigned int tlen, hashcode; + + while (argc) { + tlen = strlen(argv[0]); + hashcode = hash_HashFunction((u_char *)argv[0], tlen); + hp = (struct host *) hash_Lookup(nmhashtable, + hashcode, + nmcmp, argv[0]); + if (!hp) { + printf("%s: no matching entry\n", argv[0]); + exit(1); + } + if (!hp->flags.exten_file) { + printf("%s: no extension file\n", argv[0]); + exit(1); + } + mktagfile(hp); + argv++; + argc--; + } + exit(0); + } + /* No host names specified. Do them all. */ + hp = (struct host *) hash_FirstEntry(nmhashtable); + while (hp != NULL) { + mktagfile(hp); + hp = (struct host *) hash_NextEntry(nmhashtable); + } + return (0); +} + + + +/* + * Make a "TAG 18" file for this host. + * (Insert the RFC1497 options.) + */ + +static void +mktagfile(hp) + struct host *hp; +{ + FILE *fp; + int bytesleft, len; + byte *vp; + + if (!hp->flags.exten_file) + return; + + vp = buffer; + bytesleft = BUFFERSIZE; + bcopy(vm_rfc1048, vp, 4); /* Copy in the magic cookie */ + vp += 4; + bytesleft -= 4; + + /* + * The "extension file" options are appended by the following + * function (which is shared with bootpd.c). + */ + len = dovend_rfc1497(hp, vp, bytesleft); + vp += len; + bytesleft -= len; + + if (bytesleft < 1) { + report(LOG_ERR, "%s: too much option data", + hp->exten_file->string); + return; + } + *vp++ = TAG_END; + bytesleft--; + + /* Write the buffer to the extension file. */ + printf("Updating \"%s\"\n", hp->exten_file->string); + if ((fp = fopen(hp->exten_file->string, "w")) == NULL) { + report(LOG_ERR, "error opening \"%s\": %s", + hp->exten_file->string, get_errmsg()); + return; + } + len = vp - buffer; + if (len != fwrite(buffer, 1, len, fp)) { + report(LOG_ERR, "write failed on \"%s\" : %s", + hp->exten_file->string, get_errmsg()); + } + fclose(fp); + +} /* mktagfile */ + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/tools/bootptest/Makefile b/libexec/bootpd/tools/bootptest/Makefile new file mode 100644 index 0000000..fae5127 --- /dev/null +++ b/libexec/bootpd/tools/bootptest/Makefile @@ -0,0 +1,12 @@ +# Makefile +# $FreeBSD$ + +PROG= bootptest +MAN= bootptest.8 +SRCS= bootptest.c getether.c getif.c print-bootp.c report.c + +SRCDIR= ${.CURDIR}/../.. +CFLAGS+=-I${SRCDIR} +.PATH: ${SRCDIR} + +.include <bsd.prog.mk> diff --git a/libexec/bootpd/tools/bootptest/bootptest.8 b/libexec/bootpd/tools/bootptest/bootptest.8 new file mode 100644 index 0000000..4ba2724 --- /dev/null +++ b/libexec/bootpd/tools/bootptest/bootptest.8 @@ -0,0 +1,77 @@ +.\" $FreeBSD$ +.\" +.\" bootptest.8 +.Dd June 10, 1993 +.Dt BOOTPTEST 8 +.Os +.Sh NAME +.Nm bootptest +.Nd "send BOOTP queries and print responses" +.Sh SYNOPSIS +.Nm +.Op Fl f Ar bootfile +.Op Fl h +.Op Fl m Ar magic_number +.Ar server\-name +.Op Ar template\-file +.Sh DESCRIPTION +The +.Nm +utility sends BOOTP requests to the host specified as +.Ar server\-name +at one\-second intervals until either a response is received, +or until ten requests have gone unanswered. +After a response is received, +.Nm +will wait one more second listening for additional responses. +.Sh OPTIONS +.Bl -tag -width indent +.It Fl f Ar bootfile +Fill in the boot file field of the request with +.Ar bootfile . +.It Fl h +Use the hardware (Ethernet) address to identify the client. +By default, the IP address is copied into the request +indicating that this client already knows its IP address. +.It Fl m Ar magic_number +Initialize the first word of the vendor options field with +.Ar magic_number . +.El +.Pp +A +.Ar template\-file +may be specified, in which case +.Nm +uses the (binary) contents of this file to initialize the +.Em options +area of the request packet. +.Sh SEE ALSO +.Xr bootpd 8 +.Rs +.%O RFC951 +.%T "BOOTSTRAP PROTOCOL (BOOTP)" +.Re +.Rs +.%O RFC1048 +.%T "BOOTP Vendor Information Extensions" +.Re +.Sh AUTHORS +The +.Nm +utility is a combination of original and derived works. +The main program module +.Pq Pa bootptest.c +is original work by +.An Gordon W. Ross Aq Mt gwr@mc.com . +The packet printing module +.Pq Pa print\-bootp.c +is a slightly modified +version of a file from the +.Bx +.Xr tcpdump 1 +program. +.Pp +This program includes software developed by the University of +California, Lawrence Berkeley Laboratory and its contributors. +(See the copyright notice in +.Pa print\-bootp.c . ) diff --git a/libexec/bootpd/tools/bootptest/bootptest.c b/libexec/bootpd/tools/bootptest/bootptest.c new file mode 100644 index 0000000..bf27470 --- /dev/null +++ b/libexec/bootpd/tools/bootptest/bootptest.c @@ -0,0 +1,520 @@ +/* + * bootptest.c - Test out a bootp server. + * + * This simple program was put together from pieces taken from + * various places, including the CMU BOOTP client and server. + * The packet printing routine is from the Berkeley "tcpdump" + * program with some enhancements I added. The print-bootp.c + * file was shared with my copy of "tcpdump" and therefore uses + * some unusual utility routines that would normally be provided + * by various parts of the tcpdump program. Gordon W. Ross + * + * Boilerplate: + * + * This program includes software developed by the University of + * California, Lawrence Berkeley Laboratory and its contributors. + * (See the copyright notice in print-bootp.c) + * + * The remainder of this program is public domain. You may do + * whatever you like with it except claim that you wrote it. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * HISTORY: + * + * 12/02/93 Released version 1.4 (with bootp-2.3.2) + * 11/05/93 Released version 1.3 + * 10/14/93 Released version 1.2 + * 10/11/93 Released version 1.1 + * 09/28/93 Released version 1.0 + * 09/93 Original developed by Gordon W. Ross <gwr@mc.com> + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +char *usage = "bootptest [-h] server-name [vendor-data-template-file]"; + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#ifndef NO_UNISTD +#include <unistd.h> +#endif + +#include <err.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <netdb.h> +#include <assert.h> + +#include "bootp.h" +#include "bootptest.h" +#include "getif.h" +#include "getether.h" + +#include "patchlevel.h" + +static void send_request(); + +#define LOG_ERR 1 +#define BUFLEN 1024 +#define WAITSECS 1 +#define MAXWAIT 10 + +int vflag = 1; +int tflag = 0; +int thiszone; +char *progname; +unsigned char *packetp; +unsigned char *snapend; +int snaplen; + + +/* + * IP port numbers for client and server obtained from /etc/services + */ + +u_short bootps_port, bootpc_port; + + +/* + * Internet socket and interface config structures + */ + +struct sockaddr_in sin_server; /* where to send requests */ +struct sockaddr_in sin_client; /* for bind and listen */ +struct sockaddr_in sin_from; /* Packet source */ +u_char eaddr[16]; /* Ethernet address */ + +/* + * General + */ + +int debug = 1; /* Debugging flag (level) */ +char *sndbuf; /* Send packet buffer */ +char *rcvbuf; /* Receive packet buffer */ + +struct utsname my_uname; +char *hostname; + +/* + * Vendor magic cookies for CMU and RFC1048 + */ + +unsigned char vm_cmu[4] = VM_CMU; +unsigned char vm_rfc1048[4] = VM_RFC1048; +short secs; /* How long client has waited */ + +char *get_errmsg(); +extern void bootp_print(); + +/* + * Initialization such as command-line processing is done, then + * the receiver loop is started. Die when interrupted. + */ + +int +main(argc, argv) + int argc; + char **argv; +{ + struct bootp *bp; + struct servent *sep; + struct hostent *hep; + + char *servername = NULL; + char *vendor_file = NULL; + char *bp_file = NULL; + int32 server_addr; /* inet addr, network order */ + int s; /* Socket file descriptor */ + int n, fromlen, recvcnt; + int use_hwa = 0; + int32 vend_magic; + int32 xid; + + progname = strrchr(argv[0], '/'); + if (progname) + progname++; + else + progname = argv[0]; + argc--; + argv++; + + if (debug) + printf("%s: version %s.%d\n", progname, VERSION, PATCHLEVEL); + + /* + * Verify that "struct bootp" has the correct official size. + * (Catch evil compilers that do struct padding.) + */ + assert(sizeof(struct bootp) == BP_MINPKTSZ); + + if (uname(&my_uname) < 0) + errx(1, "can't get hostname"); + hostname = my_uname.nodename; + + sndbuf = malloc(BUFLEN); + rcvbuf = malloc(BUFLEN); + if (!sndbuf || !rcvbuf) { + printf("malloc failed\n"); + exit(1); + } + + /* default magic number */ + bcopy(vm_rfc1048, (char*)&vend_magic, 4); + + /* Handle option switches. */ + while (argc > 0) { + if (argv[0][0] != '-') + break; + switch (argv[0][1]) { + + case 'f': /* File name to request. */ + if (argc < 2) + goto error; + argc--; argv++; + bp_file = *argv; + break; + + case 'h': /* Use hardware address. */ + use_hwa = 1; + break; + + case 'm': /* Magic number value. */ + if (argc < 2) + goto error; + argc--; argv++; + vend_magic = inet_addr(*argv); + break; + + error: + default: + puts(usage); + exit(1); + + } + argc--; + argv++; + } + + /* Get server name (or address) for query. */ + if (argc > 0) { + servername = *argv; + argc--; + argv++; + } + /* Get optional vendor-data-template-file. */ + if (argc > 0) { + vendor_file = *argv; + argc--; + argv++; + } + if (!servername) { + printf("missing server name.\n"); + puts(usage); + exit(1); + } + /* + * Create a socket. + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("socket"); + exit(1); + } + /* + * Get server's listening port number + */ + sep = getservbyname("bootps", "udp"); + if (sep) { + bootps_port = ntohs((u_short) sep->s_port); + } else { + warnx("bootps/udp: unknown service -- using port %d", + IPPORT_BOOTPS); + bootps_port = (u_short) IPPORT_BOOTPS; + } + + /* + * Set up server socket address (for send) + */ + if (servername) { + if (isdigit(servername[0])) + server_addr = inet_addr(servername); + else { + hep = gethostbyname(servername); + if (!hep) + errx(1, "%s: unknown host", servername); + bcopy(hep->h_addr, &server_addr, sizeof(server_addr)); + } + } else { + /* Get broadcast address */ + /* XXX - not yet */ + server_addr = INADDR_ANY; + } + sin_server.sin_family = AF_INET; + sin_server.sin_port = htons(bootps_port); + sin_server.sin_addr.s_addr = server_addr; + + /* + * Get client's listening port number + */ + sep = getservbyname("bootpc", "udp"); + if (sep) { + bootpc_port = ntohs(sep->s_port); + } else { + warnx("bootpc/udp: unknown service -- using port %d", + IPPORT_BOOTPC); + bootpc_port = (u_short) IPPORT_BOOTPC; + } + + /* + * Set up client socket address (for listen) + */ + sin_client.sin_family = AF_INET; + sin_client.sin_port = htons(bootpc_port); + sin_client.sin_addr.s_addr = INADDR_ANY; + + /* + * Bind client socket to BOOTPC port. + */ + if (bind(s, (struct sockaddr *) &sin_client, sizeof(sin_client)) < 0) { + if (errno == EACCES) { + warn("bind BOOTPC port"); + errx(1, "you need to run this as root"); + } + else + err(1, "bind BOOTPC port"); + } + /* + * Build a request. + */ + bp = (struct bootp *) sndbuf; + bzero(bp, sizeof(*bp)); + bp->bp_op = BOOTREQUEST; + xid = (int32) getpid(); + bp->bp_xid = (u_int32) htonl(xid); + if (bp_file) + strncpy(bp->bp_file, bp_file, BP_FILE_LEN); + + /* + * Fill in the hardware address (or client IP address) + */ + if (use_hwa) { + struct ifreq *ifr; + + ifr = getif(s, &sin_server.sin_addr); + if (!ifr) { + printf("No interface for %s\n", servername); + exit(1); + } + if (getether(ifr->ifr_name, (char*)eaddr)) { + printf("Can not get ether addr for %s\n", ifr->ifr_name); + exit(1); + } + /* Copy Ethernet address into request packet. */ + bp->bp_htype = 1; + bp->bp_hlen = 6; + bcopy(eaddr, bp->bp_chaddr, bp->bp_hlen); + } else { + /* Fill in the client IP address. */ + hep = gethostbyname(hostname); + if (!hep) { + printf("Can not get my IP address\n"); + exit(1); + } + bcopy(hep->h_addr, &bp->bp_ciaddr, hep->h_length); + } + + /* + * Copy in the default vendor data. + */ + bcopy((char*)&vend_magic, bp->bp_vend, 4); + if (vend_magic) + bp->bp_vend[4] = TAG_END; + + /* + * Read in the "options" part of the request. + * This also determines the size of the packet. + */ + snaplen = sizeof(*bp); + if (vendor_file) { + int fd = open(vendor_file, 0); + if (fd < 0) { + perror(vendor_file); + exit(1); + } + /* Compute actual space for options. */ + n = BUFLEN - sizeof(*bp) + BP_VEND_LEN; + n = read(fd, bp->bp_vend, n); + close(fd); + if (n < 0) { + perror(vendor_file); + exit(1); + } + printf("read %d bytes of vendor template\n", n); + if (n > BP_VEND_LEN) { + printf("warning: extended options in use (len > %d)\n", + BP_VEND_LEN); + snaplen += (n - BP_VEND_LEN); + } + } + /* + * Set globals needed by print_bootp + * (called by send_request) + */ + packetp = (unsigned char *) eaddr; + snapend = (unsigned char *) sndbuf + snaplen; + + /* Send a request once per second while waiting for replies. */ + recvcnt = 0; + bp->bp_secs = secs = 0; + send_request(s); + while (1) { + struct timeval tv; + int readfds; + + tv.tv_sec = WAITSECS; + tv.tv_usec = 0L; + readfds = (1 << s); + n = select(s + 1, (fd_set *) & readfds, NULL, NULL, &tv); + if (n < 0) { + perror("select"); + break; + } + if (n == 0) { + /* + * We have not received a response in the last second. + * If we have ever received any responses, exit now. + * Otherwise, bump the "wait time" field and re-send. + */ + if (recvcnt > 0) + exit(0); + secs += WAITSECS; + if (secs > MAXWAIT) + break; + bp->bp_secs = htons(secs); + send_request(s); + continue; + } + fromlen = sizeof(sin_from); + n = recvfrom(s, rcvbuf, BUFLEN, 0, + (struct sockaddr *) &sin_from, &fromlen); + if (n <= 0) { + continue; + } + if (n < sizeof(struct bootp)) { + printf("received short packet\n"); + continue; + } + recvcnt++; + + /* Print the received packet. */ + printf("Recvd from %s", inet_ntoa(sin_from.sin_addr)); + /* set globals needed by bootp_print() */ + snaplen = n; + snapend = (unsigned char *) rcvbuf + snaplen; + bootp_print(rcvbuf, n, sin_from.sin_port, 0); + putchar('\n'); + /* + * This no longer exits immediately after receiving + * one response because it is useful to know if the + * client might get multiple responses. This code + * will now listen for one second after a response. + */ + } + errx(1, "no response from %s", servername); +} + +static void +send_request(s) + int s; +{ + /* Print the request packet. */ + printf("Sending to %s", inet_ntoa(sin_server.sin_addr)); + bootp_print(sndbuf, snaplen, sin_from.sin_port, 0); + putchar('\n'); + + /* Send the request packet. */ + if (sendto(s, sndbuf, snaplen, 0, + (struct sockaddr *) &sin_server, + sizeof(sin_server)) < 0) + { + perror("sendto server"); + exit(1); + } +} + +/* + * Print out a filename (or other ascii string). + * Return true if truncated. + */ +int +printfn(s, ep) + register u_char *s, *ep; +{ + register u_char c; + + putchar('"'); + while ((c = *s++) != '\0') { + if (s > ep) { + putchar('"'); + return (1); + } + if (!isascii(c)) { + c = toascii(c); + putchar('M'); + putchar('-'); + } + if (!isprint(c)) { + c ^= 0x40; /* DEL to ?, others to alpha */ + putchar('^'); + } + putchar(c); + } + putchar('"'); + return (0); +} + +/* + * Convert an IP addr to a string. + * (like inet_ntoa, but ina is a pointer) + */ +char * +ipaddr_string(ina) + struct in_addr *ina; +{ + static char b[24]; + u_char *p; + + p = (u_char *) ina; + snprintf(b, sizeof(b), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + return (b); +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/tools/bootptest/bootptest.h b/libexec/bootpd/tools/bootptest/bootptest.h new file mode 100644 index 0000000..a1891fe --- /dev/null +++ b/libexec/bootpd/tools/bootptest/bootptest.h @@ -0,0 +1,23 @@ +/* bootptest.h */ +/* $FreeBSD$ */ +/* + * Hacks for sharing print-bootp.c between tcpdump and bootptest. + */ +#define ESRC(p) (p) +#define EDST(p) (p) + +#ifndef USE_BFUNCS +/* Use mem/str functions */ +/* There are no overlapped copies, so memcpy is OK. */ +#define bcopy(a,b,c) memcpy(b,a,c) +#define bzero(p,l) memset(p,0,l) +#define bcmp(a,b,c) memcmp(a,b,c) +#endif + +extern int vflag; /* verbose flag */ + +/* global pointers to beginning and end of current packet (during printing) */ +extern unsigned char *packetp; +extern unsigned char *snapend; + +extern char *ipaddr_string(struct in_addr *); diff --git a/libexec/bootpd/tools/bootptest/print-bootp.c b/libexec/bootpd/tools/bootptest/print-bootp.c new file mode 100644 index 0000000..3cdb65a --- /dev/null +++ b/libexec/bootpd/tools/bootptest/print-bootp.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 1988-1990 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: + * 1. Source code distributions retain the above copyright + * notice and this paragraph in its entirety + * 2. Distributions including binary code include the above copyright + * notice and this paragraph in its entirety in the documentation + * or other materials provided with the distribution, and + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Format and print bootp packets. + * + * This file was copied from tcpdump-2.1.1 and modified. + * There is an e-mail list for tcpdump: <tcpdump@ee.lbl.gov> + * + * $FreeBSD$ + */ + +#include <stdio.h> + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <sys/time.h> /* for struct timeval in net/if.h */ +#include <net/if.h> +#include <netinet/in.h> + +#include <string.h> +#include <ctype.h> + +#include "bootp.h" +#include "bootptest.h" + +/* These decode the vendor data. */ +extern int printfn(); +static void rfc1048_print(); +static void cmu_print(); +static void other_print(); +static void dump_hex(); + +/* + * Print bootp requests + */ +void +bootp_print(bp, length, sport, dport) + struct bootp *bp; + int length; + u_short sport, dport; +{ + static char tstr[] = " [|bootp]"; + static unsigned char vm_cmu[4] = VM_CMU; + static unsigned char vm_rfc1048[4] = VM_RFC1048; + u_char *ep; + int vdlen; + +#define TCHECK(var, l) if ((u_char *)&(var) > ep - l) goto trunc + + /* Note funny sized packets */ + if (length != sizeof(struct bootp)) + (void) printf(" [len=%d]", length); + + /* 'ep' points to the end of avaible data. */ + ep = (u_char *) snapend; + + switch (bp->bp_op) { + + case BOOTREQUEST: + /* Usually, a request goes from a client to a server */ + if (sport != IPPORT_BOOTPC || dport != IPPORT_BOOTPS) + printf(" (request)"); + break; + + case BOOTREPLY: + /* Usually, a reply goes from a server to a client */ + if (sport != IPPORT_BOOTPS || dport != IPPORT_BOOTPC) + printf(" (reply)"); + break; + + default: + printf(" bootp-#%d", bp->bp_op); + } + + /* The usual hardware address type is 1 (10Mb Ethernet) */ + if (bp->bp_htype != 1) + printf(" htype:%d", bp->bp_htype); + + /* The usual length for 10Mb Ethernet address is 6 bytes */ + if (bp->bp_hlen != 6) + printf(" hlen:%d", bp->bp_hlen); + + /* Client's Hardware address */ + if (bp->bp_hlen) { + register struct ether_header *eh; + register char *e; + + TCHECK(bp->bp_chaddr[0], 6); + eh = (struct ether_header *) packetp; + if (bp->bp_op == BOOTREQUEST) + e = (char *) ESRC(eh); + else if (bp->bp_op == BOOTREPLY) + e = (char *) EDST(eh); + else + e = 0; + if (e == 0 || bcmp((char *) bp->bp_chaddr, e, 6)) + dump_hex(bp->bp_chaddr, bp->bp_hlen); + } + /* Only print interesting fields */ + if (bp->bp_hops) + printf(" hops:%d", bp->bp_hops); + + if (bp->bp_xid) + printf(" xid:%ld", (long)ntohl(bp->bp_xid)); + + if (bp->bp_secs) + printf(" secs:%d", ntohs(bp->bp_secs)); + + /* Client's ip address */ + TCHECK(bp->bp_ciaddr, sizeof(bp->bp_ciaddr)); + if (bp->bp_ciaddr.s_addr) + printf(" C:%s", ipaddr_string(&bp->bp_ciaddr)); + + /* 'your' ip address (bootp client) */ + TCHECK(bp->bp_yiaddr, sizeof(bp->bp_yiaddr)); + if (bp->bp_yiaddr.s_addr) + printf(" Y:%s", ipaddr_string(&bp->bp_yiaddr)); + + /* Server's ip address */ + TCHECK(bp->bp_siaddr, sizeof(bp->bp_siaddr)); + if (bp->bp_siaddr.s_addr) + printf(" S:%s", ipaddr_string(&bp->bp_siaddr)); + + /* Gateway's ip address */ + TCHECK(bp->bp_giaddr, sizeof(bp->bp_giaddr)); + if (bp->bp_giaddr.s_addr) + printf(" G:%s", ipaddr_string(&bp->bp_giaddr)); + + TCHECK(bp->bp_sname[0], sizeof(bp->bp_sname)); + if (*bp->bp_sname) { + printf(" sname:"); + if (printfn(bp->bp_sname, ep)) { + fputs(tstr + 1, stdout); + return; + } + } + TCHECK(bp->bp_file[0], sizeof(bp->bp_file)); + if (*bp->bp_file) { + printf(" file:"); + if (printfn(bp->bp_file, ep)) { + fputs(tstr + 1, stdout); + return; + } + } + /* Don't try to decode the vendor buffer unless we're verbose */ + if (vflag <= 0) + return; + + vdlen = sizeof(bp->bp_vend); + /* Vendor data can extend to the end of the packet. */ + if (vdlen < (ep - bp->bp_vend)) + vdlen = (ep - bp->bp_vend); + + TCHECK(bp->bp_vend[0], vdlen); + printf(" vend"); + if (!bcmp(bp->bp_vend, vm_rfc1048, sizeof(u_int32))) + rfc1048_print(bp->bp_vend, vdlen); + else if (!bcmp(bp->bp_vend, vm_cmu, sizeof(u_int32))) + cmu_print(bp->bp_vend, vdlen); + else + other_print(bp->bp_vend, vdlen); + + return; + trunc: + fputs(tstr, stdout); +#undef TCHECK +} + +/* + * Option description data follows. + * These are described in: RFC-1048, RFC-1395, RFC-1497, RFC-1533 + * + * The first char of each option string encodes the data format: + * ?: unknown + * a: ASCII + * b: byte (8-bit) + * i: inet address + * l: int32 + * s: short (16-bit) + */ +char * +rfc1048_opts[] = { + /* Originally from RFC-1048: */ + "?PAD", /* 0: Padding - special, no data. */ + "iSM", /* 1: subnet mask (RFC950)*/ + "lTZ", /* 2: time offset, seconds from UTC */ + "iGW", /* 3: gateways (or routers) */ + "iTS", /* 4: time servers (RFC868) */ + "iINS", /* 5: IEN name servers (IEN116) */ + "iDNS", /* 6: domain name servers (RFC1035)(1034?) */ + "iLOG", /* 7: MIT log servers */ + "iCS", /* 8: cookie servers (RFC865) */ + "iLPR", /* 9: lpr server (RFC1179) */ + "iIPS", /* 10: impress servers (Imagen) */ + "iRLP", /* 11: resource location servers (RFC887) */ + "aHN", /* 12: host name (ASCII) */ + "sBFS", /* 13: boot file size (in 512 byte blocks) */ + + /* Added by RFC-1395: */ + "aDUMP", /* 14: Merit Dump File */ + "aDNAM", /* 15: Domain Name (for DNS) */ + "iSWAP", /* 16: Swap Server */ + "aROOT", /* 17: Root Path */ + + /* Added by RFC-1497: */ + "aEXTF", /* 18: Extensions Path (more options) */ + + /* Added by RFC-1533: (many, many options...) */ +#if 1 /* These might not be worth recognizing by name. */ + + /* IP Layer Parameters, per-host (RFC-1533, sect. 4) */ + "bIP-forward", /* 19: IP Forwarding flag */ + "bIP-srcroute", /* 20: IP Source Routing Enable flag */ + "iIP-filters", /* 21: IP Policy Filter (addr pairs) */ + "sIP-maxudp", /* 22: IP Max-UDP reassembly size */ + "bIP-ttlive", /* 23: IP Time to Live */ + "lIP-pmtuage", /* 24: IP Path MTU aging timeout */ + "sIP-pmtutab", /* 25: IP Path MTU plateau table */ + + /* IP parameters, per-interface (RFC-1533, sect. 5) */ + "sIP-mtu-sz", /* 26: IP MTU size */ + "bIP-mtu-sl", /* 27: IP MTU all subnets local */ + "bIP-bcast1", /* 28: IP Broadcast Addr ones flag */ + "bIP-mask-d", /* 29: IP do mask discovery */ + "bIP-mask-s", /* 30: IP do mask supplier */ + "bIP-rt-dsc", /* 31: IP do router discovery */ + "iIP-rt-sa", /* 32: IP router solicitation addr */ + "iIP-routes", /* 33: IP static routes (dst,router) */ + + /* Link Layer parameters, per-interface (RFC-1533, sect. 6) */ + "bLL-trailer", /* 34: do tralier encapsulation */ + "lLL-arp-tmo", /* 35: ARP cache timeout */ + "bLL-ether2", /* 36: Ethernet version 2 (IEEE 802.3) */ + + /* TCP parameters (RFC-1533, sect. 7) */ + "bTCP-def-ttl", /* 37: default time to live */ + "lTCP-KA-tmo", /* 38: keepalive time interval */ + "bTCP-KA-junk", /* 39: keepalive sends extra junk */ + + /* Application and Service Parameters (RFC-1533, sect. 8) */ + "aNISDOM", /* 40: NIS Domain (Sun YP) */ + "iNISSRV", /* 41: NIS Servers */ + "iNTPSRV", /* 42: NTP (time) Servers (RFC 1129) */ + "?VSINFO", /* 43: Vendor Specific Info (encapsulated) */ + "iNBiosNS", /* 44: NetBIOS Name Server (RFC-1001,1..2) */ + "iNBiosDD", /* 45: NetBIOS Datagram Dist. Server. */ + "bNBiosNT", /* 46: NetBIOS Note Type */ + "?NBiosS", /* 47: NetBIOS Scope */ + "iXW-FS", /* 48: X Window System Font Servers */ + "iXW-DM", /* 49: X Window System Display Managers */ + + /* DHCP extensions (RFC-1533, sect. 9) */ +#endif +}; +#define KNOWN_OPTIONS (sizeof(rfc1048_opts) / sizeof(rfc1048_opts[0])) + +static void +rfc1048_print(bp, length) + register u_char *bp; + int length; +{ + u_char tag; + u_char *ep; + register int len; + u_int32 ul; + u_short us; + struct in_addr ia; + char *optstr; + + printf("-rfc1395"); + + /* Step over magic cookie */ + bp += sizeof(int32); + /* Setup end pointer */ + ep = bp + length; + while (bp < ep) { + tag = *bp++; + /* Check for tags with no data first. */ + if (tag == TAG_PAD) + continue; + if (tag == TAG_END) + return; + if (tag < KNOWN_OPTIONS) { + optstr = rfc1048_opts[tag]; + printf(" %s:", optstr + 1); + } else { + printf(" T%d:", tag); + optstr = "?"; + } + /* Now scan the length byte. */ + len = *bp++; + if (bp + len > ep) { + /* truncated option */ + printf(" |(%d>%td)", len, ep - bp); + return; + } + /* Print the option value(s). */ + switch (optstr[0]) { + + case 'a': /* ASCII string */ + printfn(bp, bp + len); + bp += len; + len = 0; + break; + + case 's': /* Word formats */ + while (len >= 2) { + bcopy((char *) bp, (char *) &us, 2); + printf("%d", ntohs(us)); + bp += 2; + len -= 2; + if (len) printf(","); + } + if (len) printf("(junk=%d)", len); + break; + + case 'l': /* Long words */ + while (len >= 4) { + bcopy((char *) bp, (char *) &ul, 4); + printf("%ld", (long)ntohl(ul)); + bp += 4; + len -= 4; + if (len) printf(","); + } + if (len) printf("(junk=%d)", len); + break; + + case 'i': /* INET addresses */ + while (len >= 4) { + bcopy((char *) bp, (char *) &ia, 4); + printf("%s", ipaddr_string(&ia)); + bp += 4; + len -= 4; + if (len) printf(","); + } + if (len) printf("(junk=%d)", len); + break; + + case 'b': + default: + break; + + } /* switch */ + + /* Print as characters, if appropriate. */ + if (len) { + dump_hex(bp, len); + if (isascii(*bp) && isprint(*bp)) { + printf("("); + printfn(bp, bp + len); + printf(")"); + } + bp += len; + len = 0; + } + } /* while bp < ep */ +} + +static void +cmu_print(bp, length) + register u_char *bp; + int length; +{ + struct cmu_vend *v; + u_char *ep; + + printf("-cmu"); + + v = (struct cmu_vend *) bp; + if (length < sizeof(*v)) { + printf(" |L=%d", length); + return; + } + /* Setup end pointer */ + ep = bp + length; + + /* Subnet mask */ + if (v->v_flags & VF_SMASK) { + printf(" SM:%s", ipaddr_string(&v->v_smask)); + } + /* Default gateway */ + if (v->v_dgate.s_addr) + printf(" GW:%s", ipaddr_string(&v->v_dgate)); + + /* Domain name servers */ + if (v->v_dns1.s_addr) + printf(" DNS1:%s", ipaddr_string(&v->v_dns1)); + if (v->v_dns2.s_addr) + printf(" DNS2:%s", ipaddr_string(&v->v_dns2)); + + /* IEN-116 name servers */ + if (v->v_ins1.s_addr) + printf(" INS1:%s", ipaddr_string(&v->v_ins1)); + if (v->v_ins2.s_addr) + printf(" INS2:%s", ipaddr_string(&v->v_ins2)); + + /* Time servers */ + if (v->v_ts1.s_addr) + printf(" TS1:%s", ipaddr_string(&v->v_ts1)); + if (v->v_ts2.s_addr) + printf(" TS2:%s", ipaddr_string(&v->v_ts2)); + +} + + +/* + * Print out arbitrary, unknown vendor data. + */ + +static void +other_print(bp, length) + register u_char *bp; + int length; +{ + u_char *ep; /* end pointer */ + u_char *zp; /* points one past last non-zero byte */ + + /* Setup end pointer */ + ep = bp + length; + + /* Find the last non-zero byte. */ + for (zp = ep; zp > bp; zp--) { + if (zp[-1] != 0) + break; + } + + /* Print the all-zero case in a compact representation. */ + if (zp == bp) { + printf("-all-zero"); + return; + } + printf("-unknown"); + + /* Are there enough trailing zeros to make "00..." worthwhile? */ + if (zp + 2 > ep) + zp = ep; /* print them all normally */ + + /* Now just print all the non-zero data. */ + while (bp < zp) { + printf(".%02X", *bp); + bp++; + } + + if (zp < ep) + printf(".00..."); + + return; +} + +static void +dump_hex(bp, len) + u_char *bp; + int len; +{ + while (len > 0) { + printf("%02X", *bp); + bp++; + len--; + if (len) printf("."); + } +} + +/* + * Local Variables: + * tab-width: 4 + * c-indent-level: 4 + * c-argdecl-indent: 4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: -4 + * c-label-offset: -4 + * c-brace-offset: 0 + * End: + */ diff --git a/libexec/bootpd/trygetea.c b/libexec/bootpd/trygetea.c new file mode 100644 index 0000000..0ae63a3 --- /dev/null +++ b/libexec/bootpd/trygetea.c @@ -0,0 +1,55 @@ +/* + * trygetea.c - test program for getether.c + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#if defined(SUNOS) || defined(SVR4) +#include <sys/sockio.h> +#endif + +#ifdef _AIX32 +#include <sys/time.h> /* for struct timeval in net/if.h */ +#endif +#include <net/if.h> /* for struct ifreq */ +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#include <netdb.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> + +#include "getether.h" + +int debug = 0; +char *progname; + +void +main(argc, argv) + int argc; + char **argv; +{ + u_char ea[16]; /* Ethernet address */ + int i; + + progname = argv[0]; /* for report */ + + if (argc < 2) { + printf("need interface name\n"); + exit(1); + } + if ((i = getether(argv[1], (char*)ea)) < 0) { + printf("Could not get Ethernet address (rc=%d)\n", i); + exit(1); + } + printf("Ether-addr"); + for (i = 0; i < 6; i++) + printf(":%x", ea[i] & 0xFF); + printf("\n"); + + exit(0); +} diff --git a/libexec/bootpd/trygetif.c b/libexec/bootpd/trygetif.c new file mode 100644 index 0000000..0e2cca1 --- /dev/null +++ b/libexec/bootpd/trygetif.c @@ -0,0 +1,74 @@ +/* + * trygetif.c - test program for getif.c + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#if defined(SUNOS) || defined(SVR4) +#include <sys/sockio.h> +#endif + +#ifdef _AIX32 +#include <sys/time.h> /* for struct timeval in net/if.h */ +#endif +#include <net/if.h> /* for struct ifreq */ +#include <netinet/in.h> +#include <arpa/inet.h> /* inet_ntoa */ + +#include <netdb.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> + +#include "getif.h" + +int debug = 0; +char *progname; + +void +main(argc, argv) + int argc; + char **argv; +{ + struct hostent *hep; + struct sockaddr_in *sip; /* Interface address */ + struct ifreq *ifr; + struct in_addr dst_addr; + struct in_addr *dap; + int s; + + progname = argv[0]; /* for report */ + + dap = NULL; + if (argc > 1) { + dap = &dst_addr; + if (isdigit(argv[1][0])) + dst_addr.s_addr = inet_addr(argv[1]); + else { + hep = gethostbyname(argv[1]); + if (!hep) { + printf("gethostbyname(%s)\n", argv[1]); + exit(1); + } + memcpy(&dst_addr, hep->h_addr, sizeof(dst_addr)); + } + } + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket open"); + exit(1); + } + ifr = getif(s, dap); + if (!ifr) { + printf("no interface for address\n"); + exit(1); + } + printf("Intf-name:%s\n", ifr->ifr_name); + sip = (struct sockaddr_in *) &(ifr->ifr_addr); + printf("Intf-addr:%s\n", inet_ntoa(sip->sin_addr)); + + exit(0); +} diff --git a/libexec/bootpd/trylook.c b/libexec/bootpd/trylook.c new file mode 100644 index 0000000..9484d04 --- /dev/null +++ b/libexec/bootpd/trylook.c @@ -0,0 +1,58 @@ +/* + * trylook.c - test program for lookup.c + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <netinet/in.h> +#include <stdio.h> + +#include "report.h" +#include "lookup.h" + +extern char *ether_ntoa(); +extern char *inet_ntoa(); + +int debug = 0; +char *progname; + +void +main(argc, argv) + int argc; + char **argv; +{ + int i; + struct in_addr in; + char *a; + u_char *hwa; + + progname = argv[0]; /* for report */ + + for (i = 1; i < argc; i++) { + + /* Host name */ + printf("%s:", argv[i]); + + /* IP addr */ + if (lookup_ipa(argv[i], &in.s_addr)) + a = "?"; + else + a = inet_ntoa(in); + printf(" ipa=%s", a); + + /* Ether addr */ + printf(" hwa="); + hwa = lookup_hwa(argv[i], 1); + if (!hwa) + printf("?\n"); + else { + int i; + for (i = 0; i < 6; i++) + printf(":%x", hwa[i] & 0xFF); + putchar('\n'); + } + + } + exit(0); +} diff --git a/libexec/bootpd/tzone.c b/libexec/bootpd/tzone.c new file mode 100644 index 0000000..354fb54 --- /dev/null +++ b/libexec/bootpd/tzone.c @@ -0,0 +1,48 @@ +/* + * tzone.c - get the timezone + * + * This is shared by bootpd and bootpef + * + * $FreeBSD$ + */ + +#ifdef SVR4 +/* XXX - Is this really SunOS specific? -gwr */ +/* This is in <time.h> but only visible if (__STDC__ == 1). */ +extern long timezone; +#else /* SVR4 */ +/* BSD or SunOS */ +# include <time.h> +# include <syslog.h> +#endif /* SVR4 */ + +#include "bptypes.h" +#include "report.h" +#include "tzone.h" + +/* This is what other modules use. */ +int32 secondswest; + +/* + * Get our timezone offset so we can give it to clients if the + * configuration file doesn't specify one. + */ +void +tzone_init() +{ +#ifdef SVR4 + /* XXX - Is this really SunOS specific? -gwr */ + secondswest = timezone; +#else /* SVR4 */ + struct tm *tm; + time_t now; + + (void)time(&now); + if ((tm = localtime(&now)) == NULL) { + secondswest = 0; /* Assume GMT for lack of anything better */ + report(LOG_ERR, "localtime() failed"); + } else { + secondswest = -tm->tm_gmtoff; + } +#endif /* SVR4 */ +} diff --git a/libexec/bootpd/tzone.h b/libexec/bootpd/tzone.h new file mode 100644 index 0000000..ddd67c4 --- /dev/null +++ b/libexec/bootpd/tzone.h @@ -0,0 +1,3 @@ +/* tzone.h */ +extern int32 secondswest; +extern void tzone_init(); diff --git a/libexec/casper/Makefile b/libexec/casper/Makefile new file mode 100644 index 0000000..ed6bd7b --- /dev/null +++ b/libexec/casper/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +SUBDIR= dns +SUBDIR+=grp +SUBDIR+=pwd +SUBDIR+=random +SUBDIR+=sysctl + +.include <bsd.subdir.mk> diff --git a/libexec/casper/dns/Makefile b/libexec/casper/dns/Makefile new file mode 100644 index 0000000..245493e --- /dev/null +++ b/libexec/casper/dns/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR} ${.CURDIR}/../../../sbin/casper + +PROG= dns + +SRCS= dns.c + +DPADD= ${LIBCAPSICUM} ${LIBCASPER} ${LIBNV} ${LIBPJDLOG} ${LIBUTIL} +LDADD= -lcapsicum -lcasper -lnv -lpjdlog -lutil + +BINDIR= /libexec/casper + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../../lib/libcapsicum +CFLAGS+=-I${.CURDIR}/../../../lib/libcasper +CFLAGS+=-I${.CURDIR}/../../../lib/libpjdlog +CFLAGS+=-I${.CURDIR}/../../../sbin/casper + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/casper/dns/dns.c b/libexec/casper/dns/dns.c new file mode 100644 index 0000000..6be022a --- /dev/null +++ b/libexec/casper/dns/dns.c @@ -0,0 +1,425 @@ +/*- + * Copyright (c) 2012-2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <netinet/in.h> + +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> + +#include <libcapsicum.h> +#include <libcapsicum_dns.h> +#include <libcasper.h> +#include <nv.h> +#include <pjdlog.h> + +static bool +dns_allowed_type(const nvlist_t *limits, const char *type) +{ + const char *name; + bool notypes; + void *cookie; + + if (limits == NULL) + return (true); + + notypes = true; + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, "type", sizeof("type") - 1) != 0) + continue; + notypes = false; + if (strcmp(nvlist_get_string(limits, name), type) == 0) + return (true); + } + + /* If there are no types at all, allow any type. */ + if (notypes) + return (true); + + return (false); +} + +static bool +dns_allowed_family(const nvlist_t *limits, int family) +{ + const char *name; + bool nofamilies; + void *cookie; + + if (limits == NULL) + return (true); + + nofamilies = true; + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, "family", sizeof("family") - 1) != 0) + continue; + nofamilies = false; + if (family == AF_UNSPEC) + continue; + if (nvlist_get_number(limits, name) == (uint64_t)family) + return (true); + } + + /* If there are no families at all, allow any family. */ + if (nofamilies) + return (true); + + return (false); +} + +static void +hostent_pack(const struct hostent *hp, nvlist_t *nvl) +{ + unsigned int ii; + + nvlist_add_string(nvl, "name", hp->h_name); + nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype); + nvlist_add_number(nvl, "length", (uint64_t)hp->h_length); + + if (hp->h_aliases == NULL) { + nvlist_add_number(nvl, "naliases", 0); + } else { + for (ii = 0; hp->h_aliases[ii] != NULL; ii++) { + nvlist_addf_string(nvl, hp->h_aliases[ii], "alias%u", + ii); + } + nvlist_add_number(nvl, "naliases", (uint64_t)ii); + } + + if (hp->h_addr_list == NULL) { + nvlist_add_number(nvl, "naddrs", 0); + } else { + for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) { + nvlist_addf_binary(nvl, hp->h_addr_list[ii], + (size_t)hp->h_length, "addr%u", ii); + } + nvlist_add_number(nvl, "naddrs", (uint64_t)ii); + } +} + +static int +dns_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + int family; + + if (!dns_allowed_type(limits, "NAME")) + return (NO_RECOVERY); + + family = (int)nvlist_get_number(nvlin, "family"); + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + hp = gethostbyname2(nvlist_get_string(nvlin, "name"), family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout); + return (0); +} + +static int +dns_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + const void *addr; + size_t addrsize; + int family; + + if (!dns_allowed_type(limits, "ADDR")) + return (NO_RECOVERY); + + family = (int)nvlist_get_number(nvlin, "family"); + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + addr = nvlist_get_binary(nvlin, "addr", &addrsize); + hp = gethostbyaddr(addr, (socklen_t)addrsize, family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout); + return (0); +} + +static int +dns_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct sockaddr_storage sast; + const void *sabin; + char *host, *serv; + size_t sabinsize, hostlen, servlen; + socklen_t salen; + int error, flags; + + if (!dns_allowed_type(limits, "NAME")) + return (NO_RECOVERY); + + error = 0; + host = serv = NULL; + memset(&sast, 0, sizeof(sast)); + + hostlen = (size_t)nvlist_get_number(nvlin, "hostlen"); + servlen = (size_t)nvlist_get_number(nvlin, "servlen"); + + if (hostlen > 0) { + host = calloc(1, hostlen + 1); + if (host == NULL) { + error = EAI_MEMORY; + goto out; + } + } + if (servlen > 0) { + serv = calloc(1, servlen + 1); + if (serv == NULL) { + error = EAI_MEMORY; + goto out; + } + } + + sabin = nvlist_get_binary(nvlin, "sa", &sabinsize); + if (sabinsize > sizeof(sast)) { + error = EAI_FAIL; + goto out; + } + + memcpy(&sast, sabin, sabinsize); + salen = (socklen_t)sabinsize; + + if ((sast.ss_family != AF_INET || + salen != sizeof(struct sockaddr_in)) && + (sast.ss_family != AF_INET6 || + salen != sizeof(struct sockaddr_in6))) { + error = EAI_FAIL; + goto out; + } + + if (!dns_allowed_family(limits, (int)sast.ss_family)) + return (NO_RECOVERY); + + flags = (int)nvlist_get_number(nvlin, "flags"); + + error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen, + serv, servlen, flags); + if (error != 0) + goto out; + + nvlist_move_string(nvlout, "host", host); + nvlist_move_string(nvlout, "serv", serv); +out: + if (error != 0) { + free(host); + free(serv); + } + return (error); +} + +static nvlist_t * +addrinfo_pack(const struct addrinfo *ai) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags); + nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family); + nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype); + nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol); + nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen); + nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname); + + return (nvl); +} + +static int +dns_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct addrinfo hints, *hintsp, *res, *cur; + const char *hostname, *servname; + nvlist_t *elem; + unsigned int ii; + int error, family; + + if (!dns_allowed_type(limits, "ADDR")) + return (NO_RECOVERY); + + hostname = nvlist_get_string(nvlin, "hostname"); + servname = nvlist_get_string(nvlin, "servname"); + if (nvlist_exists_number(nvlin, "hints.ai_flags")) { + size_t addrlen; + + hints.ai_flags = (int)nvlist_get_number(nvlin, + "hints.ai_flags"); + hints.ai_family = (int)nvlist_get_number(nvlin, + "hints.ai_family"); + hints.ai_socktype = (int)nvlist_get_number(nvlin, + "hints.ai_socktype"); + hints.ai_protocol = (int)nvlist_get_number(nvlin, + "hints.ai_protocol"); + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hintsp = &hints; + family = hints.ai_family; + } else { + hintsp = NULL; + family = AF_UNSPEC; + } + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + error = getaddrinfo(hostname, servname, hintsp, &res); + if (error != 0) + goto out; + + for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) { + elem = addrinfo_pack(cur); + nvlist_movef_nvlist(nvlout, elem, "res%u", ii); + } + + freeaddrinfo(res); + error = 0; +out: + return (error); +} + +static bool +limit_has_entry(const nvlist_t *limits, const char *prefix) +{ + const char *name; + size_t prefixlen; + void *cookie; + + if (limits == NULL) + return (false); + + prefixlen = strlen(prefix); + + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, prefix, prefixlen) == 0) + return (true); + } + + return (false); +} + +static int +dns_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int nvtype; + bool hastype, hasfamily; + + hastype = false; + hasfamily = false; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) { + if (nvtype == NV_TYPE_STRING) { + const char *type; + + if (strncmp(name, "type", sizeof("type") - 1) != 0) + return (EINVAL); + type = nvlist_get_string(newlimits, name); + if (strcmp(type, "ADDR") != 0 && + strcmp(type, "NAME") != 0) { + return (EINVAL); + } + if (!dns_allowed_type(oldlimits, type)) + return (ENOTCAPABLE); + hastype = true; + } else if (nvtype == NV_TYPE_NUMBER) { + int family; + + if (strncmp(name, "family", sizeof("family") - 1) != 0) + return (EINVAL); + family = (int)nvlist_get_number(newlimits, name); + if (!dns_allowed_family(oldlimits, family)) + return (ENOTCAPABLE); + hasfamily = true; + } else { + return (EINVAL); + } + } + + /* + * If the new limit doesn't mention type or family we have to + * check if the current limit does have those. Missing type or + * family in the limit means that all types or families are + * allowed. + */ + if (!hastype) { + if (limit_has_entry(oldlimits, "type")) + return (ENOTCAPABLE); + } + if (!hasfamily) { + if (limit_has_entry(oldlimits, "family")) + return (ENOTCAPABLE); + } + + return (0); +} + +static int +dns_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (strcmp(cmd, "gethostbyname") == 0) + error = dns_gethostbyname(limits, nvlin, nvlout); + else if (strcmp(cmd, "gethostbyaddr") == 0) + error = dns_gethostbyaddr(limits, nvlin, nvlout); + else if (strcmp(cmd, "getnameinfo") == 0) + error = dns_getnameinfo(limits, nvlin, nvlout); + else if (strcmp(cmd, "getaddrinfo") == 0) + error = dns_getaddrinfo(limits, nvlin, nvlout); + else + error = NO_RECOVERY; + + return (error); +} + +int +main(int argc, char *argv[]) +{ + + return (service_start("system.dns", PARENT_FILENO, dns_limit, + dns_command, argc, argv)); +} diff --git a/libexec/casper/grp/Makefile b/libexec/casper/grp/Makefile new file mode 100644 index 0000000..502cd26 --- /dev/null +++ b/libexec/casper/grp/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR} ${.CURDIR}/../../../sbin/casper + +PROG= grp + +SRCS= grp.c + +DPADD= ${LIBCAPSICUM} ${LIBCASPER} ${LIBNV} ${LIBPJDLOG} ${LIBUTIL} +LDADD= -lcapsicum -lcasper -lnv -lpjdlog -lutil + +BINDIR= /libexec/casper + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../../lib/libcapsicum +CFLAGS+=-I${.CURDIR}/../../../lib/libcasper +CFLAGS+=-I${.CURDIR}/../../../lib/libpjdlog +CFLAGS+=-I${.CURDIR}/../../../sbin/casper + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/casper/grp/grp.c b/libexec/casper/grp/grp.c new file mode 100644 index 0000000..ba22f62 --- /dev/null +++ b/libexec/casper/grp/grp.c @@ -0,0 +1,384 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <grp.h> +#include <stdlib.h> +#include <string.h> + +#include <libcapsicum.h> +#include <libcasper.h> +#include <nv.h> +#include <pjdlog.h> + +static bool +grp_allowed_cmd(const nvlist_t *limits, const char *cmd) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed commands, then all commands + * are allowed. + */ + if (!nvlist_exists_nvlist(limits, "cmds")) + return (true); + + limits = nvlist_get_nvlist(limits, "cmds"); + return (nvlist_exists_null(limits, cmd)); +} + +static int +grp_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!grp_allowed_cmd(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_allowed_group(const nvlist_t *limits, const char *gname, gid_t gid) +{ + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed groups, then all groups are allowed. + */ + if (!nvlist_exists_nvlist(limits, "groups")) + return (true); + + limits = nvlist_get_nvlist(limits, "groups"); + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + if (gid != (gid_t)-1 && + nvlist_get_number(limits, name) == (uint64_t)gid) { + return (true); + } + break; + case NV_TYPE_STRING: + if (gname != NULL && + strcmp(nvlist_get_string(limits, name), + gname) == 0) { + return (true); + } + break; + default: + PJDLOG_ABORT("Unexpected type %d.", type); + } + } + + return (false); +} + +static int +grp_allowed_groups(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name, *gname; + void *cookie; + gid_t gid; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + gid = (gid_t)nvlist_get_number(newlimits, name); + gname = NULL; + break; + case NV_TYPE_STRING: + gid = (gid_t)-1; + gname = nvlist_get_string(newlimits, name); + break; + default: + return (EINVAL); + } + if (!grp_allowed_group(oldlimits, gname, gid)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_allowed_field(const nvlist_t *limits, const char *field) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed fields, then all fields are allowed. + */ + if (!nvlist_exists_nvlist(limits, "fields")) + return (true); + + limits = nvlist_get_nvlist(limits, "fields"); + return (nvlist_exists_null(limits, field)); +} + +static int +grp_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!grp_allowed_field(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_pack(const nvlist_t *limits, const struct group *grp, nvlist_t *nvl) +{ + + if (grp == NULL) + return (true); + + /* + * If either name or GID is allowed, we allow it. + */ + if (!grp_allowed_group(limits, grp->gr_name, grp->gr_gid)) + return (false); + + if (grp_allowed_field(limits, "gr_name")) + nvlist_add_string(nvl, "gr_name", grp->gr_name); + else + nvlist_add_string(nvl, "gr_name", ""); + if (grp_allowed_field(limits, "gr_passwd")) + nvlist_add_string(nvl, "gr_passwd", grp->gr_passwd); + else + nvlist_add_string(nvl, "gr_passwd", ""); + if (grp_allowed_field(limits, "gr_gid")) + nvlist_add_number(nvl, "gr_gid", (uint64_t)grp->gr_gid); + else + nvlist_add_number(nvl, "gr_gid", (uint64_t)-1); + if (grp_allowed_field(limits, "gr_mem") && grp->gr_mem[0] != NULL) { + unsigned int ngroups; + + for (ngroups = 0; grp->gr_mem[ngroups] != NULL; ngroups++) { + nvlist_addf_string(nvl, grp->gr_mem[ngroups], + "gr_mem[%u]", ngroups); + } + nvlist_add_number(nvl, "gr_nmem", (uint64_t)ngroups); + } + + return (true); +} + +static int +grp_getgrent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct group *grp; + + for (;;) { + errno = 0; + grp = getgrent(); + if (errno != 0) + return (errno); + if (grp_pack(limits, grp, nvlout)) + return (0); + } + + /* NOTREACHED */ +} + +static int +grp_getgrnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct group *grp; + const char *name; + + if (!nvlist_exists_string(nvlin, "name")) + return (EINVAL); + name = nvlist_get_string(nvlin, "name"); + PJDLOG_ASSERT(name != NULL); + + errno = 0; + grp = getgrnam(name); + if (errno != 0) + return (errno); + + (void)grp_pack(limits, grp, nvlout); + + return (0); +} + +static int +grp_getgrgid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct group *grp; + gid_t gid; + + if (!nvlist_exists_number(nvlin, "gid")) + return (EINVAL); + + gid = (gid_t)nvlist_get_number(nvlin, "gid"); + + errno = 0; + grp = getgrgid(gid); + if (errno != 0) + return (errno); + + (void)grp_pack(limits, grp, nvlout); + + return (0); +} + +static int +grp_setgroupent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + int stayopen; + + if (!nvlist_exists_bool(nvlin, "stayopen")) + return (EINVAL); + + stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0; + + return (setgroupent(stayopen) == 0 ? EFAULT : 0); +} + +static int +grp_setgrent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + + return (setgrent() == 0 ? EFAULT : 0); +} + +static int +grp_endgrent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + + endgrent(); + + return (0); +} + +static int +grp_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *limits; + const char *name; + void *cookie; + int error, type; + + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") && + !nvlist_exists_nvlist(newlimits, "cmds")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") && + !nvlist_exists_nvlist(newlimits, "fields")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "groups") && + !nvlist_exists_nvlist(newlimits, "groups")) { + return (ENOTCAPABLE); + } + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NVLIST) + return (EINVAL); + limits = nvlist_get_nvlist(newlimits, name); + if (strcmp(name, "cmds") == 0) + error = grp_allowed_cmds(oldlimits, limits); + else if (strcmp(name, "fields") == 0) + error = grp_allowed_fields(oldlimits, limits); + else if (strcmp(name, "groups") == 0) + error = grp_allowed_groups(oldlimits, limits); + else + error = EINVAL; + if (error != 0) + return (error); + } + + return (0); +} + +static int +grp_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (!grp_allowed_cmd(limits, cmd)) + return (ENOTCAPABLE); + + if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0) + error = grp_getgrent(limits, nvlin, nvlout); + else if (strcmp(cmd, "getgrnam") == 0 || strcmp(cmd, "getgrnam_r") == 0) + error = grp_getgrnam(limits, nvlin, nvlout); + else if (strcmp(cmd, "getgrgid") == 0 || strcmp(cmd, "getgrgid_r") == 0) + error = grp_getgrgid(limits, nvlin, nvlout); + else if (strcmp(cmd, "setgroupent") == 0) + error = grp_setgroupent(limits, nvlin, nvlout); + else if (strcmp(cmd, "setgrent") == 0) + error = grp_setgrent(limits, nvlin, nvlout); + else if (strcmp(cmd, "endgrent") == 0) + error = grp_endgrent(limits, nvlin, nvlout); + else + error = EINVAL; + + return (error); +} + +int +main(int argc, char *argv[]) +{ + + return (service_start("system.grp", PARENT_FILENO, grp_limit, + grp_command, argc, argv)); +} diff --git a/libexec/casper/pwd/Makefile b/libexec/casper/pwd/Makefile new file mode 100644 index 0000000..ebf6cac --- /dev/null +++ b/libexec/casper/pwd/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR} ${.CURDIR}/../../../sbin/casper + +PROG= pwd + +SRCS= pwd.c + +DPADD= ${LIBCAPSICUM} ${LIBCASPER} ${LIBNV} ${LIBPJDLOG} ${LIBUTIL} +LDADD= -lcapsicum -lcasper -lnv -lpjdlog -lutil + +BINDIR= /libexec/casper + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../../lib/libcapsicum +CFLAGS+=-I${.CURDIR}/../../../lib/libcasper +CFLAGS+=-I${.CURDIR}/../../../lib/libpjdlog +CFLAGS+=-I${.CURDIR}/../../../sbin/casper + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/casper/pwd/pwd.c b/libexec/casper/pwd/pwd.c new file mode 100644 index 0000000..be51ad0 --- /dev/null +++ b/libexec/casper/pwd/pwd.c @@ -0,0 +1,429 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> + +#include <libcapsicum.h> +#include <libcasper.h> +#include <nv.h> +#include <pjdlog.h> + +static bool +pwd_allowed_cmd(const nvlist_t *limits, const char *cmd) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed commands, then all commands + * are allowed. + */ + if (!nvlist_exists_nvlist(limits, "cmds")) + return (true); + + limits = nvlist_get_nvlist(limits, "cmds"); + return (nvlist_exists_null(limits, cmd)); +} + +static int +pwd_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!pwd_allowed_cmd(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_allowed_user(const nvlist_t *limits, const char *uname, uid_t uid) +{ + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed users, then all users are allowed. + */ + if (!nvlist_exists_nvlist(limits, "users")) + return (true); + + limits = nvlist_get_nvlist(limits, "users"); + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + if (uid != (uid_t)-1 && + nvlist_get_number(limits, name) == (uint64_t)uid) { + return (true); + } + break; + case NV_TYPE_STRING: + if (uname != NULL && + strcmp(nvlist_get_string(limits, name), + uname) == 0) { + return (true); + } + break; + default: + PJDLOG_ABORT("Unexpected type %d.", type); + } + } + + return (false); +} + +static int +pwd_allowed_users(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name, *uname; + void *cookie; + uid_t uid; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + uid = (uid_t)nvlist_get_number(newlimits, name); + uname = NULL; + break; + case NV_TYPE_STRING: + uid = (uid_t)-1; + uname = nvlist_get_string(newlimits, name); + break; + default: + return (EINVAL); + } + if (!pwd_allowed_user(oldlimits, uname, uid)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_allowed_field(const nvlist_t *limits, const char *field) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed fields, then all fields are allowed. + */ + if (!nvlist_exists_nvlist(limits, "fields")) + return (true); + + limits = nvlist_get_nvlist(limits, "fields"); + return (nvlist_exists_null(limits, field)); +} + +static int +pwd_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!pwd_allowed_field(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_pack(const nvlist_t *limits, const struct passwd *pwd, nvlist_t *nvl) +{ + int fields; + + if (pwd == NULL) + return (true); + + /* + * If either name or UID is allowed, we allow it. + */ + if (!pwd_allowed_user(limits, pwd->pw_name, pwd->pw_uid)) + return (false); + + fields = pwd->pw_fields; + + if (pwd_allowed_field(limits, "pw_name")) { + nvlist_add_string(nvl, "pw_name", pwd->pw_name); + } else { + nvlist_add_string(nvl, "pw_name", ""); + fields &= ~_PWF_NAME; + } + if (pwd_allowed_field(limits, "pw_uid")) { + nvlist_add_number(nvl, "pw_uid", (uint64_t)pwd->pw_uid); + } else { + nvlist_add_number(nvl, "pw_uid", (uint64_t)-1); + fields &= ~_PWF_UID; + } + if (pwd_allowed_field(limits, "pw_gid")) { + nvlist_add_number(nvl, "pw_gid", (uint64_t)pwd->pw_gid); + } else { + nvlist_add_number(nvl, "pw_gid", (uint64_t)-1); + fields &= ~_PWF_GID; + } + if (pwd_allowed_field(limits, "pw_change")) { + nvlist_add_number(nvl, "pw_change", (uint64_t)pwd->pw_change); + } else { + nvlist_add_number(nvl, "pw_change", (uint64_t)0); + fields &= ~_PWF_CHANGE; + } + if (pwd_allowed_field(limits, "pw_passwd")) { + nvlist_add_string(nvl, "pw_passwd", pwd->pw_passwd); + } else { + nvlist_add_string(nvl, "pw_passwd", ""); + fields &= ~_PWF_PASSWD; + } + if (pwd_allowed_field(limits, "pw_class")) { + nvlist_add_string(nvl, "pw_class", pwd->pw_class); + } else { + nvlist_add_string(nvl, "pw_class", ""); + fields &= ~_PWF_CLASS; + } + if (pwd_allowed_field(limits, "pw_gecos")) { + nvlist_add_string(nvl, "pw_gecos", pwd->pw_gecos); + } else { + nvlist_add_string(nvl, "pw_gecos", ""); + fields &= ~_PWF_GECOS; + } + if (pwd_allowed_field(limits, "pw_dir")) { + nvlist_add_string(nvl, "pw_dir", pwd->pw_dir); + } else { + nvlist_add_string(nvl, "pw_dir", ""); + fields &= ~_PWF_DIR; + } + if (pwd_allowed_field(limits, "pw_shell")) { + nvlist_add_string(nvl, "pw_shell", pwd->pw_shell); + } else { + nvlist_add_string(nvl, "pw_shell", ""); + fields &= ~_PWF_SHELL; + } + if (pwd_allowed_field(limits, "pw_expire")) { + nvlist_add_number(nvl, "pw_expire", (uint64_t)pwd->pw_expire); + } else { + nvlist_add_number(nvl, "pw_expire", (uint64_t)0); + fields &= ~_PWF_EXPIRE; + } + nvlist_add_number(nvl, "pw_fields", (uint64_t)fields); + + return (true); +} + +static int +pwd_getpwent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct passwd *pwd; + + for (;;) { + errno = 0; + pwd = getpwent(); + if (errno != 0) + return (errno); + if (pwd_pack(limits, pwd, nvlout)) + return (0); + } + + /* NOTREACHED */ +} + +static int +pwd_getpwnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct passwd *pwd; + const char *name; + + if (!nvlist_exists_string(nvlin, "name")) + return (EINVAL); + name = nvlist_get_string(nvlin, "name"); + PJDLOG_ASSERT(name != NULL); + + errno = 0; + pwd = getpwnam(name); + if (errno != 0) + return (errno); + + (void)pwd_pack(limits, pwd, nvlout); + + return (0); +} + +static int +pwd_getpwuid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct passwd *pwd; + uid_t uid; + + if (!nvlist_exists_number(nvlin, "uid")) + return (EINVAL); + + uid = (uid_t)nvlist_get_number(nvlin, "uid"); + + errno = 0; + pwd = getpwuid(uid); + if (errno != 0) + return (errno); + + (void)pwd_pack(limits, pwd, nvlout); + + return (0); +} + +static int +pwd_setpassent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + int stayopen; + + if (!nvlist_exists_bool(nvlin, "stayopen")) + return (EINVAL); + + stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0; + + return (setpassent(stayopen) == 0 ? EFAULT : 0); +} + +static int +pwd_setpwent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + + setpwent(); + + return (0); +} + +static int +pwd_endpwent(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + + endpwent(); + + return (0); +} + +static int +pwd_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *limits; + const char *name; + void *cookie; + int error, type; + + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") && + !nvlist_exists_nvlist(newlimits, "cmds")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") && + !nvlist_exists_nvlist(newlimits, "fields")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "users") && + !nvlist_exists_nvlist(newlimits, "users")) { + return (ENOTCAPABLE); + } + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NVLIST) + return (EINVAL); + limits = nvlist_get_nvlist(newlimits, name); + if (strcmp(name, "cmds") == 0) + error = pwd_allowed_cmds(oldlimits, limits); + else if (strcmp(name, "fields") == 0) + error = pwd_allowed_fields(oldlimits, limits); + else if (strcmp(name, "users") == 0) + error = pwd_allowed_users(oldlimits, limits); + else + error = EINVAL; + if (error != 0) + return (error); + } + + return (0); +} + +static int +pwd_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (!pwd_allowed_cmd(limits, cmd)) + return (ENOTCAPABLE); + + if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) + error = pwd_getpwent(limits, nvlin, nvlout); + else if (strcmp(cmd, "getpwnam") == 0 || strcmp(cmd, "getpwnam_r") == 0) + error = pwd_getpwnam(limits, nvlin, nvlout); + else if (strcmp(cmd, "getpwuid") == 0 || strcmp(cmd, "getpwuid_r") == 0) + error = pwd_getpwuid(limits, nvlin, nvlout); + else if (strcmp(cmd, "setpassent") == 0) + error = pwd_setpassent(limits, nvlin, nvlout); + else if (strcmp(cmd, "setpwent") == 0) + error = pwd_setpwent(limits, nvlin, nvlout); + else if (strcmp(cmd, "endpwent") == 0) + error = pwd_endpwent(limits, nvlin, nvlout); + else + error = EINVAL; + + return (error); +} + +int +main(int argc, char *argv[]) +{ + + return (service_start("system.pwd", PARENT_FILENO, pwd_limit, + pwd_command, argc, argv)); +} diff --git a/libexec/casper/random/Makefile b/libexec/casper/random/Makefile new file mode 100644 index 0000000..46c9739 --- /dev/null +++ b/libexec/casper/random/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR} ${.CURDIR}/../../../sbin/casper + +PROG= random + +SRCS= random.c + +DPADD= ${LIBCAPSICUM} ${LIBCASPER} ${LIBNV} ${LIBPJDLOG} ${LIBUTIL} +LDADD= -lcapsicum -lcasper -lnv -lpjdlog -lutil + +BINDIR= /libexec/casper + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../../lib/libcapsicum +CFLAGS+=-I${.CURDIR}/../../../lib/libcasper +CFLAGS+=-I${.CURDIR}/../../../lib/libpjdlog +CFLAGS+=-I${.CURDIR}/../../../sbin/casper + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/casper/random/random.c b/libexec/casper/random/random.c new file mode 100644 index 0000000..56f1afb --- /dev/null +++ b/libexec/casper/random/random.c @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2012-2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <libcapsicum.h> +#include <libcasper.h> +#include <nv.h> +#include <pjdlog.h> + +#define MAXSIZE (1024 * 1024) + +static int +random_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + void *data; + size_t size; + + if (strcmp(cmd, "generate") != 0) + return (EINVAL); + if (!nvlist_exists_number(nvlin, "size")) + return (EINVAL); + + size = (size_t)nvlist_get_number(nvlin, "size"); + if (size == 0 || size > MAXSIZE) + return (EINVAL); + + data = malloc(size); + if (data == NULL) + return (ENOMEM); + + arc4random_buf(data, size); + + nvlist_move_binary(nvlout, "data", data, size); + + return (0); +} + +int +main(int argc, char *argv[]) +{ + + /* + * TODO: Sandbox this. + */ + + return (service_start("system.random", PARENT_FILENO, NULL, + random_command, argc, argv)); +} diff --git a/libexec/casper/sysctl/Makefile b/libexec/casper/sysctl/Makefile new file mode 100644 index 0000000..432008e --- /dev/null +++ b/libexec/casper/sysctl/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR} ${.CURDIR}/../../../sbin/casper + +PROG= sysctl + +SRCS= sysctl.c + +DPADD= ${LIBCAPSICUM} ${LIBCASPER} ${LIBNV} ${LIBPJDLOG} ${LIBUTIL} +LDADD= -lcapsicum -lcasper -lnv -lpjdlog -lutil + +BINDIR= /libexec/casper + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../../lib/libcapsicum +CFLAGS+=-I${.CURDIR}/../../../lib/libcasper +CFLAGS+=-I${.CURDIR}/../../../lib/libpjdlog +CFLAGS+=-I${.CURDIR}/../../../sbin/casper + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/casper/sysctl/sysctl.c b/libexec/casper/sysctl/sysctl.c new file mode 100644 index 0000000..8bb43b6 --- /dev/null +++ b/libexec/casper/sysctl/sysctl.c @@ -0,0 +1,249 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/sysctl.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <libcapsicum.h> +#include <libcapsicum_sysctl.h> +#include <libcasper.h> +#include <nv.h> +#include <pjdlog.h> + +static int +sysctl_check_one(const nvlist_t *nvl, bool islimit) +{ + const char *name; + void *cookie; + int type; + unsigned int fields; + + /* NULL nvl is of course invalid. */ + if (nvl == NULL) + return (EINVAL); + if (nvlist_error(nvl) != 0) + return (nvlist_error(nvl)); + +#define HAS_NAME 0x01 +#define HAS_OPERATION 0x02 + + fields = 0; + cookie = NULL; + while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) { + /* We accept only one 'name' and one 'operation' in nvl. */ + if (strcmp(name, "name") == 0) { + if (type != NV_TYPE_STRING) + return (EINVAL); + /* Only one 'name' can be present. */ + if ((fields & HAS_NAME) != 0) + return (EINVAL); + fields |= HAS_NAME; + } else if (strcmp(name, "operation") == 0) { + uint64_t operation; + + if (type != NV_TYPE_NUMBER) + return (EINVAL); + /* + * We accept only CAP_SYSCTL_READ and + * CAP_SYSCTL_WRITE flags. + */ + operation = nvlist_get_number(nvl, name); + if ((operation & ~(CAP_SYSCTL_RDWR)) != 0) + return (EINVAL); + /* ...but there has to be at least one of them. */ + if ((operation & (CAP_SYSCTL_RDWR)) == 0) + return (EINVAL); + /* Only one 'operation' can be present. */ + if ((fields & HAS_OPERATION) != 0) + return (EINVAL); + fields |= HAS_OPERATION; + } else if (islimit) { + /* If this is limit, there can be no other fields. */ + return (EINVAL); + } + } + + /* Both fields has to be there. */ + if (fields != (HAS_NAME | HAS_OPERATION)) + return (EINVAL); + +#undef HAS_OPERATION +#undef HAS_NAME + + return (0); +} + +static bool +sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation) +{ + uint64_t operation; + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + PJDLOG_ASSERT(type == NV_TYPE_NUMBER); + + operation = nvlist_get_number(limits, name); + if ((operation & choperation) != choperation) + continue; + + if ((operation & CAP_SYSCTL_RECURSIVE) == 0) { + if (strcmp(name, chname) != 0) + continue; + } else { + size_t namelen; + + namelen = strlen(name); + if (strncmp(name, chname, namelen) != 0) + continue; + if (chname[namelen] != '.' && chname[namelen] != '\0') + continue; + } + + return (true); + } + + return (false); +} + +static int +sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *nvl; + const char *name; + void *cookie; + uint64_t operation; + int error, type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NUMBER) + return (EINVAL); + operation = nvlist_get_number(newlimits, name); + if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0) + return (EINVAL); + if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0) + return (EINVAL); + if (!sysctl_allowed(oldlimits, name, operation)) + return (ENOTCAPABLE); + } + + return (0); +} + +static int +sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + const char *name; + const void *newp; + void *oldp; + uint64_t operation; + size_t oldlen, newlen; + size_t *oldlenp; + int error; + + if (strcmp(cmd, "sysctl") != 0) + return (EINVAL); + error = sysctl_check_one(nvlin, false); + if (error != 0) + return (error); + + name = nvlist_get_string(nvlin, "name"); + operation = nvlist_get_number(nvlin, "operation"); + if (!sysctl_allowed(limits, name, operation)) + return (ENOTCAPABLE); + + if ((operation & CAP_SYSCTL_WRITE) != 0) { + if (!nvlist_exists_binary(nvlin, "newp")) + return (EINVAL); + newp = nvlist_get_binary(nvlin, "newp", &newlen); + PJDLOG_ASSERT(newp != NULL && newlen > 0); + } else { + newp = NULL; + newlen = 0; + } + + if ((operation & CAP_SYSCTL_READ) != 0) { + if (nvlist_exists_null(nvlin, "justsize")) { + oldp = NULL; + oldlen = 0; + oldlenp = &oldlen; + } else { + if (!nvlist_exists_number(nvlin, "oldlen")) + return (EINVAL); + oldlen = (size_t)nvlist_get_number(nvlin, "oldlen"); + if (oldlen == 0) + return (EINVAL); + oldp = calloc(1, oldlen); + if (oldp == NULL) + return (ENOMEM); + oldlenp = &oldlen; + } + } else { + oldp = NULL; + oldlen = 0; + oldlenp = NULL; + } + + if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) { + error = errno; + free(oldp); + return (error); + } + + if ((operation & CAP_SYSCTL_READ) != 0) { + if (nvlist_exists_null(nvlin, "justsize")) + nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen); + else + nvlist_move_binary(nvlout, "oldp", oldp, oldlen); + } + + return (0); +} + +int +main(int argc, char *argv[]) +{ + + return (service_start("system.sysctl", PARENT_FILENO, sysctl_limit, + sysctl_command, argc, argv)); +} diff --git a/libexec/comsat/Makefile b/libexec/comsat/Makefile new file mode 100644 index 0000000..c501ccc --- /dev/null +++ b/libexec/comsat/Makefile @@ -0,0 +1,7 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= comsat +MAN= comsat.8 + +.include <bsd.prog.mk> diff --git a/libexec/comsat/comsat.8 b/libexec/comsat/comsat.8 new file mode 100644 index 0000000..5a58ad0 --- /dev/null +++ b/libexec/comsat/comsat.8 @@ -0,0 +1,111 @@ +.\" Copyright (c) 1983, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)comsat.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" +.Dd January 21, 2010 +.Dt COMSAT 8 +.Os +.Sh NAME +.Nm comsat +.Nd biff server +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility is the server process which receives reports of incoming mail +and notifies users if they have requested this service. +The +.Nm +utility receives messages on a datagram port associated with the +.Dq biff +service +specification (see +.Xr services 5 +and +.Xr inetd 8 ) . +The one line messages are of the form: +.Pp +.D1 Ar user Ns @ Ns Ar mailbox Ns - Ns Ar offset Ns Op : Ns Ar mailbox-name +.Pp +If the +.Ar user +specified is logged in to the system and the associated terminal has +the owner execute bit turned on (by a +.Dq Nm biff Cm y ) , +the +.Ar offset +is used as a seek offset into the appropriate mailbox file and +the first 7 lines or 560 characters of the message are printed +on the user's terminal. +Lines which appear to be part of +the message header other than the +.Dq Li From , +.Dq Li \&To , +.Dq Li Date , +or +.Dq Li Subject +lines are not included in the displayed message. +.Pp +If the +.Ar user +specified is logged in to the system and the associated terminal has +the group execute bit turned on (by a +.Dq Nm biff Cm b ) , +two bell characters +.Tn ( ASCII +\\007) are printed on the user's terminal. +.Pp +If +.Ar mailbox-name +omitted, standard mailbox assumed. +.Sh FILES +.Bl -tag -width ".Pa /var/mail/user" -compact +.It Pa /var/run/utx.active +to find out who is logged on and on what terminals +.It Pa /var/mail/user +standard mailbox +.El +.Sh SEE ALSO +.Xr biff 1 , +.Xr inetd 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . +.Sh BUGS +The message header filtering is prone to error. +The density of the information presented is near the theoretical minimum. +.Pp +Users should be notified of mail which arrives on other +machines than the one to which they are currently logged in. +.Pp +The notification should appear in a separate window so it +does not mess up the screen. diff --git a/libexec/comsat/comsat.c b/libexec/comsat/comsat.c new file mode 100644 index 0000000..4420f00 --- /dev/null +++ b/libexec/comsat/comsat.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 1980, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1980, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)comsat.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <paths.h> +#include <pwd.h> +#include <termios.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <utmpx.h> + +static int debug = 0; +#define dsyslog if (debug) syslog + +#define MAXIDLE 120 + +static char hostname[MAXHOSTNAMELEN]; + +static void jkfprintf(FILE *, char[], char[], off_t); +static void mailfor(char *); +static void notify(struct utmpx *, char[], off_t, int); +static void reapchildren(int); + +int +main(int argc __unused, char *argv[] __unused) +{ + struct sockaddr_in from; + socklen_t fromlen; + int cc; + char msgbuf[256]; + + /* verify proper invocation */ + fromlen = sizeof(from); + if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) + err(1, "getsockname"); + openlog("comsat", LOG_PID, LOG_DAEMON); + if (chdir(_PATH_MAILDIR)) { + syslog(LOG_ERR, "chdir: %s: %m", _PATH_MAILDIR); + (void) recv(0, msgbuf, sizeof(msgbuf) - 1, 0); + exit(1); + } + (void)gethostname(hostname, sizeof(hostname)); + (void)signal(SIGTTOU, SIG_IGN); + (void)signal(SIGCHLD, reapchildren); + for (;;) { + cc = recv(0, msgbuf, sizeof(msgbuf) - 1, 0); + if (cc <= 0) { + if (errno != EINTR) + sleep(1); + errno = 0; + continue; + } + msgbuf[cc] = '\0'; + mailfor(msgbuf); + sigsetmask(0L); + } +} + +static void +reapchildren(int signo __unused) +{ + while (wait3(NULL, WNOHANG, NULL) > 0); +} + +static void +mailfor(char *name) +{ + struct utmpx *utp; + char *cp; + char *file; + off_t offset; + int folder; + char buf[sizeof(_PATH_MAILDIR) + sizeof(utp->ut_user) + 1]; + char buf2[sizeof(_PATH_MAILDIR) + sizeof(utp->ut_user) + 1]; + + if (!(cp = strchr(name, '@'))) + return; + *cp = '\0'; + offset = strtoll(cp + 1, NULL, 10); + if (!(cp = strchr(cp + 1, ':'))) + file = name; + else + file = cp + 1; + sprintf(buf, "%s/%.*s", _PATH_MAILDIR, (int)sizeof(utp->ut_user), + name); + if (*file != '/') { + sprintf(buf2, "%s/%.*s", _PATH_MAILDIR, + (int)sizeof(utp->ut_user), file); + file = buf2; + } + folder = strcmp(buf, file); + setutxent(); + while ((utp = getutxent()) != NULL) + if (utp->ut_type == USER_PROCESS && !strcmp(utp->ut_user, name)) + notify(utp, file, offset, folder); + endutxent(); +} + +static const char *cr; + +static void +notify(struct utmpx *utp, char file[], off_t offset, int folder) +{ + FILE *tp; + struct stat stb; + struct termios tio; + char tty[20]; + const char *s = utp->ut_line; + + if (strncmp(s, "pts/", 4) == 0) + s += 4; + if (strchr(s, '/')) { + /* A slash is an attempt to break security... */ + syslog(LOG_AUTH | LOG_NOTICE, "Unexpected `/' in `%s'", + utp->ut_line); + return; + } + (void)snprintf(tty, sizeof(tty), "%s%.*s", + _PATH_DEV, (int)sizeof(utp->ut_line), utp->ut_line); + if (stat(tty, &stb) == -1 || !(stb.st_mode & (S_IXUSR | S_IXGRP))) { + dsyslog(LOG_DEBUG, "%s: wrong mode on %s", utp->ut_user, tty); + return; + } + dsyslog(LOG_DEBUG, "notify %s on %s", utp->ut_user, tty); + switch (fork()) { + case -1: + syslog(LOG_NOTICE, "fork failed (%m)"); + return; + case 0: + break; + default: + return; + } + if ((tp = fopen(tty, "w")) == NULL) { + dsyslog(LOG_ERR, "%s: %s", tty, strerror(errno)); + _exit(1); + } + (void)tcgetattr(fileno(tp), &tio); + cr = ((tio.c_oflag & (OPOST|ONLCR)) == (OPOST|ONLCR)) ? "\n" : "\n\r"; + switch (stb.st_mode & (S_IXUSR | S_IXGRP)) { + case S_IXUSR: + case (S_IXUSR | S_IXGRP): + (void)fprintf(tp, + "%s\007New mail for %s@%.*s\007 has arrived%s%s%s:%s----%s", + cr, utp->ut_user, (int)sizeof(hostname), hostname, + folder ? cr : "", folder ? "to " : "", folder ? file : "", + cr, cr); + jkfprintf(tp, utp->ut_user, file, offset); + break; + case S_IXGRP: + (void)fprintf(tp, "\007"); + (void)fflush(tp); + (void)sleep(1); + (void)fprintf(tp, "\007"); + break; + default: + break; + } + (void)fclose(tp); + _exit(0); +} + +static void +jkfprintf(FILE *tp, char user[], char file[], off_t offset) +{ + unsigned char *cp, ch; + FILE *fi; + int linecnt, charcnt, inheader; + struct passwd *p; + unsigned char line[BUFSIZ]; + + /* Set effective uid to user in case mail drop is on nfs */ + if ((p = getpwnam(user)) != NULL) + (void) setuid(p->pw_uid); + + if ((fi = fopen(file, "r")) == NULL) + return; + + (void)fseeko(fi, offset, SEEK_CUR); + /* + * Print the first 7 lines or 560 characters of the new mail + * (whichever comes first). Skip header crap other than + * From, Subject, To, and Date. + */ + linecnt = 7; + charcnt = 560; + inheader = 1; + while (fgets(line, sizeof(line), fi) != NULL) { + if (inheader) { + if (line[0] == '\n') { + inheader = 0; + continue; + } + if (line[0] == ' ' || line[0] == '\t' || + (strncmp(line, "From:", 5) && + strncmp(line, "Subject:", 8))) + continue; + } + if (linecnt <= 0 || charcnt <= 0) { + (void)fprintf(tp, "...more...%s", cr); + (void)fclose(fi); + return; + } + /* strip weird stuff so can't trojan horse stupid terminals */ + for (cp = line; (ch = *cp) && ch != '\n'; ++cp, --charcnt) { + /* disable upper controls and enable all other + 8bit codes due to lack of locale knowledge + */ + if (((ch & 0x80) && ch < 0xA0) || + (!(ch & 0x80) && !isprint(ch) && + !isspace(ch) && ch != '\a' && ch != '\b') + ) { + if (ch & 0x80) { + ch &= ~0x80; + (void)fputs("M-", tp); + } + if (iscntrl(ch)) { + ch ^= 0x40; + (void)fputc('^', tp); + } + } + (void)fputc(ch, tp); + } + (void)fputs(cr, tp); + --linecnt; + } + (void)fprintf(tp, "----%s\n", cr); + (void)fclose(fi); +} diff --git a/libexec/dma-mbox-create/Makefile b/libexec/dma-mbox-create/Makefile new file mode 100644 index 0000000..f0a2341 --- /dev/null +++ b/libexec/dma-mbox-create/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../contrib/dma + +CFLAGS= -I${.CURDIR}/../../contrib/dma \ + -DHAVE_REALLOCF -DHAVE_STRLCPY -DHAVE_GETPROGNAME \ + -DCONF_PATH='"/etc/dma"' \ + -DLIBEXEC_PATH='"/usr/libexec"' -DDMA_VERSION='"v0.9+"' + +MAN= + +WARNS= 2 + +PROG= dma-mbox-create +BINGRP= mail +BINMODE= 4554 + +.include <bsd.prog.mk> diff --git a/libexec/dma/Makefile b/libexec/dma/Makefile new file mode 100644 index 0000000..757faf4 --- /dev/null +++ b/libexec/dma/Makefile @@ -0,0 +1,40 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../contrib/dma + +CFLAGS= -I${.CURDIR}/../../contrib/dma \ + -DHAVE_REALLOCF -DHAVE_STRLCPY -DHAVE_GETPROGNAME \ + -DCONF_PATH='"/etc/dma"' \ + -DLIBEXEC_PATH='"/usr/libexec"' -DDMA_VERSION='"v0.9+"' \ + -DDMA_ROOT_USER='"mailnull"' \ + -DDMA_GROUP='"mail"' +DPADD= ${LIBSSL} ${LIBCRYPTO} +LDADD= -lssl -lcrypto + +PROG= dma +SRCS= aliases_parse.y \ + aliases_scan.l \ + base64.c \ + conf.c \ + crypto.c \ + dma.c \ + dns.c \ + local.c \ + mail.c \ + net.c \ + spool.c \ + util.c +MAN8= dma.8 +YFLAGS+= -i +CLEANFILES= aliases_parse.i + +BINGRP= mail +BINMODE= 2555 + +.include <bsd.compiler.mk> + +.if ${COMPILER_TYPE} == gcc +WARNS= 5 +.endif + +.include <bsd.prog.mk> diff --git a/libexec/fingerd/Makefile b/libexec/fingerd/Makefile new file mode 100644 index 0000000..b6382ad --- /dev/null +++ b/libexec/fingerd/Makefile @@ -0,0 +1,12 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= fingerd +DPADD= ${LIBUTIL} +LDADD= -lutil +MAN= fingerd.8 + +WARNS?= 2 +WFORMAT=0 + +.include <bsd.prog.mk> diff --git a/libexec/fingerd/fingerd.8 b/libexec/fingerd/fingerd.8 new file mode 100644 index 0000000..304479f --- /dev/null +++ b/libexec/fingerd/fingerd.8 @@ -0,0 +1,177 @@ +.\" Copyright (c) 1980, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)fingerd.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" +.Dd April 1, 2010 +.Dt FINGERD 8 +.Os +.Sh NAME +.Nm fingerd +.Nd remote user information server +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl k +.Op Fl s +.Op Fl l +.Op Fl p Ar filename +.Sh DESCRIPTION +The +.Nm +utility uses a simple protocol based on +.%T RFC1196 +that provides an interface to +.Xr finger 1 +at several network sites. +It is supposed to return a friendly, +human-oriented status report on either the system at the moment +or a particular person in depth. +There is no required format and the +protocol consists mostly of specifying a single +.Dq "command line" , +thus, +.Nm +can also be used to implement other protocols in conjunction with the +.Fl p +flag. +.Pp +The +.Nm +utility is started by +.Xr inetd 8 , +which listens for +.Tn TCP +requests at port 79. +Once connected it reads a single command line +terminated by a +.Aq Tn CRLF +which is passed to +.Xr finger 1 . +The +.Nm +utility closes its connections as soon as the output is finished. +.Pp +If the line is null (i.e., just a +.Aq Tn CRLF +is sent) then +.Xr finger 1 +returns a +.Dq default +report that lists all people logged into +the system at that moment. +.Pp +If a user name is specified (e.g.\& +.Pf eric Aq Tn CRLF ) +then the +response lists more extended information for only that particular user, +whether logged in or not. +Allowable +.Dq names +in the command line include both +.Dq login names +and +.Dq user names . +If a name is ambiguous, all possible derivations are returned. +.Pp +The following options may be passed to +.Nm +as server program arguments in +.Pa /etc/inetd.conf : +.Bl -tag -width indent +.It Fl d +Enable debugging mode. +In debugging mode, +.Nm +will not attempt any network-related operations on +.Va stdin , +and it will print the full +.Nm finger +command line +to +.Va stderr +before executing it. +.It Fl k +Suppress login information. +See the description of the +.Fl k +option in +.Xr finger 1 +for details. +.It Fl s +Enable secure mode. +Queries without a user name are rejected and +forwarding of queries to other remote hosts is denied. +.It Fl l +Enable logging. +The name of the host originating the query is reported via +.Xr syslog 3 +at LOG_NOTICE priority. +.It Fl p +Use an alternate program as the local information provider. +The default local program +executed by +.Nm +is +.Xr finger 1 . +By specifying a customized local server, +this option allows a system manager +to have more control over what information is +provided to remote sites. +If +.Fl p +is specified, +.Nm +will also set the environment variable +.Ev FINGERD_REMOTE_HOST +to the name of the host making the request. +.El +.Sh SEE ALSO +.Xr finger 1 , +.Xr inetd 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.3 . +.Sh BUGS +Connecting directly to the server from a +.Tn TIP +or an equally narrow-minded +.Tn TELNET Ns \-protocol +user program can result +in meaningless attempts at option negotiation being sent to the +server, which will foul up the command line interpretation. +The +.Nm +utility should be taught to filter out +.Tn IAC Ns \'s +and perhaps even respond +negatively +.Pq Tn IAC WON'T +to all option commands received. diff --git a/libexec/fingerd/fingerd.c b/libexec/fingerd/fingerd.c new file mode 100644 index 0000000..be344d4 --- /dev/null +++ b/libexec/fingerd/fingerd.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1983, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)fingerd.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <errno.h> + +#include <unistd.h> +#include <syslog.h> +#include <libutil.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "pathnames.h" + +void logerr(const char *, ...) __printflike(1, 2) __dead2; + +int +main(int argc, char *argv[]) +{ + FILE *fp; + int ch; + char *lp; + struct sockaddr_storage ss; + socklen_t sval; + int p[2], debug, kflag, logging, pflag, secure; +#define ENTRIES 50 + char **ap, *av[ENTRIES + 1], **comp, line[1024], *prog; + char rhost[MAXHOSTNAMELEN]; + + prog = _PATH_FINGER; + debug = logging = kflag = pflag = secure = 0; + openlog("fingerd", LOG_PID | LOG_CONS, LOG_DAEMON); + opterr = 0; + while ((ch = getopt(argc, argv, "dklp:s")) != -1) + switch (ch) { + case 'd': + debug = 1; + break; + case 'k': + kflag = 1; + break; + case 'l': + logging = 1; + break; + case 'p': + prog = optarg; + pflag = 1; + break; + case 's': + secure = 1; + break; + case '?': + default: + logerr("illegal option -- %c", optopt); + } + + /* + * Enable server-side Transaction TCP. + */ + if (!debug) { + int one = 1; + if (setsockopt(STDOUT_FILENO, IPPROTO_TCP, TCP_NOPUSH, &one, + sizeof one) < 0) { + logerr("setsockopt(TCP_NOPUSH) failed: %m"); + } + } + + if (!fgets(line, sizeof(line), stdin)) + exit(1); + + if (!debug && (logging || pflag)) { + sval = sizeof(ss); + if (getpeername(0, (struct sockaddr *)&ss, &sval) < 0) + logerr("getpeername: %s", strerror(errno)); + realhostname_sa(rhost, sizeof rhost - 1, + (struct sockaddr *)&ss, sval); + rhost[sizeof(rhost) - 1] = '\0'; + if (pflag) + setenv("FINGERD_REMOTE_HOST", rhost, 1); + } + + if (logging) { + char *t; + char *end; + + end = memchr(line, 0, sizeof(line)); + if (end == NULL) { + if ((t = malloc(sizeof(line) + 1)) == NULL) + logerr("malloc: %s", strerror(errno)); + memcpy(t, line, sizeof(line)); + t[sizeof(line)] = 0; + } else { + if ((t = strdup(line)) == NULL) + logerr("strdup: %s", strerror(errno)); + } + for (end = t; *end; end++) + if (*end == '\n' || *end == '\r') + *end = ' '; + syslog(LOG_NOTICE, "query from %s: `%s'", rhost, t); + } + + comp = &av[2]; + av[3] = "--"; + if (kflag) + *comp-- = "-k"; + for (lp = line, ap = &av[4];;) { + *ap = strtok(lp, " \t\r\n"); + if (!*ap) { + if (secure && ap == &av[4]) { + puts("must provide username\r\n"); + exit(1); + } + break; + } + if (secure && strchr(*ap, '@')) { + puts("forwarding service denied\r\n"); + exit(1); + } + + /* RFC742: "/[Ww]" == "-l" */ + if ((*ap)[0] == '/' && ((*ap)[1] == 'W' || (*ap)[1] == 'w')) { + *comp-- = "-l"; + } + else if (++ap == av + ENTRIES) { + *ap = NULL; + break; + } + lp = NULL; + } + + if ((lp = strrchr(prog, '/')) != NULL) + *comp = ++lp; + else + *comp = prog; + if (pipe(p) < 0) + logerr("pipe: %s", strerror(errno)); + + if (debug) { + fprintf(stderr, "%s", prog); + for (ap = comp; *ap != NULL; ++ap) + fprintf(stderr, " %s", *ap); + fprintf(stderr, "\n"); + } + + switch(vfork()) { + case 0: + (void)close(p[0]); + if (p[1] != STDOUT_FILENO) { + (void)dup2(p[1], STDOUT_FILENO); + (void)close(p[1]); + } + dup2(STDOUT_FILENO, STDERR_FILENO); + + execv(prog, comp); + write(STDERR_FILENO, prog, strlen(prog)); +#define MSG ": cannot execute\n" + write(STDERR_FILENO, MSG, strlen(MSG)); +#undef MSG + _exit(1); + case -1: + logerr("fork: %s", strerror(errno)); + } + (void)close(p[1]); + if (!(fp = fdopen(p[0], "r"))) + logerr("fdopen: %s", strerror(errno)); + while ((ch = getc(fp)) != EOF) { + if (ch == '\n') + putchar('\r'); + putchar(ch); + } + exit(0); +} + +#include <stdarg.h> + +void +logerr(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + (void)vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + exit(1); + /* NOTREACHED */ +} diff --git a/libexec/fingerd/pathnames.h b/libexec/fingerd/pathnames.h new file mode 100644 index 0000000..0a91541 --- /dev/null +++ b/libexec/fingerd/pathnames.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * + * $FreeBSD$ + */ + +#define _PATH_FINGER "/usr/bin/finger" diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile new file mode 100644 index 0000000..0c7c982 --- /dev/null +++ b/libexec/ftpd/Makefile @@ -0,0 +1,40 @@ +# @(#)Makefile 8.2 (Berkeley) 4/4/94 +# $FreeBSD$ + +.include <src.opts.mk> + +PROG= ftpd +MAN= ftpd.8 ftpchroot.5 +SRCS= ftpd.c ftpcmd.y logwtmp.c popen.c + +CFLAGS+=-DSETPROCTITLE -DLOGIN_CAP -DVIRTUAL_HOSTING +CFLAGS+=-I${.CURDIR} +YFLAGS= +WARNS?= 2 +WFORMAT=0 + +DPADD= ${LIBUTIL} ${LIBCRYPT} +LDADD= -lutil -lcrypt + +# XXX Kluge! Conversation mechanism needs to be fixed. +DPADD+= ${LIBOPIE} ${LIBMD} +LDADD+= -lopie -lmd + +LSDIR= ../../bin/ls +.PATH: ${.CURDIR}/${LSDIR} +SRCS+= ls.c cmp.c print.c util.c +CFLAGS+=-Dmain=ls_main -I${.CURDIR}/${LSDIR} +DPADD+= ${LIBM} +LDADD+= -lm + +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+=-DINET6 +.endif + +.if ${MK_PAM_SUPPORT} != "no" +CFLAGS+=-DUSE_PAM +DPADD+= ${LIBPAM} +LDADD+= ${MINUSLPAM} +.endif + +.include <bsd.prog.mk> diff --git a/libexec/ftpd/config.h b/libexec/ftpd/config.h new file mode 100644 index 0000000..8e10bbb --- /dev/null +++ b/libexec/ftpd/config.h @@ -0,0 +1,281 @@ +/* $FreeBSD$ */ + + +/* config.h. Generated automatically by configure. */ +/* config.h.in. Generated automatically from configure.in by autoheader. */ +/* $Id: config.h.in,v 1.15 2001/04/28 07:11:46 lukem Exp $ */ + + +/* Define if the closedir function returns void instead of int. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to empty if the keyword does not work. */ +/* #undef const */ + +/* Define if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Define if your Fortran 77 compiler doesn't accept -c and -o together. */ +/* #undef F77_NO_MINUS_C_MINUS_O */ + +/* Define to `long' if <sys/types.h> doesn't define. */ +/* #undef off_t */ + +/* Define to the type of arg1 for select(). */ +/* #undef SELECT_TYPE_ARG1 */ + +/* Define to the type of args 2, 3 and 4 for select(). */ +/* #undef SELECT_TYPE_ARG234 */ + +/* Define to the type of arg5 for select(). */ +/* #undef SELECT_TYPE_ARG5 */ + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if you can safely include both <sys/time.h> and <time.h>. */ +#define TIME_WITH_SYS_TIME 1 + +/* Define if the closedir function returns void instead of int. */ +/* #undef VOID_CLOSEDIR */ + +/* The number of bytes in a off_t. */ +#define SIZEOF_OFF_T 0 + +/* Define if you have the err function. */ +#define HAVE_ERR 1 + +/* Define if you have the fgetln function. */ +#define HAVE_FGETLN 1 + +/* Define if you have the flock function. */ +#define HAVE_FLOCK 1 + +/* Define if you have the fparseln function. */ +#define HAVE_FPARSELN 1 + +/* Define if you have the fts_open function. */ +#define HAVE_FTS_OPEN 1 + +/* Define if you have the getaddrinfo function. */ +#define HAVE_GETADDRINFO 1 + +/* Define if you have the getgrouplist function. */ +#define HAVE_GETGROUPLIST 1 + +/* Define if you have the getnameinfo function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define if you have the getspnam function. */ +/* #undef HAVE_GETSPNAM */ + +/* Define if you have the getusershell function. */ +#define HAVE_GETUSERSHELL 1 + +/* Define if you have the inet_net_pton function. */ +#define HAVE_INET_NET_PTON 1 + +/* Define if you have the inet_ntop function. */ +#define HAVE_INET_NTOP 1 + +/* Define if you have the inet_pton function. */ +#define HAVE_INET_PTON 1 + +/* Define if you have the lockf function. */ +#define HAVE_LOCKF 1 + +/* Define if you have the mkstemp function. */ +#define HAVE_MKSTEMP 1 + +/* Define if you have the setlogin function. */ +#define HAVE_SETLOGIN 1 + +/* Define if you have the setproctitle function. */ +#define HAVE_SETPROCTITLE 1 + +/* Define if you have the sl_init function. */ +#define HAVE_SL_INIT 1 + +/* Define if you have the snprintf function. */ +#define HAVE_SNPRINTF 1 + +/* Define if you have the strdup function. */ +#define HAVE_STRDUP 1 + +/* Define if you have the strerror function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the strlcat function. */ +#define HAVE_STRLCAT 1 + +/* Define if you have the strlcpy function. */ +#define HAVE_STRLCPY 1 + +/* Define if you have the strmode function. */ +#define HAVE_STRMODE 1 + +/* Define if you have the strsep function. */ +#define HAVE_STRSEP 1 + +/* Define if you have the strtoll function. */ +#define HAVE_STRTOLL 1 + +/* Define if you have the user_from_uid function. */ +#define HAVE_USER_FROM_UID 1 + +/* Define if you have the usleep function. */ +#define HAVE_USLEEP 1 + +/* Define if you have the vfork function. */ +#define HAVE_VFORK 1 + +/* Define if you have the vsyslog function. */ +#define HAVE_VSYSLOG 1 + +/* Define if you have the <arpa/nameser.h> header file. */ +#define HAVE_ARPA_NAMESER_H 1 + +/* Define if you have the <dirent.h> header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the <err.h> header file. */ +#define HAVE_ERR_H 1 + +/* Define if you have the <fts.h> header file. */ +#define HAVE_FTS_H 1 + +/* Define if you have the <libutil.h> header file. */ +#define HAVE_LIBUTIL_H 1 + +/* Define if you have the <ndir.h> header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the <paths.h> header file. */ +#define HAVE_PATHS_H 1 + +/* Define if you have the <sys/dir.h> header file. */ +#define HAVE_SYS_DIR_H 1 + +/* Define if you have the <sys/ndir.h> header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the <sys/sysmacros.h> header file. */ +/* #undef HAVE_SYS_SYSMACROS_H */ + +/* Define if you have the <util.h> header file. */ +/* #undef HAVE_UTIL_H */ + +/* Define if you have the crypt library (-lcrypt). */ +#define HAVE_LIBCRYPT 1 + +/* Define if you have the nsl library (-lnsl). */ +/* #undef HAVE_LIBNSL */ + +/* Define if you have the skey library (-lskey). */ +/* #undef HAVE_LIBSKEY */ + +/* Define if you have the socket library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define if you have the util library (-lutil). */ +#define HAVE_LIBUTIL 1 + +/* Define if your compiler supports `long long' */ +#define HAVE_LONG_LONG 1 + +/* Define if *printf() uses %qd to print `long long' (otherwise uses %lld) */ +#define HAVE_PRINTF_QD 1 + +/* Define if in_port_t exists */ +#define HAVE_IN_PORT_T 1 + +/* Define if struct sockaddr.sa_len exists (implies sockaddr_in.sin_len, etc) */ +#define HAVE_SOCKADDR_SA_LEN 1 + +/* Define if socklen_t exists */ +#define HAVE_SOCKLEN_T 1 + +/* Define if AF_INET6 exists in <sys/socket.h> */ +#define HAVE_AF_INET6 1 + +/* Define if `struct sockaddr_in6' exists in <netinet/in.h> */ +#define HAVE_SOCKADDR_IN6 1 + +/* Define if `struct addrinfo' exists in <netdb.h> */ +#define HAVE_ADDRINFO 1 + +/* + * Define if <netdb.h> contains AI_NUMERICHOST et al. + * Systems which only implement RFC2133 will need this. + */ +#define HAVE_RFC2553_NETDB 1 + +/* Define if `struct direct' has a d_namlen element */ +#define HAVE_D_NAMLEN 1 + +/* Define if struct passwd.pw_expire exists. */ +#define HAVE_PW_EXPIRE 1 + +/* Define if GLOB_BRACE, gl_path and gl_match exist in <glob.h> */ +#define HAVE_WORKING_GLOB 1 + +/* Define if crypt() is declared in <unistd.h> */ +#define HAVE_CRYPT_D 1 + +/* Define if fclose() is declared in <stdio.h> */ +#define HAVE_FCLOSE_D 1 + +/* Define if optarg is declared in <stdlib.h> or <unistd.h> */ +#define HAVE_OPTARG_D 1 + +/* Define if optind is declared in <stdlib.h> or <unistd.h> */ +#define HAVE_OPTIND_D 1 + +/* Define if optreset exists */ +#define HAVE_OPTRESET 1 + +/* Define if pclose() is declared in <stdio.h> */ +#define HAVE_PCLOSE_D 1 + +/* Define if getusershell() is declared in <unistd.h> */ +#define HAVE_GETUSERSHELL_D 1 + +/* Define if `long long' is supported and sizeof(off_t) >= 8 */ +#define HAVE_QUAD_SUPPORT 1 + +/* Define if not using in-built /bin/ls code */ +/* #undef NO_INTERNAL_LS */ + +/* Define if using S/Key */ +/* #undef SKEY */ + +/* + * Define this if compiling with SOCKS (the firewall traversal library). + * Also, you must define connect, getsockname, bind, accept, listen, and + * select to their R-versions. + */ +/* #undef SOCKS */ +/* #undef SOCKS4 */ +/* #undef SOCKS5 */ +/* #undef connect */ +/* #undef getsockname */ +/* #undef bind */ +/* #undef accept */ +/* #undef listen */ +/* #undef select */ +/* #undef dup */ +/* #undef dup2 */ +/* #undef fclose */ +/* #undef gethostbyname */ +/* #undef getpeername */ +/* #undef read */ +/* #undef recv */ +/* #undef recvfrom */ +/* #undef rresvport */ +/* #undef send */ +/* #undef sendto */ +/* #undef shutdown */ +/* #undef write */ + +/* Define if you have the <arpa/ftp.h> header file. */ +#define HAVE_FTP_NAMES 1 diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h new file mode 100644 index 0000000..4f16f7b --- /dev/null +++ b/libexec/ftpd/extern.h @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.2 (Berkeley) 4/4/94 + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +void blkfree(char **); +char **copyblk(char **); +void cwd(char *); +void delete(char *); +void dologout(int); +void fatalerror(char *); +void ftpd_logwtmp(char *, char *, struct sockaddr *addr); +int ftpd_pclose(FILE *); +FILE *ftpd_popen(char *, char *); +int getline(char *, int, FILE *); +void lreply(int, const char *, ...) __printflike(2, 3); +void makedir(char *); +void nack(char *); +void pass(char *); +void passive(void); +void long_passive(char *, int); +void perror_reply(int, char *); +void pwd(void); +void removedir(char *); +void renamecmd(char *, char *); +char *renamefrom(char *); +void reply(int, const char *, ...) __printflike(2, 3); +void retrieve(char *, char *); +void send_file_list(char *); +#ifdef OLD_SETPROCTITLE +void setproctitle(const char *, ...); +#endif +void statcmd(void); +void statfilecmd(char *); +void store(char *, char *, int); +void upper(char *); +void user(char *); +void yyerror(char *); +int yyparse(void); +int ls_main(int, char **); + +extern int assumeutf8; +extern char cbuf[]; +extern union sockunion data_dest; +extern int epsvall; +extern int form; +extern int ftpdebug; +extern int guest; +extern union sockunion his_addr; +extern char *homedir; +extern int hostinfo; +extern char *hostname; +extern int maxtimeout; +extern int logged_in; +extern int logging; +extern int noepsv; +extern int noguestretr; +extern int noretr; +extern int paranoid; +extern struct passwd *pw; +extern int pdata; +extern char proctitle[]; +extern int readonly; +extern off_t restart_point; +extern int timeout; +extern char tmpline[]; +extern int type; +extern char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */ +extern int usedefault; + +struct sockaddr_in; +struct sockaddr_in6; +union sockunion { + struct sockinet { + u_char si_len; + u_char si_family; + u_short si_port; + } su_si; + struct sockaddr_in su_sin; + struct sockaddr_in6 su_sin6; +}; +#define su_len su_si.si_len +#define su_family su_si.si_family +#define su_port su_si.si_port diff --git a/libexec/ftpd/ftpchroot.5 b/libexec/ftpd/ftpchroot.5 new file mode 100644 index 0000000..216ed3d --- /dev/null +++ b/libexec/ftpd/ftpchroot.5 @@ -0,0 +1,120 @@ +.\" Copyright (c) 2003 FreeBSD Project +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd January 26, 2003 +.Dt FTPCHROOT 5 +.Os +.Sh NAME +.Nm ftpchroot +.Nd "list users and groups subject to FTP access restrictions" +.Sh DESCRIPTION +The file +.Nm +is read by +.Xr ftpd 8 +at the beginning of an FTP session, after having authenticated the user. +Each line in +.Nm +corresponds to a user or group. +If a line in +.Nm +matches the current user or a group he is a member of, +access restrictions will be applied to this +session by changing its root directory with +.Xr chroot 2 +to that specified on the line or to the user's login directory. +.Pp +The order of records in +.Nm +is important because the first match will be used. +Fields on each line are separated by tabs or spaces. +.Pp +The first field specifies a user or group name. +If it is prefixed by an +.Dq at +sign, +.Ql @ , +it specifies a group name; +the line will match each user who is a member of this group. +As a special case, a single +.Ql @ +in this field will match any user. +A username is specified otherwise. +.Pp +The optional second field describes the directory for the user +or each member of the group to be locked up in using +.Xr chroot 2 . +Be it omitted, the user's login directory will be used. +If it is not an absolute pathname, then it will be relative +to the user's login directory. +If it contains the +.Pa /./ +separator, +.Xr ftpd 8 +will treat its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. +.Sh FILES +.Bl -tag -width ".Pa /etc/ftpchroot" -compact +.It Pa /etc/ftpchroot +.El +.Sh EXAMPLES +These lines in +.Nm +will lock up the user +.Dq Li webuser +and each member of the group +.Dq Li hostee +in their respective login directories: +.Bd -literal -offset indent +webuser +@hostee +.Ed +.Pp +And this line will tell +.Xr ftpd 8 +to lock up the user +.Dq Li joe +in +.Pa /var/spool/ftp +and then to change the current directory to +.Pa /joe , +which is relative to the session's new root: +.Pp +.Dl "joe /var/spool/ftp/./joe" +.Pp +And finally the following line will lock up every user connecting +through FTP in his respective +.Pa ~/public_html , +thus lowering possible impact on the system +from intrinsic insecurity of FTP: +.Pp +.Dl "@ public_html" +.Sh SEE ALSO +.Xr chroot 2 , +.Xr group 5 , +.Xr passwd 5 , +.Xr ftpd 8 diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y new file mode 100644 index 0000000..94475ec --- /dev/null +++ b/libexec/ftpd/ftpcmd.y @@ -0,0 +1,1813 @@ +/* + * Copyright (c) 1985, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 + */ + +/* + * Grammar for FTP commands. + * See RFC 959. + */ + +%{ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; +#endif +#endif /* not lint */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/ftp.h> + +#include <ctype.h> +#include <errno.h> +#include <glob.h> +#include <libutil.h> +#include <limits.h> +#include <md5.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "extern.h" +#include "pathnames.h" + +off_t restart_point; + +static int cmd_type; +static int cmd_form; +static int cmd_bytesz; +static int state; +char cbuf[512]; +char *fromname = NULL; + +%} + +%union { + struct { + off_t o; + int i; + } u; + char *s; +} + +%token + A B C E F I + L N P R S T + ALL + + SP CRLF COMMA + + USER PASS ACCT REIN QUIT PORT + PASV TYPE STRU MODE RETR STOR + APPE MLFL MAIL MSND MSOM MSAM + MRSQ MRCP ALLO REST RNFR RNTO + ABOR DELE CWD LIST NLST SITE + STAT HELP NOOP MKD RMD PWD + CDUP STOU SMNT SYST SIZE MDTM + LPRT LPSV EPRT EPSV FEAT + + UMASK IDLE CHMOD MDFIVE + + LEXERR NOTIMPL + +%token <s> STRING +%token <u> NUMBER + +%type <u.i> check_login octal_number byte_size +%type <u.i> check_login_ro check_login_epsv +%type <u.i> struct_code mode_code type_code form_code +%type <s> pathstring pathname password username +%type <s> ALL NOTIMPL + +%start cmd_list + +%% + +cmd_list + : /* empty */ + | cmd_list cmd + { + if (fromname) + free(fromname); + fromname = NULL; + restart_point = 0; + } + | cmd_list rcmd + ; + +cmd + : USER SP username CRLF + { + user($3); + free($3); + } + | PASS SP password CRLF + { + pass($3); + free($3); + } + | PASS CRLF + { + pass(""); + } + | PORT check_login SP host_port CRLF + { + if (epsvall) { + reply(501, "No PORT allowed after EPSV ALL."); + goto port_done; + } + if (!$2) + goto port_done; + if (port_check("PORT") == 1) + goto port_done; +#ifdef INET6 + if ((his_addr.su_family != AF_INET6 || + !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) { + /* shoud never happen */ + usedefault = 1; + reply(500, "Invalid address rejected."); + goto port_done; + } + port_check_v6("pcmd"); +#endif + port_done: + ; + } + | LPRT check_login SP host_long_port CRLF + { + if (epsvall) { + reply(501, "No LPRT allowed after EPSV ALL."); + goto lprt_done; + } + if (!$2) + goto lprt_done; + if (port_check("LPRT") == 1) + goto lprt_done; +#ifdef INET6 + if (his_addr.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + goto lprt_done; + } + if (port_check_v6("LPRT") == 1) + goto lprt_done; +#endif + lprt_done: + ; + } + | EPRT check_login SP STRING CRLF + { + char delim; + char *tmp = NULL; + char *p, *q; + char *result[3]; + struct addrinfo hints; + struct addrinfo *res; + int i; + + if (epsvall) { + reply(501, "No EPRT allowed after EPSV ALL."); + goto eprt_done; + } + if (!$2) + goto eprt_done; + + memset(&data_dest, 0, sizeof(data_dest)); + tmp = strdup($4); + if (ftpdebug) + syslog(LOG_DEBUG, "%s", tmp); + if (!tmp) { + fatalerror("not enough core"); + /*NOTREACHED*/ + } + p = tmp; + delim = p[0]; + p++; + memset(result, 0, sizeof(result)); + for (i = 0; i < 3; i++) { + q = strchr(p, delim); + if (!q || *q != delim) { + parsefail: + reply(500, + "Invalid argument, rejected."); + if (tmp) + free(tmp); + usedefault = 1; + goto eprt_done; + } + *q++ = '\0'; + result[i] = p; + if (ftpdebug) + syslog(LOG_DEBUG, "%d: %s", i, p); + p = q; + } + + /* some more sanity check */ + p = result[0]; + while (*p) { + if (!isdigit(*p)) + goto parsefail; + p++; + } + p = result[2]; + while (*p) { + if (!isdigit(*p)) + goto parsefail; + p++; + } + + /* grab address */ + memset(&hints, 0, sizeof(hints)); + if (atoi(result[0]) == 1) + hints.ai_family = PF_INET; +#ifdef INET6 + else if (atoi(result[0]) == 2) + hints.ai_family = PF_INET6; +#endif + else + hints.ai_family = PF_UNSPEC; /*XXX*/ + hints.ai_socktype = SOCK_STREAM; + i = getaddrinfo(result[1], result[2], &hints, &res); + if (i) + goto parsefail; + memcpy(&data_dest, res->ai_addr, res->ai_addrlen); +#ifdef INET6 + if (his_addr.su_family == AF_INET6 + && data_dest.su_family == AF_INET6) { + /* XXX more sanity checks! */ + data_dest.su_sin6.sin6_scope_id = + his_addr.su_sin6.sin6_scope_id; + } +#endif + free(tmp); + tmp = NULL; + + if (port_check("EPRT") == 1) + goto eprt_done; +#ifdef INET6 + if (his_addr.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + goto eprt_done; + } + if (port_check_v6("EPRT") == 1) + goto eprt_done; +#endif + eprt_done: + free($4); + } + | PASV check_login CRLF + { + if (epsvall) + reply(501, "No PASV allowed after EPSV ALL."); + else if ($2) + passive(); + } + | LPSV check_login CRLF + { + if (epsvall) + reply(501, "No LPSV allowed after EPSV ALL."); + else if ($2) + long_passive("LPSV", PF_UNSPEC); + } + | EPSV check_login_epsv SP NUMBER CRLF + { + if ($2) { + int pf; + switch ($4.i) { + case 1: + pf = PF_INET; + break; +#ifdef INET6 + case 2: + pf = PF_INET6; + break; +#endif + default: + pf = -1; /*junk value*/ + break; + } + long_passive("EPSV", pf); + } + } + | EPSV check_login_epsv SP ALL CRLF + { + if ($2) { + reply(200, "EPSV ALL command successful."); + epsvall++; + } + } + | EPSV check_login_epsv CRLF + { + if ($2) + long_passive("EPSV", PF_UNSPEC); + } + | TYPE check_login SP type_code CRLF + { + if ($2) { + switch (cmd_type) { + + case TYPE_A: + if (cmd_form == FORM_N) { + reply(200, "Type set to A."); + type = cmd_type; + form = cmd_form; + } else + reply(504, "Form must be N."); + break; + + case TYPE_E: + reply(504, "Type E not implemented."); + break; + + case TYPE_I: + reply(200, "Type set to I."); + type = cmd_type; + break; + + case TYPE_L: +#if CHAR_BIT == 8 + if (cmd_bytesz == 8) { + reply(200, + "Type set to L (byte size 8)."); + type = cmd_type; + } else + reply(504, "Byte size must be 8."); +#else /* CHAR_BIT == 8 */ + UNIMPLEMENTED for CHAR_BIT != 8 +#endif /* CHAR_BIT == 8 */ + } + } + } + | STRU check_login SP struct_code CRLF + { + if ($2) { + switch ($4) { + + case STRU_F: + reply(200, "STRU F accepted."); + break; + + default: + reply(504, "Unimplemented STRU type."); + } + } + } + | MODE check_login SP mode_code CRLF + { + if ($2) { + switch ($4) { + + case MODE_S: + reply(200, "MODE S accepted."); + break; + + default: + reply(502, "Unimplemented MODE type."); + } + } + } + | ALLO check_login SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | ALLO check_login SP NUMBER SP R SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | RETR check_login SP pathname CRLF + { + if (noretr || (guest && noguestretr)) + reply(500, "RETR command disabled."); + else if ($2 && $4 != NULL) + retrieve(NULL, $4); + + if ($4 != NULL) + free($4); + } + | STOR check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 0); + if ($4 != NULL) + free($4); + } + | APPE check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "a", 0); + if ($4 != NULL) + free($4); + } + | NLST check_login CRLF + { + if ($2) + send_file_list("."); + } + | NLST check_login SP pathstring CRLF + { + if ($2) + send_file_list($4); + free($4); + } + | LIST check_login CRLF + { + if ($2) + retrieve(_PATH_LS " -lgA", ""); + } + | LIST check_login SP pathstring CRLF + { + if ($2) + retrieve(_PATH_LS " -lgA %s", $4); + free($4); + } + | STAT check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + statfilecmd($4); + if ($4 != NULL) + free($4); + } + | STAT check_login CRLF + { + if ($2) { + statcmd(); + } + } + | DELE check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + delete($4); + if ($4 != NULL) + free($4); + } + | RNTO check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) { + if (fromname) { + renamecmd(fromname, $4); + free(fromname); + fromname = NULL; + } else { + reply(503, "Bad sequence of commands."); + } + } + if ($4 != NULL) + free($4); + } + | ABOR check_login CRLF + { + if ($2) + reply(225, "ABOR command successful."); + } + | CWD check_login CRLF + { + if ($2) { + cwd(homedir); + } + } + | CWD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + cwd($4); + if ($4 != NULL) + free($4); + } + | HELP CRLF + { + help(cmdtab, NULL); + } + | HELP SP STRING CRLF + { + char *cp = $3; + + if (strncasecmp(cp, "SITE", 4) == 0) { + cp = $3 + 4; + if (*cp == ' ') + cp++; + if (*cp) + help(sitetab, cp); + else + help(sitetab, NULL); + } else + help(cmdtab, $3); + free($3); + } + | NOOP CRLF + { + reply(200, "NOOP command successful."); + } + | MKD check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + makedir($4); + if ($4 != NULL) + free($4); + } + | RMD check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + removedir($4); + if ($4 != NULL) + free($4); + } + | PWD check_login CRLF + { + if ($2) + pwd(); + } + | CDUP check_login CRLF + { + if ($2) + cwd(".."); + } + | SITE SP HELP CRLF + { + help(sitetab, NULL); + } + | SITE SP HELP SP STRING CRLF + { + help(sitetab, $5); + free($5); + } + | SITE SP MDFIVE check_login SP pathname CRLF + { + char p[64], *q; + + if ($4 && $6) { + q = MD5File($6, p); + if (q != NULL) + reply(200, "MD5(%s) = %s", $6, p); + else + perror_reply(550, $6); + } + if ($6) + free($6); + } + | SITE SP UMASK check_login CRLF + { + int oldmask; + + if ($4) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o.", oldmask); + } + } + | SITE SP UMASK check_login SP octal_number CRLF + { + int oldmask; + + if ($4) { + if (($6 == -1) || ($6 > 0777)) { + reply(501, "Bad UMASK value."); + } else { + oldmask = umask($6); + reply(200, + "UMASK set to %03o (was %03o).", + $6, oldmask); + } + } + } + | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF + { + if ($4 && ($8 != NULL)) { + if (($6 == -1 ) || ($6 > 0777)) + reply(501, "Bad mode value."); + else if (chmod($8, $6) < 0) + perror_reply(550, $8); + else + reply(200, "CHMOD command successful."); + } + if ($8 != NULL) + free($8); + } + | SITE SP check_login IDLE CRLF + { + if ($3) + reply(200, + "Current IDLE time limit is %d seconds; max %d.", + timeout, maxtimeout); + } + | SITE SP check_login IDLE SP NUMBER CRLF + { + if ($3) { + if ($6.i < 30 || $6.i > maxtimeout) { + reply(501, + "Maximum IDLE time must be between 30 and %d seconds.", + maxtimeout); + } else { + timeout = $6.i; + (void) alarm(timeout); + reply(200, + "Maximum IDLE time set to %d seconds.", + timeout); + } + } + } + | STOU check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 1); + if ($4 != NULL) + free($4); + } + | FEAT CRLF + { + lreply(211, "Extensions supported:"); +#if 0 + /* XXX these two keywords are non-standard */ + printf(" EPRT\r\n"); + if (!noepsv) + printf(" EPSV\r\n"); +#endif + printf(" MDTM\r\n"); + printf(" REST STREAM\r\n"); + printf(" SIZE\r\n"); + if (assumeutf8) { + /* TVFS requires UTF8, see RFC 3659 */ + printf(" TVFS\r\n"); + printf(" UTF8\r\n"); + } + reply(211, "End."); + } + | SYST check_login CRLF + { + if ($2) { + if (hostinfo) +#ifdef BSD + reply(215, "UNIX Type: L%d Version: BSD-%d", + CHAR_BIT, BSD); +#else /* BSD */ + reply(215, "UNIX Type: L%d", CHAR_BIT); +#endif /* BSD */ + else + reply(215, "UNKNOWN Type: L%d", CHAR_BIT); + } + } + + /* + * SIZE is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return size of file in a format suitable for + * using with RESTART (we just count bytes). + */ + | SIZE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + sizecmd($4); + if ($4 != NULL) + free($4); + } + + /* + * MDTM is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return modification time of file as an ISO 3307 + * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx + * where xxx is the fractional second (of any precision, + * not necessarily 3 digits) + */ + | MDTM check_login SP pathname CRLF + { + if ($2 && $4 != NULL) { + struct stat stbuf; + if (stat($4, &stbuf) < 0) + perror_reply(550, $4); + else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", $4); + } else { + struct tm *t; + t = gmtime(&stbuf.st_mtime); + reply(213, + "%04d%02d%02d%02d%02d%02d", + 1900 + t->tm_year, + t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + } + if ($4 != NULL) + free($4); + } + | QUIT CRLF + { + reply(221, "Goodbye."); + dologout(0); + } + | NOTIMPL + { + nack($1); + } + | error + { + yyclearin; /* discard lookahead data */ + yyerrok; /* clear error condition */ + state = CMD; /* reset lexer state */ + } + ; +rcmd + : RNFR check_login_ro SP pathname CRLF + { + restart_point = 0; + if ($2 && $4) { + if (fromname) + free(fromname); + fromname = NULL; + if (renamefrom($4)) + fromname = $4; + else + free($4); + } else if ($4) { + free($4); + } + } + | REST check_login SP NUMBER CRLF + { + if ($2) { + if (fromname) + free(fromname); + fromname = NULL; + restart_point = $4.o; + reply(350, "Restarting at %jd. %s", + (intmax_t)restart_point, + "Send STORE or RETRIEVE to initiate transfer."); + } + } + ; + +username + : STRING + ; + +password + : /* empty */ + { + $$ = (char *)calloc(1, sizeof(char)); + } + | STRING + ; + +byte_size + : NUMBER + { + $$ = $1.i; + } + ; + +host_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER + { + char *a, *p; + + data_dest.su_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_sin.sin_port; + p[0] = $9.i; p[1] = $11.i; + a = (char *)&data_dest.su_sin.sin_addr; + a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i; + } + ; + +host_long_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in6); + data_dest.su_family = AF_INET6; + p = (char *)&data_dest.su_port; + p[0] = $39.i; p[1] = $41.i; + a = (char *)&data_dest.su_sin6.sin6_addr; + a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; + a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i; + a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i; + a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i; + if (his_addr.su_family == AF_INET6) { + /* XXX more sanity checks! */ + data_dest.su_sin6.sin6_scope_id = + his_addr.su_sin6.sin6_scope_id; + } + if ($1.i != 6 || $3.i != 16 || $37.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_sin.sin_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_port; + p[0] = $15.i; p[1] = $17.i; + a = (char *)&data_dest.su_sin.sin_addr; + a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; + if ($1.i != 4 || $3.i != 4 || $13.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + ; + +form_code + : N + { + $$ = FORM_N; + } + | T + { + $$ = FORM_T; + } + | C + { + $$ = FORM_C; + } + ; + +type_code + : A + { + cmd_type = TYPE_A; + cmd_form = FORM_N; + } + | A SP form_code + { + cmd_type = TYPE_A; + cmd_form = $3; + } + | E + { + cmd_type = TYPE_E; + cmd_form = FORM_N; + } + | E SP form_code + { + cmd_type = TYPE_E; + cmd_form = $3; + } + | I + { + cmd_type = TYPE_I; + } + | L + { + cmd_type = TYPE_L; + cmd_bytesz = CHAR_BIT; + } + | L SP byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $3; + } + /* this is for a bug in the BBN ftp */ + | L byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $2; + } + ; + +struct_code + : F + { + $$ = STRU_F; + } + | R + { + $$ = STRU_R; + } + | P + { + $$ = STRU_P; + } + ; + +mode_code + : S + { + $$ = MODE_S; + } + | B + { + $$ = MODE_B; + } + | C + { + $$ = MODE_C; + } + ; + +pathname + : pathstring + { + if (logged_in && $1) { + char *p; + + /* + * Expand ~user manually since glob(3) + * will return the unexpanded pathname + * if the corresponding file/directory + * doesn't exist yet. Using sole glob(3) + * would break natural commands like + * MKD ~user/newdir + * or + * RNTO ~/newfile + */ + if ((p = exptilde($1)) != NULL) { + $$ = expglob(p); + free(p); + } else + $$ = NULL; + free($1); + } else + $$ = $1; + } + ; + +pathstring + : STRING + ; + +octal_number + : NUMBER + { + int ret, dec, multby, digit; + + /* + * Convert a number that was read as decimal number + * to what it would be if it had been read as octal. + */ + dec = $1.i; + multby = 1; + ret = 0; + while (dec) { + digit = dec%10; + if (digit > 7) { + ret = -1; + break; + } + ret += digit * multby; + multby *= 8; + dec /= 10; + } + $$ = ret; + } + ; + + +check_login + : /* empty */ + { + $$ = check_login1(); + } + ; + +check_login_epsv + : /* empty */ + { + if (noepsv) { + reply(500, "EPSV command disabled."); + $$ = 0; + } + else + $$ = check_login1(); + } + ; + +check_login_ro + : /* empty */ + { + if (readonly) { + reply(550, "Permission denied."); + $$ = 0; + } + else + $$ = check_login1(); + } + ; + +%% + +#define CMD 0 /* beginning of command */ +#define ARGS 1 /* expect miscellaneous arguments */ +#define STR1 2 /* expect SP followed by STRING */ +#define STR2 3 /* expect STRING */ +#define OSTR 4 /* optional SP then STRING */ +#define ZSTR1 5 /* optional SP then optional STRING */ +#define ZSTR2 6 /* optional STRING after SP */ +#define SITECMD 7 /* SITE command */ +#define NSTR 8 /* Number followed by a string */ + +#define MAXGLOBARGS 1000 + +#define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */ + +struct tab { + char *name; + short token; + short state; + short implemented; /* 1 if command is implemented */ + char *help; +}; + +struct tab cmdtab[] = { /* In order defined in RFC 765 */ + { "USER", USER, STR1, 1, "<sp> username" }, + { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" }, + { "ACCT", ACCT, STR1, 0, "(specify account)" }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, + { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, + { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, + { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" }, + { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." }, + { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" }, + { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, + { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" }, + { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" }, + { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" }, + { "STRU", STRU, ARGS, 1, "(specify file structure)" }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, + { "RETR", RETR, STR1, 1, "<sp> file-name" }, + { "STOR", STOR, STR1, 1, "<sp> file-name" }, + { "APPE", APPE, STR1, 1, "<sp> file-name" }, + { "MLFL", MLFL, OSTR, 0, "(mail file)" }, + { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, + { "REST", REST, ARGS, 1, "<sp> offset (restart command)" }, + { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, + { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, + { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, + { "DELE", DELE, STR1, 1, "<sp> file-name" }, + { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, + { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, + { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, + { "FEAT", FEAT, ARGS, 1, "(get extended features)" }, + { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { "NOOP", NOOP, ARGS, 1, "" }, + { "MKD", MKD, STR1, 1, "<sp> path-name" }, + { "XMKD", MKD, STR1, 1, "<sp> path-name" }, + { "RMD", RMD, STR1, 1, "<sp> path-name" }, + { "XRMD", RMD, STR1, 1, "<sp> path-name" }, + { "PWD", PWD, ARGS, 1, "(return current directory)" }, + { "XPWD", PWD, ARGS, 1, "(return current directory)" }, + { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "STOU", STOU, STR1, 1, "<sp> file-name" }, + { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, + { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, + { NULL, 0, 0, 0, 0 } +}; + +struct tab sitetab[] = { + { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" }, + { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, + { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, + { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { NULL, 0, 0, 0, 0 } +}; + +static char *copy(char *); +static char *expglob(char *); +static char *exptilde(char *); +static void help(struct tab *, char *); +static struct tab * + lookup(struct tab *, char *); +static int port_check(const char *); +#ifdef INET6 +static int port_check_v6(const char *); +#endif +static void sizecmd(char *); +static void toolong(int); +#ifdef INET6 +static void v4map_data_dest(void); +#endif +static int yylex(void); + +static struct tab * +lookup(struct tab *p, char *cmd) +{ + + for (; p->name != NULL; p++) + if (strcmp(cmd, p->name) == 0) + return (p); + return (0); +} + +#include <arpa/telnet.h> + +/* + * getline - a hacked up version of fgets to ignore TELNET escape codes. + */ +int +getline(char *s, int n, FILE *iop) +{ + int c; + register char *cs; + sigset_t sset, osset; + + cs = s; +/* tmpline may contain saved command from urgent mode interruption */ + for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { + *cs++ = tmpline[c]; + if (tmpline[c] == '\n') { + *cs++ = '\0'; + if (ftpdebug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(0); + } + if (c == 0) + tmpline[0] = '\0'; + } + /* SIGURG would interrupt stdio if not blocked during the read loop */ + sigemptyset(&sset); + sigaddset(&sset, SIGURG); + sigprocmask(SIG_BLOCK, &sset, &osset); + while ((c = getc(iop)) != EOF) { + c &= 0377; + if (c == IAC) { + if ((c = getc(iop)) == EOF) + goto got_eof; + c &= 0377; + switch (c) { + case WILL: + case WONT: + if ((c = getc(iop)) == EOF) + goto got_eof; + printf("%c%c%c", IAC, DONT, 0377&c); + (void) fflush(stdout); + continue; + case DO: + case DONT: + if ((c = getc(iop)) == EOF) + goto got_eof; + printf("%c%c%c", IAC, WONT, 0377&c); + (void) fflush(stdout); + continue; + case IAC: + break; + default: + continue; /* ignore command */ + } + } + *cs++ = c; + if (--n <= 0) { + /* + * If command doesn't fit into buffer, discard the + * rest of the command and indicate truncation. + * This prevents the command to be split up into + * multiple commands. + */ + while (c != '\n' && (c = getc(iop)) != EOF) + ; + return (-2); + } + if (c == '\n') + break; + } +got_eof: + sigprocmask(SIG_SETMASK, &osset, NULL); + if (c == EOF && cs == s) + return (-1); + *cs++ = '\0'; + if (ftpdebug) { + if (!guest && strncasecmp("pass ", s, 5) == 0) { + /* Don't syslog passwords */ + syslog(LOG_DEBUG, "command: %.5s ???", s); + } else { + register char *cp; + register int len; + + /* Don't syslog trailing CR-LF */ + len = strlen(s); + cp = s + len - 1; + while (cp >= s && (*cp == '\n' || *cp == '\r')) { + --cp; + --len; + } + syslog(LOG_DEBUG, "command: %.*s", len, s); + } + } + return (0); +} + +static void +toolong(int signo) +{ + + reply(421, + "Timeout (%d seconds): closing control connection.", timeout); + if (logging) + syslog(LOG_INFO, "User %s timed out after %d seconds", + (pw ? pw -> pw_name : "unknown"), timeout); + dologout(1); +} + +static int +yylex(void) +{ + static int cpos; + char *cp, *cp2; + struct tab *p; + int n; + char c; + + for (;;) { + switch (state) { + + case CMD: + (void) signal(SIGALRM, toolong); + (void) alarm(timeout); + n = getline(cbuf, sizeof(cbuf)-1, stdin); + if (n == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (n == -2) { + reply(500, "Command too long."); + (void) alarm(0); + continue; + } + (void) alarm(0); +#ifdef SETPROCTITLE + if (strncasecmp(cbuf, "PASS", 4) != 0) + setproctitle("%s: %s", proctitle, cbuf); +#endif /* SETPROCTITLE */ + if ((cp = strchr(cbuf, '\r'))) { + *cp++ = '\n'; + *cp = '\0'; + } + if ((cp = strpbrk(cbuf, " \n"))) + cpos = cp - cbuf; + if (cpos == 0) + cpos = 4; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cbuf); + p = lookup(cmdtab, cbuf); + cbuf[cpos] = c; + if (p != 0) { + yylval.s = p->name; + if (!p->implemented) + return (NOTIMPL); /* state remains CMD */ + state = p->state; + return (p->token); + } + break; + + case SITECMD: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + cp = &cbuf[cpos]; + if ((cp2 = strpbrk(cp, " \n"))) + cpos = cp2 - cbuf; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cp); + p = lookup(sitetab, cp); + cbuf[cpos] = c; + if (guest == 0 && p != 0) { + yylval.s = p->name; + if (!p->implemented) { + state = CMD; + return (NOTIMPL); + } + state = p->state; + return (p->token); + } + state = CMD; + break; + + case ZSTR1: + case OSTR: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR1: + dostr1: + if (cbuf[cpos] == ' ') { + cpos++; + state = state == OSTR ? STR2 : state+1; + return (SP); + } + break; + + case ZSTR2: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR2: + cp = &cbuf[cpos]; + n = strlen(cp); + cpos += n - 1; + /* + * Make sure the string is nonempty and \n terminated. + */ + if (n > 1 && cbuf[cpos] == '\n') { + cbuf[cpos] = '\0'; + yylval.s = copy(cp); + cbuf[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.u.i = atoi(cp); + cbuf[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + case ARGS: + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.u.i = atoi(cp); + yylval.u.o = strtoull(cp, NULL, 10); + cbuf[cpos] = c; + return (NUMBER); + } + if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 + && !isalnum(cbuf[cpos + 3])) { + cpos += 3; + return ALL; + } + switch (cbuf[cpos++]) { + + case '\n': + state = CMD; + return (CRLF); + + case ' ': + return (SP); + + case ',': + return (COMMA); + + case 'A': + case 'a': + return (A); + + case 'B': + case 'b': + return (B); + + case 'C': + case 'c': + return (C); + + case 'E': + case 'e': + return (E); + + case 'F': + case 'f': + return (F); + + case 'I': + case 'i': + return (I); + + case 'L': + case 'l': + return (L); + + case 'N': + case 'n': + return (N); + + case 'P': + case 'p': + return (P); + + case 'R': + case 'r': + return (R); + + case 'S': + case 's': + return (S); + + case 'T': + case 't': + return (T); + + } + break; + + default: + fatalerror("Unknown state in scanner."); + } + state = CMD; + return (LEXERR); + } +} + +void +upper(char *s) +{ + while (*s != '\0') { + if (islower(*s)) + *s = toupper(*s); + s++; + } +} + +static char * +copy(char *s) +{ + char *p; + + p = malloc(strlen(s) + 1); + if (p == NULL) + fatalerror("Ran out of memory."); + (void) strcpy(p, s); + return (p); +} + +static void +help(struct tab *ctab, char *s) +{ + struct tab *c; + int width, NCMDS; + char *type; + + if (ctab == sitetab) + type = "SITE "; + else + type = ""; + width = 0, NCMDS = 0; + for (c = ctab; c->name != NULL; c++) { + int len = strlen(c->name); + + if (len > width) + width = len; + NCMDS++; + } + width = (width + 8) &~ 7; + if (s == 0) { + int i, j, w; + int columns, lines; + + lreply(214, "The following %scommands are recognized %s.", + type, "(* =>'s unimplemented)"); + columns = 76 / width; + if (columns == 0) + columns = 1; + lines = (NCMDS + columns - 1) / columns; + for (i = 0; i < lines; i++) { + printf(" "); + for (j = 0; j < columns; j++) { + c = ctab + j * lines + i; + printf("%s%c", c->name, + c->implemented ? ' ' : '*'); + if (c + lines >= &ctab[NCMDS]) + break; + w = strlen(c->name) + 1; + while (w < width) { + putchar(' '); + w++; + } + } + printf("\r\n"); + } + (void) fflush(stdout); + if (hostinfo) + reply(214, "Direct comments to ftp-bugs@%s.", hostname); + else + reply(214, "End."); + return; + } + upper(s); + c = lookup(ctab, s); + if (c == NULL) { + reply(502, "Unknown command %s.", s); + return; + } + if (c->implemented) + reply(214, "Syntax: %s%s %s", type, c->name, c->help); + else + reply(214, "%s%-*s\t%s; unimplemented.", type, width, + c->name, c->help); +} + +static void +sizecmd(char *filename) +{ + switch (type) { + case TYPE_L: + case TYPE_I: { + struct stat stbuf; + if (stat(filename, &stbuf) < 0) + perror_reply(550, filename); + else if (!S_ISREG(stbuf.st_mode)) + reply(550, "%s: not a plain file.", filename); + else + reply(213, "%jd", (intmax_t)stbuf.st_size); + break; } + case TYPE_A: { + FILE *fin; + int c; + off_t count; + struct stat stbuf; + fin = fopen(filename, "r"); + if (fin == NULL) { + perror_reply(550, filename); + return; + } + if (fstat(fileno(fin), &stbuf) < 0) { + perror_reply(550, filename); + (void) fclose(fin); + return; + } else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", filename); + (void) fclose(fin); + return; + } else if (stbuf.st_size > MAXASIZE) { + reply(550, "%s: too large for type A SIZE.", filename); + (void) fclose(fin); + return; + } + + count = 0; + while((c=getc(fin)) != EOF) { + if (c == '\n') /* will get expanded to \r\n */ + count++; + count++; + } + (void) fclose(fin); + + reply(213, "%jd", (intmax_t)count); + break; } + default: + reply(504, "SIZE not implemented for type %s.", + typenames[type]); + } +} + +/* Return 1, if port check is done. Return 0, if not yet. */ +static int +port_check(const char *pcmd) +{ + if (his_addr.su_family == AF_INET) { + if (data_dest.su_family != AF_INET) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return 1; + } + if (paranoid && + ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || + memcmp(&data_dest.su_sin.sin_addr, + &his_addr.su_sin.sin_addr, + sizeof(data_dest.su_sin.sin_addr)))) { + usedefault = 1; + reply(500, "Illegal PORT range rejected."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "%s command successful.", pcmd); + } + return 1; + } + return 0; +} + +static int +check_login1(void) +{ + if (logged_in) + return 1; + else { + reply(530, "Please login with USER and PASS."); + return 0; + } +} + +/* + * Replace leading "~user" in a pathname by the user's login directory. + * Returned string will be in a freshly malloced buffer unless it's NULL. + */ +static char * +exptilde(char *s) +{ + char *p, *q; + char *path, *user; + struct passwd *ppw; + + if ((p = strdup(s)) == NULL) + return (NULL); + if (*p != '~') + return (p); + + user = p + 1; /* skip tilde */ + if ((path = strchr(p, '/')) != NULL) + *(path++) = '\0'; /* separate ~user from the rest of path */ + if (*user == '\0') /* no user specified, use the current user */ + user = pw->pw_name; + /* read passwd even for the current user since we may be chrooted */ + if ((ppw = getpwnam(user)) != NULL) { + /* user found, substitute login directory for ~user */ + if (path) + asprintf(&q, "%s/%s", ppw->pw_dir, path); + else + q = strdup(ppw->pw_dir); + free(p); + p = q; + } else { + /* user not found, undo the damage */ + if (path) + path[-1] = '/'; + } + return (p); +} + +/* + * Expand glob(3) patterns possibly present in a pathname. + * Avoid expanding to a pathname including '\r' or '\n' in order to + * not disrupt the FTP protocol. + * The expansion found must be unique. + * Return the result as a malloced string, or NULL if an error occurred. + * + * Problem: this production is used for all pathname + * processing, but only gives a 550 error reply. + * This is a valid reply in some cases but not in others. + */ +static char * +expglob(char *s) +{ + char *p, **pp, *rval; + int flags = GLOB_BRACE | GLOB_NOCHECK; + int n; + glob_t gl; + + memset(&gl, 0, sizeof(gl)); + flags |= GLOB_LIMIT; + gl.gl_matchc = MAXGLOBARGS; + if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) { + for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++) + if (*(*pp + strcspn(*pp, "\r\n")) == '\0') { + p = *pp; + n++; + } + if (n == 0) + rval = strdup(s); + else if (n == 1) + rval = strdup(p); + else { + reply(550, "Wildcard is ambiguous."); + rval = NULL; + } + } else { + reply(550, "Wildcard expansion error."); + rval = NULL; + } + globfree(&gl); + return (rval); +} + +#ifdef INET6 +/* Return 1, if port check is done. Return 0, if not yet. */ +static int +port_check_v6(const char *pcmd) +{ + if (his_addr.su_family == AF_INET6) { + if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) + /* Convert data_dest into v4 mapped sockaddr.*/ + v4map_data_dest(); + if (data_dest.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return 1; + } + if (paranoid && + ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || + memcmp(&data_dest.su_sin6.sin6_addr, + &his_addr.su_sin6.sin6_addr, + sizeof(data_dest.su_sin6.sin6_addr)))) { + usedefault = 1; + reply(500, "Illegal PORT range rejected."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "%s command successful.", pcmd); + } + return 1; + } + return 0; +} + +static void +v4map_data_dest(void) +{ + struct in_addr savedaddr; + int savedport; + + if (data_dest.su_family != AF_INET) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return; + } + + savedaddr = data_dest.su_sin.sin_addr; + savedport = data_dest.su_port; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6); + data_dest.su_sin6.sin6_family = AF_INET6; + data_dest.su_sin6.sin6_port = savedport; + memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2); + memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12], + (caddr_t)&savedaddr, sizeof(savedaddr)); +} +#endif diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 new file mode 100644 index 0000000..50565e9 --- /dev/null +++ b/libexec/ftpd/ftpd.8 @@ -0,0 +1,574 @@ +.\" Copyright (c) 1985, 1988, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)ftpd.8 8.2 (Berkeley) 4/19/94 +.\" $FreeBSD$ +.\" +.Dd January 21, 2010 +.Dt FTPD 8 +.Os +.Sh NAME +.Nm ftpd +.Nd Internet File Transfer Protocol server +.Sh SYNOPSIS +.Nm +.Op Fl 468ADdEhMmOoRrSUvW +.Op Fl l Op Fl l +.Op Fl a Ar address +.Op Fl P Ar port +.Op Fl p Ar file +.Op Fl T Ar maxtimeout +.Op Fl t Ar timeout +.Op Fl u Ar umask +.Sh DESCRIPTION +The +.Nm +utility is the +Internet File Transfer Protocol +server process. +The server uses the +.Tn TCP +protocol +and listens at the port specified with the +.Fl P +option or in the +.Dq ftp +service specification; see +.Xr services 5 . +.Pp +Available options: +.Bl -tag -width indent +.It Fl 4 +When +.Fl D +is specified, accept connections via +.Dv AF_INET +socket. +.It Fl 6 +When +.Fl D +is specified, accept connections via +.Dv AF_INET6 +socket. +.It Fl 8 +Enable transparent UTF-8 mode. +RFC\ 2640 compliant clients will be told that the character encoding +used by the server is UTF-8, which is the only effect of the option. +.Pp +This option does not enable any encoding conversion for server file names; +it implies instead that the names of files on the server are encoded +in UTF-8. +As for files uploaded via FTP, it is the duty of the RFC\ 2640 compliant +client to convert their names from the client's local encoding to UTF-8. +FTP command names and own +.Nm +messages are always encoded in ASCII, which is a subset of UTF-8. +Hence no need for server-side conversion at all. +.It Fl A +Allow only anonymous ftp access. +.It Fl a +When +.Fl D +is specified, accept connections only on the specified +.Ar address . +.It Fl D +With this option set, +.Nm +will detach and become a daemon, accepting connections on the FTP port and +forking children processes to handle them. +This is lower overhead than starting +.Nm +from +.Xr inetd 8 +and is thus useful on busy servers to reduce load. +.It Fl d +Debugging information is written to the syslog using +.Dv LOG_FTP . +.It Fl E +Disable the EPSV command. +This is useful for servers behind older firewalls. +.It Fl h +Disable printing host-specific information, such as the +server software version or hostname, in server messages. +.It Fl l +Each successful and failed +.Xr ftp 1 +session is logged using syslog with a facility of +.Dv LOG_FTP . +If this option is specified twice, the retrieve (get), store (put), append, +delete, make directory, remove directory and rename operations and +their filename arguments are also logged. +By default, +.Xr syslogd 8 +logs these to +.Pa /var/log/xferlog . +.It Fl M +Prevent anonymous users from creating directories. +.It Fl m +Permit anonymous users to overwrite or modify +existing files if allowed by file system permissions. +By default, anonymous users cannot modify existing files; +in particular, files to upload will be created under a unique name. +.It Fl O +Put server in write-only mode for anonymous users only. +RETR is disabled for anonymous users, preventing anonymous downloads. +This has no effect if +.Fl o +is also specified. +.It Fl o +Put server in write-only mode. +RETR is disabled, preventing downloads. +.It Fl P +When +.Fl D +is specified, accept connections at +.Ar port , +specified as a numeric value or service name, instead of at the default +.Dq ftp +port. +.It Fl p +When +.Fl D +is specified, write the daemon's process ID to +.Ar file +instead of the default pid file, +.Pa /var/run/ftpd.pid . +.It Fl R +With this option set, +.Nm +will revert to historical behavior with regard to security checks on +user operations and restrictions on PORT requests. +Currently, +.Nm +will only honor PORT commands directed to unprivileged ports on the +remote user's host (which violates the FTP protocol specification but +closes some security holes). +.It Fl r +Put server in read-only mode. +All commands which may modify the local file system are disabled. +.It Fl S +With this option set, +.Nm +logs all anonymous file downloads to the file +.Pa /var/log/ftpd +when this file exists. +.It Fl T +A client may also request a different timeout period; +the maximum period allowed may be set to +.Ar timeout +seconds with the +.Fl T +option. +The default limit is 2 hours. +.It Fl t +The inactivity timeout period is set to +.Ar timeout +seconds (the default is 15 minutes). +.It Fl U +This option instructs ftpd to use data ports in the range of +.Dv IP_PORTRANGE_DEFAULT +instead of in the range of +.Dv IP_PORTRANGE_HIGH . +Such a change may be useful for some specific firewall configurations; +see +.Xr ip 4 +for more information. +.Pp +Note that option is a virtual no-op in +.Fx 5.0 +and above; both port +ranges are identical by default. +.It Fl u +The default file creation mode mask is set to +.Ar umask , +which is expected to be an octal numeric value. +Refer to +.Xr umask 2 +for details. +This option may be overridden by +.Xr login.conf 5 . +.It Fl v +A synonym for +.Fl d . +.It Fl W +Do not log FTP sessions to the user accounting database. +.El +.Pp +The file +.Pa /var/run/nologin +can be used to disable ftp access. +If the file exists, +.Nm +displays it and exits. +If the file +.Pa /etc/ftpwelcome +exists, +.Nm +prints it before issuing the +.Dq ready +message. +If the file +.Pa /etc/ftpmotd +exists, +.Nm +prints it after a successful login. +Note the motd file used is the one +relative to the login environment. +This means the one in +.Pa ~ftp/etc +in the anonymous user's case. +.Pp +The ftp server currently supports the following ftp requests. +The case of the requests is ignored. +Requests marked [RW] are +disabled if +.Fl r +is specified. +.Bl -column "Request" -offset indent +.It Sy Request Ta Sy "Description" +.It ABOR Ta "abort previous command" +.It ACCT Ta "specify account (ignored)" +.It ALLO Ta "allocate storage (vacuously)" +.It APPE Ta "append to a file [RW]" +.It CDUP Ta "change to parent of current working directory" +.It CWD Ta "change working directory" +.It DELE Ta "delete a file [RW]" +.It EPRT Ta "specify data connection port, multiprotocol" +.It EPSV Ta "prepare for server-to-server transfer, multiprotocol" +.It FEAT Ta "give information on extended features of server" +.It HELP Ta "give help information" +.It LIST Ta "give list files in a directory" Pq Dq Li "ls -lgA" +.It LPRT Ta "specify data connection port, multiprotocol" +.It LPSV Ta "prepare for server-to-server transfer, multiprotocol" +.It MDTM Ta "show last modification time of file" +.It MKD Ta "make a directory [RW]" +.It MODE Ta "specify data transfer" Em mode +.It NLST Ta "give name list of files in directory" +.It NOOP Ta "do nothing" +.It PASS Ta "specify password" +.It PASV Ta "prepare for server-to-server transfer" +.It PORT Ta "specify data connection port" +.It PWD Ta "print the current working directory" +.It QUIT Ta "terminate session" +.It REST Ta "restart incomplete transfer" +.It RETR Ta "retrieve a file" +.It RMD Ta "remove a directory [RW]" +.It RNFR Ta "specify rename-from file name [RW]" +.It RNTO Ta "specify rename-to file name [RW]" +.It SITE Ta "non-standard commands (see next section)" +.It SIZE Ta "return size of file" +.It STAT Ta "return status of server" +.It STOR Ta "store a file [RW]" +.It STOU Ta "store a file with a unique name [RW]" +.It STRU Ta "specify data transfer" Em structure +.It SYST Ta "show operating system type of server system" +.It TYPE Ta "specify data transfer" Em type +.It USER Ta "specify user name" +.It XCUP Ta "change to parent of current working directory (deprecated)" +.It XCWD Ta "change working directory (deprecated)" +.It XMKD Ta "make a directory (deprecated) [RW]" +.It XPWD Ta "print the current working directory (deprecated)" +.It XRMD Ta "remove a directory (deprecated) [RW]" +.El +.Pp +The following non-standard or +.Ux +specific commands are supported +by the +SITE request. +.Bl -column Request -offset indent +.It Sy Request Ta Sy Description +.It UMASK Ta change umask, e.g. ``SITE UMASK 002'' +.It IDLE Ta set idle-timer, e.g. ``SITE IDLE 60'' +.It CHMOD Ta "change mode of a file [RW], e.g. ``SITE CHMOD 755 filename''" +.It MD5 Ta "report the files MD5 checksum, e.g. ``SITE MD5 filename''" +.It HELP Ta give help information +.El +.Pp +Note: SITE requests are disabled in case of anonymous logins. +.Pp +The remaining ftp requests specified in Internet RFC 959 +are +recognized, but not implemented. +MDTM and SIZE are not specified in RFC 959, but will appear in the +next updated FTP RFC. +To avoid possible denial-of-service attacks, SIZE requests against +files larger than 10240 bytes will be denied if the current transfer +type is ASCII. +.Pp +The ftp server will abort an active file transfer only when the +ABOR +command is preceded by a Telnet "Interrupt Process" (IP) +signal and a Telnet "Synch" signal in the command Telnet stream, +as described in Internet RFC 959. +If a +STAT +command is received during a data transfer, preceded by a Telnet IP +and Synch, transfer status will be returned. +.Pp +The +.Nm +utility interprets file names according to the +.Dq globbing +conventions used by +.Xr csh 1 . +This allows users to utilize the metacharacters +.Dq Li \&*?[]{}~ . +.Pp +The +.Nm +utility authenticates users according to six rules. +.Bl -enum -offset indent +.It +The login name must be in the password data base +and not have a null password. +In this case a password must be provided by the client before any +file operations may be performed. +If the user has an OPIE key, the response from a successful USER +command will include an OPIE challenge. +The client may choose to respond with a PASS command giving either +a standard password or an OPIE one-time password. +The server will automatically determine which type of +password it has been given and attempt to authenticate accordingly. +See +.Xr opie 4 +for more information on OPIE authentication. +.It +The login name must not appear in the file +.Pa /etc/ftpusers . +.It +The login name must not be a member of a group specified in the file +.Pa /etc/ftpusers . +Entries in this file interpreted as group names are prefixed by an "at" +.Ql \&@ +sign. +.It +The user must have a standard shell returned by +.Xr getusershell 3 . +.It +If the user name appears in the file +.Pa /etc/ftpchroot , +or the user is a member of a group with a group entry in this file, +i.e., one prefixed with +.Ql \&@ , +the session's root will be changed to the directory specified +in this file or to the user's login directory by +.Xr chroot 2 +as for an +.Dq anonymous +or +.Dq ftp +account (see next item). +See +.Xr ftpchroot 5 +for a detailed description of the format of this file. +This facility may also be triggered by enabling the boolean "ftp-chroot" +capability in +.Xr login.conf 5 . +However, the user must still supply a password. +This feature is intended as a compromise between a fully anonymous +account and a fully privileged account. +The account should also be set up as for an anonymous account. +.It +If the user name is +.Dq anonymous +or +.Dq ftp , +an +anonymous ftp account must be present in the password +file (user +.Dq ftp ) . +In this case the user is allowed +to log in by specifying any password (by convention an email address for +the user should be used as the password). +When the +.Fl S +option is set, all transfers are logged as well. +.El +.Pp +In the last case, +.Nm +takes special measures to restrict the client's access privileges. +The server performs a +.Xr chroot 2 +to the home directory of the +.Dq ftp +user. +As a special case if the +.Dq ftp +user's home directory pathname contains the +.Pa /./ +separator, +.Nm +uses its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. +A typical example for this case would be +.Pa /usr/local/ftp/./pub . +In order that system security is not breached, it is recommended +that the +.Dq ftp +subtree be constructed with care, following these rules: +.Bl -tag -width "~ftp/pub" -offset indent +.It Pa ~ftp +Make the home directory owned by +.Dq root +and unwritable by anyone. +.It Pa ~ftp/etc +Make this directory owned by +.Dq root +and unwritable by anyone (mode 555). +The files pwd.db (see +.Xr passwd 5 ) +and +.Xr group 5 +must be present for the +.Xr ls 1 +command to be able to produce owner names rather than numbers. +The password field in +.Xr passwd 5 +is not used, and should not contain real passwords. +The file +.Pa ftpmotd , +if present, will be printed after a successful login. +These files should be mode 444. +.It Pa ~ftp/pub +This directory and the subdirectories beneath it should be owned +by the users and groups responsible for placing files in them, +and be writable only by them (mode 755 or 775). +They should +.Em not +be owned or writable by +.Dq ftp +or its group, otherwise guest users +can fill the drive with unwanted files. +.El +.Pp +If the system has multiple IP addresses, +.Nm +supports the idea of virtual hosts, which provides the ability to +define multiple anonymous ftp areas, each one allocated to a different +internet address. +The file +.Pa /etc/ftphosts +contains information pertaining to each of the virtual hosts. +Each host is defined on its own line which contains a number of +fields separated by whitespace: +.Bl -tag -offset indent -width hostname +.It hostname +Contains the hostname or IP address of the virtual host. +.It user +Contains a user record in the system password file. +As with normal anonymous ftp, this user's access uid, gid and group +memberships determine file access to the anonymous ftp area. +The anonymous ftp area (to which any user is chrooted on login) +is determined by the home directory defined for the account. +User id and group for any ftp account may be the same as for the +standard ftp user. +.It statfile +File to which all file transfers are logged, which +defaults to +.Pa /var/log/ftpd . +.It welcome +This file is the welcome message displayed before the server ready +prompt. +It defaults to +.Pa /etc/ftpwelcome . +.It motd +This file is displayed after the user logs in. +It defaults to +.Pa /etc/ftpmotd . +.El +.Pp +Lines beginning with a '#' are ignored and can be used to include +comments. +.Pp +Defining a virtual host for the primary IP address or hostname +changes the default for ftp logins to that address. +The 'user', 'statfile', 'welcome' and 'motd' fields may be left +blank, or a single hyphen '-' used to indicate that the default +value is to be used. +.Pp +As with any anonymous login configuration, due care must be given +to setup and maintenance to guard against security related problems. +.Pp +The +.Nm +utility has internal support for handling remote requests to list +files, and will not execute +.Pa /bin/ls +in either a chrooted or non-chrooted environment. +The +.Pa ~/bin/ls +executable need not be placed into the chrooted tree, nor need the +.Pa ~/bin +directory exist. +.Sh FILES +.Bl -tag -width ".Pa /var/run/ftpd.pid" -compact +.It Pa /etc/ftpusers +List of unwelcome/restricted users. +.It Pa /etc/ftpchroot +List of normal users who should be chroot'd. +.It Pa /etc/ftphosts +Virtual hosting configuration file. +.It Pa /etc/ftpwelcome +Welcome notice. +.It Pa /etc/ftpmotd +Welcome notice after login. +.It Pa /var/run/ftpd.pid +Default pid file for daemon mode. +.It Pa /var/run/nologin +Displayed and access refused. +.It Pa /var/log/ftpd +Log file for anonymous transfers. +.It Pa /var/log/xferlog +Default place for session logs. +.El +.Sh SEE ALSO +.Xr ftp 1 , +.Xr umask 2 , +.Xr getusershell 3 , +.Xr opie 4 , +.Xr ftpchroot 5 , +.Xr login.conf 5 , +.Xr inetd 8 , +.Xr syslogd 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . +IPv6 support was added in WIDE Hydrangea IPv6 stack kit. +.Sh BUGS +The server must run as the super-user +to create sockets with privileged port numbers. +It maintains +an effective user id of the logged in user, reverting to +the super-user only when binding addresses to sockets. +The +possible security holes have been extensively +scrutinized, but are possibly incomplete. diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c new file mode 100644 index 0000000..b3c78f6 --- /dev/null +++ b/libexec/ftpd/ftpd.c @@ -0,0 +1,3490 @@ +/* + * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char copyright[] = +"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; +#endif +#endif /* not lint */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * FTP server. + */ +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#define FTP_NAMES +#include <arpa/ftp.h> +#include <arpa/inet.h> +#include <arpa/telnet.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <grp.h> +#include <opie.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <libutil.h> +#ifdef LOGIN_CAP +#include <login_cap.h> +#endif + +#ifdef USE_PAM +#include <security/pam_appl.h> +#endif + +#include "pathnames.h" +#include "extern.h" + +#include <stdarg.h> + +static char version[] = "Version 6.00LS"; +#undef main + +union sockunion ctrl_addr; +union sockunion data_source; +union sockunion data_dest; +union sockunion his_addr; +union sockunion pasv_addr; + +int daemon_mode; +int data; +int dataport; +int hostinfo = 1; /* print host-specific info in messages */ +int logged_in; +struct passwd *pw; +char *homedir; +int ftpdebug; +int timeout = 900; /* timeout after 15 minutes of inactivity */ +int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ +int logging; +int restricted_data_ports = 1; +int paranoid = 1; /* be extra careful about security */ +int anon_only = 0; /* Only anonymous ftp allowed */ +int assumeutf8 = 0; /* Assume that server file names are in UTF-8 */ +int guest; +int dochroot; +char *chrootdir; +int dowtmp = 1; +int stats; +int statfd = -1; +int type; +int form; +int stru; /* avoid C keyword */ +int mode; +int usedefault = 1; /* for data transfers */ +int pdata = -1; /* for passive mode */ +int readonly = 0; /* Server is in readonly mode. */ +int noepsv = 0; /* EPSV command is disabled. */ +int noretr = 0; /* RETR command is disabled. */ +int noguestretr = 0; /* RETR command is disabled for anon users. */ +int noguestmkd = 0; /* MKD command is disabled for anon users. */ +int noguestmod = 1; /* anon users may not modify existing files. */ + +off_t file_size; +off_t byte_count; +#if !defined(CMASK) || CMASK == 0 +#undef CMASK +#define CMASK 027 +#endif +int defumask = CMASK; /* default umask value */ +char tmpline[7]; +char *hostname; +int epsvall = 0; + +#ifdef VIRTUAL_HOSTING +char *ftpuser; + +static struct ftphost { + struct ftphost *next; + struct addrinfo *hostinfo; + char *hostname; + char *anonuser; + char *statfile; + char *welcome; + char *loginmsg; +} *thishost, *firsthost; + +#endif +char remotehost[NI_MAXHOST]; +char *ident = NULL; + +static char wtmpid[20]; + +#ifdef USE_PAM +static int auth_pam(struct passwd**, const char*); +pam_handle_t *pamh = NULL; +#endif + +static struct opie opiedata; +static char opieprompt[OPIE_CHALLENGE_MAX+1]; +static int pwok; + +char *pid_file = NULL; /* means default location to pidfile(3) */ + +/* + * Limit number of pathnames that glob can return. + * A limit of 0 indicates the number of pathnames is unlimited. + */ +#define MAXGLOBARGS 16384 +# + +/* + * Timeout intervals for retrying connections + * to hosts that don't accept PORT cmds. This + * is a kludge, but given the problems with TCP... + */ +#define SWAITMAX 90 /* wait at most 90 seconds */ +#define SWAITINT 5 /* interval between retries */ + +int swaitmax = SWAITMAX; +int swaitint = SWAITINT; + +#ifdef SETPROCTITLE +#ifdef OLD_SETPROCTITLE +char **Argv = NULL; /* pointer to argument vector */ +char *LastArgv = NULL; /* end of argv */ +#endif /* OLD_SETPROCTITLE */ +char proctitle[LINE_MAX]; /* initial part of title */ +#endif /* SETPROCTITLE */ + +#define LOGCMD(cmd, file) logcmd((cmd), (file), NULL, -1) +#define LOGCMD2(cmd, file1, file2) logcmd((cmd), (file1), (file2), -1) +#define LOGBYTES(cmd, file, cnt) logcmd((cmd), (file), NULL, (cnt)) + +static volatile sig_atomic_t recvurg; +static int transflag; /* NB: for debugging only */ + +#define STARTXFER flagxfer(1) +#define ENDXFER flagxfer(0) + +#define START_UNSAFE maskurg(1) +#define END_UNSAFE maskurg(0) + +/* It's OK to put an `else' clause after this macro. */ +#define CHECKOOB(action) \ + if (recvurg) { \ + recvurg = 0; \ + if (myoob()) { \ + ENDXFER; \ + action; \ + } \ + } + +#ifdef VIRTUAL_HOSTING +static void inithosts(int); +static void selecthost(union sockunion *); +#endif +static void ack(char *); +static void sigurg(int); +static void maskurg(int); +static void flagxfer(int); +static int myoob(void); +static int checkuser(char *, char *, int, char **, int *); +static FILE *dataconn(char *, off_t, char *); +static void dolog(struct sockaddr *); +static void end_login(void); +static FILE *getdatasock(char *); +static int guniquefd(char *, char **); +static void lostconn(int); +static void sigquit(int); +static int receive_data(FILE *, FILE *); +static int send_data(FILE *, FILE *, size_t, off_t, int); +static struct passwd * + sgetpwnam(char *); +static char *sgetsave(char *); +static void reapchild(int); +static void appendf(char **, char *, ...) __printflike(2, 3); +static void logcmd(char *, char *, char *, off_t); +static void logxfer(char *, off_t, time_t); +static char *doublequote(char *); +static int *socksetup(int, char *, const char *); + +int +main(int argc, char *argv[], char **envp) +{ + socklen_t addrlen; + int ch, on = 1, tos; + char *cp, line[LINE_MAX]; + FILE *fd; + char *bindname = NULL; + const char *bindport = "ftp"; + int family = AF_UNSPEC; + struct sigaction sa; + + tzset(); /* in case no timezone database in ~ftp */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + +#ifdef OLD_SETPROCTITLE + /* + * Save start and extent of argv for setproctitle. + */ + Argv = argv; + while (*envp) + envp++; + LastArgv = envp[-1] + strlen(envp[-1]); +#endif /* OLD_SETPROCTITLE */ + + /* + * Prevent diagnostic messages from appearing on stderr. + * We run as a daemon or from inetd; in both cases, there's + * more reason in logging to syslog. + */ + (void) freopen(_PATH_DEVNULL, "w", stderr); + opterr = 0; + + /* + * LOG_NDELAY sets up the logging connection immediately, + * necessary for anonymous ftp's that chroot and can't do it later. + */ + openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); + + while ((ch = getopt(argc, argv, + "468a:AdDEhlmMoOp:P:rRSt:T:u:UvW")) != -1) { + switch (ch) { + case '4': + family = (family == AF_INET6) ? AF_UNSPEC : AF_INET; + break; + + case '6': + family = (family == AF_INET) ? AF_UNSPEC : AF_INET6; + break; + + case '8': + assumeutf8 = 1; + break; + + case 'a': + bindname = optarg; + break; + + case 'A': + anon_only = 1; + break; + + case 'd': + ftpdebug++; + break; + + case 'D': + daemon_mode++; + break; + + case 'E': + noepsv = 1; + break; + + case 'h': + hostinfo = 0; + break; + + case 'l': + logging++; /* > 1 == extra logging */ + break; + + case 'm': + noguestmod = 0; + break; + + case 'M': + noguestmkd = 1; + break; + + case 'o': + noretr = 1; + break; + + case 'O': + noguestretr = 1; + break; + + case 'p': + pid_file = optarg; + break; + + case 'P': + bindport = optarg; + break; + + case 'r': + readonly = 1; + break; + + case 'R': + paranoid = 0; + break; + + case 'S': + stats++; + break; + + case 't': + timeout = atoi(optarg); + if (maxtimeout < timeout) + maxtimeout = timeout; + break; + + case 'T': + maxtimeout = atoi(optarg); + if (timeout > maxtimeout) + timeout = maxtimeout; + break; + + case 'u': + { + long val = 0; + + val = strtol(optarg, &optarg, 8); + if (*optarg != '\0' || val < 0) + syslog(LOG_WARNING, "bad value for -u"); + else + defumask = val; + break; + } + case 'U': + restricted_data_ports = 0; + break; + + case 'v': + ftpdebug++; + break; + + case 'W': + dowtmp = 0; + break; + + default: + syslog(LOG_WARNING, "unknown flag -%c ignored", optopt); + break; + } + } + + if (daemon_mode) { + int *ctl_sock, fd, maxfd = -1, nfds, i; + fd_set defreadfds, readfds; + pid_t pid; + struct pidfh *pfh; + + if ((pfh = pidfile_open(pid_file, 0600, &pid)) == NULL) { + if (errno == EEXIST) { + syslog(LOG_ERR, "%s already running, pid %d", + getprogname(), (int)pid); + exit(1); + } + syslog(LOG_WARNING, "pidfile_open: %m"); + } + + /* + * Detach from parent. + */ + if (daemon(1, 1) < 0) { + syslog(LOG_ERR, "failed to become a daemon"); + exit(1); + } + + if (pfh != NULL && pidfile_write(pfh) == -1) + syslog(LOG_WARNING, "pidfile_write: %m"); + + sa.sa_handler = reapchild; + (void)sigaction(SIGCHLD, &sa, NULL); + +#ifdef VIRTUAL_HOSTING + inithosts(family); +#endif + + /* + * Open a socket, bind it to the FTP port, and start + * listening. + */ + ctl_sock = socksetup(family, bindname, bindport); + if (ctl_sock == NULL) + exit(1); + + FD_ZERO(&defreadfds); + for (i = 1; i <= *ctl_sock; i++) { + FD_SET(ctl_sock[i], &defreadfds); + if (listen(ctl_sock[i], 32) < 0) { + syslog(LOG_ERR, "control listen: %m"); + exit(1); + } + if (maxfd < ctl_sock[i]) + maxfd = ctl_sock[i]; + } + + /* + * Loop forever accepting connection requests and forking off + * children to handle them. + */ + while (1) { + FD_COPY(&defreadfds, &readfds); + nfds = select(maxfd + 1, &readfds, NULL, NULL, 0); + if (nfds <= 0) { + if (nfds < 0 && errno != EINTR) + syslog(LOG_WARNING, "select: %m"); + continue; + } + + pid = -1; + for (i = 1; i <= *ctl_sock; i++) + if (FD_ISSET(ctl_sock[i], &readfds)) { + addrlen = sizeof(his_addr); + fd = accept(ctl_sock[i], + (struct sockaddr *)&his_addr, + &addrlen); + if (fd == -1) { + syslog(LOG_WARNING, + "accept: %m"); + continue; + } + switch (pid = fork()) { + case 0: + /* child */ + (void) dup2(fd, 0); + (void) dup2(fd, 1); + (void) close(fd); + for (i = 1; i <= *ctl_sock; i++) + close(ctl_sock[i]); + if (pfh != NULL) + pidfile_close(pfh); + goto gotchild; + case -1: + syslog(LOG_WARNING, "fork: %m"); + /* FALLTHROUGH */ + default: + close(fd); + } + } + } + } else { + addrlen = sizeof(his_addr); + if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + +#ifdef VIRTUAL_HOSTING + if (his_addr.su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) + family = AF_INET; + else + family = his_addr.su_family; + inithosts(family); +#endif + } + +gotchild: + sa.sa_handler = SIG_DFL; + (void)sigaction(SIGCHLD, &sa, NULL); + + sa.sa_handler = sigurg; + sa.sa_flags = 0; /* don't restart syscalls for SIGURG */ + (void)sigaction(SIGURG, &sa, NULL); + + sigfillset(&sa.sa_mask); /* block all signals in handler */ + sa.sa_flags = SA_RESTART; + sa.sa_handler = sigquit; + (void)sigaction(SIGHUP, &sa, NULL); + (void)sigaction(SIGINT, &sa, NULL); + (void)sigaction(SIGQUIT, &sa, NULL); + (void)sigaction(SIGTERM, &sa, NULL); + + sa.sa_handler = lostconn; + (void)sigaction(SIGPIPE, &sa, NULL); + + addrlen = sizeof(ctrl_addr); + if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); + exit(1); + } + dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */ +#ifdef VIRTUAL_HOSTING + /* select our identity from virtual host table */ + selecthost(&ctrl_addr); +#endif +#ifdef IP_TOS + if (ctrl_addr.su_family == AF_INET) + { + tos = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m"); + } +#endif + /* + * Disable Nagle on the control channel so that we don't have to wait + * for peer's ACK before issuing our next reply. + */ + if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m"); + + data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1); + + (void)snprintf(wtmpid, sizeof(wtmpid), "%xftpd", getpid()); + + /* Try to handle urgent data inline */ +#ifdef SO_OOBINLINE + if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m"); +#endif + +#ifdef F_SETOWN + if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) + syslog(LOG_ERR, "fcntl F_SETOWN: %m"); +#endif + dolog((struct sockaddr *)&his_addr); + /* + * Set up default state + */ + data = -1; + type = TYPE_A; + form = FORM_N; + stru = STRU_F; + mode = MODE_S; + tmpline[0] = '\0'; + + /* If logins are disabled, print out the message. */ + if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(530, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + reply(530, "System not available."); + exit(0); + } +#ifdef VIRTUAL_HOSTING + fd = fopen(thishost->welcome, "r"); +#else + fd = fopen(_PATH_FTPWELCOME, "r"); +#endif + if (fd != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(220, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + /* reply(220,) must follow */ + } +#ifndef VIRTUAL_HOSTING + if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) + fatalerror("Ran out of memory."); + if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0) + hostname[0] = '\0'; + hostname[MAXHOSTNAMELEN - 1] = '\0'; +#endif + if (hostinfo) + reply(220, "%s FTP server (%s) ready.", hostname, version); + else + reply(220, "FTP server ready."); + for (;;) + (void) yyparse(); + /* NOTREACHED */ +} + +static void +lostconn(int signo) +{ + + if (ftpdebug) + syslog(LOG_DEBUG, "lost connection"); + dologout(1); +} + +static void +sigquit(int signo) +{ + + syslog(LOG_ERR, "got signal %d", signo); + dologout(1); +} + +#ifdef VIRTUAL_HOSTING +/* + * read in virtual host tables (if they exist) + */ + +static void +inithosts(int family) +{ + int insert; + size_t len; + FILE *fp; + char *cp, *mp, *line; + char *hostname; + char *vhost, *anonuser, *statfile, *welcome, *loginmsg; + struct ftphost *hrp, *lhrp; + struct addrinfo hints, *res, *ai; + + /* + * Fill in the default host information + */ + if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) + fatalerror("Ran out of memory."); + if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0) + hostname[0] = '\0'; + hostname[MAXHOSTNAMELEN - 1] = '\0'; + if ((hrp = malloc(sizeof(struct ftphost))) == NULL) + fatalerror("Ran out of memory."); + hrp->hostname = hostname; + hrp->hostinfo = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0) + hrp->hostinfo = res; + hrp->statfile = _PATH_FTPDSTATFILE; + hrp->welcome = _PATH_FTPWELCOME; + hrp->loginmsg = _PATH_FTPLOGINMESG; + hrp->anonuser = "ftp"; + hrp->next = NULL; + thishost = firsthost = lhrp = hrp; + if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) { + int addrsize, gothost; + void *addr; + struct hostent *hp; + + while ((line = fgetln(fp, &len)) != NULL) { + int i, hp_error; + + /* skip comments */ + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + mp = NULL; + } else { + if ((mp = malloc(len + 1)) == NULL) + fatalerror("Ran out of memory."); + memcpy(mp, line, len); + mp[len] = '\0'; + line = mp; + } + cp = strtok(line, " \t"); + /* skip empty lines */ + if (cp == NULL) + goto nextline; + vhost = cp; + + /* set defaults */ + anonuser = "ftp"; + statfile = _PATH_FTPDSTATFILE; + welcome = _PATH_FTPWELCOME; + loginmsg = _PATH_FTPLOGINMESG; + + /* + * Preparse the line so we can use its info + * for all the addresses associated with + * the virtual host name. + * Field 0, the virtual host name, is special: + * it's already parsed off and will be strdup'ed + * later, after we know its canonical form. + */ + for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++) + if (*cp != '-' && (cp = strdup(cp))) + switch (i) { + case 1: /* anon user permissions */ + anonuser = cp; + break; + case 2: /* statistics file */ + statfile = cp; + break; + case 3: /* welcome message */ + welcome = cp; + break; + case 4: /* login message */ + loginmsg = cp; + break; + default: /* programming error */ + abort(); + /* NOTREACHED */ + } + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(vhost, NULL, &hints, &res) != 0) + goto nextline; + for (ai = res; ai != NULL && ai->ai_addr != NULL; + ai = ai->ai_next) { + + gothost = 0; + for (hrp = firsthost; hrp != NULL; hrp = hrp->next) { + struct addrinfo *hi; + + for (hi = hrp->hostinfo; hi != NULL; + hi = hi->ai_next) + if (hi->ai_addrlen == ai->ai_addrlen && + memcmp(hi->ai_addr, + ai->ai_addr, + ai->ai_addr->sa_len) == 0) { + gothost++; + break; + } + if (gothost) + break; + } + if (hrp == NULL) { + if ((hrp = malloc(sizeof(struct ftphost))) == NULL) + goto nextline; + hrp->hostname = NULL; + insert = 1; + } else { + if (hrp->hostinfo && hrp->hostinfo != res) + freeaddrinfo(hrp->hostinfo); + insert = 0; /* host already in the chain */ + } + hrp->hostinfo = res; + + /* + * determine hostname to use. + * force defined name if there is a valid alias + * otherwise fallback to primary hostname + */ + /* XXX: getaddrinfo() can't do alias check */ + switch(hrp->hostinfo->ai_family) { + case AF_INET: + addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr; + addrsize = sizeof(struct in_addr); + break; + case AF_INET6: + addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr; + addrsize = sizeof(struct in6_addr); + break; + default: + /* should not reach here */ + freeaddrinfo(hrp->hostinfo); + if (insert) + free(hrp); /*not in chain, can free*/ + else + hrp->hostinfo = NULL; /*mark as blank*/ + goto nextline; + /* NOTREACHED */ + } + if ((hp = getipnodebyaddr(addr, addrsize, + hrp->hostinfo->ai_family, + &hp_error)) != NULL) { + if (strcmp(vhost, hp->h_name) != 0) { + if (hp->h_aliases == NULL) + vhost = hp->h_name; + else { + i = 0; + while (hp->h_aliases[i] && + strcmp(vhost, hp->h_aliases[i]) != 0) + ++i; + if (hp->h_aliases[i] == NULL) + vhost = hp->h_name; + } + } + } + if (hrp->hostname && + strcmp(hrp->hostname, vhost) != 0) { + free(hrp->hostname); + hrp->hostname = NULL; + } + if (hrp->hostname == NULL && + (hrp->hostname = strdup(vhost)) == NULL) { + freeaddrinfo(hrp->hostinfo); + hrp->hostinfo = NULL; /* mark as blank */ + if (hp) + freehostent(hp); + goto nextline; + } + hrp->anonuser = anonuser; + hrp->statfile = statfile; + hrp->welcome = welcome; + hrp->loginmsg = loginmsg; + if (insert) { + hrp->next = NULL; + lhrp->next = hrp; + lhrp = hrp; + } + if (hp) + freehostent(hp); + } +nextline: + if (mp) + free(mp); + } + (void) fclose(fp); + } +} + +static void +selecthost(union sockunion *su) +{ + struct ftphost *hrp; + u_int16_t port; +#ifdef INET6 + struct in6_addr *mapped_in6 = NULL; +#endif + struct addrinfo *hi; + +#ifdef INET6 + /* + * XXX IPv4 mapped IPv6 addr consideraton, + * specified in rfc2373. + */ + if (su->su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr)) + mapped_in6 = &su->su_sin6.sin6_addr; +#endif + + hrp = thishost = firsthost; /* default */ + port = su->su_port; + su->su_port = 0; + while (hrp != NULL) { + for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) { + if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) { + thishost = hrp; + goto found; + } +#ifdef INET6 + /* XXX IPv4 mapped IPv6 addr consideraton */ + if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL && + (memcmp(&mapped_in6->s6_addr[12], + &((struct sockaddr_in *)hi->ai_addr)->sin_addr, + sizeof(struct in_addr)) == 0)) { + thishost = hrp; + goto found; + } +#endif + } + hrp = hrp->next; + } +found: + su->su_port = port; + /* setup static variables as appropriate */ + hostname = thishost->hostname; + ftpuser = thishost->anonuser; +} +#endif + +/* + * Helper function for sgetpwnam(). + */ +static char * +sgetsave(char *s) +{ + char *new = malloc(strlen(s) + 1); + + if (new == NULL) { + reply(421, "Ran out of memory."); + dologout(1); + /* NOTREACHED */ + } + (void) strcpy(new, s); + return (new); +} + +/* + * Save the result of a getpwnam. Used for USER command, since + * the data returned must not be clobbered by any other command + * (e.g., globbing). + * NB: The data returned by sgetpwnam() will remain valid until + * the next call to this function. Its difference from getpwnam() + * is that sgetpwnam() is known to be called from ftpd code only. + */ +static struct passwd * +sgetpwnam(char *name) +{ + static struct passwd save; + struct passwd *p; + + if ((p = getpwnam(name)) == NULL) + return (p); + if (save.pw_name) { + free(save.pw_name); + free(save.pw_passwd); + free(save.pw_class); + free(save.pw_gecos); + free(save.pw_dir); + free(save.pw_shell); + } + save = *p; + save.pw_name = sgetsave(p->pw_name); + save.pw_passwd = sgetsave(p->pw_passwd); + save.pw_class = sgetsave(p->pw_class); + save.pw_gecos = sgetsave(p->pw_gecos); + save.pw_dir = sgetsave(p->pw_dir); + save.pw_shell = sgetsave(p->pw_shell); + return (&save); +} + +static int login_attempts; /* number of failed login attempts */ +static int askpasswd; /* had user command, ask for passwd */ +static char curname[MAXLOGNAME]; /* current USER name */ + +/* + * USER command. + * Sets global passwd pointer pw if named account exists and is acceptable; + * sets askpasswd if a PASS command is expected. If logged in previously, + * need to reset state. If name is "ftp" or "anonymous", the name is not in + * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. + * If account doesn't exist, ask for passwd anyway. Otherwise, check user + * requesting login privileges. Disallow anyone who does not have a standard + * shell as returned by getusershell(). Disallow anyone mentioned in the file + * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. + */ +void +user(char *name) +{ + int ecode; + char *cp, *shell; + + if (logged_in) { + if (guest) { + reply(530, "Can't change user from guest login."); + return; + } else if (dochroot) { + reply(530, "Can't change user from chroot user."); + return; + } + end_login(); + } + + guest = 0; +#ifdef VIRTUAL_HOSTING + pw = sgetpwnam(thishost->anonuser); +#else + pw = sgetpwnam("ftp"); +#endif + if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { + if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL, &ecode) || + (ecode != 0 && ecode != ENOENT)) + reply(530, "User %s access denied.", name); + else if (checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL, &ecode) || + (ecode != 0 && ecode != ENOENT)) + reply(530, "User %s access denied.", name); + else if (pw != NULL) { + guest = 1; + askpasswd = 1; + reply(331, + "Guest login ok, send your email address as password."); + } else + reply(530, "User %s unknown.", name); + if (!askpasswd && logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); + return; + } + if (anon_only != 0) { + reply(530, "Sorry, only anonymous ftp allowed."); + return; + } + + if ((pw = sgetpwnam(name))) { + if ((shell = pw->pw_shell) == NULL || *shell == 0) + shell = _PATH_BSHELL; + setusershell(); + while ((cp = getusershell()) != NULL) + if (strcmp(cp, shell) == 0) + break; + endusershell(); + + if (cp == NULL || + (checkuser(_PATH_FTPUSERS, name, 1, NULL, &ecode) || + (ecode != 0 && ecode != ENOENT))) { + reply(530, "User %s access denied.", name); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s, %s", + remotehost, name); + pw = NULL; + return; + } + } + if (logging) + strncpy(curname, name, sizeof(curname)-1); + + pwok = 0; +#ifdef USE_PAM + /* XXX Kluge! The conversation mechanism needs to be fixed. */ +#endif + if (opiechallenge(&opiedata, name, opieprompt) == 0) { + pwok = (pw != NULL) && + opieaccessfile(remotehost) && + opiealways(pw->pw_dir); + reply(331, "Response to %s %s for %s.", + opieprompt, pwok ? "requested" : "required", name); + } else { + pwok = 1; + reply(331, "Password required for %s.", name); + } + askpasswd = 1; + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep(login_attempts); +} + +/* + * Check if a user is in the file "fname", + * return a pointer to a malloc'd string with the rest + * of the matching line in "residue" if not NULL. + */ +static int +checkuser(char *fname, char *name, int pwset, char **residue, int *ecode) +{ + FILE *fd; + int found = 0; + size_t len; + char *line, *mp, *p; + + if (ecode != NULL) + *ecode = 0; + if ((fd = fopen(fname, "r")) != NULL) { + while (!found && (line = fgetln(fd, &len)) != NULL) { + /* skip comments */ + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + mp = NULL; + } else { + if ((mp = malloc(len + 1)) == NULL) + fatalerror("Ran out of memory."); + memcpy(mp, line, len); + mp[len] = '\0'; + line = mp; + } + /* avoid possible leading and trailing whitespace */ + p = strtok(line, " \t"); + /* skip empty lines */ + if (p == NULL) + goto nextline; + /* + * if first chr is '@', check group membership + */ + if (p[0] == '@') { + int i = 0; + struct group *grp; + + if (p[1] == '\0') /* single @ matches anyone */ + found = 1; + else { + if ((grp = getgrnam(p+1)) == NULL) + goto nextline; + /* + * Check user's default group + */ + if (pwset && grp->gr_gid == pw->pw_gid) + found = 1; + /* + * Check supplementary groups + */ + while (!found && grp->gr_mem[i]) + found = strcmp(name, + grp->gr_mem[i++]) + == 0; + } + } + /* + * Otherwise, just check for username match + */ + else + found = strcmp(p, name) == 0; + /* + * Save the rest of line to "residue" if matched + */ + if (found && residue) { + if ((p = strtok(NULL, "")) != NULL) + p += strspn(p, " \t"); + if (p && *p) { + if ((*residue = strdup(p)) == NULL) + fatalerror("Ran out of memory."); + } else + *residue = NULL; + } +nextline: + if (mp) + free(mp); + } + (void) fclose(fd); + } else if (ecode != NULL) + *ecode = errno; + return (found); +} + +/* + * Terminate login as previous user, if any, resetting state; + * used when USER command is given or login fails. + */ +static void +end_login(void) +{ +#ifdef USE_PAM + int e; +#endif + + (void) seteuid(0); + if (logged_in && dowtmp) + ftpd_logwtmp(wtmpid, NULL, NULL); + pw = NULL; +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN | + LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH | + LOGIN_SETENV)); +#endif +#ifdef USE_PAM + if (pamh) { + if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); + if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + pamh = NULL; + } +#endif + logged_in = 0; + guest = 0; + dochroot = 0; +} + +#ifdef USE_PAM + +/* + * the following code is stolen from imap-uw PAM authentication module and + * login.c + */ +#define COPY_STRING(s) (s ? strdup(s) : NULL) + +struct cred_t { + const char *uname; /* user name */ + const char *pass; /* password */ +}; +typedef struct cred_t cred_t; + +static int +auth_conv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata) +{ + int i; + cred_t *cred = (cred_t *) appdata; + struct pam_response *reply; + + reply = calloc(num_msg, sizeof *reply); + if (reply == NULL) + return PAM_BUF_ERR; + + for (i = 0; i < num_msg; i++) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_ON: /* assume want user name */ + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = COPY_STRING(cred->uname); + /* PAM frees resp. */ + break; + case PAM_PROMPT_ECHO_OFF: /* assume want password */ + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = COPY_STRING(cred->pass); + /* PAM frees resp. */ + break; + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = NULL; + break; + default: /* unknown message style */ + free(reply); + return PAM_CONV_ERR; + } + } + + *resp = reply; + return PAM_SUCCESS; +} + +/* + * Attempt to authenticate the user using PAM. Returns 0 if the user is + * authenticated, or 1 if not authenticated. If some sort of PAM system + * error occurs (e.g., the "/etc/pam.conf" file is missing) then this + * function returns -1. This can be used as an indication that we should + * fall back to a different authentication mechanism. + */ +static int +auth_pam(struct passwd **ppw, const char *pass) +{ + const char *tmpl_user; + const void *item; + int rval; + int e; + cred_t auth_cred = { (*ppw)->pw_name, pass }; + struct pam_conv conv = { &auth_conv, &auth_cred }; + + e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh); + if (e != PAM_SUCCESS) { + /* + * In OpenPAM, it's OK to pass NULL to pam_strerror() + * if context creation has failed in the first place. + */ + syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e)); + return -1; + } + + e = pam_set_item(pamh, PAM_RHOST, remotehost); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", + pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + return -1; + } + + e = pam_authenticate(pamh, 0); + switch (e) { + case PAM_SUCCESS: + /* + * With PAM we support the concept of a "template" + * user. The user enters a login name which is + * authenticated by PAM, usually via a remote service + * such as RADIUS or TACACS+. If authentication + * succeeds, a different but related "template" name + * is used for setting the credentials, shell, and + * home directory. The name the user enters need only + * exist on the remote authentication server, but the + * template name must be present in the local password + * database. + * + * This is supported by two various mechanisms in the + * individual modules. However, from the application's + * point of view, the template user is always passed + * back as a changed value of the PAM_USER item. + */ + if ((e = pam_get_item(pamh, PAM_USER, &item)) == + PAM_SUCCESS) { + tmpl_user = (const char *) item; + if (strcmp((*ppw)->pw_name, tmpl_user) != 0) + *ppw = getpwnam(tmpl_user); + } else + syslog(LOG_ERR, "Couldn't get PAM_USER: %s", + pam_strerror(pamh, e)); + rval = 0; + break; + + case PAM_AUTH_ERR: + case PAM_USER_UNKNOWN: + case PAM_MAXTRIES: + rval = 1; + break; + + default: + syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); + rval = -1; + break; + } + + if (rval == 0) { + e = pam_acct_mgmt(pamh, 0); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_acct_mgmt: %s", + pam_strerror(pamh, e)); + rval = 1; + } + } + + if (rval != 0) { + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + } + return rval; +} + +#endif /* USE_PAM */ + +void +pass(char *passwd) +{ + int rval, ecode; + FILE *fd; +#ifdef LOGIN_CAP + login_cap_t *lc = NULL; +#endif +#ifdef USE_PAM + int e; +#endif + char *residue = NULL; + char *xpasswd; + + if (logged_in || askpasswd == 0) { + reply(503, "Login with USER first."); + return; + } + askpasswd = 0; + if (!guest) { /* "ftp" is only account allowed no password */ + if (pw == NULL) { + rval = 1; /* failure below */ + goto skip; + } +#ifdef USE_PAM + rval = auth_pam(&pw, passwd); + if (rval >= 0) { + opieunlock(); + goto skip; + } +#endif + if (opieverify(&opiedata, passwd) == 0) + xpasswd = pw->pw_passwd; + else if (pwok) { + xpasswd = crypt(passwd, pw->pw_passwd); + if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0') + xpasswd = ":"; + } else { + rval = 1; + goto skip; + } + rval = strcmp(pw->pw_passwd, xpasswd); + if (pw->pw_expire && time(NULL) >= pw->pw_expire) + rval = 1; /* failure */ +skip: + /* + * If rval == 1, the user failed the authentication check + * above. If rval == 0, either PAM or local authentication + * succeeded. + */ + if (rval) { + reply(530, "Login incorrect."); + if (logging) { + syslog(LOG_NOTICE, + "FTP LOGIN FAILED FROM %s", + remotehost); + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "FTP LOGIN FAILED FROM %s, %s", + remotehost, curname); + } + pw = NULL; + if (login_attempts++ >= 5) { + syslog(LOG_NOTICE, + "repeated login failures from %s", + remotehost); + exit(0); + } + return; + } + } + login_attempts = 0; /* this time successful */ + if (setegid(pw->pw_gid) < 0) { + reply(550, "Can't set gid."); + return; + } + /* May be overridden by login.conf */ + (void) umask(defumask); +#ifdef LOGIN_CAP + if ((lc = login_getpwclass(pw)) != NULL) { + char remote_ip[NI_MAXHOST]; + + if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, + remote_ip, sizeof(remote_ip) - 1, NULL, 0, + NI_NUMERICHOST)) + *remote_ip = 0; + remote_ip[sizeof(remote_ip) - 1] = 0; + if (!auth_hostok(lc, remotehost, remote_ip)) { + syslog(LOG_INFO|LOG_AUTH, + "FTP LOGIN FAILED (HOST) as %s: permission denied.", + pw->pw_name); + reply(530, "Permission denied."); + pw = NULL; + return; + } + if (!auth_timeok(lc, time(NULL))) { + reply(530, "Login not available right now."); + pw = NULL; + return; + } + } + setusercontext(lc, pw, 0, LOGIN_SETALL & + ~(LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV)); +#else + setlogin(pw->pw_name); + (void) initgroups(pw->pw_name, pw->pw_gid); +#endif + +#ifdef USE_PAM + if (pamh) { + if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e)); + } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); + } + } +#endif + + dochroot = + checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue, &ecode) +#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ + || login_getcapbool(lc, "ftp-chroot", 0) +#endif + ; + /* + * It is possible that checkuser() failed to open the chroot file. + * If this is the case, report that logins are un-available, since we + * have no way of checking whether or not the user should be chrooted. + * We ignore ENOENT since it is not required that this file be present. + */ + if (ecode != 0 && ecode != ENOENT) { + reply(530, "Login not available right now."); + return; + } + chrootdir = NULL; + + /* Disable wtmp logging when chrooting. */ + if (dochroot || guest) + dowtmp = 0; + if (dowtmp) + ftpd_logwtmp(wtmpid, pw->pw_name, + (struct sockaddr *)&his_addr); + logged_in = 1; + + if (guest && stats && statfd < 0) +#ifdef VIRTUAL_HOSTING + statfd = open(thishost->statfile, O_WRONLY|O_APPEND); +#else + statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND); +#endif + if (statfd < 0) + stats = 0; + + /* + * For a chrooted local user, + * a) see whether ftpchroot(5) specifies a chroot directory, + * b) extract the directory pathname from the line, + * c) expand it to the absolute pathname if necessary. + */ + if (dochroot && residue && + (chrootdir = strtok(residue, " \t")) != NULL) { + if (chrootdir[0] != '/') + asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir); + else + chrootdir = strdup(chrootdir); /* make it permanent */ + if (chrootdir == NULL) + fatalerror("Ran out of memory."); + } + if (guest || dochroot) { + /* + * If no chroot directory set yet, use the login directory. + * Copy it so it can be modified while pw->pw_dir stays intact. + */ + if (chrootdir == NULL && + (chrootdir = strdup(pw->pw_dir)) == NULL) + fatalerror("Ran out of memory."); + /* + * Check for the "/chroot/./home" syntax, + * separate the chroot and home directory pathnames. + */ + if ((homedir = strstr(chrootdir, "/./")) != NULL) { + *(homedir++) = '\0'; /* wipe '/' */ + homedir++; /* skip '.' */ + } else { + /* + * We MUST do a chdir() after the chroot. Otherwise + * the old current directory will be accessible as "." + * outside the new root! + */ + homedir = "/"; + } + /* + * Finally, do chroot() + */ + if (chroot(chrootdir) < 0) { + reply(550, "Can't change root."); + goto bad; + } + __FreeBSD_libc_enter_restricted_mode(); + } else /* real user w/o chroot */ + homedir = pw->pw_dir; + /* + * Set euid *before* doing chdir() so + * a) the user won't be carried to a directory that he couldn't reach + * on his own due to no permission to upper path components, + * b) NFS mounted homedirs w/restrictive permissions will be accessible + * (uid 0 has no root power over NFS if not mapped explicitly.) + */ + if (seteuid(pw->pw_uid) < 0) { + reply(550, "Can't set uid."); + goto bad; + } + if (chdir(homedir) < 0) { + if (guest || dochroot) { + reply(550, "Can't change to base directory."); + goto bad; + } else { + if (chdir("/") < 0) { + reply(550, "Root is inaccessible."); + goto bad; + } + lreply(230, "No directory! Logging in with home=/."); + } + } + + /* + * Display a login message, if it exists. + * N.B. reply(230,) must follow the message. + */ +#ifdef VIRTUAL_HOSTING + fd = fopen(thishost->loginmsg, "r"); +#else + fd = fopen(_PATH_FTPLOGINMESG, "r"); +#endif + if (fd != NULL) { + char *cp, line[LINE_MAX]; + + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(230, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + } + if (guest) { + if (ident != NULL) + free(ident); + ident = strdup(passwd); + if (ident == NULL) + fatalerror("Ran out of memory."); + + reply(230, "Guest login ok, access restrictions apply."); +#ifdef SETPROCTITLE +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous(%s)/%s", remotehost, hostname, + passwd); + else +#endif + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%s", remotehost, passwd); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", + remotehost, passwd); + } else { + if (dochroot) + reply(230, "User %s logged in, " + "access restrictions apply.", pw->pw_name); + else + reply(230, "User %s logged in.", pw->pw_name); + +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: user/%s", remotehost, pw->pw_name); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", + remotehost, pw->pw_name); + } + if (logging && (guest || dochroot)) + syslog(LOG_INFO, "session root changed to %s", chrootdir); +#ifdef LOGIN_CAP + login_close(lc); +#endif + if (residue) + free(residue); + return; +bad: + /* Forget all about it... */ +#ifdef LOGIN_CAP + login_close(lc); +#endif + if (residue) + free(residue); + end_login(); +} + +void +retrieve(char *cmd, char *name) +{ + FILE *fin, *dout; + struct stat st; + int (*closefunc)(FILE *); + time_t start; + + if (cmd == 0) { + fin = fopen(name, "r"), closefunc = fclose; + st.st_size = 0; + } else { + char line[BUFSIZ]; + + (void) snprintf(line, sizeof(line), cmd, name), name = line; + fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; + st.st_size = -1; + st.st_blksize = BUFSIZ; + } + if (fin == NULL) { + if (errno != 0) { + perror_reply(550, name); + if (cmd == 0) { + LOGCMD("get", name); + } + } + return; + } + byte_count = -1; + if (cmd == 0) { + if (fstat(fileno(fin), &st) < 0) { + perror_reply(550, name); + goto done; + } + if (!S_ISREG(st.st_mode)) { + /* + * Never sending a raw directory is a workaround + * for buggy clients that will attempt to RETR + * a directory before listing it, e.g., Mozilla. + * Preventing a guest from getting irregular files + * is a simple security measure. + */ + if (S_ISDIR(st.st_mode) || guest) { + reply(550, "%s: not a plain file.", name); + goto done; + } + st.st_size = -1; + /* st.st_blksize is set for all descriptor types */ + } + } + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fin)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + } else if (lseek(fileno(fin), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + dout = dataconn(name, st.st_size, "w"); + if (dout == NULL) + goto done; + time(&start); + send_data(fin, dout, st.st_blksize, st.st_size, + restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode)); + if (cmd == 0 && guest && stats && byte_count > 0) + logxfer(name, byte_count, start); + (void) fclose(dout); + data = -1; + pdata = -1; +done: + if (cmd == 0) + LOGBYTES("get", name, byte_count); + (*closefunc)(fin); +} + +void +store(char *name, char *mode, int unique) +{ + int fd; + FILE *fout, *din; + int (*closefunc)(FILE *); + + if (*mode == 'a') { /* APPE */ + if (unique) { + /* Programming error */ + syslog(LOG_ERR, "Internal: unique flag to APPE"); + unique = 0; + } + if (guest && noguestmod) { + reply(550, "Appending to existing file denied."); + goto err; + } + restart_point = 0; /* not affected by preceding REST */ + } + if (unique) /* STOU overrides REST */ + restart_point = 0; + if (guest && noguestmod) { + if (restart_point) { /* guest STOR w/REST */ + reply(550, "Modifying existing file denied."); + goto err; + } else /* treat guest STOR as STOU */ + unique = 1; + } + + if (restart_point) + mode = "r+"; /* so ASCII manual seek can work */ + if (unique) { + if ((fd = guniquefd(name, &name)) < 0) + goto err; + fout = fdopen(fd, mode); + } else + fout = fopen(name, mode); + closefunc = fclose; + if (fout == NULL) { + perror_reply(553, name); + goto err; + } + byte_count = -1; + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fout)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + /* + * We must do this seek to "current" position + * because we are changing from reading to + * writing. + */ + if (fseeko(fout, 0, SEEK_CUR) < 0) { + perror_reply(550, name); + goto done; + } + } else if (lseek(fileno(fout), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + din = dataconn(name, -1, "r"); + if (din == NULL) + goto done; + if (receive_data(din, fout) == 0) { + if (unique) + reply(226, "Transfer complete (unique file name:%s).", + name); + else + reply(226, "Transfer complete."); + } + (void) fclose(din); + data = -1; + pdata = -1; +done: + LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count); + (*closefunc)(fout); + return; +err: + LOGCMD(*mode == 'a' ? "append" : "put" , name); + return; +} + +static FILE * +getdatasock(char *mode) +{ + int on = 1, s, t, tries; + + if (data >= 0) + return (fdopen(data, mode)); + + s = socket(data_dest.su_family, SOCK_STREAM, 0); + if (s < 0) + goto bad; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m"); + /* anchor socket to avoid multi-homing problems */ + data_source = ctrl_addr; + data_source.su_port = htons(dataport); + (void) seteuid(0); + for (tries = 1; ; tries++) { + /* + * We should loop here since it's possible that + * another ftpd instance has passed this point and is + * trying to open a data connection in active mode now. + * Until the other connection is opened, we'll be getting + * EADDRINUSE because no SOCK_STREAM sockets in the system + * can share both local and remote addresses, localIP:20 + * and *:* in this case. + */ + if (bind(s, (struct sockaddr *)&data_source, + data_source.su_len) >= 0) + break; + if (errno != EADDRINUSE || tries > 10) + goto bad; + sleep(tries); + } + (void) seteuid(pw->pw_uid); +#ifdef IP_TOS + if (data_source.su_family == AF_INET) + { + on = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0) + syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m"); + } +#endif +#ifdef TCP_NOPUSH + /* + * Turn off push flag to keep sender TCP from sending short packets + * at the boundaries of each write(). + */ + on = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0) + syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m"); +#endif + return (fdopen(s, mode)); +bad: + /* Return the real value of errno (close may change it) */ + t = errno; + (void) seteuid(pw->pw_uid); + (void) close(s); + errno = t; + return (NULL); +} + +static FILE * +dataconn(char *name, off_t size, char *mode) +{ + char sizebuf[32]; + FILE *file; + int retry = 0, tos, conerrno; + + file_size = size; + byte_count = 0; + if (size != -1) + (void) snprintf(sizebuf, sizeof(sizebuf), + " (%jd bytes)", (intmax_t)size); + else + *sizebuf = '\0'; + if (pdata >= 0) { + union sockunion from; + socklen_t fromlen = ctrl_addr.su_len; + int flags, s; + struct timeval timeout; + fd_set set; + + FD_ZERO(&set); + FD_SET(pdata, &set); + + timeout.tv_usec = 0; + timeout.tv_sec = 120; + + /* + * Granted a socket is in the blocking I/O mode, + * accept() will block after a successful select() + * if the selected connection dies in between. + * Therefore set the non-blocking I/O flag here. + */ + if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || + fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1) + goto pdata_err; + if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 || + (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) + goto pdata_err; + (void) close(pdata); + pdata = s; + /* + * Unset the inherited non-blocking I/O flag + * on the child socket so stdio can work on it. + */ + if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || + fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1) + goto pdata_err; +#ifdef IP_TOS + if (from.su_family == AF_INET) + { + tos = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m"); + } +#endif + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (fdopen(pdata, mode)); +pdata_err: + reply(425, "Can't open data connection."); + (void) close(pdata); + pdata = -1; + return (NULL); + } + if (data >= 0) { + reply(125, "Using existing data connection for '%s'%s.", + name, sizebuf); + usedefault = 1; + return (fdopen(data, mode)); + } + if (usedefault) + data_dest = his_addr; + usedefault = 1; + do { + file = getdatasock(mode); + if (file == NULL) { + char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV]; + + if (getnameinfo((struct sockaddr *)&data_source, + data_source.su_len, + hostbuf, sizeof(hostbuf) - 1, + portbuf, sizeof(portbuf) - 1, + NI_NUMERICHOST|NI_NUMERICSERV)) + *hostbuf = *portbuf = 0; + hostbuf[sizeof(hostbuf) - 1] = 0; + portbuf[sizeof(portbuf) - 1] = 0; + reply(425, "Can't create data socket (%s,%s): %s.", + hostbuf, portbuf, strerror(errno)); + return (NULL); + } + data = fileno(file); + conerrno = 0; + if (connect(data, (struct sockaddr *)&data_dest, + data_dest.su_len) == 0) + break; + conerrno = errno; + (void) fclose(file); + data = -1; + if (conerrno == EADDRINUSE) { + sleep(swaitint); + retry += swaitint; + } else { + break; + } + } while (retry <= swaitmax); + if (conerrno != 0) { + reply(425, "Can't build data connection: %s.", + strerror(conerrno)); + return (NULL); + } + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (file); +} + +/* + * A helper macro to avoid code duplication + * in send_data() and receive_data(). + * + * XXX We have to block SIGURG during putc() because BSD stdio + * is unable to restart interrupted write operations and hence + * the entire buffer contents will be lost as soon as a write() + * call indicates EINTR to stdio. + */ +#define FTPD_PUTC(ch, file, label) \ + do { \ + int ret; \ + \ + do { \ + START_UNSAFE; \ + ret = putc((ch), (file)); \ + END_UNSAFE; \ + CHECKOOB(return (-1)) \ + else if (ferror(file)) \ + goto label; \ + clearerr(file); \ + } while (ret == EOF); \ + } while (0) + +/* + * Tranfer the contents of "instr" to "outstr" peer using the appropriate + * encapsulation of the data subject to Mode, Structure, and Type. + * + * NB: Form isn't handled. + */ +static int +send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg) +{ + int c, cp, filefd, netfd; + char *buf; + + STARTXFER; + + switch (type) { + + case TYPE_A: + cp = EOF; + for (;;) { + c = getc(instr); + CHECKOOB(return (-1)) + else if (c == EOF && ferror(instr)) + goto file_err; + if (c == EOF) { + if (ferror(instr)) { /* resume after OOB */ + clearerr(instr); + continue; + } + if (feof(instr)) /* EOF */ + break; + syslog(LOG_ERR, "Internal: impossible condition" + " on file after getc()"); + goto file_err; + } + if (c == '\n' && cp != '\r') { + FTPD_PUTC('\r', outstr, data_err); + byte_count++; + } + FTPD_PUTC(c, outstr, data_err); + byte_count++; + cp = c; + } +#ifdef notyet /* BSD stdio isn't ready for that */ + while (fflush(outstr) == EOF) { + CHECKOOB(return (-1)) + else + goto data_err; + clearerr(outstr); + } + ENDXFER; +#else + ENDXFER; + if (fflush(outstr) == EOF) + goto data_err; +#endif + reply(226, "Transfer complete."); + return (0); + + case TYPE_I: + case TYPE_L: + /* + * isreg is only set if we are not doing restart and we + * are sending a regular file + */ + netfd = fileno(outstr); + filefd = fileno(instr); + + if (isreg) { + char *msg = "Transfer complete."; + off_t cnt, offset; + int err; + + cnt = offset = 0; + + while (filesize > 0) { + err = sendfile(filefd, netfd, offset, 0, + NULL, &cnt, 0); + /* + * Calculate byte_count before OOB processing. + * It can be used in myoob() later. + */ + byte_count += cnt; + offset += cnt; + filesize -= cnt; + CHECKOOB(return (-1)) + else if (err == -1) { + if (errno != EINTR && + cnt == 0 && offset == 0) + goto oldway; + goto data_err; + } + if (err == -1) /* resume after OOB */ + continue; + /* + * We hit the EOF prematurely. + * Perhaps the file was externally truncated. + */ + if (cnt == 0) { + msg = "Transfer finished due to " + "premature end of file."; + break; + } + } + ENDXFER; + reply(226, "%s", msg); + return (0); + } + +oldway: + if ((buf = malloc(blksize)) == NULL) { + ENDXFER; + reply(451, "Ran out of memory."); + return (-1); + } + + for (;;) { + int cnt, len; + char *bp; + + cnt = read(filefd, buf, blksize); + CHECKOOB(free(buf); return (-1)) + else if (cnt < 0) { + free(buf); + goto file_err; + } + if (cnt < 0) /* resume after OOB */ + continue; + if (cnt == 0) /* EOF */ + break; + for (len = cnt, bp = buf; len > 0;) { + cnt = write(netfd, bp, len); + CHECKOOB(free(buf); return (-1)) + else if (cnt < 0) { + free(buf); + goto data_err; + } + if (cnt <= 0) + continue; + len -= cnt; + bp += cnt; + byte_count += cnt; + } + } + ENDXFER; + free(buf); + reply(226, "Transfer complete."); + return (0); + default: + ENDXFER; + reply(550, "Unimplemented TYPE %d in send_data.", type); + return (-1); + } + +data_err: + ENDXFER; + perror_reply(426, "Data connection"); + return (-1); + +file_err: + ENDXFER; + perror_reply(551, "Error on input file"); + return (-1); +} + +/* + * Transfer data from peer to "outstr" using the appropriate encapulation of + * the data subject to Mode, Structure, and Type. + * + * N.B.: Form isn't handled. + */ +static int +receive_data(FILE *instr, FILE *outstr) +{ + int c, cp; + int bare_lfs = 0; + + STARTXFER; + + switch (type) { + + case TYPE_I: + case TYPE_L: + for (;;) { + int cnt, len; + char *bp; + char buf[BUFSIZ]; + + cnt = read(fileno(instr), buf, sizeof(buf)); + CHECKOOB(return (-1)) + else if (cnt < 0) + goto data_err; + if (cnt < 0) /* resume after OOB */ + continue; + if (cnt == 0) /* EOF */ + break; + for (len = cnt, bp = buf; len > 0;) { + cnt = write(fileno(outstr), bp, len); + CHECKOOB(return (-1)) + else if (cnt < 0) + goto file_err; + if (cnt <= 0) + continue; + len -= cnt; + bp += cnt; + byte_count += cnt; + } + } + ENDXFER; + return (0); + + case TYPE_E: + ENDXFER; + reply(553, "TYPE E not implemented."); + return (-1); + + case TYPE_A: + cp = EOF; + for (;;) { + c = getc(instr); + CHECKOOB(return (-1)) + else if (c == EOF && ferror(instr)) + goto data_err; + if (c == EOF && ferror(instr)) { /* resume after OOB */ + clearerr(instr); + continue; + } + + if (cp == '\r') { + if (c != '\n') + FTPD_PUTC('\r', outstr, file_err); + } else + if (c == '\n') + bare_lfs++; + if (c == '\r') { + byte_count++; + cp = c; + continue; + } + + /* Check for EOF here in order not to lose last \r. */ + if (c == EOF) { + if (feof(instr)) /* EOF */ + break; + syslog(LOG_ERR, "Internal: impossible condition" + " on data stream after getc()"); + goto data_err; + } + + byte_count++; + FTPD_PUTC(c, outstr, file_err); + cp = c; + } +#ifdef notyet /* BSD stdio isn't ready for that */ + while (fflush(outstr) == EOF) { + CHECKOOB(return (-1)) + else + goto file_err; + clearerr(outstr); + } + ENDXFER; +#else + ENDXFER; + if (fflush(outstr) == EOF) + goto file_err; +#endif + if (bare_lfs) { + lreply(226, + "WARNING! %d bare linefeeds received in ASCII mode.", + bare_lfs); + (void)printf(" File may not have transferred correctly.\r\n"); + } + return (0); + default: + ENDXFER; + reply(550, "Unimplemented TYPE %d in receive_data.", type); + return (-1); + } + +data_err: + ENDXFER; + perror_reply(426, "Data connection"); + return (-1); + +file_err: + ENDXFER; + perror_reply(452, "Error writing to file"); + return (-1); +} + +void +statfilecmd(char *filename) +{ + FILE *fin; + int atstart; + int c, code; + char line[LINE_MAX]; + struct stat st; + + code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213; + (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename); + fin = ftpd_popen(line, "r"); + if (fin == NULL) { + perror_reply(551, filename); + return; + } + lreply(code, "Status of %s:", filename); + atstart = 1; + while ((c = getc(fin)) != EOF) { + if (c == '\n') { + if (ferror(stdout)){ + perror_reply(421, "Control connection"); + (void) ftpd_pclose(fin); + dologout(1); + /* NOTREACHED */ + } + if (ferror(fin)) { + perror_reply(551, filename); + (void) ftpd_pclose(fin); + return; + } + (void) putc('\r', stdout); + } + /* + * RFC 959 says neutral text should be prepended before + * a leading 3-digit number followed by whitespace, but + * many ftp clients can be confused by any leading digits, + * as a matter of fact. + */ + if (atstart && isdigit(c)) + (void) putc(' ', stdout); + (void) putc(c, stdout); + atstart = (c == '\n'); + } + (void) ftpd_pclose(fin); + reply(code, "End of status."); +} + +void +statcmd(void) +{ + union sockunion *su; + u_char *a, *p; + char hname[NI_MAXHOST]; + int ispassive; + + if (hostinfo) { + lreply(211, "%s FTP server status:", hostname); + printf(" %s\r\n", version); + } else + lreply(211, "FTP server status:"); + printf(" Connected to %s", remotehost); + if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, + hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) { + hname[sizeof(hname) - 1] = 0; + if (strcmp(hname, remotehost) != 0) + printf(" (%s)", hname); + } + printf("\r\n"); + if (logged_in) { + if (guest) + printf(" Logged in anonymously\r\n"); + else + printf(" Logged in as %s\r\n", pw->pw_name); + } else if (askpasswd) + printf(" Waiting for password\r\n"); + else + printf(" Waiting for user name\r\n"); + printf(" TYPE: %s", typenames[type]); + if (type == TYPE_A || type == TYPE_E) + printf(", FORM: %s", formnames[form]); + if (type == TYPE_L) +#if CHAR_BIT == 8 + printf(" %d", CHAR_BIT); +#else + printf(" %d", bytesize); /* need definition! */ +#endif + printf("; STRUcture: %s; transfer MODE: %s\r\n", + strunames[stru], modenames[mode]); + if (data != -1) + printf(" Data connection open\r\n"); + else if (pdata != -1) { + ispassive = 1; + su = &pasv_addr; + goto printaddr; + } else if (usedefault == 0) { + ispassive = 0; + su = &data_dest; +printaddr: +#define UC(b) (((int) b) & 0xff) + if (epsvall) { + printf(" EPSV only mode (EPSV ALL)\r\n"); + goto epsvonly; + } + + /* PORT/PASV */ + if (su->su_family == AF_INET) { + a = (u_char *) &su->su_sin.sin_addr; + p = (u_char *) &su->su_sin.sin_port; + printf(" %s (%d,%d,%d,%d,%d,%d)\r\n", + ispassive ? "PASV" : "PORT", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])); + } + + /* LPRT/LPSV */ + { + int alen, af, i; + + switch (su->su_family) { + case AF_INET: + a = (u_char *) &su->su_sin.sin_addr; + p = (u_char *) &su->su_sin.sin_port; + alen = sizeof(su->su_sin.sin_addr); + af = 4; + break; + case AF_INET6: + a = (u_char *) &su->su_sin6.sin6_addr; + p = (u_char *) &su->su_sin6.sin6_port; + alen = sizeof(su->su_sin6.sin6_addr); + af = 6; + break; + default: + af = 0; + break; + } + if (af) { + printf(" %s (%d,%d,", ispassive ? "LPSV" : "LPRT", + af, alen); + for (i = 0; i < alen; i++) + printf("%d,", UC(a[i])); + printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1])); + } + } + +epsvonly:; + /* EPRT/EPSV */ + { + int af; + + switch (su->su_family) { + case AF_INET: + af = 1; + break; + case AF_INET6: + af = 2; + break; + default: + af = 0; + break; + } + if (af) { + union sockunion tmp; + + tmp = *su; + if (tmp.su_family == AF_INET6) + tmp.su_sin6.sin6_scope_id = 0; + if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len, + hname, sizeof(hname) - 1, NULL, 0, + NI_NUMERICHOST)) { + hname[sizeof(hname) - 1] = 0; + printf(" %s |%d|%s|%d|\r\n", + ispassive ? "EPSV" : "EPRT", + af, hname, htons(tmp.su_port)); + } + } + } +#undef UC + } else + printf(" No data connection\r\n"); + reply(211, "End of status."); +} + +void +fatalerror(char *s) +{ + + reply(451, "Error in server: %s", s); + reply(221, "Closing connection due to server error."); + dologout(0); + /* NOTREACHED */ +} + +void +reply(int n, const char *fmt, ...) +{ + va_list ap; + + (void)printf("%d ", n); + va_start(ap, fmt); + (void)vprintf(fmt, ap); + va_end(ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (ftpdebug) { + syslog(LOG_DEBUG, "<--- %d ", n); + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); + } +} + +void +lreply(int n, const char *fmt, ...) +{ + va_list ap; + + (void)printf("%d- ", n); + va_start(ap, fmt); + (void)vprintf(fmt, ap); + va_end(ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (ftpdebug) { + syslog(LOG_DEBUG, "<--- %d- ", n); + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); + } +} + +static void +ack(char *s) +{ + + reply(250, "%s command successful.", s); +} + +void +nack(char *s) +{ + + reply(502, "%s command not implemented.", s); +} + +/* ARGSUSED */ +void +yyerror(char *s) +{ + char *cp; + + if ((cp = strchr(cbuf,'\n'))) + *cp = '\0'; + reply(500, "%s: command not understood.", cbuf); +} + +void +delete(char *name) +{ + struct stat st; + + LOGCMD("delete", name); + if (lstat(name, &st) < 0) { + perror_reply(550, name); + return; + } + if (S_ISDIR(st.st_mode)) { + if (rmdir(name) < 0) { + perror_reply(550, name); + return; + } + goto done; + } + if (guest && noguestmod) { + reply(550, "Operation not permitted."); + return; + } + if (unlink(name) < 0) { + perror_reply(550, name); + return; + } +done: + ack("DELE"); +} + +void +cwd(char *path) +{ + + if (chdir(path) < 0) + perror_reply(550, path); + else + ack("CWD"); +} + +void +makedir(char *name) +{ + char *s; + + LOGCMD("mkdir", name); + if (guest && noguestmkd) + reply(550, "Operation not permitted."); + else if (mkdir(name, 0777) < 0) + perror_reply(550, name); + else { + if ((s = doublequote(name)) == NULL) + fatalerror("Ran out of memory."); + reply(257, "\"%s\" directory created.", s); + free(s); + } +} + +void +removedir(char *name) +{ + + LOGCMD("rmdir", name); + if (rmdir(name) < 0) + perror_reply(550, name); + else + ack("RMD"); +} + +void +pwd(void) +{ + char *s, path[MAXPATHLEN + 1]; + + if (getcwd(path, sizeof(path)) == NULL) + perror_reply(550, "Get current directory"); + else { + if ((s = doublequote(path)) == NULL) + fatalerror("Ran out of memory."); + reply(257, "\"%s\" is current directory.", s); + free(s); + } +} + +char * +renamefrom(char *name) +{ + struct stat st; + + if (guest && noguestmod) { + reply(550, "Operation not permitted."); + return (NULL); + } + if (lstat(name, &st) < 0) { + perror_reply(550, name); + return (NULL); + } + reply(350, "File exists, ready for destination name."); + return (name); +} + +void +renamecmd(char *from, char *to) +{ + struct stat st; + + LOGCMD2("rename", from, to); + + if (guest && (stat(to, &st) == 0)) { + reply(550, "%s: permission denied.", to); + return; + } + + if (rename(from, to) < 0) + perror_reply(550, "rename"); + else + ack("RNTO"); +} + +static void +dolog(struct sockaddr *who) +{ + char who_name[NI_MAXHOST]; + + realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len); + remotehost[sizeof(remotehost) - 1] = 0; + if (getnameinfo(who, who->sa_len, + who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST)) + *who_name = 0; + who_name[sizeof(who_name) - 1] = 0; + +#ifdef SETPROCTITLE +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)", + remotehost, hostname); + else +#endif + snprintf(proctitle, sizeof(proctitle), "%s: connected", + remotehost); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + + if (logging) { +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + syslog(LOG_INFO, "connection from %s (%s) to %s", + remotehost, who_name, hostname); + else +#endif + syslog(LOG_INFO, "connection from %s (%s)", + remotehost, who_name); + } +} + +/* + * Record logout in wtmp file + * and exit with supplied status. + */ +void +dologout(int status) +{ + + if (logged_in && dowtmp) { + (void) seteuid(0); + ftpd_logwtmp(wtmpid, NULL, NULL); + } + /* beware of flushing buffers after a SIGPIPE */ + _exit(status); +} + +static void +sigurg(int signo) +{ + + recvurg = 1; +} + +static void +maskurg(int flag) +{ + int oerrno; + sigset_t sset; + + if (!transflag) { + syslog(LOG_ERR, "Internal: maskurg() while no transfer"); + return; + } + oerrno = errno; + sigemptyset(&sset); + sigaddset(&sset, SIGURG); + sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL); + errno = oerrno; +} + +static void +flagxfer(int flag) +{ + + if (flag) { + if (transflag) + syslog(LOG_ERR, "Internal: flagxfer(1): " + "transfer already under way"); + transflag = 1; + maskurg(0); + recvurg = 0; + } else { + if (!transflag) + syslog(LOG_ERR, "Internal: flagxfer(0): " + "no active transfer"); + maskurg(1); + transflag = 0; + } +} + +/* + * Returns 0 if OK to resume or -1 if abort requested. + */ +static int +myoob(void) +{ + char *cp; + int ret; + + if (!transflag) { + syslog(LOG_ERR, "Internal: myoob() while no transfer"); + return (0); + } + cp = tmpline; + ret = getline(cp, 7, stdin); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + /* Ignore truncated command. */ + return (0); + } + upper(cp); + if (strcmp(cp, "ABOR\r\n") == 0) { + tmpline[0] = '\0'; + reply(426, "Transfer aborted. Data connection closed."); + reply(226, "Abort successful."); + return (-1); + } + if (strcmp(cp, "STAT\r\n") == 0) { + tmpline[0] = '\0'; + if (file_size != -1) + reply(213, "Status: %jd of %jd bytes transferred.", + (intmax_t)byte_count, (intmax_t)file_size); + else + reply(213, "Status: %jd bytes transferred.", + (intmax_t)byte_count); + } + return (0); +} + +/* + * Note: a response of 425 is not mentioned as a possible response to + * the PASV command in RFC959. However, it has been blessed as + * a legitimate response by Jon Postel in a telephone conversation + * with Rick Adams on 25 Jan 89. + */ +void +passive(void) +{ + socklen_t len; + int on; + char *p, *a; + + if (pdata >= 0) /* close old port if one set */ + close(pdata); + + pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + on = 1; + if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); + + (void) seteuid(0); + +#ifdef IP_PORTRANGE + if (ctrl_addr.su_family == AF_INET) { + on = restricted_data_ports ? IP_PORTRANGE_HIGH + : IP_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif +#ifdef IPV6_PORTRANGE + if (ctrl_addr.su_family == AF_INET6) { + on = restricted_data_ports ? IPV6_PORTRANGE_HIGH + : IPV6_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif + + pasv_addr = ctrl_addr; + pasv_addr.su_port = 0; + if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0) + goto pasv_error; + + (void) seteuid(pw->pw_uid); + + len = sizeof(pasv_addr); + if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + if (pasv_addr.su_family == AF_INET) + a = (char *) &pasv_addr.su_sin.sin_addr; + else if (pasv_addr.su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) + a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; + else + goto pasv_error; + + p = (char *) &pasv_addr.su_port; + +#define UC(b) (((int) b) & 0xff) + + reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), + UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); + return; + +pasv_error: + (void) seteuid(pw->pw_uid); + (void) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +/* + * Long Passive defined in RFC 1639. + * 228 Entering Long Passive Mode + * (af, hal, h1, h2, h3,..., pal, p1, p2...) + */ + +void +long_passive(char *cmd, int pf) +{ + socklen_t len; + int on; + char *p, *a; + + if (pdata >= 0) /* close old port if one set */ + close(pdata); + + if (pf != PF_UNSPEC) { + if (ctrl_addr.su_family != pf) { + switch (ctrl_addr.su_family) { + case AF_INET: + pf = 1; + break; + case AF_INET6: + pf = 2; + break; + default: + pf = 0; + break; + } + /* + * XXX + * only EPRT/EPSV ready clients will understand this + */ + if (strcmp(cmd, "EPSV") == 0 && pf) { + reply(522, "Network protocol mismatch, " + "use (%d)", pf); + } else + reply(501, "Network protocol mismatch."); /*XXX*/ + + return; + } + } + + pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + on = 1; + if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); + + (void) seteuid(0); + + pasv_addr = ctrl_addr; + pasv_addr.su_port = 0; + len = pasv_addr.su_len; + +#ifdef IP_PORTRANGE + if (ctrl_addr.su_family == AF_INET) { + on = restricted_data_ports ? IP_PORTRANGE_HIGH + : IP_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif +#ifdef IPV6_PORTRANGE + if (ctrl_addr.su_family == AF_INET6) { + on = restricted_data_ports ? IPV6_PORTRANGE_HIGH + : IPV6_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif + + if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0) + goto pasv_error; + + (void) seteuid(pw->pw_uid); + + if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + +#define UC(b) (((int) b) & 0xff) + + if (strcmp(cmd, "LPSV") == 0) { + p = (char *)&pasv_addr.su_port; + switch (pasv_addr.su_family) { + case AF_INET: + a = (char *) &pasv_addr.su_sin.sin_addr; + v4_reply: + reply(228, +"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)", + 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + 2, UC(p[0]), UC(p[1])); + return; + case AF_INET6: + if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) { + a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; + goto v4_reply; + } + a = (char *) &pasv_addr.su_sin6.sin6_addr; + reply(228, +"Entering Long Passive Mode " +"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)", + 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), + UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), + UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), + 2, UC(p[0]), UC(p[1])); + return; + } + } else if (strcmp(cmd, "EPSV") == 0) { + switch (pasv_addr.su_family) { + case AF_INET: + case AF_INET6: + reply(229, "Entering Extended Passive Mode (|||%d|)", + ntohs(pasv_addr.su_port)); + return; + } + } else { + /* more proper error code? */ + } + +pasv_error: + (void) seteuid(pw->pw_uid); + (void) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +/* + * Generate unique name for file with basename "local" + * and open the file in order to avoid possible races. + * Try "local" first, then "local.1", "local.2" etc, up to "local.99". + * Return descriptor to the file, set "name" to its name. + * + * Generates failure reply on error. + */ +static int +guniquefd(char *local, char **name) +{ + static char new[MAXPATHLEN]; + struct stat st; + char *cp; + int count; + int fd; + + cp = strrchr(local, '/'); + if (cp) + *cp = '\0'; + if (stat(cp ? local : ".", &st) < 0) { + perror_reply(553, cp ? local : "."); + return (-1); + } + if (cp) { + /* + * Let not overwrite dirname with counter suffix. + * -4 is for /nn\0 + * In this extreme case dot won't be put in front of suffix. + */ + if (strlen(local) > sizeof(new) - 4) { + reply(553, "Pathname too long."); + return (-1); + } + *cp = '/'; + } + /* -4 is for the .nn<null> we put on the end below */ + (void) snprintf(new, sizeof(new) - 4, "%s", local); + cp = new + strlen(new); + /* + * Don't generate dotfile unless requested explicitly. + * This covers the case when basename gets truncated off + * by buffer size. + */ + if (cp > new && cp[-1] != '/') + *cp++ = '.'; + for (count = 0; count < 100; count++) { + /* At count 0 try unmodified name */ + if (count) + (void)sprintf(cp, "%d", count); + if ((fd = open(count ? new : local, + O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) { + *name = count ? new : local; + return (fd); + } + if (errno != EEXIST) { + perror_reply(553, count ? new : local); + return (-1); + } + } + reply(452, "Unique file name cannot be created."); + return (-1); +} + +/* + * Format and send reply containing system error number. + */ +void +perror_reply(int code, char *string) +{ + + reply(code, "%s: %s.", string, strerror(errno)); +} + +static char *onefile[] = { + "", + 0 +}; + +void +send_file_list(char *whichf) +{ + struct stat st; + DIR *dirp = NULL; + struct dirent *dir; + FILE *dout = NULL; + char **dirlist, *dirname; + int simple = 0; + int freeglob = 0; + glob_t gl; + + if (strpbrk(whichf, "~{[*?") != NULL) { + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + gl.gl_matchc = MAXGLOBARGS; + flags |= GLOB_LIMIT; + freeglob = 1; + if (glob(whichf, flags, 0, &gl)) { + reply(550, "No matching files found."); + goto out; + } else if (gl.gl_pathc == 0) { + errno = ENOENT; + perror_reply(550, whichf); + goto out; + } + dirlist = gl.gl_pathv; + } else { + onefile[0] = whichf; + dirlist = onefile; + simple = 1; + } + + while ((dirname = *dirlist++)) { + if (stat(dirname, &st) < 0) { + /* + * If user typed "ls -l", etc, and the client + * used NLST, do what the user meant. + */ + if (dirname[0] == '-' && *dirlist == NULL && + dout == NULL) + retrieve(_PATH_LS " %s", dirname); + else + perror_reply(550, whichf); + goto out; + } + + if (S_ISREG(st.st_mode)) { + if (dout == NULL) { + dout = dataconn("file list", -1, "w"); + if (dout == NULL) + goto out; + STARTXFER; + } + START_UNSAFE; + fprintf(dout, "%s%s\n", dirname, + type == TYPE_A ? "\r" : ""); + END_UNSAFE; + if (ferror(dout)) + goto data_err; + byte_count += strlen(dirname) + + (type == TYPE_A ? 2 : 1); + CHECKOOB(goto abrt); + continue; + } else if (!S_ISDIR(st.st_mode)) + continue; + + if ((dirp = opendir(dirname)) == NULL) + continue; + + while ((dir = readdir(dirp)) != NULL) { + char nbuf[MAXPATHLEN]; + + CHECKOOB(goto abrt); + + if (dir->d_name[0] == '.' && dir->d_namlen == 1) + continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && + dir->d_namlen == 2) + continue; + + snprintf(nbuf, sizeof(nbuf), + "%s/%s", dirname, dir->d_name); + + /* + * We have to do a stat to insure it's + * not a directory or special file. + */ + if (simple || (stat(nbuf, &st) == 0 && + S_ISREG(st.st_mode))) { + if (dout == NULL) { + dout = dataconn("file list", -1, "w"); + if (dout == NULL) + goto out; + STARTXFER; + } + START_UNSAFE; + if (nbuf[0] == '.' && nbuf[1] == '/') + fprintf(dout, "%s%s\n", &nbuf[2], + type == TYPE_A ? "\r" : ""); + else + fprintf(dout, "%s%s\n", nbuf, + type == TYPE_A ? "\r" : ""); + END_UNSAFE; + if (ferror(dout)) + goto data_err; + byte_count += strlen(nbuf) + + (type == TYPE_A ? 2 : 1); + CHECKOOB(goto abrt); + } + } + (void) closedir(dirp); + dirp = NULL; + } + + if (dout == NULL) + reply(550, "No files found."); + else if (ferror(dout)) +data_err: perror_reply(550, "Data connection"); + else + reply(226, "Transfer complete."); +out: + if (dout) { + ENDXFER; +abrt: + (void) fclose(dout); + data = -1; + pdata = -1; + } + if (dirp) + (void) closedir(dirp); + if (freeglob) { + freeglob = 0; + globfree(&gl); + } +} + +void +reapchild(int signo) +{ + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +#ifdef OLD_SETPROCTITLE +/* + * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) + * Warning, since this is usually started from inetd.conf, it often doesn't + * have much of an environment or arglist to overwrite. + */ +void +setproctitle(const char *fmt, ...) +{ + int i; + va_list ap; + char *p, *bp, ch; + char buf[LINE_MAX]; + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf), fmt, ap); + + /* make ps print our process name */ + p = Argv[0]; + *p++ = '-'; + + i = strlen(buf); + if (i > LastArgv - p - 2) { + i = LastArgv - p - 2; + buf[i] = '\0'; + } + bp = buf; + while (ch = *bp++) + if (ch != '\n' && ch != '\r') + *p++ = ch; + while (p < LastArgv) + *p++ = ' '; +} +#endif /* OLD_SETPROCTITLE */ + +static void +appendf(char **strp, char *fmt, ...) +{ + va_list ap; + char *ostr, *p; + + va_start(ap, fmt); + vasprintf(&p, fmt, ap); + va_end(ap); + if (p == NULL) + fatalerror("Ran out of memory."); + if (*strp == NULL) + *strp = p; + else { + ostr = *strp; + asprintf(strp, "%s%s", ostr, p); + if (*strp == NULL) + fatalerror("Ran out of memory."); + free(ostr); + } +} + +static void +logcmd(char *cmd, char *file1, char *file2, off_t cnt) +{ + char *msg = NULL; + char wd[MAXPATHLEN + 1]; + + if (logging <= 1) + return; + + if (getcwd(wd, sizeof(wd) - 1) == NULL) + strcpy(wd, strerror(errno)); + + appendf(&msg, "%s", cmd); + if (file1) + appendf(&msg, " %s", file1); + if (file2) + appendf(&msg, " %s", file2); + if (cnt >= 0) + appendf(&msg, " = %jd bytes", (intmax_t)cnt); + appendf(&msg, " (wd: %s", wd); + if (guest || dochroot) + appendf(&msg, "; chrooted"); + appendf(&msg, ")"); + syslog(LOG_INFO, "%s", msg); + free(msg); +} + +static void +logxfer(char *name, off_t size, time_t start) +{ + char buf[MAXPATHLEN + 1024]; + char path[MAXPATHLEN + 1]; + time_t now; + + if (statfd >= 0) { + time(&now); + if (realpath(name, path) == NULL) { + syslog(LOG_NOTICE, "realpath failed on %s: %m", path); + return; + } + snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%jd!%ld\n", + ctime(&now)+4, ident, remotehost, + path, (intmax_t)size, + (long)(now - start + (now == start))); + write(statfd, buf, strlen(buf)); + } +} + +static char * +doublequote(char *s) +{ + int n; + char *p, *s2; + + for (p = s, n = 0; *p; p++) + if (*p == '"') + n++; + + if ((s2 = malloc(p - s + n + 1)) == NULL) + return (NULL); + + for (p = s2; *s; s++, p++) { + if ((*p = *s) == '"') + *(++p) = '"'; + } + *p = '\0'; + + return (s2); +} + +/* setup server socket for specified address family */ +/* if af is PF_UNSPEC more than one socket may be returned */ +/* the returned list is dynamically allocated, so caller needs to free it */ +static int * +socksetup(int af, char *bindname, const char *bindport) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks; + const int on = 1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(bindname, bindport, &hints, &res); + if (error) { + syslog(LOG_ERR, "%s", gai_strerror(error)); + if (error == EAI_SYSTEM) + syslog(LOG_ERR, "%s", strerror(errno)); + return NULL; + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r; r = r->ai_next, maxs++) + ; + socks = malloc((maxs + 1) * sizeof(int)); + if (!socks) { + freeaddrinfo(res); + syslog(LOG_ERR, "couldn't allocate memory for sockets"); + return NULL; + } + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + syslog(LOG_DEBUG, "control socket: %m"); + continue; + } + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) < 0) + syslog(LOG_WARNING, + "control setsockopt (SO_REUSEADDR): %m"); + if (r->ai_family == AF_INET6) { + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof(on)) < 0) + syslog(LOG_WARNING, + "control setsockopt (IPV6_V6ONLY): %m"); + } + if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) { + syslog(LOG_DEBUG, "control bind: %m"); + close(*s); + continue; + } + (*socks)++; + s++; + } + + if (res) + freeaddrinfo(res); + + if (*socks == 0) { + syslog(LOG_ERR, "control socket: Couldn't bind to any socket"); + free(socks); + return NULL; + } + return(socks); +} diff --git a/libexec/ftpd/logwtmp.c b/libexec/ftpd/logwtmp.c new file mode 100644 index 0000000..9beade3 --- /dev/null +++ b/libexec/ftpd/logwtmp.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)logwtmp.c 8.1 (Berkeley) 6/4/93"; +#endif +#endif /* not lint */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <libutil.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <utmpx.h> +#include "extern.h" + +void +ftpd_logwtmp(char *id, char *user, struct sockaddr *addr) +{ + struct utmpx ut; + + memset(&ut, 0, sizeof(ut)); + + if (user != NULL) { + /* Log in. */ + ut.ut_type = USER_PROCESS; + (void)strncpy(ut.ut_user, user, sizeof(ut.ut_user)); + if (addr != NULL) + realhostname_sa(ut.ut_host, sizeof(ut.ut_host), + addr, addr->sa_len); + } else { + /* Log out. */ + ut.ut_type = DEAD_PROCESS; + } + + ut.ut_pid = getpid(); + gettimeofday(&ut.ut_tv, NULL); + (void)strncpy(ut.ut_id, id, sizeof(ut.ut_id)); + (void)strncpy(ut.ut_line, "ftpd", sizeof(ut.ut_line)); + + pututxline(&ut); +} diff --git a/libexec/ftpd/pathnames.h b/libexec/ftpd/pathnames.h new file mode 100644 index 0000000..2083034 --- /dev/null +++ b/libexec/ftpd/pathnames.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * $FreeBSD$ + */ + +#include <paths.h> + +#define _PATH_FTPCHROOT "/etc/ftpchroot" +#define _PATH_FTPWELCOME "/etc/ftpwelcome" +#define _PATH_FTPLOGINMESG "/etc/ftpmotd" +#define _PATH_FTPHOSTS "/etc/ftphosts" +#define _PATH_FTPDSTATFILE "/var/log/ftpd" +#define _PATH_LS "/bin/ls" diff --git a/libexec/ftpd/popen.c b/libexec/ftpd/popen.c new file mode 100644 index 0000000..03b8f7a --- /dev/null +++ b/libexec/ftpd/popen.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; +#endif +#endif /* not lint */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/wait.h> +#include <netinet/in.h> + +#include <errno.h> +#include <glob.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" +#include "pathnames.h" +#include <syslog.h> +#include <time.h> + +#define MAXUSRARGS 100 +#define MAXGLOBARGS 1000 + +/* + * Special version of popen which avoids call to shell. This ensures noone + * may create a pipe to a hidden program as a side effect of a list or dir + * command. + */ +static int *pids; +static int fds; + +FILE * +ftpd_popen(char *program, char *type) +{ + char *cp; + FILE *iop; + int argc, gargc, pdes[2], pid; + char **pop, *argv[MAXUSRARGS], *gargv[MAXGLOBARGS]; + + if (((*type != 'r') && (*type != 'w')) || type[1]) + return (NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return (NULL); + if ((pids = malloc(fds * sizeof(int))) == NULL) + return (NULL); + memset(pids, 0, fds * sizeof(int)); + } + if (pipe(pdes) < 0) + return (NULL); + + /* break up string into pieces */ + for (argc = 0, cp = program; argc < MAXUSRARGS; cp = NULL) { + if (!(argv[argc++] = strtok(cp, " \t\n"))) + break; + } + argv[argc - 1] = NULL; + + /* glob each piece */ + gargv[0] = argv[0]; + for (gargc = argc = 1; argv[argc] && gargc < (MAXGLOBARGS-1); argc++) { + glob_t gl; + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + gl.gl_matchc = MAXGLOBARGS; + flags |= GLOB_LIMIT; + if (glob(argv[argc], flags, NULL, &gl)) + gargv[gargc++] = strdup(argv[argc]); + else if (gl.gl_pathc > 0) { + for (pop = gl.gl_pathv; *pop && gargc < (MAXGLOBARGS-1); + pop++) + gargv[gargc++] = strdup(*pop); + } + globfree(&gl); + } + gargv[gargc] = NULL; + + iop = NULL; + fflush(NULL); + pid = (strcmp(gargv[0], _PATH_LS) == 0) ? fork() : vfork(); + switch(pid) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != STDOUT_FILENO) { + dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + dup2(STDOUT_FILENO, STDERR_FILENO); /* stderr too! */ + (void)close(pdes[0]); + } else { + if (pdes[0] != STDIN_FILENO) { + dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + (void)close(pdes[1]); + } + /* Drop privileges before proceeding */ + if (getuid() != geteuid() && setuid(geteuid()) < 0) + _exit(1); + if (strcmp(gargv[0], _PATH_LS) == 0) { + /* Reset getopt for ls_main() */ + optreset = optind = optopt = 1; + /* Close syslogging to remove pwd.db missing msgs */ + closelog(); + /* Trigger to sense new /etc/localtime after chroot */ + if (getenv("TZ") == NULL) { + setenv("TZ", "", 0); + tzset(); + unsetenv("TZ"); + tzset(); + } + exit(ls_main(gargc, gargv)); + } + execv(gargv[0], gargv); + _exit(1); + } + /* parent; assume fdopen can't fail... */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + pids[fileno(iop)] = pid; + +pfree: for (argc = 1; gargv[argc] != NULL; argc++) + free(gargv[argc]); + + return (iop); +} + +int +ftpd_pclose(FILE *iop) +{ + int fdes, omask, status; + pid_t pid; + + /* + * pclose returns -1 if stream is not associated with a + * `popened' command, or, if already `pclosed'. + */ + if (pids == 0 || pids[fdes = fileno(iop)] == 0) + return (-1); + (void)fclose(iop); + omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); + while ((pid = waitpid(pids[fdes], &status, 0)) < 0 && errno == EINTR) + continue; + (void)sigsetmask(omask); + pids[fdes] = 0; + if (pid < 0) + return (pid); + if (WIFEXITED(status)) + return (WEXITSTATUS(status)); + return (1); +} diff --git a/libexec/getty/Makefile b/libexec/getty/Makefile new file mode 100644 index 0000000..f967958 --- /dev/null +++ b/libexec/getty/Makefile @@ -0,0 +1,14 @@ +# from: @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= getty +SRCS= main.c init.c subr.c chat.c +DPADD= ${LIBUTIL} +LDADD= -lutil +MAN= gettytab.5 ttys.5 getty.8 + +WARNS?= 1 +WFORMAT=0 + +.include <bsd.prog.mk> + diff --git a/libexec/getty/chat.c b/libexec/getty/chat.c new file mode 100644 index 0000000..4817c97 --- /dev/null +++ b/libexec/getty/chat.c @@ -0,0 +1,488 @@ +/*- + * Copyright (c) 1997 + * David L Nugent <davidn@blaze.net.au>. + * All rights reserved. + * + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. This work was done expressly for inclusion into FreeBSD. Other use + * is permitted provided this notation is included. + * 4. Absolutely no warranty of function or purpose is made by the authors. + * 5. Modifications may be freely made to this file providing the above + * conditions are met. + * + * Modem chat module - send/expect style functions for getty + * For semi-intelligent modem handling. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/utsname.h> + +#include <ctype.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "gettytab.h" +#include "extern.h" + +#define PAUSE_CH (unsigned char)'\xff' /* pause kludge */ + +#define CHATDEBUG_RECEIVE 0x01 +#define CHATDEBUG_SEND 0x02 +#define CHATDEBUG_EXPECT 0x04 +#define CHATDEBUG_MISC 0x08 + +#define CHATDEBUG_DEFAULT 0 +#define CHAT_DEFAULT_TIMEOUT 10 + + +static int chat_debug = CHATDEBUG_DEFAULT; +static int chat_alarm = CHAT_DEFAULT_TIMEOUT; /* Default */ + +static volatile int alarmed = 0; + + +static void chat_alrm(int); +static int chat_unalarm(void); +static int getdigit(unsigned char **, int, int); +static char **read_chat(char **); +static char *cleanchr(char **, unsigned char); +static char *cleanstr(const unsigned char *, int); +static const char *result(int); +static int chat_expect(const char *); +static int chat_send(char const *); + + +/* + * alarm signal handler + * handle timeouts in read/write + * change stdin to non-blocking mode to prevent + * possible hang in read(). + */ + +static void +chat_alrm(int signo __unused) +{ + int on = 1; + + alarm(1); + alarmed = 1; + signal(SIGALRM, chat_alrm); + ioctl(STDIN_FILENO, FIONBIO, &on); +} + + +/* + * Turn back on blocking mode reset by chat_alrm() + */ + +static int +chat_unalarm(void) +{ + int off = 0; + return ioctl(STDIN_FILENO, FIONBIO, &off); +} + + +/* + * convert a string of a given base (octal/hex) to binary + */ + +static int +getdigit(unsigned char **ptr, int base, int max) +{ + int i, val = 0; + char * q; + + static const char xdigits[] = "0123456789abcdef"; + + for (i = 0, q = *ptr; i++ < max; ++q) { + int sval; + const char * s = strchr(xdigits, tolower(*q)); + + if (s == NULL || (sval = s - xdigits) >= base) + break; + val = (val * base) + sval; + } + *ptr = q; + return val; +} + + +/* + * read_chat() + * Convert a whitespace delimtied string into an array + * of strings, being expect/send pairs + */ + +static char ** +read_chat(char **chatstr) +{ + char *str = *chatstr; + char **res = NULL; + + if (str != NULL) { + char *tmp = NULL; + int l; + + if ((l=strlen(str)) > 0 && (tmp=malloc(l + 1)) != NULL && + (res=malloc((l / 2 + 1) * sizeof(char *))) != NULL) { + static char ws[] = " \t"; + char * p; + + for (l = 0, p = strtok(strcpy(tmp, str), ws); + p != NULL; + p = strtok(NULL, ws)) + { + unsigned char *q, *r; + + /* Read escapes */ + for (q = r = (unsigned char *)p; *r; ++q) + { + if (*q == '\\') + { + /* handle special escapes */ + switch (*++q) + { + case 'a': /* bell */ + *r++ = '\a'; + break; + case 'r': /* cr */ + *r++ = '\r'; + break; + case 'n': /* nl */ + *r++ = '\n'; + break; + case 'f': /* ff */ + *r++ = '\f'; + break; + case 'b': /* bs */ + *r++ = '\b'; + break; + case 'e': /* esc */ + *r++ = 27; + break; + case 't': /* tab */ + *r++ = '\t'; + break; + case 'p': /* pause */ + *r++ = PAUSE_CH; + break; + case 's': + case 'S': /* space */ + *r++ = ' '; + break; + case 'x': /* hexdigit */ + ++q; + *r++ = getdigit(&q, 16, 2); + --q; + break; + case '0': /* octal */ + ++q; + *r++ = getdigit(&q, 8, 3); + --q; + break; + default: /* literal */ + *r++ = *q; + break; + case 0: /* not past eos */ + --q; + break; + } + } else { + /* copy standard character */ + *r++ = *q; + } + } + + /* Remove surrounding quotes, if any + */ + if (*p == '"' || *p == '\'') { + q = strrchr(p+1, *p); + if (q != NULL && *q == *p && q[1] == '\0') { + *q = '\0'; + strcpy(p, p+1); + } + } + + res[l++] = p; + } + res[l] = NULL; + *chatstr = tmp; + return res; + } + free(tmp); + } + return res; +} + + +/* + * clean a character for display (ctrl/meta character) + */ + +static char * +cleanchr(char **buf, unsigned char ch) +{ + int l; + static char tmpbuf[5]; + char * tmp = buf ? *buf : tmpbuf; + + if (ch & 0x80) { + strcpy(tmp, "M-"); + l = 2; + ch &= 0x7f; + } else + l = 0; + + if (ch < 32) { + tmp[l++] = '^'; + tmp[l++] = ch + '@'; + } else if (ch == 127) { + tmp[l++] = '^'; + tmp[l++] = '?'; + } else + tmp[l++] = ch; + tmp[l] = '\0'; + + if (buf) + *buf = tmp + l; + return tmp; +} + + +/* + * clean a string for display (ctrl/meta characters) + */ + +static char * +cleanstr(const unsigned char *s, int l) +{ + static unsigned char * tmp = NULL; + static int tmplen = 0; + + if (tmplen < l * 4 + 1) + tmp = realloc(tmp, tmplen = l * 4 + 1); + + if (tmp == NULL) { + tmplen = 0; + return (char *)"(mem alloc error)"; + } else { + int i = 0; + char * p = tmp; + + while (i < l) + cleanchr(&p, s[i++]); + *p = '\0'; + } + + return tmp; +} + + +/* + * return result as a pseudo-english word + */ + +static const char * +result(int r) +{ + static const char * results[] = { + "OK", "MEMERROR", "IOERROR", "TIMEOUT" + }; + return results[r & 3]; +} + + +/* + * chat_expect() + * scan input for an expected string + */ + +static int +chat_expect(const char *str) +{ + int len, r = 0; + + if (chat_debug & CHATDEBUG_EXPECT) + syslog(LOG_DEBUG, "chat_expect '%s'", cleanstr(str, strlen(str))); + + if ((len = strlen(str)) > 0) { + int i = 0; + char * got; + + if ((got = malloc(len + 1)) == NULL) + r = 1; + else { + + memset(got, 0, len+1); + alarm(chat_alarm); + alarmed = 0; + + while (r == 0 && i < len) { + if (alarmed) + r = 3; + else { + unsigned char ch; + + if (read(STDIN_FILENO, &ch, 1) == 1) { + + if (chat_debug & CHATDEBUG_RECEIVE) + syslog(LOG_DEBUG, "chat_recv '%s' m=%d", + cleanchr(NULL, ch), i); + + if (ch == str[i]) + got[i++] = ch; + else if (i > 0) { + int j = 1; + + /* See if we can resync on a + * partial match in our buffer + */ + while (j < i && memcmp(got + j, str, i - j) != 0) + j++; + if (j < i) + memcpy(got, got + j, i - j); + i -= j; + } + } else + r = alarmed ? 3 : 2; + } + } + alarm(0); + chat_unalarm(); + alarmed = 0; + free(got); + } + } + + if (chat_debug & CHATDEBUG_EXPECT) + syslog(LOG_DEBUG, "chat_expect %s", result(r)); + + return r; +} + + +/* + * chat_send() + * send a chat string + */ + +static int +chat_send(char const *str) +{ + int r = 0; + + if (chat_debug & CHATDEBUG_SEND) + syslog(LOG_DEBUG, "chat_send '%s'", cleanstr(str, strlen(str))); + + if (*str) { + alarm(chat_alarm); + alarmed = 0; + while (r == 0 && *str) + { + unsigned char ch = (unsigned char)*str++; + + if (alarmed) + r = 3; + else if (ch == PAUSE_CH) + usleep(500000); /* 1/2 second */ + else { + usleep(10000); /* be kind to modem */ + if (write(STDOUT_FILENO, &ch, 1) != 1) + r = alarmed ? 3 : 2; + } + } + alarm(0); + chat_unalarm(); + alarmed = 0; + } + + if (chat_debug & CHATDEBUG_SEND) + syslog(LOG_DEBUG, "chat_send %s", result(r)); + + return r; +} + + +/* + * getty_chat() + * + * Termination codes: + * -1 - no script supplied + * 0 - script terminated correctly + * 1 - invalid argument, expect string too large, etc. + * 2 - error on an I/O operation or fatal error condition + * 3 - timeout waiting for a simple string + * + * Parameters: + * char *scrstr - unparsed chat script + * timeout - seconds timeout + * debug - debug value (bitmask) + */ + +int +getty_chat(char *scrstr, int timeout, int debug) +{ + int r = -1; + + chat_alarm = timeout ? timeout : CHAT_DEFAULT_TIMEOUT; + chat_debug = debug; + + if (scrstr != NULL) { + char **script; + + if (chat_debug & CHATDEBUG_MISC) + syslog(LOG_DEBUG, "getty_chat script='%s'", scrstr); + + if ((script = read_chat(&scrstr)) != NULL) { + int i = r = 0; + int off = 0; + sig_t old_alarm; + + /* + * We need to be in raw mode for all this + * Rely on caller... + */ + + old_alarm = signal(SIGALRM, chat_alrm); + chat_unalarm(); /* Force blocking mode at start */ + + /* + * This is the send/expect loop + */ + while (r == 0 && script[i] != NULL) + if ((r = chat_expect(script[i++])) == 0 && script[i] != NULL) + r = chat_send(script[i++]); + + signal(SIGALRM, old_alarm); + free(script); + free(scrstr); + + /* + * Ensure stdin is in blocking mode + */ + ioctl(STDIN_FILENO, FIONBIO, &off); + } + + if (chat_debug & CHATDEBUG_MISC) + syslog(LOG_DEBUG, "getty_chat %s", result(r)); + + } + return r; +} diff --git a/libexec/getty/extern.h b/libexec/getty/extern.h new file mode 100644 index 0000000..c7b73a8 --- /dev/null +++ b/libexec/getty/extern.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)extern.h 8.1 (Berkeley) 6/4/93 + * $FreeBSD$ + */ + +struct delayval; +struct termios; + +extern char hostname[]; +extern int hopcount; +extern struct termios tmode, omode; +extern struct gettyflags gettyflags[]; +extern struct gettynums gettynums[]; +extern struct gettystrs gettystrs[]; + +int adelay(int, struct delayval *); +const char *autobaud(void); +int delaybits(void); +void edithost(const char *); +void gendefaults(void); +void gettable(const char *, char *); +void makeenv(char *[]); +const char *portselector(void); +void set_ttydefaults(int); +void setchars(void); +void setdefaults(void); +void set_flags(int); +int speed(int); +int getty_chat(char *, int, int); diff --git a/libexec/getty/getty.8 b/libexec/getty/getty.8 new file mode 100644 index 0000000..0f3bca7 --- /dev/null +++ b/libexec/getty/getty.8 @@ -0,0 +1,124 @@ +.\" Copyright (c) 1980, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)getty.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" " +.Dd June 4, 1993 +.Dt GETTY 8 +.Os +.Sh NAME +.Nm getty +.Nd set terminal mode +.Sh SYNOPSIS +.Nm +.Oo +.Ar type +.Op Ar tty +.Oc +.Sh DESCRIPTION +The +.Nm +utility is called by +.Xr init 8 +to open and initialize the tty line, read a login name, and invoke +.Xr login 1 . +.Pp +The argument +.Ar tty +is the special device file in +.Pa /dev +to open for the terminal (for example, ``ttyh0''). +If there is no argument or the argument is +.Sq Fl , +the tty line is assumed to be open as file descriptor 0. +.Pp +The +.Ar type +argument can be used to make +.Nm +treat the terminal line specially. +This argument is used as an index into the +.Xr gettytab 5 +database, to determine the characteristics of the line. +If there is no argument, or there is no such table, the +.Em default +table is used. +If there is no +.Pa /etc/gettytab +a set of system defaults is used. +If indicated by the table located, +.Nm +will clear the terminal screen, +print a banner heading, +and prompt for a login name. +Usually either the banner or the login prompt will include +the system hostname. +.Pp +Most of the default actions of +.Nm +can be circumvented, or modified, by a suitable +.Pa gettytab +table. +.Pp +The +.Nm +utility can be set to timeout after some interval, +which will cause dial up lines to hang up +if the login name is not entered reasonably quickly. +.Sh FILES +.Bl -tag -width /etc/gettytab -compact +.It Pa /etc/gettytab +.It Pa /etc/ttys +.El +.Sh DIAGNOSTICS +.Bl -diag +.It "ttyxx: No such device or address." +.It "ttyxx: No such file or address." +.Pp +A terminal which is turned +on in the +.Pa ttys +file cannot be opened, likely because the requisite +lines are either not configured into the system, the associated device +was not attached during boot-time system configuration, +or the special file in +.Pa /dev +does not exist. +.El +.Sh SEE ALSO +.Xr login 1 , +.Xr ioctl 2 , +.Xr tty 4 , +.Xr gettytab 5 , +.Xr ttys 5 , +.Xr init 8 +.Sh HISTORY +A +.Nm +utility appeared in +.At v6 . diff --git a/libexec/getty/gettytab.5 b/libexec/getty/gettytab.5 new file mode 100644 index 0000000..16cd15a --- /dev/null +++ b/libexec/getty/gettytab.5 @@ -0,0 +1,537 @@ +.\" Copyright (c) 1983, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)gettytab.5 8.4 (Berkeley) 4/19/94 +.\" $FreeBSD$ +.\" " +.Dd April 19, 1994 +.Dt GETTYTAB 5 +.Os +.Sh NAME +.Nm gettytab +.Nd terminal configuration data base +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +file +is a simplified version of the +.Xr termcap 5 +data base +used to describe terminal lines. +The initial terminal login process +.Xr getty 8 +accesses the +.Nm +file each time it starts, allowing simpler +reconfiguration of terminal characteristics. +Each entry in the data base +is used to describe one class of terminals. +.Pp +There is a default terminal class, +.Va default , +that is used to set global defaults for all other classes. +(That is, the +.Va default +entry is read, then the entry for the class required +is used to override particular settings.) +.Sh CAPABILITIES +Refer to +.Xr termcap 5 +for a description of the file layout. +The +.Va default +column below lists defaults obtained if there is +no entry in the table obtained, nor one in the special +.Va default +table. +.Bl -column Name Type /usr/bin/login +.It Sy "Name Type Default Description" +.It "ac str unused expect-send chat script for modem answer" +.It "al str unused user to auto-login instead of prompting" +.It "ap bool false terminal uses any parity" +.It "bk str 0377 alternate end of line character (input break)" +.It "c0 num unused tty control flags to write messages" +.It "c1 num unused tty control flags to read login name" +.It "c2 num unused tty control flags to leave terminal as" +.It "ce bool false use crt erase algorithm" +.It "ck bool false use crt kill algorithm" +.It "cl str" Ta Dv NULL Ta +.No "screen clear sequence" +.It "co bool false console - add" +.Ql \en +after login prompt +.It "ct num 10 chat timeout for" +.Va \&ac +and +.Va \&ic +scripts +.It "dc num 0 chat debug bitmask" +.It "de num 0 delay secs and flush input before writing first prompt" +.It "df str %+ the" Xr strftime 3 "format used for \&%d in the banner message" +.It "ds str" Ta So Li ^Y Sc Ta +.No "delayed suspend character" +.It "dx bool false set" +.Dv DECCTLQ +.It "ec bool false leave echo" +.Em OFF +.It "ep bool false terminal uses even parity" +.It "er str" Ta So Li ^? Sc Ta +.No "erase character" +.It "et str" Ta So Li ^D Sc Ta +.No "end of text" +.Pq Dv EOF +character +.It "ev str" Ta Dv NULL Ta +.No "initial environment" +.It "f0 num unused tty mode flags to write messages" +.It "f1 num unused tty mode flags to read login name" +.It "f2 num unused tty mode flags to leave terminal as" +.It "fl str" Ta So Li ^O Sc Ta +.No "output flush character" +.It "hc bool false do" +.Em NOT +hangup line on last close +.It "he str" Ta Dv NULL Ta +.No "hostname editing string" +.It "hn str hostname hostname" +.It "ht bool false terminal has real tabs" +.It "hw bool false do cts/rts hardware flow control" +.It "i0 num unused tty input flags to write messages" +.It "i1 num unused tty input flags to read login name" +.It "i2 num unused tty input flags to leave terminal as" +.It "ic str unused expect-send chat script for modem initialization" +.It "if str unused display named file before prompt, like /etc/issue" +.It "ig bool false ignore garbage characters in login name" +.It "im str" Ta Dv NULL Ta +.No "initial (banner) message" +.It "in str" Ta So Li ^C Sc Ta +.No "interrupt character" +.It "is num unused input speed" +.It "kl str" Ta So Li ^U Sc Ta +.No "kill character" +.It "l0 num unused tty local flags to write messages" +.It "l1 num unused tty local flags to read login name" +.It "l2 num unused tty local flags to leave terminal as" +.It "lm str login: login prompt" +.It "ln str" Ta So Li ^V Sc Ta +.No "``literal next'' character" +.It "lo str" Ta Pa /usr/bin/login Ta +.No "program to exec when name obtained" +.It "mb bool false do flow control based on carrier" +.It "nc bool false terminal does not supply carrier (set clocal)" +.It "nl bool false terminal has (or might have) a newline character" +.It "np bool false terminal uses no parity (i.e. 8-bit characters)" +.It "nx str default next table (for auto speed selection)" +.It "o0 num unused tty output flags to write messages" +.It "o1 num unused tty output flags to read login name" +.It "o2 num unused tty output flags to leave terminal as" +.It "op bool false terminal uses odd parity" +.It "os num unused output speed" +.It "pc str" Ta So Li \e0 Sc Ta +.No "pad character" +.It "pe bool false use printer (hard copy) erase algorithm" +.It "pf num 0 delay" +between first prompt and following flush (seconds) +.It "pl bool false start PPP login program unconditionally if" +.Va \&pp +is specified +.It "pp str unused PPP login program" +.It "ps bool false line connected to a" +.Tn MICOM +port selector +.It "qu str" Ta So Li \&^\e Sc Ta +.No "quit character" +.It "rp str" Ta So Li ^R Sc Ta +.No "line retype character" +.It "rt num unused ring timeout when using" +.Va \&ac +.It "rw bool false do" +.Em NOT +use raw for input, use cbreak +.It "sp num unused line speed (input and output)" +.It "su str" Ta So Li ^Z Sc Ta +.No "suspend character" +.It "tc str none table continuation" +.It "to num 0 timeout (seconds)" +.It "tt str" Ta Dv NULL Ta +.No "terminal type (for environment)" +.It "ub bool false do unbuffered output (of prompts etc)" +.It "we str" Ta So Li ^W Sc Ta +.No "word erase character" +.It "xc bool false do" +.Em NOT +echo control chars as +.Ql ^X +.It "xf str" Ta So Li ^S Sc Ta Dv XOFF +(stop output) character +.It "xn str" Ta So Li ^Q Sc Ta Dv XON +(start output) character +.It "Lo str C the locale name used for \&%d in the banner message" +.El +.Pp +The following capabilities are no longer supported by +.Xr getty 8 : +.Bl -column Name Type /usr/bin/login +.It "bd num 0 backspace delay" +.It "cb bool false use crt backspace mode" +.It "cd num 0 carriage-return delay" +.It "fd num 0 form-feed (vertical motion) delay" +.It "lc bool false terminal has lower case" +.It "nd num 0 newline (line-feed) delay" +.It "uc bool false terminal is known upper case only" +.El +.Pp +If no line speed is specified, speed will not be altered +from that which prevails when getty is entered. +Specifying an input or output speed will override +line speed for stated direction only. +.Pp +Terminal modes to be used for the output of the message, +for input of the login name, +and to leave the terminal set as upon completion, +are derived from the boolean flags specified. +If the derivation should prove inadequate, +any (or all) of these three may be overridden +with one of the +.Va \&c0 , +.Va \&c1 , +.Va \&c2 , +.Va \&i0 , +.Va \&i1 , +.Va \&i2 , +.Va \&l0 , +.Va \&l1 , +.Va \&l2 , +.Va \&o0 , +.Va \&o1 , +or +.Va \&o2 +numeric specifications, which can be used to specify +(usually in octal, with a leading '0') +the exact values of the flags. +These flags correspond to the termios +.Va c_cflag , +.Va c_iflag , +.Va c_lflag , +and +.Va c_oflag +fields, respectively. +Each these sets must be completely specified to be effective. +The +.Va \&f0 , +.Va \&f1 , +and +.Va \&f2 +are excepted for backwards compatibility with a previous incarnation of +the TTY sub-system. +In these flags the bottom 16 bits of the (32 bits) +value contain the sgttyb +.Va sg_flags +field, while the top 16 bits represent the local mode word. +.Pp +Should +.Xr getty 8 +receive a null character +(presumed to indicate a line break) +it will restart using the table indicated by the +.Va \&nx +entry. +If there is none, it will re-use its original table. +.Pp +Delays are specified in milliseconds, the nearest possible +delay available in the tty driver will be used. +Should greater certainty be desired, delays +with values 0, 1, 2, and 3 are interpreted as +choosing that particular delay algorithm from the driver. +.Pp +The +.Va \&cl +screen clear string may be preceded by a (decimal) number +of milliseconds of delay required (a la termcap). +This delay is simulated by repeated use of the pad character +.Va \&pc . +.Pp +The initial message, login message, and initial file; +.Va \&im , +.Va \&lm +and +.Va \&if +may include any of the following character sequences, which expand to +information about the environment in which +.Xr getty 8 +is running. +.Bl -tag -offset indent -width \&%xxxxxxxxxxxxxx +.It \&%d +The current date and time formatted according to the +.Va \&Lo +and +.Va \&df +strings. +.It \&%h +The hostname of the machine, which is normally obtained from the +system using +.Xr gethostname 3 , +but may also be overridden by the +.Va \&hn +table entry. +In either case it may be edited with the +.Va \&he +string. +A '@' in the +.Va \&he +string causes one character from the real hostname to +be copied to the final hostname. +A '#' in the +.Va \&he +string causes the next character of the real hostname +to be skipped. +Each character that +is neither '@' nor '#' is copied into the final hostname. +Surplus '@' and '#' characters are ignored. +.It \&%t +The tty name. +.It "\&%m, \&%r, \&%s, \&%v" +The type of machine, release of the operating system, name of the +operating system, and version of the kernel, respectively, as +returned by +.Xr uname 3 . +.It \&%% +A +.Dq % +character. +.El +.Pp +When getty execs the login process, given +in the +.Va \&lo +string (usually +.Dq Pa /usr/bin/login ) , +it will have set +the environment to include the terminal type, as indicated +by the +.Va \&tt +string (if it exists). +The +.Va \&ev +string, can be used to enter additional data into +the environment. +It is a list of comma separated strings, each of which +will presumably be of the form +.Li name=value . +.Pp +If a non-zero timeout is specified, with +.Va \&to , +then getty will exit within the indicated +number of seconds, either having +received a login name and passed control +to +.Xr login 1 , +or having received an alarm signal, and exited. +This may be useful to hangup dial in lines. +.Pp +Output from +.Xr getty 8 +is even parity unless +.Va \&op +or +.Va \&np +is specified. +The +.Va \&op +string +may be specified with +.Va \&ap +to allow any parity on input, but generate odd parity output. +Note: this only applies while getty is being run, +terminal driver limitations prevent a more complete +implementation. +The +.Xr getty 8 +utility does not check parity of input characters in +.Dv RAW +mode. +.Pp +If a +.Va \&pp +string is specified and a PPP link bring-up sequence is recognized, +getty will invoke the program referenced by the +.Va \&pp +option. +This can be used to handle incoming PPP calls. +If the +.Va \&pl +option is true as well, +.Xr getty 8 +will skip the user name prompt and the PPP detection phase, and will +invoke the program specified by +.Va \&pp +instantly. +.Pp +.Nm Getty +provides some basic intelligent modem handling by providing a chat +script feature available via two capabilities: +.Pp +.Bl -tag -offset indent -width \&xxxxxxxx -compact +.It ic +Chat script to initialize modem. +.It ac +Chat script to answer a call. +.El +.Pp +A chat script is a set of expect/send string pairs. +When a chat string starts, +.Nm getty +will wait for the first string, and if it finds it, will send the +second, and so on. +Strings specified are separated by one or more tabs or spaces. +Strings may contain standard ASCII characters and special 'escapes', +which consist of a backslash character followed by one or more +characters which are interpreted as follows: +.Pp +.Bl -tag -offset indent -width \&xxxxxxxx -compact +.It \ea +bell character. +.It \eb +backspace. +.It \en +newline. +.It \ee +escape. +.It \ef +formfeed. +.It \ep +half-second pause. +.It \er +carriage return. +.It \eS , \es +space character. +.It \et +tab. +.It \exNN +hexadecimal byte value. +.It \e0NNN +octal byte value. +.El +.Pp +Note that the +.Ql \ep +sequence is only valid for send strings and causes a half-second +pause between sending the previous and next characters. +Hexadecimal values are, at most, 2 hex digits long, and octal +values are a maximum of 3 octal digits. +.Pp +The +.Va \&ic +chat sequence is used to initialize a modem or similar device. +A typical example of an init chat script for a modem with a +hayes compatible command set might look like this: +.Pp +.Dl :ic="" ATE0Q0V1\er OK\er ATS0=0\er OK\er: +.Pp +This script waits for nothing (which always succeeds), sends +a sequence to ensure that the modem is in the correct mode +(suppress command echo, send responses in verbose mode), +and then disables auto-answer. +It waits for an "OK" response before it terminates. +The init sequence is used to check modem responses to ensure that +the modem is functioning correctly. +If the init script fails to complete, +.Nm getty +considers this to be fatal, and results in an error logged via +.Xr syslogd 8 , +and exiting. +.Pp +Similarly, an answer chat script is used to manually answer the +phone in response to (usually) a "RING". +When run with an answer script, +.Nm getty +opens the port in non-blocking mode, clears any extraneous input +and waits for data on the port. +As soon as any data is available, the answer chat script is +started and scanned for a string, and responds according to +the answer chat script. +With a hayes compatible modem, this would normally look something +like: +.Pp +.Dl :ac=RING\er ATA\er CONNECT: +.Pp +This causes the modem to answer the call via the "ATA" command, +then scans input for a "CONNECT" string. +If this is received before a +.Va \&ct +timeout, then a normal login sequence commences. +.Pp +The +.Va \&ct +capability specifies a timeout for all send and expect strings. +This timeout is set individually for each expect wait and send +string and must be at least as long as the time it takes for +a connection to be established between a remote and local +modem (usually around 10 seconds). +.Pp +In most situations, you will want to flush any additional +input after the connection has been detected, and the +.Va \&de +capability may be used to do that, as well as delay for a +short time after the connection has been established during +which all of the connection data has been sent by the modem. +.Sh SEE ALSO +.Xr login 1 , +.Xr gethostname 3 , +.Xr uname 3 , +.Xr termcap 5 , +.Xr getty 8 , +.Xr telnetd 8 +.Sh HISTORY +The +.Nm +file format appeared in +.Bx 4.2 . +.Sh BUGS +The special characters (erase, kill, etc.) are reset to system defaults +by +.Xr login 1 . +In +.Em all +cases, '#' or '^H' typed in a login name will be treated as +an erase character, and '@' will be treated as a kill character. +.Pp +The delay stuff is a real crock. +Apart form its general lack of flexibility, some +of the delay algorithms are not implemented. +The terminal driver should support sane delay settings. +.Pp +The +.Va \&he +capability is stupid. +.Pp +The +.Xr termcap 5 +format is horrid, something more rational should +have been chosen. diff --git a/libexec/getty/gettytab.h b/libexec/getty/gettytab.h new file mode 100644 index 0000000..67ad95f --- /dev/null +++ b/libexec/getty/gettytab.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 1983, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)gettytab.h 8.2 (Berkeley) 3/30/94 + * $FreeBSD$ + */ + +/* + * Getty description definitions. + */ +struct gettystrs { + const char *field; /* name to lookup in gettytab */ + char *defalt; /* value we find by looking in defaults */ + char *value; /* value that we find there */ +}; + +struct gettynums { + const char *field; /* name to lookup */ + long defalt; /* number we find in defaults */ + long value; /* number we find there */ + int set; /* we actually got this one */ +}; + +struct gettyflags { + const char *field; /* name to lookup */ + char invrt; /* name existing in gettytab --> false */ + char defalt; /* true/false in defaults */ + char value; /* true/false flag */ + char set; /* we found it */ +}; + +/* + * String values. + */ +#define NX gettystrs[0].value +#define CL gettystrs[1].value +#define IM gettystrs[2].value +#define LM gettystrs[3].value +#define ER gettystrs[4].value +#define KL gettystrs[5].value +#define ET gettystrs[6].value +#define PC gettystrs[7].value +#define TT gettystrs[8].value +#define EV gettystrs[9].value +#define LO gettystrs[10].value +#define HN gettystrs[11].value +#define HE gettystrs[12].value +#define IN gettystrs[13].value +#define QU gettystrs[14].value +#define XN gettystrs[15].value +#define XF gettystrs[16].value +#define BK gettystrs[17].value +#define SU gettystrs[18].value +#define DS gettystrs[19].value +#define RP gettystrs[20].value +#define FL gettystrs[21].value +#define WE gettystrs[22].value +#define LN gettystrs[23].value +#define Lo gettystrs[24].value +#define PP gettystrs[25].value +#define IF gettystrs[26].value +#define IC gettystrs[27].value +#define AC gettystrs[28].value +#define AL gettystrs[29].value +#define DF gettystrs[30].value + +/* + * Numeric definitions. + */ +#define IS gettynums[0].value +#define OS gettynums[1].value +#define SP gettynums[2].value +#define ND gettynums[3].value +#define CD gettynums[4].value +#define TD gettynums[5].value +#define FD gettynums[6].value +#define BD gettynums[7].value +#define TO gettynums[8].value +#define F0 gettynums[9].value +#define F0set gettynums[9].set +#define F1 gettynums[10].value +#define F1set gettynums[10].set +#define F2 gettynums[11].value +#define F2set gettynums[11].set +#define PF gettynums[12].value +#define C0 gettynums[13].value +#define C0set gettynums[13].set +#define C1 gettynums[14].value +#define C1set gettynums[14].set +#define C2 gettynums[15].value +#define C2set gettynums[15].set +#define I0 gettynums[16].value +#define I0set gettynums[16].set +#define I1 gettynums[17].value +#define I1set gettynums[17].set +#define I2 gettynums[18].value +#define I2set gettynums[18].set +#define L0 gettynums[19].value +#define L0set gettynums[19].set +#define L1 gettynums[20].value +#define L1set gettynums[20].set +#define L2 gettynums[21].value +#define L2set gettynums[21].set +#define O0 gettynums[22].value +#define O0set gettynums[22].set +#define O1 gettynums[23].value +#define O1set gettynums[23].set +#define O2 gettynums[24].value +#define O2set gettynums[24].set +#define DE gettynums[25].value +#define RTset gettynums[26].set +#define RT gettynums[26].value +#define CT gettynums[27].value +#define DC gettynums[28].value + +/* + * Boolean values. + */ +#define HT gettyflags[0].value +#define NL gettyflags[1].value +#define EP gettyflags[2].value +#define EPset gettyflags[2].set +#define OP gettyflags[3].value +#define OPset gettyflags[3].set +#define AP gettyflags[4].value +#define APset gettyflags[4].set +#define EC gettyflags[5].value +#define CO gettyflags[6].value +#define CB gettyflags[7].value +#define CK gettyflags[8].value +#define CE gettyflags[9].value +#define PE gettyflags[10].value +#define RW gettyflags[11].value +#define XC gettyflags[12].value +#define LC gettyflags[13].value +#define UC gettyflags[14].value +#define IG gettyflags[15].value +#define PS gettyflags[16].value +#define HC gettyflags[17].value +#define UB gettyflags[18].value +#define AB gettyflags[19].value +#define DX gettyflags[20].value +#define NP gettyflags[21].value +#define NPset gettyflags[21].set +#define MB gettyflags[22].value +#define HW gettyflags[23].value +#define NC gettyflags[24].value +#define PL gettyflags[25].value diff --git a/libexec/getty/init.c b/libexec/getty/init.c new file mode 100644 index 0000000..6959e8d --- /dev/null +++ b/libexec/getty/init.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)from: init.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * Getty table initializations. + * + * Melbourne getty. + */ +#include <termios.h> +#include "gettytab.h" +#include "extern.h" +#include "pathnames.h" + +static char loginmsg[] = "login: "; +static char nullstr[] = ""; +static char loginprg[] = _PATH_LOGIN; +static char datefmt[] = "%+"; + +struct gettystrs gettystrs[] = { + { "nx" }, /* next table */ + { "cl" }, /* screen clear characters */ + { "im" }, /* initial message */ + { "lm", loginmsg }, /* login message */ + { "er", &omode.c_cc[VERASE] }, /* erase character */ + { "kl", &omode.c_cc[VKILL] }, /* kill character */ + { "et", &omode.c_cc[VEOF] }, /* eof chatacter (eot) */ + { "pc", nullstr }, /* pad character */ + { "tt" }, /* terminal type */ + { "ev" }, /* environment */ + { "lo", loginprg }, /* login program */ + { "hn", hostname }, /* host name */ + { "he" }, /* host name edit */ + { "in", &omode.c_cc[VINTR] }, /* interrupt char */ + { "qu", &omode.c_cc[VQUIT] }, /* quit char */ + { "xn", &omode.c_cc[VSTART] }, /* XON (start) char */ + { "xf", &omode.c_cc[VSTOP] }, /* XOFF (stop) char */ + { "bk", &omode.c_cc[VEOL] }, /* brk char (alt \n) */ + { "su", &omode.c_cc[VSUSP] }, /* suspend char */ + { "ds", &omode.c_cc[VDSUSP] }, /* delayed suspend */ + { "rp", &omode.c_cc[VREPRINT] },/* reprint char */ + { "fl", &omode.c_cc[VDISCARD] },/* flush output */ + { "we", &omode.c_cc[VWERASE] }, /* word erase */ + { "ln", &omode.c_cc[VLNEXT] }, /* literal next */ + { "Lo" }, /* locale for strftime() */ + { "pp" }, /* ppp login program */ + { "if" }, /* sysv-like 'issue' filename */ + { "ic" }, /* modem init-chat */ + { "ac" }, /* modem answer-chat */ + { "al" }, /* user to auto-login */ + { "df", datefmt}, /* format for strftime() */ + { 0 } +}; + +struct gettynums gettynums[] = { + { "is" }, /* input speed */ + { "os" }, /* output speed */ + { "sp" }, /* both speeds */ + { "nd" }, /* newline delay */ + { "cd" }, /* carriage-return delay */ + { "td" }, /* tab delay */ + { "fd" }, /* form-feed delay */ + { "bd" }, /* backspace delay */ + { "to" }, /* timeout */ + { "f0" }, /* output flags */ + { "f1" }, /* input flags */ + { "f2" }, /* user mode flags */ + { "pf" }, /* delay before flush at 1st prompt */ + { "c0" }, /* output c_flags */ + { "c1" }, /* input c_flags */ + { "c2" }, /* user mode c_flags */ + { "i0" }, /* output i_flags */ + { "i1" }, /* input i_flags */ + { "i2" }, /* user mode i_flags */ + { "l0" }, /* output l_flags */ + { "l1" }, /* input l_flags */ + { "l2" }, /* user mode l_flags */ + { "o0" }, /* output o_flags */ + { "o1" }, /* input o_flags */ + { "o2" }, /* user mode o_flags */ + { "de" }, /* delay before sending 1st prompt */ + { "rt" }, /* reset timeout */ + { "ct" }, /* chat script timeout */ + { "dc" }, /* debug chat script value */ + { 0 } +}; + + +struct gettyflags gettyflags[] = { + { "ht", 0 }, /* has tabs */ + { "nl", 1 }, /* has newline char */ + { "ep", 0 }, /* even parity */ + { "op", 0 }, /* odd parity */ + { "ap", 0 }, /* any parity */ + { "ec", 1 }, /* no echo */ + { "co", 0 }, /* console special */ + { "cb", 0 }, /* crt backspace */ + { "ck", 0 }, /* crt kill */ + { "ce", 0 }, /* crt erase */ + { "pe", 0 }, /* printer erase */ + { "rw", 1 }, /* don't use raw */ + { "xc", 1 }, /* don't ^X ctl chars */ + { "lc", 0 }, /* terminal las lower case */ + { "uc", 0 }, /* terminal has no lower case */ + { "ig", 0 }, /* ignore garbage */ + { "ps", 0 }, /* do port selector speed select */ + { "hc", 1 }, /* don't set hangup on close */ + { "ub", 0 }, /* unbuffered output */ + { "ab", 0 }, /* auto-baud detect with '\r' */ + { "dx", 0 }, /* set decctlq */ + { "np", 0 }, /* no parity at all (8bit chars) */ + { "mb", 0 }, /* do MDMBUF flow control */ + { "hw", 0 }, /* do CTSRTS flow control */ + { "nc", 0 }, /* set clocal (no carrier) */ + { "pl", 0 }, /* use PPP instead of login(1) */ + { 0 } +}; diff --git a/libexec/getty/main.c b/libexec/getty/main.c new file mode 100644 index 0000000..94ecb07 --- /dev/null +++ b/libexec/getty/main.c @@ -0,0 +1,814 @@ +/*- + * Copyright (c) 1980, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1980, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)from: main.c 8.1 (Berkeley) 6/20/93"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/ttydefaults.h> +#include <sys/utsname.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <libutil.h> +#include <setjmp.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#include "gettytab.h" +#include "extern.h" +#include "pathnames.h" + +/* + * Set the amount of running time that getty should accumulate + * before deciding that something is wrong and exit. + */ +#define GETTY_TIMEOUT 60 /* seconds */ + +#undef CTRL +#define CTRL(x) (x&037) + +/* defines for auto detection of incoming PPP calls (->PAP/CHAP) */ + +#define PPP_FRAME 0x7e /* PPP Framing character */ +#define PPP_STATION 0xff /* "All Station" character */ +#define PPP_ESCAPE 0x7d /* Escape Character */ +#define PPP_CONTROL 0x03 /* PPP Control Field */ +#define PPP_CONTROL_ESCAPED 0x23 /* PPP Control Field, escaped */ +#define PPP_LCP_HI 0xc0 /* LCP protocol - high byte */ +#define PPP_LCP_LOW 0x21 /* LCP protocol - low byte */ + +/* original mode; flags've been reset using values from <sys/ttydefaults.h> */ +struct termios omode; +/* current mode */ +struct termios tmode; + +int crmod, digit, lower, upper; + +char hostname[MAXHOSTNAMELEN]; +char name[MAXLOGNAME*3]; +char dev[] = _PATH_DEV; +char ttyn[32]; + +#define OBUFSIZ 128 +#define TABBUFSIZ 512 + +char defent[TABBUFSIZ]; +char tabent[TABBUFSIZ]; +const char *tname; + +char *env[128]; + +char partab[] = { + 0001,0201,0201,0001,0201,0001,0001,0201, + 0202,0004,0003,0205,0005,0206,0201,0001, + 0201,0001,0001,0201,0001,0201,0201,0001, + 0001,0201,0201,0001,0201,0001,0001,0201, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0000,0200,0200,0000,0200,0000,0000,0200, + 0000,0200,0200,0000,0200,0000,0000,0200, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0000,0200,0200,0000,0200,0000,0000,0200, + 0000,0200,0200,0000,0200,0000,0000,0200, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0000,0200,0200,0000,0200,0000,0000,0200, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0200,0000,0000,0200,0000,0200,0200,0000, + 0000,0200,0200,0000,0200,0000,0000,0201 +}; + +#define ERASE tmode.c_cc[VERASE] +#define KILL tmode.c_cc[VKILL] +#define EOT tmode.c_cc[VEOF] + +#define puts Gputs + +static void defttymode(void); +static void dingdong(int); +static void dogettytab(void); +static int getname(void); +static void interrupt(int); +static void oflush(void); +static void prompt(void); +static void putchr(int); +static void putf(const char *); +static void putpad(const char *); +static void puts(const char *); +static void timeoverrun(int); +static char *getline(int); +static void setttymode(int); +static int opentty(const char *, int); + +jmp_buf timeout; + +static void +dingdong(int signo __unused) +{ + alarm(0); + longjmp(timeout, 1); +} + +jmp_buf intrupt; + +static void +interrupt(int signo __unused) +{ + longjmp(intrupt, 1); +} + +/* + * Action to take when getty is running too long. + */ +static void +timeoverrun(int signo __unused) +{ + + syslog(LOG_ERR, "getty exiting due to excessive running time"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + extern char **environ; + int first_sleep = 1, first_time = 1; + struct rlimit limit; + int rval; + + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + openlog("getty", LOG_CONS|LOG_PID, LOG_AUTH); + gethostname(hostname, sizeof(hostname) - 1); + hostname[sizeof(hostname) - 1] = '\0'; + if (hostname[0] == '\0') + strcpy(hostname, "Amnesiac"); + + /* + * Limit running time to deal with broken or dead lines. + */ + (void)signal(SIGXCPU, timeoverrun); + limit.rlim_max = RLIM_INFINITY; + limit.rlim_cur = GETTY_TIMEOUT; + (void)setrlimit(RLIMIT_CPU, &limit); + + gettable("default", defent); + gendefaults(); + tname = "default"; + if (argc > 1) + tname = argv[1]; + + /* + * The following is a work around for vhangup interactions + * which cause great problems getting window systems started. + * If the tty line is "-", we do the old style getty presuming + * that the file descriptors are already set up for us. + * J. Gettys - MIT Project Athena. + */ + if (argc <= 2 || strcmp(argv[2], "-") == 0) + strcpy(ttyn, ttyname(STDIN_FILENO)); + else { + strcpy(ttyn, dev); + strncat(ttyn, argv[2], sizeof(ttyn)-sizeof(dev)); + if (strcmp(argv[0], "+") != 0) { + chown(ttyn, 0, 0); + chmod(ttyn, 0600); + revoke(ttyn); + + /* + * Do the first scan through gettytab. + * Terminal mode parameters will be wrong until + * defttymode() called, but they're irrelevant for + * the initial setup of the terminal device. + */ + dogettytab(); + + /* + * Init or answer modem sequence has been specified. + */ + if (IC || AC) { + if (!opentty(ttyn, O_RDWR|O_NONBLOCK)) + exit(1); + defttymode(); + setttymode(1); + } + + if (IC) { + if (getty_chat(IC, CT, DC) > 0) { + syslog(LOG_ERR, "modem init problem on %s", ttyn); + (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); + exit(1); + } + } + + if (AC) { + int i, rfds; + struct timeval to; + + rfds = 1 << 0; /* FD_SET */ + to.tv_sec = RT; + to.tv_usec = 0; + i = select(32, (fd_set*)&rfds, (fd_set*)NULL, + (fd_set*)NULL, RT ? &to : NULL); + if (i < 0) { + syslog(LOG_ERR, "select %s: %m", ttyn); + } else if (i == 0) { + syslog(LOG_NOTICE, "recycle tty %s", ttyn); + (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); + exit(0); /* recycle for init */ + } + i = getty_chat(AC, CT, DC); + if (i > 0) { + syslog(LOG_ERR, "modem answer problem on %s", ttyn); + (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); + exit(1); + } + } else { /* maybe blocking open */ + if (!opentty(ttyn, O_RDWR | (NC ? O_NONBLOCK : 0 ))) + exit(1); + } + } + } + + defttymode(); + for (;;) { + + /* + * if a delay was specified then sleep for that + * number of seconds before writing the initial prompt + */ + if (first_sleep && DE) { + sleep(DE); + /* remove any noise */ + (void)tcflush(STDIN_FILENO, TCIOFLUSH); + } + first_sleep = 0; + + setttymode(0); + if (AB) { + tname = autobaud(); + dogettytab(); + continue; + } + if (PS) { + tname = portselector(); + dogettytab(); + continue; + } + if (CL && *CL) + putpad(CL); + edithost(HE); + + /* if this is the first time through this, and an + issue file has been given, then send it */ + if (first_time && IF) { + int fd; + + if ((fd = open(IF, O_RDONLY)) != -1) { + char * cp; + + while ((cp = getline(fd)) != NULL) { + putf(cp); + } + close(fd); + } + } + first_time = 0; + + if (IM && *IM && !(PL && PP)) + putf(IM); + if (setjmp(timeout)) { + cfsetispeed(&tmode, B0); + cfsetospeed(&tmode, B0); + (void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode); + exit(1); + } + if (TO) { + signal(SIGALRM, dingdong); + alarm(TO); + } + + rval = 0; + if (AL) { + const char *p = AL; + char *q = name; + + while (*p && q < &name[sizeof name - 1]) { + if (isupper(*p)) + upper = 1; + else if (islower(*p)) + lower = 1; + else if (isdigit(*p)) + digit = 1; + *q++ = *p++; + } + } else if (!(PL && PP)) + rval = getname(); + if (rval == 2 || (PL && PP)) { + oflush(); + alarm(0); + limit.rlim_max = RLIM_INFINITY; + limit.rlim_cur = RLIM_INFINITY; + (void)setrlimit(RLIMIT_CPU, &limit); + execle(PP, "ppplogin", ttyn, (char *) 0, env); + syslog(LOG_ERR, "%s: %m", PP); + exit(1); + } else if (rval || AL) { + int i; + + oflush(); + alarm(0); + signal(SIGALRM, SIG_DFL); + if (name[0] == '\0') + continue; + if (name[0] == '-') { + puts("user names may not start with '-'."); + continue; + } + if (!(upper || lower || digit)) { + if (AL) { + syslog(LOG_ERR, + "invalid auto-login name: %s", AL); + exit(1); + } else + continue; + } + set_flags(2); + if (crmod) { + tmode.c_iflag |= ICRNL; + tmode.c_oflag |= ONLCR; + } +#if REALLY_OLD_TTYS + if (upper || UC) + tmode.sg_flags |= LCASE; + if (lower || LC) + tmode.sg_flags &= ~LCASE; +#endif + if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { + syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); + exit(1); + } + signal(SIGINT, SIG_DFL); + for (i = 0; environ[i] != (char *)0; i++) + env[i] = environ[i]; + makeenv(&env[i]); + + limit.rlim_max = RLIM_INFINITY; + limit.rlim_cur = RLIM_INFINITY; + (void)setrlimit(RLIMIT_CPU, &limit); + execle(LO, "login", AL ? "-fp" : "-p", name, + (char *) 0, env); + syslog(LOG_ERR, "%s: %m", LO); + exit(1); + } + alarm(0); + signal(SIGALRM, SIG_DFL); + signal(SIGINT, SIG_IGN); + if (NX && *NX) { + tname = NX; + dogettytab(); + } + } +} + +static int +opentty(const char *tty, int flags) +{ + int i; + int failopenlogged = 0; + + while ((i = open(tty, flags)) == -1) + { + if (!failopenlogged) { + syslog(LOG_ERR, "open %s: %m", tty); + failopenlogged = 1; + } + sleep(60); + } + if (login_tty(i) < 0) { + if (daemon(0,0) < 0) { + syslog(LOG_ERR,"daemon: %m"); + close(i); + return 0; + } + if (login_tty(i) < 0) { + syslog(LOG_ERR, "login_tty %s: %m", tty); + close(i); + return 0; + } + } + return 1; +} + +static void +defttymode(void) +{ + struct termios def; + + /* Start with default tty settings. */ + if (tcgetattr(STDIN_FILENO, &tmode) < 0) { + syslog(LOG_ERR, "tcgetattr %s: %m", ttyn); + exit(1); + } + omode = tmode; /* fill c_cc for dogettytab() */ + dogettytab(); + /* + * Don't rely on the driver too much, and initialize crucial + * things according to <sys/ttydefaults.h>. Avoid clobbering + * the c_cc[] settings however, the console drivers might wish + * to leave their idea of the preferred VERASE key value + * there. + */ + cfmakesane(&def); + tmode.c_iflag = def.c_iflag; + tmode.c_oflag = def.c_oflag; + tmode.c_lflag = def.c_lflag; + tmode.c_cflag = def.c_cflag; + if (NC) + tmode.c_cflag |= CLOCAL; + omode = tmode; +} + +static void +setttymode(int raw) +{ + int off = 0; + + (void)tcflush(STDIN_FILENO, TCIOFLUSH); /* clear out the crap */ + ioctl(STDIN_FILENO, FIONBIO, &off); /* turn off non-blocking mode */ + ioctl(STDIN_FILENO, FIOASYNC, &off); /* ditto for async mode */ + + if (IS) + cfsetispeed(&tmode, speed(IS)); + else if (SP) + cfsetispeed(&tmode, speed(SP)); + if (OS) + cfsetospeed(&tmode, speed(OS)); + else if (SP) + cfsetospeed(&tmode, speed(SP)); + set_flags(0); + setchars(); + if (raw) + cfmakeraw(&tmode); + if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { + syslog(LOG_ERR, "tcsetattr %s: %m", ttyn); + exit(1); + } +} + + +static int +getname(void) +{ + int c; + char *np; + unsigned char cs; + int ppp_state = 0; + int ppp_connection = 0; + + /* + * Interrupt may happen if we use CBREAK mode + */ + if (setjmp(intrupt)) { + signal(SIGINT, SIG_IGN); + return (0); + } + signal(SIGINT, interrupt); + set_flags(1); + prompt(); + oflush(); + if (PF > 0) { + sleep(PF); + PF = 0; + } + if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) { + syslog(LOG_ERR, "%s: %m", ttyn); + exit(1); + } + crmod = digit = lower = upper = 0; + np = name; + for (;;) { + oflush(); + if (read(STDIN_FILENO, &cs, 1) <= 0) + exit(0); + if ((c = cs&0177) == 0) + return (0); + + /* PPP detection state machine.. + Look for sequences: + PPP_FRAME, PPP_STATION, PPP_ESCAPE, PPP_CONTROL_ESCAPED or + PPP_FRAME, PPP_STATION, PPP_CONTROL (deviant from RFC) + See RFC1662. + Derived from code from Michael Hancock, <michaelh@cet.co.jp> + and Erik 'PPP' Olson, <eriko@wrq.com> + */ + + if (PP && (cs == PPP_FRAME)) { + ppp_state = 1; + } else if (ppp_state == 1 && cs == PPP_STATION) { + ppp_state = 2; + } else if (ppp_state == 2 && cs == PPP_ESCAPE) { + ppp_state = 3; + } else if ((ppp_state == 2 && cs == PPP_CONTROL) + || (ppp_state == 3 && cs == PPP_CONTROL_ESCAPED)) { + ppp_state = 4; + } else if (ppp_state == 4 && cs == PPP_LCP_HI) { + ppp_state = 5; + } else if (ppp_state == 5 && cs == PPP_LCP_LOW) { + ppp_connection = 1; + break; + } else { + ppp_state = 0; + } + + if (c == EOT || c == CTRL('d')) + exit(0); + if (c == '\r' || c == '\n' || np >= &name[sizeof name-1]) { + putf("\r\n"); + break; + } + if (islower(c)) + lower = 1; + else if (isupper(c)) + upper = 1; + else if (c == ERASE || c == '\b' || c == 0177) { + if (np > name) { + np--; + if (cfgetospeed(&tmode) >= 1200) + puts("\b \b"); + else + putchr(cs); + } + continue; + } else if (c == KILL || c == CTRL('u')) { + putchr('\r'); + if (cfgetospeed(&tmode) < 1200) + putchr('\n'); + /* this is the way they do it down under ... */ + else if (np > name) + puts(" \r"); + prompt(); + digit = lower = upper = 0; + np = name; + continue; + } else if (isdigit(c)) + digit = 1; + if (IG && (c <= ' ' || c > 0176)) + continue; + *np++ = c; + putchr(cs); + } + signal(SIGINT, SIG_IGN); + *np = 0; + if (c == '\r') + crmod = 1; + if ((upper && !lower && !LC) || UC) + for (np = name; *np; np++) + if (isupper(*np)) + *np = tolower(*np); + return (1 + ppp_connection); +} + +static void +putpad(const char *s) +{ + int pad = 0; + speed_t ospeed = cfgetospeed(&tmode); + + if (isdigit(*s)) { + while (isdigit(*s)) { + pad *= 10; + pad += *s++ - '0'; + } + pad *= 10; + if (*s == '.' && isdigit(s[1])) { + pad += s[1] - '0'; + s += 2; + } + } + + puts(s); + /* + * If no delay needed, or output speed is + * not comprehensible, then don't try to delay. + */ + if (pad == 0 || ospeed <= 0) + return; + + /* + * Round up by a half a character frame, and then do the delay. + * Too bad there are no user program accessible programmed delays. + * Transmitting pad characters slows many terminals down and also + * loads the system. + */ + pad = (pad * ospeed + 50000) / 100000; + while (pad--) + putchr(*PC); +} + +static void +puts(const char *s) +{ + while (*s) + putchr(*s++); +} + +char outbuf[OBUFSIZ]; +int obufcnt = 0; + +static void +putchr(int cc) +{ + char c; + + c = cc; + if (!NP) { + c |= partab[c&0177] & 0200; + if (OP) + c ^= 0200; + } + if (!UB) { + outbuf[obufcnt++] = c; + if (obufcnt >= OBUFSIZ) + oflush(); + } else + write(STDOUT_FILENO, &c, 1); +} + +static void +oflush(void) +{ + if (obufcnt) + write(STDOUT_FILENO, outbuf, obufcnt); + obufcnt = 0; +} + +static void +prompt(void) +{ + + putf(LM); + if (CO) + putchr('\n'); +} + + +static char * +getline(int fd) +{ + int i = 0; + static char linebuf[512]; + + /* + * This is certainly slow, but it avoids having to include + * stdio.h unnecessarily. Issue files should be small anyway. + */ + while (i < (sizeof linebuf - 3) && read(fd, linebuf+i, 1)==1) { + if (linebuf[i] == '\n') { + /* Don't rely on newline mode, assume raw */ + linebuf[i++] = '\r'; + linebuf[i++] = '\n'; + linebuf[i] = '\0'; + return linebuf; + } + ++i; + } + linebuf[i] = '\0'; + return i ? linebuf : 0; +} + +static void +putf(const char *cp) +{ + extern char editedhost[]; + time_t t; + char *slash, db[100]; + + static struct utsname kerninfo; + + if (!*kerninfo.sysname) + uname(&kerninfo); + + while (*cp) { + if (*cp != '%') { + putchr(*cp++); + continue; + } + switch (*++cp) { + + case 't': + slash = strrchr(ttyn, '/'); + if (slash == (char *) 0) + puts(ttyn); + else + puts(&slash[1]); + break; + + case 'h': + puts(editedhost); + break; + + case 'd': { + t = (time_t)0; + (void)time(&t); + if (Lo) + (void)setlocale(LC_TIME, Lo); + (void)strftime(db, sizeof(db), DF, localtime(&t)); + puts(db); + break; + + case 's': + puts(kerninfo.sysname); + break; + + case 'm': + puts(kerninfo.machine); + break; + + case 'r': + puts(kerninfo.release); + break; + + case 'v': + puts(kerninfo.version); + break; + } + + case '%': + putchr('%'); + break; + } + cp++; + } +} + +/* + * Read a gettytab database entry and perform necessary quirks. + */ +static void +dogettytab(void) +{ + + /* Read the database entry. */ + gettable(tname, tabent); + + /* + * Avoid inheriting the parity values from the default entry + * if any of them is set in the current entry. + * Mixing different parity settings is unreasonable. + */ + if (OPset || EPset || APset || NPset) + OPset = EPset = APset = NPset = 1; + + /* Fill in default values for unset capabilities. */ + setdefaults(); +} diff --git a/libexec/getty/pathnames.h b/libexec/getty/pathnames.h new file mode 100644 index 0000000..df59a2c --- /dev/null +++ b/libexec/getty/pathnames.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * $FreeBSD$ + */ + +#include <paths.h> + +#define _PATH_GETTYTAB "/etc/gettytab" +#define _PATH_LOGIN "/usr/bin/login" diff --git a/libexec/getty/subr.c b/libexec/getty/subr.c new file mode 100644 index 0000000..26d2173 --- /dev/null +++ b/libexec/getty/subr.c @@ -0,0 +1,693 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)from: subr.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * Melbourne getty. + */ +#ifdef DEBUG +#include <stdio.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/time.h> +#include <syslog.h> + +#include "gettytab.h" +#include "pathnames.h" +#include "extern.h" + + + +/* + * Get a table entry. + */ +void +gettable(const char *name, char *buf) +{ + struct gettystrs *sp; + struct gettynums *np; + struct gettyflags *fp; + long n; + int l; + char *p; + char *msg = NULL; + const char *dba[2]; + + static int firsttime = 1; + + dba[0] = _PATH_GETTYTAB; + dba[1] = 0; + + if (firsttime) { + /* + * we need to strdup() anything in the strings array + * initially in order to simplify things later + */ + for (sp = gettystrs; sp->field; sp++) + if (sp->value != NULL) { + /* handle these ones more carefully */ + if (sp >= &gettystrs[4] && sp <= &gettystrs[6]) + l = 2; + else + l = strlen(sp->value) + 1; + if ((p = malloc(l)) != NULL) { + strncpy(p, sp->value, l); + p[l-1] = '\0'; + } + /* + * replace, even if NULL, else we'll + * have problems with free()ing static mem + */ + sp->value = p; + } + firsttime = 0; + } + + switch (cgetent(&buf, (char **)dba, (char *)name)) { + case 1: + msg = "%s: couldn't resolve 'tc=' in gettytab '%s'"; + case 0: + break; + case -1: + msg = "%s: unknown gettytab entry '%s'"; + break; + case -2: + msg = "%s: retrieving gettytab entry '%s': %m"; + break; + case -3: + msg = "%s: recursive 'tc=' reference gettytab entry '%s'"; + break; + default: + msg = "%s: unexpected cgetent() error for entry '%s'"; + break; + } + + if (msg != NULL) { + syslog(LOG_ERR, msg, "getty", name); + return; + } + + for (sp = gettystrs; sp->field; sp++) { + if ((l = cgetstr(buf, (char*)sp->field, &p)) >= 0) { + if (sp->value) { + /* prefer existing value */ + if (strcmp(p, sp->value) != 0) + free(sp->value); + else { + free(p); + p = sp->value; + } + } + sp->value = p; + } else if (l == -1) { + free(sp->value); + sp->value = NULL; + } + } + + for (np = gettynums; np->field; np++) { + if (cgetnum(buf, (char*)np->field, &n) == -1) + np->set = 0; + else { + np->set = 1; + np->value = n; + } + } + + for (fp = gettyflags; fp->field; fp++) { + if (cgetcap(buf, (char *)fp->field, ':') == NULL) + fp->set = 0; + else { + fp->set = 1; + fp->value = 1 ^ fp->invrt; + } + } + +#ifdef DEBUG + printf("name=\"%s\", buf=\"%s\"\r\n", name, buf); + for (sp = gettystrs; sp->field; sp++) + printf("cgetstr: %s=%s\r\n", sp->field, sp->value); + for (np = gettynums; np->field; np++) + printf("cgetnum: %s=%d\r\n", np->field, np->value); + for (fp = gettyflags; fp->field; fp++) + printf("cgetflags: %s='%c' set='%c'\r\n", fp->field, + fp->value + '0', fp->set + '0'); +#endif /* DEBUG */ +} + +void +gendefaults(void) +{ + struct gettystrs *sp; + struct gettynums *np; + struct gettyflags *fp; + + for (sp = gettystrs; sp->field; sp++) + if (sp->value) + sp->defalt = strdup(sp->value); + for (np = gettynums; np->field; np++) + if (np->set) + np->defalt = np->value; + for (fp = gettyflags; fp->field; fp++) + if (fp->set) + fp->defalt = fp->value; + else + fp->defalt = fp->invrt; +} + +void +setdefaults(void) +{ + struct gettystrs *sp; + struct gettynums *np; + struct gettyflags *fp; + + for (sp = gettystrs; sp->field; sp++) + if (!sp->value) + sp->value = !sp->defalt ? sp->defalt + : strdup(sp->defalt); + for (np = gettynums; np->field; np++) + if (!np->set) + np->value = np->defalt; + for (fp = gettyflags; fp->field; fp++) + if (!fp->set) + fp->value = fp->defalt; +} + +static char ** +charnames[] = { + &ER, &KL, &IN, &QU, &XN, &XF, &ET, &BK, + &SU, &DS, &RP, &FL, &WE, &LN, 0 +}; + +static char * +charvars[] = { + &tmode.c_cc[VERASE], &tmode.c_cc[VKILL], &tmode.c_cc[VINTR], + &tmode.c_cc[VQUIT], &tmode.c_cc[VSTART], &tmode.c_cc[VSTOP], + &tmode.c_cc[VEOF], &tmode.c_cc[VEOL], &tmode.c_cc[VSUSP], + &tmode.c_cc[VDSUSP], &tmode.c_cc[VREPRINT], &tmode.c_cc[VDISCARD], + &tmode.c_cc[VWERASE], &tmode.c_cc[VLNEXT], 0 +}; + +void +setchars(void) +{ + int i; + const char *p; + + for (i = 0; charnames[i]; i++) { + p = *charnames[i]; + if (p && *p) + *charvars[i] = *p; + else + *charvars[i] = _POSIX_VDISABLE; + } +} + +/* Macros to clear/set/test flags. */ +#define SET(t, f) (t) |= (f) +#define CLR(t, f) (t) &= ~(f) +#define ISSET(t, f) ((t) & (f)) + +void +set_flags(int n) +{ + tcflag_t iflag, oflag, cflag, lflag; + + + switch (n) { + case 0: + if (C0set && I0set && L0set && O0set) { + tmode.c_cflag = C0; + tmode.c_iflag = I0; + tmode.c_lflag = L0; + tmode.c_oflag = O0; + return; + } + break; + case 1: + if (C1set && I1set && L1set && O1set) { + tmode.c_cflag = C1; + tmode.c_iflag = I1; + tmode.c_lflag = L1; + tmode.c_oflag = O1; + return; + } + break; + default: + if (C2set && I2set && L2set && O2set) { + tmode.c_cflag = C2; + tmode.c_iflag = I2; + tmode.c_lflag = L2; + tmode.c_oflag = O2; + return; + } + break; + } + + iflag = omode.c_iflag; + oflag = omode.c_oflag; + cflag = omode.c_cflag; + lflag = omode.c_lflag; + + if (NP) { + CLR(cflag, CSIZE|PARENB); + SET(cflag, CS8); + CLR(iflag, ISTRIP|INPCK|IGNPAR); + } else if (AP || EP || OP) { + CLR(cflag, CSIZE); + SET(cflag, CS7|PARENB); + SET(iflag, ISTRIP); + if (OP && !EP) { + SET(iflag, INPCK|IGNPAR); + SET(cflag, PARODD); + if (AP) + CLR(iflag, INPCK); + } else if (EP && !OP) { + SET(iflag, INPCK|IGNPAR); + CLR(cflag, PARODD); + if (AP) + CLR(iflag, INPCK); + } else if (AP || (EP && OP)) { + CLR(iflag, INPCK|IGNPAR); + CLR(cflag, PARODD); + } + } /* else, leave as is */ + +#if 0 + if (UC) + f |= LCASE; +#endif + + if (HC) + SET(cflag, HUPCL); + else + CLR(cflag, HUPCL); + + if (MB) + SET(cflag, MDMBUF); + else + CLR(cflag, MDMBUF); + + if (HW) + SET(cflag, CRTSCTS); + else + CLR(cflag, CRTSCTS); + + if (NL) { + SET(iflag, ICRNL); + SET(oflag, ONLCR|OPOST); + } else { + CLR(iflag, ICRNL); + CLR(oflag, ONLCR); + } + + if (!HT) + SET(oflag, OXTABS|OPOST); + else + CLR(oflag, OXTABS); + +#ifdef XXX_DELAY + SET(f, delaybits()); +#endif + + if (n == 1) { /* read mode flags */ + if (RW) { + iflag = 0; + CLR(oflag, OPOST); + CLR(cflag, CSIZE|PARENB); + SET(cflag, CS8); + lflag = 0; + } else { + CLR(lflag, ICANON); + } + goto out; + } + + if (n == 0) + goto out; + +#if 0 + if (CB) + SET(f, CRTBS); +#endif + + if (CE) + SET(lflag, ECHOE); + else + CLR(lflag, ECHOE); + + if (CK) + SET(lflag, ECHOKE); + else + CLR(lflag, ECHOKE); + + if (PE) + SET(lflag, ECHOPRT); + else + CLR(lflag, ECHOPRT); + + if (EC) + SET(lflag, ECHO); + else + CLR(lflag, ECHO); + + if (XC) + SET(lflag, ECHOCTL); + else + CLR(lflag, ECHOCTL); + + if (DX) + SET(lflag, IXANY); + else + CLR(lflag, IXANY); + +out: + tmode.c_iflag = iflag; + tmode.c_oflag = oflag; + tmode.c_cflag = cflag; + tmode.c_lflag = lflag; +} + + +#ifdef XXX_DELAY +struct delayval { + unsigned delay; /* delay in ms */ + int bits; +}; + +/* + * below are random guesses, I can't be bothered checking + */ + +struct delayval crdelay[] = { + { 1, CR1 }, + { 2, CR2 }, + { 3, CR3 }, + { 83, CR1 }, + { 166, CR2 }, + { 0, CR3 }, +}; + +struct delayval nldelay[] = { + { 1, NL1 }, /* special, calculated */ + { 2, NL2 }, + { 3, NL3 }, + { 100, NL2 }, + { 0, NL3 }, +}; + +struct delayval bsdelay[] = { + { 1, BS1 }, + { 0, 0 }, +}; + +struct delayval ffdelay[] = { + { 1, FF1 }, + { 1750, FF1 }, + { 0, FF1 }, +}; + +struct delayval tbdelay[] = { + { 1, TAB1 }, + { 2, TAB2 }, + { 3, XTABS }, /* this is expand tabs */ + { 100, TAB1 }, + { 0, TAB2 }, +}; + +int +delaybits(void) +{ + int f; + + f = adelay(CD, crdelay); + f |= adelay(ND, nldelay); + f |= adelay(FD, ffdelay); + f |= adelay(TD, tbdelay); + f |= adelay(BD, bsdelay); + return (f); +} + +int +adelay(int ms, struct delayval *dp) +{ + if (ms == 0) + return (0); + while (dp->delay && ms > dp->delay) + dp++; + return (dp->bits); +} +#endif + +char editedhost[MAXHOSTNAMELEN]; + +void +edithost(const char *pat) +{ + const char *host = HN; + char *res = editedhost; + + if (!pat) + pat = ""; + while (*pat) { + switch (*pat) { + + case '#': + if (*host) + host++; + break; + + case '@': + if (*host) + *res++ = *host++; + break; + + default: + *res++ = *pat; + break; + + } + if (res == &editedhost[sizeof editedhost - 1]) { + *res = '\0'; + return; + } + pat++; + } + if (*host) + strncpy(res, host, sizeof editedhost - (res - editedhost) - 1); + else + *res = '\0'; + editedhost[sizeof editedhost - 1] = '\0'; +} + +static struct speedtab { + int speed; + int uxname; +} speedtab[] = { + { 50, B50 }, + { 75, B75 }, + { 110, B110 }, + { 134, B134 }, + { 150, B150 }, + { 200, B200 }, + { 300, B300 }, + { 600, B600 }, + { 1200, B1200 }, + { 1800, B1800 }, + { 2400, B2400 }, + { 4800, B4800 }, + { 9600, B9600 }, + { 19200, EXTA }, + { 19, EXTA }, /* for people who say 19.2K */ + { 38400, EXTB }, + { 38, EXTB }, + { 7200, EXTB }, /* alternative */ + { 57600, B57600 }, + { 115200, B115200 }, + { 230400, B230400 }, + { 0 } +}; + +int +speed(int val) +{ + struct speedtab *sp; + + if (val <= B230400) + return (val); + + for (sp = speedtab; sp->speed; sp++) + if (sp->speed == val) + return (sp->uxname); + + return (B300); /* default in impossible cases */ +} + +void +makeenv(char *env[]) +{ + static char termbuf[128] = "TERM="; + char *p, *q; + char **ep; + + ep = env; + if (TT && *TT) { + strlcat(termbuf, TT, sizeof(termbuf)); + *ep++ = termbuf; + } + if ((p = EV)) { + q = p; + while ((q = strchr(q, ','))) { + *q++ = '\0'; + *ep++ = p; + p = q; + } + if (*p) + *ep++ = p; + } + *ep = (char *)0; +} + +/* + * This speed select mechanism is written for the Develcon DATASWITCH. + * The Develcon sends a string of the form "B{speed}\n" at a predefined + * baud rate. This string indicates the user's actual speed. + * The routine below returns the terminal type mapped from derived speed. + */ +struct portselect { + const char *ps_baud; + const char *ps_type; +} portspeeds[] = { + { "B110", "std.110" }, + { "B134", "std.134" }, + { "B150", "std.150" }, + { "B300", "std.300" }, + { "B600", "std.600" }, + { "B1200", "std.1200" }, + { "B2400", "std.2400" }, + { "B4800", "std.4800" }, + { "B9600", "std.9600" }, + { "B19200", "std.19200" }, + { 0 } +}; + +const char * +portselector(void) +{ + char c, baud[20]; + const char *type = "default"; + struct portselect *ps; + int len; + + alarm(5*60); + for (len = 0; len < sizeof (baud) - 1; len++) { + if (read(STDIN_FILENO, &c, 1) <= 0) + break; + c &= 0177; + if (c == '\n' || c == '\r') + break; + if (c == 'B') + len = 0; /* in case of leading garbage */ + baud[len] = c; + } + baud[len] = '\0'; + for (ps = portspeeds; ps->ps_baud; ps++) + if (strcmp(ps->ps_baud, baud) == 0) { + type = ps->ps_type; + break; + } + sleep(2); /* wait for connection to complete */ + return (type); +} + +/* + * This auto-baud speed select mechanism is written for the Micom 600 + * portselector. Selection is done by looking at how the character '\r' + * is garbled at the different speeds. + */ +const char * +autobaud(void) +{ + int rfds; + struct timeval timeout; + char c; + const char *type = "9600-baud"; + + (void)tcflush(0, TCIOFLUSH); + rfds = 1 << 0; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + if (select(32, (fd_set *)&rfds, (fd_set *)NULL, + (fd_set *)NULL, &timeout) <= 0) + return (type); + if (read(STDIN_FILENO, &c, sizeof(char)) != sizeof(char)) + return (type); + timeout.tv_sec = 0; + timeout.tv_usec = 20; + (void) select(32, (fd_set *)NULL, (fd_set *)NULL, + (fd_set *)NULL, &timeout); + (void)tcflush(0, TCIOFLUSH); + switch (c & 0377) { + + case 0200: /* 300-baud */ + type = "300-baud"; + break; + + case 0346: /* 1200-baud */ + type = "1200-baud"; + break; + + case 015: /* 2400-baud */ + case 0215: + type = "2400-baud"; + break; + + default: /* 4800-baud */ + type = "4800-baud"; + break; + + case 0377: /* 9600-baud */ + type = "9600-baud"; + break; + } + return (type); +} diff --git a/libexec/getty/ttys.5 b/libexec/getty/ttys.5 new file mode 100644 index 0000000..d4c7f1d --- /dev/null +++ b/libexec/getty/ttys.5 @@ -0,0 +1,170 @@ +.\" Copyright (c) 1985, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)ttys.5 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" " +.Dd March 9, 2014 +.Dt TTYS 5 +.Os +.Sh NAME +.Nm ttys +.Nd terminal initialization information +.Sh DESCRIPTION +The file +.Nm +contains information that is used by various routines to initialize +and control the use of terminal special files. +Pseudo-terminals (see +.Xr pts 4 ) +are not listed. +This information is read with the +.Xr getttyent 3 +library routines. +There is one line in the +.Nm +file per special device file. +Fields are separated by tabs and/or spaces. +Fields comprised of more than one word should be enclosed in double +quotes (``"''). +Blank lines and comments may appear anywhere in the file; comments +are delimited by hash marks (``#'') and new lines. +Any unspecified fields will default to null. +.Pp +The first field is normally the +name of the terminal special file as it is found in +.Pa /dev . +However, it can be any arbitrary string +when the associated command is not related to a tty. +.Pp +The second field of the file is the command to execute for the line, +usually +.Xr getty 8 , +which initializes and opens the line, setting the speed, waiting for +a user name and executing the +.Xr login 1 +program. +It can be, however, any desired command, for example +the start up for a window system terminal emulator or some other +daemon process, and can contain multiple words if quoted. +.Pp +The third field is the type of terminal usually connected to that +tty line, normally the one found in the +.Xr termcap 5 +data base file. +The environment variable +.Ev TERM +is initialized with the value by +either +.Xr getty 8 +or +.Xr login 1 . +.Pp +The remaining fields set flags in the +.Fa ty_status +entry (see +.Xr getttyent 3 ) , +specify a window system process that +.Xr init 8 +will maintain for the terminal line, optionally determine the +type of tty (whether dialin, network or otherwise), +or specify a tty group +name that allows the login class database (see +.Xr login.conf 5 ) +to refer to many ttys as a group, to selectively allow or +deny access or enable or disable accounting facilities for +ttys as a group. +.Pp +As flag values, the strings ``on'' and ``off'' specify that +.Xr init 8 +should (should not) execute the command given in the second field. +``onifconsole'' will cause this line to be enabled if and only if it is +an active kernel console device (it is equivalent to ``on'' in this +case). +The flag ``secure'' (if the console is enabled) allows users with a +uid of 0 to login on +this line. +The flag ``dialin'' indicates that a tty entry describes a dialin +line, and ``network'' is obsolete and does nothing. +Either of these strings may also be specified in the terminal type +field. +The string ``window='' may be followed by a quoted command +string which +.Xr init 8 +will execute +.Em before +starting the command specified by the second field. +.Pp +The string ``group='' may be followed by a group name comprised of +alphanumeric characters that can be used by +.Xr login.conf 5 +to refer to many tty lines as a group to enable or disable access +and accounting facilities. +If no group is specified, then the tty becomes a member of the group +"none". +For backwards compatibility, the ``group='' should appear last on the +line, immediately before the optional comment. +.Pp +Both the second field and any command specified with ``window='' +will be split into words and executed using +.Xr execve 2 . +Words are separated by any combinations of tabs and spaces. +Arguments containing whitespace should be enclosed in single quotes +.Pq Li ' . +Note that no shell-style globbing or other variable substitution occurs. +.Sh FILES +.Bl -tag -width /etc/ttys -compact +.It Pa /etc/ttys +.El +.Sh EXAMPLES +.Bd -literal +# root login on console at 1200 baud +console "/usr/libexec/getty std.1200" vt100 on secure +# dialup at 1200 baud, no root logins +ttyd0 "/usr/libexec/getty d1200" dialup on group=dialup # 555-1234 +# Mike's terminal: hp2621 +ttyh0 "/usr/libexec/getty std.9600" hp2621-nl on group=dialup # 457 Evans +# John's terminal: vt100 +ttyh1 "/usr/libexec/getty std.9600" vt100 on group=dialup # 459 Evans +# terminal emulate/window system +ttyv0 "/usr/local/bin/xterm -display :0" xterm on window="/usr/local/bin/X :0" +.Ed +.Sh SEE ALSO +.Xr login 1 , +.Xr getttyent 3 , +.Xr gettytab 5 , +.Xr login.conf 5 , +.Xr termcap 5 , +.Xr getty 8 , +.Xr init 8 +.\".Xr init 8 , +.\".Xr ttyflags 8 +.Sh HISTORY +A +.Nm +file appeared in +.At v6 . diff --git a/libexec/hyperv/Makefile b/libexec/hyperv/Makefile new file mode 100644 index 0000000..37abdf5 --- /dev/null +++ b/libexec/hyperv/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../contrib/hyperv/tools/scripts + +BINDIR= /usr/libexec/hyperv + +SCRIPTS= hv_set_ifconfig hv_get_dns_info hv_get_dhcp_info +NO_OBJ= + +.include <bsd.prog.mk> diff --git a/libexec/mail.local/Makefile b/libexec/mail.local/Makefile new file mode 100644 index 0000000..3e59609 --- /dev/null +++ b/libexec/mail.local/Makefile @@ -0,0 +1,33 @@ +# @(#)Makefile 8.1 (Berkeley) 7/19/93 +# $FreeBSD$ + +SENDMAIL_DIR=${.CURDIR}/../../contrib/sendmail +.PATH: ${SENDMAIL_DIR}/mail.local + +PROG= mail.local +SRCS= mail.local.c +MAN= mail.local.8 +CFLAGS+=-I${SENDMAIL_DIR}/include -I. + +WARNS?= 2 +WFORMAT=0 + +LIBSMDIR= ${.OBJDIR}/../../lib/libsm +LIBSM= ${LIBSMDIR}/libsm.a + +DPADD= ${LIBSM} +LDADD= ${LIBSM} + +SRCS+= sm_os.h +CLEANFILES+=sm_os.h + +# User customizations to the sendmail build environment +CFLAGS+=${SENDMAIL_CFLAGS} +DPADD+=${SENDMAIL_DPADD} +LDADD+=${SENDMAIL_LDADD} +LDFLAGS+=${SENDMAIL_LDFLAGS} + +sm_os.h: + ln -sf ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h sm_os.h + +.include <bsd.prog.mk> diff --git a/libexec/mknetid/Makefile b/libexec/mknetid/Makefile new file mode 100644 index 0000000..5ca0662 --- /dev/null +++ b/libexec/mknetid/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= mknetid +SRCS= mknetid.c hash.c parse_group.c + +MAN= netid.5 mknetid.8 + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/libexec/mknetid/hash.c b/libexec/mknetid/hash.c new file mode 100644 index 0000000..b4a39cb --- /dev/null +++ b/libexec/mknetid/hash.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include "hash.h" + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * This hash function is stolen directly from the + * Berkeley DB package. It already exists inside libc, but + * it's declared static which prevents us from calling it + * from here. + */ +/* + * OZ's original sdbm hash + */ +u_int32_t +hash(const void *keyarg, size_t len) +{ + const u_char *key; + size_t loop; + u_int32_t h; + +#define HASHC h = *key++ + 65599 * h + + h = 0; + key = keyarg; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { + HASHC; + /* FALLTHROUGH */ + case 7: + HASHC; + /* FALLTHROUGH */ + case 6: + HASHC; + /* FALLTHROUGH */ + case 5: + HASHC; + /* FALLTHROUGH */ + case 4: + HASHC; + /* FALLTHROUGH */ + case 3: + HASHC; + /* FALLTHROUGH */ + case 2: + HASHC; + /* FALLTHROUGH */ + case 1: + HASHC; + } while (--loop); + } + } + return (h); +} + +/* + * Generate a hash value for a given key (character string). + * We mask off all but the lower 8 bits since our table array + * can only hole 256 elements. + */ +u_int32_t hashkey(char *key) +{ + + if (key == NULL) + return (-1); + return(hash((void *)key, strlen(key)) & HASH_MASK); +} + +/* Find an entry in the hash table (may be hanging off a linked list). */ +struct grouplist *lookup(struct member_entry *table[], char *key) +{ + struct member_entry *cur; + + cur = table[hashkey(key)]; + + while (cur) { + if (!strcmp(cur->key, key)) + return(cur->groups); + cur = cur->next; + } + + return(NULL); +} + +struct grouplist dummy = { 99999, NULL }; + +/* + * Store a group member entry and/or update its grouplist. + */ +void mstore (struct member_entry *table[], char *key, int gid, int dup) +{ + struct member_entry *cur, *new; + struct grouplist *tmp; + u_int32_t i; + + i = hashkey(key); + cur = table[i]; + + if (!dup) { + tmp = (struct grouplist *)malloc(sizeof(struct grouplist)); + tmp->groupid = gid; + tmp->next = NULL; + } + + /* Check if all we have to do is insert a new groupname. */ + while (cur) { + if (!dup && !strcmp(cur->key, key)) { + tmp->next = cur->groups; + cur->groups = tmp; + return; + } + cur = cur->next; + } + + /* Didn't find a match -- add the whole mess to the table. */ + new = (struct member_entry *)malloc(sizeof(struct member_entry)); + new->key = strdup(key); + if (!dup) + new->groups = tmp; + else + new->groups = (struct grouplist *)&dummy; + new->next = table[i]; + table[i] = new; + + return; +} diff --git a/libexec/mknetid/hash.h b/libexec/mknetid/hash.h new file mode 100644 index 0000000..7918ae2 --- /dev/null +++ b/libexec/mknetid/hash.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 1995, 1996 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* Groupid entry hung off a member_entry node. */ +struct grouplist { + gid_t groupid; + struct grouplist *next; +}; + +/* Entry in the cooked member list hash table. */ +struct member_entry { + char *key; /* username */ + struct grouplist *groups; + struct member_entry *next; +}; + +/* Table size (chosen arbitrarily). Not too big, not too small. */ +#define TABLESIZE 1024 +#define HASH_MASK 0x000003FF + +extern void mstore(struct member_entry ** , char *, int, int); +extern struct grouplist *lookup(struct member_entry **, char *); + diff --git a/libexec/mknetid/mknetid.8 b/libexec/mknetid/mknetid.8 new file mode 100644 index 0000000..08b8e74 --- /dev/null +++ b/libexec/mknetid/mknetid.8 @@ -0,0 +1,152 @@ +.\" Copyright (c) 1995, 1996 +.\" Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Bill Paul. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 23, 1996 +.Dt MKNETID 8 +.Os +.Sh NAME +.Nm mknetid +.Nd "generate netid map data" +.Sh SYNOPSIS +.Nm +.Op Fl q +.Op Fl g Ar group_file +.Op Fl p Ar passwd_file +.Op Fl h Ar hosts_file +.Op Fl n Ar netid_file +.Op Fl d Ar domain +.Sh DESCRIPTION +The +.Nm +utility processes the contents of the +.Xr group 5 , +.Xr passwd 5 , +.Xr hosts 5 +and +.Xr netid 5 +files into the format used to generate the +.Pa netid.byname +.Tn NIS +map. +This map is used to hold credential information for both users +and hosts in an operating system independent format. +.Pp +The +.Nm +utility checks for duplicate occurrences of netids and filters +them out. +.Pp +The +.Nm +utility prints its results on the standard output. +It is usually called +only by +.Pa /var/yp/Makefile +when rebuilding the +.Tn NIS +maps. +.Sh OPTIONS +The +.Nm +utility supports the following options: +.Bl -tag -width indent +.It Fl q +Normally, +.Nm +prints a warning message when it encounters a duplicate netid. +This flag turns on 'quiet' mode, allowing the warnings to be +suppressed. +Other error messages may still be generated. +.It Fl g Ar group_file +Specify the location of the group information +file. +The compiled-in default is +.Pa /etc/group . +.It Fl p Ar passwd_file +Specify the location of the passwd information +file. +The compiled-in default is +.Pa /etc/passwd . +.It Fl h Ar hosts_file +Specify the location of the hosts database +file. +The compiled-in default is +.Pa /etc/hosts . +.It Fl n Ar netid_file +Specify the location of the netid information +file. +The compiled-in default is +.Pa /etc/netid . +Note that no error is generated if the netid database cannot be +found. +The netid database is not likely to be present on most systems +until +.Tn Secure RPC +support is added to +.Fx . +.It Fl d Ar domain +By default, the +.Nm +utility uses the system domainname when generating netid records. +If +the system domainname is not set, the domain must be specified on the +command line with the +.Fl d +flag. +If the domainname is set, the +.Fl d +flag may be used to override it. +.El +.Sh FILES +.Bl -tag -width /var/yp/Makefile -compact +.It Pa /var/yp/Makefile +the Makefile that calls +.Nm yp_mkdb +and +.Nm +to build the +.Tn NIS +databases +.It Pa /etc/group +the default group database file +.It Pa /etc/passwd +the default passwd database file +.It Pa /etc/hosts +the default hosts database file +.It Pa /etc/netid +the default netid database file +.El +.Sh SEE ALSO +.Xr yp 8 , +.Xr yp_mkdb 8 +.Sh AUTHORS +.An Bill Paul Aq Mt wpaul@ctr.columbia.edu diff --git a/libexec/mknetid/mknetid.c b/libexec/mknetid/mknetid.c new file mode 100644 index 0000000..6686866 --- /dev/null +++ b/libexec/mknetid/mknetid.c @@ -0,0 +1,306 @@ +/* + * Copyright (c) 1995, 1996 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * netid map generator program + * + * Written by Bill Paul <wpaul@ctr.columbia.edu> + * Center for Telecommunications Research + * Columbia University, New York City + */ + +#include <sys/types.h> + +#include <rpc/rpc.h> +#include <rpcsvc/yp_prot.h> +#include <rpcsvc/ypclnt.h> + +#include <err.h> +#include <grp.h> +#include <netdb.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hash.h" + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#define LINSIZ 1024 +#define OPSYS "unix" + +/* Default location of group file. */ +char *groupfile = _PATH_GROUP; +/* Default location of master.passwd file. */ +char *passfile = _PATH_PASSWD; +/* Default location of hosts file. */ +char *hostsfile = _PATH_HOSTS; +/* Default location of netid file */ +char *netidfile = "/etc/netid"; + +/* + * Stored hash table of 'reverse' group member database + * which we will construct. + */ +struct member_entry *mtable[TABLESIZE]; + +/* + * Dupe table: used to keep track of entries so we don't + * print the same thing twice. + */ +struct member_entry *dtable[TABLESIZE]; + +extern struct group *_getgrent(void); +extern int _setgrent(void); +extern void _endgrent(void); + +static void +usage(void) +{ + fprintf (stderr, "%s\n%s\n", + "usage: mknetid [-q] [-g group_file] [-p passwd_file] [-h hosts_file]", + " [-n netid_file] [-d domain]"); + exit(1); +} + +extern FILE *_gr_fp; + +int +main(int argc, char *argv[]) +{ + FILE *gfp, *pfp, *hfp, *nfp; + char readbuf[LINSIZ]; + char writebuf[LINSIZ]; + struct group *gr; + struct grouplist *glist; + char *domain; + int ch; + gid_t i; + char *ptr, *pidptr, *gidptr, *hptr; + int quiet = 0; + + domain = NULL; + while ((ch = getopt(argc, argv, "g:p:h:n:d:q")) != -1) { + switch(ch) { + case 'g': + groupfile = optarg; + break; + case 'p': + passfile = optarg; + break; + case 'h': + hostsfile = optarg; + break; + case 'n': + netidfile = optarg; + break; + case 'd': + domain = optarg; + break; + case 'q': + quiet++; + break; + default: + usage(); + break; + } + } + + if (domain == NULL) { + if (yp_get_default_domain(&domain)) + errx(1, "no domain name specified and default \ +domain not set"); + } + + if ((gfp = fopen(groupfile, "r")) == NULL) { + err(1, "%s", groupfile); + } + + if ((pfp = fopen(passfile, "r")) == NULL) { + err(1, "%s", passfile); + } + + if ((hfp = fopen(hostsfile, "r")) == NULL) { + err(1, "%s", hostsfile); + } + + if ((nfp = fopen(netidfile, "r")) == NULL) { + /* netid is optional -- just continue */ + nfp = NULL; + } + + _gr_fp = gfp; + + /* Load all the group membership info into a hash table. */ + + _setgrent(); + while((gr = _getgrent()) != NULL) { + while(*gr->gr_mem) { + mstore(mtable, *gr->gr_mem, gr->gr_gid, 0); + gr->gr_mem++; + } + } + + fclose(gfp); + _endgrent(); + + /* + * Now parse the passwd database, spewing out the extra + * group information we just stored if necessary. + */ + while(fgets(readbuf, LINSIZ, pfp)) { + /* Ignore comments: ^[ \t]*# */ + for (ptr = readbuf; *ptr != '\0'; ptr++) + if (*ptr != ' ' && *ptr != '\t') + break; + if (*ptr == '#' || *ptr == '\0') + continue; + if ((ptr = strchr(readbuf, ':')) == NULL) { + warnx("bad passwd file entry: %s", readbuf); + continue; + } + *ptr = '\0'; + ptr++; + if ((ptr = strchr(ptr, ':')) == NULL) { + warnx("bad passwd file entry: %s", readbuf); + continue; + } + *ptr = '\0'; + ptr++; + pidptr = ptr; + if ((ptr = strchr(ptr, ':')) == NULL) { + warnx("bad passwd file entry: %s", readbuf); + continue; + } + *ptr = '\0'; + ptr++; + gidptr = ptr; + if ((ptr = strchr(ptr, ':')) == NULL) { + warnx("bad passwd file entry: %s", readbuf); + continue; + } + *ptr = '\0'; + i = atol(gidptr); + + snprintf(writebuf, sizeof(writebuf), "%s.%s@%s", OPSYS, + pidptr, domain); + + if (lookup(dtable, writebuf)) { + if (!quiet) + warnx("duplicate netid '%s.%s@%s' -- skipping", + OPSYS, pidptr, domain); + continue; + } else { + mstore(dtable, writebuf, 0, 1); + } + printf("%s.%s@%s %s:%s", OPSYS, pidptr, domain, pidptr, gidptr); + if ((glist = lookup(mtable, (char *)&readbuf)) != NULL) { + while(glist) { + if (glist->groupid != i) + printf(",%lu", (u_long)glist->groupid); + glist = glist->next; + } + } + printf ("\n"); + } + + fclose(pfp); + + /* + * Now parse the hosts database (this part sucks). + */ + + while ((ptr = fgets(readbuf, LINSIZ, hfp))) { + if (*ptr == '#') + continue; + if (!(hptr = strpbrk(ptr, "#\n"))) + continue; + *hptr = '\0'; + if (!(hptr = strpbrk(ptr, " \t"))) + continue; + *hptr++ = '\0'; + ptr = hptr; + while (*ptr == ' ' || *ptr == '\t') + ptr++; + if (!(hptr = strpbrk(ptr, " \t"))) + continue; + *hptr++ = '\0'; + snprintf(writebuf, sizeof(writebuf), "%s.%s@%s", OPSYS, + ptr, domain); + if (lookup(dtable, (char *)&writebuf)) { + if (!quiet) + warnx("duplicate netid '%s' -- skipping", + writebuf); + continue; + } else { + mstore(dtable, (char *)&writebuf, 0, 1); + } + printf ("%s.%s@%s 0:%s\n", OPSYS, ptr, domain, ptr); + } + + fclose(hfp); + + /* + * Lastly, copy out any extra information in the netid + * file. If it's not open, just ignore it: it's optional anyway. + */ + + if (nfp != NULL) { + while(fgets(readbuf, LINSIZ, nfp)) { + if (readbuf[0] == '#') + continue; + if ((ptr = strpbrk((char*)&readbuf, " \t")) == NULL) { + warnx("bad netid entry: '%s'", readbuf); + continue; + } + + writebuf[0] = *ptr; + *ptr = '\0'; + if (lookup(dtable, (char *)&readbuf)) { + if (!quiet) + warnx("duplicate netid '%s' -- skipping", + readbuf); + continue; + } else { + mstore(dtable, (char *)&readbuf, 0, 1); + } + *ptr = writebuf[0]; + printf("%s",readbuf); + } + fclose(nfp); + } + + exit(0); +} diff --git a/libexec/mknetid/netid.5 b/libexec/mknetid/netid.5 new file mode 100644 index 0000000..b2ec827 --- /dev/null +++ b/libexec/mknetid/netid.5 @@ -0,0 +1,91 @@ +.\" Copyright (c) 1996 Mats O Jansson <moj@stacken.kth.se> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Mats O Jansson +.\" 4. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd January 13, 1996 +.Dt NETID 5 +.Os +.Sh NAME +.Nm netid +.Nd +.Tn YP +network credential file +.Sh DESCRIPTION +Files in +.Nm +format are rare. +One lives in the +.Tn YP +map +.Pa netid.byname . +The format is rather simple. +Each row consists of two items: a key and a value. +When created by +.Xr mknetid 8 +there are three types of records. +.Pp +The first type is information about which GIDs a UID has: +.Pp +.Sm off +.Li unix . Ao Ar uid Ac @ Aq Ar yp-domain +.Sm on +.Sm off +.Ao Ar uid Ac : Ao Ar gid Ac , Aq Ar gid +.Sm on +.Pp +The second type contains information about hosts: +.Pp +.Sm off +.Li unix . Ao Ar hostname Ac @ Aq Ar yp-domain +.Sm on +.Sm off +.Li 0 : Aq Ar hostname +.Sm on +.Pp +The third type refers to records from a +.Nm +file other than the two types above. +.Sh FILES +.Bl -tag -width ".Pa /etc/netid" -compact +.It Pa /etc/netid +for lines not generated automatically by +.Xr mknetid 8 +.El +.Sh EXAMPLES +A configuration file might look like the following: +.Bd -literal +unix.10714@kaka 10714:400,10 +unix.jodie@kaka 0:jodie +.Ed +.Sh SEE ALSO +.Xr mknetid 8 , +.Xr yp 8 +.Sh AUTHORS +.An Mats O Jansson Aq Mt moj@stacken.kth.se diff --git a/libexec/mknetid/parse_group.c b/libexec/mknetid/parse_group.c new file mode 100644 index 0000000..6244a8c --- /dev/null +++ b/libexec/mknetid/parse_group.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)getgrent.c 8.2 (Berkeley) 3/21/94"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * This is a slightly modified chunk of getgrent(3). All the YP support + * and unneeded functions have been stripped out. + */ + +#include <sys/types.h> +#include <grp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +FILE *_gr_fp; +static struct group _gr_group; +static int _gr_stayopen; +static int grscan(int, int); +static int start_gr(void); + +#define MAXGRP 200 +static char *members[MAXGRP]; +#define MAXLINELENGTH 1024 +static char line[MAXLINELENGTH]; + +struct group * +_getgrent(void) +{ + if (!_gr_fp && !start_gr()) { + return NULL; + } + + + if (!grscan(0, 0)) + return(NULL); + return(&_gr_group); +} + +static int +start_gr(void) +{ + return 1; +} + +int +_setgroupent(int stayopen) +{ + if (!start_gr()) + return(0); + _gr_stayopen = stayopen; + return(1); +} + +int +_setgrent(void) +{ + return(_setgroupent(0)); +} + +void +_endgrent(void) +{ + if (_gr_fp) { + (void)fclose(_gr_fp); + _gr_fp = NULL; + } +} + +static int +grscan(int search, int gid) +{ + char *cp, **m; + char *bp; + for (;;) { + if (!fgets(line, sizeof(line), _gr_fp)) + return(0); + bp = line; + /* skip lines that are too big */ + if (!strchr(line, '\n')) { + int ch; + + while ((ch = getc(_gr_fp)) != '\n' && ch != EOF) + ; + continue; + } + if ((_gr_group.gr_name = strsep(&bp, ":\n")) == NULL) + break; + if (_gr_group.gr_name[0] == '+') + continue; + if ((_gr_group.gr_passwd = strsep(&bp, ":\n")) == NULL) + break; + if (!(cp = strsep(&bp, ":\n"))) + continue; + _gr_group.gr_gid = atoi(cp); + if (search && _gr_group.gr_gid != gid) + continue; + cp = NULL; + if (bp == NULL) /* !! Must check for this! */ + break; + for (m = _gr_group.gr_mem = members;; bp++) { + if (m == &members[MAXGRP - 1]) + break; + if (*bp == ',') { + if (cp) { + *bp = '\0'; + *m++ = cp; + cp = NULL; + } + } else if (*bp == '\0' || *bp == '\n' || *bp == ' ') { + if (cp) { + *bp = '\0'; + *m++ = cp; + } + break; + } else if (cp == NULL) + cp = bp; + } + *m = NULL; + return(1); + } + /* NOTREACHED */ + return (0); +} diff --git a/libexec/pppoed/Makefile b/libexec/pppoed/Makefile new file mode 100644 index 0000000..1ffaffe --- /dev/null +++ b/libexec/pppoed/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= pppoed +DPADD= ${LIBNETGRAPH} +LDADD= -lnetgraph +MAN= pppoed.8 + +WARNS?= 1 +WFORMAT=0 + +.include <bsd.prog.mk> diff --git a/libexec/pppoed/pppoed.8 b/libexec/pppoed/pppoed.8 new file mode 100644 index 0000000..6841bfd --- /dev/null +++ b/libexec/pppoed/pppoed.8 @@ -0,0 +1,219 @@ +.\"- +.\" Copyright (c) 1999-2001 Brian Somers <brian@Awfulhak.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd November 8, 1999 +.Dt PPPOED 8 +.Os +.Sh NAME +.Nm pppoed +.Nd handle incoming PPP over Ethernet connections +.Sh SYNOPSIS +.Nm +.Op Fl Fd\& +.Op Fl P Ar pidfile +.Op Fl a Ar name +.Op Fl e Ar exec | Fl l Ar label +.Op Fl n Ar ngdebug +.Op Fl p Ar provider +.Ar interface +.Sh DESCRIPTION +The +.Nm +utility listens to the given +.Ar interface +for PPP over Ethernet (PPPoE) service request packets, and actions them +by negotiating a session then invoking a +.Xr ppp 8 +program. +The negotiation is implemented by the +.Dq pppoe +netgraph node. +See +.Xr ng_pppoe 4 +for details. +.Pp +The +.Nm +utility +will only offer services to clients requesting services from the given +.Ar provider , +which is taken as an empty name if not provided. +If a provider name of +.Dq * +is given, any PPPoE requests will be offered service. +.Pp +The supplied +.Ar name +will be given as the access concentrator name when establishing the connection. +If no +.Ar name +is given, the current base hostname is used. +.Pp +After receiving a request (PADI) from the PPPoE netgraph node, +.Nm +.Xr fork 2 Ns s +a child process and returns to service further requests. +The child process offers service +(using +.Ar name ) +and waits for a +.Dv SUCCESS +indication from the PPPoE node. +On receipt of the +.Dv SUCCESS +indication, +.Nm +will execute +.Pp +.D1 Ic exec Pa /usr/sbin/ppp Fl direct Ar label +.Pp +as a shell sub-process. +If +.Ar label +has not been specified, it defaults to +.Ar provider . +It is possible to specify another command using the +.Ar exec +argument. +This is mandatory if +.Ar provider +and +.Ar label +are not given. +The child process will have standard input and standard output +attached to the same +.Xr netgraph 4 +data socket +(see +.Xr ng_socket 4 ) +when started. +.Pp +The environment variables +.Ev HISMACADDR +and +.Ev ACNAME +are made available to the child process and are set to the MAC address of +the peer and the name of the AC respectively. +.Pp +Upon invocation, +.Nm +will attach a +.Dq pppoe +netgraph node to the relevant +.Dq ether +node using +.Dq Ar interface Ns \&: +as the node name, and then connect that +.Dq pppoe +node to a local +.Dq socket +node. +If the +.Fl F +option has not been given, +.Nm +will then go into the background and disassociate itself from the controlling +terminal. +When the +.Fl F +option is given, +.Nm +stays in the foreground. +.Pp +If the +.Fl d +option is given, additional diagnostics are provided (see the +.Sx DIAGNOSTICS +section below). +If the +.Fl n +option is given, +.Fn NgSetDebug +is called with an argument of +.Ar ngdebug . +.Pp +If +.Ar pidfile +is given, +.Nm +will write its process ID to this file on startup. +.Sh DIAGNOSTICS +After creating the necessary +.Xr netgraph 4 +nodes as described above, +.Nm +uses +.Xr syslogd 8 +to report all incoming connections. +If the +.Fl d +option is given, +.Nm +will report on the child processes creation of a new netgraph socket, its +service offer and the invocation of the +.Xr ppp 8 +program. +If the +.Fl n +option is given, netgraph diagnostic messages are also redirected to +.Xr syslogd 8 . +.Pp +It is sometimes useful to add the following to +.Pa /etc/syslog.conf : +.Bd -literal -offset indent +!pppoed +*.* /var/log/pppoed.log +.Ed +.Pp +and the following to +.Pa /etc/newsyslog.conf : +.Pp +.Dl "/var/log/pppoed.log 640 3 100 * Z" +.Sh SEE ALSO +.Xr NgSetDebug 3 , +.Xr netgraph 4 , +.Xr ng_ether 4 , +.Xr ng_pppoe 4 , +.Xr ng_socket 4 , +.Xr syslog.conf 5 , +.Xr ppp 8 , +.Xr syslogd 8 +.Sh HISTORY +The +.Nm +utility was written by +.An Brian Somers Aq Mt brian@Awfulhak.org +and first appeared in +.Fx 3.4 . +.Sh BUGS +If another netgraph node is using the given interface, +.Nm +will fail to start. +This is because +.Xr netgraph 4 +does not currently allow node chaining. +This may change in the future. diff --git a/libexec/pppoed/pppoed.c b/libexec/pppoed/pppoed.c new file mode 100644 index 0000000..a5c0b7a --- /dev/null +++ b/libexec/pppoed/pppoed.c @@ -0,0 +1,692 @@ +/*- + * Copyright (c) 1999-2001 Brian Somers <brian@Awfulhak.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <netgraph.h> +#include <net/ethernet.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netgraph/ng_ether.h> +#include <netgraph/ng_message.h> +#include <netgraph/ng_pppoe.h> +#include <netgraph/ng_socket.h> + +#include <errno.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <sys/fcntl.h> +#ifndef NOKLDLOAD +#include <sys/linker.h> +#include <sys/module.h> +#endif +#include <sys/uio.h> +#include <sys/wait.h> +#include <syslog.h> +#include <termios.h> +#include <unistd.h> + + +#define DEFAULT_EXEC_PREFIX "exec /usr/sbin/ppp -direct " +#define HISMACADDR "HISMACADDR" +#define SESSION_ID "SESSION_ID" + +static void nglogx(const char *, ...) __printflike(1, 2); + +static int ReceivedSignal; + +static int +usage(const char *prog) +{ + fprintf(stderr, "usage: %s [-Fd] [-P pidfile] [-a name] [-e exec | -l label]" + " [-n ngdebug] [-p provider] interface\n", prog); + return EX_USAGE; +} + +static void +Farewell(int sig) +{ + ReceivedSignal = sig; +} + +static int +ConfigureNode(const char *prog, const char *iface, const char *provider, + int cs, int ds, int debug, struct ngm_connect *ngc) +{ + /* + * We're going to do this with the passed `ds' & `cs' descriptors: + * + * .---------. + * | ether | + * | <iface> | + * `---------' + * (orphan) ds cs + * | | | + * | | | + * (ethernet) | | + * .---------. .-----------. + * | pppoe | | socket | + * | <iface> |(pppoe-<pid>)<---->(pppoe-<pid>)| <unnamed> | + * `--------- `-----------' + * (exec-<pid>) + * ^ .-----------. .-------------. + * | | socket | | ppp -direct | + * `--->(exec-<pid>)| <unnamed> |--fd--| provider | + * `-----------' `-------------' + * + * where there are potentially many ppp processes running off of the + * same PPPoE node. + * The exec-<pid> hook isn't made 'till we Spawn(). + */ + + char *epath, *spath; + struct ngpppoe_init_data *data; + const struct hooklist *hlist; + const struct nodeinfo *ninfo; + const struct linkinfo *nlink; + struct ngm_mkpeer mkp; + struct ng_mesg *resp; + u_char rbuf[2048]; + int f, plen; + + /* + * Ask for a list of hooks attached to the "ether" node. This node should + * magically exist as a way of hooking stuff onto an ethernet device + */ + epath = (char *)alloca(strlen(iface) + 2); + sprintf(epath, "%s:", iface); + + if (debug) + fprintf(stderr, "Sending NGM_LISTHOOKS to %s\n", epath); + + if (NgSendMsg(cs, epath, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, NULL, 0) < 0) { + if (errno == ENOENT) + fprintf(stderr, "%s Cannot send a netgraph message: Invalid interface\n", + epath); + else + fprintf(stderr, "%s Cannot send a netgraph message: %s\n", + epath, strerror(errno)); + return EX_UNAVAILABLE; + } + + /* Get our list back */ + resp = (struct ng_mesg *)rbuf; + if (NgRecvMsg(cs, resp, sizeof rbuf, NULL) <= 0) { + perror("Cannot get netgraph response"); + return EX_UNAVAILABLE; + } + + hlist = (const struct hooklist *)resp->data; + ninfo = &hlist->nodeinfo; + + if (debug) + fprintf(stderr, "Got reply from id [%x]: Type %s with %d hooks\n", + ninfo->id, ninfo->type, ninfo->hooks); + + /* Make sure we've got the right type of node */ + if (strncmp(ninfo->type, NG_ETHER_NODE_TYPE, sizeof NG_ETHER_NODE_TYPE - 1)) { + fprintf(stderr, "%s Unexpected node type ``%s'' (wanted ``" + NG_ETHER_NODE_TYPE "'')\n", epath, ninfo->type); + return EX_DATAERR; + } + + /* look for a hook already attached. */ + for (f = 0; f < ninfo->hooks; f++) { + nlink = &hlist->link[f]; + + if (debug) + fprintf(stderr, " Got [%x]:%s -> [%x]:%s\n", ninfo->id, + nlink->ourhook, nlink->nodeinfo.id, nlink->peerhook); + + if (!strcmp(nlink->ourhook, NG_ETHER_HOOK_ORPHAN) || + !strcmp(nlink->ourhook, NG_ETHER_HOOK_DIVERT)) { + /* + * Something is using the data coming out of this `ether' node. + * If it's a PPPoE node, we use that node, otherwise we complain that + * someone else is using the node. + */ + if (strcmp(nlink->nodeinfo.type, NG_PPPOE_NODE_TYPE)) { + fprintf(stderr, "%s Node type %s is currently active\n", + epath, nlink->nodeinfo.type); + return EX_UNAVAILABLE; + } + break; + } + } + + if (f == ninfo->hooks) { + /* + * Create a new PPPoE node connected to the `ether' node using + * the magic `orphan' and `ethernet' hooks + */ + snprintf(mkp.type, sizeof mkp.type, "%s", NG_PPPOE_NODE_TYPE); + snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", NG_ETHER_HOOK_ORPHAN); + snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", NG_PPPOE_HOOK_ETHERNET); + + if (debug) + fprintf(stderr, "Send MKPEER: %s%s -> [type %s]:%s\n", epath, + mkp.ourhook, mkp.type, mkp.peerhook); + + if (NgSendMsg(cs, epath, NGM_GENERIC_COOKIE, + NGM_MKPEER, &mkp, sizeof mkp) < 0) { + fprintf(stderr, "%s Cannot create a peer PPPoE node: %s\n", + epath, strerror(errno)); + return EX_OSERR; + } + } + + /* Connect the PPPoE node to our socket node. */ + snprintf(ngc->path, sizeof ngc->path, "%s%s", epath, NG_ETHER_HOOK_ORPHAN); + snprintf(ngc->ourhook, sizeof ngc->ourhook, "pppoe-%ld", (long)getpid()); + memcpy(ngc->peerhook, ngc->ourhook, sizeof ngc->peerhook); + + if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, + NGM_CONNECT, ngc, sizeof *ngc) < 0) { + perror("Cannot CONNECT PPPoE and socket nodes"); + return EX_OSERR; + } + + plen = strlen(provider); + + data = (struct ngpppoe_init_data *)alloca(sizeof *data + plen); + snprintf(data->hook, sizeof data->hook, "%s", ngc->peerhook); + memcpy(data->data, provider, plen); + data->data_len = plen; + + spath = (char *)alloca(strlen(ngc->peerhook) + 3); + strcpy(spath, ".:"); + strcpy(spath + 2, ngc->ourhook); + + if (debug) { + if (provider) + fprintf(stderr, "Sending PPPOE_LISTEN to %s, provider %s\n", + spath, provider); + else + fprintf(stderr, "Sending PPPOE_LISTEN to %s\n", spath); + } + + if (NgSendMsg(cs, spath, NGM_PPPOE_COOKIE, NGM_PPPOE_LISTEN, + data, sizeof *data + plen) == -1) { + fprintf(stderr, "%s: Cannot LISTEN on netgraph node: %s\n", + spath, strerror(errno)); + return EX_OSERR; + } + + return 0; +} + +static void +Spawn(const char *prog, const char *acname, const char *provider, + const char *exec, struct ngm_connect ngc, int cs, int ds, void *request, + int sz, int debug) +{ + char msgbuf[sizeof(struct ng_mesg) + sizeof(struct ngpppoe_sts)]; + struct ng_mesg *rep = (struct ng_mesg *)msgbuf; + struct ngpppoe_sts *sts = (struct ngpppoe_sts *)(msgbuf + sizeof *rep); + struct ngpppoe_init_data *data; + char env[18], unknown[14], sessionid[5], *path; + unsigned char *macaddr; + const char *msg; + int ret, slen; + + switch ((ret = fork())) { + case -1: + syslog(LOG_ERR, "fork: %m"); + break; + + case 0: + switch (fork()) { + case 0: + break; + case -1: + _exit(errno); + default: + _exit(0); + } + close(cs); + close(ds); + + /* Create a new socket node */ + if (debug) + syslog(LOG_INFO, "Creating a new socket node"); + + if (NgMkSockNode(NULL, &cs, &ds) == -1) { + syslog(LOG_ERR, "Cannot create netgraph socket node: %m"); + _exit(EX_CANTCREAT); + } + + /* Connect the PPPoE node to our new socket node. */ + snprintf(ngc.ourhook, sizeof ngc.ourhook, "exec-%ld", (long)getpid()); + memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook); + + if (debug) + syslog(LOG_INFO, "Sending CONNECT from .:%s -> %s.%s", + ngc.ourhook, ngc.path, ngc.peerhook); + if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, + NGM_CONNECT, &ngc, sizeof ngc) < 0) { + syslog(LOG_ERR, "Cannot CONNECT PPPoE and socket nodes: %m"); + _exit(EX_OSERR); + } + + /* + * If we tell the socket node not to LINGER, it will go away when + * the last hook is removed. + */ + if (debug) + syslog(LOG_INFO, "Sending NGM_SOCK_CMD_NOLINGER to socket"); + if (NgSendMsg(cs, ".:", NGM_SOCKET_COOKIE, + NGM_SOCK_CMD_NOLINGER, NULL, 0) < 0) { + syslog(LOG_ERR, "Cannot send NGM_SOCK_CMD_NOLINGER: %m"); + _exit(EX_OSERR); + } + + /* Put the PPPoE node into OFFER mode */ + slen = strlen(acname); + data = (struct ngpppoe_init_data *)alloca(sizeof *data + slen); + snprintf(data->hook, sizeof data->hook, "%s", ngc.ourhook); + memcpy(data->data, acname, slen); + data->data_len = slen; + + path = (char *)alloca(strlen(ngc.ourhook) + 3); + strcpy(path, ".:"); + strcpy(path + 2, ngc.ourhook); + + syslog(LOG_INFO, "Offering to %s as access concentrator %s", + path, acname); + if (NgSendMsg(cs, path, NGM_PPPOE_COOKIE, NGM_PPPOE_OFFER, + data, sizeof *data + slen) == -1) { + syslog(LOG_INFO, "%s: Cannot OFFER on netgraph node: %m", path); + _exit(EX_OSERR); + } + /* If we have a provider code, set it */ + if (provider) { + slen = strlen(provider); + data = (struct ngpppoe_init_data *)alloca(sizeof *data + slen); + snprintf(data->hook, sizeof data->hook, "%s", ngc.ourhook); + memcpy(data->data, provider, slen); + data->data_len = slen; + + syslog(LOG_INFO, "adding to %s as offered service %s", + path, acname); + if (NgSendMsg(cs, path, NGM_PPPOE_COOKIE, NGM_PPPOE_SERVICE, + data, sizeof *data + slen) == -1) { + syslog(LOG_INFO, "%s: Cannot add service on netgraph node: %m", path); + _exit(EX_OSERR); + } + } + + /* Put the peer's MAC address in the environment */ + if (sz >= sizeof(struct ether_header)) { + macaddr = ((struct ether_header *)request)->ether_shost; + snprintf(env, sizeof(env), "%x:%x:%x:%x:%x:%x", + macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], + macaddr[5]); + if (setenv(HISMACADDR, env, 1) != 0) + syslog(LOG_INFO, "setenv: cannot set %s: %m", HISMACADDR); + } + + /* And send our request data to the waiting node */ + if (debug) + syslog(LOG_INFO, "Sending original request to %s (%d bytes)", path, sz); + if (NgSendData(ds, ngc.ourhook, request, sz) == -1) { + syslog(LOG_ERR, "Cannot send original request to %s: %m", path); + _exit(EX_OSERR); + } + + /* Then wait for a success indication */ + + if (debug) + syslog(LOG_INFO, "Waiting for a SUCCESS reply %s", path); + + do { + if ((ret = NgRecvMsg(cs, rep, sizeof msgbuf, NULL)) < 0) { + syslog(LOG_ERR, "%s: Cannot receive a message: %m", path); + _exit(EX_OSERR); + } + + if (ret == 0) { + /* The socket has been closed */ + syslog(LOG_INFO, "%s: Client timed out", path); + _exit(EX_TEMPFAIL); + } + + if (rep->header.version != NG_VERSION) { + syslog(LOG_ERR, "%ld: Unexpected netgraph version, expected %ld", + (long)rep->header.version, (long)NG_VERSION); + _exit(EX_PROTOCOL); + } + + if (rep->header.typecookie != NGM_PPPOE_COOKIE) { + syslog(LOG_INFO, "%ld: Unexpected netgraph cookie, expected %ld", + (long)rep->header.typecookie, (long)NGM_PPPOE_COOKIE); + continue; + } + + switch (rep->header.cmd) { + case NGM_PPPOE_SET_FLAG: msg = "SET_FLAG"; break; + case NGM_PPPOE_CONNECT: msg = "CONNECT"; break; + case NGM_PPPOE_LISTEN: msg = "LISTEN"; break; + case NGM_PPPOE_OFFER: msg = "OFFER"; break; + case NGM_PPPOE_SUCCESS: msg = "SUCCESS"; break; + case NGM_PPPOE_FAIL: msg = "FAIL"; break; + case NGM_PPPOE_CLOSE: msg = "CLOSE"; break; + case NGM_PPPOE_GET_STATUS: msg = "GET_STATUS"; break; + case NGM_PPPOE_ACNAME: + msg = "ACNAME"; + if (setenv("ACNAME", sts->hook, 1) != 0) + syslog(LOG_WARNING, "setenv: cannot set ACNAME=%s: %m", + sts->hook); + break; + case NGM_PPPOE_SESSIONID: + msg = "SESSIONID"; + snprintf(sessionid, sizeof sessionid, "%04x", *(u_int16_t *)sts); + if (setenv("SESSIONID", sessionid, 1) != 0) + syslog(LOG_WARNING, "setenv: cannot set SESSIONID=%s: %m", + sessionid); + break; + default: + snprintf(unknown, sizeof unknown, "<%d>", (int)rep->header.cmd); + msg = unknown; + break; + } + + switch (rep->header.cmd) { + case NGM_PPPOE_FAIL: + case NGM_PPPOE_CLOSE: + syslog(LOG_ERR, "Received NGM_PPPOE_%s (hook \"%s\")", + msg, sts->hook); + _exit(0); + } + + syslog(LOG_INFO, "Received NGM_PPPOE_%s (hook \"%s\")", msg, sts->hook); + } while (rep->header.cmd != NGM_PPPOE_SUCCESS); + + dup2(ds, STDIN_FILENO); + dup2(ds, STDOUT_FILENO); + close(ds); + close(cs); + + setsid(); + syslog(LOG_INFO, "Executing: %s", exec); + execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", exec, (char *)NULL); + syslog(LOG_ERR, "execlp failed: %m"); + _exit(EX_OSFILE); + + default: + wait(&ret); + errno = ret; + if (errno) + syslog(LOG_ERR, "Second fork failed: %m"); + break; + } +} + +#ifndef NOKLDLOAD +static int +LoadModules(void) +{ + const char *module[] = { "netgraph", "ng_socket", "ng_ether", "ng_pppoe" }; + int f; + + for (f = 0; f < sizeof module / sizeof *module; f++) + if (modfind(module[f]) == -1 && kldload(module[f]) == -1) { + fprintf(stderr, "kldload: %s: %s\n", module[f], strerror(errno)); + return 0; + } + + return 1; +} +#endif + +static void +nglog(const char *fmt, ...) +{ + char nfmt[256]; + va_list ap; + + snprintf(nfmt, sizeof nfmt, "%s: %s", fmt, strerror(errno)); + va_start(ap, fmt); + vsyslog(LOG_INFO, nfmt, ap); + va_end(ap); +} + +static void +nglogx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_INFO, fmt, ap); + va_end(ap); +} + +int +main(int argc, char *argv[]) +{ + char hostname[MAXHOSTNAMELEN], *exec, rhook[NG_HOOKSIZ]; + unsigned char response[1024]; + const char *label, *prog, *provider, *acname; + struct ngm_connect ngc; + struct sigaction act; + int ch, cs, ds, ret, optF, optd, optn, sz, f; + const char *pidfile; + + prog = strrchr(argv[0], '/'); + prog = prog ? prog + 1 : argv[0]; + pidfile = NULL; + exec = NULL; + label = NULL; + acname = NULL; + provider = ""; + optF = optd = optn = 0; + + while ((ch = getopt(argc, argv, "FP:a:de:l:n:p:")) != -1) { + switch (ch) { + case 'F': + optF = 1; + break; + + case 'P': + pidfile = optarg; + break; + + case 'a': + acname = optarg; + break; + + case 'd': + optd = 1; + break; + + case 'e': + exec = optarg; + break; + + case 'l': + label = optarg; + break; + + case 'n': + optn = 1; + NgSetDebug(atoi(optarg)); + break; + + case 'p': + provider = optarg; + break; + + default: + return usage(prog); + } + } + + if (optind >= argc || optind + 2 < argc) + return usage(prog); + + if (exec != NULL && label != NULL) + return usage(prog); + + if (exec == NULL) { + if (label == NULL) + label = provider; + if (label == NULL) { + fprintf(stderr, "%s: Either a provider, a label or an exec command" + " must be given\n", prog); + return usage(prog); + } + exec = (char *)alloca(sizeof DEFAULT_EXEC_PREFIX + strlen(label)); + if (exec == NULL) { + fprintf(stderr, "%s: Cannot allocate %zu bytes\n", prog, + sizeof DEFAULT_EXEC_PREFIX + strlen(label)); + return EX_OSERR; + } + strcpy(exec, DEFAULT_EXEC_PREFIX); + strcpy(exec + sizeof DEFAULT_EXEC_PREFIX - 1, label); + } + + if (acname == NULL) { + char *dot; + + if (gethostname(hostname, sizeof hostname)) + strcpy(hostname, "localhost"); + else if ((dot = strchr(hostname, '.'))) + *dot = '\0'; + + acname = hostname; + } + +#ifndef NOKLDLOAD + if (!LoadModules()) + return EX_UNAVAILABLE; +#endif + + /* Create a socket node */ + if (NgMkSockNode(NULL, &cs, &ds) == -1) { + perror("Cannot create netgraph socket node"); + return EX_CANTCREAT; + } + + /* Connect it up (and fill in `ngc') */ + if ((ret = ConfigureNode(prog, argv[optind], provider, cs, ds, + optd, &ngc)) != 0) { + close(cs); + close(ds); + return ret; + } + + if (!optF && daemon(1, 0) == -1) { + perror("daemon()"); + close(cs); + close(ds); + return EX_OSERR; + } + + + if (pidfile != NULL) { + FILE *fp; + + if ((fp = fopen(pidfile, "w")) == NULL) { + perror(pidfile); + close(cs); + close(ds); + return EX_CANTCREAT; + } else { + fprintf(fp, "%d\n", (int)getpid()); + fclose(fp); + } + } + + openlog(prog, LOG_PID | (optF ? LOG_PERROR : 0), LOG_DAEMON); + if (!optF && optn) + NgSetErrLog(nglog, nglogx); + + memset(&act, '\0', sizeof act); + act.sa_handler = Farewell; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGINT, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + + while (!ReceivedSignal) { + if (*provider) + syslog(LOG_INFO, "Listening as provider %s", provider); + else + syslog(LOG_INFO, "Listening"); + + switch (sz = NgRecvData(ds, response, sizeof response, rhook)) { + case -1: + syslog(LOG_INFO, "NgRecvData: %m"); + break; + case 0: + syslog(LOG_INFO, "NgRecvData: socket closed"); + break; + default: + if (optd) { + char *dbuf, *ptr; + + ptr = dbuf = alloca(sz * 2 + 1); + for (f = 0; f < sz; f++, ptr += 2) + sprintf(ptr, "%02x", (u_char)response[f]); + *ptr = '\0'; + syslog(LOG_INFO, "Got %d bytes of data: %s", sz, dbuf); + } + } + if (sz <= 0) { + ret = EX_UNAVAILABLE; + break; + } + Spawn(prog, acname, provider, exec, ngc, cs, ds, response, sz, optd); + } + + if (pidfile) + remove(pidfile); + + if (ReceivedSignal) { + syslog(LOG_INFO, "Received signal %d, exiting", ReceivedSignal); + + signal(ReceivedSignal, SIG_DFL); + raise(ReceivedSignal); + + /* NOTREACHED */ + + ret = -ReceivedSignal; + } + + return ret; +} diff --git a/libexec/rbootd/Makefile b/libexec/rbootd/Makefile new file mode 100644 index 0000000..70b6555 --- /dev/null +++ b/libexec/rbootd/Makefile @@ -0,0 +1,11 @@ +# from: @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= rbootd +SRCS= bpf.c conf.c parseconf.c rbootd.c rmpproto.c utils.c +MAN= rbootd.8 + +WARNS?= 1 +WFORMAT=0 + +.include <bsd.prog.mk> diff --git a/libexec/rbootd/bpf.c b/libexec/rbootd/bpf.c new file mode 100644 index 0000000..6291f1e --- /dev/null +++ b/libexec/rbootd/bpf.c @@ -0,0 +1,406 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)bpf.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: bpf.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)bpf.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/bpf.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "defs.h" +#include "pathnames.h" + +static int BpfFd = -1; +static unsigned BpfLen = 0; +static u_int8_t *BpfPkt = NULL; + +/* +** BpfOpen -- Open and initialize a BPF device. +** +** Parameters: +** None. +** +** Returns: +** File descriptor of opened BPF device (for select() etc). +** +** Side Effects: +** If an error is encountered, the program terminates here. +*/ +int +BpfOpen(void) +{ + struct ifreq ifr; + char bpfdev[32]; + int n = 0; + + /* + * Open the first available BPF device. + */ + do { + (void) sprintf(bpfdev, _PATH_BPF, n++); + BpfFd = open(bpfdev, O_RDWR); + } while (BpfFd < 0 && (errno == EBUSY || errno == EPERM)); + + if (BpfFd < 0) { + syslog(LOG_ERR, "bpf: no available devices: %m"); + Exit(0); + } + + /* + * Set interface name for bpf device, get data link layer + * type and make sure it's type Ethernet. + */ + (void) strncpy(ifr.ifr_name, IntfName, sizeof(ifr.ifr_name)); + if (ioctl(BpfFd, BIOCSETIF, (caddr_t)&ifr) < 0) { + syslog(LOG_ERR, "bpf: ioctl(BIOCSETIF,%s): %m", IntfName); + Exit(0); + } + + /* + * Make sure we are dealing with an Ethernet device. + */ + if (ioctl(BpfFd, BIOCGDLT, (caddr_t)&n) < 0) { + syslog(LOG_ERR, "bpf: ioctl(BIOCGDLT): %m"); + Exit(0); + } + if (n != DLT_EN10MB) { + syslog(LOG_ERR,"bpf: %s: data-link type %d unsupported", + IntfName, n); + Exit(0); + } + + /* + * On read(), return packets immediately (do not buffer them). + */ + n = 1; + if (ioctl(BpfFd, BIOCIMMEDIATE, (caddr_t)&n) < 0) { + syslog(LOG_ERR, "bpf: ioctl(BIOCIMMEDIATE): %m"); + Exit(0); + } + + /* + * Try to enable the chip/driver's multicast address filter to + * grab our RMP address. If this fails, try promiscuous mode. + * If this fails, there's no way we are going to get any RMP + * packets so just exit here. + */ +#ifdef MSG_EOR + ifr.ifr_addr.sa_len = RMP_ADDRLEN + 2; +#endif + ifr.ifr_addr.sa_family = AF_UNSPEC; + memmove((char *)&ifr.ifr_addr.sa_data[0], &RmpMcastAddr[0], RMP_ADDRLEN); + if (ioctl(BpfFd, BIOCPROMISC, (caddr_t)0) < 0) { + syslog(LOG_ERR, "bpf: can't set promiscuous mode: %m"); + Exit(0); + } + + /* + * Ask BPF how much buffer space it requires and allocate one. + */ + if (ioctl(BpfFd, BIOCGBLEN, (caddr_t)&BpfLen) < 0) { + syslog(LOG_ERR, "bpf: ioctl(BIOCGBLEN): %m"); + Exit(0); + } + if (BpfPkt == NULL) + BpfPkt = (u_int8_t *)malloc(BpfLen); + + if (BpfPkt == NULL) { + syslog(LOG_ERR, "bpf: out of memory (%u bytes for bpfpkt)", + BpfLen); + Exit(0); + } + + /* + * Write a little program to snarf RMP Boot packets and stuff + * it down BPF's throat (i.e. set up the packet filter). + */ + { +#define RMP ((struct rmp_packet *)0) + static struct bpf_insn bpf_insn[] = { + { BPF_LD|BPF_B|BPF_ABS, 0, 0, (long)&RMP->hp_llc.dsap }, + { BPF_JMP|BPF_JEQ|BPF_K, 0, 5, IEEE_DSAP_HP }, + { BPF_LD|BPF_H|BPF_ABS, 0, 0, (long)&RMP->hp_llc.cntrl }, + { BPF_JMP|BPF_JEQ|BPF_K, 0, 3, IEEE_CNTL_HP }, + { BPF_LD|BPF_H|BPF_ABS, 0, 0, (long)&RMP->hp_llc.dxsap }, + { BPF_JMP|BPF_JEQ|BPF_K, 0, 1, HPEXT_DXSAP }, + { BPF_RET|BPF_K, 0, 0, RMP_MAX_PACKET }, + { BPF_RET|BPF_K, 0, 0, 0x0 } + }; +#undef RMP + static struct bpf_program bpf_pgm = { + sizeof(bpf_insn)/sizeof(bpf_insn[0]), bpf_insn + }; + + if (ioctl(BpfFd, BIOCSETF, (caddr_t)&bpf_pgm) < 0) { + syslog(LOG_ERR, "bpf: ioctl(BIOCSETF): %m"); + Exit(0); + } + } + + return(BpfFd); +} + +/* +** BPF GetIntfName -- Return the name of a network interface attached to +** the system, or 0 if none can be found. The interface +** must be configured up; the lowest unit number is +** preferred; loopback is ignored. +** +** Parameters: +** errmsg - if no network interface found, *errmsg explains why. +** +** Returns: +** A (static) pointer to interface name, or NULL on error. +** +** Side Effects: +** None. +*/ +char * +BpfGetIntfName(char **errmsg) +{ + struct ifreq ibuf[8], *ifrp, *ifend, *mp; + struct ifconf ifc; + int fd; + int minunit, n; + char *cp; + static char device[sizeof(ifrp->ifr_name)]; + static char errbuf[128] = "No Error!"; + + if (errmsg != NULL) + *errmsg = errbuf; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + (void) strcpy(errbuf, "bpf: socket: %m"); + return(NULL); + } + ifc.ifc_len = sizeof ibuf; + ifc.ifc_buf = (caddr_t)ibuf; + + if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 || + ifc.ifc_len < sizeof(struct ifreq)) { + (void) strcpy(errbuf, "bpf: ioctl(SIOCGIFCONF): %m"); + return(NULL); + } + ifrp = ibuf; + ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len); + + mp = 0; + minunit = 666; + for (; ifrp < ifend; ++ifrp) { + if (ioctl(fd, SIOCGIFFLAGS, (char *)ifrp) < 0) { + (void) strcpy(errbuf, "bpf: ioctl(SIOCGIFFLAGS): %m"); + return(NULL); + } + + /* + * If interface is down or this is the loopback interface, + * ignore it. + */ + if ((ifrp->ifr_flags & IFF_UP) == 0 || +#ifdef IFF_LOOPBACK + (ifrp->ifr_flags & IFF_LOOPBACK)) +#else + (strcmp(ifrp->ifr_name, "lo0") == 0)) +#endif + continue; + + for (cp = ifrp->ifr_name; !isdigit(*cp); ++cp) + ; + n = atoi(cp); + if (n < minunit) { + minunit = n; + mp = ifrp; + } + } + + (void) close(fd); + if (mp == 0) { + (void) strcpy(errbuf, "bpf: no interfaces found"); + return(NULL); + } + + (void) strcpy(device, mp->ifr_name); + return(device); +} + +/* +** BpfRead -- Read packets from a BPF device and fill in `rconn'. +** +** Parameters: +** rconn - filled in with next packet. +** doread - is True if we can issue a read() syscall. +** +** Returns: +** True if `rconn' contains a new packet, False otherwise. +** +** Side Effects: +** None. +*/ +int +BpfRead(RMPCONN *rconn, int doread) +{ + int datlen, caplen, hdrlen; + static u_int8_t *bp = NULL, *ep = NULL; + int cc; + + /* + * The read() may block, or it may return one or more packets. + * We let the caller decide whether or not we can issue a read(). + */ + if (doread) { + if ((cc = read(BpfFd, (char *)BpfPkt, (int)BpfLen)) < 0) { + syslog(LOG_ERR, "bpf: read: %m"); + return(0); + } else { + bp = BpfPkt; + ep = BpfPkt + cc; + } + } + +#define bhp ((struct bpf_hdr *)bp) + /* + * If there is a new packet in the buffer, stuff it into `rconn' + * and return a success indication. + */ + if (bp < ep) { + datlen = bhp->bh_datalen; + caplen = bhp->bh_caplen; + hdrlen = bhp->bh_hdrlen; + + if (caplen != datlen) + syslog(LOG_ERR, + "bpf: short packet dropped (%d of %d bytes)", + caplen, datlen); + else if (caplen > sizeof(struct rmp_packet)) + syslog(LOG_ERR, "bpf: large packet dropped (%d bytes)", + caplen); + else { + rconn->rmplen = caplen; + memmove((char *)&rconn->tstamp, (char *)&bhp->bh_tstamp, + sizeof(struct timeval)); + memmove((char *)&rconn->rmp, (char *)bp + hdrlen, caplen); + } + bp += BPF_WORDALIGN(caplen + hdrlen); + return(1); + } +#undef bhp + + return(0); +} + +/* +** BpfWrite -- Write packet to BPF device. +** +** Parameters: +** rconn - packet to send. +** +** Returns: +** True if write succeeded, False otherwise. +** +** Side Effects: +** None. +*/ +int +BpfWrite(RMPCONN *rconn) +{ + if (write(BpfFd, (char *)&rconn->rmp, rconn->rmplen) < 0) { + syslog(LOG_ERR, "write: %s: %m", EnetStr(rconn)); + return(0); + } + + return(1); +} + +/* +** BpfClose -- Close a BPF device. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** None. +*/ +void +BpfClose(void) +{ + struct ifreq ifr; + + if (BpfPkt != NULL) { + free((char *)BpfPkt); + BpfPkt = NULL; + } + + if (BpfFd == -1) + return; + +#ifdef MSG_EOR + ifr.ifr_addr.sa_len = RMP_ADDRLEN + 2; +#endif + ifr.ifr_addr.sa_family = AF_UNSPEC; + memmove((char *)&ifr.ifr_addr.sa_data[0], &RmpMcastAddr[0], RMP_ADDRLEN); + if (ioctl(BpfFd, SIOCDELMULTI, (caddr_t)&ifr) < 0) + (void) ioctl(BpfFd, BIOCPROMISC, (caddr_t)0); + + (void) close(BpfFd); + BpfFd = -1; +} diff --git a/libexec/rbootd/conf.c b/libexec/rbootd/conf.c new file mode 100644 index 0000000..32b8782 --- /dev/null +++ b/libexec/rbootd/conf.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)conf.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: conf.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)conf.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> + +#include <stdio.h> +#include "defs.h" +#include "pathnames.h" + +/* +** Define (and possibly initialize) global variables here. +** +** Caveat: +** The maximum number of bootable files (`char *BootFiles[]') is +** limited to C_MAXFILE (i.e. the maximum number of files that +** can be spec'd in the configuration file). This was done to +** simplify the boot file search code. +*/ + +char MyHost[MAXHOSTNAMELEN]; /* host name */ +pid_t MyPid; /* process id */ +int DebugFlg = 0; /* set true if debugging */ +int BootAny = 0; /* set true if we boot anyone */ + +char *ConfigFile = NULL; /* configuration file */ +char *DfltConfig = _PATH_RBOOTDCONF; /* default configuration file */ +char *PidFile = _PATH_RBOOTDPID; /* file w/pid of server */ +char *BootDir = _PATH_RBOOTDLIB; /* directory w/boot files */ +char *DbgFile = _PATH_RBOOTDDBG; /* debug output file */ + +FILE *DbgFp = NULL; /* debug file pointer */ +char *IntfName = NULL; /* intf we are attached to */ + +u_int16_t SessionID = 0; /* generated session ID */ + +char *BootFiles[C_MAXFILE]; /* list of boot files */ + +CLIENT *Clients = NULL; /* list of addrs we'll accept */ +RMPCONN *RmpConns = NULL; /* list of active connections */ + +u_int8_t RmpMcastAddr[RMP_ADDRLEN] = RMP_ADDR; /* RMP multicast address */ diff --git a/libexec/rbootd/defs.h b/libexec/rbootd/defs.h new file mode 100644 index 0000000..ebf8243 --- /dev/null +++ b/libexec/rbootd/defs.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)defs.h 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: defs.h 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + * + * $FreeBSD$ + */ + +#include "rmp.h" +#include "rmp_var.h" + +/* +** Common #define's and external variables. All other files should +** include this. +*/ + +/* + * This may be defined in <sys/param.h>, if not, it's defined here. + */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* + * SIGUSR1 and SIGUSR2 are defined in <signal.h> for 4.3BSD systems. + */ +#ifndef SIGUSR1 +#define SIGUSR1 SIGEMT +#endif +#ifndef SIGUSR2 +#define SIGUSR2 SIGFPE +#endif + +/* + * These can be faster & more efficient than strcmp()/strncmp()... + */ +#define STREQN(s1,s2) ((*s1 == *s2) && (strcmp(s1,s2) == 0)) +#define STRNEQN(s1,s2,n) ((*s1 == *s2) && (strncmp(s1,s2,n) == 0)) + +/* + * Configuration file limitations. + */ +#define C_MAXFILE 10 /* max number of boot-able files */ +#define C_LINELEN 1024 /* max length of line */ + +/* + * Direction of packet (used as argument to DispPkt). + */ +#define DIR_RCVD 0 +#define DIR_SENT 1 +#define DIR_NONE 2 + +/* + * These need not be functions, so... + */ +#define FreeStr(str) free(str) +#define FreeClient(cli) free(cli) +#define GenSessID() (++SessionID ? SessionID: ++SessionID) + +/* + * Converting an Ethernet address to a string is done in many routines. + * Using `rmp.hp_hdr.saddr' works because this field is *never* changed; + * it will *always* contain the source address of the packet. + */ +#define EnetStr(rptr) GetEtherAddr(&(rptr)->rmp.hp_hdr.saddr[0]) + +/* + * Every machine we can boot will have one of these allocated for it + * (unless there are no restrictions on who we can boot). + */ +typedef struct client_s { + u_int8_t addr[RMP_ADDRLEN]; /* addr of machine */ + char *files[C_MAXFILE]; /* boot-able files */ + struct client_s *next; /* ptr to next */ +} CLIENT; + +/* + * Every active connection has one of these allocated for it. + */ +typedef struct rmpconn_s { + struct rmp_packet rmp; /* RMP packet */ + int rmplen; /* length of packet */ + struct timeval tstamp; /* last time active */ + int bootfd; /* open boot file */ + struct rmpconn_s *next; /* ptr to next */ +} RMPCONN; + +/* + * All these variables are defined in "conf.c". + */ +extern char MyHost[]; /* this hosts' name */ +extern pid_t MyPid; /* this processes' ID */ +extern int DebugFlg; /* set true if debugging */ +extern int BootAny; /* set true if we can boot anyone */ + +extern char *ConfigFile; /* configuration file */ +extern char *DfltConfig; /* default configuration file */ +extern char *DbgFile; /* debug output file */ +extern char *PidFile; /* file containing pid of server */ +extern char *BootDir; /* directory w/boot files */ + +extern FILE *DbgFp; /* debug file pointer */ +extern char *IntfName; /* interface we are attached to */ + +extern u_int16_t SessionID; /* generated session ID */ + +extern char *BootFiles[]; /* list of boot files */ + +extern CLIENT *Clients; /* list of addrs we'll accept */ +extern RMPCONN *RmpConns; /* list of active connections */ + +extern u_int8_t RmpMcastAddr[]; /* RMP multicast address */ + +void AddConn(RMPCONN *); +int BootDone(RMPCONN *); +void BpfClose(void); +char *BpfGetIntfName(char **); +int BpfOpen(void); +int BpfRead(RMPCONN *, int); +int BpfWrite(RMPCONN *); +void DebugOff(int); +void DebugOn(int); +void DispPkt(RMPCONN *, int); +void DoTimeout(void); +void DspFlnm(u_int, char *); +void Exit(int); +CLIENT *FindClient(RMPCONN *); +RMPCONN *FindConn(RMPCONN *); +void FreeClients(void); +void FreeConn(RMPCONN *); +void FreeConns(void); +int GetBootFiles(void); +char *GetEtherAddr(u_int8_t *); +CLIENT *NewClient(u_int8_t *); +RMPCONN *NewConn(RMPCONN *); +char *NewStr(char *); +u_int8_t *ParseAddr(char *); +int ParseConfig(void); +void ProcessPacket(RMPCONN *, CLIENT *); +void ReConfig(int); +void RemoveConn(RMPCONN *); +int SendBootRepl(struct rmp_packet *, RMPCONN *, char *[]); +int SendFileNo(struct rmp_packet *, RMPCONN *, char *[]); +int SendPacket(RMPCONN *); +int SendReadRepl(RMPCONN *); +int SendServerID(RMPCONN *); diff --git a/libexec/rbootd/parseconf.c b/libexec/rbootd/parseconf.c new file mode 100644 index 0000000..58b9ac5 --- /dev/null +++ b/libexec/rbootd/parseconf.c @@ -0,0 +1,358 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)parseconf.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: parseconf.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)parseconf.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <ctype.h> +#include <dirent.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include "defs.h" + +/* +** ParseConfig -- parse the config file into linked list of clients. +** +** Parameters: +** None. +** +** Returns: +** 1 on success, 0 otherwise. +** +** Side Effects: +** - Linked list of clients will be (re)allocated. +** +** Warnings: +** - GetBootFiles() must be called before this routine +** to create a linked list of default boot files. +*/ +int +ParseConfig(void) +{ + FILE *fp; + CLIENT *client; + u_int8_t *addr; + char line[C_LINELEN]; + char *cp, *bcp; + int i, j; + int omask, linecnt = 0; + + if (BootAny) /* ignore config file */ + return(1); + + FreeClients(); /* delete old list of clients */ + + if ((fp = fopen(ConfigFile, "r")) == NULL) { + syslog(LOG_ERR, "ParseConfig: can't open config file (%s)", + ConfigFile); + return(0); + } + + /* + * We've got to block SIGHUP to prevent reconfiguration while + * dealing with the linked list of Clients. This can be done + * when actually linking the new client into the list, but + * this could have unexpected results if the server was HUP'd + * whilst reconfiguring. Hence, it is done here. + */ + omask = sigblock(sigmask(SIGHUP)); + + /* + * GETSTR positions `bcp' at the start of the current token, + * and null terminates it. `cp' is positioned at the start + * of the next token. spaces & commas are separators. + */ +#define GETSTR while (isspace(*cp) || *cp == ',') cp++; \ + bcp = cp; \ + while (*cp && *cp!=',' && !isspace(*cp)) cp++; \ + if (*cp) *cp++ = '\0' + + /* + * For each line, parse it into a new CLIENT struct. + */ + while (fgets(line, C_LINELEN, fp) != NULL) { + linecnt++; /* line counter */ + + if (*line == '\0' || *line == '#') /* ignore comment */ + continue; + + if ((cp = strchr(line,'#')) != NULL) /* trash comments */ + *cp = '\0'; + + cp = line; /* init `cp' */ + GETSTR; /* get RMP addr */ + if (bcp == cp) /* all delimiters */ + continue; + + /* + * Get an RMP address from a string. Abort on failure. + */ + if ((addr = ParseAddr(bcp)) == NULL) { + syslog(LOG_ERR, + "ParseConfig: line %d: can't parse <%s>", + linecnt, bcp); + continue; + } + + if ((client = NewClient(addr)) == NULL) /* alloc new client */ + continue; + + GETSTR; /* get first file */ + + /* + * If no boot files are spec'd, use the default list. + * Otherwise, validate each file (`bcp') against the + * list of boot-able files. + */ + i = 0; + if (bcp == cp) /* no files spec'd */ + for (; i < C_MAXFILE && BootFiles[i] != NULL; i++) + client->files[i] = BootFiles[i]; + else { + do { + /* + * For each boot file spec'd, make sure it's + * in our list. If so, include a pointer to + * it in the CLIENT's list of boot files. + */ + for (j = 0; ; j++) { + if (j==C_MAXFILE||BootFiles[j]==NULL) { + syslog(LOG_ERR, "ParseConfig: line %d: no boot file (%s)", + linecnt, bcp); + break; + } + if (STREQN(BootFiles[j], bcp)) { + if (i < C_MAXFILE) + client->files[i++] = + BootFiles[j]; + else + syslog(LOG_ERR, "ParseConfig: line %d: too many boot files (%s)", + linecnt, bcp); + break; + } + } + GETSTR; /* get next file */ + } while (bcp != cp); + + /* + * Restricted list of boot files were spec'd, + * however, none of them were found. Since we + * apparently can't let them boot "just anything", + * the entire record is invalidated. + */ + if (i == 0) { + FreeClient(client); + continue; + } + } + + /* + * Link this client into the linked list of clients. + * SIGHUP has already been blocked. + */ + if (Clients) + client->next = Clients; + Clients = client; + } + + (void) fclose(fp); /* close config file */ + + (void) sigsetmask(omask); /* reset signal mask */ + + return(1); /* return success */ +} + +/* +** ParseAddr -- Parse a string containing an RMP address. +** +** This routine is fairly liberal at parsing an RMP address. The +** address must contain 6 octets consisting of between 0 and 2 hex +** chars (upper/lower case) separated by colons. If two colons are +** together (e.g. "::", the octet between them is recorded as being +** zero. Hence, the following addrs are all valid and parse to the +** same thing: +** +** 08:00:09:00:66:ad 8::9:0:66:AD 8::9::66:aD +** +** For clarity, an RMP address is really an Ethernet address, but +** since the HP boot code uses IEEE 802.3, it's really an IEEE +** 802.3 address. Of course, all of these are identical. +** +** Parameters: +** str - string representation of an RMP address. +** +** Returns: +** pointer to a static array of RMP_ADDRLEN bytes. +** +** Side Effects: +** None. +** +** Warnings: +** - The return value points to a static buffer; it must +** be copied if it's to be saved. +*/ +u_int8_t * +ParseAddr(char *str) +{ + static u_int8_t addr[RMP_ADDRLEN]; + char *cp; + unsigned i; + int part, subpart; + + memset((char *)&addr[0], 0, RMP_ADDRLEN); /* zero static buffer */ + + part = subpart = 0; + for (cp = str; *cp; cp++) { + /* + * A colon (`:') must be used to delimit each octet. + */ + if (*cp == ':') { + if (++part == RMP_ADDRLEN) /* too many parts */ + return(NULL); + subpart = 0; + continue; + } + + /* + * Convert hex character to an integer. + */ + if (isdigit(*cp)) + i = *cp - '0'; + else { + i = (isupper(*cp)? tolower(*cp): *cp) - 'a' + 10; + if (i < 10 || i > 15) /* not a hex char */ + return(NULL); + } + + if (subpart++) { + if (subpart > 2) /* too many hex chars */ + return(NULL); + addr[part] <<= 4; + } + addr[part] |= i; + } + + if (part != (RMP_ADDRLEN-1)) /* too few parts */ + return(NULL); + + return(&addr[0]); +} + +/* +** GetBootFiles -- record list of files in current (boot) directory. +** +** Parameters: +** None. +** +** Returns: +** Number of boot files on success, 0 on failure. +** +** Side Effects: +** Strings in `BootFiles' are freed/allocated. +** +** Warnings: +** - After this routine is called, ParseConfig() must be +** called to re-order it's list of boot file pointers. +*/ +int +GetBootFiles(void) +{ + DIR *dfd; + struct stat statb; + struct dirent *dp; + int i; + + /* + * Free the current list of boot files. + */ + for (i = 0; i < C_MAXFILE && BootFiles[i] != NULL; i++) { + FreeStr(BootFiles[i]); + BootFiles[i] = NULL; + } + + /* + * Open current directory to read boot file names. + */ + if ((dfd = opendir(".")) == NULL) { /* open BootDir */ + syslog(LOG_ERR, "GetBootFiles: can't open directory (%s)\n", + BootDir); + return(0); + } + + /* + * Read each boot file name and allocate space for it in the + * list of boot files (BootFiles). All boot files read after + * C_MAXFILE will be ignored. + */ + i = 0; + for (dp = readdir(dfd); dp != NULL; dp = readdir(dfd)) { + if (stat(dp->d_name, &statb) < 0 || + (statb.st_mode & S_IFMT) != S_IFREG) + continue; + if (i == C_MAXFILE) + syslog(LOG_ERR, + "GetBootFiles: too many boot files (%s ignored)", + dp->d_name); + else if ((BootFiles[i] = NewStr(dp->d_name)) != NULL) + i++; + } + + (void) closedir(dfd); /* close BootDir */ + + if (i == 0) /* can't find any boot files */ + syslog(LOG_ERR, "GetBootFiles: no boot files (%s)\n", BootDir); + + return(i); +} diff --git a/libexec/rbootd/pathnames.h b/libexec/rbootd/pathnames.h new file mode 100644 index 0000000..54f8c67 --- /dev/null +++ b/libexec/rbootd/pathnames.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: pathnames.h 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + * + * $FreeBSD$ + */ + +#define _PATH_BPF "/dev/bpf%d" +#define _PATH_RBOOTDCONF "/etc/rbootd.conf" +#define _PATH_RBOOTDDBG "/tmp/rbootd.dbg" +#define _PATH_RBOOTDLIB "/usr/mdec/rbootd" +#define _PATH_RBOOTDPID "/var/run/rbootd.pid" diff --git a/libexec/rbootd/rbootd.8 b/libexec/rbootd/rbootd.8 new file mode 100644 index 0000000..6baf9f2 --- /dev/null +++ b/libexec/rbootd/rbootd.8 @@ -0,0 +1,153 @@ +.\" Copyright (c) 1988, 1992 The University of Utah and the Center +.\" for Software Science (CSS). +.\" Copyright (c) 1992, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Center for Software Science of the University of Utah Computer +.\" Science Department. CSS requests users of this software to return +.\" to css-dist@cs.utah.edu any improvements that they make and grant +.\" CSS redistribution rights. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)rbootd.8 8.2 (Berkeley) 12/11/93 +.\" $FreeBSD$ +.\" +.\" Utah Hdr: rbootd.man 3.1 92/07/06 +.\" Author: Jeff Forys, University of Utah CSS +.\" +.Dd December 11, 1993 +.Dt RBOOTD 8 +.Os +.Sh NAME +.Nm rbootd +.Nd HP remote boot server +.Sh SYNOPSIS +.Nm +.Op Fl ad +.Op Fl i Ar interface +.Op config_file +.Sh DESCRIPTION +The +.Nm +utility services boot requests from Hewlett-Packard workstations over a +local area network. +All boot files must reside in the boot file directory; further, if a +client supplies path information in its boot request, it will be silently +stripped away before processing. +By default, +.Nm +only responds to requests from machines listed in its configuration file. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a +Respond to boot requests from any machine. +The configuration file is ignored if this option is specified. +.It Fl d +Run +.Nm +in debug mode. +Packets sent and received are displayed to the terminal. +.It Fl i Ar interface +Service boot requests on specified interface. +If unspecified, +.Nm +searches the system interface list for the lowest numbered, configured +``up'' interface (excluding loopback). +Ties are broken by choosing the earliest match. +.El +.Pp +Specifying +.Ar config_file +on the command line causes +.Nm +to use a different configuration file from the default. +.Pp +The configuration file is a text file where each line describes a particular +machine. +A line must start with a machine's Ethernet address followed by an optional +list of boot file names. +An Ethernet address is specified in hexadecimal with each of its six octets +separated by a colon. +The boot file names come from the boot file directory. +The ethernet address and boot file(s) must be separated by white-space +and/or comma characters. +A pound sign causes the remainder of a line to be ignored. +.Pp +Here is a sample configuration file: +.Bd -literal +# +# ethernet addr boot file(s) comments +# +08:00:09:0:66:ad SYSHPBSD # snake (4.3BSD) +08:00:09:0:59:5b # vandy (anything) +8::9:1:C6:75 SYSHPBSD,SYSHPUX # jaguar (either) +.Ed +.Pp +The +.Nm +utility logs status and error messages via +.Xr syslog 3 . +A startup message is always logged, and in the case of fatal errors (or +deadly signals) a message is logged announcing the server's termination. +In general, a non-fatal error is handled by ignoring the event that caused +it (e.g.\& an invalid Ethernet address in the config file causes that line +to be invalidated). +.Pp +The following signals have the specified effect when sent to the server +process using the +.Xr kill 1 +command: +.Bl -tag -width SIGUSR1 -offset xxxxxxxx +.It SIGHUP +Drop all active connections and reconfigure. +.It SIGUSR1 +Turn on debugging, do nothing if already on. +.It SIGUSR2 +Turn off debugging, do nothing if already off. +.El +.Sh "FILES" +.Bl -tag -width /usr/libexec/rbootd -compact +.It Pa /dev/bpf# +packet-filter device +.It Pa /etc/rbootd.conf +configuration file +.It Pa /tmp/rbootd.dbg +debug output +.It Pa /usr/mdec/rbootd +directory containing boot files +.It Pa /var/run/rbootd.pid +process id +.El +.Sh SEE ALSO +.Xr kill 1 , +.Xr socket 2 , +.Xr signal 3 , +.Xr syslog 3 +.Sh BUGS +If multiple servers are started on the same interface, each will receive +and respond to the same boot packets. diff --git a/libexec/rbootd/rbootd.c b/libexec/rbootd/rbootd.c new file mode 100644 index 0000000..de142f1 --- /dev/null +++ b/libexec/rbootd/rbootd.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)rbootd.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: rbootd.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1992, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)rbootd.c 8.1 (Berkeley) 6/4/93"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/time.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "defs.h" + +static void usage(void); + +int +main(int argc, char *argv[]) +{ + int c, fd, omask, maxfds; + fd_set rset; + + /* + * Close any open file descriptors. + * Temporarily leave stdin & stdout open for `-d', + * and stderr open for any pre-syslog error messages. + */ + { + int i, nfds = getdtablesize(); + + for (i = 0; i < nfds; i++) + if (i != fileno(stdin) && i != fileno(stdout) && + i != fileno(stderr)) + (void) close(i); + } + + /* + * Parse any arguments. + */ + while ((c = getopt(argc, argv, "adi:")) != -1) + switch(c) { + case 'a': + BootAny++; + break; + case 'd': + DebugFlg++; + break; + case 'i': + IntfName = optarg; + break; + default: + usage(); + } + for (; optind < argc; optind++) { + if (ConfigFile == NULL) + ConfigFile = argv[optind]; + else { + warnx("too many config files (`%s' ignored)", + argv[optind]); + } + } + + if (ConfigFile == NULL) /* use default config file */ + ConfigFile = DfltConfig; + + if (DebugFlg) { + DbgFp = stdout; /* output to stdout */ + + (void) signal(SIGUSR1, SIG_IGN); /* dont muck w/DbgFp */ + (void) signal(SIGUSR2, SIG_IGN); + (void) fclose(stderr); /* finished with it */ + } else { + if (daemon(0, 0)) + err(1, "can't detach from terminal"); + + (void) signal(SIGUSR1, DebugOn); + (void) signal(SIGUSR2, DebugOff); + } + + openlog("rbootd", LOG_PID, LOG_DAEMON); + + /* + * If no interface was specified, get one now. + * + * This is convoluted because we want to get the default interface + * name for the syslog("restarted") message. If BpfGetIntfName() + * runs into an error, it will return a syslog-able error message + * (in `errmsg') which will be displayed here. + */ + if (IntfName == NULL) { + char *errmsg; + + if ((IntfName = BpfGetIntfName(&errmsg)) == NULL) { + /* Backslash to avoid trigraph '??)'. */ + syslog(LOG_NOTICE, "restarted (?\?)"); + /* BpfGetIntfName() returns safe names, using %m */ + syslog(LOG_ERR, "%s", errmsg); + Exit(0); + } + } + + syslog(LOG_NOTICE, "restarted (%s)", IntfName); + + (void) signal(SIGHUP, ReConfig); + (void) signal(SIGINT, Exit); + (void) signal(SIGTERM, Exit); + + /* + * Grab our host name and pid. + */ + if (gethostname(MyHost, MAXHOSTNAMELEN - 1) < 0) { + syslog(LOG_ERR, "gethostname: %m"); + Exit(0); + } + MyHost[MAXHOSTNAMELEN - 1] = '\0'; + + MyPid = getpid(); + + /* + * Write proc's pid to a file. + */ + { + FILE *fp; + + if ((fp = fopen(PidFile, "w")) != NULL) { + (void) fprintf(fp, "%d\n", (int) MyPid); + (void) fclose(fp); + } else { + syslog(LOG_WARNING, "fopen: failed (%s)", PidFile); + } + } + + /* + * All boot files are relative to the boot directory, we might + * as well chdir() there to make life easier. + */ + if (chdir(BootDir) < 0) { + syslog(LOG_ERR, "chdir: %m (%s)", BootDir); + Exit(0); + } + + /* + * Initial configuration. + */ + omask = sigblock(sigmask(SIGHUP)); /* prevent reconfig's */ + if (GetBootFiles() == 0) /* get list of boot files */ + Exit(0); + if (ParseConfig() == 0) /* parse config file */ + Exit(0); + + /* + * Open and initialize a BPF device for the appropriate interface. + * If an error is encountered, a message is displayed and Exit() + * is called. + */ + fd = BpfOpen(); + + (void) sigsetmask(omask); /* allow reconfig's */ + + /* + * Main loop: receive a packet, determine where it came from, + * and if we service this host, call routine to handle request. + */ + maxfds = fd + 1; + FD_ZERO(&rset); + FD_SET(fd, &rset); + for (;;) { + struct timeval timeout; + fd_set r; + int nsel; + + r = rset; + + if (RmpConns == NULL) { /* timeout isn't necessary */ + nsel = select(maxfds, &r, NULL, NULL, NULL); + } else { + timeout.tv_sec = RMP_TIMEOUT; + timeout.tv_usec = 0; + nsel = select(maxfds, &r, NULL, NULL, &timeout); + } + + if (nsel < 0) { + if (errno == EINTR) + continue; + syslog(LOG_ERR, "select: %m"); + Exit(0); + } else if (nsel == 0) { /* timeout */ + DoTimeout(); /* clear stale conns */ + continue; + } + + if (FD_ISSET(fd, &r)) { + RMPCONN rconn; + CLIENT *client, *FindClient(); + int doread = 1; + + while (BpfRead(&rconn, doread)) { + doread = 0; + + if (DbgFp != NULL) /* display packet */ + DispPkt(&rconn,DIR_RCVD); + + omask = sigblock(sigmask(SIGHUP)); + + /* + * If we do not restrict service, set the + * client to NULL (ProcessPacket() handles + * this). Otherwise, check that we can + * service this host; if not, log a message + * and ignore the packet. + */ + if (BootAny) { + client = NULL; + } else if ((client=FindClient(&rconn))==NULL) { + syslog(LOG_INFO, + "%s: boot packet ignored", + EnetStr(&rconn)); + (void) sigsetmask(omask); + continue; + } + + ProcessPacket(&rconn,client); + + (void) sigsetmask(omask); + } + } + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: rbootd [-ad] [-i interface] [config_file]\n"); + exit (1); +} + +/* +** DoTimeout -- Free any connections that have timed out. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - Timed out connections in `RmpConns' will be freed. +*/ +void +DoTimeout(void) +{ + RMPCONN *rtmp; + time_t now; + + /* + * For each active connection, if RMP_TIMEOUT seconds have passed + * since the last packet was sent, delete the connection. + */ + now = time(NULL); + for (rtmp = RmpConns; rtmp != NULL; rtmp = rtmp->next) + if ((rtmp->tstamp.tv_sec + RMP_TIMEOUT) < now) { + syslog(LOG_WARNING, "%s: connection timed out (%u)", + EnetStr(rtmp), rtmp->rmp.r_type); + RemoveConn(rtmp); + } +} + +/* +** FindClient -- Find client associated with a packet. +** +** Parameters: +** rconn - the new packet. +** +** Returns: +** Pointer to client info if found, NULL otherwise. +** +** Side Effects: +** None. +** +** Warnings: +** - This routine must be called with SIGHUP blocked since +** a reconfigure can invalidate the information returned. +*/ + +CLIENT * +FindClient(RMPCONN *rconn) +{ + CLIENT *ctmp; + + for (ctmp = Clients; ctmp != NULL; ctmp = ctmp->next) + if (bcmp((char *)&rconn->rmp.hp_hdr.saddr[0], + (char *)&ctmp->addr[0], RMP_ADDRLEN) == 0) + break; + + return(ctmp); +} + +/* +** Exit -- Log an error message and exit. +** +** Parameters: +** sig - caught signal (or zero if not dying on a signal). +** +** Returns: +** Does not return. +** +** Side Effects: +** - This process ceases to exist. +*/ +void +Exit(int sig) +{ + if (sig > 0) + syslog(LOG_ERR, "going down on signal %d", sig); + else + syslog(LOG_ERR, "going down with fatal error"); + BpfClose(); + exit(1); +} + +/* +** ReConfig -- Get new list of boot files and reread config files. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - All active connections are dropped. +** - List of boot-able files is changed. +** - List of clients is changed. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +void +ReConfig(int signo __unused) +{ + syslog(LOG_NOTICE, "reconfiguring boot server"); + + FreeConns(); + + if (GetBootFiles() == 0) + Exit(0); + + if (ParseConfig() == 0) + Exit(0); +} + +/* +** DebugOff -- Turn off debugging. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - Debug file is closed. +*/ +void +DebugOff(int signo __unused) +{ + if (DbgFp != NULL) + (void) fclose(DbgFp); + + DbgFp = NULL; +} + +/* +** DebugOn -- Turn on debugging. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - Debug file is opened/truncated if not already opened, +** otherwise do nothing. +*/ +void +DebugOn(int signo __unused) +{ + if (DbgFp == NULL) { + if ((DbgFp = fopen(DbgFile, "w")) == NULL) + syslog(LOG_ERR, "can't open debug file (%s)", DbgFile); + } +} diff --git a/libexec/rbootd/rmp.h b/libexec/rbootd/rmp.h new file mode 100644 index 0000000..09b67ee --- /dev/null +++ b/libexec/rbootd/rmp.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)rmp.h 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: rmp.h 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + * + * $FreeBSD$ + */ + +/* + * Define MIN/MAX sizes of RMP (ethernet) packet. + * For ease of computation, the 4 octet CRC field is not included. + * + * MCLBYTES is for bpfwrite(); it is adamant about using a cluster. + */ + +#define RMP_MAX_PACKET MIN(1514,MCLBYTES) +#define RMP_MIN_PACKET 60 + +/* + * Define RMP/Ethernet Multicast address (9:0:9:0:0:4) and its length. + */ +#define RMP_ADDR { 0x9, 0x0, 0x9, 0x0, 0x0, 0x4 } +#define RMP_ADDRLEN 6 + +/* + * Define IEEE802.2 (Logical Link Control) information. + */ +#define IEEE_DSAP_HP 0xF8 /* Destination Service Access Point */ +#define IEEE_SSAP_HP 0xF8 /* Source Service Access Point */ +#define IEEE_CNTL_HP 0x0300 /* Type 1 / I format control information */ + +#define HPEXT_DXSAP 0x608 /* HP Destination Service Access Point */ +#define HPEXT_SXSAP 0x609 /* HP Source Service Access Point */ + +/* + * 802.3-style "Ethernet" header. + */ + +struct hp_hdr { + u_int8_t daddr[RMP_ADDRLEN]; + u_int8_t saddr[RMP_ADDRLEN]; + u_int16_t len; +}; + +/* + * HP uses 802.2 LLC with their own local extensions. This struct makes + * sense out of this data (encapsulated in the above 802.3 packet). + */ + +struct hp_llc { + u_int8_t dsap; /* 802.2 DSAP */ + u_int8_t ssap; /* 802.2 SSAP */ + u_int16_t cntrl; /* 802.2 control field */ + u_int16_t filler; /* HP filler (must be zero) */ + u_int16_t dxsap; /* HP extended DSAP */ + u_int16_t sxsap; /* HP extended SSAP */ +}; diff --git a/libexec/rbootd/rmp_var.h b/libexec/rbootd/rmp_var.h new file mode 100644 index 0000000..fdaacf7 --- /dev/null +++ b/libexec/rbootd/rmp_var.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)rmp_var.h 8.1 (Berkeley) 6/4/93 + * + * from: Utah Hdr: rmp_var.h 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + * + * $FreeBSD$ + */ + +/* + * Possible values for "rmp_type" fields. + */ + +#define RMP_BOOT_REQ 1 /* boot request packet */ +#define RMP_BOOT_REPL 129 /* boot reply packet */ +#define RMP_READ_REQ 2 /* read request packet */ +#define RMP_READ_REPL 130 /* read reply packet */ +#define RMP_BOOT_DONE 3 /* boot complete packet */ + +/* + * Useful constants. + */ + +#define RMP_VERSION 2 /* protocol version */ +#define RMP_TIMEOUT 600 /* timeout connection after ten minutes */ +#define RMP_PROBESID 0xffff /* session ID for probes */ +#define RMP_HOSTLEN 13 /* max length of server's name */ +#define RMP_MACHLEN 20 /* length of machine type field */ + +/* + * RMP error codes + */ + +#define RMP_E_OKAY 0 +#define RMP_E_EOF 2 /* read reply: returned end of file */ +#define RMP_E_ABORT 3 /* abort operation */ +#define RMP_E_BUSY 4 /* boot reply: server busy */ +#define RMP_E_TIMEOUT 5 /* lengthen time out (not implemented) */ +#define RMP_E_NOFILE 16 /* boot reply: file does not exist */ +#define RMP_E_OPENFILE 17 /* boot reply: file open failed */ +#define RMP_E_NODFLT 18 /* boot reply: default file does not exist */ +#define RMP_E_OPENDFLT 19 /* boot reply: default file open failed */ +#define RMP_E_BADSID 25 /* read reply: bad session ID */ +#define RMP_E_BADPACKET 27 /* Bad packet detected */ + +/* + * RMPDATALEN is the maximum number of data octets that can be stuffed + * into an RMP packet. This excludes the 802.2 LLC w/HP extensions. + */ +#define RMPDATALEN (RMP_MAX_PACKET - (sizeof(struct hp_hdr) + \ + sizeof(struct hp_llc))) + +/* + * Define sizes of packets we send. Boot and Read replies are variable + * in length depending on the length of `s'. + * + * Also, define how much space `restofpkt' can take up for outgoing + * Boot and Read replies. Boot Request packets are effectively + * limited to 255 bytes due to the preceding 1-byte length field. + */ + +#define RMPBOOTSIZE(s) (sizeof(struct hp_hdr) + sizeof(struct hp_llc) + \ + sizeof(struct rmp_boot_repl) + s - sizeof(restofpkt)) +#define RMPREADSIZE(s) (sizeof(struct hp_hdr) + sizeof(struct hp_llc) + \ + sizeof(struct rmp_read_repl) + s - sizeof(restofpkt) \ + - sizeof(u_int8_t)) +#define RMPDONESIZE (sizeof(struct hp_hdr) + sizeof(struct hp_llc) + \ + sizeof(struct rmp_boot_done)) +#define RMPBOOTDATA 255 +#define RMPREADDATA (RMPDATALEN - \ + (2*sizeof(u_int8_t)+sizeof(u_int16_t)+sizeof(u_word))) + +/* + * This protocol defines some field sizes as "rest of ethernet packet". + * There is no easy way to specify this in C, so we use a one character + * field to denote it, and index past it to the end of the packet. + */ + +typedef char restofpkt; + +/* + * Due to the RMP packet layout, we'll run into alignment problems + * on machines that can't access (or don't, by default, align) words + * on half-word boundaries. If you know that your machine does not suffer + * from this problem, add it to the vax/tahoe/m68k #define below. + * + * The following macros are used to deal with this problem: + * WORDZE(w) Return True if u_word `w' is zero, False otherwise. + * ZEROWORD(w) Set u_word `w' to zero. + * COPYWORD(w1,w2) Copy u_word `w1' to `w2'. + * GETWORD(w,i) Copy u_word `w' into int `i'. + * PUTWORD(i,w) Copy int `i' into u_word `w'. + * + * N.B. Endianness is handled by use of ntohl/htonl + */ +#if defined(__vax__) || defined(__tahoe__) || defined(__m68k__) + +typedef u_int32_t u_word; + +#define WORDZE(w) ((w) == 0) +#define ZEROWORD(w) (w) = 0 +#define COPYWORD(w1,w2) (w2) = (w1) +#define GETWORD(w, i) (i) = ntohl(w) +#define PUTWORD(i, w) (w) = htonl(i) + +#else + +#define _WORD_HIGHPART 0 +#define _WORD_LOWPART 1 + +typedef struct _uword { u_int16_t val[2]; } u_word; + +#define WORDZE(w) \ + ((w.val[_WORD_HIGHPART] == 0) && (w.val[_WORD_LOWPART] == 0)) +#define ZEROWORD(w) \ + (w).val[_WORD_HIGHPART] = (w).val[_WORD_LOWPART] = 0 +#define COPYWORD(w1, w2) \ + { (w2).val[_WORD_HIGHPART] = (w1).val[_WORD_HIGHPART]; \ + (w2).val[_WORD_LOWPART] = (w1).val[_WORD_LOWPART]; \ + } +#define GETWORD(w, i) \ + (i) = (((u_int32_t)ntohs((w).val[_WORD_HIGHPART])) << 16) | ntohs((w).val[_WORD_LOWPART]) +#define PUTWORD(i, w) \ + { (w).val[_WORD_HIGHPART] = htons((u_int16_t) ((i >> 16) & 0xffff)); \ + (w).val[_WORD_LOWPART] = htons((u_int16_t) (i & 0xffff)); \ + } + +#endif + +/* + * Packet structures. + */ + +struct rmp_raw { /* generic RMP packet */ + u_int8_t rmp_type; /* packet type */ + u_int8_t rmp_rawdata[RMPDATALEN-1]; +}; + +struct rmp_boot_req { /* boot request */ + u_int8_t rmp_type; /* packet type (RMP_BOOT_REQ) */ + u_int8_t rmp_retcode; /* return code (0) */ + u_word rmp_seqno; /* sequence number (real time clock) */ + u_int16_t rmp_session; /* session id (normally 0) */ + u_int16_t rmp_version; /* protocol version (RMP_VERSION) */ + char rmp_machtype[RMP_MACHLEN]; /* machine type */ + u_int8_t rmp_flnmsize; /* length of rmp_flnm */ + restofpkt rmp_flnm; /* name of file to be read */ +}; + +struct rmp_boot_repl { /* boot reply */ + u_int8_t rmp_type; /* packet type (RMP_BOOT_REPL) */ + u_int8_t rmp_retcode; /* return code (normally 0) */ + u_word rmp_seqno; /* sequence number (from boot req) */ + u_int16_t rmp_session; /* session id (generated) */ + u_int16_t rmp_version; /* protocol version (RMP_VERSION) */ + u_int8_t rmp_flnmsize; /* length of rmp_flnm */ + restofpkt rmp_flnm; /* name of file (from boot req) */ +}; + +struct rmp_read_req { /* read request */ + u_int8_t rmp_type; /* packet type (RMP_READ_REQ) */ + u_int8_t rmp_retcode; /* return code (0) */ + u_word rmp_offset; /* file relative byte offset */ + u_int16_t rmp_session; /* session id (from boot repl) */ + u_int16_t rmp_size; /* max no of bytes to send */ +}; + +struct rmp_read_repl { /* read reply */ + u_int8_t rmp_type; /* packet type (RMP_READ_REPL) */ + u_int8_t rmp_retcode; /* return code (normally 0) */ + u_word rmp_offset; /* byte offset (from read req) */ + u_int16_t rmp_session; /* session id (from read req) */ + restofpkt rmp_data; /* data (max size from read req) */ + u_int8_t rmp_unused; /* padding to 16-bit boundary */ +}; + +struct rmp_boot_done { /* boot complete */ + u_int8_t rmp_type; /* packet type (RMP_BOOT_DONE) */ + u_int8_t rmp_retcode; /* return code (0) */ + u_word rmp_unused; /* not used (0) */ + u_int16_t rmp_session; /* session id (from read repl) */ +}; + +struct rmp_packet { + struct hp_hdr hp_hdr; + struct hp_llc hp_llc; + union { + struct rmp_boot_req rmp_brq; /* boot request */ + struct rmp_boot_repl rmp_brpl; /* boot reply */ + struct rmp_read_req rmp_rrq; /* read request */ + struct rmp_read_repl rmp_rrpl; /* read reply */ + struct rmp_boot_done rmp_done; /* boot complete */ + struct rmp_raw rmp_raw; /* raw data */ + } rmp_proto; +}; + +/* + * Make life easier... + */ + +#define r_type rmp_proto.rmp_raw.rmp_type +#define r_data rmp_proto.rmp_raw.rmp_rawdata +#define r_brq rmp_proto.rmp_brq +#define r_brpl rmp_proto.rmp_brpl +#define r_rrq rmp_proto.rmp_rrq +#define r_rrpl rmp_proto.rmp_rrpl +#define r_done rmp_proto.rmp_done diff --git a/libexec/rbootd/rmpproto.c b/libexec/rbootd/rmpproto.c new file mode 100644 index 0000000..aa4be56 --- /dev/null +++ b/libexec/rbootd/rmpproto.c @@ -0,0 +1,583 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)rmpproto.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: rmpproto.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)rmpproto.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <netinet/in.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "defs.h" + +/* +** ProcessPacket -- determine packet type and do what's required. +** +** An RMP BOOT packet has been received. Look at the type field +** and process Boot Requests, Read Requests, and Boot Complete +** packets. Any other type will be dropped with a warning msg. +** +** Parameters: +** rconn - the new connection +** client - list of files available to this host +** +** Returns: +** Nothing. +** +** Side Effects: +** - If this is a valid boot request, it will be added to +** the linked list of outstanding requests (RmpConns). +** - If this is a valid boot complete, its associated +** entry in RmpConns will be deleted. +** - Also, unless we run out of memory, a reply will be +** sent to the host that sent the packet. +*/ +void +ProcessPacket(RMPCONN *rconn, CLIENT *client) +{ + struct rmp_packet *rmp; + RMPCONN *rconnout; + + rmp = &rconn->rmp; /* cache pointer to RMP packet */ + + switch(rmp->r_type) { /* do what we came here to do */ + case RMP_BOOT_REQ: /* boot request */ + if ((rconnout = NewConn(rconn)) == NULL) + return; + + /* + * If the Session ID is 0xffff, this is a "probe" + * packet and we do not want to add the connection + * to the linked list of active connections. There + * are two types of probe packets, if the Sequence + * Number is 0 they want to know our host name, o/w + * they want the name of the file associated with + * the number spec'd by the Sequence Number. + * + * If this is an actual boot request, open the file + * and send a reply. If SendBootRepl() does not + * return 0, add the connection to the linked list + * of active connections, otherwise delete it since + * an error was encountered. + */ + if (ntohs(rmp->r_brq.rmp_session) == RMP_PROBESID) { + if (WORDZE(rmp->r_brq.rmp_seqno)) + (void) SendServerID(rconnout); + else + (void) SendFileNo(rmp, rconnout, + client? client->files: + BootFiles); + FreeConn(rconnout); + } else { + if (SendBootRepl(rmp, rconnout, + client? client->files: BootFiles)) + AddConn(rconnout); + else + FreeConn(rconnout); + } + break; + + case RMP_BOOT_REPL: /* boot reply (not valid) */ + syslog(LOG_WARNING, "%s: sent a boot reply", + EnetStr(rconn)); + break; + + case RMP_READ_REQ: /* read request */ + /* + * Send a portion of the boot file. + */ + (void) SendReadRepl(rconn); + break; + + case RMP_READ_REPL: /* read reply (not valid) */ + syslog(LOG_WARNING, "%s: sent a read reply", + EnetStr(rconn)); + break; + + case RMP_BOOT_DONE: /* boot complete */ + /* + * Remove the entry from the linked list of active + * connections. + */ + (void) BootDone(rconn); + break; + + default: /* unknown RMP packet type */ + syslog(LOG_WARNING, "%s: unknown packet type (%u)", + EnetStr(rconn), rmp->r_type); + } +} + +/* +** SendServerID -- send our host name to who ever requested it. +** +** Parameters: +** rconn - the reply packet to be formatted. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +SendServerID(RMPCONN *rconn) +{ + struct rmp_packet *rpl; + char *src, *dst; + u_int8_t *size; + + rpl = &rconn->rmp; /* cache ptr to RMP packet */ + + /* + * Set up assorted fields in reply packet. + */ + rpl->r_brpl.rmp_type = RMP_BOOT_REPL; + rpl->r_brpl.rmp_retcode = RMP_E_OKAY; + ZEROWORD(rpl->r_brpl.rmp_seqno); + rpl->r_brpl.rmp_session = 0; + rpl->r_brpl.rmp_version = htons(RMP_VERSION); + + size = &rpl->r_brpl.rmp_flnmsize; /* ptr to length of host name */ + + /* + * Copy our host name into the reply packet incrementing the + * length as we go. Stop at RMP_HOSTLEN or the first dot. + */ + src = MyHost; + dst = (char *) &rpl->r_brpl.rmp_flnm; + for (*size = 0; *size < RMP_HOSTLEN; (*size)++) { + if (*src == '.' || *src == '\0') + break; + *dst++ = *src++; + } + + rconn->rmplen = RMPBOOTSIZE(*size); /* set packet length */ + + return(SendPacket(rconn)); /* send packet */ +} + +/* +** SendFileNo -- send the name of a bootable file to the requester. +** +** Parameters: +** req - RMP BOOT packet containing the request. +** rconn - the reply packet to be formatted. +** filelist - list of files available to the requester. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +SendFileNo(struct rmp_packet *req, RMPCONN *rconn, char *filelist[]) +{ + struct rmp_packet *rpl; + char *src, *dst; + u_int8_t *size; + int i; + + GETWORD(req->r_brpl.rmp_seqno, i); /* SeqNo is really FileNo */ + rpl = &rconn->rmp; /* cache ptr to RMP packet */ + + /* + * Set up assorted fields in reply packet. + */ + rpl->r_brpl.rmp_type = RMP_BOOT_REPL; + PUTWORD(i, rpl->r_brpl.rmp_seqno); + i--; + rpl->r_brpl.rmp_session = 0; + rpl->r_brpl.rmp_version = htons(RMP_VERSION); + + size = &rpl->r_brpl.rmp_flnmsize; /* ptr to length of filename */ + *size = 0; /* init length to zero */ + + /* + * Copy the file name into the reply packet incrementing the + * length as we go. Stop at end of string or when RMPBOOTDATA + * characters have been copied. Also, set return code to + * indicate success or "no more files". + */ + if (i < C_MAXFILE && filelist[i] != NULL) { + src = filelist[i]; + dst = (char *)&rpl->r_brpl.rmp_flnm; + for (; *src && *size < RMPBOOTDATA; (*size)++) { + if (*src == '\0') + break; + *dst++ = *src++; + } + rpl->r_brpl.rmp_retcode = RMP_E_OKAY; + } else + rpl->r_brpl.rmp_retcode = RMP_E_NODFLT; + + rconn->rmplen = RMPBOOTSIZE(*size); /* set packet length */ + + return(SendPacket(rconn)); /* send packet */ +} + +/* +** SendBootRepl -- open boot file and respond to boot request. +** +** Parameters: +** req - RMP BOOT packet containing the request. +** rconn - the reply packet to be formatted. +** filelist - list of files available to the requester. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +SendBootRepl(struct rmp_packet *req, RMPCONN *rconn, char *filelist[]) +{ + int retval; + char *filename, filepath[RMPBOOTDATA+1]; + RMPCONN *oldconn; + struct rmp_packet *rpl; + char *src, *dst1, *dst2; + u_int8_t i; + + /* + * If another connection already exists, delete it since we + * are obviously starting again. + */ + if ((oldconn = FindConn(rconn)) != NULL) { + syslog(LOG_WARNING, "%s: dropping existing connection", + EnetStr(oldconn)); + RemoveConn(oldconn); + } + + rpl = &rconn->rmp; /* cache ptr to RMP packet */ + + /* + * Set up assorted fields in reply packet. + */ + rpl->r_brpl.rmp_type = RMP_BOOT_REPL; + COPYWORD(req->r_brq.rmp_seqno, rpl->r_brpl.rmp_seqno); + rpl->r_brpl.rmp_session = htons(GenSessID()); + rpl->r_brpl.rmp_version = htons(RMP_VERSION); + rpl->r_brpl.rmp_flnmsize = req->r_brq.rmp_flnmsize; + + /* + * Copy file name to `filepath' string, and into reply packet. + */ + src = &req->r_brq.rmp_flnm; + dst1 = filepath; + dst2 = &rpl->r_brpl.rmp_flnm; + for (i = 0; i < req->r_brq.rmp_flnmsize; i++) + *dst1++ = *dst2++ = *src++; + *dst1 = '\0'; + + /* + * If we are booting HP-UX machines, their secondary loader will + * ask for files like "/hp-ux". As a security measure, we do not + * allow boot files to lay outside the boot directory (unless they + * are purposely link'd out. So, make `filename' become the path- + * stripped file name and spoof the client into thinking that it + * really got what it wanted. + */ + filename = (filename = strrchr(filepath,'/'))? ++filename: filepath; + + /* + * Check that this is a valid boot file name. + */ + for (i = 0; i < C_MAXFILE && filelist[i] != NULL; i++) + if (STREQN(filename, filelist[i])) + goto match; + + /* + * Invalid boot file name, set error and send reply packet. + */ + rpl->r_brpl.rmp_retcode = RMP_E_NOFILE; + retval = 0; + goto sendpkt; + +match: + /* + * This is a valid boot file. Open the file and save the file + * descriptor associated with this connection and set success + * indication. If the file couldnt be opened, set error: + * "no such file or dir" - RMP_E_NOFILE + * "file table overflow" - RMP_E_BUSY + * "too many open files" - RMP_E_BUSY + * anything else - RMP_E_OPENFILE + */ + if ((rconn->bootfd = open(filename, O_RDONLY, 0600)) < 0) { + rpl->r_brpl.rmp_retcode = (errno == ENOENT)? RMP_E_NOFILE: + (errno == EMFILE || errno == ENFILE)? RMP_E_BUSY: + RMP_E_OPENFILE; + retval = 0; + } else { + rpl->r_brpl.rmp_retcode = RMP_E_OKAY; + retval = 1; + } + +sendpkt: + syslog(LOG_INFO, "%s: request to boot %s (%s)", + EnetStr(rconn), filename, retval? "granted": "denied"); + + rconn->rmplen = RMPBOOTSIZE(rpl->r_brpl.rmp_flnmsize); + + return (retval & SendPacket(rconn)); +} + +/* +** SendReadRepl -- send a portion of the boot file to the requester. +** +** Parameters: +** rconn - the reply packet to be formatted. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +SendReadRepl(RMPCONN *rconn) +{ + int retval = 0; + RMPCONN *oldconn; + struct rmp_packet *rpl, *req; + int size = 0; + int madeconn = 0; + + /* + * Find the old connection. If one doesn't exist, create one only + * to return the error code. + */ + if ((oldconn = FindConn(rconn)) == NULL) { + if ((oldconn = NewConn(rconn)) == NULL) + return(0); + syslog(LOG_ERR, "SendReadRepl: no active connection (%s)", + EnetStr(rconn)); + madeconn++; + } + + req = &rconn->rmp; /* cache ptr to request packet */ + rpl = &oldconn->rmp; /* cache ptr to reply packet */ + + if (madeconn) { /* no active connection above; abort */ + rpl->r_rrpl.rmp_retcode = RMP_E_ABORT; + retval = 1; + goto sendpkt; + } + + /* + * Make sure Session ID's match. + */ + if (ntohs(req->r_rrq.rmp_session) != + ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session): + ntohs(rpl->r_rrpl.rmp_session))) { + syslog(LOG_ERR, "SendReadRepl: bad session id (%s)", + EnetStr(rconn)); + rpl->r_rrpl.rmp_retcode = RMP_E_BADSID; + retval = 1; + goto sendpkt; + } + + /* + * If the requester asks for more data than we can fit, + * silently clamp the request size down to RMPREADDATA. + * + * N.B. I do not know if this is "legal", however it seems + * to work. This is necessary for bpfwrite() on machines + * with MCLBYTES less than 1514. + */ + if (ntohs(req->r_rrq.rmp_size) > RMPREADDATA) + req->r_rrq.rmp_size = htons(RMPREADDATA); + + /* + * Position read head on file according to info in request packet. + */ + GETWORD(req->r_rrq.rmp_offset, size); + if (lseek(oldconn->bootfd, (off_t)size, SEEK_SET) < 0) { + syslog(LOG_ERR, "SendReadRepl: lseek: %m (%s)", + EnetStr(rconn)); + rpl->r_rrpl.rmp_retcode = RMP_E_ABORT; + retval = 1; + goto sendpkt; + } + + /* + * Read data directly into reply packet. + */ + if ((size = read(oldconn->bootfd, &rpl->r_rrpl.rmp_data, + (int) ntohs(req->r_rrq.rmp_size))) <= 0) { + if (size < 0) { + syslog(LOG_ERR, "SendReadRepl: read: %m (%s)", + EnetStr(rconn)); + rpl->r_rrpl.rmp_retcode = RMP_E_ABORT; + } else { + rpl->r_rrpl.rmp_retcode = RMP_E_EOF; + } + retval = 1; + goto sendpkt; + } + + /* + * Set success indication. + */ + rpl->r_rrpl.rmp_retcode = RMP_E_OKAY; + +sendpkt: + /* + * Set up assorted fields in reply packet. + */ + rpl->r_rrpl.rmp_type = RMP_READ_REPL; + COPYWORD(req->r_rrq.rmp_offset, rpl->r_rrpl.rmp_offset); + rpl->r_rrpl.rmp_session = req->r_rrq.rmp_session; + + oldconn->rmplen = RMPREADSIZE(size); /* set size of packet */ + + retval &= SendPacket(oldconn); /* send packet */ + + if (madeconn) /* clean up after ourself */ + FreeConn(oldconn); + + return (retval); +} + +/* +** BootDone -- free up memory allocated for a connection. +** +** Parameters: +** rconn - incoming boot complete packet. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +BootDone(RMPCONN *rconn) +{ + RMPCONN *oldconn; + struct rmp_packet *rpl; + + /* + * If we can't find the connection, ignore the request. + */ + if ((oldconn = FindConn(rconn)) == NULL) { + syslog(LOG_ERR, "BootDone: no existing connection (%s)", + EnetStr(rconn)); + return(0); + } + + rpl = &oldconn->rmp; /* cache ptr to RMP packet */ + + /* + * Make sure Session ID's match. + */ + if (ntohs(rconn->rmp.r_rrq.rmp_session) != + ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session): + ntohs(rpl->r_rrpl.rmp_session))) { + syslog(LOG_ERR, "BootDone: bad session id (%s)", + EnetStr(rconn)); + return(0); + } + + RemoveConn(oldconn); /* remove connection */ + + syslog(LOG_INFO, "%s: boot complete", EnetStr(rconn)); + + return(1); +} + +/* +** SendPacket -- send an RMP packet to a remote host. +** +** Parameters: +** rconn - packet to be sent. +** +** Returns: +** 1 on success, 0 on failure. +** +** Side Effects: +** none. +*/ +int +SendPacket(RMPCONN *rconn) +{ + /* + * Set Ethernet Destination address to Source (BPF and the enet + * driver will take care of getting our source address set). + */ + memmove((char *)&rconn->rmp.hp_hdr.daddr[0], + (char *)&rconn->rmp.hp_hdr.saddr[0], RMP_ADDRLEN); + rconn->rmp.hp_hdr.len = htons(rconn->rmplen - sizeof(struct hp_hdr)); + + /* + * Reverse 802.2/HP Extended Source & Destination Access Pts. + */ + rconn->rmp.hp_llc.dxsap = htons(HPEXT_SXSAP); + rconn->rmp.hp_llc.sxsap = htons(HPEXT_DXSAP); + + /* + * Last time this connection was active. + */ + (void)gettimeofday(&rconn->tstamp, NULL); + + if (DbgFp != NULL) /* display packet */ + DispPkt(rconn,DIR_SENT); + + /* + * Send RMP packet to remote host. + */ + return(BpfWrite(rconn)); +} diff --git a/libexec/rbootd/utils.c b/libexec/rbootd/utils.c new file mode 100644 index 0000000..81ef584 --- /dev/null +++ b/libexec/rbootd/utils.c @@ -0,0 +1,544 @@ +/* + * Copyright (c) 1988, 1992 The University of Utah and the Center + * for Software Science (CSS). + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Center for Software Science of the University of Utah Computer + * Science Department. CSS requests users of this software to return + * to css-dist@cs.utah.edu any improvements that they make and grant + * CSS redistribution rights. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)utils.c 8.1 (Berkeley) 6/4/93 + * + * From: Utah Hdr: utils.c 3.1 92/07/06 + * Author: Jeff Forys, University of Utah CSS + */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)utils.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <netinet/in.h> + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include "defs.h" + +/* +** DispPkt -- Display the contents of an RMPCONN packet. +** +** Parameters: +** rconn - packet to be displayed. +** direct - direction packet is going (DIR_*). +** +** Returns: +** Nothing. +** +** Side Effects: +** None. +*/ +void +DispPkt(RMPCONN *rconn, int direct) +{ + static const char BootFmt[] = "\t\tRetCode:%u SeqNo:%x SessID:%x Vers:%u"; + static const char ReadFmt[] = "\t\tRetCode:%u Offset:%x SessID:%x\n"; + + struct tm *tmp; + struct rmp_packet *rmp; + int i, omask; + u_int32_t t; + + /* + * Since we will be working with RmpConns as well as DbgFp, we + * must block signals that can affect either. + */ + omask = sigblock(sigmask(SIGHUP)|sigmask(SIGUSR1)|sigmask(SIGUSR2)); + + if (DbgFp == NULL) { /* sanity */ + (void) sigsetmask(omask); + return; + } + + /* display direction packet is going using '>>>' or '<<<' */ + fputs((direct==DIR_RCVD)?"<<< ":(direct==DIR_SENT)?">>> ":"", DbgFp); + + /* display packet timestamp */ + tmp = localtime((time_t *)&rconn->tstamp.tv_sec); + fprintf(DbgFp, "%02d:%02d:%02d.%06ld ", tmp->tm_hour, tmp->tm_min, + tmp->tm_sec, rconn->tstamp.tv_usec); + + /* display src or dst addr and information about network interface */ + fprintf(DbgFp, "Addr: %s Intf: %s\n", EnetStr(rconn), IntfName); + + rmp = &rconn->rmp; + + /* display IEEE 802.2 Logical Link Control header */ + (void) fprintf(DbgFp, "\t802.2 LLC: DSAP:%x SSAP:%x CTRL:%x\n", + rmp->hp_llc.dsap, rmp->hp_llc.ssap, ntohs(rmp->hp_llc.cntrl)); + + /* display HP extensions to 802.2 Logical Link Control header */ + (void) fprintf(DbgFp, "\tHP Ext: DXSAP:%x SXSAP:%x\n", + ntohs(rmp->hp_llc.dxsap), ntohs(rmp->hp_llc.sxsap)); + + /* + * Display information about RMP packet using type field to + * determine what kind of packet this is. + */ + switch(rmp->r_type) { + case RMP_BOOT_REQ: /* boot request */ + (void) fprintf(DbgFp, "\tBoot Request:"); + GETWORD(rmp->r_brq.rmp_seqno, t); + if (ntohs(rmp->r_brq.rmp_session) == RMP_PROBESID) { + if (WORDZE(rmp->r_brq.rmp_seqno)) + fputs(" (Send Server ID)", DbgFp); + else + fprintf(DbgFp," (Send Filename #%u)",t); + } + (void) fputc('\n', DbgFp); + (void) fprintf(DbgFp, BootFmt, rmp->r_brq.rmp_retcode, + t, ntohs(rmp->r_brq.rmp_session), + ntohs(rmp->r_brq.rmp_version)); + (void) fprintf(DbgFp, "\n\t\tMachine Type: "); + for (i = 0; i < RMP_MACHLEN; i++) + (void) fputc(rmp->r_brq.rmp_machtype[i], DbgFp); + DspFlnm(rmp->r_brq.rmp_flnmsize, &rmp->r_brq.rmp_flnm); + break; + case RMP_BOOT_REPL: /* boot reply */ + fprintf(DbgFp, "\tBoot Reply:\n"); + GETWORD(rmp->r_brpl.rmp_seqno, t); + (void) fprintf(DbgFp, BootFmt, rmp->r_brpl.rmp_retcode, + t, ntohs(rmp->r_brpl.rmp_session), + ntohs(rmp->r_brpl.rmp_version)); + DspFlnm(rmp->r_brpl.rmp_flnmsize,&rmp->r_brpl.rmp_flnm); + break; + case RMP_READ_REQ: /* read request */ + (void) fprintf(DbgFp, "\tRead Request:\n"); + GETWORD(rmp->r_rrq.rmp_offset, t); + (void) fprintf(DbgFp, ReadFmt, rmp->r_rrq.rmp_retcode, + t, ntohs(rmp->r_rrq.rmp_session)); + (void) fprintf(DbgFp, "\t\tNoOfBytes: %u\n", + ntohs(rmp->r_rrq.rmp_size)); + break; + case RMP_READ_REPL: /* read reply */ + (void) fprintf(DbgFp, "\tRead Reply:\n"); + GETWORD(rmp->r_rrpl.rmp_offset, t); + (void) fprintf(DbgFp, ReadFmt, rmp->r_rrpl.rmp_retcode, + t, ntohs(rmp->r_rrpl.rmp_session)); + (void) fprintf(DbgFp, "\t\tNoOfBytesSent: %zu\n", + rconn->rmplen - RMPREADSIZE(0)); + break; + case RMP_BOOT_DONE: /* boot complete */ + (void) fprintf(DbgFp, "\tBoot Complete:\n"); + (void) fprintf(DbgFp, "\t\tRetCode:%u SessID:%x\n", + rmp->r_done.rmp_retcode, + ntohs(rmp->r_done.rmp_session)); + break; + default: /* ??? */ + (void) fprintf(DbgFp, "\tUnknown Type:(%d)\n", + rmp->r_type); + } + (void) fputc('\n', DbgFp); + (void) fflush(DbgFp); + + (void) sigsetmask(omask); /* reset old signal mask */ +} + + +/* +** GetEtherAddr -- convert an RMP (Ethernet) address into a string. +** +** An RMP BOOT packet has been received. Look at the type field +** and process Boot Requests, Read Requests, and Boot Complete +** packets. Any other type will be dropped with a warning msg. +** +** Parameters: +** addr - array of RMP_ADDRLEN bytes. +** +** Returns: +** Pointer to static string representation of `addr'. +** +** Side Effects: +** None. +** +** Warnings: +** - The return value points to a static buffer; it must +** be copied if it's to be saved. +*/ +char * +GetEtherAddr(u_int8_t *addr) +{ + static char Hex[] = "0123456789abcdef"; + static char etherstr[RMP_ADDRLEN*3]; + int i; + char *cp; + + /* + * For each byte in `addr', convert it to "<hexchar><hexchar>:". + * The last byte does not get a trailing `:' appended. + */ + i = 0; + cp = etherstr; + for(;;) { + *cp++ = Hex[*addr >> 4 & 0xf]; + *cp++ = Hex[*addr++ & 0xf]; + if (++i == RMP_ADDRLEN) + break; + *cp++ = ':'; + } + *cp = '\0'; + + return(etherstr); +} + + +/* +** DispFlnm -- Print a string of bytes to DbgFp (often, a file name). +** +** Parameters: +** size - number of bytes to print. +** flnm - address of first byte. +** +** Returns: +** Nothing. +** +** Side Effects: +** - Characters are sent to `DbgFp'. +*/ +void +DspFlnm(u_int size, char *flnm) +{ + int i; + + (void) fprintf(DbgFp, "\n\t\tFile Name (%u): <", size); + for (i = 0; i < size; i++) + (void) fputc(*flnm++, DbgFp); + (void) fputs(">\n", DbgFp); +} + + +/* +** NewClient -- allocate memory for a new CLIENT. +** +** Parameters: +** addr - RMP (Ethernet) address of new client. +** +** Returns: +** Ptr to new CLIENT or NULL if we ran out of memory. +** +** Side Effects: +** - Memory will be malloc'd for the new CLIENT. +** - If malloc() fails, a log message will be generated. +*/ +CLIENT * +NewClient(u_int8_t *addr) +{ + CLIENT *ctmp; + + if ((ctmp = (CLIENT *) malloc(sizeof(CLIENT))) == NULL) { + syslog(LOG_ERR, "NewClient: out of memory (%s)", + GetEtherAddr(addr)); + return(NULL); + } + + memset(ctmp, 0, sizeof(CLIENT)); + memmove(&ctmp->addr[0], addr, RMP_ADDRLEN); + return(ctmp); +} + +/* +** FreeClient -- free linked list of Clients. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - All malloc'd memory associated with the linked list of +** CLIENTS will be free'd; `Clients' will be set to NULL. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +void +FreeClients(void) +{ + CLIENT *ctmp; + + while (Clients != NULL) { + ctmp = Clients; + Clients = Clients->next; + FreeClient(ctmp); + } +} + +/* +** NewStr -- allocate memory for a character array. +** +** Parameters: +** str - null terminated character array. +** +** Returns: +** Ptr to new character array or NULL if we ran out of memory. +** +** Side Effects: +** - Memory will be malloc'd for the new character array. +** - If malloc() fails, a log message will be generated. +*/ +char * +NewStr(char *str) +{ + char *stmp; + + if ((stmp = (char *)malloc((unsigned) (strlen(str)+1))) == NULL) { + syslog(LOG_ERR, "NewStr: out of memory (%s)", str); + return(NULL); + } + + (void) strcpy(stmp, str); + return(stmp); +} + +/* +** To save time, NewConn and FreeConn maintain a cache of one RMPCONN +** in `LastFree' (defined below). +*/ + +static RMPCONN *LastFree = NULL; + +/* +** NewConn -- allocate memory for a new RMPCONN connection. +** +** Parameters: +** rconn - initialization template for new connection. +** +** Returns: +** Ptr to new RMPCONN or NULL if we ran out of memory. +** +** Side Effects: +** - Memory may be malloc'd for the new RMPCONN (if not cached). +** - If malloc() fails, a log message will be generated. +*/ +RMPCONN * +NewConn(RMPCONN *rconn) +{ + RMPCONN *rtmp; + + if (LastFree == NULL) { /* nothing cached; make a new one */ + if ((rtmp = (RMPCONN *) malloc(sizeof(RMPCONN))) == NULL) { + syslog(LOG_ERR, "NewConn: out of memory (%s)", + EnetStr(rconn)); + return(NULL); + } + } else { /* use the cached RMPCONN */ + rtmp = LastFree; + LastFree = NULL; + } + + /* + * Copy template into `rtmp', init file descriptor to `-1' and + * set ptr to next elem NULL. + */ + memmove((char *)rtmp, (char *)rconn, sizeof(RMPCONN)); + rtmp->bootfd = -1; + rtmp->next = NULL; + + return(rtmp); +} + +/* +** FreeConn -- Free memory associated with an RMPCONN connection. +** +** Parameters: +** rtmp - ptr to RMPCONN to be free'd. +** +** Returns: +** Nothing. +** +** Side Effects: +** - Memory associated with `rtmp' may be free'd (or cached). +** - File desc associated with `rtmp->bootfd' will be closed. +*/ +void +FreeConn(RMPCONN *rtmp) +{ + /* + * If the file descriptor is in use, close the file. + */ + if (rtmp->bootfd >= 0) { + (void) close(rtmp->bootfd); + rtmp->bootfd = -1; + } + + if (LastFree == NULL) /* cache for next time */ + rtmp = LastFree; + else /* already one cached; free this one */ + free((char *)rtmp); +} + +/* +** FreeConns -- free linked list of RMPCONN connections. +** +** Parameters: +** None. +** +** Returns: +** Nothing. +** +** Side Effects: +** - All malloc'd memory associated with the linked list of +** connections will be free'd; `RmpConns' will be set to NULL. +** - If LastFree is != NULL, it too will be free'd & NULL'd. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +void +FreeConns(void) +{ + RMPCONN *rtmp; + + while (RmpConns != NULL) { + rtmp = RmpConns; + RmpConns = RmpConns->next; + FreeConn(rtmp); + } + + if (LastFree != NULL) { + free((char *)LastFree); + LastFree = NULL; + } +} + +/* +** AddConn -- Add a connection to the linked list of connections. +** +** Parameters: +** rconn - connection to be added. +** +** Returns: +** Nothing. +** +** Side Effects: +** - RmpConn will point to new connection. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +void +AddConn(RMPCONN *rconn) +{ + if (RmpConns != NULL) + rconn->next = RmpConns; + RmpConns = rconn; +} + +/* +** FindConn -- Find a connection in the linked list of connections. +** +** We use the RMP (Ethernet) address as the basis for determining +** if this is the same connection. According to the Remote Maint +** Protocol, we can only have one connection with any machine. +** +** Parameters: +** rconn - connection to be found. +** +** Returns: +** Matching connection from linked list or NULL if not found. +** +** Side Effects: +** None. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +RMPCONN * +FindConn(RMPCONN *rconn) +{ + RMPCONN *rtmp; + + for (rtmp = RmpConns; rtmp != NULL; rtmp = rtmp->next) + if (bcmp((char *)&rconn->rmp.hp_hdr.saddr[0], + (char *)&rtmp->rmp.hp_hdr.saddr[0], RMP_ADDRLEN) == 0) + break; + + return(rtmp); +} + +/* +** RemoveConn -- Remove a connection from the linked list of connections. +** +** Parameters: +** rconn - connection to be removed. +** +** Returns: +** Nothing. +** +** Side Effects: +** - If found, an RMPCONN will cease to exist and it will +** be removed from the linked list. +** +** Warnings: +** - This routine must be called with SIGHUP blocked. +*/ +void +RemoveConn(RMPCONN *rconn) +{ + RMPCONN *thisrconn, *lastrconn; + + if (RmpConns == rconn) { /* easy case */ + RmpConns = RmpConns->next; + FreeConn(rconn); + } else { /* must traverse linked list */ + lastrconn = RmpConns; /* set back ptr */ + thisrconn = lastrconn->next; /* set current ptr */ + while (thisrconn != NULL) { + if (rconn == thisrconn) { /* found it */ + lastrconn->next = thisrconn->next; + FreeConn(thisrconn); + break; + } + lastrconn = thisrconn; + thisrconn = thisrconn->next; + } + } +} diff --git a/libexec/revnetgroup/Makefile b/libexec/revnetgroup/Makefile new file mode 100644 index 0000000..d3b36fa --- /dev/null +++ b/libexec/revnetgroup/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= revnetgroup +SRCS= revnetgroup.c hash.c parse_netgroup.c + +MAN= revnetgroup.8 + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/libexec/revnetgroup/hash.c b/libexec/revnetgroup/hash.c new file mode 100644 index 0000000..1180080 --- /dev/null +++ b/libexec/revnetgroup/hash.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include "hash.h" + +/* + * This hash function is stolen directly from the + * Berkeley DB package. It already exists inside libc, but + * it's declared static which prevents us from calling it + * from here. + */ +/* + * OZ's original sdbm hash + */ +u_int32_t +hash(const void *keyarg, size_t len) +{ + const u_char *key; + size_t loop; + u_int32_t h; + +#define HASHC h = *key++ + 65599 * h + + h = 0; + key = keyarg; + if (len > 0) { + loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) { + case 0: + do { + HASHC; + /* FALLTHROUGH */ + case 7: + HASHC; + /* FALLTHROUGH */ + case 6: + HASHC; + /* FALLTHROUGH */ + case 5: + HASHC; + /* FALLTHROUGH */ + case 4: + HASHC; + /* FALLTHROUGH */ + case 3: + HASHC; + /* FALLTHROUGH */ + case 2: + HASHC; + /* FALLTHROUGH */ + case 1: + HASHC; + } while (--loop); + } + } + return (h); +} + +/* + * Generate a hash value for a given key (character string). + * We mask off all but the lower 8 bits since our table array + * can only hold 256 elements. + */ +u_int32_t +hashkey(char *key) +{ + + if (key == NULL) + return (-1); + return(hash((void *)key, strlen(key)) & HASH_MASK); +} + +/* Find an entry in the hash table (may be hanging off a linked list). */ +char * +lookup(struct group_entry *table[], char *key) +{ + struct group_entry *cur; + + cur = table[hashkey(key)]; + + while (cur) { + if (!strcmp(cur->key, key)) + return(cur->data); + cur = cur->next; + } + + return(NULL); +} + +/* + * Store an entry in the main netgroup hash table. Here's how this + * works: the table can only be so big when we initialize it (TABLESIZE) + * but the number of netgroups in the /etc/netgroup file could easily be + * much larger than the table. Since our hash values are adjusted to + * never be greater than TABLESIZE too, this means it won't be long before + * we find ourselves with two keys that hash to the same value. + * + * One way to deal with this is to malloc(2) a second table and start + * doing indirection, but this is a pain in the butt and it's not worth + * going to all that trouble for a dinky little program like this. Instead, + * we turn each table entry into a linked list and simply link keys + * with the same hash value together at the same index location within + * the table. + * + * That's a lot of comment for such a small piece of code, isn't it. + */ +void +store(struct group_entry *table[], char *key, char *data) +{ + struct group_entry *new; + u_int32_t i; + + i = hashkey(key); + + new = (struct group_entry *)malloc(sizeof(struct group_entry)); + new->key = strdup(key); + new->data = strdup(data); + new->next = table[i]; + table[i] = new; + + return; +} + +/* + * Store a group member entry and/or update its grouplist. This is + * a bit more complicated than the previous function since we have to + * maintain not only the hash table of group members, each group member + * structure also has a linked list of groups hung off it. If handed + * a member name that we haven't encountered before, we have to do + * two things: add that member to the table (possibly hanging them + * off the end of a linked list, as above), and add a group name to + * the member's grouplist list. If we're handed a name that already has + * an entry in the table, then we just have to do one thing, which is + * to update its grouplist. + */ +void +mstore(struct member_entry *table[], char *key, char *data, char *domain) +{ + struct member_entry *cur, *new; + struct grouplist *tmp; + u_int32_t i; + + i = hashkey(key); + cur = table[i]; + + tmp = (struct grouplist *)malloc(sizeof(struct grouplist)); + tmp->groupname = strdup(data); + tmp->next = NULL; + + /* Check if all we have to do is insert a new groupname. */ + while (cur) { + if (!strcmp(cur->key, key)) { + tmp->next = cur->groups; + cur->groups = tmp; + return; + } + cur = cur->next; + } + + /* Didn't find a match -- add the whole mess to the table. */ + new = (struct member_entry *)malloc(sizeof(struct member_entry)); + new->key = strdup(key); + new->domain = domain ? strdup(domain) : "*"; + new->groups = tmp; + new->next = table[i]; + table[i] = new; + + return; +} diff --git a/libexec/revnetgroup/hash.h b/libexec/revnetgroup/hash.h new file mode 100644 index 0000000..2485901 --- /dev/null +++ b/libexec/revnetgroup/hash.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* Groupname entry hung off a member_entry node. */ +struct grouplist { + char *groupname; + struct grouplist *next; +}; + +/* Entry in the cooked member list hash table. */ +struct member_entry { + char *key; + char *domain; + struct grouplist *groups; + struct member_entry *next; +}; + +/* Entry in the raw netgroup table. */ +struct group_entry { + char *key; + char *data; + struct group_entry *next; +}; + +/* Table size (chosen arbitrarily). Not too big, not too small. */ +#define TABLESIZE 256 +#define HASH_MASK 0x000000FF + +#define LINSIZ 1024 * 10 + +extern void store(struct group_entry ** , char *, char *); +extern void mstore(struct member_entry ** , char *, char *, char *); +extern char *lookup(struct group_entry **, char *); +extern void __endnetgrent(void); +extern void __setnetgrent(char *); +extern int __getnetgrent(char **, char **, char **); diff --git a/libexec/revnetgroup/parse_netgroup.c b/libexec/revnetgroup/parse_netgroup.c new file mode 100644 index 0000000..b891481 --- /dev/null +++ b/libexec/revnetgroup/parse_netgroup.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Rick Macklem at The University of Guelph. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * This is a specially hacked-up version of getnetgrent.c used to parse + * data from the stored hash table of netgroup info rather than from a + * file. It's used mainly for the parse_netgroup() function. All the YP + * stuff and file support has been stripped out since it isn't needed. + */ + +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include "hash.h" + +/* + * Static Variables and functions used by setnetgrent(), getnetgrent() and + * __endnetgrent(). + * There are two linked lists: + * - linelist is just used by setnetgrent() to parse the net group file via. + * parse_netgrp() + * - netgrp is the list of entries for the current netgroup + */ +struct linelist { + struct linelist *l_next; /* Chain ptr. */ + int l_parsed; /* Flag for cycles */ + char *l_groupname; /* Name of netgroup */ + char *l_line; /* Netgroup entrie(s) to be parsed */ +}; + +struct netgrp { + struct netgrp *ng_next; /* Chain ptr */ + char *ng_str[3]; /* Field pointers, see below */ +}; +#define NG_HOST 0 /* Host name */ +#define NG_USER 1 /* User name */ +#define NG_DOM 2 /* and Domain name */ + +static struct linelist *linehead = (struct linelist *)0; +static struct netgrp *nextgrp = (struct netgrp *)0; +static struct { + struct netgrp *gr; + char *grname; +} grouphead = { + (struct netgrp *)0, + (char *)0, +}; +static int parse_netgrp(char *group); +static struct linelist *read_for_group(char *group); +extern struct group_entry *gtable[]; + +/* + * setnetgrent() + * Parse the netgroup file looking for the netgroup and build the list + * of netgrp structures. Let parse_netgrp() and read_for_group() do + * most of the work. + */ +void +__setnetgrent(char *group) +{ + /* Sanity check */ + + if (group == NULL || !strlen(group)) + return; + + if (grouphead.gr == (struct netgrp *)0 || + strcmp(group, grouphead.grname)) { + __endnetgrent(); + if (parse_netgrp(group)) + __endnetgrent(); + else { + grouphead.grname = (char *) + malloc(strlen(group) + 1); + strcpy(grouphead.grname, group); + } + } + nextgrp = grouphead.gr; +} + +/* + * Get the next netgroup off the list. + */ +int +__getnetgrent(char **hostp, char **userp, char **domp) +{ + if (nextgrp) { + *hostp = nextgrp->ng_str[NG_HOST]; + *userp = nextgrp->ng_str[NG_USER]; + *domp = nextgrp->ng_str[NG_DOM]; + nextgrp = nextgrp->ng_next; + return (1); + } + return (0); +} + +/* + * __endnetgrent() - cleanup + */ +void +__endnetgrent(void) +{ + struct linelist *lp, *olp; + struct netgrp *gp, *ogp; + + lp = linehead; + while (lp) { + olp = lp; + lp = lp->l_next; + free(olp->l_groupname); + free(olp->l_line); + free((char *)olp); + } + linehead = (struct linelist *)0; + if (grouphead.grname) { + free(grouphead.grname); + grouphead.grname = (char *)0; + } + gp = grouphead.gr; + while (gp) { + ogp = gp; + gp = gp->ng_next; + if (ogp->ng_str[NG_HOST]) + free(ogp->ng_str[NG_HOST]); + if (ogp->ng_str[NG_USER]) + free(ogp->ng_str[NG_USER]); + if (ogp->ng_str[NG_DOM]) + free(ogp->ng_str[NG_DOM]); + free((char *)ogp); + } + grouphead.gr = (struct netgrp *)0; +} + +/* + * Parse the netgroup file setting up the linked lists. + */ +static int +parse_netgrp(char *group) +{ + char *spos, *epos; + int len, strpos; +#ifdef DEBUG + int fields; +#endif + char *pos, *gpos; + struct netgrp *grp; + struct linelist *lp = linehead; + + /* + * First, see if the line has already been read in. + */ + while (lp) { + if (!strcmp(group, lp->l_groupname)) + break; + lp = lp->l_next; + } + if (lp == (struct linelist *)0 && + (lp = read_for_group(group)) == (struct linelist *)0) + return (1); + if (lp->l_parsed) { +#ifdef DEBUG + /* + * This error message is largely superfluous since the + * code handles the error condition successfully, and + * spewing it out from inside libc can actually hose + * certain programs. + */ + warnx("cycle in netgroup %s", lp->l_groupname); +#endif + return (1); + } else + lp->l_parsed = 1; + pos = lp->l_line; + /* Watch for null pointer dereferences, dammit! */ + while (pos != NULL && *pos != '\0') { + if (*pos == '(') { + grp = (struct netgrp *)malloc(sizeof (struct netgrp)); + bzero((char *)grp, sizeof (struct netgrp)); + grp->ng_next = grouphead.gr; + grouphead.gr = grp; + pos++; + gpos = strsep(&pos, ")"); +#ifdef DEBUG + fields = 0; +#endif + for (strpos = 0; strpos < 3; strpos++) { + if ((spos = strsep(&gpos, ","))) { +#ifdef DEBUG + fields++; +#endif + while (*spos == ' ' || *spos == '\t') + spos++; + if ((epos = strpbrk(spos, " \t"))) { + *epos = '\0'; + len = epos - spos; + } else + len = strlen(spos); + if (len > 0) { + grp->ng_str[strpos] = (char *) + malloc(len + 1); + bcopy(spos, grp->ng_str[strpos], + len + 1); + } + } else { + /* + * All other systems I've tested + * return NULL for empty netgroup + * fields. It's up to user programs + * to handle the NULLs appropriately. + */ + grp->ng_str[strpos] = NULL; + } + } +#ifdef DEBUG + /* + * Note: on other platforms, malformed netgroup + * entries are not normally flagged. While we + * can catch bad entries and report them, we should + * stay silent by default for compatibility's sake. + */ + if (fields < 3) + warnx("bad entry (%s%s%s%s%s) in netgroup \"%s\"", + grp->ng_str[NG_HOST] == NULL ? "" : grp->ng_str[NG_HOST], + grp->ng_str[NG_USER] == NULL ? "" : ",", + grp->ng_str[NG_USER] == NULL ? "" : grp->ng_str[NG_USER], + grp->ng_str[NG_DOM] == NULL ? "" : ",", + grp->ng_str[NG_DOM] == NULL ? "" : grp->ng_str[NG_DOM], + lp->l_groupname); +#endif + } else { + spos = strsep(&pos, ", \t"); + if (parse_netgrp(spos)) + continue; + } + /* Watch for null pointer dereferences, dammit! */ + if (pos != NULL) + while (*pos == ' ' || *pos == ',' || *pos == '\t') + pos++; + } + return (0); +} + +/* + * Read the netgroup file and save lines until the line for the netgroup + * is found. Return 1 if eof is encountered. + */ +static struct linelist * +read_for_group(char *group) +{ + char *pos, *spos, *linep = NULL, *olinep = NULL; + int len, olen; + int cont; + struct linelist *lp; + char line[LINSIZ + 1]; + char *data = NULL; + + data = lookup (gtable, group); + sprintf(line, "%s %s", group, data); + pos = (char *)&line; +#ifdef CANT_HAPPEN + if (*pos == '#') + continue; +#endif + while (*pos == ' ' || *pos == '\t') + pos++; + spos = pos; + while (*pos != ' ' && *pos != '\t' && *pos != '\n' && + *pos != '\0') + pos++; + len = pos - spos; + while (*pos == ' ' || *pos == '\t') + pos++; + if (*pos != '\n' && *pos != '\0') { + lp = (struct linelist *)malloc(sizeof (*lp)); + lp->l_parsed = 0; + lp->l_groupname = (char *)malloc(len + 1); + bcopy(spos, lp->l_groupname, len); + *(lp->l_groupname + len) = '\0'; + len = strlen(pos); + olen = 0; + /* + * Loop around handling line continuations. + */ + do { + if (*(pos + len - 1) == '\n') + len--; + if (*(pos + len - 1) == '\\') { + len--; + cont = 1; + } else + cont = 0; + if (len > 0) { + linep = (char *)malloc(olen + len + 1); + if (olen > 0) { + bcopy(olinep, linep, olen); + free(olinep); + } + bcopy(pos, linep + olen, len); + olen += len; + *(linep + olen) = '\0'; + olinep = linep; + } +#ifdef CANT_HAPPEN + if (cont) { + if (fgets(line, LINSIZ, netf)) { + pos = line; + len = strlen(pos); + } else + cont = 0; + } +#endif + } while (cont); + lp->l_line = linep; + lp->l_next = linehead; + linehead = lp; +#ifdef CANT_HAPPEN + /* + * If this is the one we wanted, we are done. + */ + if (!strcmp(lp->l_groupname, group)) +#endif + return (lp); + } + return ((struct linelist *)0); +} diff --git a/libexec/revnetgroup/revnetgroup.8 b/libexec/revnetgroup/revnetgroup.8 new file mode 100644 index 0000000..a17d966 --- /dev/null +++ b/libexec/revnetgroup/revnetgroup.8 @@ -0,0 +1,159 @@ +.\" Copyright (c) 1995 +.\" Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Bill Paul. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd October 24, 1995 +.Dt REVNETGROUP 8 +.Os +.Sh NAME +.Nm revnetgroup +.Nd "generate reverse netgroup data" +.Sh SYNOPSIS +.Nm +.Fl u | h +.Op Fl f Ar netgroup_file +.Sh DESCRIPTION +The +.Nm +utility processes the contents of a file in +.Xr netgroup 5 +format into what is called +.Pa reverse netgroup +form. +That is, where the original file shows +netgroup memberships in terms of which members reside in a particular +group, the reverse netgroup format specifies what groups are associated +with a particular member. +This information is used to generate the +.Pa netgroup.byuser +and +.Pa netgroup.byhost +.Tn NIS +maps. +These reverse netgroup maps are used to help speed up +netgroup lookups, particularly for the +.Fn innetgr +library function. +.Pp +For example, the standard +.Pa /etc/netgroup +file may list a netgroup and a list of its members. +Here, the +netgroup is considered the +.Em key +and the member names are the +.Em data . +By contrast, the reverse +.Pa netgroup.byuser +database lists each unique +member as the key and the netgroups to which the members belong become +the data. +Separate databases are created to hold information pertaining +to users and hosts; this allows netgroup username lookups +and netgroup hostname lookups to be performed using independent keyspaces. +.Pp +By constructing these reverse netgroup databases (and the corresponding +.Tn NIS +maps) in advance, the +.Xr getnetgrent 3 +library functions are spared from having to work out the dependencies +themselves on the fly. +This is important on networks with large numbers +of users and hosts, since it can take a considerable amount of time +to process very large netgroup databases. +.Pp +The +.Nm +utility prints its results on the standard output. +It is usually called +only by +.Pa /var/yp/Makefile +when rebuilding the +.Tn NIS +netgroup maps. +.Sh OPTIONS +The +.Nm +utility supports the following options: +.Bl -tag -width indent +.It Fl u +Generate +.Pa netgroup.byuser +output; only username information in the +original netgroup file is processed. +.It Fl h +Generate +.Pa netgroup.byhost +output; only hostname information in the +original netgroup file is processed. +(Note at least one of the +.Fl u +or +.Fl h +flags must be specified.) +.It Op Fl f Ar netgroup_file +The +.Nm +utility uses +.Pa /etc/netgroup +as its default input file. +The +.Fl f +flag allows the user to specify an alternate input file. +Specifying ``-'' +as the input file causes +.Nm +to read from the standard input. +.El +.Sh FILES +.Bl -tag -width /var/yp/Makefile -compact +.It Pa /var/yp/Makefile +the Makefile that calls +.Nm yp_mkdb +and +.Nm +to build the +.Tn NIS +databases +.It Pa /etc/netgroup +the default netgroup database file. +This file is most often found +only on the +.Tn NIS +master server +.El +.Sh SEE ALSO +.Xr getnetgrent 3 , +.Xr netgroup 5 , +.Xr yp 8 , +.Xr yp_mkdb 8 +.Sh AUTHORS +.An Bill Paul Aq Mt wpaul@ctr.columbia.edu diff --git a/libexec/revnetgroup/revnetgroup.c b/libexec/revnetgroup/revnetgroup.c new file mode 100644 index 0000000..505c482 --- /dev/null +++ b/libexec/revnetgroup/revnetgroup.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * reverse netgroup map generator program + * + * Written by Bill Paul <wpaul@ctr.columbia.edu> + * Center for Telecommunications Research + * Columbia University, New York City + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "hash.h" + +/* Default location of netgroup file. */ +char *netgroup = "/etc/netgroup"; + +/* Stored hash table version of 'forward' netgroup database. */ +struct group_entry *gtable[TABLESIZE]; + +/* + * Stored hash table of 'reverse' netgroup member database + * which we will construct. + */ +struct member_entry *mtable[TABLESIZE]; + +static void +usage(void) +{ + fprintf (stderr,"usage: revnetgroup -u | -h [-f netgroup_file]\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp; + char readbuf[LINSIZ]; + struct group_entry *gcur; + struct member_entry *mcur; + char *host, *user, *domain; + int ch; + char *key = NULL, *data = NULL; + int hosts = -1, i; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, "uhf:")) != -1) { + switch(ch) { + case 'u': + if (hosts != -1) { + warnx("please use only one of -u or -h"); + usage(); + } + hosts = 0; + break; + case 'h': + if (hosts != -1) { + warnx("please use only one of -u or -h"); + usage(); + } + hosts = 1; + break; + case 'f': + netgroup = optarg; + break; + default: + usage(); + break; + } + } + + if (hosts == -1) + usage(); + + if (strcmp(netgroup, "-")) { + if ((fp = fopen(netgroup, "r")) == NULL) { + err(1, "%s", netgroup); + } + } else { + fp = stdin; + } + + /* Stuff all the netgroup names and members into a hash table. */ + while (fgets(readbuf, LINSIZ, fp)) { + if (readbuf[0] == '#') + continue; + /* handle backslash line continuations */ + while(readbuf[strlen(readbuf) - 2] == '\\') { + fgets((char *)&readbuf[strlen(readbuf) - 2], + sizeof(readbuf) - strlen(readbuf), fp); + } + data = NULL; + if ((data = (char *)(strpbrk(readbuf, " \t") + 1)) < (char *)2) + continue; + key = (char *)&readbuf; + *(data - 1) = '\0'; + store(gtable, key, data); + } + + fclose(fp); + + /* + * Find all members of each netgroup and keep track of which + * group they belong to. + */ + for (i = 0; i < TABLESIZE; i++) { + gcur = gtable[i]; + while(gcur) { + __setnetgrent(gcur->key); + while(__getnetgrent(&host, &user, &domain) != 0) { + if (hosts ? host && strcmp(host,"-") : user && strcmp(user, "-")) + mstore(mtable, hosts ? host : user, gcur->key, domain); + } + gcur = gcur->next; + } + } + + /* Release resources used by the netgroup parser code. */ + __endnetgrent(); + + /* Spew out the results. */ + for (i = 0; i < TABLESIZE; i++) { + mcur = mtable[i]; + while(mcur) { + struct grouplist *tmp; + printf ("%s.%s\t", mcur->key, mcur->domain); + tmp = mcur->groups; + while(tmp) { + printf ("%s", tmp->groupname); + tmp = tmp->next; + if (tmp) + printf(","); + } + mcur = mcur->next; + printf ("\n"); + } + } + + /* Let the OS free all our resources. */ + exit(0); +} diff --git a/libexec/rlogind/Makefile b/libexec/rlogind/Makefile new file mode 100644 index 0000000..de0fb20 --- /dev/null +++ b/libexec/rlogind/Makefile @@ -0,0 +1,16 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +.include <src.opts.mk> + +PROG= rlogind +MAN= rlogind.8 +DPADD= ${LIBUTIL} +LDADD= -lutil +WARNS?= 2 + +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+= -DINET6 +.endif + +.include <bsd.prog.mk> diff --git a/libexec/rlogind/rlogind.8 b/libexec/rlogind/rlogind.8 new file mode 100644 index 0000000..209cec9 --- /dev/null +++ b/libexec/rlogind/rlogind.8 @@ -0,0 +1,193 @@ +.\" Copyright (c) 1983, 1989, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)rlogind.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" +.Dd February 9, 2005 +.Dt RLOGIND 8 +.Os +.Sh NAME +.Nm rlogind +.Nd remote login server +.Sh SYNOPSIS +.Nm +.Op Fl Daln +.Sh DESCRIPTION +The +.Nm +utility is the server for the +.Xr rlogin 1 +program. +The server provides a remote login facility +with authentication based on privileged port numbers from trusted hosts. +.Pp +Options supported by +.Nm : +.Bl -tag -width indent +.It Fl D +Set TCP_NODELAY socket option. +This improves responsiveness at the expense of +some additional network traffic. +.It Fl a +Ask hostname for verification. +.It Fl l +Prevent any authentication based on the user's +.Dq Pa .rhosts +file, unless the user is logging in as the superuser. +.It Fl n +Disable keep-alive messages. +.El +.Pp +The +.Nm +utility listens for service requests at the port indicated in +the +.Dq login +service specification; see +.Xr services 5 . +When a service request is received the following protocol +is initiated: +.Bl -enum +.It +The server checks the client's source port. +If the port is not in the range 512-1023, the server +aborts the connection. +.It +The server checks the client's source address +and requests the corresponding host name (see +.Xr gethostbyaddr 3 , +.Xr hosts 5 +and +.Xr named 8 ) . +If the hostname cannot be determined, +the dot-notation representation of the host address is used. +If the hostname is in the same domain as the server (according to +the last two components of the domain name), +or if the +.Fl a +option is given, +the addresses for the hostname are requested, +verifying that the name and address correspond. +Normal authentication is bypassed if the address verification fails. +.El +.Pp +Once the source port and address have been checked, +.Nm +proceeds with the authentication process described in +.Xr rshd 8 . +It then allocates a pseudo terminal (see +.Xr pty 4 ) , +and manipulates file descriptors so that the slave +half of the pseudo terminal becomes the +.Em stdin , +.Em stdout , +and +.Em stderr +for a login process. +The login process is an instance of the +.Xr login 1 +program, invoked with the +.Fl f +option if authentication has succeeded. +If automatic authentication fails, the user is +prompted to log in as if on a standard terminal line. +.Pp +The parent of the login process manipulates the master side of +the pseudo terminal, operating as an intermediary +between the login process and the client instance of the +.Xr rlogin 1 +program. +In normal operation, the packet protocol described +in +.Xr pty 4 +is invoked to provide +.Ql ^S/^Q +type facilities and propagate +interrupt signals to the remote programs. +The login process +propagates the client terminal's baud rate and terminal type, +as found in the environment variable, +.Ev TERM ; +see +.Xr environ 7 . +The screen or window size of the terminal is requested from the client, +and window size changes from the client are propagated to the pseudo terminal. +.Pp +Transport-level keepalive messages are enabled unless the +.Fl n +option is present. +The use of keepalive messages allows sessions to be timed out +if the client crashes or becomes unreachable. +.Sh FILES +.Bl -tag -width /etc/hostsxxxxxxxx -compact +.It Pa /etc/hosts +.It Pa /etc/hosts.equiv +.It Ev $HOME Ns Pa /.rhosts +.It Pa /var/run/nologin +.El +.Sh DIAGNOSTICS +All initial diagnostic messages are indicated +by a leading byte with a value of 1, +after which any network connections are closed. +If there are no errors before +.Xr login 1 +is invoked, a null byte is returned as in indication of success. +.Bl -tag -width Ds +.It Sy Try again. +A +.Xr fork 2 +by the server failed. +.El +.Sh SEE ALSO +.Xr login 1 , +.Xr ruserok 3 , +.Xr hosts 5 , +.Xr hosts.equiv 5 , +.Xr login.conf 5 , +.Xr nologin 5 , +.Xr services 5 , +.Xr rshd 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . +.Pp +IPv6 support was added by WIDE/KAME project. +.Sh BUGS +The authentication procedure used here assumes the integrity +of each client machine and the connecting medium. +This is +insecure, but is useful in an +.Dq open +environment. +.Pp +A facility to allow all data exchanges to be encrypted should be +present. +.Pp +A more extensible protocol should be used. diff --git a/libexec/rlogind/rlogind.c b/libexec/rlogind/rlogind.c new file mode 100644 index 0000000..d64d7bf --- /dev/null +++ b/libexec/rlogind/rlogind.c @@ -0,0 +1,585 @@ +/*- + * Copyright (c) 1983, 1988, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2002 Networks Associates Technology, Inc. + * All rights reserved. + * + * Portions of this software were developed for the FreeBSD Project by + * ThinkSec AS and NAI Labs, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1983, 1988, 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static const char sccsid[] = "@(#)rlogind.c 8.1 (Berkeley) 6/4/93"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * remote login server: + * \0 + * remuser\0 + * locuser\0 + * terminal_type/speed\0 + * data + */ + +#define FD_SETSIZE 16 /* don't need many bits for select */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <termios.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <errno.h> +#include <libutil.h> +#include <paths.h> +#include <pwd.h> +#include <syslog.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +#ifndef TIOCPKT_WINDOW +#define TIOCPKT_WINDOW 0x80 +#endif + +#define ARGSTR "Daln" + +char *env[2]; +#define NMAX 30 +char lusername[NMAX+1], rusername[NMAX+1]; +static char term[64] = "TERM="; +#define ENVSIZE (sizeof("TERM=")-1) /* skip null for concatenation */ +int keepalive = 1; +int check_all = 0; +int no_delay; + +struct passwd *pwd; + +union sockunion { + struct sockinet { + u_char si_len; + u_char si_family; + u_short si_port; + } su_si; + struct sockaddr_in su_sin; + struct sockaddr_in6 su_sin6; +}; +#define su_len su_si.si_len +#define su_family su_si.si_family +#define su_port su_si.si_port + +void doit(int, union sockunion *); +int control(int, char *, int); +void protocol(int, int); +void cleanup(int); +void fatal(int, char *, int); +int do_rlogin(union sockunion *); +void getstr(char *, int, char *); +void setup_term(int); +int do_krb_login(struct sockaddr_in *); +void usage(void); + + +int +main(int argc, char *argv[]) +{ + extern int __check_rhosts_file; + union sockunion from; + socklen_t fromlen; + int ch, on; + + openlog("rlogind", LOG_PID | LOG_CONS, LOG_AUTH); + + opterr = 0; + while ((ch = getopt(argc, argv, ARGSTR)) != -1) + switch (ch) { + case 'D': + no_delay = 1; + break; + case 'a': + check_all = 1; + break; + case 'l': + __check_rhosts_file = 0; + break; + case 'n': + keepalive = 0; + break; + case '?': + default: + usage(); + break; + } + argc -= optind; + argv += optind; + + fromlen = sizeof (from); + if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) { + syslog(LOG_ERR,"Can't get peer name of remote host: %m"); + fatal(STDERR_FILENO, "Can't get peer name of remote host", 1); + } + on = 1; + if (keepalive && + setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) + syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); + if (no_delay && + setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m"); + if (from.su_family == AF_INET) + { + on = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); + } + + doit(0, &from); + return 0; +} + +int child; +int netf; +char line[MAXPATHLEN]; +int confirmed; + +struct winsize win = { 0, 0, 0, 0 }; + + +void +doit(int f, union sockunion *fromp) +{ + int master, pid, on = 1; + int authenticated = 0; + char hostname[2 * MAXHOSTNAMELEN + 1]; + char nameinfo[2 * INET6_ADDRSTRLEN + 1]; + char c; + + alarm(60); + read(f, &c, 1); + + if (c != 0) + exit(1); + + alarm(0); + + realhostname_sa(hostname, sizeof(hostname) - 1, + (struct sockaddr *)fromp, fromp->su_len); + /* error check ? */ + fromp->su_port = ntohs((u_short)fromp->su_port); + hostname[sizeof(hostname) - 1] = '\0'; + + { + if ((fromp->su_family != AF_INET +#ifdef INET6 + && fromp->su_family != AF_INET6 +#endif + ) || + fromp->su_port >= IPPORT_RESERVED || + fromp->su_port < IPPORT_RESERVED/2) { + getnameinfo((struct sockaddr *)fromp, + fromp->su_len, + nameinfo, sizeof(nameinfo), NULL, 0, + NI_NUMERICHOST); + /* error check ? */ + syslog(LOG_NOTICE, "Connection from %s on illegal port", + nameinfo); + fatal(f, "Permission denied", 0); + } +#ifdef IP_OPTIONS + if (fromp->su_family == AF_INET) + { + u_char optbuf[BUFSIZ/3]; + socklen_t optsize = sizeof(optbuf); + int ipproto, i; + struct protoent *ip; + + if ((ip = getprotobyname("ip")) != NULL) + ipproto = ip->p_proto; + else + ipproto = IPPROTO_IP; + if (getsockopt(0, ipproto, IP_OPTIONS, (char *)optbuf, + &optsize) == 0 && optsize != 0) { + for (i = 0; i < optsize; ) { + u_char c = optbuf[i]; + if (c == IPOPT_LSRR || c == IPOPT_SSRR) { + syslog(LOG_NOTICE, + "Connection refused from %s with IP option %s", + inet_ntoa(fromp->su_sin.sin_addr), + c == IPOPT_LSRR ? "LSRR" : "SSRR"); + exit(1); + } + if (c == IPOPT_EOL) + break; + i += (c == IPOPT_NOP) ? 1 : optbuf[i+1]; + } + } + } +#endif + if (do_rlogin(fromp) == 0) + authenticated++; + } + if (confirmed == 0) { + write(f, "", 1); + confirmed = 1; /* we sent the null! */ + } + netf = f; + + pid = forkpty(&master, line, NULL, &win); + if (pid < 0) { + if (errno == ENOENT) + fatal(f, "Out of ptys", 0); + else + fatal(f, "Forkpty", 1); + } + if (pid == 0) { + if (f > 2) /* f should always be 0, but... */ + (void) close(f); + setup_term(0); + if (*lusername=='-') { + syslog(LOG_ERR, "tried to pass user \"%s\" to login", + lusername); + fatal(STDERR_FILENO, "invalid user", 0); + } + if (authenticated) { + execl(_PATH_LOGIN, "login", "-p", + "-h", hostname, "-f", lusername, (char *)NULL); + } else + execl(_PATH_LOGIN, "login", "-p", + "-h", hostname, lusername, (char *)NULL); + fatal(STDERR_FILENO, _PATH_LOGIN, 1); + /*NOTREACHED*/ + } + ioctl(f, FIONBIO, &on); + ioctl(master, FIONBIO, &on); + ioctl(master, TIOCPKT, &on); + signal(SIGCHLD, cleanup); + protocol(f, master); + signal(SIGCHLD, SIG_IGN); + cleanup(0); +} + +char magic[2] = { 0377, 0377 }; +char oobdata[] = {TIOCPKT_WINDOW}; + +/* + * Handle a "control" request (signaled by magic being present) + * in the data stream. For now, we are only willing to handle + * window size changes. + */ +int +control(int pty, char *cp, int n) +{ + struct winsize w; + + if (n < 4 + (int)sizeof(w) || cp[2] != 's' || cp[3] != 's') + return (0); + oobdata[0] &= ~TIOCPKT_WINDOW; /* we know he heard */ + bcopy(cp+4, (char *)&w, sizeof(w)); + w.ws_row = ntohs(w.ws_row); + w.ws_col = ntohs(w.ws_col); + w.ws_xpixel = ntohs(w.ws_xpixel); + w.ws_ypixel = ntohs(w.ws_ypixel); + (void)ioctl(pty, TIOCSWINSZ, &w); + return (4+sizeof (w)); +} + +/* + * rlogin "protocol" machine. + */ +void +protocol(int f, int p) +{ + char pibuf[1024+1], fibuf[1024], *pbp = NULL, *fbp = NULL; + int pcc = 0, fcc = 0; + int cc, nfd, n; + char cntl; + + /* + * Must ignore SIGTTOU, otherwise we'll stop + * when we try and set slave pty's window shape + * (our controlling tty is the master pty). + */ + (void) signal(SIGTTOU, SIG_IGN); + send(f, oobdata, 1, MSG_OOB); /* indicate new rlogin */ + if (f > p) + nfd = f + 1; + else + nfd = p + 1; + if (nfd > FD_SETSIZE) { + syslog(LOG_ERR, "select mask too small, increase FD_SETSIZE"); + fatal(f, "internal error (select mask too small)", 0); + } + for (;;) { + fd_set ibits, obits, ebits, *omask; + + FD_ZERO(&ebits); + FD_ZERO(&ibits); + FD_ZERO(&obits); + omask = (fd_set *)NULL; + if (fcc) { + FD_SET(p, &obits); + omask = &obits; + } else + FD_SET(f, &ibits); + if (pcc >= 0) { + if (pcc) { + FD_SET(f, &obits); + omask = &obits; + } else + FD_SET(p, &ibits); + } + FD_SET(p, &ebits); + if ((n = select(nfd, &ibits, omask, &ebits, 0)) < 0) { + if (errno == EINTR) + continue; + fatal(f, "select", 1); + } + if (n == 0) { + /* shouldn't happen... */ + sleep(5); + continue; + } +#define pkcontrol(c) ((c)&(TIOCPKT_FLUSHWRITE|TIOCPKT_NOSTOP|TIOCPKT_DOSTOP)) + if (FD_ISSET(p, &ebits)) { + cc = read(p, &cntl, 1); + if (cc == 1 && pkcontrol(cntl)) { + cntl |= oobdata[0]; + send(f, &cntl, 1, MSG_OOB); + if (cntl & TIOCPKT_FLUSHWRITE) { + pcc = 0; + FD_CLR(p, &ibits); + } + } + } + if (FD_ISSET(f, &ibits)) { + fcc = read(f, fibuf, sizeof(fibuf)); + if (fcc < 0 && errno == EWOULDBLOCK) + fcc = 0; + else { + char *cp; + int left, n; + + if (fcc <= 0) + break; + fbp = fibuf; + + top: + for (cp = fibuf; cp < fibuf+fcc-1; cp++) + if (cp[0] == magic[0] && + cp[1] == magic[1]) { + left = fcc - (cp-fibuf); + n = control(p, cp, left); + if (n) { + left -= n; + if (left > 0) + bcopy(cp+n, cp, left); + fcc -= n; + goto top; /* n^2 */ + } + } + FD_SET(p, &obits); /* try write */ + } + } + + if (FD_ISSET(p, &obits) && fcc > 0) { + cc = write(p, fbp, fcc); + if (cc > 0) { + fcc -= cc; + fbp += cc; + } + } + + if (FD_ISSET(p, &ibits)) { + pcc = read(p, pibuf, sizeof (pibuf)); + pbp = pibuf; + if (pcc < 0 && errno == EWOULDBLOCK) + pcc = 0; + else if (pcc <= 0) + break; + else if (pibuf[0] == 0) { + pbp++, pcc--; + FD_SET(f, &obits); /* try write */ + } else { + if (pkcontrol(pibuf[0])) { + pibuf[0] |= oobdata[0]; + send(f, &pibuf[0], 1, MSG_OOB); + } + pcc = 0; + } + } + if ((FD_ISSET(f, &obits)) && pcc > 0) { + cc = write(f, pbp, pcc); + if (cc < 0 && errno == EWOULDBLOCK) { + /* + * This happens when we try write after read + * from p, but some old kernels balk at large + * writes even when select returns true. + */ + if (!FD_ISSET(p, &ibits)) + sleep(5); + continue; + } + if (cc > 0) { + pcc -= cc; + pbp += cc; + } + } + } +} + +void +cleanup(int signo __unused) +{ + + shutdown(netf, SHUT_RDWR); + exit(1); +} + +void +fatal(int f, char *msg, int syserr) +{ + int len; + char buf[BUFSIZ], *bp = buf; + + /* + * Prepend binary one to message if we haven't sent + * the magic null as confirmation. + */ + if (!confirmed) + *bp++ = '\01'; /* error indicator */ + if (syserr) + len = snprintf(bp, sizeof(buf), "rlogind: %s: %s.\r\n", + msg, strerror(errno)); + else + len = snprintf(bp, sizeof(buf), "rlogind: %s.\r\n", msg); + if (len < 0) + len = 0; + (void) write(f, buf, bp + len - buf); + exit(1); +} + +int +do_rlogin(union sockunion *dest) +{ + + getstr(rusername, sizeof(rusername), "remuser too long"); + getstr(lusername, sizeof(lusername), "locuser too long"); + getstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type too long"); + + pwd = getpwnam(lusername); + if (pwd == NULL) + return (-1); + /* XXX why don't we syslog() failure? */ + + return (iruserok_sa(dest, dest->su_len, pwd->pw_uid == 0, rusername, + lusername)); +} + +void +getstr(char *buf, int cnt, char *errmsg) +{ + char c; + + do { + if (read(STDIN_FILENO, &c, 1) != 1) + exit(1); + if (--cnt < 0) + fatal(STDOUT_FILENO, errmsg, 0); + *buf++ = c; + } while (c != 0); +} + +extern char **environ; + +void +setup_term(int fd) +{ + char *cp; + char *speed; + struct termios tt, def; + + cp = strchr(term + ENVSIZE, '/'); +#ifndef notyet + tcgetattr(fd, &tt); + if (cp) { + *cp++ = '\0'; + speed = cp; + cp = strchr(speed, '/'); + if (cp) + *cp++ = '\0'; + cfsetspeed(&tt, atoi(speed)); + } + + cfmakesane(&def); + tt.c_iflag = def.c_iflag; + tt.c_oflag = def.c_oflag; + tt.c_lflag = def.c_lflag; + tcsetattr(fd, TCSAFLUSH, &tt); +#else + if (cp) { + *cp++ = '\0'; + speed = cp; + cp = strchr(speed, '/'); + if (cp) + *cp++ = '\0'; + tcgetattr(fd, &tt); + cfsetspeed(&tt, atoi(speed)); + tcsetattr(fd, TCSAFLUSH, &tt); + } +#endif + + env[0] = term; + env[1] = 0; + environ = env; +} + +void +usage(void) +{ + syslog(LOG_ERR, "usage: rlogind [-" ARGSTR "]"); +} diff --git a/libexec/rpc.rquotad/Makefile b/libexec/rpc.rquotad/Makefile new file mode 100644 index 0000000..95d4415 --- /dev/null +++ b/libexec/rpc.rquotad/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG = rpc.rquotad +SRCS = rquotad.c +MAN = rpc.rquotad.8 + +DPADD= ${LIBRPCSVC} ${LIBUTIL} +LDADD= -lrpcsvc -lutil + +.include <bsd.prog.mk> diff --git a/libexec/rpc.rquotad/rpc.rquotad.8 b/libexec/rpc.rquotad/rpc.rquotad.8 new file mode 100644 index 0000000..4e14da0 --- /dev/null +++ b/libexec/rpc.rquotad/rpc.rquotad.8 @@ -0,0 +1,67 @@ +.\" +.\" Copyright (c) 1994 Theo de Raadt +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Theo de Raadt. +.\" 4. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 22, 1994 +.Dt RPC.RQUOTAD 8 +.Os +.Sh NAME +.Nm rpc.rquotad +.Nd remote quota server +.Sh SYNOPSIS +.Nm /usr/libexec/rpc.rquotad +.Sh DESCRIPTION +The +.Nm +utility is a +.Xr rpc 3 +server which returns quotas for a user of a local file system +which is NFS-mounted onto a remote machine. +The +.Xr quota 1 +utility uses the results to display user quotas for remote file systems. +The +.Nm +utility is normally invoked by +.Xr inetd 8 . +.Pp +The +.Nm +utility uses an +.Tn RPC +protocol defined in +.Pa /usr/include/rpcsvc/rquota.x . +.Sh SEE ALSO +.Xr quota 1 +.Sh BUGS +.Bx 4.4 +and +.Fx +support group quotas but the rquota protocol does not. diff --git a/libexec/rpc.rquotad/rquotad.c b/libexec/rpc.rquotad/rquotad.c new file mode 100644 index 0000000..d9ce06a --- /dev/null +++ b/libexec/rpc.rquotad/rquotad.c @@ -0,0 +1,222 @@ +/* + * by Manuel Bouyer (bouyer@ensta.fr) + * + * There is no copyright, you can use it as you want. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <ufs/ufs/quota.h> +#include <rpc/rpc.h> +#include <rpcsvc/rquota.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <ctype.h> +#include <errno.h> +#include <fstab.h> +#include <grp.h> +#include <libutil.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +static void rquota_service(struct svc_req *request, SVCXPRT *transp); +static void sendquota(struct svc_req *request, SVCXPRT *transp); +static void initfs(void); +static int getfsquota(long id, char *path, struct dqblk *dqblk); + +static struct quotafile **qfa; /* array of qfs */ +static int nqf, szqf; /* number of qfs and size of array */ +static int from_inetd = 1; + +static void +cleanup(int sig) +{ + + (void)sig; + (void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL); + exit(0); +} + +int +main(void) +{ + SVCXPRT *transp; + int ok; + struct sockaddr_storage from; + socklen_t fromlen; + + fromlen = sizeof(from); + if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) + from_inetd = 0; + + if (!from_inetd) { + daemon(0, 0); + (void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL); + (void)signal(SIGINT, cleanup); + (void)signal(SIGTERM, cleanup); + (void)signal(SIGHUP, cleanup); + } + + openlog("rpc.rquotad", LOG_CONS|LOG_PID, LOG_DAEMON); + + /* create and register the service */ + if (from_inetd) { + transp = svc_tli_create(0, NULL, NULL, 0, 0); + if (transp == NULL) { + syslog(LOG_ERR, "couldn't create udp service."); + exit(1); + } + ok = svc_reg(transp, RQUOTAPROG, RQUOTAVERS, + rquota_service, NULL); + } else { + ok = svc_create(rquota_service, + RQUOTAPROG, RQUOTAVERS, "udp"); + } + if (!ok) { + syslog(LOG_ERR, + "unable to register (RQUOTAPROG, RQUOTAVERS, %s)", + from_inetd ? "(inetd)" : "udp"); + exit(1); + } + + initfs(); + svc_run(); + syslog(LOG_ERR, "svc_run returned"); + exit(1); +} + +static void +rquota_service(struct svc_req *request, SVCXPRT *transp) +{ + + switch (request->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL); + break; + case RQUOTAPROC_GETQUOTA: + case RQUOTAPROC_GETACTIVEQUOTA: + sendquota(request, transp); + break; + default: + svcerr_noproc(transp); + break; + } + if (from_inetd) + exit(0); +} + +/* read quota for the specified id, and send it */ +static void +sendquota(struct svc_req *request, SVCXPRT *transp) +{ + struct getquota_args getq_args; + struct getquota_rslt getq_rslt; + struct dqblk dqblk; + struct timeval timev; + int scale; + + bzero(&getq_args, sizeof(getq_args)); + if (!svc_getargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) { + svcerr_decode(transp); + return; + } + if (request->rq_cred.oa_flavor != AUTH_UNIX) { + /* bad auth */ + getq_rslt.status = Q_EPERM; + } else if (!getfsquota(getq_args.gqa_uid, getq_args.gqa_pathp, &dqblk)) { + /* failed, return noquota */ + getq_rslt.status = Q_NOQUOTA; + } else { + gettimeofday(&timev, NULL); + getq_rslt.status = Q_OK; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE; + scale = 1 << flsll(dqblk.dqb_bhardlimit >> 32); + getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize = + DEV_BSIZE * scale; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit = + dqblk.dqb_bhardlimit / scale; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit = + dqblk.dqb_bsoftlimit / scale; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks = + dqblk.dqb_curblocks / scale; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit = + dqblk.dqb_ihardlimit; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit = + dqblk.dqb_isoftlimit; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles = + dqblk.dqb_curinodes; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft = + dqblk.dqb_btime - timev.tv_sec; + getq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft = + dqblk.dqb_itime - timev.tv_sec; + } + if (!svc_sendreply(transp, (xdrproc_t)xdr_getquota_rslt, &getq_rslt)) + svcerr_systemerr(transp); + if (!svc_freeargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) { + syslog(LOG_ERR, "unable to free arguments"); + exit(1); + } +} + +static void +initfs(void) +{ + struct fstab *fs; + + setfsent(); + szqf = 8; + if ((qfa = malloc(szqf * sizeof *qfa)) == NULL) + goto enomem; + while ((fs = getfsent())) { + if (strcmp(fs->fs_vfstype, "ufs")) + continue; + if (nqf >= szqf) { + szqf *= 2; + if ((qfa = reallocf(qfa, szqf * sizeof *qfa)) == NULL) + goto enomem; + } + if ((qfa[nqf] = quota_open(fs, USRQUOTA, O_RDONLY)) == NULL) { + if (errno != EOPNOTSUPP) + goto fserr; + continue; + } + ++nqf; + /* XXX */ + } + endfsent(); + return; +enomem: + syslog(LOG_ERR, "out of memory"); + exit(1); +fserr: + syslog(LOG_ERR, "%s: %s", fs->fs_file, strerror(errno)); + exit(1); +} + +/* + * gets the quotas for id, filesystem path. + * Return 0 if fail, 1 otherwise + */ +static int +getfsquota(long id, char *path, struct dqblk *dqblk) +{ + int i; + + for (i = 0; i < nqf; ++i) + if (quota_check_path(qfa[i], path) == 1) + return (quota_read(qfa[i], dqblk, id) == 0); + return (0); +} diff --git a/libexec/rpc.rstatd/Makefile b/libexec/rpc.rstatd/Makefile new file mode 100644 index 0000000..a42225f --- /dev/null +++ b/libexec/rpc.rstatd/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG = rpc.rstatd +SRCS = rstatd.c rstat_proc.c +MAN = rpc.rstatd.8 + +DPADD= ${LIBRPCSVC} ${LIBUTIL} ${LIBDEVSTAT} ${LIBKVM} +LDADD= -lrpcsvc -lutil -ldevstat -lkvm + +WARNS?= 1 + +.include <bsd.prog.mk> diff --git a/libexec/rpc.rstatd/rpc.rstatd.8 b/libexec/rpc.rstatd/rpc.rstatd.8 new file mode 100644 index 0000000..87512ca --- /dev/null +++ b/libexec/rpc.rstatd/rpc.rstatd.8 @@ -0,0 +1,61 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1985, 1991 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 7, 1993 +.Dt RPC.RSTATD 8 +.Os +.Sh NAME +.Nm rpc.rstatd +.Nd kernel statistics server +.Sh SYNOPSIS +.Nm /usr/libexec/rpc.rstatd +.Sh DESCRIPTION +The +.Nm +utility +is a server which returns performance statistics obtained from the kernel. +These statistics are read using the +.Xr rup 1 +command. +The +.Nm +daemon is normally invoked by +.Xr inetd 8 . +.Pp +The +.Nm +utility uses an +.Tn RPC +protocol defined in +.Pa /usr/include/rpcsvc/rstat.x . +.Sh SEE ALSO +.Xr rup 1 , +.Xr inetd 8 diff --git a/libexec/rpc.rstatd/rstat_proc.c b/libexec/rpc.rstatd/rstat_proc.c new file mode 100644 index 0000000..1b66c64 --- /dev/null +++ b/libexec/rpc.rstatd/rstat_proc.c @@ -0,0 +1,477 @@ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)rpc.rstatd.c 1.1 86/09/25 Copyr 1984 Sun Micro"; +static char sccsid[] = "from: @(#)rstat_proc.c 2.2 88/08/01 4.0 RPCSRC"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif + +/* + * rstat service: built with rstat.x and derived from rpc.rstatd.c + * + * Copyright (c) 1984 by Sun Microsystems, Inc. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/vmmeter.h> +#include <sys/param.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <devstat.h> + +#include <net/if.h> +#include <net/if_mib.h> + +#undef FSHIFT /* Use protocol's shift and scale values */ +#undef FSCALE +#undef if_ipackets +#undef if_ierrors +#undef if_opackets +#undef if_oerrors +#undef if_collisions +#include <rpcsvc/rstat.h> + +int haveadisk(void); +void updatexfers(int, int *); +int stats_service(void); + +extern int from_inetd; +int sincelastreq = 0; /* number of alarms since last request */ +extern int closedown; + +union { + struct stats s1; + struct statsswtch s2; + struct statstime s3; +} stats_all; + +void updatestat(); +static int stat_is_init = 0; + +static int cp_time_xlat[RSTAT_CPUSTATES] = { CP_USER, CP_NICE, CP_SYS, + CP_IDLE }; +static long bsd_cp_time[CPUSTATES]; + + +#ifndef FSCALE +#define FSCALE (1 << 8) +#endif + +void +stat_init(void) +{ + stat_is_init = 1; + alarm(0); + updatestat(); + (void) signal(SIGALRM, updatestat); + alarm(1); +} + +statstime * +rstatproc_stats_3_svc(void *argp, struct svc_req *rqstp) +{ + if (! stat_is_init) + stat_init(); + sincelastreq = 0; + return(&stats_all.s3); +} + +statsswtch * +rstatproc_stats_2_svc(void *argp, struct svc_req *rqstp) +{ + if (! stat_is_init) + stat_init(); + sincelastreq = 0; + return(&stats_all.s2); +} + +stats * +rstatproc_stats_1_svc(void *argp, struct svc_req *rqstp) +{ + if (! stat_is_init) + stat_init(); + sincelastreq = 0; + return(&stats_all.s1); +} + +u_int * +rstatproc_havedisk_3_svc(void *argp, struct svc_req *rqstp) +{ + static u_int have; + + if (! stat_is_init) + stat_init(); + sincelastreq = 0; + have = haveadisk(); + return(&have); +} + +u_int * +rstatproc_havedisk_2_svc(void *argp, struct svc_req *rqstp) +{ + return(rstatproc_havedisk_3_svc(argp, rqstp)); +} + +u_int * +rstatproc_havedisk_1_svc(void *argp, struct svc_req *rqstp) +{ + return(rstatproc_havedisk_3_svc(argp, rqstp)); +} + +void +updatestat(void) +{ + int i, hz; + struct clockinfo clockrate; + struct vmmeter cnt; + struct ifmibdata ifmd; + double avrun[3]; + struct timeval tm, btm; + int mib[6]; + size_t len; + int ifcount; + +#ifdef DEBUG + fprintf(stderr, "entering updatestat\n"); +#endif + if (sincelastreq >= closedown) { +#ifdef DEBUG + fprintf(stderr, "about to closedown\n"); +#endif + if (from_inetd) + exit(0); + else { + stat_is_init = 0; + return; + } + } + sincelastreq++; + + mib[0] = CTL_KERN; + mib[1] = KERN_CLOCKRATE; + len = sizeof clockrate; + if (sysctl(mib, 2, &clockrate, &len, 0, 0) < 0) { + syslog(LOG_ERR, "sysctl(kern.clockrate): %m"); + exit(1); + } + hz = clockrate.hz; + + len = sizeof(bsd_cp_time); + if (sysctlbyname("kern.cp_time", bsd_cp_time, &len, 0, 0) < 0) { + syslog(LOG_ERR, "sysctl(kern.cp_time): %m"); + exit(1); + } + for(i = 0; i < RSTAT_CPUSTATES ; i++) + stats_all.s1.cp_time[i] = bsd_cp_time[cp_time_xlat[i]]; + + (void)getloadavg(avrun, sizeof(avrun) / sizeof(avrun[0])); + + stats_all.s2.avenrun[0] = avrun[0] * FSCALE; + stats_all.s2.avenrun[1] = avrun[1] * FSCALE; + stats_all.s2.avenrun[2] = avrun[2] * FSCALE; + + mib[0] = CTL_KERN; + mib[1] = KERN_BOOTTIME; + len = sizeof btm; + if (sysctl(mib, 2, &btm, &len, 0, 0) < 0) { + syslog(LOG_ERR, "sysctl(kern.boottime): %m"); + exit(1); + } + + stats_all.s2.boottime.tv_sec = btm.tv_sec; + stats_all.s2.boottime.tv_usec = btm.tv_usec; + + +#ifdef DEBUG + fprintf(stderr, "%d %d %d %d\n", stats_all.s1.cp_time[0], + stats_all.s1.cp_time[1], stats_all.s1.cp_time[2], stats_all.s1.cp_time[3]); +#endif + +#define FETCH_CNT(stat, cnt) do { \ + len = sizeof((stat)); \ + if (sysctlbyname("vm.stats." #cnt , &(stat), &len, 0, 0) < 0) { \ + syslog(LOG_ERR, "sysctl(vm.stats." #cnt "): %m"); \ + exit(1); \ + } \ +} while (0) + + FETCH_CNT(stats_all.s1.v_pgpgin, vm.v_vnodepgsin); + FETCH_CNT(stats_all.s1.v_pgpgout, vm.v_vnodepgsout); + FETCH_CNT(stats_all.s1.v_pswpin, vm.v_swappgsin); + FETCH_CNT(stats_all.s1.v_pswpout, vm.v_swappgsout); + FETCH_CNT(stats_all.s1.v_intr, sys.v_intr); + FETCH_CNT(stats_all.s2.v_swtch, sys.v_swtch); + (void)gettimeofday(&tm, NULL); + stats_all.s1.v_intr -= hz*(tm.tv_sec - btm.tv_sec) + + hz*(tm.tv_usec - btm.tv_usec)/1000000; + + /* update disk transfers */ + updatexfers(RSTAT_DK_NDRIVE, stats_all.s1.dk_xfer); + + mib[0] = CTL_NET; + mib[1] = PF_LINK; + mib[2] = NETLINK_GENERIC; + mib[3] = IFMIB_SYSTEM; + mib[4] = IFMIB_IFCOUNT; + len = sizeof ifcount; + if (sysctl(mib, 5, &ifcount, &len, 0, 0) < 0) { + syslog(LOG_ERR, "sysctl(net.link.generic.system.ifcount): %m"); + exit(1); + } + + stats_all.s1.if_ipackets = 0; + stats_all.s1.if_opackets = 0; + stats_all.s1.if_ierrors = 0; + stats_all.s1.if_oerrors = 0; + stats_all.s1.if_collisions = 0; + for (i = 1; i <= ifcount; i++) { + len = sizeof ifmd; + mib[3] = IFMIB_IFDATA; + mib[4] = i; + mib[5] = IFDATA_GENERAL; + if (sysctl(mib, 6, &ifmd, &len, 0, 0) < 0) { + if (errno == ENOENT) + continue; + + syslog(LOG_ERR, "sysctl(net.link.ifdata.%d.general)" + ": %m", i); + exit(1); + } + + stats_all.s1.if_ipackets += ifmd.ifmd_data.ifi_ipackets; + stats_all.s1.if_opackets += ifmd.ifmd_data.ifi_opackets; + stats_all.s1.if_ierrors += ifmd.ifmd_data.ifi_ierrors; + stats_all.s1.if_oerrors += ifmd.ifmd_data.ifi_oerrors; + stats_all.s1.if_collisions += ifmd.ifmd_data.ifi_collisions; + } + (void)gettimeofday(&tm, NULL); + stats_all.s3.curtime.tv_sec = tm.tv_sec; + stats_all.s3.curtime.tv_usec = tm.tv_usec; + alarm(1); +} + +/* + * returns true if have a disk + */ +int +haveadisk(void) +{ + register int i; + struct statinfo stats; + int num_devices, retval = 0; + + if ((num_devices = devstat_getnumdevs(NULL)) < 0) { + syslog(LOG_ERR, "rstatd: can't get number of devices: %s", + devstat_errbuf); + exit(1); + } + + if (devstat_checkversion(NULL) < 0) { + syslog(LOG_ERR, "rstatd: %s", devstat_errbuf); + exit(1); + } + + stats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); + bzero(stats.dinfo, sizeof(struct devinfo)); + + if (devstat_getdevs(NULL, &stats) == -1) { + syslog(LOG_ERR, "rstatd: can't get device list: %s", + devstat_errbuf); + exit(1); + } + for (i = 0; i < stats.dinfo->numdevs; i++) { + if (((stats.dinfo->devices[i].device_type + & DEVSTAT_TYPE_MASK) == DEVSTAT_TYPE_DIRECT) + && ((stats.dinfo->devices[i].device_type + & DEVSTAT_TYPE_PASS) == 0)) { + retval = 1; + break; + } + } + + if (stats.dinfo->mem_ptr) + free(stats.dinfo->mem_ptr); + + free(stats.dinfo); + return(retval); +} + +void +updatexfers(int numdevs, int *devs) +{ + register int i, j, k, t; + struct statinfo stats; + int num_devices = 0; + u_int64_t total_transfers; + + if ((num_devices = devstat_getnumdevs(NULL)) < 0) { + syslog(LOG_ERR, "rstatd: can't get number of devices: %s", + devstat_errbuf); + exit(1); + } + + if (devstat_checkversion(NULL) < 0) { + syslog(LOG_ERR, "rstatd: %s", devstat_errbuf); + exit(1); + } + + stats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); + bzero(stats.dinfo, sizeof(struct devinfo)); + + if (devstat_getdevs(NULL, &stats) == -1) { + syslog(LOG_ERR, "rstatd: can't get device list: %s", + devstat_errbuf); + exit(1); + } + + for (i = 0, j = 0; i < stats.dinfo->numdevs && j < numdevs; i++) { + if (((stats.dinfo->devices[i].device_type + & DEVSTAT_TYPE_MASK) == DEVSTAT_TYPE_DIRECT) + && ((stats.dinfo->devices[i].device_type + & DEVSTAT_TYPE_PASS) == 0)) { + total_transfers = 0; + for (k = 0; k < DEVSTAT_N_TRANS_FLAGS; k++) + total_transfers += + stats.dinfo->devices[i].operations[k]; + /* + * XXX KDM If the total transfers for this device + * are greater than the amount we can fit in a + * signed integer, just set them to the maximum + * amount we can fit in a signed integer. I have a + * feeling that the rstat protocol assumes 32-bit + * integers, so this could well break on a 64-bit + * architecture like the Alpha. + */ + if (total_transfers > INT_MAX) + t = INT_MAX; + else + t = total_transfers; + devs[j] = t; + j++; + } + } + + if (stats.dinfo->mem_ptr) + free(stats.dinfo->mem_ptr); + + free(stats.dinfo); +} + +void +rstat_service(struct svc_req *rqstp, SVCXPRT *transp) +{ + union { + int fill; + } argument; + char *result; + bool_t (*xdr_argument)(), (*xdr_result)(); + char *(*local)(); + + switch (rqstp->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, NULL); + goto leave; + + case RSTATPROC_STATS: + xdr_argument = xdr_void; + xdr_result = xdr_statstime; + switch (rqstp->rq_vers) { + case RSTATVERS_ORIG: + local = (char *(*)()) rstatproc_stats_1_svc; + break; + case RSTATVERS_SWTCH: + local = (char *(*)()) rstatproc_stats_2_svc; + break; + case RSTATVERS_TIME: + local = (char *(*)()) rstatproc_stats_3_svc; + break; + default: + svcerr_progvers(transp, RSTATVERS_ORIG, RSTATVERS_TIME); + goto leave; + /*NOTREACHED*/ + } + break; + + case RSTATPROC_HAVEDISK: + xdr_argument = xdr_void; + xdr_result = xdr_u_int; + switch (rqstp->rq_vers) { + case RSTATVERS_ORIG: + local = (char *(*)()) rstatproc_havedisk_1_svc; + break; + case RSTATVERS_SWTCH: + local = (char *(*)()) rstatproc_havedisk_2_svc; + break; + case RSTATVERS_TIME: + local = (char *(*)()) rstatproc_havedisk_3_svc; + break; + default: + svcerr_progvers(transp, RSTATVERS_ORIG, RSTATVERS_TIME); + goto leave; + /*NOTREACHED*/ + } + break; + + default: + svcerr_noproc(transp); + goto leave; + } + bzero((char *)&argument, sizeof(argument)); + if (!svc_getargs(transp, (xdrproc_t)xdr_argument, &argument)) { + svcerr_decode(transp); + goto leave; + } + result = (*local)(&argument, rqstp); + if (result != NULL && + !svc_sendreply(transp, (xdrproc_t)xdr_result, result)) { + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, (xdrproc_t)xdr_argument, &argument)) + errx(1, "unable to free arguments"); +leave: + if (from_inetd) + exit(0); +} diff --git a/libexec/rpc.rstatd/rstatd.c b/libexec/rpc.rstatd/rstatd.c new file mode 100644 index 0000000..bb74eb8 --- /dev/null +++ b/libexec/rpc.rstatd/rstatd.c @@ -0,0 +1,129 @@ +/*- + * Copyright (c) 1993, John Brezak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <stdlib.h> +#include <rpc/rpc.h> +#include <signal.h> +#include <syslog.h> +#include <rpcsvc/rstat.h> + +extern void rstat_service(struct svc_req *, SVCXPRT *); + +int from_inetd = 1; /* started from inetd ? */ +int closedown = 20; /* how long to wait before going dormant */ + +void +cleanup(int sig __unused) +{ + (void) rpcb_unset(RSTATPROG, RSTATVERS_TIME, NULL); + (void) rpcb_unset(RSTATPROG, RSTATVERS_SWTCH, NULL); + (void) rpcb_unset(RSTATPROG, RSTATVERS_ORIG, NULL); + exit(0); +} + +int +main(int argc, char *argv[]) +{ + SVCXPRT *transp; + int ok; + struct sockaddr_storage from; + socklen_t fromlen; + + if (argc == 2) + closedown = atoi(argv[1]); + if (closedown <= 0) + closedown = 20; + + /* + * See if inetd started us + */ + fromlen = sizeof(from); + if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) { + from_inetd = 0; + } + + if (!from_inetd) { + daemon(0, 0); + + (void)rpcb_unset(RSTATPROG, RSTATVERS_TIME, NULL); + (void)rpcb_unset(RSTATPROG, RSTATVERS_SWTCH, NULL); + (void)rpcb_unset(RSTATPROG, RSTATVERS_ORIG, NULL); + + (void) signal(SIGINT, cleanup); + (void) signal(SIGTERM, cleanup); + (void) signal(SIGHUP, cleanup); + } + + openlog("rpc.rstatd", LOG_CONS|LOG_PID, LOG_DAEMON); + + if (from_inetd) { + transp = svc_tli_create(0, NULL, NULL, 0, 0); + if (transp == NULL) { + syslog(LOG_ERR, "cannot create udp service."); + exit(1); + } + ok = svc_reg(transp, RSTATPROG, RSTATVERS_TIME, + rstat_service, NULL); + } else + ok = svc_create(rstat_service, + RSTATPROG, RSTATVERS_TIME, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (RSTATPROG, RSTATVERS_TIME, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + if (from_inetd) + ok = svc_reg(transp, RSTATPROG, RSTATVERS_SWTCH, + rstat_service, NULL); + else + ok = svc_create(rstat_service, + RSTATPROG, RSTATVERS_SWTCH, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (RSTATPROG, RSTATVERS_SWTCH, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + if (from_inetd) + ok = svc_reg(transp, RSTATPROG, RSTATVERS_ORIG, + rstat_service, NULL); + else + ok = svc_create(rstat_service, + RSTATPROG, RSTATVERS_ORIG, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (RSTATPROG, RSTATVERS_ORIG, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + + svc_run(); + syslog(LOG_ERR, "svc_run returned"); + exit(1); +} diff --git a/libexec/rpc.rusersd/Makefile b/libexec/rpc.rusersd/Makefile new file mode 100644 index 0000000..df2be37 --- /dev/null +++ b/libexec/rpc.rusersd/Makefile @@ -0,0 +1,15 @@ +# $FreeBSD$ + +PROG = rpc.rusersd +SRCS = rusersd.c rusers_proc.c extern.h +MAN = rpc.rusersd.8 + +DPADD= ${LIBRPCSVC} ${LIBUTIL} +LDADD= -lrpcsvc -lutil + +#.if exists(/usr/X11R6/include/X11/extensions/xidle.h) +#CFLAGS+= -DXIDLE +#LDADD+= -L/usr/X11R6/lib -lXext -lX11 +#.endif + +.include <bsd.prog.mk> diff --git a/libexec/rpc.rusersd/extern.h b/libexec/rpc.rusersd/extern.h new file mode 100644 index 0000000..ac91526 --- /dev/null +++ b/libexec/rpc.rusersd/extern.h @@ -0,0 +1,34 @@ +/*- + * Copyright (c) 1993, John Brezak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +extern int from_inetd; + +void rusers_service(struct svc_req *, SVCXPRT *); diff --git a/libexec/rpc.rusersd/rpc.rusersd.8 b/libexec/rpc.rusersd/rpc.rusersd.8 new file mode 100644 index 0000000..f9f1a1d --- /dev/null +++ b/libexec/rpc.rusersd/rpc.rusersd.8 @@ -0,0 +1,64 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1985, 1991 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 7, 1993 +.Dt RPC.RUSERSD 8 +.Os +.Sh NAME +.Nm rpc.rusersd +.Nd logged in users server +.Sh SYNOPSIS +.Nm /usr/libexec/rpc.rusersd +.Sh DESCRIPTION +The +.Nm +utility is a server which returns information about users +currently logged in to the system. +.Pp +The currently logged in users are queried using the +.Xr rusers 1 +command. +The +.Nm +daemon is normally invoked by +.Xr inetd 8 . +.Pp +The +.Nm +utility uses an +.Tn RPC +protocol defined in +.Pa /usr/include/rpcsvc/rnusers.x . +.Sh SEE ALSO +.Xr rusers 1 , +.Xr w 1 , +.Xr who 1 , +.Xr inetd 8 diff --git a/libexec/rpc.rusersd/rusers_proc.c b/libexec/rpc.rusersd/rusers_proc.c new file mode 100644 index 0000000..51e9a4d --- /dev/null +++ b/libexec/rpc.rusersd/rusers_proc.c @@ -0,0 +1,332 @@ +/*- + * Copyright (c) 1993, John Brezak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#ifdef DEBUG +#include <errno.h> +#endif +#include <stdio.h> +#include <string.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <syslog.h> +#include <utmpx.h> +#ifdef XIDLE +#include <setjmp.h> +#include <X11/Xlib.h> +#include <X11/extensions/xidle.h> +#endif +#include <rpcsvc/rnusers.h> + +#include "extern.h" + +#ifndef _PATH_DEV +#define _PATH_DEV "/dev" +#endif + +static utmpidle utmp_idle[MAXUSERS]; +static utmp old_utmp[MAXUSERS]; +static struct utmpx utmp_list[MAXUSERS]; + +#ifdef XIDLE +static Display *dpy; + +static jmp_buf openAbort; + +static void +abortOpen(void) +{ + longjmp (openAbort, 1); +} + +XqueryIdle(char *display) +{ + int first_event, first_error; + Time IdleTime; + + (void) signal (SIGALRM, abortOpen); + (void) alarm ((unsigned) 10); + if (!setjmp (openAbort)) { + if (!(dpy= XOpenDisplay(display))) { + syslog(LOG_ERR, "Cannot open display %s", display); + return(-1); + } + if (XidleQueryExtension(dpy, &first_event, &first_error)) { + if (!XGetIdleTime(dpy, &IdleTime)) { + syslog(LOG_ERR, "%s: unable to get idle time", display); + return(-1); + } + } else { + syslog(LOG_ERR, "%s: Xidle extension not loaded", display); + return(-1); + } + XCloseDisplay(dpy); + } else { + syslog(LOG_ERR, "%s: server grabbed for over 10 seconds", display); + return(-1); + } + (void) signal (SIGALRM, SIG_DFL); + (void) alarm ((unsigned) 0); + + IdleTime /= 1000; + return((IdleTime + 30) / 60); +} +#endif + +static u_int +getidle(const char *tty, const char *display __unused) +{ + struct stat st; + char ttyname[PATH_MAX]; + time_t now; + u_long idle; + + /* + * If this is an X terminal or console, then try the + * XIdle extension + */ +#ifdef XIDLE + if (display && *display && (idle = XqueryIdle(display)) >= 0) + return(idle); +#endif + idle = 0; + if (*tty == 'X') { + u_long kbd_idle, mouse_idle; +#if !defined(__FreeBSD__) + kbd_idle = getidle("kbd", NULL); +#else + kbd_idle = getidle("vga", NULL); +#endif + mouse_idle = getidle("mouse", NULL); + idle = (kbd_idle < mouse_idle)?kbd_idle:mouse_idle; + } else { + sprintf(ttyname, "%s/%s", _PATH_DEV, tty); + if (stat(ttyname, &st) < 0) { +#ifdef DEBUG + printf("%s: %s\n", ttyname, strerror(errno)); +#endif + return(-1); + } + time(&now); +#ifdef DEBUG + printf("%s: now=%d atime=%d\n", ttyname, now, + st.st_atime); +#endif + idle = now - st.st_atime; + idle = (idle + 30) / 60; /* secs->mins */ + } + + return(idle); +} + +static utmpidlearr * +do_names_2(void) +{ + static utmpidlearr ut; + struct utmpx *usr; + int nusers = 0; + + memset(&ut, 0, sizeof(ut)); + ut.utmpidlearr_val = &utmp_idle[0]; + + setutxent(); + while ((usr = getutxent()) != NULL && nusers < MAXUSERS) { + if (usr->ut_type != USER_PROCESS) + continue; + + memcpy(&utmp_list[nusers], usr, sizeof(*usr)); + utmp_idle[nusers].ui_utmp.ut_time = usr->ut_tv.tv_sec; + utmp_idle[nusers].ui_idle = + getidle(usr->ut_line, usr->ut_host); + utmp_idle[nusers].ui_utmp.ut_line = + utmp_list[nusers].ut_line; + utmp_idle[nusers].ui_utmp.ut_name = + utmp_list[nusers].ut_user; + utmp_idle[nusers].ui_utmp.ut_host = + utmp_list[nusers].ut_host; + + nusers++; + } + endutxent(); + + ut.utmpidlearr_len = nusers; + return(&ut); +} + +static int * +rusers_num(void *argp __unused, struct svc_req *rqstp __unused) +{ + static int num_users = 0; + struct utmpx *usr; + + setutxent(); + while ((usr = getutxent()) != NULL) { + if (usr->ut_type != USER_PROCESS) + continue; + num_users++; + } + endutxent(); + + return(&num_users); +} + +static utmparr * +do_names_1(void) +{ + utmpidlearr *utidle; + static utmparr ut; + unsigned int i; + + bzero((char *)&ut, sizeof(ut)); + + utidle = do_names_2(); + if (utidle) { + ut.utmparr_len = utidle->utmpidlearr_len; + ut.utmparr_val = &old_utmp[0]; + for (i = 0; i < ut.utmparr_len; i++) + bcopy(&utmp_idle[i].ui_utmp, &old_utmp[i], + sizeof(old_utmp[0])); + + } + + return(&ut); +} + +utmpidlearr * +rusersproc_names_2_svc(void *argp __unused, struct svc_req *rqstp __unused) +{ + + return (do_names_2()); +} + +utmpidlearr * +rusersproc_allnames_2_svc(void *argp __unused, struct svc_req *rqstp __unused) +{ + + return (do_names_2()); +} + +utmparr * +rusersproc_names_1_svc(void *argp __unused, struct svc_req *rqstp __unused) +{ + + return (do_names_1()); +} + +utmparr * +rusersproc_allnames_1_svc(void *argp __unused, struct svc_req *rqstp __unused) +{ + + return (do_names_1()); +} + +typedef void *(*rusersproc_t)(void *, struct svc_req *); + +void +rusers_service(struct svc_req *rqstp, SVCXPRT *transp) +{ + union { + int fill; + } argument; + char *result; + xdrproc_t xdr_argument, xdr_result; + rusersproc_t local; + + switch (rqstp->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, NULL); + goto leave; + + case RUSERSPROC_NUM: + xdr_argument = (xdrproc_t)xdr_void; + xdr_result = (xdrproc_t)xdr_int; + local = (rusersproc_t)rusers_num; + break; + + case RUSERSPROC_NAMES: + xdr_argument = (xdrproc_t)xdr_void; + xdr_result = (xdrproc_t)xdr_utmpidlearr; + switch (rqstp->rq_vers) { + case RUSERSVERS_ORIG: + local = (rusersproc_t)rusersproc_names_1_svc; + break; + case RUSERSVERS_IDLE: + local = (rusersproc_t)rusersproc_names_2_svc; + break; + default: + svcerr_progvers(transp, RUSERSVERS_ORIG, RUSERSVERS_IDLE); + goto leave; + /*NOTREACHED*/ + } + break; + + case RUSERSPROC_ALLNAMES: + xdr_argument = (xdrproc_t)xdr_void; + xdr_result = (xdrproc_t)xdr_utmpidlearr; + switch (rqstp->rq_vers) { + case RUSERSVERS_ORIG: + local = (rusersproc_t)rusersproc_allnames_1_svc; + break; + case RUSERSVERS_IDLE: + local = (rusersproc_t)rusersproc_allnames_2_svc; + break; + default: + svcerr_progvers(transp, RUSERSVERS_ORIG, RUSERSVERS_IDLE); + goto leave; + /*NOTREACHED*/ + } + break; + + default: + svcerr_noproc(transp); + goto leave; + } + bzero(&argument, sizeof(argument)); + if (!svc_getargs(transp, (xdrproc_t)xdr_argument, &argument)) { + svcerr_decode(transp); + goto leave; + } + result = (*local)(&argument, rqstp); + if (result != NULL && + !svc_sendreply(transp, (xdrproc_t)xdr_result, result)) { + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, (xdrproc_t)xdr_argument, &argument)) { + syslog(LOG_ERR, "unable to free arguments"); + exit(1); + } +leave: + if (from_inetd) + exit(0); +} diff --git a/libexec/rpc.rusersd/rusersd.c b/libexec/rpc.rusersd/rusersd.c new file mode 100644 index 0000000..2a1e7bd --- /dev/null +++ b/libexec/rpc.rusersd/rusersd.c @@ -0,0 +1,112 @@ +/*- + * Copyright (c) 1993, John Brezak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <stdlib.h> +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <signal.h> +#include <syslog.h> +#include <rpcsvc/rnusers.h> + +#include "extern.h" + +int from_inetd = 1; + +static void +cleanup(int sig __unused) +{ + (void) rpcb_unset(RUSERSPROG, RUSERSVERS_IDLE, NULL); + (void) rpcb_unset(RUSERSPROG, RUSERSVERS_ORIG, NULL); + exit(0); +} + +int +main(int argc __unused, char *argv[] __unused) +{ + SVCXPRT *transp = NULL; /* Keep compiler happy. */ + int ok; + struct sockaddr_storage from; + socklen_t fromlen; + + /* + * See if inetd started us + */ + fromlen = sizeof(from); + if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) { + from_inetd = 0; + } + + if (!from_inetd) { + daemon(0, 0); + + (void) rpcb_unset(RUSERSPROG, RUSERSVERS_IDLE, NULL); + (void) rpcb_unset(RUSERSPROG, RUSERSVERS_ORIG, NULL); + + (void) signal(SIGINT, cleanup); + (void) signal(SIGTERM, cleanup); + (void) signal(SIGHUP, cleanup); + } + + openlog("rpc.rusersd", LOG_CONS|LOG_PID, LOG_DAEMON); + + if (from_inetd) { + transp = svc_tli_create(0, NULL, NULL, 0, 0); + if (transp == NULL) { + syslog(LOG_ERR, "cannot create udp service."); + exit(1); + } + ok = svc_reg(transp, RUSERSPROG, RUSERSVERS_IDLE, + rusers_service, NULL); + } else + ok = svc_create(rusers_service, + RUSERSPROG, RUSERSVERS_IDLE, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (RUSERSPROG, RUSERSVERS_IDLE, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + if (from_inetd) + ok = svc_reg(transp, RUSERSPROG, RUSERSVERS_ORIG, + rusers_service, NULL); + else + ok = svc_create(rusers_service, + RUSERSPROG, RUSERSVERS_ORIG, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (RUSERSPROG, RUSERSVERS_ORIG, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + + svc_run(); + syslog(LOG_ERR, "svc_run returned"); + exit(1); +} diff --git a/libexec/rpc.rwalld/Makefile b/libexec/rpc.rwalld/Makefile new file mode 100644 index 0000000..b09d663 --- /dev/null +++ b/libexec/rpc.rwalld/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG = rpc.rwalld +SRCS = rwalld.c +MAN = rpc.rwalld.8 + +DPADD= ${LIBUTIL} +LDADD= -lutil + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/libexec/rpc.rwalld/rpc.rwalld.8 b/libexec/rpc.rwalld/rpc.rwalld.8 new file mode 100644 index 0000000..6b2d206 --- /dev/null +++ b/libexec/rpc.rwalld/rpc.rwalld.8 @@ -0,0 +1,79 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1985, 1991 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 7, 1993 +.Dt RPC.RWALLD 8 +.Os +.Sh NAME +.Nm rpc.rwalld +.Nd write messages to users currently logged in server +.Sh SYNOPSIS +.Nm /usr/libexec/rpc.rwalld +.Op Fl n +.Sh DESCRIPTION +The +.Nm +utility is a server which will send a message to users +currently logged in to the system. +This server +invokes the +.Xr wall 1 +command to actually write the messages to the +system. +.Pp +The following option is available: +.Bl -tag -width indent +.It Fl n +Do not become a daemon. +This option is only available when +.Nm +is not invoked by +.Xr inetd 8 . +.El +.Pp +Messages are sent to this server by the +.Xr rwall 1 +command. +The +.Nm +daemon is normally invoked by +.Xr inetd 8 . +.Pp +The +.Nm +utility uses an +.Tn RPC +protocol defined in +.Pa /usr/include/rpcsvc/rwall.x . +.Sh SEE ALSO +.Xr rwall 1 , +.Xr wall 1 , +.Xr inetd 8 diff --git a/libexec/rpc.rwalld/rwalld.c b/libexec/rpc.rwalld/rwalld.c new file mode 100644 index 0000000..41ddb14 --- /dev/null +++ b/libexec/rpc.rwalld/rwalld.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 1993 Christopher G. Demetriou + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <err.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <arpa/inet.h> +#include <rpc/rpc.h> +#include <rpcsvc/rwall.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef OSF +#define WALL_CMD "/usr/sbin/wall" +#else +#define WALL_CMD "/usr/bin/wall -n" +#endif + +void wallprog_1(struct svc_req *rqstp, SVCXPRT *transp); +void possess(void); +void killkids(int sig); +static void usage(void); + +int nodaemon = 0; +int from_inetd = 1; + +int +main(int argc, char *argv[]) +{ + SVCXPRT *transp; + socklen_t salen; + int ok; + struct sockaddr_storage sa; + + if (argc == 2 && !strcmp(argv[1], "-n")) + nodaemon = 1; + if (argc != 1 && !nodaemon) + usage(); + + if (geteuid() == 0) { + struct passwd *pep = getpwnam("nobody"); + if (pep) + setuid(pep->pw_uid); + else + setuid(getuid()); + } + + /* + * See if inetd started us + */ + salen = sizeof(sa); + if (getsockname(0, (struct sockaddr *)&sa, &salen) < 0) { + from_inetd = 0; + } + + if (!from_inetd) { + if (!nodaemon) + possess(); + + (void)rpcb_unset(WALLPROG, WALLVERS, NULL); + } + + (void)signal(SIGCHLD, killkids); + + openlog("rpc.rwalld", LOG_CONS|LOG_PID, LOG_DAEMON); + + /* create and register the service */ + if (from_inetd) { + transp = svc_tli_create(0, NULL, NULL, 0, 0); + if (transp == NULL) { + syslog(LOG_ERR, "couldn't create udp service."); + exit(1); + } + ok = svc_reg(transp, WALLPROG, WALLVERS, + wallprog_1, NULL); + } else + ok = svc_create(wallprog_1, + WALLPROG, WALLVERS, "udp"); + if (!ok) { + syslog(LOG_ERR, "unable to register (WALLPROG, WALLVERS, %s)", (!from_inetd)?"udp":"(inetd)"); + exit(1); + } + svc_run(); + syslog(LOG_ERR, "svc_run returned"); + exit(1); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: rpc.rwalld [-n]\n"); + exit(1); +} + +void +possess(void) +{ + daemon(0, 0); +} + +void +killkids(int sig __unused) +{ + while(wait4(-1, NULL, WNOHANG, NULL) > 0) + ; +} + +void * +wallproc_wall_1_svc(wrapstring *s, struct svc_req *rqstp __unused) +{ + static void *dummy = NULL; + + /* fork, popen wall with special option, and send the message */ + if (fork() == 0) { + FILE *pfp; + + pfp = popen(WALL_CMD, "w"); + if (pfp != NULL) { + fprintf(pfp, "\007\007%s", *s); + pclose(pfp); + exit(0); + } + } + return(&dummy); +} + +void +wallprog_1(struct svc_req *rqstp, SVCXPRT *transp) +{ + union { + char *wallproc_wall_1_arg; + } argument; + char *result; + bool_t (*xdr_argument)(), (*xdr_result)(); + char *(*local)(); + + switch (rqstp->rq_proc) { + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, NULL); + goto leave; + + case WALLPROC_WALL: + xdr_argument = xdr_wrapstring; + xdr_result = xdr_void; + local = (char *(*)()) wallproc_wall_1_svc; + break; + + default: + svcerr_noproc(transp); + goto leave; + } + bzero(&argument, sizeof(argument)); + if (!svc_getargs(transp, (xdrproc_t)xdr_argument, &argument)) { + svcerr_decode(transp); + goto leave; + } + result = (*local)(&argument, rqstp); + if (result != NULL && + !svc_sendreply(transp, (xdrproc_t)xdr_result, result)) { + svcerr_systemerr(transp); + } + if (!svc_freeargs(transp, (xdrproc_t)xdr_argument, &argument)) { + syslog(LOG_ERR, "unable to free arguments"); + exit(1); + } +leave: + if (from_inetd) + exit(0); +} diff --git a/libexec/rpc.sprayd/Makefile b/libexec/rpc.sprayd/Makefile new file mode 100644 index 0000000..5b1cb24 --- /dev/null +++ b/libexec/rpc.sprayd/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG = rpc.sprayd +SRCS = sprayd.c +MAN = rpc.sprayd.8 + +DPADD= ${LIBRPCSVC} +LDADD= -lrpcsvc + +WARNS?= 2 + +.include <bsd.prog.mk> diff --git a/libexec/rpc.sprayd/rpc.sprayd.8 b/libexec/rpc.sprayd/rpc.sprayd.8 new file mode 100644 index 0000000..8840043 --- /dev/null +++ b/libexec/rpc.sprayd/rpc.sprayd.8 @@ -0,0 +1,59 @@ +.\" +.\" Copyright (c) 1994 Christos Zoulas +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Christos Zoulas. +.\" 4. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 22, 1994 +.Dt RPC.SPRAYD 8 +.Os +.Sh NAME +.Nm rpc.sprayd +.Nd spray server +.Sh SYNOPSIS +.Nm /usr/libexec/rpc.sprayd +.Sh DESCRIPTION +The +.Nm +utility +is a server which records packets sent by the +.Xr spray 8 +command and sends a traffic report to the originator of the packets. +The +.Nm +daemon is normally invoked by +.Xr inetd 8 . +.Pp +The +.Nm +utility uses an +.Tn RPC +protocol defined in +.Pa /usr/include/rpcsvc/spray.x . +.Sh SEE ALSO +.Xr spray 8 diff --git a/libexec/rpc.sprayd/sprayd.c b/libexec/rpc.sprayd/sprayd.c new file mode 100644 index 0000000..9c7cee1 --- /dev/null +++ b/libexec/rpc.sprayd/sprayd.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 1994 Christos Zoulas + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Christos Zoulas. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <rpc/rpc.h> +#include <rpcsvc/spray.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <syslog.h> +#include <unistd.h> + +static void spray_service(struct svc_req *, SVCXPRT *); + +static int from_inetd = 1; + +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) + +#define TIMEOUT 120 + +void +cleanup(int sig __unused) +{ + (void) rpcb_unset(SPRAYPROG, SPRAYVERS, NULL); + exit(0); +} + +void +die(int sig __unused) +{ + exit(0); +} + +int +main(int argc, char *argv[]) +{ + SVCXPRT *transp; + int ok; + struct sockaddr_storage from; + socklen_t fromlen; + + /* + * See if inetd started us + */ + fromlen = sizeof(from); + if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0) { + from_inetd = 0; + } + + if (!from_inetd) { + daemon(0, 0); + + (void) rpcb_unset(SPRAYPROG, SPRAYVERS, NULL); + + (void) signal(SIGINT, cleanup); + (void) signal(SIGTERM, cleanup); + (void) signal(SIGHUP, cleanup); + } else { + (void) signal(SIGALRM, die); + alarm(TIMEOUT); + } + + openlog("rpc.sprayd", LOG_CONS|LOG_PID, LOG_DAEMON); + + if (from_inetd) { + transp = svc_tli_create(0, NULL, NULL, 0, 0); + if (transp == NULL) { + syslog(LOG_ERR, "cannot create udp service."); + exit(1); + } + ok = svc_reg(transp, SPRAYPROG, SPRAYVERS, + spray_service, NULL); + } else + ok = svc_create(spray_service, + SPRAYPROG, SPRAYVERS, "udp"); + if (!ok) { + syslog(LOG_ERR, + "unable to register (SPRAYPROG, SPRAYVERS, %s)", + (!from_inetd)?"udp":"(inetd)"); + return 1; + } + + svc_run(); + syslog(LOG_ERR, "svc_run returned"); + return 1; +} + + +static void +spray_service(struct svc_req *rqstp, SVCXPRT *transp) +{ + static spraycumul scum; + static struct timeval clear, get; + + switch (rqstp->rq_proc) { + case SPRAYPROC_CLEAR: + scum.counter = 0; + (void) gettimeofday(&clear, 0); + /*FALLTHROUGH*/ + + case NULLPROC: + (void)svc_sendreply(transp, (xdrproc_t)xdr_void, NULL); + return; + + case SPRAYPROC_SPRAY: + scum.counter++; + return; + + case SPRAYPROC_GET: + (void) gettimeofday(&get, 0); + timersub(&get, &clear, &get); + scum.clock.sec = get.tv_sec; + scum.clock.usec = get.tv_usec; + break; + + default: + svcerr_noproc(transp); + return; + } + + if (!svc_sendreply(transp, (xdrproc_t)xdr_spraycumul, &scum)) { + svcerr_systemerr(transp); + syslog(LOG_ERR, "bad svc_sendreply"); + } +} diff --git a/libexec/rshd/Makefile b/libexec/rshd/Makefile new file mode 100644 index 0000000..f6881e8 --- /dev/null +++ b/libexec/rshd/Makefile @@ -0,0 +1,13 @@ +# From: @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= rshd +MAN= rshd.8 + +WARNS?= 3 +WFORMAT=0 + +DPADD= ${LIBUTIL} ${LIBPAM} +LDADD= -lutil ${MINUSLPAM} + +.include <bsd.prog.mk> diff --git a/libexec/rshd/rshd.8 b/libexec/rshd/rshd.8 new file mode 100644 index 0000000..6c6e57c --- /dev/null +++ b/libexec/rshd/rshd.8 @@ -0,0 +1,268 @@ +.\" Copyright (c) 1983, 1989, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)rshd.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" +.Dd June 4, 1993 +.Dt RSHD 8 +.Os +.Sh NAME +.Nm rshd +.Nd remote shell server +.Sh SYNOPSIS +.Nm +.Op Fl aDLln +.Sh DESCRIPTION +The +.Nm +utility +is the server for the +.Xr rcmd 3 +routine and, consequently, for the +.Xr rsh 1 +utility. +The server provides remote execution facilities +with authentication based on privileged port numbers from trusted hosts. +.Pp +The +.Nm +utility listens for service requests at the port indicated in +the +.Dq cmd +service specification; see +.Xr services 5 . +When a service request is received the following protocol +is initiated: +.Bl -enum +.It +The server checks the client's source port. +If the port is not in the range 512-1023, the server +aborts the connection. +.It +The server reads characters from the socket up +to a +.Tn NUL +(`\e0') byte. +The resultant string is +interpreted as an +.Tn ASCII +number, base 10. +.It +If the number received in step 2 is non-zero, +it is interpreted as the port number of a secondary +stream to be used for the +.Em stderr . +A second connection is then created to the specified +port on the client's machine. +The source port of this +second connection is also in the range 512-1023. +.It +The server checks the client's source address +and requests the corresponding host name (see +.Xr gethostbyaddr 3 , +.Xr hosts 5 +and +.Xr named 8 ) . +If the hostname cannot be determined or the hostname and address do +not match after verification, +the dot-notation representation of the host address is used. +.It +A null terminated user name of at most 16 characters +is retrieved on the initial socket. +This user name +is interpreted as the user identity on the +.Em client Ns 's +machine. +.It +A null terminated user name of at most 16 characters +is retrieved on the initial socket. +This user name +is interpreted as a user identity to use on the +.Em server Ns 's +machine. +.It +A null terminated command to be passed to a +shell is retrieved on the initial socket. +The length of +the command is limited by the upper bound on the size of +the system's argument list. +.It +The +.Nm +utility then validates the user using +.Xr ruserok 3 , +which uses the file +.Pa /etc/hosts.equiv +and the +.Pa .rhosts +file found in the user's home directory. +The +.Fl l +option prevents +.Xr ruserok 3 +from doing any validation based on the user's +.Pa .rhosts +file, +unless the user is the superuser. +.It +A +.Tn NUL +byte is returned on the initial socket +and the command line is passed to the normal login +shell of the user. +The +shell inherits the network connections established +by +.Nm . +.El +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a +This flag is ignored, and is present for compatibility purposes. +.It Fl D +Sets the TCP_NODELAY socket option, which improves the performance +of small back-to-back writes at the expense of additional network +traffic. +.It Fl L +Causes all successful accesses to be logged to +.Xr syslogd 8 +as +.Li auth.info +messages. +.It Fl l +Do not use the user's +.Pa .rhosts +file for authentication, unless the user is the superuser. +.It Fl n +Turn off transport level keepalive messages. +This will prevent sessions +from timing out if the client crashes or becomes unreachable. +.El +.Sh FILES +.Bl -tag -width /var/run/nologin -compact +.It Pa /etc/hosts +.It Pa /etc/hosts.equiv +.It Pa /etc/login.conf +.It Ev $HOME Ns Pa /.rhosts +.Pp +.It Pa /etc/pam.conf +.Nm +uses +.Pa /etc/pam.conf +entries with service name +.Dq rsh . +Authentication modules requiring passwords (such as +.Nm pam_unix ) +are not supported. +.El +.Sh DIAGNOSTICS +Except for the last one listed below, +all diagnostic messages +are returned on the initial socket, +after which any network connections are closed. +An error is indicated by a leading byte with a value of +1 (0 is returned in step 10 above upon successful completion +of all the steps prior to the execution of the login shell). +.Bl -tag -width indent +.It Sy Locuser too long. +The name of the user on the client's machine is +longer than 16 characters. +.It Sy Ruser too long. +The name of the user on the remote machine is +longer than 16 characters. +.It Sy Command too long. +The command line passed exceeds the size of the argument +list (as configured into the system). +.It Sy Login incorrect. +No password file entry for the user name existed +or the authentication procedure described above failed. +.It Sy Remote directory. +The +.Xr chdir 2 +function to the home directory failed. +.It Sy Logins not available right now. +The +.Xr rsh 1 +utility was attempted outside the allowed hours defined in +.Pa /etc/login.conf +for the local user's login class. +.It Sy Can't make pipe. +The pipe needed for the +.Em stderr , +was not created. +.It Sy Can't fork; try again. +A +.Xr fork 2 +by the server failed. +.It Sy <shellname>: ... +The user's login shell could not be started. +This message is returned +on the connection associated with the +.Em stderr , +and is not preceded by a flag byte. +.El +.Sh SEE ALSO +.Xr rlogin 1 , +.Xr rsh 1 , +.Xr gethostbyaddr 3 , +.Xr rcmd 3 , +.Xr ruserok 3 , +.Xr hosts 5 , +.Xr hosts.equiv 5 , +.Xr login.conf 5 , +.Xr services 5 , +.Xr named 8 , +.Xr rlogind 8 , +.Xr syslogd 8 +.Sh HISTORY +IPv6 support was added by WIDE/KAME project. +.Sh BUGS +The authentication procedure used here assumes the integrity +of each client machine and the connecting medium. +This is +insecure, but is useful in an +.Dq open +environment. +.Pp +A facility to allow all data exchanges to be encrypted should be +present. +.Pp +Post-PAM, +.Fx +also needs the following patch applied besides properly configuring +.Pa .rhosts : +.Bd -literal -offset indent +--- etc/pam.d/rsh.orig Wed Dec 17 14:36:20 2003 ++++ etc/pam.d/rsh Wed Dec 17 14:30:43 2003 +@@ -9 +9 @@ +-auth required pam_rhosts.so no_warn ++auth required pam_rhosts.so no_warn allow_root +.Ed +.Pp +A more extensible protocol (such as Telnet) should be used. diff --git a/libexec/rshd/rshd.c b/libexec/rshd/rshd.c new file mode 100644 index 0000000..b315040 --- /dev/null +++ b/libexec/rshd/rshd.c @@ -0,0 +1,589 @@ +/*- + * Copyright (c) 1988, 1989, 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2002 Networks Associates Technology, Inc. + * All rights reserved. + * + * Portions of this software were developed for the FreeBSD Project by + * ThinkSec AS and NAI Labs, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1988, 1989, 1992, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static const char sccsid[] = "@(#)rshd.c 8.2 (Berkeley) 4/6/94"; +#endif +#endif /* not lint */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * remote shell server: + * [port]\0 + * ruser\0 + * luser\0 + * command\0 + * data + */ +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <paths.h> +#include <pwd.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <login_cap.h> + +#include <security/pam_appl.h> +#include <security/openpam.h> +#include <sys/wait.h> + +static struct pam_conv pamc = { openpam_nullconv, NULL }; +static pam_handle_t *pamh; +static int pam_err; + +#define PAM_END { \ + if ((pam_err = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) \ + syslog(LOG_ERR|LOG_AUTH, "pam_setcred(): %s", pam_strerror(pamh, pam_err)); \ + if ((pam_err = pam_close_session(pamh,0)) != PAM_SUCCESS) \ + syslog(LOG_ERR|LOG_AUTH, "pam_close_session(): %s", pam_strerror(pamh, pam_err)); \ + if ((pam_err = pam_end(pamh, pam_err)) != PAM_SUCCESS) \ + syslog(LOG_ERR|LOG_AUTH, "pam_end(): %s", pam_strerror(pamh, pam_err)); \ +} + +int keepalive = 1; +int log_success; /* If TRUE, log all successful accesses */ +int sent_null; +int no_delay; + +void doit(struct sockaddr *); +static void rshd_errx(int, const char *, ...) __printf0like(2, 3); +void getstr(char *, int, const char *); +int local_domain(char *); +char *topdomain(char *); +void usage(void); + +char slash[] = "/"; +char bshell[] = _PATH_BSHELL; + +#define OPTIONS "aDLln" + +int +main(int argc, char *argv[]) +{ + extern int __check_rhosts_file; + struct linger linger; + socklen_t fromlen; + int ch, on = 1; + struct sockaddr_storage from; + + openlog("rshd", LOG_PID, LOG_DAEMON); + + opterr = 0; + while ((ch = getopt(argc, argv, OPTIONS)) != -1) + switch (ch) { + case 'a': + /* ignored for compatibility */ + break; + case 'l': + __check_rhosts_file = 0; + break; + case 'n': + keepalive = 0; + break; + case 'D': + no_delay = 1; + break; + case 'L': + log_success = 1; + break; + case '?': + default: + usage(); + break; + } + + argc -= optind; + argv += optind; + + fromlen = sizeof (from); + if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) { + syslog(LOG_ERR, "getpeername: %m"); + exit(1); + } + if (keepalive && + setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, + sizeof(on)) < 0) + syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); + linger.l_onoff = 1; + linger.l_linger = 60; /* XXX */ + if (setsockopt(0, SOL_SOCKET, SO_LINGER, (char *)&linger, + sizeof (linger)) < 0) + syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m"); + if (no_delay && + setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m"); + doit((struct sockaddr *)&from); + /* NOTREACHED */ + return(0); +} + +extern char **environ; + +void +doit(struct sockaddr *fromp) +{ + extern char *__rcmd_errstr; /* syslog hook from libc/net/rcmd.c. */ + struct passwd *pwd; + u_short port; + fd_set ready, readfrom; + int cc, fd, nfd, pv[2], pid, s; + int one = 1; + const char *cp, *errorstr; + char sig, buf[BUFSIZ]; + char *cmdbuf, luser[16], ruser[16]; + char rhost[2 * MAXHOSTNAMELEN + 1]; + char numericname[INET6_ADDRSTRLEN]; + int af, srcport; + int maxcmdlen; + login_cap_t *lc; + + maxcmdlen = (int)sysconf(_SC_ARG_MAX); + if (maxcmdlen <= 0 || (cmdbuf = malloc(maxcmdlen)) == NULL) + exit(1); + + (void) signal(SIGINT, SIG_DFL); + (void) signal(SIGQUIT, SIG_DFL); + (void) signal(SIGTERM, SIG_DFL); + af = fromp->sa_family; + srcport = ntohs(*((in_port_t *)&fromp->sa_data)); + if (af == AF_INET) { + inet_ntop(af, &((struct sockaddr_in *)fromp)->sin_addr, + numericname, sizeof numericname); + } else if (af == AF_INET6) { + inet_ntop(af, &((struct sockaddr_in6 *)fromp)->sin6_addr, + numericname, sizeof numericname); + } else { + syslog(LOG_ERR, "malformed \"from\" address (af %d)", af); + exit(1); + } +#ifdef IP_OPTIONS + if (af == AF_INET) { + u_char optbuf[BUFSIZ/3]; + socklen_t optsize = sizeof(optbuf), ipproto, i; + struct protoent *ip; + + if ((ip = getprotobyname("ip")) != NULL) + ipproto = ip->p_proto; + else + ipproto = IPPROTO_IP; + if (!getsockopt(0, ipproto, IP_OPTIONS, optbuf, &optsize) && + optsize != 0) { + for (i = 0; i < optsize; ) { + u_char c = optbuf[i]; + if (c == IPOPT_LSRR || c == IPOPT_SSRR) { + syslog(LOG_NOTICE, + "connection refused from %s with IP option %s", + numericname, + c == IPOPT_LSRR ? "LSRR" : "SSRR"); + exit(1); + } + if (c == IPOPT_EOL) + break; + i += (c == IPOPT_NOP) ? 1 : optbuf[i+1]; + } + } + } +#endif + + if (srcport >= IPPORT_RESERVED || + srcport < IPPORT_RESERVED/2) { + syslog(LOG_NOTICE|LOG_AUTH, + "connection from %s on illegal port %u", + numericname, + srcport); + exit(1); + } + + (void) alarm(60); + port = 0; + s = 0; /* not set or used if port == 0 */ + for (;;) { + char c; + if ((cc = read(STDIN_FILENO, &c, 1)) != 1) { + if (cc < 0) + syslog(LOG_NOTICE, "read: %m"); + shutdown(0, SHUT_RDWR); + exit(1); + } + if (c == 0) + break; + port = port * 10 + c - '0'; + } + + (void) alarm(0); + if (port != 0) { + int lport = IPPORT_RESERVED - 1; + s = rresvport_af(&lport, af); + if (s < 0) { + syslog(LOG_ERR, "can't get stderr port: %m"); + exit(1); + } + if (port >= IPPORT_RESERVED || + port < IPPORT_RESERVED/2) { + syslog(LOG_NOTICE|LOG_AUTH, + "2nd socket from %s on unreserved port %u", + numericname, + port); + exit(1); + } + *((in_port_t *)&fromp->sa_data) = htons(port); + if (connect(s, fromp, fromp->sa_len) < 0) { + syslog(LOG_INFO, "connect second port %d: %m", port); + exit(1); + } + } + + errorstr = NULL; + realhostname_sa(rhost, sizeof(rhost) - 1, fromp, fromp->sa_len); + rhost[sizeof(rhost) - 1] = '\0'; + /* XXX truncation! */ + + (void) alarm(60); + getstr(ruser, sizeof(ruser), "ruser"); + getstr(luser, sizeof(luser), "luser"); + getstr(cmdbuf, maxcmdlen, "command"); + (void) alarm(0); + + pam_err = pam_start("rsh", luser, &pamc, &pamh); + if (pam_err != PAM_SUCCESS) { + syslog(LOG_ERR|LOG_AUTH, "pam_start(): %s", + pam_strerror(pamh, pam_err)); + rshd_errx(1, "Login incorrect."); + } + + if ((pam_err = pam_set_item(pamh, PAM_RUSER, ruser)) != PAM_SUCCESS || + (pam_err = pam_set_item(pamh, PAM_RHOST, rhost)) != PAM_SUCCESS) { + syslog(LOG_ERR|LOG_AUTH, "pam_set_item(): %s", + pam_strerror(pamh, pam_err)); + rshd_errx(1, "Login incorrect."); + } + + pam_err = pam_authenticate(pamh, 0); + if (pam_err == PAM_SUCCESS) { + if ((pam_err = pam_get_user(pamh, &cp, NULL)) == PAM_SUCCESS) { + strncpy(luser, cp, sizeof(luser)); + luser[sizeof(luser) - 1] = '\0'; + /* XXX truncation! */ + } + pam_err = pam_acct_mgmt(pamh, 0); + } + if (pam_err != PAM_SUCCESS) { + syslog(LOG_INFO|LOG_AUTH, + "%s@%s as %s: permission denied (%s). cmd='%.80s'", + ruser, rhost, luser, pam_strerror(pamh, pam_err), cmdbuf); + rshd_errx(1, "Login incorrect."); + } + + setpwent(); + pwd = getpwnam(luser); + if (pwd == NULL) { + syslog(LOG_INFO|LOG_AUTH, + "%s@%s as %s: unknown login. cmd='%.80s'", + ruser, rhost, luser, cmdbuf); + if (errorstr == NULL) + errorstr = "Login incorrect."; + rshd_errx(1, errorstr, rhost); + } + + lc = login_getpwclass(pwd); + if (pwd->pw_uid) + auth_checknologin(lc); + + if (chdir(pwd->pw_dir) < 0) { + if (chdir("/") < 0 || + login_getcapbool(lc, "requirehome", !!pwd->pw_uid)) { + syslog(LOG_INFO|LOG_AUTH, + "%s@%s as %s: no home directory. cmd='%.80s'", + ruser, rhost, luser, cmdbuf); + rshd_errx(0, "No remote home directory."); + } + pwd->pw_dir = slash; + } + + if (lc != NULL && fromp->sa_family == AF_INET) { /*XXX*/ + char remote_ip[MAXHOSTNAMELEN]; + + strncpy(remote_ip, numericname, + sizeof(remote_ip) - 1); + remote_ip[sizeof(remote_ip) - 1] = 0; + /* XXX truncation! */ + if (!auth_hostok(lc, rhost, remote_ip)) { + syslog(LOG_INFO|LOG_AUTH, + "%s@%s as %s: permission denied (%s). cmd='%.80s'", + ruser, rhost, luser, __rcmd_errstr, + cmdbuf); + rshd_errx(1, "Login incorrect."); + } + if (!auth_timeok(lc, time(NULL))) + rshd_errx(1, "Logins not available right now"); + } + + /* + * PAM modules might add supplementary groups in + * pam_setcred(), so initialize them first. + * But we need to open the session as root. + */ + if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) != 0) { + syslog(LOG_ERR, "setusercontext: %m"); + exit(1); + } + + if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, pam_err)); + } else if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, pam_err)); + } + + (void) write(STDERR_FILENO, "\0", 1); + sent_null = 1; + + if (port) { + if (pipe(pv) < 0) + rshd_errx(1, "Can't make pipe."); + pid = fork(); + if (pid == -1) + rshd_errx(1, "Can't fork; try again."); + if (pid) { + (void) close(0); + (void) close(1); + (void) close(2); + (void) close(pv[1]); + + FD_ZERO(&readfrom); + FD_SET(s, &readfrom); + FD_SET(pv[0], &readfrom); + if (pv[0] > s) + nfd = pv[0]; + else + nfd = s; + ioctl(pv[0], FIONBIO, (char *)&one); + + /* should set s nbio! */ + nfd++; + do { + ready = readfrom; + if (select(nfd, &ready, (fd_set *)0, + (fd_set *)0, (struct timeval *)0) < 0) + break; + if (FD_ISSET(s, &ready)) { + int ret; + ret = read(s, &sig, 1); + if (ret <= 0) + FD_CLR(s, &readfrom); + else + killpg(pid, sig); + } + if (FD_ISSET(pv[0], &ready)) { + errno = 0; + cc = read(pv[0], buf, sizeof(buf)); + if (cc <= 0) { + shutdown(s, SHUT_RDWR); + FD_CLR(pv[0], &readfrom); + } else { + (void)write(s, buf, cc); + } + } + + } while (FD_ISSET(s, &readfrom) || + FD_ISSET(pv[0], &readfrom)); + PAM_END; + exit(0); + } + (void) close(s); + (void) close(pv[0]); + dup2(pv[1], 2); + close(pv[1]); + } + else { + pid = fork(); + if (pid == -1) + rshd_errx(1, "Can't fork; try again."); + if (pid) { + /* Parent. */ + while (wait(NULL) > 0 || errno == EINTR) + /* nothing */ ; + PAM_END; + exit(0); + } + } + + for (fd = getdtablesize(); fd > 2; fd--) + (void) close(fd); + if (setsid() == -1) + syslog(LOG_ERR, "setsid() failed: %m"); + if (setlogin(pwd->pw_name) < 0) + syslog(LOG_ERR, "setlogin() failed: %m"); + + if (*pwd->pw_shell == '\0') + pwd->pw_shell = bshell; + (void) pam_setenv(pamh, "HOME", pwd->pw_dir, 1); + (void) pam_setenv(pamh, "SHELL", pwd->pw_shell, 1); + (void) pam_setenv(pamh, "USER", pwd->pw_name, 1); + (void) pam_setenv(pamh, "PATH", _PATH_DEFPATH, 1); + environ = pam_getenvlist(pamh); + (void) pam_end(pamh, pam_err); + cp = strrchr(pwd->pw_shell, '/'); + if (cp) + cp++; + else + cp = pwd->pw_shell; + + if (setusercontext(lc, pwd, pwd->pw_uid, + LOGIN_SETALL & ~LOGIN_SETGROUP) < 0) { + syslog(LOG_ERR, "setusercontext(): %m"); + exit(1); + } + login_close(lc); + endpwent(); + if (log_success || pwd->pw_uid == 0) { + syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: cmd='%.80s'", + ruser, rhost, luser, cmdbuf); + } + execl(pwd->pw_shell, cp, "-c", cmdbuf, (char *)NULL); + err(1, "%s", pwd->pw_shell); + exit(1); +} + +/* + * Report error to client. Note: can't be used until second socket has + * connected to client, or older clients will hang waiting for that + * connection first. + */ + +static void +rshd_errx(int errcode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if (sent_null == 0) + write(STDERR_FILENO, "\1", 1); + + verrx(errcode, fmt, ap); + /* NOTREACHED */ +} + +void +getstr(char *buf, int cnt, const char *error) +{ + char c; + + do { + if (read(STDIN_FILENO, &c, 1) != 1) + exit(1); + *buf++ = c; + if (--cnt == 0) + rshd_errx(1, "%s too long", error); + } while (c != 0); +} + +/* + * Check whether host h is in our local domain, + * defined as sharing the last two components of the domain part, + * or the entire domain part if the local domain has only one component. + * If either name is unqualified (contains no '.'), + * assume that the host is local, as it will be + * interpreted as such. + */ +int +local_domain(char *h) +{ + char localhost[MAXHOSTNAMELEN]; + char *p1, *p2; + + localhost[0] = 0; + (void) gethostname(localhost, sizeof(localhost) - 1); + localhost[sizeof(localhost) - 1] = '\0'; + /* XXX truncation! */ + p1 = topdomain(localhost); + p2 = topdomain(h); + if (p1 == NULL || p2 == NULL || !strcasecmp(p1, p2)) + return (1); + return (0); +} + +char * +topdomain(char *h) +{ + char *p, *maybe = NULL; + int dots = 0; + + for (p = h + strlen(h); p >= h; p--) { + if (*p == '.') { + if (++dots == 2) + return (p); + maybe = p; + } + } + return (maybe); +} + +void +usage(void) +{ + + syslog(LOG_ERR, "usage: rshd [-%s]", OPTIONS); + exit(2); +} diff --git a/libexec/rtld-aout/shlib.c b/libexec/rtld-aout/shlib.c new file mode 100644 index 0000000..70c5313 --- /dev/null +++ b/libexec/rtld-aout/shlib.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 1993 Paul Kranenburg + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Paul Kranenburg. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/time.h> +#include <a.out.h> +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/link_aout.h> +#include "shlib.h" +#include "support.h" + +/* + * Standard directories to search for files specified by -l. + */ +#ifndef STANDARD_SEARCH_DIRS +#define STANDARD_SEARCH_DIRS "/usr/lib/aout" +#endif + +/* + * Actual vector of library search directories, + * including `-L'ed and LD_LIBRARY_PATH spec'd ones. + */ +char **search_dirs; +int n_search_dirs; + +static const char *standard_search_dirs[] = { + STANDARD_SEARCH_DIRS +}; + + +void +add_search_dir(const char *name) +{ + int n; + + for (n = 0; n < n_search_dirs; n++) + if (strcmp(search_dirs[n], name) == 0) + return; + n_search_dirs++; + search_dirs = (char **) + xrealloc(search_dirs, n_search_dirs * sizeof search_dirs[0]); + search_dirs[n_search_dirs - 1] = strdup(name); +} + +void +add_search_path(char *path) +{ + register char *cp, *dup; + + if (path == NULL) + return; + + /* Add search directories from `path' */ + path = dup = strdup(path); + while ((cp = strsep(&path, ":")) != NULL) + add_search_dir(cp); + free(dup); +} + +void +std_search_path(void) +{ + int i, n; + + /* Append standard search directories */ + n = sizeof standard_search_dirs / sizeof standard_search_dirs[0]; + for (i = 0; i < n; i++) + add_search_dir(standard_search_dirs[i]); +} + +/* + * Return true if CP points to a valid dewey number. + * Decode and leave the result in the array DEWEY. + * Return the number of decoded entries in DEWEY. + */ + +int +getdewey(int dewey[], char *cp) +{ + int i, n; + + for (n = 0, i = 0; i < MAXDEWEY; i++) { + if (*cp == '\0') + break; + + if (*cp == '.') cp++; + if (!isdigit(*cp)) + return 0; + + dewey[n++] = strtol(cp, &cp, 10); + } + + return n; +} + +/* + * Compare two dewey arrays. + * Return -1 if `d1' represents a smaller value than `d2'. + * Return 1 if `d1' represents a greater value than `d2'. + * Return 0 if equal. + */ +int +cmpndewey(int d1[], int n1, int d2[], int n2) +{ + register int i; + + for (i = 0; i < n1 && i < n2; i++) { + if (d1[i] < d2[i]) + return -1; + if (d1[i] > d2[i]) + return 1; + } + + if (n1 == n2) + return 0; + + if (i == n1) + return -1; + + if (i == n2) + return 1; + + errx(1, "cmpndewey: can't happen"); + return 0; +} + +/* + * Search directories for a shared library matching the given + * major and minor version numbers. See search_lib_dir() below for + * the detailed matching rules. + * + * As soon as a directory with an acceptable match is found, the search + * terminates. Subsequent directories are not searched for a better + * match. This is in conformance with the SunOS searching rules. Also, + * it avoids a lot of directory searches that are virtually guaranteed to + * be fruitless. + * + * The return value is a full pathname to the matching library. The + * string is dynamically allocated. If no matching library is found, the + * function returns NULL. + */ + +char * +findshlib(char *name, int *majorp, int *minorp, int do_dot_a) +{ + int i; + + for (i = 0; i < n_search_dirs; i++) { + char *path; + + path = search_lib_dir(search_dirs[i], name, majorp, minorp, + do_dot_a); + if(path != NULL) + return path; + } + + return NULL; +} + +/* + * Search library directories for a file with the given name. The + * return value is a full pathname to the matching file. The string + * is dynamically allocated. If no matching file is found, the function + * returns NULL. + */ + +char * +find_lib_file(const char *name) +{ + int i; + + for (i = 0; i < n_search_dirs; i++) { + char *path = concat(search_dirs[i], "/", name); + struct stat sb; + + if (lstat(path, &sb) != -1) /* We found it */ + return path; + + free(path); + } + + return NULL; +} + +/* + * Search a given directory for a library (preferably shared) satisfying + * the given criteria. + * + * The matching rules are as follows: + * + * if(*majorp == -1) + * find the library with the highest major version; + * else + * insist on a major version identical to *majorp; + * + * Always find the library with the highest minor version; + * if(*minorp != -1) + * insist on a minor version >= *minorp; + * + * It is invalid to specify a specific minor number while wildcarding + * the major number. + * + * The actual major and minor numbers found are returned via the pointer + * arguments. + * + * A suitable shared library is always preferred over a static (.a) library. + * If do_dot_a is false, then a static library will not be accepted in + * any case. + * + * The return value is a full pathname to the matching library. The + * string is dynamically allocated. If no matching library is found, the + * function returns NULL. + */ + +char * +search_lib_dir(char *dir, char *name, int *majorp, int *minorp, int do_dot_a) +{ + size_t namelen; + DIR *dd; + struct dirent *dp; + int best_dewey[MAXDEWEY]; + int best_ndewey; + char dot_a_name[MAXNAMLEN+1]; + char dot_so_name[MAXNAMLEN+1]; + + if((dd = opendir(dir)) == NULL) + return NULL; + + namelen = strlen(name); + best_ndewey = 0; + dot_a_name[0] = '\0'; + dot_so_name[0] = '\0'; + + while((dp = readdir(dd)) != NULL) { + char *extension; + + if(strlen(dp->d_name) < 3 + namelen + 2 || /* lib+xxx+.a */ + strncmp(dp->d_name, "lib", 3) != 0 || + strncmp(dp->d_name + 3, name, namelen) != 0 || + dp->d_name[3+namelen] != '.') + continue; + + extension = dp->d_name + 3 + namelen + 1; /* a or so.* */ + + if(strncmp(extension, "so.", 3) == 0) { + int cur_dewey[MAXDEWEY]; + int cur_ndewey; + + cur_ndewey = getdewey(cur_dewey, extension+3); + if(cur_ndewey < 2) /* Too few version numbers */ + continue; + + if(*majorp != -1) { /* Need exact match on major */ + if(cur_dewey[0] != *majorp) + continue; + if(*minorp != -1) { /* Need minor >= minimum */ + if(cur_dewey[1] < *minorp) + continue; + } + } + + if(cmpndewey(cur_dewey, cur_ndewey, best_dewey, + best_ndewey) <= 0) /* No better than prior match */ + continue; + + /* We found a better match */ + strcpy(dot_so_name, dp->d_name); + bcopy(cur_dewey, best_dewey, + cur_ndewey * sizeof best_dewey[0]); + best_ndewey = cur_ndewey; + } else if(do_dot_a && strcmp(extension, "a") == 0) + strcpy(dot_a_name, dp->d_name); + } + closedir(dd); + + if(dot_so_name[0] != '\0') { + *majorp = best_dewey[0]; + *minorp = best_dewey[1]; + return concat(dir, "/", dot_so_name); + } + + if(dot_a_name[0] != '\0') + return concat(dir, "/", dot_a_name); + + return NULL; +} diff --git a/libexec/rtld-aout/shlib.h b/libexec/rtld-aout/shlib.h new file mode 100644 index 0000000..4b6d753 --- /dev/null +++ b/libexec/rtld-aout/shlib.h @@ -0,0 +1,43 @@ +/*- + * Copyright (C) 1996 + * Peter Wemm. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + *- + * $FreeBSD$ + */ + +/* + * prototypes for shlib.c. Big deal. + */ + +extern char **search_dirs; +extern int n_search_dirs; + +void add_search_dir __P((const char *)); +void add_search_path __P((char *)); +void std_search_path __P((void)); +int getdewey __P((int[], char *)); +int cmpndewey __P((int[], int, int[], int)); +char *findshlib __P((char *, int *, int *, int)); +char *find_lib_file __P((const char *)); +char *search_lib_dir __P((char *, char *, int *, int *, int)); diff --git a/libexec/rtld-aout/support.c b/libexec/rtld-aout/support.c new file mode 100644 index 0000000..eb8bd29 --- /dev/null +++ b/libexec/rtld-aout/support.c @@ -0,0 +1,82 @@ +/* + * Generic "support" routines to replace those obtained from libiberty for ld. + * + * I've collected these from random bits of (published) code I've written + * over the years, not that they are a big deal. peter@freebsd.org + *- + * Copyright (C) 1996 + * Peter Wemm. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + *- + * $FreeBSD$ + */ +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> +#include <err.h> + +#include "support.h" + +char * +concat(const char *s1, const char *s2, const char *s3) +{ + int len = 1; + char *s; + if (s1) + len += strlen(s1); + if (s2) + len += strlen(s2); + if (s3) + len += strlen(s3); + s = xmalloc(len); + s[0] = '\0'; + if (s1) + strcat(s, s1); + if (s2) + strcat(s, s2); + if (s3) + strcat(s, s3); + return s; +} + +void * +xmalloc(size_t n) +{ + char *p = malloc(n); + + if (p == NULL) + errx(1, "Could not allocate memory"); + + return p; +} + +void * +xrealloc(void *p, size_t n) +{ + p = realloc(p, n); + + if (p == NULL) + errx(1, "Could not allocate memory"); + + return p; +} diff --git a/libexec/rtld-aout/support.h b/libexec/rtld-aout/support.h new file mode 100644 index 0000000..da03407 --- /dev/null +++ b/libexec/rtld-aout/support.h @@ -0,0 +1,35 @@ +/*- + * Copyright (C) 1996 + * Peter Wemm. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + *- + * $FreeBSD$ + */ + +/* + * prototypes for support.c. Big deal. + */ + +void *xmalloc __P((size_t)); +void *xrealloc __P((void *, size_t)); +char *concat __P((const char *, const char *, const char *)); diff --git a/libexec/rtld-elf/Makefile b/libexec/rtld-elf/Makefile new file mode 100644 index 0000000..cc30132 --- /dev/null +++ b/libexec/rtld-elf/Makefile @@ -0,0 +1,90 @@ +# $FreeBSD$ + +# Use the following command to build local debug version of dynamic +# linker: +# make DEBUG_FLAGS=-g DEBUG=-DDEBUG MK_TESTS=no all + +.include <src.opts.mk> +MK_SSP= no + +PROG?= ld-elf.so.1 +SRCS= rtld_start.S \ + reloc.c rtld.c rtld_lock.c rtld_printf.c map_object.c \ + malloc.c xmalloc.c debug.c libmap.c +MAN= rtld.1 +CSTD?= gnu99 +TOPSRCDIR= ${.CURDIR}/../.. +CFLAGS+= -Wall -DFREEBSD_ELF -DIN_RTLD +CFLAGS+= -I${TOPSRCDIR}/lib/csu/common +.if exists(${.CURDIR}/${MACHINE_ARCH}) +RTLD_ARCH= ${MACHINE_ARCH} +.else +RTLD_ARCH= ${MACHINE_CPUARCH} +.endif +CFLAGS+= -I${.CURDIR}/${RTLD_ARCH} -I${.CURDIR} +.if ${MACHINE_ARCH} == "powerpc64" +CFLAGS+= -mcall-aixdesc +LDFLAGS+= -nostdlib -e _rtld_start +.else +LDFLAGS+= -nostdlib -e .rtld_start +.endif +WARNS?= 2 +INSTALLFLAGS= -C -b +PRECIOUSPROG= +BINDIR= /libexec +SYMLINKS= ${BINDIR}/${PROG} /usr/libexec/${PROG} +MLINKS= rtld.1 ld-elf.so.1.1 \ + rtld.1 ld.so.1 + +.if ${MACHINE_CPUARCH} == "sparc64" +CFLAGS+= -fPIC +.else +CFLAGS+= -fpic +.endif +CFLAGS+= -DPIC $(DEBUG) +LDFLAGS+= -shared -Wl,-Bsymbolic +DPADD= ${LIBC_PIC} +LDADD= -lc_pic + +.if ${MACHINE_CPUARCH} == "arm" +# Some of the required math functions (div & mod) are implemented in +# libcompiler_rt on ARM. The library also needs to be placed first to be +# correctly linked. As some of the functions are used before we have +# shared libraries. +DPADD+= ${LIBCOMPILER_RT} +LDADD+= -lcompiler_rt +.endif + + + +.if ${MK_SYMVER} == "yes" +LIBCDIR= ${TOPSRCDIR}/lib/libc +VERSION_DEF= ${LIBCDIR}/Versions.def +SYMBOL_MAPS= ${.CURDIR}/Symbol.map +VERSION_MAP= Version.map +LDFLAGS+= -Wl,--version-script=${VERSION_MAP} + +${PROG}: ${VERSION_MAP} + +.if exists(${.CURDIR}/${RTLD_ARCH}/Symbol.map) +SYMBOL_MAPS+= ${.CURDIR}/${RTLD_ARCH}/Symbol.map +.endif +.endif + +.sinclude "${.CURDIR}/${RTLD_ARCH}/Makefile.inc" + +# Since moving rtld-elf to /libexec, we need to create a symlink. +# Fixup the existing binary that's there so we can symlink over it. +beforeinstall: +.if exists(${DESTDIR}/usr/libexec/${PROG}) + -chflags -h noschg ${DESTDIR}/usr/libexec/${PROG} +.endif + +.PATH: ${.CURDIR}/${RTLD_ARCH} + +.if ${MK_TESTS} != "no" +SUBDIR+= tests +.endif + +.include <bsd.symver.mk> +.include <bsd.prog.mk> diff --git a/libexec/rtld-elf/Symbol.map b/libexec/rtld-elf/Symbol.map new file mode 100644 index 0000000..5ea7d7e --- /dev/null +++ b/libexec/rtld-elf/Symbol.map @@ -0,0 +1,34 @@ +/* + * $FreeBSD$ + */ + +FBSD_1.0 { + _rtld_error; + dlclose; + dlerror; + dlopen; + dlsym; + dlfunc; + dlvsym; + dladdr; + dllockinit; + dlinfo; + dl_iterate_phdr; + r_debug_state; + __tls_get_addr; +}; + +FBSD_1.3 { + fdlopen; +}; + +FBSDprivate_1.0 { + _rtld_thread_init; + _rtld_allocate_tls; + _rtld_free_tls; + _rtld_atfork_pre; + _rtld_atfork_post; + _rtld_addr_phdr; + _rtld_get_stack_prot; + _r_debug_postinit; +}; diff --git a/libexec/rtld-elf/amd64/Makefile.inc b/libexec/rtld-elf/amd64/Makefile.inc new file mode 100644 index 0000000..7528dbe --- /dev/null +++ b/libexec/rtld-elf/amd64/Makefile.inc @@ -0,0 +1,6 @@ +# $FreeBSD$ + +CFLAGS+= -mno-mmx -mno-3dnow -mno-sse -mno-sse2 -mno-sse3 -msoft-float +# Uncomment this to build the dynamic linker as an executable instead +# of a shared library: +#LDSCRIPT= ${.CURDIR}/${MACHINE_CPUARCH}/elf_rtld.x diff --git a/libexec/rtld-elf/amd64/elf_rtld.x b/libexec/rtld-elf/amd64/elf_rtld.x new file mode 100644 index 0000000..e19168c --- /dev/null +++ b/libexec/rtld-elf/amd64/elf_rtld.x @@ -0,0 +1,131 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", + "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SEARCH_DIR(/usr/lib); SEARCH_DIR(/usr/obj/usr/src/tmp/usr/i386-unknown-freebsdelf/lib); +/* Do we need any of these for elf? + __DYNAMIC = 0; */ +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0x08000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.text : + { *(.rel.text) *(.rel.gnu.linkonce.t*) } + .rela.text : + { *(.rela.text) *(.rela.gnu.linkonce.t*) } + .rel.data : + { *(.rel.data) *(.rel.gnu.linkonce.d*) } + .rela.data : + { *(.rela.data) *(.rela.gnu.linkonce.d*) } + .rel.rodata : + { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } + .rela.rodata : + { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .init : { *(.init) } =0x9090 + .plt : { *(.plt) } + .text : + { + *(.text) + *(.stub) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.gnu.linkonce.t*) + } =0x9090 + _etext = .; + PROVIDE (etext = .); + .fini : { *(.fini) } =0x9090 + .rodata : { *(.rodata) *(.gnu.linkonce.r*) } + .rodata1 : { *(.rodata1) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x1000) + (. & (0x1000 - 1)); + .data : + { + *(.data) + *(.gnu.linkonce.d*) + CONSTRUCTORS + } + .data1 : { *(.data1) } + .ctors : + { + *(.ctors) + } + .dtors : + { + *(.dtors) + } + .got : { *(.got.plt) *(.got) } + .dynamic : { *(.dynamic) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : { *(.sdata) } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + . = ALIGN(32 / 8); + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} diff --git a/libexec/rtld-elf/amd64/reloc.c b/libexec/rtld-elf/amd64/reloc.c new file mode 100644 index 0000000..35f33cc --- /dev/null +++ b/libexec/rtld-elf/amd64/reloc.c @@ -0,0 +1,471 @@ +/*- + * Copyright 1996, 1997, 1998, 1999 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Dynamic linker for ELF. + * + * John Polstra <jdp@polstra.com>. + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <machine/sysarch.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "debug.h" +#include "rtld.h" + +/* + * Process the special R_X86_64_COPY relocations in the main program. These + * copy data from a shared object into a region in the main program's BSS + * segment. + * + * Returns 0 on success, -1 on failure. + */ +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + assert(dstobj->mainprog); /* COPY relocations are invalid elsewhere */ + + relalim = (const Elf_Rela *) ((caddr_t) dstobj->rela + dstobj->relasize); + for (rela = dstobj->rela; rela < relalim; rela++) { + if (ELF_R_TYPE(rela->r_info) == R_X86_64_COPY) { + void *dstaddr; + const Elf_Sym *dstsym; + const char *name; + size_t size; + const void *srcaddr; + const Elf_Sym *srcsym; + const Obj_Entry *srcobj, *defobj; + SymLook req; + int res; + + dstaddr = (void *) (dstobj->relocbase + rela->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rela->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + + if (srcobj == NULL) { + _rtld_error("Undefined symbol \"%s\" referenced from COPY" + " relocation in %s", name, dstobj->path); + return -1; + } + + srcaddr = (const void *) (defobj->relocbase + srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + } + } + + return 0; +} + +/* Initialize the special GOT entries. */ +void +init_pltgot(Obj_Entry *obj) +{ + if (obj->pltgot != NULL) { + obj->pltgot[1] = (Elf_Addr) obj; + obj->pltgot[2] = (Elf_Addr) &_rtld_bind_start; + } +} + +/* Process the non-PLT relocations. */ +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + SymCache *cache; + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr *where, symval; + Elf32_Addr *where32; + int r; + + r = -1; + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + if (obj != obj_rtld) { + cache = calloc(obj->dynsymcount, sizeof(SymCache)); + /* No need to check for NULL here */ + } else + cache = NULL; + + relalim = (const Elf_Rela *)((caddr_t)obj->rela + obj->relasize); + for (rela = obj->rela; rela < relalim; rela++) { + /* + * First, resolve symbol for relocations which + * reference symbols. + */ + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_64: + case R_X86_64_PC32: + case R_X86_64_GLOB_DAT: + case R_X86_64_TPOFF64: + case R_X86_64_TPOFF32: + case R_X86_64_DTPMOD64: + case R_X86_64_DTPOFF64: + case R_X86_64_DTPOFF32: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, + &defobj, flags, cache, lockstate); + if (def == NULL) + goto done; + /* + * If symbol is IFUNC, only perform relocation + * when caller allowed it by passing + * SYMLOOK_IFUNC flag. Skip the relocations + * otherwise. + * + * Also error out in case IFUNC relocations + * are specified for TLS, which cannot be + * usefully interpreted. + */ + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) { + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_64: + case R_X86_64_PC32: + case R_X86_64_GLOB_DAT: + if ((flags & SYMLOOK_IFUNC) == 0) { + obj->non_plt_gnu_ifunc = true; + continue; + } + symval = (Elf_Addr)rtld_resolve_ifunc( + defobj, def); + break; + case R_X86_64_TPOFF64: + case R_X86_64_TPOFF32: + case R_X86_64_DTPMOD64: + case R_X86_64_DTPOFF64: + case R_X86_64_DTPOFF32: + _rtld_error("%s: IFUNC for TLS reloc", + obj->path); + goto done; + } + } else { + if ((flags & SYMLOOK_IFUNC) != 0) + continue; + symval = (Elf_Addr)defobj->relocbase + + def->st_value; + } + break; + default: + if ((flags & SYMLOOK_IFUNC) != 0) + continue; + break; + } + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + where32 = (Elf32_Addr *)where; + + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_NONE: + break; + case R_X86_64_64: + *where = symval + rela->r_addend; + break; + case R_X86_64_PC32: + /* + * I don't think the dynamic linker should + * ever see this type of relocation. But the + * binutils-2.6 tools sometimes generate it. + */ + *where32 = (Elf32_Addr)(unsigned long)(symval + + rela->r_addend - (Elf_Addr)where); + break; + /* missing: R_X86_64_GOT32 R_X86_64_PLT32 */ + case R_X86_64_COPY: + /* + * These are deferred until all other relocations have + * been done. All we do here is make sure that the COPY + * relocation is not in a shared library. They are allowed + * only in executable files. + */ + if (!obj->mainprog) { + _rtld_error("%s: Unexpected R_X86_64_COPY " + "relocation in shared library", obj->path); + goto done; + } + break; + case R_X86_64_GLOB_DAT: + *where = symval; + break; + case R_X86_64_TPOFF64: + /* + * We lazily allocate offsets for static TLS + * as we see the first relocation that + * references the TLS block. This allows us to + * support (small amounts of) static TLS in + * dynamically loaded modules. If we run out + * of space, we generate an error. + */ + if (!defobj->tls_done) { + if (!allocate_tls_offset((Obj_Entry*) defobj)) { + _rtld_error("%s: No space available " + "for static Thread Local Storage", + obj->path); + goto done; + } + } + *where = (Elf_Addr)(def->st_value - defobj->tlsoffset + + rela->r_addend); + break; + case R_X86_64_TPOFF32: + /* + * We lazily allocate offsets for static TLS + * as we see the first relocation that + * references the TLS block. This allows us to + * support (small amounts of) static TLS in + * dynamically loaded modules. If we run out + * of space, we generate an error. + */ + if (!defobj->tls_done) { + if (!allocate_tls_offset((Obj_Entry*) defobj)) { + _rtld_error("%s: No space available " + "for static Thread Local Storage", + obj->path); + goto done; + } + } + *where32 = (Elf32_Addr)(def->st_value - + defobj->tlsoffset + rela->r_addend); + break; + case R_X86_64_DTPMOD64: + *where += (Elf_Addr)defobj->tlsindex; + break; + case R_X86_64_DTPOFF64: + *where += (Elf_Addr)(def->st_value + rela->r_addend); + break; + case R_X86_64_DTPOFF32: + *where32 += (Elf32_Addr)(def->st_value + + rela->r_addend); + break; + case R_X86_64_RELATIVE: + *where = (Elf_Addr)(obj->relocbase + rela->r_addend); + break; + /* + * missing: + * R_X86_64_GOTPCREL, R_X86_64_32, R_X86_64_32S, R_X86_64_16, + * R_X86_64_PC16, R_X86_64_8, R_X86_64_PC8 + */ + default: + _rtld_error("%s: Unsupported relocation type %u" + " in non-PLT relocations\n", obj->path, + (unsigned int)ELF_R_TYPE(rela->r_info)); + goto done; + } + } + r = 0; +done: + free(cache); + return (r); +} + +/* Process the PLT relocations. */ +int +reloc_plt(Obj_Entry *obj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + Elf_Addr *where; + + switch(ELF_R_TYPE(rela->r_info)) { + case R_X86_64_JMP_SLOT: + /* Relocate the GOT slot pointing into the PLT. */ + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + *where += (Elf_Addr)obj->relocbase; + break; + + case R_X86_64_IRELATIVE: + obj->irelative = true; + break; + + default: + _rtld_error("Unknown relocation type %x in PLT", + (unsigned int)ELF_R_TYPE(rela->r_info)); + return (-1); + } + } + return 0; +} + +/* Relocate the jump slots in an object. */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + if (obj->jmpslots_done) + return 0; + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + Elf_Addr *where, target; + const Elf_Sym *def; + const Obj_Entry *defobj; + + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_JMP_SLOT: + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) + return (-1); + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) { + obj->gnu_ifunc = true; + continue; + } + target = (Elf_Addr)(defobj->relocbase + def->st_value + rela->r_addend); + reloc_jmpslot(where, target, defobj, obj, (const Elf_Rel *)rela); + break; + + case R_X86_64_IRELATIVE: + break; + + default: + _rtld_error("Unknown relocation type %x in PLT", + (unsigned int)ELF_R_TYPE(rela->r_info)); + return (-1); + } + } + obj->jmpslots_done = true; + return 0; +} + +int +reloc_iresolve(Obj_Entry *obj, RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + if (!obj->irelative) + return (0); + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + Elf_Addr *where, target, *ptr; + + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_JMP_SLOT: + break; + + case R_X86_64_IRELATIVE: + ptr = (Elf_Addr *)(obj->relocbase + rela->r_addend); + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + lock_release(rtld_bind_lock, lockstate); + target = ((Elf_Addr (*)(void))ptr)(); + wlock_acquire(rtld_bind_lock, lockstate); + *where = target; + break; + } + } + obj->irelative = false; + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + if (!obj->gnu_ifunc) + return (0); + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + Elf_Addr *where, target; + const Elf_Sym *def; + const Obj_Entry *defobj; + + switch (ELF_R_TYPE(rela->r_info)) { + case R_X86_64_JMP_SLOT: + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) + return (-1); + if (ELF_ST_TYPE(def->st_info) != STT_GNU_IFUNC) + continue; + lock_release(rtld_bind_lock, lockstate); + target = (Elf_Addr)rtld_resolve_ifunc(defobj, def); + wlock_acquire(rtld_bind_lock, lockstate); + reloc_jmpslot(where, target, defobj, obj, (const Elf_Rel *)rela); + break; + } + } + obj->gnu_ifunc = false; + return (0); +} + +void +allocate_initial_tls(Obj_Entry *objs) +{ + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + tls_static_space = tls_last_offset + RTLD_STATIC_TLS_EXTRA; + amd64_set_fsbase(allocate_tls(objs, 0, + 3*sizeof(Elf_Addr), sizeof(Elf_Addr))); +} + +void *__tls_get_addr(tls_index *ti) +{ + Elf_Addr** segbase; + + __asm __volatile("movq %%fs:0, %0" : "=r" (segbase)); + + return tls_get_addr_common(&segbase[1], ti->ti_module, ti->ti_offset); +} diff --git a/libexec/rtld-elf/amd64/rtld_machdep.h b/libexec/rtld-elf/amd64/rtld_machdep.h new file mode 100644 index 0000000..7b5d4d2 --- /dev/null +++ b/libexec/rtld-elf/amd64/rtld_machdep.h @@ -0,0 +1,82 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) \ + ((const Elf_Dyn *)((obj)->relocbase + (Elf_Addr)&_DYNAMIC)) + +/* Fixup the jump slot at "where" to transfer control to "target". */ +static inline Elf_Addr +reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *obj, + const struct Struct_Obj_Entry *refobj, const Elf_Rel *rel) +{ +#ifdef dbg + dbg("reloc_jmpslot: *%p = %p", (void *)(where), + (void *)(target)); +#endif + (*(Elf_Addr *)(where) = (Elf_Addr)(target)); + return target; +} + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(size, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round((prev_offset) + (size), align) +#define calculate_tls_end(off, size) (off) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +extern void *__tls_get_addr(tls_index *ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/amd64/rtld_start.S b/libexec/rtld-elf/amd64/rtld_start.S new file mode 100644 index 0000000..2481f09 --- /dev/null +++ b/libexec/rtld-elf/amd64/rtld_start.S @@ -0,0 +1,159 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + + .text + .align 4 + .globl .rtld_start + .type .rtld_start,@function +.rtld_start: + xorq %rbp,%rbp # Clear frame pointer for good form + subq $24,%rsp # A place to store exit procedure addr + movq %rdi,%r12 + movq %rsp,%rsi # save address of exit proc + movq %rsp,%rdx # construct address of obj_main + addq $8,%rdx + call _rtld@PLT # Call rtld(sp); returns entry point + popq %rsi # Get exit procedure address + movq %r12,%rdi # *ap +/* + * At this point, %rax contains the entry point of the main program, and + * %rdx contains a pointer to a termination function that should be + * registered with atexit(). (crt1.o registers it.) + */ +.globl .rtld_goto_main +.rtld_goto_main: # This symbol exists just to make debugging easier. + jmp *%rax # Enter main program + + +/* + * Binder entry point. Control is transferred to here by code in the PLT. + * On entry, there are two arguments on the stack. In ascending address + * order, they are (1) "obj", a pointer to the calling object's Obj_Entry, + * and (2) "reloff", the byte offset of the appropriate relocation entry + * in the PLT relocation table. + * + * We are careful to preserve all registers, even the caller-save + * registers. That is because this code may be invoked by low-level + * assembly-language code that is not ABI-compliant. + * + * Stack map: + * reloff 0x60 + * obj 0x58 + * spare 0x50 + * rflags 0x48 + * rax 0x40 + * rdx 0x38 + * rcx 0x30 + * rsi 0x28 + * rdi 0x20 + * r8 0x18 + * r9 0x10 + * r10 0x8 + * r11 0x0 + */ + .align 4 + .globl _rtld_bind_start + .type _rtld_bind_start,@function +_rtld_bind_start: + .cfi_startproc + .cfi_adjust_cfa_offset 16 + subq $8,%rsp + .cfi_adjust_cfa_offset 8 + pushfq # Save rflags + .cfi_adjust_cfa_offset 8 + pushq %rax # Save %rax + .cfi_adjust_cfa_offset 8 + .cfi_offset %rax,-32 + pushq %rdx # Save %rdx + .cfi_adjust_cfa_offset 8 + .cfi_offset %rdx,-40 + pushq %rcx # Save %rcx + .cfi_adjust_cfa_offset 8 + .cfi_offset %rcx,-48 + pushq %rsi # Save %rsi + .cfi_adjust_cfa_offset 8 + .cfi_offset %rsi,-56 + pushq %rdi # Save %rdi + .cfi_adjust_cfa_offset 8 + .cfi_offset %rdi,-64 + pushq %r8 # Save %r8 + .cfi_adjust_cfa_offset 8 + .cfi_offset %r8,-72 + pushq %r9 # Save %r9 + .cfi_adjust_cfa_offset 8 + .cfi_offset %r9,-80 + pushq %r10 # Save %r10 + .cfi_adjust_cfa_offset 8 + .cfi_offset %r10,-88 + pushq %r11 # Save %r11 + .cfi_adjust_cfa_offset 8 + .cfi_offset %r11,-96 + + movq 0x58(%rsp),%rdi # Fetch obj argument + movq 0x60(%rsp),%rsi # Fetch reloff argument + leaq (%rsi,%rsi,2),%rsi # multiply by 3 + leaq (,%rsi,8),%rsi # now 8, for 24 (sizeof Elf_Rela) + + call _rtld_bind@PLT # Transfer control to the binder + /* Now %rax contains the entry point of the function being called. */ + + movq %rax,0x60(%rsp) # Store target over reloff argument + popq %r11 # Restore %r11 + .cfi_adjust_cfa_offset -8 + .cfi_restore %r11 + popq %r10 # Restore %r10 + .cfi_adjust_cfa_offset -8 + .cfi_restore %r10 + popq %r9 # Restore %r9 + .cfi_adjust_cfa_offset -8 + .cfi_restore %r9 + popq %r8 # Restore %r8 + .cfi_adjust_cfa_offset -8 + .cfi_restore %r8 + popq %rdi # Restore %rdi + .cfi_adjust_cfa_offset -8 + .cfi_restore %rdi + popq %rsi # Restore %rsi + .cfi_adjust_cfa_offset -8 + .cfi_restore %rsi + popq %rcx # Restore %rcx + .cfi_adjust_cfa_offset -8 + .cfi_restore %rcx + popq %rdx # Restore %rdx + .cfi_adjust_cfa_offset -8 + .cfi_restore %rdx + popq %rax # Restore %rax + .cfi_adjust_cfa_offset -8 + .cfi_restore %rax + popfq # Restore rflags + .cfi_adjust_cfa_offset -8 + leaq 16(%rsp),%rsp # Discard spare, obj, do not change rflags + ret # "Return" to target address + .cfi_endproc + .size _rtld_bind_start, . - _rtld_bind_start + + .section .note.GNU-stack,"",%progbits diff --git a/libexec/rtld-elf/arm/Makefile.inc b/libexec/rtld-elf/arm/Makefile.inc new file mode 100644 index 0000000..e8c0da7 --- /dev/null +++ b/libexec/rtld-elf/arm/Makefile.inc @@ -0,0 +1 @@ +# $FreeBSD$ diff --git a/libexec/rtld-elf/arm/reloc.c b/libexec/rtld-elf/arm/reloc.c new file mode 100644 index 0000000..9cbdc0e --- /dev/null +++ b/libexec/rtld-elf/arm/reloc.c @@ -0,0 +1,475 @@ +/* $NetBSD: mdreloc.c,v 1.23 2003/07/26 15:04:38 mrg Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +#include <sys/param.h> +#include <sys/mman.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "machine/sysarch.h" + +#include "debug.h" +#include "rtld.h" + +void +init_pltgot(Obj_Entry *obj) +{ + if (obj->pltgot != NULL) { + obj->pltgot[1] = (Elf_Addr) obj; + obj->pltgot[2] = (Elf_Addr) &_rtld_bind_start; + } +} + +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + assert(dstobj->mainprog); /* COPY relocations are invalid elsewhere */ + + rellim = (const Elf_Rel *) ((caddr_t) dstobj->rel + dstobj->relsize); + for (rel = dstobj->rel; rel < rellim; rel++) { + if (ELF_R_TYPE(rel->r_info) == R_ARM_COPY) { + void *dstaddr; + const Elf_Sym *dstsym; + const char *name; + size_t size; + const void *srcaddr; + const Elf_Sym *srcsym; + const Obj_Entry *srcobj, *defobj; + SymLook req; + int res; + + dstaddr = (void *) (dstobj->relocbase + rel->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rel->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, + ELF_R_SYM(rel->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; + srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + if (srcobj == NULL) { + _rtld_error( +"Undefined symbol \"%s\" referenced from COPY relocation in %s", + name, dstobj->path); + return (-1); + } + + srcaddr = (const void *)(defobj->relocbase + + srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + } + } + return 0; +} + +void _rtld_bind_start(void); +void _rtld_relocate_nonplt_self(Elf_Dyn *, Elf_Addr); + +int open(); +int _open(); +void +_rtld_relocate_nonplt_self(Elf_Dyn *dynp, Elf_Addr relocbase) +{ + const Elf_Rel *rel = 0, *rellim; + Elf_Addr relsz = 0; + Elf_Addr *where; + uint32_t size; + + for (; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + case DT_REL: + rel = (const Elf_Rel *)(relocbase + dynp->d_un.d_ptr); + break; + case DT_RELSZ: + relsz = dynp->d_un.d_val; + break; + } + } + rellim = (const Elf_Rel *)((caddr_t)rel + relsz); + size = (rellim - 1)->r_offset - rel->r_offset; + for (; rel < rellim; rel++) { + where = (Elf_Addr *)(relocbase + rel->r_offset); + + *where += (Elf_Addr)relocbase; + } +} +/* + * It is possible for the compiler to emit relocations for unaligned data. + * We handle this situation with these inlines. + */ +#define RELOC_ALIGNED_P(x) \ + (((uintptr_t)(x) & (sizeof(void *) - 1)) == 0) + +static __inline Elf_Addr +load_ptr(void *where) +{ + Elf_Addr res; + + memcpy(&res, where, sizeof(res)); + + return (res); +} + +static __inline void +store_ptr(void *where, Elf_Addr val) +{ + + memcpy(where, &val, sizeof(val)); +} + +static int +reloc_nonplt_object(Obj_Entry *obj, const Elf_Rel *rel, SymCache *cache, + int flags, RtldLockState *lockstate) +{ + Elf_Addr *where; + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr tmp; + unsigned long symnum; + + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + symnum = ELF_R_SYM(rel->r_info); + + switch (ELF_R_TYPE(rel->r_info)) { + case R_ARM_NONE: + break; + +#if 1 /* XXX should not occur */ + case R_ARM_PC24: { /* word32 S - P + A */ + Elf32_Sword addend; + + /* + * Extract addend and sign-extend if needed. + */ + addend = *where; + if (addend & 0x00800000) + addend |= 0xff000000; + + def = find_symdef(symnum, obj, &defobj, flags, cache, + lockstate); + if (def == NULL) + return -1; + tmp = (Elf_Addr)obj->relocbase + def->st_value + - (Elf_Addr)where + (addend << 2); + if ((tmp & 0xfe000000) != 0xfe000000 && + (tmp & 0xfe000000) != 0) { + _rtld_error( + "%s: R_ARM_PC24 relocation @ %p to %s failed " + "(displacement %ld (%#lx) out of range)", + obj->path, where, + obj->strtab + obj->symtab[symnum].st_name, + (long) tmp, (long) tmp); + return -1; + } + tmp >>= 2; + *where = (*where & 0xff000000) | (tmp & 0x00ffffff); + dbg("PC24 %s in %s --> %p @ %p in %s", + obj->strtab + obj->symtab[symnum].st_name, + obj->path, (void *)*where, where, defobj->path); + break; + } +#endif + + case R_ARM_ABS32: /* word32 B + S + A */ + case R_ARM_GLOB_DAT: /* word32 B + S */ + def = find_symdef(symnum, obj, &defobj, flags, cache, + lockstate); + if (def == NULL) + return -1; + if (__predict_true(RELOC_ALIGNED_P(where))) { + tmp = *where + (Elf_Addr)defobj->relocbase + + def->st_value; + *where = tmp; + } else { + tmp = load_ptr(where) + + (Elf_Addr)defobj->relocbase + + def->st_value; + store_ptr(where, tmp); + } + dbg("ABS32/GLOB_DAT %s in %s --> %p @ %p in %s", + obj->strtab + obj->symtab[symnum].st_name, + obj->path, (void *)tmp, where, defobj->path); + break; + + case R_ARM_RELATIVE: /* word32 B + A */ + if (__predict_true(RELOC_ALIGNED_P(where))) { + tmp = *where + (Elf_Addr)obj->relocbase; + *where = tmp; + } else { + tmp = load_ptr(where) + + (Elf_Addr)obj->relocbase; + store_ptr(where, tmp); + } + dbg("RELATIVE in %s --> %p", obj->path, + (void *)tmp); + break; + + case R_ARM_COPY: + /* + * These are deferred until all other relocations have + * been done. All we do here is make sure that the + * COPY relocation is not in a shared library. They + * are allowed only in executable files. + */ + if (!obj->mainprog) { + _rtld_error( + "%s: Unexpected R_COPY relocation in shared library", + obj->path); + return -1; + } + dbg("COPY (avoid in main)"); + break; + + case R_ARM_TLS_DTPOFF32: + def = find_symdef(symnum, obj, &defobj, flags, cache, + lockstate); + if (def == NULL) + return -1; + + tmp = (Elf_Addr)(def->st_value); + if (__predict_true(RELOC_ALIGNED_P(where))) + *where = tmp; + else + store_ptr(where, tmp); + + dbg("TLS_DTPOFF32 %s in %s --> %p", + obj->strtab + obj->symtab[symnum].st_name, + obj->path, (void *)tmp); + + break; + case R_ARM_TLS_DTPMOD32: + def = find_symdef(symnum, obj, &defobj, flags, cache, + lockstate); + if (def == NULL) + return -1; + + tmp = (Elf_Addr)(defobj->tlsindex); + if (__predict_true(RELOC_ALIGNED_P(where))) + *where = tmp; + else + store_ptr(where, tmp); + + dbg("TLS_DTPMOD32 %s in %s --> %p", + obj->strtab + obj->symtab[symnum].st_name, + obj->path, (void *)tmp); + + break; + + case R_ARM_TLS_TPOFF32: + def = find_symdef(symnum, obj, &defobj, flags, cache, + lockstate); + if (def == NULL) + return -1; + + if (!defobj->tls_done && allocate_tls_offset(obj)) + return -1; + + /* XXX: FIXME */ + tmp = (Elf_Addr)def->st_value + defobj->tlsoffset + + TLS_TCB_SIZE; + if (__predict_true(RELOC_ALIGNED_P(where))) + *where = tmp; + else + store_ptr(where, tmp); + dbg("TLS_TPOFF32 %s in %s --> %p", + obj->strtab + obj->symtab[symnum].st_name, + obj->path, (void *)tmp); + break; + + + default: + dbg("sym = %lu, type = %lu, offset = %p, " + "contents = %p, symbol = %s", + symnum, (u_long)ELF_R_TYPE(rel->r_info), + (void *)rel->r_offset, (void *)load_ptr(where), + obj->strtab + obj->symtab[symnum].st_name); + _rtld_error("%s: Unsupported relocation type %ld " + "in non-PLT relocations\n", + obj->path, (u_long) ELF_R_TYPE(rel->r_info)); + return -1; + } + return 0; +} + +/* + * * Process non-PLT relocations + * */ +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + SymCache *cache; + int r = -1; + + /* The relocation for the dynamic loader has already been done. */ + if (obj == obj_rtld) + return (0); + if ((flags & SYMLOOK_IFUNC) != 0) + /* XXX not implemented */ + return (0); + + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + cache = calloc(obj->dynsymcount, sizeof(SymCache)); + /* No need to check for NULL here */ + + rellim = (const Elf_Rel *)((caddr_t)obj->rel + obj->relsize); + for (rel = obj->rel; rel < rellim; rel++) { + if (reloc_nonplt_object(obj, rel, cache, flags, lockstate) < 0) + goto done; + } + r = 0; +done: + if (cache != NULL) + free(cache); + return (r); +} + +/* + * * Process the PLT relocations. + * */ +int +reloc_plt(Obj_Entry *obj) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + rellim = (const Elf_Rel *)((char *)obj->pltrel + + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + Elf_Addr *where; + + assert(ELF_R_TYPE(rel->r_info) == R_ARM_JUMP_SLOT); + + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + *where += (Elf_Addr )obj->relocbase; + } + + return (0); +} + +/* + * * LD_BIND_NOW was set - force relocation for all jump slots + * */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Obj_Entry *defobj; + const Elf_Rel *rellim; + const Elf_Rel *rel; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Addr target; + + rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + assert(ELF_R_TYPE(rel->r_info) == R_ARM_JUMP_SLOT); + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) { + dbg("reloc_jmpslots: sym not found"); + return (-1); + } + + target = (Elf_Addr)(defobj->relocbase + def->st_value); + reloc_jmpslot(where, target, defobj, obj, + (const Elf_Rel *) rel); + } + + obj->jmpslots_done = true; + + return (0); +} + +int +reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, + struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +Elf_Addr +reloc_jmpslot(Elf_Addr *where, Elf_Addr target, const Obj_Entry *defobj, + const Obj_Entry *obj, const Elf_Rel *rel) +{ + + assert(ELF_R_TYPE(rel->r_info) == R_ARM_JUMP_SLOT); + + if (*where != target) + *where = target; + + return target; +} + +void +allocate_initial_tls(Obj_Entry *objs) +{ +#ifdef ARM_TP_ADDRESS + void **_tp = (void **)ARM_TP_ADDRESS; +#endif + + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + + tls_static_space = tls_last_offset + tls_last_size + RTLD_STATIC_TLS_EXTRA; + +#ifdef ARM_TP_ADDRESS + (*_tp) = (void *) allocate_tls(objs, NULL, TLS_TCB_SIZE, 8); +#else + sysarch(ARM_SET_TP, allocate_tls(objs, NULL, TLS_TCB_SIZE, 8)); +#endif +} + +void * +__tls_get_addr(tls_index* ti) +{ + char *p; +#ifdef ARM_TP_ADDRESS + void **_tp = (void **)ARM_TP_ADDRESS; + + p = tls_get_addr_common((Elf_Addr **)(*_tp), ti->ti_module, ti->ti_offset); +#else + void *_tp; + __asm __volatile("mrc p15, 0, %0, c13, c0, 3" \ + : "=r" (_tp)); + p = tls_get_addr_common((Elf_Addr **)(_tp), ti->ti_module, ti->ti_offset); +#endif + + return (p); +} diff --git a/libexec/rtld-elf/arm/rtld_machdep.h b/libexec/rtld-elf/arm/rtld_machdep.h new file mode 100644 index 0000000..f980de0 --- /dev/null +++ b/libexec/rtld-elf/arm/rtld_machdep.h @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) (&_DYNAMIC) + +Elf_Addr reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *defobj, + const struct Struct_Obj_Entry *obj, + const Elf_Rel *rel); + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +#define TLS_TCB_SIZE 8 +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(8, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round(prev_offset + prev_size, align) +#define calculate_tls_end(off, size) ((off) + (size)) + +/* + * Lazy binding entry point, called via PLT. + */ +void _rtld_bind_start(void); + +extern void *__tls_get_addr(tls_index *ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/arm/rtld_start.S b/libexec/rtld-elf/arm/rtld_start.S new file mode 100644 index 0000000..c482808 --- /dev/null +++ b/libexec/rtld-elf/arm/rtld_start.S @@ -0,0 +1,99 @@ +/* $NetBSD: rtld_start.S,v 1.7 2002/09/12 17:18:38 mycroft Exp $ */ + +/*- + * Copyright (c) 1998, 2002 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Matt Thomas and by Charles M. Hannum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <machine/asm.h> +__FBSDID("$FreeBSD$"); + + .text + .align 0 + .globl .rtld_start + .type .rtld_start,%function +.rtld_start: + mov r6, sp /* save the stack pointer */ + bic sp, sp, #7 /* align the stack pointer */ + sub sp, sp, #8 /* make room for obj_main & exit proc */ + mov r4, r0 /* save ps_strings */ + ldr sl, .L2 + ldr r5, .L2+4 + ldr r0, .L2+8 +.L1: + add sl, pc, sl + ldr r5, [sl, r5] + ldr r0, [sl, r0] + + sub r1, sl, r5 /* relocbase */ + add r0, r1, r0 /* &_DYNAMIC */ + bl _rtld_relocate_nonplt_self + mov r1, sp + add r2, sp, #4 + mov r0, r6 /* load the sp the kernel gave us */ + bl _rtld /* call the shared loader */ + mov r3, r0 /* save entry point */ + + ldr r2, [sp, #0] /* r2 = cleanup */ + ldr r1, [sp, #4] /* r1 = obj_main */ + mov sp, r6 /* restore stack */ + mov r0, r4 /* restore ps_strings */ + mov pc, r3 /* jump to the entry point */ +.L2: + .word _GLOBAL_OFFSET_TABLE_ - (.L1+8) + .word _GLOBAL_OFFSET_TABLE_(GOT) + .word _DYNAMIC(GOT) + + .align 0 + .globl _rtld_bind_start + .type _rtld_bind_start,%function +/* + * stack[0] = RA + * ip = &GOT[n+3] + * lr = &GOT[2] + */ +_rtld_bind_start: + stmdb sp!,{r0-r5,sl,fp} + + sub r1, ip, lr /* r1 = 4 * (n + 1) */ + sub r1, r1, #4 /* r1 = 4 * n */ + add r1, r1, r1 /* r1 = 8 * n */ + + ldr r0, [lr, #-4] /* get obj ptr from GOT[1] */ + mov r4, ip /* save GOT location */ + + mov r5, sp /* Save the stack pointer */ + bic sp, sp, #7 /* Align the stack pointer */ + bl _rtld_bind /* Call the binder */ + mov sp, r5 /* Restore the old stack pointer */ + + str r0, [r4] /* save address in GOT */ + mov ip, r0 /* save new address */ + + ldmia sp!,{r0-r5,sl,fp,lr} /* restore the stack */ + mov pc, ip /* jump to the new address */ + diff --git a/libexec/rtld-elf/debug.c b/libexec/rtld-elf/debug.c new file mode 100644 index 0000000..8f8311c --- /dev/null +++ b/libexec/rtld-elf/debug.c @@ -0,0 +1,143 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Support for printing debugging messages. + */ + +#include <stdarg.h> +#include <stdio.h> + +#include "debug.h" +#include "rtld.h" +#include "rtld_printf.h" + +static const char rel_header[] = + " symbol name r_info r_offset st_value st_size address value\n" + " ------------------------------------------------------------------------------\n"; +static const char rel_format[] = " %-25s %6lx %08lx %08lx %7d %10p %08lx\n"; + +int debug = 0; + +void +debug_printf(const char *format, ...) +{ + if (debug) { + va_list ap; + va_start(ap, format); + + rtld_vfdprintf(STDERR_FILENO, format, ap); + rtld_fdputchar(STDERR_FILENO, '\n'); + + va_end(ap); + } +} + +void +dump_relocations (Obj_Entry *obj0) +{ + Obj_Entry *obj; + + for (obj = obj0; obj != NULL; obj = obj->next) { + dump_obj_relocations(obj); + } +} + +void +dump_obj_relocations (Obj_Entry *obj) +{ + + rtld_printf("Object \"%s\", relocbase %p\n", obj->path, obj->relocbase); + + if (obj->relsize) { + rtld_printf("Non-PLT Relocations: %ld\n", + (obj->relsize / sizeof(Elf_Rel))); + dump_Elf_Rel(obj, obj->rel, obj->relsize); + } + + if (obj->relasize) { + rtld_printf("Non-PLT Relocations with Addend: %ld\n", + (obj->relasize / sizeof(Elf_Rela))); + dump_Elf_Rela(obj, obj->rela, obj->relasize); + } + + if (obj->pltrelsize) { + rtld_printf("PLT Relocations: %ld\n", + (obj->pltrelsize / sizeof(Elf_Rel))); + dump_Elf_Rel(obj, obj->pltrel, obj->pltrelsize); + } + + if (obj->pltrelasize) { + rtld_printf("PLT Relocations with Addend: %ld\n", + (obj->pltrelasize / sizeof(Elf_Rela))); + dump_Elf_Rela(obj, obj->pltrela, obj->pltrelasize); + } +} + +void +dump_Elf_Rel (Obj_Entry *obj, const Elf_Rel *rel0, u_long relsize) +{ + const Elf_Rel *rel; + const Elf_Rel *rellim; + const Elf_Sym *sym; + Elf_Addr *dstaddr; + + rtld_putstr(rel_header); + rellim = (const Elf_Rel *)((const char *)rel0 + relsize); + for (rel = rel0; rel < rellim; rel++) { + dstaddr = (Elf_Addr *)(obj->relocbase + rel->r_offset); + sym = obj->symtab + ELF_R_SYM(rel->r_info); + rtld_printf(rel_format, + obj->strtab + sym->st_name, + (u_long)rel->r_info, (u_long)rel->r_offset, + (u_long)sym->st_value, (int)sym->st_size, + dstaddr, (u_long)*dstaddr); + } + return; +} + +void +dump_Elf_Rela (Obj_Entry *obj, const Elf_Rela *rela0, u_long relasize) +{ + const Elf_Rela *rela; + const Elf_Rela *relalim; + const Elf_Sym *sym; + Elf_Addr *dstaddr; + + rtld_putstr(rel_header); + relalim = (const Elf_Rela *)((const char *)rela0 + relasize); + for (rela = rela0; rela < relalim; rela++) { + dstaddr = (Elf_Addr *)(obj->relocbase + rela->r_offset); + sym = obj->symtab + ELF_R_SYM(rela->r_info); + rtld_printf(rel_format, + obj->strtab + sym->st_name, + (u_long)rela->r_info, (u_long)rela->r_offset, + (u_long)sym->st_value, (int)sym->st_size, + dstaddr, (u_long)*dstaddr); + } + return; +} diff --git a/libexec/rtld-elf/debug.h b/libexec/rtld-elf/debug.h new file mode 100644 index 0000000..98fdfb4 --- /dev/null +++ b/libexec/rtld-elf/debug.h @@ -0,0 +1,66 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Support for printing debugging messages. + */ + +#ifndef DEBUG_H +#define DEBUG_H 1 + +#ifndef __GNUC__ +#error "This file must be compiled with GCC" +#endif + +#include <sys/cdefs.h> + +#include <string.h> +#include <unistd.h> + +extern void debug_printf(const char *, ...) __printflike(1, 2); +extern int debug; + +#ifdef DEBUG +#define dbg(...) debug_printf(__VA_ARGS__) +#else +#define dbg(...) ((void) 0) +#endif + +#ifndef COMPAT_32BIT +#define _MYNAME "ld-elf.so.1" +#else +#define _MYNAME "ld-elf32.so.1" +#endif + +#define assert(cond) ((cond) ? (void) 0 : \ + (msg(_MYNAME ": assert failed: " __FILE__ ":" \ + __XSTRING(__LINE__) "\n"), abort())) +#define msg(s) write(STDOUT_FILENO, s, strlen(s)) +#define trace() msg(_MYNAME ": " __XSTRING(__LINE__) "\n") + + +#endif /* DEBUG_H */ diff --git a/libexec/rtld-elf/i386/Makefile.inc b/libexec/rtld-elf/i386/Makefile.inc new file mode 100644 index 0000000..7528dbe --- /dev/null +++ b/libexec/rtld-elf/i386/Makefile.inc @@ -0,0 +1,6 @@ +# $FreeBSD$ + +CFLAGS+= -mno-mmx -mno-3dnow -mno-sse -mno-sse2 -mno-sse3 -msoft-float +# Uncomment this to build the dynamic linker as an executable instead +# of a shared library: +#LDSCRIPT= ${.CURDIR}/${MACHINE_CPUARCH}/elf_rtld.x diff --git a/libexec/rtld-elf/i386/Symbol.map b/libexec/rtld-elf/i386/Symbol.map new file mode 100644 index 0000000..b807310 --- /dev/null +++ b/libexec/rtld-elf/i386/Symbol.map @@ -0,0 +1,7 @@ +/* + * $FreeBSD$ + */ + +FBSD_1.0 { + ___tls_get_addr; +}; diff --git a/libexec/rtld-elf/i386/elf_rtld.x b/libexec/rtld-elf/i386/elf_rtld.x new file mode 100644 index 0000000..e19168c --- /dev/null +++ b/libexec/rtld-elf/i386/elf_rtld.x @@ -0,0 +1,131 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", + "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SEARCH_DIR(/usr/lib); SEARCH_DIR(/usr/obj/usr/src/tmp/usr/i386-unknown-freebsdelf/lib); +/* Do we need any of these for elf? + __DYNAMIC = 0; */ +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0x08000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.text : + { *(.rel.text) *(.rel.gnu.linkonce.t*) } + .rela.text : + { *(.rela.text) *(.rela.gnu.linkonce.t*) } + .rel.data : + { *(.rel.data) *(.rel.gnu.linkonce.d*) } + .rela.data : + { *(.rela.data) *(.rela.gnu.linkonce.d*) } + .rel.rodata : + { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } + .rela.rodata : + { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .init : { *(.init) } =0x9090 + .plt : { *(.plt) } + .text : + { + *(.text) + *(.stub) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.gnu.linkonce.t*) + } =0x9090 + _etext = .; + PROVIDE (etext = .); + .fini : { *(.fini) } =0x9090 + .rodata : { *(.rodata) *(.gnu.linkonce.r*) } + .rodata1 : { *(.rodata1) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x1000) + (. & (0x1000 - 1)); + .data : + { + *(.data) + *(.gnu.linkonce.d*) + CONSTRUCTORS + } + .data1 : { *(.data1) } + .ctors : + { + *(.ctors) + } + .dtors : + { + *(.dtors) + } + .got : { *(.got.plt) *(.got) } + .dynamic : { *(.dynamic) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : { *(.sdata) } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + . = ALIGN(32 / 8); + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} diff --git a/libexec/rtld-elf/i386/reloc.c b/libexec/rtld-elf/i386/reloc.c new file mode 100644 index 0000000..c1e0a39 --- /dev/null +++ b/libexec/rtld-elf/i386/reloc.c @@ -0,0 +1,439 @@ +/*- + * Copyright 1996, 1997, 1998, 1999 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Dynamic linker for ELF. + * + * John Polstra <jdp@polstra.com>. + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <machine/segments.h> +#include <machine/sysarch.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "debug.h" +#include "rtld.h" + +/* + * Process the special R_386_COPY relocations in the main program. These + * copy data from a shared object into a region in the main program's BSS + * segment. + * + * Returns 0 on success, -1 on failure. + */ +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + assert(dstobj->mainprog); /* COPY relocations are invalid elsewhere */ + + rellim = (const Elf_Rel *) ((caddr_t) dstobj->rel + dstobj->relsize); + for (rel = dstobj->rel; rel < rellim; rel++) { + if (ELF_R_TYPE(rel->r_info) == R_386_COPY) { + void *dstaddr; + const Elf_Sym *dstsym; + const char *name; + size_t size; + const void *srcaddr; + const Elf_Sym *srcsym; + const Obj_Entry *srcobj, *defobj; + SymLook req; + int res; + + dstaddr = (void *) (dstobj->relocbase + rel->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rel->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, ELF_R_SYM(rel->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + + if (srcobj == NULL) { + _rtld_error("Undefined symbol \"%s\" referenced from COPY" + " relocation in %s", name, dstobj->path); + return -1; + } + + srcaddr = (const void *) (defobj->relocbase + srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + } + } + + return 0; +} + +/* Initialize the special GOT entries. */ +void +init_pltgot(Obj_Entry *obj) +{ + if (obj->pltgot != NULL) { + obj->pltgot[1] = (Elf_Addr) obj; + obj->pltgot[2] = (Elf_Addr) &_rtld_bind_start; + } +} + +/* Process the non-PLT relocations. */ +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + SymCache *cache; + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr *where, symval, add; + int r; + + r = -1; + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + if (obj != obj_rtld) { + cache = calloc(obj->dynsymcount, sizeof(SymCache)); + /* No need to check for NULL here */ + } else + cache = NULL; + + rellim = (const Elf_Rel *)((caddr_t) obj->rel + obj->relsize); + for (rel = obj->rel; rel < rellim; rel++) { + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_32: + case R_386_PC32: + case R_386_GLOB_DAT: + case R_386_TLS_TPOFF: + case R_386_TLS_TPOFF32: + case R_386_TLS_DTPMOD32: + case R_386_TLS_DTPOFF32: + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, + flags, cache, lockstate); + if (def == NULL) + goto done; + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) { + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_32: + case R_386_PC32: + case R_386_GLOB_DAT: + if ((flags & SYMLOOK_IFUNC) == 0) { + obj->non_plt_gnu_ifunc = true; + continue; + } + symval = (Elf_Addr)rtld_resolve_ifunc( + defobj, def); + break; + case R_386_TLS_TPOFF: + case R_386_TLS_TPOFF32: + case R_386_TLS_DTPMOD32: + case R_386_TLS_DTPOFF32: + _rtld_error("%s: IFUNC for TLS reloc", + obj->path); + goto done; + } + } else { + if ((flags & SYMLOOK_IFUNC) != 0) + continue; + symval = (Elf_Addr)defobj->relocbase + + def->st_value; + } + break; + default: + if ((flags & SYMLOOK_IFUNC) != 0) + continue; + break; + } + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_NONE: + break; + case R_386_32: + *where += symval; + break; + case R_386_PC32: + /* + * I don't think the dynamic linker should ever + * see this type of relocation. But the + * binutils-2.6 tools sometimes generate it. + */ + *where += symval - (Elf_Addr)where; + break; + case R_386_COPY: + /* + * These are deferred until all other + * relocations have been done. All we do here + * is make sure that the COPY relocation is + * not in a shared library. They are allowed + * only in executable files. + */ + if (!obj->mainprog) { + _rtld_error("%s: Unexpected R_386_COPY " + "relocation in shared library", obj->path); + goto done; + } + break; + case R_386_GLOB_DAT: + *where = symval; + break; + case R_386_RELATIVE: + *where += (Elf_Addr)obj->relocbase; + break; + case R_386_TLS_TPOFF: + case R_386_TLS_TPOFF32: + /* + * We lazily allocate offsets for static TLS + * as we see the first relocation that + * references the TLS block. This allows us to + * support (small amounts of) static TLS in + * dynamically loaded modules. If we run out + * of space, we generate an error. + */ + if (!defobj->tls_done) { + if (!allocate_tls_offset((Obj_Entry*) defobj)) { + _rtld_error("%s: No space available " + "for static Thread Local Storage", + obj->path); + goto done; + } + } + add = (Elf_Addr)(def->st_value - defobj->tlsoffset); + if (ELF_R_TYPE(rel->r_info) == R_386_TLS_TPOFF) + *where += add; + else + *where -= add; + break; + case R_386_TLS_DTPMOD32: + *where += (Elf_Addr)defobj->tlsindex; + break; + case R_386_TLS_DTPOFF32: + *where += (Elf_Addr) def->st_value; + break; + default: + _rtld_error("%s: Unsupported relocation type %d" + " in non-PLT relocations\n", obj->path, + ELF_R_TYPE(rel->r_info)); + goto done; + } + } + r = 0; +done: + free(cache); + return (r); +} + +/* Process the PLT relocations. */ +int +reloc_plt(Obj_Entry *obj) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + Elf_Addr *where/*, val*/; + + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_JMP_SLOT: + /* Relocate the GOT slot pointing into the PLT. */ + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + *where += (Elf_Addr)obj->relocbase; + break; + + case R_386_IRELATIVE: + obj->irelative = true; + break; + + default: + _rtld_error("Unknown relocation type %x in PLT", + ELF_R_TYPE(rel->r_info)); + return (-1); + } + } + return 0; +} + +/* Relocate the jump slots in an object. */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + if (obj->jmpslots_done) + return 0; + rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + Elf_Addr *where, target; + const Elf_Sym *def; + const Obj_Entry *defobj; + + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_JMP_SLOT: + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) + return (-1); + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) { + obj->gnu_ifunc = true; + continue; + } + target = (Elf_Addr)(defobj->relocbase + def->st_value); + reloc_jmpslot(where, target, defobj, obj, rel); + break; + + case R_386_IRELATIVE: + break; + + default: + _rtld_error("Unknown relocation type %x in PLT", + ELF_R_TYPE(rel->r_info)); + return (-1); + } + } + + obj->jmpslots_done = true; + return 0; +} + +int +reloc_iresolve(Obj_Entry *obj, RtldLockState *lockstate) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + Elf_Addr *where, target; + + if (!obj->irelative) + return (0); + rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_IRELATIVE: + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + lock_release(rtld_bind_lock, lockstate); + target = ((Elf_Addr (*)(void))(obj->relocbase + *where))(); + wlock_acquire(rtld_bind_lock, lockstate); + *where = target; + break; + } + } + obj->irelative = false; + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Elf_Rel *rellim; + const Elf_Rel *rel; + + if (!obj->gnu_ifunc) + return (0); + rellim = (const Elf_Rel *)((char *)obj->pltrel + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + Elf_Addr *where, target; + const Elf_Sym *def; + const Obj_Entry *defobj; + + switch (ELF_R_TYPE(rel->r_info)) { + case R_386_JMP_SLOT: + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) + return (-1); + if (ELF_ST_TYPE(def->st_info) != STT_GNU_IFUNC) + continue; + lock_release(rtld_bind_lock, lockstate); + target = (Elf_Addr)rtld_resolve_ifunc(defobj, def); + wlock_acquire(rtld_bind_lock, lockstate); + reloc_jmpslot(where, target, defobj, obj, rel); + break; + } + } + + obj->gnu_ifunc = false; + return (0); +} + +void +allocate_initial_tls(Obj_Entry *objs) +{ + void* tls; + + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + tls_static_space = tls_last_offset + RTLD_STATIC_TLS_EXTRA; + tls = allocate_tls(objs, NULL, 3*sizeof(Elf_Addr), sizeof(Elf_Addr)); + i386_set_gsbase(tls); +} + +/* GNU ABI */ +__attribute__((__regparm__(1))) +void *___tls_get_addr(tls_index *ti) +{ + Elf_Addr** segbase; + + __asm __volatile("movl %%gs:0, %0" : "=r" (segbase)); + + return tls_get_addr_common(&segbase[1], ti->ti_module, ti->ti_offset); +} + +/* Sun ABI */ +void *__tls_get_addr(tls_index *ti) +{ + Elf_Addr** segbase; + + __asm __volatile("movl %%gs:0, %0" : "=r" (segbase)); + + return tls_get_addr_common(&segbase[1], ti->ti_module, ti->ti_offset); +} diff --git a/libexec/rtld-elf/i386/rtld_machdep.h b/libexec/rtld-elf/i386/rtld_machdep.h new file mode 100644 index 0000000..dfbe2e1 --- /dev/null +++ b/libexec/rtld-elf/i386/rtld_machdep.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) \ + ((const Elf_Dyn *)((obj)->relocbase + (Elf_Addr)&_DYNAMIC)) + +/* Fixup the jump slot at "where" to transfer control to "target". */ +static inline Elf_Addr +reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *obj, + const struct Struct_Obj_Entry *refobj, const Elf_Rel *rel) +{ +#ifdef dbg + dbg("reloc_jmpslot: *%p = %p", (void *)(where), + (void *)(target)); +#endif + (*(Elf_Addr *)(where) = (Elf_Addr)(target)); + return target; +} + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(size, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round((prev_offset) + (size), align) +#define calculate_tls_end(off, size) (off) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +extern void *___tls_get_addr(tls_index *ti) __attribute__((__regparm__(1))); +extern void *__tls_get_addr(tls_index *ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/i386/rtld_start.S b/libexec/rtld-elf/i386/rtld_start.S new file mode 100644 index 0000000..e7df748 --- /dev/null +++ b/libexec/rtld-elf/i386/rtld_start.S @@ -0,0 +1,93 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + + .text + .align 4 + .globl .rtld_start + .type .rtld_start,@function +.rtld_start: + xorl %ebp,%ebp # Clear frame pointer for good form + movl %esp,%eax # Save initial stack pointer + movl %esp,%esi # Save initial stack pointer + andl $0xfffffff0,%esp # Align stack pointer + subl $16,%esp # A place to store exit procedure addr + movl %esp,%ebx # save address of exit proc + movl %esp,%ecx # construct address of obj_main + addl $4,%ecx + subl $4,%esp # Keep stack aligned + pushl %ecx # Pass address of obj_main + pushl %ebx # Pass address of exit proc + pushl %eax # Pass initial stack pointer to rtld + call _rtld@PLT # Call rtld(sp); returns entry point + addl $16,%esp # Remove arguments from stack + popl %edx # Get exit procedure address + movl %esi,%esp # Ignore obj_main +/* + * At this point, %eax contains the entry point of the main program, and + * %edx contains a pointer to a termination function that should be + * registered with atexit(). (crt1.o registers it.) + */ +.globl .rtld_goto_main +.rtld_goto_main: # This symbol exists just to make debugging easier. + jmp *%eax # Enter main program + + +/* + * Binder entry point. Control is transferred to here by code in the PLT. + * On entry, there are two arguments on the stack. In ascending address + * order, they are (1) "obj", a pointer to the calling object's Obj_Entry, + * and (2) "reloff", the byte offset of the appropriate relocation entry + * in the PLT relocation table. + * + * We are careful to preserve all registers, even the caller-save + * registers. That is because this code may be invoked by low-level + * assembly-language code that is not ABI-compliant. + */ + .align 4 + .globl _rtld_bind_start + .type _rtld_bind_start,@function +_rtld_bind_start: + pushf # Save eflags + pushl %eax # Save %eax + pushl %edx # Save %edx + pushl %ecx # Save %ecx + pushl 20(%esp) # Copy reloff argument + pushl 20(%esp) # Copy obj argument + + call _rtld_bind@PLT # Transfer control to the binder + /* Now %eax contains the entry point of the function being called. */ + + addl $8,%esp # Discard binder arguments + movl %eax,20(%esp) # Store target over obj argument + popl %ecx # Restore %ecx + popl %edx # Restore %edx + popl %eax # Restore %eax + popf # Restore eflags + leal 4(%esp),%esp # Discard reloff, do not change eflags + ret # "Return" to target address + + .section .note.GNU-stack,"",%progbits diff --git a/libexec/rtld-elf/libmap.c b/libexec/rtld-elf/libmap.c new file mode 100644 index 0000000..691ad52 --- /dev/null +++ b/libexec/rtld-elf/libmap.c @@ -0,0 +1,478 @@ +/* + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/fcntl.h> +#include <sys/mman.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "debug.h" +#include "rtld.h" +#include "libmap.h" + +#ifndef _PATH_LIBMAP_CONF +#define _PATH_LIBMAP_CONF "/etc/libmap.conf" +#endif + +#ifdef COMPAT_32BIT +#undef _PATH_LIBMAP_CONF +#define _PATH_LIBMAP_CONF "/etc/libmap32.conf" +#endif + +TAILQ_HEAD(lm_list, lm); +struct lm { + char *f; + char *t; + TAILQ_ENTRY(lm) lm_link; +}; + +TAILQ_HEAD(lmp_list, lmp) lmp_head = TAILQ_HEAD_INITIALIZER(lmp_head); +struct lmp { + char *p; + enum { T_EXACT=0, T_BASENAME, T_DIRECTORY } type; + struct lm_list lml; + TAILQ_ENTRY(lmp) lmp_link; +}; + +static TAILQ_HEAD(lmc_list, lmc) lmc_head = TAILQ_HEAD_INITIALIZER(lmc_head); +struct lmc { + char *path; + TAILQ_ENTRY(lmc) next; +}; + +static int lm_count; + +static void lmc_parse(char *, size_t); +static void lmc_parse_file(char *); +static void lmc_parse_dir(char *); +static void lm_add(const char *, const char *, const char *); +static void lm_free(struct lm_list *); +static char *lml_find(struct lm_list *, const char *); +static struct lm_list *lmp_find(const char *); +static struct lm_list *lmp_init(char *); +static const char *quickbasename(const char *); + +#define iseol(c) (((c) == '#') || ((c) == '\0') || \ + ((c) == '\n') || ((c) == '\r')) + +/* + * Do not use ctype.h macros, which rely on working TLS. It is + * too early to have thread-local variables functional. + */ +#define rtld_isspace(c) ((c) == ' ' || (c) == '\t') + +int +lm_init(char *libmap_override) +{ + char *p; + + dbg("lm_init(\"%s\")", libmap_override); + TAILQ_INIT(&lmp_head); + + lmc_parse_file(_PATH_LIBMAP_CONF); + + if (libmap_override) { + /* + * Do some character replacement to make $LDLIBMAP look + * like a text file, then parse it. + */ + libmap_override = xstrdup(libmap_override); + for (p = libmap_override; *p; p++) { + switch (*p) { + case '=': + *p = ' '; + break; + case ',': + *p = '\n'; + break; + } + } + lmc_parse(libmap_override, p - libmap_override); + free(libmap_override); + } + + return (lm_count == 0); +} + +static void +lmc_parse_file(char *path) +{ + struct lmc *p; + struct stat st; + int fd; + char *rpath; + char *lm_map; + + rpath = realpath(path, NULL); + if (rpath == NULL) + return; + + TAILQ_FOREACH(p, &lmc_head, next) { + if (strcmp(p->path, rpath) == 0) { + free(rpath); + return; + } + } + + fd = open(rpath, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + dbg("lm_parse_file: open(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + if (fstat(fd, &st) == -1) { + close(fd); + dbg("lm_parse_file: fstat(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + lm_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (lm_map == (const char *)MAP_FAILED) { + close(fd); + dbg("lm_parse_file: mmap(\"%s\") failed, %s", rpath, + rtld_strerror(errno)); + free(rpath); + return; + } + close(fd); + p = xmalloc(sizeof(struct lmc)); + p->path = rpath; + TAILQ_INSERT_HEAD(&lmc_head, p, next); + lmc_parse(lm_map, st.st_size); + munmap(lm_map, st.st_size); +} + +static void +lmc_parse_dir(char *idir) +{ + DIR *d; + struct dirent *dp; + struct lmc *p; + char conffile[MAXPATHLEN]; + char *ext; + char *rpath; + + rpath = realpath(idir, NULL); + if (rpath == NULL) + return; + + TAILQ_FOREACH(p, &lmc_head, next) { + if (strcmp(p->path, rpath) == 0) { + free(rpath); + return; + } + } + d = opendir(idir); + if (d == NULL) { + free(rpath); + return; + } + + p = xmalloc(sizeof(struct lmc)); + p->path = rpath; + TAILQ_INSERT_HEAD(&lmc_head, p, next); + + while ((dp = readdir(d)) != NULL) { + if (dp->d_ino == 0) + continue; + if (dp->d_type != DT_REG) + continue; + ext = strrchr(dp->d_name, '.'); + if (ext == NULL) + continue; + if (strcmp(ext, ".conf") != 0) + continue; + if (strlcpy(conffile, idir, MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + if (strlcat(conffile, "/", MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + if (strlcat(conffile, dp->d_name, MAXPATHLEN) >= MAXPATHLEN) + continue; /* too long */ + lmc_parse_file(conffile); + } + closedir(d); +} + +static void +lmc_parse(char *lm_p, size_t lm_len) +{ + char *cp, *f, *t, *c, *p; + char prog[MAXPATHLEN]; + /* allow includedir + full length path */ + char line[MAXPATHLEN + 13]; + size_t cnt; + int i; + + cnt = 0; + p = NULL; + while (cnt < lm_len) { + i = 0; + while (cnt < lm_len && lm_p[cnt] != '\n' && + i < sizeof(line) - 1) { + line[i] = lm_p[cnt]; + cnt++; + i++; + } + line[i] = '\0'; + while (cnt < lm_len && lm_p[cnt] != '\n') + cnt++; + /* skip over nl */ + cnt++; + + cp = &line[0]; + t = f = c = NULL; + + /* Skip over leading space */ + while (rtld_isspace(*cp)) cp++; + + /* Found a comment or EOL */ + if (iseol(*cp)) continue; + + /* Found a constraint selector */ + if (*cp == '[') { + cp++; + + /* Skip leading space */ + while (rtld_isspace(*cp)) cp++; + + /* Found comment, EOL or end of selector */ + if (iseol(*cp) || *cp == ']') + continue; + + c = cp++; + /* Skip to end of word */ + while (!rtld_isspace(*cp) && !iseol(*cp) && *cp != ']') + cp++; + + /* Skip and zero out trailing space */ + while (rtld_isspace(*cp)) *cp++ = '\0'; + + /* Check if there is a closing brace */ + if (*cp != ']') continue; + + /* Terminate string if there was no trailing space */ + *cp++ = '\0'; + + /* + * There should be nothing except whitespace or comment + from this point to the end of the line. + */ + while(rtld_isspace(*cp)) cp++; + if (!iseol(*cp)) continue; + + if (strlcpy(prog, c, sizeof prog) >= sizeof prog) + continue; + p = prog; + continue; + } + + /* Parse the 'from' candidate. */ + f = cp++; + while (!rtld_isspace(*cp) && !iseol(*cp)) cp++; + + /* Skip and zero out the trailing whitespace */ + while (rtld_isspace(*cp)) *cp++ = '\0'; + + /* Found a comment or EOL */ + if (iseol(*cp)) continue; + + /* Parse 'to' mapping */ + t = cp++; + while (!rtld_isspace(*cp) && !iseol(*cp)) cp++; + + /* Skip and zero out the trailing whitespace */ + while (rtld_isspace(*cp)) *cp++ = '\0'; + + /* Should be no extra tokens at this point */ + if (!iseol(*cp)) continue; + + *cp = '\0'; + if (strcmp(f, "includedir") == 0) + lmc_parse_dir(t); + else if (strcmp(f, "include") == 0) + lmc_parse_file(t); + else + lm_add(p, f, t); + } +} + +static void +lm_free (struct lm_list *lml) +{ + struct lm *lm; + + dbg("%s(%p)", __func__, lml); + + while (!TAILQ_EMPTY(lml)) { + lm = TAILQ_FIRST(lml); + TAILQ_REMOVE(lml, lm, lm_link); + free(lm->f); + free(lm->t); + free(lm); + } + return; +} + +void +lm_fini (void) +{ + struct lmp *lmp; + struct lmc *p; + + dbg("%s()", __func__); + + while (!TAILQ_EMPTY(&lmc_head)) { + p = TAILQ_FIRST(&lmc_head); + TAILQ_REMOVE(&lmc_head, p, next); + free(p->path); + free(p); + } + + while (!TAILQ_EMPTY(&lmp_head)) { + lmp = TAILQ_FIRST(&lmp_head); + TAILQ_REMOVE(&lmp_head, lmp, lmp_link); + free(lmp->p); + lm_free(&lmp->lml); + free(lmp); + } + return; +} + +static void +lm_add (const char *p, const char *f, const char *t) +{ + struct lm_list *lml; + struct lm *lm; + + if (p == NULL) + p = "$DEFAULT$"; + + dbg("%s(\"%s\", \"%s\", \"%s\")", __func__, p, f, t); + + if ((lml = lmp_find(p)) == NULL) + lml = lmp_init(xstrdup(p)); + + lm = xmalloc(sizeof(struct lm)); + lm->f = xstrdup(f); + lm->t = xstrdup(t); + TAILQ_INSERT_HEAD(lml, lm, lm_link); + lm_count++; +} + +char * +lm_find (const char *p, const char *f) +{ + struct lm_list *lml; + char *t; + + dbg("%s(\"%s\", \"%s\")", __func__, p, f); + + if (p != NULL && (lml = lmp_find(p)) != NULL) { + t = lml_find(lml, f); + if (t != NULL) { + /* + * Add a global mapping if we have + * a successful constrained match. + */ + lm_add(NULL, f, t); + return (t); + } + } + lml = lmp_find("$DEFAULT$"); + if (lml != NULL) + return (lml_find(lml, f)); + else + return (NULL); +} + +/* Given a libmap translation list and a library name, return the + replacement library, or NULL */ +char * +lm_findn (const char *p, const char *f, const int n) +{ + char pathbuf[64], *s, *t; + + if (n < sizeof(pathbuf) - 1) + s = pathbuf; + else + s = xmalloc(n + 1); + memcpy(s, f, n); + s[n] = '\0'; + t = lm_find(p, s); + if (s != pathbuf) + free(s); + return (t); +} + +static char * +lml_find (struct lm_list *lmh, const char *f) +{ + struct lm *lm; + + dbg("%s(%p, \"%s\")", __func__, lmh, f); + + TAILQ_FOREACH(lm, lmh, lm_link) + if (strcmp(f, lm->f) == 0) + return (lm->t); + return (NULL); +} + +/* Given an executable name, return a pointer to the translation list or + NULL if no matches */ +static struct lm_list * +lmp_find (const char *n) +{ + struct lmp *lmp; + + dbg("%s(\"%s\")", __func__, n); + + TAILQ_FOREACH(lmp, &lmp_head, lmp_link) + if ((lmp->type == T_EXACT && strcmp(n, lmp->p) == 0) || + (lmp->type == T_DIRECTORY && strncmp(n, lmp->p, strlen(lmp->p)) == 0) || + (lmp->type == T_BASENAME && strcmp(quickbasename(n), lmp->p) == 0)) + return (&lmp->lml); + return (NULL); +} + +static struct lm_list * +lmp_init (char *n) +{ + struct lmp *lmp; + + dbg("%s(\"%s\")", __func__, n); + + lmp = xmalloc(sizeof(struct lmp)); + lmp->p = n; + if (n[strlen(n)-1] == '/') + lmp->type = T_DIRECTORY; + else if (strchr(n,'/') == NULL) + lmp->type = T_BASENAME; + else + lmp->type = T_EXACT; + TAILQ_INIT(&lmp->lml); + TAILQ_INSERT_HEAD(&lmp_head, lmp, lmp_link); + + return (&lmp->lml); +} + +/* libc basename is overkill. Return a pointer to the character after the + last /, or the original string if there are no slashes. */ +static const char * +quickbasename (const char *path) +{ + const char *p = path; + for (; *path; path++) { + if (*path == '/') + p = path+1; + } + return (p); +} diff --git a/libexec/rtld-elf/libmap.h b/libexec/rtld-elf/libmap.h new file mode 100644 index 0000000..54b4a2f --- /dev/null +++ b/libexec/rtld-elf/libmap.h @@ -0,0 +1,8 @@ +/* + * $FreeBSD$ + */ + +int lm_init (char *); +void lm_fini (void); +char * lm_find (const char *, const char *); +char * lm_findn (const char *, const char *, const int); diff --git a/libexec/rtld-elf/malloc.c b/libexec/rtld-elf/malloc.c new file mode 100644 index 0000000..9f7dbe0 --- /dev/null +++ b/libexec/rtld-elf/malloc.c @@ -0,0 +1,491 @@ +/*- + * Copyright (c) 1983 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +/*static char *sccsid = "from: @(#)malloc.c 5.11 (Berkeley) 2/23/91";*/ +static char *rcsid = "$FreeBSD$"; +#endif /* LIBC_SCCS and not lint */ + +/* + * malloc.c (Caltech) 2/21/82 + * Chris Kingsley, kingsley@cit-20. + * + * This is a very fast storage allocator. It allocates blocks of a small + * number of different sizes, and keeps free lists of each size. Blocks that + * don't exactly fit are passed up to the next larger size. In this + * implementation, the available sizes are 2^n-4 (or 2^n-10) bytes long. + * This is designed for use in a virtual memory environment. + */ + +#include <sys/types.h> +#include <sys/sysctl.h> +#include <paths.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/mman.h> +#include "rtld_printf.h" + +static void morecore(); +static int findbucket(); + +/* + * Pre-allocate mmap'ed pages + */ +#define NPOOLPAGES (32*1024/pagesz) +static caddr_t pagepool_start, pagepool_end; +static int morepages(); + +/* + * The overhead on a block is at least 4 bytes. When free, this space + * contains a pointer to the next free block, and the bottom two bits must + * be zero. When in use, the first byte is set to MAGIC, and the second + * byte is the size index. The remaining bytes are for alignment. + * If range checking is enabled then a second word holds the size of the + * requested block, less 1, rounded up to a multiple of sizeof(RMAGIC). + * The order of elements is critical: ov_magic must overlay the low order + * bits of ov_next, and ov_magic can not be a valid ov_next bit pattern. + */ +union overhead { + union overhead *ov_next; /* when free */ + struct { + u_char ovu_magic; /* magic number */ + u_char ovu_index; /* bucket # */ +#ifdef RCHECK + u_short ovu_rmagic; /* range magic number */ + u_int ovu_size; /* actual block size */ +#endif + } ovu; +#define ov_magic ovu.ovu_magic +#define ov_index ovu.ovu_index +#define ov_rmagic ovu.ovu_rmagic +#define ov_size ovu.ovu_size +}; + +#define MAGIC 0xef /* magic # on accounting info */ +#define RMAGIC 0x5555 /* magic # on range info */ + +#ifdef RCHECK +#define RSLOP sizeof (u_short) +#else +#define RSLOP 0 +#endif + +/* + * nextf[i] is the pointer to the next free block of size 2^(i+3). The + * smallest allocatable block is 8 bytes. The overhead information + * precedes the data area returned to the user. + */ +#define NBUCKETS 30 +static union overhead *nextf[NBUCKETS]; + +static int pagesz; /* page size */ +static int pagebucket; /* page size bucket */ + +#ifdef MSTATS +/* + * nmalloc[i] is the difference between the number of mallocs and frees + * for a given block size. + */ +static u_int nmalloc[NBUCKETS]; +#include <stdio.h> +#endif + +#if defined(MALLOC_DEBUG) || defined(RCHECK) +#define ASSERT(p) if (!(p)) botch("p") +#include <stdio.h> +static void +botch(s) + char *s; +{ + fprintf(stderr, "\r\nassertion botched: %s\r\n", s); + (void) fflush(stderr); /* just in case user buffered it */ + abort(); +} +#else +#define ASSERT(p) +#endif + +/* Debugging stuff */ +#define TRACE() rtld_printf("TRACE %s:%d\n", __FILE__, __LINE__) + +/* + * The array of supported page sizes is provided by the user, i.e., the + * program that calls this storage allocator. That program must initialize + * the array before making its first call to allocate storage. The array + * must contain at least one page size. The page sizes must be stored in + * increasing order. + */ +extern size_t *pagesizes; + +void * +malloc(nbytes) + size_t nbytes; +{ + register union overhead *op; + register int bucket; + register long n; + register unsigned amt; + + /* + * First time malloc is called, setup page size and + * align break pointer so all data will be page aligned. + */ + if (pagesz == 0) { + pagesz = n = pagesizes[0]; + if (morepages(NPOOLPAGES) == 0) + return NULL; + op = (union overhead *)(pagepool_start); + n = n - sizeof (*op) - ((long)op & (n - 1)); + if (n < 0) + n += pagesz; + if (n) { + pagepool_start += n; + } + bucket = 0; + amt = 8; + while ((unsigned)pagesz > amt) { + amt <<= 1; + bucket++; + } + pagebucket = bucket; + } + /* + * Convert amount of memory requested into closest block size + * stored in hash buckets which satisfies request. + * Account for space used per block for accounting. + */ + if (nbytes <= (unsigned long)(n = pagesz - sizeof (*op) - RSLOP)) { +#ifndef RCHECK + amt = 8; /* size of first bucket */ + bucket = 0; +#else + amt = 16; /* size of first bucket */ + bucket = 1; +#endif + n = -(sizeof (*op) + RSLOP); + } else { + amt = pagesz; + bucket = pagebucket; + } + while (nbytes > amt + n) { + amt <<= 1; + if (amt == 0) + return (NULL); + bucket++; + } + /* + * If nothing in hash bucket right now, + * request more memory from the system. + */ + if ((op = nextf[bucket]) == NULL) { + morecore(bucket); + if ((op = nextf[bucket]) == NULL) + return (NULL); + } + /* remove from linked list */ + nextf[bucket] = op->ov_next; + op->ov_magic = MAGIC; + op->ov_index = bucket; +#ifdef MSTATS + nmalloc[bucket]++; +#endif +#ifdef RCHECK + /* + * Record allocated size of block and + * bound space with magic numbers. + */ + op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); + op->ov_rmagic = RMAGIC; + *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; +#endif + return ((char *)(op + 1)); +} + +void * +calloc(size_t num, size_t size) +{ + void *ret; + + if (size != 0 && (num * size) / size != num) { + /* size_t overflow. */ + return (NULL); + } + + if ((ret = malloc(num * size)) != NULL) + memset(ret, 0, num * size); + + return (ret); +} + +/* + * Allocate more memory to the indicated bucket. + */ +static void +morecore(bucket) + int bucket; +{ + register union overhead *op; + register int sz; /* size of desired block */ + int amt; /* amount to allocate */ + int nblks; /* how many blocks we get */ + + /* + * sbrk_size <= 0 only for big, FLUFFY, requests (about + * 2^30 bytes on a VAX, I think) or for a negative arg. + */ + sz = 1 << (bucket + 3); +#ifdef MALLOC_DEBUG + ASSERT(sz > 0); +#else + if (sz <= 0) + return; +#endif + if (sz < pagesz) { + amt = pagesz; + nblks = amt / sz; + } else { + amt = sz + pagesz; + nblks = 1; + } + if (amt > pagepool_end - pagepool_start) + if (morepages(amt/pagesz + NPOOLPAGES) == 0) + return; + op = (union overhead *)pagepool_start; + pagepool_start += amt; + + /* + * Add new memory allocated to that on + * free list for this hash bucket. + */ + nextf[bucket] = op; + while (--nblks > 0) { + op->ov_next = (union overhead *)((caddr_t)op + sz); + op = (union overhead *)((caddr_t)op + sz); + } +} + +void +free(cp) + void *cp; +{ + register int size; + register union overhead *op; + + if (cp == NULL) + return; + op = (union overhead *)((caddr_t)cp - sizeof (union overhead)); +#ifdef MALLOC_DEBUG + ASSERT(op->ov_magic == MAGIC); /* make sure it was in use */ +#else + if (op->ov_magic != MAGIC) + return; /* sanity */ +#endif +#ifdef RCHECK + ASSERT(op->ov_rmagic == RMAGIC); + ASSERT(*(u_short *)((caddr_t)(op + 1) + op->ov_size) == RMAGIC); +#endif + size = op->ov_index; + ASSERT(size < NBUCKETS); + op->ov_next = nextf[size]; /* also clobbers ov_magic */ + nextf[size] = op; +#ifdef MSTATS + nmalloc[size]--; +#endif +} + +/* + * When a program attempts "storage compaction" as mentioned in the + * old malloc man page, it realloc's an already freed block. Usually + * this is the last block it freed; occasionally it might be farther + * back. We have to search all the free lists for the block in order + * to determine its bucket: 1st we make one pass thru the lists + * checking only the first block in each; if that fails we search + * ``realloc_srchlen'' blocks in each list for a match (the variable + * is extern so the caller can modify it). If that fails we just copy + * however many bytes was given to realloc() and hope it's not huge. + */ +int realloc_srchlen = 4; /* 4 should be plenty, -1 =>'s whole list */ + +void * +realloc(cp, nbytes) + void *cp; + size_t nbytes; +{ + register u_int onb; + register int i; + union overhead *op; + char *res; + int was_alloced = 0; + + if (cp == NULL) + return (malloc(nbytes)); + op = (union overhead *)((caddr_t)cp - sizeof (union overhead)); + if (op->ov_magic == MAGIC) { + was_alloced++; + i = op->ov_index; + } else { + /* + * Already free, doing "compaction". + * + * Search for the old block of memory on the + * free list. First, check the most common + * case (last element free'd), then (this failing) + * the last ``realloc_srchlen'' items free'd. + * If all lookups fail, then assume the size of + * the memory block being realloc'd is the + * largest possible (so that all "nbytes" of new + * memory are copied into). Note that this could cause + * a memory fault if the old area was tiny, and the moon + * is gibbous. However, that is very unlikely. + */ + if ((i = findbucket(op, 1)) < 0 && + (i = findbucket(op, realloc_srchlen)) < 0) + i = NBUCKETS; + } + onb = 1 << (i + 3); + if (onb < (u_int)pagesz) + onb -= sizeof (*op) + RSLOP; + else + onb += pagesz - sizeof (*op) - RSLOP; + /* avoid the copy if same size block */ + if (was_alloced) { + if (i) { + i = 1 << (i + 2); + if (i < pagesz) + i -= sizeof (*op) + RSLOP; + else + i += pagesz - sizeof (*op) - RSLOP; + } + if (nbytes <= onb && nbytes > (size_t)i) { +#ifdef RCHECK + op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); + *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; +#endif + return(cp); + } else + free(cp); + } + if ((res = malloc(nbytes)) == NULL) + return (NULL); + if (cp != res) /* common optimization if "compacting" */ + bcopy(cp, res, (nbytes < onb) ? nbytes : onb); + return (res); +} + +/* + * Search ``srchlen'' elements of each free list for a block whose + * header starts at ``freep''. If srchlen is -1 search the whole list. + * Return bucket number, or -1 if not found. + */ +static int +findbucket(freep, srchlen) + union overhead *freep; + int srchlen; +{ + register union overhead *p; + register int i, j; + + for (i = 0; i < NBUCKETS; i++) { + j = 0; + for (p = nextf[i]; p && j != srchlen; p = p->ov_next) { + if (p == freep) + return (i); + j++; + } + } + return (-1); +} + +#ifdef MSTATS +/* + * mstats - print out statistics about malloc + * + * Prints two lines of numbers, one showing the length of the free list + * for each size category, the second showing the number of mallocs - + * frees for each size category. + */ +mstats(s) + char *s; +{ + register int i, j; + register union overhead *p; + int totfree = 0, + totused = 0; + + fprintf(stderr, "Memory allocation statistics %s\nfree:\t", s); + for (i = 0; i < NBUCKETS; i++) { + for (j = 0, p = nextf[i]; p; p = p->ov_next, j++) + ; + fprintf(stderr, " %d", j); + totfree += j * (1 << (i + 3)); + } + fprintf(stderr, "\nused:\t"); + for (i = 0; i < NBUCKETS; i++) { + fprintf(stderr, " %d", nmalloc[i]); + totused += nmalloc[i] * (1 << (i + 3)); + } + fprintf(stderr, "\n\tTotal in use: %d, total free: %d\n", + totused, totfree); +} +#endif + + +static int +morepages(n) +int n; +{ + int fd = -1; + int offset; + + if (pagepool_end - pagepool_start > pagesz) { + caddr_t addr = (caddr_t) + (((long)pagepool_start + pagesz - 1) & ~(pagesz - 1)); + if (munmap(addr, pagepool_end - addr) != 0) + rtld_fdprintf(STDERR_FILENO, "morepages: munmap %p", + addr); + } + + offset = (long)pagepool_start - ((long)pagepool_start & ~(pagesz - 1)); + + if ((pagepool_start = mmap(0, n * pagesz, + PROT_READ|PROT_WRITE, + MAP_ANON|MAP_COPY, fd, 0)) == (caddr_t)-1) { + rtld_printf("Cannot map anonymous memory\n"); + return 0; + } + pagepool_end = pagepool_start + n * pagesz; + pagepool_start += offset; + + return n; +} diff --git a/libexec/rtld-elf/map_object.c b/libexec/rtld-elf/map_object.c new file mode 100644 index 0000000..2e17fbf --- /dev/null +++ b/libexec/rtld-elf/map_object.c @@ -0,0 +1,449 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include <errno.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "debug.h" +#include "rtld.h" + +static Elf_Ehdr *get_elf_header(int, const char *); +static int convert_prot(int); /* Elf flags -> mmap protection */ +static int convert_flags(int); /* Elf flags -> mmap flags */ + +/* + * Map a shared object into memory. The "fd" argument is a file descriptor, + * which must be open on the object and positioned at its beginning. + * The "path" argument is a pathname that is used only for error messages. + * + * The return value is a pointer to a newly-allocated Obj_Entry structure + * for the shared object. Returns NULL on failure. + */ +Obj_Entry * +map_object(int fd, const char *path, const struct stat *sb) +{ + Obj_Entry *obj; + Elf_Ehdr *hdr; + int i; + Elf_Phdr *phdr; + Elf_Phdr *phlimit; + Elf_Phdr **segs; + int nsegs; + Elf_Phdr *phdyn; + Elf_Phdr *phinterp; + Elf_Phdr *phtls; + caddr_t mapbase; + size_t mapsize; + Elf_Addr base_vaddr; + Elf_Addr base_vlimit; + caddr_t base_addr; + int base_flags; + Elf_Off data_offset; + Elf_Addr data_vaddr; + Elf_Addr data_vlimit; + caddr_t data_addr; + int data_prot; + int data_flags; + Elf_Addr clear_vaddr; + caddr_t clear_addr; + caddr_t clear_page; + Elf_Addr phdr_vaddr; + size_t nclear, phsize; + Elf_Addr bss_vaddr; + Elf_Addr bss_vlimit; + caddr_t bss_addr; + Elf_Word stack_flags; + Elf_Addr relro_page; + size_t relro_size; + Elf_Addr note_start; + Elf_Addr note_end; + + hdr = get_elf_header(fd, path); + if (hdr == NULL) + return (NULL); + + /* + * Scan the program header entries, and save key information. + * + * We expect that the loadable segments are ordered by load address. + */ + phdr = (Elf_Phdr *) ((char *)hdr + hdr->e_phoff); + phsize = hdr->e_phnum * sizeof (phdr[0]); + phlimit = phdr + hdr->e_phnum; + nsegs = -1; + phdyn = phinterp = phtls = NULL; + phdr_vaddr = 0; + relro_page = 0; + relro_size = 0; + note_start = 0; + note_end = 0; + segs = alloca(sizeof(segs[0]) * hdr->e_phnum); + stack_flags = RTLD_DEFAULT_STACK_PF_EXEC | PF_R | PF_W; + while (phdr < phlimit) { + switch (phdr->p_type) { + + case PT_INTERP: + phinterp = phdr; + break; + + case PT_LOAD: + segs[++nsegs] = phdr; + if ((segs[nsegs]->p_align & (PAGE_SIZE - 1)) != 0) { + _rtld_error("%s: PT_LOAD segment %d not page-aligned", + path, nsegs); + goto error; + } + break; + + case PT_PHDR: + phdr_vaddr = phdr->p_vaddr; + phsize = phdr->p_memsz; + break; + + case PT_DYNAMIC: + phdyn = phdr; + break; + + case PT_TLS: + phtls = phdr; + break; + + case PT_GNU_STACK: + stack_flags = phdr->p_flags; + break; + + case PT_GNU_RELRO: + relro_page = phdr->p_vaddr; + relro_size = phdr->p_memsz; + break; + + case PT_NOTE: + if (phdr->p_offset > PAGE_SIZE || + phdr->p_offset + phdr->p_filesz > PAGE_SIZE) + break; + note_start = (Elf_Addr)(char *)hdr + phdr->p_offset; + note_end = note_start + phdr->p_filesz; + break; + } + + ++phdr; + } + if (phdyn == NULL) { + _rtld_error("%s: object is not dynamically-linked", path); + goto error; + } + + if (nsegs < 0) { + _rtld_error("%s: too few PT_LOAD segments", path); + goto error; + } + + /* + * Map the entire address space of the object, to stake out our + * contiguous region, and to establish the base address for relocation. + */ + base_vaddr = trunc_page(segs[0]->p_vaddr); + base_vlimit = round_page(segs[nsegs]->p_vaddr + segs[nsegs]->p_memsz); + mapsize = base_vlimit - base_vaddr; + base_addr = (caddr_t) base_vaddr; + base_flags = MAP_PRIVATE | MAP_ANON | MAP_NOCORE; + if (npagesizes > 1 && round_page(segs[0]->p_filesz) >= pagesizes[1]) + base_flags |= MAP_ALIGNED_SUPER; + + mapbase = mmap(base_addr, mapsize, PROT_NONE, base_flags, -1, 0); + if (mapbase == (caddr_t) -1) { + _rtld_error("%s: mmap of entire address space failed: %s", + path, rtld_strerror(errno)); + goto error; + } + if (base_addr != NULL && mapbase != base_addr) { + _rtld_error("%s: mmap returned wrong address: wanted %p, got %p", + path, base_addr, mapbase); + goto error1; + } + + for (i = 0; i <= nsegs; i++) { + /* Overlay the segment onto the proper region. */ + data_offset = trunc_page(segs[i]->p_offset); + data_vaddr = trunc_page(segs[i]->p_vaddr); + data_vlimit = round_page(segs[i]->p_vaddr + segs[i]->p_filesz); + data_addr = mapbase + (data_vaddr - base_vaddr); + data_prot = convert_prot(segs[i]->p_flags); + data_flags = convert_flags(segs[i]->p_flags) | MAP_FIXED; + if (mmap(data_addr, data_vlimit - data_vaddr, data_prot, + data_flags | MAP_PREFAULT_READ, fd, data_offset) == (caddr_t) -1) { + _rtld_error("%s: mmap of data failed: %s", path, + rtld_strerror(errno)); + goto error1; + } + + /* Do BSS setup */ + if (segs[i]->p_filesz != segs[i]->p_memsz) { + + /* Clear any BSS in the last page of the segment. */ + clear_vaddr = segs[i]->p_vaddr + segs[i]->p_filesz; + clear_addr = mapbase + (clear_vaddr - base_vaddr); + clear_page = mapbase + (trunc_page(clear_vaddr) - base_vaddr); + + if ((nclear = data_vlimit - clear_vaddr) > 0) { + /* Make sure the end of the segment is writable */ + if ((data_prot & PROT_WRITE) == 0 && -1 == + mprotect(clear_page, PAGE_SIZE, data_prot|PROT_WRITE)) { + _rtld_error("%s: mprotect failed: %s", path, + rtld_strerror(errno)); + goto error1; + } + + memset(clear_addr, 0, nclear); + + /* Reset the data protection back */ + if ((data_prot & PROT_WRITE) == 0) + mprotect(clear_page, PAGE_SIZE, data_prot); + } + + /* Overlay the BSS segment onto the proper region. */ + bss_vaddr = data_vlimit; + bss_vlimit = round_page(segs[i]->p_vaddr + segs[i]->p_memsz); + bss_addr = mapbase + (bss_vaddr - base_vaddr); + if (bss_vlimit > bss_vaddr) { /* There is something to do */ + if (mmap(bss_addr, bss_vlimit - bss_vaddr, data_prot, + data_flags | MAP_ANON, -1, 0) == (caddr_t)-1) { + _rtld_error("%s: mmap of bss failed: %s", path, + rtld_strerror(errno)); + goto error1; + } + } + } + + if (phdr_vaddr == 0 && data_offset <= hdr->e_phoff && + (data_vlimit - data_vaddr + data_offset) >= + (hdr->e_phoff + hdr->e_phnum * sizeof (Elf_Phdr))) { + phdr_vaddr = data_vaddr + hdr->e_phoff - data_offset; + } + } + + obj = obj_new(); + if (sb != NULL) { + obj->dev = sb->st_dev; + obj->ino = sb->st_ino; + } + obj->mapbase = mapbase; + obj->mapsize = mapsize; + obj->textsize = round_page(segs[0]->p_vaddr + segs[0]->p_memsz) - + base_vaddr; + obj->vaddrbase = base_vaddr; + obj->relocbase = mapbase - base_vaddr; + obj->dynamic = (const Elf_Dyn *) (obj->relocbase + phdyn->p_vaddr); + if (hdr->e_entry != 0) + obj->entry = (caddr_t) (obj->relocbase + hdr->e_entry); + if (phdr_vaddr != 0) { + obj->phdr = (const Elf_Phdr *) (obj->relocbase + phdr_vaddr); + } else { + obj->phdr = malloc(phsize); + if (obj->phdr == NULL) { + obj_free(obj); + _rtld_error("%s: cannot allocate program header", path); + goto error1; + } + memcpy((char *)obj->phdr, (char *)hdr + hdr->e_phoff, phsize); + obj->phdr_alloc = true; + } + obj->phsize = phsize; + if (phinterp != NULL) + obj->interp = (const char *) (obj->relocbase + phinterp->p_vaddr); + if (phtls != NULL) { + tls_dtv_generation++; + obj->tlsindex = ++tls_max_index; + obj->tlssize = phtls->p_memsz; + obj->tlsalign = phtls->p_align; + obj->tlsinitsize = phtls->p_filesz; + obj->tlsinit = mapbase + phtls->p_vaddr; + } + obj->stack_flags = stack_flags; + obj->relro_page = obj->relocbase + trunc_page(relro_page); + obj->relro_size = round_page(relro_size); + if (note_start < note_end) + digest_notes(obj, note_start, note_end); + munmap(hdr, PAGE_SIZE); + return (obj); + +error1: + munmap(mapbase, mapsize); +error: + munmap(hdr, PAGE_SIZE); + return (NULL); +} + +static Elf_Ehdr * +get_elf_header(int fd, const char *path) +{ + Elf_Ehdr *hdr; + + hdr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_PREFAULT_READ, + fd, 0); + if (hdr == (Elf_Ehdr *)MAP_FAILED) { + _rtld_error("%s: read error: %s", path, rtld_strerror(errno)); + return (NULL); + } + + /* Make sure the file is valid */ + if (!IS_ELF(*hdr)) { + _rtld_error("%s: invalid file format", path); + goto error; + } + if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || + hdr->e_ident[EI_DATA] != ELF_TARG_DATA) { + _rtld_error("%s: unsupported file layout", path); + goto error; + } + if (hdr->e_ident[EI_VERSION] != EV_CURRENT || + hdr->e_version != EV_CURRENT) { + _rtld_error("%s: unsupported file version", path); + goto error; + } + if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) { + _rtld_error("%s: unsupported file type", path); + goto error; + } + if (hdr->e_machine != ELF_TARG_MACH) { + _rtld_error("%s: unsupported machine", path); + goto error; + } + + /* + * We rely on the program header being in the first page. This is + * not strictly required by the ABI specification, but it seems to + * always true in practice. And, it simplifies things considerably. + */ + if (hdr->e_phentsize != sizeof(Elf_Phdr)) { + _rtld_error( + "%s: invalid shared object: e_phentsize != sizeof(Elf_Phdr)", path); + goto error; + } + if (hdr->e_phoff + hdr->e_phnum * sizeof(Elf_Phdr) > + (size_t)PAGE_SIZE) { + _rtld_error("%s: program header too large", path); + goto error; + } + return (hdr); + +error: + munmap(hdr, PAGE_SIZE); + return (NULL); +} + +void +obj_free(Obj_Entry *obj) +{ + Objlist_Entry *elm; + + if (obj->tls_done) + free_tls_offset(obj); + while (obj->needed != NULL) { + Needed_Entry *needed = obj->needed; + obj->needed = needed->next; + free(needed); + } + while (!STAILQ_EMPTY(&obj->names)) { + Name_Entry *entry = STAILQ_FIRST(&obj->names); + STAILQ_REMOVE_HEAD(&obj->names, link); + free(entry); + } + while (!STAILQ_EMPTY(&obj->dldags)) { + elm = STAILQ_FIRST(&obj->dldags); + STAILQ_REMOVE_HEAD(&obj->dldags, link); + free(elm); + } + while (!STAILQ_EMPTY(&obj->dagmembers)) { + elm = STAILQ_FIRST(&obj->dagmembers); + STAILQ_REMOVE_HEAD(&obj->dagmembers, link); + free(elm); + } + if (obj->vertab) + free(obj->vertab); + if (obj->origin_path) + free(obj->origin_path); + if (obj->z_origin) + free(obj->rpath); + if (obj->priv) + free(obj->priv); + if (obj->path) + free(obj->path); + if (obj->phdr_alloc) + free((void *)obj->phdr); + free(obj); +} + +Obj_Entry * +obj_new(void) +{ + Obj_Entry *obj; + + obj = CNEW(Obj_Entry); + STAILQ_INIT(&obj->dldags); + STAILQ_INIT(&obj->dagmembers); + STAILQ_INIT(&obj->names); + return obj; +} + +/* + * Given a set of ELF protection flags, return the corresponding protection + * flags for MMAP. + */ +static int +convert_prot(int elfflags) +{ + int prot = 0; + if (elfflags & PF_R) + prot |= PROT_READ; + if (elfflags & PF_W) + prot |= PROT_WRITE; + if (elfflags & PF_X) + prot |= PROT_EXEC; + return prot; +} + +static int +convert_flags(int elfflags) +{ + int flags = MAP_PRIVATE; /* All mappings are private */ + + /* + * Readonly mappings are marked "MAP_NOCORE", because they can be + * reconstructed by a debugger. + */ + if (!(elfflags & PF_W)) + flags |= MAP_NOCORE; + return flags; +} diff --git a/libexec/rtld-elf/mips/reloc.c b/libexec/rtld-elf/mips/reloc.c new file mode 100644 index 0000000..4e750d7 --- /dev/null +++ b/libexec/rtld-elf/mips/reloc.c @@ -0,0 +1,648 @@ +/* $NetBSD: mips_reloc.c,v 1.58 2010/01/14 11:57:06 skrll Exp $ */ + +/* + * Copyright 1997 Michael L. Hitch <mhitch@montana.edu> + * Portions copyright 2002 Charles M. Hannum <root@ihack.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/endian.h> + +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include <machine/sysarch.h> +#include <machine/tls.h> + +#include "debug.h" +#include "rtld.h" + +#ifdef __mips_n64 +#define GOT1_MASK 0x8000000000000000UL +#else +#define GOT1_MASK 0x80000000UL +#endif + +void +init_pltgot(Obj_Entry *obj) +{ + if (obj->pltgot != NULL) { + obj->pltgot[0] = (Elf_Addr) &_rtld_bind_start; + if (obj->pltgot[1] & 0x80000000) + obj->pltgot[1] = (Elf_Addr) obj | GOT1_MASK; + } +} + +int +do_copy_relocations(Obj_Entry *dstobj) +{ + /* Do nothing */ + return 0; +} + +void _rtld_relocate_nonplt_self(Elf_Dyn *, Elf_Addr); + +/* + * It is possible for the compiler to emit relocations for unaligned data. + * We handle this situation with these inlines. + */ +#ifdef __mips_n64 +/* + * ELF64 MIPS encodes the relocs uniquely. The first 32-bits of info contain + * the symbol index. The top 32-bits contain three relocation types encoded + * in big-endian integer with first relocation in LSB. This means for little + * endian we have to byte swap that integer (r_type). + */ +#define Elf_Sxword Elf64_Sxword +#define ELF_R_NXTTYPE_64_P(r_type) ((((r_type) >> 8) & 0xff) == R_TYPE(64)) +#if BYTE_ORDER == LITTLE_ENDIAN +#undef ELF_R_SYM +#undef ELF_R_TYPE +#define ELF_R_SYM(r_info) ((r_info) & 0xffffffff) +#define ELF_R_TYPE(r_info) bswap32((r_info) >> 32) +#endif +#else +#define ELF_R_NXTTYPE_64_P(r_type) (0) +#define Elf_Sxword Elf32_Sword +#endif + +static __inline Elf_Sxword +load_ptr(void *where, size_t len) +{ + Elf_Sxword val; + + if (__predict_true(((uintptr_t)where & (len - 1)) == 0)) { +#ifdef __mips_n64 + if (len == sizeof(Elf_Sxword)) + return *(Elf_Sxword *)where; +#endif + return *(Elf_Sword *)where; + } + + val = 0; +#if BYTE_ORDER == LITTLE_ENDIAN + (void)memcpy(&val, where, len); +#endif +#if BYTE_ORDER == BIG_ENDIAN + (void)memcpy((uint8_t *)((&val)+1) - len, where, len); +#endif + return (len == sizeof(Elf_Sxword)) ? val : (Elf_Sword)val; +} + +static __inline void +store_ptr(void *where, Elf_Sxword val, size_t len) +{ + if (__predict_true(((uintptr_t)where & (len - 1)) == 0)) { +#ifdef __mips_n64 + if (len == sizeof(Elf_Sxword)) { + *(Elf_Sxword *)where = val; + return; + } +#endif + *(Elf_Sword *)where = val; + return; + } +#if BYTE_ORDER == LITTLE_ENDIAN + (void)memcpy(where, &val, len); +#endif +#if BYTE_ORDER == BIG_ENDIAN + (void)memcpy(where, (const uint8_t *)((&val)+1) - len, len); +#endif +} + +void +_rtld_relocate_nonplt_self(Elf_Dyn *dynp, Elf_Addr relocbase) +{ + const Elf_Rel *rel = 0, *rellim; + Elf_Addr relsz = 0; + const Elf_Sym *symtab = NULL, *sym; + Elf_Addr *where; + Elf_Addr *got = NULL; + Elf_Word local_gotno = 0, symtabno = 0, gotsym = 0; + size_t i; + + for (; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + case DT_REL: + rel = (const Elf_Rel *)(relocbase + dynp->d_un.d_ptr); + break; + case DT_RELSZ: + relsz = dynp->d_un.d_val; + break; + case DT_SYMTAB: + symtab = (const Elf_Sym *)(relocbase + dynp->d_un.d_ptr); + break; + case DT_PLTGOT: + got = (Elf_Addr *)(relocbase + dynp->d_un.d_ptr); + break; + case DT_MIPS_LOCAL_GOTNO: + local_gotno = dynp->d_un.d_val; + break; + case DT_MIPS_SYMTABNO: + symtabno = dynp->d_un.d_val; + break; + case DT_MIPS_GOTSYM: + gotsym = dynp->d_un.d_val; + break; + } + } + + i = (got[1] & GOT1_MASK) ? 2 : 1; + /* Relocate the local GOT entries */ + got += i; + for (; i < local_gotno; i++) { + *got++ += relocbase; + } + + sym = symtab + gotsym; + /* Now do the global GOT entries */ + for (i = gotsym; i < symtabno; i++) { + *got = sym->st_value + relocbase; + ++sym; + ++got; + } + + rellim = (const Elf_Rel *)((caddr_t)rel + relsz); + for (; rel < rellim; rel++) { + Elf_Word r_symndx, r_type; + + where = (void *)(relocbase + rel->r_offset); + + r_symndx = ELF_R_SYM(rel->r_info); + r_type = ELF_R_TYPE(rel->r_info); + + switch (r_type & 0xff) { + case R_TYPE(REL32): { + const size_t rlen = + ELF_R_NXTTYPE_64_P(r_type) + ? sizeof(Elf_Sxword) + : sizeof(Elf_Sword); + Elf_Sxword old = load_ptr(where, rlen); + Elf_Sxword val = old; +#ifdef __mips_n64 + assert(r_type == R_TYPE(REL32) + || r_type == (R_TYPE(REL32)|(R_TYPE(64) << 8))); +#endif + assert(r_symndx < gotsym); + sym = symtab + r_symndx; + assert(ELF_ST_BIND(sym->st_info) == STB_LOCAL); + val += relocbase; + store_ptr(where, val, sizeof(Elf_Sword)); + dbg("REL32/L(%p) %p -> %p in <self>", + where, (void *)old, (void *)val); + store_ptr(where, val, rlen); + break; + } + + case R_TYPE(GPREL32): + case R_TYPE(NONE): + break; + + + default: + abort(); + break; + } + } +} + +Elf_Addr +_mips_rtld_bind(Obj_Entry *obj, Elf_Size reloff) +{ + Elf_Addr *got = obj->pltgot; + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr target; + + def = find_symdef(reloff, obj, &defobj, SYMLOOK_IN_PLT, NULL, + NULL); + if (def == NULL) + _rtld_error("bind failed no symbol"); + + target = (Elf_Addr)(defobj->relocbase + def->st_value); + dbg("bind now/fixup at %s sym # %jd in %s --> was=%p new=%p", + obj->path, + (intmax_t)reloff, defobj->strtab + def->st_name, + (void *)got[obj->local_gotno + reloff - obj->gotsym], + (void *)target); + got[obj->local_gotno + reloff - obj->gotsym] = target; + return (Elf_Addr)target; +} + +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rel *rel; + const Elf_Rel *rellim; + Elf_Addr *got = obj->pltgot; + const Elf_Sym *sym, *def; + const Obj_Entry *defobj; + Elf_Word i; +#ifdef SUPPORT_OLD_BROKEN_LD + int broken; +#endif + + /* The relocation for the dynamic loader has already been done. */ + if (obj == obj_rtld) + return (0); + + if ((flags & SYMLOOK_IFUNC) != 0) + /* XXX not implemented */ + return (0); + +#ifdef SUPPORT_OLD_BROKEN_LD + broken = 0; + sym = obj->symtab; + for (i = 1; i < 12; i++) + if (sym[i].st_info == ELF_ST_INFO(STB_LOCAL, STT_NOTYPE)) + broken = 1; + dbg("%s: broken=%d", obj->path, broken); +#endif + + i = (got[1] & GOT1_MASK) ? 2 : 1; + + /* Relocate the local GOT entries */ + got += i; + dbg("got:%p for %d entries adding %p", + got, obj->local_gotno, obj->relocbase); + for (; i < obj->local_gotno; i++) { + *got += (Elf_Addr)obj->relocbase; + got++; + } + sym = obj->symtab + obj->gotsym; + + dbg("got:%p for %d entries", + got, obj->symtabno); + /* Now do the global GOT entries */ + for (i = obj->gotsym; i < obj->symtabno; i++) { + dbg(" doing got %d sym %p (%s, %lx)", i - obj->gotsym, sym, + sym->st_name + obj->strtab, (u_long) *got); + +#ifdef SUPPORT_OLD_BROKEN_LD + if (ELF_ST_TYPE(sym->st_info) == STT_FUNC && + broken && sym->st_shndx == SHN_UNDEF) { + /* + * XXX DANGER WILL ROBINSON! + * You might think this is stupid, as it intentionally + * defeats lazy binding -- and you'd be right. + * Unfortunately, for lazy binding to work right, we + * need to a way to force the GOT slots used for + * function pointers to be resolved immediately. This + * is supposed to be done automatically by the linker, + * by not outputting a PLT slot and setting st_value + * to 0 if there are non-PLT references, but older + * versions of GNU ld do not do this. + */ + def = find_symdef(i, obj, &defobj, flags, NULL, + lockstate); + if (def == NULL) + return -1; + *got = def->st_value + (Elf_Addr)defobj->relocbase; + } else +#endif + if (ELF_ST_TYPE(sym->st_info) == STT_FUNC && + sym->st_value != 0 && sym->st_shndx == SHN_UNDEF) { + /* + * If there are non-PLT references to the function, + * st_value should be 0, forcing us to resolve the + * address immediately. + * + * XXX DANGER WILL ROBINSON! + * The linker is not outputting PLT slots for calls to + * functions that are defined in the same shared + * library. This is a bug, because it can screw up + * link ordering rules if the symbol is defined in + * more than one module. For now, if there is a + * definition, we fail the test above and force a full + * symbol lookup. This means that all intra-module + * calls are bound immediately. - mycroft, 2003/09/24 + */ + *got = sym->st_value + (Elf_Addr)obj->relocbase; + if ((Elf_Addr)(*got) == (Elf_Addr)obj->relocbase) { + dbg("Warning2, i:%d maps to relocbase address:%p", + i, obj->relocbase); + } + + } else if (sym->st_info == ELF_ST_INFO(STB_GLOBAL, STT_SECTION)) { + /* Symbols with index SHN_ABS are not relocated. */ + if (sym->st_shndx != SHN_ABS) { + *got = sym->st_value + + (Elf_Addr)obj->relocbase; + if ((Elf_Addr)(*got) == (Elf_Addr)obj->relocbase) { + dbg("Warning3, i:%d maps to relocbase address:%p", + i, obj->relocbase); + } + } + } else { + /* TODO: add cache here */ + def = find_symdef(i, obj, &defobj, flags, NULL, + lockstate); + if (def == NULL) { + dbg("Warning4, can't find symbole %d", i); + return -1; + } + *got = def->st_value + (Elf_Addr)defobj->relocbase; + if ((Elf_Addr)(*got) == (Elf_Addr)obj->relocbase) { + dbg("Warning4, i:%d maps to relocbase address:%p", + i, obj->relocbase); + dbg("via first obj symbol %s", + obj->strtab + obj->symtab[i].st_name); + dbg("found in obj %p:%s", + defobj, defobj->path); + } + } + + dbg(" --> now %lx", (u_long) *got); + ++sym; + ++got; + } + + got = obj->pltgot; + rellim = (const Elf_Rel *)((caddr_t)obj->rel + obj->relsize); + for (rel = obj->rel; rel < rellim; rel++) { + Elf_Word r_symndx, r_type; + void *where; + + where = obj->relocbase + rel->r_offset; + r_symndx = ELF_R_SYM(rel->r_info); + r_type = ELF_R_TYPE(rel->r_info); + + switch (r_type & 0xff) { + case R_TYPE(NONE): + break; + + case R_TYPE(REL32): { + /* 32-bit PC-relative reference */ + const size_t rlen = + ELF_R_NXTTYPE_64_P(r_type) + ? sizeof(Elf_Sxword) + : sizeof(Elf_Sword); + Elf_Sxword old = load_ptr(where, rlen); + Elf_Sxword val = old; + + def = obj->symtab + r_symndx; + + if (r_symndx >= obj->gotsym) { + val += got[obj->local_gotno + r_symndx - obj->gotsym]; + dbg("REL32/G(%p) %p --> %p (%s) in %s", + where, (void *)old, (void *)val, + obj->strtab + def->st_name, + obj->path); + } else { + /* + * XXX: ABI DIFFERENCE! + * + * Old NetBSD binutils would generate shared + * libs with section-relative relocations being + * already adjusted for the start address of + * the section. + * + * New binutils, OTOH, generate shared libs + * with the same relocations being based at + * zero, so we need to add in the start address + * of the section. + * + * --rkb, Oct 6, 2001 + */ + + if (def->st_info == + ELF_ST_INFO(STB_LOCAL, STT_SECTION) +#ifdef SUPPORT_OLD_BROKEN_LD + && !broken +#endif + ) + val += (Elf_Addr)def->st_value; + + val += (Elf_Addr)obj->relocbase; + + dbg("REL32/L(%p) %p -> %p (%s) in %s", + where, (void *)old, (void *)val, + obj->strtab + def->st_name, obj->path); + } + store_ptr(where, val, rlen); + break; + } + +#ifdef __mips_n64 + case R_TYPE(TLS_DTPMOD64): +#else + case R_TYPE(TLS_DTPMOD32): +#endif + { + + const size_t rlen = sizeof(Elf_Addr); + Elf_Addr old = load_ptr(where, rlen); + Elf_Addr val = old; + + def = find_symdef(r_symndx, obj, &defobj, flags, NULL, + lockstate); + if (def == NULL) + return -1; + + val += (Elf_Addr)defobj->tlsindex; + + store_ptr(where, val, rlen); + dbg("DTPMOD %s in %s %p --> %p in %s", + obj->strtab + obj->symtab[r_symndx].st_name, + obj->path, (void *)old, (void*)val, defobj->path); + break; + } + +#ifdef __mips_n64 + case R_TYPE(TLS_DTPREL64): +#else + case R_TYPE(TLS_DTPREL32): +#endif + { + const size_t rlen = sizeof(Elf_Addr); + Elf_Addr old = load_ptr(where, rlen); + Elf_Addr val = old; + + def = find_symdef(r_symndx, obj, &defobj, flags, NULL, + lockstate); + if (def == NULL) + return -1; + + if (!defobj->tls_done && allocate_tls_offset(obj)) + return -1; + + val += (Elf_Addr)def->st_value - TLS_DTP_OFFSET; + store_ptr(where, val, rlen); + + dbg("DTPREL %s in %s %p --> %p in %s", + obj->strtab + obj->symtab[r_symndx].st_name, + obj->path, (void*)old, (void *)val, defobj->path); + break; + } + +#ifdef __mips_n64 + case R_TYPE(TLS_TPREL64): +#else + case R_TYPE(TLS_TPREL32): +#endif + { + const size_t rlen = sizeof(Elf_Addr); + Elf_Addr old = load_ptr(where, rlen); + Elf_Addr val = old; + + def = find_symdef(r_symndx, obj, &defobj, flags, NULL, + lockstate); + + if (def == NULL) + return -1; + + if (!defobj->tls_done && allocate_tls_offset(obj)) + return -1; + + val += (Elf_Addr)(def->st_value + defobj->tlsoffset + - TLS_TP_OFFSET - TLS_TCB_SIZE); + store_ptr(where, val, rlen); + + dbg("TPREL %s in %s %p --> %p in %s", + obj->strtab + obj->symtab[r_symndx].st_name, + obj->path, (void*)old, (void *)val, defobj->path); + break; + } + + + + default: + dbg("sym = %lu, type = %lu, offset = %p, " + "contents = %p, symbol = %s", + (u_long)r_symndx, (u_long)ELF_R_TYPE(rel->r_info), + (void *)rel->r_offset, + (void *)load_ptr(where, sizeof(Elf_Sword)), + obj->strtab + obj->symtab[r_symndx].st_name); + _rtld_error("%s: Unsupported relocation type %ld " + "in non-PLT relocations", + obj->path, (u_long) ELF_R_TYPE(rel->r_info)); + return -1; + } + } + + return 0; +} + +/* + * Process the PLT relocations. + */ +int +reloc_plt(Obj_Entry *obj) +{ +#if 0 + const Elf_Rel *rellim; + const Elf_Rel *rel; + + dbg("reloc_plt obj:%p pltrel:%p sz:%s", obj, obj->pltrel, (int)obj->pltrelsize); + dbg("gottable %p num syms:%s", obj->pltgot, obj->symtabno ); + dbg("*****************************************************"); + rellim = (const Elf_Rel *)((char *)obj->pltrel + + obj->pltrelsize); + for (rel = obj->pltrel; rel < rellim; rel++) { + Elf_Addr *where; + where = (Elf_Addr *)(obj->relocbase + rel->r_offset); + *where += (Elf_Addr )obj->relocbase; + } + +#endif + /* PLT fixups were done above in the GOT relocation. */ + return (0); +} + +/* + * LD_BIND_NOW was set - force relocation for all jump slots + */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + /* Do nothing */ + obj->jmpslots_done = true; + + return (0); +} + +int +reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, + struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +Elf_Addr +reloc_jmpslot(Elf_Addr *where, Elf_Addr target, const Obj_Entry *defobj, + const Obj_Entry *obj, const Elf_Rel *rel) +{ + + /* Do nothing */ + + return target; +} + +void +allocate_initial_tls(Obj_Entry *objs) +{ + char *tls; + + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + tls_static_space = tls_last_offset + tls_last_size + RTLD_STATIC_TLS_EXTRA; + + tls = (char *) allocate_tls(objs, NULL, TLS_TCB_SIZE, 8); + + sysarch(MIPS_SET_TLS, tls); +} + +void * +__tls_get_addr(tls_index* ti) +{ + Elf_Addr** tls; + char *p; + + sysarch(MIPS_GET_TLS, &tls); + + p = tls_get_addr_common(tls, ti->ti_module, ti->ti_offset + TLS_DTP_OFFSET); + + return (p); +} diff --git a/libexec/rtld-elf/mips/rtld_machdep.h b/libexec/rtld-elf/mips/rtld_machdep.h new file mode 100644 index 0000000..befbf13 --- /dev/null +++ b/libexec/rtld-elf/mips/rtld_machdep.h @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> +#include <machine/tls.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) (&_DYNAMIC) + +Elf_Addr reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *defobj, + const struct Struct_Obj_Entry *obj, + const Elf_Rel *rel); + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(TLS_TCB_SIZE, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round(prev_offset + prev_size, align) +#define calculate_tls_end(off, size) ((off) + (size)) + +/* + * Lazy binding entry point, called via PLT. + */ +void _rtld_bind_start(void); + +extern void *__tls_get_addr(tls_index *ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/mips/rtld_start.S b/libexec/rtld-elf/mips/rtld_start.S new file mode 100644 index 0000000..a35fced --- /dev/null +++ b/libexec/rtld-elf/mips/rtld_start.S @@ -0,0 +1,166 @@ +/* $NetBSD: rtld_start.S,v 1.10 2009/12/14 00:41:19 matt Exp $ */ + +/* + * Copyright 1997 Michael L. Hitch <mhitch@montana.edu> + * Portions copyright 2002 Charles M. Hannum <root@ihack.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <machine/asm.h> + +.globl _C_LABEL(_rtld_relocate_nonplt_self) +.globl _C_LABEL(_rtld) + +#define PTR_SIZE (1<<PTR_SCALESHIFT) + +/* + * a0 stack pointer + * a1 rtld cleanup (filled in by dynamic loader) + * a2 rtld object (filled in by dynamic loader) + * a3 ps_strings + */ +LEAF(rtld_start) + .frame sp, 4*PTR_SIZE, ra + .mask 0x10090000,-PTR_SIZE + .set noreorder + SETUP_GP + PTR_SUBU sp, 4*PTR_SIZE /* adjust stack pointer */ + SETUP_GP64(s4, rtld_start) + SAVE_GP(0) + /* -> 1*PTR_SIZE(sp) for atexit */ + /* -> 2*PTR_SIZE(sp) for obj_main */ + move s0, a0 /* save stack pointer from a0 */ + move s3, a3 /* save ps_strings pointer */ + + PTR_LA a1, 1f + bal 1f + PTR_LA t0, _C_LABEL(_rtld_relocate_nonplt_self) +1: PTR_SUBU a1, ra, a1 /* relocbase */ + PTR_LA a0, _DYNAMIC + PTR_ADDU t9, a1, t0 + jalr t9 /* _rtld_relocate_nonplt_self(dynp, relocabase) */ + PTR_ADDU a0, a1, a0 /* &_DYNAMIC */ + + move a0, s0 /* sp */ + PTR_ADDU a1, sp, 2*PTR_SIZE /* &our atexit function */ + PTR_ADDU a2, sp, 3*PTR_SIZE /* obj_main entry */ + PTR_SUBU sp, 4*SZREG /* ABI requires to reserve memory for 4 regs */ + PTR_LA t9, _C_LABEL(_rtld) + jalr t9 /* v0 = _rtld(sp, cleanup, objp) */ + nop + PTR_ADDU sp, 4*SZREG + + PTR_L a1, 2*PTR_SIZE(sp) /* our atexit function */ + PTR_L a2, 3*PTR_SIZE(sp) /* obj_main entry */ + PTR_ADDU sp, 4*PTR_SIZE /* readjust stack */ + move a0, s0 /* stack pointer */ + move t9, v0 + PTR_SUBU sp, 4*SZREG /* ABI requires to reserve memory for 4 regs */ + move ra,t9 /* RA == PC signals backtrace routine to stop */ + j t9 /* _start(sp, cleanup, obj); */ + move a3, s3 /* restore ps_strings */ +END(rtld_start) + +#define XCALLFRAME_SIZ (12*SZREG) +#define XCALLFRAME_RA (10*SZREG) +#define XCALLFRAME_GP (9*SZREG) +#define XCALLFRAME_S0 (8*SZREG) +#define XCALLFRAME_A3 (7*SZREG) +#define XCALLFRAME_A2 (6*SZREG) +#define XCALLFRAME_A1 (5*SZREG) +#define XCALLFRAME_A0 (4*SZREG) +#if defined(__mips_n32) || defined(__mips_n64) +#define XCALLFRAME_A7 (3*SZREG) +#define XCALLFRAME_A6 (2*SZREG) +#define XCALLFRAME_A5 (1*SZREG) +#define XCALLFRAME_A4 (0*SZREG) +#endif + + .globl _rtld_bind_start + .ent _rtld_bind_start +_rtld_bind_start: + .frame sp, XCALLFRAME_SIZ, $15 + move v1, gp /* save old GP */ +#if defined(__mips_o32) || defined(__mips_o64) + PTR_ADDU t9, 8 /* modify T9 to point at .cpload */ +#endif + SETUP_GP + PTR_SUBU sp, XCALLFRAME_SIZ /* save arguments and sp value in stack */ + SETUP_GP64(XCALLFRAME_GP, _rtld_bind_start) + SAVE_GP(XCALLFRAME_GP) +#if defined(__mips_n32) || defined(__mips_n64) + REG_S a4, XCALLFRAME_A4(sp) + REG_S a5, XCALLFRAME_A5(sp) + REG_S a6, XCALLFRAME_A6(sp) + REG_S a7, XCALLFRAME_A7(sp) +#endif + REG_S a0, XCALLFRAME_A0(sp) + REG_S a1, XCALLFRAME_A1(sp) + REG_S a2, XCALLFRAME_A2(sp) + REG_S a3, XCALLFRAME_A3(sp) + REG_S $15, XCALLFRAME_RA(sp) /* ra is in t7/t3 */ + REG_S s0, XCALLFRAME_S0(sp) + move s0, sp + + move a0, v1 /* old GP */ + PTR_SUBU a0, a0, 0x7ff0 /* The offset of $gp from the */ + /* beginning of the .got section: */ + /* $gp = .got + 0x7ff0, so */ + /* .got = $gp - 0x7ff0 */ + /* Simple math as you can see. */ +#if defined(__mips_n64) + ld a0, 8(a0) /* object = pltgot[1] */ + and a0, a0, 0x7fffffffffffffff +#else + lw a0, 4(a0) /* object = pltgot[1] & 0x7fffffff */ + and a0, a0, 0x7fffffff +#endif + move a1, t8 /* symbol index */ + + PTR_LA t9, _C_LABEL(_mips_rtld_bind) + jalr t9 + nop + + move sp, s0 + REG_L ra, XCALLFRAME_RA(sp) + REG_L s0, XCALLFRAME_S0(sp) + REG_L a0, XCALLFRAME_A0(sp) + REG_L a1, XCALLFRAME_A1(sp) + REG_L a2, XCALLFRAME_A2(sp) + REG_L a3, XCALLFRAME_A3(sp) +#if defined(__mips_n32) || defined(__mips_n64) + REG_L a4, XCALLFRAME_A4(sp) + REG_L a5, XCALLFRAME_A5(sp) + REG_L a6, XCALLFRAME_A6(sp) + REG_L a7, XCALLFRAME_A7(sp) +#endif + RESTORE_GP64 + PTR_ADDU sp, XCALLFRAME_SIZ + move t9, v0 + jr t9 + nop +END(_rtld_bind_start) diff --git a/libexec/rtld-elf/powerpc/Makefile.inc b/libexec/rtld-elf/powerpc/Makefile.inc new file mode 100644 index 0000000..e8c0da7 --- /dev/null +++ b/libexec/rtld-elf/powerpc/Makefile.inc @@ -0,0 +1 @@ +# $FreeBSD$ diff --git a/libexec/rtld-elf/powerpc/reloc.c b/libexec/rtld-elf/powerpc/reloc.c new file mode 100644 index 0000000..89e5536 --- /dev/null +++ b/libexec/rtld-elf/powerpc/reloc.c @@ -0,0 +1,656 @@ +/* $NetBSD: ppc_reloc.c,v 1.10 2001/09/10 06:09:41 mycroft Exp $ */ + +/*- + * Copyright (C) 1998 Tsubai Masanari + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/mman.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <machine/cpu.h> +#include <machine/atomic.h> +#include <machine/md_var.h> + +#include "debug.h" +#include "rtld.h" + +#define _ppc_ha(x) ((((u_int32_t)(x) & 0x8000) ? \ + ((u_int32_t)(x) + 0x10000) : (u_int32_t)(x)) >> 16) +#define _ppc_la(x) ((u_int32_t)(x) & 0xffff) + +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +#define PLT_EXTENDED_BEGIN (1 << 13) +#define JMPTAB_BASE(N) (18 + N*2 + ((N > PLT_EXTENDED_BEGIN) ? \ + (N - PLT_EXTENDED_BEGIN)*2 : 0)) + +/* + * Process the R_PPC_COPY relocations + */ +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + /* + * COPY relocs are invalid outside of the main program + */ + assert(dstobj->mainprog); + + relalim = (const Elf_Rela *) ((caddr_t) dstobj->rela + + dstobj->relasize); + for (rela = dstobj->rela; rela < relalim; rela++) { + void *dstaddr; + const Elf_Sym *dstsym; + const char *name; + size_t size; + const void *srcaddr; + const Elf_Sym *srcsym = NULL; + const Obj_Entry *srcobj, *defobj; + SymLook req; + int res; + + if (ELF_R_TYPE(rela->r_info) != R_PPC_COPY) { + continue; + } + + dstaddr = (void *) (dstobj->relocbase + rela->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rela->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; + srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + + if (srcobj == NULL) { + _rtld_error("Undefined symbol \"%s\" " + " referenced from COPY" + " relocation in %s", name, dstobj->path); + return (-1); + } + + srcaddr = (const void *) (defobj->relocbase+srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + dbg("copy_reloc: src=%p,dst=%p,size=%d\n",srcaddr,dstaddr,size); + } + + return (0); +} + + +/* + * Perform early relocation of the run-time linker image + */ +void +reloc_non_plt_self(Elf_Dyn *dynp, Elf_Addr relocbase) +{ + const Elf_Rela *rela = 0, *relalim; + Elf_Addr relasz = 0; + Elf_Addr *where; + + /* + * Extract the rela/relasz values from the dynamic section + */ + for (; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + case DT_RELA: + rela = (const Elf_Rela *)(relocbase+dynp->d_un.d_ptr); + break; + case DT_RELASZ: + relasz = dynp->d_un.d_val; + break; + } + } + + /* + * Relocate these values + */ + relalim = (const Elf_Rela *)((caddr_t)rela + relasz); + for (; rela < relalim; rela++) { + where = (Elf_Addr *)(relocbase + rela->r_offset); + *where = (Elf_Addr)(relocbase + rela->r_addend); + } +} + + +/* + * Relocate a non-PLT object with addend. + */ +static int +reloc_nonplt_object(Obj_Entry *obj_rtld, Obj_Entry *obj, const Elf_Rela *rela, + SymCache *cache, int flags, RtldLockState *lockstate) +{ + Elf_Addr *where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr tmp; + + switch (ELF_R_TYPE(rela->r_info)) { + + case R_PPC_NONE: + break; + + case R_PPC_ADDR32: /* word32 S + A */ + case R_PPC_GLOB_DAT: /* word32 S + A */ + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + if (def == NULL) { + return (-1); + } + + tmp = (Elf_Addr)(defobj->relocbase + def->st_value + + rela->r_addend); + + /* Don't issue write if unnecessary; avoid COW page fault */ + if (*where != tmp) { + *where = tmp; + } + break; + + case R_PPC_RELATIVE: /* word32 B + A */ + tmp = (Elf_Addr)(obj->relocbase + rela->r_addend); + + /* As above, don't issue write unnecessarily */ + if (*where != tmp) { + *where = tmp; + } + break; + + case R_PPC_COPY: + /* + * These are deferred until all other relocations + * have been done. All we do here is make sure + * that the COPY relocation is not in a shared + * library. They are allowed only in executable + * files. + */ + if (!obj->mainprog) { + _rtld_error("%s: Unexpected R_COPY " + " relocation in shared library", + obj->path); + return (-1); + } + break; + + case R_PPC_JMP_SLOT: + /* + * These will be handled by the plt/jmpslot routines + */ + break; + + case R_PPC_DTPMOD32: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + *where = (Elf_Addr) defobj->tlsindex; + + break; + + case R_PPC_TPREL32: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + /* + * We lazily allocate offsets for static TLS as we + * see the first relocation that references the + * TLS block. This allows us to support (small + * amounts of) static TLS in dynamically loaded + * modules. If we run out of space, we generate an + * error. + */ + if (!defobj->tls_done) { + if (!allocate_tls_offset((Obj_Entry*) defobj)) { + _rtld_error("%s: No space available for static " + "Thread Local Storage", obj->path); + return (-1); + } + } + + *(Elf_Addr **)where = *where * sizeof(Elf_Addr) + + (Elf_Addr *)(def->st_value + rela->r_addend + + defobj->tlsoffset - TLS_TP_OFFSET); + + break; + + case R_PPC_DTPREL32: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + *where += (Elf_Addr)(def->st_value + rela->r_addend + - TLS_DTV_OFFSET); + + break; + + default: + _rtld_error("%s: Unsupported relocation type %d" + " in non-PLT relocations\n", obj->path, + ELF_R_TYPE(rela->r_info)); + return (-1); + } + return (0); +} + + +/* + * Process non-PLT relocations + */ +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + SymCache *cache; + int r = -1; + + if ((flags & SYMLOOK_IFUNC) != 0) + /* XXX not implemented */ + return (0); + + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + if (obj != obj_rtld) { + cache = calloc(obj->dynsymcount, sizeof(SymCache)); + /* No need to check for NULL here */ + } else + cache = NULL; + + /* + * From the SVR4 PPC ABI: + * "The PowerPC family uses only the Elf32_Rela relocation + * entries with explicit addends." + */ + relalim = (const Elf_Rela *)((caddr_t)obj->rela + obj->relasize); + for (rela = obj->rela; rela < relalim; rela++) { + if (reloc_nonplt_object(obj_rtld, obj, rela, cache, flags, + lockstate) < 0) + goto done; + } + r = 0; +done: + if (cache != NULL) + free(cache); + + /* Synchronize icache for text seg in case we made any changes */ + __syncicache(obj->mapbase, obj->textsize); + + return (r); +} + +/* + * Initialise a PLT slot to the resolving trampoline + */ +static int +reloc_plt_object(Obj_Entry *obj, const Elf_Rela *rela) +{ + Elf_Word *where = (Elf_Word *)(obj->relocbase + rela->r_offset); + Elf_Addr *pltresolve, *pltlongresolve, *jmptab; + Elf_Addr distance; + int N = obj->pltrelasize / sizeof(Elf_Rela); + int reloff; + + reloff = rela - obj->pltrela; + + if (reloff < 0) + return (-1); + + pltlongresolve = obj->pltgot + 5; + pltresolve = pltlongresolve + 5; + + distance = (Elf_Addr)pltresolve - (Elf_Addr)(where + 1); + + dbg(" reloc_plt_object: where=%p,pltres=%p,reloff=%x,distance=%x", + (void *)where, (void *)pltresolve, reloff, distance); + + if (reloff < PLT_EXTENDED_BEGIN) { + /* li r11,reloff */ + /* b pltresolve */ + where[0] = 0x39600000 | reloff; + where[1] = 0x48000000 | (distance & 0x03fffffc); + } else { + jmptab = obj->pltgot + JMPTAB_BASE(N); + jmptab[reloff] = (u_int)pltlongresolve; + + /* lis r11,jmptab[reloff]@ha */ + /* lwzu r12,jmptab[reloff]@l(r11) */ + /* mtctr r12 */ + /* bctr */ + where[0] = 0x3d600000 | _ppc_ha(&jmptab[reloff]); + where[1] = 0x858b0000 | _ppc_la(&jmptab[reloff]); + where[2] = 0x7d8903a6; + where[3] = 0x4e800420; + } + + + /* + * The icache will be sync'd in reloc_plt, which is called + * after all the slots have been updated + */ + + return (0); +} + + +/* + * Process the PLT relocations. + */ +int +reloc_plt(Obj_Entry *obj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + int N = obj->pltrelasize / sizeof(Elf_Rela); + + if (obj->pltrelasize != 0) { + + relalim = (const Elf_Rela *)((char *)obj->pltrela + + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + assert(ELF_R_TYPE(rela->r_info) == R_PPC_JMP_SLOT); + + if (reloc_plt_object(obj, rela) < 0) { + return (-1); + } + } + } + + /* + * Sync the icache for the byte range represented by the + * trampoline routines and call slots. + */ + if (obj->pltgot != NULL) + __syncicache(obj->pltgot, JMPTAB_BASE(N)*4); + + return (0); +} + + +/* + * LD_BIND_NOW was set - force relocation for all jump slots + */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Obj_Entry *defobj; + const Elf_Rela *relalim; + const Elf_Rela *rela; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Addr target; + + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + assert(ELF_R_TYPE(rela->r_info) == R_PPC_JMP_SLOT); + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) { + dbg("reloc_jmpslots: sym not found"); + return (-1); + } + + target = (Elf_Addr)(defobj->relocbase + def->st_value); + +#if 0 + /* PG XXX */ + dbg("\"%s\" in \"%s\" --> %p in \"%s\"", + defobj->strtab + def->st_name, basename(obj->path), + (void *)target, basename(defobj->path)); +#endif + + reloc_jmpslot(where, target, defobj, obj, + (const Elf_Rel *) rela); + } + + obj->jmpslots_done = true; + + return (0); +} + + +/* + * Update the value of a PLT jump slot. Branch directly to the target if + * it is within +/- 32Mb, otherwise go indirectly via the pltcall + * trampoline call and jump table. + */ +Elf_Addr +reloc_jmpslot(Elf_Addr *wherep, Elf_Addr target, const Obj_Entry *defobj, + const Obj_Entry *obj, const Elf_Rel *rel) +{ + Elf_Addr offset; + const Elf_Rela *rela = (const Elf_Rela *) rel; + + dbg(" reloc_jmpslot: where=%p, target=%p", + (void *)wherep, (void *)target); + + /* + * At the PLT entry pointed at by `wherep', construct + * a direct transfer to the now fully resolved function + * address. + */ + offset = target - (Elf_Addr)wherep; + + if (abs(offset) < 32*1024*1024) { /* inside 32MB? */ + /* b value # branch directly */ + *wherep = 0x48000000 | (offset & 0x03fffffc); + __syncicache(wherep, 4); + } else { + Elf_Addr *pltcall, *jmptab; + int distance; + int N = obj->pltrelasize / sizeof(Elf_Rela); + int reloff = rela - obj->pltrela; + + if (reloff < 0) + return (-1); + + pltcall = obj->pltgot; + + dbg(" reloc_jmpslot: indir, reloff=%x, N=%x\n", + reloff, N); + + jmptab = obj->pltgot + JMPTAB_BASE(N); + jmptab[reloff] = target; + mb(); /* Order jmptab update before next changes */ + + if (reloff < PLT_EXTENDED_BEGIN) { + /* for extended PLT entries, we keep the old code */ + + distance = (Elf_Addr)pltcall - (Elf_Addr)(wherep + 1); + + /* li r11,reloff */ + /* b pltcall # use indirect pltcall routine */ + + /* first instruction same as before */ + wherep[1] = 0x48000000 | (distance & 0x03fffffc); + __syncicache(wherep, 8); + } + } + + return (target); +} + +int +reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, + struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +/* + * Setup the plt glue routines. + */ +#define PLTCALL_SIZE 20 +#define PLTLONGRESOLVE_SIZE 20 +#define PLTRESOLVE_SIZE 24 + +void +init_pltgot(Obj_Entry *obj) +{ + Elf_Word *pltcall, *pltresolve, *pltlongresolve; + Elf_Word *jmptab; + int N = obj->pltrelasize / sizeof(Elf_Rela); + + pltcall = obj->pltgot; + + if (pltcall == NULL) { + return; + } + + /* + * From the SVR4 PPC ABI: + * + * 'The first 18 words (72 bytes) of the PLT are reserved for + * use by the dynamic linker. + * ... + * 'If the executable or shared object requires N procedure + * linkage table entries, the link editor shall reserve 3*N + * words (12*N bytes) following the 18 reserved words. The + * first 2*N of these words are the procedure linkage table + * entries themselves. The static linker directs calls to bytes + * (72 + (i-1)*8), for i between 1 and N inclusive. The remaining + * N words (4*N bytes) are reserved for use by the dynamic linker.' + */ + + /* + * Copy the absolute-call assembler stub into the first part of + * the reserved PLT area. + */ + memcpy(pltcall, _rtld_powerpc_pltcall, PLTCALL_SIZE); + + /* + * Determine the address of the jumptable, which is the dyn-linker + * reserved area after the call cells. Write the absolute address + * of the jumptable into the absolute-call assembler code so it + * can determine this address. + */ + jmptab = obj->pltgot + JMPTAB_BASE(N); + pltcall[1] |= _ppc_ha(jmptab); /* addis 11,11,jmptab@ha */ + pltcall[2] |= _ppc_la(jmptab); /* lwz 11,jmptab@l(11) */ + + /* + * Skip down 20 bytes into the initial reserved area and copy + * in the standard resolving assembler call. Into this assembler, + * insert the absolute address of the _rtld_bind_start routine + * and the address of the relocation object. + * + * We place pltlongresolve first, so it can fix up its arguments + * and then fall through to the regular PLT resolver. + */ + pltlongresolve = obj->pltgot + 5; + + memcpy(pltlongresolve, _rtld_powerpc_pltlongresolve, + PLTLONGRESOLVE_SIZE); + pltlongresolve[0] |= _ppc_ha(jmptab); /* lis 12,jmptab@ha */ + pltlongresolve[1] |= _ppc_la(jmptab); /* addi 12,12,jmptab@l */ + + pltresolve = pltlongresolve + PLTLONGRESOLVE_SIZE/sizeof(uint32_t); + memcpy(pltresolve, _rtld_powerpc_pltresolve, PLTRESOLVE_SIZE); + pltresolve[0] |= _ppc_ha(_rtld_bind_start); + pltresolve[1] |= _ppc_la(_rtld_bind_start); + pltresolve[3] |= _ppc_ha(obj); + pltresolve[4] |= _ppc_la(obj); + + /* + * The icache will be sync'd in reloc_plt, which is called + * after all the slots have been updated + */ +} + +void +allocate_initial_tls(Obj_Entry *list) +{ + register Elf_Addr **tp __asm__("r2"); + Elf_Addr **_tp; + + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + + tls_static_space = tls_last_offset + tls_last_size + RTLD_STATIC_TLS_EXTRA; + + _tp = (Elf_Addr **) ((char *) allocate_tls(list, NULL, TLS_TCB_SIZE, 8) + + TLS_TP_OFFSET + TLS_TCB_SIZE); + + /* + * XXX gcc seems to ignore 'tp = _tp;' + */ + + __asm __volatile("mr %0,%1" : "=r"(tp) : "r"(_tp)); +} + +void* +__tls_get_addr(tls_index* ti) +{ + register Elf_Addr **tp __asm__("r2"); + char *p; + + p = tls_get_addr_common((Elf_Addr**)((Elf_Addr)tp - TLS_TP_OFFSET + - TLS_TCB_SIZE), ti->ti_module, ti->ti_offset); + + return (p + TLS_DTV_OFFSET); +} diff --git a/libexec/rtld-elf/powerpc/rtld_machdep.h b/libexec/rtld-elf/powerpc/rtld_machdep.h new file mode 100644 index 0000000..1ddf1bc --- /dev/null +++ b/libexec/rtld-elf/powerpc/rtld_machdep.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) (&_DYNAMIC) + +Elf_Addr reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *defobj, + const struct Struct_Obj_Entry *obj, + const Elf_Rel *rel); + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +/* + * Lazy binding entry point, called via PLT. + */ +void _rtld_bind_start(void); + +/* + * PLT functions. Not really correct prototypes, but the + * symbol values are needed. + */ +void _rtld_powerpc_pltlongresolve(void); +void _rtld_powerpc_pltresolve(void); +void _rtld_powerpc_pltcall(void); + +/* + * TLS + */ + +#define TLS_TP_OFFSET 0x7000 +#define TLS_DTV_OFFSET 0x8000 +#define TLS_TCB_SIZE 8 + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(8, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round(prev_offset + prev_size, align) +#define calculate_tls_end(off, size) ((off) + (size)) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +extern void *__tls_get_addr(tls_index* ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/powerpc/rtld_start.S b/libexec/rtld-elf/powerpc/rtld_start.S new file mode 100644 index 0000000..28ec19b --- /dev/null +++ b/libexec/rtld-elf/powerpc/rtld_start.S @@ -0,0 +1,200 @@ +/* $NetBSD: rtld_start.S,v 1.4 2001/09/26 04:06:43 mycroft Exp $ */ + +/*- + * Copyright (C) 1998 Tsubai Masanari + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <machine/asm.h> + +.extern _GLOBAL_OFFSET_TABLE_ +.extern _DYNAMIC + +_ENTRY(.rtld_start) + stwu %r1,-48(%r1) /* 16-byte aligned stack for reg saves + + exit_proc & obj _rtld args + + backchain & lrsave stack frame */ + stw %r3,16(%r1) /* argc */ + stw %r4,20(%r1) /* argv */ + stw %r5,24(%r1) /* envp */ +/* stw %r6,28(%r1) *//* obj (always 0) */ +/* stw %r7,32(%r1) *//* cleanup (always 0) */ + stw %r8,36(%r1) /* ps_strings */ + + /* + * Perform initial relocation of ld-elf.so. Not as easy as it + * sounds. + * - perform small forward branch to put PC into link reg + * - use link-time constants to determine offset to the + * _DYNAMIC section and the GOT. Add these to the PC to + * convert to absolute addresses. + * - sync icache to allow execution of the SVR4 ABI-specified + * blrl instruction preceding the GOT + * - Use this instruction to determine the GOT absolute address + * - read GOT[0], which is the SVR4 ABI-specified link-time + * value of _DYNAMIC. Subtract this value from the absolute + * value to determine the load address + * - call reloc_non_plt_self() to fix up ld-elf.so's relocations + */ + bl 1f + .long _DYNAMIC-. + .long _GLOBAL_OFFSET_TABLE_-. /* branch lr + 4 */ +1: + mflr %r3 /* PC value at .long */ + lwz %r4,4(%r3) + add %r4,%r4,%r3 /* &_GLOBAL_OFFSET_TABLE-4, blrl insn. */ + dcbst %r0,%r4 /* sync i-cache with d-cache */ + sync + icbi %r0,%r4 + isync + + lwz %r4,0(%r3) /* offset to _DYNAMIC */ + add %r3,%r4,%r3 /* r3 = &_DYNAMIC, absolute value */ + + bl _GLOBAL_OFFSET_TABLE_@local-4 + mflr %r4 /* &_GLOBAL_OFFSET_TABLE_, absolute value */ + lwz %r4,0(%r4) /* linker &_DYNAMIC, from got[0] */ + subf %r4,%r4,%r3 /* subtract to calculate relocbase */ + + bl reloc_non_plt_self@plt /* reloc_non_plt_self(&_DYNAMIC,base) */ + + /* + * The _rtld() function likes to see a stack layout containing + * { argc, argv[0], argv[1] ... argv[N], 0, env[0], ... , env[N] } + * Since the PowerPC stack was 16-byte aligned at exec time, the + * original stack layout has to be found by moving back a word + * from the argv pointer. + */ + lwz %r4,20(%r1) /* restore argv */ + addi %r3,%r4,-4 /* locate argc ptr, &argv[-1] */ + + addi %r4,%r1,8 /* &exit_proc on stack */ + addi %r5,%r1,12 /* &obj_main on stack */ + + bl _rtld@plt /* &_start = _rtld(sp, &exit_proc, &obj_main)*/ + mtlr %r3 + + /* + * Restore args, with new obj/exit proc + */ + lwz %r3,16(%r1) /* argc */ + lwz %r4,20(%r1) /* argv */ + lwz %r5,24(%r1) /* envp */ + lwz %r6,12(%r1) /* obj */ + lwz %r7,8(%r1) /* exit proc */ + lwz %r8,36(%r1) /* ps_strings */ + addi %r1,%r1,48 /* restore original stackptr */ + + blrl /* _start(argc, argv, envp, obj, cleanup, ps_strings) */ + + li %r0,1 /* _exit() */ + sc + +/* + * _rtld_bind_start() + * + * Call into the MI binder. This routine is reached via the PLT call cell, + * and then _rtld_powerpc_pltresolve(). + * On entry, %r11 contains the index of the PLT cell, and %r12 contains + * a pointer to the ELF object for the file. + * Save all registers, call into the binder to resolve and fixup the external + * routine, and then transfer to the external routine on return. + */ + .globl _rtld_bind + +_ENTRY(_rtld_bind_start) + stwu %r1,-160(%r1) # stack space for 29 regs + r0/lr/cr + stw %r0,20(%r1) # save r0 + mflr %r0 + stw %r0,16(%r1) # save lr + mfcr %r0 + stw %r0,12(%r1) # save cr + stmw %r3,24(%r1) # save r3-r31 + + mr %r3,%r12 # obj + mulli %r4,%r11,12 # rela index * sizeof(Elf_Rela) + bl _rtld_bind@PLT # target addr = _rtld_bind(obj, reloff) + mtctr %r3 # move absolute target addr into ctr + + lmw %r3,24(%r1) # restore r3-r31 + lwz %r0,12(%r1) # restore cr + mtcr %r0 + lwz %r0,16(%r1) # restore lr + mtlr %r0 + lwz %r0,20(%r1) # restore r0 + + addi %r1,%r1,160 # restore stack + bctr # jump to target + + +/* + * _rtld_powerpc_pltresolve() + * + * This routine is copied into the latter part of the 72-byte reserved + * area at the start of the PLT. The absolute address of the _rtld_bind_start + * routine, and the ELF object for the loaded file, are inserted into + * the code by the reloc.c:init_pltgot() routine. + * The first time an external routine is called, the PLT slot will + * set up %r11 to the offset of the slot, and will jump to this routine. + * The ELF object is shifted into %r11, and _rtld_bind_start is called + * to complete the binding. + */ +_ENTRY(_rtld_powerpc_pltlongresolve) + lis %r12,0 # lis 12,jmptab@ha + addi %r12,%r12,0 # addi 12,12,jmptab@l + subf %r11,%r12,%r11 # reloff + li %r12,2 + srw %r11,%r11,%r12 # index = reloff/sizeof(Elf_Addr) +_ENTRY(_rtld_powerpc_pltresolve) + lis %r12,0 # lis 12,_rtld_bind_start@ha + addi %r12,%r12,0 # addi 12,12,_rtld_bind_start@l + mtctr %r12 + lis %r12,0 # lis 12,obj@ha + addi %r12,%r12,0 # addi 12,12,obj@l + bctr + +/* + * _rtld_powerpc_pltcall() + * + * This routine is copied into the 72-byte reserved area at the + * start of the PLT. The reloc.c:init_pltgot() routine inserts + * the absolute address of the jumptable. + * Control is transferred to this routine when the binder has + * located the external routine, but determined that it is > 32Mb + * from the PLT slot. Code is inserted into the PLT slot to set up + * %r11 with the jumptable index, and jump to here, where the + * absolute address of the external routine is loaded from the + * jumptable and transferred to + */ +_ENTRY(_rtld_powerpc_pltcall) + slwi %r11,%r11,2 # jmptab offset = index * 4 + addis %r11,%r11,0 # addis 11,11,jmptab@ha + lwz %r11,0(%r11) # lwz 11,jmptab@l(11) + mtctr %r11 + bctr # (*jmptab[index])() + + .section .note.GNU-stack,"",%progbits diff --git a/libexec/rtld-elf/powerpc64/Makefile.inc b/libexec/rtld-elf/powerpc64/Makefile.inc new file mode 100644 index 0000000..e8c0da7 --- /dev/null +++ b/libexec/rtld-elf/powerpc64/Makefile.inc @@ -0,0 +1 @@ +# $FreeBSD$ diff --git a/libexec/rtld-elf/powerpc64/reloc.c b/libexec/rtld-elf/powerpc64/reloc.c new file mode 100644 index 0000000..65db28f --- /dev/null +++ b/libexec/rtld-elf/powerpc64/reloc.c @@ -0,0 +1,520 @@ +/* $NetBSD: ppc_reloc.c,v 1.10 2001/09/10 06:09:41 mycroft Exp $ */ + +/*- + * Copyright (C) 1998 Tsubai Masanari + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/mman.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <machine/cpu.h> +#include <machine/md_var.h> + +#include "debug.h" +#include "rtld.h" + +struct funcdesc { + Elf_Addr addr; + Elf_Addr toc; + Elf_Addr env; +}; + +/* + * Process the R_PPC_COPY relocations + */ +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + /* + * COPY relocs are invalid outside of the main program + */ + assert(dstobj->mainprog); + + relalim = (const Elf_Rela *) ((caddr_t) dstobj->rela + + dstobj->relasize); + for (rela = dstobj->rela; rela < relalim; rela++) { + void *dstaddr; + const Elf_Sym *dstsym; + const char *name; + size_t size; + const void *srcaddr; + const Elf_Sym *srcsym = NULL; + const Obj_Entry *srcobj, *defobj; + SymLook req; + int res; + + if (ELF_R_TYPE(rela->r_info) != R_PPC_COPY) { + continue; + } + + dstaddr = (void *) (dstobj->relocbase + rela->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rela->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, ELF_R_SYM(rela->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; + srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + + if (srcobj == NULL) { + _rtld_error("Undefined symbol \"%s\" " + " referenced from COPY" + " relocation in %s", name, dstobj->path); + return (-1); + } + + srcaddr = (const void *) (defobj->relocbase+srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + dbg("copy_reloc: src=%p,dst=%p,size=%zd\n",srcaddr,dstaddr,size); + } + + return (0); +} + + +/* + * Perform early relocation of the run-time linker image + */ +void +reloc_non_plt_self(Elf_Dyn *dynp, Elf_Addr relocbase) +{ + const Elf_Rela *rela = 0, *relalim; + Elf_Addr relasz = 0; + Elf_Addr *where; + + /* + * Extract the rela/relasz values from the dynamic section + */ + for (; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + case DT_RELA: + rela = (const Elf_Rela *)(relocbase+dynp->d_un.d_ptr); + break; + case DT_RELASZ: + relasz = dynp->d_un.d_val; + break; + } + } + + /* + * Relocate these values + */ + relalim = (const Elf_Rela *)((caddr_t)rela + relasz); + for (; rela < relalim; rela++) { + where = (Elf_Addr *)(relocbase + rela->r_offset); + *where = (Elf_Addr)(relocbase + rela->r_addend); + } +} + + +/* + * Relocate a non-PLT object with addend. + */ +static int +reloc_nonplt_object(Obj_Entry *obj_rtld, Obj_Entry *obj, const Elf_Rela *rela, + SymCache *cache, int flags, RtldLockState *lockstate) +{ + Elf_Addr *where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr tmp; + + switch (ELF_R_TYPE(rela->r_info)) { + + case R_PPC_NONE: + break; + + case R_PPC64_UADDR64: /* doubleword64 S + A */ + case R_PPC64_ADDR64: + case R_PPC_GLOB_DAT: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + if (def == NULL) { + return (-1); + } + + tmp = (Elf_Addr)(defobj->relocbase + def->st_value + + rela->r_addend); + + /* Don't issue write if unnecessary; avoid COW page fault */ + if (*where != tmp) { + *where = tmp; + } + break; + + case R_PPC_RELATIVE: /* doubleword64 B + A */ + tmp = (Elf_Addr)(obj->relocbase + rela->r_addend); + + /* As above, don't issue write unnecessarily */ + if (*where != tmp) { + *where = tmp; + } + break; + + case R_PPC_COPY: + /* + * These are deferred until all other relocations + * have been done. All we do here is make sure + * that the COPY relocation is not in a shared + * library. They are allowed only in executable + * files. + */ + if (!obj->mainprog) { + _rtld_error("%s: Unexpected R_COPY " + " relocation in shared library", + obj->path); + return (-1); + } + break; + + case R_PPC_JMP_SLOT: + /* + * These will be handled by the plt/jmpslot routines + */ + break; + + case R_PPC64_DTPMOD64: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + *where = (Elf_Addr) defobj->tlsindex; + + break; + + case R_PPC64_TPREL64: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + /* + * We lazily allocate offsets for static TLS as we + * see the first relocation that references the + * TLS block. This allows us to support (small + * amounts of) static TLS in dynamically loaded + * modules. If we run out of space, we generate an + * error. + */ + if (!defobj->tls_done) { + if (!allocate_tls_offset((Obj_Entry*) defobj)) { + _rtld_error("%s: No space available for static " + "Thread Local Storage", obj->path); + return (-1); + } + } + + *(Elf_Addr **)where = *where * sizeof(Elf_Addr) + + (Elf_Addr *)(def->st_value + rela->r_addend + + defobj->tlsoffset - TLS_TP_OFFSET); + + break; + + case R_PPC64_DTPREL64: + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + + if (def == NULL) + return (-1); + + *where += (Elf_Addr)(def->st_value + rela->r_addend + - TLS_DTV_OFFSET); + + break; + + default: + _rtld_error("%s: Unsupported relocation type %ld" + " in non-PLT relocations\n", obj->path, + ELF_R_TYPE(rela->r_info)); + return (-1); + } + return (0); +} + + +/* + * Process non-PLT relocations + */ +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + SymCache *cache; + int bytes = obj->dynsymcount * sizeof(SymCache); + int r = -1; + + if ((flags & SYMLOOK_IFUNC) != 0) + /* XXX not implemented */ + return (0); + + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + if (obj != obj_rtld) { + cache = mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_ANON, + -1, 0); + if (cache == MAP_FAILED) + cache = NULL; + } else + cache = NULL; + + /* + * From the SVR4 PPC ABI: + * "The PowerPC family uses only the Elf32_Rela relocation + * entries with explicit addends." + */ + relalim = (const Elf_Rela *)((caddr_t)obj->rela + obj->relasize); + for (rela = obj->rela; rela < relalim; rela++) { + if (reloc_nonplt_object(obj_rtld, obj, rela, cache, flags, + lockstate) < 0) + goto done; + } + r = 0; +done: + if (cache) + munmap(cache, bytes); + + /* Synchronize icache for text seg in case we made any changes */ + __syncicache(obj->mapbase, obj->textsize); + + return (r); +} + + +/* + * Initialise a PLT slot to the resolving trampoline + */ +static int +reloc_plt_object(Obj_Entry *obj, const Elf_Rela *rela) +{ + Elf_Addr *where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + Elf_Addr *glink; + long reloff; + + reloff = rela - obj->pltrela; + + if (obj->priv == NULL) + obj->priv = xmalloc(obj->pltrelasize); + glink = obj->priv + reloff*sizeof(Elf_Addr)*2; + + dbg(" reloc_plt_object: where=%p,reloff=%lx,glink=%p", (void *)where, reloff, glink); + + memcpy(where, _rtld_bind_start, sizeof(struct funcdesc)); + ((struct funcdesc *)(where))->env = (Elf_Addr)glink; + *(glink++) = (Elf_Addr)obj; + *(glink++) = reloff*sizeof(Elf_Rela); + + return (0); +} + + +/* + * Process the PLT relocations. + */ +int +reloc_plt(Obj_Entry *obj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + + if (obj->pltrelasize != 0) { + relalim = (const Elf_Rela *)((char *)obj->pltrela + + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + assert(ELF_R_TYPE(rela->r_info) == R_PPC_JMP_SLOT); + + if (reloc_plt_object(obj, rela) < 0) { + return (-1); + } + } + } + + return (0); +} + + +/* + * LD_BIND_NOW was set - force relocation for all jump slots + */ +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Obj_Entry *defobj; + const Elf_Rela *relalim; + const Elf_Rela *rela; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Addr target; + + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + assert(ELF_R_TYPE(rela->r_info) == R_PPC_JMP_SLOT); + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) { + dbg("reloc_jmpslots: sym not found"); + return (-1); + } + + target = (Elf_Addr)(defobj->relocbase + def->st_value); + +#if 0 + /* PG XXX */ + dbg("\"%s\" in \"%s\" --> %p in \"%s\"", + defobj->strtab + def->st_name, basename(obj->path), + (void *)target, basename(defobj->path)); +#endif + + if (def == &sym_zero) { + /* Zero undefined weak symbols */ + bzero(where, sizeof(struct funcdesc)); + } else { + reloc_jmpslot(where, target, defobj, obj, + (const Elf_Rel *) rela); + } + } + + obj->jmpslots_done = true; + + return (0); +} + + +/* + * Update the value of a PLT jump slot. + */ +Elf_Addr +reloc_jmpslot(Elf_Addr *wherep, Elf_Addr target, const Obj_Entry *defobj, + const Obj_Entry *obj, const Elf_Rel *rel) +{ + dbg(" reloc_jmpslot: where=%p, target=%p (%#lx + %#lx)", + (void *)wherep, (void *)target, *(Elf_Addr *)target, + (Elf_Addr)defobj->relocbase); + + /* + * At the PLT entry pointed at by `wherep', construct + * a direct transfer to the now fully resolved function + * address. + */ + + memcpy(wherep, (void *)target, sizeof(struct funcdesc)); + if (((struct funcdesc *)(wherep))->addr < (Elf_Addr)defobj->relocbase) { + /* + * XXX: It is possible (e.g. LD_BIND_NOW) that the function + * descriptor we are copying has not yet been relocated. + * If this happens, fix it. + */ + + ((struct funcdesc *)(wherep))->addr += + (Elf_Addr)defobj->relocbase; + ((struct funcdesc *)(wherep))->toc += + (Elf_Addr)defobj->relocbase; + } + + __asm __volatile("dcbst 0,%0; sync" :: "r"(wherep) : "memory"); + + return (target); +} + +int +reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, + struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +void +init_pltgot(Obj_Entry *obj) +{ +} + +void +allocate_initial_tls(Obj_Entry *list) +{ + Elf_Addr **tp; + + /* + * Fix the size of the static TLS block by using the maximum + * offset allocated so far and adding a bit for dynamic modules to + * use. + */ + + tls_static_space = tls_last_offset + tls_last_size + RTLD_STATIC_TLS_EXTRA; + + tp = (Elf_Addr **) ((char *)allocate_tls(list, NULL, TLS_TCB_SIZE, 16) + + TLS_TP_OFFSET + TLS_TCB_SIZE); + + __asm __volatile("mr 13,%0" :: "r"(tp)); +} + +void* +__tls_get_addr(tls_index* ti) +{ + Elf_Addr **tp; + char *p; + + __asm __volatile("mr %0,13" : "=r"(tp)); + p = tls_get_addr_common((Elf_Addr**)((Elf_Addr)tp - TLS_TP_OFFSET + - TLS_TCB_SIZE), ti->ti_module, ti->ti_offset); + + return (p + TLS_DTV_OFFSET); +} diff --git a/libexec/rtld-elf/powerpc64/rtld_machdep.h b/libexec/rtld-elf/powerpc64/rtld_machdep.h new file mode 100644 index 0000000..b88ed9d --- /dev/null +++ b/libexec/rtld-elf/powerpc64/rtld_machdep.h @@ -0,0 +1,85 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +#define rtld_dynamic(obj) (&_DYNAMIC) + +Elf_Addr reloc_jmpslot(Elf_Addr *where, Elf_Addr target, + const struct Struct_Obj_Entry *defobj, + const struct Struct_Obj_Entry *obj, + const Elf_Rel *rel); + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +/* + * Lazy binding entry point, called via PLT. + */ +void _rtld_bind_start(void); + +/* + * TLS + */ + +#define TLS_TP_OFFSET 0x7000 +#define TLS_DTV_OFFSET 0x8000 +#define TLS_TCB_SIZE 16 + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(16, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round(prev_offset + prev_size, align) +#define calculate_tls_end(off, size) ((off) + (size)) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +extern void *__tls_get_addr(tls_index* ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC PF_X +#define RTLD_DEFAULT_STACK_EXEC PROT_EXEC + +#endif diff --git a/libexec/rtld-elf/powerpc64/rtld_start.S b/libexec/rtld-elf/powerpc64/rtld_start.S new file mode 100644 index 0000000..186e16e --- /dev/null +++ b/libexec/rtld-elf/powerpc64/rtld_start.S @@ -0,0 +1,162 @@ +/* $NetBSD: rtld_start.S,v 1.4 2001/09/26 04:06:43 mycroft Exp $ */ + +/*- + * Copyright (C) 1998 Tsubai Masanari + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <machine/asm.h> + +.extern _GLOBAL_OFFSET_TABLE_ +.extern _DYNAMIC + +_ENTRY(_rtld_start) + stdu %r1,-144(%r1) /* 16-byte aligned stack for reg saves + + exit_proc & obj _rtld args + + backchain & lrsave stack frame */ + std %r3,96(%r1) /* argc */ + std %r4,104(%r1) /* argv */ + std %r5,112(%r1) /* envp */ +/* std %r6,120(%r1) *//* obj (always 0) */ +/* std %r7,128(%r1) *//* cleanup (always 0) */ + std %r8,136(%r1) /* ps_strings */ + + /* + * Perform initial relocation of ld-elf.so. Not as easy as it + * sounds. + * - perform small forward branch to put PC into link reg + * - use link-time constants to determine offset to the + * _DYNAMIC section and the GOT. Add these to the PC to + * convert to absolute addresses. + * - call reloc_non_plt_self() to fix up ld-elf.so's relocations + */ + + bl 1f + .llong _DYNAMIC-. +1: + mflr %r3 /* PC value at .llong */ + ld %r4,0(%r3) /* offset to _DYNAMIC */ + add %r3,%r4,%r3 /* r3 = &_DYNAMIC, absolute value */ + + ld %r4,-0x8000(%r2) /* First TOC entry is TOC base */ + subf %r4,%r4,%r2 /* Subtract from real TOC base to get base */ + + bl reloc_non_plt_self /* reloc_non_plt_self(&_DYNAMIC,base) */ + nop + + /* + * The _rtld() function likes to see a stack layout containing + * { argc, argv[0], argv[1] ... argv[N], 0, env[0], ... , env[N] } + * Since the PowerPC stack was 16-byte aligned at exec time, the + * original stack layout has to be found by moving back a word + * from the argv pointer. + */ + ld %r4,104(%r1) + addi %r3,%r4,-8 /* locate argc ptr, &argv[-1] */ + addi %r4,%r1,128 /* &exit_proc on stack */ + addi %r5,%r1,120 /* &obj_main on stack */ + + bl _rtld /* &_start = _rtld(sp, &exit_proc, &obj_main)*/ + nop + ld %r2,8(%r3) + ld %r11,16(%r3) + ld %r3,0(%r3) + mtlr %r3 + + /* + * Restore args, with new obj/exit proc + */ + ld %r3,96(%r1) /* argc */ + ld %r4,104(%r1) /* argv */ + ld %r5,112(%r1) /* envp */ + ld %r6,120(%r1) /* obj */ + ld %r7,128(%r1) /* exit proc */ + ld %r8,136(%r1) /* ps_strings */ + + blrl /* _start(argc, argv, envp, obj, cleanup, ps_strings) */ + + li %r0,1 /* _exit() */ + sc + +/* + * _rtld_bind_start() + * + * Call into the MI binder. This routine is reached via the PLT call cell + * On entry, %r11 contains a pointer to the (object, relocation) tuple. + * + * Save all registers, call into the binder to resolve and fixup the external + * routine, and then transfer to the external routine on return. + */ + .globl _rtld_bind + +_ENTRY(_rtld_bind_start) + mflr %r0 + std %r0,16(%r1) # save lr + mfcr %r0 + std %r0,8(%r1) # save cr + + stdu %r1,-48-12*8(%r1) # stack space for 8 regs + header + # + 2 save regs + std %r3,64+0*8(%r1) # save r3-r31 + std %r4,64+1*8(%r1) + std %r5,64+2*8(%r1) + std %r6,64+3*8(%r1) + std %r7,64+4*8(%r1) + std %r8,64+5*8(%r1) + std %r9,64+6*8(%r1) + std %r10,64+7*8(%r1) + std %r12,64+8*8(%r1) + + ld %r3,0(%r11) + ld %r4,8(%r11) + bl _rtld_bind # target addr = _rtld_bind(obj, reloff) + nop + + ld %r2,8(%r3) + ld %r11,16(%r3) + ld %r3,0(%r3) + mtctr %r3 # move absolute target addr into ctr + + ld %r3,64+0*8(%r1) # restore r3-r31 + ld %r4,64+1*8(%r1) + ld %r5,64+2*8(%r1) + ld %r6,64+3*8(%r1) + ld %r7,64+4*8(%r1) + ld %r8,64+5*8(%r1) + ld %r9,64+6*8(%r1) + ld %r10,64+7*8(%r1) + ld %r12,64+8*8(%r1) + + ld %r1,0(%r1) # restore stack + ld %r0,8(%r1) # restore cr + mtcr %r0 + ld %r0,16(%r1) # restore lr + mtlr %r0 + + bctr # jump to target + + .section .note.GNU-stack,"",%progbits diff --git a/libexec/rtld-elf/rtld.1 b/libexec/rtld-elf/rtld.1 new file mode 100644 index 0000000..a8e9d68 --- /dev/null +++ b/libexec/rtld-elf/rtld.1 @@ -0,0 +1,295 @@ +.\" Copyright (c) 1995 Paul Kranenburg +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgment: +.\" This product includes software developed by Paul Kranenburg. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd June 20, 2014 +.Dt RTLD 1 +.Os +.Sh NAME +.Nm ld-elf.so.1 , +.Nm ld.so , +.Nm rtld +.Nd run-time link-editor +.Sh DESCRIPTION +The +.Nm +utility is a self-contained shared object providing run-time +support for loading and link-editing shared objects into a process' +address space. +It is also commonly known as the dynamic linker. +It uses the data structures +contained within dynamically linked programs to determine which shared +libraries are needed and loads them using the +.Xr mmap 2 +system call. +.Pp +After all shared libraries have been successfully loaded, +.Nm +proceeds to resolve external references from both the main program and +all objects loaded. +A mechanism is provided for initialization routines +to be called on a per-object basis, giving a shared object an opportunity +to perform any extra set-up before execution of the program proper begins. +This is useful for C++ libraries that contain static constructors. +.Pp +When resolving dependencies for the loaded objects, +.Nm +may be allowed to translate dynamic token strings in rpath and soname +by setting +.Fl "z origin" +option of the static linker +.Xr ld 1 . +The following strings are recognized now: +.Bl -tag -width ".Pa $PLATFORM" +.It Pa $ORIGIN +Translated to the full path of the loaded object. +.It Pa $OSNAME +Translated to the name of the operating system implementation. +.It Pa $OSREL +Translated to the release level of the operating system. +.It Pa $PLATFORM +Translated to the machine hardware platform. +.El +.Pp +The +.Nm +utility itself is loaded by the kernel together with any dynamically-linked +program that is to be executed. +The kernel transfers control to the +dynamic linker. +After the dynamic linker has finished loading, +relocating, and initializing the program and its required shared +objects, it transfers control to the entry point of the program. +The following search order is used to locate required shared objects: +.Pp +.Bl -enum -offset indent -compact +.It +.Dv DT_RPATH +of the referencing object unless that object also contains a +.Dv DT_RUNPATH +tag +.It +.Dv DT_RPATH +of the program unless the referencing object contains a +.Dv DT_RUNPATH +tag +.It +Path indicated by +.Ev LD_LIBRARY_PATH +environment variable +.It +.Dv DT_RUNPATH +of the referencing object +.It +Hints file produced by the +.Xr ldconfig 8 +utility +.It +The +.Pa /lib +and +.Pa /usr/lib +directories, unless the referencing object was linked using the +.Dq Fl z Ar nodefaultlib +option +.El +.Pp +The +.Nm +utility +recognizes a number of environment variables that can be used to modify +its behaviour. +On 64-bit architectures, the linker for 32-bit objects recognizes +all the environment variables listed below, but is being prefixed with +.Ev LD_32_ , +for example: +.Ev LD_32_TRACE_LOADED_OBJECTS . +.Bl -tag -width ".Ev LD_LIBMAP_DISABLE" +.It Ev LD_DUMP_REL_POST +If set, +.Nm +will print a table containing all relocations after symbol +binding and relocation. +.It Ev LD_DUMP_REL_PRE +If set, +.Nm +will print a table containing all relocations before symbol +binding and relocation. +.It Ev LD_LIBMAP +A library replacement list in the same format as +.Xr libmap.conf 5 . +For convenience, the characters +.Ql = +and +.Ql \&, +can be used instead of a space and a newline. +This variable is parsed after +.Xr libmap.conf 5 , +and will override its entries. +This variable is unset for set-user-ID and set-group-ID programs. +.It Ev LD_LIBMAP_DISABLE +If set, disables the use of +.Xr libmap.conf 5 +and +.Ev LD_LIBMAP . +This variable is unset for set-user-ID and set-group-ID programs. +.It Ev LD_ELF_HINTS_PATH +This variable will override the default location of +.Dq hints +file. +This variable is unset for set-user-ID and set-group-ID programs. +.It Ev LD_LIBRARY_PATH +A colon separated list of directories, overriding the default search path +for shared libraries. +This variable is unset for set-user-ID and set-group-ID programs. +.It Ev LD_LIBRARY_PATH_RPATH +If the variable is specified and has a value starting with +any of \'y\', \'Y\' or \'1\' symbols, the path specified by +.Ev LD_LIBRARY_PATH +variable is allowed to override the path from +.Dv DT_RPATH +for binaries which does not contain +.Dv DT_RUNPATH +tag. +For such binaries, when the variable +.Ev LD_LIBRARY_PATH_RPATH +is set, +.Dq Fl z Ar nodefaultlib +link-time option is ignored as well. +.It Ev LD_PRELOAD +A list of shared libraries, separated by colons and/or white space, +to be linked in before any +other shared libraries. +If the directory is not specified then +the directories specified by +.Ev LD_LIBRARY_PATH +will be searched first +followed by the set of built-in standard directories. +This variable is unset for set-user-ID and set-group-ID programs. +.Ev LD_LIBRARY_PATH_FDS +A colon separated list of file descriptor numbers for library directories. +This is intended for use within +.Xr capsicum 4 +sandboxes, when global namespaces such as the filesystem are unavailable. +It is consulted just after LD_LIBRARY_PATH. +This variable is unset for set-user-ID and set-group-ID programs. +.It Ev LD_BIND_NOW +When set to a nonempty string, causes +.Nm +to relocate all external function calls before starting execution of the +program. +Normally, function calls are bound lazily, at the first call +of each function. +.Ev LD_BIND_NOW +increases the start-up time of a program, but it avoids run-time +surprises caused by unexpectedly undefined functions. +.It Ev LD_TRACE_LOADED_OBJECTS +When set to a nonempty string, causes +.Nm +to exit after loading the shared objects and printing a summary which includes +the absolute pathnames of all objects, to standard output. +.It Ev LD_TRACE_LOADED_OBJECTS_ALL +When set to a nonempty string, causes +.Nm +to expand the summary to indicate which objects caused each object to +be loaded. +.It Ev LD_TRACE_LOADED_OBJECTS_FMT1 +.It Ev LD_TRACE_LOADED_OBJECTS_FMT2 +When set, these variables are interpreted as format strings a la +.Xr printf 3 +to customize the trace output and are used by +.Xr ldd 1 Ns 's +.Fl f +option and allows +.Xr ldd 1 +to be operated as a filter more conveniently. +If the dependency name starts with string +.Pa lib , +.Ev LD_TRACE_LOADED_OBJECTS_FMT1 +is used, otherwise +.Ev LD_TRACE_LOADED_OBJECTS_FMT2 +is used. +The following conversions can be used: +.Bl -tag -width 4n +.It Li %a +The main program's name +(also known as +.Dq __progname ) . +.It Li \&%A +The value of the environment variable +.Ev LD_TRACE_LOADED_OBJECTS_PROGNAME . +Typically used to print both the names of programs and shared libraries +being inspected using +.Xr ldd 1 . +.It Li %o +The library name. +.It Li %p +The full pathname as determined by +.Nm rtld Ns 's +library search rules. +.It Li %x +The library's load address. +.El +.Pp +Additionally, +.Ql \en +and +.Ql \et +are recognized and have their usual meaning. +.It Ev LD_UTRACE +If set, +.Nm +will log events such as the loading and unloading of shared objects via +.Xr utrace 2 . +.It Ev LD_LOADFLTR +If set, +.Nm +will process the filtee dependencies of the loaded objects immediately, +instead of postponing it until required. +Normally, the filtees are opened at the time of the first symbol resolution +from the filter object. +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/ld-elf32.so.hints" -compact +.It Pa /var/run/ld-elf.so.hints +Hints file. +.It Pa /var/run/ld-elf32.so.hints +Hints file for 32-bit binaries on 64-bit system. +.It Pa /etc/libmap.conf +The libmap configuration file. +.It Pa /etc/libmap32.conf +The libmap configuration file for 32-bit binaries on 64-bit system. +.El +.Sh SEE ALSO +.Xr ld 1 , +.Xr ldd 1 , +.Xr capsicum 4 , +.Xr elf 5 , +.Xr libmap.conf 5 , +.Xr ldconfig 8 diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c new file mode 100644 index 0000000..cc7afda --- /dev/null +++ b/libexec/rtld-elf/rtld.c @@ -0,0 +1,5021 @@ +/*- + * Copyright 1996, 1997, 1998, 1999, 2000 John D. Polstra. + * Copyright 2003 Alexander Kabaev <kan@FreeBSD.ORG>. + * Copyright 2009-2012 Konstantin Belousov <kib@FreeBSD.ORG>. + * Copyright 2012 John Marino <draco@marino.st>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Dynamic linker for ELF. + * + * John Polstra <jdp@polstra.com>. + */ + +#ifndef __GNUC__ +#error "GCC is needed to compile this file" +#endif + +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/uio.h> +#include <sys/utsname.h> +#include <sys/ktrace.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "debug.h" +#include "rtld.h" +#include "libmap.h" +#include "rtld_tls.h" +#include "rtld_printf.h" +#include "notes.h" + +#ifndef COMPAT_32BIT +#define PATH_RTLD "/libexec/ld-elf.so.1" +#else +#define PATH_RTLD "/libexec/ld-elf32.so.1" +#endif + +/* Types. */ +typedef void (*func_ptr_type)(); +typedef void * (*path_enum_proc) (const char *path, size_t len, void *arg); + +/* + * Function declarations. + */ +static const char *basename(const char *); +static void die(void) __dead2; +static void digest_dynamic1(Obj_Entry *, int, const Elf_Dyn **, + const Elf_Dyn **, const Elf_Dyn **); +static void digest_dynamic2(Obj_Entry *, const Elf_Dyn *, const Elf_Dyn *, + const Elf_Dyn *); +static void digest_dynamic(Obj_Entry *, int); +static Obj_Entry *digest_phdr(const Elf_Phdr *, int, caddr_t, const char *); +static Obj_Entry *dlcheck(void *); +static Obj_Entry *dlopen_object(const char *name, int fd, Obj_Entry *refobj, + int lo_flags, int mode, RtldLockState *lockstate); +static Obj_Entry *do_load_object(int, const char *, char *, struct stat *, int); +static int do_search_info(const Obj_Entry *obj, int, struct dl_serinfo *); +static bool donelist_check(DoneList *, const Obj_Entry *); +static void errmsg_restore(char *); +static char *errmsg_save(void); +static void *fill_search_info(const char *, size_t, void *); +static char *find_library(const char *, const Obj_Entry *, int *); +static const char *gethints(bool); +static void init_dag(Obj_Entry *); +static void init_pagesizes(Elf_Auxinfo **aux_info); +static void init_rtld(caddr_t, Elf_Auxinfo **); +static void initlist_add_neededs(Needed_Entry *, Objlist *); +static void initlist_add_objects(Obj_Entry *, Obj_Entry **, Objlist *); +static void linkmap_add(Obj_Entry *); +static void linkmap_delete(Obj_Entry *); +static void load_filtees(Obj_Entry *, int flags, RtldLockState *); +static void unload_filtees(Obj_Entry *); +static int load_needed_objects(Obj_Entry *, int); +static int load_preload_objects(void); +static Obj_Entry *load_object(const char *, int fd, const Obj_Entry *, int); +static void map_stacks_exec(RtldLockState *); +static Obj_Entry *obj_from_addr(const void *); +static void objlist_call_fini(Objlist *, Obj_Entry *, RtldLockState *); +static void objlist_call_init(Objlist *, RtldLockState *); +static void objlist_clear(Objlist *); +static Objlist_Entry *objlist_find(Objlist *, const Obj_Entry *); +static void objlist_init(Objlist *); +static void objlist_push_head(Objlist *, Obj_Entry *); +static void objlist_push_tail(Objlist *, Obj_Entry *); +static void objlist_put_after(Objlist *, Obj_Entry *, Obj_Entry *); +static void objlist_remove(Objlist *, Obj_Entry *); +static int parse_libdir(const char *); +static void *path_enumerate(const char *, path_enum_proc, void *); +static int relocate_object_dag(Obj_Entry *root, bool bind_now, + Obj_Entry *rtldobj, int flags, RtldLockState *lockstate); +static int relocate_object(Obj_Entry *obj, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate); +static int relocate_objects(Obj_Entry *, bool, Obj_Entry *, int, + RtldLockState *); +static int resolve_objects_ifunc(Obj_Entry *first, bool bind_now, + int flags, RtldLockState *lockstate); +static int rtld_dirname(const char *, char *); +static int rtld_dirname_abs(const char *, char *); +static void *rtld_dlopen(const char *name, int fd, int mode); +static void rtld_exit(void); +static char *search_library_path(const char *, const char *); +static char *search_library_pathfds(const char *, const char *, int *); +static const void **get_program_var_addr(const char *, RtldLockState *); +static void set_program_var(const char *, const void *); +static int symlook_default(SymLook *, const Obj_Entry *refobj); +static int symlook_global(SymLook *, DoneList *); +static void symlook_init_from_req(SymLook *, const SymLook *); +static int symlook_list(SymLook *, const Objlist *, DoneList *); +static int symlook_needed(SymLook *, const Needed_Entry *, DoneList *); +static int symlook_obj1_sysv(SymLook *, const Obj_Entry *); +static int symlook_obj1_gnu(SymLook *, const Obj_Entry *); +static void trace_loaded_objects(Obj_Entry *); +static void unlink_object(Obj_Entry *); +static void unload_object(Obj_Entry *); +static void unref_dag(Obj_Entry *); +static void ref_dag(Obj_Entry *); +static char *origin_subst_one(char *, const char *, const char *, bool); +static char *origin_subst(char *, const char *); +static void preinit_main(void); +static int rtld_verify_versions(const Objlist *); +static int rtld_verify_object_versions(Obj_Entry *); +static void object_add_name(Obj_Entry *, const char *); +static int object_match_name(const Obj_Entry *, const char *); +static void ld_utrace_log(int, void *, void *, size_t, int, const char *); +static void rtld_fill_dl_phdr_info(const Obj_Entry *obj, + struct dl_phdr_info *phdr_info); +static uint32_t gnu_hash(const char *); +static bool matched_symbol(SymLook *, const Obj_Entry *, Sym_Match_Result *, + const unsigned long); + +void r_debug_state(struct r_debug *, struct link_map *) __noinline; +void _r_debug_postinit(struct link_map *) __noinline; + +/* + * Data declarations. + */ +static char *error_message; /* Message for dlerror(), or NULL */ +struct r_debug r_debug; /* for GDB; */ +static bool libmap_disable; /* Disable libmap */ +static bool ld_loadfltr; /* Immediate filters processing */ +static char *libmap_override; /* Maps to use in addition to libmap.conf */ +static bool trust; /* False for setuid and setgid programs */ +static bool dangerous_ld_env; /* True if environment variables have been + used to affect the libraries loaded */ +static char *ld_bind_now; /* Environment variable for immediate binding */ +static char *ld_debug; /* Environment variable for debugging */ +static char *ld_library_path; /* Environment variable for search path */ +static char *ld_library_dirs; /* Environment variable for library descriptors */ +static char *ld_preload; /* Environment variable for libraries to + load first */ +static char *ld_elf_hints_path; /* Environment variable for alternative hints path */ +static char *ld_tracing; /* Called from ldd to print libs */ +static char *ld_utrace; /* Use utrace() to log events. */ +static Obj_Entry *obj_list; /* Head of linked list of shared objects */ +static Obj_Entry **obj_tail; /* Link field of last object in list */ +static Obj_Entry *obj_main; /* The main program shared object */ +static Obj_Entry obj_rtld; /* The dynamic linker shared object */ +static unsigned int obj_count; /* Number of objects in obj_list */ +static unsigned int obj_loads; /* Number of objects in obj_list */ + +static Objlist list_global = /* Objects dlopened with RTLD_GLOBAL */ + STAILQ_HEAD_INITIALIZER(list_global); +static Objlist list_main = /* Objects loaded at program startup */ + STAILQ_HEAD_INITIALIZER(list_main); +static Objlist list_fini = /* Objects needing fini() calls */ + STAILQ_HEAD_INITIALIZER(list_fini); + +Elf_Sym sym_zero; /* For resolving undefined weak refs. */ + +#define GDB_STATE(s,m) r_debug.r_state = s; r_debug_state(&r_debug,m); + +extern Elf_Dyn _DYNAMIC; +#pragma weak _DYNAMIC +#ifndef RTLD_IS_DYNAMIC +#define RTLD_IS_DYNAMIC() (&_DYNAMIC != NULL) +#endif + +int npagesizes, osreldate; +size_t *pagesizes; + +long __stack_chk_guard[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + +static int stack_prot = PROT_READ | PROT_WRITE | RTLD_DEFAULT_STACK_EXEC; +static int max_stack_flags; + +/* + * Global declarations normally provided by crt1. The dynamic linker is + * not built with crt1, so we have to provide them ourselves. + */ +char *__progname; +char **environ; + +/* + * Used to pass argc, argv to init functions. + */ +int main_argc; +char **main_argv; + +/* + * Globals to control TLS allocation. + */ +size_t tls_last_offset; /* Static TLS offset of last module */ +size_t tls_last_size; /* Static TLS size of last module */ +size_t tls_static_space; /* Static TLS space allocated */ +size_t tls_static_max_align; +int tls_dtv_generation = 1; /* Used to detect when dtv size changes */ +int tls_max_index = 1; /* Largest module index allocated */ + +bool ld_library_path_rpath = false; + +/* + * Fill in a DoneList with an allocation large enough to hold all of + * the currently-loaded objects. Keep this as a macro since it calls + * alloca and we want that to occur within the scope of the caller. + */ +#define donelist_init(dlp) \ + ((dlp)->objs = alloca(obj_count * sizeof (dlp)->objs[0]), \ + assert((dlp)->objs != NULL), \ + (dlp)->num_alloc = obj_count, \ + (dlp)->num_used = 0) + +#define UTRACE_DLOPEN_START 1 +#define UTRACE_DLOPEN_STOP 2 +#define UTRACE_DLCLOSE_START 3 +#define UTRACE_DLCLOSE_STOP 4 +#define UTRACE_LOAD_OBJECT 5 +#define UTRACE_UNLOAD_OBJECT 6 +#define UTRACE_ADD_RUNDEP 7 +#define UTRACE_PRELOAD_FINISHED 8 +#define UTRACE_INIT_CALL 9 +#define UTRACE_FINI_CALL 10 + +struct utrace_rtld { + char sig[4]; /* 'RTLD' */ + int event; + void *handle; + void *mapbase; /* Used for 'parent' and 'init/fini' */ + size_t mapsize; + int refcnt; /* Used for 'mode' */ + char name[MAXPATHLEN]; +}; + +#define LD_UTRACE(e, h, mb, ms, r, n) do { \ + if (ld_utrace != NULL) \ + ld_utrace_log(e, h, mb, ms, r, n); \ +} while (0) + +static void +ld_utrace_log(int event, void *handle, void *mapbase, size_t mapsize, + int refcnt, const char *name) +{ + struct utrace_rtld ut; + + ut.sig[0] = 'R'; + ut.sig[1] = 'T'; + ut.sig[2] = 'L'; + ut.sig[3] = 'D'; + ut.event = event; + ut.handle = handle; + ut.mapbase = mapbase; + ut.mapsize = mapsize; + ut.refcnt = refcnt; + bzero(ut.name, sizeof(ut.name)); + if (name) + strlcpy(ut.name, name, sizeof(ut.name)); + utrace(&ut, sizeof(ut)); +} + +/* + * Main entry point for dynamic linking. The first argument is the + * stack pointer. The stack is expected to be laid out as described + * in the SVR4 ABI specification, Intel 386 Processor Supplement. + * Specifically, the stack pointer points to a word containing + * ARGC. Following that in the stack is a null-terminated sequence + * of pointers to argument strings. Then comes a null-terminated + * sequence of pointers to environment strings. Finally, there is a + * sequence of "auxiliary vector" entries. + * + * The second argument points to a place to store the dynamic linker's + * exit procedure pointer and the third to a place to store the main + * program's object. + * + * The return value is the main program's entry point. + */ +func_ptr_type +_rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) +{ + Elf_Auxinfo *aux_info[AT_COUNT]; + int i; + int argc; + char **argv; + char **env; + Elf_Auxinfo *aux; + Elf_Auxinfo *auxp; + const char *argv0; + Objlist_Entry *entry; + Obj_Entry *obj; + Obj_Entry **preload_tail; + Obj_Entry *last_interposer; + Objlist initlist; + RtldLockState lockstate; + char *library_path_rpath; + int mib[2]; + size_t len; + + /* + * On entry, the dynamic linker itself has not been relocated yet. + * Be very careful not to reference any global data until after + * init_rtld has returned. It is OK to reference file-scope statics + * and string constants, and to call static and global functions. + */ + + /* Find the auxiliary vector on the stack. */ + argc = *sp++; + argv = (char **) sp; + sp += argc + 1; /* Skip over arguments and NULL terminator */ + env = (char **) sp; + while (*sp++ != 0) /* Skip over environment, and NULL terminator */ + ; + aux = (Elf_Auxinfo *) sp; + + /* Digest the auxiliary vector. */ + for (i = 0; i < AT_COUNT; i++) + aux_info[i] = NULL; + for (auxp = aux; auxp->a_type != AT_NULL; auxp++) { + if (auxp->a_type < AT_COUNT) + aux_info[auxp->a_type] = auxp; + } + + /* Initialize and relocate ourselves. */ + assert(aux_info[AT_BASE] != NULL); + init_rtld((caddr_t) aux_info[AT_BASE]->a_un.a_ptr, aux_info); + + __progname = obj_rtld.path; + argv0 = argv[0] != NULL ? argv[0] : "(null)"; + environ = env; + main_argc = argc; + main_argv = argv; + + if (aux_info[AT_CANARY] != NULL && + aux_info[AT_CANARY]->a_un.a_ptr != NULL) { + i = aux_info[AT_CANARYLEN]->a_un.a_val; + if (i > sizeof(__stack_chk_guard)) + i = sizeof(__stack_chk_guard); + memcpy(__stack_chk_guard, aux_info[AT_CANARY]->a_un.a_ptr, i); + } else { + mib[0] = CTL_KERN; + mib[1] = KERN_ARND; + + len = sizeof(__stack_chk_guard); + if (sysctl(mib, 2, __stack_chk_guard, &len, NULL, 0) == -1 || + len != sizeof(__stack_chk_guard)) { + /* If sysctl was unsuccessful, use the "terminator canary". */ + ((unsigned char *)(void *)__stack_chk_guard)[0] = 0; + ((unsigned char *)(void *)__stack_chk_guard)[1] = 0; + ((unsigned char *)(void *)__stack_chk_guard)[2] = '\n'; + ((unsigned char *)(void *)__stack_chk_guard)[3] = 255; + } + } + + trust = !issetugid(); + + ld_bind_now = getenv(LD_ "BIND_NOW"); + /* + * If the process is tainted, then we un-set the dangerous environment + * variables. The process will be marked as tainted until setuid(2) + * is called. If any child process calls setuid(2) we do not want any + * future processes to honor the potentially un-safe variables. + */ + if (!trust) { + if (unsetenv(LD_ "PRELOAD") || unsetenv(LD_ "LIBMAP") || + unsetenv(LD_ "LIBRARY_PATH") || unsetenv(LD_ "LIBRARY_PATH_FDS") || + unsetenv(LD_ "LIBMAP_DISABLE") || + unsetenv(LD_ "DEBUG") || unsetenv(LD_ "ELF_HINTS_PATH") || + unsetenv(LD_ "LOADFLTR") || unsetenv(LD_ "LIBRARY_PATH_RPATH")) { + _rtld_error("environment corrupt; aborting"); + die(); + } + } + ld_debug = getenv(LD_ "DEBUG"); + libmap_disable = getenv(LD_ "LIBMAP_DISABLE") != NULL; + libmap_override = getenv(LD_ "LIBMAP"); + ld_library_path = getenv(LD_ "LIBRARY_PATH"); + ld_library_dirs = getenv(LD_ "LIBRARY_PATH_FDS"); + ld_preload = getenv(LD_ "PRELOAD"); + ld_elf_hints_path = getenv(LD_ "ELF_HINTS_PATH"); + ld_loadfltr = getenv(LD_ "LOADFLTR") != NULL; + library_path_rpath = getenv(LD_ "LIBRARY_PATH_RPATH"); + if (library_path_rpath != NULL) { + if (library_path_rpath[0] == 'y' || + library_path_rpath[0] == 'Y' || + library_path_rpath[0] == '1') + ld_library_path_rpath = true; + else + ld_library_path_rpath = false; + } + dangerous_ld_env = libmap_disable || (libmap_override != NULL) || + (ld_library_path != NULL) || (ld_preload != NULL) || + (ld_elf_hints_path != NULL) || ld_loadfltr; + ld_tracing = getenv(LD_ "TRACE_LOADED_OBJECTS"); + ld_utrace = getenv(LD_ "UTRACE"); + + if ((ld_elf_hints_path == NULL) || strlen(ld_elf_hints_path) == 0) + ld_elf_hints_path = _PATH_ELF_HINTS; + + if (ld_debug != NULL && *ld_debug != '\0') + debug = 1; + dbg("%s is initialized, base address = %p", __progname, + (caddr_t) aux_info[AT_BASE]->a_un.a_ptr); + dbg("RTLD dynamic = %p", obj_rtld.dynamic); + dbg("RTLD pltgot = %p", obj_rtld.pltgot); + + dbg("initializing thread locks"); + lockdflt_init(); + + /* + * Load the main program, or process its program header if it is + * already loaded. + */ + if (aux_info[AT_EXECFD] != NULL) { /* Load the main program. */ + int fd = aux_info[AT_EXECFD]->a_un.a_val; + dbg("loading main program"); + obj_main = map_object(fd, argv0, NULL); + close(fd); + if (obj_main == NULL) + die(); + max_stack_flags = obj->stack_flags; + } else { /* Main program already loaded. */ + const Elf_Phdr *phdr; + int phnum; + caddr_t entry; + + dbg("processing main program's program header"); + assert(aux_info[AT_PHDR] != NULL); + phdr = (const Elf_Phdr *) aux_info[AT_PHDR]->a_un.a_ptr; + assert(aux_info[AT_PHNUM] != NULL); + phnum = aux_info[AT_PHNUM]->a_un.a_val; + assert(aux_info[AT_PHENT] != NULL); + assert(aux_info[AT_PHENT]->a_un.a_val == sizeof(Elf_Phdr)); + assert(aux_info[AT_ENTRY] != NULL); + entry = (caddr_t) aux_info[AT_ENTRY]->a_un.a_ptr; + if ((obj_main = digest_phdr(phdr, phnum, entry, argv0)) == NULL) + die(); + } + + if (aux_info[AT_EXECPATH] != 0) { + char *kexecpath; + char buf[MAXPATHLEN]; + + kexecpath = aux_info[AT_EXECPATH]->a_un.a_ptr; + dbg("AT_EXECPATH %p %s", kexecpath, kexecpath); + if (kexecpath[0] == '/') + obj_main->path = kexecpath; + else if (getcwd(buf, sizeof(buf)) == NULL || + strlcat(buf, "/", sizeof(buf)) >= sizeof(buf) || + strlcat(buf, kexecpath, sizeof(buf)) >= sizeof(buf)) + obj_main->path = xstrdup(argv0); + else + obj_main->path = xstrdup(buf); + } else { + dbg("No AT_EXECPATH"); + obj_main->path = xstrdup(argv0); + } + dbg("obj_main path %s", obj_main->path); + obj_main->mainprog = true; + + if (aux_info[AT_STACKPROT] != NULL && + aux_info[AT_STACKPROT]->a_un.a_val != 0) + stack_prot = aux_info[AT_STACKPROT]->a_un.a_val; + + /* + * Get the actual dynamic linker pathname from the executable if + * possible. (It should always be possible.) That ensures that + * gdb will find the right dynamic linker even if a non-standard + * one is being used. + */ + if (obj_main->interp != NULL && + strcmp(obj_main->interp, obj_rtld.path) != 0) { + free(obj_rtld.path); + obj_rtld.path = xstrdup(obj_main->interp); + __progname = obj_rtld.path; + } + + digest_dynamic(obj_main, 0); + dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d", + obj_main->path, obj_main->valid_hash_sysv, obj_main->valid_hash_gnu, + obj_main->dynsymcount); + + linkmap_add(obj_main); + linkmap_add(&obj_rtld); + + /* Link the main program into the list of objects. */ + *obj_tail = obj_main; + obj_tail = &obj_main->next; + obj_count++; + obj_loads++; + + /* Initialize a fake symbol for resolving undefined weak references. */ + sym_zero.st_info = ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE); + sym_zero.st_shndx = SHN_UNDEF; + sym_zero.st_value = -(uintptr_t)obj_main->relocbase; + + if (!libmap_disable) + libmap_disable = (bool)lm_init(libmap_override); + + dbg("loading LD_PRELOAD libraries"); + if (load_preload_objects() == -1) + die(); + preload_tail = obj_tail; + + dbg("loading needed objects"); + if (load_needed_objects(obj_main, 0) == -1) + die(); + + /* Make a list of all objects loaded at startup. */ + last_interposer = obj_main; + for (obj = obj_list; obj != NULL; obj = obj->next) { + if (obj->z_interpose && obj != obj_main) { + objlist_put_after(&list_main, last_interposer, obj); + last_interposer = obj; + } else { + objlist_push_tail(&list_main, obj); + } + obj->refcount++; + } + + dbg("checking for required versions"); + if (rtld_verify_versions(&list_main) == -1 && !ld_tracing) + die(); + + if (ld_tracing) { /* We're done */ + trace_loaded_objects(obj_main); + exit(0); + } + + if (getenv(LD_ "DUMP_REL_PRE") != NULL) { + dump_relocations(obj_main); + exit (0); + } + + /* + * Processing tls relocations requires having the tls offsets + * initialized. Prepare offsets before starting initial + * relocation processing. + */ + dbg("initializing initial thread local storage offsets"); + STAILQ_FOREACH(entry, &list_main, link) { + /* + * Allocate all the initial objects out of the static TLS + * block even if they didn't ask for it. + */ + allocate_tls_offset(entry->obj); + } + + if (relocate_objects(obj_main, + ld_bind_now != NULL && *ld_bind_now != '\0', + &obj_rtld, SYMLOOK_EARLY, NULL) == -1) + die(); + + dbg("doing copy relocations"); + if (do_copy_relocations(obj_main) == -1) + die(); + + if (getenv(LD_ "DUMP_REL_POST") != NULL) { + dump_relocations(obj_main); + exit (0); + } + + /* + * Setup TLS for main thread. This must be done after the + * relocations are processed, since tls initialization section + * might be the subject for relocations. + */ + dbg("initializing initial thread local storage"); + allocate_initial_tls(obj_list); + + dbg("initializing key program variables"); + set_program_var("__progname", argv[0] != NULL ? basename(argv[0]) : ""); + set_program_var("environ", env); + set_program_var("__elf_aux_vector", aux); + + /* Make a list of init functions to call. */ + objlist_init(&initlist); + initlist_add_objects(obj_list, preload_tail, &initlist); + + r_debug_state(NULL, &obj_main->linkmap); /* say hello to gdb! */ + + map_stacks_exec(NULL); + + dbg("resolving ifuncs"); + if (resolve_objects_ifunc(obj_main, + ld_bind_now != NULL && *ld_bind_now != '\0', SYMLOOK_EARLY, + NULL) == -1) + die(); + + if (!obj_main->crt_no_init) { + /* + * Make sure we don't call the main program's init and fini + * functions for binaries linked with old crt1 which calls + * _init itself. + */ + obj_main->init = obj_main->fini = (Elf_Addr)NULL; + obj_main->preinit_array = obj_main->init_array = + obj_main->fini_array = (Elf_Addr)NULL; + } + + wlock_acquire(rtld_bind_lock, &lockstate); + if (obj_main->crt_no_init) + preinit_main(); + objlist_call_init(&initlist, &lockstate); + _r_debug_postinit(&obj_main->linkmap); + objlist_clear(&initlist); + dbg("loading filtees"); + for (obj = obj_list->next; obj != NULL; obj = obj->next) { + if (ld_loadfltr || obj->z_loadfltr) + load_filtees(obj, 0, &lockstate); + } + lock_release(rtld_bind_lock, &lockstate); + + dbg("transferring control to program entry point = %p", obj_main->entry); + + /* Return the exit procedure and the program entry point. */ + *exit_proc = rtld_exit; + *objp = obj_main; + return (func_ptr_type) obj_main->entry; +} + +void * +rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def) +{ + void *ptr; + Elf_Addr target; + + ptr = (void *)make_function_pointer(def, obj); + target = ((Elf_Addr (*)(void))ptr)(); + return ((void *)target); +} + +Elf_Addr +_rtld_bind(Obj_Entry *obj, Elf_Size reloff) +{ + const Elf_Rel *rel; + const Elf_Sym *def; + const Obj_Entry *defobj; + Elf_Addr *where; + Elf_Addr target; + RtldLockState lockstate; + + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); + if (obj->pltrel) + rel = (const Elf_Rel *) ((caddr_t) obj->pltrel + reloff); + else + rel = (const Elf_Rel *) ((caddr_t) obj->pltrela + reloff); + + where = (Elf_Addr *) (obj->relocbase + rel->r_offset); + def = find_symdef(ELF_R_SYM(rel->r_info), obj, &defobj, true, NULL, + &lockstate); + if (def == NULL) + die(); + if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) + target = (Elf_Addr)rtld_resolve_ifunc(defobj, def); + else + target = (Elf_Addr)(defobj->relocbase + def->st_value); + + dbg("\"%s\" in \"%s\" ==> %p in \"%s\"", + defobj->strtab + def->st_name, basename(obj->path), + (void *)target, basename(defobj->path)); + + /* + * Write the new contents for the jmpslot. Note that depending on + * architecture, the value which we need to return back to the + * lazy binding trampoline may or may not be the target + * address. The value returned from reloc_jmpslot() is the value + * that the trampoline needs. + */ + target = reloc_jmpslot(where, target, defobj, obj, rel); + lock_release(rtld_bind_lock, &lockstate); + return target; +} + +/* + * Error reporting function. Use it like printf. If formats the message + * into a buffer, and sets things up so that the next call to dlerror() + * will return the message. + */ +void +_rtld_error(const char *fmt, ...) +{ + static char buf[512]; + va_list ap; + + va_start(ap, fmt); + rtld_vsnprintf(buf, sizeof buf, fmt, ap); + error_message = buf; + va_end(ap); +} + +/* + * Return a dynamically-allocated copy of the current error message, if any. + */ +static char * +errmsg_save(void) +{ + return error_message == NULL ? NULL : xstrdup(error_message); +} + +/* + * Restore the current error message from a copy which was previously saved + * by errmsg_save(). The copy is freed. + */ +static void +errmsg_restore(char *saved_msg) +{ + if (saved_msg == NULL) + error_message = NULL; + else { + _rtld_error("%s", saved_msg); + free(saved_msg); + } +} + +static const char * +basename(const char *name) +{ + const char *p = strrchr(name, '/'); + return p != NULL ? p + 1 : name; +} + +static struct utsname uts; + +static char * +origin_subst_one(char *real, const char *kw, const char *subst, + bool may_free) +{ + char *p, *p1, *res, *resp; + int subst_len, kw_len, subst_count, old_len, new_len; + + kw_len = strlen(kw); + + /* + * First, count the number of the keyword occurences, to + * preallocate the final string. + */ + for (p = real, subst_count = 0;; p = p1 + kw_len, subst_count++) { + p1 = strstr(p, kw); + if (p1 == NULL) + break; + } + + /* + * If the keyword is not found, just return. + */ + if (subst_count == 0) + return (may_free ? real : xstrdup(real)); + + /* + * There is indeed something to substitute. Calculate the + * length of the resulting string, and allocate it. + */ + subst_len = strlen(subst); + old_len = strlen(real); + new_len = old_len + (subst_len - kw_len) * subst_count; + res = xmalloc(new_len + 1); + + /* + * Now, execute the substitution loop. + */ + for (p = real, resp = res, *resp = '\0';;) { + p1 = strstr(p, kw); + if (p1 != NULL) { + /* Copy the prefix before keyword. */ + memcpy(resp, p, p1 - p); + resp += p1 - p; + /* Keyword replacement. */ + memcpy(resp, subst, subst_len); + resp += subst_len; + *resp = '\0'; + p = p1 + kw_len; + } else + break; + } + + /* Copy to the end of string and finish. */ + strcat(resp, p); + if (may_free) + free(real); + return (res); +} + +static char * +origin_subst(char *real, const char *origin_path) +{ + char *res1, *res2, *res3, *res4; + + if (uts.sysname[0] == '\0') { + if (uname(&uts) != 0) { + _rtld_error("utsname failed: %d", errno); + return (NULL); + } + } + res1 = origin_subst_one(real, "$ORIGIN", origin_path, false); + res2 = origin_subst_one(res1, "$OSNAME", uts.sysname, true); + res3 = origin_subst_one(res2, "$OSREL", uts.release, true); + res4 = origin_subst_one(res3, "$PLATFORM", uts.machine, true); + return (res4); +} + +static void +die(void) +{ + const char *msg = dlerror(); + + if (msg == NULL) + msg = "Fatal error"; + rtld_fdputstr(STDERR_FILENO, msg); + rtld_fdputchar(STDERR_FILENO, '\n'); + _exit(1); +} + +/* + * Process a shared object's DYNAMIC section, and save the important + * information in its Obj_Entry structure. + */ +static void +digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath, + const Elf_Dyn **dyn_soname, const Elf_Dyn **dyn_runpath) +{ + const Elf_Dyn *dynp; + Needed_Entry **needed_tail = &obj->needed; + Needed_Entry **needed_filtees_tail = &obj->needed_filtees; + Needed_Entry **needed_aux_filtees_tail = &obj->needed_aux_filtees; + const Elf_Hashelt *hashtab; + const Elf32_Word *hashval; + Elf32_Word bkt, nmaskwords; + int bloom_size32; + bool nmw_power2; + int plttype = DT_REL; + + *dyn_rpath = NULL; + *dyn_soname = NULL; + *dyn_runpath = NULL; + + obj->bind_now = false; + for (dynp = obj->dynamic; dynp->d_tag != DT_NULL; dynp++) { + switch (dynp->d_tag) { + + case DT_REL: + obj->rel = (const Elf_Rel *) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_RELSZ: + obj->relsize = dynp->d_un.d_val; + break; + + case DT_RELENT: + assert(dynp->d_un.d_val == sizeof(Elf_Rel)); + break; + + case DT_JMPREL: + obj->pltrel = (const Elf_Rel *) + (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_PLTRELSZ: + obj->pltrelsize = dynp->d_un.d_val; + break; + + case DT_RELA: + obj->rela = (const Elf_Rela *) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_RELASZ: + obj->relasize = dynp->d_un.d_val; + break; + + case DT_RELAENT: + assert(dynp->d_un.d_val == sizeof(Elf_Rela)); + break; + + case DT_PLTREL: + plttype = dynp->d_un.d_val; + assert(dynp->d_un.d_val == DT_REL || plttype == DT_RELA); + break; + + case DT_SYMTAB: + obj->symtab = (const Elf_Sym *) + (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_SYMENT: + assert(dynp->d_un.d_val == sizeof(Elf_Sym)); + break; + + case DT_STRTAB: + obj->strtab = (const char *) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_STRSZ: + obj->strsize = dynp->d_un.d_val; + break; + + case DT_VERNEED: + obj->verneed = (const Elf_Verneed *) (obj->relocbase + + dynp->d_un.d_val); + break; + + case DT_VERNEEDNUM: + obj->verneednum = dynp->d_un.d_val; + break; + + case DT_VERDEF: + obj->verdef = (const Elf_Verdef *) (obj->relocbase + + dynp->d_un.d_val); + break; + + case DT_VERDEFNUM: + obj->verdefnum = dynp->d_un.d_val; + break; + + case DT_VERSYM: + obj->versyms = (const Elf_Versym *)(obj->relocbase + + dynp->d_un.d_val); + break; + + case DT_HASH: + { + hashtab = (const Elf_Hashelt *)(obj->relocbase + + dynp->d_un.d_ptr); + obj->nbuckets = hashtab[0]; + obj->nchains = hashtab[1]; + obj->buckets = hashtab + 2; + obj->chains = obj->buckets + obj->nbuckets; + obj->valid_hash_sysv = obj->nbuckets > 0 && obj->nchains > 0 && + obj->buckets != NULL; + } + break; + + case DT_GNU_HASH: + { + hashtab = (const Elf_Hashelt *)(obj->relocbase + + dynp->d_un.d_ptr); + obj->nbuckets_gnu = hashtab[0]; + obj->symndx_gnu = hashtab[1]; + nmaskwords = hashtab[2]; + bloom_size32 = (__ELF_WORD_SIZE / 32) * nmaskwords; + /* Number of bitmask words is required to be power of 2 */ + nmw_power2 = ((nmaskwords & (nmaskwords - 1)) == 0); + obj->maskwords_bm_gnu = nmaskwords - 1; + obj->shift2_gnu = hashtab[3]; + obj->bloom_gnu = (Elf_Addr *) (hashtab + 4); + obj->buckets_gnu = hashtab + 4 + bloom_size32; + obj->chain_zero_gnu = obj->buckets_gnu + obj->nbuckets_gnu - + obj->symndx_gnu; + obj->valid_hash_gnu = nmw_power2 && obj->nbuckets_gnu > 0 && + obj->buckets_gnu != NULL; + } + break; + + case DT_NEEDED: + if (!obj->rtld) { + Needed_Entry *nep = NEW(Needed_Entry); + nep->name = dynp->d_un.d_val; + nep->obj = NULL; + nep->next = NULL; + + *needed_tail = nep; + needed_tail = &nep->next; + } + break; + + case DT_FILTER: + if (!obj->rtld) { + Needed_Entry *nep = NEW(Needed_Entry); + nep->name = dynp->d_un.d_val; + nep->obj = NULL; + nep->next = NULL; + + *needed_filtees_tail = nep; + needed_filtees_tail = &nep->next; + } + break; + + case DT_AUXILIARY: + if (!obj->rtld) { + Needed_Entry *nep = NEW(Needed_Entry); + nep->name = dynp->d_un.d_val; + nep->obj = NULL; + nep->next = NULL; + + *needed_aux_filtees_tail = nep; + needed_aux_filtees_tail = &nep->next; + } + break; + + case DT_PLTGOT: + obj->pltgot = (Elf_Addr *) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_TEXTREL: + obj->textrel = true; + break; + + case DT_SYMBOLIC: + obj->symbolic = true; + break; + + case DT_RPATH: + /* + * We have to wait until later to process this, because we + * might not have gotten the address of the string table yet. + */ + *dyn_rpath = dynp; + break; + + case DT_SONAME: + *dyn_soname = dynp; + break; + + case DT_RUNPATH: + *dyn_runpath = dynp; + break; + + case DT_INIT: + obj->init = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_PREINIT_ARRAY: + obj->preinit_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_PREINIT_ARRAYSZ: + obj->preinit_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + + case DT_INIT_ARRAY: + obj->init_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_INIT_ARRAYSZ: + obj->init_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + + case DT_FINI: + obj->fini = (Elf_Addr) (obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_FINI_ARRAY: + obj->fini_array = (Elf_Addr)(obj->relocbase + dynp->d_un.d_ptr); + break; + + case DT_FINI_ARRAYSZ: + obj->fini_array_num = dynp->d_un.d_val / sizeof(Elf_Addr); + break; + + /* + * Don't process DT_DEBUG on MIPS as the dynamic section + * is mapped read-only. DT_MIPS_RLD_MAP is used instead. + */ + +#ifndef __mips__ + case DT_DEBUG: + /* XXX - not implemented yet */ + if (!early) + dbg("Filling in DT_DEBUG entry"); + ((Elf_Dyn*)dynp)->d_un.d_ptr = (Elf_Addr) &r_debug; + break; +#endif + + case DT_FLAGS: + if ((dynp->d_un.d_val & DF_ORIGIN) && trust) + obj->z_origin = true; + if (dynp->d_un.d_val & DF_SYMBOLIC) + obj->symbolic = true; + if (dynp->d_un.d_val & DF_TEXTREL) + obj->textrel = true; + if (dynp->d_un.d_val & DF_BIND_NOW) + obj->bind_now = true; + /*if (dynp->d_un.d_val & DF_STATIC_TLS) + ;*/ + break; +#ifdef __mips__ + case DT_MIPS_LOCAL_GOTNO: + obj->local_gotno = dynp->d_un.d_val; + break; + + case DT_MIPS_SYMTABNO: + obj->symtabno = dynp->d_un.d_val; + break; + + case DT_MIPS_GOTSYM: + obj->gotsym = dynp->d_un.d_val; + break; + + case DT_MIPS_RLD_MAP: + *((Elf_Addr *)(dynp->d_un.d_ptr)) = (Elf_Addr) &r_debug; + break; +#endif + + case DT_FLAGS_1: + if (dynp->d_un.d_val & DF_1_NOOPEN) + obj->z_noopen = true; + if ((dynp->d_un.d_val & DF_1_ORIGIN) && trust) + obj->z_origin = true; + /*if (dynp->d_un.d_val & DF_1_GLOBAL) + XXX ;*/ + if (dynp->d_un.d_val & DF_1_BIND_NOW) + obj->bind_now = true; + if (dynp->d_un.d_val & DF_1_NODELETE) + obj->z_nodelete = true; + if (dynp->d_un.d_val & DF_1_LOADFLTR) + obj->z_loadfltr = true; + if (dynp->d_un.d_val & DF_1_INTERPOSE) + obj->z_interpose = true; + if (dynp->d_un.d_val & DF_1_NODEFLIB) + obj->z_nodeflib = true; + break; + + default: + if (!early) { + dbg("Ignoring d_tag %ld = %#lx", (long)dynp->d_tag, + (long)dynp->d_tag); + } + break; + } + } + + obj->traced = false; + + if (plttype == DT_RELA) { + obj->pltrela = (const Elf_Rela *) obj->pltrel; + obj->pltrel = NULL; + obj->pltrelasize = obj->pltrelsize; + obj->pltrelsize = 0; + } + + /* Determine size of dynsym table (equal to nchains of sysv hash) */ + if (obj->valid_hash_sysv) + obj->dynsymcount = obj->nchains; + else if (obj->valid_hash_gnu) { + obj->dynsymcount = 0; + for (bkt = 0; bkt < obj->nbuckets_gnu; bkt++) { + if (obj->buckets_gnu[bkt] == 0) + continue; + hashval = &obj->chain_zero_gnu[obj->buckets_gnu[bkt]]; + do + obj->dynsymcount++; + while ((*hashval++ & 1u) == 0); + } + obj->dynsymcount += obj->symndx_gnu; + } +} + +static void +digest_dynamic2(Obj_Entry *obj, const Elf_Dyn *dyn_rpath, + const Elf_Dyn *dyn_soname, const Elf_Dyn *dyn_runpath) +{ + + if (obj->z_origin && obj->origin_path == NULL) { + obj->origin_path = xmalloc(PATH_MAX); + if (rtld_dirname_abs(obj->path, obj->origin_path) == -1) + die(); + } + + if (dyn_runpath != NULL) { + obj->runpath = (char *)obj->strtab + dyn_runpath->d_un.d_val; + if (obj->z_origin) + obj->runpath = origin_subst(obj->runpath, obj->origin_path); + } + else if (dyn_rpath != NULL) { + obj->rpath = (char *)obj->strtab + dyn_rpath->d_un.d_val; + if (obj->z_origin) + obj->rpath = origin_subst(obj->rpath, obj->origin_path); + } + + if (dyn_soname != NULL) + object_add_name(obj, obj->strtab + dyn_soname->d_un.d_val); +} + +static void +digest_dynamic(Obj_Entry *obj, int early) +{ + const Elf_Dyn *dyn_rpath; + const Elf_Dyn *dyn_soname; + const Elf_Dyn *dyn_runpath; + + digest_dynamic1(obj, early, &dyn_rpath, &dyn_soname, &dyn_runpath); + digest_dynamic2(obj, dyn_rpath, dyn_soname, dyn_runpath); +} + +/* + * Process a shared object's program header. This is used only for the + * main program, when the kernel has already loaded the main program + * into memory before calling the dynamic linker. It creates and + * returns an Obj_Entry structure. + */ +static Obj_Entry * +digest_phdr(const Elf_Phdr *phdr, int phnum, caddr_t entry, const char *path) +{ + Obj_Entry *obj; + const Elf_Phdr *phlimit = phdr + phnum; + const Elf_Phdr *ph; + Elf_Addr note_start, note_end; + int nsegs = 0; + + obj = obj_new(); + for (ph = phdr; ph < phlimit; ph++) { + if (ph->p_type != PT_PHDR) + continue; + + obj->phdr = phdr; + obj->phsize = ph->p_memsz; + obj->relocbase = (caddr_t)phdr - ph->p_vaddr; + break; + } + + obj->stack_flags = PF_X | PF_R | PF_W; + + for (ph = phdr; ph < phlimit; ph++) { + switch (ph->p_type) { + + case PT_INTERP: + obj->interp = (const char *)(ph->p_vaddr + obj->relocbase); + break; + + case PT_LOAD: + if (nsegs == 0) { /* First load segment */ + obj->vaddrbase = trunc_page(ph->p_vaddr); + obj->mapbase = obj->vaddrbase + obj->relocbase; + obj->textsize = round_page(ph->p_vaddr + ph->p_memsz) - + obj->vaddrbase; + } else { /* Last load segment */ + obj->mapsize = round_page(ph->p_vaddr + ph->p_memsz) - + obj->vaddrbase; + } + nsegs++; + break; + + case PT_DYNAMIC: + obj->dynamic = (const Elf_Dyn *)(ph->p_vaddr + obj->relocbase); + break; + + case PT_TLS: + obj->tlsindex = 1; + obj->tlssize = ph->p_memsz; + obj->tlsalign = ph->p_align; + obj->tlsinitsize = ph->p_filesz; + obj->tlsinit = (void*)(ph->p_vaddr + obj->relocbase); + break; + + case PT_GNU_STACK: + obj->stack_flags = ph->p_flags; + break; + + case PT_GNU_RELRO: + obj->relro_page = obj->relocbase + trunc_page(ph->p_vaddr); + obj->relro_size = round_page(ph->p_memsz); + break; + + case PT_NOTE: + note_start = (Elf_Addr)obj->relocbase + ph->p_vaddr; + note_end = note_start + ph->p_filesz; + digest_notes(obj, note_start, note_end); + break; + } + } + if (nsegs < 1) { + _rtld_error("%s: too few PT_LOAD segments", path); + return NULL; + } + + obj->entry = entry; + return obj; +} + +void +digest_notes(Obj_Entry *obj, Elf_Addr note_start, Elf_Addr note_end) +{ + const Elf_Note *note; + const char *note_name; + uintptr_t p; + + for (note = (const Elf_Note *)note_start; (Elf_Addr)note < note_end; + note = (const Elf_Note *)((const char *)(note + 1) + + roundup2(note->n_namesz, sizeof(Elf32_Addr)) + + roundup2(note->n_descsz, sizeof(Elf32_Addr)))) { + if (note->n_namesz != sizeof(NOTE_FREEBSD_VENDOR) || + note->n_descsz != sizeof(int32_t)) + continue; + if (note->n_type != ABI_NOTETYPE && + note->n_type != CRT_NOINIT_NOTETYPE) + continue; + note_name = (const char *)(note + 1); + if (strncmp(NOTE_FREEBSD_VENDOR, note_name, + sizeof(NOTE_FREEBSD_VENDOR)) != 0) + continue; + switch (note->n_type) { + case ABI_NOTETYPE: + /* FreeBSD osrel note */ + p = (uintptr_t)(note + 1); + p += roundup2(note->n_namesz, sizeof(Elf32_Addr)); + obj->osrel = *(const int32_t *)(p); + dbg("note osrel %d", obj->osrel); + break; + case CRT_NOINIT_NOTETYPE: + /* FreeBSD 'crt does not call init' note */ + obj->crt_no_init = true; + dbg("note crt_no_init"); + break; + } + } +} + +static Obj_Entry * +dlcheck(void *handle) +{ + Obj_Entry *obj; + + for (obj = obj_list; obj != NULL; obj = obj->next) + if (obj == (Obj_Entry *) handle) + break; + + if (obj == NULL || obj->refcount == 0 || obj->dl_refcount == 0) { + _rtld_error("Invalid shared object handle %p", handle); + return NULL; + } + return obj; +} + +/* + * If the given object is already in the donelist, return true. Otherwise + * add the object to the list and return false. + */ +static bool +donelist_check(DoneList *dlp, const Obj_Entry *obj) +{ + unsigned int i; + + for (i = 0; i < dlp->num_used; i++) + if (dlp->objs[i] == obj) + return true; + /* + * Our donelist allocation should always be sufficient. But if + * our threads locking isn't working properly, more shared objects + * could have been loaded since we allocated the list. That should + * never happen, but we'll handle it properly just in case it does. + */ + if (dlp->num_used < dlp->num_alloc) + dlp->objs[dlp->num_used++] = obj; + return false; +} + +/* + * Hash function for symbol table lookup. Don't even think about changing + * this. It is specified by the System V ABI. + */ +unsigned long +elf_hash(const char *name) +{ + const unsigned char *p = (const unsigned char *) name; + unsigned long h = 0; + unsigned long g; + + while (*p != '\0') { + h = (h << 4) + *p++; + if ((g = h & 0xf0000000) != 0) + h ^= g >> 24; + h &= ~g; + } + return h; +} + +/* + * The GNU hash function is the Daniel J. Bernstein hash clipped to 32 bits + * unsigned in case it's implemented with a wider type. + */ +static uint32_t +gnu_hash(const char *s) +{ + uint32_t h; + unsigned char c; + + h = 5381; + for (c = *s; c != '\0'; c = *++s) + h = h * 33 + c; + return (h & 0xffffffff); +} + + +/* + * Find the library with the given name, and return its full pathname. + * The returned string is dynamically allocated. Generates an error + * message and returns NULL if the library cannot be found. + * + * If the second argument is non-NULL, then it refers to an already- + * loaded shared object, whose library search path will be searched. + * + * If a library is successfully located via LD_LIBRARY_PATH_FDS, its + * descriptor (which is close-on-exec) will be passed out via the third + * argument. + * + * The search order is: + * DT_RPATH in the referencing file _unless_ DT_RUNPATH is present (1) + * DT_RPATH of the main object if DSO without defined DT_RUNPATH (1) + * LD_LIBRARY_PATH + * DT_RUNPATH in the referencing file + * ldconfig hints (if -z nodefaultlib, filter out default library directories + * from list) + * /lib:/usr/lib _unless_ the referencing file is linked with -z nodefaultlib + * + * (1) Handled in digest_dynamic2 - rpath left NULL if runpath defined. + */ +static char * +find_library(const char *xname, const Obj_Entry *refobj, int *fdp) +{ + char *pathname; + char *name; + bool nodeflib, objgiven; + + objgiven = refobj != NULL; + if (strchr(xname, '/') != NULL) { /* Hard coded pathname */ + if (xname[0] != '/' && !trust) { + _rtld_error("Absolute pathname required for shared object \"%s\"", + xname); + return NULL; + } + if (objgiven && refobj->z_origin) { + return (origin_subst(__DECONST(char *, xname), + refobj->origin_path)); + } else { + return (xstrdup(xname)); + } + } + + if (libmap_disable || !objgiven || + (name = lm_find(refobj->path, xname)) == NULL) + name = (char *)xname; + + dbg(" Searching for \"%s\"", name); + + /* + * If refobj->rpath != NULL, then refobj->runpath is NULL. Fall + * back to pre-conforming behaviour if user requested so with + * LD_LIBRARY_PATH_RPATH environment variable and ignore -z + * nodeflib. + */ + if (objgiven && refobj->rpath != NULL && ld_library_path_rpath) { + if ((pathname = search_library_path(name, ld_library_path)) != NULL || + (refobj != NULL && + (pathname = search_library_path(name, refobj->rpath)) != NULL) || + (pathname = search_library_pathfds(name, ld_library_dirs, fdp)) != NULL || + (pathname = search_library_path(name, gethints(false))) != NULL || + (pathname = search_library_path(name, STANDARD_LIBRARY_PATH)) != NULL) + return (pathname); + } else { + nodeflib = objgiven ? refobj->z_nodeflib : false; + if ((objgiven && + (pathname = search_library_path(name, refobj->rpath)) != NULL) || + (objgiven && refobj->runpath == NULL && refobj != obj_main && + (pathname = search_library_path(name, obj_main->rpath)) != NULL) || + (pathname = search_library_path(name, ld_library_path)) != NULL || + (objgiven && + (pathname = search_library_path(name, refobj->runpath)) != NULL) || + (pathname = search_library_pathfds(name, ld_library_dirs, fdp)) != NULL || + (pathname = search_library_path(name, gethints(nodeflib))) != NULL || + (objgiven && !nodeflib && + (pathname = search_library_path(name, STANDARD_LIBRARY_PATH)) != NULL)) + return (pathname); + } + + if (objgiven && refobj->path != NULL) { + _rtld_error("Shared object \"%s\" not found, required by \"%s\"", + name, basename(refobj->path)); + } else { + _rtld_error("Shared object \"%s\" not found", name); + } + return NULL; +} + +/* + * Given a symbol number in a referencing object, find the corresponding + * definition of the symbol. Returns a pointer to the symbol, or NULL if + * no definition was found. Returns a pointer to the Obj_Entry of the + * defining object via the reference parameter DEFOBJ_OUT. + */ +const Elf_Sym * +find_symdef(unsigned long symnum, const Obj_Entry *refobj, + const Obj_Entry **defobj_out, int flags, SymCache *cache, + RtldLockState *lockstate) +{ + const Elf_Sym *ref; + const Elf_Sym *def; + const Obj_Entry *defobj; + SymLook req; + const char *name; + int res; + + /* + * If we have already found this symbol, get the information from + * the cache. + */ + if (symnum >= refobj->dynsymcount) + return NULL; /* Bad object */ + if (cache != NULL && cache[symnum].sym != NULL) { + *defobj_out = cache[symnum].obj; + return cache[symnum].sym; + } + + ref = refobj->symtab + symnum; + name = refobj->strtab + ref->st_name; + def = NULL; + defobj = NULL; + + /* + * We don't have to do a full scale lookup if the symbol is local. + * We know it will bind to the instance in this load module; to + * which we already have a pointer (ie ref). By not doing a lookup, + * we not only improve performance, but it also avoids unresolvable + * symbols when local symbols are not in the hash table. This has + * been seen with the ia64 toolchain. + */ + if (ELF_ST_BIND(ref->st_info) != STB_LOCAL) { + if (ELF_ST_TYPE(ref->st_info) == STT_SECTION) { + _rtld_error("%s: Bogus symbol table entry %lu", refobj->path, + symnum); + } + symlook_init(&req, name); + req.flags = flags; + req.ventry = fetch_ventry(refobj, symnum); + req.lockstate = lockstate; + res = symlook_default(&req, refobj); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } else { + def = ref; + defobj = refobj; + } + + /* + * If we found no definition and the reference is weak, treat the + * symbol as having the value zero. + */ + if (def == NULL && ELF_ST_BIND(ref->st_info) == STB_WEAK) { + def = &sym_zero; + defobj = obj_main; + } + + if (def != NULL) { + *defobj_out = defobj; + /* Record the information in the cache to avoid subsequent lookups. */ + if (cache != NULL) { + cache[symnum].sym = def; + cache[symnum].obj = defobj; + } + } else { + if (refobj != &obj_rtld) + _rtld_error("%s: Undefined symbol \"%s\"", refobj->path, name); + } + return def; +} + +/* + * Return the search path from the ldconfig hints file, reading it if + * necessary. If nostdlib is true, then the default search paths are + * not added to result. + * + * Returns NULL if there are problems with the hints file, + * or if the search path there is empty. + */ +static const char * +gethints(bool nostdlib) +{ + static char *hints, *filtered_path; + struct elfhints_hdr hdr; + struct fill_search_info_args sargs, hargs; + struct dl_serinfo smeta, hmeta, *SLPinfo, *hintinfo; + struct dl_serpath *SLPpath, *hintpath; + char *p; + unsigned int SLPndx, hintndx, fndx, fcount; + int fd; + size_t flen; + bool skip; + + /* First call, read the hints file */ + if (hints == NULL) { + /* Keep from trying again in case the hints file is bad. */ + hints = ""; + + if ((fd = open(ld_elf_hints_path, O_RDONLY | O_CLOEXEC)) == -1) + return (NULL); + if (read(fd, &hdr, sizeof hdr) != sizeof hdr || + hdr.magic != ELFHINTS_MAGIC || + hdr.version != 1) { + close(fd); + return (NULL); + } + p = xmalloc(hdr.dirlistlen + 1); + if (lseek(fd, hdr.strtab + hdr.dirlist, SEEK_SET) == -1 || + read(fd, p, hdr.dirlistlen + 1) != + (ssize_t)hdr.dirlistlen + 1) { + free(p); + close(fd); + return (NULL); + } + hints = p; + close(fd); + } + + /* + * If caller agreed to receive list which includes the default + * paths, we are done. Otherwise, if we still did not + * calculated filtered result, do it now. + */ + if (!nostdlib) + return (hints[0] != '\0' ? hints : NULL); + if (filtered_path != NULL) + goto filt_ret; + + /* + * Obtain the list of all configured search paths, and the + * list of the default paths. + * + * First estimate the size of the results. + */ + smeta.dls_size = __offsetof(struct dl_serinfo, dls_serpath); + smeta.dls_cnt = 0; + hmeta.dls_size = __offsetof(struct dl_serinfo, dls_serpath); + hmeta.dls_cnt = 0; + + sargs.request = RTLD_DI_SERINFOSIZE; + sargs.serinfo = &smeta; + hargs.request = RTLD_DI_SERINFOSIZE; + hargs.serinfo = &hmeta; + + path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &sargs); + path_enumerate(p, fill_search_info, &hargs); + + SLPinfo = xmalloc(smeta.dls_size); + hintinfo = xmalloc(hmeta.dls_size); + + /* + * Next fetch both sets of paths. + */ + sargs.request = RTLD_DI_SERINFO; + sargs.serinfo = SLPinfo; + sargs.serpath = &SLPinfo->dls_serpath[0]; + sargs.strspace = (char *)&SLPinfo->dls_serpath[smeta.dls_cnt]; + + hargs.request = RTLD_DI_SERINFO; + hargs.serinfo = hintinfo; + hargs.serpath = &hintinfo->dls_serpath[0]; + hargs.strspace = (char *)&hintinfo->dls_serpath[hmeta.dls_cnt]; + + path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &sargs); + path_enumerate(p, fill_search_info, &hargs); + + /* + * Now calculate the difference between two sets, by excluding + * standard paths from the full set. + */ + fndx = 0; + fcount = 0; + filtered_path = xmalloc(hdr.dirlistlen + 1); + hintpath = &hintinfo->dls_serpath[0]; + for (hintndx = 0; hintndx < hmeta.dls_cnt; hintndx++, hintpath++) { + skip = false; + SLPpath = &SLPinfo->dls_serpath[0]; + /* + * Check each standard path against current. + */ + for (SLPndx = 0; SLPndx < smeta.dls_cnt; SLPndx++, SLPpath++) { + /* matched, skip the path */ + if (!strcmp(hintpath->dls_name, SLPpath->dls_name)) { + skip = true; + break; + } + } + if (skip) + continue; + /* + * Not matched against any standard path, add the path + * to result. Separate consequtive paths with ':'. + */ + if (fcount > 0) { + filtered_path[fndx] = ':'; + fndx++; + } + fcount++; + flen = strlen(hintpath->dls_name); + strncpy((filtered_path + fndx), hintpath->dls_name, flen); + fndx += flen; + } + filtered_path[fndx] = '\0'; + + free(SLPinfo); + free(hintinfo); + +filt_ret: + return (filtered_path[0] != '\0' ? filtered_path : NULL); +} + +static void +init_dag(Obj_Entry *root) +{ + const Needed_Entry *needed; + const Objlist_Entry *elm; + DoneList donelist; + + if (root->dag_inited) + return; + donelist_init(&donelist); + + /* Root object belongs to own DAG. */ + objlist_push_tail(&root->dldags, root); + objlist_push_tail(&root->dagmembers, root); + donelist_check(&donelist, root); + + /* + * Add dependencies of root object to DAG in breadth order + * by exploiting the fact that each new object get added + * to the tail of the dagmembers list. + */ + STAILQ_FOREACH(elm, &root->dagmembers, link) { + for (needed = elm->obj->needed; needed != NULL; needed = needed->next) { + if (needed->obj == NULL || donelist_check(&donelist, needed->obj)) + continue; + objlist_push_tail(&needed->obj->dldags, root); + objlist_push_tail(&root->dagmembers, needed->obj); + } + } + root->dag_inited = true; +} + +static void +process_nodelete(Obj_Entry *root) +{ + const Objlist_Entry *elm; + + /* + * Walk over object DAG and process every dependent object that + * is marked as DF_1_NODELETE. They need to grow their own DAG, + * which then should have its reference upped separately. + */ + STAILQ_FOREACH(elm, &root->dagmembers, link) { + if (elm->obj != NULL && elm->obj->z_nodelete && + !elm->obj->ref_nodel) { + dbg("obj %s nodelete", elm->obj->path); + init_dag(elm->obj); + ref_dag(elm->obj); + elm->obj->ref_nodel = true; + } + } +} +/* + * Initialize the dynamic linker. The argument is the address at which + * the dynamic linker has been mapped into memory. The primary task of + * this function is to relocate the dynamic linker. + */ +static void +init_rtld(caddr_t mapbase, Elf_Auxinfo **aux_info) +{ + Obj_Entry objtmp; /* Temporary rtld object */ + const Elf_Dyn *dyn_rpath; + const Elf_Dyn *dyn_soname; + const Elf_Dyn *dyn_runpath; + +#ifdef RTLD_INIT_PAGESIZES_EARLY + /* The page size is required by the dynamic memory allocator. */ + init_pagesizes(aux_info); +#endif + + /* + * Conjure up an Obj_Entry structure for the dynamic linker. + * + * The "path" member can't be initialized yet because string constants + * cannot yet be accessed. Below we will set it correctly. + */ + memset(&objtmp, 0, sizeof(objtmp)); + objtmp.path = NULL; + objtmp.rtld = true; + objtmp.mapbase = mapbase; +#ifdef PIC + objtmp.relocbase = mapbase; +#endif + if (RTLD_IS_DYNAMIC()) { + objtmp.dynamic = rtld_dynamic(&objtmp); + digest_dynamic1(&objtmp, 1, &dyn_rpath, &dyn_soname, &dyn_runpath); + assert(objtmp.needed == NULL); +#if !defined(__mips__) + /* MIPS has a bogus DT_TEXTREL. */ + assert(!objtmp.textrel); +#endif + + /* + * Temporarily put the dynamic linker entry into the object list, so + * that symbols can be found. + */ + + relocate_objects(&objtmp, true, &objtmp, 0, NULL); + } + + /* Initialize the object list. */ + obj_tail = &obj_list; + + /* Now that non-local variables can be accesses, copy out obj_rtld. */ + memcpy(&obj_rtld, &objtmp, sizeof(obj_rtld)); + +#ifndef RTLD_INIT_PAGESIZES_EARLY + /* The page size is required by the dynamic memory allocator. */ + init_pagesizes(aux_info); +#endif + + if (aux_info[AT_OSRELDATE] != NULL) + osreldate = aux_info[AT_OSRELDATE]->a_un.a_val; + + digest_dynamic2(&obj_rtld, dyn_rpath, dyn_soname, dyn_runpath); + + /* Replace the path with a dynamically allocated copy. */ + obj_rtld.path = xstrdup(PATH_RTLD); + + r_debug.r_brk = r_debug_state; + r_debug.r_state = RT_CONSISTENT; +} + +/* + * Retrieve the array of supported page sizes. The kernel provides the page + * sizes in increasing order. + */ +static void +init_pagesizes(Elf_Auxinfo **aux_info) +{ + static size_t psa[MAXPAGESIZES]; + int mib[2]; + size_t len, size; + + if (aux_info[AT_PAGESIZES] != NULL && aux_info[AT_PAGESIZESLEN] != + NULL) { + size = aux_info[AT_PAGESIZESLEN]->a_un.a_val; + pagesizes = aux_info[AT_PAGESIZES]->a_un.a_ptr; + } else { + len = 2; + if (sysctlnametomib("hw.pagesizes", mib, &len) == 0) + size = sizeof(psa); + else { + /* As a fallback, retrieve the base page size. */ + size = sizeof(psa[0]); + if (aux_info[AT_PAGESZ] != NULL) { + psa[0] = aux_info[AT_PAGESZ]->a_un.a_val; + goto psa_filled; + } else { + mib[0] = CTL_HW; + mib[1] = HW_PAGESIZE; + len = 2; + } + } + if (sysctl(mib, len, psa, &size, NULL, 0) == -1) { + _rtld_error("sysctl for hw.pagesize(s) failed"); + die(); + } +psa_filled: + pagesizes = psa; + } + npagesizes = size / sizeof(pagesizes[0]); + /* Discard any invalid entries at the end of the array. */ + while (npagesizes > 0 && pagesizes[npagesizes - 1] == 0) + npagesizes--; +} + +/* + * Add the init functions from a needed object list (and its recursive + * needed objects) to "list". This is not used directly; it is a helper + * function for initlist_add_objects(). The write lock must be held + * when this function is called. + */ +static void +initlist_add_neededs(Needed_Entry *needed, Objlist *list) +{ + /* Recursively process the successor needed objects. */ + if (needed->next != NULL) + initlist_add_neededs(needed->next, list); + + /* Process the current needed object. */ + if (needed->obj != NULL) + initlist_add_objects(needed->obj, &needed->obj->next, list); +} + +/* + * Scan all of the DAGs rooted in the range of objects from "obj" to + * "tail" and add their init functions to "list". This recurses over + * the DAGs and ensure the proper init ordering such that each object's + * needed libraries are initialized before the object itself. At the + * same time, this function adds the objects to the global finalization + * list "list_fini" in the opposite order. The write lock must be + * held when this function is called. + */ +static void +initlist_add_objects(Obj_Entry *obj, Obj_Entry **tail, Objlist *list) +{ + + if (obj->init_scanned || obj->init_done) + return; + obj->init_scanned = true; + + /* Recursively process the successor objects. */ + if (&obj->next != tail) + initlist_add_objects(obj->next, tail, list); + + /* Recursively process the needed objects. */ + if (obj->needed != NULL) + initlist_add_neededs(obj->needed, list); + if (obj->needed_filtees != NULL) + initlist_add_neededs(obj->needed_filtees, list); + if (obj->needed_aux_filtees != NULL) + initlist_add_neededs(obj->needed_aux_filtees, list); + + /* Add the object to the init list. */ + if (obj->preinit_array != (Elf_Addr)NULL || obj->init != (Elf_Addr)NULL || + obj->init_array != (Elf_Addr)NULL) + objlist_push_tail(list, obj); + + /* Add the object to the global fini list in the reverse order. */ + if ((obj->fini != (Elf_Addr)NULL || obj->fini_array != (Elf_Addr)NULL) + && !obj->on_fini_list) { + objlist_push_head(&list_fini, obj); + obj->on_fini_list = true; + } +} + +#ifndef FPTR_TARGET +#define FPTR_TARGET(f) ((Elf_Addr) (f)) +#endif + +static void +free_needed_filtees(Needed_Entry *n) +{ + Needed_Entry *needed, *needed1; + + for (needed = n; needed != NULL; needed = needed->next) { + if (needed->obj != NULL) { + dlclose(needed->obj); + needed->obj = NULL; + } + } + for (needed = n; needed != NULL; needed = needed1) { + needed1 = needed->next; + free(needed); + } +} + +static void +unload_filtees(Obj_Entry *obj) +{ + + free_needed_filtees(obj->needed_filtees); + obj->needed_filtees = NULL; + free_needed_filtees(obj->needed_aux_filtees); + obj->needed_aux_filtees = NULL; + obj->filtees_loaded = false; +} + +static void +load_filtee1(Obj_Entry *obj, Needed_Entry *needed, int flags, + RtldLockState *lockstate) +{ + + for (; needed != NULL; needed = needed->next) { + needed->obj = dlopen_object(obj->strtab + needed->name, -1, obj, + flags, ((ld_loadfltr || obj->z_loadfltr) ? RTLD_NOW : RTLD_LAZY) | + RTLD_LOCAL, lockstate); + } +} + +static void +load_filtees(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + + lock_restart_for_upgrade(lockstate); + if (!obj->filtees_loaded) { + load_filtee1(obj, obj->needed_filtees, flags, lockstate); + load_filtee1(obj, obj->needed_aux_filtees, flags, lockstate); + obj->filtees_loaded = true; + } +} + +static int +process_needed(Obj_Entry *obj, Needed_Entry *needed, int flags) +{ + Obj_Entry *obj1; + + for (; needed != NULL; needed = needed->next) { + obj1 = needed->obj = load_object(obj->strtab + needed->name, -1, obj, + flags & ~RTLD_LO_NOLOAD); + if (obj1 == NULL && !ld_tracing && (flags & RTLD_LO_FILTEES) == 0) + return (-1); + } + return (0); +} + +/* + * Given a shared object, traverse its list of needed objects, and load + * each of them. Returns 0 on success. Generates an error message and + * returns -1 on failure. + */ +static int +load_needed_objects(Obj_Entry *first, int flags) +{ + Obj_Entry *obj; + + for (obj = first; obj != NULL; obj = obj->next) { + if (process_needed(obj, obj->needed, flags) == -1) + return (-1); + } + return (0); +} + +static int +load_preload_objects(void) +{ + char *p = ld_preload; + Obj_Entry *obj; + static const char delim[] = " \t:;"; + + if (p == NULL) + return 0; + + p += strspn(p, delim); + while (*p != '\0') { + size_t len = strcspn(p, delim); + char savech; + + savech = p[len]; + p[len] = '\0'; + obj = load_object(p, -1, NULL, 0); + if (obj == NULL) + return -1; /* XXX - cleanup */ + obj->z_interpose = true; + p[len] = savech; + p += len; + p += strspn(p, delim); + } + LD_UTRACE(UTRACE_PRELOAD_FINISHED, NULL, NULL, 0, 0, NULL); + return 0; +} + +static const char * +printable_path(const char *path) +{ + + return (path == NULL ? "<unknown>" : path); +} + +/* + * Load a shared object into memory, if it is not already loaded. The + * object may be specified by name or by user-supplied file descriptor + * fd_u. In the later case, the fd_u descriptor is not closed, but its + * duplicate is. + * + * Returns a pointer to the Obj_Entry for the object. Returns NULL + * on failure. + */ +static Obj_Entry * +load_object(const char *name, int fd_u, const Obj_Entry *refobj, int flags) +{ + Obj_Entry *obj; + int fd; + struct stat sb; + char *path; + + fd = -1; + if (name != NULL) { + for (obj = obj_list->next; obj != NULL; obj = obj->next) { + if (object_match_name(obj, name)) + return (obj); + } + + path = find_library(name, refobj, &fd); + if (path == NULL) + return (NULL); + } else + path = NULL; + + if (fd >= 0) { + /* + * search_library_pathfds() opens a fresh file descriptor for the + * library, so there is no need to dup(). + */ + } else if (fd_u == -1) { + /* + * If we didn't find a match by pathname, or the name is not + * supplied, open the file and check again by device and inode. + * This avoids false mismatches caused by multiple links or ".." + * in pathnames. + * + * To avoid a race, we open the file and use fstat() rather than + * using stat(). + */ + if ((fd = open(path, O_RDONLY | O_CLOEXEC)) == -1) { + _rtld_error("Cannot open \"%s\"", path); + free(path); + return (NULL); + } + } else { + fd = fcntl(fd_u, F_DUPFD_CLOEXEC, 0); + if (fd == -1) { + _rtld_error("Cannot dup fd"); + free(path); + return (NULL); + } + } + if (fstat(fd, &sb) == -1) { + _rtld_error("Cannot fstat \"%s\"", printable_path(path)); + close(fd); + free(path); + return NULL; + } + for (obj = obj_list->next; obj != NULL; obj = obj->next) + if (obj->ino == sb.st_ino && obj->dev == sb.st_dev) + break; + if (obj != NULL && name != NULL) { + object_add_name(obj, name); + free(path); + close(fd); + return obj; + } + if (flags & RTLD_LO_NOLOAD) { + free(path); + close(fd); + return (NULL); + } + + /* First use of this object, so we must map it in */ + obj = do_load_object(fd, name, path, &sb, flags); + if (obj == NULL) + free(path); + close(fd); + + return obj; +} + +static Obj_Entry * +do_load_object(int fd, const char *name, char *path, struct stat *sbp, + int flags) +{ + Obj_Entry *obj; + struct statfs fs; + + /* + * but first, make sure that environment variables haven't been + * used to circumvent the noexec flag on a filesystem. + */ + if (dangerous_ld_env) { + if (fstatfs(fd, &fs) != 0) { + _rtld_error("Cannot fstatfs \"%s\"", printable_path(path)); + return NULL; + } + if (fs.f_flags & MNT_NOEXEC) { + _rtld_error("Cannot execute objects on %s\n", fs.f_mntonname); + return NULL; + } + } + dbg("loading \"%s\"", printable_path(path)); + obj = map_object(fd, printable_path(path), sbp); + if (obj == NULL) + return NULL; + + /* + * If DT_SONAME is present in the object, digest_dynamic2 already + * added it to the object names. + */ + if (name != NULL) + object_add_name(obj, name); + obj->path = path; + digest_dynamic(obj, 0); + dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d", obj->path, + obj->valid_hash_sysv, obj->valid_hash_gnu, obj->dynsymcount); + if (obj->z_noopen && (flags & (RTLD_LO_DLOPEN | RTLD_LO_TRACE)) == + RTLD_LO_DLOPEN) { + dbg("refusing to load non-loadable \"%s\"", obj->path); + _rtld_error("Cannot dlopen non-loadable %s", obj->path); + munmap(obj->mapbase, obj->mapsize); + obj_free(obj); + return (NULL); + } + + *obj_tail = obj; + obj_tail = &obj->next; + obj_count++; + obj_loads++; + linkmap_add(obj); /* for GDB & dlinfo() */ + max_stack_flags |= obj->stack_flags; + + dbg(" %p .. %p: %s", obj->mapbase, + obj->mapbase + obj->mapsize - 1, obj->path); + if (obj->textrel) + dbg(" WARNING: %s has impure text", obj->path); + LD_UTRACE(UTRACE_LOAD_OBJECT, obj, obj->mapbase, obj->mapsize, 0, + obj->path); + + return obj; +} + +static Obj_Entry * +obj_from_addr(const void *addr) +{ + Obj_Entry *obj; + + for (obj = obj_list; obj != NULL; obj = obj->next) { + if (addr < (void *) obj->mapbase) + continue; + if (addr < (void *) (obj->mapbase + obj->mapsize)) + return obj; + } + return NULL; +} + +static void +preinit_main(void) +{ + Elf_Addr *preinit_addr; + int index; + + preinit_addr = (Elf_Addr *)obj_main->preinit_array; + if (preinit_addr == NULL) + return; + + for (index = 0; index < obj_main->preinit_array_num; index++) { + if (preinit_addr[index] != 0 && preinit_addr[index] != 1) { + dbg("calling preinit function for %s at %p", obj_main->path, + (void *)preinit_addr[index]); + LD_UTRACE(UTRACE_INIT_CALL, obj_main, (void *)preinit_addr[index], + 0, 0, obj_main->path); + call_init_pointer(obj_main, preinit_addr[index]); + } + } +} + +/* + * Call the finalization functions for each of the objects in "list" + * belonging to the DAG of "root" and referenced once. If NULL "root" + * is specified, every finalization function will be called regardless + * of the reference count and the list elements won't be freed. All of + * the objects are expected to have non-NULL fini functions. + */ +static void +objlist_call_fini(Objlist *list, Obj_Entry *root, RtldLockState *lockstate) +{ + Objlist_Entry *elm; + char *saved_msg; + Elf_Addr *fini_addr; + int index; + + assert(root == NULL || root->refcount == 1); + + /* + * Preserve the current error message since a fini function might + * call into the dynamic linker and overwrite it. + */ + saved_msg = errmsg_save(); + do { + STAILQ_FOREACH(elm, list, link) { + if (root != NULL && (elm->obj->refcount != 1 || + objlist_find(&root->dagmembers, elm->obj) == NULL)) + continue; + /* Remove object from fini list to prevent recursive invocation. */ + STAILQ_REMOVE(list, elm, Struct_Objlist_Entry, link); + /* + * XXX: If a dlopen() call references an object while the + * fini function is in progress, we might end up trying to + * unload the referenced object in dlclose() or the object + * won't be unloaded although its fini function has been + * called. + */ + lock_release(rtld_bind_lock, lockstate); + + /* + * It is legal to have both DT_FINI and DT_FINI_ARRAY defined. + * When this happens, DT_FINI_ARRAY is processed first. + */ + fini_addr = (Elf_Addr *)elm->obj->fini_array; + if (fini_addr != NULL && elm->obj->fini_array_num > 0) { + for (index = elm->obj->fini_array_num - 1; index >= 0; + index--) { + if (fini_addr[index] != 0 && fini_addr[index] != 1) { + dbg("calling fini function for %s at %p", + elm->obj->path, (void *)fini_addr[index]); + LD_UTRACE(UTRACE_FINI_CALL, elm->obj, + (void *)fini_addr[index], 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, fini_addr[index]); + } + } + } + if (elm->obj->fini != (Elf_Addr)NULL) { + dbg("calling fini function for %s at %p", elm->obj->path, + (void *)elm->obj->fini); + LD_UTRACE(UTRACE_FINI_CALL, elm->obj, (void *)elm->obj->fini, + 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, elm->obj->fini); + } + wlock_acquire(rtld_bind_lock, lockstate); + /* No need to free anything if process is going down. */ + if (root != NULL) + free(elm); + /* + * We must restart the list traversal after every fini call + * because a dlclose() call from the fini function or from + * another thread might have modified the reference counts. + */ + break; + } + } while (elm != NULL); + errmsg_restore(saved_msg); +} + +/* + * Call the initialization functions for each of the objects in + * "list". All of the objects are expected to have non-NULL init + * functions. + */ +static void +objlist_call_init(Objlist *list, RtldLockState *lockstate) +{ + Objlist_Entry *elm; + Obj_Entry *obj; + char *saved_msg; + Elf_Addr *init_addr; + int index; + + /* + * Clean init_scanned flag so that objects can be rechecked and + * possibly initialized earlier if any of vectors called below + * cause the change by using dlopen. + */ + for (obj = obj_list; obj != NULL; obj = obj->next) + obj->init_scanned = false; + + /* + * Preserve the current error message since an init function might + * call into the dynamic linker and overwrite it. + */ + saved_msg = errmsg_save(); + STAILQ_FOREACH(elm, list, link) { + if (elm->obj->init_done) /* Initialized early. */ + continue; + /* + * Race: other thread might try to use this object before current + * one completes the initilization. Not much can be done here + * without better locking. + */ + elm->obj->init_done = true; + lock_release(rtld_bind_lock, lockstate); + + /* + * It is legal to have both DT_INIT and DT_INIT_ARRAY defined. + * When this happens, DT_INIT is processed first. + */ + if (elm->obj->init != (Elf_Addr)NULL) { + dbg("calling init function for %s at %p", elm->obj->path, + (void *)elm->obj->init); + LD_UTRACE(UTRACE_INIT_CALL, elm->obj, (void *)elm->obj->init, + 0, 0, elm->obj->path); + call_initfini_pointer(elm->obj, elm->obj->init); + } + init_addr = (Elf_Addr *)elm->obj->init_array; + if (init_addr != NULL) { + for (index = 0; index < elm->obj->init_array_num; index++) { + if (init_addr[index] != 0 && init_addr[index] != 1) { + dbg("calling init function for %s at %p", elm->obj->path, + (void *)init_addr[index]); + LD_UTRACE(UTRACE_INIT_CALL, elm->obj, + (void *)init_addr[index], 0, 0, elm->obj->path); + call_init_pointer(elm->obj, init_addr[index]); + } + } + } + wlock_acquire(rtld_bind_lock, lockstate); + } + errmsg_restore(saved_msg); +} + +static void +objlist_clear(Objlist *list) +{ + Objlist_Entry *elm; + + while (!STAILQ_EMPTY(list)) { + elm = STAILQ_FIRST(list); + STAILQ_REMOVE_HEAD(list, link); + free(elm); + } +} + +static Objlist_Entry * +objlist_find(Objlist *list, const Obj_Entry *obj) +{ + Objlist_Entry *elm; + + STAILQ_FOREACH(elm, list, link) + if (elm->obj == obj) + return elm; + return NULL; +} + +static void +objlist_init(Objlist *list) +{ + STAILQ_INIT(list); +} + +static void +objlist_push_head(Objlist *list, Obj_Entry *obj) +{ + Objlist_Entry *elm; + + elm = NEW(Objlist_Entry); + elm->obj = obj; + STAILQ_INSERT_HEAD(list, elm, link); +} + +static void +objlist_push_tail(Objlist *list, Obj_Entry *obj) +{ + Objlist_Entry *elm; + + elm = NEW(Objlist_Entry); + elm->obj = obj; + STAILQ_INSERT_TAIL(list, elm, link); +} + +static void +objlist_put_after(Objlist *list, Obj_Entry *listobj, Obj_Entry *obj) +{ + Objlist_Entry *elm, *listelm; + + STAILQ_FOREACH(listelm, list, link) { + if (listelm->obj == listobj) + break; + } + elm = NEW(Objlist_Entry); + elm->obj = obj; + if (listelm != NULL) + STAILQ_INSERT_AFTER(list, listelm, elm, link); + else + STAILQ_INSERT_TAIL(list, elm, link); +} + +static void +objlist_remove(Objlist *list, Obj_Entry *obj) +{ + Objlist_Entry *elm; + + if ((elm = objlist_find(list, obj)) != NULL) { + STAILQ_REMOVE(list, elm, Struct_Objlist_Entry, link); + free(elm); + } +} + +/* + * Relocate dag rooted in the specified object. + * Returns 0 on success, or -1 on failure. + */ + +static int +relocate_object_dag(Obj_Entry *root, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + Objlist_Entry *elm; + int error; + + error = 0; + STAILQ_FOREACH(elm, &root->dagmembers, link) { + error = relocate_object(elm->obj, bind_now, rtldobj, flags, + lockstate); + if (error == -1) + break; + } + return (error); +} + +/* + * Relocate single object. + * Returns 0 on success, or -1 on failure. + */ +static int +relocate_object(Obj_Entry *obj, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + + if (obj->relocated) + return (0); + obj->relocated = true; + if (obj != rtldobj) + dbg("relocating \"%s\"", obj->path); + + if (obj->symtab == NULL || obj->strtab == NULL || + !(obj->valid_hash_sysv || obj->valid_hash_gnu)) { + _rtld_error("%s: Shared object has no run-time symbol table", + obj->path); + return (-1); + } + + if (obj->textrel) { + /* There are relocations to the write-protected text segment. */ + if (mprotect(obj->mapbase, obj->textsize, + PROT_READ|PROT_WRITE|PROT_EXEC) == -1) { + _rtld_error("%s: Cannot write-enable text segment: %s", + obj->path, rtld_strerror(errno)); + return (-1); + } + } + + /* Process the non-PLT non-IFUNC relocations. */ + if (reloc_non_plt(obj, rtldobj, flags, lockstate)) + return (-1); + + if (obj->textrel) { /* Re-protected the text segment. */ + if (mprotect(obj->mapbase, obj->textsize, + PROT_READ|PROT_EXEC) == -1) { + _rtld_error("%s: Cannot write-protect text segment: %s", + obj->path, rtld_strerror(errno)); + return (-1); + } + } + + /* Set the special PLT or GOT entries. */ + init_pltgot(obj); + + /* Process the PLT relocations. */ + if (reloc_plt(obj) == -1) + return (-1); + /* Relocate the jump slots if we are doing immediate binding. */ + if (obj->bind_now || bind_now) + if (reloc_jmpslots(obj, flags, lockstate) == -1) + return (-1); + + /* + * Process the non-PLT IFUNC relocations. The relocations are + * processed in two phases, because IFUNC resolvers may + * reference other symbols, which must be readily processed + * before resolvers are called. + */ + if (obj->non_plt_gnu_ifunc && + reloc_non_plt(obj, rtldobj, flags | SYMLOOK_IFUNC, lockstate)) + return (-1); + + if (obj->relro_size > 0) { + if (mprotect(obj->relro_page, obj->relro_size, + PROT_READ) == -1) { + _rtld_error("%s: Cannot enforce relro protection: %s", + obj->path, rtld_strerror(errno)); + return (-1); + } + } + + /* + * Set up the magic number and version in the Obj_Entry. These + * were checked in the crt1.o from the original ElfKit, so we + * set them for backward compatibility. + */ + obj->magic = RTLD_MAGIC; + obj->version = RTLD_VERSION; + + return (0); +} + +/* + * Relocate newly-loaded shared objects. The argument is a pointer to + * the Obj_Entry for the first such object. All objects from the first + * to the end of the list of objects are relocated. Returns 0 on success, + * or -1 on failure. + */ +static int +relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj, + int flags, RtldLockState *lockstate) +{ + Obj_Entry *obj; + int error; + + for (error = 0, obj = first; obj != NULL; obj = obj->next) { + error = relocate_object(obj, bind_now, rtldobj, flags, + lockstate); + if (error == -1) + break; + } + return (error); +} + +/* + * The handling of R_MACHINE_IRELATIVE relocations and jumpslots + * referencing STT_GNU_IFUNC symbols is postponed till the other + * relocations are done. The indirect functions specified as + * ifunc are allowed to call other symbols, so we need to have + * objects relocated before asking for resolution from indirects. + * + * The R_MACHINE_IRELATIVE slots are resolved in greedy fashion, + * instead of the usual lazy handling of PLT slots. It is + * consistent with how GNU does it. + */ +static int +resolve_object_ifunc(Obj_Entry *obj, bool bind_now, int flags, + RtldLockState *lockstate) +{ + if (obj->irelative && reloc_iresolve(obj, lockstate) == -1) + return (-1); + if ((obj->bind_now || bind_now) && obj->gnu_ifunc && + reloc_gnu_ifunc(obj, flags, lockstate) == -1) + return (-1); + return (0); +} + +static int +resolve_objects_ifunc(Obj_Entry *first, bool bind_now, int flags, + RtldLockState *lockstate) +{ + Obj_Entry *obj; + + for (obj = first; obj != NULL; obj = obj->next) { + if (resolve_object_ifunc(obj, bind_now, flags, lockstate) == -1) + return (-1); + } + return (0); +} + +static int +initlist_objects_ifunc(Objlist *list, bool bind_now, int flags, + RtldLockState *lockstate) +{ + Objlist_Entry *elm; + + STAILQ_FOREACH(elm, list, link) { + if (resolve_object_ifunc(elm->obj, bind_now, flags, + lockstate) == -1) + return (-1); + } + return (0); +} + +/* + * Cleanup procedure. It will be called (by the atexit mechanism) just + * before the process exits. + */ +static void +rtld_exit(void) +{ + RtldLockState lockstate; + + wlock_acquire(rtld_bind_lock, &lockstate); + dbg("rtld_exit()"); + objlist_call_fini(&list_fini, NULL, &lockstate); + /* No need to remove the items from the list, since we are exiting. */ + if (!libmap_disable) + lm_fini(); + lock_release(rtld_bind_lock, &lockstate); +} + +/* + * Iterate over a search path, translate each element, and invoke the + * callback on the result. + */ +static void * +path_enumerate(const char *path, path_enum_proc callback, void *arg) +{ + const char *trans; + if (path == NULL) + return (NULL); + + path += strspn(path, ":;"); + while (*path != '\0') { + size_t len; + char *res; + + len = strcspn(path, ":;"); + trans = lm_findn(NULL, path, len); + if (trans) + res = callback(trans, strlen(trans), arg); + else + res = callback(path, len, arg); + + if (res != NULL) + return (res); + + path += len; + path += strspn(path, ":;"); + } + + return (NULL); +} + +struct try_library_args { + const char *name; + size_t namelen; + char *buffer; + size_t buflen; +}; + +static void * +try_library_path(const char *dir, size_t dirlen, void *param) +{ + struct try_library_args *arg; + + arg = param; + if (*dir == '/' || trust) { + char *pathname; + + if (dirlen + 1 + arg->namelen + 1 > arg->buflen) + return (NULL); + + pathname = arg->buffer; + strncpy(pathname, dir, dirlen); + pathname[dirlen] = '/'; + strcpy(pathname + dirlen + 1, arg->name); + + dbg(" Trying \"%s\"", pathname); + if (access(pathname, F_OK) == 0) { /* We found it */ + pathname = xmalloc(dirlen + 1 + arg->namelen + 1); + strcpy(pathname, arg->buffer); + return (pathname); + } + } + return (NULL); +} + +static char * +search_library_path(const char *name, const char *path) +{ + char *p; + struct try_library_args arg; + + if (path == NULL) + return NULL; + + arg.name = name; + arg.namelen = strlen(name); + arg.buffer = xmalloc(PATH_MAX); + arg.buflen = PATH_MAX; + + p = path_enumerate(path, try_library_path, &arg); + + free(arg.buffer); + + return (p); +} + + +/* + * Finds the library with the given name using the directory descriptors + * listed in the LD_LIBRARY_PATH_FDS environment variable. + * + * Returns a freshly-opened close-on-exec file descriptor for the library, + * or -1 if the library cannot be found. + */ +static char * +search_library_pathfds(const char *name, const char *path, int *fdp) +{ + char *envcopy, *fdstr, *found, *last_token; + size_t len; + int dirfd, fd; + + dbg("%s('%s', '%s', fdp)", __func__, name, path); + + /* Don't load from user-specified libdirs into setuid binaries. */ + if (!trust) + return (NULL); + + /* We can't do anything if LD_LIBRARY_PATH_FDS isn't set. */ + if (path == NULL) + return (NULL); + + /* LD_LIBRARY_PATH_FDS only works with relative paths. */ + if (name[0] == '/') { + dbg("Absolute path (%s) passed to %s", name, __func__); + return (NULL); + } + + /* + * Use strtok_r() to walk the FD:FD:FD list. This requires a local + * copy of the path, as strtok_r rewrites separator tokens + * with '\0'. + */ + found = NULL; + envcopy = xstrdup(path); + for (fdstr = strtok_r(envcopy, ":", &last_token); fdstr != NULL; + fdstr = strtok_r(NULL, ":", &last_token)) { + dirfd = parse_libdir(fdstr); + if (dirfd < 0) + break; + fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + *fdp = fd; + len = strlen(fdstr) + strlen(name) + 3; + found = xmalloc(len); + if (rtld_snprintf(found, len, "#%d/%s", dirfd, name) < 0) { + _rtld_error("error generating '%d/%s'", + dirfd, name); + die(); + } + dbg("open('%s') => %d", found, fd); + break; + } + } + free(envcopy); + + return (found); +} + + +int +dlclose(void *handle) +{ + Obj_Entry *root; + RtldLockState lockstate; + + wlock_acquire(rtld_bind_lock, &lockstate); + root = dlcheck(handle); + if (root == NULL) { + lock_release(rtld_bind_lock, &lockstate); + return -1; + } + LD_UTRACE(UTRACE_DLCLOSE_START, handle, NULL, 0, root->dl_refcount, + root->path); + + /* Unreference the object and its dependencies. */ + root->dl_refcount--; + + if (root->refcount == 1) { + /* + * The object will be no longer referenced, so we must unload it. + * First, call the fini functions. + */ + objlist_call_fini(&list_fini, root, &lockstate); + + unref_dag(root); + + /* Finish cleaning up the newly-unreferenced objects. */ + GDB_STATE(RT_DELETE,&root->linkmap); + unload_object(root); + GDB_STATE(RT_CONSISTENT,NULL); + } else + unref_dag(root); + + LD_UTRACE(UTRACE_DLCLOSE_STOP, handle, NULL, 0, 0, NULL); + lock_release(rtld_bind_lock, &lockstate); + return 0; +} + +char * +dlerror(void) +{ + char *msg = error_message; + error_message = NULL; + return msg; +} + +/* + * This function is deprecated and has no effect. + */ +void +dllockinit(void *context, + void *(*lock_create)(void *context), + void (*rlock_acquire)(void *lock), + void (*wlock_acquire)(void *lock), + void (*lock_release)(void *lock), + void (*lock_destroy)(void *lock), + void (*context_destroy)(void *context)) +{ + static void *cur_context; + static void (*cur_context_destroy)(void *); + + /* Just destroy the context from the previous call, if necessary. */ + if (cur_context_destroy != NULL) + cur_context_destroy(cur_context); + cur_context = context; + cur_context_destroy = context_destroy; +} + +void * +dlopen(const char *name, int mode) +{ + + return (rtld_dlopen(name, -1, mode)); +} + +void * +fdlopen(int fd, int mode) +{ + + return (rtld_dlopen(NULL, fd, mode)); +} + +static void * +rtld_dlopen(const char *name, int fd, int mode) +{ + RtldLockState lockstate; + int lo_flags; + + LD_UTRACE(UTRACE_DLOPEN_START, NULL, NULL, 0, mode, name); + ld_tracing = (mode & RTLD_TRACE) == 0 ? NULL : "1"; + if (ld_tracing != NULL) { + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); + environ = (char **)*get_program_var_addr("environ", &lockstate); + lock_release(rtld_bind_lock, &lockstate); + } + lo_flags = RTLD_LO_DLOPEN; + if (mode & RTLD_NODELETE) + lo_flags |= RTLD_LO_NODELETE; + if (mode & RTLD_NOLOAD) + lo_flags |= RTLD_LO_NOLOAD; + if (ld_tracing != NULL) + lo_flags |= RTLD_LO_TRACE; + + return (dlopen_object(name, fd, obj_main, lo_flags, + mode & (RTLD_MODEMASK | RTLD_GLOBAL), NULL)); +} + +static void +dlopen_cleanup(Obj_Entry *obj) +{ + + obj->dl_refcount--; + unref_dag(obj); + if (obj->refcount == 0) + unload_object(obj); +} + +static Obj_Entry * +dlopen_object(const char *name, int fd, Obj_Entry *refobj, int lo_flags, + int mode, RtldLockState *lockstate) +{ + Obj_Entry **old_obj_tail; + Obj_Entry *obj; + Objlist initlist; + RtldLockState mlockstate; + int result; + + objlist_init(&initlist); + + if (lockstate == NULL && !(lo_flags & RTLD_LO_EARLY)) { + wlock_acquire(rtld_bind_lock, &mlockstate); + lockstate = &mlockstate; + } + GDB_STATE(RT_ADD,NULL); + + old_obj_tail = obj_tail; + obj = NULL; + if (name == NULL && fd == -1) { + obj = obj_main; + obj->refcount++; + } else { + obj = load_object(name, fd, refobj, lo_flags); + } + + if (obj) { + obj->dl_refcount++; + if (mode & RTLD_GLOBAL && objlist_find(&list_global, obj) == NULL) + objlist_push_tail(&list_global, obj); + if (*old_obj_tail != NULL) { /* We loaded something new. */ + assert(*old_obj_tail == obj); + result = load_needed_objects(obj, + lo_flags & (RTLD_LO_DLOPEN | RTLD_LO_EARLY)); + init_dag(obj); + ref_dag(obj); + if (result != -1) + result = rtld_verify_versions(&obj->dagmembers); + if (result != -1 && ld_tracing) + goto trace; + if (result == -1 || relocate_object_dag(obj, + (mode & RTLD_MODEMASK) == RTLD_NOW, &obj_rtld, + (lo_flags & RTLD_LO_EARLY) ? SYMLOOK_EARLY : 0, + lockstate) == -1) { + dlopen_cleanup(obj); + obj = NULL; + } else if (lo_flags & RTLD_LO_EARLY) { + /* + * Do not call the init functions for early loaded + * filtees. The image is still not initialized enough + * for them to work. + * + * Our object is found by the global object list and + * will be ordered among all init calls done right + * before transferring control to main. + */ + } else { + /* Make list of init functions to call. */ + initlist_add_objects(obj, &obj->next, &initlist); + } + /* + * Process all no_delete objects here, given them own + * DAGs to prevent their dependencies from being unloaded. + * This has to be done after we have loaded all of the + * dependencies, so that we do not miss any. + */ + if (obj != NULL) + process_nodelete(obj); + } else { + /* + * Bump the reference counts for objects on this DAG. If + * this is the first dlopen() call for the object that was + * already loaded as a dependency, initialize the dag + * starting at it. + */ + init_dag(obj); + ref_dag(obj); + + if ((lo_flags & RTLD_LO_TRACE) != 0) + goto trace; + } + if (obj != NULL && ((lo_flags & RTLD_LO_NODELETE) != 0 || + obj->z_nodelete) && !obj->ref_nodel) { + dbg("obj %s nodelete", obj->path); + ref_dag(obj); + obj->z_nodelete = obj->ref_nodel = true; + } + } + + LD_UTRACE(UTRACE_DLOPEN_STOP, obj, NULL, 0, obj ? obj->dl_refcount : 0, + name); + GDB_STATE(RT_CONSISTENT,obj ? &obj->linkmap : NULL); + + if (!(lo_flags & RTLD_LO_EARLY)) { + map_stacks_exec(lockstate); + } + + if (initlist_objects_ifunc(&initlist, (mode & RTLD_MODEMASK) == RTLD_NOW, + (lo_flags & RTLD_LO_EARLY) ? SYMLOOK_EARLY : 0, + lockstate) == -1) { + objlist_clear(&initlist); + dlopen_cleanup(obj); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); + return (NULL); + } + + if (!(lo_flags & RTLD_LO_EARLY)) { + /* Call the init functions. */ + objlist_call_init(&initlist, lockstate); + } + objlist_clear(&initlist); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); + return obj; +trace: + trace_loaded_objects(obj); + if (lockstate == &mlockstate) + lock_release(rtld_bind_lock, lockstate); + exit(0); +} + +static void * +do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve, + int flags) +{ + DoneList donelist; + const Obj_Entry *obj, *defobj; + const Elf_Sym *def; + SymLook req; + RtldLockState lockstate; + tls_index ti; + int res; + + def = NULL; + defobj = NULL; + symlook_init(&req, name); + req.ventry = ve; + req.flags = flags | SYMLOOK_IN_PLT; + req.lockstate = &lockstate; + + rlock_acquire(rtld_bind_lock, &lockstate); + if (sigsetjmp(lockstate.env, 0) != 0) + lock_upgrade(rtld_bind_lock, &lockstate); + if (handle == NULL || handle == RTLD_NEXT || + handle == RTLD_DEFAULT || handle == RTLD_SELF) { + + if ((obj = obj_from_addr(retaddr)) == NULL) { + _rtld_error("Cannot determine caller's shared object"); + lock_release(rtld_bind_lock, &lockstate); + return NULL; + } + if (handle == NULL) { /* Just the caller's shared object. */ + res = symlook_obj(&req, obj); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } else if (handle == RTLD_NEXT || /* Objects after caller's */ + handle == RTLD_SELF) { /* ... caller included */ + if (handle == RTLD_NEXT) + obj = obj->next; + for (; obj != NULL; obj = obj->next) { + res = symlook_obj(&req, obj); + if (res == 0) { + if (def == NULL || + ELF_ST_BIND(req.sym_out->st_info) != STB_WEAK) { + def = req.sym_out; + defobj = req.defobj_out; + if (ELF_ST_BIND(def->st_info) != STB_WEAK) + break; + } + } + } + /* + * Search the dynamic linker itself, and possibly resolve the + * symbol from there. This is how the application links to + * dynamic linker services such as dlopen. + */ + if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { + res = symlook_obj(&req, &obj_rtld); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } else { + assert(handle == RTLD_DEFAULT); + res = symlook_default(&req, obj); + if (res == 0) { + defobj = req.defobj_out; + def = req.sym_out; + } + } + } else { + if ((obj = dlcheck(handle)) == NULL) { + lock_release(rtld_bind_lock, &lockstate); + return NULL; + } + + donelist_init(&donelist); + if (obj->mainprog) { + /* Handle obtained by dlopen(NULL, ...) implies global scope. */ + res = symlook_global(&req, &donelist); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + /* + * Search the dynamic linker itself, and possibly resolve the + * symbol from there. This is how the application links to + * dynamic linker services such as dlopen. + */ + if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) { + res = symlook_obj(&req, &obj_rtld); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } + else { + /* Search the whole DAG rooted at the given object. */ + res = symlook_list(&req, &obj->dagmembers, &donelist); + if (res == 0) { + def = req.sym_out; + defobj = req.defobj_out; + } + } + } + + if (def != NULL) { + lock_release(rtld_bind_lock, &lockstate); + + /* + * The value required by the caller is derived from the value + * of the symbol. this is simply the relocated value of the + * symbol. + */ + if (ELF_ST_TYPE(def->st_info) == STT_FUNC) + return (make_function_pointer(def, defobj)); + else if (ELF_ST_TYPE(def->st_info) == STT_GNU_IFUNC) + return (rtld_resolve_ifunc(defobj, def)); + else if (ELF_ST_TYPE(def->st_info) == STT_TLS) { + ti.ti_module = defobj->tlsindex; + ti.ti_offset = def->st_value; + return (__tls_get_addr(&ti)); + } else + return (defobj->relocbase + def->st_value); + } + + _rtld_error("Undefined symbol \"%s\"", name); + lock_release(rtld_bind_lock, &lockstate); + return NULL; +} + +void * +dlsym(void *handle, const char *name) +{ + return do_dlsym(handle, name, __builtin_return_address(0), NULL, + SYMLOOK_DLSYM); +} + +dlfunc_t +dlfunc(void *handle, const char *name) +{ + union { + void *d; + dlfunc_t f; + } rv; + + rv.d = do_dlsym(handle, name, __builtin_return_address(0), NULL, + SYMLOOK_DLSYM); + return (rv.f); +} + +void * +dlvsym(void *handle, const char *name, const char *version) +{ + Ver_Entry ventry; + + ventry.name = version; + ventry.file = NULL; + ventry.hash = elf_hash(version); + ventry.flags= 0; + return do_dlsym(handle, name, __builtin_return_address(0), &ventry, + SYMLOOK_DLSYM); +} + +int +_rtld_addr_phdr(const void *addr, struct dl_phdr_info *phdr_info) +{ + const Obj_Entry *obj; + RtldLockState lockstate; + + rlock_acquire(rtld_bind_lock, &lockstate); + obj = obj_from_addr(addr); + if (obj == NULL) { + _rtld_error("No shared object contains address"); + lock_release(rtld_bind_lock, &lockstate); + return (0); + } + rtld_fill_dl_phdr_info(obj, phdr_info); + lock_release(rtld_bind_lock, &lockstate); + return (1); +} + +int +dladdr(const void *addr, Dl_info *info) +{ + const Obj_Entry *obj; + const Elf_Sym *def; + void *symbol_addr; + unsigned long symoffset; + RtldLockState lockstate; + + rlock_acquire(rtld_bind_lock, &lockstate); + obj = obj_from_addr(addr); + if (obj == NULL) { + _rtld_error("No shared object contains address"); + lock_release(rtld_bind_lock, &lockstate); + return 0; + } + info->dli_fname = obj->path; + info->dli_fbase = obj->mapbase; + info->dli_saddr = (void *)0; + info->dli_sname = NULL; + + /* + * Walk the symbol list looking for the symbol whose address is + * closest to the address sent in. + */ + for (symoffset = 0; symoffset < obj->dynsymcount; symoffset++) { + def = obj->symtab + symoffset; + + /* + * For skip the symbol if st_shndx is either SHN_UNDEF or + * SHN_COMMON. + */ + if (def->st_shndx == SHN_UNDEF || def->st_shndx == SHN_COMMON) + continue; + + /* + * If the symbol is greater than the specified address, or if it + * is further away from addr than the current nearest symbol, + * then reject it. + */ + symbol_addr = obj->relocbase + def->st_value; + if (symbol_addr > addr || symbol_addr < info->dli_saddr) + continue; + + /* Update our idea of the nearest symbol. */ + info->dli_sname = obj->strtab + def->st_name; + info->dli_saddr = symbol_addr; + + /* Exact match? */ + if (info->dli_saddr == addr) + break; + } + lock_release(rtld_bind_lock, &lockstate); + return 1; +} + +int +dlinfo(void *handle, int request, void *p) +{ + const Obj_Entry *obj; + RtldLockState lockstate; + int error; + + rlock_acquire(rtld_bind_lock, &lockstate); + + if (handle == NULL || handle == RTLD_SELF) { + void *retaddr; + + retaddr = __builtin_return_address(0); /* __GNUC__ only */ + if ((obj = obj_from_addr(retaddr)) == NULL) + _rtld_error("Cannot determine caller's shared object"); + } else + obj = dlcheck(handle); + + if (obj == NULL) { + lock_release(rtld_bind_lock, &lockstate); + return (-1); + } + + error = 0; + switch (request) { + case RTLD_DI_LINKMAP: + *((struct link_map const **)p) = &obj->linkmap; + break; + case RTLD_DI_ORIGIN: + error = rtld_dirname(obj->path, p); + break; + + case RTLD_DI_SERINFOSIZE: + case RTLD_DI_SERINFO: + error = do_search_info(obj, request, (struct dl_serinfo *)p); + break; + + default: + _rtld_error("Invalid request %d passed to dlinfo()", request); + error = -1; + } + + lock_release(rtld_bind_lock, &lockstate); + + return (error); +} + +static void +rtld_fill_dl_phdr_info(const Obj_Entry *obj, struct dl_phdr_info *phdr_info) +{ + + phdr_info->dlpi_addr = (Elf_Addr)obj->relocbase; + phdr_info->dlpi_name = obj->path; + phdr_info->dlpi_phdr = obj->phdr; + phdr_info->dlpi_phnum = obj->phsize / sizeof(obj->phdr[0]); + phdr_info->dlpi_tls_modid = obj->tlsindex; + phdr_info->dlpi_tls_data = obj->tlsinit; + phdr_info->dlpi_adds = obj_loads; + phdr_info->dlpi_subs = obj_loads - obj_count; +} + +int +dl_iterate_phdr(__dl_iterate_hdr_callback callback, void *param) +{ + struct dl_phdr_info phdr_info; + const Obj_Entry *obj; + RtldLockState bind_lockstate, phdr_lockstate; + int error; + + wlock_acquire(rtld_phdr_lock, &phdr_lockstate); + rlock_acquire(rtld_bind_lock, &bind_lockstate); + + error = 0; + + for (obj = obj_list; obj != NULL; obj = obj->next) { + rtld_fill_dl_phdr_info(obj, &phdr_info); + if ((error = callback(&phdr_info, sizeof phdr_info, param)) != 0) + break; + + } + if (error == 0) { + rtld_fill_dl_phdr_info(&obj_rtld, &phdr_info); + error = callback(&phdr_info, sizeof(phdr_info), param); + } + + lock_release(rtld_bind_lock, &bind_lockstate); + lock_release(rtld_phdr_lock, &phdr_lockstate); + + return (error); +} + +static void * +fill_search_info(const char *dir, size_t dirlen, void *param) +{ + struct fill_search_info_args *arg; + + arg = param; + + if (arg->request == RTLD_DI_SERINFOSIZE) { + arg->serinfo->dls_cnt ++; + arg->serinfo->dls_size += sizeof(struct dl_serpath) + dirlen + 1; + } else { + struct dl_serpath *s_entry; + + s_entry = arg->serpath; + s_entry->dls_name = arg->strspace; + s_entry->dls_flags = arg->flags; + + strncpy(arg->strspace, dir, dirlen); + arg->strspace[dirlen] = '\0'; + + arg->strspace += dirlen + 1; + arg->serpath++; + } + + return (NULL); +} + +static int +do_search_info(const Obj_Entry *obj, int request, struct dl_serinfo *info) +{ + struct dl_serinfo _info; + struct fill_search_info_args args; + + args.request = RTLD_DI_SERINFOSIZE; + args.serinfo = &_info; + + _info.dls_size = __offsetof(struct dl_serinfo, dls_serpath); + _info.dls_cnt = 0; + + path_enumerate(obj->rpath, fill_search_info, &args); + path_enumerate(ld_library_path, fill_search_info, &args); + path_enumerate(obj->runpath, fill_search_info, &args); + path_enumerate(gethints(obj->z_nodeflib), fill_search_info, &args); + if (!obj->z_nodeflib) + path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &args); + + + if (request == RTLD_DI_SERINFOSIZE) { + info->dls_size = _info.dls_size; + info->dls_cnt = _info.dls_cnt; + return (0); + } + + if (info->dls_cnt != _info.dls_cnt || info->dls_size != _info.dls_size) { + _rtld_error("Uninitialized Dl_serinfo struct passed to dlinfo()"); + return (-1); + } + + args.request = RTLD_DI_SERINFO; + args.serinfo = info; + args.serpath = &info->dls_serpath[0]; + args.strspace = (char *)&info->dls_serpath[_info.dls_cnt]; + + args.flags = LA_SER_RUNPATH; + if (path_enumerate(obj->rpath, fill_search_info, &args) != NULL) + return (-1); + + args.flags = LA_SER_LIBPATH; + if (path_enumerate(ld_library_path, fill_search_info, &args) != NULL) + return (-1); + + args.flags = LA_SER_RUNPATH; + if (path_enumerate(obj->runpath, fill_search_info, &args) != NULL) + return (-1); + + args.flags = LA_SER_CONFIG; + if (path_enumerate(gethints(obj->z_nodeflib), fill_search_info, &args) + != NULL) + return (-1); + + args.flags = LA_SER_DEFAULT; + if (!obj->z_nodeflib && + path_enumerate(STANDARD_LIBRARY_PATH, fill_search_info, &args) != NULL) + return (-1); + return (0); +} + +static int +rtld_dirname(const char *path, char *bname) +{ + const char *endp; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + bname[0] = '.'; + bname[1] = '\0'; + return (0); + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + bname[0] = *endp == '/' ? '/' : '.'; + bname[1] = '\0'; + return (0); + } else { + do { + endp--; + } while (endp > path && *endp == '/'); + } + + if (endp - path + 2 > PATH_MAX) + { + _rtld_error("Filename is too long: %s", path); + return(-1); + } + + strncpy(bname, path, endp - path + 1); + bname[endp - path + 1] = '\0'; + return (0); +} + +static int +rtld_dirname_abs(const char *path, char *base) +{ + char base_rel[PATH_MAX]; + + if (rtld_dirname(path, base) == -1) + return (-1); + if (base[0] == '/') + return (0); + if (getcwd(base_rel, sizeof(base_rel)) == NULL || + strlcat(base_rel, "/", sizeof(base_rel)) >= sizeof(base_rel) || + strlcat(base_rel, base, sizeof(base_rel)) >= sizeof(base_rel)) + return (-1); + strcpy(base, base_rel); + return (0); +} + +static void +linkmap_add(Obj_Entry *obj) +{ + struct link_map *l = &obj->linkmap; + struct link_map *prev; + + obj->linkmap.l_name = obj->path; + obj->linkmap.l_addr = obj->mapbase; + obj->linkmap.l_ld = obj->dynamic; +#ifdef __mips__ + /* GDB needs load offset on MIPS to use the symbols */ + obj->linkmap.l_offs = obj->relocbase; +#endif + + if (r_debug.r_map == NULL) { + r_debug.r_map = l; + return; + } + + /* + * Scan to the end of the list, but not past the entry for the + * dynamic linker, which we want to keep at the very end. + */ + for (prev = r_debug.r_map; + prev->l_next != NULL && prev->l_next != &obj_rtld.linkmap; + prev = prev->l_next) + ; + + /* Link in the new entry. */ + l->l_prev = prev; + l->l_next = prev->l_next; + if (l->l_next != NULL) + l->l_next->l_prev = l; + prev->l_next = l; +} + +static void +linkmap_delete(Obj_Entry *obj) +{ + struct link_map *l = &obj->linkmap; + + if (l->l_prev == NULL) { + if ((r_debug.r_map = l->l_next) != NULL) + l->l_next->l_prev = NULL; + return; + } + + if ((l->l_prev->l_next = l->l_next) != NULL) + l->l_next->l_prev = l->l_prev; +} + +/* + * Function for the debugger to set a breakpoint on to gain control. + * + * The two parameters allow the debugger to easily find and determine + * what the runtime loader is doing and to whom it is doing it. + * + * When the loadhook trap is hit (r_debug_state, set at program + * initialization), the arguments can be found on the stack: + * + * +8 struct link_map *m + * +4 struct r_debug *rd + * +0 RetAddr + */ +void +r_debug_state(struct r_debug* rd, struct link_map *m) +{ + /* + * The following is a hack to force the compiler to emit calls to + * this function, even when optimizing. If the function is empty, + * the compiler is not obliged to emit any code for calls to it, + * even when marked __noinline. However, gdb depends on those + * calls being made. + */ + __compiler_membar(); +} + +/* + * A function called after init routines have completed. This can be used to + * break before a program's entry routine is called, and can be used when + * main is not available in the symbol table. + */ +void +_r_debug_postinit(struct link_map *m) +{ + + /* See r_debug_state(). */ + __compiler_membar(); +} + +/* + * Get address of the pointer variable in the main program. + * Prefer non-weak symbol over the weak one. + */ +static const void ** +get_program_var_addr(const char *name, RtldLockState *lockstate) +{ + SymLook req; + DoneList donelist; + + symlook_init(&req, name); + req.lockstate = lockstate; + donelist_init(&donelist); + if (symlook_global(&req, &donelist) != 0) + return (NULL); + if (ELF_ST_TYPE(req.sym_out->st_info) == STT_FUNC) + return ((const void **)make_function_pointer(req.sym_out, + req.defobj_out)); + else if (ELF_ST_TYPE(req.sym_out->st_info) == STT_GNU_IFUNC) + return ((const void **)rtld_resolve_ifunc(req.defobj_out, req.sym_out)); + else + return ((const void **)(req.defobj_out->relocbase + + req.sym_out->st_value)); +} + +/* + * Set a pointer variable in the main program to the given value. This + * is used to set key variables such as "environ" before any of the + * init functions are called. + */ +static void +set_program_var(const char *name, const void *value) +{ + const void **addr; + + if ((addr = get_program_var_addr(name, NULL)) != NULL) { + dbg("\"%s\": *%p <-- %p", name, addr, value); + *addr = value; + } +} + +/* + * Search the global objects, including dependencies and main object, + * for the given symbol. + */ +static int +symlook_global(SymLook *req, DoneList *donelist) +{ + SymLook req1; + const Objlist_Entry *elm; + int res; + + symlook_init_from_req(&req1, req); + + /* Search all objects loaded at program start up. */ + if (req->defobj_out == NULL || + ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) { + res = symlook_list(&req1, &list_main, donelist); + if (res == 0 && (req->defobj_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + /* Search all DAGs whose roots are RTLD_GLOBAL objects. */ + STAILQ_FOREACH(elm, &list_global, link) { + if (req->defobj_out != NULL && + ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK) + break; + res = symlook_list(&req1, &elm->obj->dagmembers, donelist); + if (res == 0 && (req->defobj_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + return (req->sym_out != NULL ? 0 : ESRCH); +} + +/* + * Given a symbol name in a referencing object, find the corresponding + * definition of the symbol. Returns a pointer to the symbol, or NULL if + * no definition was found. Returns a pointer to the Obj_Entry of the + * defining object via the reference parameter DEFOBJ_OUT. + */ +static int +symlook_default(SymLook *req, const Obj_Entry *refobj) +{ + DoneList donelist; + const Objlist_Entry *elm; + SymLook req1; + int res; + + donelist_init(&donelist); + symlook_init_from_req(&req1, req); + + /* Look first in the referencing object if linked symbolically. */ + if (refobj->symbolic && !donelist_check(&donelist, refobj)) { + res = symlook_obj(&req1, refobj); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + symlook_global(req, &donelist); + + /* Search all dlopened DAGs containing the referencing object. */ + STAILQ_FOREACH(elm, &refobj->dldags, link) { + if (req->sym_out != NULL && + ELF_ST_BIND(req->sym_out->st_info) != STB_WEAK) + break; + res = symlook_list(&req1, &elm->obj->dagmembers, &donelist); + if (res == 0 && (req->sym_out == NULL || + ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + /* + * Search the dynamic linker itself, and possibly resolve the + * symbol from there. This is how the application links to + * dynamic linker services such as dlopen. + */ + if (req->sym_out == NULL || + ELF_ST_BIND(req->sym_out->st_info) == STB_WEAK) { + res = symlook_obj(&req1, &obj_rtld); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + assert(req->defobj_out != NULL); + } + } + + return (req->sym_out != NULL ? 0 : ESRCH); +} + +static int +symlook_list(SymLook *req, const Objlist *objlist, DoneList *dlp) +{ + const Elf_Sym *def; + const Obj_Entry *defobj; + const Objlist_Entry *elm; + SymLook req1; + int res; + + def = NULL; + defobj = NULL; + STAILQ_FOREACH(elm, objlist, link) { + if (donelist_check(dlp, elm->obj)) + continue; + symlook_init_from_req(&req1, req); + if ((res = symlook_obj(&req1, elm->obj)) == 0) { + if (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK) { + def = req1.sym_out; + defobj = req1.defobj_out; + if (ELF_ST_BIND(def->st_info) != STB_WEAK) + break; + } + } + } + if (def != NULL) { + req->sym_out = def; + req->defobj_out = defobj; + return (0); + } + return (ESRCH); +} + +/* + * Search the chain of DAGS cointed to by the given Needed_Entry + * for a symbol of the given name. Each DAG is scanned completely + * before advancing to the next one. Returns a pointer to the symbol, + * or NULL if no definition was found. + */ +static int +symlook_needed(SymLook *req, const Needed_Entry *needed, DoneList *dlp) +{ + const Elf_Sym *def; + const Needed_Entry *n; + const Obj_Entry *defobj; + SymLook req1; + int res; + + def = NULL; + defobj = NULL; + symlook_init_from_req(&req1, req); + for (n = needed; n != NULL; n = n->next) { + if (n->obj == NULL || + (res = symlook_list(&req1, &n->obj->dagmembers, dlp)) != 0) + continue; + if (def == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK) { + def = req1.sym_out; + defobj = req1.defobj_out; + if (ELF_ST_BIND(def->st_info) != STB_WEAK) + break; + } + } + if (def != NULL) { + req->sym_out = def; + req->defobj_out = defobj; + return (0); + } + return (ESRCH); +} + +/* + * Search the symbol table of a single shared object for a symbol of + * the given name and version, if requested. Returns a pointer to the + * symbol, or NULL if no definition was found. If the object is + * filter, return filtered symbol from filtee. + * + * The symbol's hash value is passed in for efficiency reasons; that + * eliminates many recomputations of the hash value. + */ +int +symlook_obj(SymLook *req, const Obj_Entry *obj) +{ + DoneList donelist; + SymLook req1; + int flags, res, mres; + + /* + * If there is at least one valid hash at this point, we prefer to + * use the faster GNU version if available. + */ + if (obj->valid_hash_gnu) + mres = symlook_obj1_gnu(req, obj); + else if (obj->valid_hash_sysv) + mres = symlook_obj1_sysv(req, obj); + else + return (EINVAL); + + if (mres == 0) { + if (obj->needed_filtees != NULL) { + flags = (req->flags & SYMLOOK_EARLY) ? RTLD_LO_EARLY : 0; + load_filtees(__DECONST(Obj_Entry *, obj), flags, req->lockstate); + donelist_init(&donelist); + symlook_init_from_req(&req1, req); + res = symlook_needed(&req1, obj->needed_filtees, &donelist); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + } + return (res); + } + if (obj->needed_aux_filtees != NULL) { + flags = (req->flags & SYMLOOK_EARLY) ? RTLD_LO_EARLY : 0; + load_filtees(__DECONST(Obj_Entry *, obj), flags, req->lockstate); + donelist_init(&donelist); + symlook_init_from_req(&req1, req); + res = symlook_needed(&req1, obj->needed_aux_filtees, &donelist); + if (res == 0) { + req->sym_out = req1.sym_out; + req->defobj_out = req1.defobj_out; + return (res); + } + } + } + return (mres); +} + +/* Symbol match routine common to both hash functions */ +static bool +matched_symbol(SymLook *req, const Obj_Entry *obj, Sym_Match_Result *result, + const unsigned long symnum) +{ + Elf_Versym verndx; + const Elf_Sym *symp; + const char *strp; + + symp = obj->symtab + symnum; + strp = obj->strtab + symp->st_name; + + switch (ELF_ST_TYPE(symp->st_info)) { + case STT_FUNC: + case STT_NOTYPE: + case STT_OBJECT: + case STT_COMMON: + case STT_GNU_IFUNC: + if (symp->st_value == 0) + return (false); + /* fallthrough */ + case STT_TLS: + if (symp->st_shndx != SHN_UNDEF) + break; +#ifndef __mips__ + else if (((req->flags & SYMLOOK_IN_PLT) == 0) && + (ELF_ST_TYPE(symp->st_info) == STT_FUNC)) + break; + /* fallthrough */ +#endif + default: + return (false); + } + if (req->name[0] != strp[0] || strcmp(req->name, strp) != 0) + return (false); + + if (req->ventry == NULL) { + if (obj->versyms != NULL) { + verndx = VER_NDX(obj->versyms[symnum]); + if (verndx > obj->vernum) { + _rtld_error( + "%s: symbol %s references wrong version %d", + obj->path, obj->strtab + symnum, verndx); + return (false); + } + /* + * If we are not called from dlsym (i.e. this + * is a normal relocation from unversioned + * binary), accept the symbol immediately if + * it happens to have first version after this + * shared object became versioned. Otherwise, + * if symbol is versioned and not hidden, + * remember it. If it is the only symbol with + * this name exported by the shared object, it + * will be returned as a match by the calling + * function. If symbol is global (verndx < 2) + * accept it unconditionally. + */ + if ((req->flags & SYMLOOK_DLSYM) == 0 && + verndx == VER_NDX_GIVEN) { + result->sym_out = symp; + return (true); + } + else if (verndx >= VER_NDX_GIVEN) { + if ((obj->versyms[symnum] & VER_NDX_HIDDEN) + == 0) { + if (result->vsymp == NULL) + result->vsymp = symp; + result->vcount++; + } + return (false); + } + } + result->sym_out = symp; + return (true); + } + if (obj->versyms == NULL) { + if (object_match_name(obj, req->ventry->name)) { + _rtld_error("%s: object %s should provide version %s " + "for symbol %s", obj_rtld.path, obj->path, + req->ventry->name, obj->strtab + symnum); + return (false); + } + } else { + verndx = VER_NDX(obj->versyms[symnum]); + if (verndx > obj->vernum) { + _rtld_error("%s: symbol %s references wrong version %d", + obj->path, obj->strtab + symnum, verndx); + return (false); + } + if (obj->vertab[verndx].hash != req->ventry->hash || + strcmp(obj->vertab[verndx].name, req->ventry->name)) { + /* + * Version does not match. Look if this is a + * global symbol and if it is not hidden. If + * global symbol (verndx < 2) is available, + * use it. Do not return symbol if we are + * called by dlvsym, because dlvsym looks for + * a specific version and default one is not + * what dlvsym wants. + */ + if ((req->flags & SYMLOOK_DLSYM) || + (verndx >= VER_NDX_GIVEN) || + (obj->versyms[symnum] & VER_NDX_HIDDEN)) + return (false); + } + } + result->sym_out = symp; + return (true); +} + +/* + * Search for symbol using SysV hash function. + * obj->buckets is known not to be NULL at this point; the test for this was + * performed with the obj->valid_hash_sysv assignment. + */ +static int +symlook_obj1_sysv(SymLook *req, const Obj_Entry *obj) +{ + unsigned long symnum; + Sym_Match_Result matchres; + + matchres.sym_out = NULL; + matchres.vsymp = NULL; + matchres.vcount = 0; + + for (symnum = obj->buckets[req->hash % obj->nbuckets]; + symnum != STN_UNDEF; symnum = obj->chains[symnum]) { + if (symnum >= obj->nchains) + return (ESRCH); /* Bad object */ + + if (matched_symbol(req, obj, &matchres, symnum)) { + req->sym_out = matchres.sym_out; + req->defobj_out = obj; + return (0); + } + } + if (matchres.vcount == 1) { + req->sym_out = matchres.vsymp; + req->defobj_out = obj; + return (0); + } + return (ESRCH); +} + +/* Search for symbol using GNU hash function */ +static int +symlook_obj1_gnu(SymLook *req, const Obj_Entry *obj) +{ + Elf_Addr bloom_word; + const Elf32_Word *hashval; + Elf32_Word bucket; + Sym_Match_Result matchres; + unsigned int h1, h2; + unsigned long symnum; + + matchres.sym_out = NULL; + matchres.vsymp = NULL; + matchres.vcount = 0; + + /* Pick right bitmask word from Bloom filter array */ + bloom_word = obj->bloom_gnu[(req->hash_gnu / __ELF_WORD_SIZE) & + obj->maskwords_bm_gnu]; + + /* Calculate modulus word size of gnu hash and its derivative */ + h1 = req->hash_gnu & (__ELF_WORD_SIZE - 1); + h2 = ((req->hash_gnu >> obj->shift2_gnu) & (__ELF_WORD_SIZE - 1)); + + /* Filter out the "definitely not in set" queries */ + if (((bloom_word >> h1) & (bloom_word >> h2) & 1) == 0) + return (ESRCH); + + /* Locate hash chain and corresponding value element*/ + bucket = obj->buckets_gnu[req->hash_gnu % obj->nbuckets_gnu]; + if (bucket == 0) + return (ESRCH); + hashval = &obj->chain_zero_gnu[bucket]; + do { + if (((*hashval ^ req->hash_gnu) >> 1) == 0) { + symnum = hashval - obj->chain_zero_gnu; + if (matched_symbol(req, obj, &matchres, symnum)) { + req->sym_out = matchres.sym_out; + req->defobj_out = obj; + return (0); + } + } + } while ((*hashval++ & 1) == 0); + if (matchres.vcount == 1) { + req->sym_out = matchres.vsymp; + req->defobj_out = obj; + return (0); + } + return (ESRCH); +} + +static void +trace_loaded_objects(Obj_Entry *obj) +{ + char *fmt1, *fmt2, *fmt, *main_local, *list_containers; + int c; + + if ((main_local = getenv(LD_ "TRACE_LOADED_OBJECTS_PROGNAME")) == NULL) + main_local = ""; + + if ((fmt1 = getenv(LD_ "TRACE_LOADED_OBJECTS_FMT1")) == NULL) + fmt1 = "\t%o => %p (%x)\n"; + + if ((fmt2 = getenv(LD_ "TRACE_LOADED_OBJECTS_FMT2")) == NULL) + fmt2 = "\t%o (%x)\n"; + + list_containers = getenv(LD_ "TRACE_LOADED_OBJECTS_ALL"); + + for (; obj; obj = obj->next) { + Needed_Entry *needed; + char *name, *path; + bool is_lib; + + if (list_containers && obj->needed != NULL) + rtld_printf("%s:\n", obj->path); + for (needed = obj->needed; needed; needed = needed->next) { + if (needed->obj != NULL) { + if (needed->obj->traced && !list_containers) + continue; + needed->obj->traced = true; + path = needed->obj->path; + } else + path = "not found"; + + name = (char *)obj->strtab + needed->name; + is_lib = strncmp(name, "lib", 3) == 0; /* XXX - bogus */ + + fmt = is_lib ? fmt1 : fmt2; + while ((c = *fmt++) != '\0') { + switch (c) { + default: + rtld_putchar(c); + continue; + case '\\': + switch (c = *fmt) { + case '\0': + continue; + case 'n': + rtld_putchar('\n'); + break; + case 't': + rtld_putchar('\t'); + break; + } + break; + case '%': + switch (c = *fmt) { + case '\0': + continue; + case '%': + default: + rtld_putchar(c); + break; + case 'A': + rtld_putstr(main_local); + break; + case 'a': + rtld_putstr(obj_main->path); + break; + case 'o': + rtld_putstr(name); + break; +#if 0 + case 'm': + rtld_printf("%d", sodp->sod_major); + break; + case 'n': + rtld_printf("%d", sodp->sod_minor); + break; +#endif + case 'p': + rtld_putstr(path); + break; + case 'x': + rtld_printf("%p", needed->obj ? needed->obj->mapbase : + 0); + break; + } + break; + } + ++fmt; + } + } + } +} + +/* + * Unload a dlopened object and its dependencies from memory and from + * our data structures. It is assumed that the DAG rooted in the + * object has already been unreferenced, and that the object has a + * reference count of 0. + */ +static void +unload_object(Obj_Entry *root) +{ + Obj_Entry *obj; + Obj_Entry **linkp; + + assert(root->refcount == 0); + + /* + * Pass over the DAG removing unreferenced objects from + * appropriate lists. + */ + unlink_object(root); + + /* Unmap all objects that are no longer referenced. */ + linkp = &obj_list->next; + while ((obj = *linkp) != NULL) { + if (obj->refcount == 0) { + LD_UTRACE(UTRACE_UNLOAD_OBJECT, obj, obj->mapbase, obj->mapsize, 0, + obj->path); + dbg("unloading \"%s\"", obj->path); + unload_filtees(root); + munmap(obj->mapbase, obj->mapsize); + linkmap_delete(obj); + *linkp = obj->next; + obj_count--; + obj_free(obj); + } else + linkp = &obj->next; + } + obj_tail = linkp; +} + +static void +unlink_object(Obj_Entry *root) +{ + Objlist_Entry *elm; + + if (root->refcount == 0) { + /* Remove the object from the RTLD_GLOBAL list. */ + objlist_remove(&list_global, root); + + /* Remove the object from all objects' DAG lists. */ + STAILQ_FOREACH(elm, &root->dagmembers, link) { + objlist_remove(&elm->obj->dldags, root); + if (elm->obj != root) + unlink_object(elm->obj); + } + } +} + +static void +ref_dag(Obj_Entry *root) +{ + Objlist_Entry *elm; + + assert(root->dag_inited); + STAILQ_FOREACH(elm, &root->dagmembers, link) + elm->obj->refcount++; +} + +static void +unref_dag(Obj_Entry *root) +{ + Objlist_Entry *elm; + + assert(root->dag_inited); + STAILQ_FOREACH(elm, &root->dagmembers, link) + elm->obj->refcount--; +} + +/* + * Common code for MD __tls_get_addr(). + */ +static void *tls_get_addr_slow(Elf_Addr **, int, size_t) __noinline; +static void * +tls_get_addr_slow(Elf_Addr **dtvp, int index, size_t offset) +{ + Elf_Addr *newdtv, *dtv; + RtldLockState lockstate; + int to_copy; + + dtv = *dtvp; + /* Check dtv generation in case new modules have arrived */ + if (dtv[0] != tls_dtv_generation) { + wlock_acquire(rtld_bind_lock, &lockstate); + newdtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); + to_copy = dtv[1]; + if (to_copy > tls_max_index) + to_copy = tls_max_index; + memcpy(&newdtv[2], &dtv[2], to_copy * sizeof(Elf_Addr)); + newdtv[0] = tls_dtv_generation; + newdtv[1] = tls_max_index; + free(dtv); + lock_release(rtld_bind_lock, &lockstate); + dtv = *dtvp = newdtv; + } + + /* Dynamically allocate module TLS if necessary */ + if (dtv[index + 1] == 0) { + /* Signal safe, wlock will block out signals. */ + wlock_acquire(rtld_bind_lock, &lockstate); + if (!dtv[index + 1]) + dtv[index + 1] = (Elf_Addr)allocate_module_tls(index); + lock_release(rtld_bind_lock, &lockstate); + } + return ((void *)(dtv[index + 1] + offset)); +} + +void * +tls_get_addr_common(Elf_Addr **dtvp, int index, size_t offset) +{ + Elf_Addr *dtv; + + dtv = *dtvp; + /* Check dtv generation in case new modules have arrived */ + if (__predict_true(dtv[0] == tls_dtv_generation && + dtv[index + 1] != 0)) + return ((void *)(dtv[index + 1] + offset)); + return (tls_get_addr_slow(dtvp, index, offset)); +} + +#if defined(__arm__) || defined(__mips__) || defined(__powerpc__) + +/* + * Allocate Static TLS using the Variant I method. + */ +void * +allocate_tls(Obj_Entry *objs, void *oldtcb, size_t tcbsize, size_t tcbalign) +{ + Obj_Entry *obj; + char *tcb; + Elf_Addr **tls; + Elf_Addr *dtv; + Elf_Addr addr; + int i; + + if (oldtcb != NULL && tcbsize == TLS_TCB_SIZE) + return (oldtcb); + + assert(tcbsize >= TLS_TCB_SIZE); + tcb = xcalloc(1, tls_static_space - TLS_TCB_SIZE + tcbsize); + tls = (Elf_Addr **)(tcb + tcbsize - TLS_TCB_SIZE); + + if (oldtcb != NULL) { + memcpy(tls, oldtcb, tls_static_space); + free(oldtcb); + + /* Adjust the DTV. */ + dtv = tls[0]; + for (i = 0; i < dtv[1]; i++) { + if (dtv[i+2] >= (Elf_Addr)oldtcb && + dtv[i+2] < (Elf_Addr)oldtcb + tls_static_space) { + dtv[i+2] = dtv[i+2] - (Elf_Addr)oldtcb + (Elf_Addr)tls; + } + } + } else { + dtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); + tls[0] = dtv; + dtv[0] = tls_dtv_generation; + dtv[1] = tls_max_index; + + for (obj = objs; obj; obj = obj->next) { + if (obj->tlsoffset > 0) { + addr = (Elf_Addr)tls + obj->tlsoffset; + if (obj->tlsinitsize > 0) + memcpy((void*) addr, obj->tlsinit, obj->tlsinitsize); + if (obj->tlssize > obj->tlsinitsize) + memset((void*) (addr + obj->tlsinitsize), 0, + obj->tlssize - obj->tlsinitsize); + dtv[obj->tlsindex + 1] = addr; + } + } + } + + return (tcb); +} + +void +free_tls(void *tcb, size_t tcbsize, size_t tcbalign) +{ + Elf_Addr *dtv; + Elf_Addr tlsstart, tlsend; + int dtvsize, i; + + assert(tcbsize >= TLS_TCB_SIZE); + + tlsstart = (Elf_Addr)tcb + tcbsize - TLS_TCB_SIZE; + tlsend = tlsstart + tls_static_space; + + dtv = *(Elf_Addr **)tlsstart; + dtvsize = dtv[1]; + for (i = 0; i < dtvsize; i++) { + if (dtv[i+2] && (dtv[i+2] < tlsstart || dtv[i+2] >= tlsend)) { + free((void*)dtv[i+2]); + } + } + free(dtv); + free(tcb); +} + +#endif + +#if defined(__i386__) || defined(__amd64__) || defined(__sparc64__) + +/* + * Allocate Static TLS using the Variant II method. + */ +void * +allocate_tls(Obj_Entry *objs, void *oldtls, size_t tcbsize, size_t tcbalign) +{ + Obj_Entry *obj; + size_t size, ralign; + char *tls; + Elf_Addr *dtv, *olddtv; + Elf_Addr segbase, oldsegbase, addr; + int i; + + ralign = tcbalign; + if (tls_static_max_align > ralign) + ralign = tls_static_max_align; + size = round(tls_static_space, ralign) + round(tcbsize, ralign); + + assert(tcbsize >= 2*sizeof(Elf_Addr)); + tls = malloc_aligned(size, ralign); + dtv = xcalloc(tls_max_index + 2, sizeof(Elf_Addr)); + + segbase = (Elf_Addr)(tls + round(tls_static_space, ralign)); + ((Elf_Addr*)segbase)[0] = segbase; + ((Elf_Addr*)segbase)[1] = (Elf_Addr) dtv; + + dtv[0] = tls_dtv_generation; + dtv[1] = tls_max_index; + + if (oldtls) { + /* + * Copy the static TLS block over whole. + */ + oldsegbase = (Elf_Addr) oldtls; + memcpy((void *)(segbase - tls_static_space), + (const void *)(oldsegbase - tls_static_space), + tls_static_space); + + /* + * If any dynamic TLS blocks have been created tls_get_addr(), + * move them over. + */ + olddtv = ((Elf_Addr**)oldsegbase)[1]; + for (i = 0; i < olddtv[1]; i++) { + if (olddtv[i+2] < oldsegbase - size || olddtv[i+2] > oldsegbase) { + dtv[i+2] = olddtv[i+2]; + olddtv[i+2] = 0; + } + } + + /* + * We assume that this block was the one we created with + * allocate_initial_tls(). + */ + free_tls(oldtls, 2*sizeof(Elf_Addr), sizeof(Elf_Addr)); + } else { + for (obj = objs; obj; obj = obj->next) { + if (obj->tlsoffset) { + addr = segbase - obj->tlsoffset; + memset((void*) (addr + obj->tlsinitsize), + 0, obj->tlssize - obj->tlsinitsize); + if (obj->tlsinit) + memcpy((void*) addr, obj->tlsinit, obj->tlsinitsize); + dtv[obj->tlsindex + 1] = addr; + } + } + } + + return (void*) segbase; +} + +void +free_tls(void *tls, size_t tcbsize, size_t tcbalign) +{ + Elf_Addr* dtv; + size_t size, ralign; + int dtvsize, i; + Elf_Addr tlsstart, tlsend; + + /* + * Figure out the size of the initial TLS block so that we can + * find stuff which ___tls_get_addr() allocated dynamically. + */ + ralign = tcbalign; + if (tls_static_max_align > ralign) + ralign = tls_static_max_align; + size = round(tls_static_space, ralign); + + dtv = ((Elf_Addr**)tls)[1]; + dtvsize = dtv[1]; + tlsend = (Elf_Addr) tls; + tlsstart = tlsend - size; + for (i = 0; i < dtvsize; i++) { + if (dtv[i + 2] != 0 && (dtv[i + 2] < tlsstart || dtv[i + 2] > tlsend)) { + free_aligned((void *)dtv[i + 2]); + } + } + + free_aligned((void *)tlsstart); + free((void*) dtv); +} + +#endif + +/* + * Allocate TLS block for module with given index. + */ +void * +allocate_module_tls(int index) +{ + Obj_Entry* obj; + char* p; + + for (obj = obj_list; obj; obj = obj->next) { + if (obj->tlsindex == index) + break; + } + if (!obj) { + _rtld_error("Can't find module with TLS index %d", index); + die(); + } + + p = malloc_aligned(obj->tlssize, obj->tlsalign); + memcpy(p, obj->tlsinit, obj->tlsinitsize); + memset(p + obj->tlsinitsize, 0, obj->tlssize - obj->tlsinitsize); + + return p; +} + +bool +allocate_tls_offset(Obj_Entry *obj) +{ + size_t off; + + if (obj->tls_done) + return true; + + if (obj->tlssize == 0) { + obj->tls_done = true; + return true; + } + + if (obj->tlsindex == 1) + off = calculate_first_tls_offset(obj->tlssize, obj->tlsalign); + else + off = calculate_tls_offset(tls_last_offset, tls_last_size, + obj->tlssize, obj->tlsalign); + + /* + * If we have already fixed the size of the static TLS block, we + * must stay within that size. When allocating the static TLS, we + * leave a small amount of space spare to be used for dynamically + * loading modules which use static TLS. + */ + if (tls_static_space != 0) { + if (calculate_tls_end(off, obj->tlssize) > tls_static_space) + return false; + } else if (obj->tlsalign > tls_static_max_align) { + tls_static_max_align = obj->tlsalign; + } + + tls_last_offset = obj->tlsoffset = off; + tls_last_size = obj->tlssize; + obj->tls_done = true; + + return true; +} + +void +free_tls_offset(Obj_Entry *obj) +{ + + /* + * If we were the last thing to allocate out of the static TLS + * block, we give our space back to the 'allocator'. This is a + * simplistic workaround to allow libGL.so.1 to be loaded and + * unloaded multiple times. + */ + if (calculate_tls_end(obj->tlsoffset, obj->tlssize) + == calculate_tls_end(tls_last_offset, tls_last_size)) { + tls_last_offset -= obj->tlssize; + tls_last_size = 0; + } +} + +void * +_rtld_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign) +{ + void *ret; + RtldLockState lockstate; + + wlock_acquire(rtld_bind_lock, &lockstate); + ret = allocate_tls(obj_list, oldtls, tcbsize, tcbalign); + lock_release(rtld_bind_lock, &lockstate); + return (ret); +} + +void +_rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign) +{ + RtldLockState lockstate; + + wlock_acquire(rtld_bind_lock, &lockstate); + free_tls(tcb, tcbsize, tcbalign); + lock_release(rtld_bind_lock, &lockstate); +} + +static void +object_add_name(Obj_Entry *obj, const char *name) +{ + Name_Entry *entry; + size_t len; + + len = strlen(name); + entry = malloc(sizeof(Name_Entry) + len); + + if (entry != NULL) { + strcpy(entry->name, name); + STAILQ_INSERT_TAIL(&obj->names, entry, link); + } +} + +static int +object_match_name(const Obj_Entry *obj, const char *name) +{ + Name_Entry *entry; + + STAILQ_FOREACH(entry, &obj->names, link) { + if (strcmp(name, entry->name) == 0) + return (1); + } + return (0); +} + +static Obj_Entry * +locate_dependency(const Obj_Entry *obj, const char *name) +{ + const Objlist_Entry *entry; + const Needed_Entry *needed; + + STAILQ_FOREACH(entry, &list_main, link) { + if (object_match_name(entry->obj, name)) + return entry->obj; + } + + for (needed = obj->needed; needed != NULL; needed = needed->next) { + if (strcmp(obj->strtab + needed->name, name) == 0 || + (needed->obj != NULL && object_match_name(needed->obj, name))) { + /* + * If there is DT_NEEDED for the name we are looking for, + * we are all set. Note that object might not be found if + * dependency was not loaded yet, so the function can + * return NULL here. This is expected and handled + * properly by the caller. + */ + return (needed->obj); + } + } + _rtld_error("%s: Unexpected inconsistency: dependency %s not found", + obj->path, name); + die(); +} + +static int +check_object_provided_version(Obj_Entry *refobj, const Obj_Entry *depobj, + const Elf_Vernaux *vna) +{ + const Elf_Verdef *vd; + const char *vername; + + vername = refobj->strtab + vna->vna_name; + vd = depobj->verdef; + if (vd == NULL) { + _rtld_error("%s: version %s required by %s not defined", + depobj->path, vername, refobj->path); + return (-1); + } + for (;;) { + if (vd->vd_version != VER_DEF_CURRENT) { + _rtld_error("%s: Unsupported version %d of Elf_Verdef entry", + depobj->path, vd->vd_version); + return (-1); + } + if (vna->vna_hash == vd->vd_hash) { + const Elf_Verdaux *aux = (const Elf_Verdaux *) + ((char *)vd + vd->vd_aux); + if (strcmp(vername, depobj->strtab + aux->vda_name) == 0) + return (0); + } + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + if (vna->vna_flags & VER_FLG_WEAK) + return (0); + _rtld_error("%s: version %s required by %s not found", + depobj->path, vername, refobj->path); + return (-1); +} + +static int +rtld_verify_object_versions(Obj_Entry *obj) +{ + const Elf_Verneed *vn; + const Elf_Verdef *vd; + const Elf_Verdaux *vda; + const Elf_Vernaux *vna; + const Obj_Entry *depobj; + int maxvernum, vernum; + + if (obj->ver_checked) + return (0); + obj->ver_checked = true; + + maxvernum = 0; + /* + * Walk over defined and required version records and figure out + * max index used by any of them. Do very basic sanity checking + * while there. + */ + vn = obj->verneed; + while (vn != NULL) { + if (vn->vn_version != VER_NEED_CURRENT) { + _rtld_error("%s: Unsupported version %d of Elf_Verneed entry", + obj->path, vn->vn_version); + return (-1); + } + vna = (const Elf_Vernaux *) ((char *)vn + vn->vn_aux); + for (;;) { + vernum = VER_NEED_IDX(vna->vna_other); + if (vernum > maxvernum) + maxvernum = vernum; + if (vna->vna_next == 0) + break; + vna = (const Elf_Vernaux *) ((char *)vna + vna->vna_next); + } + if (vn->vn_next == 0) + break; + vn = (const Elf_Verneed *) ((char *)vn + vn->vn_next); + } + + vd = obj->verdef; + while (vd != NULL) { + if (vd->vd_version != VER_DEF_CURRENT) { + _rtld_error("%s: Unsupported version %d of Elf_Verdef entry", + obj->path, vd->vd_version); + return (-1); + } + vernum = VER_DEF_IDX(vd->vd_ndx); + if (vernum > maxvernum) + maxvernum = vernum; + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + + if (maxvernum == 0) + return (0); + + /* + * Store version information in array indexable by version index. + * Verify that object version requirements are satisfied along the + * way. + */ + obj->vernum = maxvernum + 1; + obj->vertab = xcalloc(obj->vernum, sizeof(Ver_Entry)); + + vd = obj->verdef; + while (vd != NULL) { + if ((vd->vd_flags & VER_FLG_BASE) == 0) { + vernum = VER_DEF_IDX(vd->vd_ndx); + assert(vernum <= maxvernum); + vda = (const Elf_Verdaux *)((char *)vd + vd->vd_aux); + obj->vertab[vernum].hash = vd->vd_hash; + obj->vertab[vernum].name = obj->strtab + vda->vda_name; + obj->vertab[vernum].file = NULL; + obj->vertab[vernum].flags = 0; + } + if (vd->vd_next == 0) + break; + vd = (const Elf_Verdef *) ((char *)vd + vd->vd_next); + } + + vn = obj->verneed; + while (vn != NULL) { + depobj = locate_dependency(obj, obj->strtab + vn->vn_file); + if (depobj == NULL) + return (-1); + vna = (const Elf_Vernaux *) ((char *)vn + vn->vn_aux); + for (;;) { + if (check_object_provided_version(obj, depobj, vna)) + return (-1); + vernum = VER_NEED_IDX(vna->vna_other); + assert(vernum <= maxvernum); + obj->vertab[vernum].hash = vna->vna_hash; + obj->vertab[vernum].name = obj->strtab + vna->vna_name; + obj->vertab[vernum].file = obj->strtab + vn->vn_file; + obj->vertab[vernum].flags = (vna->vna_other & VER_NEED_HIDDEN) ? + VER_INFO_HIDDEN : 0; + if (vna->vna_next == 0) + break; + vna = (const Elf_Vernaux *) ((char *)vna + vna->vna_next); + } + if (vn->vn_next == 0) + break; + vn = (const Elf_Verneed *) ((char *)vn + vn->vn_next); + } + return 0; +} + +static int +rtld_verify_versions(const Objlist *objlist) +{ + Objlist_Entry *entry; + int rc; + + rc = 0; + STAILQ_FOREACH(entry, objlist, link) { + /* + * Skip dummy objects or objects that have their version requirements + * already checked. + */ + if (entry->obj->strtab == NULL || entry->obj->vertab != NULL) + continue; + if (rtld_verify_object_versions(entry->obj) == -1) { + rc = -1; + if (ld_tracing == NULL) + break; + } + } + if (rc == 0 || ld_tracing != NULL) + rc = rtld_verify_object_versions(&obj_rtld); + return rc; +} + +const Ver_Entry * +fetch_ventry(const Obj_Entry *obj, unsigned long symnum) +{ + Elf_Versym vernum; + + if (obj->vertab) { + vernum = VER_NDX(obj->versyms[symnum]); + if (vernum >= obj->vernum) { + _rtld_error("%s: symbol %s has wrong verneed value %d", + obj->path, obj->strtab + symnum, vernum); + } else if (obj->vertab[vernum].hash != 0) { + return &obj->vertab[vernum]; + } + } + return NULL; +} + +int +_rtld_get_stack_prot(void) +{ + + return (stack_prot); +} + +static void +map_stacks_exec(RtldLockState *lockstate) +{ + void (*thr_map_stacks_exec)(void); + + if ((max_stack_flags & PF_X) == 0 || (stack_prot & PROT_EXEC) != 0) + return; + thr_map_stacks_exec = (void (*)(void))(uintptr_t) + get_program_var_addr("__pthread_map_stacks_exec", lockstate); + if (thr_map_stacks_exec != NULL) { + stack_prot |= PROT_EXEC; + thr_map_stacks_exec(); + } +} + +void +symlook_init(SymLook *dst, const char *name) +{ + + bzero(dst, sizeof(*dst)); + dst->name = name; + dst->hash = elf_hash(name); + dst->hash_gnu = gnu_hash(name); +} + +static void +symlook_init_from_req(SymLook *dst, const SymLook *src) +{ + + dst->name = src->name; + dst->hash = src->hash; + dst->hash_gnu = src->hash_gnu; + dst->ventry = src->ventry; + dst->flags = src->flags; + dst->defobj_out = NULL; + dst->sym_out = NULL; + dst->lockstate = src->lockstate; +} + + +/* + * Parse a file descriptor number without pulling in more of libc (e.g. atoi). + */ +static int +parse_libdir(const char *str) +{ + static const int RADIX = 10; /* XXXJA: possibly support hex? */ + const char *orig; + int fd; + char c; + + orig = str; + fd = 0; + for (c = *str; c != '\0'; c = *++str) { + if (c < '0' || c > '9') + return (-1); + + fd *= RADIX; + fd += c - '0'; + } + + /* Make sure we actually parsed something. */ + if (str == orig) { + _rtld_error("failed to parse directory FD from '%s'", str); + return (-1); + } + return (fd); +} + +/* + * Overrides for libc_pic-provided functions. + */ + +int +__getosreldate(void) +{ + size_t len; + int oid[2]; + int error, osrel; + + if (osreldate != 0) + return (osreldate); + + oid[0] = CTL_KERN; + oid[1] = KERN_OSRELDATE; + osrel = 0; + len = sizeof(osrel); + error = sysctl(oid, 2, &osrel, &len, NULL, 0); + if (error == 0 && osrel > 0 && len == sizeof(osrel)) + osreldate = osrel; + return (osreldate); +} + +void +exit(int status) +{ + + _exit(status); +} + +void (*__cleanup)(void); +int __isthreaded = 0; +int _thread_autoinit_dummy_decl = 1; + +/* + * No unresolved symbols for rtld. + */ +void +__pthread_cxa_finalize(struct dl_phdr_info *a) +{ +} + +void +__stack_chk_fail(void) +{ + + _rtld_error("stack overflow detected; terminated"); + die(); +} +__weak_reference(__stack_chk_fail, __stack_chk_fail_local); + +void +__chk_fail(void) +{ + + _rtld_error("buffer overflow detected; terminated"); + die(); +} + +const char * +rtld_strerror(int errnum) +{ + + if (errnum < 0 || errnum >= sys_nerr) + return ("Unknown error"); + return (sys_errlist[errnum]); +} diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h new file mode 100644 index 0000000..ace229f --- /dev/null +++ b/libexec/rtld-elf/rtld.h @@ -0,0 +1,406 @@ +/*- + * Copyright 1996, 1997, 1998, 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_H /* { */ +#define RTLD_H 1 + +#include <machine/elf.h> +#include <sys/types.h> +#include <sys/queue.h> + +#include <elf-hints.h> +#include <link.h> +#include <stdarg.h> +#include <setjmp.h> +#include <stddef.h> + +#include "rtld_lock.h" +#include "rtld_machdep.h" + +#ifdef COMPAT_32BIT +#undef STANDARD_LIBRARY_PATH +#undef _PATH_ELF_HINTS +#define _PATH_ELF_HINTS "/var/run/ld-elf32.so.hints" +/* For running 32 bit binaries */ +#define STANDARD_LIBRARY_PATH "/lib32:/usr/lib32" +#define LD_ "LD_32_" +#endif + +#ifndef STANDARD_LIBRARY_PATH +#define STANDARD_LIBRARY_PATH "/lib:/usr/lib" +#endif +#ifndef LD_ +#define LD_ "LD_" +#endif + +#define NEW(type) ((type *) xmalloc(sizeof(type))) +#define CNEW(type) ((type *) xcalloc(1, sizeof(type))) + +/* We might as well do booleans like C++. */ +typedef unsigned char bool; +#define false 0 +#define true 1 + +extern size_t tls_last_offset; +extern size_t tls_last_size; +extern size_t tls_static_space; +extern int tls_dtv_generation; +extern int tls_max_index; + +extern int npagesizes; +extern size_t *pagesizes; + +extern int main_argc; +extern char **main_argv; +extern char **environ; + +struct stat; +struct Struct_Obj_Entry; + +/* Lists of shared objects */ +typedef struct Struct_Objlist_Entry { + STAILQ_ENTRY(Struct_Objlist_Entry) link; + struct Struct_Obj_Entry *obj; +} Objlist_Entry; + +typedef STAILQ_HEAD(Struct_Objlist, Struct_Objlist_Entry) Objlist; + +/* Types of init and fini functions */ +typedef void (*InitFunc)(void); +typedef void (*InitArrFunc)(int, char **, char **); + +/* Lists of shared object dependencies */ +typedef struct Struct_Needed_Entry { + struct Struct_Needed_Entry *next; + struct Struct_Obj_Entry *obj; + unsigned long name; /* Offset of name in string table */ +} Needed_Entry; + +typedef struct Struct_Name_Entry { + STAILQ_ENTRY(Struct_Name_Entry) link; + char name[1]; +} Name_Entry; + +/* Lock object */ +typedef struct Struct_LockInfo { + void *context; /* Client context for creating locks */ + void *thelock; /* The one big lock */ + /* Debugging aids. */ + volatile int rcount; /* Number of readers holding lock */ + volatile int wcount; /* Number of writers holding lock */ + /* Methods */ + void *(*lock_create)(void *context); + void (*rlock_acquire)(void *lock); + void (*wlock_acquire)(void *lock); + void (*rlock_release)(void *lock); + void (*wlock_release)(void *lock); + void (*lock_destroy)(void *lock); + void (*context_destroy)(void *context); +} LockInfo; + +typedef struct Struct_Ver_Entry { + Elf_Word hash; + unsigned int flags; + const char *name; + const char *file; +} Ver_Entry; + +typedef struct Struct_Sym_Match_Result { + const Elf_Sym *sym_out; + const Elf_Sym *vsymp; + int vcount; +} Sym_Match_Result; + +#define VER_INFO_HIDDEN 0x01 + +/* + * Shared object descriptor. + * + * Items marked with "(%)" are dynamically allocated, and must be freed + * when the structure is destroyed. + * + * CAUTION: It appears that the JDK port peeks into these structures. + * It looks at "next" and "mapbase" at least. Don't add new members + * near the front, until this can be straightened out. + */ +typedef struct Struct_Obj_Entry { + /* + * These two items have to be set right for compatibility with the + * original ElfKit crt1.o. + */ + Elf_Size magic; /* Magic number (sanity check) */ + Elf_Size version; /* Version number of struct format */ + + struct Struct_Obj_Entry *next; + char *path; /* Pathname of underlying file (%) */ + char *origin_path; /* Directory path of origin file */ + int refcount; + int dl_refcount; /* Number of times loaded by dlopen */ + + /* These items are computed by map_object() or by digest_phdr(). */ + caddr_t mapbase; /* Base address of mapped region */ + size_t mapsize; /* Size of mapped region in bytes */ + size_t textsize; /* Size of text segment in bytes */ + Elf_Addr vaddrbase; /* Base address in shared object file */ + caddr_t relocbase; /* Relocation constant = mapbase - vaddrbase */ + const Elf_Dyn *dynamic; /* Dynamic section */ + caddr_t entry; /* Entry point */ + const Elf_Phdr *phdr; /* Program header if it is mapped, else NULL */ + size_t phsize; /* Size of program header in bytes */ + const char *interp; /* Pathname of the interpreter, if any */ + Elf_Word stack_flags; + + /* TLS information */ + int tlsindex; /* Index in DTV for this module */ + void *tlsinit; /* Base address of TLS init block */ + size_t tlsinitsize; /* Size of TLS init block for this module */ + size_t tlssize; /* Size of TLS block for this module */ + size_t tlsoffset; /* Offset of static TLS block for this module */ + size_t tlsalign; /* Alignment of static TLS block */ + + caddr_t relro_page; + size_t relro_size; + + /* Items from the dynamic section. */ + Elf_Addr *pltgot; /* PLT or GOT, depending on architecture */ + const Elf_Rel *rel; /* Relocation entries */ + unsigned long relsize; /* Size in bytes of relocation info */ + const Elf_Rela *rela; /* Relocation entries with addend */ + unsigned long relasize; /* Size in bytes of addend relocation info */ + const Elf_Rel *pltrel; /* PLT relocation entries */ + unsigned long pltrelsize; /* Size in bytes of PLT relocation info */ + const Elf_Rela *pltrela; /* PLT relocation entries with addend */ + unsigned long pltrelasize; /* Size in bytes of PLT addend reloc info */ + const Elf_Sym *symtab; /* Symbol table */ + const char *strtab; /* String table */ + unsigned long strsize; /* Size in bytes of string table */ +#ifdef __mips__ + Elf_Word local_gotno; /* Number of local GOT entries */ + Elf_Word symtabno; /* Number of dynamic symbols */ + Elf_Word gotsym; /* First dynamic symbol in GOT */ +#endif + + const Elf_Verneed *verneed; /* Required versions. */ + Elf_Word verneednum; /* Number of entries in verneed table */ + const Elf_Verdef *verdef; /* Provided versions. */ + Elf_Word verdefnum; /* Number of entries in verdef table */ + const Elf_Versym *versyms; /* Symbol versions table */ + + const Elf_Hashelt *buckets; /* Hash table buckets array */ + unsigned long nbuckets; /* Number of buckets */ + const Elf_Hashelt *chains; /* Hash table chain array */ + unsigned long nchains; /* Number of entries in chain array */ + + Elf32_Word nbuckets_gnu; /* Number of GNU hash buckets*/ + Elf32_Word symndx_gnu; /* 1st accessible symbol on dynsym table */ + Elf32_Word maskwords_bm_gnu; /* Bloom filter words - 1 (bitmask) */ + Elf32_Word shift2_gnu; /* Bloom filter shift count */ + Elf32_Word dynsymcount; /* Total entries in dynsym table */ + Elf_Addr *bloom_gnu; /* Bloom filter used by GNU hash func */ + const Elf_Hashelt *buckets_gnu; /* GNU hash table bucket array */ + const Elf_Hashelt *chain_zero_gnu; /* GNU hash table value array (Zeroed) */ + + char *rpath; /* Search path specified in object */ + char *runpath; /* Search path with different priority */ + Needed_Entry *needed; /* Shared objects needed by this one (%) */ + Needed_Entry *needed_filtees; + Needed_Entry *needed_aux_filtees; + + STAILQ_HEAD(, Struct_Name_Entry) names; /* List of names for this object we + know about. */ + Ver_Entry *vertab; /* Versions required /defined by this object */ + int vernum; /* Number of entries in vertab */ + + Elf_Addr init; /* Initialization function to call */ + Elf_Addr fini; /* Termination function to call */ + Elf_Addr preinit_array; /* Pre-initialization array of functions */ + Elf_Addr init_array; /* Initialization array of functions */ + Elf_Addr fini_array; /* Termination array of functions */ + int preinit_array_num; /* Number of entries in preinit_array */ + int init_array_num; /* Number of entries in init_array */ + int fini_array_num; /* Number of entries in fini_array */ + + int32_t osrel; /* OSREL note value */ + + bool mainprog : 1; /* True if this is the main program */ + bool rtld : 1; /* True if this is the dynamic linker */ + bool relocated : 1; /* True if processed by relocate_objects() */ + bool ver_checked : 1; /* True if processed by rtld_verify_object_versions */ + bool textrel : 1; /* True if there are relocations to text seg */ + bool symbolic : 1; /* True if generated with "-Bsymbolic" */ + bool bind_now : 1; /* True if all relocations should be made first */ + bool traced : 1; /* Already printed in ldd trace output */ + bool jmpslots_done : 1; /* Already have relocated the jump slots */ + bool init_done : 1; /* Already have added object to init list */ + bool tls_done : 1; /* Already allocated offset for static TLS */ + bool phdr_alloc : 1; /* Phdr is allocated and needs to be freed. */ + bool z_origin : 1; /* Process rpath and soname tokens */ + bool z_nodelete : 1; /* Do not unload the object and dependencies */ + bool z_noopen : 1; /* Do not load on dlopen */ + bool z_loadfltr : 1; /* Immediately load filtees */ + bool z_interpose : 1; /* Interpose all objects but main */ + bool z_nodeflib : 1; /* Don't search default library path */ + bool ref_nodel : 1; /* Refcount increased to prevent dlclose */ + bool init_scanned: 1; /* Object is already on init list. */ + bool on_fini_list: 1; /* Object is already on fini list. */ + bool dag_inited : 1; /* Object has its DAG initialized. */ + bool filtees_loaded : 1; /* Filtees loaded */ + bool irelative : 1; /* Object has R_MACHDEP_IRELATIVE relocs */ + bool gnu_ifunc : 1; /* Object has references to STT_GNU_IFUNC */ + bool non_plt_gnu_ifunc : 1; /* Object has non-plt IFUNC references */ + bool crt_no_init : 1; /* Object' crt does not call _init/_fini */ + bool valid_hash_sysv : 1; /* A valid System V hash hash tag is available */ + bool valid_hash_gnu : 1; /* A valid GNU hash tag is available */ + + struct link_map linkmap; /* For GDB and dlinfo() */ + Objlist dldags; /* Object belongs to these dlopened DAGs (%) */ + Objlist dagmembers; /* DAG has these members (%) */ + dev_t dev; /* Object's filesystem's device */ + ino_t ino; /* Object's inode number */ + void *priv; /* Platform-dependent */ +} Obj_Entry; + +#define RTLD_MAGIC 0xd550b87a +#define RTLD_VERSION 1 + +#define RTLD_STATIC_TLS_EXTRA 128 + +/* Flags to be passed into symlook_ family of functions. */ +#define SYMLOOK_IN_PLT 0x01 /* Lookup for PLT symbol */ +#define SYMLOOK_DLSYM 0x02 /* Return newest versioned symbol. Used by + dlsym. */ +#define SYMLOOK_EARLY 0x04 /* Symlook is done during initialization. */ +#define SYMLOOK_IFUNC 0x08 /* Allow IFUNC processing in + reloc_non_plt(). */ + +/* Flags for load_object(). */ +#define RTLD_LO_NOLOAD 0x01 /* dlopen() specified RTLD_NOLOAD. */ +#define RTLD_LO_DLOPEN 0x02 /* Load_object() called from dlopen(). */ +#define RTLD_LO_TRACE 0x04 /* Only tracing. */ +#define RTLD_LO_NODELETE 0x08 /* Loaded object cannot be closed. */ +#define RTLD_LO_FILTEES 0x10 /* Loading filtee. */ +#define RTLD_LO_EARLY 0x20 /* Do not call ctors, postpone it to the + initialization during the image start. */ + +/* + * Symbol cache entry used during relocation to avoid multiple lookups + * of the same symbol. + */ +typedef struct Struct_SymCache { + const Elf_Sym *sym; /* Symbol table entry */ + const Obj_Entry *obj; /* Shared object which defines it */ +} SymCache; + +/* + * This structure provides a reentrant way to keep a list of objects and + * check which ones have already been processed in some way. + */ +typedef struct Struct_DoneList { + const Obj_Entry **objs; /* Array of object pointers */ + unsigned int num_alloc; /* Allocated size of the array */ + unsigned int num_used; /* Number of array slots used */ +} DoneList; + +struct Struct_RtldLockState { + int lockstate; + sigjmp_buf env; +}; + +struct fill_search_info_args { + int request; + unsigned int flags; + struct dl_serinfo *serinfo; + struct dl_serpath *serpath; + char *strspace; +}; + +/* + * The pack of arguments and results for the symbol lookup functions. + */ +typedef struct Struct_SymLook { + const char *name; + unsigned long hash; + uint32_t hash_gnu; + const Ver_Entry *ventry; + int flags; + const Obj_Entry *defobj_out; + const Elf_Sym *sym_out; + struct Struct_RtldLockState *lockstate; +} SymLook; + +void _rtld_error(const char *, ...) __printflike(1, 2); +const char *rtld_strerror(int); +Obj_Entry *map_object(int, const char *, const struct stat *); +void *xcalloc(size_t, size_t); +void *xmalloc(size_t); +char *xstrdup(const char *); +void *malloc_aligned(size_t size, size_t align); +void free_aligned(void *ptr); +extern Elf_Addr _GLOBAL_OFFSET_TABLE_[]; +extern Elf_Sym sym_zero; /* For resolving undefined weak refs. */ + +void dump_relocations(Obj_Entry *); +void dump_obj_relocations(Obj_Entry *); +void dump_Elf_Rel(Obj_Entry *, const Elf_Rel *, u_long); +void dump_Elf_Rela(Obj_Entry *, const Elf_Rela *, u_long); + +/* + * Function declarations. + */ +unsigned long elf_hash(const char *); +const Elf_Sym *find_symdef(unsigned long, const Obj_Entry *, + const Obj_Entry **, int, SymCache *, struct Struct_RtldLockState *); +void init_pltgot(Obj_Entry *); +void lockdflt_init(void); +void digest_notes(Obj_Entry *, Elf_Addr, Elf_Addr); +void obj_free(Obj_Entry *); +Obj_Entry *obj_new(void); +void _rtld_bind_start(void); +void *rtld_resolve_ifunc(const Obj_Entry *obj, const Elf_Sym *def); +void symlook_init(SymLook *, const char *); +int symlook_obj(SymLook *, const Obj_Entry *); +void *tls_get_addr_common(Elf_Addr** dtvp, int index, size_t offset); +void *allocate_tls(Obj_Entry *, void *, size_t, size_t); +void free_tls(void *, size_t, size_t); +void *allocate_module_tls(int index); +bool allocate_tls_offset(Obj_Entry *obj); +void free_tls_offset(Obj_Entry *obj); +const Ver_Entry *fetch_ventry(const Obj_Entry *obj, unsigned long); + +/* + * MD function declarations. + */ +int do_copy_relocations(Obj_Entry *); +int reloc_non_plt(Obj_Entry *, Obj_Entry *, int flags, + struct Struct_RtldLockState *); +int reloc_plt(Obj_Entry *); +int reloc_jmpslots(Obj_Entry *, int flags, struct Struct_RtldLockState *); +int reloc_iresolve(Obj_Entry *, struct Struct_RtldLockState *); +int reloc_gnu_ifunc(Obj_Entry *, int flags, struct Struct_RtldLockState *); +void allocate_initial_tls(Obj_Entry *); + +#endif /* } */ diff --git a/libexec/rtld-elf/rtld_lock.c b/libexec/rtld-elf/rtld_lock.c new file mode 100644 index 0000000..ea16c4d --- /dev/null +++ b/libexec/rtld-elf/rtld_lock.c @@ -0,0 +1,400 @@ +/*- + * Copyright 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * from: FreeBSD: src/libexec/rtld-elf/sparc64/lockdflt.c,v 1.3 2002/10/09 + * $FreeBSD$ + */ + +/* + * Thread locking implementation for the dynamic linker. + * + * We use the "simple, non-scalable reader-preference lock" from: + * + * J. M. Mellor-Crummey and M. L. Scott. "Scalable Reader-Writer + * Synchronization for Shared-Memory Multiprocessors." 3rd ACM Symp. on + * Principles and Practice of Parallel Programming, April 1991. + * + * In this algorithm the lock is a single word. Its low-order bit is + * set when a writer holds the lock. The remaining high-order bits + * contain a count of readers desiring the lock. The algorithm requires + * atomic "compare_and_store" and "add" operations, which we implement + * using assembly language sequences in "rtld_start.S". + */ + +#include <sys/param.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> + +#include "debug.h" +#include "rtld.h" +#include "rtld_machdep.h" + +#define WAFLAG 0x1 /* A writer holds the lock */ +#define RC_INCR 0x2 /* Adjusts count of readers desiring lock */ + +typedef struct Struct_Lock { + volatile u_int lock; + void *base; +} Lock; + +static sigset_t fullsigmask, oldsigmask; +static int thread_flag; + +static void * +def_lock_create() +{ + void *base; + char *p; + uintptr_t r; + Lock *l; + + /* + * Arrange for the lock to occupy its own cache line. First, we + * optimistically allocate just a cache line, hoping that malloc + * will give us a well-aligned block of memory. If that doesn't + * work, we allocate a larger block and take a well-aligned cache + * line from it. + */ + base = xmalloc(CACHE_LINE_SIZE); + p = (char *)base; + if ((uintptr_t)p % CACHE_LINE_SIZE != 0) { + free(base); + base = xmalloc(2 * CACHE_LINE_SIZE); + p = (char *)base; + if ((r = (uintptr_t)p % CACHE_LINE_SIZE) != 0) + p += CACHE_LINE_SIZE - r; + } + l = (Lock *)p; + l->base = base; + l->lock = 0; + return l; +} + +static void +def_lock_destroy(void *lock) +{ + Lock *l = (Lock *)lock; + + free(l->base); +} + +static void +def_rlock_acquire(void *lock) +{ + Lock *l = (Lock *)lock; + + atomic_add_acq_int(&l->lock, RC_INCR); + while (l->lock & WAFLAG) + ; /* Spin */ +} + +static void +def_wlock_acquire(void *lock) +{ + Lock *l = (Lock *)lock; + sigset_t tmp_oldsigmask; + + for ( ; ; ) { + sigprocmask(SIG_BLOCK, &fullsigmask, &tmp_oldsigmask); + if (atomic_cmpset_acq_int(&l->lock, 0, WAFLAG)) + break; + sigprocmask(SIG_SETMASK, &tmp_oldsigmask, NULL); + } + oldsigmask = tmp_oldsigmask; +} + +static void +def_lock_release(void *lock) +{ + Lock *l = (Lock *)lock; + + if ((l->lock & WAFLAG) == 0) + atomic_add_rel_int(&l->lock, -RC_INCR); + else { + atomic_add_rel_int(&l->lock, -WAFLAG); + sigprocmask(SIG_SETMASK, &oldsigmask, NULL); + } +} + +static int +def_thread_set_flag(int mask) +{ + int old_val = thread_flag; + thread_flag |= mask; + return (old_val); +} + +static int +def_thread_clr_flag(int mask) +{ + int old_val = thread_flag; + thread_flag &= ~mask; + return (old_val); +} + +/* + * Public interface exposed to the rest of the dynamic linker. + */ +static struct RtldLockInfo lockinfo; +static struct RtldLockInfo deflockinfo; + +static __inline int +thread_mask_set(int mask) +{ + return lockinfo.thread_set_flag(mask); +} + +static __inline void +thread_mask_clear(int mask) +{ + lockinfo.thread_clr_flag(mask); +} + +#define RTLD_LOCK_CNT 3 +struct rtld_lock { + void *handle; + int mask; +} rtld_locks[RTLD_LOCK_CNT]; + +rtld_lock_t rtld_bind_lock = &rtld_locks[0]; +rtld_lock_t rtld_libc_lock = &rtld_locks[1]; +rtld_lock_t rtld_phdr_lock = &rtld_locks[2]; + +void +rlock_acquire(rtld_lock_t lock, RtldLockState *lockstate) +{ + + if (lockstate == NULL) + return; + + if (thread_mask_set(lock->mask) & lock->mask) { + dbg("rlock_acquire: recursed"); + lockstate->lockstate = RTLD_LOCK_UNLOCKED; + return; + } + lockinfo.rlock_acquire(lock->handle); + lockstate->lockstate = RTLD_LOCK_RLOCKED; +} + +void +wlock_acquire(rtld_lock_t lock, RtldLockState *lockstate) +{ + + if (lockstate == NULL) + return; + + if (thread_mask_set(lock->mask) & lock->mask) { + dbg("wlock_acquire: recursed"); + lockstate->lockstate = RTLD_LOCK_UNLOCKED; + return; + } + lockinfo.wlock_acquire(lock->handle); + lockstate->lockstate = RTLD_LOCK_WLOCKED; +} + +void +lock_release(rtld_lock_t lock, RtldLockState *lockstate) +{ + + if (lockstate == NULL) + return; + + switch (lockstate->lockstate) { + case RTLD_LOCK_UNLOCKED: + break; + case RTLD_LOCK_RLOCKED: + case RTLD_LOCK_WLOCKED: + thread_mask_clear(lock->mask); + lockinfo.lock_release(lock->handle); + break; + default: + assert(0); + } +} + +void +lock_upgrade(rtld_lock_t lock, RtldLockState *lockstate) +{ + + if (lockstate == NULL) + return; + + lock_release(lock, lockstate); + wlock_acquire(lock, lockstate); +} + +void +lock_restart_for_upgrade(RtldLockState *lockstate) +{ + + if (lockstate == NULL) + return; + + switch (lockstate->lockstate) { + case RTLD_LOCK_UNLOCKED: + case RTLD_LOCK_WLOCKED: + break; + case RTLD_LOCK_RLOCKED: + siglongjmp(lockstate->env, 1); + break; + default: + assert(0); + } +} + +void +lockdflt_init() +{ + int i; + + deflockinfo.rtli_version = RTLI_VERSION; + deflockinfo.lock_create = def_lock_create; + deflockinfo.lock_destroy = def_lock_destroy; + deflockinfo.rlock_acquire = def_rlock_acquire; + deflockinfo.wlock_acquire = def_wlock_acquire; + deflockinfo.lock_release = def_lock_release; + deflockinfo.thread_set_flag = def_thread_set_flag; + deflockinfo.thread_clr_flag = def_thread_clr_flag; + deflockinfo.at_fork = NULL; + + for (i = 0; i < RTLD_LOCK_CNT; i++) { + rtld_locks[i].mask = (1 << i); + rtld_locks[i].handle = NULL; + } + + memcpy(&lockinfo, &deflockinfo, sizeof(lockinfo)); + _rtld_thread_init(NULL); + /* + * Construct a mask to block all signals except traps which might + * conceivably be generated within the dynamic linker itself. + */ + sigfillset(&fullsigmask); + sigdelset(&fullsigmask, SIGILL); + sigdelset(&fullsigmask, SIGTRAP); + sigdelset(&fullsigmask, SIGABRT); + sigdelset(&fullsigmask, SIGEMT); + sigdelset(&fullsigmask, SIGFPE); + sigdelset(&fullsigmask, SIGBUS); + sigdelset(&fullsigmask, SIGSEGV); + sigdelset(&fullsigmask, SIGSYS); +} + +/* + * Callback function to allow threads implementation to + * register their own locking primitives if the default + * one is not suitable. + * The current context should be the only context + * executing at the invocation time. + */ +void +_rtld_thread_init(struct RtldLockInfo *pli) +{ + int flags, i; + void *locks[RTLD_LOCK_CNT]; + + /* disable all locking while this function is running */ + flags = thread_mask_set(~0); + + if (pli == NULL) + pli = &deflockinfo; + + + for (i = 0; i < RTLD_LOCK_CNT; i++) + if ((locks[i] = pli->lock_create()) == NULL) + break; + + if (i < RTLD_LOCK_CNT) { + while (--i >= 0) + pli->lock_destroy(locks[i]); + abort(); + } + + for (i = 0; i < RTLD_LOCK_CNT; i++) { + if (rtld_locks[i].handle == NULL) + continue; + if (flags & rtld_locks[i].mask) + lockinfo.lock_release(rtld_locks[i].handle); + lockinfo.lock_destroy(rtld_locks[i].handle); + } + + for (i = 0; i < RTLD_LOCK_CNT; i++) { + rtld_locks[i].handle = locks[i]; + if (flags & rtld_locks[i].mask) + pli->wlock_acquire(rtld_locks[i].handle); + } + + lockinfo.lock_create = pli->lock_create; + lockinfo.lock_destroy = pli->lock_destroy; + lockinfo.rlock_acquire = pli->rlock_acquire; + lockinfo.wlock_acquire = pli->wlock_acquire; + lockinfo.lock_release = pli->lock_release; + lockinfo.thread_set_flag = pli->thread_set_flag; + lockinfo.thread_clr_flag = pli->thread_clr_flag; + lockinfo.at_fork = pli->at_fork; + + /* restore thread locking state, this time with new locks */ + thread_mask_clear(~0); + thread_mask_set(flags); + dbg("_rtld_thread_init: done"); +} + +void +_rtld_atfork_pre(int *locks) +{ + RtldLockState ls[2]; + + if (locks == NULL) + return; + + /* + * Warning: this does not work with the rtld compat locks + * above, since the thread signal mask is corrupted (set to + * all signals blocked) if two locks are taken in write mode. + * The caller of the _rtld_atfork_pre() must provide the + * working implementation of the locks, and libthr locks are + * fine. + */ + wlock_acquire(rtld_phdr_lock, &ls[0]); + wlock_acquire(rtld_bind_lock, &ls[1]); + + /* XXXKIB: I am really sorry for this. */ + locks[0] = ls[1].lockstate; + locks[2] = ls[0].lockstate; +} + +void +_rtld_atfork_post(int *locks) +{ + RtldLockState ls[2]; + + if (locks == NULL) + return; + + bzero(ls, sizeof(ls)); + ls[0].lockstate = locks[2]; + ls[1].lockstate = locks[0]; + lock_release(rtld_bind_lock, &ls[1]); + lock_release(rtld_phdr_lock, &ls[0]); +} diff --git a/libexec/rtld-elf/rtld_lock.h b/libexec/rtld-elf/rtld_lock.h new file mode 100644 index 0000000..fa63787 --- /dev/null +++ b/libexec/rtld-elf/rtld_lock.h @@ -0,0 +1,75 @@ +/*- + * Copyright 2003 Alexander Kabaev. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _RTLD_LOCK_H_ +#define _RTLD_LOCK_H_ + +#define RTLI_VERSION 0x01 +#define MAX_RTLD_LOCKS 8 + +struct RtldLockInfo +{ + unsigned int rtli_version; + void *(*lock_create)(void); + void (*lock_destroy)(void *); + void (*rlock_acquire)(void *); + void (*wlock_acquire)(void *); + void (*lock_release)(void *); + int (*thread_set_flag)(int); + int (*thread_clr_flag)(int); + void (*at_fork)(void); +}; + +extern void _rtld_thread_init(struct RtldLockInfo *); +extern void _rtld_atfork_pre(int *); +extern void _rtld_atfork_post(int *); + +#ifdef IN_RTLD + +struct rtld_lock; +typedef struct rtld_lock *rtld_lock_t; + +extern rtld_lock_t rtld_bind_lock; +extern rtld_lock_t rtld_libc_lock; +extern rtld_lock_t rtld_phdr_lock; + +#define RTLD_LOCK_UNLOCKED 0 +#define RTLD_LOCK_RLOCKED 1 +#define RTLD_LOCK_WLOCKED 2 + +struct Struct_RtldLockState; +typedef struct Struct_RtldLockState RtldLockState; + +void rlock_acquire(rtld_lock_t, RtldLockState *); +void wlock_acquire(rtld_lock_t, RtldLockState *); +void lock_release(rtld_lock_t, RtldLockState *); +void lock_upgrade(rtld_lock_t, RtldLockState *); +void lock_restart_for_upgrade(RtldLockState *); + +#endif /* IN_RTLD */ + +#endif diff --git a/libexec/rtld-elf/rtld_printf.c b/libexec/rtld-elf/rtld_printf.c new file mode 100644 index 0000000..9d945dc --- /dev/null +++ b/libexec/rtld-elf/rtld_printf.c @@ -0,0 +1,501 @@ +/*- + * Copyright (c) 1986, 1988, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * Copyright (c) 2011 Konstantin Belousov <kib@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include "rtld_printf.h" + +#define MAXNBUF (sizeof(intmax_t) * NBBY + 1) + +#define PRINT_METHOD_SNPRINTF 1 +#define PRINT_METHOD_WRITE 2 + +struct snprintf_arg { + int method; + char *str; + char *buf; + size_t remain; + size_t buf_total; + int fd; +}; + +static void +printf_out(struct snprintf_arg *info) +{ + + if (info->remain == info->buf_total) + return; + write(info->fd, info->buf, info->buf_total - info->remain); + info->str = info->buf; + info->remain = info->buf_total; +} + +static void +snprintf_func(int ch, struct snprintf_arg *const info) +{ + + switch (info->method) { + case PRINT_METHOD_SNPRINTF: + if (info->remain >= 2) { + *info->str++ = ch; + info->remain--; + } + break; + case PRINT_METHOD_WRITE: + if (info->remain > 0) { + *info->str++ = ch; + info->remain--; + } else + printf_out(info); + break; + } +} + +static char const hex2ascii_lower[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +static char const hex2ascii_upper[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +#define hex2ascii(hex) (hex2ascii_lower[hex]) +#define hex2ascii_upper(hex) (hex2ascii_upper[hex]) + +static __inline int +imax(int a, int b) +{ + + return (a > b ? a : b); +} + +static char * +ksprintn(char *nbuf, uintmax_t num, int base, int *lenp, int upper) +{ + char *p, c; + + p = nbuf; + *p = '\0'; + do { + c = upper ? hex2ascii_upper(num % base) : + hex2ascii(num % base); + *++p = c; + } while (num /= base); + if (lenp) + *lenp = p - nbuf; + return (p); +} + +static int +kvprintf(char const *fmt, struct snprintf_arg *arg, int radix, va_list ap) +{ +#define PCHAR(c) snprintf_func((c), arg) + char nbuf[MAXNBUF]; + const char *p, *percent, *q; + u_char *up; + int ch, n; + uintmax_t num; + int base, lflag, qflag, tmp, width, ladjust, sharpflag, neg, sign, dot; + int cflag, hflag, jflag, tflag, zflag; + int dwidth, upper; + char padc; + int stop = 0, retval = 0; + + num = 0; + + if (fmt == NULL) + fmt = "(fmt null)\n"; + + if (radix < 2 || radix > 36) + radix = 10; + + for (;;) { + padc = ' '; + width = 0; + while ((ch = (u_char)*fmt++) != '%' || stop) { + if (ch == '\0') + return (retval); + PCHAR(ch); + } + percent = fmt - 1; + qflag = 0; lflag = 0; ladjust = 0; sharpflag = 0; neg = 0; + sign = 0; dot = 0; dwidth = 0; upper = 0; + cflag = 0; hflag = 0; jflag = 0; tflag = 0; zflag = 0; +reswitch: switch (ch = (u_char)*fmt++) { + case '.': + dot = 1; + goto reswitch; + case '#': + sharpflag = 1; + goto reswitch; + case '+': + sign = 1; + goto reswitch; + case '-': + ladjust = 1; + goto reswitch; + case '%': + PCHAR(ch); + break; + case '*': + if (!dot) { + width = va_arg(ap, int); + if (width < 0) { + ladjust = !ladjust; + width = -width; + } + } else { + dwidth = va_arg(ap, int); + } + goto reswitch; + case '0': + if (!dot) { + padc = '0'; + goto reswitch; + } + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + for (n = 0;; ++fmt) { + n = n * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + if (dot) + dwidth = n; + else + width = n; + goto reswitch; + case 'b': + num = (u_int)va_arg(ap, int); + p = va_arg(ap, char *); + for (q = ksprintn(nbuf, num, *p++, NULL, 0); *q;) + PCHAR(*q--); + + if (num == 0) + break; + + for (tmp = 0; *p;) { + n = *p++; + if (num & (1 << (n - 1))) { + PCHAR(tmp ? ',' : '<'); + for (; (n = *p) > ' '; ++p) + PCHAR(n); + tmp = 1; + } else + for (; *p > ' '; ++p) + continue; + } + if (tmp) + PCHAR('>'); + break; + case 'c': + PCHAR(va_arg(ap, int)); + break; + case 'D': + up = va_arg(ap, u_char *); + p = va_arg(ap, char *); + if (!width) + width = 16; + while(width--) { + PCHAR(hex2ascii(*up >> 4)); + PCHAR(hex2ascii(*up & 0x0f)); + up++; + if (width) + for (q=p;*q;q++) + PCHAR(*q); + } + break; + case 'd': + case 'i': + base = 10; + sign = 1; + goto handle_sign; + case 'h': + if (hflag) { + hflag = 0; + cflag = 1; + } else + hflag = 1; + goto reswitch; + case 'j': + jflag = 1; + goto reswitch; + case 'l': + if (lflag) { + lflag = 0; + qflag = 1; + } else + lflag = 1; + goto reswitch; + case 'n': + if (jflag) + *(va_arg(ap, intmax_t *)) = retval; + else if (qflag) + *(va_arg(ap, quad_t *)) = retval; + else if (lflag) + *(va_arg(ap, long *)) = retval; + else if (zflag) + *(va_arg(ap, size_t *)) = retval; + else if (hflag) + *(va_arg(ap, short *)) = retval; + else if (cflag) + *(va_arg(ap, char *)) = retval; + else + *(va_arg(ap, int *)) = retval; + break; + case 'o': + base = 8; + goto handle_nosign; + case 'p': + base = 16; + sharpflag = (width == 0); + sign = 0; + num = (uintptr_t)va_arg(ap, void *); + goto number; + case 'q': + qflag = 1; + goto reswitch; + case 'r': + base = radix; + if (sign) + goto handle_sign; + goto handle_nosign; + case 's': + p = va_arg(ap, char *); + if (p == NULL) + p = "(null)"; + if (!dot) + n = strlen (p); + else + for (n = 0; n < dwidth && p[n]; n++) + continue; + + width -= n; + + if (!ladjust && width > 0) + while (width--) + PCHAR(padc); + while (n--) + PCHAR(*p++); + if (ladjust && width > 0) + while (width--) + PCHAR(padc); + break; + case 't': + tflag = 1; + goto reswitch; + case 'u': + base = 10; + goto handle_nosign; + case 'X': + upper = 1; + case 'x': + base = 16; + goto handle_nosign; + case 'y': + base = 16; + sign = 1; + goto handle_sign; + case 'z': + zflag = 1; + goto reswitch; +handle_nosign: + sign = 0; + if (jflag) + num = va_arg(ap, uintmax_t); + else if (qflag) + num = va_arg(ap, u_quad_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, u_long); + else if (zflag) + num = va_arg(ap, size_t); + else if (hflag) + num = (u_short)va_arg(ap, int); + else if (cflag) + num = (u_char)va_arg(ap, int); + else + num = va_arg(ap, u_int); + goto number; +handle_sign: + if (jflag) + num = va_arg(ap, intmax_t); + else if (qflag) + num = va_arg(ap, quad_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (lflag) + num = va_arg(ap, long); + else if (zflag) + num = va_arg(ap, ssize_t); + else if (hflag) + num = (short)va_arg(ap, int); + else if (cflag) + num = (char)va_arg(ap, int); + else + num = va_arg(ap, int); +number: + if (sign && (intmax_t)num < 0) { + neg = 1; + num = -(intmax_t)num; + } + p = ksprintn(nbuf, num, base, &n, upper); + tmp = 0; + if (sharpflag && num != 0) { + if (base == 8) + tmp++; + else if (base == 16) + tmp += 2; + } + if (neg) + tmp++; + + if (!ladjust && padc == '0') + dwidth = width - tmp; + width -= tmp + imax(dwidth, n); + dwidth -= n; + if (!ladjust) + while (width-- > 0) + PCHAR(' '); + if (neg) + PCHAR('-'); + if (sharpflag && num != 0) { + if (base == 8) { + PCHAR('0'); + } else if (base == 16) { + PCHAR('0'); + PCHAR('x'); + } + } + while (dwidth-- > 0) + PCHAR('0'); + + while (*p) + PCHAR(*p--); + + if (ladjust) + while (width-- > 0) + PCHAR(' '); + + break; + default: + while (percent < fmt) + PCHAR(*percent++); + /* + * Since we ignore an formatting argument it is no + * longer safe to obey the remaining formatting + * arguments as the arguments will no longer match + * the format specs. + */ + stop = 1; + break; + } + } +#undef PCHAR +} + +int +rtld_snprintf(char *buf, size_t bufsize, const char *fmt, ...) +{ + va_list ap; + int retval; + + va_start(ap, fmt); + retval = rtld_vsnprintf(buf, bufsize, fmt, ap); + va_end(ap); + return (retval); +} + +int +rtld_vsnprintf(char *buf, size_t bufsize, const char *fmt, va_list ap) +{ + struct snprintf_arg info; + int retval; + + info.method = PRINT_METHOD_SNPRINTF; + info.buf = info.str = buf; + info.buf_total = info.remain = bufsize; + info.fd = -1; + retval = kvprintf(fmt, &info, 10, ap); + if (info.remain >= 1) + *info.str++ = '\0'; + return (retval); +} + +int +rtld_vfdprintf(int fd, const char *fmt, va_list ap) +{ + char buf[512]; + struct snprintf_arg info; + int retval; + + info.method = PRINT_METHOD_WRITE; + info.buf = info.str = buf; + info.buf_total = info.remain = sizeof(buf); + info.fd = fd; + retval = kvprintf(fmt, &info, 10, ap); + printf_out(&info); + return (retval); +} + +int +rtld_fdprintf(int fd, const char *fmt, ...) +{ + va_list ap; + int retval; + + va_start(ap, fmt); + retval = rtld_vfdprintf(fd, fmt, ap); + va_end(ap); + return (retval); +} + +void +rtld_fdputstr(int fd, const char *str) +{ + + write(fd, str, strlen(str)); +} + +void +rtld_fdputchar(int fd, int c) +{ + char c1; + + c1 = c; + write(fd, &c1, 1); +} diff --git a/libexec/rtld-elf/rtld_printf.h b/libexec/rtld-elf/rtld_printf.h new file mode 100644 index 0000000..aaf2971 --- /dev/null +++ b/libexec/rtld-elf/rtld_printf.h @@ -0,0 +1,46 @@ +/*- + * Copyright 2011 Konstantin Belousov <kib@FreeBSD.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_PRINTF_H +#define RTLD_PRINTF_H 1 + +#include <sys/cdefs.h> +#include <unistd.h> + +int rtld_snprintf(char *buf, size_t bufsize, const char *fmt, ...) + __printflike(3, 4); +int rtld_vsnprintf(char *buf, size_t bufsize, const char *fmt, va_list ap); +int rtld_vfdprintf(int fd, const char *fmt, va_list ap); +int rtld_fdprintf(int fd, const char *fmt, ...) __printflike(2, 3); +void rtld_fdputstr(int fd, const char *str); +void rtld_fdputchar(int fd, int c); + +#define rtld_printf(...) rtld_fdprintf(STDOUT_FILENO, __VA_ARGS__) +#define rtld_putstr(str) rtld_fdputstr(STDOUT_FILENO, (str)) +#define rtld_putchar(c) rtld_fdputchar(STDOUT_FILENO, (c)) + +#endif diff --git a/libexec/rtld-elf/rtld_tls.h b/libexec/rtld-elf/rtld_tls.h new file mode 100644 index 0000000..b85db59 --- /dev/null +++ b/libexec/rtld-elf/rtld_tls.h @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2004 Doug Rabson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Semi-public interface from thread libraries to rtld for managing + * TLS. + */ + +#ifndef _RTLD_TLS_H_ +#define _RTLD_TLS_H_ + +/* + * Allocate a TLS block for a new thread. The memory allocated will + * include 'tcbsize' bytes aligned to a 'tcbalign' boundary (in bytes) + * for the thread library's private purposes. The location of the TCB + * block is returned by this function. For architectures using + * 'Variant I' TLS, the thread local storage follows the TCB, and for + * 'Variant II', the thread local storage precedes it. For + * architectures using the 'Variant II' model (e.g. i386, amd64, + * sparc64), the TCB must begin with two pointer fields which are used + * by rtld for its TLS implementation. For the 'Variant I' model, the + * TCB must begin with a single pointer field for rtld's + * implementation. + * + * If the value of 'oldtls' is non-NULL, the new TLS block will be + * initialised using the values contained in 'oldtls' and 'oldtls' + * will be freed. This is typically used when initialising a thread + * library to migrate from using the initial bootstrap TLS block + * created by rtld to one which contains suitable thread library + * private data. + * + * The value returned from this function is suitable for installing + * directly into the thread pointer register. + */ +extern void *_rtld_allocate_tls(void* oldtls, size_t tcbsize, size_t tcbalign); + +/* + * Free a TLS block allocated using _rtld_allocate_tls(). The tcbsize + * and tcbalign parameters must be the same as those used to allocate + * the block. + */ +extern void _rtld_free_tls(void *tcb, size_t tcbsize, size_t tcbalign); + +#endif diff --git a/libexec/rtld-elf/sparc64/Makefile.inc b/libexec/rtld-elf/sparc64/Makefile.inc new file mode 100644 index 0000000..e8c0da7 --- /dev/null +++ b/libexec/rtld-elf/sparc64/Makefile.inc @@ -0,0 +1 @@ +# $FreeBSD$ diff --git a/libexec/rtld-elf/sparc64/reloc.c b/libexec/rtld-elf/sparc64/reloc.c new file mode 100644 index 0000000..738a847 --- /dev/null +++ b/libexec/rtld-elf/sparc64/reloc.c @@ -0,0 +1,855 @@ +/* $NetBSD: mdreloc.c,v 1.42 2008/04/28 20:23:04 martin Exp $ */ + +/*- + * Copyright (c) 2000 Eduardo Horvath. + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Paul Kranenburg. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/mman.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "debug.h" +#include "rtld.h" + +/* + * The following table holds for each relocation type: + * - the width in bits of the memory location the relocation + * applies to (not currently used) + * - the number of bits the relocation value must be shifted to the + * right (i.e. discard least significant bits) to fit into + * the appropriate field in the instruction word. + * - flags indicating whether + * * the relocation involves a symbol + * * the relocation is relative to the current position + * * the relocation is for a GOT entry + * * the relocation is relative to the load address + * + */ +#define _RF_S 0x80000000 /* Resolve symbol */ +#define _RF_A 0x40000000 /* Use addend */ +#define _RF_P 0x20000000 /* Location relative */ +#define _RF_G 0x10000000 /* GOT offset */ +#define _RF_B 0x08000000 /* Load address relative */ +#define _RF_U 0x04000000 /* Unaligned */ +#define _RF_X 0x02000000 /* Bare symbols, needs proc */ +#define _RF_D 0x01000000 /* Use dynamic TLS offset */ +#define _RF_O 0x00800000 /* Use static TLS offset */ +#define _RF_I 0x00400000 /* Use TLS object ID */ +#define _RF_SZ(s) (((s) & 0xff) << 8) /* memory target size */ +#define _RF_RS(s) ( (s) & 0xff) /* right shift */ +static const int reloc_target_flags[] = { + 0, /* NONE */ + _RF_S|_RF_A| _RF_SZ(8) | _RF_RS(0), /* 8 */ + _RF_S|_RF_A| _RF_SZ(16) | _RF_RS(0), /* 16 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* 32 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(8) | _RF_RS(0), /* DISP_8 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(16) | _RF_RS(0), /* DISP_16 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(0), /* DISP_32 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* WDISP_30 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* WDISP_22 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(10), /* HI22 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 22 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 13 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* LO10 */ + _RF_G| _RF_SZ(32) | _RF_RS(0), /* GOT10 */ + _RF_G| _RF_SZ(32) | _RF_RS(0), /* GOT13 */ + _RF_G| _RF_SZ(32) | _RF_RS(10), /* GOT22 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(0), /* PC10 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(10), /* PC22 */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* WPLT30 */ + _RF_SZ(32) | _RF_RS(0), /* COPY */ + _RF_S|_RF_A| _RF_SZ(64) | _RF_RS(0), /* GLOB_DAT */ + _RF_SZ(32) | _RF_RS(0), /* JMP_SLOT */ + _RF_A| _RF_B| _RF_SZ(64) | _RF_RS(0), /* RELATIVE */ + _RF_S|_RF_A| _RF_U| _RF_SZ(32) | _RF_RS(0), /* UA_32 */ + + _RF_A| _RF_SZ(32) | _RF_RS(0), /* PLT32 */ + _RF_A| _RF_SZ(32) | _RF_RS(10), /* HIPLT22 */ + _RF_A| _RF_SZ(32) | _RF_RS(0), /* LOPLT10 */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(0), /* PCPLT32 */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(10), /* PCPLT22 */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(0), /* PCPLT10 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 10 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 11 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(64) | _RF_RS(0), /* 64 */ + _RF_S|_RF_A|/*extra*/ _RF_SZ(32) | _RF_RS(0), /* OLO10 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(42), /* HH22 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(32), /* HM10 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(10), /* LM22 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(42), /* PC_HH22 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(32), /* PC_HM10 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(10), /* PC_LM22 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* WDISP16 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* WDISP19 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* GLOB_JMP */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 7 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 5 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* 6 */ + _RF_S|_RF_A|_RF_P| _RF_SZ(64) | _RF_RS(0), /* DISP64 */ + _RF_A| _RF_SZ(64) | _RF_RS(0), /* PLT64 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(10), /* HIX22 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* LOX10 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(22), /* H44 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(12), /* M44 */ + _RF_S|_RF_A|_RF_X| _RF_SZ(32) | _RF_RS(0), /* L44 */ + _RF_S|_RF_A| _RF_SZ(64) | _RF_RS(0), /* REGISTER */ + _RF_S|_RF_A| _RF_U| _RF_SZ(64) | _RF_RS(0), /* UA64 */ + _RF_S|_RF_A| _RF_U| _RF_SZ(16) | _RF_RS(0), /* UA16 */ + + /* TLS */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(10), /* GD_HI22 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* GD_LO10 */ + 0, /* GD_ADD */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* GD_CALL */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(10), /* LDM_HI22 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* LDM_LO10 */ + 0, /* LDM_ADD */ + _RF_A|_RF_P| _RF_SZ(32) | _RF_RS(2), /* LDM_CALL */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(10), /* LDO_HIX22 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* LDO_LOX10 */ + 0, /* LDO_ADD */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(10), /* IE_HI22 */ + _RF_S|_RF_A| _RF_SZ(32) | _RF_RS(0), /* IE_LO10 */ + 0, /* IE_LD */ + 0, /* IE_LDX */ + 0, /* IE_ADD */ + _RF_S|_RF_A| _RF_O| _RF_SZ(32) | _RF_RS(10), /* LE_HIX22 */ + _RF_S|_RF_A| _RF_O| _RF_SZ(32) | _RF_RS(0), /* LE_LOX10 */ + _RF_S| _RF_I| _RF_SZ(32) | _RF_RS(0), /* DTPMOD32 */ + _RF_S| _RF_I| _RF_SZ(64) | _RF_RS(0), /* DTPMOD64 */ + _RF_S|_RF_A| _RF_D| _RF_SZ(32) | _RF_RS(0), /* DTPOFF32 */ + _RF_S|_RF_A| _RF_D| _RF_SZ(64) | _RF_RS(0), /* DTPOFF64 */ + _RF_S|_RF_A| _RF_O| _RF_SZ(32) | _RF_RS(0), /* TPOFF32 */ + _RF_S|_RF_A| _RF_O| _RF_SZ(64) | _RF_RS(0) /* TPOFF64 */ +}; + +#if 0 +static const char *const reloc_names[] = { + "NONE", "8", "16", "32", "DISP_8", "DISP_16", "DISP_32", "WDISP_30", + "WDISP_22", "HI22", "22", "13", "LO10", "GOT10", "GOT13", "GOT22", + "PC10", "PC22", "WPLT30", "COPY", "GLOB_DAT", "JMP_SLOT", "RELATIVE", + "UA_32", "PLT32", "HIPLT22", "LOPLT10", "LOPLT10", "PCPLT22", + "PCPLT32", "10", "11", "64", "OLO10", "HH22", "HM10", "LM22", + "PC_HH22", "PC_HM10", "PC_LM22", "WDISP16", "WDISP19", "GLOB_JMP", + "7", "5", "6", "DISP64", "PLT64", "HIX22", "LOX10", "H44", "M44", + "L44", "REGISTER", "UA64", "UA16", "GD_HI22", "GD_LO10", "GD_ADD", + "GD_CALL", "LDM_HI22", "LDMO10", "LDM_ADD", "LDM_CALL", "LDO_HIX22", + "LDO_LOX10", "LDO_ADD", "IE_HI22", "IE_LO10", "IE_LD", "IE_LDX", + "IE_ADD", "LE_HIX22", "LE_LOX10", "DTPMOD32", "DTPMOD64", "DTPOFF32", + "DTPOFF64", "TPOFF32", "TPOFF64" +}; +#endif + +#define RELOC_RESOLVE_SYMBOL(t) ((reloc_target_flags[t] & _RF_S) != 0) +#define RELOC_PC_RELATIVE(t) ((reloc_target_flags[t] & _RF_P) != 0) +#define RELOC_BASE_RELATIVE(t) ((reloc_target_flags[t] & _RF_B) != 0) +#define RELOC_UNALIGNED(t) ((reloc_target_flags[t] & _RF_U) != 0) +#define RELOC_USE_ADDEND(t) ((reloc_target_flags[t] & _RF_A) != 0) +#define RELOC_BARE_SYMBOL(t) ((reloc_target_flags[t] & _RF_X) != 0) +#define RELOC_USE_TLS_DOFF(t) ((reloc_target_flags[t] & _RF_D) != 0) +#define RELOC_USE_TLS_OFF(t) ((reloc_target_flags[t] & _RF_O) != 0) +#define RELOC_USE_TLS_ID(t) ((reloc_target_flags[t] & _RF_I) != 0) +#define RELOC_TARGET_SIZE(t) ((reloc_target_flags[t] >> 8) & 0xff) +#define RELOC_VALUE_RIGHTSHIFT(t) (reloc_target_flags[t] & 0xff) + +static const long reloc_target_bitmask[] = { +#define _BM(x) (~(-(1ULL << (x)))) + 0, /* NONE */ + _BM(8), _BM(16), _BM(32), /* 8, 16, 32 */ + _BM(8), _BM(16), _BM(32), /* DISP8, DISP16, DISP32 */ + _BM(30), _BM(22), /* WDISP30, WDISP22 */ + _BM(22), _BM(22), /* HI22, 22 */ + _BM(13), _BM(10), /* 13, LO10 */ + _BM(10), _BM(13), _BM(22), /* GOT10, GOT13, GOT22 */ + _BM(10), _BM(22), /* PC10, PC22 */ + _BM(30), 0, /* WPLT30, COPY */ + _BM(32), _BM(32), _BM(32), /* GLOB_DAT, JMP_SLOT, RELATIVE */ + _BM(32), _BM(32), /* UA32, PLT32 */ + _BM(22), _BM(10), /* HIPLT22, LOPLT10 */ + _BM(32), _BM(22), _BM(10), /* PCPLT32, PCPLT22, PCPLT10 */ + _BM(10), _BM(11), -1, /* 10, 11, 64 */ + _BM(13), _BM(22), /* OLO10, HH22 */ + _BM(10), _BM(22), /* HM10, LM22 */ + _BM(22), _BM(10), _BM(22), /* PC_HH22, PC_HM10, PC_LM22 */ + _BM(16), _BM(19), /* WDISP16, WDISP19 */ + -1, /* GLOB_JMP */ + _BM(7), _BM(5), _BM(6), /* 7, 5, 6 */ + -1, -1, /* DISP64, PLT64 */ + _BM(22), _BM(13), /* HIX22, LOX10 */ + _BM(22), _BM(10), _BM(13), /* H44, M44, L44 */ + -1, -1, _BM(16), /* REGISTER, UA64, UA16 */ + _BM(22), _BM(10), 0, _BM(30), /* GD_HI22, GD_LO10, GD_ADD, GD_CALL */ + _BM(22), _BM(10), 0, /* LDM_HI22, LDMO10, LDM_ADD */ + _BM(30), /* LDM_CALL */ + _BM(22), _BM(10), 0, /* LDO_HIX22, LDO_LOX10, LDO_ADD */ + _BM(22), _BM(10), 0, 0, /* IE_HI22, IE_LO10, IE_LD, IE_LDX */ + 0, /* IE_ADD */ + _BM(22), _BM(13), /* LE_HIX22, LE_LOX10 */ + _BM(32), -1, /* DTPMOD32, DTPMOD64 */ + _BM(32), -1, /* DTPOFF32, DTPOFF64 */ + _BM(32), -1 /* TPOFF32, TPOFF64 */ +#undef _BM +}; +#define RELOC_VALUE_BITMASK(t) (reloc_target_bitmask[t]) + +#undef flush +#define flush(va, offs) \ + __asm __volatile("flush %0 + %1" : : "r" (va), "I" (offs)); + +static int reloc_nonplt_object(Obj_Entry *obj, const Elf_Rela *rela, + SymCache *cache, int flags, RtldLockState *lockstate); +static void install_plt(Elf_Word *pltgot, Elf_Addr proc); + +extern char _rtld_bind_start_0[]; +extern char _rtld_bind_start_1[]; + +int +do_copy_relocations(Obj_Entry *dstobj) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + const Elf_Sym *dstsym; + const Elf_Sym *srcsym; + void *dstaddr; + const void *srcaddr; + const Obj_Entry *srcobj, *defobj; + SymLook req; + const char *name; + size_t size; + int res; + + assert(dstobj->mainprog); /* COPY relocations are invalid elsewhere */ + + relalim = (const Elf_Rela *)((caddr_t)dstobj->rela + dstobj->relasize); + for (rela = dstobj->rela; rela < relalim; rela++) { + if (ELF_R_TYPE(rela->r_info) == R_SPARC_COPY) { + dstaddr = (void *)(dstobj->relocbase + rela->r_offset); + dstsym = dstobj->symtab + ELF_R_SYM(rela->r_info); + name = dstobj->strtab + dstsym->st_name; + size = dstsym->st_size; + symlook_init(&req, name); + req.ventry = fetch_ventry(dstobj, + ELF_R_SYM(rela->r_info)); + req.flags = SYMLOOK_EARLY; + + for (srcobj = dstobj->next; srcobj != NULL; + srcobj = srcobj->next) { + res = symlook_obj(&req, srcobj); + if (res == 0) { + srcsym = req.sym_out; + defobj = req.defobj_out; + break; + } + } + if (srcobj == NULL) { + _rtld_error("Undefined symbol \"%s\"" + "referenced from COPY relocation" + "in %s", name, dstobj->path); + return (-1); + } + + srcaddr = (const void *)(defobj->relocbase + + srcsym->st_value); + memcpy(dstaddr, srcaddr, size); + } + } + + return (0); +} + +int +reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags, + RtldLockState *lockstate) +{ + const Elf_Rela *relalim; + const Elf_Rela *rela; + SymCache *cache; + int r = -1; + + if ((flags & SYMLOOK_IFUNC) != 0) + /* XXX not implemented */ + return (0); + + /* + * The dynamic loader may be called from a thread, we have + * limited amounts of stack available so we cannot use alloca(). + */ + if (obj != obj_rtld) { + cache = calloc(obj->dynsymcount, sizeof(SymCache)); + /* No need to check for NULL here */ + } else + cache = NULL; + + relalim = (const Elf_Rela *)((caddr_t)obj->rela + obj->relasize); + for (rela = obj->rela; rela < relalim; rela++) { + if (reloc_nonplt_object(obj, rela, cache, flags, lockstate) < 0) + goto done; + } + r = 0; +done: + if (cache != NULL) + free(cache); + return (r); +} + +static int +reloc_nonplt_object(Obj_Entry *obj, const Elf_Rela *rela, SymCache *cache, + int flags, RtldLockState *lockstate) +{ + const Obj_Entry *defobj; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Word *where32; + Elf_Word type; + Elf_Addr value; + Elf_Addr mask; + + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + where32 = (Elf_Word *)where; + defobj = NULL; + def = NULL; + + type = ELF64_R_TYPE_ID(rela->r_info); + if (type == R_SPARC_NONE) + return (0); + + /* We do JMP_SLOTs below. */ + if (type == R_SPARC_JMP_SLOT) + return (0); + + /* COPY relocs are also handled elsewhere. */ + if (type == R_SPARC_COPY) + return (0); + + /* Ignore ADD and CALL relocations for dynamic TLS references. */ + if (type == R_SPARC_TLS_GD_ADD || type == R_SPARC_TLS_GD_CALL || + type == R_SPARC_TLS_LDM_ADD || type == R_SPARC_TLS_LDM_CALL || + type == R_SPARC_TLS_LDO_ADD) + return (0); + + /* + * Note: R_SPARC_TLS_TPOFF64 must be the numerically largest + * relocation type. + */ + if (type >= sizeof(reloc_target_bitmask) / + sizeof(*reloc_target_bitmask)) { + _rtld_error("%s: Unsupported relocation type %d in non-PLT " + "object\n", obj->path, type); + return (-1); + } + + value = rela->r_addend; + + /* + * Handle relative relocs here, because we might not be able to access + * globals yet. + */ + if (type == R_SPARC_RELATIVE) { + /* XXXX -- apparently we ignore the preexisting value. */ + *where = (Elf_Addr)(obj->relocbase + value); + return (0); + } + + /* + * If we get here while relocating rtld itself, we will crash because + * a non-local variable is accessed. + */ + if (RELOC_RESOLVE_SYMBOL(type)) { + /* Find the symbol. */ + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + flags, cache, lockstate); + if (def == NULL) + return (-1); + + if (RELOC_USE_TLS_ID(type)) + value = (Elf_Addr)defobj->tlsindex; + else if (RELOC_USE_TLS_DOFF(type)) + value += (Elf_Addr)def->st_value; + else if (RELOC_USE_TLS_OFF(type)) { + /* + * We lazily allocate offsets for static TLS as we + * see the first relocation that references the TLS + * block. This allows us to support (small amounts + * of) static TLS in dynamically loaded modules. If + * we run out of space, we generate an error. + */ + if (!defobj->tls_done && + !allocate_tls_offset((Obj_Entry*)defobj)) { + _rtld_error("%s: No space available for " + "static Thread Local Storage", obj->path); + return (-1); + } + value += (Elf_Addr)(def->st_value - + defobj->tlsoffset); + } else { + /* Add in the symbol's absolute address. */ + value += (Elf_Addr)(def->st_value + + defobj->relocbase); + } + } + + if (type == R_SPARC_OLO10) + value = (value & 0x3ff) + ELF64_R_TYPE_DATA(rela->r_info); + + if (type == R_SPARC_HIX22 || type == R_SPARC_TLS_LE_HIX22) + value ^= 0xffffffffffffffff; + + if (RELOC_PC_RELATIVE(type)) + value -= (Elf_Addr)where; + + if (RELOC_BASE_RELATIVE(type)) { + /* + * Note that even though sparcs use `Elf_rela' exclusively + * we still need the implicit memory addend in relocations + * referring to GOT entries. Undoubtedly, someone f*cked + * this up in the distant past, and now we're stuck with + * it in the name of compatibility for all eternity ... + * + * In any case, the implicit and explicit should be mutually + * exclusive. We provide a check for that here. + */ + /* XXXX -- apparently we ignore the preexisting value */ + value += (Elf_Addr)(obj->relocbase); + } + + mask = RELOC_VALUE_BITMASK(type); + value >>= RELOC_VALUE_RIGHTSHIFT(type); + value &= mask; + + if (type == R_SPARC_LOX10 || type == R_SPARC_TLS_LE_LOX10) + value |= 0x1c00; + + if (RELOC_UNALIGNED(type)) { + /* Handle unaligned relocations. */ + Elf_Addr tmp; + char *ptr; + int size; + int i; + + size = RELOC_TARGET_SIZE(type) / 8; + ptr = (char *)where; + tmp = 0; + + /* Read it in one byte at a time. */ + for (i = 0; i < size; i++) + tmp = (tmp << 8) | ptr[i]; + + tmp &= ~mask; + tmp |= value; + + /* Write it back out. */ + for (i = 0; i < size; i++) + ptr[i] = ((tmp >> ((size - i - 1) * 8)) & 0xff); + } else if (RELOC_TARGET_SIZE(type) > 32) { + *where &= ~mask; + *where |= value; + } else { + *where32 &= ~mask; + *where32 |= value; + } + + return (0); +} + +int +reloc_plt(Obj_Entry *obj) +{ +#if 0 + const Obj_Entry *defobj; + const Elf_Rela *relalim; + const Elf_Rela *rela; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Addr value; + + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + if (rela->r_addend == 0) + continue; + assert(ELF64_R_TYPE_ID(rela->r_info) == R_SPARC_JMP_SLOT); + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + true, NULL, lockstate); + value = (Elf_Addr)(defobj->relocbase + def->st_value); + *where = value; + } +#endif + return (0); +} + +/* + * Instruction templates: + */ +#define BAA 0x10400000 /* ba,a %xcc, 0 */ +#define SETHI 0x03000000 /* sethi %hi(0), %g1 */ +#define JMP 0x81c06000 /* jmpl %g1+%lo(0), %g0 */ +#define NOP 0x01000000 /* sethi %hi(0), %g0 */ +#define OR 0x82806000 /* or %g1, 0, %g1 */ +#define XOR 0x82c06000 /* xor %g1, 0, %g1 */ +#define MOV71 0x8283a000 /* or %o7, 0, %g1 */ +#define MOV17 0x9c806000 /* or %g1, 0, %o7 */ +#define CALL 0x40000000 /* call 0 */ +#define SLLX 0x8b407000 /* sllx %g1, 0, %g1 */ +#define SETHIG5 0x0b000000 /* sethi %hi(0), %g5 */ +#define ORG5 0x82804005 /* or %g1, %g5, %g1 */ + +/* %hi(v) with variable shift */ +#define HIVAL(v, s) (((v) >> (s)) & 0x003fffff) +#define LOVAL(v) ((v) & 0x000003ff) + +int +reloc_jmpslots(Obj_Entry *obj, int flags, RtldLockState *lockstate) +{ + const Obj_Entry *defobj; + const Elf_Rela *relalim; + const Elf_Rela *rela; + const Elf_Sym *def; + Elf_Addr *where; + Elf_Addr target; + + relalim = (const Elf_Rela *)((char *)obj->pltrela + obj->pltrelasize); + for (rela = obj->pltrela; rela < relalim; rela++) { + assert(ELF64_R_TYPE_ID(rela->r_info) == R_SPARC_JMP_SLOT); + where = (Elf_Addr *)(obj->relocbase + rela->r_offset); + def = find_symdef(ELF_R_SYM(rela->r_info), obj, &defobj, + SYMLOOK_IN_PLT | flags, NULL, lockstate); + if (def == NULL) + return -1; + target = (Elf_Addr)(defobj->relocbase + def->st_value); + reloc_jmpslot(where, target, defobj, obj, (Elf_Rel *)rela); + } + obj->jmpslots_done = true; + return (0); +} + +int +reloc_iresolve(Obj_Entry *obj, struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +int +reloc_gnu_ifunc(Obj_Entry *obj, int flags, + struct Struct_RtldLockState *lockstate) +{ + + /* XXX not implemented */ + return (0); +} + +Elf_Addr +reloc_jmpslot(Elf_Addr *wherep, Elf_Addr target, const Obj_Entry *obj, + const Obj_Entry *refobj, const Elf_Rel *rel) +{ + const Elf_Rela *rela = (const Elf_Rela *)rel; + Elf_Addr offset; + Elf_Word *where; + + if (rela - refobj->pltrela < 32764) { + /* + * At the PLT entry pointed at by `where', we now construct + * a direct transfer to the now fully resolved function + * address. + * + * A PLT entry is supposed to start by looking like this: + * + * sethi (. - .PLT0), %g1 + * ba,a %xcc, .PLT1 + * nop + * nop + * nop + * nop + * nop + * nop + * + * When we replace these entries we start from the second + * entry and do it in reverse order so the last thing we + * do is replace the branch. That allows us to change this + * atomically. + * + * We now need to find out how far we need to jump. We + * have a choice of several different relocation techniques + * which are increasingly expensive. + */ + where = (Elf_Word *)wherep; + offset = ((Elf_Addr)where) - target; + if (offset <= (1L<<20) && offset >= -(1L<<20)) { + /* + * We're within 1MB -- we can use a direct branch + * instruction. + * + * We can generate this pattern: + * + * sethi %hi(. - .PLT0), %g1 + * ba,a %xcc, addr + * nop + * nop + * nop + * nop + * nop + * nop + * + */ + where[1] = BAA | ((offset >> 2) &0x3fffff); + flush(where, 4); + } else if (target >= 0 && target < (1L<<32)) { + /* + * We're within 32-bits of address zero. + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * sethi %hi(addr), %g1 + * jmp %g1+%lo(addr) + * nop + * nop + * nop + * nop + * nop + * + */ + where[2] = JMP | LOVAL(target); + flush(where, 8); + where[1] = SETHI | HIVAL(target, 10); + flush(where, 4); + } else if (target <= 0 && target > -(1L<<32)) { + /* + * We're within 32-bits of address -1. + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * sethi %hix(addr), %g1 + * xor %g1, %lox(addr), %g1 + * jmp %g1 + * nop + * nop + * nop + * nop + * + */ + where[3] = JMP; + flush(where, 12); + where[2] = XOR | ((~target) & 0x00001fff); + flush(where, 8); + where[1] = SETHI | HIVAL(~target, 10); + flush(where, 4); + } else if (offset <= (1L<<32) && offset >= -((1L<<32) - 4)) { + /* + * We're within 32-bits -- we can use a direct call + * insn + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * mov %o7, %g1 + * call (.+offset) + * mov %g1, %o7 + * nop + * nop + * nop + * nop + * + */ + where[3] = MOV17; + flush(where, 12); + where[2] = CALL | ((offset >> 4) & 0x3fffffff); + flush(where, 8); + where[1] = MOV71; + flush(where, 4); + } else if (offset >= 0 && offset < (1L<<44)) { + /* + * We're within 44 bits. We can generate this + * pattern: + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * sethi %h44(addr), %g1 + * or %g1, %m44(addr), %g1 + * sllx %g1, 12, %g1 + * jmp %g1+%l44(addr) + * nop + * nop + * nop + * + */ + where[4] = JMP | LOVAL(offset); + flush(where, 16); + where[3] = SLLX | 12; + flush(where, 12); + where[2] = OR | (((offset) >> 12) & 0x00001fff); + flush(where, 8); + where[1] = SETHI | HIVAL(offset, 22); + flush(where, 4); + } else if (offset < 0 && offset > -(1L<<44)) { + /* + * We're within 44 bits. We can generate this + * pattern: + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * sethi %h44(-addr), %g1 + * xor %g1, %m44(-addr), %g1 + * sllx %g1, 12, %g1 + * jmp %g1+%l44(addr) + * nop + * nop + * nop + * + */ + where[4] = JMP | LOVAL(offset); + flush(where, 16); + where[3] = SLLX | 12; + flush(where, 12); + where[2] = XOR | (((~offset) >> 12) & 0x00001fff); + flush(where, 8); + where[1] = SETHI | HIVAL(~offset, 22); + flush(where, 4); + } else { + /* + * We need to load all 64-bits + * + * The resulting code in the jump slot is: + * + * sethi %hi(. - .PLT0), %g1 + * sethi %hh(addr), %g1 + * sethi %lm(addr), %g5 + * or %g1, %hm(addr), %g1 + * sllx %g1, 32, %g1 + * or %g1, %g5, %g1 + * jmp %g1+%lo(addr) + * nop + * + */ + where[6] = JMP | LOVAL(target); + flush(where, 24); + where[5] = ORG5; + flush(where, 20); + where[4] = SLLX | 32; + flush(where, 16); + where[3] = OR | LOVAL((target) >> 32); + flush(where, 12); + where[2] = SETHIG5 | HIVAL(target, 10); + flush(where, 8); + where[1] = SETHI | HIVAL(target, 42); + flush(where, 4); + } + } else { + /* + * This is a high PLT slot; the relocation offset specifies a + * pointer that needs to be frobbed; no actual code needs to + * be modified. The pointer to be calculated needs the addend + * added and the reference object relocation base subtraced. + */ + *wherep = target + rela->r_addend - + (Elf_Addr)refobj->relocbase; + } + + return (target); +} + +/* + * Install rtld function call into this PLT slot. + */ +#define SAVE 0x9de3bf50 +#define SETHI_l0 0x21000000 +#define SETHI_l1 0x23000000 +#define OR_l0_l0 0xa0142000 +#define SLLX_l0_32_l0 0xa12c3020 +#define OR_l0_l1_l0 0xa0140011 +#define JMPL_l0_o1 0x93c42000 +#define MOV_g1_o0 0x90100001 + +void +init_pltgot(Obj_Entry *obj) +{ + Elf_Word *entry; + + if (obj->pltgot != NULL) { + entry = (Elf_Word *)obj->pltgot; + install_plt(&entry[0], (Elf_Addr)_rtld_bind_start_0); + install_plt(&entry[8], (Elf_Addr)_rtld_bind_start_1); + obj->pltgot[8] = (Elf_Addr)obj; + } +} + +static void +install_plt(Elf_Word *pltgot, Elf_Addr proc) +{ + pltgot[0] = SAVE; + flush(pltgot, 0); + pltgot[1] = SETHI_l0 | HIVAL(proc, 42); + flush(pltgot, 4); + pltgot[2] = SETHI_l1 | HIVAL(proc, 10); + flush(pltgot, 8); + pltgot[3] = OR_l0_l0 | LOVAL((proc) >> 32); + flush(pltgot, 12); + pltgot[4] = SLLX_l0_32_l0; + flush(pltgot, 16); + pltgot[5] = OR_l0_l1_l0; + flush(pltgot, 20); + pltgot[6] = JMPL_l0_o1 | LOVAL(proc); + flush(pltgot, 24); + pltgot[7] = MOV_g1_o0; + flush(pltgot, 28); +} + +void +allocate_initial_tls(Obj_Entry *objs) +{ + Elf_Addr* tpval; + + /* + * Fix the size of the static TLS block by using the maximum offset + * allocated so far and adding a bit for dynamic modules to use. + */ + tls_static_space = tls_last_offset + RTLD_STATIC_TLS_EXTRA; + tpval = allocate_tls(objs, NULL, 3 * sizeof(Elf_Addr), + sizeof(Elf_Addr)); + __asm __volatile("mov %0, %%g7" : : "r" (tpval)); +} + +void *__tls_get_addr(tls_index *ti) +{ + register Elf_Addr** tp __asm__("%g7"); + + return (tls_get_addr_common(tp, ti->ti_module, ti->ti_offset)); +} diff --git a/libexec/rtld-elf/sparc64/rtld_machdep.h b/libexec/rtld-elf/sparc64/rtld_machdep.h new file mode 100644 index 0000000..44fe2cf --- /dev/null +++ b/libexec/rtld-elf/sparc64/rtld_machdep.h @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 1999, 2000 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef RTLD_MACHDEP_H +#define RTLD_MACHDEP_H 1 + +#include <sys/types.h> +#include <machine/atomic.h> + +struct Struct_Obj_Entry; + +/* Return the address of the .dynamic section in the dynamic linker. */ +Elf_Dyn *rtld_dynamic_addr(void); +#define rtld_dynamic(obj) rtld_dynamic_addr() +#define RTLD_IS_DYNAMIC() (rtld_dynamic_addr() != NULL) + +Elf_Addr reloc_jmpslot(Elf_Addr *, Elf_Addr, + const struct Struct_Obj_Entry *, + const struct Struct_Obj_Entry *, + const Elf_Rel *); + +#define make_function_pointer(def, defobj) \ + ((defobj)->relocbase + (def)->st_value) + +#define call_initfini_pointer(obj, target) \ + (((InitFunc)(target))()) + +#define call_init_pointer(obj, target) \ + (((InitArrFunc)(target))(main_argc, main_argv, environ)) + +#define round(size, align) \ + (((size) + (align) - 1) & ~((align) - 1)) +#define calculate_first_tls_offset(size, align) \ + round(size, align) +#define calculate_tls_offset(prev_offset, prev_size, size, align) \ + round((prev_offset) + (size), align) +#define calculate_tls_end(off, size) ((off) + (size)) + +typedef struct { + unsigned long ti_module; + unsigned long ti_offset; +} tls_index; + +extern void *__tls_get_addr(tls_index *ti); + +#define RTLD_DEFAULT_STACK_PF_EXEC 0 +#define RTLD_DEFAULT_STACK_EXEC 0 + +#endif diff --git a/libexec/rtld-elf/sparc64/rtld_start.S b/libexec/rtld-elf/sparc64/rtld_start.S new file mode 100644 index 0000000..58bdd1b --- /dev/null +++ b/libexec/rtld-elf/sparc64/rtld_start.S @@ -0,0 +1,170 @@ +/* $NetBSD: rtld_start.S,v 1.5 2001/08/14 22:17:48 eeh Exp $ */ + +/*- + * Copyright (c) 2001 Jake Burkholder. + * Copyright (c) 2000 Eduardo Horvath. + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas and Paul Kranenburg. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <machine/asm.h> + +/* + * ELF: + * On startup the stack should contain 16 extended word register save + * area, followed by the arg count, etc. + */ + +ENTRY(.rtld_start) + clr %fp + mov %o0, %l0 + mov %o3, %l1 + + sub %sp, 16, %sp + add %sp, SPOFF + CCFSZ + 0x0, %o1 + call _rtld + add %sp, SPOFF + CCFSZ + 0x8, %o2 + + ldx [%sp + SPOFF + CCFSZ + 0x0], %o1 + ldx [%sp + SPOFF + CCFSZ + 0x8], %o2 + add %sp, 16, %sp + + mov %l1, %o3 + jmp %o0 + mov %l0, %o0 +END(.rtld_start) + +/* + * Find the address of _DYNAMIC by disassembling a call instruction to it. + * Binutils may not fill in the GOT as expected on other architectures. + */ +.weak _DYNAMIC + +ENTRY(rtld_dynamic_addr) + save %sp, -CCFSZ, %sp + call 1f + nop + call _DYNAMIC + 8 +1: lduw [%o7 + 8], %o0 + sll %o0, 2, %o0 + sra %o0, 0, %o0 + ret + restore %o0, %o7, %o0 +END(rtld_dynamic_addr) + + /* + * We have two separate entry points to the runtime linker. + * I'm implementing this following the SPARC v9 ABI spec. + * + * _rtld_bind_start_0(x, y) is called from .PLT0, and is used for + * PLT entries above 32768. + * + * _rtld_bind_start_1(x, y) is called from .PLT1, and is used for + * PLT entries below 32768. + * + * The first two entries of PLT2 contain the xword object pointer. + * + * These routines are called with two longword arguments, + * x and y. To calculate the address of the entry, + * _rtld_bind_start_1(x, y) does: + * + * n = x >> 15; + * + * and _rtld_bind_start_0(x, y) should do, according to the SCD: + * + * i = x - y - 1048596; + * n = 32768 + (i/5120)*160 + (i%5120)/24; + * + * Note that the number of 1048596 from above is incorrect; rather, + * we need to use HIPLTOFFS as defined below. + * + * Neither routine needs to issue a save since it's already been + * done in the PLT entry. + */ + +#define NPLTLOSLOTS 32768 +#define PLTSLOTSZ 32 +/* + * - 16 to compensate for the difference of the positions of the jumps that + * generate the arguments in .PLT0 and the high plt entry. + */ +#define HIPLTOFFS (NPLTLOSLOTS * PLTSLOTSZ - 16) + +ENTRY(_rtld_bind_start_0) + sethi %hi(HIPLTOFFS), %l1 + or %l1, %lo(HIPLTOFFS), %l1 + sub %o0, %o1, %l0 /* x - y */ + sub %l0, %l1, %l0 /* i = x - y - HIPLTOFFS */ + sethi %hi(5120), %l7 + sdivx %l0, %l7, %l1 /* Calculate i / 5120 */ + mulx %l1, %l7, %l3 + sub %l0, %l3, %l2 /* And i % 5120 */ + mulx %l1, 160, %l5 /* (i / 5120) * 160 */ + sdivx %l2, 24, %l4 /* (i % 5120) / 24 */ + sethi %hi(NPLTLOSLOTS), %l6 + add %l4, %l5, %l4 /* (i / 5120) * 160 + (i % 5120) / 24 */ + add %l4, %l6, %l4 /* + NPLTLOSLOTS */ + sub %l4, 4, %l4 /* XXX: 4 entries are reserved */ + + sllx %l4, 1, %l5 /* Each element is an Elf_Rela which */ + add %l5, %l4, %l4 /* is 3 longwords or 24 bytes. */ + sllx %l4, 3, %l4 /* So multiply by 24. */ + + ldx [%o1 + (10*4)], %o0 /* Load object pointer from PLT2 */ + + call _rtld_bind /* Call _rtld_bind(obj, offset) */ + mov %l4, %o1 + + jmp %o0 /* return value == function address */ + restore /* Dump our stack frame */ +END(_rtld_bind_start_0) + +ENTRY(_rtld_bind_start_1) + srax %o0, 15, %o2 /* %o0 is the index to our PLT slot */ + sub %o2, 4, %o2 /* XXX: 4 entries are reserved */ + + sllx %o2, 1, %o3 /* Each element is an Elf_Rela which */ + add %o3, %o2, %o2 /* is 3 longwords or 24 bytes. */ + sllx %o2, 3, %o2 /* So multiply by 24. */ + + ldx [%o1 + 8], %o0 /* The object pointer is at [%o1 + 8] */ + + call _rtld_bind /* Call _rtld_bind(obj, offset) */ + mov %o2, %o1 + + jmp %o0 /* return value == function address */ + restore /* Dump our stack frame */ +END(_rtld_bind_start_1) diff --git a/libexec/rtld-elf/tests/Makefile b/libexec/rtld-elf/tests/Makefile new file mode 100644 index 0000000..f384218 --- /dev/null +++ b/libexec/rtld-elf/tests/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +TESTSDIR= ${TESTSBASE}/libexec/rtld-elf +SUBDIR+= libpythagoras target + +ATF_TESTS_C= ld_library_pathfds + +.include <bsd.test.mk> diff --git a/libexec/rtld-elf/tests/ld_library_pathfds.c b/libexec/rtld-elf/tests/ld_library_pathfds.c new file mode 100644 index 0000000..1100894 --- /dev/null +++ b/libexec/rtld-elf/tests/ld_library_pathfds.c @@ -0,0 +1,221 @@ +/*- + * Copyright 2014 Jonathan Anderson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <atf-c.h> +#include <fcntl.h> +#include <stdio.h> + + +struct descriptors { + int binary; + int testdir; + int root; + int etc; + int usr; +}; + +static void setup(struct descriptors *, const atf_tc_t *); +static void expect_success(int binary, char *pathfds); +static void expect_missing_library(int binary, char *pathfds); + +static void try_to_run(int binary, int expected_exit_status, + char * const *env, const char *expected_out, const char *expected_err); +static int opendir(const char *name); +static int opendirat(int parent, const char *name); + + +ATF_TC_WITHOUT_HEAD(missing_library); +ATF_TC_BODY(missing_library, tc) +{ + struct descriptors files; + + setup(&files, tc); + expect_missing_library(files.binary, NULL); +} + + +ATF_TC_WITHOUT_HEAD(wrong_library_directories); +ATF_TC_BODY(wrong_library_directories, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE( + asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=%d", files.etc) > 0); + + expect_missing_library(files.binary, pathfds); +} + + +ATF_TC_WITHOUT_HEAD(bad_library_directories); +ATF_TC_BODY(bad_library_directories, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE(asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=::") > 0); + + expect_missing_library(files.binary, pathfds); +} + + +ATF_TC_WITHOUT_HEAD(single_library_directory); +ATF_TC_BODY(single_library_directory, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE( + asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=%d", files.testdir) > 0); + + expect_success(files.binary, pathfds); +} + + +ATF_TC_WITHOUT_HEAD(first_library_directory); +ATF_TC_BODY(first_library_directory, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE( + asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=%d:%d", + files.testdir, files.etc) > 0); + + expect_success(files.binary, pathfds); +} + + +ATF_TC_WITHOUT_HEAD(middle_library_directory); +ATF_TC_BODY(middle_library_directory, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE( + asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=%d:%d:%d", + files.root, files.testdir, files.usr) > 0); + + expect_success(files.binary, pathfds); +} + + +ATF_TC_WITHOUT_HEAD(last_library_directory); +ATF_TC_BODY(last_library_directory, tc) +{ + struct descriptors files; + char *pathfds; + + setup(&files, tc); + ATF_REQUIRE( + asprintf(&pathfds, "LD_LIBRARY_PATH_FDS=%d:%d", + files.root, files.testdir) > 0); + + expect_success(files.binary, pathfds); +} + + + +/* Register test cases with ATF. */ +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, missing_library); + ATF_TP_ADD_TC(tp, wrong_library_directories); + ATF_TP_ADD_TC(tp, bad_library_directories); + ATF_TP_ADD_TC(tp, single_library_directory); + ATF_TP_ADD_TC(tp, first_library_directory); + ATF_TP_ADD_TC(tp, middle_library_directory); + ATF_TP_ADD_TC(tp, last_library_directory); + + return atf_no_error(); +} + + +static void +setup(struct descriptors *dp, const atf_tc_t *tc) +{ + + dp->testdir = opendir(atf_tc_get_config_var(tc, "srcdir")); + ATF_REQUIRE(dp->testdir >= 0); + ATF_REQUIRE( + (dp->binary = openat(dp->testdir, "target", O_RDONLY)) >= 0); + + ATF_REQUIRE((dp->root = opendir("/")) >= 0); + ATF_REQUIRE((dp->etc = opendirat(dp->root, "etc")) >= 0); + ATF_REQUIRE((dp->usr = opendirat(dp->root, "usr")) >= 0); +} + +static void +expect_success(int binary, char *pathfds) +{ + char * const env[] = { pathfds, NULL }; + try_to_run(binary, 0, env, "the hypotenuse of 3 and 4 is 5\n", ""); +} + +static void +expect_missing_library(int binary, char *pathfds) +{ + char * const env[] = { pathfds, NULL }; + try_to_run(binary, 1, env, "", + "Shared object \"libpythagoras.so.0\" not found," + " required by \"target\"\n"); +} + + +static void +try_to_run(int binary, int exit_status, char * const *env, + const char *expected_out, const char *expected_err) +{ + pid_t child = atf_utils_fork(); + + if (child == 0) { + char * const args[] = { "target", NULL }; + + fexecve(binary, args, env); + atf_tc_fail("fexecve() failed"); + } + + atf_utils_wait(child, exit_status, expected_out, expected_err); +} + + +static int +opendir(const char *name) +{ + return open(name, O_RDONLY | O_DIRECTORY); +} + +static int +opendirat(int parent, const char *name) +{ + return openat(parent, name, O_RDONLY | O_DIRECTORY); +} diff --git a/libexec/rtld-elf/tests/libpythagoras/Makefile b/libexec/rtld-elf/tests/libpythagoras/Makefile new file mode 100644 index 0000000..618612f --- /dev/null +++ b/libexec/rtld-elf/tests/libpythagoras/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +LIB= pythagoras +SHLIB_MAJOR= 0 + +LIBDIR= ${TESTSBASE}/libexec/rtld-elf +SHLIBDIR= ${TESTSBASE}/libexec/rtld-elf + +SRCS= pythagoras.c + +DPADD= ${LIBM} +LDADD= -lm + +.include <bsd.lib.mk> diff --git a/libexec/rtld-elf/tests/libpythagoras/pythagoras.c b/libexec/rtld-elf/tests/libpythagoras/pythagoras.c new file mode 100644 index 0000000..c7fa604 --- /dev/null +++ b/libexec/rtld-elf/tests/libpythagoras/pythagoras.c @@ -0,0 +1,42 @@ +/*- + * Copyright 2014 Jonathan Anderson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <errno.h> +#include <math.h> + +#include "pythagoras.h" + +double +pythagorean_theorem(double a, double b) +{ + + if (a <= 0 || b <= 0) { + errno = ERANGE; + return (-1.0); + } + return (sqrt(pow(a, 2) + pow(b, 2))); +} diff --git a/libexec/rtld-elf/tests/libpythagoras/pythagoras.h b/libexec/rtld-elf/tests/libpythagoras/pythagoras.h new file mode 100644 index 0000000..ef5379c --- /dev/null +++ b/libexec/rtld-elf/tests/libpythagoras/pythagoras.h @@ -0,0 +1,28 @@ +/*- + * Copyright 2014 Jonathan Anderson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +double pythagorean_theorem(double, double); diff --git a/libexec/rtld-elf/tests/target/Makefile b/libexec/rtld-elf/tests/target/Makefile new file mode 100644 index 0000000..d5305f9 --- /dev/null +++ b/libexec/rtld-elf/tests/target/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +PROG= target +BINDIR= ${TESTSBASE}/libexec/rtld-elf + +CFLAGS+= -I${.CURDIR}/../libpythagoras + +LDFLAGS+= -L${.OBJDIR}/../libpythagoras +DPADD+= ${.OBJDIR}/../libpythagoras/libpythagoras.a +LDADD= -lpythagoras + +MAN= + +.include <bsd.prog.mk> diff --git a/libexec/rtld-elf/tests/target/target.c b/libexec/rtld-elf/tests/target/target.c new file mode 100644 index 0000000..e88b899 --- /dev/null +++ b/libexec/rtld-elf/tests/target/target.c @@ -0,0 +1,39 @@ +/*- + * Copyright 2014 Jonathan Anderson. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "pythagoras.h" + +#include <stdio.h> + +int +main(int argc, char *argv[]) +{ + float hypotenuse = pythagorean_theorem(3, 4); + printf("the hypotenuse of 3 and 4 is %d\n", (int) hypotenuse); + + return 0; +} diff --git a/libexec/rtld-elf/xmalloc.c b/libexec/rtld-elf/xmalloc.c new file mode 100644 index 0000000..ed195df --- /dev/null +++ b/libexec/rtld-elf/xmalloc.c @@ -0,0 +1,97 @@ +/*- + * Copyright 1996-1998 John D. Polstra. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "rtld.h" +#include "rtld_printf.h" + +void * +xcalloc(size_t number, size_t size) +{ + void *p; + + p = calloc(number, size); + if (p == NULL) { + rtld_fdputstr(STDERR_FILENO, "Out of memory\n"); + _exit(1); + } + return (p); +} + +void * +xmalloc(size_t size) +{ + void *p = malloc(size); + if (p == NULL) { + rtld_fdputstr(STDERR_FILENO, "Out of memory\n"); + _exit(1); + } + return p; +} + +char * +xstrdup(const char *str) +{ + char *copy; + size_t len; + + len = strlen(str) + 1; + copy = xmalloc(len); + memcpy(copy, str, len); + return (copy); +} + +void * +malloc_aligned(size_t size, size_t align) +{ + void *mem, *res; + + if (align < sizeof(void *)) + align = sizeof(void *); + + mem = xmalloc(size + sizeof(void *) + align - 1); + res = (void *)round((uintptr_t)mem + sizeof(void *), align); + *(void **)((uintptr_t)res - sizeof(void *)) = mem; + return (res); +} + +void +free_aligned(void *ptr) +{ + void *mem; + uintptr_t x; + + if (ptr == NULL) + return; + x = (uintptr_t)ptr; + x -= sizeof(void *); + mem = *(void **)x; + free(mem); +} diff --git a/libexec/save-entropy/Makefile b/libexec/save-entropy/Makefile new file mode 100644 index 0000000..3e27c48 --- /dev/null +++ b/libexec/save-entropy/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +SCRIPTS= save-entropy.sh +NO_OBJ= + +.include <bsd.prog.mk> diff --git a/libexec/save-entropy/save-entropy.sh b/libexec/save-entropy/save-entropy.sh new file mode 100755 index 0000000..06319d5 --- /dev/null +++ b/libexec/save-entropy/save-entropy.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# +# Copyright (c) 2001-2006,2012 Douglas Barton, dougb@FreeBSD.org +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +# This script is called by cron to store bits of randomness which are +# then used to seed /dev/random on boot. + +# Originally developed by Doug Barton, dougb@FreeBSD.org + +PATH=/bin:/usr/bin + +# If there is a global system configuration file, suck it in. +# +if [ -r /etc/defaults/rc.conf ]; then + . /etc/defaults/rc.conf + source_rc_confs 2>/dev/null +elif [ -r /etc/rc.conf ]; then + . /etc/rc.conf 2>/dev/null +fi + +[ $(/sbin/sysctl -n security.jail.jailed) = 0 ] || exit 0 + +case ${entropy_dir} in +[Nn][Oo]) + exit 0 + ;; +*) + entropy_dir=${entropy_dir:-/var/db/entropy} + ;; +esac + +entropy_save_sz=${entropy_save_sz:-4096} +entropy_save_num=${entropy_save_num:-8} + +if [ ! -d "${entropy_dir}" ]; then + install -d -o operator -g operator -m 0700 "${entropy_dir}" || { + logger -is -t "$0" The entropy directory "${entropy_dir}" does \ + not exist, and cannot be created. Therefore no entropy can \ + be saved.; exit 1; } +fi + +cd "${entropy_dir}" || { + logger -is -t "$0" Cannot cd to the entropy directory: "${entropy_dir}". \ + Entropy file rotation is aborted.; exit 1; } + +for f in saved-entropy.*; do + case "${f}" in saved-entropy.\*) continue ;; esac # No files match + [ ${f#saved-entropy\.} -ge ${entropy_save_num} ] && unlink ${f} +done + +umask 377 + +n=$(( ${entropy_save_num} - 1 )) +while [ ${n} -ge 1 ]; do + if [ -f "saved-entropy.${n}" ]; then + mv "saved-entropy.${n}" "saved-entropy.$(( ${n} + 1 ))" + elif [ -e "saved-entropy.${n}" -o -L "saved-entropy.${n}" ]; then + logger -is -t "$0" \ + "${entropy_dir}/saved-entropy.${n}" is not a regular file, and so \ + it will not be rotated. Entropy file rotation is aborted. + exit 1 + fi + n=$(( ${n} - 1 )) +done + +dd if=/dev/random of=saved-entropy.1 bs=${entropy_save_sz} count=1 2>/dev/null + +exit 0 diff --git a/libexec/smrsh/Makefile b/libexec/smrsh/Makefile new file mode 100644 index 0000000..ae86155 --- /dev/null +++ b/libexec/smrsh/Makefile @@ -0,0 +1,32 @@ +# @(#)Makefile 8.1 (Berkeley) 7/2/95 +# $FreeBSD$ + +SENDMAIL_DIR=${.CURDIR}/../../contrib/sendmail +.PATH: ${SENDMAIL_DIR}/smrsh + +PROG= smrsh +SRCS= smrsh.c +MAN= smrsh.8 +CFLAGS+=-I${SENDMAIL_DIR}/src -I${SENDMAIL_DIR}/include -I. + +LIBSMDIR= ${.OBJDIR}/../../lib/libsm +LIBSM= ${LIBSMDIR}/libsm.a + +DPADD= ${LIBSM} +LDADD= ${LIBSM} + +WARNS?= 2 + +SRCS+= sm_os.h +CLEANFILES+=sm_os.h + +# User customizations to the sendmail build environment +CFLAGS+=${SENDMAIL_CFLAGS} +DPADD+=${SENDMAIL_DPADD} +LDADD+=${SENDMAIL_LDADD} +LDFLAGS+=${SENDMAIL_LDFLAGS} + +sm_os.h: + ln -sf ${SENDMAIL_DIR}/include/sm/os/sm_os_freebsd.h sm_os.h + +.include <bsd.prog.mk> diff --git a/libexec/talkd/Makefile b/libexec/talkd/Makefile new file mode 100644 index 0000000..cc0c597 --- /dev/null +++ b/libexec/talkd/Makefile @@ -0,0 +1,10 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= ntalkd +SRCS= talkd.c announce.c process.c table.c print.c ttymsg.c +.PATH: ${.CURDIR}/../../usr.bin/wall +MAN= talkd.8 +CFLAGS+=-I${.CURDIR}/../../usr.bin/wall + +.include <bsd.prog.mk> diff --git a/libexec/talkd/announce.c b/libexec/talkd/announce.c new file mode 100644 index 0000000..00c01ac --- /dev/null +++ b/libexec/talkd/announce.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)announce.c 8.3 (Berkeley) 4/28/95"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/socket.h> + +#include <protocols/talkd.h> + +#include <errno.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <vis.h> + +#include "ttymsg.h" +#include "extern.h" + +/* + * Announce an invitation to talk. + */ + +/* + * See if the user is accepting messages. If so, announce that + * a talk is requested. + */ +int +announce(CTL_MSG *request, const char *remote_machine) +{ + char full_tty[32]; + struct stat stbuf; + + (void)snprintf(full_tty, sizeof(full_tty), + "%s%s", _PATH_DEV, request->r_tty); + if (stat(full_tty, &stbuf) < 0 || (stbuf.st_mode&020) == 0) + return (PERMISSION_DENIED); + return (print_mesg(request->r_tty, request, remote_machine)); +} + +#define max(a,b) ( (a) > (b) ? (a) : (b) ) +#define N_LINES 5 +#define N_CHARS 256 + +/* + * Build a block of characters containing the message. + * It is sent blank filled and in a single block to + * try to keep the message in one piece if the recipient + * in vi at the time + */ +int +print_mesg(const char *tty, CTL_MSG *request, + const char *remote_machine) +{ + struct timeval now; + time_t clock_sec; + struct tm *localclock; + struct iovec iovec; + char line_buf[N_LINES][N_CHARS]; + int sizes[N_LINES]; + char big_buf[N_LINES*N_CHARS]; + char *bptr, *lptr, *vis_user; + int i, j, max_size; + + i = 0; + max_size = 0; + gettimeofday(&now, NULL); + clock_sec = now.tv_sec; + localclock = localtime(&clock_sec); + (void)snprintf(line_buf[i], N_CHARS, " "); + sizes[i] = strlen(line_buf[i]); + max_size = max(max_size, sizes[i]); + i++; + (void)snprintf(line_buf[i], N_CHARS, + "Message from Talk_Daemon@%s at %d:%02d on %d/%.2d/%.2d ...", + hostname, localclock->tm_hour , localclock->tm_min, + localclock->tm_year + 1900, localclock->tm_mon + 1, + localclock->tm_mday); + sizes[i] = strlen(line_buf[i]); + max_size = max(max_size, sizes[i]); + i++; + + vis_user = malloc(strlen(request->l_name) * 4 + 1); + strvis(vis_user, request->l_name, VIS_CSTYLE); + (void)snprintf(line_buf[i], N_CHARS, + "talk: connection requested by %s@%s", vis_user, remote_machine); + sizes[i] = strlen(line_buf[i]); + max_size = max(max_size, sizes[i]); + i++; + (void)snprintf(line_buf[i], N_CHARS, "talk: respond with: talk %s@%s", + vis_user, remote_machine); + sizes[i] = strlen(line_buf[i]); + max_size = max(max_size, sizes[i]); + i++; + (void)snprintf(line_buf[i], N_CHARS, " "); + sizes[i] = strlen(line_buf[i]); + max_size = max(max_size, sizes[i]); + i++; + bptr = big_buf; + *bptr++ = '\007'; /* send something to wake them up */ + *bptr++ = '\r'; /* add a \r in case of raw mode */ + *bptr++ = '\n'; + for (i = 0; i < N_LINES; i++) { + /* copy the line into the big buffer */ + lptr = line_buf[i]; + while (*lptr != '\0') + *(bptr++) = *(lptr++); + /* pad out the rest of the lines with blanks */ + for (j = sizes[i]; j < max_size + 2; j++) + *(bptr++) = ' '; + *(bptr++) = '\r'; /* add a \r in case of raw mode */ + *(bptr++) = '\n'; + } + *bptr = '\0'; + iovec.iov_base = big_buf; + iovec.iov_len = bptr - big_buf; + /* + * we choose a timeout of RING_WAIT-5 seconds so that we don't + * stack up processes trying to write messages to a tty + * that is permanently blocked. + */ + if (ttymsg(&iovec, 1, tty, RING_WAIT - 5) != NULL) + return (FAILED); + + return (SUCCESS); +} diff --git a/libexec/talkd/extern.h b/libexec/talkd/extern.h new file mode 100644 index 0000000..a91cb25 --- /dev/null +++ b/libexec/talkd/extern.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2002 M. Warner Losh. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +extern int debug; +extern char hostname[]; + +int announce(CTL_MSG *, const char *); +int delete_invite(u_int32_t); +void do_announce(CTL_MSG *, CTL_RESPONSE *); +CTL_MSG *find_match(CTL_MSG *request); +CTL_MSG *find_request(CTL_MSG *request); +int find_user(const char *name, char *tty); +void insert_table(CTL_MSG *, CTL_RESPONSE *); +int new_id(void); +int print_mesg(const char *, CTL_MSG *, const char *); +void print_request(const char *, CTL_MSG *); +void print_response(const char *, CTL_RESPONSE *); +void process_request(CTL_MSG *mp, CTL_RESPONSE *rp); +void timeout(int sig); diff --git a/libexec/talkd/print.c b/libexec/talkd/print.c new file mode 100644 index 0000000..9a74961 --- /dev/null +++ b/libexec/talkd/print.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)print.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* debug print routines */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <protocols/talkd.h> +#include <stdio.h> +#include <syslog.h> + +#include "extern.h" + +static const char *types[] = + { "leave_invite", "look_up", "delete", "announce" }; +#define NTYPES (sizeof (types) / sizeof (types[0])) +static const char *answers[] = + { "success", "not_here", "failed", "machine_unknown", "permission_denied", + "unknown_request", "badversion", "badaddr", "badctladdr" }; +#define NANSWERS (sizeof (answers) / sizeof (answers[0])) + +void +print_request(const char *cp, CTL_MSG *mp) +{ + const char *tp; + char tbuf[80]; + + if (mp->type > NTYPES) { + (void)snprintf(tbuf, sizeof(tbuf), "type %d", mp->type); + tp = tbuf; + } else + tp = types[mp->type]; + syslog(LOG_DEBUG, "%s: %s: id %lu, l_user %s, r_user %s, r_tty %s", + cp, tp, (long)mp->id_num, mp->l_name, mp->r_name, mp->r_tty); +} + +void +print_response(const char *cp, CTL_RESPONSE *rp) +{ + const char *tp, *ap; + char tbuf[80], abuf[80]; + + if (rp->type > NTYPES) { + (void)snprintf(tbuf, sizeof(tbuf), "type %d", rp->type); + tp = tbuf; + } else + tp = types[rp->type]; + if (rp->answer > NANSWERS) { + (void)snprintf(abuf, sizeof(abuf), "answer %d", rp->answer); + ap = abuf; + } else + ap = answers[rp->answer]; + syslog(LOG_DEBUG, "%s: %s: %s, id %d", cp, tp, ap, ntohl(rp->id_num)); +} diff --git a/libexec/talkd/process.c b/libexec/talkd/process.c new file mode 100644 index 0000000..742043f --- /dev/null +++ b/libexec/talkd/process.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)process.c 8.2 (Berkeley) 11/16/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * process.c handles the requests, which can be of three types: + * ANNOUNCE - announce to a user that a talk is wanted + * LEAVE_INVITE - insert the request into the table + * LOOK_UP - look up to see if a request is waiting in + * in the table for the local user + * DELETE - delete invitation + */ +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <protocols/talkd.h> +#include <ctype.h> +#include <err.h> +#include <netdb.h> +#include <paths.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <utmpx.h> + +#include "extern.h" + +void +process_request(CTL_MSG *mp, CTL_RESPONSE *rp) +{ + CTL_MSG *ptr; + char *s; + + rp->vers = TALK_VERSION; + rp->type = mp->type; + rp->id_num = htonl(0); + if (mp->vers != TALK_VERSION) { + syslog(LOG_WARNING, "bad protocol version %d", mp->vers); + rp->answer = BADVERSION; + return; + } + mp->id_num = ntohl(mp->id_num); + mp->addr.sa_family = ntohs(mp->addr.sa_family); + if (mp->addr.sa_family != AF_INET) { + syslog(LOG_WARNING, "bad address, family %d", + mp->addr.sa_family); + rp->answer = BADADDR; + return; + } + mp->ctl_addr.sa_family = ntohs(mp->ctl_addr.sa_family); + if (mp->ctl_addr.sa_family != AF_INET) { + syslog(LOG_WARNING, "bad control address, family %d", + mp->ctl_addr.sa_family); + rp->answer = BADCTLADDR; + return; + } + for (s = mp->l_name; *s; s++) + if (!isprint(*s)) { + syslog(LOG_NOTICE, "illegal user name. Aborting"); + rp->answer = FAILED; + return; + } + mp->pid = ntohl(mp->pid); + if (debug) + print_request("process_request", mp); + switch (mp->type) { + + case ANNOUNCE: + do_announce(mp, rp); + break; + + case LEAVE_INVITE: + ptr = find_request(mp); + if (ptr != (CTL_MSG *)0) { + rp->id_num = htonl(ptr->id_num); + rp->answer = SUCCESS; + } else + insert_table(mp, rp); + break; + + case LOOK_UP: + ptr = find_match(mp); + if (ptr != (CTL_MSG *)0) { + rp->id_num = htonl(ptr->id_num); + rp->addr = ptr->addr; + rp->addr.sa_family = htons(ptr->addr.sa_family); + rp->answer = SUCCESS; + } else + rp->answer = NOT_HERE; + break; + + case DELETE: + rp->answer = delete_invite(mp->id_num); + break; + + default: + rp->answer = UNKNOWN_REQUEST; + break; + } + if (debug) + print_response("process_request", rp); +} + +void +do_announce(CTL_MSG *mp, CTL_RESPONSE *rp) +{ + struct hostent *hp; + CTL_MSG *ptr; + int result; + + /* see if the user is logged */ + result = find_user(mp->r_name, mp->r_tty); + if (result != SUCCESS) { + rp->answer = result; + return; + } +#define satosin(sa) ((struct sockaddr_in *)(void *)(sa)) + hp = gethostbyaddr(&satosin(&mp->ctl_addr)->sin_addr, + sizeof (struct in_addr), AF_INET); + if (hp == (struct hostent *)0) { + rp->answer = MACHINE_UNKNOWN; + return; + } + ptr = find_request(mp); + if (ptr == (CTL_MSG *) 0) { + insert_table(mp, rp); + rp->answer = announce(mp, hp->h_name); + return; + } + if (mp->id_num > ptr->id_num) { + /* + * This is an explicit re-announce, so update the id_num + * field to avoid duplicates and re-announce the talk. + */ + ptr->id_num = new_id(); + rp->id_num = htonl(ptr->id_num); + rp->answer = announce(mp, hp->h_name); + } else { + /* a duplicated request, so ignore it */ + rp->id_num = htonl(ptr->id_num); + rp->answer = SUCCESS; + } +} + +/* + * Search utmp for the local user + */ +int +find_user(const char *name, char *tty) +{ + struct utmpx *ut; + int status; + struct stat statb; + time_t best = 0; + char ftty[sizeof(_PATH_DEV) - 1 + sizeof(ut->ut_line)]; + + setutxent(); + status = NOT_HERE; + (void) strcpy(ftty, _PATH_DEV); + while ((ut = getutxent()) != NULL) + if (ut->ut_type == USER_PROCESS && + strcmp(ut->ut_user, name) == 0) { + if (*tty == '\0' || best != 0) { + if (best == 0) + status = PERMISSION_DENIED; + /* no particular tty was requested */ + (void) strcpy(ftty + sizeof(_PATH_DEV) - 1, + ut->ut_line); + if (stat(ftty, &statb) == 0) { + if (!(statb.st_mode & 020)) + continue; + if (statb.st_atime > best) { + best = statb.st_atime; + (void) strcpy(tty, ut->ut_line); + status = SUCCESS; + continue; + } + } + } + if (strcmp(ut->ut_line, tty) == 0) { + status = SUCCESS; + break; + } + } + endutxent(); + return (status); +} diff --git a/libexec/talkd/table.c b/libexec/talkd/table.c new file mode 100644 index 0000000..70b71b2 --- /dev/null +++ b/libexec/talkd/table.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)table.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * Routines to handle insertion, deletion, etc on the table + * of requests kept by the daemon. Nothing fancy here, linear + * search on a double-linked list. A time is kept with each + * entry so that overly old invitations can be eliminated. + * + * Consider this a mis-guided attempt at modularity + */ +#include <sys/param.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <protocols/talkd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "extern.h" + +#define MAX_ID 16000 /* << 2^15 so I don't have sign troubles */ + +#define NIL ((TABLE_ENTRY *)0) + +static struct timeval tp; + +typedef struct table_entry TABLE_ENTRY; + +struct table_entry { + CTL_MSG request; + long time; + TABLE_ENTRY *next; + TABLE_ENTRY *last; +}; + +static void delete(TABLE_ENTRY *); + +static TABLE_ENTRY *table = NIL; + +/* + * Look in the table for an invitation that matches the current + * request looking for an invitation + */ +CTL_MSG * +find_match(CTL_MSG *request) +{ + TABLE_ENTRY *ptr; + time_t current_time; + + gettimeofday(&tp, NULL); + current_time = tp.tv_sec; + if (debug) + print_request("find_match", request); + for (ptr = table; ptr != NIL; ptr = ptr->next) { + if ((ptr->time - current_time) > MAX_LIFE) { + /* the entry is too old */ + if (debug) + print_request("deleting expired entry", + &ptr->request); + delete(ptr); + continue; + } + if (debug) + print_request("", &ptr->request); + if (strcmp(request->l_name, ptr->request.r_name) == 0 && + strcmp(request->r_name, ptr->request.l_name) == 0 && + ptr->request.type == LEAVE_INVITE) + return (&ptr->request); + } + return ((CTL_MSG *)0); +} + +/* + * Look for an identical request, as opposed to a complimentary + * one as find_match does + */ +CTL_MSG * +find_request(CTL_MSG *request) +{ + TABLE_ENTRY *ptr; + time_t current_time; + + gettimeofday(&tp, NULL); + current_time = tp.tv_sec; + /* + * See if this is a repeated message, and check for + * out of date entries in the table while we are it. + */ + if (debug) + print_request("find_request", request); + for (ptr = table; ptr != NIL; ptr = ptr->next) { + if ((ptr->time - current_time) > MAX_LIFE) { + /* the entry is too old */ + if (debug) + print_request("deleting expired entry", + &ptr->request); + delete(ptr); + continue; + } + if (debug) + print_request("", &ptr->request); + if (strcmp(request->r_name, ptr->request.r_name) == 0 && + strcmp(request->l_name, ptr->request.l_name) == 0 && + request->type == ptr->request.type && + request->pid == ptr->request.pid) { + /* update the time if we 'touch' it */ + ptr->time = current_time; + return (&ptr->request); + } + } + return ((CTL_MSG *)0); +} + +void +insert_table(CTL_MSG *request, CTL_RESPONSE *response) +{ + TABLE_ENTRY *ptr; + time_t current_time; + + gettimeofday(&tp, NULL); + current_time = tp.tv_sec; + request->id_num = new_id(); + response->id_num = htonl(request->id_num); + /* insert a new entry into the top of the list */ + ptr = (TABLE_ENTRY *)malloc(sizeof(TABLE_ENTRY)); + if (ptr == NIL) { + syslog(LOG_ERR, "insert_table: Out of memory"); + _exit(1); + } + ptr->time = current_time; + ptr->request = *request; + ptr->next = table; + if (ptr->next != NIL) + ptr->next->last = ptr; + ptr->last = NIL; + table = ptr; +} + +/* + * Generate a unique non-zero sequence number + */ +int +new_id(void) +{ + static int current_id = 0; + + current_id = (current_id + 1) % MAX_ID; + /* 0 is reserved, helps to pick up bugs */ + if (current_id == 0) + current_id = 1; + return (current_id); +} + +/* + * Delete the invitation with id 'id_num' + */ +int +delete_invite(u_int32_t id_num) +{ + TABLE_ENTRY *ptr; + + ptr = table; + if (debug) + syslog(LOG_DEBUG, "delete_invite(%d)", id_num); + for (ptr = table; ptr != NIL; ptr = ptr->next) { + if (ptr->request.id_num == id_num) + break; + if (debug) + print_request("", &ptr->request); + } + if (ptr != NIL) { + delete(ptr); + return (SUCCESS); + } + return (NOT_HERE); +} + +/* + * Classic delete from a double-linked list + */ +static void +delete(TABLE_ENTRY *ptr) +{ + + if (debug) + print_request("delete", &ptr->request); + if (table == ptr) + table = ptr->next; + else if (ptr->last != NIL) + ptr->last->next = ptr->next; + if (ptr->next != NIL) + ptr->next->last = ptr->last; + free((char *)ptr); +} diff --git a/libexec/talkd/talkd.8 b/libexec/talkd/talkd.8 new file mode 100644 index 0000000..27f3a4e --- /dev/null +++ b/libexec/talkd/talkd.8 @@ -0,0 +1,76 @@ +.\" Copyright (c) 1983, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)talkd.8 8.2 (Berkeley) 12/11/93 +.\" $FreeBSD$ +.\" +.Dd December 11, 1993 +.Dt TALKD 8 +.Os +.Sh NAME +.Nm talkd +.Nd remote user communication server +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility +is the server that notifies a user that someone else wants to +initiate a conversation. +It acts as a repository of invitations, responding to requests +by clients wishing to rendezvous to hold a conversation. +In normal operation, a client, the caller, +initiates a rendezvous by sending a +.Tn CTL_MSG +to the server of +type +.Tn LOOK_UP +(see +.In protocols/talkd.h ) . +This causes the server to search its invitation +tables to check if an invitation currently exists for the caller +(to speak to the callee specified in the message). +.Pp +If the lookup fails, +the caller then sends an +.Tn ANNOUNCE +message causing the server to +broadcast an announcement on the callee's login ports requesting contact. +.Pp +When the callee responds, the local server uses the +recorded invitation to respond with the appropriate rendezvous +address and the caller and callee client programs establish a +stream connection through which the conversation takes place. +.Sh SEE ALSO +.Xr talk 1 , +.Xr write 1 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.3 . diff --git a/libexec/talkd/talkd.c b/libexec/talkd/talkd.c new file mode 100644 index 0000000..a444fe7 --- /dev/null +++ b/libexec/talkd/talkd.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1983, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)talkd.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +/* + * The top level of the daemon, the format is heavily borrowed + * from rwhod.c. Basically: find out who and where you are; + * disconnect all descriptors and ttys, and then endless + * loop on waiting for and processing requests + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <protocols/talkd.h> +#include <err.h> +#include <errno.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "extern.h" + +static CTL_MSG request; +static CTL_RESPONSE response; + +int debug = 0; +static long lastmsgtime; + +char hostname[MAXHOSTNAMELEN]; + +#define TIMEOUT 30 +#define MAXIDLE 120 + +int +main(int argc, char *argv[]) +{ + register CTL_MSG *mp = &request; + int cc; + struct sockaddr ctl_addr; + +#ifdef NOTDEF + /* + * removed so ntalkd can run in tty sandbox + */ + if (getuid()) + errx(1, "getuid: not super-user"); +#endif + openlog("talkd", LOG_PID, LOG_DAEMON); + if (gethostname(hostname, sizeof(hostname) - 1) < 0) { + syslog(LOG_ERR, "gethostname: %m"); + _exit(1); + } + hostname[sizeof(hostname) - 1] = '\0'; + if (chdir(_PATH_DEV) < 0) { + syslog(LOG_ERR, "chdir: %s: %m", _PATH_DEV); + _exit(1); + } + if (argc > 1 && strcmp(argv[1], "-d") == 0) + debug = 1; + signal(SIGALRM, timeout); + alarm(TIMEOUT); + for (;;) { + cc = recv(0, (char *)mp, sizeof(*mp), 0); + if (cc != sizeof (*mp)) { + if (cc < 0 && errno != EINTR) + syslog(LOG_WARNING, "recv: %m"); + continue; + } + lastmsgtime = time(0); + (void)memcpy(&ctl_addr, &mp->ctl_addr, sizeof(ctl_addr)); + ctl_addr.sa_family = ntohs(mp->ctl_addr.sa_family); + ctl_addr.sa_len = sizeof(ctl_addr); + process_request(mp, &response); + /* can block here, is this what I want? */ + cc = sendto(STDIN_FILENO, (char *)&response, + sizeof(response), 0, &ctl_addr, sizeof(ctl_addr)); + if (cc != sizeof (response)) + syslog(LOG_WARNING, "sendto: %m"); + } +} + +void +timeout(int sig __unused) +{ + int save_errno = errno; + + if (time(0) - lastmsgtime >= MAXIDLE) + _exit(0); + alarm(TIMEOUT); + errno = save_errno; +} diff --git a/libexec/tcpd/Makefile b/libexec/tcpd/Makefile new file mode 100644 index 0000000..f3fbf29 --- /dev/null +++ b/libexec/tcpd/Makefile @@ -0,0 +1,23 @@ +# $FreeBSD$ + +.include <src.opts.mk> + +.PATH: ${.CURDIR}/../../contrib/tcp_wrappers + +PROG= tcpd +MAN= tcpd.8 +CSTD?= c89 +CFLAGS+=-DREAL_DAEMON_DIR=\"/usr/libexec\" \ + -DSEVERITY=LOG_INFO -DRFC931_TIMEOUT=10 \ + -DHOSTS_DENY=\"/etc/hosts.deny\" -DHOSTS_ALLOW=\"/etc/hosts.allow\" \ + -DFACILITY=LOG_DAEMON +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+=-DINET6 +.endif + +DPADD= ${LIBWRAP} +LDADD= -lwrap + +WARNS?= 1 + +.include <bsd.prog.mk> diff --git a/libexec/telnetd/Makefile b/libexec/telnetd/Makefile new file mode 100644 index 0000000..690b03c --- /dev/null +++ b/libexec/telnetd/Makefile @@ -0,0 +1,47 @@ +# $FreeBSD$ + +# Do not define -DKLUDGELINEMODE, as it does not interact well with many +# telnet implementations. + +.include <src.opts.mk> + +TELNETDIR= ${.CURDIR}/../../contrib/telnet +.PATH: ${TELNETDIR}/telnetd + +PROG= telnetd +MAN= telnetd.8 + +SRCS= global.c slc.c state.c sys_term.c telnetd.c \ + termstat.c utility.c + +WARNS?= 2 +WFORMAT?= 0 + +CFLAGS+= -DLINEMODE -DUSE_TERMIO -DDIAGNOSTICS -DOLD_ENVIRON \ + -DENV_HACK -DSTREAMSPTY + +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+= -DINET6 +.endif + +CFLAGS+= -I${TELNETDIR} + +LIBTELNET= ${.OBJDIR}/../../lib/libtelnet/libtelnet.a + +DPADD= ${LIBUTIL} ${LIBTERMCAPW} ${LIBTELNET} +LDADD= -lutil -ltermcapw ${LIBTELNET} + +.if ${MK_OPENSSL} != "no" +SRCS+= authenc.c +CFLAGS+= -DAUTHENTICATION -DENCRYPTION +DPADD+= ${LIBMP} ${LIBCRYPTO} ${LIBCRYPT} ${LIBPAM} +LDADD+= -lmp -lcrypto -lcrypt ${MINUSLPAM} +.endif + +.if ${MK_KERBEROS_SUPPORT} != "no" +CFLAGS+= -DKRB5 -DFORWARD -Dnet_write=telnet_net_write +DPADD+= ${LIBKRB5} ${LIBHX509} ${LIBASN1} ${LIBROKEN} ${LIBCOM_ERR} +LDADD+= -lkrb5 -lhx509 -lasn1 -lroken -lcom_err +.endif + +.include <bsd.prog.mk> diff --git a/libexec/tests/Makefile b/libexec/tests/Makefile new file mode 100644 index 0000000..665efab --- /dev/null +++ b/libexec/tests/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +TESTSDIR= ${TESTSBASE}/libexec + +.PATH: ${.CURDIR:H:H}/tests +KYUAFILE= yes + +.include <bsd.test.mk> diff --git a/libexec/tftp-proxy/Makefile b/libexec/tftp-proxy/Makefile new file mode 100644 index 0000000..d8541c4 --- /dev/null +++ b/libexec/tftp-proxy/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../contrib/pf/tftp-proxy + +PROG= tftp-proxy +SRCS= tftp-proxy.c filter.c +MAN= tftp-proxy.8 + +WARNS?= 3 + +.include <bsd.prog.mk> diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile new file mode 100644 index 0000000..f005001 --- /dev/null +++ b/libexec/tftpd/Makefile @@ -0,0 +1,13 @@ +# @(#)Makefile 8.1 (Berkeley) 6/4/93 +# $FreeBSD$ + +PROG= tftpd +MAN= tftpd.8 +SRCS= tftp-file.c tftp-io.c tftp-options.c tftp-transfer.c tftp-utils.c +SRCS+= tftpd.c +WFORMAT=0 + +DPADD= ${LIBWRAP} +LDADD= -lwrap + +.include <bsd.prog.mk> diff --git a/libexec/tftpd/tftp-file.c b/libexec/tftpd/tftp-file.c new file mode 100644 index 0000000..6b8fb6e --- /dev/null +++ b/libexec/tftpd/tftp-file.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "tftp-file.h" +#include "tftp-utils.h" + +static FILE *file; +static int convert; + +static char convbuffer[66000]; +static int gotcr = 0; + +static size_t +convert_from_net(char *buffer, size_t count) +{ + size_t i, n; + + /* + * Convert all CR/LF to LF and all CR,NUL to CR + */ + + n = 0; + for (i = 0; i < count; i++) { + + if (gotcr == 0) { + convbuffer[n++] = buffer[i]; + gotcr = (buffer[i] == '\r'); + continue; + } + + /* CR, NULL -> CR */ + if (buffer[i] == '\0') { + gotcr = 0; + continue; + } + + /* CR, LF -> LF */ + if (buffer[i] == '\n') { + if (n == 0) { + if (ftell(file) != 0) { + fseek(file, -1, SEEK_END); + convbuffer[n++] = '\n'; + } else { + /* This shouldn't happen */ + tftp_log(LOG_ERR, + "Received LF as first character"); + abort(); + } + } else + convbuffer[n-1] = '\n'; + gotcr = 0; + continue; + } + + /* Everything else just accept as is */ + convbuffer[n++] = buffer[i]; + gotcr = (buffer[i] == '\r'); + continue; + } + + return fwrite(convbuffer, 1, n, file); +} + +static size_t +convert_to_net(char *buffer, size_t count, int init) +{ + size_t i; + static size_t n = 0, in = 0; + static int newline = 0; + + if (init) { + newline = 0; + n = 0; + in = 0; + return 0 ; + } + + /* + * Convert all LF to CR,LF and all CR to CR,NUL + */ + i = 0; + + if (newline) { + buffer[i++] = newline; + newline = 0; + } + + while (i < count) { + if (n == in) { + /* When done we're done */ + if (feof(file)) break; + + /* Otherwise read another bunch */ + in = fread(convbuffer, 1, count, file); + if (in == 0) break; + n = 0; + } + + /* CR -> CR,NULL */ + if (convbuffer[n] == '\r') { + buffer[i++] = '\r'; + buffer[i++] = '\0'; + n++; + continue; + } + + /* LF -> CR,LF */ + if (convbuffer[n] == '\n') { + buffer[i++] = '\r'; + buffer[i++] = '\n'; + n++; + continue; + } + + buffer[i++] = convbuffer[n++]; + } + + if (i > count) { + /* + * Whoops... that isn't alllowed (but it will happen + * when there is a CR or LF at the end of the buffer) + */ + newline = buffer[i-1]; + } + + if (i < count) { + /* We are done! */ + return i; + } else + return count; + +} + +int +write_init(int fd, FILE *f, const char *mode) +{ + + if (f == NULL) { + file = fdopen(fd, "w"); + if (file == NULL) { + int en = errno; + tftp_log(LOG_ERR, "fdopen() failed: %s", + strerror(errno)); + return en; + } + } else + file = f; + convert = !strcmp(mode, "netascii"); + return 0; +} + +size_t +write_file(char *buffer, int count) +{ + + if (convert == 0) + return fwrite(buffer, 1, count, file); + + return convert_from_net(buffer, count); +} + +int +write_close(void) +{ + + if (fclose(file) != 0) { + tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno)); + return 1; + } + return 0; +} + +int +read_init(int fd, FILE *f, const char *mode) +{ + + convert_to_net(NULL, 0, 1); + if (f == NULL) { + file = fdopen(fd, "r"); + if (file == NULL) { + int en = errno; + tftp_log(LOG_ERR, "fdopen() failed: %s", + strerror(errno)); + return en; + } + } else + file = f; + convert = !strcmp(mode, "netascii"); + return 0; +} + +size_t +read_file(char *buffer, int count) +{ + + if (convert == 0) + return fread(buffer, 1, count, file); + + return convert_to_net(buffer, count, 0); +} + +int +read_close(void) +{ + + if (fclose(file) != 0) { + tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno)); + return 1; + } + return 0; +} + + +/* When an error has occurred, it is possible that the two sides + * are out of synch. Ie: that what I think is the other side's + * response to packet N is really their response to packet N-1. + * + * So, to try to prevent that, we flush all the input queued up + * for us on the network connection on our host. + * + * We return the number of packets we flushed (mostly for reporting + * when trace is active). + */ + +int +synchnet(int peer) /* socket to flush */ +{ + int i, j = 0; + char rbuf[MAXPKTSIZE]; + struct sockaddr_storage from; + socklen_t fromlen; + + while (1) { + (void) ioctl(peer, FIONREAD, &i); + if (i) { + j++; + fromlen = sizeof from; + (void) recvfrom(peer, rbuf, sizeof (rbuf), 0, + (struct sockaddr *)&from, &fromlen); + } else { + return(j); + } + } +} diff --git a/libexec/tftpd/tftp-file.h b/libexec/tftpd/tftp-file.h new file mode 100644 index 0000000..fcc4d0d --- /dev/null +++ b/libexec/tftpd/tftp-file.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +int write_init(int fd, FILE *f, const char *mode); +size_t write_file(char *buffer, int count); +int write_close(void); + +int read_init(int fd, FILE *f, const char *mode); +size_t read_file(char *buffer, int count); +int read_close(void); + +int synchnet(int peer); diff --git a/libexec/tftpd/tftp-io.c b/libexec/tftpd/tftp-io.c new file mode 100644 index 0000000..6dabf4f --- /dev/null +++ b/libexec/tftpd/tftp-io.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "tftp-file.h" +#include "tftp-io.h" +#include "tftp-utils.h" +#include "tftp-options.h" + +struct sockaddr_storage peer_sock; +struct sockaddr_storage me_sock; + +static int send_packet(int peer, uint16_t block, char *pkt, int size); + +static struct errmsg { + int e_code; + const char *e_msg; +} errmsgs[] = { + { EUNDEF, "Undefined error code" }, + { ENOTFOUND, "File not found" }, + { EACCESS, "Access violation" }, + { ENOSPACE, "Disk full or allocation exceeded" }, + { EBADOP, "Illegal TFTP operation" }, + { EBADID, "Unknown transfer ID" }, + { EEXISTS, "File already exists" }, + { ENOUSER, "No such user" }, + { EOPTNEG, "Option negotiation" }, + { -1, NULL } +}; + +#define DROPPACKET(s) \ + if (packetdroppercentage != 0 && \ + random()%100 < packetdroppercentage) { \ + tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \ + return; \ + } +#define DROPPACKETn(s,n) \ + if (packetdroppercentage != 0 && \ + random()%100 < packetdroppercentage) { \ + tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \ + return (n); \ + } + +const char * +errtomsg(int error) +{ + static char ebuf[40]; + struct errmsg *pe; + + if (error == 0) + return ("success"); + for (pe = errmsgs; pe->e_code >= 0; pe++) + if (pe->e_code == error) + return (pe->e_msg); + snprintf(ebuf, sizeof(ebuf), "error %d", error); + return (ebuf); +} + +static int +send_packet(int peer, uint16_t block, char *pkt, int size) +{ + int i; + int t = 1; + + for (i = 0; i < 12 ; i++) { + DROPPACKETn("send_packet", 0); + + if (sendto(peer, pkt, size, 0, (struct sockaddr *)&peer_sock, + peer_sock.ss_len) == size) { + if (i) + tftp_log(LOG_ERR, + "%s block %d, attempt %d successful", + packettype(ntohs(((struct tftphdr *) + (pkt))->th_opcode)), block, i); + return (0); + } + tftp_log(LOG_ERR, + "%s block %d, attempt %d failed (Error %d: %s)", + packettype(ntohs(((struct tftphdr *)(pkt))->th_opcode)), + block, i, errno, strerror(errno)); + sleep(t); + if (t < 32) + t <<= 1; + } + tftp_log(LOG_ERR, "send_packet: %s", strerror(errno)); + return (1); +} + +/* + * Send an ERROR packet (error message). + * Error code passed in is one of the + * standard TFTP codes, or a UNIX errno + * offset by 100. + */ +void +send_error(int peer, int error) +{ + struct tftphdr *tp; + int length; + struct errmsg *pe; + char buf[MAXPKTSIZE]; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending ERROR %d", error); + + DROPPACKET("send_error"); + + tp = (struct tftphdr *)buf; + tp->th_opcode = htons((u_short)ERROR); + tp->th_code = htons((u_short)error); + for (pe = errmsgs; pe->e_code >= 0; pe++) + if (pe->e_code == error) + break; + if (pe->e_code < 0) { + pe->e_msg = strerror(error - 100); + tp->th_code = EUNDEF; /* set 'undef' errorcode */ + } + strcpy(tp->th_msg, pe->e_msg); + length = strlen(pe->e_msg); + tp->th_msg[length] = '\0'; + length += 5; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending ERROR %d: %s", error, tp->th_msg); + + if (sendto(peer, buf, length, 0, + (struct sockaddr *)&peer_sock, peer_sock.ss_len) != length) + tftp_log(LOG_ERR, "send_error: %s", strerror(errno)); +} + +/* + * Send an WRQ packet (write request). + */ +int +send_wrq(int peer, char *filename, char *mode) +{ + int n; + struct tftphdr *tp; + char *bp; + char buf[MAXPKTSIZE]; + int size; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending WRQ: filename: '%s', mode '%s'", + filename, mode + ); + + DROPPACKETn("send_wrq", 1); + + tp = (struct tftphdr *)buf; + tp->th_opcode = htons((u_short)WRQ); + size = 2; + + bp = tp->th_stuff; + strcpy(bp, filename); + bp += strlen(filename); + *bp = 0; + bp++; + size += strlen(filename) + 1; + + strcpy(bp, mode); + bp += strlen(mode); + *bp = 0; + bp++; + size += strlen(mode) + 1; + + if (options_rfc_enabled) + size += make_options(peer, bp, sizeof(buf) - size); + + n = sendto(peer, buf, size, 0, + (struct sockaddr *)&peer_sock, peer_sock.ss_len); + if (n != size) { + tftp_log(LOG_ERR, "send_wrq: %s", strerror(errno)); + return (1); + } + return (0); +} + +/* + * Send an RRQ packet (write request). + */ +int +send_rrq(int peer, char *filename, char *mode) +{ + int n; + struct tftphdr *tp; + char *bp; + char buf[MAXPKTSIZE]; + int size; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending RRQ: filename: '%s', mode '%s'", + filename, mode + ); + + DROPPACKETn("send_rrq", 1); + + tp = (struct tftphdr *)buf; + tp->th_opcode = htons((u_short)RRQ); + size = 2; + + bp = tp->th_stuff; + strcpy(bp, filename); + bp += strlen(filename); + *bp = 0; + bp++; + size += strlen(filename) + 1; + + strcpy(bp, mode); + bp += strlen(mode); + *bp = 0; + bp++; + size += strlen(mode) + 1; + + if (options_rfc_enabled) { + options[OPT_TSIZE].o_request = strdup("0"); + size += make_options(peer, bp, sizeof(buf) - size); + } + + n = sendto(peer, buf, size, 0, + (struct sockaddr *)&peer_sock, peer_sock.ss_len); + if (n != size) { + tftp_log(LOG_ERR, "send_rrq: %d %s", n, strerror(errno)); + return (1); + } + return (0); +} + +/* + * Send an OACK packet (option acknowledgement). + */ +int +send_oack(int peer) +{ + struct tftphdr *tp; + int size, i, n; + char *bp; + char buf[MAXPKTSIZE]; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending OACK"); + + DROPPACKETn("send_oack", 0); + + /* + * Send back an options acknowledgement (only the ones with + * a reply for) + */ + tp = (struct tftphdr *)buf; + bp = buf + 2; + size = sizeof(buf) - 2; + tp->th_opcode = htons((u_short)OACK); + for (i = 0; options[i].o_type != NULL; i++) { + if (options[i].o_reply != NULL) { + n = snprintf(bp, size, "%s%c%s", options[i].o_type, + 0, options[i].o_reply); + bp += n+1; + size -= n+1; + if (size < 0) { + tftp_log(LOG_ERR, "oack: buffer overflow"); + exit(1); + } + } + } + size = bp - buf; + + if (sendto(peer, buf, size, 0, + (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { + tftp_log(LOG_INFO, "send_oack: %s", strerror(errno)); + return (1); + } + + return (0); +} + +/* + * Send an ACK packet (acknowledgement). + */ +int +send_ack(int fp, uint16_t block) +{ + struct tftphdr *tp; + int size; + char buf[MAXPKTSIZE]; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending ACK for block %d", block); + + DROPPACKETn("send_ack", 0); + + tp = (struct tftphdr *)buf; + size = sizeof(buf) - 2; + tp->th_opcode = htons((u_short)ACK); + tp->th_block = htons((u_short)block); + size = 4; + + if (sendto(fp, buf, size, 0, + (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { + tftp_log(LOG_INFO, "send_ack: %s", strerror(errno)); + return (1); + } + + return (0); +} + +/* + * Send a DATA packet + */ +int +send_data(int peer, uint16_t block, char *data, int size) +{ + char buf[MAXPKTSIZE]; + struct tftphdr *pkt; + int n; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Sending DATA packet %d of %d bytes", + block, size); + + DROPPACKETn("send_data", 0); + + pkt = (struct tftphdr *)buf; + + pkt->th_opcode = htons((u_short)DATA); + pkt->th_block = htons((u_short)block); + memcpy(pkt->th_data, data, size); + + n = send_packet(peer, block, (char *)pkt, size + 4); + return (n); +} + + +/* + * Receive a packet + */ +static jmp_buf timeoutbuf; + +static void +timeout(int sig __unused) +{ + + /* tftp_log(LOG_DEBUG, "Timeout\n"); Inside a signal handler... */ + longjmp(timeoutbuf, 1); +} + +int +receive_packet(int peer, char *data, int size, struct sockaddr_storage *from, + int thistimeout) +{ + struct tftphdr *pkt; + struct sockaddr_storage from_local; + struct sockaddr_storage *pfrom; + socklen_t fromlen; + int n; + static int waiting; + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, + "Waiting %d seconds for packet", timeoutpacket); + + pkt = (struct tftphdr *)data; + + waiting = 0; + signal(SIGALRM, timeout); + setjmp(timeoutbuf); + alarm(thistimeout); + + if (waiting > 0) { + alarm(0); + return (RP_TIMEOUT); + } + + if (waiting > 0) { + tftp_log(LOG_ERR, "receive_packet: timeout"); + alarm(0); + return (RP_TIMEOUT); + } + + waiting++; + pfrom = (from == NULL) ? &from_local : from; + fromlen = sizeof(*pfrom); + n = recvfrom(peer, data, size, 0, (struct sockaddr *)pfrom, &fromlen); + + alarm(0); + + DROPPACKETn("receive_packet", RP_TIMEOUT); + + if (n < 0) { + tftp_log(LOG_ERR, "receive_packet: timeout"); + return (RP_TIMEOUT); + } + + alarm(0); + + if (n < 0) { + /* No idea what could have happened if it isn't a timeout */ + tftp_log(LOG_ERR, "receive_packet: %s", strerror(errno)); + return (RP_RECVFROM); + } + if (n < 4) { + tftp_log(LOG_ERR, + "receive_packet: packet too small (%d bytes)", n); + return (RP_TOOSMALL); + } + + pkt->th_opcode = ntohs((u_short)pkt->th_opcode); + if (pkt->th_opcode == DATA || + pkt->th_opcode == ACK) + pkt->th_block = ntohs((u_short)pkt->th_block); + + if (pkt->th_opcode == DATA && n > pktsize) { + tftp_log(LOG_ERR, "receive_packet: packet too big"); + return (RP_TOOBIG); + } + + if (((struct sockaddr_in *)(pfrom))->sin_addr.s_addr != + ((struct sockaddr_in *)(&peer_sock))->sin_addr.s_addr) { + tftp_log(LOG_ERR, + "receive_packet: received packet from wrong source"); + return (RP_WRONGSOURCE); + } + + if (pkt->th_opcode == ERROR) { + tftp_log(pkt->th_code == EUNDEF ? LOG_DEBUG : LOG_ERR, + "Got ERROR packet: %s", pkt->th_msg); + return (RP_ERROR); + } + + if (debug&DEBUG_PACKETS) + tftp_log(LOG_DEBUG, "Received %d bytes in a %s packet", + n, packettype(pkt->th_opcode)); + + return n - 4; +} diff --git a/libexec/tftpd/tftp-io.h b/libexec/tftpd/tftp-io.h new file mode 100644 index 0000000..70558bc --- /dev/null +++ b/libexec/tftpd/tftp-io.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#define RP_NONE 0 +#define RP_RECVFROM -1 +#define RP_TOOSMALL -2 +#define RP_ERROR -3 +#define RP_WRONGSOURCE -4 +#define RP_TIMEOUT -5 +#define RP_TOOBIG -6 + +const char *errtomsg(int); +void send_error(int peer, int); +int send_wrq(int peer, char *, char *); +int send_rrq(int peer, char *, char *); +int send_oack(int peer); +int send_ack(int peer, unsigned short); +int send_data(int peer, uint16_t, char *, int); +int receive_packet(int peer, char *, int, struct sockaddr_storage *, int); + +extern struct sockaddr_storage peer_sock; +extern struct sockaddr_storage me_sock; diff --git a/libexec/tftpd/tftp-options.c b/libexec/tftpd/tftp-options.c new file mode 100644 index 0000000..f926f32 --- /dev/null +++ b/libexec/tftpd/tftp-options.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "tftp-utils.h" +#include "tftp-io.h" +#include "tftp-options.h" + +/* + * Option handlers + */ + +struct options options[] = { + { "tsize", NULL, NULL, NULL /* option_tsize */, 1 }, + { "timeout", NULL, NULL, option_timeout, 1 }, + { "blksize", NULL, NULL, option_blksize, 1 }, + { "blksize2", NULL, NULL, option_blksize2, 0 }, + { "rollover", NULL, NULL, option_rollover, 0 }, + { NULL, NULL, NULL, NULL, 0 } +}; + +/* By default allow them */ +int options_rfc_enabled = 1; +int options_extra_enabled = 1; + +/* + * Rules for the option handlers: + * - If there is no o_request, there will be no processing. + * + * For servers + * - Logging is done as warnings. + * - The handler exit()s if there is a serious problem with the + * values submitted in the option. + * + * For clients + * - Logging is done as errors. After all, the server shouldn't + * return rubbish. + * - The handler returns if there is a serious problem with the + * values submitted in the option. + * - Sending the EBADOP packets is done by the handler. + */ + +int +option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode, + struct stat *stbuf) +{ + + if (options[OPT_TSIZE].o_request == NULL) + return (0); + + if (mode == RRQ) + asprintf(&options[OPT_TSIZE].o_reply, + "%ju", stbuf->st_size); + else + /* XXX Allows writes of all sizes. */ + options[OPT_TSIZE].o_reply = + strdup(options[OPT_TSIZE].o_request); + return (0); +} + +int +option_timeout(int peer) +{ + int to; + + if (options[OPT_TIMEOUT].o_request == NULL) + return (0); + + to = atoi(options[OPT_TIMEOUT].o_request); + if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { + tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, + "Received bad value for timeout. " + "Should be between %d and %d, received %d", + TIMEOUT_MIN, TIMEOUT_MAX, to); + send_error(peer, EBADOP); + if (acting_as_client) + return (1); + exit(1); + } else { + timeoutpacket = to; + options[OPT_TIMEOUT].o_reply = + strdup(options[OPT_TIMEOUT].o_request); + } + settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); + + if (debug&DEBUG_OPTIONS) + tftp_log(LOG_DEBUG, "Setting timeout to '%s'", + options[OPT_TIMEOUT].o_reply); + + return (0); +} + +int +option_rollover(int peer) +{ + + if (options[OPT_ROLLOVER].o_request == NULL) + return (0); + + if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 + && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { + tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, + "Bad value for rollover, " + "should be either 0 or 1, received '%s', " + "ignoring request", + options[OPT_ROLLOVER].o_request); + if (acting_as_client) { + send_error(peer, EBADOP); + return (1); + } + return (0); + } + options[OPT_ROLLOVER].o_reply = + strdup(options[OPT_ROLLOVER].o_request); + + if (debug&DEBUG_OPTIONS) + tftp_log(LOG_DEBUG, "Setting rollover to '%s'", + options[OPT_ROLLOVER].o_reply); + + return (0); +} + +int +option_blksize(int peer) +{ + u_long maxdgram; + size_t len; + + if (options[OPT_BLKSIZE].o_request == NULL) + return (0); + + /* maximum size of an UDP packet according to the system */ + len = sizeof(maxdgram); + if (sysctlbyname("net.inet.udp.maxdgram", + &maxdgram, &len, NULL, 0) < 0) { + tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); + return (acting_as_client ? 1 : 0); + } + + int size = atoi(options[OPT_BLKSIZE].o_request); + if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { + if (acting_as_client) { + tftp_log(LOG_ERR, + "Invalid blocksize (%d bytes), aborting", + size); + send_error(peer, EBADOP); + return (1); + } else { + tftp_log(LOG_WARNING, + "Invalid blocksize (%d bytes), ignoring request", + size); + return (0); + } + } + + if (size > (int)maxdgram) { + if (acting_as_client) { + tftp_log(LOG_ERR, + "Invalid blocksize (%d bytes), " + "net.inet.udp.maxdgram sysctl limits it to " + "%ld bytes.\n", size, maxdgram); + send_error(peer, EBADOP); + return (1); + } else { + tftp_log(LOG_WARNING, + "Invalid blocksize (%d bytes), " + "net.inet.udp.maxdgram sysctl limits it to " + "%ld bytes.\n", size, maxdgram); + size = maxdgram; + /* No reason to return */ + } + } + + asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); + segsize = size; + pktsize = size + 4; + if (debug&DEBUG_OPTIONS) + tftp_log(LOG_DEBUG, "Setting blksize to '%s'", + options[OPT_BLKSIZE].o_reply); + + return (0); +} + +int +option_blksize2(int peer __unused) +{ + u_long maxdgram; + int size, i; + size_t len; + + int sizes[] = { + 8, 16, 32, 64, 128, 256, 512, 1024, + 2048, 4096, 8192, 16384, 32768, 0 + }; + + if (options[OPT_BLKSIZE2].o_request == NULL) + return (0); + + /* maximum size of an UDP packet according to the system */ + len = sizeof(maxdgram); + if (sysctlbyname("net.inet.udp.maxdgram", + &maxdgram, &len, NULL, 0) < 0) { + tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); + return (acting_as_client ? 1 : 0); + } + + size = atoi(options[OPT_BLKSIZE2].o_request); + for (i = 0; sizes[i] != 0; i++) { + if (size == sizes[i]) break; + } + if (sizes[i] == 0) { + tftp_log(LOG_INFO, + "Invalid blocksize2 (%d bytes), ignoring request", size); + return (acting_as_client ? 1 : 0); + } + + if (size > (int)maxdgram) { + for (i = 0; sizes[i+1] != 0; i++) { + if ((int)maxdgram < sizes[i+1]) break; + } + tftp_log(LOG_INFO, + "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " + "sysctl limits it to %ld bytes.\n", size, maxdgram); + size = sizes[i]; + /* No need to return */ + } + + asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); + segsize = size; + pktsize = size + 4; + if (debug&DEBUG_OPTIONS) + tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", + options[OPT_BLKSIZE2].o_reply); + + return (0); +} + +/* + * Append the available options to the header + */ +uint16_t +make_options(int peer __unused, char *buffer, uint16_t size) { + int i; + char *value; + const char *option; + uint16_t length; + uint16_t returnsize = 0; + + if (!options_rfc_enabled) return (0); + + for (i = 0; options[i].o_type != NULL; i++) { + if (options[i].rfc == 0 && !options_extra_enabled) + continue; + + option = options[i].o_type; + if (acting_as_client) + value = options[i].o_request; + else + value = options[i].o_reply; + if (value == NULL) + continue; + + length = strlen(value) + strlen(option) + 2; + if (size <= length) { + tftp_log(LOG_ERR, + "Running out of option space for " + "option '%s' with value '%s': " + "needed %d bytes, got %d bytes", + option, value, size, length); + continue; + } + + sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); + size -= length; + buffer += length; + returnsize += length; + } + + return (returnsize); +} + +/* + * Parse the received options in the header + */ +int +parse_options(int peer, char *buffer, uint16_t size) +{ + int i, options_failed; + char *c, *cp, *option, *value; + + if (!options_rfc_enabled) return (0); + + /* Parse the options */ + cp = buffer; + options_failed = 0; + while (size > 0) { + option = cp; + i = get_field(peer, cp, size); + cp += i; + + value = cp; + i = get_field(peer, cp, size); + cp += i; + + /* We are at the end */ + if (*option == '\0') break; + + if (debug&DEBUG_OPTIONS) + tftp_log(LOG_DEBUG, + "option: '%s' value: '%s'", option, value); + + for (c = option; *c; c++) + if (isupper(*c)) + *c = tolower(*c); + for (i = 0; options[i].o_type != NULL; i++) { + if (strcmp(option, options[i].o_type) == 0) { + if (!acting_as_client) + options[i].o_request = value; + if (!options_extra_enabled && !options[i].rfc) { + tftp_log(LOG_INFO, + "Option '%s' with value '%s' found " + "but it is not an RFC option", + option, value); + continue; + } + if (options[i].o_handler) + options_failed += + (options[i].o_handler)(peer); + break; + } + } + if (options[i].o_type == NULL) + tftp_log(LOG_WARNING, + "Unknown option: '%s'", option); + + size -= strlen(option) + strlen(value) + 2; + } + + return (options_failed); +} + +/* + * Set some default values in the options + */ +void +init_options(void) +{ + + options[OPT_ROLLOVER].o_request = strdup("0"); +} diff --git a/libexec/tftpd/tftp-options.h b/libexec/tftpd/tftp-options.h new file mode 100644 index 0000000..d8bd2fc --- /dev/null +++ b/libexec/tftpd/tftp-options.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Options + */ + +void init_options(void); +uint16_t make_options(int peer, char *buffer, uint16_t size); +int parse_options(int peer, char *buffer, uint16_t size); + +/* Call back functions */ +int option_tsize(int peer, struct tftphdr *, int, struct stat *); +int option_timeout(int peer); +int option_blksize(int peer); +int option_blksize2(int peer); +int option_rollover(int peer); + +extern int options_extra_enabled; +extern int options_rfc_enabled; + +struct options { + const char *o_type; + char *o_request; + char *o_reply; + int (*o_handler)(int peer); + int rfc; +}; + +extern struct options options[]; +enum opt_enum { + OPT_TSIZE = 0, + OPT_TIMEOUT, + OPT_BLKSIZE, + OPT_BLKSIZE2, + OPT_ROLLOVER, +}; diff --git a/libexec/tftpd/tftp-transfer.c b/libexec/tftpd/tftp-transfer.c new file mode 100644 index 0000000..d0c8a95 --- /dev/null +++ b/libexec/tftpd/tftp-transfer.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "tftp-file.h" +#include "tftp-io.h" +#include "tftp-utils.h" +#include "tftp-options.h" +#include "tftp-transfer.h" + +/* + * Send a file via the TFTP data session. + */ +void +tftp_send(int peer, uint16_t *block, struct tftp_stats *ts) +{ + struct tftphdr *rp; + int size, n_data, n_ack, try; + uint16_t oldblock; + char sendbuffer[MAXPKTSIZE]; + char recvbuffer[MAXPKTSIZE]; + + rp = (struct tftphdr *)recvbuffer; + *block = 1; + ts->amount = 0; + do { + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Sending block %d", *block); + + size = read_file(sendbuffer, segsize); + if (size < 0) { + tftp_log(LOG_ERR, "read_file returned %d", size); + send_error(peer, errno + 100); + goto abort; + } + + for (try = 0; ; try++) { + n_data = send_data(peer, *block, sendbuffer, size); + if (n_data > 0) { + if (try == maxtimeouts) { + tftp_log(LOG_ERR, + "Cannot send DATA packet #%d, " + "giving up", *block); + return; + } + tftp_log(LOG_ERR, + "Cannot send DATA packet #%d, trying again", + *block); + continue; + } + + n_ack = receive_packet(peer, recvbuffer, + MAXPKTSIZE, NULL, timeoutpacket); + if (n_ack < 0) { + if (n_ack == RP_TIMEOUT) { + if (try == maxtimeouts) { + tftp_log(LOG_ERR, + "Timeout #%d send ACK %d " + "giving up", try, *block); + return; + } + tftp_log(LOG_WARNING, + "Timeout #%d on ACK %d", + try, *block); + continue; + } + + /* Either read failure or ERROR packet */ + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_ERR, "Aborting: %s", + rp_strerror(n_ack)); + goto abort; + } + if (rp->th_opcode == ACK) { + ts->blocks++; + if (rp->th_block == *block) { + ts->amount += size; + break; + } + + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (rp->th_block == (*block - 1)) { + ts->retries++; + continue; + } + } + + } + oldblock = *block; + (*block)++; + if (oldblock > *block) { + if (options[OPT_ROLLOVER].o_request == NULL) { + /* + * "rollover" option not specified in + * tftp client. Default to rolling block + * counter to 0. + */ + *block = 0; + } else { + *block = atoi(options[OPT_ROLLOVER].o_request); + } + + ts->rollovers++; + } + gettimeofday(&(ts->tstop), NULL); + } while (size == segsize); +abort: + return; +} + +/* + * Receive a file via the TFTP data session. + * + * - It could be that the first block has already arrived while + * trying to figure out if we were receiving options or not. In + * that case it is passed to this function. + */ +void +tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts, + struct tftphdr *firstblock, size_t fb_size) +{ + struct tftphdr *rp; + uint16_t oldblock; + int n_data, n_ack, writesize, i, retry; + char recvbuffer[MAXPKTSIZE]; + + ts->amount = 0; + + if (firstblock != NULL) { + writesize = write_file(firstblock->th_data, fb_size); + ts->amount += writesize; + for (i = 0; ; i++) { + n_ack = send_ack(peer, *block); + if (n_ack > 0) { + if (i == maxtimeouts) { + tftp_log(LOG_ERR, + "Cannot send ACK packet #%d, " + "giving up", *block); + return; + } + tftp_log(LOG_ERR, + "Cannot send ACK packet #%d, trying again", + *block); + continue; + } + + break; + } + + if (fb_size != segsize) { + gettimeofday(&(ts->tstop), NULL); + return; + } + } + + rp = (struct tftphdr *)recvbuffer; + do { + oldblock = *block; + (*block)++; + if (oldblock > *block) { + if (options[OPT_ROLLOVER].o_request == NULL) { + /* + * "rollover" option not specified in + * tftp client. Default to rolling block + * counter to 0. + */ + *block = 0; + } else { + *block = atoi(options[OPT_ROLLOVER].o_request); + } + + ts->rollovers++; + } + + for (retry = 0; ; retry++) { + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, + "Receiving DATA block %d", *block); + + n_data = receive_packet(peer, recvbuffer, + MAXPKTSIZE, NULL, timeoutpacket); + if (n_data < 0) { + if (retry == maxtimeouts) { + tftp_log(LOG_ERR, + "Timeout #%d on DATA block %d, " + "giving up", retry, *block); + return; + } + if (n_data == RP_TIMEOUT) { + tftp_log(LOG_WARNING, + "Timeout #%d on DATA block %d", + retry, *block); + send_ack(peer, oldblock); + continue; + } + + /* Either read failure or ERROR packet */ + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Aborting: %s", + rp_strerror(n_data)); + goto abort; + } + if (rp->th_opcode == DATA) { + ts->blocks++; + + if (rp->th_block == *block) + break; + + tftp_log(LOG_WARNING, + "Expected DATA block %d, got block %d", + *block, rp->th_block); + + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (rp->th_block == (*block-1)) { + tftp_log(LOG_INFO, "Trying to sync"); + *block = oldblock; + ts->retries++; + goto send_ack; /* rexmit */ + } + + } else { + tftp_log(LOG_WARNING, + "Expected DATA block, got %s block", + packettype(rp->th_opcode)); + } + } + + if (n_data > 0) { + writesize = write_file(rp->th_data, n_data); + ts->amount += writesize; + if (writesize <= 0) { + tftp_log(LOG_ERR, + "write_file returned %d", writesize); + if (writesize < 0) + send_error(peer, errno + 100); + else + send_error(peer, ENOSPACE); + goto abort; + } + } + +send_ack: + for (i = 0; ; i++) { + n_ack = send_ack(peer, *block); + if (n_ack > 0) { + + if (i == maxtimeouts) { + tftp_log(LOG_ERR, + "Cannot send ACK packet #%d, " + "giving up", *block); + return; + } + + tftp_log(LOG_ERR, + "Cannot send ACK packet #%d, trying again", + *block); + continue; + } + + break; + } + gettimeofday(&(ts->tstop), NULL); + } while (n_data == segsize); + + /* Don't do late packet management for the client implementation */ + if (acting_as_client) + return; + + for (i = 0; ; i++) { + n_data = receive_packet(peer, (char *)rp, pktsize, + NULL, timeoutpacket); + if (n_data <= 0) + break; + if (n_data > 0 && + rp->th_opcode == DATA && /* and got a data block */ + *block == rp->th_block) /* then my last ack was lost */ + send_ack(peer, *block); /* resend final ack */ + } + +abort: + return; +} diff --git a/libexec/tftpd/tftp-transfer.h b/libexec/tftpd/tftp-transfer.h new file mode 100644 index 0000000..2cfa2df --- /dev/null +++ b/libexec/tftpd/tftp-transfer.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +void tftp_send(int peer, uint16_t *block, struct tftp_stats *tp); +void tftp_receive(int peer, uint16_t *block, struct tftp_stats *tp, + struct tftphdr *firstblock, size_t fb_size); diff --git a/libexec/tftpd/tftp-utils.c b/libexec/tftpd/tftp-utils.c new file mode 100644 index 0000000..c55f120 --- /dev/null +++ b/libexec/tftpd/tftp-utils.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "tftp-utils.h" +#include "tftp-io.h" + +/* + * Default values, can be changed later via the TFTP Options + */ +int timeoutpacket = TIMEOUT; +int timeoutnetwork = MAX_TIMEOUTS * TIMEOUT; +int maxtimeouts = MAX_TIMEOUTS; +uint16_t segsize = SEGSIZE; +uint16_t pktsize = SEGSIZE + 4; + +int acting_as_client; + + +/* + * Set timeout values for packet reception. The idea is that you + * get 'maxtimeouts' of 5 seconds between 'timeoutpacket' (i.e. the + * first timeout) to 'timeoutnetwork' (i.e. the last timeout) + */ +int +settimeouts(int _timeoutpacket, int _timeoutnetwork, int _maxtimeouts __unused) +{ + int i; + + /* We cannot do impossible things */ + if (_timeoutpacket >= _timeoutnetwork) + return (0); + + maxtimeouts = 0; + i = _timeoutpacket; + while (i < _timeoutnetwork || maxtimeouts < MIN_TIMEOUTS) { + maxtimeouts++; + i += 5; + } + + timeoutpacket = _timeoutpacket; + timeoutnetwork = i; + return (1); +} + +/* translate IPv4 mapped IPv6 address to IPv4 address */ +void +unmappedaddr(struct sockaddr_in6 *sin6) +{ + struct sockaddr_in *sin4; + u_int32_t addr; + int port; + + if (sin6->sin6_family != AF_INET6 || + !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + return; + sin4 = (struct sockaddr_in *)sin6; + memcpy(&addr, &sin6->sin6_addr.s6_addr[12], sizeof(addr)); + port = sin6->sin6_port; + memset(sin4, 0, sizeof(struct sockaddr_in)); + sin4->sin_addr.s_addr = addr; + sin4->sin_port = port; + sin4->sin_family = AF_INET; + sin4->sin_len = sizeof(struct sockaddr_in); +} + +/* Get a field from a \0 separated string */ +ssize_t +get_field(int peer, char *buffer, ssize_t size) +{ + char *cp = buffer; + + while (cp < buffer + size) { + if (*cp == '\0') break; + cp++; + } + if (*cp != '\0') { + tftp_log(LOG_ERR, "Bad option - no trailing \\0 found"); + send_error(peer, EBADOP); + exit(1); + } + return (cp - buffer + 1); +} + +/* + * Logging functions + */ +static int _tftp_logtostdout = 1; + +void +tftp_openlog(const char *ident, int logopt, int facility) +{ + + _tftp_logtostdout = (ident == NULL); + if (_tftp_logtostdout == 0) + openlog(ident, logopt, facility); +} + +void +tftp_closelog(void) +{ + + if (_tftp_logtostdout == 0) + closelog(); +} + +void +tftp_log(int priority, const char *message, ...) +{ + va_list ap; + char *s; + + va_start(ap, message); + if (_tftp_logtostdout == 0) { + vasprintf(&s, message, ap); + syslog(priority, "%s", s); + } else { + vprintf(message, ap); + printf("\n"); + } + va_end(ap); +} + +/* + * Packet types + */ +struct packettypes packettypes[] = { + { RRQ, "RRQ" }, + { WRQ, "WRQ" }, + { DATA, "DATA" }, + { ACK, "ACK" }, + { ERROR, "ERROR" }, + { OACK, "OACK" }, + { 0, NULL }, +}; + +const char * +packettype(int type) +{ + static char failed[100]; + int i = 0; + + while (packettypes[i].name != NULL) { + if (packettypes[i].value == type) + break; + i++; + } + if (packettypes[i].name != NULL) + return packettypes[i].name; + sprintf(failed, "unknown (type: %d)", type); + return (failed); +} + +/* + * Debugs + */ +int debug = DEBUG_NONE; +struct debugs debugs[] = { + { DEBUG_PACKETS, "packet", "Packet debugging" }, + { DEBUG_SIMPLE, "simple", "Simple debugging" }, + { DEBUG_OPTIONS, "options", "Options debugging" }, + { DEBUG_ACCESS, "access", "TCPd access debugging" }, + { DEBUG_NONE, NULL, "No debugging" }, +}; +int packetdroppercentage = 0; + +int +debug_find(char *s) +{ + int i = 0; + + while (debugs[i].name != NULL) { + if (strcasecmp(debugs[i].name, s) == 0) + break; + i++; + } + return (debugs[i].value); +} + +int +debug_finds(char *s) +{ + int i = 0; + char *ps = s; + + while (s != NULL) { + ps = strchr(s, ' '); + if (ps != NULL) + *ps = '\0'; + i += debug_find(s); + if (ps != NULL) + *ps = ' '; + s = ps; + } + return (i); +} + +const char * +debug_show(int d) +{ + static char s[100]; + int i = 0; + + s[0] = '\0'; + while (debugs[i].name != NULL) { + if (d&debugs[i].value) { + if (s[0] != '\0') + strcat(s, " "); + strcat(s, debugs[i].name); + } + i++; + } + if (s[0] != '\0') + return (s); + return ("none"); +} + +/* + * RP_ + */ +struct rp_errors rp_errors[] = { + { RP_TIMEOUT, "Network timeout" }, + { RP_TOOSMALL, "Not enough data bytes" }, + { RP_WRONGSOURCE, "Invalid IP address of UDP port" }, + { RP_ERROR, "Error packet" }, + { RP_RECVFROM, "recvfrom() complained" }, + { RP_TOOBIG, "Too many data bytes" }, + { RP_NONE, NULL } +}; + +char * +rp_strerror(int error) +{ + static char s[100]; + int i = 0; + + while (rp_errors[i].desc != NULL) { + if (rp_errors[i].error == error) { + strcpy(s, rp_errors[i].desc); + } + i++; + } + if (s[0] == '\0') + sprintf(s, "unknown (error=%d)", error); + return (s); +} + +/* + * Performance figures + */ + +void +stats_init(struct tftp_stats *ts) +{ + + ts->amount = 0; + ts->rollovers = 0; + ts->retries = 0; + ts->blocks = 0; + ts->amount = 0; + gettimeofday(&(ts->tstart), NULL); +} + +void +printstats(const char *direction, int verbose, struct tftp_stats *ts) +{ + double delta; /* compute delta in 1/10's second units */ + + delta = ((ts->tstop.tv_sec*10.)+(ts->tstop.tv_usec/100000)) - + ((ts->tstart.tv_sec*10.)+(ts->tstart.tv_usec/100000)); + delta = delta/10.; /* back to seconds */ + + printf("%s %zu bytes during %.1f seconds in %u blocks", + direction, ts->amount, delta, ts->blocks); + + if (ts->rollovers != 0) + printf(" with %d rollover%s", + ts->rollovers, ts->rollovers != 1 ? "s" : ""); + + if (verbose) + printf(" [%.0f bits/sec]", (ts->amount*8.)/delta); + putchar('\n'); +} diff --git a/libexec/tftpd/tftp-utils.h b/libexec/tftpd/tftp-utils.h new file mode 100644 index 0000000..c1becf3 --- /dev/null +++ b/libexec/tftpd/tftp-utils.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2008 Edwin Groothuis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + */ +#define TIMEOUT 5 +#define MAX_TIMEOUTS 5 + +/* Generic values */ +#define MAXSEGSIZE 65464 /* Maximum size of the data segment */ +#define MAXPKTSIZE (MAXSEGSIZE + 4) /* Maximum size of the packet */ + +/* For the blksize option */ +#define BLKSIZE_MIN 8 /* Minimum size of the data segment */ +#define BLKSIZE_MAX MAXSEGSIZE /* Maximum size of the data segment */ + +/* For the timeout option */ +#define TIMEOUT_MIN 0 /* Minimum timeout value */ +#define TIMEOUT_MAX 255 /* Maximum timeout value */ +#define MIN_TIMEOUTS 3 + +extern int timeoutpacket; +extern int timeoutnetwork; +extern int maxtimeouts; +int settimeouts(int timeoutpacket, int timeoutnetwork, int maxtimeouts); + +extern uint16_t segsize; +extern uint16_t pktsize; + +extern int acting_as_client; + +/* + */ +void unmappedaddr(struct sockaddr_in6 *sin6); +ssize_t get_field(int peer, char *buffer, ssize_t size); + +/* + * Packet types + */ +struct packettypes { + int value; + const char *const name; +}; +extern struct packettypes packettypes[]; +const char *packettype(int); + +/* + * RP_ + */ +struct rp_errors { + int error; + const char *const desc; +}; +extern struct rp_errors rp_errors[]; +char *rp_strerror(int error); + +/* + * Debug features + */ +#define DEBUG_NONE 0x0000 +#define DEBUG_PACKETS 0x0001 +#define DEBUG_SIMPLE 0x0002 +#define DEBUG_OPTIONS 0x0004 +#define DEBUG_ACCESS 0x0008 +struct debugs { + int value; + const char *const name; + const char *const desc; +}; +extern int debug; +extern struct debugs debugs[]; +extern int packetdroppercentage; +int debug_find(char *s); +int debug_finds(char *s); +const char *debug_show(int d); + +/* + * Log routines + */ +#define DEBUG(s) tftp_log(LOG_DEBUG, "%s", s) +extern int tftp_logtostdout; +void tftp_openlog(const char *ident, int logopt, int facility); +void tftp_closelog(void); +void tftp_log(int priority, const char *message, ...) __printflike(2, 3); + +/* + * Performance figures + */ +struct tftp_stats { + size_t amount; + int rollovers; + uint32_t blocks; + int retries; + struct timeval tstart; + struct timeval tstop; +}; + +void stats_init(struct tftp_stats *ts); +void printstats(const char *direction, int verbose, struct tftp_stats *ts); diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8 new file mode 100644 index 0000000..0071264 --- /dev/null +++ b/libexec/tftpd/tftpd.8 @@ -0,0 +1,306 @@ +.\" Copyright (c) 1983, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tftpd.8 8.1 (Berkeley) 6/4/93 +.\" $FreeBSD$ +.\" +.Dd June 22, 2011 +.Dt TFTPD 8 +.Os +.Sh NAME +.Nm tftpd +.Nd Internet Trivial File Transfer Protocol server +.Sh SYNOPSIS +.Nm tftpd +.Op Fl cdClnow +.Op Fl F Ar strftime-format +.Op Fl s Ar directory +.Op Fl u Ar user +.Op Fl U Ar umask +.Op Ar directory ... +.Sh DESCRIPTION +The +.Nm +utility is a server which supports the +Internet Trivial File Transfer +Protocol +.Pq Tn RFC 1350 . +The +.Tn TFTP +server operates +at the port indicated in the +.Ql tftp +service description; +see +.Xr services 5 . +The server is normally started by +.Xr inetd 8 . +.Pp +The use of +.Xr tftp 1 +does not require an account or password on the remote system. +Due to the lack of authentication information, +.Nm +will allow only publicly readable files to be +accessed. +Files containing the string +.Dq Li "/../" +or starting with +.Dq Li "../" +are not allowed. +Files may be written only if they already exist and are publicly writable. +Note that this extends the concept of +.Dq public +to include +all users on all hosts that can be reached through the network; +this may not be appropriate on all systems, and its implications +should be considered before enabling tftp service. +The server should have the user ID with the lowest possible privilege. +.Pp +Access to files may be restricted by invoking +.Nm +with a list of directories by including up to 20 pathnames +as server program arguments in +.Xr inetd.conf 5 . +In this case access is restricted to files whose +names are prefixed by the one of the given directories. +The given directories are also treated as a search path for +relative filename requests. +.Pp +The +.Fl s +option provides additional security by changing +the root directory of +.Nm , +thereby prohibiting accesses to outside of the specified +.Ar directory . +Because +.Xr chroot 2 +requires super-user privileges, +.Nm +must be run as +.Li root . +However, after performing the +.Xr chroot 2 +call, +.Nm +will set its user ID to that of the specified +.Ar user , +or +.Dq Li nobody +if no +.Fl u +option is specified. +.Pp +The options are: +.Bl -tag -width Ds +.It Fl c +Changes the default root directory of a connecting host via +.Xr chroot 2 +based on the connecting IP address. +This prevents multiple clients from writing to the same file at the same time. +If the directory does not exist, the client connection is refused. +The +.Fl s +option is required for +.Fl c +and the specified +.Ar directory +is used as a base. +.It Fl C +Operates the same as +.Fl c +except it falls back to +.Ar directory +specified via +.Fl s +if a directory does not exist for the client's IP. +.It Fl F +Use this +.Xr strftime 3 +compatible format string for the creation of the suffix if +.Fl W +is specified. +By default the string "%Y%m%d" is used. +.It Fl d, d Ar [value] +Enables debug output. +If +.Ar value +is not specified, then the debug level is increased by one +for each instance of +.Fl d +which is specified. +.Pp +If +.Ar value +is specified, then the debug level is set to +.Ar value . +The debug level is a bitmask implemented in +.Pa src/libexec/tftpd/tftp-utils.h . +Valid values are 0 (DEBUG_NONE), 1 (DEBUG_PACKETS), 2, (DEBUG_SIMPLE), +4 (DEBUG_OPTIONS), and 8 (DEBUG_ACCESS). Multiple debug values can be combined +in the bitmask by logically OR'ing the values. For example, specifying +.Fl d +.Ar 15 +will enable all the debug values. +.It Fl l +Log all requests using +.Xr syslog 3 +with the facility of +.Dv LOG_FTP . +.Sy Note : +Logging of +.Dv LOG_FTP +messages +must also be enabled in the syslog configuration file, +.Xr syslog.conf 5 . +.It Fl n +Suppress negative acknowledgement of requests for nonexistent +relative filenames. +.It Fl o +Disable support for RFC2347 style TFTP Options. +.It Fl s Ar directory +Cause +.Nm +to change its root directory to +.Ar directory . +After doing that but before accepting commands, +.Nm +will switch credentials to an unprivileged user. +.It Fl u Ar user +Switch credentials to +.Ar user +(default +.Dq Li nobody ) +when the +.Fl s +option is used. +The user must be specified by name, not a numeric UID. +.It Fl U Ar umask +Set the +.Ar umask +for newly created files. +The default is 022 +.Pq Dv S_IWGRP | S_IWOTH . +.It Fl w +Allow write requests to create new files. +By default +.Nm +requires that the file specified in a write request exist. +Note that this only works in directories writable by the user +specified with +.Fl u +option +.It Fl W +As +.Fl w +but append a YYYYMMDD.nn sequence number to the end of the filename. +Note that the string YYYYMMDD can be changed with the +.Fl F +option. +.El +.Sh SEE ALSO +.Xr tftp 1 , +.Xr chroot 2 , +.Xr syslog 3 , +.Xr inetd.conf 5 , +.Xr services 5 , +.Xr syslog.conf 5 , +.Xr inetd 8 +.Pp +The following RFC's are supported: +.Rs +.%T RFC 1350: The TFTP Protocol (Revision 2) +.Re +.Rs +.%T RFC 2347: TFTP Option Extension +.Re +.Rs +.%T RFC 2348: TFTP Blocksize Option +.Re +.Rs +.%T RFC 2349: TFTP Timeout Interval and Transfer Size Options +.Re +.Pp +The non-standard +.Cm rollover +and +.Cm blksize2 +TFTP options are mentioned here: +.Rs +.%T Extending TFTP +.%U http://www.compuphase.com/tftp.htm +.Re +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 ; +the +.Fl s +option was introduced in +.Fx 2.2 , +the +.Fl u +option was introduced in +.Fx 4.2 , +the +.Fl c +option was introduced in +.Fx 4.3 , +and the +.Fl F +and +.Fl W +options were introduced in +.Fx 7.4 . +.Pp +Support for Timeout Interval and Transfer Size Options (RFC2349) +was introduced in +.Fx 5.0 , +support for the TFTP Blocksize Option (RFC2348) and the blksize2 option +was introduced in +.Fx 7.4 . +.Pp +Edwin Groothuis <edwin@FreeBSD.org> performed a major rewrite of the +.Nm +and +.Xr tftp 1 +code to support RFC2348. +.Sh NOTES +Files larger than 33,553,919 octets (65535 blocks, last one <512 +octets) cannot be correctly transferred without client and server +supporting blocksize negotiation (RFCs 2347 and 2348), +or the non-standard TFTP rollover option. +As a kludge, +.Nm +accepts a sequence of block number which wrap to zero after 65535, +even if the rollover option is not specified. +.Pp +Many tftp clients will not transfer files over 16,776,703 octets +(32767 blocks), as they incorrectly count the block number using +a signed rather than unsigned 16-bit integer. diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c new file mode 100644 index 0000000..571fa59 --- /dev/null +++ b/libexec/tftpd/tftpd.c @@ -0,0 +1,837 @@ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1983, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Trivial file transfer protocol server. + * + * This version includes many modifications by Jim Guyton + * <guyton@rand-unix>. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/tftp.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <pwd.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <tcpd.h> +#include <unistd.h> + +#include "tftp-file.h" +#include "tftp-io.h" +#include "tftp-utils.h" +#include "tftp-transfer.h" +#include "tftp-options.h" + +static void tftp_wrq(int peer, char *, ssize_t); +static void tftp_rrq(int peer, char *, ssize_t); + +/* + * Null-terminated directory prefix list for absolute pathname requests and + * search list for relative pathname requests. + * + * MAXDIRS should be at least as large as the number of arguments that + * inetd allows (currently 20). + */ +#define MAXDIRS 20 +static struct dirlist { + const char *name; + int len; +} dirs[MAXDIRS+1]; +static int suppress_naks; +static int logging; +static int ipchroot; +static int create_new = 0; +static const char *newfile_format = "%Y%m%d"; +static int increase_name = 0; +static mode_t mask = S_IWGRP | S_IWOTH; + +struct formats; +static void tftp_recvfile(int peer, const char *mode); +static void tftp_xmitfile(int peer, const char *mode); +static int validate_access(int peer, char **, int); +static char peername[NI_MAXHOST]; + +static FILE *file; + +static struct formats { + const char *f_mode; + int f_convert; +} formats[] = { + { "netascii", 1 }, + { "octet", 0 }, + { NULL, 0 } +}; + +int +main(int argc, char *argv[]) +{ + struct tftphdr *tp; + int peer; + socklen_t peerlen, len; + ssize_t n; + int ch; + char *chroot_dir = NULL; + struct passwd *nobody; + const char *chuser = "nobody"; + char recvbuffer[MAXPKTSIZE]; + int allow_ro = 1, allow_wo = 1; + + tzset(); /* syslog in localtime */ + acting_as_client = 0; + + tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); + while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { + switch (ch) { + case 'c': + ipchroot = 1; + break; + case 'C': + ipchroot = 2; + break; + case 'd': + if (atoi(optarg) != 0) + debug += atoi(optarg); + else + debug |= debug_finds(optarg); + break; + case 'F': + newfile_format = optarg; + break; + case 'l': + logging = 1; + break; + case 'n': + suppress_naks = 1; + break; + case 'o': + options_rfc_enabled = 0; + break; + case 'O': + options_extra_enabled = 0; + break; + case 'p': + packetdroppercentage = atoi(optarg); + tftp_log(LOG_INFO, + "Randomly dropping %d out of 100 packets", + packetdroppercentage); + break; + case 's': + chroot_dir = optarg; + break; + case 'u': + chuser = optarg; + break; + case 'U': + mask = strtol(optarg, NULL, 0); + break; + case 'w': + create_new = 1; + break; + case 'W': + create_new = 1; + increase_name = 1; + break; + default: + tftp_log(LOG_WARNING, + "ignoring unknown option -%c", ch); + } + } + if (optind < argc) { + struct dirlist *dirp; + + /* Get list of directory prefixes. Skip relative pathnames. */ + for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; + optind++) { + if (argv[optind][0] == '/') { + dirp->name = argv[optind]; + dirp->len = strlen(dirp->name); + dirp++; + } + } + } + else if (chroot_dir) { + dirs->name = "/"; + dirs->len = 1; + } + if (ipchroot > 0 && chroot_dir == NULL) { + tftp_log(LOG_ERR, "-c requires -s"); + exit(1); + } + + umask(mask); + + { + int on = 1; + if (ioctl(0, FIONBIO, &on) < 0) { + tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); + exit(1); + } + } + + /* Find out who we are talking to and what we are going to do */ + peerlen = sizeof(peer_sock); + n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, + (struct sockaddr *)&peer_sock, &peerlen); + if (n < 0) { + tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); + exit(1); + } + getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, + peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); + + /* + * Now that we have read the message out of the UDP + * socket, we fork and exit. Thus, inetd will go back + * to listening to the tftp port, and the next request + * to come in will start up a new instance of tftpd. + * + * We do this so that inetd can run tftpd in "wait" mode. + * The problem with tftpd running in "nowait" mode is that + * inetd may get one or more successful "selects" on the + * tftp port before we do our receive, so more than one + * instance of tftpd may be started up. Worse, if tftpd + * break before doing the above "recvfrom", inetd would + * spawn endless instances, clogging the system. + */ + { + int i, pid; + + for (i = 1; i < 20; i++) { + pid = fork(); + if (pid < 0) { + sleep(i); + /* + * flush out to most recently sent request. + * + * This may drop some request, but those + * will be resent by the clients when + * they timeout. The positive effect of + * this flush is to (try to) prevent more + * than one tftpd being started up to service + * a single request from a single client. + */ + peerlen = sizeof peer_sock; + i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, + (struct sockaddr *)&peer_sock, &peerlen); + if (i > 0) { + n = i; + } + } else { + break; + } + } + if (pid < 0) { + tftp_log(LOG_ERR, "fork: %s", strerror(errno)); + exit(1); + } else if (pid != 0) { + exit(0); + } + } + + /* + * See if the client is allowed to talk to me. + * (This needs to be done before the chroot()) + */ + { + struct request_info req; + + request_init(&req, RQ_CLIENT_ADDR, peername, 0); + request_set(&req, RQ_DAEMON, "tftpd", 0); + + if (hosts_access(&req) == 0) { + if (debug&DEBUG_ACCESS) + tftp_log(LOG_WARNING, + "Access denied by 'tftpd' entry " + "in /etc/hosts.allow"); + + /* + * Full access might be disabled, but maybe the + * client is allowed to do read-only access. + */ + request_set(&req, RQ_DAEMON, "tftpd-ro", 0); + allow_ro = hosts_access(&req); + + request_set(&req, RQ_DAEMON, "tftpd-wo", 0); + allow_wo = hosts_access(&req); + + if (allow_ro == 0 && allow_wo == 0) { + tftp_log(LOG_WARNING, + "Unauthorized access from %s", peername); + exit(1); + } + + if (debug&DEBUG_ACCESS) { + if (allow_ro) + tftp_log(LOG_WARNING, + "But allowed readonly access " + "via 'tftpd-ro' entry"); + if (allow_wo) + tftp_log(LOG_WARNING, + "But allowed writeonly access " + "via 'tftpd-wo' entry"); + } + } else + if (debug&DEBUG_ACCESS) + tftp_log(LOG_WARNING, + "Full access allowed" + "in /etc/hosts.allow"); + } + + /* + * Since we exit here, we should do that only after the above + * recvfrom to keep inetd from constantly forking should there + * be a problem. See the above comment about system clogging. + */ + if (chroot_dir) { + if (ipchroot > 0) { + char *tempchroot; + struct stat sb; + int statret; + struct sockaddr_storage ss; + char hbuf[NI_MAXHOST]; + + statret = -1; + memcpy(&ss, &peer_sock, peer_sock.ss_len); + unmappedaddr((struct sockaddr_in6 *)&ss); + getnameinfo((struct sockaddr *)&ss, ss.ss_len, + hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST); + asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); + if (ipchroot == 2) + statret = stat(tempchroot, &sb); + if (ipchroot == 1 || + (statret == 0 && (sb.st_mode & S_IFDIR))) + chroot_dir = tempchroot; + } + /* Must get this before chroot because /etc might go away */ + if ((nobody = getpwnam(chuser)) == NULL) { + tftp_log(LOG_ERR, "%s: no such user", chuser); + exit(1); + } + if (chroot(chroot_dir)) { + tftp_log(LOG_ERR, "chroot: %s: %s", + chroot_dir, strerror(errno)); + exit(1); + } + chdir("/"); + setgroups(1, &nobody->pw_gid); + if (setuid(nobody->pw_uid) != 0) { + tftp_log(LOG_ERR, "setuid failed"); + exit(1); + } + } + + len = sizeof(me_sock); + if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { + switch (me_sock.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&me_sock)->sin_port = 0; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; + break; + default: + /* unsupported */ + break; + } + } else { + memset(&me_sock, 0, sizeof(me_sock)); + me_sock.ss_family = peer_sock.ss_family; + me_sock.ss_len = peer_sock.ss_len; + } + close(0); + close(1); + peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); + if (peer < 0) { + tftp_log(LOG_ERR, "socket: %s", strerror(errno)); + exit(1); + } + if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { + tftp_log(LOG_ERR, "bind: %s", strerror(errno)); + exit(1); + } + + tp = (struct tftphdr *)recvbuffer; + tp->th_opcode = ntohs(tp->th_opcode); + if (tp->th_opcode == RRQ) { + if (allow_ro) + tftp_rrq(peer, tp->th_stuff, n - 1); + else { + tftp_log(LOG_WARNING, + "%s read access denied", peername); + exit(1); + } + } + if (tp->th_opcode == WRQ) { + if (allow_wo) + tftp_wrq(peer, tp->th_stuff, n - 1); + else { + tftp_log(LOG_WARNING, + "%s write access denied", peername); + exit(1); + } + } + exit(1); +} + +static void +reduce_path(char *fn) +{ + char *slash, *ptr; + + /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ + while ((slash = strstr(fn, "/./")) != NULL) { + for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) + ; + slash += 2; + while (*slash) + *++ptr = *++slash; + } + + /* Now reduce all "/something/+../" to "/" */ + while ((slash = strstr(fn, "/../")) != NULL) { + if (slash == fn) + break; + for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) + ; + for (ptr--; ptr >= fn; ptr--) + if (*ptr == '/') + break; + if (ptr < fn) + break; + slash += 3; + while (*slash) + *++ptr = *++slash; + } +} + +static char * +parse_header(int peer, char *recvbuffer, ssize_t size, + char **filename, char **mode) +{ + char *cp; + int i; + struct formats *pf; + + *mode = NULL; + cp = recvbuffer; + + i = get_field(peer, recvbuffer, size); + if (i >= PATH_MAX) { + tftp_log(LOG_ERR, "Bad option - filename too long"); + send_error(peer, EBADOP); + exit(1); + } + *filename = recvbuffer; + tftp_log(LOG_INFO, "Filename: '%s'", *filename); + cp += i; + + i = get_field(peer, cp, size); + *mode = cp; + cp += i; + + /* Find the file transfer mode */ + for (cp = *mode; *cp; cp++) + if (isupper(*cp)) + *cp = tolower(*cp); + for (pf = formats; pf->f_mode; pf++) + if (strcmp(pf->f_mode, *mode) == 0) + break; + if (pf->f_mode == NULL) { + tftp_log(LOG_ERR, + "Bad option - Unknown transfer mode (%s)", *mode); + send_error(peer, EBADOP); + exit(1); + } + tftp_log(LOG_INFO, "Mode: '%s'", *mode); + + return (cp + 1); +} + +/* + * WRQ - receive a file from the client + */ +void +tftp_wrq(int peer, char *recvbuffer, ssize_t size) +{ + char *cp; + int has_options = 0, ecode; + char *filename, *mode; + char fnbuf[PATH_MAX]; + + cp = parse_header(peer, recvbuffer, size, &filename, &mode); + size -= (cp - recvbuffer) + 1; + + strcpy(fnbuf, filename); + reduce_path(fnbuf); + filename = fnbuf; + + if (size > 0) { + if (options_rfc_enabled) + has_options = !parse_options(peer, cp, size); + else + tftp_log(LOG_INFO, "Options found but not enabled"); + } + + ecode = validate_access(peer, &filename, WRQ); + if (ecode == 0) { + if (has_options) + send_oack(peer); + else + send_ack(peer, 0); + } + if (logging) { + tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, + filename, errtomsg(ecode)); + } + + tftp_recvfile(peer, mode); + exit(0); +} + +/* + * RRQ - send a file to the client + */ +void +tftp_rrq(int peer, char *recvbuffer, ssize_t size) +{ + char *cp; + int has_options = 0, ecode; + char *filename, *mode; + char fnbuf[PATH_MAX]; + + cp = parse_header(peer, recvbuffer, size, &filename, &mode); + size -= (cp - recvbuffer) + 1; + + strcpy(fnbuf, filename); + reduce_path(fnbuf); + filename = fnbuf; + + if (size > 0) { + if (options_rfc_enabled) + has_options = !parse_options(peer, cp, size); + else + tftp_log(LOG_INFO, "Options found but not enabled"); + } + + ecode = validate_access(peer, &filename, RRQ); + if (ecode == 0) { + if (has_options) { + int n; + char lrecvbuffer[MAXPKTSIZE]; + struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; + + send_oack(peer); + n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, + NULL, timeoutpacket); + if (n < 0) { + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Aborting: %s", + rp_strerror(n)); + return; + } + if (rp->th_opcode != ACK) { + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, + "Expected ACK, got %s on OACK", + packettype(rp->th_opcode)); + return; + } + } + } + + if (logging) + tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, + filename, errtomsg(ecode)); + + if (ecode) { + /* + * Avoid storms of naks to a RRQ broadcast for a relative + * bootfile pathname from a diskless Sun. + */ + if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) + exit(0); + send_error(peer, ecode); + exit(1); + } + tftp_xmitfile(peer, mode); +} + +/* + * Find the next value for YYYYMMDD.nn when the file to be written should + * be unique. Due to the limitations of nn, we will fail if nn reaches 100. + * Besides, that is four updates per hour on a file, which is kind of + * execessive anyway. + */ +static int +find_next_name(char *filename, int *fd) +{ + int i; + time_t tval; + size_t len; + struct tm lt; + char yyyymmdd[MAXPATHLEN]; + char newname[MAXPATHLEN]; + + /* Create the YYYYMMDD part of the filename */ + time(&tval); + lt = *localtime(&tval); + len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); + if (len == 0) { + syslog(LOG_WARNING, + "Filename suffix too long (%d characters maximum)", + MAXPATHLEN); + return (EACCESS); + } + + /* Make sure the new filename is not too long */ + if (strlen(filename) > MAXPATHLEN - len - 5) { + syslog(LOG_WARNING, + "Filename too long (%zd characters, %zd maximum)", + strlen(filename), MAXPATHLEN - len - 5); + return (EACCESS); + } + + /* Find the first file which doesn't exist */ + for (i = 0; i < 100; i++) { + sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); + *fd = open(newname, + O_WRONLY | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH); + if (*fd > 0) + return 0; + } + + return (EEXIST); +} + +/* + * Validate file access. Since we + * have no uid or gid, for now require + * file to exist and be publicly + * readable/writable. + * If we were invoked with arguments + * from inetd then the file must also be + * in one of the given directory prefixes. + * Note also, full path name must be + * given as we have no login directory. + */ +int +validate_access(int peer, char **filep, int mode) +{ + struct stat stbuf; + int fd; + int error; + struct dirlist *dirp; + static char pathname[MAXPATHLEN]; + char *filename = *filep; + + /* + * Prevent tricksters from getting around the directory restrictions + */ + if (strstr(filename, "/../")) + return (EACCESS); + + if (*filename == '/') { + /* + * Allow the request if it's in one of the approved locations. + * Special case: check the null prefix ("/") by looking + * for length = 1 and relying on the arg. processing that + * it's a /. + */ + for (dirp = dirs; dirp->name != NULL; dirp++) { + if (dirp->len == 1 || + (!strncmp(filename, dirp->name, dirp->len) && + filename[dirp->len] == '/')) + break; + } + /* If directory list is empty, allow access to any file */ + if (dirp->name == NULL && dirp != dirs) + return (EACCESS); + if (stat(filename, &stbuf) < 0) + return (errno == ENOENT ? ENOTFOUND : EACCESS); + if ((stbuf.st_mode & S_IFMT) != S_IFREG) + return (ENOTFOUND); + if (mode == RRQ) { + if ((stbuf.st_mode & S_IROTH) == 0) + return (EACCESS); + } else { + if ((stbuf.st_mode & S_IWOTH) == 0) + return (EACCESS); + } + } else { + int err; + + /* + * Relative file name: search the approved locations for it. + * Don't allow write requests that avoid directory + * restrictions. + */ + + if (!strncmp(filename, "../", 3)) + return (EACCESS); + + /* + * If the file exists in one of the directories and isn't + * readable, continue looking. However, change the error code + * to give an indication that the file exists. + */ + err = ENOTFOUND; + for (dirp = dirs; dirp->name != NULL; dirp++) { + snprintf(pathname, sizeof(pathname), "%s/%s", + dirp->name, filename); + if (stat(pathname, &stbuf) == 0 && + (stbuf.st_mode & S_IFMT) == S_IFREG) { + if ((stbuf.st_mode & S_IROTH) != 0) { + break; + } + err = EACCESS; + } + } + if (dirp->name != NULL) + *filep = filename = pathname; + else if (mode == RRQ) + return (err); + } + + /* + * This option is handled here because it (might) require(s) the + * size of the file. + */ + option_tsize(peer, NULL, mode, &stbuf); + + if (mode == RRQ) + fd = open(filename, O_RDONLY); + else { + if (create_new) { + if (increase_name) { + error = find_next_name(filename, &fd); + if (error > 0) + return (error + 100); + } else + fd = open(filename, + O_WRONLY | O_TRUNC | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | + S_IWGRP | S_IROTH | S_IWOTH ); + } else + fd = open(filename, O_WRONLY | O_TRUNC); + } + if (fd < 0) + return (errno + 100); + file = fdopen(fd, (mode == RRQ)? "r":"w"); + if (file == NULL) { + close(fd); + return (errno + 100); + } + return (0); +} + +static void +tftp_xmitfile(int peer, const char *mode) +{ + uint16_t block; + time_t now; + struct tftp_stats ts; + + now = time(NULL); + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Transmitting file"); + + read_init(0, file, mode); + block = 1; + tftp_send(peer, &block, &ts); + read_close(); + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds", + (intmax_t)ts.amount, (intmax_t)time(NULL) - now); +} + +static void +tftp_recvfile(int peer, const char *mode) +{ + uint16_t block; + struct timeval now1, now2; + struct tftp_stats ts; + + gettimeofday(&now1, NULL); + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Receiving file"); + + write_init(0, file, mode); + + block = 0; + tftp_receive(peer, &block, &ts, NULL, 0); + + write_close(); + gettimeofday(&now2, NULL); + + if (debug&DEBUG_SIMPLE) { + double f; + if (now1.tv_usec > now2.tv_usec) { + now2.tv_usec += 1000000; + now2.tv_sec--; + } + + f = now2.tv_sec - now1.tv_sec + + (now2.tv_usec - now1.tv_usec) / 100000.0; + tftp_log(LOG_INFO, + "Download of %jd bytes in %d blocks completed after %0.1f seconds\n", + (intmax_t)ts.amount, block, f); + } + + return; +} diff --git a/libexec/ulog-helper/Makefile b/libexec/ulog-helper/Makefile new file mode 100644 index 0000000..764f0de --- /dev/null +++ b/libexec/ulog-helper/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= ulog-helper +BINOWN= root +BINMODE=4555 +MAN= + +DPADD= ${LIBULOG} +LDADD= -lulog + +.include <bsd.prog.mk> diff --git a/libexec/ulog-helper/ulog-helper.c b/libexec/ulog-helper/ulog-helper.c new file mode 100644 index 0000000..99cd5d2 --- /dev/null +++ b/libexec/ulog-helper/ulog-helper.c @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 2009 Ed Schouten <ed@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <pwd.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <ulog.h> + +/* + * This setuid helper utility writes user login records to disk. + * Unprivileged processes are not capable of writing records to utmpx, + * but we do want to allow this for pseudo-terminals. Because a file + * descriptor to a pseudo-terminal master device can only be obtained by + * processes using the pseudo-terminal, we expect such a descriptor on + * stdin. + * + * It uses the real user ID of the calling process to determine the + * username. It does allow users to log arbitrary hostnames. + */ + +static const char * +get_username(void) +{ + const struct passwd *pw; + const char *login; + uid_t uid; + + /* + * Attempt to determine the username corresponding to this login + * session. First, validate the results of getlogin() against + * the password database. If getlogin() returns invalid data, + * return an arbitrary username corresponding to this uid. + */ + uid = getuid(); + if ((login = getlogin()) != NULL && (pw = getpwnam(login)) != NULL && + pw->pw_uid == uid) + return (login); + if ((pw = getpwuid(uid)) != NULL) + return (pw->pw_name); + return (NULL); +} + +int +main(int argc, char *argv[]) +{ + const char *line, *user, *host; + + /* Device line name. */ + if ((line = ptsname(STDIN_FILENO)) == NULL) + return (EX_USAGE); + + if ((argc == 2 || argc == 3) && strcmp(argv[1], "login") == 0) { + /* Username. */ + user = get_username(); + if (user == NULL) + return (EX_OSERR); + + /* Hostname. */ + host = argc == 3 ? argv[2] : NULL; + + ulog_login(line, user, host); + return (EX_OK); + } else if (argc == 2 && strcmp(argv[1], "logout") == 0) { + ulog_logout(line); + return (EX_OK); + } + + return (EX_USAGE); +} diff --git a/libexec/ypxfr/Makefile b/libexec/ypxfr/Makefile new file mode 100644 index 0000000..476a296 --- /dev/null +++ b/libexec/ypxfr/Makefile @@ -0,0 +1,47 @@ +# $FreeBSD$ + +PROG= ypxfr +SRCS= yp_dblookup.c yp_dbwrite.c yp_error.c \ + ypxfr_getmap.c ypxfr_main.c ypxfr_misc.c \ + ypxfrd_getmap.c \ + ${GENSRCS} +GENSRCS=yp.h yp_clnt.c ypxfr_clnt.c + +.PATH: ${.CURDIR}/../../usr.sbin/ypserv + +MAN= ypxfr.8 + +CFLAGS+= -I. + +WARNS?= 2 +WFORMAT=0 + +DPADD= ${LIBRPCSVC} +LDADD= -lrpcsvc + +CLEANFILES= ${GENSRCS} + +RPCDIR= ${.CURDIR}/../../include/rpcsvc +RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -I -C + +ypxfr_clnt.c: ${RPCDIR}/yp.x + rm -f ${.TARGET} + ${RPCGEN} -DYPPUSH_ONLY -l -o ${.TARGET} ${RPCDIR}/yp.x + +yp_clnt.c: ${RPCDIR}/yp.x + rm -f ${.TARGET} + ${RPCGEN} -DYPSERV_ONLY -l -o ${.TARGET} ${RPCDIR}/yp.x + +yp.h: ${RPCDIR}/yp.x + rm -f ${.TARGET} + ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/yp.x + +# ypxfrd_xdr.c: ${RPCDIR}/ypxfrd.x +# rm -f ${.TARGET} +# ${RPCGEN} -c -o ${.TARGET} ${RPCDIR}/ypxfrd.x + +ypxfrd.h: ${RPCDIR}/ypxfrd.x + rm -f ${.TARGET} + ${RPCGEN} -h -o ${.TARGET} ${RPCDIR}/ypxfrd.x + +.include <bsd.prog.mk> diff --git a/libexec/ypxfr/yp_dbwrite.c b/libexec/ypxfr/yp_dbwrite.c new file mode 100644 index 0000000..488f9e4 --- /dev/null +++ b/libexec/ypxfr/yp_dbwrite.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <db.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <rpcsvc/yp.h> +#include "ypxfr_extern.h" + +#define PERM_SECURE (S_IRUSR|S_IWUSR) + +/* + * Open a DB database read/write + */ +DB * +yp_open_db_rw(const char *domain, const char *map, const int flags) +{ + DB *dbp; + char buf[1025]; + + + yp_errno = YP_TRUE; + + if (map[0] == '.' || strchr(map, '/')) { + yp_errno = YP_BADARGS; + return (NULL); + } + +#define FLAGS O_RDWR|O_EXLOCK|O_EXCL|O_CREAT + + snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, domain, map); + dbp = dbopen(buf,flags ? flags : FLAGS,PERM_SECURE,DB_HASH,&openinfo); + + if (dbp == NULL) { + switch (errno) { + case ENOENT: + yp_errno = YP_NOMAP; + break; + case EFTYPE: + yp_errno = YP_BADDB; + break; + default: + yp_errno = YP_YPERR; + break; + } + } + + return (dbp); +} + +int +yp_put_record(DB *dbp, DBT *key, DBT *data, int allow_overwrite) +{ + int rval; + + if ((rval = (dbp->put)(dbp,key,data, allow_overwrite ? 0 : + R_NOOVERWRITE))) { + switch (rval) { + case 1: + return(YP_FALSE); + break; + case -1: + default: + (void)(dbp->close)(dbp); + return(YP_BADDB); + break; + } + } + + return(YP_TRUE); +} diff --git a/libexec/ypxfr/ypxfr.8 b/libexec/ypxfr/ypxfr.8 new file mode 100644 index 0000000..3e07586 --- /dev/null +++ b/libexec/ypxfr/ypxfr.8 @@ -0,0 +1,308 @@ +.\" Copyright (c) 1995 +.\" Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Bill Paul. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd February 5, 1995 +.Dt YPXFR 8 +.Os +.Sh NAME +.Nm ypxfr +.Nd "transfer NIS database from remote server to local host" +.Sh SYNOPSIS +.Nm /usr/libexec/ypxfr +.Op Fl f +.Op Fl c +.Op Fl d Ar target domain +.Op Fl h Ar source host +.Op Fl s Ar source domain +.Op Fl p Ar path +.Op Fl C Ar taskid program-number ipaddr port +.Ar mapname +.Sh DESCRIPTION +The +.Nm +utility copies an +.Tn NIS +database (or +.Pa map ) +from one +.Tn NIS +server to another using +.Tn NIS +services. +In +.Fx , +.Nm +is generally invoked by +.Xr ypserv 8 +when it receives a map transfer request from +.Xr yppush 8 . +The +.Nm +utility is used primarily in environments where several +.Tn NIS +servers are in use in a single domain. +One server, the +.Tn NIS +master, maintains +the canonical copies of all +.Tn NIS +maps, and all the other servers, +the +.Tn NIS +slaves, copy new versions of the maps from the master whenever +any updates are made (i.e., when a user updates their password via +.Xr yppasswd 1 ) . +.Pp +When run, +.Nm +creates a temporary database file in +.Pa /var/yp/[domainname] , +and fills it with the contents of +.Ar mapname +as supplied by the specified +.Ar source host . +When the entire map has been transferred, +.Nm +deletes the original copy of +.Ar mapname +and moves the temporary copy into its place. +When the transfer is +complete, +.Nm +will attempt to send a 'clear current map' request to the local +.Xr ypserv 8 +process to clear any possible references it may still have to the +stale map. +.Pp +Note that all files created by +.Nm +are owner readable and writable only for security reasons. +Since the +.Tn NIS +maps and the directory in which they reside are normally owned by +root, this prevents non-privileged users from making unauthorized +modifications. +.Pp +In order to maintain consistency across all +.Tn NIS +servers, +.Nm +can be run periodically in a +.Xr cron 8 +job. +Maps which change infrequently +need only be updated once a day (preferably late at night when system +usage is lowest), whereas those that are subject to frequent changes +(such a +.Pa passwd.byname +and +.Pa passwd.byuid ) +should be updated perhaps once every hour. +Using +.Xr cron 8 +to automatically +update the +.Tn NIS +maps is not strictly mandatory since all updates should +be propagated by +.Xr yppush 8 +when +.Pa /var/yp/Makefile +is run on the +.Tn NIS +master server, however it is good practice +on large networks where possible outages could cause +.Tn NIS +servers to fall out of sync with each other. +.Pp +When +.Nm +is invoked without a controlling terminal, e.g.\& from inside +.Xr ypserv 8 , +it logs all its output using the +.Xr syslog 3 +facility. +.Sh NOTES +The +.Fx +version of +.Nm +has support for a special map transfer protocol which works in +conjunction with the +.Fx +.Xr rpc.ypxfrd 8 +server. +This protocol allows it to transfer raw map database files from +the +.Tn NIS +master server and can be many times faster than the standard +transfer method, particularly for very large +.Tn NIS +maps. +The +.Nm +utility will check to see if the +.Xr rpc.ypxfrd 8 +server is registered on the +.Tn NIS +master server and attempt to use +it if it is present. +If it is not it will fall back to the standard +transfer method, copying the map contents from +.Xr ypserv 8 +and creating new maps instead. +.Pp +Note that while the +.Fx +ypxfrd protocol is conceptually similar +to the SunOS ypxfrd protocol, +the +.Fx +protocol is not compatible with +Sun's, therefore it will not work with Sun's ypxfrd server. +.Fx +slave systems can still transfer maps from any +.No non- Ns Fx +.Tn NIS +server, +however they will only be able to take advantage of the faster protocol +if the master server is also running +.Fx . +.Sh OPTIONS +The following options and flags are supported by +.Nm : +.Bl -tag -width indent +.It Fl f +Force a map transfer. +Normally, +.Nm +will not transfer a map if it determines that the +.Tn NIS +master's copy +is not newer than the existing copy already on the local host: the +.Fl f +flag forces a transfer regardless of which server's version is more recent. +.It Fl c +Do not send a 'clear current map' request to the +.Xr ypserv 8 +process running on the local host. +This flag is normally used when +invoking +.Nm +manually on a machine that is not yet running +.Xr ypserv 8 . +Without this flag, failure to contact the local +.Tn NIS +server will cause +.Nm +to abort the transfer. +.It Fl d Ar target domain +Specify a target domain other than the current +.Tn NIS +domain. +.It Fl h Ar source host +Specify the name of the host from which to copy the +.Tn NIS +maps. +This option +is used to ensure that +.Nm +only copies maps from the +.Tn NIS +master server. +.It Fl s Ar source domain +Specify the domain from which to transfer a map, in the event that +the transfer is being done across two different +.Tn NIS +domains. +.It Fl p Ar path +Specify the top level directory containing the +.Tn NIS +maps. +By +default, this path is +.Pa /var/yp . +The +.Fl p +flag allows you to specify an alternate path should you wish to +store your +.Tn NIS +maps in a different part of the file system. +The +.Tn NIS +server, +.Xr ypserv 8 , +passes this flag to +.Nm +if it too has been told to use an alternate path. +.It Fl C Ar taskid program-number ipaddr port +These options are used only when +.Nm +is invoked by +.Xr ypserv 8 +in response to a map transfer request initiated by +.Xr yppush 8 . +In this instance, +.Nm +needs to 'callback' to the +.Xr yppush 8 +process and interact with it, so +.Xr yppush 8 +passes to it an IP address +.Ar ipaddr , +port number +.Ar port , +registered program number +.Ar program-number +and a transaction ID +.Ar taskid +that it can use to contact the waiting +.Xr yppush 8 +process on the master server. +.It Ar mapname +The name of the map to transfer. +.El +.Sh FILES +.Bl -tag -width Pa -compact +.It Pa /var/yp/[domainname]/[maps] +The +.Tn NIS +maps for a particular +.Tn NIS +domain. +.El +.Sh SEE ALSO +.Xr yp 8 , +.Xr yppush 8 , +.Xr ypserv 8 +.Sh AUTHORS +.An Bill Paul Aq Mt wpaul@ctr.columbia.edu diff --git a/libexec/ypxfr/ypxfr_extern.h b/libexec/ypxfr/ypxfr_extern.h new file mode 100644 index 0000000..f843b64 --- /dev/null +++ b/libexec/ypxfr/ypxfr_extern.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include <sys/types.h> +#include <limits.h> +#include <paths.h> +#include <db.h> +#include <rpcsvc/yp.h> + +extern HASHINFO openinfo; +extern BTREEINFO openinfo_b; + +#ifndef _PATH_YP +#define _PATH_YP "/var/yp/" +#endif + +extern char *yp_dir; +extern int debug; +extern enum ypstat yp_errno; +extern void yp_error(const char *, ...); +extern int _yp_check(char **); +extern const char *ypxfrerr_string(ypxfrstat); +extern DB *yp_open_db_rw(const char *, const char *, const int); +extern void yp_init_dbs(void); +extern int yp_put_record(DB *, DBT *, DBT *, int); +extern int yp_get_record(const char *, const char *, const DBT *, DBT *, int); +extern int ypxfr_get_map(char *, char *, char *, int (*)(int, char *, int, char *, int, char*)); +extern char *ypxfr_get_master(char *, char *, char *, const int); +extern unsigned long ypxfr_get_order(char *, char *, char *, const int); +extern int ypxfr_match(char *, char *, char *, char *, unsigned long); +extern char *ypxfxerr_string(ypxfrstat); +extern int ypxfrd_get_map(char *, char *, char *, char *); diff --git a/libexec/ypxfr/ypxfr_getmap.c b/libexec/ypxfr/ypxfr_getmap.c new file mode 100644 index 0000000..4b8794f --- /dev/null +++ b/libexec/ypxfr/ypxfr_getmap.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <stdio.h> +#include <time.h> +#include <sys/types.h> +#include <rpc/rpc.h> +#include <rpc/xdr.h> +#include <rpcsvc/yp.h> +#include "ypxfr_extern.h" + +extern bool_t xdr_ypresp_all_seq(XDR *, unsigned long *); + +int (*ypresp_allfn)(); +void *ypresp_data; +extern DB *specdbp; +extern enum ypstat yp_errno; + +/* + * This is largely the same as yp_all() except we do the transfer + * from a specific server without the aid of ypbind(8). We need to + * be able to specify the source host explicitly since ypxfr may + * only transfer maps from the NIS master server for any given domain. + * However, if we use the libc version of yp_all(), we could end up + * talking to one of the slaves instead. We do need to dig into libc + * a little though, since it contains the magic XDR function we need. + */ +int +ypxfr_get_map(char *map, char *domain, char *host, + int (*callback)(int, char *, int, char *, int, char*)) +{ + CLIENT *clnt; + ypreq_nokey req; + unsigned long status; + struct timeval timeout; + + timeout.tv_usec = 0; + timeout.tv_sec = 10; + + /* YPPROC_ALL is a TCP service */ + if ((clnt = clnt_create(host, YPPROG, YPVERS, "tcp")) == NULL) { + yp_error("%s", clnt_spcreateerror("failed to \ +create tcp handle")); + yp_errno = (enum ypstat)YPXFR_YPERR; + return(1); + } + + req.domain = domain; + req.map = map; + ypresp_allfn = callback; + ypresp_data = NULL; + + (void)clnt_call(clnt, YPPROC_ALL, (xdrproc_t)xdr_ypreq_nokey, &req, + (xdrproc_t)xdr_ypresp_all_seq, &status, timeout); + + clnt_destroy(clnt); + + if (status == YP_NOMORE) + return(0); + + if (status != YP_TRUE) { + yp_errno = (enum ypstat)YPXFR_YPERR; + return(1); + } + + return(0); +} diff --git a/libexec/ypxfr/ypxfr_main.c b/libexec/ypxfr/ypxfr_main.c new file mode 100644 index 0000000..70fd1f5 --- /dev/null +++ b/libexec/ypxfr/ypxfr_main.c @@ -0,0 +1,577 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <rpc/rpc.h> +#include <rpc/clnt.h> +#include <rpcsvc/yp.h> +#include <rpcsvc/ypclnt.h> +#include <rpcsvc/ypxfrd.h> +#include "ypxfr_extern.h" + +char *progname = "ypxfr"; +char *yp_dir = _PATH_YP; +int _rpcpmstart = 0; +int ypxfr_use_yplib = 0; /* Assume the worst. */ +int ypxfr_clear = 1; +int ypxfr_prognum = 0; +struct sockaddr_in ypxfr_callback_addr; +struct yppushresp_xfr ypxfr_resp; +DB *dbp; + +static void +ypxfr_exit(ypxfrstat retval, char *temp) +{ + CLIENT *clnt; + int sock = RPC_ANYSOCK; + struct timeval timeout; + + /* Clean up no matter what happened previously. */ + if (temp != NULL) { + if (dbp != NULL) + (void)(dbp->close)(dbp); + if (unlink(temp) == -1) { + yp_error("failed to unlink %s",strerror(errno)); + } + } + + if (ypxfr_prognum) { + timeout.tv_sec = 20; + timeout.tv_usec = 0; + + if ((clnt = clntudp_create(&ypxfr_callback_addr, ypxfr_prognum, + 1, timeout, &sock)) == NULL) { + yp_error("%s", clnt_spcreateerror("failed to " + "establish callback handle")); + exit(1); + } + + ypxfr_resp.status = (yppush_status)retval; + + if (yppushproc_xfrresp_1(&ypxfr_resp, clnt) == NULL) { + yp_error("%s", clnt_sperror(clnt, "callback failed")); + clnt_destroy(clnt); + exit(1); + } + clnt_destroy(clnt); + } else { + yp_error("Exiting: %s", ypxfrerr_string(retval)); + } + + exit(0); +} + +static void +usage(void) +{ + if (_rpcpmstart) { + ypxfr_exit(YPXFR_BADARGS,NULL); + } else { + fprintf(stderr, "%s\n%s\n%s\n", + "usage: ypxfr [-f] [-c] [-d target domain] [-h source host]", + " [-s source domain] [-p path]", + " [-C taskid program-number ipaddr port] mapname"); + exit(1); + } +} + +int +ypxfr_foreach(int status, char *key, int keylen, char *val, int vallen, + char *data) +{ + DBT dbkey, dbval; + + if (status != YP_TRUE) + return (status); + + /* + * XXX Do not attempt to write zero-length keys or + * data into a Berkeley DB hash database. It causes a + * strange failure mode where sequential searches get + * caught in an infinite loop. + */ + if (keylen) { + dbkey.data = key; + dbkey.size = keylen; + } else { + dbkey.data = ""; + dbkey.size = 1; + } + if (vallen) { + dbval.data = val; + dbval.size = vallen; + } else { + dbval.data = ""; + dbval.size = 1; + } + + if (yp_put_record(dbp, &dbkey, &dbval, 0) != YP_TRUE) + return(yp_errno); + + return (0); +} + +int +main(int argc, char *argv[]) +{ + int ch; + int ypxfr_force = 0; + char *ypxfr_dest_domain = NULL; + char *ypxfr_source_host = NULL; + char *ypxfr_source_domain = NULL; + char *ypxfr_local_domain = NULL; + char *ypxfr_master = NULL; + unsigned long ypxfr_order = -1, ypxfr_skew_check = -1; + char *ypxfr_mapname = NULL; + int ypxfr_args = 0; + char ypxfr_temp_map[MAXPATHLEN + 2]; + char tempmap[MAXPATHLEN + 2]; + char buf[MAXPATHLEN + 2]; + DBT key, data; + int remoteport; + int interdom = 0; + int secure = 0; + + debug = 1; + + if (!isatty(fileno(stderr))) { + openlog("ypxfr", LOG_PID, LOG_DAEMON); + _rpcpmstart = 1; + } + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, "fcd:h:s:p:C:")) != -1) { + int my_optind; + switch (ch) { + case 'f': + ypxfr_force++; + ypxfr_args++; + break; + case 'c': + ypxfr_clear = 0; + ypxfr_args++; + break; + case 'd': + ypxfr_dest_domain = optarg; + ypxfr_args += 2; + break; + case 'h': + ypxfr_source_host = optarg; + ypxfr_args += 2; + break; + case 's': + ypxfr_source_domain = optarg; + ypxfr_args += 2; + break; + case 'p': + yp_dir = optarg; + ypxfr_args += 2; + break; + case 'C': + /* + * Whoever decided that the -C flag should take + * four arguments is a twit. + */ + my_optind = optind - 1; + if (argv[my_optind] == NULL || !strlen(argv[my_optind])) { + yp_error("transaction ID not specified"); + usage(); + } + ypxfr_resp.transid = atol(argv[my_optind]); + my_optind++; + if (argv[my_optind] == NULL || !strlen(argv[my_optind])) { + yp_error("RPC program number not specified"); + usage(); + } + ypxfr_prognum = atol(argv[my_optind]); + my_optind++; + if (argv[my_optind] == NULL || !strlen(argv[my_optind])) { + yp_error("address not specified"); + usage(); + } + if (!inet_aton(argv[my_optind], &ypxfr_callback_addr.sin_addr)) { + yp_error("failed to convert '%s' to IP addr", + argv[my_optind]); + exit(1); + } + my_optind++; + if (argv[my_optind] == NULL || !strlen(argv[my_optind])) { + yp_error("port not specified"); + usage(); + } + ypxfr_callback_addr.sin_port = htons((u_short)atoi(argv[my_optind])); + ypxfr_args += 5; + break; + default: + usage(); + break; + } + } + + ypxfr_mapname = argv[ypxfr_args + 1]; + + if (ypxfr_mapname == NULL) { + yp_error("no map name specified"); + usage(); + } + + /* Always the case. */ + ypxfr_callback_addr.sin_family = AF_INET; + + /* Determine if local NIS client facilities are turned on. */ + if (!yp_get_default_domain(&ypxfr_local_domain) && + _yp_check(&ypxfr_local_domain)) + ypxfr_use_yplib = 1; + + /* + * If no destination domain is specified, assume that the + * local default domain is to be used and try to obtain it. + * Fails if NIS client facilities are turned off. + */ + if (ypxfr_dest_domain == NULL) { + if (ypxfr_use_yplib) { + yp_get_default_domain(&ypxfr_dest_domain); + } else { + yp_error("no destination domain specified and \ +the local domain name isn't set"); + ypxfr_exit(YPXFR_BADARGS,NULL); + } + } + + /* + * If a source domain is not specified, assume it to + * be the same as the destination domain. + */ + if (ypxfr_source_domain == NULL) { + ypxfr_source_domain = ypxfr_dest_domain; + } + + /* + * If the source host is not specified, assume it to be the + * master for the specified map. If local NIS client facilities + * are turned on, we can figure this out using yp_master(). + * If not, we have to see if a local copy of the map exists + * and extract its YP_MASTER_NAME record. If _that_ fails, + * we are stuck and must ask the user for more information. + */ + if (ypxfr_source_host == NULL) { + if (!ypxfr_use_yplib) { + /* + * Double whammy: NIS isn't turned on and the user + * didn't specify a source host. + */ + char *dptr; + key.data = "YP_MASTER_NAME"; + key.size = sizeof("YP_MASTER_NAME") - 1; + + if (yp_get_record(ypxfr_dest_domain, ypxfr_mapname, + &key, &data, 1) != YP_TRUE) { + yp_error("no source host specified"); + ypxfr_exit(YPXFR_BADARGS,NULL); + } + dptr = data.data; + dptr[data.size] = '\0'; + ypxfr_master = ypxfr_source_host = strdup(dptr); + } + } else { + if (ypxfr_use_yplib) + ypxfr_use_yplib = 0; + } + + if (ypxfr_master == NULL) { + if ((ypxfr_master = ypxfr_get_master(ypxfr_source_domain, + ypxfr_mapname, + ypxfr_source_host, + ypxfr_use_yplib)) == NULL) { + yp_error("failed to find master of %s in domain %s: %s", + ypxfr_mapname, ypxfr_source_domain, + ypxfrerr_string((ypxfrstat)yp_errno)); + ypxfr_exit(YPXFR_MADDR,NULL); + } + } + + /* + * If we got here and ypxfr_source_host is still undefined, + * it means we had to resort to using yp_master() to find the + * master server for the map. The source host and master should + * be identical. + */ + if (ypxfr_source_host == NULL) + ypxfr_source_host = ypxfr_master; + + /* + * Don't talk to ypservs on unprivileged ports. + */ + remoteport = getrpcport(ypxfr_source_host, YPPROG, YPVERS, IPPROTO_UDP); + if (remoteport >= IPPORT_RESERVED) { + yp_error("ypserv on %s not running on reserved port", + ypxfr_source_host); + ypxfr_exit(YPXFR_REFUSED, NULL); + } + + if ((ypxfr_order = ypxfr_get_order(ypxfr_source_domain, + ypxfr_mapname, + ypxfr_master, 0)) == 0) { + yp_error("failed to get order number of %s: %s", + ypxfr_mapname, yp_errno == YPXFR_SUCC ? + "map has order 0" : + ypxfrerr_string((ypxfrstat)yp_errno)); + ypxfr_exit(YPXFR_YPERR,NULL); + } + + if (ypxfr_match(ypxfr_master, ypxfr_source_domain, ypxfr_mapname, + "YP_INTERDOMAIN", sizeof("YP_INTERDOMAIN") - 1)) + interdom++; + + if (ypxfr_match(ypxfr_master, ypxfr_source_domain, ypxfr_mapname, + "YP_SECURE", sizeof("YP_SECURE") - 1)) + secure++; + + key.data = "YP_LAST_MODIFIED"; + key.size = sizeof("YP_LAST_MODIFIED") - 1; + + /* The order number is immaterial when the 'force' flag is set. */ + + if (!ypxfr_force) { + int ignore = 0; + if (yp_get_record(ypxfr_dest_domain,ypxfr_mapname,&key,&data,1) != YP_TRUE) { + switch (yp_errno) { + case YP_NOKEY: + ypxfr_exit(YPXFR_FORCE,NULL); + break; + case YP_NOMAP: + /* + * If the map doesn't exist, we're + * creating it. Ignore the error. + */ + ignore++; + break; + case YP_BADDB: + default: + ypxfr_exit(YPXFR_DBM,NULL); + break; + } + } + if (!ignore && ypxfr_order <= atoi(data.data)) + ypxfr_exit(YPXFR_AGE, NULL); + + } + + /* Construct a temporary map file name */ + snprintf(tempmap, sizeof(tempmap), "%s.%d",ypxfr_mapname, getpid()); + snprintf(ypxfr_temp_map, sizeof(ypxfr_temp_map), "%s/%s/%s", yp_dir, + ypxfr_dest_domain, tempmap); + + if ((remoteport = getrpcport(ypxfr_source_host, YPXFRD_FREEBSD_PROG, + YPXFRD_FREEBSD_VERS, IPPROTO_TCP))) { + + /* Don't talk to rpc.ypxfrds on unprovileged ports. */ + if (remoteport >= IPPORT_RESERVED) { + yp_error("rpc.ypxfrd on %s not using privileged port", + ypxfr_source_host); + ypxfr_exit(YPXFR_REFUSED, NULL); + } + + /* Try to send using ypxfrd. If it fails, use old method. */ + if (!ypxfrd_get_map(ypxfr_source_host, ypxfr_mapname, + ypxfr_source_domain, ypxfr_temp_map)) + goto leave; + } + + /* Open the temporary map read/write. */ + if ((dbp = yp_open_db_rw(ypxfr_dest_domain, tempmap, 0)) == NULL) { + yp_error("failed to open temporary map file"); + ypxfr_exit(YPXFR_DBM,NULL); + } + + /* + * Fill in the keys we already know, such as the order number, + * master name, input file name (we actually make up a bogus + * name for that) and output file name. + */ + snprintf(buf, sizeof(buf), "%lu", ypxfr_order); + data.data = buf; + data.size = strlen(buf); + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to write order number to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + + key.data = "YP_MASTER_NAME"; + key.size = sizeof("YP_MASTER_NAME") - 1; + data.data = ypxfr_master; + data.size = strlen(ypxfr_master); + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to write master name to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + + key.data = "YP_DOMAIN_NAME"; + key.size = sizeof("YP_DOMAIN_NAME") - 1; + data.data = ypxfr_dest_domain; + data.size = strlen(ypxfr_dest_domain); + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to write domain name to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + + snprintf (buf, sizeof(buf), "%s:%s", ypxfr_source_host, ypxfr_mapname); + + key.data = "YP_INPUT_NAME"; + key.size = sizeof("YP_INPUT_NAME") - 1; + data.data = &buf; + data.size = strlen(buf); + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to write input name to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + + } + + snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, ypxfr_dest_domain, + ypxfr_mapname); + + key.data = "YP_OUTPUT_NAME"; + key.size = sizeof("YP_OUTPUT_NAME") - 1; + data.data = &buf; + data.size = strlen(buf); + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to write output name to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + + if (interdom) { + key.data = "YP_INTERDOMAIN"; + key.size = sizeof("YP_INTERDOMAIN") - 1; + data.data = ""; + data.size = 0; + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to add interdomain flag to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + } + + if (secure) { + key.data = "YP_SECURE"; + key.size = sizeof("YP_SECURE") - 1; + data.data = ""; + data.size = 0; + + if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) { + yp_error("failed to add secure flag to database"); + ypxfr_exit(YPXFR_DBM,ypxfr_temp_map); + } + } + + /* Now suck over the contents of the map from the master. */ + + if (ypxfr_get_map(ypxfr_mapname,ypxfr_source_domain, + ypxfr_source_host, ypxfr_foreach)){ + yp_error("failed to retrieve map from source host"); + ypxfr_exit(YPXFR_YPERR,ypxfr_temp_map); + } + + (void)(dbp->close)(dbp); + dbp = NULL; /* <- yes, it seems this is necessary. */ + +leave: + + snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, ypxfr_dest_domain, + ypxfr_mapname); + + /* Peek at the order number again and check for skew. */ + if ((ypxfr_skew_check = ypxfr_get_order(ypxfr_source_domain, + ypxfr_mapname, + ypxfr_master, 0)) == 0) { + yp_error("failed to get order number of %s: %s", + ypxfr_mapname, yp_errno == YPXFR_SUCC ? + "map has order 0" : + ypxfrerr_string((ypxfrstat)yp_errno)); + ypxfr_exit(YPXFR_YPERR,ypxfr_temp_map); + } + + if (ypxfr_order != ypxfr_skew_check) + ypxfr_exit(YPXFR_SKEW,ypxfr_temp_map); + + /* + * Send a YPPROC_CLEAR to the local ypserv. + */ + if (ypxfr_clear) { + char in = 0; + char *out = NULL; + int stat; + if ((stat = callrpc("localhost",YPPROG,YPVERS,YPPROC_CLEAR, + (xdrproc_t)xdr_void, (void *)&in, + (xdrproc_t)xdr_void, (void *)out)) != RPC_SUCCESS) { + yp_error("failed to send 'clear' to local ypserv: %s", + clnt_sperrno((enum clnt_stat) stat)); + ypxfr_exit(YPXFR_CLEAR, ypxfr_temp_map); + } + } + + /* + * Put the new map in place immediately. I'm not sure if the + * kernel does an unlink() and rename() atomically in the event + * that we move a new copy of a map over the top of an existing + * one, but there's less chance of a race condition happening + * than if we were to do the unlink() ourselves. + */ + if (rename(ypxfr_temp_map, buf) == -1) { + yp_error("rename(%s,%s) failed: %s", ypxfr_temp_map, buf, + strerror(errno)); + ypxfr_exit(YPXFR_FILE,NULL); + } + + ypxfr_exit(YPXFR_SUCC,NULL); + + return(1); +} diff --git a/libexec/ypxfr/ypxfr_misc.c b/libexec/ypxfr/ypxfr_misc.c new file mode 100644 index 0000000..323fd53 --- /dev/null +++ b/libexec/ypxfr/ypxfr_misc.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 1995 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/param.h> +#include <rpc/rpc.h> +#include <rpcsvc/yp.h> +#include <rpcsvc/ypclnt.h> +#include "ypxfr_extern.h" + +const char * +ypxfrerr_string(ypxfrstat code) +{ + switch (code) { + case YPXFR_SUCC: + return ("Map successfully transferred"); + break; + case YPXFR_AGE: + return ("Master's version not newer"); + break; + case YPXFR_NOMAP: + return ("No such map in server's domain"); + break; + case YPXFR_NODOM: + return ("Domain not supported by server"); + break; + case YPXFR_RSRC: + return ("Local resource allocation failure"); + break; + case YPXFR_RPC: + return ("RPC failure talking to server"); + break; + case YPXFR_MADDR: + return ("Could not get master server address"); + break; + case YPXFR_YPERR: + return ("NIS server/map database error"); + break; + case YPXFR_BADARGS: + return ("Request arguments bad"); + break; + case YPXFR_DBM: + return ("Local database operation failed"); + break; + case YPXFR_FILE: + return ("Local file I/O operation failed"); + break; + case YPXFR_SKEW: + return ("Map version skew during transfer"); + break; + case YPXFR_CLEAR: + return ("Couldn't send \"clear\" request to local ypserv"); + break; + case YPXFR_FORCE: + return ("No local order number in map -- use -f flag"); + break; + case YPXFR_XFRERR: + return ("General ypxfr error"); + break; + case YPXFR_REFUSED: + return ("Transfer request refused by ypserv"); + break; + default: + return ("Unknown error code"); + break; + } +} + +/* + * These are wrappers for the usual yp_master() and yp_order() functions. + * They can use either local yplib functions (the real yp_master() and + * yp_order()) or do direct RPCs to a specified server. The latter is + * necessary if ypxfr is run on a machine that isn't configured as an + * NIS client (this can happen very easily: a given machine need not be + * an NIS client in order to be an NIS server). + */ + +/* + * Careful: yp_master() returns a pointer to a dynamically allocated + * buffer. Calling ypproc_master_2() ourselves also returns a pointer + * to dynamically allocated memory, though this time it's memory + * allocated by the XDR routines. We have to rememver to free() or + * xdr_free() the memory as required to avoid leaking memory. + */ +char * +ypxfr_get_master(char *domain, char *map, char *source, const int yplib) +{ + static char mastername[MAXPATHLEN + 2]; + + bzero((char *)&mastername, sizeof(mastername)); + + if (yplib) { + int res; + char *master; + if ((res = yp_master(domain, map, &master))) { + switch (res) { + case YPERR_DOMAIN: + yp_errno = (enum ypstat)YPXFR_NODOM; + break; + case YPERR_MAP: + yp_errno = (enum ypstat)YPXFR_NOMAP; + break; + case YPERR_YPERR: + default: + yp_errno = (enum ypstat)YPXFR_YPERR; + break; + } + return(NULL); + } else { + snprintf(mastername, sizeof(mastername), "%s", master); + free(master); + return((char *)&mastername); + } + } else { + CLIENT *clnt; + ypresp_master *resp; + ypreq_nokey req; + + if ((clnt = clnt_create(source,YPPROG,YPVERS,"udp")) == NULL) { + yp_error("%s",clnt_spcreateerror("failed to \ +create udp handle to ypserv")); + yp_errno = (enum ypstat)YPXFR_RPC; + return(NULL); + } + + req.map = map; + req.domain = domain; + if ((resp = ypproc_master_2(&req, clnt)) == NULL) { + yp_error("%s",clnt_sperror(clnt,"YPPROC_MASTER \ +failed")); + clnt_destroy(clnt); + yp_errno = (enum ypstat)YPXFR_RPC; + return(NULL); + } + clnt_destroy(clnt); + if (resp->stat != YP_TRUE) { + switch (resp->stat) { + case YP_NODOM: + yp_errno = (enum ypstat)YPXFR_NODOM; + break; + case YP_NOMAP: + yp_errno = (enum ypstat)YPXFR_NOMAP; + break; + case YP_YPERR: + default: + yp_errno = (enum ypstat)YPXFR_YPERR; + break; + } + return(NULL); + } + snprintf(mastername, sizeof(mastername), "%s", resp->peer); +/* xdr_free(xdr_ypresp_master, (char *)&resp); */ + return((char *)&mastername); + } +} + +unsigned long +ypxfr_get_order(char *domain, char *map, char *source, const int yplib) +{ + if (yplib) { + unsigned int order; + int res; + if ((res = yp_order(domain, map, &order))) { + switch (res) { + case YPERR_DOMAIN: + yp_errno = (enum ypstat)YPXFR_NODOM; + break; + case YPERR_MAP: + yp_errno = (enum ypstat)YPXFR_NOMAP; + break; + case YPERR_YPERR: + default: + yp_errno = (enum ypstat)YPXFR_YPERR; + break; + } + return(0); + } else + return(order); + } else { + CLIENT *clnt; + ypresp_order *resp; + ypreq_nokey req; + + if ((clnt = clnt_create(source,YPPROG,YPVERS,"udp")) == NULL) { + yp_error("%s",clnt_spcreateerror("couldn't create \ +udp handle to ypserv")); + yp_errno = (enum ypstat)YPXFR_RPC; + return(0); + } + req.map = map; + req.domain = domain; + if ((resp = ypproc_order_2(&req, clnt)) == NULL) { + yp_error("%s", clnt_sperror(clnt, "YPPROC_ORDER \ +failed")); + clnt_destroy(clnt); + yp_errno = (enum ypstat)YPXFR_RPC; + return(0); + } + clnt_destroy(clnt); + if (resp->stat != YP_TRUE) { + switch (resp->stat) { + case YP_NODOM: + yp_errno = (enum ypstat)YPXFR_NODOM; + break; + case YP_NOMAP: + yp_errno = (enum ypstat)YPXFR_NOMAP; + break; + case YP_YPERR: + default: + yp_errno = (enum ypstat)YPXFR_YPERR; + break; + } + return(0); + } + return(resp->ordernum); + } +} + +int +ypxfr_match(char *server, char *domain, char *map, char *key, + unsigned long keylen) +{ + ypreq_key ypkey; + ypresp_val *ypval; + CLIENT *clnt; + static char buf[YPMAXRECORD + 2]; + + bzero(buf, sizeof(buf)); + + if ((clnt = clnt_create(server, YPPROG,YPVERS,"udp")) == NULL) { + yp_error("failed to create UDP handle: %s", + clnt_spcreateerror(server)); + return(0); + } + + ypkey.domain = domain; + ypkey.map = map; + ypkey.key.keydat_len = keylen; + ypkey.key.keydat_val = key; + + if ((ypval = ypproc_match_2(&ypkey, clnt)) == NULL) { + clnt_destroy(clnt); + yp_error("%s: %s", server, + clnt_sperror(clnt,"YPPROC_MATCH failed")); + return(0); + } + + clnt_destroy(clnt); + + if (ypval->stat != YP_TRUE) { + xdr_free((xdrproc_t)xdr_ypresp_val, ypval); + return(0); + } + + xdr_free((xdrproc_t)xdr_ypresp_val, ypval); + + return(1); +} diff --git a/libexec/ypxfr/ypxfrd_getmap.c b/libexec/ypxfr/ypxfrd_getmap.c new file mode 100644 index 0000000..b1424ac --- /dev/null +++ b/libexec/ypxfr/ypxfrd_getmap.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 1995, 1996 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <rpcsvc/ypxfrd.h> +#include <rpcsvc/yp.h> +#include <rpc/rpc.h> +#include <sys/uio.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "ypxfr_extern.h" + +int fp = 0; + +static bool_t +xdr_my_xfr(register XDR *xdrs, xfr *objp) +{ + while (1) { + if (!xdr_xfr(xdrs, objp)) + return(FALSE); + if (objp->ok == TRUE) { + if (write(fp, objp->xfr_u.xfrblock_buf.xfrblock_buf_val, + objp->xfr_u.xfrblock_buf.xfrblock_buf_len) == -1) { + yp_error("write failed: %s", strerror(errno)); + return(FALSE); + } + } + xdr_free((xdrproc_t)xdr_xfr, (char *)objp); + if (objp->ok == FALSE) { + switch (objp->xfr_u.xfrstat) { + case(XFR_DONE): + return(TRUE); + break; + case(XFR_READ_ERR): + yp_error("got read error from rpc.ypxfrd"); + return(FALSE); + break; + case(XFR_ACCESS): + yp_error("rpc.ypxfrd couldn't access the map"); + return(FALSE); + break; + case(XFR_DENIED): + yp_error("access to map denied by rpc.ypxfrd"); + return(FALSE); + break; + case(XFR_DB_TYPE_MISMATCH): + yp_error("client/server DB type mismatch"); + return(FALSE); + break; + case(XFR_DB_ENDIAN_MISMATCH): + yp_error("client/server byte order mismatch"); + return(FALSE); + break; + default: + yp_error("got unknown status from rpc.ypxfrd"); + return(FALSE); + break; + } + } + } +} + +#define PERM_SECURE (S_IRUSR|S_IWUSR) + +int +ypxfrd_get_map(char *host, char *map, char *domain, char *tmpname) +{ + CLIENT *clnt; + struct ypxfr_mapname req; + struct xfr resp; + struct timeval timeout = { 0, 25 }; + int status = 0; + + req.xfrmap = map; + req.xfrdomain = domain; + req.xfrmap_filename = ""; + req.xfr_db_type = XFR_DB_BSD_HASH; /* + req.xfr_byte_order = XFR_ENDIAN_ANY; * Berkeley DB isn't + * byte-order sensitive. + */ + + bzero((char *)&resp, sizeof(resp)); + + if ((clnt = clnt_create(host, YPXFRD_FREEBSD_PROG, + YPXFRD_FREEBSD_VERS, "tcp")) == NULL) { + return(1); + } + + if ((fp = open(tmpname, O_RDWR|O_CREAT, PERM_SECURE)) == -1) { + clnt_destroy(clnt); + yp_error("couldn't open %s: %s", tmpname, strerror(errno)); + return(1); + } + + if (clnt_call(clnt,YPXFRD_GETMAP, + (xdrproc_t)xdr_ypxfr_mapname, (char *)&req, + (xdrproc_t)xdr_my_xfr, (char *)&resp, + timeout) != RPC_SUCCESS) { + yp_error("%s", clnt_sperror(clnt,"call to rpc.ypxfrd failed")); + status++; + unlink(tmpname); + } + + clnt_destroy(clnt); + close(fp); + return(status); +} |