diff --git a/NEWS b/NEWS index b180eb2..af7eb69 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -Pound -- history of user-visible changes. 2024-09-30 +Pound -- history of user-visible changes. 2024-10-02 See the end of file for copying conditions. Pound is a continuation of the software originally developed by @@ -7,7 +7,7 @@ on 2022-09-19. See the README file for details. Please send pound bug reports to -Version 4.13.90 (git,resolver) +Version 4.13.90 (git) * Dynamic backends @@ -155,6 +155,37 @@ balancing algorightm applies when selecting an emergency backend. All statements valid within a "Backend" section are also valid within an emergency backend declaration. +* Remote access to the management interface + +A new backend type "Control" is introduced to make it possible to +access the management interface remotely. The example below shows +how to configure pound to expose the management interface on +http://192.0.2.1:3434: + + ListenHTTP + Address 192.0.2.1 + Port 3434 + Service + ACL "secure" + Control + End + End + +* poundctl + +Changes in poundctl functionality reflect those in the management +interface. First of all, the -s option accepts URL as its argument: + + -s https://user:password@hostname:8080/path + +Additionally, the following new options are implemented: + + -C FILE Load CA certificates from FILE. If FILE is a + directory, all PEM files will be loaded from it. + -K FILE Load client certificate and key from FILE. During TLS + handshake, send them to the peer for authentication. + -k Insecure mode: disable peer verification. + * configure Removed historic "--with-owner" and "--with-group" options. diff --git a/doc/pound.8 b/doc/pound.8 index 5aa3f5f..3f4d481 100644 --- a/doc/pound.8 +++ b/doc/pound.8 @@ -14,7 +14,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with pound. If not, see . -.TH POUND 8 "September 27, 2024" "pound" "System Manager's Manual" +.TH POUND 8 "October 1, 2024" "pound" "System Manager's Manual" .SH NAME pound \- HTTP/HTTPS reverse-proxy and load-balancer .SH SYNOPSIS @@ -769,16 +769,15 @@ effect until next statement or end of the configuration file, whichever occurs first. .SS Control socket .B Pound -can be instructed to listen on a UNIX socket for management requests, +can be instructed to listen for management requests, which will allow you to obtain information about the running instance, change state of configured listeners, services, and backends, etc. These requests may be issued by using the .BR poundctl (8) utility. .PP -The use of this -.I control socket -is configured via the +Usually, a UNIX socket is used to communicate with the management interface. +It is configured via the .B Control statement. This statement has two forms. In .I inline @@ -822,6 +821,15 @@ global statements is used. When set to .B true it will change the owner of the socket file to that specified by those two statements. +.PP +It is also possible to have +.B pound +listen for management requests on an INET or INET6 address. See +below, the section +.BR "Backend definitions" , +description of the +.B Control +statement. .SH "HTTP Listener" An HTTP listener defines an address and port that .B pound @@ -1595,6 +1603,41 @@ may define multiple backends per service, in which case .B pound will attempt to load-balance between them. .TP +.B Control +Enables a special \fBcontrol backend\fR -- a management interface that +returns information about the running \fBpound\fR server, makes it +possible to change state of configured listeners, services and +backends, and provides other management facilities. The management +interface is discussed in detail in +section +.BR "Control socket" , +above. The \fBControl\fR backend provides remote access to this +interface. Be careful to properly protect the control interface +by use of HTTPS, ACLs and/or basic authorization, e.g.: +.IP +.RS +.EX +ListenHTTPS + Address 192.0.2.1 + Port 443 + Cert "/etc/ssl/priv/example.pem" + + Service + Not BasicAuth "pound/htpasswd" + Rewrite response + SetHeader "WWW-Authenticate: Basic realm=\"Restricted access\"" + End + Error 401 + End + + Service + ACL "secure" + Control + End +End +.EE +.RE +.TP \fBRedirect\fR [\fIcode\fR] "\fIurl\fR" This is a special type of backend. Instead of sending the request to a backend .B pound diff --git a/doc/pound.texi b/doc/pound.texi index 94bf934..a451040 100644 --- a/doc/pound.texi +++ b/doc/pound.texi @@ -972,7 +972,7 @@ authentication are declared after that service. This way, any request that does not convey an @code{Authentication} header with credentials matching an entry from your password file will match this service, and will be replied to with a properly formatted 401 response, which will -prompt the remote user to authenticate himself. On the other hand, +prompt the remote user to authenticate themselves. On the other hand, authorized requests will not match this service and will eventually be handled by one of the services declared after it. @@ -2587,15 +2587,15 @@ When logging, replace the last byte of client IP addresses with 0. Default: log the client address in full. @end deffn -@node Control socket -@section Control socket settings -@cindex control socket -@command{Pound} can be instructed to listen on a UNIX socket for management -requests, which will allow you to obtain information about the running -instance, change state of configured listeners, services, and backends, -etc. These requests are normally issued by the poundctl utility -(@pxref{poundctl}). +@node Management interface +@section Management Interface +@command{Pound} provides a management interface, which allows one to +obtain information about the running instance, change state of +configured listeners, services, and backends, etc. These requests are +normally issued by the poundctl utility (@pxref{poundctl}). +@cindex control socket +The management interface is usually made available via a UNIX socket. Properties of this @dfn{control socket} are configured via the @code{Control} statement. It has two forms: @dfn{directive} and @dfn{section}. @@ -2638,6 +2638,73 @@ End @end group @end example +@node Service Control +@subsection Remote Access to Management Interface +@kwindex Control + To make the management interface available remotely, use the +@code{Control} statement in a @code{Service} section: + +@example +@group +ListenHTTP + Address 192.0.2.1 + Port 80 + Service + Control + End +End +@end group +@end example + + This configuration makes management interface accessible via +@indicateurl{http://192.0.2.1/}. Due to obvious security +implications, you'd be better off protecting the @code{Control} +endpoint using HTTPS, ACLs and/or basic authorization. The example +below illustrates all these techniques: + +@example +ListenHTTPS + Address 192.0.2.1 + Port 443 + Cert "/etc/ssl/priv/example.pem" + Disable TLSv1 + + Service + Not BasicAuth "pound/htpasswd" + Rewrite response + SetHeader "WWW-Authenticate: Basic realm=\"Restricted access\"" + End + Error 401 + End + + Service + ACL "secure" + Control + End +End +@end example + +@xref{Authentication}, for a discussion of @code{BasicAuth} statement, +and see @ref{ACL} for a discussion of access control lists (this +example assumes that the ACL @samp{secure} is defined elsewhere in the +configuration file). + +If you wish to make management interface available under a special +path, make sure the path prefix is stripped off, e.g.: + +@example +@group +Service + URL -re "^/control(/.+)" + SetURL "$1" + Control +End +@end group +@end example + +@xref{Service Request and Response Modification}, for details about +@code{SetURL} statement and request modification in general. + @node Timeouts @section Timeouts @@ -3511,6 +3578,7 @@ certificates after host-specific certificates). @code{Cert} directives must precede all other SSL-specific directives. @end deffn +@anchor{ClientCert} @deffn {ListenHTTPS} ClientCert @var{mode} @var{depth} Specifies whether the listener must ask for the client's HTTPS certificate. Allowed values for @var{mode} are: @@ -4150,6 +4218,12 @@ then the request path is ignored. @xref{Redirects}, for a detailed discussion of this backend and its use. @end deffn +@deffn {Service directive} Control +Defines a special backend that serves @dfn{pound management +interface}. @xref{Service Control}, for a detailed discussion of this +feature. +@end deffn + @anchor{Metrics} @deffn {Service directive} Metrics This directive defines a special backend that generates Openmetric @@ -4362,11 +4436,12 @@ defines its own retry interval value. the running instance and allows you to change some of them. The program communicates with the running @command{pound} daemon via -a UNIX socket. For this to work, @command{pound} configuration file -must contain a @code{Control} statement (@pxref{Control statement}). -When started, @command{poundctl} opens the default @file{pound.cfg} -file, looks up for this statement and then uses the pathname defined -in it as the control socket file. +a UNIX socket or remotely, via HTTP or HTTPS. To use UNIX socket, +@command{pound} configuration file must contain a @code{Control} +statement (@pxref{Control statement}). When started, +@command{poundctl} opens the default @file{pound.cfg} file, looks up +for this statement and then uses the pathname defined in it as the +control socket file. This behavior can be altered in two ways. First, if the configuration file is in a non-standard location, the pathname @@ -4374,6 +4449,10 @@ of this file can be given to the program using the @option{-f} command line option. Secondly, the socket name can be supplied in the command line explicitly, using the @option{-s} option. + To access remote @command{pound} daemon via HTTP or HTTPS, supply +its URL as argument to the @option{-s} option. @xref{poundctl +remote}, for a detailed discussion. + The program invocation syntax is: @example @@ -4479,11 +4558,46 @@ be specified. Add a session with the given @var{key}. @end deffn +@node poundctl remote +@section Using @command{poundctl} for remote access + Starting from version 4.14, @command{pound} is able to provide its +management interface via HTTP (@pxref{Service Control}). To use +remote management interface, supply its URL with the @option{-s} +command line option. The URL syntax is: + +@example +@var{scheme}://[@var{user}[:@var{password}]@@]@var{hostname}[:@var{port}][/@var{path}] +@end example + +@noindent +where @samp{[]} denote optional parts. Valid @var{scheme} values are +@samp{http} and @samp{https}. Use @var{user} and @var{password} if +the interface is protected by basic authentication (@pxref{Authentication}). + +When using @samp{https} protocol, you may need to supply the +@dfn{certificate authority} file using the @option{-C @var{file}} +option, where @var{file} is the name of the file in PEM format. This +option can also take a directory name as its argument. In this case +all PEM files from that directory will be loaded. + +To disable peer certificate verification, use the @option{-k} option. + +If @command{pound} configuration requires the client to send its +certificate for authentication (@pxref{ClientCert}), use the +@option{-K @var{file}} option to supply it. Its argument, @var{file}, +is the name of the file with the certificate and key in PEM format. + @node poundctl options @section @command{poundctl} options The following options are understood by @command{poundctl}: @table @option +@kwindex -C, poundctl +@item -C @var{file} +@itemx -C @var{dir} +Read certificate authority files from @var{file} or from files in +@var{dir}. @xref{poundctl remote}. + @kwindex -f, poundctl @item -f @var{file} Read @command{pound} configuration from @var{file}, instead of the @@ -4497,13 +4611,24 @@ Sets indentation level for JSON output to @var{n} columns. @item -j Use JSON output format. +@kwindex -K, poundctl +@item -K @var{file} +Read client certificate and private key from @var{file}. +@xref{poundctl remote}. + +@kwindex -k +@item -k +Disable peer certificate verification. @xref{poundctl remote}. + @kwindex -h, poundctl @item -h Shows a short help output and exits. @kwindex -s, poundctl @item -s @var{socket} -Sets pathname of the control socket. +@itemx -s @var{url} +Sets pathname of the control socket, or its URL (for remote access, +@pxref{poundctl remote}). @kwindex -T, poundctl @item -T @var{file} diff --git a/doc/poundctl.8 b/doc/poundctl.8 index 5c6ef25..db31e35 100644 --- a/doc/poundctl.8 +++ b/doc/poundctl.8 @@ -14,15 +14,17 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with pound. If not, see . -.TH POUNDCTL 8 "February 21, 2024" "poundctl" "System Manager's Manual" +.TH POUNDCTL 8 "October 2, 2024" "poundctl" "System Manager's Manual" .SH NAME poundctl \- control the pound daemon .SH SYNOPSIS .B poundctl -[\fB\-Vvh\fR] +[\fB\-kVvh\fR] +[\fB\-C \fIFILE\fR] [\fB\-f \fIFILE\fR] [\fB\-i \fIN\fR] [\fB\-j\fR] +[\fB\-K \fIFILE\fR] [\fB\-s \fISOCKET\fR] [\fB\-T \fITEMPLATE-FILE\fR] [\fB\-t \fITEMPLATE-NAME\fR] @@ -55,6 +57,16 @@ command line explicitly, using the .B \-s option. .PP +The management interface of a remote +.B pound +server can be accessed by giving its full URL as argument to the +.B \-s +option, e.g. +.PP +.EX +poundctl -s https://proxy.example.org/control +.EE +.PP The \fICOMMAND\fR argument instructs the program what action it is supposed to perform. Missing \fICOMMAND\fR is equivalent to .BR list . @@ -168,6 +180,10 @@ The default file defines two templates: \fBdefault\fR and \fBxml\fR. .SH OPTIONS .TP +\fB\-C \fIFILE\fR +Load certificate authority files from \fIFILE\fR. \fIFILE\fR can also +be a directory containing CA certificates in PEM format. +.TP \fB\-f \fIFILE\fR Location of \fBpound\fR configuration file. .TP @@ -177,11 +193,23 @@ Sets indentation level for JSON output. \fB\-j\fR JSON output format. .TP +\fB\-K \fIFILE\fR +Load client certificate and key from \fIFILE\fR and send them to the +server during handshake for authentication. +.TP +.B \-k +Disable peer verification. +.TP \fB\-h\fR Shows a short help output and exit. .TP \fB\-s \fISOCKET\fR -Sets control socket pathname. +Sets control socket pathname. \fISOCKET\fR can aslo be a URL in the +form: +.IP +{\fBhttp\fR|\fBhttps\fR}\fB://\fR[\fIUSER\fR[\fB:\fIPASS\fR]\fB@\fR]\fIHOSTNAME\fR[\fB:\fIPORT\fT][\fB/\fIPATH\fR] +.IP +where \fB{|}\fR denote alternative forms and \fB[]\fR enclose optional parts. .TP \fB\-T \fITEMPLATE-FILE\fR Sets the name of the template file to use. diff --git a/src/config.c b/src/config.c index 53efd43..2080d4a 100644 --- a/src/config.c +++ b/src/config.c @@ -2512,6 +2512,20 @@ parse_emergency (void *call_data, void *section_data) return PARSER_OK; } +static int +parse_control_backend (void *call_data, void *section_data) +{ + BALANCER_LIST *bml = call_data; + BACKEND *be; + + XZALLOC (be); + be->be_type = BE_CONTROL; + be->priority = 1; + pthread_mutex_init (&be->mut, NULL); + balancer_add_backend (balancer_list_get_normal (bml), be); + return PARSER_OK; +} + static int parse_metrics (void *call_data, void *section_data) { @@ -3983,6 +3997,11 @@ static PARSER_TABLE service_parsetab[] = { .parser = parse_metrics, .off = offsetof (SERVICE, backends) }, + { + .name = "Control", + .parser = parse_control_backend, + .off = offsetof (SERVICE, backends) + }, { .name = "Session", .parser = parse_session @@ -5624,7 +5643,7 @@ static PARSER_TABLE control_parsetab[] = { }; static int -parse_control (void *call_data, void *section_data) +parse_control_listener (void *call_data, void *section_data) { struct token *tok; LISTENER *lst; @@ -6090,7 +6109,7 @@ static PARSER_TABLE top_level_parsetab[] = { }, { .name = "Control", - .parser = parse_control + .parser = parse_control_listener }, { .name = "Anonymise", diff --git a/src/http.c b/src/http.c index 69b989c..0c1a154 100644 --- a/src/http.c +++ b/src/http.c @@ -762,6 +762,16 @@ expand_url (char const *url, POUND_HTTP *phttp, int has_uri) static int rewrite_apply (REWRITE_RULE_HEAD *rewrite_rules, struct http_request *request, POUND_HTTP *phttp); +static int +control_response (POUND_HTTP *phttp) +{ + if (rewrite_apply (&phttp->lstn->rewrite[REWRITE_REQUEST], &phttp->request, + phttp) + || rewrite_apply (&phttp->svc->rewrite[REWRITE_REQUEST], &phttp->request, + phttp)) + return HTTP_STATUS_INTERNAL_SERVER_ERROR; + return control_response_basic (phttp); +} /* * Reply with a redirect */ @@ -4536,6 +4546,8 @@ do_http (POUND_HTTP *phttp) phttp->cl = bb; if (BIO_do_handshake (phttp->cl) <= 0) { + logmsg (LOG_ERR, "(%"PRItid") handshake failed: %s", + POUND_TID (), ERR_error_string (ERR_get_error (), NULL)); return; } else diff --git a/src/pound.h b/src/pound.h index 43d21b2..f37d0f1 100644 --- a/src/pound.h +++ b/src/pound.h @@ -1204,7 +1204,7 @@ static inline void stringbuf_init_log (struct stringbuf *sb) } char const *sess_type_to_str (int type); -int control_response (POUND_HTTP *arg); +int control_response_basic (POUND_HTTP *arg); void pound_atexit (void (*func) (void *), void *arg); int unlink_at_exit (char const *file_name); diff --git a/src/poundctl.c b/src/poundctl.c index a8f9ebe..11f93db 100644 --- a/src/poundctl.c +++ b/src/poundctl.c @@ -20,8 +20,26 @@ #include "json.h" #include +typedef struct +{ + int tls; + char *path; + char *host; + char *user; + char *pass; + socklen_t addrlen; + union + { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + } addr; +} URL; + char *conf_name = POUND_CONF; char *socket_name; +URL *url; int json_option; int indent_option; int verbose_option; @@ -29,6 +47,35 @@ char *tmpl_path; char *tmpl_file = "poundctl.tmpl"; char *tmpl_name = "default"; +char *ca_file; +char *ca_path; +int disable_peer_verify; +char *client_cert_file; + +static void +openssl_errormsg (int code, char const *fmt, ...) +{ + va_list ap; + unsigned long n = ERR_get_error (); + + fprintf (stderr, "%s: ", progname); + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + fprintf (stderr, ": %s", ERR_error_string (n, NULL)); + + if ((n = ERR_get_error ()) != 0) + { + do + { + fprintf (stderr, ": %s", ERR_error_string (n, NULL)); + } + while ((n = ERR_get_error ()) != 0); + } + fputc ('\n', stderr); + if (code) + exit (code); +} + struct keyword { char const *name; @@ -177,28 +224,124 @@ get_socket_name (void) return socket_name; } -BIO * -open_socket (void) +static void +url_parse_host (char *str, URL *url) { - struct sockaddr_un ctrl; - int fd; - BIO *bio; + struct addrinfo *addr; + struct addrinfo hints; + int n = strcspn (str, "/"); + char *host = xstrndup (str, n); + char *p; + int rc; - if (verbose_option) - errormsg (0, 0, "connecting to %s", socket_name); + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + + if (host[0] == '[' && host[n-1] == ']') + hints.ai_family = AF_INET6; + else + hints.ai_family = AF_UNSPEC; + + if ((p = strchr (host, ':')) != NULL) + *p++ = 0; + else + p = 0; + + if ((rc = getaddrinfo (host, p, &hints, &addr)) == 0) + { + if (addr->ai_family == AF_INET || addr->ai_family == AF_INET6) + { + url->addrlen = addr->ai_addrlen; + memcpy (&url->addr, addr->ai_addr, addr->ai_addrlen); + } + else + errormsg (1, 0, "unexpected address family"); + freeaddrinfo (addr); + } + else + errormsg (1, 0, "can't resolve %s: %s", host, gai_strerror (rc)); + + url->host = host; + url->path = xstrdup (str + n); +} + +static void +url_parse_creds (char *str, URL *url) +{ + int n, j; + char **dst = &url->user; + + switch (str[n = strcspn (str, ":@")]) + { + case ':': + j = strcspn (str + n + 1, "@"); + if (str[n + 1 + j] != '@') + break; + url->user = xstrndup (str, n); + str += n + 1; + n = j; + dst = &url->pass; + /* fall through */ + case '@': + *dst = xstrndup (str, n); + str += n + 1; + /* fall through */ + } + url_parse_host (str, url); +} + +static void +url_parse_scheme (char *str, URL *url) +{ + char *p; + int len; - if (strlen (socket_name) > sizeof (ctrl.sun_path)) + if ((p = strchr (str, ':')) != NULL && p[1] == '/' && p[2] == '/') + { + if ((len = (p - str)) == 4 && memcmp (str, "http", len) == 0) + url->tls = 0; + else if (len == 5 && memcmp (str, "https", len) == 0) + url->tls = 1; + else + errormsg (1, 0, "unsupported URL scheme: %s", str); + str = p + 3; + + url_parse_creds (str, url); + } + else { - errormsg (1, 0, "socket name too long"); + url->tls = 0; + url->path = xstrdup (""); + url->host = xstrdup ("localhost"); + url->user = NULL; + url->pass = NULL; + url->addrlen = sizeof (struct sockaddr_un); + url->addr.un.sun_family = AF_UNIX; + if (strlen (str) > sizeof (url->addr.un.sun_path)) + errormsg (1, 0, "socket name too long"); + strncpy (url->addr.un.sun_path, str, sizeof (url->addr.un.sun_path)); } +} - ctrl.sun_family = AF_UNIX; - strncpy (ctrl.sun_path, socket_name, sizeof (ctrl.sun_path)); - if ((fd = socket (PF_UNIX, SOCK_STREAM, 0)) < 0) +static URL * +url_parse (char *str) +{ + URL *url = xzalloc (sizeof (*url)); + url_parse_scheme (str, url); + return url; +} + +BIO * +open_socket (URL *url) +{ + int fd; + BIO *bio, *bb; + + if ((fd = socket (url->addr.sa.sa_family, SOCK_STREAM, 0)) < 0) { errormsg (1, errno, "socket"); } - if (connect (fd, (struct sockaddr *) &ctrl, sizeof (ctrl)) < 0) + if (connect (fd, &url->addr.sa, url->addrlen) < 0) { errormsg (1, errno, "connect"); exit (1); @@ -208,6 +351,66 @@ open_socket (void) { errormsg (1, 0, "BIO_new_fd failed"); } + BIO_set_close (bio, BIO_CLOSE); + + if (url->tls) + { + SSL_CTX *ctx; + SSL *ssl; + X509 *x509; + int verify_result; + + if ((ctx = SSL_CTX_new (SSLv23_client_method ())) == NULL) + errormsg (1, 0, "SSL_CTX_new"); + SSL_CTX_set_verify (ctx, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_mode (ctx, SSL_MODE_AUTO_RETRY); + + if (!disable_peer_verify) + { + if (!SSL_CTX_load_verify_locations (ctx, ca_file, ca_path)) + openssl_errormsg (1, "SSL_CTX_load_verify_locations"); + } + + if (client_cert_file) + { + if (SSL_CTX_use_certificate_chain_file (ctx, client_cert_file) != 1) + openssl_errormsg (1, "SSL_CTX_use_certificate_chain_file"); + if (SSL_CTX_use_PrivateKey_file (ctx, client_cert_file, SSL_FILETYPE_PEM) != 1) + openssl_errormsg (1, "SSL_CTX_use_PrivateKey_file"); + if (SSL_CTX_check_private_key (ctx) != 1) + openssl_errormsg (1, "SSL_CTX_check_private_key failed"); + } + + if ((ssl = SSL_new (ctx)) == NULL) + errormsg (1, 0, "SSL_new"); + SSL_set_tlsext_host_name (ssl, url->host); + SSL_set_bio (ssl, bio, bio); + + if ((bb = BIO_new (BIO_f_ssl ())) == NULL) + errormsg (1, 0, "BIO_new"); + BIO_set_ssl (bb, ssl, BIO_CLOSE); + BIO_set_ssl_mode (bb, 1); + + if (BIO_do_handshake (bb) <= 0) + errormsg (1, 0, "BIO_do_handshake failed: %s", + ERR_error_string (ERR_get_error (), NULL)); + + if (!disable_peer_verify && + (x509 = SSL_get_peer_certificate (ssl)) != NULL && + (verify_result = SSL_get_verify_result (ssl)) != X509_V_OK) + { + errormsg (1, 0, "certificate verification failed: %s", + X509_verify_cert_error_string (verify_result)); + } + + bio = bb; + } + + if ((bb = BIO_new (BIO_f_buffer ())) == NULL) + errormsg (1, 0, "BIO_f_buffer failed"); + BIO_set_buffer_size (bb, MAXBUF); + BIO_set_close (bb, BIO_CLOSE); + bio = BIO_push (bb, bio); return bio; } @@ -469,6 +672,50 @@ print_json (struct json_value *val, FILE *fp) json_value_format (val, &format, 0); } +static void +send_request (BIO *bio, char const *method, char const *fmt, ...) +{ + va_list ap; + + BIO_printf (bio, "%s %s/", method, url->path); + va_start (ap, fmt); + BIO_vprintf (bio, fmt, ap); + va_end (ap); + BIO_printf (bio, " HTTP/1.1\r\n" + "Host: %s\r\n", + url->host); + if (url->pass) + { + size_t len = strlen (url->user) + strlen (url->pass) + 1; + char *buf = xmalloc (len); + char iobuf[MAXBUF]; + int inlen; + BIO *bb, *b64; + + strcat (strcat (strcpy (buf, url->user), ":"), url->pass); + + if ((b64 = BIO_new (BIO_f_base64 ())) == NULL) + errormsg (1, errno, "BIO_f_base64"); + + if ((bb = BIO_new (BIO_s_mem ())) == NULL) + errormsg (1, errno, "BIO_s_mem"); + + b64 = BIO_push (b64, bb); + BIO_write (b64, buf, len); + BIO_flush (b64); + inlen = BIO_read (bb, iobuf, sizeof (iobuf)); + if (inlen == -1) + errormsg (1, errno, "failed to encode credentials"); + BIO_free_all (b64); + + BIO_printf (bio, "Authorization: Basic "); + BIO_write (bio, iobuf, inlen-1); + BIO_printf (bio, "\r\n"); + } + BIO_printf (bio, "\r\n"); + BIO_flush (bio); +} + int command_list (BIO *bio, int argc, char **argv) { @@ -481,9 +728,7 @@ command_list (BIO *bio, int argc, char **argv) { errormsg (1, 0, "too many arguments"); } - BIO_printf (bio, "GET /listener%s HTTP/1.1\r\n" - "Host: localhost\r\n\r\n", - uri); + send_request (bio, "GET", "listener%s", uri); val = read_response (bio); if (json_option) print_json (val, stdout); @@ -517,9 +762,7 @@ command_on_off (BIO *bio, int argc, char **argv, char const *verb) errormsg (1, 0, "too many arguments"); } uri = argv[0]; - BIO_printf (bio, "%s /listener%s HTTP/1.1\r\n" - "Host: localhost\r\n\r\n", - verb, uri); + send_request (bio, verb, "listener%s", uri); val = read_response (bio); if (json_option) print_json (val, stdout); @@ -579,9 +822,7 @@ command_delete_session (BIO *bio, int argc, char **argv) } key = argv[1]; - BIO_printf (bio, "DELETE /session%s?key=%s HTTP/1.1\r\n" - "Host: localhost\r\n\r\n", - uri, key); + send_request (bio, "DELETE", "session%s?key=%s", uri, key); val = read_response (bio); if (json_option) print_json (val, stdout); @@ -626,9 +867,7 @@ command_add_session (BIO *bio, int argc, char **argv) } key = argv[1]; - BIO_printf (bio, "PUT /session%s?key=%s HTTP/1.1\r\n" - "Host: localhost\r\n\r\n", - uri, key); + send_request (bio, "PUT", "session%s?key=%s", uri, key); val = read_response (bio); if (json_option) print_json (val, stdout); @@ -699,10 +938,13 @@ static char *usage_text[] = { " del same as delete", "", "OPTIONS:", + " -C FILE load CA certificates from FILE (or directory)", " -f FILE location of pound configuration file", " -i N indentation level for JSON output", " -j JSON output format", - " -s SOCKET sets control socket pathname", + " -K FILE load client certificate and key from FILE", + " -k disable peer verification", + " -s SOCKET sets control socket pathname or URL", " -t FILE read templates from this file", " -T NAME name of the default template", " -v verbose output", @@ -935,14 +1177,24 @@ main (int argc, char **argv) int c; BIO *bio; COMMAND command; - + struct stat sb; + set_progname (argv[0]); json_memabrt = xnomem; - while ((c = getopt (argc, argv, "f:i:jhs:T:t:vV")) != EOF) + while ((c = getopt (argc, argv, "C:f:i:jK:khs:T:t:vV")) != EOF) { switch (c) { + case 'C': + if (stat (optarg, &sb)) + errormsg (1, errno, "can't stat %s", optarg); + else if (S_ISDIR (sb.st_mode)) + ca_path = optarg; + else + ca_file = optarg; + break; + case 'f': conf_name = optarg; break; @@ -962,6 +1214,14 @@ main (int argc, char **argv) json_option = 1; break; + case 'K': + client_cert_file = optarg; + break; + + case 'k': + disable_peer_verify = 1; + break; + case 'T': tmpl_name = optarg; break; @@ -1007,7 +1267,13 @@ main (int argc, char **argv) } read_template (); - bio = open_socket (); + + url = url_parse (socket_name); + + if (verbose_option) + errormsg (0, 0, "connecting to %s", socket_name); + + bio = open_socket (url); return command (bio, argc, argv); } diff --git a/src/svc.c b/src/svc.c index 64dee71..6a648c2 100644 --- a/src/svc.c +++ b/src/svc.c @@ -2951,7 +2951,7 @@ find_endpoint (int method, const char *uri, int *errcode) } int -control_response (POUND_HTTP *arg) +control_response_basic (POUND_HTTP *arg) { struct endpoint *ep; int code;