27
27
#include <stddef.h>
28
28
#include <limits.h>
29
29
30
- #include <sys/socket.h>
31
30
#include <sys/types.h>
31
+ #include <sys/socket.h>
32
32
#include <sys/stat.h>
33
33
#include <sys/un.h>
34
34
#include <sys/wait.h>
35
+ #include <netdb.h>
35
36
#include <unistd.h>
36
37
#include <fcntl.h>
37
38
#include "qrexec.h"
@@ -206,7 +207,7 @@ static int qubes_connect(int s, const char *connect_path, const size_t total_pat
206
207
}
207
208
208
209
static int execute_qrexec_service (
209
- const struct qrexec_parsed_command * cmd ,
210
+ struct qrexec_parsed_command * cmd ,
210
211
int * pid , int * stdin_fd , int * stdout_fd , int * stderr_fd ,
211
212
struct buffer * stdin_buffer );
212
213
@@ -405,7 +406,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
405
406
/* Parse service name ("qubes.Service") */
406
407
407
408
const char * const plus = memchr (start , '+' , descriptor_len );
408
- size_t const name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
409
+ size_t name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
409
410
if (name_len > NAME_MAX ) {
410
411
LOG (ERROR , "Service name too long to execute (length %zu)" , name_len );
411
412
goto err ;
@@ -421,6 +422,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
421
422
}
422
423
memcpy (cmd -> service_name , start , name_len );
423
424
cmd -> service_name [name_len ] = '\0' ;
425
+ cmd -> arg = plus != NULL ? plus + 1 : NULL ;
424
426
425
427
/* If there is no service argument, add a trailing "+" to the descriptor */
426
428
size_t const descriptor_buffer_size = descriptor_len + 1 + (plus == NULL );
@@ -488,7 +490,7 @@ int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
488
490
}
489
491
490
492
int execute_parsed_qubes_rpc_command (
491
- const struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
493
+ struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
492
494
int * stdout_fd , int * stderr_fd , struct buffer * stdin_buffer ) {
493
495
if (cmd -> service_descriptor ) {
494
496
// Proper Qubes RPC call
@@ -501,8 +503,145 @@ int execute_parsed_qubes_rpc_command(
501
503
}
502
504
}
503
505
506
+ static int qubes_tcp_connect (const char * host , const char * port )
507
+ {
508
+ // Work around a glibc bug: overly-large port numbers not rejected
509
+ {
510
+ if (* port < '1' || * port > '9' )
511
+ goto invalid_port ;
512
+ const char * end = port ;
513
+ errno = 0 ;
514
+ long r = strtol (port , (char * * )& end , 10 );
515
+ if (r < 1 || r > 65535 || * end != 0 || errno )
516
+ goto invalid_port ;
517
+ }
518
+ /* If there is ':' or '%' in the host, then this must be an IPv6 address, not
519
+ * a hostname. */
520
+ bool const must_be_ipv6_addr = strchr (host , ':' ) != NULL || strchr (host , '%' ) != NULL ;
521
+ LOG (DEBUG , "Connecting to %s%s%s:%s" ,
522
+ must_be_ipv6_addr ? "[" : "" ,
523
+ host ,
524
+ must_be_ipv6_addr ? "]" : "" ,
525
+ port );
526
+ struct addrinfo hints = {
527
+ .ai_flags = AI_NUMERICSERV | (must_be_ipv6_addr ? AI_NUMERICHOST : 0 ),
528
+ .ai_family = must_be_ipv6_addr ? AF_INET6 : AF_UNSPEC ,
529
+ .ai_socktype = SOCK_STREAM ,
530
+ .ai_protocol = IPPROTO_TCP ,
531
+ }, * addrs ;
532
+ int rc = getaddrinfo (host , port , & hints , & addrs );
533
+ if (rc != 0 ) {
534
+ /* data comes from symlink or from qrexec service argument, which has already
535
+ * been sanitized */
536
+ LOG (ERROR , "getaddrinfo(%s, %s) failed: %s" , host , port , gai_strerror (rc ));
537
+ return -1 ;
538
+ }
539
+ rc = -1 ;
540
+ size_t addresses = 1 , used_addresses = 0 ;
541
+ assert (addrs != NULL && "getaddrinfo() returned zero addresses" );
542
+ for (struct addrinfo * p = addrs -> ai_next ; p != NULL ; p = p -> ai_next ) {
543
+ addresses ++ ;
544
+ }
545
+ struct pollfd * fds = calloc (addresses , sizeof (struct pollfd ));
546
+ if (fds == NULL ) {
547
+ LOG (ERROR , "Out of memory allocating %zu pollfds!" , addresses );
548
+ goto freeaddrs ;
549
+ }
550
+ for (struct addrinfo * p = addrs ; p != NULL ; p = p -> ai_next ) {
551
+ switch (p -> ai_family ) {
552
+ case AF_INET :
553
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in ));
554
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in * )p -> ai_addr )-> sin_port ));
555
+ break ;
556
+ case AF_INET6 :
557
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in6 ));
558
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in6 * )p -> ai_addr )-> sin6_port ));
559
+ break ;
560
+ default :
561
+ LOG (ERROR , "Unknown socket family" );
562
+ break ;
563
+ }
564
+ int sockfd = socket (p -> ai_family ,
565
+ p -> ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK ,
566
+ p -> ai_protocol );
567
+ if (sockfd < 0 ) {
568
+ LOG (ERROR , "Cannot create socket, skipping" );
569
+ continue ;
570
+ }
571
+ int res = connect (sockfd , p -> ai_addr , p -> ai_addrlen );
572
+ if (res == 0 ) {
573
+ rc = sockfd ;
574
+ goto done ;
575
+ }
576
+ if (errno != EINPROGRESS ) {
577
+ PERROR ("connect" );
578
+ close (sockfd );
579
+ continue ;
580
+ }
581
+ fds [used_addresses ].fd = sockfd ;
582
+ fds [used_addresses ].events = POLLIN |POLLOUT |POLLPRI |POLLRDHUP ;
583
+ used_addresses ++ ;
584
+ }
585
+ /* FIXME: USE EPOLL!!!!!!! */
586
+ while (used_addresses != 0 ) {
587
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
588
+ fds [i ].revents = 0 ;
589
+ }
590
+ int res = poll (fds , used_addresses , 1000 /* 1 second */ );
591
+ if (res > 0 ) {
592
+ struct pollfd * p = fds ;
593
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
594
+ if (fds [i ].revents == 0 )
595
+ * p ++ = fds [i ];
596
+ else if (fds [i ].revents & ~(short )(POLLIN | POLLOUT )) {
597
+ LOG (ERROR , "FD %d (offset %zu) had events 0%s%s%s%s%s%s" , fds [i ].fd , i ,
598
+ (fds [i ].revents & POLLIN ) ? " | POLLIN" : "" ,
599
+ (fds [i ].revents & POLLOUT ) ? " | POLLOUT" : "" ,
600
+ (fds [i ].revents & POLLPRI ) ? " | POLLPRI" : "" ,
601
+ (fds [i ].revents & POLLERR ) ? " | POLLERR" : "" ,
602
+ (fds [i ].revents & POLLHUP ) ? " | POLLHUP" : "" ,
603
+ (fds [i ].revents & POLLNVAL ) ? " | POLLNVAL" : "" );
604
+ close (fds [i ].fd );
605
+ fds [i ].fd = -1 ;
606
+ } else {
607
+ rc = fds [i ].fd ;
608
+ fds [i ].fd = -1 ;
609
+ goto done ;
610
+ }
611
+ }
612
+ used_addresses = (size_t )(p - fds );
613
+ } else if (res == 0 ) {
614
+ LOG (ERROR , "TCP connection timeout" );
615
+ break ;
616
+ } else if (errno == EINTR || errno == EAGAIN ) {
617
+ continue ;
618
+ } else {
619
+ PERROR ("poll" );
620
+ break ;
621
+ }
622
+ }
623
+ done :
624
+ if (rc < 0 )
625
+ LOG (ERROR , "None of %zu TCP sockets could connect" , addresses );
626
+ else
627
+ LOG (DEBUG , "Connection succeeded" );
628
+ /* Close all FDs but the chosen one */
629
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
630
+ if (fds [i ].fd != -1 ) {
631
+ close (fds [i ].fd );
632
+ }
633
+ }
634
+ free (fds );
635
+ freeaddrs :
636
+ freeaddrinfo (addrs );
637
+ return rc ;
638
+ invalid_port :
639
+ LOG (ERROR , "Invalid port number %s" , port );
640
+ return -1 ;
641
+ }
642
+
504
643
static int execute_qrexec_service (
505
- const struct qrexec_parsed_command * cmd ,
644
+ struct qrexec_parsed_command * cmd ,
506
645
int * pid , int * stdin_fd , int * stdout_fd , int * stderr_fd ,
507
646
struct buffer * stdin_buffer ) {
508
647
@@ -528,10 +667,11 @@ static int execute_qrexec_service(
528
667
return -1 ;
529
668
}
530
669
670
+ const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
531
671
if (S_ISSOCK (statbuf .st_mode )) {
532
672
/* Socket-based service. */
533
673
int s ;
534
- if ((s = socket (AF_UNIX , SOCK_STREAM , 0 )) == -1 ) {
674
+ if ((s = socket (AF_UNIX , SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK , 0 )) == -1 ) {
535
675
PERROR ("socket" );
536
676
return -1 ;
537
677
}
@@ -545,14 +685,60 @@ static int execute_qrexec_service(
545
685
if (stderr_fd )
546
686
* stderr_fd = -1 ;
547
687
* pid = 0 ;
548
- set_nonblock (s );
549
688
550
689
if (cmd -> send_service_descriptor ) {
551
690
/* send part after "QUBESRPC ", including trailing NUL */
552
- const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
553
691
buffer_append (stdin_buffer , desc , strlen (desc ) + 1 );
554
692
}
555
693
return 0 ;
694
+ } else if (S_ISLNK (statbuf .st_mode )) {
695
+ if (stderr_fd )
696
+ * stderr_fd = -1 ;
697
+ * pid = 0 ;
698
+ /* TCP-based service */
699
+ assert (memcmp (service_full_path , "/dev/tcp/" , sizeof "/dev/tcp" ) == 0 );
700
+ char * address = service_full_path + sizeof "/dev/tcp" ;
701
+ char * slash = strchr (address , '/' );
702
+ char * host , * port ;
703
+ if (slash == NULL ) {
704
+ if (cmd -> arg == NULL || * cmd -> arg == ' ' ) {
705
+ LOG (ERROR , "No or empty argument provided, cannot connect to %s" ,
706
+ service_full_path );
707
+ return -1 ;
708
+ }
709
+ char * ptr = cmd -> service_descriptor + (cmd -> arg - desc );
710
+ if (* address == '\0' ) {
711
+ /* Get both host and port from service arguments */
712
+ host = ptr ;
713
+ port = strrchr (ptr , '+' );
714
+ if (port == NULL ) {
715
+ LOG (ERROR , "No host provided, cannot connect at %s" , service_full_path );
716
+ return -1 ;
717
+ }
718
+ * port = '\0' ;
719
+ for (char * p = host ; p < port ; ++ p ) {
720
+ if (* p == '_' )
721
+ * p = ':' ;
722
+ else if (* p == '+' )
723
+ * p = '%' ;
724
+ }
725
+ port ++ ;
726
+ } else {
727
+ /* Get just port from service arguments */
728
+ host = address ;
729
+ port = ptr ;
730
+ }
731
+ } else {
732
+ * slash = '\0' ;
733
+ host = address ;
734
+ port = slash + 1 ;
735
+ }
736
+ int res = qubes_tcp_connect (host , port );
737
+ if (res == -1 )
738
+ return -1 ;
739
+ * stdin_fd = * stdout_fd = res ;
740
+ cmd -> send_service_descriptor = false;
741
+ return 0 ;
556
742
}
557
743
558
744
if (euidaccess (service_full_path , X_OK ) == 0 ) {
0 commit comments