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
@@ -418,7 +419,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
418
419
/* Parse service name ("qubes.Service") */
419
420
420
421
const char * const plus = memchr (start , '+' , descriptor_len );
421
- size_t const name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
422
+ size_t name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
422
423
if (name_len > NAME_MAX ) {
423
424
LOG (ERROR , "Service name too long to execute (length %zu)" , name_len );
424
425
goto err ;
@@ -430,6 +431,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
430
431
cmd -> service_name = memdupnul (start , name_len );
431
432
if (!cmd -> service_name )
432
433
goto err ;
434
+ cmd -> arg = plus != NULL ? plus + 1 : NULL ;
433
435
434
436
/* If there is no service argument, add a trailing "+" to the descriptor */
435
437
cmd -> service_descriptor = memdupnul (start , descriptor_len + (plus == NULL ));
@@ -487,7 +489,7 @@ int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
487
489
}
488
490
489
491
int execute_parsed_qubes_rpc_command (
490
- const struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
492
+ struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
491
493
int * stdout_fd , int * stderr_fd , struct buffer * stdin_buffer ) {
492
494
if (cmd -> service_descriptor ) {
493
495
// Proper Qubes RPC call
@@ -499,9 +501,156 @@ int execute_parsed_qubes_rpc_command(
499
501
pid , stdin_fd , stdout_fd , stderr_fd );
500
502
}
501
503
}
504
+ static bool validate_port (const char * port ) {
505
+ #define MAXPORT "65535"
506
+ #define MAXPORTLEN (sizeof MAXPORT - 1)
507
+ if (* port < '1' || * port > '9' )
508
+ return false;
509
+ const char * p = port + 1 ;
510
+ for (; * p != 0 ; ++ p ) {
511
+ if (* p < '0' || * p > '9' )
512
+ return false;
513
+ }
514
+ if (p - port > (ptrdiff_t )MAXPORTLEN )
515
+ return false;
516
+ if (p - port < (ptrdiff_t )MAXPORTLEN )
517
+ return true;
518
+ return memcmp (port , MAXPORT , MAXPORTLEN ) <= 0 ;
519
+ #undef MAXPORT
520
+ #undef MAXPORTLEN
521
+ }
522
+
523
+ static int qubes_tcp_connect (const char * host , const char * port )
524
+ {
525
+ // Work around a glibc bug: overly-large port numbers not rejected
526
+ if (!validate_port (port )) {
527
+ LOG (ERROR , "Invalid port number %s" , port );
528
+ return -1 ;
529
+ }
530
+ /* If there is ':' or '%' in the host, then this must be an IPv6 address, not
531
+ * a hostname. */
532
+ bool const must_be_ipv6_addr = strchr (host , ':' ) != NULL || strchr (host , '%' ) != NULL ;
533
+ LOG (DEBUG , "Connecting to %s%s%s:%s" ,
534
+ must_be_ipv6_addr ? "[" : "" ,
535
+ host ,
536
+ must_be_ipv6_addr ? "]" : "" ,
537
+ port );
538
+ struct addrinfo hints = {
539
+ .ai_flags = AI_NUMERICSERV | (must_be_ipv6_addr ? AI_NUMERICHOST : 0 ),
540
+ .ai_family = must_be_ipv6_addr ? AF_INET6 : AF_UNSPEC ,
541
+ .ai_socktype = SOCK_STREAM ,
542
+ .ai_protocol = IPPROTO_TCP ,
543
+ }, * addrs ;
544
+ int rc = getaddrinfo (host , port , & hints , & addrs );
545
+ if (rc != 0 ) {
546
+ /* data comes from symlink or from qrexec service argument, which has already
547
+ * been sanitized */
548
+ LOG (ERROR , "getaddrinfo(%s, %s) failed: %s" , host , port , gai_strerror (rc ));
549
+ return -1 ;
550
+ }
551
+ rc = -1 ;
552
+ size_t addresses = 1 , used_addresses = 0 ;
553
+ assert (addrs != NULL && "getaddrinfo() returned zero addresses" );
554
+ for (struct addrinfo * p = addrs -> ai_next ; p != NULL ; p = p -> ai_next ) {
555
+ addresses ++ ;
556
+ }
557
+ struct pollfd * fds = calloc (addresses , sizeof (struct pollfd ));
558
+ if (fds == NULL ) {
559
+ LOG (ERROR , "Out of memory allocating %zu pollfds!" , addresses );
560
+ goto freeaddrs ;
561
+ }
562
+ for (struct addrinfo * p = addrs ; p != NULL ; p = p -> ai_next ) {
563
+ switch (p -> ai_family ) {
564
+ case AF_INET :
565
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in ));
566
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in * )p -> ai_addr )-> sin_port ));
567
+ break ;
568
+ case AF_INET6 :
569
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in6 ));
570
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in6 * )p -> ai_addr )-> sin6_port ));
571
+ break ;
572
+ default :
573
+ LOG (ERROR , "Unknown socket family" );
574
+ break ;
575
+ }
576
+ int sockfd = socket (p -> ai_family ,
577
+ p -> ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK ,
578
+ p -> ai_protocol );
579
+ if (sockfd < 0 ) {
580
+ LOG (ERROR , "Cannot create socket, skipping" );
581
+ continue ;
582
+ }
583
+ int res = connect (sockfd , p -> ai_addr , p -> ai_addrlen );
584
+ if (res == 0 ) {
585
+ rc = sockfd ;
586
+ goto done ;
587
+ }
588
+ if (errno != EINPROGRESS ) {
589
+ PERROR ("connect" );
590
+ close (sockfd );
591
+ continue ;
592
+ }
593
+ fds [used_addresses ].fd = sockfd ;
594
+ fds [used_addresses ].events = POLLIN |POLLOUT |POLLPRI |POLLRDHUP ;
595
+ used_addresses ++ ;
596
+ }
597
+ /* FIXME: USE EPOLL!!!!!!! */
598
+ while (used_addresses != 0 ) {
599
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
600
+ fds [i ].revents = 0 ;
601
+ }
602
+ int res = poll (fds , used_addresses , 1000 /* 1 second */ );
603
+ if (res > 0 ) {
604
+ struct pollfd * p = fds ;
605
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
606
+ if (fds [i ].revents == 0 )
607
+ * p ++ = fds [i ];
608
+ else if (fds [i ].revents & ~(short )(POLLIN | POLLOUT )) {
609
+ LOG (ERROR , "FD %d (offset %zu) had events 0%s%s%s%s%s%s" , fds [i ].fd , i ,
610
+ (fds [i ].revents & POLLIN ) ? " | POLLIN" : "" ,
611
+ (fds [i ].revents & POLLOUT ) ? " | POLLOUT" : "" ,
612
+ (fds [i ].revents & POLLPRI ) ? " | POLLPRI" : "" ,
613
+ (fds [i ].revents & POLLERR ) ? " | POLLERR" : "" ,
614
+ (fds [i ].revents & POLLHUP ) ? " | POLLHUP" : "" ,
615
+ (fds [i ].revents & POLLNVAL ) ? " | POLLNVAL" : "" );
616
+ close (fds [i ].fd );
617
+ fds [i ].fd = -1 ;
618
+ } else {
619
+ rc = fds [i ].fd ;
620
+ fds [i ].fd = -1 ;
621
+ goto done ;
622
+ }
623
+ }
624
+ used_addresses = (size_t )(p - fds );
625
+ } else if (res == 0 ) {
626
+ LOG (ERROR , "TCP connection timeout" );
627
+ break ;
628
+ } else if (errno == EINTR || errno == EAGAIN ) {
629
+ continue ;
630
+ } else {
631
+ PERROR ("poll" );
632
+ break ;
633
+ }
634
+ }
635
+ done :
636
+ if (rc < 0 )
637
+ LOG (ERROR , "None of %zu TCP sockets could connect" , addresses );
638
+ else
639
+ LOG (DEBUG , "Connection succeeded" );
640
+ /* Close all FDs but the chosen one */
641
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
642
+ if (fds [i ].fd != -1 ) {
643
+ close (fds [i ].fd );
644
+ }
645
+ }
646
+ free (fds );
647
+ freeaddrs :
648
+ freeaddrinfo (addrs );
649
+ return rc ;
650
+ }
502
651
503
652
static int execute_qrexec_service (
504
- const struct qrexec_parsed_command * cmd ,
653
+ struct qrexec_parsed_command * cmd ,
505
654
int * pid , int * stdin_fd , int * stdout_fd , int * stderr_fd ,
506
655
struct buffer * stdin_buffer ) {
507
656
@@ -527,10 +676,11 @@ static int execute_qrexec_service(
527
676
return -1 ;
528
677
}
529
678
679
+ const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
530
680
if (S_ISSOCK (statbuf .st_mode )) {
531
681
/* Socket-based service. */
532
682
int s ;
533
- if ((s = socket (AF_UNIX , SOCK_STREAM , 0 )) == -1 ) {
683
+ if ((s = socket (AF_UNIX , SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK , 0 )) == -1 ) {
534
684
PERROR ("socket" );
535
685
return -1 ;
536
686
}
@@ -544,14 +694,62 @@ static int execute_qrexec_service(
544
694
if (stderr_fd )
545
695
* stderr_fd = -1 ;
546
696
* pid = 0 ;
547
- set_nonblock (s );
548
697
549
698
if (cmd -> send_service_descriptor ) {
550
699
/* send part after "QUBESRPC ", including trailing NUL */
551
- const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
552
700
buffer_append (stdin_buffer , desc , strlen (desc ) + 1 );
553
701
}
554
702
return 0 ;
703
+ } else if (S_ISLNK (statbuf .st_mode )) {
704
+ if (stderr_fd )
705
+ * stderr_fd = -1 ;
706
+ * pid = 0 ;
707
+ /* TCP-based service */
708
+ assert (memcmp (service_full_path , "/dev/tcp/" , sizeof "/dev/tcp" ) == 0 );
709
+ char * address = service_full_path + sizeof "/dev/tcp" ;
710
+ char * slash = strchr (address , '/' );
711
+ char * host , * port ;
712
+ if (slash == NULL ) {
713
+ if (cmd -> arg == NULL || * cmd -> arg == ' ' ) {
714
+ LOG (ERROR , "No or empty argument provided, cannot connect to %s" ,
715
+ service_full_path );
716
+ return -1 ;
717
+ }
718
+ char * ptr = cmd -> service_descriptor + (cmd -> arg - desc );
719
+ if (* address == '\0' ) {
720
+ /* Get both host and port from service arguments */
721
+ host = ptr ;
722
+ port = strrchr (ptr , '+' );
723
+ if (port == NULL ) {
724
+ LOG (ERROR , "No host provided, cannot connect at %s" , service_full_path );
725
+ return -1 ;
726
+ }
727
+ * port = '\0' ;
728
+ for (char * p = host ; p < port ; ++ p ) {
729
+ if (* p == '_' ) {
730
+ LOG (ERROR , "Underscore not allowed in hostname %s" , host );
731
+ return -1 ;
732
+ }
733
+ if (* p == '+' )
734
+ * p = ':' ;
735
+ }
736
+ port ++ ;
737
+ } else {
738
+ /* Get just port from service arguments */
739
+ host = address ;
740
+ port = ptr ;
741
+ }
742
+ } else {
743
+ * slash = '\0' ;
744
+ host = address ;
745
+ port = slash + 1 ;
746
+ }
747
+ int res = qubes_tcp_connect (host , port );
748
+ if (res == -1 )
749
+ return -1 ;
750
+ * stdin_fd = * stdout_fd = res ;
751
+ cmd -> send_service_descriptor = false;
752
+ return 0 ;
555
753
}
556
754
557
755
if (euidaccess (service_full_path , X_OK ) == 0 ) {
0 commit comments