-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME
More file actions
689 lines (593 loc) · 25 KB
/
Copy pathREADME
File metadata and controls
689 lines (593 loc) · 25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
Qore ssh module
INTRODUCTION
------------
This repository contains a new Qore SSH module based on libssh.
Unlike the existing ssh2 module, this module is intended primarily for
server-side SSH and SFTP use cases with controlled command processing,
auditable file transfer workflows, and generic integration scenarios.
This module requires Qore 2.3 or newer and the `sshutil` binary module. The
`sshutil` module provides shared abstract SSH provider interfaces used by the
server host-key provider API.
STATUS
------
This repository now includes working SSH/SFTP server foundations plus generic
virtual filesystem helpers for SFTP testing and backend virtualization.
The design principles are:
- consistent CamelCase naming for public classes and namespaces
- server-side SSH and SFTP focus
- local configuration and multi-listener support
- pluggable authentication, sandboxing, and instrumentation
- secure and auditable actions
- generic command and file integration APIs
The current API shape is documented in:
design/api-spec.md
The focused generic ConnectionProvider/DataProvider design note is:
design/data-provider-infrastructure.md
Boundary note:
- generic provider infrastructure belongs in this repo
- application-specific adapters and workflows do not
GENERIC CONNECTIONPROVIDER MODULE
---------------------------------
The repo now includes a real `SshServerConnections` qlib module for generic
`ConnectionProvider`-style local server definitions.
Current connection classes:
- `SshServerConnections::AbstractSshServerConnection`
- `SshServerConnections::SshCommandServerConnection`
- `SshServerConnections::SftpServerConnection`
The command side now also has a generic virtualization helper:
- `SshServerCommandProvider::VirtualSshCommandService`
Command-side boundary:
- command virtualization is exposed through `SshCommandServerConnection` plus
`VirtualSshCommandService`
- no generic command `DataProvider` is provided in this repo
- `DataProvider` support is reserved for the SFTP/file-exchange side where the
abstraction is a natural fit
Typed command contract:
- request: `Qore::Ssh::SshCommandRequest`
- stable fields include `session_id`, `server_name`, `listener_name`,
`username`, `principal`, `command`, `arguments`, `environment`, and
`metadata`
- `metadata.request_type` distinguishes `exec` from `subsystem`
- `metadata.subsystem` carries the subsystem name when applicable
- result: `Qore::Ssh::SshCommandResult`
- stable fields include `accepted`, `exit_status`, `message`, `stdout`,
`stderr`, and `result_data`
- higher-level integration payloads should go in `result_data`
Virtual command-service behavior:
- all policy and audit controls are optional; basic usage remains
`VirtualSshCommandService service();`
- supports static and closure-backed exec handlers
- supports static and closure-backed subsystem handlers
- supports exact-name allow/deny policy for commands and subsystems
- supports principal-aware policy overrides with `setPrincipalPolicy()`
- request capture can be bounded with `request_history_limit`
- `request_history_limit = 0` disables capture and clears retained history
- `capture_environment = False` redacts `environment` from captured requests
- `capture_arguments = False` omits structured `arguments` from captured requests
- `capture_identity = False` omits `username` and `principal` from captured requests
- `captured_string_limit` truncates captured audit strings without affecting live
command dispatch
- `max_concurrent_requests` optionally caps active virtual command dispatches
- `max_concurrent_requests_per_principal` optionally caps active virtual command
dispatches per authenticated principal
- policy-denied requests return `exit_status = 126` with reason codes:
- `command-policy-denied`
- `subsystem-policy-denied`
- `command-concurrency-above-maximum`
- `principal-command-concurrency-above-maximum`
- advanced command-side features are exposed through typed
`VirtualSshCommandServiceOptions` options and runtime setters rather than
`DataProvider` action options
Current registered schemes:
- `sshsrv`
- `sshserver`
- `sftpsrv`
- `sftpserver`
These connections return live `Qore::Ssh::SshServer` objects. Calling
`get(True)` starts the server immediately. The connection URL defines the bind
address and port for a single listener, and the connection options project to
typed `Qore::Ssh::SshServerConfig` / `Qore::Ssh::SshListenerConfig` hashes via:
- `getServerConfig()`
- `getListenerConfig()`
Listener-level operational controls include:
- grouped `limits.max_sessions` for a deterministic cap on concurrently
accepted sessions per listener
- grouped `services.enabled_auth_methods`,
`services.allow_command_service`, and `services.allow_sftp_service`
- grouped `auth` for config-driven authentication, or `auth_provider` for
user-defined provider objects implementing
`authenticate(hash<Qore::Ssh::SshAuthContextInfo>)`
Logging note:
- connection helpers, the built-in auth provider, the virtual command service,
and the virtual SFTP helper/exchange objects all accept
`Qore::Logger::LoggerInterface`
- accepted auth and command outcomes log at `info`
- rejected auth and command outcomes log at `warn`
- detailed auth attempts and virtual filesystem churn log at `detail`
- configuration-oriented command-service messages log at `debug`
- event-listener callback failures log at `error`
Minimal example:
```qore
SshServerConnections::SftpServerConnection conn({
"name": "local-sftp",
"url": "sftpsrv://127.0.0.1:0",
"opts": {
"host_keys": ("/path/to/ssh_host_ed25519_key",),
"limits": <SshServerConnections::SshServerConnectionLimits>{
"max_sessions": 8,
},
"services": <SshServerConnections::SshServerConnectionServices>{
"enabled_auth_methods": ("password",),
"allow_sftp_service": True,
"allow_command_service": False,
},
"auth": <SshServerAuthProvider::VirtualSshAuthProviderOptions>{
"methods": ("password",),
"password": <SshServerAuthProvider::VirtualSshPasswordAuthOptions>{
"users": (
<SshServerAuthProvider::VirtualSshPasswordUserInfo>{
"username": "demo",
"password": "secret",
"decision": <SshServerAuthProvider::VirtualSshAuthDecisionTemplate>{
"principal": "principal:demo",
},
},
),
},
},
},
});
Qore::Ssh::SshServer server = cast<Qore::Ssh::SshServer>(conn.get());
hash<Qore::Ssh::SshServerStatus> status = server.getStatus();
server.stop();
```
Virtual command example:
```qore
SshServerCommandProvider::VirtualSshCommandService service();
service.addTextCommand("status", "ok\n", "warn\n", 23, True, {"mode": "virtual"});
SshServerConnections::SshCommandServerConnection conn({
"name": "local-ssh-command",
"url": "sshserver://127.0.0.1:0",
"opts": {
"host_keys": ("/path/to/ssh_host_ed25519_key",),
"command_backend": service,
},
});
```
Advanced policy/audit options remain optional:
```qore
SshServerCommandProvider::VirtualSshCommandService service(
<SshServerCommandProvider::VirtualSshCommandServiceOptions>{
"policy": <SshServerCommandProvider::VirtualSshCommandPolicy>{
"allow_all_commands": True,
"allow_all_subsystems": True,
},
"audit": <SshServerCommandProvider::VirtualSshCommandAuditOptions>{
"request_history_limit": 2,
"capture_environment": False,
"capture_arguments": True,
"capture_identity": True,
"captured_string_limit": 64,
},
"limits": <SshServerCommandProvider::VirtualSshCommandLimits>{
"max_concurrent_requests": 8,
"max_concurrent_requests_per_principal": 1,
},
}
);
service.setPrincipalPolicy("principal:nobody", <SshServerCommandProvider::VirtualSshCommandPrincipalPolicy>{
"allow_all_commands": False,
"allow_all_subsystems": False,
"allowed_commands": ("status",),
"allowed_subsystems": ("metrics",),
});
service.addTextCommand("status", "ok\n", "warn\n", 23, True, {"mode": "virtual"});
service.addTextSubsystem("metrics", "metrics:ok\n", NOTHING, 0, True, {"mode": "subsystem"});
service.setAuditOptions(<SshServerCommandProvider::VirtualSshCommandAuditOptions>{
"capture_arguments": False,
"captured_string_limit": 64,
});
```
The grouped `policy`, `audit`, and `limits` hashes are the recommended
user-friendly surface for advanced controls. Individual runtime setters remain
available for direct targeted changes.
The grouped runtime setters replace their whole domain deterministically:
- `setPolicy()` replaces command/subsystem allow/deny state
- `setAuditOptions()` replaces retained-audit capture settings
- `setLimits()` replaces active-dispatch concurrency caps
- `clearPolicy()`, `clearAuditOptions()`, and `clearLimits()` restore defaults
- `getPolicy()`, `getAuditOptions()`, and `getLimits()` return typed snapshots
for inspection or higher-level configuration mapping
A runnable command-side example is also available in:
- `examples/VirtualSshCommandServer.qr`
- `examples/VirtualSshCommandLiveServer.qr`
Local preflight:
```bash
test/preflight.sh
```
This runs the local build, the core qtest sweep, the live examples, and the
docs build against `QORE_MODULE_DIR=build`.
GENERIC VIRTUAL SFTP HELPERS
----------------------------
The repo includes generic qlib helpers for virtual SFTP backends:
- `SftpServerDataProvider::VirtualSftpFilesystem`
- `SftpServerDataProvider::VirtualSftpExchange`
- `SftpServerDataProvider::VirtualSftpExchangeRecordStore`
These helpers are generic and test-focused. They do not contain
application-specific logic.
The helper layer now exposes strong typing for its user-facing results:
- `SftpServerDataProvider::VirtualSftpFilesystemOptions`
- `SftpServerDataProvider::VirtualSftpExchangeOptions`
- `SftpServerDataProvider::VirtualSftpPathRequest`
- `SftpServerDataProvider::VirtualSftpSubmissionRequest`
- `SftpServerDataProvider::VirtualSftpCompletionRequest`
- `SftpServerDataProvider::VirtualSftpRecordRequest`
- `SftpServerDataProvider::VirtualSftpRecordSubmissionRequest`
- `SftpServerDataProvider::VirtualSftpRecordCompletionRequest`
- `SftpServerDataProvider::VirtualSftpInboundPolicy`
- `SftpServerDataProvider::VirtualSftpFileSnapshot`
- `SftpServerDataProvider::VirtualSftpSnapshot`
- `SftpServerDataProvider::VirtualSftpEntryInfo`
- `SftpServerDataProvider::VirtualSftpRecordInfo`
This gives callers stable typed shapes for constructor options, helper
requests, helper results, backend request/result overloads, snapshots,
listings, and record-style queue operations, while still allowing
intentionally dynamic backend objects where that flexibility is needed.
Built-in helper-managed flags are now also projected into a typed
`VirtualSftpBuiltinState` on snapshot/stat/listing results. That covers stable
state such as archive/claim markers and exchange roles, while `metadata`
remains available for application-defined payloads like `kind`.
For exchange completion-style operations, `VirtualSftpRecordInfo.path` may be
`NOTHING` for delete results where no final path remains.
Recommended usage is to prefer the typed request hashes even for helper calls.
The virtual filesystem and exchange backends now also expose typed overloads
for the backend verbs using `Qore::Ssh::SftpPathRequest`,
`Qore::Ssh::SftpOpenRequest`, `Qore::Ssh::SftpRenameRequest`,
`Qore::Ssh::SftpReadRequest`, `Qore::Ssh::SftpReadResult`,
`Qore::Ssh::SftpWriteRequest`, `Qore::Ssh::SftpCloseRequest`,
`Qore::Ssh::SftpPathInfo`, `Qore::Ssh::SftpListResult`,
`Qore::Ssh::SftpOpenResult`, and `Qore::Ssh::SftpOperationResult`.
`SftpSession` now dispatches typed request hashes for both low-level
filesystem verbs and streamed handle verbs. Lower-level dynamic backend maps
are still available for intentionally dynamic backends, but the typed hashes
are the primary public surface described here.
The high-level `SftpSession` convenience helpers also support typed request
overloads now, so callers can stay in the typed API for:
- `submitData()`
- `submitLocalFile()`
- `retrieveData()`
- `completeRetrieveDelete()`
- `completeRetrieveClaim()`
- `completeRetrieveArchive()`
- `retrieveToLocalFile()`
For retrieval-completion flows, use the explicit typed request field
`completion_mode`.
`VirtualSftpExchangeOptions` also supports a typed `inbound_policy` for
accepting or rejecting inbound uploads. Current built-in policy fields are:
- `accept_filename_glob`
- `reject_filename_glob`
- `accept_filename_regex`
- `reject_filename_regex`
- `min_size`
- `max_size`
- `max_file_count`
- `max_total_bytes`
- `max_concurrent_uploads`
- `allow_overwrite`
- `reject_duplicate_name`
- `reject_duplicate_size`
- `reject_duplicate_hash`
- `accept_content_prefix`
- `reject_content_prefix`
- `accept_content_type_hints`
- `reject_content_type_hints`
- `validation_callback`
- `allow_hidden_files`
- `allow_principals`
- `deny_principals`
- `principal_policies`
- `principal_routes`
- `directory_policies`
These policy checks are enforced consistently for helper submissions,
`DataProvider` `submit-inbound` actions, and real SFTP uploads handled through
`VirtualSftpExchange`.
For arbitrary content validation, set `validation_callback` to a typed closure
receiving `hash<SftpServerDataProvider::VirtualSftpInboundValidationRequest>`
and returning `hash<SftpServerDataProvider::VirtualSftpInboundValidationResult>`.
Normal callback rejections should return `accepted: False` with `reason_code`
and `reason_desc`; callback exceptions are treated as real backend errors.
`VirtualSftpExchangeOptions` also supports typed retention policies:
- `inbound_retention_policy`
- `archive_retention_policy`
- `claim_retention_policy`
Each retention policy currently supports:
- `max_age_seconds`
Accepted inbound checksum generation is optional through
`generate_inbound_checksum`. When enabled, accepted inbound submit results and
events carry a typed SHA-256 checksum, and that checksum is also persisted in
virtual file metadata so later stat/list/retrieve flows can surface it without
recomputing it.
When configured, accepted inbound files and archive/claim completion targets
are stamped with typed retention metadata including `retention_queue`,
`retained_at`, and `expires_at`. Expired files can then be removed
deterministically with `VirtualSftpExchange::cleanupExpired()`.
Duplicate rejection uses typed reason codes in rejected inbound events:
- `duplicate-name-rejected`
- `duplicate-size-rejected`
- `duplicate-hash-rejected`
- `content-prefix-rejected`
- `content-type-hint-rejected`
- `file-count-above-maximum`
- `total-bytes-above-maximum`
- `concurrent-uploads-above-maximum`
When a typed helper request or live SFTP session supplies `session_info` with
`principal`, `principal_routes` can place accepted inbound files under a
principal-specific subdirectory while preserving the visible `/inbound/...`
path for that principal.
Virtual filesystem / exchange example:
```qore
SftpServerDataProvider::VirtualSftpExchange exchange(
<SftpServerDataProvider::VirtualSftpExchangeOptions>{
"archive_dir": "/archive",
"claim_dir": "/claimed",
"inbound_policy": <SftpServerDataProvider::VirtualSftpInboundPolicy>{
"accept_filename_glob": "*.dat",
"max_size": 1048576,
"allow_overwrite": False,
},
}
);
exchange.addOutboundData("hello.txt", binary("hello\n"), {"kind": "example"});
hash<SftpServerDataProvider::VirtualSftpPathRequest> retrieve_req
= <SftpServerDataProvider::VirtualSftpPathRequest>{"path": "/hello.txt"};
binary data = exchange.retrieve(retrieve_req).data;
hash<SftpServerDataProvider::VirtualSftpCompletionRequest> archive_req
= <SftpServerDataProvider::VirtualSftpCompletionRequest>{
"path": "/hello.txt",
"completion_mode": "archive",
};
exchange.completeRetrieve(archive_req);
```
Record-store example:
```qore
SftpServerDataProvider::VirtualSftpExchangeRecordStore store(
<SftpServerDataProvider::VirtualSftpExchangeOptions>{
"archive_dir": "/archive",
"claim_dir": "/claimed",
}
);
store.addOutboundRecord(<SftpServerDataProvider::VirtualSftpRecordSubmissionRequest>{
"name": "out-1.dat",
"data": binary("payload\n"),
"metadata": {"kind": "report"},
});
hash<SftpServerDataProvider::VirtualSftpRecordInfo> claimed
= store.claimOutboundRecord(<SftpServerDataProvider::VirtualSftpRecordCompletionRequest>{
"name": "out-1.dat",
"final_name": "claimed-out-1.dat",
"completion_mode": "claim",
});
```
Typed backend verb example:
```qore
SftpServerDataProvider::VirtualSftpExchange exchange();
exchange.addOutboundData("report.txt", binary("payload\n"));
hash<Qore::Ssh::SftpPathRequest> list_req
= <Qore::Ssh::SftpPathRequest>{"path": "/outbound"};
hash<Qore::Ssh::SftpListResult> listing = exchange.list(list_req);
hash<Qore::Ssh::SftpOpenRequest> read_req
= <Qore::Ssh::SftpOpenRequest>{"path": "/outbound/report.txt"};
hash<Qore::Ssh::SftpOpenResult> open_result = exchange.openRead(read_req);
```
GENERIC DATAPROVIDER MODULE
---------------------------
`SftpServerDataProvider` is now also a real generic `DataProvider` module,
not just a helper module.
Current provider surface:
- factory: `sftpserver`
- app: `SftpServerExchange`
- root provider: `SftpServerDataProvider::SftpServerDataProvider`
- child API providers:
- `submit-inbound`
- `list-outbound`
- `retrieve-outbound`
- `claim-outbound`
- `archive-outbound`
- `delete-outbound`
- `cleanup-expired`
- child event providers:
- `inbound-file-accepted`
observable entry point for inbound submission events
supporting:
`inbound-file-accepted` and `inbound-file-rejected`
- `outbound-file-claimed`
- `outbound-file-archived`
- `outbound-file-deleted`
The current provider layer is intentionally backed by the generic virtual
exchange helpers:
- `VirtualSftpExchange`
- `VirtualSftpExchangeRecordStore`
It supports:
- typed request and response data types for the generic exchange operations
- a typed inbound submission event action for config-driven observable flows
- typed outbound completion event actions for claim/archive/delete flows
- a typed retention cleanup action for deterministic inbound/archive/claim cleanup,
optionally scoped to a single authenticated principal
- constructor and action options for inbound acceptance policy
- direct factory usage through `DataProvider::getFactory("sftpserver")`
- action-catalog registration for the same generic exchange actions
- constructor-time backend injection with an existing
`VirtualSftpExchangeRecordStore` or `VirtualSftpExchange`
- direct binding to `SshServerConnections::SftpServerConnection` through the
`sftpserver` connection scheme
Minimal example:
```qore
SftpServerDataProvider::VirtualSftpExchangeRecordStore store(
<SftpServerDataProvider::VirtualSftpExchangeOptions>{
"archive_dir": "/archive",
"claim_dir": "/claim",
"inbound_policy": <SftpServerDataProvider::VirtualSftpInboundPolicy>{
"accept_filename_glob": "*.dat",
"max_size": 1048576,
},
}
);
store.addOutboundTextRecord("hello.txt", "hello\n");
AbstractDataProvider provider = DataProvider::getFactory("sftpserver").create({
"backend": store,
"inbound_policy": <SftpServerDataProvider::VirtualSftpInboundPolicy>{
"accept_filename_glob": "*.dat",
"max_size": 1048576,
},
});
hash<auto> list_result = provider.getChildProviderEx("list-outbound").doRequest({});
hash<auto> retrieve_result = provider.getChildProviderEx("retrieve-outbound").doRequest({
"name": "hello.txt",
});
hash<auto> cleanup_result = provider.getChildProviderEx("cleanup-expired").doRequest({
"queue": SftpServerDataProvider::RetentionQueueInbound,
"now_s": 1700000000,
"principal": "principal:alice",
});
```
`cleanup-expired` can be scoped to a single authenticated principal. The
cleanup result echoes that principal, and retained inbound files also carry
`retention_principal` metadata when they were accepted through a principal-aware
session or helper request.
Event example:
```qore
class MyObserver inherits DataProvider::Observer {
update(string event_id, hash<auto> data_) {
printf("EVENT %s: %y\n", event_id, data_);
}
}
AbstractDataProvider event_provider = provider.getChildProviderEx(
SftpServerDataProvider::EventInboundFileAccepted
);
MyObserver observer();
cast<DataProvider::Observable>(event_provider).registerObserver(
observer, SftpServerDataProvider::EventInboundFileAccepted);
cast<DataProvider::Observable>(event_provider).registerObserver(
observer, SftpServerDataProvider::EventInboundFileRejected);
cast<DataProvider::DelayedObservable>(event_provider).observersReady();
// inbound-file-accepted and inbound-file-rejected are the typed event ids.
// binary payloads are omitted by default; set
// inbound_event_payload_mode=SftpServerDataProvider::InboundEventPayloadInline
// when observers need them.
// live SFTP uploads also include session_id, server_name, listener_name,
// and principal in the typed event payload.
```
Outbound completion event providers use the same observer model and emit typed
claim/archive/delete events with the operation, relevant completion path,
transfer info, optional checksum metadata when the completed record carries it,
and live session context when the completion came from an authenticated SFTP
session.
Policy-driven server example:
```qore
SftpServerDataProvider::VirtualSftpExchange exchange(
<SftpServerDataProvider::VirtualSftpExchangeOptions>{
"inbound_policy": <SftpServerDataProvider::VirtualSftpInboundPolicy>{
"accept_filename_glob": "*.dat",
"max_size": 1048576,
"allow_overwrite": False,
},
"inbound_event_payload_mode": SftpServerDataProvider::InboundEventPayloadInline,
}
);
SshServerConnections::SftpServerConnection conn({
"name": "policy-example",
"url": "sftpserver://127.0.0.1:0",
"opts": {
"host_keys": ("/path/to/ssh_host_ed25519_key",),
"services": <SshServerConnections::SshServerConnectionServices>{
"enabled_auth_methods": ("none",),
"allow_command_service": False,
"allow_sftp_service": True,
},
"auth": <SshServerAuthProvider::VirtualSshAuthProviderOptions>{
"methods": ("none",),
"none": <SshServerAuthProvider::VirtualSshStaticAuthMethodOptions>{
"success_decision": <SshServerAuthProvider::VirtualSshAuthDecisionTemplate>{
"principal": "principal:nobody",
"command_policy": {"mode": "subsystem-only", "allowed_subsystems": ("sftp",)},
"sftp_policy": {"mode": "enabled"},
},
},
},
"backend": exchange,
},
});
AbstractDataProvider provider = conn.getDataProvider();
AbstractDataProvider event_provider = provider.getChildProviderEx(
SftpServerDataProvider::EventInboundFileAccepted
);
```
Connection-driven example:
```qore
SftpServerDataProvider::VirtualSftpExchangeRecordStore store(
<SftpServerDataProvider::VirtualSftpExchangeOptions>{
"archive_dir": "/archive",
"claim_dir": "/claim",
}
);
SshServerConnections::SftpServerConnection conn({
"name": "local-sftp-exchange",
"url": "sftpserver://127.0.0.1:0",
"opts": {
"host_keys": ("/path/to/ssh_host_ed25519_key",),
"backend": store,
},
});
AbstractDataProvider provider = conn.getDataProvider();
hash<auto> list_result = provider.getChildProviderEx("list-outbound").doRequest({});
```
Typed streamed backend example:
```qore
class TypedStreamBackend {
hash<Qore::Ssh::SftpOpenResult> openRead(hash<Qore::Ssh::SftpOpenRequest> req) {
return {
"path": req.path,
"type": "file",
"size": 8,
"backend_handle": {"kind": "stream", "path": req.path},
};
}
hash<Qore::Ssh::SftpReadResult> read(hash<Qore::Ssh::SftpReadRequest> req) {
return {
"data": binary("payload\n"),
"eof": True,
};
}
hash<Qore::Ssh::SftpOperationResult> closeRead(hash<Qore::Ssh::SftpCloseRequest> req) {
return {
"success": True,
"path": req.path,
};
}
}
```
EXAMPLES
--------
A runnable example is available in:
examples/VirtualSftpFilesystem.qtest
Additional record-store example:
examples/VirtualSftpExchangeRecordStore.qtest
Low-level real server example:
examples/VirtualSftpServer.qr
Real server policy/event example:
examples/VirtualSftpInboundPolicyServer.qr
LICENSE
-------
This module will be released under a choice of two licenses:
- LGPL 2.1
- MIT
BUILDING
--------
Configure the local build with the same install prefix as the active `qore`
binary, then use the local module build for tests and examples.
Typical local preflight:
```bash
test/preflight.sh
```
This rebuilds the module, runs the core qtest sweep, runs the live examples,
and rebuilds the HTML docs.