1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include "channels/common-svc.h"
21 #include "channels/pipe-svc.h"
22 #include "common/list.h"
23 #include "rdp.h"
24 
25 #include <freerdp/settings.h>
26 #include <guacamole/client.h>
27 #include <guacamole/protocol.h>
28 #include <guacamole/socket.h>
29 #include <guacamole/stream.h>
30 #include <guacamole/user.h>
31 #include <winpr/stream.h>
32 
33 #include <stdlib.h>
34 #include <string.h>
35 
guac_rdp_pipe_svc_send_pipe(guac_socket * socket,guac_rdp_pipe_svc * pipe_svc)36 void guac_rdp_pipe_svc_send_pipe(guac_socket* socket, guac_rdp_pipe_svc* pipe_svc) {
37 
38     /* Send pipe instruction for the SVC's output stream */
39     guac_protocol_send_pipe(socket, pipe_svc->output_pipe,
40             "application/octet-stream", pipe_svc->svc->name);
41 
42 }
43 
guac_rdp_pipe_svc_send_pipes(guac_user * user)44 void guac_rdp_pipe_svc_send_pipes(guac_user* user) {
45 
46     guac_client* client = user->client;
47     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
48 
49     guac_common_list_lock(rdp_client->available_svc);
50 
51     /* Send pipe for each allocated SVC's output stream */
52     guac_common_list_element* current = rdp_client->available_svc->head;
53     while (current != NULL) {
54         guac_rdp_pipe_svc_send_pipe(user->socket, (guac_rdp_pipe_svc*) current->data);
55         current = current->next;
56     }
57 
58     guac_common_list_unlock(rdp_client->available_svc);
59 
60 }
61 
guac_rdp_pipe_svc_add(guac_client * client,guac_rdp_pipe_svc * pipe_svc)62 void guac_rdp_pipe_svc_add(guac_client* client, guac_rdp_pipe_svc* pipe_svc) {
63 
64     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
65 
66     /* Add to list of available SVC */
67     guac_common_list_lock(rdp_client->available_svc);
68     guac_common_list_add(rdp_client->available_svc, pipe_svc);
69     guac_common_list_unlock(rdp_client->available_svc);
70 
71 }
72 
guac_rdp_pipe_svc_get(guac_client * client,const char * name)73 guac_rdp_pipe_svc* guac_rdp_pipe_svc_get(guac_client* client, const char* name) {
74 
75     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
76     guac_common_list_element* current;
77     guac_rdp_pipe_svc* found = NULL;
78 
79     /* For each available SVC */
80     guac_common_list_lock(rdp_client->available_svc);
81     current = rdp_client->available_svc->head;
82     while (current != NULL) {
83 
84         /* If name matches, found */
85         guac_rdp_pipe_svc* current_svc = (guac_rdp_pipe_svc*) current->data;
86         if (strcmp(current_svc->svc->name, name) == 0) {
87             found = current_svc;
88             break;
89         }
90 
91         current = current->next;
92 
93     }
94     guac_common_list_unlock(rdp_client->available_svc);
95 
96     return found;
97 
98 }
99 
guac_rdp_pipe_svc_remove(guac_client * client,const char * name)100 guac_rdp_pipe_svc* guac_rdp_pipe_svc_remove(guac_client* client, const char* name) {
101 
102     guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
103     guac_common_list_element* current;
104     guac_rdp_pipe_svc* found = NULL;
105 
106     /* For each available SVC */
107     guac_common_list_lock(rdp_client->available_svc);
108     current = rdp_client->available_svc->head;
109     while (current != NULL) {
110 
111         /* If name matches, remove entry */
112         guac_rdp_pipe_svc* current_svc = (guac_rdp_pipe_svc*) current->data;
113         if (strcmp(current_svc->svc->name, name) == 0) {
114             guac_common_list_remove(rdp_client->available_svc, current);
115             found = current_svc;
116             break;
117         }
118 
119         current = current->next;
120 
121     }
122     guac_common_list_unlock(rdp_client->available_svc);
123 
124     /* Return removed entry, if any */
125     return found;
126 
127 }
128 
guac_rdp_pipe_svc_pipe_handler(guac_user * user,guac_stream * stream,char * mimetype,char * name)129 int guac_rdp_pipe_svc_pipe_handler(guac_user* user, guac_stream* stream,
130         char* mimetype, char* name) {
131 
132     guac_rdp_pipe_svc* pipe_svc = guac_rdp_pipe_svc_get(user->client, name);
133 
134     /* Fail if no such SVC */
135     if (pipe_svc == NULL) {
136         guac_user_log(user, GUAC_LOG_WARNING, "User requested non-existent "
137                 "pipe (no such SVC configured): \"%s\"", name);
138         guac_protocol_send_ack(user->socket, stream, "FAIL (NO SUCH PIPE)",
139                 GUAC_PROTOCOL_STATUS_CLIENT_BAD_REQUEST);
140         guac_socket_flush(user->socket);
141         return 0;
142     }
143     else
144         guac_user_log(user, GUAC_LOG_DEBUG, "Inbound half of channel \"%s\" "
145                 "connected.", name);
146 
147     /* Init stream data */
148     stream->data = pipe_svc;
149     stream->blob_handler = guac_rdp_pipe_svc_blob_handler;
150 
151     return 0;
152 
153 }
154 
guac_rdp_pipe_svc_blob_handler(guac_user * user,guac_stream * stream,void * data,int length)155 int guac_rdp_pipe_svc_blob_handler(guac_user* user, guac_stream* stream,
156         void* data, int length) {
157 
158     guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) stream->data;
159 
160     /* Write blob data to SVC directly */
161     wStream* output_stream = Stream_New(NULL, length);
162     Stream_Write(output_stream, data, length);
163     guac_rdp_common_svc_write(pipe_svc->svc, output_stream);
164 
165     guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
166             GUAC_PROTOCOL_STATUS_SUCCESS);
167     guac_socket_flush(user->socket);
168     return 0;
169 
170 }
171 
guac_rdp_pipe_svc_process_connect(guac_rdp_common_svc * svc)172 void guac_rdp_pipe_svc_process_connect(guac_rdp_common_svc* svc) {
173 
174     /* Associate SVC with new Guacamole pipe */
175     guac_rdp_pipe_svc* pipe_svc = malloc(sizeof(guac_rdp_pipe_svc));
176     pipe_svc->svc = svc;
177     pipe_svc->output_pipe = guac_client_alloc_stream(svc->client);
178     svc->data = pipe_svc;
179 
180     /* SVC may now receive data from client */
181     guac_rdp_pipe_svc_add(svc->client, pipe_svc);
182 
183     /* Notify of pipe's existence */
184     guac_rdp_pipe_svc_send_pipe(svc->client->socket, pipe_svc);
185 
186 }
187 
guac_rdp_pipe_svc_process_receive(guac_rdp_common_svc * svc,wStream * input_stream)188 void guac_rdp_pipe_svc_process_receive(guac_rdp_common_svc* svc,
189         wStream* input_stream) {
190 
191     guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) svc->data;
192 
193     /* Fail if output not created */
194     if (pipe_svc->output_pipe == NULL) {
195         guac_client_log(svc->client, GUAC_LOG_WARNING, "%i bytes of data "
196                 "received from within the remote desktop session for SVC "
197                 "\"%s\" are being dropped because the outbound pipe stream "
198                 "for that SVC is not yet open. This should NOT happen.",
199                 Stream_Length(input_stream), svc->name);
200         return;
201     }
202 
203     /* Send received data as blob */
204     guac_protocol_send_blob(svc->client->socket, pipe_svc->output_pipe, Stream_Buffer(input_stream), Stream_Length(input_stream));
205     guac_socket_flush(svc->client->socket);
206 
207 }
208 
guac_rdp_pipe_svc_process_terminate(guac_rdp_common_svc * svc)209 void guac_rdp_pipe_svc_process_terminate(guac_rdp_common_svc* svc) {
210 
211     guac_rdp_pipe_svc* pipe_svc = (guac_rdp_pipe_svc*) svc->data;
212     if (pipe_svc == NULL)
213         return;
214 
215     /* Remove and free SVC */
216     guac_rdp_pipe_svc_remove(svc->client, svc->name);
217     free(pipe_svc);
218 
219 }
220 
guac_rdp_pipe_svc_load_plugin(rdpContext * context,char * name)221 void guac_rdp_pipe_svc_load_plugin(rdpContext* context, char* name) {
222 
223     /* Attempt to load support for static channel */
224     guac_rdp_common_svc_load_plugin(context, name, CHANNEL_OPTION_COMPRESS_RDP,
225             guac_rdp_pipe_svc_process_connect,
226             guac_rdp_pipe_svc_process_receive,
227             guac_rdp_pipe_svc_process_terminate);
228 
229 }
230 
231