1 module scp;
2 
3 import core.stdc.stdlib;
4 import std.stdio;
5 import std.string;
6 import std.file;
7 import std.path;
8 
9 import libssh.session;
10 import libssh.scp;
11 import libssh.utils;
12 import libssh.errors;
13 
14 import connect_ssh;
15 
16 static string[] sources;
17 static string destination;
18 static int verbosity = 0;
19 
20 struct location {
21     bool isSSH;
22     string user;
23     string host;
24     string path;
25     SSHSession session;
26     SSHSCP scp;
27     File file;
28 }
29 
30 enum Flag {
31     READ,
32     WRITE
33 }
34 
35 void usage(string argv0) {
36     stderr.writefln("Usage : %s [options] [[user@]host1:]file1 ... ", argv0);
37     stderr.writeln("                            [[user@]host2:]destination");
38     stderr.writefln("sample scp client - libssh-%s", sshVersion(0));
39     exit(0);
40 }
41 
42 bool opts(string[] argv) {
43     int i = 1;
44     while (i < argv.length) {
45         if (argv[i][0] != '-') {
46             break;
47         }
48         if (argv[i] == "-v") {
49             verbosity++;
50         } else {
51             stderr.writeln("unknown option %s", argv[i][1 .. $]);
52             usage(argv[0]);
53             return false;
54         }
55         i++;
56     }
57 
58     auto sourcesCount = cast(int) argv.length - i - 1;
59     if (sourcesCount < 1) {
60         usage(argv[0]);
61         return false;
62     }
63 
64     sources = argv[i .. $ - 1];
65     destination = argv[$ - 1];
66 
67     return true;
68 }
69 
70 location parseLocation(string loc) {
71     location result = location.init;
72 
73     auto colonIndex = loc.indexOf(':');
74     if (colonIndex >= 0) {
75         result.isSSH = true;
76         result.path = loc[colonIndex + 1 .. $];
77         auto atIndex = loc.indexOf('@');
78         if (atIndex >= 0) {
79             result.host = loc[atIndex + 1 .. colonIndex];
80             result.user = loc[0 .. atIndex];
81         } else {
82             result.host = loc[0 .. colonIndex];
83         }
84     } else {
85         result.isSSH = false;
86         result.path = loc;
87     }
88     return result;
89 }
90 
91 bool openLocation(ref location loc, Flag flag) {
92     try {
93         if (loc.isSSH && flag == Flag.WRITE) {
94             loc.session = sessionConnect(loc.host, loc.user, cast(LogVerbosity) verbosity);
95             if (loc.session is null) {
96                 return false;
97             }
98 
99             loc.scp = loc.session.newScp(SCPMode.Write, loc.path);
100             scope(failure) {
101                 loc.scp.dispose();
102                 loc.scp = null;
103             }
104 
105             loc.scp.init();
106             return true;
107         } else if (loc.isSSH && flag == Flag.READ) {
108             loc.session = sessionConnect(loc.host, loc.user, cast(LogVerbosity) verbosity);
109             if (loc.session is null) {
110                 return false;
111             }
112             
113             loc.scp = loc.session.newScp(SCPMode.Read, loc.path);
114             scope(failure) {
115                 loc.scp.dispose();
116                 loc.scp = null;
117             }
118             
119             loc.scp.init();
120             return true;
121         } else {
122             if (isDir(loc.path)) {
123                 chdir(loc.path);
124                 return true;
125             }
126 
127             loc.file = File(loc.path, flag == Flag.READ ? "r" : "w");
128             return true;
129         }
130     } catch (SSHException sshException) {
131         stderr.writefln("SSH exception. Code = %d, Message:\n%s\n",
132             sshException.errorCode, sshException.msg);
133         return false;
134     } catch (Exception exception) {
135         stderr.writefln("Exception. Message:\n%s\n", exception.msg);
136         return false;
137     }
138 }
139 
140 /** @brief copies files from source location to destination
141  * @param src source location
142  * @param dest destination location
143  * @param recursive Copy also directories
144  */
145 void doCopy(ref location src, ref location dest, bool recursive) {
146     /* recursive mode doesn't work yet */
147 
148     ulong size;
149     uint mode;
150     string fileName;
151 
152     if (!src.isSSH) {
153         size = getSize(src.path);
154         mode = getAttributes(src.path) & 0x1ff;
155         fileName = baseName(src.path);
156     } else {
157         size = 0;
158         SCPRequest r;
159         do {
160             r = src.scp.pullRequest();
161             if (r == SCPRequest.NewDir) {
162                 src.scp.denyRequest("Not in recursive mode");
163                 continue;
164             }
165 
166             if (r == SCPRequest.NewFile) {
167                 size = src.scp.requestSize64();
168                 fileName = src.scp.requestFilename();
169                 mode = src.scp.requestPermissions();
170                 break;
171             }
172         } while (r != SCPRequest.NewFile);
173     }
174 
175     if (dest.isSSH) {
176         dest.scp.pushFile64(src.path, size, mode);
177     } else {
178         if (!dest.file.isOpen()) {
179             dest.file = File(fileName, "wb");
180         }
181         if (src.isSSH) {
182             src.scp.acceptRequest();
183         }
184     }
185 
186     ulong total = 0;
187     ubyte[16384] buffer;
188     ubyte[] outBuffer;
189 
190     do {
191         size_t r;
192         if (src.isSSH) {
193             r = src.scp.read(buffer);
194             if (r == 0) {
195                 break;
196             }
197             outBuffer = buffer[0 .. r];
198         } else {
199             outBuffer = src.file.rawRead(buffer);
200             r = outBuffer.length;
201             if (outBuffer.length == 0) {
202                 break;
203             }
204         }
205 
206         if (dest.isSSH) {
207             dest.scp.write(outBuffer);
208         } else {
209             dest.file.rawWrite(outBuffer);
210         }
211 
212         total += r;
213     } while (total < size);
214 
215     stdout.writefln("wrote %d bytes", total);
216 }
217 
218 int main(string[] argv) {
219     scope(exit) sshFinalize();
220 
221     if (!opts(argv)) {
222         return -1;
223     }
224 
225     stdout.writefln("verbose = %d", verbosity);
226 
227     try {
228         auto dest = parseLocation(destination);
229         if (!openLocation(dest, Flag.WRITE)) {
230             return -1;
231         }
232         scope(exit) {
233             if (dest.isSSH) {
234                 dest.scp.dispose();
235                 dest.session.dispose();
236             } else {
237                 dest.file.close();
238             }
239         }
240 
241         for (int i = 0; i < sources.length; i++) {
242             auto src = parseLocation(sources[i]);
243             if (!openLocation(src, Flag.READ)) {
244                 return -1;
245             }
246             scope(exit) {
247                 if (src.isSSH) {
248                     src.scp.dispose();
249                     src.session.dispose();
250                 } else {
251                     src.file.close();
252                 }
253             }
254 
255             doCopy(src, dest, false);
256         }
257 
258     } catch (SSHException sshException) {
259         stderr.writefln("SSH exception. Code = %d, Message:\n%s\n",
260             sshException.errorCode, sshException.msg);
261         return -1;
262     }
263 
264     return 0;
265 }