4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 /* $Id: lpd-port.c 155 2006-04-26 02:34:54Z ktou $ */
30 #include <config-site.h>
35 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
48 #include <stropts.h> /* for sendfd */
49 #include <sys/uio.h> /* for sendmsg stuff */
51 #include <sys/sendfile.h>
58 #define JOB_ID_FILE "/var/run/rfc-1179.seq"
59 #endif /* JOB_ID_FILE */
62 sendfd(int sockfd
, int fd
)
64 syslog(LOG_DEBUG
, "sendfd(%d, %d)", sockfd
, fd
);
66 #if defined(sun) && defined(unix) && defined(I_SENDFD)
67 return (ioctl(sockfd
, I_SENDFD
, fd
));
72 struct cmsghdr cmp
[1];
73 char buf
[2]; /* send/recv 2 byte protocol */
75 iov
[0].iov_base
= buf
;
78 cmp
[0].cmsg_level
= SOL_SOCKET
;
79 cmp
[0].cmsg_type
= SCM_RIGHTS
;
80 cmp
[0].cmsg_len
= sizeof (struct cmsghdr
) + sizeof (int);
81 * (int *)CMSG_DATA(cmp
) = fd
;
85 msg
.msg_control
= cmp
;
86 msg
.msg_controllen
= sizeof (struct cmsghdr
) + sizeof (int);
88 iov
[0].iov_base
= NULL
;
90 msg
.msg_accrights
= (caddr_t
)&fd
;
91 msg
.msg_accrights
= sizeof (fd
);
98 return (sendmsg(sockfd
, &msg
, 0));
108 sock_connect(int sock
, char *host
, int timeout
)
112 #if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
113 struct sockaddr_in6 sin
;
115 struct sockaddr_in sin
;
117 static void (*old_handler
)();
122 * Get the host address and port number to connect to.
128 /* linux style NULL usage */
129 (void) memset((char *)&sin
, (int)NULL
, sizeof (sin
));
131 #if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
132 if ((hp
= getipnodebyname(host
, AF_INET6
, AI_DEFAULT
,
133 &error_num
)) == NULL
) {
137 (void) memcpy((caddr_t
)&sin
.sin6_addr
, hp
->h_addr
, hp
->h_length
);
138 sin
.sin6_family
= hp
->h_addrtype
;
140 if ((hp
= gethostbyname(host
)) == NULL
) {
145 (void) memcpy((caddr_t
)&sin
.sin_addr
, hp
->h_addr
, hp
->h_length
);
146 sin
.sin_family
= hp
->h_addrtype
;
149 if ((sp
= getservbyname("printer", "tcp")) == NULL
) {
154 #if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
155 sin
.sin6_port
= sp
->s_port
;
157 sin
.sin_port
= sp
->s_port
;
161 old_handler
= signal(SIGALRM
, null
);
162 (void) alarm(timeout
);
164 if (connect(sock
, (struct sockaddr
*)&sin
, sizeof (sin
)) < 0) {
166 (void) signal(SIGALRM
, old_handler
);
168 if (errno
== ECONNREFUSED
&& timo
<= 16) {
178 (void) signal(SIGALRM
, old_handler
);
185 int fd
, result
= getpid() % 1000;
187 /* gain back enough privilege to open the id file */
189 if ((priv_set(PRIV_ON
, PRIV_EFFECTIVE
,
190 PRIV_FILE_DAC_READ
, PRIV_FILE_DAC_WRITE
, NULL
)) < 0) {
191 syslog(LOG_ERR
, "lpd_port:next_job_id:priv_set fails: : %m");
198 /* open the sequence file */
199 if (((fd
= open(JOB_ID_FILE
, O_RDWR
)) < 0) && (errno
== ENOENT
))
200 fd
= open(JOB_ID_FILE
, O_CREAT
|O_EXCL
|O_RDWR
, 0644);
202 syslog(LOG_DEBUG
, "sequence file fd: %d", fd
);
204 /* drop our privilege again */
206 /* drop file access privilege */
207 priv_set(PRIV_OFF
, PRIV_PERMITTED
,
208 PRIV_FILE_DAC_READ
, PRIV_FILE_DAC_WRITE
, NULL
);
214 /* wait for a lock on the file */
215 if (lockf(fd
, F_LOCK
, 0) == 0) {
219 /* get the current id */
220 (void) memset(buf
, 0, sizeof (buf
));
221 if (read(fd
, buf
, sizeof (buf
)) > 0)
224 next
= ((result
< 999) ? (result
+ 1) : 0);
226 /* store the next id in the file */
227 snprintf(buf
, sizeof (buf
), "%.3d", next
);
228 if ((lseek(fd
, 0, SEEK_SET
) == 0) &&
229 (ftruncate(fd
, 0) == 0))
230 write(fd
, buf
, strlen(buf
));
234 syslog(LOG_DEBUG
, "next_job_id() is %d", result
);
245 /* gain back enough privilege to open a reserved port */
248 PRIV_ON
, PRIV_EFFECTIVE
, PRIV_NET_PRIVADDR
, NULL
)) != 0) {
249 syslog(LOG_ERR
, "priv_set fails for net_privaddr %m");
256 #if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
257 port
= 0; /* set to 0, rresvport_af() will find us one. */
258 result
= rresvport_af(&port
, AF_INET6
);
260 port
= IPPORT_RESERVED
- 1;
261 while (((result
= rresvport(&port
)) < 0) && (port
>= 0))
265 /* drop our privilege again */
267 priv_set(PRIV_OFF
, PRIV_PERMITTED
, PRIV_NET_PRIVADDR
, NULL
);
278 static struct passwd
*p
= NULL
;
280 if ((p
= getpwuid(getuid())) != NULL
)
287 add_args(int ac
, char **av
, char *buf
, size_t len
)
290 strlcat(buf
, " ", len
);
291 strlcat(buf
, *(av
++), len
);
296 massage_control_data(char *data
, int id
)
298 char *line
, *iter
= NULL
;
299 char *ptr
, *mod_ptr
, *datacpy
;
301 int host_present
= 0;
303 if (gethostname(host
, sizeof (host
)) != 0)
306 if ((datacpy
= strdup(data
)) == NULL
) {
310 for (ptr
= strtok_r(datacpy
, "\n", &iter
); ptr
!= NULL
;
311 ptr
= strtok_r(NULL
, "\n", &iter
)) {
314 if (strncmp(++ptr
, host
, strlen(host
)) != 0) {
319 } else if ((ptr
[0] == 'P') || (ptr
[0] == 'L')) {
320 /* check the user name */
321 uid_t uid
= getuid();
325 if (uid
== 0) { /* let root do what they want */
328 if ((pw
= getpwuid(uid
)) == NULL
) {
330 return (-1); /* failed */
332 len
= strlen(pw
->pw_name
);
333 if ((strncmp(++ptr
, pw
->pw_name
, len
) != 0)) {
335 return (-1); /* failed */
337 } else if ((islower(ptr
[0]) != 0) || (ptr
[0] == 'U')) {
338 /* check/fix df?XXXhostname */
341 if (strlen(ptr
) < 6) {
347 * As ptr is a copy of the string (df?XXX...) the code
348 * needs to work on the original, hence the need for
349 * mod_ptr. No need to check for a NULL mod_ptr
350 * because the required string must already exist as
351 * ptr is a copy of the original data.
354 mod_ptr
= strstr(data
, ptr
);
356 if ((mod_ptr
[0] == 'd') && (mod_ptr
[1] == 'f') &&
357 (mod_ptr
[3] == 'X') && (mod_ptr
[4] == 'X') &&
358 (mod_ptr
[5] == 'X')) {
359 mod_ptr
[3] = '0' + (id
/ 100) % 10;
360 mod_ptr
[4] = '0' + (id
/ 10) % 10;
361 mod_ptr
[5] = '0' + id
% 10;
363 if (strncmp(&mod_ptr
[6], host
, strlen(host
))
385 send_lpd_message(int fd
, char *fmt
, ...)
392 size
= vsnprintf(buf
, sizeof (buf
), fmt
, ap
);
397 syslog(LOG_DEBUG
, "lpd_messsage(%d, %s)", fd
, buf
);
399 if (write(fd
, buf
, size
) != size
) {
404 if ((read(fd
, buf
, 1) != 1) || (buf
[0] != 0))
411 send_data_file(int sock
, char *dfname
, char *name
)
419 if (strcmp(name
, "standard input") != 0) {
420 if ((fd
= open(name
, O_RDONLY
)) < 0)
423 if (fstat(fd
, &st
) < 0)
426 st
.st_size
= MAXINT
; /* should be 0 */
428 /* request data file transfer, read ack/nack */
430 if (send_lpd_message(sock
, "\003%d %s\n", st
.st_size
, dfname
) < 0)
435 if (sendfile(sock
, fd
, &off
, st
.st_size
) != st
.st_size
)
439 /* request ack/nack after the data transfer */
441 if (send_lpd_message(sock
, "") < 0)
449 send_control_file(int sock
, char *data
, int id
)
453 char *ptr
, *iter
= NULL
;
454 char *datacpy
= NULL
;
459 if ((datacpy
= strdup(data
)) == NULL
)
462 syslog(LOG_DEBUG
, "cfA: %s\n", datacpy
);
464 for (ptr
= strtok_r(datacpy
, "\n", &iter
); ptr
!= NULL
;
465 ptr
= strtok_r(NULL
, "\n", &iter
)) {
472 syslog(LOG_DEBUG
, "hostname: %s\n", host
);
477 /* request data file transfer, read ack/nack */
479 if (send_lpd_message(sock
, "\002%d cfA%.3d%s\n", len
, id
, host
) < 0)
483 if (write(sock
, data
, len
) != len
)
486 /* request ack/nack after the data transfer */
488 if (send_lpd_message(sock
, "") < 0)
496 submit_job(int sock
, char *printer
, int job_id
, char *path
)
501 char *metadata
= NULL
;
502 char *ptr
, *iter
= NULL
;
508 /* open the control file */
509 if ((fd
= open(path
, O_RDONLY
)) < 0) {
510 syslog(LOG_ERR
, "submit_job(%d, %s, %d, %s): open(): %m",
511 sock
, printer
, job_id
, path
);
515 /* get the size of the control file */
516 if (fstat(fd
, &st
) < 0) {
517 syslog(LOG_ERR
, "submit_job(%d, %s, %d, %s): fstat(): %m",
518 sock
, printer
, job_id
, path
);
523 /* allocate memory for the control file */
524 if ((metadata
= calloc(1, st
.st_size
+ 1)) == NULL
) {
525 syslog(LOG_ERR
, "submit_job(%d, %s, %d, %s): calloc(): %m",
526 sock
, printer
, job_id
, path
);
531 /* read in the control file */
532 if (read(fd
, metadata
, st
.st_size
) != st
.st_size
) {
533 syslog(LOG_ERR
, "submit_job(%d, %s, %d, %s): read(): %m",
534 sock
, printer
, job_id
, path
);
540 /* massage the control file */
541 if (massage_control_data(metadata
, job_id
) < 0) {
542 /* bad control data, dump the job */
544 "bad control file, possible subversion attempt");
551 /* request to transfer the job */
552 if (send_lpd_message(sock
, "\002%s\n", printer
) < 0) {
553 /* no such (or disabled) queue, got to love rfc-1179 */
558 /* send the control data */
559 if (send_control_file(sock
, metadata
, job_id
) < 0) {
561 write(sock
, "\001\n", 2); /* abort */
566 /* walk the control file sending the data files */
567 for (ptr
= strtok_r(metadata
, "\n", &iter
); ptr
!= NULL
;
568 ptr
= strtok_r(NULL
, "\n", &iter
)) {
574 name
= strtok_r(NULL
, "\n", &iter
);
581 if (send_data_file(sock
, ptr
, name
) < 0) {
583 write(sock
, "\001\n", 2); /* abort */
587 if (strcmp(name
, "standard input") != 0)
591 /* write back the job-id */
593 if ((fd
= open(path
, O_WRONLY
)) >= 0) {
595 write(fd
, &job_id
, sizeof (job_id
));
600 if (sent_files
!= 0) {
609 query(int fd
, char *printer
, int ac
, char **av
)
614 /* build the request */
615 snprintf(buf
, sizeof (buf
), "\04%s", printer
);
616 add_args(ac
, av
, buf
, sizeof (buf
));
617 strlcat(buf
, "\n", sizeof (buf
));
620 if (((rc
= write(fd
, buf
, len
)) >= 0) && (rc
!= len
)) {
630 cancel(int fd
, char *printer
, int ac
, char **av
)
635 /* build the request */
636 snprintf(buf
, sizeof (buf
), "\05%s %s", printer
, get_user_name());
637 add_args(ac
, av
, buf
, sizeof (buf
));
638 strlcat(buf
, "\n", sizeof (buf
));
641 if (((rc
= write(fd
, buf
, len
)) >= 0) && (rc
!= len
)) {
655 setreuid(getuid(), getuid());
657 if ((name
= strrchr(program
, '/')) == NULL
)
662 fprintf(stderr
, "usage:\t%s -H host [-t timeout] -s queue control ]\n",
664 fprintf(stderr
, "\t%s -H host [-t timeout] -c queue [user|job ...]\n",
666 fprintf(stderr
, "\t%s -H host [-t timeout] -q queue [user|job ...]\n",
672 * The main program temporarily loses privilege while searching the command
673 * line arguments. It then allocates any resources it need privilege for
674 * job-id, reserved port. Once it has the resources it needs, it perminently
675 * drops all elevated privilege. It ghen connects to the remote print service
676 * based on destination hostname. Doing it this way reduces the potenential
677 * opportunity for a breakout with elevated privilege, breakout with an
678 * unconnected reserved port, and exploitation of the remote print service
679 * by a calling program.
682 main(int ac
, char *av
[])
684 enum { OP_NONE
, OP_SUBMIT
, OP_QUERY
, OP_CANCEL
} operation
= OP_NONE
;
685 int fd
, c
, timeout
= 0, exit_code
= 0;
686 char *host
= NULL
, *queue
= NULL
;
687 uid_t uid
= getuid();
692 openlog("lpd-port", LOG_PID
, LOG_LPR
);
696 /* lose as much as we can perminently and temporarily drop the rest. */
698 if ((saveset
= priv_allocset()) == NULL
) {
699 syslog(LOG_ERR
, "lpd_port: priv_allocset saveset failed: %m\n");
703 priv_basicset(saveset
);
704 (void) priv_addset(saveset
, PRIV_NET_PRIVADDR
);
705 (void) priv_addset(saveset
, PRIV_FILE_DAC_READ
);
706 (void) priv_addset(saveset
, PRIV_FILE_DAC_WRITE
);
708 if ((setppriv(PRIV_SET
, PRIV_PERMITTED
, saveset
)) < 0) {
709 syslog(LOG_ERR
, "lpd_port:setppriv:priv_set failed: %m");
713 priv_freeset(saveset
);
716 * These privileges permanently dropped in next_job_id() and
720 if (priv_set(PRIV_OFF
, PRIV_EFFECTIVE
, PRIV_NET_PRIVADDR
,
721 PRIV_FILE_DAC_READ
, PRIV_FILE_DAC_WRITE
, (char *)NULL
) < 0) {
722 syslog(LOG_ERR
, "lpd_port:priv_set:priv_off failed: %m");
726 syslog(LOG_DEBUG
, "using privs");
729 syslog(LOG_DEBUG
, "no privs");
733 while ((c
= getopt(ac
, av
, "H:t:c:q:s:")) != EOF
) {
739 timeout
= atoi(optarg
);
742 if (operation
!= OP_NONE
)
744 operation
= OP_CANCEL
;
748 if (operation
!= OP_NONE
)
750 operation
= OP_QUERY
;
754 if (operation
!= OP_NONE
)
756 operation
= OP_SUBMIT
;
761 /* does not return */
765 if ((host
== NULL
) || (queue
== NULL
) || (timeout
< 0) ||
766 (operation
== OP_NONE
))
769 if (operation
== OP_SUBMIT
) /* get a job-id if we need it */
770 if ((c
= next_job_id()) < 0) {
771 syslog(LOG_ERR
, "lpd_port:main:next_job_id fails");
775 if ((fd
= reserved_port()) < 0) {
776 syslog(LOG_ERR
, "reserved_port() failed %m");
781 * we no longer want or need any elevated privilege, lose it all
787 /* connect to the print service */
788 if ((fd
= sock_connect(fd
, host
, timeout
)) < 0)
791 /* perform the requested operation */
793 case OP_SUBMIT
: /* transfer the job, close the fd */
794 if (submit_job(fd
, queue
, c
, av
[optind
]) < 0)
797 case OP_QUERY
: /* send the query string, return the fd */
798 if (query(fd
, queue
, ac
- optind
, &av
[optind
]) < 0)
801 case OP_CANCEL
: /* send the cancel string, return the fd */
802 if (cancel(fd
, queue
, ac
- optind
, &av
[optind
]) < 0)
805 default: /* This should never happen */
810 /* if the operation succeeded, send the fd to our parent */
811 if ((exit_code
== 0) && (sendfd(1, fd
) < 0)) {
816 /* sendfd() failed, dump the socket data for the heck of it */
817 while ((c
= read(fd
, buf
, sizeof (buf
))) > 0)
821 syslog(LOG_DEBUG
, "exit code: %d", exit_code
);