Coverage Report

Created: 2026-01-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/rdpear/client/rdpear_main.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Authentication redirection virtual channel
4
 *
5
 * Copyright 2023 David Fort <contact@hardening-consulting.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
#include <krb5.h>
20
#include <errno.h>
21
22
#include <winpr/assert.h>
23
#include <winpr/wtypes.h>
24
25
#include <winpr/crt.h>
26
#include <winpr/wlog.h>
27
#include <winpr/print.h>
28
#include <winpr/asn1.h>
29
#include <winpr/sspi.h>
30
#include <winpr/collections.h>
31
32
#include <rdpear-common/ndr.h>
33
#include <rdpear-common/rdpear_common.h>
34
#include <rdpear-common/rdpear_asn1.h>
35
36
#include <freerdp/config.h>
37
#include <freerdp/freerdp.h>
38
#include <freerdp/addin.h>
39
#include <freerdp/client/channels.h>
40
#include <freerdp/channels/log.h>
41
#include <freerdp/channels/rdpear.h>
42
43
0
#define TAG CHANNELS_TAG("rdpear.client")
44
45
#ifndef MAX_KEYTAB_NAME_LEN
46
#define MAX_KEYTAB_NAME_LEN 1100 /* Defined in MIT krb5.h */
47
#endif
48
49
/* defined in libkrb5 */
50
krb5_error_code encode_krb5_authenticator(const krb5_authenticator* rep, krb5_data** code_out);
51
krb5_error_code encode_krb5_ap_rep(const krb5_ap_rep* rep, krb5_data** code_out);
52
53
typedef struct
54
{
55
  GENERIC_DYNVC_PLUGIN base;
56
  rdpContext* rdp_context;
57
  krb5_context krbContext;
58
} RDPEAR_PLUGIN;
59
60
static const BYTE payloadHeader[16] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61
                                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
62
63
static krb5_error_code RPC_ENCRYPTION_KEY_to_keyblock(krb5_context ctx,
64
                                                      const KERB_RPC_ENCRYPTION_KEY* key,
65
                                                      krb5_keyblock** pkeyblock)
66
0
{
67
0
  WINPR_ASSERT(ctx);
68
0
  WINPR_ASSERT(key);
69
0
  WINPR_ASSERT(pkeyblock);
70
71
0
  if (!key->reserved3.length)
72
0
    return KRB5KDC_ERR_NULL_KEY;
73
74
0
  krb5_error_code rv =
75
0
      krb5_init_keyblock(ctx, (krb5_enctype)key->reserved2, key->reserved3.length, pkeyblock);
76
0
  if (rv)
77
0
    return rv;
78
79
0
  krb5_keyblock* keyblock = *pkeyblock;
80
0
  memcpy(keyblock->contents, key->reserved3.value, key->reserved3.length);
81
0
  return rv;
82
0
}
83
84
static krb5_error_code kerb_do_checksum(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
85
                                        krb5_keyusage kusage, krb5_cksumtype cksumtype,
86
                                        const KERB_ASN1_DATA* plain, krb5_checksum* out)
87
0
{
88
0
  WINPR_ASSERT(ctx);
89
0
  WINPR_ASSERT(key);
90
0
  WINPR_ASSERT(plain);
91
0
  WINPR_ASSERT(out);
92
93
0
  krb5_keyblock* keyblock = NULL;
94
0
  krb5_data data = { 0 };
95
96
0
  krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
97
0
  if (rv)
98
0
    return rv;
99
100
0
  data.data = (char*)plain->Asn1Buffer;
101
0
  data.length = plain->Asn1BufferHints.count;
102
103
0
  rv = krb5_c_make_checksum(ctx, cksumtype, keyblock, kusage, &data, out);
104
105
0
  krb5_free_keyblock(ctx, keyblock);
106
0
  return rv;
107
0
}
108
109
static krb5_error_code kerb_do_encrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
110
                                       krb5_keyusage kusage, const KERB_ASN1_DATA* plain,
111
                                       krb5_data* out)
112
0
{
113
0
  WINPR_ASSERT(ctx);
114
0
  WINPR_ASSERT(key);
115
0
  WINPR_ASSERT(plain);
116
0
  WINPR_ASSERT(out);
117
118
0
  krb5_keyblock* keyblock = NULL;
119
0
  krb5_data data = { 0 };
120
0
  krb5_enc_data enc = { 0 };
121
0
  size_t elen = 0;
122
123
0
  krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
124
0
  if (rv)
125
0
    return rv;
126
127
0
  data.data = (char*)plain->Asn1Buffer;
128
0
  data.length = plain->Asn1BufferHints.count;
129
130
0
  rv = krb5_c_encrypt_length(ctx, keyblock->enctype, data.length, &elen);
131
0
  if (rv)
132
0
    goto out;
133
0
  if (!elen || (elen > UINT32_MAX))
134
0
  {
135
0
    rv = KRB5_PARSE_MALFORMED;
136
0
    goto out;
137
0
  }
138
0
  enc.ciphertext.length = (unsigned int)elen;
139
0
  enc.ciphertext.data = malloc(elen);
140
0
  if (!enc.ciphertext.data)
141
0
  {
142
0
    rv = ENOMEM;
143
0
    goto out;
144
0
  }
145
146
0
  rv = krb5_c_encrypt(ctx, keyblock, kusage, NULL, &data, &enc);
147
148
0
  out->data = enc.ciphertext.data;
149
0
  out->length = enc.ciphertext.length;
150
0
out:
151
0
  krb5_free_keyblock(ctx, keyblock);
152
0
  return rv;
153
0
}
154
155
static krb5_error_code kerb_do_decrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
156
                                       krb5_keyusage kusage, const krb5_data* cipher,
157
                                       KERB_ASN1_DATA* plain)
158
0
{
159
0
  WINPR_ASSERT(ctx);
160
0
  WINPR_ASSERT(key);
161
0
  WINPR_ASSERT(cipher);
162
0
  WINPR_ASSERT(cipher->length);
163
0
  WINPR_ASSERT(plain);
164
165
0
  krb5_keyblock* keyblock = NULL;
166
0
  krb5_data data = { 0 };
167
0
  krb5_enc_data enc = { 0 };
168
169
0
  krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
170
0
  if (rv)
171
0
    return rv;
172
173
0
  enc.kvno = KRB5_PVNO;
174
0
  enc.enctype = (krb5_enctype)key->reserved2;
175
0
  enc.ciphertext.length = cipher->length;
176
0
  enc.ciphertext.data = cipher->data;
177
178
0
  data.length = cipher->length;
179
0
  data.data = (char*)malloc(cipher->length);
180
0
  if (!data.data)
181
0
  {
182
0
    rv = ENOMEM;
183
0
    goto out;
184
0
  }
185
186
0
  rv = krb5_c_decrypt(ctx, keyblock, kusage, NULL, &enc, &data);
187
188
0
  plain->Asn1Buffer = (BYTE*)data.data;
189
0
  plain->Asn1BufferHints.count = data.length;
190
0
out:
191
0
  krb5_free_keyblock(ctx, keyblock);
192
0
  return rv;
193
0
}
194
195
static BOOL rdpear_send_payload(RDPEAR_PLUGIN* rdpear, IWTSVirtualChannelCallback* pChannelCallback,
196
                                BOOL isKerb, wStream* payload)
197
0
{
198
0
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
199
0
  BOOL ret = FALSE;
200
0
  wStream* finalStream = NULL;
201
0
  SecBuffer cryptedBuffer = { 0 };
202
0
  wStream* unencodedContent = rdpear_encodePayload(isKerb, payload);
203
0
  if (!unencodedContent)
204
0
    goto out;
205
206
0
  const size_t unencodedLen = Stream_GetPosition(unencodedContent);
207
208
0
#if UINT32_MAX < SIZE_MAX
209
0
  if (unencodedLen > UINT32_MAX)
210
0
    goto out;
211
0
#endif
212
213
0
  SecBuffer inBuffer = { (ULONG)unencodedLen, SECBUFFER_DATA, Stream_Buffer(unencodedContent) };
214
215
0
  if (!freerdp_nla_encrypt(rdpear->rdp_context, &inBuffer, &cryptedBuffer))
216
0
    goto out;
217
218
0
  finalStream = Stream_New(NULL, 200);
219
0
  if (!finalStream)
220
0
    goto out;
221
0
  Stream_Write_UINT32(finalStream, 0x4EACC3C8);             /* ProtocolMagic (4 bytes) */
222
0
  Stream_Write_UINT32(finalStream, cryptedBuffer.cbBuffer); /* Length (4 bytes) */
223
0
  Stream_Write_UINT32(finalStream, 0x00000000);             /* Version (4 bytes) */
224
0
  Stream_Write_UINT32(finalStream, 0x00000000);             /* Reserved (4 bytes) */
225
0
  Stream_Write_UINT64(finalStream, 0);                      /* TsPkgContext (8 bytes) */
226
227
  /* payload */
228
0
  if (!Stream_EnsureRemainingCapacity(finalStream, cryptedBuffer.cbBuffer))
229
0
    goto out;
230
231
0
  Stream_Write(finalStream, cryptedBuffer.pvBuffer, cryptedBuffer.cbBuffer);
232
233
0
  const size_t pos = Stream_GetPosition(finalStream);
234
0
#if UINT32_MAX < SIZE_MAX
235
0
  if (pos > UINT32_MAX)
236
0
    goto out;
237
0
#endif
238
239
0
  UINT status =
240
0
      callback->channel->Write(callback->channel, (ULONG)pos, Stream_Buffer(finalStream), NULL);
241
0
  ret = (status == CHANNEL_RC_OK);
242
0
  if (!ret)
243
0
    WLog_DBG(TAG, "rdpear_send_payload=0x%x", status);
244
0
out:
245
0
  sspi_SecBufferFree(&cryptedBuffer);
246
0
  Stream_Free(unencodedContent, TRUE);
247
0
  Stream_Free(finalStream, TRUE);
248
0
  return ret;
249
0
}
250
251
static BOOL rdpear_prepare_response(NdrContext* rcontext, BOOL isKerb, UINT16 callId, UINT32 status,
252
                                    NdrContext** pwcontext, wStream* retStream)
253
0
{
254
0
  WINPR_ASSERT(rcontext);
255
0
  WINPR_ASSERT(pwcontext);
256
257
0
  BOOL ret = FALSE;
258
0
  *pwcontext = NULL;
259
0
  NdrContext* wcontext = ndr_context_copy(rcontext);
260
0
  if (!wcontext)
261
0
    return FALSE;
262
263
0
  if (!Stream_EnsureRemainingCapacity(retStream, sizeof(payloadHeader)))
264
0
    goto out;
265
266
0
  Stream_Write(retStream, payloadHeader, sizeof(payloadHeader));
267
268
0
  if (!ndr_write_header(wcontext, retStream) || !ndr_start_constructed(wcontext, retStream) ||
269
0
      !ndr_write_pickle(wcontext, retStream)) /* pickle header */
270
0
    goto out;
271
0
  if (isKerb)
272
0
  {
273
    /* for some reason there's 4 zero undocumented bytes here after the pickle record
274
     * in the kerberos package packets */
275
0
    UINT32 v = 0;
276
0
    if (!ndr_write_uint32(wcontext, retStream, v))
277
0
      goto out;
278
0
  }
279
0
  if (!ndr_write_uint16(wcontext, retStream, callId) || /* callId */
280
0
      !ndr_write_uint16(wcontext, retStream, 0x0000) || /* align padding */
281
0
      !ndr_write_uint32(wcontext, retStream, status) || /* status */
282
0
      !ndr_write_uint16(wcontext, retStream, callId) || /* callId */
283
0
      !ndr_write_uint16(wcontext, retStream, 0x0000))   /* align padding */
284
0
    goto out;
285
286
0
  *pwcontext = wcontext;
287
0
  ret = TRUE;
288
0
out:
289
0
  if (!ret)
290
0
    ndr_context_destroy(&wcontext);
291
0
  return ret;
292
0
}
293
294
static BOOL rdpear_kerb_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion)
295
0
{
296
0
  *pstatus = ERROR_INVALID_DATA;
297
298
0
  if (!ndr_read_uint32(rcontext, s, pversion))
299
0
    return TRUE;
300
301
0
  WLog_DBG(TAG, "-> KerbNegotiateVersion(v=0x%x)", *pversion);
302
0
  *pstatus = 0;
303
304
0
  return TRUE;
305
0
}
306
307
static BOOL rdpear_kerb_ComputeTgsChecksum(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
308
                                           UINT32* pstatus, KERB_ASN1_DATA* resp)
309
0
{
310
0
  ComputeTgsChecksumReq req = { 0 };
311
0
  krb5_checksum checksum = { 0 };
312
0
  wStream* asn1Payload = NULL;
313
314
0
  *pstatus = ERROR_INVALID_DATA;
315
0
  WLog_DBG(TAG, "-> ComputeTgsChecksum");
316
317
0
  if (!ndr_read_ComputeTgsChecksumReq(rcontext, s, NULL, &req) ||
318
0
      !ndr_treat_deferred_read(rcontext, s))
319
0
    goto out;
320
  // ComputeTgsChecksumReq_dump(WLog_Get(""), WLOG_DEBUG, &req);
321
322
0
  krb5_error_code rv =
323
0
      kerb_do_checksum(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
324
0
                       (krb5_cksumtype)req.ChecksumType, req.requestBody, &checksum);
325
0
  if (rv)
326
0
    goto out;
327
328
0
  asn1Payload = rdpear_enc_Checksum(req.ChecksumType, &checksum);
329
0
  if (!asn1Payload)
330
0
    goto out;
331
332
0
  resp->Pdu = 8;
333
0
  resp->Asn1Buffer = Stream_Buffer(asn1Payload);
334
0
  const size_t pos = Stream_GetPosition(asn1Payload);
335
0
  if (pos > UINT32_MAX)
336
0
    goto out;
337
0
  resp->Asn1BufferHints.count = (UINT32)pos;
338
0
  *pstatus = 0;
339
340
0
out:
341
0
  ndr_destroy_ComputeTgsChecksumReq(rcontext, NULL, &req);
342
0
  krb5_free_checksum_contents(rdpear->krbContext, &checksum);
343
0
  Stream_Free(asn1Payload, FALSE);
344
0
  return TRUE;
345
0
}
346
347
static BOOL rdpear_kerb_BuildEncryptedAuthData(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext,
348
                                               wStream* s, UINT32* pstatus, KERB_ASN1_DATA* asn1)
349
0
{
350
0
  BuildEncryptedAuthDataReq req = { 0 };
351
0
  krb5_data encrypted = { 0 };
352
0
  wStream* asn1Payload = NULL;
353
0
  krb5_error_code rv = 0;
354
355
0
  *pstatus = ERROR_INVALID_DATA;
356
0
  WLog_DBG(TAG, "-> BuildEncryptedAuthData");
357
358
0
  if (!ndr_read_BuildEncryptedAuthDataReq(rcontext, s, NULL, &req) ||
359
0
      !ndr_treat_deferred_read(rcontext, s))
360
0
    goto out;
361
362
0
  rv = kerb_do_encrypt(rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage,
363
0
                       req.PlainAuthData, &encrypted);
364
0
  if (rv)
365
0
    goto out;
366
367
  /* do the encoding */
368
0
  asn1Payload = rdpear_enc_EncryptedData(req.Key->reserved2, &encrypted);
369
0
  if (!asn1Payload)
370
0
    goto out;
371
372
  //  WLog_DBG(TAG, "rdpear_kerb_BuildEncryptedAuthData resp=");
373
  //  winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1Payload), Stream_GetPosition(asn1Payload));
374
0
  asn1->Pdu = 6;
375
0
  asn1->Asn1Buffer = Stream_Buffer(asn1Payload);
376
0
  const size_t pos = Stream_GetPosition(asn1Payload);
377
0
  if (pos > UINT32_MAX)
378
0
    goto out;
379
0
  asn1->Asn1BufferHints.count = (UINT32)pos;
380
0
  *pstatus = 0;
381
382
0
out:
383
0
  krb5_free_data_contents(rdpear->krbContext, &encrypted);
384
0
  ndr_destroy_BuildEncryptedAuthDataReq(rcontext, NULL, &req);
385
0
  Stream_Free(asn1Payload, FALSE);
386
0
  return TRUE;
387
0
}
388
389
static char* KERB_RPC_UNICODESTR_to_charptr(const RPC_UNICODE_STRING* src)
390
0
{
391
0
  WINPR_ASSERT(src);
392
0
  return ConvertWCharNToUtf8Alloc(src->Buffer, src->strLength, NULL);
393
0
}
394
395
static BOOL extractAuthData(const KERB_ASN1_DATA* src, krb5_authdata* authData, BOOL* haveData)
396
0
{
397
0
  WinPrAsn1Decoder dec = { 0 };
398
0
  WinPrAsn1Decoder dec2 = { 0 };
399
0
  WinPrAsn1Decoder dec3 = { 0 };
400
0
  WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
401
0
  BOOL error = FALSE;
402
0
  WinPrAsn1_INTEGER adType = 0;
403
0
  WinPrAsn1_OctetString os = { 0 };
404
405
0
  *haveData = FALSE;
406
0
  if (!WinPrAsn1DecReadSequence(&dec, &dec2))
407
0
    return FALSE;
408
409
0
  wStream subStream = WinPrAsn1DecGetStream(&dec2);
410
0
  if (!Stream_GetRemainingLength(&subStream))
411
0
    return TRUE;
412
413
0
  if (!WinPrAsn1DecReadSequence(&dec2, &dec3))
414
0
    return FALSE;
415
416
0
  if (!WinPrAsn1DecReadContextualInteger(&dec3, 0, &error, &adType) ||
417
0
      !WinPrAsn1DecReadContextualOctetString(&dec3, 1, &error, &os, FALSE))
418
0
    return FALSE;
419
420
0
  if (os.len > UINT32_MAX)
421
0
    return FALSE;
422
423
0
  authData->ad_type = adType;
424
0
  authData->length = (unsigned int)os.len;
425
0
  authData->contents = os.data;
426
0
  *haveData = TRUE;
427
0
  return TRUE;
428
0
}
429
430
static BOOL extractChecksum(const KERB_ASN1_DATA* src, krb5_checksum* dst)
431
0
{
432
0
  WinPrAsn1Decoder dec = { 0 };
433
0
  WinPrAsn1Decoder dec2 = { 0 };
434
0
  WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
435
0
  BOOL error = FALSE;
436
0
  WinPrAsn1_OctetString os;
437
438
0
  if (!WinPrAsn1DecReadSequence(&dec, &dec2))
439
0
    return FALSE;
440
441
0
  WinPrAsn1_INTEGER cksumtype = 0;
442
0
  if (!WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &cksumtype) ||
443
0
      !WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &os, FALSE))
444
0
    return FALSE;
445
446
0
  if (os.len > UINT32_MAX)
447
0
    return FALSE;
448
0
  dst->checksum_type = cksumtype;
449
0
  dst->length = (unsigned int)os.len;
450
0
  dst->contents = os.data;
451
0
  return TRUE;
452
0
}
453
454
0
#define FILETIME_TO_UNIX_OFFSET_S 11644473600LL
455
456
static LONGLONG krb5_time_to_FILETIME(const krb5_timestamp* ts, krb5_int32 usec)
457
0
{
458
0
  WINPR_ASSERT(ts);
459
0
  return (((*ts + FILETIME_TO_UNIX_OFFSET_S) * (1000LL * 1000LL) + usec) * 10LL);
460
0
}
461
462
static void krb5_free_principal_contents(krb5_context ctx, krb5_principal principal)
463
0
{
464
0
  WINPR_ASSERT(principal);
465
0
  krb5_free_data_contents(ctx, &principal->realm);
466
0
  krb5_free_data(ctx, principal->data);
467
0
}
468
469
static BOOL rdpear_kerb_CreateApReqAuthenticator(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext,
470
                                                 wStream* s, UINT32* pstatus,
471
                                                 CreateApReqAuthenticatorResp* resp)
472
0
{
473
0
  krb5_error_code rv = 0;
474
0
  wStream* asn1EncodedAuth = NULL;
475
0
  CreateApReqAuthenticatorReq req = { 0 };
476
0
  krb5_data authenticator = { 0 };
477
0
  krb5_data* der = NULL;
478
0
  krb5_keyblock* subkey = NULL;
479
0
  krb5_principal_data client = { 0 };
480
481
0
  *pstatus = ERROR_INVALID_DATA;
482
0
  WLog_DBG(TAG, "-> CreateApReqAuthenticator");
483
484
0
  if (!ndr_read_CreateApReqAuthenticatorReq(rcontext, s, NULL, &req) ||
485
0
      !ndr_treat_deferred_read(rcontext, s))
486
0
    goto out;
487
488
0
  krb5_authdata authdata = { 0 };
489
0
  krb5_authdata* authDataPtr[2] = { &authdata, NULL };
490
0
  BOOL haveData = 0;
491
492
0
  if (!extractAuthData(req.AuthData, &authdata, &haveData))
493
0
  {
494
0
    WLog_ERR(TAG, "error retrieving auth data");
495
0
    winpr_HexDump(TAG, WLOG_DEBUG, req.AuthData->Asn1Buffer,
496
0
                  req.AuthData->Asn1BufferHints.count);
497
0
    goto out;
498
0
  }
499
500
0
  if (req.SkewTime->QuadPart)
501
0
  {
502
0
    WLog_ERR(TAG, "!!!!! should handle SkewTime !!!!!");
503
0
  }
504
505
0
  if (req.SubKey)
506
0
  {
507
0
    rv = RPC_ENCRYPTION_KEY_to_keyblock(rdpear->krbContext, req.SubKey, &subkey);
508
0
    if (rv)
509
0
    {
510
0
      WLog_ERR(TAG, "error importing subkey");
511
0
      goto out;
512
0
    }
513
0
  }
514
515
0
  krb5_authenticator authent = { .checksum = NULL,
516
0
                               .subkey = NULL,
517
0
                               .seq_number = req.SequenceNumber,
518
0
                               .authorization_data = haveData ? authDataPtr : NULL };
519
520
0
  client.type = req.ClientName->NameType;
521
0
  if (req.ClientName->nameHints.count > INT32_MAX)
522
0
    goto out;
523
524
0
  client.length = (krb5_int32)req.ClientName->nameHints.count;
525
0
  client.data = calloc(req.ClientName->nameHints.count, sizeof(krb5_data));
526
0
  if (!client.data)
527
0
    goto out;
528
529
0
  for (int i = 0; i < client.length; i++)
530
0
  {
531
0
    krb5_data* cur = &client.data[i];
532
0
    cur->data = KERB_RPC_UNICODESTR_to_charptr(&req.ClientName->Names[i]);
533
0
    if (!cur->data)
534
0
      goto out;
535
0
    const size_t len = strnlen(cur->data, MAX_KEYTAB_NAME_LEN + 1);
536
0
    if (len > MAX_KEYTAB_NAME_LEN)
537
0
    {
538
0
      WLog_ERR(TAG,
539
0
               "Invalid ClientName length %" PRIuz
540
0
               ", limited to %u characters. ClientName: (%s)",
541
0
               MAX_KEYTAB_NAME_LEN, len, cur->data);
542
0
      goto out;
543
0
    }
544
0
    cur->length = (unsigned int)len;
545
0
  }
546
0
  client.realm.data = KERB_RPC_UNICODESTR_to_charptr(req.ClientRealm);
547
0
  if (!client.realm.data)
548
0
    goto out;
549
550
0
  const size_t len = strnlen(client.realm.data, MAX_KEYTAB_NAME_LEN + 1);
551
0
  if (len > MAX_KEYTAB_NAME_LEN)
552
0
  {
553
0
    WLog_ERR(TAG, "Invalid realm length %" PRIuz ", limited to %u characters. Realm: (%s)",
554
0
             MAX_KEYTAB_NAME_LEN, len, client.realm.data);
555
0
    goto out;
556
0
  }
557
0
  client.realm.length = (unsigned int)len;
558
0
  authent.client = &client;
559
560
0
  krb5_checksum checksum;
561
0
  krb5_checksum* pchecksum = NULL;
562
0
  if (req.GssChecksum)
563
0
  {
564
0
    if (!extractChecksum(req.GssChecksum, &checksum))
565
0
    {
566
0
      WLog_ERR(TAG, "Error getting the checksum");
567
0
      goto out;
568
0
    }
569
0
    pchecksum = &checksum;
570
0
  }
571
0
  authent.checksum = pchecksum;
572
573
0
  krb5_us_timeofday(rdpear->krbContext, &authent.ctime, &authent.cusec);
574
575
0
  rv = encode_krb5_authenticator(&authent, &der);
576
0
  if (rv)
577
0
  {
578
0
    WLog_ERR(TAG, "error encoding authenticator");
579
0
    goto out;
580
0
  }
581
582
0
  KERB_ASN1_DATA plain_authent = { .Pdu = 0,
583
0
                                 .Asn1Buffer = (BYTE*)der->data,
584
0
                                 .Asn1BufferHints = { .count = der->length } };
585
586
0
  rv = kerb_do_encrypt(rdpear->krbContext, req.EncryptionKey, (krb5_keyusage)req.KeyUsage,
587
0
                       &plain_authent, &authenticator);
588
0
  if (rv)
589
0
  {
590
0
    WLog_ERR(TAG, "error encrypting authenticator");
591
0
    goto out;
592
0
  }
593
594
0
  asn1EncodedAuth = rdpear_enc_EncryptedData(req.EncryptionKey->reserved2, &authenticator);
595
0
  if (!asn1EncodedAuth)
596
0
  {
597
0
    WLog_ERR(TAG, "error encoding to ASN1");
598
0
    rv = ENOMEM;
599
0
    goto out;
600
0
  }
601
602
  // WLog_DBG(TAG, "authenticator=");
603
  // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1EncodedAuth),
604
  // Stream_GetPosition(asn1EncodedAuth));
605
606
0
  const size_t size = Stream_GetPosition(asn1EncodedAuth);
607
0
  if (size > UINT32_MAX)
608
0
    goto out;
609
0
  resp->Authenticator.Asn1BufferHints.count = (UINT32)size;
610
0
  resp->Authenticator.Asn1Buffer = Stream_Buffer(asn1EncodedAuth);
611
0
  resp->AuthenticatorTime.QuadPart = krb5_time_to_FILETIME(&authent.ctime, authent.cusec);
612
0
  *pstatus = 0;
613
614
0
out:
615
0
  resp->Authenticator.Pdu = 6;
616
0
  resp->KerbProtocolError = rv;
617
0
  krb5_free_principal_contents(rdpear->krbContext, &client);
618
0
  krb5_free_data(rdpear->krbContext, der);
619
0
  krb5_free_data_contents(rdpear->krbContext, &authenticator);
620
0
  if (subkey)
621
0
    krb5_free_keyblock(rdpear->krbContext, subkey);
622
0
  ndr_destroy_CreateApReqAuthenticatorReq(rcontext, NULL, &req);
623
0
  Stream_Free(asn1EncodedAuth, FALSE);
624
0
  return TRUE;
625
0
}
626
627
static BOOL rdpear_findEncryptedData(const KERB_ASN1_DATA* src, int* penctype, krb5_data* data)
628
0
{
629
0
  WinPrAsn1Decoder dec = { 0 };
630
0
  WinPrAsn1Decoder dec2 = { 0 };
631
0
  WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
632
0
  BOOL error = FALSE;
633
0
  WinPrAsn1_INTEGER encType = 0;
634
0
  WinPrAsn1_OctetString os = { 0 };
635
636
0
  if (!WinPrAsn1DecReadSequence(&dec, &dec2) ||
637
0
      !WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &encType) ||
638
0
      !WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &os, FALSE))
639
0
    return FALSE;
640
641
0
  if (os.len > UINT32_MAX)
642
0
    return FALSE;
643
0
  data->data = (char*)os.data;
644
0
  data->length = (unsigned int)os.len;
645
0
  *penctype = encType;
646
0
  return TRUE;
647
0
}
648
649
static BOOL rdpear_kerb_UnpackKdcReplyBody(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
650
                                           UINT32* pstatus, UnpackKdcReplyBodyResp* resp)
651
0
{
652
0
  UnpackKdcReplyBodyReq req = { 0 };
653
654
0
  *pstatus = ERROR_INVALID_DATA;
655
656
0
  if (!ndr_read_UnpackKdcReplyBodyReq(rcontext, s, NULL, &req) ||
657
0
      !ndr_treat_deferred_read(rcontext, s))
658
0
    goto out;
659
660
0
  if (req.StrengthenKey)
661
0
  {
662
0
    WLog_ERR(TAG, "StrengthenKey not supported yet");
663
0
    goto out;
664
0
  }
665
666
0
  WLog_DBG(TAG, "-> UnpackKdcReplyBody: KeyUsage=0x%x PDU=0x%x", req.KeyUsage, req.Pdu);
667
  // WLog_DBG(TAG, "encryptedPayload=");
668
  // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedData->Asn1Buffer,
669
  // req.EncryptedData->Asn1BufferHints.count);
670
671
0
  krb5_data asn1Data = { 0 };
672
0
  int encType = 0;
673
0
  if (!rdpear_findEncryptedData(req.EncryptedData, &encType, &asn1Data) || !asn1Data.length)
674
0
    goto out;
675
676
0
  resp->KerbProtocolError = kerb_do_decrypt(
677
0
      rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, &asn1Data, &resp->ReplyBody);
678
0
  resp->ReplyBody.Pdu = req.Pdu;
679
680
0
  *pstatus = 0;
681
682
0
out:
683
0
  ndr_destroy_UnpackKdcReplyBodyReq(rcontext, NULL, &req);
684
0
  return TRUE;
685
0
}
686
687
static BOOL rdpear_kerb_DecryptApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
688
                                       UINT32* pstatus, KERB_ASN1_DATA* resp)
689
0
{
690
0
  DecryptApReplyReq req = { 0 };
691
692
0
  *pstatus = ERROR_INVALID_DATA;
693
0
  if (!ndr_read_DecryptApReplyReq(rcontext, s, NULL, &req) ||
694
0
      !ndr_treat_deferred_read(rcontext, s))
695
0
    goto out;
696
697
0
  WLog_DBG(TAG, "-> DecryptApReply");
698
  // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedReply->Asn1Buffer,
699
  // req.EncryptedReply->Asn1BufferHints.count);
700
701
0
  krb5_data asn1Data = { 0 };
702
0
  int encType = 0;
703
0
  if (!rdpear_findEncryptedData(req.EncryptedReply, &encType, &asn1Data) || !asn1Data.length)
704
0
    goto out;
705
706
0
  resp->Pdu = 0x31;
707
0
  krb5_error_code rv =
708
0
      kerb_do_decrypt(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_AP_REP_ENCPART, &asn1Data, resp);
709
0
  if (rv != 0)
710
0
  {
711
0
    WLog_ERR(TAG, "error decrypting");
712
0
    goto out;
713
0
  }
714
715
  // WLog_DBG(TAG, "response=");
716
  // winpr_HexDump(TAG, WLOG_DEBUG, resp->Asn1Buffer, resp->Asn1BufferHints.count);
717
0
  *pstatus = 0;
718
0
out:
719
0
  ndr_destroy_DecryptApReplyReq(rcontext, NULL, &req);
720
0
  return TRUE;
721
0
}
722
723
static BOOL rdpear_kerb_PackApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
724
                                    UINT32* pstatus, PackApReplyResp* resp)
725
0
{
726
0
  PackApReplyReq req = { 0 };
727
0
  krb5_data asn1Data = { 0 };
728
0
  krb5_data* out = NULL;
729
730
0
  WLog_DBG(TAG, "-> PackApReply");
731
0
  *pstatus = ERROR_INVALID_DATA;
732
0
  if (!ndr_read_PackApReplyReq(rcontext, s, NULL, &req) || !ndr_treat_deferred_read(rcontext, s))
733
0
    goto out;
734
735
0
  krb5_error_code rv = kerb_do_encrypt(rdpear->krbContext, req.SessionKey,
736
0
                                       KRB5_KEYUSAGE_AP_REP_ENCPART, req.ReplyBody, &asn1Data);
737
0
  if (rv)
738
0
    goto out;
739
740
0
  krb5_ap_rep reply;
741
0
  reply.enc_part.kvno = KRB5_PVNO;
742
0
  reply.enc_part.enctype = (krb5_enctype)req.SessionKey->reserved2;
743
0
  reply.enc_part.ciphertext.length = asn1Data.length;
744
0
  reply.enc_part.ciphertext.data = asn1Data.data;
745
746
0
  rv = encode_krb5_ap_rep(&reply, &out);
747
0
  if (rv)
748
0
    goto out;
749
750
0
  resp->PackedReply = (BYTE*)out->data;
751
0
  resp->PackedReplyHints.count = out->length;
752
0
  *pstatus = 0;
753
0
out:
754
0
  free(out);
755
0
  krb5_free_data_contents(rdpear->krbContext, &asn1Data);
756
0
  ndr_destroy_PackApReplyReq(rcontext, NULL, &req);
757
0
  return TRUE;
758
0
}
759
760
static BOOL rdpear_ntlm_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion)
761
0
{
762
0
  *pstatus = ERROR_INVALID_DATA;
763
764
0
  if (!ndr_read_uint32(rcontext, s, pversion))
765
0
    return TRUE;
766
767
0
  WLog_DBG(TAG, "-> NtlmNegotiateVersion(v=0x%x)", *pversion);
768
0
  *pstatus = 0;
769
770
0
  return TRUE;
771
0
}
772
773
static UINT rdpear_decode_payload(RDPEAR_PLUGIN* rdpear,
774
                                  IWTSVirtualChannelCallback* pChannelCallback,
775
                                  const WinPrAsn1_OctetString* packageName, wStream* s)
776
0
{
777
0
  UINT ret = ERROR_INVALID_DATA;
778
0
  NdrContext* context = NULL;
779
0
  NdrContext* wcontext = NULL;
780
0
  UINT32 status = 0;
781
782
0
  UINT32 uint32Resp = 0;
783
0
  KERB_ASN1_DATA asn1Data = { 0 };
784
0
  CreateApReqAuthenticatorResp createApReqAuthenticatorResp = { 0 };
785
0
  UnpackKdcReplyBodyResp unpackKdcReplyBodyResp = { 0 };
786
0
  PackApReplyResp packApReplyResp = { 0 };
787
0
  void* resp = NULL;
788
0
  NdrMessageType respDescr = NULL;
789
790
0
  wStream* respStream = Stream_New(NULL, 500);
791
0
  if (!respStream)
792
0
    goto out;
793
794
0
  BOOL isKerb = FALSE;
795
0
  switch (rdpear_packageType_from_name(packageName))
796
0
  {
797
0
    case RDPEAR_PACKAGE_KERBEROS:
798
0
      isKerb = TRUE;
799
0
      break;
800
0
    case RDPEAR_PACKAGE_NTLM:
801
0
      isKerb = FALSE;
802
0
      break;
803
0
    default:
804
0
      WLog_ERR(TAG, "unknown package type");
805
0
      goto out;
806
0
  }
807
808
0
  Stream_Seek(s, 16); /* skip first 16 bytes */
809
0
  wStream commandStream = { 0 };
810
0
  UINT16 callId = 0;
811
0
  UINT16 callId2 = 0;
812
813
0
  context = ndr_read_header(s);
814
0
  if (!context || !ndr_read_constructed(context, s, &commandStream) ||
815
0
      !ndr_read_pickle(context, &commandStream))
816
0
    goto out;
817
818
0
  if (isKerb)
819
0
  {
820
    /* for some reason there's 4 zero undocumented bytes here after the pickle record
821
     * in the kerberos package packets */
822
0
    UINT32 v = 0;
823
0
    if (!ndr_read_uint32(context, &commandStream, &v))
824
0
      goto out;
825
0
  }
826
827
0
  if (!ndr_read_uint16(context, &commandStream, &callId) ||
828
0
      !ndr_read_uint16(context, &commandStream, &callId2) || (callId != callId2))
829
0
    goto out;
830
831
0
  ret = CHANNEL_RC_NOT_OPEN;
832
0
  switch (callId)
833
0
  {
834
0
    case RemoteCallKerbNegotiateVersion:
835
0
      resp = &uint32Resp;
836
0
      respDescr = ndr_uint32_descr();
837
838
0
      if (rdpear_kerb_version(context, &commandStream, &status, &uint32Resp))
839
0
        ret = CHANNEL_RC_OK;
840
0
      break;
841
0
    case RemoteCallKerbCreateApReqAuthenticator:
842
0
      resp = &createApReqAuthenticatorResp;
843
0
      respDescr = ndr_CreateApReqAuthenticatorResp_descr();
844
845
0
      if (rdpear_kerb_CreateApReqAuthenticator(rdpear, context, &commandStream, &status,
846
0
                                               &createApReqAuthenticatorResp))
847
0
        ret = CHANNEL_RC_OK;
848
0
      break;
849
0
    case RemoteCallKerbDecryptApReply:
850
0
      resp = &asn1Data;
851
0
      respDescr = ndr_KERB_ASN1_DATA_descr();
852
853
0
      if (rdpear_kerb_DecryptApReply(rdpear, context, &commandStream, &status, &asn1Data))
854
0
        ret = CHANNEL_RC_OK;
855
0
      break;
856
0
    case RemoteCallKerbComputeTgsChecksum:
857
0
      resp = &asn1Data;
858
0
      respDescr = ndr_KERB_ASN1_DATA_descr();
859
860
0
      if (rdpear_kerb_ComputeTgsChecksum(rdpear, context, &commandStream, &status, &asn1Data))
861
0
        ret = CHANNEL_RC_OK;
862
0
      break;
863
0
    case RemoteCallKerbBuildEncryptedAuthData:
864
0
      resp = &asn1Data;
865
0
      respDescr = ndr_KERB_ASN1_DATA_descr();
866
867
0
      if (rdpear_kerb_BuildEncryptedAuthData(rdpear, context, &commandStream, &status,
868
0
                                             &asn1Data))
869
0
        ret = CHANNEL_RC_OK;
870
0
      break;
871
0
    case RemoteCallKerbUnpackKdcReplyBody:
872
0
      resp = &unpackKdcReplyBodyResp;
873
0
      respDescr = ndr_UnpackKdcReplyBodyResp_descr();
874
875
0
      if (rdpear_kerb_UnpackKdcReplyBody(rdpear, context, &commandStream, &status,
876
0
                                         &unpackKdcReplyBodyResp))
877
0
        ret = CHANNEL_RC_OK;
878
0
      break;
879
0
    case RemoteCallKerbPackApReply:
880
0
      resp = &packApReplyResp;
881
0
      respDescr = ndr_PackApReplyResp_descr();
882
883
0
      if (rdpear_kerb_PackApReply(rdpear, context, &commandStream, &status, &packApReplyResp))
884
0
        ret = CHANNEL_RC_OK;
885
0
      break;
886
0
    case RemoteCallNtlmNegotiateVersion:
887
0
      resp = &uint32Resp;
888
0
      respDescr = ndr_uint32_descr();
889
890
0
      if (rdpear_ntlm_version(context, &commandStream, &status, &uint32Resp))
891
0
        ret = CHANNEL_RC_OK;
892
0
      break;
893
894
0
    default:
895
0
      WLog_DBG(TAG, "Unhandled callId=0x%x", callId);
896
0
      winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(&commandStream, BYTE),
897
0
                    Stream_GetRemainingLength(&commandStream));
898
0
      break;
899
0
  }
900
901
0
  if (!rdpear_prepare_response(context, isKerb, callId, status, &wcontext, respStream))
902
0
    goto out;
903
904
0
  if (resp && respDescr)
905
0
  {
906
0
    WINPR_ASSERT(respDescr->writeFn);
907
908
0
    BOOL r = respDescr->writeFn(wcontext, respStream, NULL, resp) &&
909
0
             ndr_treat_deferred_write(wcontext, respStream);
910
911
0
    if (respDescr->destroyFn)
912
0
      respDescr->destroyFn(wcontext, NULL, resp);
913
914
0
    if (!r)
915
0
    {
916
0
      WLog_DBG(TAG, "!writeFn || !ndr_treat_deferred_write");
917
0
      goto out;
918
0
    }
919
0
  }
920
921
0
  if (!ndr_end_constructed(wcontext, respStream) ||
922
0
      !rdpear_send_payload(rdpear, pChannelCallback, isKerb, respStream))
923
0
  {
924
0
    WLog_DBG(TAG, "rdpear_send_payload !!!!!!!!");
925
0
    goto out;
926
0
  }
927
0
out:
928
0
  if (context)
929
0
    ndr_context_destroy(&context);
930
931
0
  if (wcontext)
932
0
    ndr_context_destroy(&wcontext);
933
934
0
  if (respStream)
935
0
    Stream_Free(respStream, TRUE);
936
0
  return ret;
937
0
}
938
939
static UINT rdpear_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
940
0
{
941
0
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
942
0
  WINPR_ASSERT(callback);
943
0
  UINT ret = ERROR_INVALID_DATA;
944
945
  // winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, BYTE), Stream_GetRemainingLength(s));
946
947
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
948
0
    return ERROR_INVALID_DATA;
949
950
0
  UINT32 protocolMagic = 0;
951
0
  UINT32 Length = 0;
952
0
  UINT32 Version = 0;
953
0
  Stream_Read_UINT32(s, protocolMagic);
954
0
  if (protocolMagic != 0x4EACC3C8)
955
0
    return ERROR_INVALID_DATA;
956
957
0
  Stream_Read_UINT32(s, Length);
958
959
0
  Stream_Read_UINT32(s, Version);
960
0
  if (Version != 0x00000000)
961
0
    return ERROR_INVALID_DATA;
962
963
0
  Stream_Seek(s, 4); /* Reserved (4 bytes) */
964
0
  Stream_Seek(s, 8); /* TsPkgContext (8 bytes) */
965
966
0
  if (!Stream_CheckAndLogRequiredLength(TAG, s, Length))
967
0
    return ERROR_INVALID_DATA;
968
969
0
  SecBuffer inBuffer = { Length, SECBUFFER_TOKEN, Stream_PointerAs(s, void) };
970
0
  SecBuffer decrypted = { 0 };
971
972
0
  RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)callback->plugin;
973
0
  WINPR_ASSERT(rdpear);
974
0
  if (!freerdp_nla_decrypt(rdpear->rdp_context, &inBuffer, &decrypted))
975
0
    goto out;
976
977
0
  WinPrAsn1Decoder dec = { 0 };
978
0
  WinPrAsn1Decoder dec2 = { 0 };
979
0
  wStream decodedStream = { 0 };
980
0
  Stream_StaticInit(&decodedStream, decrypted.pvBuffer, decrypted.cbBuffer);
981
0
  WinPrAsn1Decoder_Init(&dec, WINPR_ASN1_DER, &decodedStream);
982
983
0
  if (!WinPrAsn1DecReadSequence(&dec, &dec2))
984
0
    goto out;
985
986
0
  WinPrAsn1_OctetString packageName = { 0 };
987
0
  WinPrAsn1_OctetString payload = { 0 };
988
0
  BOOL error = 0;
989
0
  if (!WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &packageName, FALSE))
990
0
    goto out;
991
992
0
  if (!WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &payload, FALSE))
993
0
    goto out;
994
995
0
  wStream payloadStream = { 0 };
996
0
  Stream_StaticInit(&payloadStream, payload.data, payload.len);
997
998
0
  ret = rdpear_decode_payload(rdpear, pChannelCallback, &packageName, &payloadStream);
999
0
out:
1000
0
  sspi_SecBufferFree(&decrypted);
1001
0
  return ret;
1002
0
}
1003
1004
/**
1005
 * Function description
1006
 *
1007
 * @return 0 on success, otherwise a Win32 error code
1008
 */
1009
static UINT rdpear_on_open(IWTSVirtualChannelCallback* pChannelCallback)
1010
0
{
1011
0
  WINPR_UNUSED(pChannelCallback);
1012
0
  return CHANNEL_RC_OK;
1013
0
}
1014
1015
/**
1016
 * Function description
1017
 *
1018
 * @return 0 on success, otherwise a Win32 error code
1019
 */
1020
static UINT rdpear_on_close(IWTSVirtualChannelCallback* pChannelCallback)
1021
0
{
1022
0
  WINPR_UNUSED(pChannelCallback);
1023
0
  return CHANNEL_RC_OK;
1024
0
}
1025
1026
static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
1027
0
{
1028
0
  WINPR_ASSERT(base);
1029
1030
0
  RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base;
1031
0
  krb5_free_context(rdpear->krbContext);
1032
0
}
1033
1034
static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
1035
0
{
1036
0
  WINPR_ASSERT(base);
1037
0
  WINPR_UNUSED(settings);
1038
1039
0
  RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base;
1040
0
  rdpear->rdp_context = rcontext;
1041
0
  if (krb5_init_context(&rdpear->krbContext))
1042
0
    return CHANNEL_RC_INITIALIZATION_ERROR;
1043
0
  return CHANNEL_RC_OK;
1044
0
}
1045
1046
static const IWTSVirtualChannelCallback rdpear_callbacks = { rdpear_on_data_received,
1047
                                                           rdpear_on_open, rdpear_on_close,
1048
                                                           NULL };
1049
1050
/**
1051
 * Function description
1052
 *
1053
 * @return 0 on success, otherwise a Win32 error code
1054
 */
1055
FREERDP_ENTRY_POINT(UINT rdpear_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
1056
0
{
1057
0
  return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEAR_DVC_CHANNEL_NAME,
1058
0
                                        sizeof(RDPEAR_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
1059
0
                                        &rdpear_callbacks, init_plugin_cb, terminate_plugin_cb);
1060
0
}