| /* |
| * 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. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <libintl.h> |
| #include <sys/systeminfo.h> |
| |
| #include <ctype.h> |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| |
| #include <string.h> |
| #include <syslog.h> |
| #include <errno.h> |
| #include <libintl.h> |
| |
| #include <adaptor.h> |
| |
| #define ACK(fp) { (void) fputc(NULL, fp); (void) fflush(fp); } |
| #define NACK(fp) { (void) fputc('\001', fp); (void) fflush(fp); } |
| |
| |
| /* |
| * This file contains the front-end of the BSD Print Protocol adaptor. This |
| * code assumes a BSD Socket interface to the networking side. |
| */ |
| |
| /* |
| * strsplit() splits a string into a NULL terminated array of substrings |
| * determined by a seperator. The original string is modified, and newly |
| * allocated space is only returned for the array itself. If more than |
| * 1024 substrings exist, they will be ignored. |
| */ |
| static char ** |
| strsplit(char *string, const char *seperators) |
| { |
| char *list[BUFSIZ], |
| **result; |
| int length = 0; |
| |
| if ((string == NULL) || (seperators == NULL)) |
| return (NULL); |
| |
| (void) memset(list, NULL, sizeof (list)); |
| for (list[length] = strtok(string, seperators); |
| (list[length] != NULL) && (length < (BUFSIZ - 2)); |
| list[length] = strtok(NULL, seperators)) |
| length++; |
| |
| if ((result = (char **)calloc(length+1, sizeof (char *))) != NULL) |
| (void) memcpy(result, list, length * sizeof (char *)); |
| |
| return (result); |
| } |
| |
| |
| /* |
| * remote_host_name() gets the hostname of the "peer" on the socket |
| * connection. |
| */ |
| static char * |
| remote_host_name(FILE *fp) |
| { |
| struct hostent *hp; |
| struct sockaddr_in6 peer; |
| socklen_t peer_len = sizeof (peer); |
| int fd = fileno(fp); |
| int error_num; |
| char myname[MAXHOSTNAMELEN], tmp_buf[INET6_ADDRSTRLEN]; |
| char *hostname; |
| |
| /* who is our peer ? */ |
| if (getpeername(fd, (struct sockaddr *)&peer, &peer_len) < 0) { |
| if ((errno != ENOTSOCK) && (errno != EINVAL)) |
| return (NULL); |
| else |
| return (strdup("localhost")); |
| } |
| |
| /* get their name or return a string containing their address */ |
| if ((hp = getipnodebyaddr((const char *)&peer.sin6_addr, |
| sizeof (struct in6_addr), AF_INET6, |
| &error_num)) == NULL) { |
| return (strdup(inet_ntop(peer.sin6_family, |
| &peer.sin6_addr, tmp_buf, sizeof (tmp_buf)))); |
| } |
| |
| /* is it "localhost" ? */ |
| if (strcasecmp(hp->h_name, "localhost") == 0) |
| return (strdup("localhost")); |
| |
| /* duplicate the name because gethostbyXXXX() is not reentrant */ |
| hostname = strdup(hp->h_name); |
| (void) sysinfo(SI_HOSTNAME, myname, sizeof (myname)); |
| |
| /* is it from one of my addresses ? */ |
| if ((hp = getipnodebyname(myname, AF_INET6, AI_ALL|AI_V4MAPPED, |
| &error_num)) != NULL) { |
| struct in6_addr **tmp = (struct in6_addr **)hp->h_addr_list; |
| int i = 0; |
| |
| while (tmp[i] != NULL) { |
| if (memcmp(tmp[i++], &peer.sin6_addr, hp->h_length) |
| == 0) { |
| free(hostname); |
| return (strdup("localhost")); |
| } |
| } |
| } |
| |
| /* It must be someone else */ |
| return (hostname); |
| } |
| |
| static int |
| request_id_no(const char *filename) |
| { |
| int id = -1; |
| |
| /* get the first number embedded in the string */ |
| while ((filename != NULL) && (*filename != NULL) && |
| (isdigit(*filename) == 0)) |
| filename++; |
| |
| if ((filename != NULL) && (*filename != NULL)) |
| id = atoi(filename); |
| |
| return (id); |
| } |
| |
| |
| static void |
| abort_transfer(char **files) |
| { |
| syslog(LOG_DEBUG, "abort_transfer()"); |
| while ((files != NULL) && (*files != NULL)) |
| (void) unlink(*files++); |
| } |
| |
| static int lock_fd = -1; |
| static int lock_job_id = -1; |
| |
| static void |
| unlock_job() |
| { |
| syslog(LOG_DEBUG, "unlock_job(): fd = %d, id = %d\n", lock_fd, |
| lock_job_id); |
| if (lock_fd != -1) { |
| char name[12]; |
| |
| snprintf(name, sizeof (name), "%d", lock_job_id); |
| unlink(name); |
| close(lock_fd); |
| lock_fd = -1; |
| lock_job_id = -1; |
| } |
| } |
| |
| |
| static void |
| lock_job(int job_id) |
| { |
| int try = 5; |
| char name[12]; |
| |
| syslog(LOG_DEBUG, "lock_job(%d): fd = %d, id = %d\n", job_id, lock_fd, |
| lock_job_id); |
| if ((lock_job_id != -1) && (lock_job_id != job_id)) |
| unlock_job(); |
| |
| snprintf(name, sizeof (name), "%d", job_id); |
| while ((try > 0) && (lock_fd < 0)) { |
| if ((lock_fd = open(name, O_CREAT|O_EXCL|O_WRONLY, 0600)) < 0) { |
| if (errno == EEXIST) { |
| sleep(3); |
| lock_fd = open(name, O_WRONLY); |
| } |
| } |
| try--; |
| } |
| |
| if (lockf(lock_fd, F_LOCK, 0) < 0) { |
| syslog(LOG_DEBUG|LOG_INFO, |
| "failed to lock job: %d, file(%s), fd(%d): %m", |
| job_id, name, lock_fd); |
| close(lock_fd); |
| unlink(name); |
| job_id = lock_fd = -1; |
| } |
| |
| lock_job_id = job_id; |
| } |
| |
| |
| /* |
| * transfer_job() retrieves jobs being sent from a remote system and |
| * submits them as they are completely received. |
| */ |
| static int |
| transfer_job(const char *printer, const char *host, FILE *ifp, |
| FILE *ofp) |
| { |
| char buf[BUFSIZ], |
| *tmp; |
| char *cf = NULL; |
| char *df_list[64]; /* don't allow more that 64 files */ |
| int file_no = 0; |
| int current_request = -1; |
| int tmp_id; |
| int psize; |
| |
| |
| (void) memset(&df_list, NULL, sizeof (df_list)); |
| |
| if (adaptor_spooler_accepting_jobs(printer) < 0) { |
| syslog(LOG_DEBUG, |
| "attempt to transfer job(s) to disabled printer" |
| " or unknown printer %s", printer); |
| return (-1); |
| } |
| |
| ACK(ofp); |
| /* start to receive job(s) */ |
| while (fgets(buf, sizeof (buf), ifp) != NULL) { |
| int size = 0; |
| char *name = NULL; |
| char *ptr; |
| int fd; |
| int count; |
| |
| /* |
| * When receiving jobs, buf[0] can only be 1,2 or 3 |
| * Anything else, abort transfer - rfc1179 |
| */ |
| if ((buf[0] < 1 || buf[0] > 3)) { |
| syslog(LOG_ERR, |
| "protocol error - bad message from client"); |
| syslog(LOG_DEBUG, "control file contained: (%s)", |
| (cf ? cf : "NULL")); |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| |
| if (strlen(&buf[1]) == 0) /* just a null */ |
| continue; |
| |
| count = size = atoi(strtok(&buf[1], "\n\t ")); |
| if ((tmp = strtok(NULL, "\n\t ")) != NULL) { |
| if ((name = strrchr(tmp, '/')) != NULL) { |
| /* for security */ |
| syslog(LOG_INFO|LOG_NOTICE, |
| "Attempt to tranfer Absolute path: %s", |
| tmp); |
| name++; |
| } else |
| name = tmp; |
| |
| tmp_id = request_id_no(name); |
| if ((cf != NULL) && (df_list[0] != NULL) && |
| (tmp_id != current_request)) { |
| if (adaptor_submit_job(printer, host, cf, |
| df_list) != 0) { |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| while (file_no-- > 0) |
| free(df_list[file_no]); |
| (void) memset(df_list, NULL, sizeof (df_list)); |
| file_no = 0; |
| free(cf); |
| cf = NULL; |
| } |
| if (tmp_id != current_request) |
| lock_job(tmp_id); |
| |
| } else if (buf[0] != 1) { |
| syslog(LOG_ERR, "Bad xfer message(%d), no file name", |
| buf[0]); |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| current_request = tmp_id; |
| tmp = NULL; |
| |
| switch (buf[0]) { |
| case '\1': /* Abort Transfer */ |
| /* remove files on file_list */ |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| case '\2': /* Transfer Control File */ |
| syslog(LOG_DEBUG, "control(%s, %d)", name, size); |
| if ((cf = malloc(size + 1)) == NULL) { |
| NACK(ofp); |
| break; |
| } |
| (void) memset(cf, NULL, size + 1); |
| ACK(ofp); |
| ptr = cf; |
| while (count > 0) |
| if (((fd = fread(ptr, 1, count, ifp)) == 0) && |
| (feof(ifp) != 0)) { |
| syslog(LOG_DEBUG, |
| "connection closed(%s): %m", |
| name); |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } else { |
| ptr += fd; |
| count -= fd; |
| } |
| if (fgetc(ifp) != 0) { /* get ACK/NACK */ |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| ACK(ofp); |
| break; |
| case '\3': /* Transfer Data File */ |
| syslog(LOG_DEBUG, "data(%s, %d)", name, size); |
| if ((fd = open(name, O_RDWR|O_TRUNC|O_CREAT|O_EXCL, |
| 0640)) < 0) { |
| syslog(LOG_ERR, "open(%s): %m", name); |
| abort_transfer(df_list); |
| unlock_job(); |
| NACK(ofp); |
| return (-1); |
| } |
| if (ftruncate(fd, size) < 0) { |
| syslog(LOG_ERR, "ftruncate(%s): %m", name); |
| (void) close(fd); |
| (void) unlink(name); |
| abort_transfer(df_list); |
| unlock_job(); |
| NACK(ofp); |
| return (-1); |
| } |
| if ((size > 0) && |
| ((tmp = mmap((caddr_t)0, (size_t)size, |
| PROT_READ|PROT_WRITE, |
| (MAP_SHARED | MAP_NORESERVE), |
| fd, (off_t)0)) == (char *)MAP_FAILED)) { |
| syslog(LOG_ERR, "mmap(%d, %d): %m", size, fd); |
| (void) close(fd); |
| (void) unlink(name); |
| abort_transfer(df_list); |
| unlock_job(); |
| NACK(ofp); |
| return (-1); |
| } |
| |
| /* Make sure that there is adequate space */ |
| if ((psize = pwrite(fd, tmp, size, 0)) < size) { |
| syslog(LOG_ERR, |
| "pwrite(,, %d ,) returns:(%d) %m", |
| size, psize); |
| (void) close(fd); |
| (void) unlink(name); |
| (void) munmap(tmp, size); |
| abort_transfer(df_list); |
| unlock_job(); |
| NACK(ofp); |
| return (-1); |
| } |
| (void) close(fd); |
| ACK(ofp); |
| ptr = tmp; |
| while (count > 0) |
| if (((fd = fread(ptr, 1, count, ifp)) == 0) && |
| (feof(ifp) != 0)) { |
| syslog(LOG_DEBUG, |
| "connection closed(%s): %m", |
| name); |
| unlink(name); |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } else { |
| ptr += fd; |
| count -= fd; |
| } |
| (void) munmap(tmp, size); |
| if (fgetc(ifp) != 0) { /* get ACK/NACK */ |
| unlink(name); |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| /* |
| * make sure we don't overflow df_list. |
| */ |
| if (file_no >= sizeof (df_list)/sizeof (df_list[0])) { |
| syslog(LOG_ALERT|LOG_AUTH, |
| "prevented in.lpd exploit from host %s", |
| host); |
| unlink(name); |
| abort_transfer(df_list); |
| return (-1); |
| } |
| |
| df_list[file_no++] = strdup(name); |
| ACK(ofp); |
| break; |
| default: |
| abort_transfer(df_list); |
| unlock_job(); |
| syslog(LOG_ERR, "protocol screwup"); |
| return (-1); |
| } |
| } |
| if ((cf != NULL) && (file_no != 0)) { |
| if (adaptor_submit_job(printer, host, cf, df_list) != 0) { |
| abort_transfer(df_list); |
| unlock_job(); |
| return (-1); |
| } |
| while (file_no-- > 0) |
| free(df_list[file_no]); |
| free(cf); |
| } else |
| abort_transfer(df_list); |
| unlock_job(); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * This is the entry point for this program. The program takes the |
| * following options: |
| * (none) |
| */ |
| int |
| main(int ac, char *av[]) |
| { |
| FILE *ifp = stdin, |
| *ofp = stdout; |
| int c, |
| rc; |
| char buf[BUFSIZ], |
| **args, |
| *host, |
| *dir, |
| *printer, |
| *requestor; |
| |
| |
| openlog("bsd-gw", LOG_PID, LOG_LPR); |
| |
| while ((c = getopt(ac, av, "d")) != EOF) |
| switch (c) { |
| case 'd': |
| default: |
| ; |
| } |
| |
| if (fgets(buf, sizeof (buf), ifp) == NULL) { |
| if (feof(ifp) == 0) |
| syslog(LOG_ERR, "Error reading from connection: %s", |
| strerror(errno)); |
| exit(1); |
| } |
| |
| #ifdef DEBUG |
| if ((buf[0] > '0') && (buf[0] < '6')) |
| buf[0] -= '0'; |
| #endif |
| |
| if ((buf[0] < 1) || (buf[0] > 5)) { |
| syslog(LOG_ERR, "Invalid protocol request (%d): %c%s", |
| buf[0], buf[0], buf); |
| (void) fprintf(ofp, |
| gettext("Invalid protocol request (%d): %c%s\n"), |
| buf[0], buf[0], buf); |
| exit(1); |
| } |
| |
| args = strsplit(&buf[1], "\t\n "); |
| printer = *args++; |
| |
| if (printer == NULL) { |
| syslog(LOG_ERR, "Can't determine requested printer"); |
| (void) fprintf(ofp, |
| gettext("Can't determine requested printer")); |
| exit(1); |
| } |
| |
| if ((host = remote_host_name(ifp)) == NULL) { |
| syslog(LOG_ERR, "Can't determine requesting host"); |
| (void) fprintf(ofp, |
| gettext("Can't determine requesting host\n")); |
| exit(1); |
| } |
| |
| if (adaptor_available(printer) < 0) { |
| if (errno == ENOENT) { |
| syslog(LOG_ERR, |
| "request to %s (unknown printer) from %s", |
| printer, host); |
| (void) fprintf(ofp, |
| gettext("%s: unknown printer\n"), printer); |
| } else { |
| syslog(LOG_ERR, |
| "Can't locate protocol adaptor for %s from %s", |
| printer, host); |
| (void) fprintf(ofp, gettext( |
| "Can't locate protocol adaptor for %s\n"), |
| printer); |
| } |
| exit(1); |
| } |
| |
| if (adaptor_spooler_available(printer) < 0) { |
| syslog(LOG_ERR, "Can't communicate with spooler for %s", |
| printer); |
| (void) fprintf(ofp, |
| gettext("Can't communicate with spooler for %s\n"), |
| printer); |
| exit(1); |
| } |
| |
| if (adaptor_client_access(printer, host, fileno(ifp)) < 0) { |
| syslog(LOG_ERR, "%s doesn't have permission to talk to %s", |
| host, printer); |
| (void) fprintf(ofp, |
| gettext("%s doesn't have permission to talk to %s\n"), |
| host, printer); |
| exit(1); |
| } |
| |
| if ((dir = adaptor_temp_dir(printer, host)) == NULL) { |
| syslog(LOG_DEBUG, "failure to locate tmp dir"); |
| return (1); |
| } |
| |
| if (chdir(dir) < 0) { |
| syslog(LOG_DEBUG, "chdir(%s): %m", dir); |
| return (1); |
| } |
| |
| switch (buf[0]) { |
| case '\1': /* restart printer */ |
| if ((rc = adaptor_restart_printer(printer)) == 0) { |
| ACK(ofp); |
| } else { |
| NACK(ofp); |
| } |
| break; |
| case '\2': /* transfer job(s) */ |
| rc = transfer_job(printer, host, ifp, ofp); |
| break; |
| case '\3': /* show queue (short) */ |
| case '\4': /* show queue (long) */ |
| rc = adaptor_show_queue(printer, ofp, buf[0], args); |
| break; |
| case '\5': /* cancel job(s) */ |
| requestor = *args++; |
| rc = adaptor_cancel_job(printer, ofp, requestor, host, args); |
| break; |
| default: |
| /* NOTREACHED */ |
| /* your system would have to be completely hosed */ |
| syslog(LOG_ERR, "reboot or reinstall your system"); |
| rc = -1; |
| } |
| (void) fflush(ofp); |
| |
| syslog(LOG_DEBUG, |
| "protocol request(%d) for %s completed with status %d", |
| buf[0], printer, rc); |
| exit(0); |
| } |