├── libssh2.dll ├── .gitignore ├── aplssh_helpers.dll ├── aplssh_helpers.so ├── Makefile ├── aplssh_helpers_test.c ├── Examples ├── SCP.dyalog ├── SCPWrite.dyalog └── direct_tcpip.dyalog ├── LICENSE ├── aplssh_helpers.h ├── SSH_C_Helpers.dyalog ├── README.md ├── aplssh_helpers.c ├── CInterop.dyalog ├── Sock.dyalog ├── APLProcess.dyalog └── SSH.dyalog /libssh2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/aplssh/master/libssh2.dll -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | aplcore 2 | .*.sw? 3 | aplssh_helpers_test 4 | *.lib 5 | *.obj 6 | *.exp 7 | -------------------------------------------------------------------------------- /aplssh_helpers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/aplssh/master/aplssh_helpers.dll -------------------------------------------------------------------------------- /aplssh_helpers.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/aplssh/master/aplssh_helpers.so -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | 3 | all: aplssh_helpers.so 4 | 5 | aplssh_helpers.so: aplssh_helpers.c 6 | $(CC) -shared -o aplssh_helpers.so -fPIC aplssh_helpers.c 7 | -------------------------------------------------------------------------------- /aplssh_helpers_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "aplhelpers.h" 4 | 5 | void test_apl_getaddrinfo() { 6 | struct apl_addr *r; 7 | fprintf(stderr,"Running: "); 8 | int i = apl_getaddrinfo(0, "www.google.com", "80", 0, &r); 9 | fprintf(stderr,"%d\n", i); 10 | 11 | while (r) { 12 | fprintf(stderr,"Found: %d %d %ld - %s\n", r->family, r->socktype, r->addrlen, r->canonname); 13 | r=r->next; 14 | } 15 | 16 | fprintf(stderr,"Done.\n"); 17 | 18 | } 19 | 20 | int main() { 21 | test_apl_getaddrinfo(); 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SCP.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/../SSH.dyalog 2 | 3 | :Namespace SCP 4 | ⍝ Sample showing how to do a simple SCP transfer 5 | 6 | _user←'marinus' 7 | _pass←'' 8 | _pub←'/home/marinus/apl_sshid/id_rsa.pub' 9 | _priv←'/home/marinus/apl_sshid/id_rsa' 10 | _host←'localhost' 11 | _port←22 12 | 13 | _a←_user _pass _pub _priv _host _port 14 | 15 | ∇ data←a SCP path;user;pass;pub;priv;host;port;session;chan;stat;size;data;amt;agn;d 16 | ⍝ arguments 17 | (user pass pub priv host port)←a 18 | 19 | #.SSH.Init 20 | 21 | session←⎕NEW #.SSH.Session(host port) 22 | 23 | ⎕←'Fingerprint: ',session.HostkeyHash'SHA1' 24 | 25 | ⍝ Try password authentication 26 | :Trap #.SSH.SSH_ERR 27 | ⎕←'Trying password authentication...' 28 | session.Userauth_Password user pass 29 | →authenticated 30 | :Else 31 | ⎕←'Failed.' 32 | :EndTrap 33 | 34 | ⍝ Try publickey authentication 35 | :Trap #.SSH.SSH_ERR 36 | ⎕←'Trying public key authentication...' 37 | session.Userauth_Publickey user pub priv pass 38 | →authenticated 39 | :Else 40 | ⎕←'Failed.' 41 | :EndTrap 42 | 43 | ⍝ No authentication methods left. 44 | ⎕←'Cannot authenticate.' 45 | →shutdown 46 | 47 | 48 | authenticated: 49 | ⎕←'Authenticated.' 50 | 51 | chan stat←session.SCP_Recv path 52 | ⎕←'File size: ',size←1⊃stat 53 | 54 | data←⍬ 55 | 56 | :While (≢data) 4 | Copyright (c) 2005,2006 Mikhail Gusarov 5 | Copyright (c) 2006-2007 The Written Word, Inc. 6 | Copyright (c) 2007 Eli Fant 7 | Copyright (c) 2009-2019 Daniel Stenberg 8 | Copyright (C) 2008, 2009 Simon Josefsson 9 | Copyright (c) 2020, Dyalog Ltd. 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | 1. Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | 3. Neither the name of the copyright holder nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /Examples/SCPWrite.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/../SSH.dyalog 2 | 3 | :Namespace SCPWrite 4 | ⍝ Sample showing how to do an SCP upload 5 | 6 | _user←'marinus' 7 | _pass←'' 8 | _pub←'/home/marinus/apl_sshid/id_rsa.pub' 9 | _priv←'/home/marinus/apl_sshid/id_rsa' 10 | _host←'localhost' 11 | _port←22 12 | 13 | _a←_user _pass _pub _priv _host _port 14 | _w←'/home/marinus/scptest.txt' (8⊥6 4 4) (⎕UCS 'Hello!') 15 | 16 | ∇ a SCPWrite(path mode data);user;pass;pub;priv;host;port;session;chan;agn;wr 17 | (user pass pub priv host port)←a 18 | 19 | #.SSH.Init 20 | 21 | session←⎕NEW #.SSH.Session(host port) 22 | ⎕←'Fingerprint: ',session.HostkeyHash'SHA1' 23 | 24 | ⍝ Try password authentication 25 | :Trap #.SSH.SSH_ERR 26 | ⎕←'Trying password authentication...' 27 | session.Userauth_Password user pass 28 | →authenticated 29 | :Else 30 | ⎕←'Failed.' 31 | :EndTrap 32 | 33 | ⍝ Try publickey authentication 34 | :Trap #.SSH.SSH_ERR 35 | ⎕←'Trying public key authentication...' 36 | session.Userauth_Publickey user pub priv pass 37 | →authenticated 38 | :Else 39 | ⎕←'Failed.' 40 | :EndTrap 41 | 42 | ⍝ No authentication methods left. 43 | ⎕←'Cannot authenticate.' 44 | →shutdown 45 | 46 | authenticated: 47 | ⎕←'Authenticated.' 48 | 49 | ⎕←'SCP session waiting to send file.' 50 | chan←session.SCP_Send(path mode(≢data)) 51 | 52 | ⎕←'Writing...' 53 | ⍝ Write all the data 54 | :While 0<≢data 55 | ⎕←(≢data),'bytes left.' 56 | agn wr←chan.Write data 57 | :If agn ⋄ :Continue ⋄ :EndIf 58 | data↓⍨←wr 59 | ⎕←'Wrote',(wr),'bytes.' 60 | :EndWhile 61 | 62 | ⎕←'Done.' 63 | 64 | ⎕←'Sending EOF...' 65 | chan.SendEOF 66 | ⎕←'Waiting for EOF...' 67 | chan.WaitEOF 68 | ⎕←'Waiting for channel to close...' 69 | chan.WaitClosed 70 | ⎕←'Done.' 71 | 72 | shutdown: 73 | 74 | ∇ 75 | 76 | :EndNamespace 77 | -------------------------------------------------------------------------------- /aplssh_helpers.h: -------------------------------------------------------------------------------- 1 | /* These are some functions to access struct fields. 2 | The structs differ per platform and there's no way to see that from APL. 3 | 4 | These functions return the fields in variables of specified sizes, which 5 | are (for now at least) big enough on all platforms. 6 | */ 7 | 8 | #ifndef __APLSSH_HELPERS_H__ 9 | #define __APLSSH_HELPERS_H__ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #ifdef _WIN32 18 | // allow building on Windows 19 | #define ADDAPI __declspec(dllexport) 20 | #define ADDCALL __cdecl 21 | 22 | // Windows socket imports 23 | #include 24 | #include 25 | 26 | #ifndef EAI_NONAME 27 | #define EAI_NONAME WSAHOST_NOT_FOUND 28 | #endif 29 | #else 30 | // Unix socket imports 31 | #include 32 | #include 33 | 34 | // Unix doesn't need these 35 | #define ADDAPI 36 | #define ADDCALL 37 | #endif 38 | 39 | ADDAPI int8_t ADDCALL test(); 40 | 41 | // access libssh2_struct_stat fields 42 | ADDAPI uint64_t ADDCALL stat_size(libssh2_struct_stat *s); 43 | ADDAPI int32_t ADDCALL stat_mode(libssh2_struct_stat *s); 44 | ADDAPI int64_t ADDCALL stat_atime(libssh2_struct_stat *s); 45 | ADDAPI int64_t ADDCALL stat_mtime(libssh2_struct_stat *s); 46 | 47 | // access libssh2_knownhost fields 48 | ADDAPI uint32_t ADDCALL knownhost_magic(struct libssh2_knownhost *k); 49 | ADDAPI void* ADDCALL knownhost_node(struct libssh2_knownhost *k); 50 | ADDAPI char* ADDCALL knownhost_name(struct libssh2_knownhost *k); 51 | ADDAPI char* ADDCALL knownhost_key(struct libssh2_knownhost *k); 52 | ADDAPI int32_t ADDCALL knownhost_typemask(struct libssh2_knownhost *k); 53 | 54 | 55 | 56 | // getaddrinfo wrapper with fixed-size arguments 57 | #define CANONNAME_LEN 256 58 | struct apl_addr { 59 | int32_t family; // I4 60 | int32_t socktype; // I4 61 | uintptr_t addrlen; // P 62 | char canonname[CANONNAME_LEN]; // 0C[256] 63 | struct sockaddr *addr; // P 64 | struct apl_addr *next; // P 65 | }; 66 | 67 | // struct wrappers 68 | ADDAPI int32_t ADDCALL apl_addr_family(struct apl_addr *r); 69 | ADDAPI int32_t ADDCALL apl_addr_socktype(struct apl_addr *r); 70 | ADDAPI uintptr_t ADDCALL apl_addr_addrlen(struct apl_addr *r); 71 | ADDAPI char* ADDCALL apl_addr_canonname(struct apl_addr *r); 72 | ADDAPI struct sockaddr *ADDCALL apl_addr_sockaddr(struct apl_addr *addr); 73 | ADDAPI struct apl_addr *ADDCALL apl_addr_next(struct apl_addr *addr); 74 | 75 | ADDAPI int32_t ADDCALL apl_getaddrinfo(int32_t family, char *hostname, 76 | char *srvport, uint8_t pasv, struct apl_addr **r); 77 | ADDAPI void ADDCALL apl_freeaddrinfo(struct apl_addr *addr); 78 | 79 | struct apl_addr *apl_addr_copydata(struct addrinfo *in); 80 | struct apl_addr *alloc_apl_addr(); 81 | void apl_freeaddrinfo(struct apl_addr *addr); 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /SSH_C_Helpers.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/CInterop.dyalog 2 | 3 | :Namespace SSH_C_Helpers 4 | 5 | ∇ r←ScriptPath 6 | ⍝r←SALT_Data.SourceFile 7 | 8 | ⍝ Adám's SourceFile function 9 | r←{ ⍝ Get pathname to sourcefile for item ⍵ 10 | c←⎕NC⊂,⍕⍵ 11 | c=2.1:(SALT_Var_Data.VD[;1]⍳⊂⍵(⊢,~)'#.')⊃SALT_Var_Data.VD[;2],⊂'' 12 | c∊3.1 3.2 4.1 4.2:1↓⊃('§'∘=⊂⊢)∊¯2↑⎕NR ⍵ 13 | (r←326=⎕DR ⍵)∨c∊9+0.1×⍳8:{6::'' ⋄ ''≡f←⊃(4∘⊃¨(/⍨)(⍵≡⊃)¨)5177⌶⍬:⍵.SALT_Data.SourceFile ⋄ f}⍎⍣(~r)⊢⍵ 14 | '' 15 | }⎕THIS 16 | ∇ 17 | 18 | isOS←{⍵≡(≢⍵)↑⊃#.⎕WG'APLVersion'} 19 | 20 | init←0 21 | 22 | ⍝ initialize 23 | ∇ Init;name;path;⍙T 24 | →init/0 25 | 26 | ⍝ We expect the DLL to be in the script directory, failing that we want it on the path. 27 | path←{(~∨\'SSH_C_Helpers'⍷⍵)/⍵}ScriptPath 28 | name←'aplssh_helpers.',⊃'so' 'dll'[1+isOS'Windows'] 29 | 30 | ⍝ try loading it from the script directory 31 | path←path,name 32 | :Trap 0 33 | '⍙T'⎕NA'I1 ',path,'|test' 34 | {}42≡⍙T 35 | :Else 36 | ⍝ try loading it from the path 37 | path←name 38 | '⍙T'⎕NA'I1 ',path,'|test' 39 | {}42≡⍙T 40 | :EndTrap 41 | 42 | ⍝ still here? 43 | 44 | ⍝⍝⍝ stat 45 | ⎕NA'U8 ',path,'|stat_size P' 46 | ⎕NA'I4 ',path,'|stat_mode P' 47 | ⎕NA'I8 ',path,'|stat_atime P' 48 | ⎕NA'I8 ',path,'|stat_mtime P' 49 | 50 | ⍝⍝⍝ knownhosts 51 | ⎕NA'U4 ',path,'|knownhost_magic P' 52 | ⎕NA'P ',path,'|knownhost_node P' 53 | ⎕NA'P ',path,'|knownhost_name P' 54 | ⎕NA'P ',path,'|knownhost_key P' 55 | ⎕NA'I4 ',path,'|knownhost_typemask P' 56 | 57 | 58 | ⍝⍝ deal with getaddrinfo with wrappers 59 | ⎕NA'I4 ',path,'|apl_getaddrinfo I4 <0C <0C U1 >P' 60 | ⎕NA'I4 ',path,'|apl_freeaddrinfo P' 61 | ⎕NA'I4 ',path,'|apl_addr_family P' 62 | ⎕NA'I4 ',path,'|apl_addr_socktype P' 63 | ⎕NA'P ',path,'|apl_addr_addrlen P' 64 | ⎕NA'P ',path,'|apl_addr_canonname P' 65 | ⎕NA'P ',path,'|apl_addr_sockaddr P' 66 | ⎕NA'P ',path,'|apl_addr_next P' 67 | 68 | init←1 69 | ∇ 70 | 71 | ⍝ Read a C string at a given address, but give the empty string if 72 | ⍝ a null pointer is given. 73 | ∇ r←str ptr 74 | r←'' ⋄ →(ptr=0)/0 75 | r←#.CInterop.ReadCStr ptr 76 | ∇ 77 | 78 | ⍝ Decode a 'stat' structure 79 | ⍝ giving (size, mode, atime, mtime) 80 | ∇ (size mode atime mtime)←stat statblk 81 | size←stat_size statblk.Ref 82 | mode←stat_mode statblk.Ref 83 | atime←stat_atime statblk.Ref 84 | mtime←stat_mtime statblk.Ref 85 | ∇ 86 | 87 | ⍝ knownhosts 88 | ∇ (magic node name key typemask)←knownhost ref;sptr 89 | ⎕SIGNAL(ref=0)/⊂('EN'11)('Message' 'Null pointer') 90 | magic←knownhost_magic ref 91 | node←knownhost_node ref 92 | name←str knownhost_name ref 93 | key←str knownhost_key ref 94 | typemask←knownhost_typemask ref 95 | ∇ 96 | 97 | 98 | ⍝⍝ Decode an 'apl_addr' structure 99 | ∇ (fam type len name sa next)←apl_addr ref 100 | ⎕SIGNAL(ref=0)/⊂('EN'11)('Message' 'Null pointer') 101 | fam←apl_addr_family ref 102 | type←apl_addr_socktype ref 103 | len←apl_addr_addrlen ref 104 | name←str apl_addr_canonname ref 105 | sa←apl_addr_sockaddr ref 106 | next←apl_addr_next ref 107 | ∇ 108 | 109 | 110 | 111 | :EndNamespace 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## APLSSH 2 | 3 | This is a wrapper around the `libssh2` library. Currently, it only exposes a small fraction of its functionality. 4 | It can execute remote commands, and read and write files. 5 | 6 | Example session: 7 | 8 | ```apl 9 | 10 | ⍝ connect to a host 11 | sess←⎕NEW SSH.Session ('10.0.60.249' 22) 12 | sess.HostkeyHash 'MD5' 13 | 85 246 47 235 173 71 184 56 162 14 123 196 75 109 197 123 14 | 15 | ⍝ authenticate 16 | sess.Userauth_Publickey 'marinus' '/home/marinus/apl_sshid/id_rsa.pub' '/home/marinus/apl_sshid/id_rsa' '' 17 | sess.Authenticated 18 | 1 19 | 20 | ⍝ run a command 21 | x←sess.Exec 'ls *.txt' 22 | ⍝ the first element contains the return code, the second element contains STDOUT (as bytes) 23 | x 24 | ┌─┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 25 | │0│115 99 112 116 101 115 116 46 116 120 116 10 116 101 115 116 46 116 120 116 10 119 114 105 116 101 95 116 101 115 26 | └─┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 27 | 28 | ─────────────────────┐ 29 | 116 46 116 120 116 10│ 30 | ─────────────────────┘ 31 | ⎕UCS 2⊃x 32 | scptest.txt 33 | test.txt 34 | write_test.txt 35 | 36 | ⍝ read a file 37 | test_txt←sess.ReadFile 'test.txt' 38 | ⍝ the first element contains information about the file (size, mode, mtime, atime) 39 | ⍝ the second element contains the file's contents (as bytes) 40 | test_txt 41 | ┌───────────────────────────┬──────────────┐ 42 | │4 436 1503307650 1502811867│102 111 111 10│ 43 | └───────────────────────────┴──────────────┘ 44 | ⎕UCS 2⊃test_txt 45 | foo 46 | 47 | ⍝ write a file 48 | sess.WriteFile 'test2.txt' (⎕UCS 'This will be written.') 49 | ⍝ let's see if it's there 50 | ⎕UCS 2⊃sess.Exec'ls *.txt' 51 | scptest.txt 52 | test2.txt 53 | test.txt 54 | write_test.txt 55 | 56 | ⎕UCS 2⊃sess.Exec'cat test2.txt' 57 | This will be written. 58 | 59 | sess.Disconnect ⍬ 60 | 61 | ``` 62 | 63 | Also included is a version of `APLProcess` that supports SSH connections using these classes. 64 | 65 | To start an APL process on a remote machine: 66 | 67 | ```apl 68 | x←⎕NEW APLProcess (ws args (host user pubkey privkey executable)) 69 | ``` 70 | 71 | E.g.: 72 | 73 | ```apl 74 | x←⎕NEW APLPRocess ('~/Test.dws' '' ('10.20.30.40' 'marinus' 'id_rsa.pub' 'id_rsa' 'dyalog')) 75 | ``` 76 | 77 | ### Third-party licences 78 | 79 | Binary copies of [libssh2](https://www.libssh2.org/) included with aplssh are redistributed according to the following licence: 80 | 81 | Copyright (c) 2004-2007 Sara Golemon 82 | Copyright (c) 2005,2006 Mikhail Gusarov 83 | Copyright (c) 2006-2007 The Written Word, Inc. 84 | Copyright (c) 2007 Eli Fant 85 | Copyright (c) 2009-2014 Daniel Stenberg 86 | Copyright (C) 2008, 2009 Simon Josefsson 87 | All rights reserved. 88 | 89 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 90 | 91 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 92 | 93 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 94 | 95 | * Neither the name of the copyright holder nor the names of any other contributors may be used to endorse or promote products derived from this software without specific prior written permission. 96 | 97 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 98 | -------------------------------------------------------------------------------- /aplssh_helpers.c: -------------------------------------------------------------------------------- 1 | /* These are some functions to access struct fields. 2 | The structs differ per platform and there's no way to see that from APL. 3 | */ 4 | 5 | 6 | #include "aplssh_helpers.h" 7 | #include 8 | 9 | // test function 10 | int8_t ADDCALL test() { return 42; } 11 | 12 | // access libssh2_struct_stat fields 13 | uint64_t ADDCALL stat_size(libssh2_struct_stat *s) { return s->st_size; } 14 | int32_t ADDCALL stat_mode(libssh2_struct_stat *s) { return s->st_mode; } 15 | int64_t ADDCALL stat_atime(libssh2_struct_stat *s) { return s->st_atime; } 16 | int64_t ADDCALL stat_mtime(libssh2_struct_stat *s) { return s->st_mtime; } 17 | 18 | // access libssh2_knownhost fields 19 | uint32_t ADDCALL knownhost_magic(struct libssh2_knownhost *k) { return k->magic; } 20 | void* ADDCALL knownhost_node(struct libssh2_knownhost *k) { return k->node; } 21 | char* ADDCALL knownhost_name(struct libssh2_knownhost *k) { return k->name; } 22 | char* ADDCALL knownhost_key(struct libssh2_knownhost *k) { return k->key; } 23 | int32_t ADDCALL knownhost_typemask(struct libssh2_knownhost *k) { return k->typemask; } 24 | 25 | // getaddrinfo fields 26 | int32_t ADDCALL apl_addr_family(struct apl_addr *r) { return r->family; } 27 | int32_t ADDCALL apl_addr_socktype(struct apl_addr *r) { return r->socktype; } 28 | uintptr_t ADDCALL apl_addr_addrlen(struct apl_addr *r) { return r->addrlen; } 29 | char* ADDCALL apl_addr_canonname(struct apl_addr *r) { return r->canonname; } 30 | struct sockaddr *ADDCALL apl_addr_sockaddr(struct apl_addr *r) { return r->addr; } 31 | struct apl_addr *ADDCALL apl_addr_next(struct apl_addr *r) { return r->next; } 32 | 33 | // getaddrinfo 34 | int32_t ADDCALL apl_getaddrinfo(int32_t family, 35 | char *hostname, 36 | char *srvport, 37 | uint8_t pasv, 38 | struct apl_addr **r) { 39 | int32_t retval; 40 | 41 | // if no family is given, use all 42 | if (family==0) family=AF_UNSPEC; 43 | 44 | // if no hostname is given, set the passive flag 45 | pasv = pasv || strlen(hostname) == 0; 46 | 47 | *r=NULL; 48 | 49 | struct addrinfo hints, *res; 50 | memset(&hints, 0, sizeof(hints)); 51 | hints.ai_family=family; 52 | hints.ai_socktype = SOCK_STREAM; 53 | hints.ai_flags = AI_V4MAPPED; 54 | hints.ai_flags |= AI_CANONNAME; 55 | hints.ai_flags |= AI_ADDRCONFIG; 56 | hints.ai_flags |= AI_PASSIVE * !!pasv; 57 | 58 | if (strlen(hostname) == 0) { 59 | // treat an empty string as if it were NULL 60 | retval = getaddrinfo(NULL, srvport, &hints, &res); 61 | } else { 62 | retval = getaddrinfo(hostname, srvport, &hints, &res); 63 | } 64 | 65 | if (retval==EAI_NONAME) { 66 | // for APL's sake, treat this as returning an "empty list" rather than 67 | // signaling an error that would differ by platform 68 | *r=NULL; 69 | 70 | return 0; 71 | } 72 | if (retval!=0) return retval; // error code 73 | 74 | *r = apl_addr_copydata(res); 75 | return 0; 76 | } 77 | 78 | // copy the data from addrinfo into apl_addrinfo 79 | struct apl_addr *apl_addr_copydata(struct addrinfo *in) { 80 | // allocate a new object 81 | 82 | struct apl_addr *out_start, *out = alloc_apl_addr(); 83 | out_start = out; 84 | 85 | while (in != NULL) { 86 | if (out == NULL) return NULL; // allocation failed 87 | 88 | out->family = (int32_t) in->ai_family; 89 | out->socktype = (int32_t) in->ai_socktype; 90 | out->addrlen = (uintptr_t) in->ai_addrlen; 91 | out->addr = in->ai_addr; 92 | if (in->ai_canonname == NULL) { 93 | out->canonname[0]='\0'; 94 | } else { 95 | strncpy(out->canonname, in->ai_canonname, CANONNAME_LEN); 96 | out->canonname[CANONNAME_LEN-1]='\0'; 97 | } 98 | 99 | // if there is a next one, copy that one 100 | in = in->ai_next; 101 | if (in != NULL) { 102 | out->next = alloc_apl_addr(); 103 | out = out->next; 104 | } 105 | } 106 | 107 | return out_start; 108 | } 109 | 110 | // allocate a new apl_addrinfo 111 | struct apl_addr *alloc_apl_addr() { 112 | struct apl_addr *a; 113 | if (!(a = malloc(sizeof(struct apl_addr)))) return NULL; 114 | memset(a, 0, sizeof(struct apl_addr)); 115 | return a; 116 | } 117 | 118 | // free them all 119 | void ADDCALL apl_freeaddrinfo(struct apl_addr *addr) { 120 | do { 121 | struct apl_addr *next = addr->next; 122 | free(addr); 123 | addr = next; 124 | } while (addr != NULL); 125 | } 126 | -------------------------------------------------------------------------------- /Examples/direct_tcpip.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/../SSH.dyalog 2 | 3 | :Namespace direct_tcpip 4 | keyfile1←'/home/marinus/apl_sshid/id_rsa.pub' 5 | keyfile2←'/home/marinus/apl_sshid/id_rsa' 6 | 7 | username←'marinus' 8 | password←'' 9 | 10 | server_ip←'127.0.0.1' 11 | local_listenip←'127.0.0.1' 12 | local_listenport←2222 13 | 14 | remote_desthost←'localhost' 15 | remote_destport←22 16 | 17 | ∇ direct_tcpip methods;sock;session;auth_list;listensock;forwardsock;fhost;fport;meth;channel;poller;ps;buf;r;again 18 | ⍝ initialize SSH library 19 | #.SSH.Init 20 | 21 | ⍝ set up an SSH connection and do a handshake 22 | session←⎕NEW #.SSH.Session(server_ip 22) 23 | 24 | ⍝ show the hostkey hash 25 | ⍝ (authentication has not yet been done) 26 | ⎕←session.HostkeyHash'SHA1' 27 | 28 | ⍝ check what authentication methods are available 29 | auth_list←session.UserauthList username 30 | ⎕←'Authentication methods:',auth_list 31 | 32 | ⍝ use only the methods that are available 33 | methods∩←auth_list 34 | :For meth :In methods 35 | ⎕←'Trying ',meth 36 | :Trap #.SSH.SSH_ERR 37 | :Select meth 38 | :Case 'password' 39 | session.Userauth_Password username password 40 | ⎕←meth,' succeeded' 41 | →success 42 | :Case 'publickey' 43 | session.Userauth_Publickey username keyfile1 keyfile2 password 44 | ⎕←meth,' succeeded' 45 | →success 46 | :Else 47 | ⎕←'Unknown method.' 48 | :EndSelect 49 | :Else 50 | ⎕←'Failed.' 51 | ⎕←⎕DMX 52 | :EndTrap 53 | ⎕←'' 54 | :EndFor 55 | ⎕←'No supported authentication methods found.' 56 | →shutdown 57 | 58 | success: 59 | 60 | listensock←⎕NEW #.Sock.Socket #.Sock.Cnst.SOCK_STREAM 61 | listensock.setsockopt_int #.Sock.Cnst.SOL_SOCKET #.Sock.Cnst.SO_REUSEADDR 1 62 | 63 | listensock.Bind(local_listenip local_listenport) 64 | 65 | listensock.Listen 2 66 | 67 | ⎕←'Waiting for TCP connection on ',local_listenport,':',local_listenip 68 | 69 | forwardsock←listensock.Accept 70 | fhost fport←forwardsock.ListenConnection 71 | fport←⍎fport ⍝ convert to number 72 | 73 | ⎕←'Forwarding connection from here to remote ',remote_desthost,':',remote_destport 74 | 75 | channel←session.Channel_Direct_TCPIP remote_desthost remote_destport fhost fport 76 | 77 | ⍝ non-blocking IO 78 | session.SetBlocking 0 79 | 80 | ⍝ poll 81 | poller←⎕NEW #.Sock.Poller 82 | #.Sock.Cnst.POLLIN poller.Register forwardsock 83 | 84 | :Trap ⍬ 85 | :Repeat 86 | ⍞←'Po' 87 | ps←poller.Poll 10 88 | ⍞←'ll' 89 | ⎕←'' 90 | 91 | :If forwardsock∊ps 92 | ⍞←'Reading from socket... ' 93 | ⍝ read data from the socket 94 | buf←forwardsock.Recv 1024 95 | :If 0=≢buf 96 | ⎕←'Client disconnected' 97 | →shutdown 98 | :EndIf 99 | 100 | ⍝ write the whole buffer into the channel as needed 101 | 102 | ⍞←'Writing to channel...' 103 | :While 0<≢buf 104 | ⍞←'.' 105 | ⍝ try to write the buffer 106 | again r←channel.Write buf 107 | :If again ⋄ :Continue ⋄ :EndIf 108 | 109 | ⍝ remove the data that's actually been written from the buffer 110 | buf↓⍨←r 111 | :EndWhile 112 | ⎕←'Done' 113 | :EndIf 114 | 115 | :Repeat 116 | ⍞←'Reading from channel... ' 117 | ⍝ try to read some data from the channel 118 | again buf←channel.Read 1024 119 | :If again ⋄ :Leave ⋄ :EndIf 120 | 121 | ⍝ write the whole buffer to the socket as needed 122 | ⍞←'Writing to socket...' 123 | :While 0<≢buf 124 | ⍞←'.' 125 | r←forwardsock.Send buf 126 | buf↓⍨←r 127 | :EndWhile 128 | 129 | :If channel.EOF 130 | ⎕←'The server at ',remote_desthost,':',remote_destport,' disconnected!' 131 | →shutdown 132 | :EndIf 133 | ⎕←'Done' 134 | :EndRepeat 135 | :EndRepeat 136 | 137 | 138 | 139 | :EndTrap 140 | 141 | shutdown: 142 | 143 | ∇ 144 | :EndNamespace 145 | -------------------------------------------------------------------------------- /CInterop.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace CInterop 2 | ⍝ Low-level C interoperation 3 | 4 | init←0 5 | libc←⍬ ⍝ will be filled in by Init 6 | 7 | ⍝ What kind of architecture do we have? (32/64 bits) 8 | ∇ n←Bits 9 | n←{⊃⊃(//)⎕VFI(⍵∊⎕D)/⍵}⊃#.⎕WG'APLVersion' 10 | ∇ 11 | 12 | ∇ Init 13 | :If ∨/'Windows'⍷⊃#.⎕WG'APLVersion' 14 | InitWindows 15 | :Else 16 | InitUnix 17 | :EndIf 18 | init←1 19 | ∇ 20 | 21 | ∇ InitWindows 22 | libc←'msvcrt' 23 | 24 | ⎕NA'P msvcrt|malloc P' 25 | ⎕NA' msvcrt|free P' 26 | 27 | 'mread'⎕NA' msvcrt|memcpy =U1[] P P' 28 | 'mwrite'⎕NA'msvcrt|memcpy P =0#[]{} aren't allowed within structs 232 | 233 | ⍝ Default size of 'T': 2 on Windows and 4 on Unix 234 | td←2+2×'Windows'≢7↑⊃#.⎕WG'APLVersion' 235 | ⍝ Default size of 'P': (N/8), on an N-bit APL interpreter 236 | pd←{(⊃⊃(//)⎕VFI(⍵∊⎕D)/⍵)÷8}⊃#.⎕WG'APLVersion' 237 | 238 | dsz←('I'4)('U'4)('C'1)('T'td)('F'8)('D'16)('J'16)('P'pd) 239 | 240 | ⍝ process each field of the struct 241 | sz←0 242 | :For field :In (struct≠' ')⊆struct 243 | ⍝ do we have an array size? if not, use 1 244 | arrsz←1⌈⊃⊃(//)⎕VFI 1↓(+\+⌿1 ¯1×[1]'[]'∘.=field)/field 245 | 246 | ⍝ remove array from field if it's there 247 | field←(∧\field≠'[')/field 248 | 249 | ⍝ is there a size specified for the type? 250 | :If 0≠fsz←⊃⊃(//)⎕VFI(field∊⎕D)/field 251 | ⍝ Yes: use it 252 | sz+←fsz×arrsz 253 | :ElseIf ∨/fpos←field=⊃¨dsz 254 | ⍝ We have a standard size for it, use that 255 | sz+←arrsz×2⊃⊃fpos/dsz 256 | :Else 257 | ⍝ Invalid field specification 258 | ⎕SIGNAL⊂('EN'11)('Message' ('Invalid field specification: ',field)) 259 | :EndIf 260 | :EndFor 261 | ∇ 262 | :EndClass 263 | 264 | ⍝ This class loads some data into memory (as a byte array) 265 | ⍝ and gives you a pointer to it. The memory will be freed 266 | ⍝ automatically when the object goes out of scope. 267 | :Class DataBlock 268 | when←/⍨ 269 | 270 | :Field Private ptr←0 271 | :Field Private size←0 272 | ∇init data 273 | :Access Public 274 | :Implements Constructor 275 | 276 | ⍝ automatically convert strings to nul-terminated UTF-8 bytes 277 | :If ''≡0↑data 278 | data←0,⍨'UTF-8'⎕UCS data 279 | :EndIf 280 | 281 | ptr←#.CInterop.AllocMem (size←≢data) 282 | 'Memory allocation failed.' ⎕SIGNAL 999 when ptr=0 283 | 284 | ptr #.CInterop.WriteBytes data 285 | ∇ 286 | 287 | ∇destroy 288 | :Implements Destructor 289 | →(ptr=0)/0 290 | #.CInterop.FreeMem ptr 291 | ∇ 292 | 293 | ∇r←Ref 294 | :Access Public 295 | r←ptr 296 | ∇ 297 | 298 | ∇s←Size 299 | :Access Public 300 | s←size 301 | ∇ 302 | 303 | ⍝ Copy data in here from a given memory address 304 | ∇Load (inptr sz) 305 | :Access Public 306 | 'Size too big' ⎕SIGNAL (sz>size)/11 307 | 308 | {}#.CInterop.memcpy ptr inptr sz 309 | ∇ 310 | 311 | ∇d←Data 312 | :Access Public 313 | d←ptr #.CInterop.ReadBytes size 314 | ∇ 315 | :EndClass 316 | 317 | :EndNamespace 318 | -------------------------------------------------------------------------------- /Sock.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/CInterop.dyalog 2 | ⍝∇:require =/SSH_C_Helpers.dyalog 3 | :Namespace Sock 4 | ⍝⍝ Low-level wrapper around Berkeley sockets (on Unix) or Winsock (on Windows) 5 | ⍝⍝ Supports TCP/UDP only for now (as this is enough for the SSH library) 6 | 7 | ⍝ convert binary functions to bitwise ones 8 | bin←{2⊥⍺⍺/2⊥⍣¯1⊢(⍺⍺/⍬),⍵} 9 | 10 | ⍝ error 11 | SOCK_ERR←701 12 | 13 | :Namespace UnixErrors 14 | EPERM←1 15 | EINTR←4 16 | EBADF←9 17 | EAGAIN←11 18 | EACCESS←13 19 | EFAULT←14 20 | ENOTSOCK←88 21 | EPROTOTYPE←91 22 | EAFNOSUPPORT←97 23 | EADDRINUSE←98 24 | EADDRNOTAVAIL←99 25 | ENETUNREACH←101 26 | EISCONN←106 27 | ETIMEDOUT←110 28 | ECONNREFUSED←111 29 | EALREADY←114 30 | EINPROGRESS←115 31 | 32 | EAI_NONAME←¯2 33 | EAI_ADDRFAMILY←¯9 34 | EAI_AGAIN←¯3 35 | EAI_BADFLAGS←¯1 36 | EAI_FAIL←¯4 37 | EAI_FAMILY←¯6 38 | EAI_MEMORY←¯10 39 | EAI_NODATA←¯5 40 | EAI_SERVICE←¯8 41 | EAI_SOCKTYPE←¯7 42 | EAI_SYSTEM←¯11 43 | 44 | :EndNamespace 45 | 46 | :Namespace UnixConstants 47 | AF_UNSPEC←0 48 | AF_INET←2 49 | AF_INET6←10 50 | 51 | AI_ADDRCONFIG←32 52 | AI_V4MAPPED←8 53 | AI_CANONNAME←2 54 | AI_PASSIVE←1 55 | 56 | NI_NUMERICSERV←2 57 | 58 | SOCK_STREAM←1 59 | SOCK_DGRAM←2 60 | SOCK_RAW←3 61 | SOCK_SEQPACKET←5 62 | 63 | SOL_SOCKET←1 64 | SO_REUSEADDR←2 65 | 66 | POLLIN←1 67 | :EndNamespace 68 | 69 | :Namespace WindowsConstants 70 | AF_UNSPEC←0 71 | AF_INET←2 72 | AF_INET6←23 73 | 74 | AI_ADDRCONFIG←1024 75 | AI_V4MAPPED←2048 76 | AI_CANONNAME←2 77 | AI_PASSIVE←1 78 | 79 | NI_NUMERICSERV←8 80 | 81 | SOCK_STREAM←1 82 | SOCK_DGRAM←2 83 | SOCK_RAW←3 84 | SOCK_SEQPACKET←5 85 | 86 | SOL_SOCKET←65535 87 | SO_REUSEADDR←4 88 | 89 | POLLIN←768 90 | :EndNamespace 91 | 92 | init←0 93 | ∇ Init 94 | ⍝ don't initialize twice 95 | →init/0 96 | 97 | ⍝ Make sure the C interop code is initialized 98 | #.CInterop.Init 99 | 100 | ⍝ Make sure the C library is initialized 101 | #.SSH_C_Helpers.Init 102 | C←#.SSH_C_Helpers 103 | 104 | ⍝ Run OS-specific initialization 105 | :If 'Windows'≡7↑⊃#.⎕WG'APLVersion' 106 | InitWindows 107 | :Else 108 | InitUnix 109 | :EndIf 110 | 111 | ⎕NA'I ',socklib,'|socket I I I' 112 | ⎕NA'I ',socklib,'|bind I P P' 113 | ⎕NA'I ',socklib,'|listen I I' 114 | ⎕NA'I ',socklib,'|connect I P P' 115 | ⎕NA'I ',socklib,'|accept I P =P' 116 | ⎕NA'I ',socklib,'|send I I =U' 125 | 'setsockopt_int'⎕NA'I ',socklib,'|setsockopt I I I P' 129 | ⎕NA' ',socklib,'|freeaddrinfo P' 130 | 'getaddrinfo_p'⎕NA'I ',socklib,'|getaddrinfo P <0C P >P' 131 | 132 | ⍝ gethostinfo 133 | ⎕NA'I ',socklib,'|getnameinfo P U =0C U =0C U I' 134 | 135 | 136 | init←1 137 | ∇ 138 | 139 | ∇ InitUnix 140 | ⍝ on Unix, the socket functions are in libc 141 | socklib←#.CInterop.LibC 142 | 143 | ⍝ closing a socket is done using 'close' 144 | ⎕NA'I ',socklib,'|close I' 145 | 146 | ⍝ polling is done using 'poll' 147 | ⎕NA'I ',socklib,'|poll ={I U2 U2} I I' 148 | 149 | ⍝ geterrno is supplied by dyalib 150 | ⎕NA'I ',#.NonWindows.dyalib,'geterrno' 151 | 152 | 153 | Err←UnixErrors 154 | Cnst←UnixConstants 155 | ∇ 156 | 157 | ∇ InitWindows;wsaversion;wsadata;r 158 | ⍝ on Windows, the socket functions are in Ws2_32.dll 159 | socklib←'Ws2_32.dll' 160 | 161 | ⍝ closing a socket is done using 'closesocket', but we'll rename it 162 | 'close'⎕NA'I ',socklib,'|closesocket P' 163 | 164 | ⍝ geterrno is supplied (as WSAGetLastError) by the socket library 165 | 'geterrno'⎕NA'I ',socklib,'|WSAGetLastError' 166 | 167 | ⍝ polling is done using 'WSAPoll' 168 | 'poll'⎕NA'I ',socklib,'|WSAPoll ={I U2 U2} I I' 169 | 170 | ⍝ take care of Winsock initialization and cleanup 171 | _winsock←⎕NEW Winsock 172 | 173 | Err←UnixErrors 174 | Cnst←WindowsConstants 175 | ∇ 176 | 177 | ⍝ This class wraps the Winsock startup and cleanup. An instance of it is put into 178 | ⍝ the Sock namespace during initialization, and its destructor is responsible for 179 | ⍝ calling WSACleanup. 180 | :Class Winsock 181 | :Field Public wsaversion←256⊥2 2 ⍝ version 2.2 182 | :Field Public wsadata ⍝ if you really feel like poking around in it 183 | 184 | ∇init;start;r 185 | :Implements Constructor 186 | :Access Public 187 | 'start'⎕NA'I Ws2_32.dll|WSAStartup U2 P' 188 | wsadata←⎕NEW #.CInterop.DataBlock (256/0) ⍝ 256 bytes ought to be enough for everybody 189 | r←start wsaversion wsadata.Ref 190 | ⎕SIGNAL(r≠0)/⊂('EN'#.Sock.SOCK_ERR)('Message'('Winsock initialization failed: ',⍕r)) 191 | ∇ 192 | 193 | ∇destroy;stop;r 194 | :Implements Destructor 195 | :Access Private 196 | 'stop'⎕NA'I Ws2_32.dll|WSACleanup' 197 | r←stop 198 | ∇ 199 | :EndClass 200 | 201 | ⍝ Get host and port, given sockaddr 202 | ∇ (host port)←GetNameInfo addrblk;p;psz;r;host;port 203 | p←(psz←255)/' ' 204 | r host port←#.Sock.getnameinfo addrblk.Ref addrblk.Size p psz p psz #.Sock.Cnst.NI_NUMERICSERV 205 | :If r≠0 206 | ⎕SIGNAL('EN'SOCK_ERR)('Message' ('getnameinfo: ',⍕r)) 207 | :EndIf 208 | ∇ 209 | 210 | 211 | 212 | 213 | ⍝ Get address info, using the C library 214 | ∇ r←{family} GetAddrInfo args;host;port;pasv;rt;sptr;ptr;fam;type;len;name;sa;next;blk 215 | host port←args[1 2] 216 | pasv←0 217 | 218 | :If 3=≢args ⋄ pasv←args[3] ⋄ :EndIf 219 | :If 0=⎕NC'family' ⋄ family←0 ⋄ :EndIf 220 | 221 | ⍝ allow for the port to be passed as a numbers 222 | :If 0=⍬⍴0⍴port ⋄ port←⍕port ⋄ :EndIf 223 | 224 | ⍝ if no host is given, that means passive must be on 225 | pasv∨←0=≢host 226 | 227 | ⍝ this returns 0 instead of a platform-specific error if no host is found, 228 | ⍝ but the pointer will still be 0. 229 | rt sptr←C.apl_getaddrinfo family host port pasv 0 230 | 231 | r←⍬ 232 | 233 | ptr←sptr 234 | :While ptr≠0 235 | ⍝ Walk through the linked list and get all the etnries 236 | fam type len name sa next←C.apl_addr ptr 237 | 238 | ⍝ Copy the socket data into a DataBlokck so it can be used 239 | blk←⎕NEW #.CInterop.DataBlock (len/0) 240 | blk.Load sa len 241 | r,←⊂fam type name blk 242 | 243 | ptr←next 244 | :EndWhile 245 | 246 | :If sptr≠0 247 | {}C.apl_freeaddrinfo sptr 248 | :EndIf 249 | ∇ 250 | 251 | ⍝ Wrapper around 'poll'. 252 | :Class Poller 253 | :Field Private socks←⍬ 254 | :Field Private evts←⍬ 255 | :Field Private revts←⍬ 256 | 257 | ∇init 258 | :Access Public 259 | :Implements Constructor 260 | ∇ 261 | 262 | ⍝ register a socket to poll for events 263 | ∇sevts Register sock 264 | :Access Public 265 | :If sock∊socks 266 | ⍝ already have it 267 | 'Socket already registered.'⎕SIGNAL 11 268 | →0 269 | :EndIf 270 | socks ,← sock 271 | evts ,← sevts 272 | revts ,← 0 273 | ∇ 274 | 275 | ⍝ deregister a socket 276 | ∇Unregister sock;keep 277 | :Access Public 278 | :If ~sock∊socks 279 | ⍝ don't have it 280 | 'Socket not registered.'⎕SIGNAL 11 281 | →0 282 | :EndIf 283 | keep←socks≠sock 284 | socks/⍨←keep 285 | evts/⍨←keep 286 | revts/⍨←keep 287 | ∇ 288 | 289 | ⍝ Poll, return sockets for which events have occurred 290 | ∇s←Poll timeout;fds;r;rstruct;rfds;r_evts;r_revts 291 | :Access Public 292 | ⍝ make polling datastructure 293 | fds←↓⍉↑(socks.FD) evts revts 294 | r rstruct←#.Sock.poll fds (≢fds) timeout 295 | :If r=¯1 296 | ⎕SIGNAL⊂('EN'#.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 297 | :EndIf 298 | 299 | rfds r_evts r_revts←↓⍉↑rstruct 300 | revts←r_revts 301 | 302 | ⍝ select those sockets that had events 303 | s←(revts≠0)/socks 304 | ∇ 305 | :EndClass 306 | 307 | ⍝ IPV4 socket. 308 | :Class Socket 309 | :Field Private fd←¯1 310 | :Field Private fam 311 | :Field Private type 312 | :Field Private shouldClose←0 313 | :Field Private origAddr←⍬ 314 | 315 | bin←{2⊥⍺⍺/2⊥⍣¯1⊢(⍺⍺/⍬),⍵} 316 | 317 | ⍝ Make a socket 318 | ⍝ the third argument is ugly but for now it works 319 | ∇init (type_ fam_ dummy) 320 | :Access Public 321 | :Implements Constructor 322 | 323 | type←type_ 324 | fam←fam_ 325 | 326 | fd←#.Sock.socket fam type 0 327 | :If fd=¯1 328 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 329 | :EndIf 330 | ∇ 331 | 332 | ⍝ Make an IPV4 socket 333 | ∇init_ type_ 334 | :Access Public 335 | :Implements Constructor 336 | fam←#.Sock.Cnst.AF_INET 337 | type←type_ 338 | fd←#.Sock.socket fam type 0 339 | :If fd=¯1 340 | ⎕SIGNAL ⊂('EN' #.SOCK.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 341 | :EndIf 342 | ∇ 343 | 344 | 345 | ⍝ Initializer to pass a socket FD into 346 | ∇init_sock (addrblk fd_) 347 | :Access Public 348 | :Implements Constructor 349 | fd←fd_ 350 | shouldClose←1 351 | origAddr←addrblk 352 | ∇ 353 | 354 | ⍝ Return the originating host and port if this socket came from 'listen' 355 | ∇(host port)←ListenConnection;h;p;sin_addr 356 | :Access Public 357 | :If origAddr≡⍬ 358 | host port←'' 0 359 | :Return 360 | :EndIf 361 | 362 | host port←#.Sock.GetNameInfo origAddr 363 | 364 | ∇ 365 | 366 | ∇destroy 367 | :Access Private 368 | :Implements Destructor 369 | 370 | →(fd=¯1)/0 371 | →(~shouldClose)/0 372 | Close 373 | ∇ 374 | 375 | ⍝ Set an integer socket option 376 | ∇setsockopt_int (level option value) 377 | :Access Public 378 | {}#.Sock.setsockopt_int fd level option value 4 379 | ∇ 380 | 381 | ⍝ Close the socket 382 | ∇Close;lfd 383 | :Access Public 384 | →(fd=¯1)/0 385 | lfd←fd 386 | shouldClose←0 387 | fd←¯1 388 | {}#.Sock.close lfd 389 | ∇ 390 | 391 | ⍝ Get the file descriptor 392 | ∇n←FD 393 | :Access Public 394 | n←fd 395 | ∇ 396 | 397 | ⍝ Opening a connection and binding are basically the same thing, so 398 | ⍝ here's an operator that abstracts over everything 399 | ∇(fn InitSock pasv) (host port);addrs;addr;rt;blk 400 | ⍝ find the data for the host and port 401 | addrs←fam #.Sock.GetAddrInfo (host port pasv) 402 | ⎕SIGNAL(0=≢addrs)/⊂('EN'11)('Message' 'Address not found.') 403 | 404 | ⍝ find one that matches our type 405 | addrs←(type=2⊃¨addrs)/addrs 406 | ⎕SIGNAL(0=≢addrs)/⊂('EN'11)('Message' ('No connection to address found using type ',⍕type)) 407 | 408 | ⍝ use the first one, if there is more than one left, they all must point to the same place 409 | addr←⊃addrs 410 | 411 | ⍝ the parsed address is contained in the 4th element 412 | blk←4⊃addr 413 | 414 | ⍝ either bind or connect the socket 415 | rt←fn fd blk.Ref blk.Size 416 | 417 | :If rt≠0 418 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 419 | :EndIf 420 | 421 | shouldClose←1 422 | ∇ 423 | 424 | ⍝ Bind a socket 425 | ⍝ 'Host' may be "" in order not to filter 426 | ∇Bind (host port) 427 | :Access Public 428 | (#.Sock.bind InitSock 1) (host port) 429 | ∇ 430 | 431 | ⍝ Listen on the socket once it is bound 432 | ∇Listen backlog;r 433 | :Access Public 434 | r←#.Sock.listen fd backlog 435 | :If r=¯1 436 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 437 | :EndIf 438 | ∇ 439 | 440 | ⍝ Accept a connection from the socket once it is listening 441 | ⍝ Will return a new socket object 442 | ∇sck←Accept;addrblk;rt;sz 443 | :Access Public 444 | addrblk←⎕NEW #.CInterop.DataBlock (256/0) ⍝ 256 bytes ought to be enough for anyone 445 | rt sz←#.Sock.accept fd addrblk.Ref 256 446 | :If rt=¯1 447 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 448 | :EndIf 449 | 450 | ⍝ we're still here, so I guess we can make a new socket 451 | sck←⎕NEW Socket (addrblk rt) 452 | ∇ 453 | 454 | ⍝ Connect it 455 | ∇Connect (host port) 456 | :Access Public 457 | (#.Sock.connect InitSock 0) (host port) 458 | ∇ 459 | 460 | ⍝ Send some data via the socket (bytes, numeric) 461 | ∇r←{flags} Send data;r 462 | :Access Public 463 | ⍝ no flags → 0 464 | :If 0=⎕NC'flags' ⋄ flags←0 ⋄ :EndIf 465 | 466 | ⍝ list of flags → or them together 467 | flags←∨bin flags 468 | 469 | r←#.Sock.send fd data (≢data) flags 470 | 471 | :If r<0 472 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 473 | :EndIf 474 | ∇ 475 | 476 | ⍝ Receive data from the socket (bytes, numeric) 477 | ∇data←{flags} Recv size;r 478 | :Access Public 479 | ⍝ no flags → 0 480 | :If 0=⎕NC'flags' ⋄ flags←0 ⋄ :EndIf 481 | ⍝ list of flags → or them together 482 | flags←∨bin flags 483 | 484 | ⍝ receive some data 485 | r data←#.Sock.recv fd (size/0) size flags 486 | 487 | :If r=¯1 488 | ⎕SIGNAL ⊂('EN' #.Sock.SOCK_ERR)('Message' (⍕#.Sock.geterrno)) 489 | :EndIf 490 | data←r↑data 491 | ∇ 492 | 493 | :EndClass 494 | 495 | :EndNamespace 496 | -------------------------------------------------------------------------------- /APLProcess.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/SSH.dyalog 2 | 3 | :Class APLProcess 4 | ⍝ Start (and eventually dispose of) a Process 5 | 6 | (⎕IO ⎕ML)←1 1 7 | 8 | :Field Public Args←'' 9 | :Field Public Ws←'' 10 | :Field Public Exe←'' 11 | :Field Public Proc←⎕NS '' 12 | :Field Public onExit←'' 13 | :Field Public RunTime←0 ⍝ Boolean or name of runtime executable 14 | :Field Public IsWin←0 15 | :Field Public IsSsh←0 16 | 17 | endswith←{w←,⍵ ⋄ a←,⍺ ⋄ w≡(-(⍴a)⌊⍴w)↑a} 18 | tonum←{⊃⊃(//)⎕VFI ⍵} 19 | eis←{2>|≡⍵:,⊂⍵ ⋄ ⍵} ⍝ enclose if simple 20 | 21 | ∇ path←SourcePath;source 22 | ⍝ Determine the source path of the class 23 | 24 | :Trap 6 25 | source←⍎'(⊃⊃⎕CLASS ⎕THIS).SALT_Data.SourceFile' ⍝ ⍎ works around a bug 26 | :Else 27 | :If 0=⍴source←{((⊃¨⍵)⍳⊃⊃⎕CLASS ⎕THIS)⊃⍵,⊂''}5177⌶⍬ 28 | source←⎕WSID 29 | :Else ⋄ source←4⊃source 30 | :EndIf 31 | :EndTrap 32 | path←{(-⌊/(⌽⍵)⍳'\/')↓⍵}source 33 | ∇ 34 | 35 | ∇ make1 args;rt;cmd;ws 36 | :Access Public Instance 37 | :Implements Constructor 38 | ⍝ args is: 39 | ⍝ [1] the workspace to load 40 | ⍝ [2] any command line arguments 41 | ⍝ {[3]} if present, a Boolean indicating whether to use the runtime version, OR a character vector of the executable name to run 42 | ⍝ {[4]} if present, the RIDE_INIT parameters to use 43 | ⍝ {[5]} if present, a log-file prefix for process output 44 | 45 | args←{2>|≡⍵:,⊂⍵ ⋄ ⍵}args 46 | args←5↑args,(⍴args)↓'' '' 0 '' '' 47 | (ws cmd rt RIDE_INIT OUT_FILE)←args 48 | IsWin←IsWindows 49 | IsMac←IsMacOS 50 | PATH←SourcePath 51 | Start(ws cmd rt) 52 | ∇ 53 | 54 | ∇ Run 55 | :Access Public Instance 56 | Start(Ws Args RunTime) 57 | ∇ 58 | 59 | ∇ Start(ws args rt);psi;pid;cmd;host;port;pubkey;keyfile;exe;z;output 60 | (Ws Args)←ws args 61 | args,←' RIDE_INIT="',RIDE_INIT,'"', (0≠≢RIDE_INIT)/' RIDE_SPAWNED=1' 62 | ⍝ NB Always set RIDE_INIT to override current process setting 63 | 64 | :If ~0 2 6∊⍨10|⎕DR rt ⍝ if rt is character or nested, it defines what to start 65 | Exe←(RunTimeName⍣rt) GetCurrentExecutable ⍝ else, deduce it 66 | :Else 67 | Exe←rt 68 | rt←0 69 | :EndIf 70 | 71 | :If IsWin∧~IsSsh←326=⎕DR Exe 72 | ⎕USING←'System,System.dll' 73 | psi←⎕NEW Diagnostics.ProcessStartInfo,⊂Exe(ws,' ',args) 74 | psi.WindowStyle←Diagnostics.ProcessWindowStyle.Minimized 75 | Proc←Diagnostics.Process.Start psi 76 | :Else ⍝ Unix 77 | :If ~∨/'LOG_FILE'⍷args ⍝ By default 78 | args,←' LOG_FILE=/dev/nul ' ⍝ no log file 79 | :EndIf 80 | 81 | :If IsSsh 82 | (host port pubkey keyfile exe)←Exe 83 | cmd←args,' ',exe,' +s -q ',ws 84 | IsWin←0 85 | Proc←SshProc host port pubkey keyfile cmd 86 | :Else 87 | z←⍕GetCurrentProcessId 88 | output←(1+×≢OUT_FILE)⊃'/dev/null' OUT_FILE 89 | pid←_SH'{ ',args,' ',Exe,' +s -q ',ws,' -c APLppid=',z,' ',output,' 2>&1 & } ; echo $!' 90 | Proc.Id←pid 91 | Proc.HasExited←HasExited 92 | :EndIf 93 | Proc.StartTime←⎕NEW Time ⎕TS 94 | :EndIf 95 | ∇ 96 | 97 | ∇ Close;count;limit 98 | :Implements Destructor 99 | WaitForKill&200 0.1 ⍝ Start a new thread to do the dirty work 100 | ∇ 101 | 102 | ∇ WaitForKill(limit interval);count 103 | :If (0≠⍴onExit)∧~HasExited ⍝ If the process is still alive 104 | :Trap 0 ⋄ ⍎onExit ⋄ :EndTrap ⍝ Try this 105 | 106 | count←0 107 | :While ~HasExited 108 | {}⎕DL interval 109 | count←count+1 110 | :Until count>limit 111 | :EndIf ⍝ OK, have it your own way 112 | 113 | {}Kill Proc 114 | ∇ 115 | 116 | ∇ r←IsWindows 117 | :Access Public Shared 118 | r←'Win'≡3↑platform←⊃#.⎕WG'APLVersion' 119 | ∇ 120 | 121 | ∇ r←IsMacOS 122 | :Access Public Shared 123 | r←'Mac'≡3↑platform←⊃#.⎕WG'APLVersion' 124 | ∇ 125 | 126 | ∇ r←GetCurrentProcessId;t 127 | :Access Public Shared 128 | :If IsWin 129 | r←⍎'t'⎕NA'U4 kernel32|GetCurrentProcessId' 130 | :ElseIf IsSsh 131 | r←Proc.Pid 132 | :Else 133 | r←tonum⊃_SH'echo $PPID' 134 | :EndIf 135 | ∇ 136 | 137 | ∇ r←GetCurrentExecutable;⎕USING;t;gmfn 138 | :Access Public Shared 139 | :If IsWin 140 | r←'' 141 | :Trap 0 142 | 'gmfn'⎕NA'U4 kernel32|GetModuleFileName* P =T[] U4' 143 | r←⊃⍴/gmfn 0(1024⍴' ')1024 144 | :EndTrap 145 | :If 0∊⍴r 146 | ⎕USING←'System,system.dll' 147 | r←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG' 148 | r←r,(~(¯1↑r)∊'\/')/'/' ⍝ Add separator if necessary 149 | r←r,(Diagnostics.Process.GetCurrentProcess.ProcessName),'.exe' 150 | :EndIf 151 | ⍝:ElseIf IsSsh 152 | ⍝ ∘∘∘ ⍝ Not supported 153 | :Else ⍝ SSH just uses UNIX commands 154 | t←⊃_PS'-o args -p ',⍕GetCurrentProcessId ⍝ AWS 155 | :If '"'''∊⍨⊃t ⍝ if command begins with ' or " 156 | r←{⍵/⍨{∧\⍵∨≠\⍵}⍵=⊃⍵}t 157 | :Else 158 | r←{⍵↑⍨¯1+1⍳⍨(¯1↓0,⍵='\')<⍵=' '}t ⍝ otherwise find first non-escaped space (this will fail on files that end with '\\') 159 | :EndIf 160 | :EndIf 161 | ∇ 162 | 163 | ∇ r←RunTimeName exe 164 | ⍝ Assumes that: 165 | ⍝ Windows runtime ends in "rt.exe" 166 | ⍝ *NIX runtime ends in ".rt" 167 | r←exe 168 | :If IsWin 169 | :If 'rt.exe'≢¯6↑{('rt.ex',⍵)[⍵⍳⍨'RT.EX',⍵]}exe ⍝ deal with case insensitivity 170 | r←'rt.exe',⍨{(~∨\⌽<\⌽'.'=⍵)/⍵}exe 171 | :EndIf 172 | :Else 173 | r←exe,('.rt'≢¯3↑exe)/'.rt' 174 | :EndIf 175 | ∇ 176 | 177 | 178 | ∇ r←KillChildren Exe;kids;⎕USING;p;m;i;mask 179 | :Access Public Shared 180 | ⍝ returns [;1] pid [;2] process name of any processes that were not killed 181 | r←0 2⍴0 '' 182 | :If ~0∊⍴kids←ListProcesses Exe ⍝ All child processes using the exe 183 | :If IsWin 184 | ⎕USING←'System,system.dll' 185 | p←Diagnostics.Process.GetProcessById¨kids[;1] 186 | p.Kill 187 | ⎕DL 1 188 | :If 0≠⍴p←(~p.HasExited)/p 189 | ⎕DL 1 190 | p.Kill 191 | ⎕DL 1 192 | :If ∨/m←~p.HasExited 193 | r←(kids[;1]∊m/p.Id)⌿kids 194 | :EndIf 195 | :EndIf 196 | ⍝:ElseIf IsSsh 197 | ⍝ ∘∘∘ 'Shoot' works for SSH as well 198 | :Else 199 | mask←(⍬⍴⍴kids)⍴0 200 | :For i :In ⍳⍴mask 201 | mask[i]←Shoot kids[i;1] 202 | :EndFor 203 | r←(~mask)⌿kids 204 | :EndIf 205 | :EndIf 206 | ∇ 207 | 208 | ∇ r←{all}ListProcesses procName;me;⎕USING;procs;unames;names;name;i;pn;kid;parent;mask;n 209 | :Access Public 210 | ⍝ returns either my child processes or all processes 211 | ⍝ procName is either '' for all children, or the name of a process 212 | ⍝ r[;1] - child process number (Id) 213 | ⍝ r[;2] - child process name 214 | me←GetCurrentProcessId 215 | r←0 2⍴0 '' 216 | procName←,procName 217 | all←{6::⍵ ⋄ all}0 ⍝ default to just my childen 218 | 219 | :If IsWin 220 | ⎕USING←'System,system.dll' 221 | 222 | :If 0∊⍴procName ⋄ procs←Diagnostics.Process.GetProcesses'' 223 | :Else ⋄ procs←Diagnostics.Process.GetProcessesByName⊂procName ⋄ :EndIf 224 | :If all 225 | r←↑procs.(Id ProcessName) 226 | r⌿⍨←r[;1]≠me 227 | :Else 228 | :If 0<⍴procs 229 | unames←∪names←procs.ProcessName 230 | :For name :In unames 231 | :For i :In ⍳n←1+.=(,⊂name)⍳names 232 | pn←name,(n≠1)/'#',⍕i 233 | :Trap 0 ⍝ trap here just in case a process disappeared before we get to it 234 | parent←⎕NEW Diagnostics.PerformanceCounter('Process' 'Creating Process Id'pn) 235 | :If me=parent.NextValue 236 | kid←⎕NEW Diagnostics.PerformanceCounter('Process' 'Id Process'pn) 237 | r⍪←(kid.NextValue)name 238 | :EndIf 239 | :EndTrap 240 | :EndFor 241 | :EndFor 242 | :EndIf 243 | :EndIf 244 | ⍝:ElseIf IsSsh 245 | ⍝ ∘∘∘ 246 | :Else ⍝ Linux / SSH (_PS takes care of sending the command over SSH if necessary) 247 | ⍝ unfortunately, Ubuntu (and perhaps others) report the PPID of tasks started via ⎕SH as 1 248 | ⍝ so, the best we can do at this point is identify processes that we tagged with ppid= 249 | mask←' '∧.=procs←' ',↑_PS'-eo pid,cmd',((~all)/' | grep APLppid=',(⍕GetCurrentProcessId)),(0<⍴procName)/' | grep ',procName,' | grep -v grep' ⍝ AWS 250 | mask∧←2≥+\mask 251 | procs←↓¨mask⊂procs 252 | mask←me≠tonum¨1⊃procs ⍝ remove my task 253 | procs←mask∘/¨procs[1 2] 254 | mask←1 255 | :If 0<⍴procName 256 | mask←∨/¨(procName,' ')∘⍷¨(2⊃procs),¨' ' 257 | :EndIf 258 | mask>←∨/¨'grep '∘⍷¨2⊃procs ⍝ remove procs that are for the searches 259 | procs←mask∘/¨procs 260 | r←↑[0.1]procs 261 | :EndIf 262 | ∇ 263 | 264 | ∇ r←Kill;delay 265 | :Access Public Instance 266 | →(r←HasExited)⍴0 267 | delay←0.1 268 | :Trap 0 269 | :If IsWin 270 | Proc.Kill 271 | :Repeat 272 | ⎕DL delay 273 | delay+←delay 274 | :Until (delay>10)∨Proc.HasExited 275 | :Else ⍝ ssh or Local UNIX 276 | {}UNIXIssueKill 3 Proc.Id ⍝ issue strong interrupt 277 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 278 | :If ~HasExited 279 | {}UNIXIssueKill 9 Proc.Id ⍝ issue strong interrupt 280 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 281 | :AndIf ~HasExited 282 | :Repeat 283 | ⎕DL delay 284 | delay+←delay 285 | :Until (delay>10)∨Proc.HasExited~UNIXIsRunning Proc.Id 286 | :EndIf 287 | :EndIf 288 | r←Proc.HasExited 289 | :EndTrap 290 | ∇ 291 | 292 | ∇ r←Interrupt n;delay;z;hwnd 293 | :Access Public Instance 294 | 295 | '2 for weak or 3 for STRONG' ⎕SIGNAL (n∊2 3)↓11 296 | :If IsWin 297 | 'Not supported under Windows' ⎕SIGNAL 11 298 | ⍝ ↓↓↓ The following hack might work for a development interpreter 299 | ⍝ ↓↓↓ but 113 needs to be replace by the GUI ID for the Actions -> Interrups menu item 300 | hwnd←Proc.MainWindowHandle.ToInt32 301 | ⎕NA 'P user32|PostMessageA P U P P' 302 | z←PostMessageA hwnd 273 113 0 303 | ⍝ # 273 = WM_COMMAND 304 | ⍝ # 133 = the Actions -> Interrupt menu 305 | :Else 306 | {}UNIXIssueKill n Proc.Id ⍝ issue strong interrupt 307 | :EndIf 308 | ∇ 309 | 310 | ∇ r←Shoot Proc;MAX;res 311 | MAX←100 312 | r←0 313 | :If 0≠⎕NC⊂'Proc.HasExited' 314 | :Repeat 315 | :If ~Proc.HasExited 316 | :If IsWin 317 | Proc.Kill 318 | ⎕DL 0.2 319 | :ElseIf IsSsh 320 | {}UNIXIssueKill 3 Proc.Id 321 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 322 | ⍝ Proc.HasExited will be set by a different thread 323 | :Else 324 | {}UNIXIssueKill 3 Proc.Id ⍝ issue strong interrupt AWS 325 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 326 | :If ~Proc.HasExited←0∊⍴res←UNIXGetShortCmd Proc.Id ⍝ AWS 327 | Proc.HasExited∨←∨/''⍷⊃,/res 328 | :EndIf 329 | :EndIf 330 | :EndIf 331 | MAX-←1 332 | :Until Proc.HasExited∨MAX≤0 333 | r←Proc.HasExited 334 | :ElseIf 2=⎕NC'Proc' ⍝ just a process id? 335 | {}UNIXIssueKill 9 Proc.Id 336 | {}⎕DL 2 337 | r←~UNIXIsRunning Proc.Id ⍝ AWS 338 | :EndIf 339 | ∇ 340 | 341 | ∇ r←HasExited 342 | :Access public instance 343 | :If IsWin 344 | r←{0::⍵ ⋄ Proc.HasExited}1 345 | :Else 346 | r←Proc.HasExited←~UNIXIsRunning Proc.Id 347 | :EndIf 348 | ∇ 349 | 350 | ∇ r←IsRunning args;⎕USING;start;exe;pid;proc;diff;res 351 | :Access public shared 352 | ⍝ args - pid {exe} {startTS} 353 | r←0 354 | args←eis args 355 | (pid exe start)←3↑args,(⍴args)↓0 ''⍬ 356 | :If IsWin 357 | ⎕USING←'System,system.dll' 358 | :Trap 0 359 | proc←Diagnostics.Process.GetProcessById pid 360 | r←1 361 | :Else 362 | :Return 363 | :EndTrap 364 | :If ''≢exe 365 | r∧←exe≡proc.ProcessName 366 | :EndIf 367 | :If ⍬≢start 368 | :Trap 90 369 | diff←|-/#.DFSUtils.DateToIDN¨start(proc.StartTime.(Year Month Day Hour Minute Second Millisecond)) 370 | r∧←diff≤24 60 60 1000⊥0 1 0 0÷×/24 60 60 1000 ⍝ consider it a match within a 1 minute window 371 | :Else 372 | r←0 373 | :EndTrap 374 | :EndIf 375 | :ElseIf IsSsh 376 | r←UNIXIsRunning pid 377 | :Else 378 | r←UNIXIsRunning pid 379 | :EndIf 380 | ∇ 381 | 382 | ∇ r←SSHIsRunning pid;pids 383 | pids←(∊⎕VFI¨SSHRunCmd 'ps -o pid -x')~0 1 384 | r←pid∊pids 385 | ∇ 386 | 387 | ∇ r←Stop pid;proc 388 | :Access public 389 | ⍝ attempts to stop the process with processID pid 390 | :If IsWin 391 | ⎕USING←'System,system.dll' 392 | :Trap 0 393 | proc←Diagnostics.Process.GetProcessById pid 394 | :Else 395 | r←1 396 | :Return 397 | :EndTrap 398 | proc.Kill 399 | {}⎕DL 0.5 400 | r←~##.APLProcess.IsRunning pid 401 | :ElseIf IsSsh 402 | {}UNIXIssueKill 3 pid ⍝ sends the same command, but over SSH 403 | :ElseIf 404 | {}UNIXIssueKill 3 pid ⍝ issue strong interrupt 405 | :EndIf 406 | ∇ 407 | 408 | ∇ r←UNIXIsRunning pid;txt 409 | ⍝ Return 1 if the process is in the process table and is not a defunct 410 | r←0 411 | →(r←' '∨.≠txt←UNIXGetShortCmd pid)↓0 412 | r←~∨/''⍷txt 413 | ∇ 414 | 415 | ∇ {r}←UNIXIssueKill(signal pid) 416 | signal pid←⍕¨signal pid 417 | cmd←'kill -',signal,' ',pid,' >/dev/null 2>&1 ; echo $?' 418 | :If IsSsh 419 | r←SshRunCmd cmd 420 | :Else 421 | r←⎕SH cmd 422 | :EndIf 423 | ∇ 424 | 425 | ∇ r←UNIXGetShortCmd pid;cmd 426 | ⍝ Retrieve sort form of cmd used to start process 427 | cmd←(1+IsMac)⊃'cmd' 'command' 428 | cmd←'ps -o ',cmd,' -p ',(⍕pid),' 2>/dev/null ; exit 0' 429 | :If IsSsh 430 | r←⊃1↓SshRunCmd cmd 431 | :Else 432 | r←⊃1↓⎕SH cmd 433 | :EndIf 434 | ∇ 435 | 436 | ∇ r←_PS cmd;ps;fn 437 | ps←'ps ',⍨('AIX'≡3↑⊃'.'⎕WG'APLVersion')/'/usr/sysv/bin/' ⍝ Must use this ps on AIX 438 | :If IsSsh 439 | fn←SshRunCmd 440 | :Else 441 | fn←⎕SH 442 | :EndIf 443 | r←1↓fn ps,cmd,' 2>/dev/null; exit 0' ⍝ Remove header line 444 | ∇ 445 | 446 | ∇ r←{quietly}_SH cmd 447 | :Access public shared 448 | quietly←{6::⍵ ⋄ quietly}0 449 | :If quietly 450 | cmd←cmd,' &1' 451 | :EndIf 452 | r←{0::'' ⋄ ⎕SH ⍵}cmd 453 | ∇ 454 | 455 | :Class Time 456 | :Field Public Year 457 | :Field Public Month 458 | :Field Public Day 459 | :Field Public Hour 460 | :Field Public Minute 461 | :Field Public Second 462 | :Field Public Millisecond 463 | 464 | ∇ make ts 465 | :Implements Constructor 466 | :Access Public 467 | (Year Month Day Hour Minute Second Millisecond)←7↑ts 468 | ⎕DF(⍕¯2↑'00',⍕Day),'-',((12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec')[⍬⍴Month;]),'-',(⍕100|Year),' ',1↓⊃,/{':',¯2↑'00',⍕⍵}¨Hour Minute Second 469 | ∇ 470 | 471 | :EndClass 472 | 473 | ∇ r←ProcessUsingPort port;t 474 | ⍝ return the process ID of the process (if any) using a port 475 | :Access public shared 476 | r←⍬ 477 | :If IsWin 478 | :If ~0∊⍴t←_SH'netstat -a -n -o' 479 | :AndIf ~0∊⍴t/⍨←∨/¨'LISTENING'∘⍷¨t 480 | :AndIf ~0∊⍴t/⍨←∨/¨((':',⍕port),' ')∘⍷¨t 481 | r←∪∊¯1↑¨(//)∘⎕VFI¨t 482 | :EndIf 483 | :Else 484 | :If ~0∊⍴t←_SH'netstat -l -n -p 2>/dev/null | grep '':',(⍕port),' ''' 485 | r←∪∊{⊃(//)⎕VFI{(∧\⍵∊⎕D)/⍵}⊃¯1↑{⎕ML←3 ⋄ (' '≠⍵)⊂⍵}⍵}¨t 486 | :EndIf 487 | :EndIf 488 | ∇ 489 | 490 | ∇ r←MyDNSName;GCN 491 | :Access Public Shared 492 | 493 | :If IsWin 494 | 'GCN'⎕NA'I4 Kernel32|GetComputerNameEx* U4 >0T =U4' 495 | r←2⊃GCN 7 255 255 496 | :Return 497 | ⍝ ComputerNameNetBIOS = 0 498 | ⍝ ComputerNameDnsHostname = 1 499 | ⍝ ComputerNameDnsDomain = 2 500 | ⍝ ComputerNameDnsFullyQualified = 3 501 | ⍝ ComputerNamePhysicalNetBIOS = 4 502 | ⍝ ComputerNamePhysicalDnsHostname = 5 503 | ⍝ ComputerNamePhysicalDnsDomain = 6 504 | ⍝ ComputerNamePhysicalDnsFullyQualified = 7 <<< 505 | ⍝ ComputerNameMax = 8 506 | :ElseIf IsSsh 507 | r←⊃SshRunCmd'hostname' 508 | :ElseIf 509 | r←⊃_SH'hostname' 510 | :EndIf 511 | ∇ 512 | 513 | ∇ x←{raw} SshRunCmd cmd;sess;host;user;privkey;pubkey 514 | :If 0=⎕NC'raw' ⋄ raw←0 ⋄ :EndIf 515 | 516 | :If ~IsSsh 517 | :OrIf 0=⎕NC⊂'Proc.SSHInfo' 518 | ⎕SIGNAL⊂('EN'11)('Message' 'Not a SSH process instance.') 519 | :EndIf 520 | 521 | :Trap #.SSH.SSH_ERR 522 | host user pubkey privkey←Proc.SSHInfo 523 | sess←⎕NEW #.SSH.Session (host 22) 524 | sess.Userauth_Publickey user pubkey privkey '' 525 | x←(⎕UCS¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠10})⍣(~raw)⊢2⊃sess.Exec cmd 526 | :Else 527 | ⎕SIGNAL⊂('EN'11)('Message' ('SSH error:', ⎕DMX.Message)) 528 | :EndTrap 529 | ∇ 530 | 531 | ∇ Proc←SshProc(host user pubkey privkey cmd);sess;runcmd;pids;listpids;pid 532 | ⍝ run a command and split the output on newlines 533 | runcmd←{ 534 | rv dat←⍺.Exec ⍵ 535 | rv≠0:⎕SIGNAL('EN'11)('Command failed with error ',(⍕rv),': ',⍕⍵) 536 | ⎕UCS¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠10}dat 537 | } 538 | 539 | :Trap #.SSH.SSH_ERR 540 | sess←⎕NEW #.SSH.Session (host 22) 541 | sess.Userauth_Publickey user pubkey privkey '' 542 | 543 | ⍝ list the PIDs of currently running Dyalog instances 544 | listpids←{ 545 | pids←sess runcmd'ps -o pid,command -u ',user,' |grep dyalog|grep -v grep|grep -v sh\ |awk ''{print $1}''' 546 | (∊⎕VFI¨pids)~0 1 547 | } 548 | 549 | pids←listpids⍬ 550 | 551 | Proc←⎕NS'' 552 | Proc.HasExited←0 553 | Proc.SSHInfo←host user pubkey privkey 554 | Proc.tid←{SshRun ⍵ Proc}&cmd 555 | 556 | ⎕DL 1 ⍝ wait for process to start 557 | 558 | :If 1≤≢pid←(listpids⍬)~pids 559 | Proc.Id←Proc.Pid←⊃pid 560 | :Else 561 | ⍝ No new Dyalog process was started 562 | ⎕SIGNAL('EN'11)('Message' ('Process did not start.')) 563 | :EndIf 564 | 565 | :Else 566 | ⎕SIGNAL⊂('EN'11)('Message' ('SSH error: ',⎕DMX.Message)) 567 | :EndTrap 568 | ∇ 569 | 570 | ∇ SshRun (cmd proc);host;user;pubkey;privkey;sess 571 | host user pubkey privkey←proc.SSHInfo 572 | sess←⎕NEW #.SSH.Session (host 22) 573 | sess.Userauth_Publickey user pubkey privkey '' 574 | {}sess.Exec cmd 575 | proc.HasExited ← 1 576 | ∇ 577 | 578 | :EndClass 579 | -------------------------------------------------------------------------------- /SSH.dyalog: -------------------------------------------------------------------------------- 1 | ⍝∇:require =/CInterop.dyalog 2 | ⍝∇:require =/SSH_C_Helpers.dyalog 3 | ⍝∇:require =/Sock.dyalog 4 | 5 | :Namespace SSH 6 | ⍝ APL bindings for the libssh2 library 7 | 8 | SSH_ERR←801 ⍝ signaled when there's something wrong with the SSH library 9 | 10 | ⍝ convert binary functions to bitwise ones 11 | bin←{2⊥⍺⍺/2⊥⍣¯1⊢(⍺⍺/⍬),⍵} 12 | 13 | ∇ r←ScriptPath 14 | ⍝ Adám's SourceFile function 15 | r←{ ⍝ Get pathname to sourcefile for item ⍵ 16 | c←⎕NC⊂,⍕⍵ 17 | c=2.1:(SALT_Var_Data.VD[;1]⍳⊂⍵(⊢,~)'#.')⊃SALT_Var_Data.VD[;2],⊂'' 18 | c∊3.1 3.2 4.1 4.2:1↓⊃('§'∘=⊂⊢)∊¯2↑⎕NR ⍵ 19 | (r←326=⎕DR ⍵)∨c∊9+0.1×⍳8:{6::'' ⋄ ''≡f←⊃(4∘⊃¨(/⍨)(⍵≡⊃)¨)5177⌶⍬:⍵.SALT_Data.SourceFile ⋄ f}⍎⍣(~r)⊢⍵ 20 | '' 21 | }⎕THIS 22 | ∇ 23 | 24 | ⍝ base64 encode/decode 25 | base64enc←{ 26 | vals←⎕A,819⌶⎕A,⎕D,'+/' 27 | bits←,⍉(8/2)⊤,⍵ 28 | base64←2⊥⍉↑(⎕IO=6|⍳⍴bits)⊂bits 29 | vals[⎕IO+base64],((××4-⊢)4|⍴base64)/'=' 30 | } 31 | 32 | base64dec←{ 33 | vals←⎕A,819⌶⎕A,⎕D,'+/' 34 | bits←,⍉(6/2)⊤(vals⍳⍵~'=')-⎕IO 35 | bits←(⊢↑⍨⍴-8|⍴)bits 36 | 2⊥⍉↑(⎕IO=8|⍳⍴bits)⊂bits 37 | } 38 | 39 | init←0 40 | ∇ Init;r 41 | →init/0 ⍝ don't initialize twice 42 | 43 | ⍝ Make sure the C helper is initialized 44 | #.SSH_C_Helpers.Init 45 | 46 | ⍝ Make sure the socket library is initialized 47 | #.Sock.Init 48 | 49 | ⍝ load the library functions 50 | C.LoadLib 51 | 52 | ⍝ run the libssh2 initialization 53 | r←C.libssh2_init 0 54 | 55 | ⍝ signal an error if it did not work 56 | ⎕SIGNAL(r≠0)⊂('EN'SSH_ERR)('Message' (⍕r)) 57 | 58 | init←1 59 | ∇ 60 | 61 | ⍝ An SSH session 62 | :Class Session 63 | :Field Private Shared S ⍝ shorthand for the namespace 64 | :Field Private Shared C ⍝ shorthand for all the C functions 65 | :Field Private session←0 66 | :Field Private socket←⍬ ⍝ socket associated with the session, if there is one 67 | 68 | ⍝ Fill in arguments with default values. 69 | defaults←{ 70 | (≢⍺)↑⍵,(≢⍵)↓⍺ 71 | } 72 | 73 | ⍝ Make an error message 74 | ∇r←EMSG 75 | r←⍕LastError 76 | ∇ 77 | 78 | ∇r←Ref 79 | :Access Public 80 | r←session 81 | ∇ 82 | 83 | ∇destroy 84 | :Implements Destructor 85 | →(session=0)/0 86 | Disconnect S.SSH_DISCONNECT_BY_APPLICATION 'Session destructor called' 87 | ∇ 88 | 89 | ∇Disconnect args;reas;desc;lang;localsess 90 | :Access Public 91 | →(session=0)/0 92 | reas desc lang←(S.SSH_DISCONNECT_BY_APPLICATION '' '')defaults args 93 | localsess←session 94 | session←0 95 | {}C.libssh2_session_disconnect_ex localsess reas desc lang 96 | {}C.libssh2_session_free localsess 97 | ∇ 98 | 99 | ⍝ common initialization routine 100 | ∇_init_common 101 | ⍝ put the namespaces in the shorthand fields 102 | S ← #.SSH 103 | C ← #.SSH.C 104 | 105 | ⍝ is the SSH lib initialized yet? 106 | :If ~#.SSH.init ⋄ #.SSH.Init ⋄ :EndIf 107 | 108 | ⍝ make a session 109 | ⍝ we don't do callbacks into APL, because APL doesn't do callbacks into APL... 110 | session←C.libssh2_session_init_ex 0 0 0 0 111 | ⎕SIGNAL(session=0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 112 | ∇ 113 | 114 | ⍝ Initialize the session 115 | ∇init 116 | :Implements Constructor 117 | :Access Public 118 | _init_common 119 | ∇ 120 | 121 | ⍝ Utility initializer, which does socket creation, connection, and handshake 122 | ⍝ all at once (if you don't need anything special) 123 | ∇init_connect (host port);sock;addr;fam 124 | :Implements Constructor 125 | :Access Public 126 | _init_common ⍝ initialization 127 | 128 | ⍝ find whether to use IPV6 or IPV4 129 | addr←#.Sock.GetAddrInfo (host (⍕port)) 130 | 131 | :If 0=≢addr 132 | ⍝ None found 133 | ⎕SIGNAL⊂('EN'6)('Message' 'No such host.') 134 | :EndIf 135 | 136 | ⍝ find family of first type of addr (= what to use) 137 | fam←⊃⊃addr 138 | 139 | sock←⎕NEW #.Sock.Socket (#.Sock.Cnst.SOCK_STREAM fam 0) 140 | 141 | sock.Connect (host port) 142 | Handshake sock 143 | ∇ 144 | 145 | ⍝ Handshake. 'sock' should be a socket object 146 | ∇Handshake sock;r 147 | :Access Public 148 | r←C.libssh2_session_handshake session sock.FD 149 | ⎕SIGNAL(r≠0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 150 | socket←sock 151 | ∇ 152 | 153 | ⍝ Get the current host key 154 | ∇(key type)←Hostkey;len;p;blk 155 | :Access Public 156 | p len type←C.libssh2_session_hostkey session 0 0 157 | ⎕SIGNAL(p=0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 158 | ⍝ I'm assuming "len" is given for a reason, so let's make sure not to read past it 159 | blk←⎕NEW #.CInterop.DataBlock (len/0) 160 | blk.Load p len 161 | ⍝ Return the key base64-encoded 162 | key←S.base64enc blk.Data 163 | ∇ 164 | 165 | ⍝ Get the hostkey hash, as a byte vector 166 | ⍝ Type may be 'MD5' or 'SHA1' (or, of course, LIBSSH2_HOSTKEY_HASH_...) 167 | ∇hash←HostkeyHash type;blk;sz;ptr 168 | :Access Public 169 | sz←0 170 | 171 | ⍝ support string input 172 | :If 'md5'≡819⌶type ⋄ type←S.HOSTKEY_HASH_MD5 173 | :ElseIf 'sha1'≡819⌶type ⋄ type←S.HOSTKEY_HASH_SHA1 174 | :EndIf 175 | 176 | ⍝ get the size (as defined in the documentation) 177 | :If type≡S.HOSTKEY_HASH_MD5 ⋄ sz←16 ⋄ :EndIf 178 | :If type≡S.HOSTKEY_HASH_SHA1 ⋄ sz←20 ⋄ :EndIf 179 | 180 | ⍝ if the size is still unknown, then the hash type was invalid 181 | ⎕SIGNAL(sz=0)/⊂('EN'11)('Message' ('Invalid hash type: ',∊⍕type)) 182 | 183 | ⍝ ask for the hash type 184 | ptr←C.libssh2_hostkey_hash session type 185 | ⎕SIGNAL(ptr=0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 186 | 187 | ⍝ reserve some memory to store the hash type 188 | blk←⎕NEW #.CInterop.DataBlock (sz/0) 189 | blk.Load ptr sz 190 | hash←blk.Data 191 | ∇ 192 | 193 | ⍝ Get a list of supported authentication methods. 194 | ⍝ libssh2 returns this as a comma-delineated string, this being APL 195 | ⍝ we can return a string vector instead. 196 | ⍝ Internally, this works by trying to authenticate with SSH_USERAUTH_NONE, 197 | ⍝ might you find a server that supports this, we won't get a list back. 198 | ⍝ 199 | ⍝ This function will check if that authentication succeeded, and will then 200 | ⍝ return the empty vector. If the lack of result was due to an error, an error 201 | ⍝ will be signaled. 202 | ∇list←UserauthList username;ptr;str 203 | :Access Public 204 | 205 | ptr←C.libssh2_userauth_list session username (≢username) 206 | 207 | :If ptr=0 208 | ⍝ did the "authentication" succeed? 209 | :If Authenticated 210 | ⍝ such security 211 | list←⍬ 212 | :Else 213 | ⍝ this really is an error 214 | ⎕SIGNAL⊂('EN'S.SSH_ERR)('Message' EMSG) 215 | :EndIf 216 | :Else 217 | ⍝ we have a pointer to a comma-delimited string 218 | str←#.CInterop.ReadCStr ptr 219 | list←(str≠',')⊆str 220 | :EndIf 221 | ∇ 222 | 223 | ⍝Try to authenticate by password 224 | ∇Userauth_Password (name password);r 225 | :Access Public 226 | r←C.libssh2_userauth_password_ex session name (≢name) password (≢password) 0 227 | ⎕SIGNAL (r≠0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 228 | ∇ 229 | 230 | ⍝ Try to authenticate by public key 231 | ∇Userauth_Publickey (name pubkey privkey pass);r;pkblk;pkptr 232 | :Access Public 233 | 234 | ⍝ 'pubkey' may be zero, and must therefore be passed in as a pointer 235 | pkptr←0 236 | :If 0≠≢pubkey 237 | ⍝ a public key is given, so we need to turn it into a C string 238 | pkblk←⎕NEW #.CInterop.DataBlock pubkey 239 | pkptr←pkblk.Ref 240 | :EndIf 241 | 242 | r←C.libssh2_userauth_publickey_fromfile_ex session name (≢name) pkptr privkey pass 243 | ⎕SIGNAL (r≠0)/⊂('EN'S.SSH_ERR)('Message' EMSG) 244 | ∇ 245 | 246 | ⍝ Return the last error, as a string. 247 | ∇(num msg)←LastError;msgp;msgl 248 | :Access Public 249 | num msgp msgl←C.libssh2_session_last_error session 0 0 0 250 | msg←#.CInterop.ReadCStr msgp 251 | ∇ 252 | 253 | ⍝ See if this session is authenticated. Returns a boolean. 254 | ∇r←Authenticated 255 | :Access Public 256 | r←C.libssh2_userauth_authenticated session 257 | ∇ 258 | 259 | ∇r←GetBlocking 260 | :Access Public 261 | r←C.libssh2_session_get_blocking session 262 | ∇ 263 | 264 | ∇SetBlocking b 265 | :Access Public 266 | {}C.libssh2_session_set_blocking session b 267 | ∇ 268 | 269 | ⍝ Make a new session channel 270 | ∇ch←Channel_Open_Session;lch 271 | :Access Public 272 | lch←⎕NEW S.⍙Channel ⎕THIS 273 | lch._start_open_session 274 | ch←lch 275 | ∇ 276 | 277 | ⍝ Make a new channel 278 | ∇ch←Channel_Direct_TCPIP (desthost destport shost sport);lch 279 | :Access Public 280 | lch←⎕NEW S.⍙Channel ⎕THIS 281 | lch._start_Direct_TCPIP (desthost destport shost sport) 282 | ch←lch 283 | ∇ 284 | 285 | ⍝ Start an SCP transfer 286 | ∇(ch stat)←SCP_Recv path;lch;sb 287 | :Access Public 288 | lch←⎕NEW S.⍙Channel ⎕THIS 289 | sb←lch._start_scp_recv path 290 | stat←#.SSH_C_Helpers.stat sb 291 | ch←lch 292 | ∇ 293 | 294 | ⍝ Start an SCP upload 295 | ∇ch←SCP_Send args;path;mode;size;mtime;atime;lch 296 | :Access Public 297 | 'Path, mode, size are mandatory.'⎕SIGNAL(3>≢args)/6 298 | path mode size←args[⍳3] 299 | mtime atime←3↓5↑args,0,0 300 | lch←⎕NEW S.⍙Channel ⎕THIS 301 | lch._start_scp_send (path mode size mtime atime) 302 | ch←lch 303 | ∇ 304 | 305 | :Section Convenience 306 | ⍝ Read a file over SCP, in one go. 307 | ∇(stat data)←ReadFile path;chan;size;amt;agn;d 308 | :Access Public 309 | :If ~Authenticated ⋄ 'Not authenticated' ⎕SIGNAL 11 ⋄ :EndIf 310 | 311 | chan stat←SCP_Recv path 312 | data←⍬ 313 | size←⊃stat 314 | :While 0I I' 825 | ⎕NA'I ',l,'|libssh2_channel_free& P' 826 | ⎕NA'I ',l,'|libssh2_channel_get_exit_signal P >P >P >P >P >P >P' 827 | ⎕NA'I ',l,'|libssh2_channel_get_exit_status P' 828 | ⎕NA'I ',l,'|libssh2_channel_handle_extended_data2 P I' 829 | ⎕NA'P ',l,'|libssh2_channel_open_ex& P <0C U U U <0C U' 830 | ⎕NA'I ',l,'|libssh2_channel_process_startup& P <0C U <0C U' 831 | ⎕NA'I ',l,'|libssh2_channel_read_ex& P I =U1[] P' 832 | ⎕NA'I ',l,'|libssh2_channel_receive_window_adjust2& P U8 U1 >U' 833 | ⎕NA'I ',l,'|libssh2_channel_request_pty_ex& P <0C U <0C U I I I I' 834 | ⎕NA'I ',l,'|libssh2_channel_send_eof& P' 835 | ⎕NA' ',l,'|libssh2_channel_set_blocking& P I' 836 | ⎕NA'I ',l,'|libssh2_channel_setenv_ex& P <0C U <0C U' 837 | ⎕NA'I ',l,'|libssh2_channel_wait_closed& P' 838 | ⎕NA'I ',l,'|libssh2_channel_wait_eof& P' 839 | ⎕NA'U8 ',l,'|libssh2_channel_window_read_ex& P >U8 >U8' 840 | ⎕NA'U8 ',l,'|libssh2_channel_window_write_ex& P >U8' 841 | ⎕NA'I ',l,'|libssh2_channel_write_ex& P I I' 848 | ⎕NA'I ',l,'|libssh2_knownhost_addc P <0C <0C <0C P <0C P I P' 849 | ⎕NA'I ',l,'|libssh2_knownhost_check P <0C <0C P I P' 850 | ⎕NA'I ',l,'|libssh2_knownhost_del P P' 851 | ⎕NA' ',l,'|libssh2_knownhost_free P' 852 | ⎕NA'I ',l,'|libssh2_knownhost_get P >P P' 853 | ⎕NA'I ',l,'|libssh2_knownhost_init P' 854 | ⎕NA'I ',l,'|libssh2_knownhost_readfile P <0C I' 855 | ⎕NA'I ',l,'|libssh2_knownhost_readline P <0C P I' 856 | ⎕NA'I ',l,'|libssh2_knownhost_writefile P <0C I' 857 | ⎕NA'I ',l,'|libssh2_knownhost_writeline P P =U1[] P >P I' 858 | ⎕NA'I ',l,'|libssh2_poll ={U1 P U8 U8}[] U U8' 859 | ⎕NA'I ',l,'|libssh2_publickey_add_ex P <0C U8 P >I' 875 | ⎕NA'P ',l,'|libssh2_session_init_ex P P P P' 876 | ⎕NA'I ',l,'|libssh2_session_last_errno P' 877 | ⎕NA'I ',l,'|libssh2_session_last_error P >P >I I' 878 | ⎕NA'I ',l,'|libssh2_session_method_pref P I <0C' 879 | ⎕NA'P ',l,'|libssh2_session_methods P I' 880 | ⎕NA' ',l,'|libssh2_session_set_blocking P I' 881 | ⎕NA'I ',l,'|libssh2_session_set_last_error P I <0C' 882 | ⎕NA' ',l,'|libssh2_session_set_timeout P I8' 883 | ⎕NA'I ',l,'|libssh2_session_supported_algs P I >P' 884 | 885 | ⎕NA'I ',l,'|libssh2_sftp_close_handle P' 886 | ⎕NA'I ',l,'|libssh2_sftp_fstat_ex P P I' 887 | ⎕NA'I ',l,'|libssh2_sftp_fstatvfs P >{U8 U8 U8 U8 U8 U8 U8 U8 U8 U8 U8}' 888 | ⎕NA'I ',l,'|libssh2_sftp_fsync P' 889 | ⎕NA'I ',l,'|libssh2_sftp_init P' 890 | ⎕NA'U8 ',l,'|libssh2_sftp_last_error P' 891 | ⎕NA'I ',l,'|libssh2_sftp_mkdir_ex P <0C U U8' 892 | ⎕NA'P ',l,'|libssh2_sftp_open_ex P <0C U U8 I8 I' 893 | ⎕NA'I ',l,'|libssh2_sftp_read& P =U1[] P' 894 | ⎕NA'I ',l,'|libssh2_sftp_readdir_ex P =U1[] P =U1[] P >{U8 U8 U8 U8 U8 U8 U8}' 895 | ⎕NA'I ',l,'|libssh2_sftp_rename_ex P <0C U <0C U U8' 896 | ⎕NA'I ',l,'|libssh2_sftp_rmdir_ex P <0C U' 897 | ⎕NA' ',l,'|libssh2_sftp_seek64 P U8' 898 | ⎕NA'I ',l,'|libssh2_sftp_shutdown P' 899 | ⎕NA'I ',l,'|libssh2_sftp_stat_ex P <0C U I >{U8 U8 U8 U8 U8 U8 U8}' 900 | ⎕NA'I ',l,'|libssh2_sftp_statvfs P <0C P >{U8 U8 U8 U8 U8 U8 U8 U8 U8 U8 U8}' 901 | ⎕NA'I ',l,'|libssh2_sftp_symlink_ex P <0C U <0C U I' 902 | ⎕NA'U8 ',l,'|libssh2_sftp_tell64 P' 903 | ⎕NA'I ',l,'|libssh2_sftp_unlink_ex P <0C U' 904 | ⎕NA'I ',l,'|libssh2_sftp_write& P <0C P' 905 | 906 | ⎕NA'I ',l,'|libssh2_userauth_authenticated P' 907 | ⎕NA'P ',l,'|libssh2_userauth_list P <0C U' 908 | ⎕NA'I ',l,'|libssh2_userauth_password_ex P <0C U <0C U P' 909 | ⎕NA'I ',l,'|libssh2_userauth_publickey_fromfile_ex P <0C U P <0C <0C' 910 | ⎕NA'I ',l,'|libssh2_userauth_publickey_frommemory P <0C U <0C U <0C U <0C' 911 | 912 | ⎕NA'P ',l,'|libssh2_version I' 913 | 914 | 915 | ∇ 916 | :EndNamespace 917 | 918 | :EndNamespace 919 | --------------------------------------------------------------------------------