diff options
Diffstat (limited to 'usr/src/lib/print/libpapi-lpd/common/lpd-port.c')
-rw-r--r-- | usr/src/lib/print/libpapi-lpd/common/lpd-port.c | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/usr/src/lib/print/libpapi-lpd/common/lpd-port.c b/usr/src/lib/print/libpapi-lpd/common/lpd-port.c new file mode 100644 index 0000000000..2cc106652f --- /dev/null +++ b/usr/src/lib/print/libpapi-lpd/common/lpd-port.c @@ -0,0 +1,768 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + */ + +/* $Id: lpd-port.c 155 2006-04-26 02:34:54Z ktou $ */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <config-site.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <syslog.h> +#include <values.h> +#include <stropts.h> /* for sendfd */ +#include <sys/uio.h> /* for sendmsg stuff */ +#include <pwd.h> +#include <sys/sendfile.h> +#include <ctype.h> +#include <alloca.h> +#ifdef HAVE_PRIV_H +#include <priv.h> +#endif +#include <papi_impl.h> + +#ifndef JOB_ID_FILE +#define JOB_ID_FILE "/var/run/rfc-1179.seq" +#endif /* JOB_ID_FILE */ + +static int +sendfd(int sockfd, int fd) +{ + syslog(LOG_DEBUG, "sendfd(%d, %d)", sockfd, fd); + +#if defined(sun) && defined(unix) && defined(I_SENDFD) + return (ioctl(sockfd, I_SENDFD, fd)); +#else + struct iovec iov[1]; + struct msghdr msg; +#ifdef CMSG_DATA + struct cmsghdr cmp[1]; + char buf[2]; /* send/recv 2 byte protocol */ + + iov[0].iov_base = buf; + iov[0].iov_len = 2; + + cmp[0].cmsg_level = SOL_SOCKET; + cmp[0].cmsg_type = SCM_RIGHTS; + cmp[0].cmsg_len = sizeof (struct cmsghdr) + sizeof (int); + * (int *)CMSG_DATA(cmp) = fd; + + buf[1] = 0; + buf[0] = 0; + msg.msg_control = cmp; + msg.msg_controllen = sizeof (struct cmsghdr) + sizeof (int); +#else + iov[0].iov_base = NULL; + iov[0].iov_len = 0; + msg.msg_accrights = (caddr_t)&fd; + msg.msg_accrights = sizeof (fd); +#endif + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + + return (sendmsg(sockfd, &msg, 0)); +#endif +} + +static void +null(int i) +{ +} + +static int +sock_connect(int sock, uri_t *uri, int timeout) +{ + struct hostent *hp; + struct servent *sp; +#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF) + struct sockaddr_in6 sin; +#else + struct sockaddr_in sin; +#endif + static void (*old_handler)(); + int err, + error_num; + unsigned timo = 1; + int port = -1; + + + /* + * Get the host address and port number to connect to. + */ + if ((uri == NULL) || (uri->host == NULL)) { + return (-1); + } + + /* linux style NULL usage */ + (void) memset((char *)&sin, (int)NULL, sizeof (sin)); + +#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF) + if ((hp = getipnodebyname(uri->host, AF_INET6, AI_DEFAULT, + &error_num)) == NULL) { + errno = ENOENT; + return (-1); + } + (void) memcpy((caddr_t)&sin.sin6_addr, hp->h_addr, hp->h_length); + sin.sin6_family = hp->h_addrtype; +#else + if ((hp = gethostbyname(uri->host)) == NULL) { + errno = ENOENT; + return (-1); + } + + (void) memcpy((caddr_t)&sin.sin_addr, hp->h_addr, hp->h_length); + sin.sin_family = hp->h_addrtype; +#endif + + if ((sp = getservbyname("printer", "tcp")) == NULL) { + errno = ENOENT; + return (-1); + } + + if (uri->port != NULL) + port = atoi(uri->port); + if (port < 0) + port = sp->s_port; + +#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF) + sin.sin6_port = port; +#else + sin.sin_port = port; +#endif + +retry: + old_handler = signal(SIGALRM, null); + (void) alarm(timeout); + + if (connect(sock, (struct sockaddr *)&sin, sizeof (sin)) < 0) { + (void) alarm(0); + (void) signal(SIGALRM, old_handler); + + if (errno == ECONNREFUSED && timo <= 16) { + (void) sleep(timo); + timo *= 2; + goto retry; + } + + return (-1); + } + + (void) alarm(0); + (void) signal(SIGALRM, old_handler); + return (sock); +} + +static int +next_job_id() +{ + int fd, result = getpid() % 1000; + + /* gain back enough privilege to open the id file */ +#ifdef PRIV_ALLSETS + if ((priv_set(PRIV_ON, PRIV_EFFECTIVE, + PRIV_FILE_DAC_READ, PRIV_FILE_DAC_WRITE, NULL)) < 0) { + syslog(LOG_ERR, "lpd_port:next_job_id:priv_set fails: : %m"); + return (-1); + } +#else + seteuid(0); +#endif + + /* open the sequence file */ + if (((fd = open(JOB_ID_FILE, O_RDWR)) < 0) && (errno == ENOENT)) + fd = open(JOB_ID_FILE, O_CREAT|O_EXCL|O_RDWR, 0644); + + syslog(LOG_DEBUG, "sequence file fd: %d", fd); + + /* drop our privilege again */ +#ifdef PRIV_ALLSETS + /* drop file access privilege */ + priv_set(PRIV_OFF, PRIV_PERMITTED, + PRIV_FILE_DAC_READ, PRIV_FILE_DAC_WRITE, NULL); +#else + seteuid(getuid()); +#endif + + if (fd >= 0) { + /* wait for a lock on the file */ + if (lockf(fd, F_LOCK, 0) == 0) { + char buf[8]; + int next; + + /* get the current id */ + (void) memset(buf, 0, sizeof (buf)); + if (read(fd, buf, sizeof (buf)) > 0) + result = atoi(buf); + + next = ((result < 999) ? (result + 1) : 0); + + /* store the next id in the file */ + snprintf(buf, sizeof (buf), "%.3d", next); + if ((lseek(fd, 0, SEEK_SET) == 0) && + (ftruncate(fd, 0) == 0)) + write(fd, buf, strlen(buf)); + } + } + syslog(LOG_DEBUG, "next_job_id() is %d", result); + + return (result); +} + +static int +reserved_port() +{ + int result = -1; + int port; + + /* gain back enough privilege to open a reserved port */ +#ifdef PRIV_ALLSETS + if ((priv_set( + PRIV_ON, PRIV_EFFECTIVE, PRIV_NET_PRIVADDR, NULL)) != 0) { + syslog(LOG_ERR, "priv_set fails for net_privaddr %m"); + return (-1); + } +#else + seteuid(0); +#endif + +#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF) + port = 0; /* set to 0, rresvport_af() will find us one. */ + result = rresvport_af(&port, AF_INET6); +#else + port = IPPORT_RESERVED - 1; + while (((result = rresvport(&port)) < 0) && (port >= 0)) + port--; +#endif + + /* drop our privilege again */ +#ifdef PRIV_ALLSETS + priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_NET_PRIVADDR, NULL); +#else + seteuid(getuid()); +#endif + + return (result); +} + +static char * +get_user_name() +{ + static struct passwd *p = NULL; + + if ((p = getpwuid(getuid())) != NULL) + return (p->pw_name); + else + return ("unknown"); +} + +static void +add_args(int ac, char **av, char *buf, size_t len) +{ + while (ac--) { + strlcat(buf, " ", len); + strlcat(buf, *(av++), len); + } +} + +static int +massage_control_data(char *data, int id) +{ + char *line, *iter = NULL; + char *ptr; + char host[BUFSIZ]; + + gethostname(host, sizeof (host)); + + for (ptr = strchr(data, '\n'); ptr != NULL; ptr = strchr(ptr, '\n')) { + ptr++; + + if (ptr[0] == 'H') { + if (strncmp(++ptr, host, strlen(host)) != 0) + return (-1); + } else if ((ptr[0] == 'P') || (ptr[0] == 'L')) { + /* check the user name */ + uid_t uid = getuid(); + struct passwd *pw; + int len; + + if (uid == 0) /* let root do what they want */ + continue; + if ((pw = getpwuid(uid)) == NULL) + return (-1); /* failed */ + len = strlen(pw->pw_name); + if ((strncmp(++ptr, pw->pw_name, len) != 0) || + (ptr[len] != '\n')) + return (-1); /* failed */ + } else if ((islower(ptr[0]) != 0) || (ptr[0] == 'U')) { + /* check/fix df?XXXhostname */ + ptr++; + + if (strlen(ptr) < 6) + return (-1); + if ((ptr[0] == 'd') && (ptr[1] == 'f') && + (ptr[3] == 'X') && (ptr[4] == 'X') && + (ptr[5] == 'X')) { + ptr[3] = '0' + (id / 100) % 10; + ptr[4] = '0' + (id / 10) % 10; + ptr[5] = '0' + id % 10; + + if (strncmp(&ptr[6], host, strlen(host)) != 0) + return (-1); + } else + return (-1); + } + } + return (1); +} + +static int +send_lpd_message(int fd, char *fmt, ...) +{ + char buf[BUFSIZ]; + size_t size; + va_list ap; + + va_start(ap, fmt); + size = vsnprintf(buf, sizeof (buf), fmt, ap); + va_end(ap); + if (size == 0) + size = 1; + + syslog(LOG_DEBUG, "lpd_messsage(%d, %s)", fd, buf); + + if (write(fd, buf, size) != size) { + errno = EIO; + return (-1); + } + + if ((read(fd, buf, 1) != 1) || (buf[0] != 0)) + return (-1); + + return (0); +} + +static int +send_data_file(int sock, char *dfname, char *name) +{ + size_t len; + off_t off = 0; + struct stat st; + char buf[32]; + int fd = -1; + + if (strcmp(name, "standard input") != 0) { + if ((fd = open(name, O_RDONLY)) < 0) + return (-1); + + if (fstat(fd, &st) < 0) + return (-1); + } else + st.st_size = MAXINT; /* should be 0 */ + + /* request data file transfer, read ack/nack */ + errno = ENOSPC; + if (send_lpd_message(sock, "\003%d %s\n", st.st_size, dfname) < 0) + return (-1); + + if (fd != -1) { + /* write the data */ + if (sendfile(sock, fd, &off, st.st_size) != st.st_size) + return (-1); + close(fd); + + /* request ack/nack after the data transfer */ + errno = EIO; + if (send_lpd_message(sock, "") < 0) + return (-1); + } + + return (0); +} + +static int +send_control_file(int sock, char *data, int id) +{ + int len; + char buf[BUFSIZ]; + char *host = "localhost"; + + len = strlen(data); + + /* request data file transfer, read ack/nack */ + errno = ENOSPC; + if (send_lpd_message(sock, "\002%d cfA%.3d%s\n", len, id, host) < 0) + return (-1); + + /* write the data */ + if (write(sock, data, len) != len) + return (-1); + + /* request ack/nack after the data transfer */ + errno = EIO; + if (send_lpd_message(sock, "") < 0) + return (-1); + + return (0); +} + + +static int +submit_job(int sock, uri_t *uri, int job_id, char *path) +{ + int current = 0; + off_t off = 0; + char *metadata = NULL; + char *ptr, *iter = NULL; + int fd, err; + int sent_files = 0; + char buf[BUFSIZ]; + size_t len; + char *printer = queue_name_from_uri(uri); + + /* read in the control file */ + if ((fd = open(path, O_RDONLY)) >= 0) { + struct stat st; + + if (fstat(fd, &st) < 0) { + close(fd); + return (-1); + } + + metadata = alloca(st.st_size + 1); + memset(metadata, 0, st.st_size + 1); + + if (read(fd, metadata, st.st_size) != st.st_size) { + close(fd); + free(metadata); + metadata = NULL; + return (-1); + } + + } else { + syslog(LOG_ERR, + "lpd-port:submit_job:open failed : %m path %s", path); + return (-1); + } + + /* massage the control file */ + if (massage_control_data(metadata, job_id) < 0) { + /* bad control data, dump the job */ + syslog(LOG_ALERT, + "bad control file, possible subversion attempt"); + } + + /* request to transfer the job */ + if (send_lpd_message(sock, "\002%s\n", printer) < 0) { + /* no such (or disabled) queue, got to love rfc-1179 */ + errno = ENOENT; + return (-1); + } + + /* send the control data */ + if (send_control_file(sock, metadata, job_id) < 0) { + err = errno; + write(sock, "\001\n", 2); /* abort */ + errno = err; + return (-1); + } + + /* walk the control file sending the data files */ + for (ptr = strtok_r(metadata, "\n", &iter); ptr != NULL; + ptr = strtok_r(NULL, "\n", &iter)) { + char *name = NULL; + + if (ptr[0] != 'U') + continue; + + name = strtok_r(NULL, "\n", &iter); + if (name[0] != 'N') + continue; + + ptr++; + name++; + + if (send_data_file(sock, ptr, name) < 0) { + err = errno; + write(sock, "\001\n", 2); /* abort */ + errno = err; + return (-1); + } + if (strcmp(name, "standard input") != 0) + sent_files++; + } + + /* write back the job-id */ + err = errno; + if ((fd = open(path, O_WRONLY)) >= 0) { + ftruncate(fd, 0); + write(fd, &job_id, sizeof (job_id)); + close(fd); + } + errno = err; + + if (sent_files != 0) { + err = errno; + close(sock); + errno = err; + } + + return (0); +} + +static int +query(int fd, uri_t *uri, int ac, char **av) +{ + char buf[BUFSIZ]; + int rc, len; + char *printer = queue_name_from_uri(uri); + + /* build the request */ + snprintf(buf, sizeof (buf), "\04%s", printer); + add_args(ac, av, buf, sizeof (buf)); + strlcat(buf, "\n", sizeof (buf)); + len = strlen(buf); + + if (((rc = write(fd, buf, len)) >= 0) && (rc != len)) { + errno = EMSGSIZE; + rc = -1; + } else + rc = 0; + + return (rc); +} + +static int +cancel(int fd, uri_t *uri, int ac, char **av) +{ + char buf[BUFSIZ]; + int rc, len; + char *printer = queue_name_from_uri(uri); + + /* build the request */ + snprintf(buf, sizeof (buf), "\05%s %s", printer, get_user_name()); + add_args(ac, av, buf, sizeof (buf)); + strlcat(buf, "\n", sizeof (buf)); + len = strlen(buf); + + if (((rc = write(fd, buf, len)) >= 0) && (rc != len)) { + errno = EMSGSIZE; + rc = -1; + } else + rc = 0; + + return (rc); +} + +static void +usage(char *program) +{ + char *name; + + setreuid(getuid(), getuid()); + + if ((name = strrchr(program, '/')) == NULL) + name = program; + else + name++; + + fprintf(stderr, "usage:\t%s -u uri [-t timeout] " + "[-s control ]\n", name); + fprintf(stderr, "\t%s -u uri [-t timeout] " + "[-c user|job ...]\n", name); + fprintf(stderr, "\t%s -u uri [-t timeout] " + "[-q user|job ...]\n", name); + exit(EINVAL); +} + +/* + * The main program temporarily loses privilege while searching the command + * line arguments. It then allocates any resources it need privilege for + * job-id, reserved port. Once it has the resources it needs, it perminently + * drops all elevated privilege. It ghen connects to the remote print service + * based on destination hostname. Doing it this way reduces the potenential + * opportunity for a breakout with elevated privilege, breakout with an + * unconnected reserved port, and exploitation of the remote print service + * by a calling program. + */ +int +main(int ac, char *av[]) +{ + enum { OP_NONE, OP_SUBMIT, OP_QUERY, OP_CANCEL } operation = OP_NONE; + int fd, c, timeout = 0, exit_code = 0; + uri_t *uri = NULL; + uid_t uid = getuid(); +#ifdef PRIV_ALLSETS + priv_set_t *saveset = NULL; +#endif + + openlog("lpd-port", LOG_PID, LOG_LPR); + +#ifdef PRIV_ALLSETS + + /* lose as much as we can perminently and temporarily drop the rest. */ + + if ((saveset = priv_str_to_set("PRIV_NET_PRIVADDR," + "PRIV_FILE_DAC_READ,PRIV_FILE_DAC_WRITE,", + ",", (const char **)NULL)) == NULL) { + syslog(LOG_ERR, + "lpd_port: priv_str_to_set saveset failed: %m\n"); + return (-1); + } + + if ((setppriv(PRIV_SET, PRIV_PERMITTED, saveset)) < 0) { + syslog(LOG_ERR, "lpd_port:setppriv:priv_set failed: %m"); + return (-1); + } + + /* + * These privileges permanently dropped in next_job_id() and + * reserved_port() + */ + + if ((setppriv(PRIV_OFF, PRIV_EFFECTIVE, saveset)) < 0) { + syslog(LOG_ERR, "lpd_port:setppriv:priv_off failed: %m"); + return (-1); + } + + priv_freeset(saveset); + + syslog(LOG_DEBUG, "using privs"); +#else + + syslog(LOG_DEBUG, "no privs"); + seteuid(uid); +#endif + + + while ((c = getopt(ac, av, "cqst:u:")) != EOF) { + switch (c) { + case 'c': + if (operation != OP_NONE) + usage(av[0]); + operation = OP_CANCEL; + break; + case 'q': + if (operation != OP_NONE) + usage(av[0]); + operation = OP_QUERY; + break; + case 's': + if (operation != OP_NONE) + usage(av[0]); + operation = OP_SUBMIT; + break; + case 't': + timeout = atoi(optarg); + break; + case 'u': + if (uri_from_string(optarg, &uri) < 0) + usage(av[0]); + break; + default: + usage(av[0]); + /* does not return */ + } + } + + if ((uri == NULL) || (timeout < 0) || (operation == OP_NONE)) + usage(av[0]); + + if ((strcasecmp(uri->scheme, "lpd") != 0) && + (strcasecmp(uri->scheme, "rfc-1179") != 0)) + usage(av[0]); + + if (operation == OP_SUBMIT) /* get a job-id if we need it */ + if ((c = next_job_id()) < 0) { + syslog(LOG_ERR, "lpd_port:main:next_job_id fails"); + return (-1); + } + + if ((fd = reserved_port()) < 0) { + syslog(LOG_ERR, "reserved_port() failed %m"); + return (errno); + } + + /* + * we no longer want or need any elevated privilege, lose it all + * permanently. + */ + + setreuid(uid, uid); + + + /* connect to the print service */ + if ((fd = sock_connect(fd, uri, timeout)) < 0) + return (errno); + + /* perform the requested operation */ + switch (operation) { + case OP_SUBMIT: /* transfer the job, close the fd */ + if (submit_job(fd, uri, c, av[optind]) < 0) + exit_code = errno; + break; + case OP_QUERY: /* send the query string, return the fd */ + if (query(fd, uri, ac - optind, &av[optind]) < 0) + exit_code = errno; + break; + case OP_CANCEL: /* send the cancel string, return the fd */ + if (cancel(fd, uri, ac - optind, &av[optind]) < 0) + exit_code = errno; + break; + default: /* This should never happen */ + exit_code = EINVAL; + } + + + /* if the operation succeeded, send the fd to our parent */ + if ((exit_code == 0) && (sendfd(1, fd) < 0)) { + char buf[BUFSIZ]; + + exit_code = errno; + + /* sendfd() failed, dump the socket data for the heck of it */ + while ((c = read(fd, buf, sizeof (buf))) > 0) + write(1, buf, c); + } + + syslog(LOG_DEBUG, "exit code: %d", exit_code); + return (exit_code); +} |