Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/parallel/client/parallel_main.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Redirected Parallel Port Device Service
4
 *
5
 * Copyright 2010 O.S. Systems Software Ltda.
6
 * Copyright 2010 Eduardo Fiss Beloni <beloni@ossystems.com.br>
7
 * Copyright 2015 Thincast Technologies GmbH
8
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 *     http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22
23
#include <freerdp/config.h>
24
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
29
#include <fcntl.h>
30
#include <errno.h>
31
32
#ifndef _WIN32
33
#include <termios.h>
34
#include <strings.h>
35
#include <sys/ioctl.h>
36
#endif
37
38
#ifdef __LINUX__
39
#include <linux/ppdev.h>
40
#include <linux/parport.h>
41
#endif
42
43
#include <winpr/crt.h>
44
#include <winpr/assert.h>
45
#include <winpr/synch.h>
46
#include <winpr/thread.h>
47
#include <winpr/stream.h>
48
#include <winpr/collections.h>
49
#include <winpr/interlocked.h>
50
51
#include <freerdp/types.h>
52
#include <freerdp/freerdp.h>
53
#include <freerdp/constants.h>
54
#include <freerdp/channels/rdpdr.h>
55
#include <freerdp/channels/log.h>
56
#include <freerdp/utils/rdpdr_utils.h>
57
58
0
#define TAG CHANNELS_TAG("drive.client")
59
60
typedef struct
61
{
62
  DEVICE device;
63
64
  int file;
65
  char* path;
66
  UINT32 id;
67
68
  HANDLE thread;
69
  wMessageQueue* queue;
70
  rdpContext* rdpcontext;
71
  wLog* log;
72
} PARALLEL_DEVICE;
73
74
/**
75
 * Function description
76
 *
77
 * @return 0 on success, otherwise a Win32 error code
78
 */
79
static UINT parallel_process_irp_create(PARALLEL_DEVICE* parallel, IRP* irp)
80
0
{
81
0
  char* path = nullptr;
82
0
  UINT32 PathLength = 0;
83
84
0
  WINPR_ASSERT(parallel);
85
0
  WINPR_ASSERT(irp);
86
87
0
  if (!Stream_SafeSeek(irp->input, 28))
88
0
    return ERROR_INVALID_DATA;
89
  /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */
90
  /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */
91
0
  if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4))
92
0
    return ERROR_INVALID_DATA;
93
0
  Stream_Read_UINT32(irp->input, PathLength);
94
0
  if (PathLength < sizeof(WCHAR))
95
0
    return ERROR_INVALID_DATA;
96
0
  const WCHAR* ptr = Stream_ConstPointer(irp->input);
97
0
  if (!Stream_SafeSeek(irp->input, PathLength))
98
0
    return ERROR_INVALID_DATA;
99
0
  path = ConvertWCharNToUtf8Alloc(ptr, PathLength / sizeof(WCHAR), nullptr);
100
0
  if (!path)
101
0
    return CHANNEL_RC_NO_MEMORY;
102
103
0
  if (parallel->file >= 0)
104
0
  {
105
0
    WLog_ERR(TAG, "Duplicate create request for %s. Aborting.", path);
106
0
    free(path);
107
0
    return ERROR_INVALID_DATA;
108
0
  }
109
0
  parallel->id = irp->devman->id_sequence++;
110
0
  parallel->file = open(parallel->path, O_RDWR);
111
112
0
  if (parallel->file < 0)
113
0
  {
114
0
    irp->IoStatus = STATUS_ACCESS_DENIED;
115
0
    parallel->id = 0;
116
0
  }
117
0
  else
118
0
  {
119
    /* all read and write operations should be non-blocking */
120
0
    if (fcntl(parallel->file, F_SETFL, O_NONBLOCK) == -1)
121
0
    {
122
0
    }
123
0
  }
124
125
0
  Stream_Write_UINT32(irp->output, parallel->id);
126
0
  Stream_Write_UINT8(irp->output, 0);
127
0
  free(path);
128
0
  return CHANNEL_RC_OK;
129
0
}
130
131
/**
132
 * Function description
133
 *
134
 * @return 0 on success, otherwise a Win32 error code
135
 */
136
static UINT parallel_process_irp_close(PARALLEL_DEVICE* parallel, IRP* irp)
137
0
{
138
0
  WINPR_ASSERT(parallel);
139
0
  WINPR_ASSERT(irp);
140
141
0
  (void)close(parallel->file);
142
0
  parallel->file = -1;
143
144
0
  Stream_Zero(irp->output, 5); /* Padding(5) */
145
0
  return CHANNEL_RC_OK;
146
0
}
147
148
/**
149
 * Function description
150
 *
151
 * @return 0 on success, otherwise a Win32 error code
152
 */
153
static UINT parallel_process_irp_read(PARALLEL_DEVICE* parallel, IRP* irp)
154
0
{
155
0
  UINT32 Length = 0;
156
0
  UINT64 Offset = 0;
157
0
  ssize_t status = 0;
158
0
  BYTE* buffer = nullptr;
159
160
0
  WINPR_ASSERT(parallel);
161
0
  WINPR_ASSERT(irp);
162
163
0
  if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
164
0
    return ERROR_INVALID_DATA;
165
0
  Stream_Read_UINT32(irp->input, Length);
166
0
  Stream_Read_UINT64(irp->input, Offset);
167
0
  (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message
168
                 * ignored */
169
170
0
  if (parallel->file < 0)
171
0
  {
172
0
    WLog_ERR(TAG, "Device not open, aborting");
173
0
    return ERROR_DEVICE_NOT_AVAILABLE;
174
0
  }
175
176
0
  buffer = (BYTE*)calloc(Length, sizeof(BYTE));
177
178
0
  if (!buffer)
179
0
  {
180
0
    WLog_Print(parallel->log, WLOG_ERROR, "malloc failed!");
181
0
    return CHANNEL_RC_NO_MEMORY;
182
0
  }
183
184
0
  status = read(parallel->file, buffer, Length);
185
186
0
  if ((status < 0) || (status > UINT32_MAX))
187
0
  {
188
0
    irp->IoStatus = STATUS_UNSUCCESSFUL;
189
0
    free(buffer);
190
0
    buffer = nullptr;
191
0
    Length = 0;
192
0
  }
193
0
  else
194
0
  {
195
0
    Length = (UINT32)status;
196
0
  }
197
198
0
  Stream_Write_UINT32(irp->output, Length);
199
200
0
  if (Length > 0)
201
0
  {
202
0
    if (!Stream_EnsureRemainingCapacity(irp->output, Length))
203
0
    {
204
0
      WLog_Print(parallel->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
205
0
      free(buffer);
206
0
      return CHANNEL_RC_NO_MEMORY;
207
0
    }
208
209
0
    Stream_Write(irp->output, buffer, Length);
210
0
  }
211
212
0
  free(buffer);
213
0
  return CHANNEL_RC_OK;
214
0
}
215
216
/**
217
 * Function description
218
 *
219
 * @return 0 on success, otherwise a Win32 error code
220
 */
221
static UINT parallel_process_irp_write(PARALLEL_DEVICE* parallel, IRP* irp)
222
0
{
223
0
  UINT32 len = 0;
224
0
  UINT32 Length = 0;
225
0
  UINT64 Offset = 0;
226
227
0
  WINPR_ASSERT(parallel);
228
0
  WINPR_ASSERT(irp);
229
230
0
  if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12))
231
0
    return ERROR_INVALID_DATA;
232
233
0
  Stream_Read_UINT32(irp->input, Length);
234
0
  Stream_Read_UINT64(irp->input, Offset);
235
0
  (void)Offset; /* [MS-RDPESP] 3.2.5.1.5 Processing a Server Write Request Message
236
                 * ignore offset */
237
0
  if (!Stream_SafeSeek(irp->input, 20)) /* Padding */
238
0
    return ERROR_INVALID_DATA;
239
0
  const void* ptr = Stream_ConstPointer(irp->input);
240
0
  if (!Stream_SafeSeek(irp->input, Length))
241
0
    return ERROR_INVALID_DATA;
242
0
  len = Length;
243
244
0
  if (parallel->file < 0)
245
0
  {
246
0
    WLog_ERR(TAG, "Device not open, aborting");
247
0
    return ERROR_DEVICE_NOT_AVAILABLE;
248
0
  }
249
0
  while (len > 0)
250
0
  {
251
0
    const ssize_t status = write(parallel->file, ptr, len);
252
253
0
    if ((status < 0) || (status > len))
254
0
    {
255
0
      irp->IoStatus = STATUS_UNSUCCESSFUL;
256
0
      Length = 0;
257
0
      break;
258
0
    }
259
260
0
    Stream_Seek(irp->input, WINPR_ASSERTING_INT_CAST(size_t, status));
261
0
    len -= status;
262
0
  }
263
264
0
  Stream_Write_UINT32(irp->output, Length);
265
0
  Stream_Write_UINT8(irp->output, 0); /* Padding */
266
0
  return CHANNEL_RC_OK;
267
0
}
268
269
/**
270
 * Function description
271
 *
272
 * @return 0 on success, otherwise a Win32 error code
273
 */
274
static UINT parallel_process_irp_device_control(WINPR_ATTR_UNUSED PARALLEL_DEVICE* parallel,
275
                                                IRP* irp)
276
0
{
277
0
  WINPR_ASSERT(parallel);
278
0
  WINPR_ASSERT(irp);
279
280
0
  Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */
281
0
  return CHANNEL_RC_OK;
282
0
}
283
284
/**
285
 * Function description
286
 *
287
 * @return 0 on success, otherwise a Win32 error code
288
 */
289
290
static UINT parallel_eval(UINT error, IRP* irp)
291
0
{
292
0
  WINPR_ASSERT(irp);
293
0
  if (error == CHANNEL_RC_OK)
294
0
  {
295
0
    WINPR_ASSERT(irp->Complete);
296
0
    return irp->Complete(irp);
297
0
  }
298
299
0
  WLog_ERR(TAG, "IRP %s failed with %" PRIu32, rdpdr_irp_string(irp->MajorFunction), error);
300
0
  WINPR_ASSERT(irp->Discard);
301
0
  irp->Discard(irp);
302
0
  return error;
303
0
}
304
305
static UINT parallel_process_irp(PARALLEL_DEVICE* parallel, IRP* irp)
306
0
{
307
0
  UINT error = ERROR_INTERNAL_ERROR;
308
309
0
  WINPR_ASSERT(parallel);
310
0
  WINPR_ASSERT(irp);
311
312
0
  switch (irp->MajorFunction)
313
0
  {
314
0
    case IRP_MJ_CREATE:
315
0
      error = parallel_process_irp_create(parallel, irp);
316
0
      break;
317
318
0
    case IRP_MJ_CLOSE:
319
0
      error = parallel_process_irp_close(parallel, irp);
320
0
      break;
321
322
0
    case IRP_MJ_READ:
323
0
      error = parallel_process_irp_read(parallel, irp);
324
0
      break;
325
326
0
    case IRP_MJ_WRITE:
327
0
      error = parallel_process_irp_write(parallel, irp);
328
0
      break;
329
330
0
    case IRP_MJ_DEVICE_CONTROL:
331
0
      error = parallel_process_irp_device_control(parallel, irp);
332
0
      break;
333
334
0
    default:
335
0
      irp->IoStatus = STATUS_NOT_SUPPORTED;
336
0
      error = CHANNEL_RC_OK;
337
0
      break;
338
0
  }
339
340
0
  error = parallel_eval(error, irp);
341
342
0
  DWORD level = WLOG_TRACE;
343
0
  if (error)
344
0
    level = WLOG_WARN;
345
346
0
  WLog_Print(parallel->log, level,
347
0
             "[%s|0x%08" PRIx32 "] completed with %s [0x%08" PRIx32 "] (IoStatus %s [0x%08" PRIx32
348
0
             "])",
349
0
             rdpdr_irp_string(irp->MajorFunction), irp->MajorFunction, WTSErrorToString(error),
350
0
             error, NtStatus2Tag(irp->IoStatus), WINPR_CXX_COMPAT_CAST(UINT32, irp->IoStatus));
351
352
0
  return error;
353
0
}
354
355
static DWORD WINAPI parallel_thread_func(LPVOID arg)
356
0
{
357
0
  PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)arg;
358
0
  UINT error = CHANNEL_RC_OK;
359
360
0
  WINPR_ASSERT(parallel);
361
0
  while (1)
362
0
  {
363
0
    if (!MessageQueue_Wait(parallel->queue))
364
0
    {
365
0
      WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Wait failed!");
366
0
      error = ERROR_INTERNAL_ERROR;
367
0
      break;
368
0
    }
369
370
0
    wMessage message = WINPR_C_ARRAY_INIT;
371
0
    if (!MessageQueue_Peek(parallel->queue, &message, TRUE))
372
0
    {
373
0
      WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Peek failed!");
374
0
      error = ERROR_INTERNAL_ERROR;
375
0
      break;
376
0
    }
377
378
0
    if (message.id == WMQ_QUIT)
379
0
      break;
380
381
0
    IRP* irp = (IRP*)message.wParam;
382
383
0
    error = parallel_process_irp(parallel, irp);
384
0
    if (error)
385
0
    {
386
0
      WLog_Print(parallel->log, WLOG_ERROR,
387
0
                 "parallel_process_irp failed with error %" PRIu32 "!", error);
388
0
      break;
389
0
    }
390
0
  }
391
392
0
  if (error && parallel->rdpcontext)
393
0
    setChannelError(parallel->rdpcontext, error, "parallel_thread_func reported an error");
394
395
0
  ExitThread(error);
396
0
  return error;
397
0
}
398
399
/**
400
 * Function description
401
 *
402
 * @return 0 on success, otherwise a Win32 error code
403
 */
404
static UINT parallel_irp_request(DEVICE* device, IRP* irp)
405
0
{
406
0
  PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)device;
407
408
0
  WINPR_ASSERT(parallel);
409
410
0
  if (!MessageQueue_Post(parallel->queue, nullptr, 0, (void*)irp, nullptr))
411
0
  {
412
0
    WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Post failed!");
413
0
    irp->Discard(irp);
414
0
    return ERROR_INTERNAL_ERROR;
415
0
  }
416
417
0
  return CHANNEL_RC_OK;
418
0
}
419
420
/**
421
 * Function description
422
 *
423
 * @return 0 on success, otherwise a Win32 error code
424
 */
425
static UINT parallel_free_int(PARALLEL_DEVICE* parallel)
426
0
{
427
0
  if (parallel)
428
0
  {
429
0
    if (!MessageQueue_PostQuit(parallel->queue, 0) ||
430
0
        (WaitForSingleObject(parallel->thread, INFINITE) == WAIT_FAILED))
431
0
    {
432
0
      const UINT error = GetLastError();
433
0
      WLog_Print(parallel->log, WLOG_ERROR,
434
0
                 "WaitForSingleObject failed with error %" PRIu32 "!", error);
435
0
    }
436
437
0
    (void)CloseHandle(parallel->thread);
438
0
    Stream_Free(parallel->device.data, TRUE);
439
0
    MessageQueue_Free(parallel->queue);
440
0
  }
441
0
  free(parallel);
442
0
  return CHANNEL_RC_OK;
443
0
}
444
445
static UINT parallel_free(DEVICE* device)
446
0
{
447
0
  if (device)
448
0
    return parallel_free_int((PARALLEL_DEVICE*)device);
449
0
  return CHANNEL_RC_OK;
450
0
}
451
452
static void parallel_message_free(void* obj)
453
0
{
454
0
  wMessage* msg = obj;
455
0
  if (!msg)
456
0
    return;
457
0
  if (msg->id != 0)
458
0
    return;
459
460
0
  IRP* irp = (IRP*)msg->wParam;
461
0
  if (!irp)
462
0
    return;
463
0
  WINPR_ASSERT(irp->Discard);
464
0
  irp->Discard(irp);
465
0
}
466
467
/**
468
 * Function description
469
 *
470
 * @return 0 on success, otherwise a Win32 error code
471
 */
472
FREERDP_ENTRY_POINT(
473
    UINT VCAPITYPE parallel_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
474
0
{
475
0
  PARALLEL_DEVICE* parallel = nullptr;
476
0
  UINT error = 0;
477
478
0
  WINPR_ASSERT(pEntryPoints);
479
480
0
  RDPDR_PARALLEL* device = (RDPDR_PARALLEL*)pEntryPoints->device;
481
0
  WINPR_ASSERT(device);
482
483
0
  wLog* log = WLog_Get(TAG);
484
0
  WINPR_ASSERT(log);
485
486
0
  char* name = device->device.Name;
487
0
  char* path = device->Path;
488
489
0
  if (!name || (name[0] == '*') || !path)
490
0
  {
491
    /* TODO: implement auto detection of parallel ports */
492
0
    WLog_Print(log, WLOG_WARN, "Autodetection not implemented, no ports will be redirected");
493
0
    return CHANNEL_RC_INITIALIZATION_ERROR;
494
0
  }
495
496
0
  if (name[0] && path[0])
497
0
  {
498
0
    parallel = (PARALLEL_DEVICE*)calloc(1, sizeof(PARALLEL_DEVICE));
499
500
0
    if (!parallel)
501
0
    {
502
0
      WLog_Print(log, WLOG_ERROR, "calloc failed!");
503
0
      return CHANNEL_RC_NO_MEMORY;
504
0
    }
505
506
0
    parallel->file = -1;
507
0
    parallel->log = log;
508
0
    parallel->device.type = RDPDR_DTYP_PARALLEL;
509
0
    parallel->device.name = name;
510
0
    parallel->device.IRPRequest = parallel_irp_request;
511
0
    parallel->device.Free = parallel_free;
512
0
    parallel->rdpcontext = pEntryPoints->rdpcontext;
513
0
    const size_t length = strlen(name);
514
0
    parallel->device.data = Stream_New(nullptr, length + 1);
515
516
0
    if (!parallel->device.data)
517
0
    {
518
0
      WLog_Print(parallel->log, WLOG_ERROR, "Stream_New failed!");
519
0
      error = CHANNEL_RC_NO_MEMORY;
520
0
      goto error_out;
521
0
    }
522
523
0
    for (size_t i = 0; i <= length; i++)
524
0
      Stream_Write_INT8(parallel->device.data, name[i] < 0 ? '_' : name[i]);
525
526
0
    parallel->path = path;
527
0
    parallel->queue = MessageQueue_New(nullptr);
528
529
0
    if (!parallel->queue)
530
0
    {
531
0
      WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_New failed!");
532
0
      error = CHANNEL_RC_NO_MEMORY;
533
0
      goto error_out;
534
0
    }
535
536
0
    wObject* obj = MessageQueue_Object(parallel->queue);
537
0
    WINPR_ASSERT(obj);
538
0
    obj->fnObjectFree = parallel_message_free;
539
540
0
    error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &parallel->device);
541
0
    if (error)
542
0
    {
543
0
      WLog_Print(parallel->log, WLOG_ERROR, "RegisterDevice failed with error %" PRIu32 "!",
544
0
                 error);
545
0
      goto error_out;
546
0
    }
547
548
0
    parallel->thread = CreateThread(nullptr, 0, parallel_thread_func, parallel, 0, nullptr);
549
0
    if (!parallel->thread)
550
0
    {
551
0
      WLog_Print(parallel->log, WLOG_ERROR, "CreateThread failed!");
552
0
      error = ERROR_INTERNAL_ERROR;
553
0
      goto error_out;
554
0
    }
555
0
  }
556
557
0
  return CHANNEL_RC_OK;
558
0
error_out:
559
0
  parallel_free_int(parallel);
560
0
  return error;
561
0
}