#include "qemu/base64.h"
#include "io/channel-socket.h"
#include "io/channel-file.h"
+#include "io/channel-tls.h"
#include <unistd.h>
#include <fcntl.h>
return NULL;
}
- drv = g_new0(ParallelCharDriver, 1);
- drv->fd = fd;
- drv->mode = IEEE1284_MODE_COMPAT;
-
chr = qemu_chr_alloc(backend, errp);
if (!chr) {
return NULL;
}
+
+ drv = g_new0(ParallelCharDriver, 1);
+ chr->opaque = drv;
chr->chr_write = null_chr_write;
chr->chr_ioctl = pp_ioctl;
chr->chr_close = pp_close;
- chr->opaque = drv;
+
+ drv->fd = fd;
+ drv->mode = IEEE1284_MODE_COMPAT;
return chr;
}
/* TCP Net console */
typedef struct {
- QIOChannel *ioc;
+ QIOChannel *ioc; /* Client I/O channel */
+ QIOChannelSocket *sioc; /* Client master channel */
QIOChannelSocket *listen_ioc;
guint listen_tag;
+ QCryptoTLSCreds *tls_creds;
int connected;
int max_size;
int do_telnetopt;
QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr, NULL);
}
remove_fd_in_watch(chr);
+ object_unref(OBJECT(s->sioc));
+ s->sioc = NULL;
object_unref(OBJECT(s->ioc));
s->ioc = NULL;
g_free(chr->filename);
return size;
}
-#ifndef _WIN32
-CharDriverState *qemu_chr_open_eventfd(int eventfd)
-{
- CharDriverState *chr = qemu_chr_open_fd(eventfd, eventfd, NULL, NULL);
-
- if (chr) {
- chr->avail_connections = 1;
- }
-
- return chr;
-}
-#endif
-
static void tcp_chr_connect(void *opaque)
{
CharDriverState *chr = opaque;
TCPCharDriver *s = chr->opaque;
- QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
g_free(chr->filename);
- chr->filename = sockaddr_to_str(&sioc->localAddr, sioc->localAddrLen,
- &sioc->remoteAddr, sioc->remoteAddrLen,
- s->is_listen, s->is_telnet);
+ chr->filename = sockaddr_to_str(
+ &s->sioc->localAddr, s->sioc->localAddrLen,
+ &s->sioc->remoteAddr, s->sioc->remoteAddrLen,
+ s->is_listen, s->is_telnet);
s->connected = 1;
if (s->ioc) {
}
}
-#define IACSET(x,a,b,c) x[0] = a; x[1] = b; x[2] = c;
-static void tcp_chr_telnet_init(QIOChannel *ioc)
+typedef struct {
+ CharDriverState *chr;
+ char buf[12];
+ size_t buflen;
+} TCPCharDriverTelnetInit;
+
+static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc,
+ GIOCondition cond G_GNUC_UNUSED,
+ gpointer user_data)
+{
+ TCPCharDriverTelnetInit *init = user_data;
+ ssize_t ret;
+
+ ret = qio_channel_write(ioc, init->buf, init->buflen, NULL);
+ if (ret < 0) {
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ ret = 0;
+ } else {
+ tcp_chr_disconnect(init->chr);
+ return FALSE;
+ }
+ }
+ init->buflen -= ret;
+
+ if (init->buflen == 0) {
+ tcp_chr_connect(init->chr);
+ return FALSE;
+ }
+
+ memmove(init->buf, init->buf + ret, init->buflen);
+
+ return TRUE;
+}
+
+static void tcp_chr_telnet_init(CharDriverState *chr)
+{
+ TCPCharDriver *s = chr->opaque;
+ TCPCharDriverTelnetInit *init =
+ g_new0(TCPCharDriverTelnetInit, 1);
+ size_t n = 0;
+
+ init->chr = chr;
+ init->buflen = 12;
+
+#define IACSET(x, a, b, c) \
+ do { \
+ x[n++] = a; \
+ x[n++] = b; \
+ x[n++] = c; \
+ } while (0)
+
+ /* Prep the telnet negotion to put telnet in binary,
+ * no echo, single char mode */
+ IACSET(init->buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */
+ IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */
+ IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */
+ IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */
+
+#undef IACSET
+
+ qio_channel_add_watch(
+ s->ioc, G_IO_OUT,
+ tcp_chr_telnet_init_io,
+ init, NULL);
+}
+
+
+static void tcp_chr_tls_handshake(Object *source,
+ Error *err,
+ gpointer user_data)
+{
+ CharDriverState *chr = user_data;
+ TCPCharDriver *s = chr->opaque;
+
+ if (err) {
+ tcp_chr_disconnect(chr);
+ } else {
+ if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+ }
+}
+
+
+static void tcp_chr_tls_init(CharDriverState *chr)
{
- char buf[3];
- /* Send the telnet negotion to put telnet in binary, no echo, single char mode */
- IACSET(buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */
- qio_channel_write(ioc, buf, 3, NULL);
- IACSET(buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */
- qio_channel_write(ioc, buf, 3, NULL);
- IACSET(buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */
- qio_channel_write(ioc, buf, 3, NULL);
- IACSET(buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */
- qio_channel_write(ioc, buf, 3, NULL);
+ TCPCharDriver *s = chr->opaque;
+ QIOChannelTLS *tioc;
+ Error *err = NULL;
+
+ if (s->is_listen) {
+ tioc = qio_channel_tls_new_server(
+ s->ioc, s->tls_creds,
+ NULL, /* XXX Use an ACL */
+ &err);
+ } else {
+ tioc = qio_channel_tls_new_client(
+ s->ioc, s->tls_creds,
+ s->addr->u.inet->host,
+ &err);
+ }
+ if (tioc == NULL) {
+ error_free(err);
+ tcp_chr_disconnect(chr);
+ }
+ object_unref(OBJECT(s->ioc));
+ s->ioc = QIO_CHANNEL(tioc);
+
+ qio_channel_tls_handshake(tioc,
+ tcp_chr_tls_handshake,
+ chr,
+ NULL);
}
+
static int tcp_chr_new_client(CharDriverState *chr, QIOChannelSocket *sioc)
{
TCPCharDriver *s = chr->opaque;
s->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(sioc));
+ s->sioc = sioc;
+ object_ref(OBJECT(sioc));
if (s->do_nodelay) {
qio_channel_set_delay(s->ioc, false);
g_source_remove(s->listen_tag);
s->listen_tag = 0;
}
- tcp_chr_connect(chr);
+
+ if (s->tls_creds) {
+ tcp_chr_tls_init(chr);
+ } else {
+ if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+ }
return 0;
}
void *opaque)
{
CharDriverState *chr = opaque;
- TCPCharDriver *s = chr->opaque;
QIOChannelSocket *sioc;
sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(channel),
return TRUE;
}
- if (s->do_telnetopt) {
- tcp_chr_telnet_init(QIO_CHANNEL(sioc));
- }
-
tcp_chr_new_client(chr, sioc);
object_unref(OBJECT(sioc));
}
g_free(s->read_msgfds);
}
+ if (s->tls_creds) {
+ object_unref(OBJECT(s->tls_creds));
+ }
if (s->write_msgfds_num) {
g_free(s->write_msgfds);
}
const char *path = qemu_opt_get(opts, "path");
const char *host = qemu_opt_get(opts, "host");
const char *port = qemu_opt_get(opts, "port");
+ const char *tls_creds = qemu_opt_get(opts, "tls-creds");
SocketAddress *addr;
if (!path) {
error_setg(errp, "chardev: socket: no port given");
return;
}
+ } else {
+ if (tls_creds) {
+ error_setg(errp, "TLS can only be used over TCP socket");
+ return;
+ }
}
backend->u.socket = g_new0(ChardevSocket, 1);
backend->u.socket->wait = is_waitconnect;
backend->u.socket->has_reconnect = true;
backend->u.socket->reconnect = reconnect;
+ backend->u.socket->tls_creds = g_strdup(tls_creds);
addr = g_new0(SocketAddress, 1);
if (path) {
},{
.name = "telnet",
.type = QEMU_OPT_BOOL,
+ },{
+ .name = "tls-creds",
+ .type = QEMU_OPT_STRING,
},{
.name = "width",
.type = QEMU_OPT_NUMBER,
s->is_listen = is_listen;
s->is_telnet = is_telnet;
s->do_nodelay = do_nodelay;
+ if (sock->tls_creds) {
+ Object *creds;
+ creds = object_resolve_path_component(
+ object_get_objects_root(), sock->tls_creds);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ sock->tls_creds);
+ goto error;
+ }
+ s->tls_creds = (QCryptoTLSCreds *)
+ object_dynamic_cast(creds,
+ TYPE_QCRYPTO_TLS_CREDS);
+ if (!s->tls_creds) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ sock->tls_creds);
+ goto error;
+ }
+ object_ref(OBJECT(s->tls_creds));
+ if (is_listen) {
+ if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for server endpoint");
+ goto error;
+ }
+ } else {
+ if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+ error_setg(errp, "%s",
+ "Expected TLS credentials for client endpoint");
+ goto error;
+ }
+ }
+ }
+
qapi_copy_SocketAddress(&s->addr, sock->addr);
chr->opaque = s;
if (s->reconnect_time) {
socket_try_connect(chr);
} else if (!qemu_chr_open_socket_fd(chr, errp)) {
- g_free(s);
- qemu_chr_free_common(chr);
- return NULL;
+ goto error;
}
if (is_listen && is_waitconnect) {
}
return chr;
+
+ error:
+ if (s->tls_creds) {
+ object_unref(OBJECT(s->tls_creds));
+ }
+ g_free(s);
+ qemu_chr_free_common(chr);
+ return NULL;
}
static CharDriverState *qmp_chardev_open_udp(const char *id,