blob: 28a27e6759a5dab93cb70a6a325d8b6adccef9e4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/rdpdr/rdpdr-messages.h"
#include "channels/rdpdr/rdpdr.h"
#include "rdp.h"
#include "settings.h"
#include <freerdp/channels/rdpdr.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdlib.h>
#include <string.h>
/**
* Sends a Client Announce Reply message. The Client Announce Reply message is
* required to be sent in response to the Server Announce Request message. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/d6fe6d1b-c145-4a6f-99aa-4fe3cdcea398
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param major
* The major version of the RDPDR protocol in use. This value must always
* be 1.
*
* @param minor
* The minor version of the RDPDR protocol in use. This value must be
* either 2, 5, 10, 12, or 13.
*
* @param client_id
* The client ID received in the Server Announce Request, or a randomly
* generated ID.
*/
static void guac_rdpdr_send_client_announce_reply(guac_rdp_common_svc* svc,
unsigned int major, unsigned int minor, unsigned int client_id) {
wStream* output_stream = Stream_New(NULL, 12);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENTID_CONFIRM);
/* Write content */
Stream_Write_UINT16(output_stream, major);
Stream_Write_UINT16(output_stream, minor);
Stream_Write_UINT32(output_stream, client_id);
guac_rdp_common_svc_write(svc, output_stream);
}
/**
* Sends a Client Name Request message. The Client Name Request message is used
* by the client to announce its own name. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/902497f1-3b1c-4aee-95f8-1668f9b7b7d2
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*
* @param name
* The name that should be used for the client.
*/
static void guac_rdpdr_send_client_name_request(guac_rdp_common_svc* svc,
const char* name) {
int name_bytes = strlen(name) + 1;
wStream* output_stream = Stream_New(NULL, 16 + name_bytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_NAME);
/* Write content */
Stream_Write_UINT32(output_stream, 0); /* ASCII */
Stream_Write_UINT32(output_stream, 0); /* 0 required by RDPDR spec */
Stream_Write_UINT32(output_stream, name_bytes);
Stream_Write(output_stream, name, name_bytes);
guac_rdp_common_svc_write(svc, output_stream);
}
/**
* Sends a Client Core Capability Response message. The Client Core Capability
* Response message is used to announce the client's capabilities, in response
* to receiving the server's capabilities via a Server Core Capability Request.
* See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/f513bf87-cca0-488a-ac5c-18cf18f4a7e1
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*/
static void guac_rdpdr_send_client_capability(guac_rdp_common_svc* svc) {
wStream* output_stream = Stream_New(NULL, 256);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Sending capabilities...");
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_CAPABILITY);
/* Capability count + padding */
Stream_Write_UINT16(output_stream, 3);
Stream_Write_UINT16(output_stream, 0); /* Padding */
/* General capability header */
Stream_Write_UINT16(output_stream, CAP_GENERAL_TYPE);
Stream_Write_UINT16(output_stream, 44);
Stream_Write_UINT32(output_stream, GENERAL_CAPABILITY_VERSION_02);
/* General capability data */
Stream_Write_UINT32(output_stream, GUAC_OS_TYPE); /* osType - required to be ignored */
Stream_Write_UINT32(output_stream, 0); /* osVersion */
Stream_Write_UINT16(output_stream, 1); /* protocolMajor - must be set to 1 */
Stream_Write_UINT16(output_stream, RDPDR_MINOR_RDP_VERSION_5_2); /* protocolMinor */
Stream_Write_UINT32(output_stream, 0xFFFF); /* ioCode1 */
Stream_Write_UINT32(output_stream, 0); /* ioCode2 */
Stream_Write_UINT32(output_stream,
RDPDR_DEVICE_REMOVE_PDUS
| RDPDR_CLIENT_DISPLAY_NAME_PDU
| RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
Stream_Write_UINT32(output_stream, 0); /* extraFlags1 */
Stream_Write_UINT32(output_stream, 0); /* extraFlags2 */
Stream_Write_UINT32(output_stream, 0); /* SpecialTypeDeviceCap */
/* Printer support header */
Stream_Write_UINT16(output_stream, CAP_PRINTER_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, PRINT_CAPABILITY_VERSION_01);
/* Drive support header */
Stream_Write_UINT16(output_stream, CAP_DRIVE_TYPE);
Stream_Write_UINT16(output_stream, 8);
Stream_Write_UINT32(output_stream, DRIVE_CAPABILITY_VERSION_02);
guac_rdp_common_svc_write(svc, output_stream);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Capabilities sent.");
}
/**
* Sends a Client Device List Announce Request message. The Client Device List
* Announce Request message is used by the client to enumerate all devices
* which should be made available within the RDP session via RDPDR. See:
*
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/10ef9ada-cba2-4384-ab60-7b6290ed4a9a
*
* @param svc
* The guac_rdp_common_svc representing the static virtual channel being
* used for RDPDR.
*/
static void guac_rdpdr_send_client_device_list_announce_request(guac_rdp_common_svc* svc) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
/* Calculate number of bytes needed for the stream */
int streamBytes = 16;
for (int i=0; i < rdpdr->devices_registered; i++)
streamBytes += rdpdr->devices[i].device_announce_len;
/* Allocate the stream */
wStream* output_stream = Stream_New(NULL, streamBytes);
/* Write header */
Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE);
Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICELIST_ANNOUNCE);
/* Get the stream for each of the devices. */
Stream_Write_UINT32(output_stream, rdpdr->devices_registered);
for (int i=0; i<rdpdr->devices_registered; i++) {
Stream_Write(output_stream,
Stream_Buffer(rdpdr->devices[i].device_announce),
rdpdr->devices[i].device_announce_len);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Registered device %i (%s)",
rdpdr->devices[i].device_id, rdpdr->devices[i].device_name);
}
guac_rdp_common_svc_write(svc, output_stream);
guac_client_log(svc->client, GUAC_LOG_DEBUG, "All supported devices sent.");
}
void guac_rdpdr_process_server_announce(guac_rdp_common_svc* svc,
wStream* input_stream) {
unsigned int major, minor, client_id;
Stream_Read_UINT16(input_stream, major);
Stream_Read_UINT16(input_stream, minor);
Stream_Read_UINT32(input_stream, client_id);
/* Must choose own client ID if minor not >= 12 */
if (minor < 12)
client_id = random() & 0xFFFF;
guac_client_log(svc->client, GUAC_LOG_INFO, "Connected to RDPDR %u.%u as client 0x%04x", major, minor, client_id);
/* Respond to announce */
guac_rdpdr_send_client_announce_reply(svc, major, minor, client_id);
/* Name request */
guac_rdpdr_send_client_name_request(svc, ((guac_rdp_client*) svc->client->data)->settings->client_name);
}
void guac_rdpdr_process_clientid_confirm(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Client ID confirmed");
}
void guac_rdpdr_process_device_reply(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
unsigned int device_id, ntstatus;
int severity, c, n, facility, code;
Stream_Read_UINT32(input_stream, device_id);
Stream_Read_UINT32(input_stream, ntstatus);
severity = (ntstatus & 0xC0000000) >> 30;
c = (ntstatus & 0x20000000) >> 29;
n = (ntstatus & 0x10000000) >> 28;
facility = (ntstatus & 0x0FFF0000) >> 16;
code = ntstatus & 0x0000FFFF;
/* Log error / information */
if (device_id < rdpdr->devices_registered) {
if (severity == 0x0)
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Device %i (%s) connected successfully",
device_id, rdpdr->devices[device_id].device_name);
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Problem connecting device %i (%s): "
"severity=0x%x, c=0x%x, n=0x%x, facility=0x%x, code=0x%x",
device_id, rdpdr->devices[device_id].device_name,
severity, c, n, facility, code);
}
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: 0x%08x", device_id);
}
void guac_rdpdr_process_device_iorequest(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data;
guac_rdpdr_iorequest iorequest;
/* Read header */
Stream_Read_UINT32(input_stream, iorequest.device_id);
Stream_Read_UINT32(input_stream, iorequest.file_id);
Stream_Read_UINT32(input_stream, iorequest.completion_id);
Stream_Read_UINT32(input_stream, iorequest.major_func);
Stream_Read_UINT32(input_stream, iorequest.minor_func);
/* If printer, run printer handlers */
if (iorequest.device_id >= 0 && iorequest.device_id < rdpdr->devices_registered) {
/* Call handler on device */
guac_rdpdr_device* device = &(rdpdr->devices[iorequest.device_id]);
device->iorequest_handler(svc, device, &iorequest, input_stream);
}
else
guac_client_log(svc->client, GUAC_LOG_ERROR, "Unknown device ID: "
"0x%08x", iorequest.device_id);
}
void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc,
wStream* input_stream) {
int count;
int i;
/* Read header */
Stream_Read_UINT16(input_stream, count);
Stream_Seek(input_stream, 2);
/* Parse capabilities */
for (i=0; i<count; i++) {
int type;
int length;
Stream_Read_UINT16(input_stream, type);
Stream_Read_UINT16(input_stream, length);
/* Ignore all for now */
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring server capability set type=0x%04x, length=%i", type, length);
Stream_Seek(input_stream, length - 4);
}
/* Send own capabilities */
guac_rdpdr_send_client_capability(svc);
}
void guac_rdpdr_process_user_loggedon(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_INFO, "RDPDR user logged on");
guac_rdpdr_send_client_device_list_announce_request(svc);
}
void guac_rdpdr_process_prn_cache_data(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring printer cached configuration data");
}
void guac_rdpdr_process_prn_using_xps(guac_rdp_common_svc* svc,
wStream* input_stream) {
guac_client_log(svc->client, GUAC_LOG_WARNING, "Printer unexpectedly switched to XPS mode");
}