1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22
23%%% Description: SFTP functions
24
25-module(ssh_xfer).
26
27-export([attach/2, attach/3, connect/3, connect/4, connect/5]).
28-export([open/6, opendir/3, readdir/3, close/3, read/5, write/5,
29	 rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4,
30	 stat/4, fstat/4, lstat/4, setstat/4,
31	 readlink/3, fsetstat/4, symlink/4,
32	 protocol_version_request/2,
33	 xf_reply/2,
34	 xf_send_reply/3, xf_send_names/3, xf_send_name/4,
35	 xf_send_status/3, xf_send_status/4, xf_send_status/5,
36	 xf_send_handle/3, xf_send_attr/3, xf_send_data/3,
37	 encode_erlang_status/1,
38	 decode_open_flags/2, encode_open_flags/1,
39	 decode_ace_mask/1, decode_ext/1,
40	 decode_ATTR/2, encode_ATTR/2]).
41
42-include("ssh.hrl").
43-include("ssh_xfer.hrl").
44
45-import(lists, [foldl/3, reverse/1]).
46
47-define(is_set(F, Bits),
48	((F) band (Bits)) == (F)).
49
50-define(XFER_PACKET_SIZE, 65536).
51-define(XFER_WINDOW_SIZE, 20*?XFER_PACKET_SIZE).
52
53attach(CM, Opts) ->
54    open_xfer(CM, Opts, []).
55
56attach(CM, Opts, ChanOpts) ->
57    open_xfer(CM, Opts, ChanOpts).
58
59
60connect(Host, Port, Opts) ->
61    case ssh:connect(Host, Port, Opts) of
62	{ok, CM} -> open_xfer(CM, Opts, []);
63	Error -> Error
64    end.
65
66connect(Host, Port, Opts, Timeout) ->
67    connect(Host, Port, Opts, [], Timeout).
68
69connect(Host, Port, Opts, ChanOpts, Timeout) ->
70    case ssh:connect(Host, Port, Opts, Timeout) of
71	{ok, CM} -> open_xfer(CM, [{timeout, Timeout}|Opts], ChanOpts);
72	{error, Timeout} -> {error, timeout};
73	Error -> Error
74    end.
75
76
77open_xfer(CM, Opts, ChanOpts) ->
78    TMO = proplists:get_value(timeout, Opts, infinity),
79    WindowSize = proplists:get_value(window_size, ChanOpts,  ?XFER_WINDOW_SIZE),
80    PacketSize = proplists:get_value(packet_size, ChanOpts,  ?XFER_PACKET_SIZE),
81    case ssh_connection:session_channel(CM, WindowSize, PacketSize, TMO) of
82	{ok, ChannelId} ->
83	    {ok, ChannelId, CM};
84	Error ->
85	    Error
86    end.
87
88protocol_version_request(XF, Version) ->
89    xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(Version)>>).
90
91open(XF, ReqID, FileName, Access, Flags, Attrs) ->
92    Vsn = XF#ssh_xfer.vsn,
93    MBits = if Vsn >= 5 ->
94		    M = encode_ace_mask(Access),
95		    ?uint32(M);
96	       true ->
97		    (<<>>)
98	    end,
99    F = encode_open_flags(Flags),
100    xf_request(XF,?SSH_FXP_OPEN,
101	       [?uint32(ReqID),
102		?string_utf8(FileName),
103		MBits,
104		?uint32(F),
105		encode_ATTR(Vsn,Attrs)]).
106
107opendir(XF, ReqID, DirName) ->
108    xf_request(XF, ?SSH_FXP_OPENDIR,
109	       [?uint32(ReqID),
110		?string_utf8(DirName)]).
111
112
113close(XF, ReqID, Handle) ->
114    xf_request(XF, ?SSH_FXP_CLOSE,
115	       [?uint32(ReqID),
116		?binary(Handle)]).
117
118read(XF, ReqID, Handle, Offset, Length) ->
119    xf_request(XF, ?SSH_FXP_READ,
120	       [?uint32(ReqID),
121		?binary(Handle),
122		?uint64(Offset),
123		?uint32(Length)]).
124
125readdir(XF, ReqID, Handle) ->
126    xf_request(XF, ?SSH_FXP_READDIR,
127	       [?uint32(ReqID),
128		?binary(Handle)]).
129
130write(XF,ReqID, Handle, Offset, Data) ->
131    Data1 = if
132		is_binary(Data) ->
133		    Data;
134		is_list(Data) ->
135		    unicode:characters_to_binary(Data)
136	    end,
137    xf_request(XF,?SSH_FXP_WRITE,
138	       [?uint32(ReqID),
139		?binary(Handle),
140		?uint64(Offset),
141		?binary(Data1)]).
142
143%% Remove a file
144remove(XF, ReqID, File) ->
145    xf_request(XF, ?SSH_FXP_REMOVE,
146	       [?uint32(ReqID),
147		?string_utf8(File)]).
148
149%% Rename a file/directory
150rename(XF, ReqID, OldPath, NewPath, Flags) ->
151    Vsn = XF#ssh_xfer.vsn,
152    FlagBits
153	= if Vsn >= 5 ->
154		  F0 = encode_rename_flags(Flags),
155		  ?uint32(F0);
156	     true ->
157		  (<<>>)
158	  end,
159    xf_request(XF, ?SSH_FXP_RENAME,
160	       [?uint32(ReqID),
161		?string_utf8(OldPath),
162		?string_utf8(NewPath),
163		FlagBits]).
164
165
166
167%% Create directory
168mkdir(XF, ReqID, Path, Attrs) ->
169    xf_request(XF, ?SSH_FXP_MKDIR,
170	       [?uint32(ReqID),
171		?string_utf8(Path),
172		encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
173
174%% Remove a directory
175rmdir(XF, ReqID, Dir) ->
176    xf_request(XF, ?SSH_FXP_RMDIR,
177	       [?uint32(ReqID),
178		?string_utf8(Dir)]).
179
180%% Stat file
181stat(XF, ReqID, Path, Flags) ->
182    Vsn = XF#ssh_xfer.vsn,
183    AttrFlags = if Vsn >= 5 ->
184			F = encode_attr_flags(Vsn, Flags),
185			?uint32(F);
186		   true ->
187			[]
188		end,
189    xf_request(XF, ?SSH_FXP_STAT,
190	       [?uint32(ReqID),
191		?string_utf8(Path),
192		AttrFlags]).
193
194
195%% Stat file - follow symbolic links
196lstat(XF, ReqID, Path, Flags) ->
197    Vsn = XF#ssh_xfer.vsn,
198    AttrFlags = if Vsn >= 5 ->
199			F = encode_attr_flags(Vsn, Flags),
200			?uint32(F);
201		   true ->
202			[]
203		end,
204    xf_request(XF, ?SSH_FXP_LSTAT,
205	     [?uint32(ReqID),
206	      ?string_utf8(Path),
207	      AttrFlags]).
208
209%% Stat open file
210fstat(XF, ReqID, Handle, Flags) ->
211    Vsn = XF#ssh_xfer.vsn,
212    AttrFlags = if Vsn >= 5 ->
213			F = encode_attr_flags(Vsn, Flags),
214			?uint32(F);
215		   true ->
216			[]
217		end,
218    xf_request(XF, ?SSH_FXP_FSTAT,
219	       [?uint32(ReqID),
220		?binary(Handle),
221		AttrFlags]).
222
223%% Modify file attributes
224setstat(XF, ReqID, Path, Attrs) ->
225    xf_request(XF, ?SSH_FXP_SETSTAT,
226	       [?uint32(ReqID),
227		?string_utf8(Path),
228		encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
229
230
231%% Modify file attributes
232fsetstat(XF, ReqID, Handle, Attrs) ->
233    xf_request(XF, ?SSH_FXP_FSETSTAT,
234	       [?uint32(ReqID),
235		?binary(Handle),
236		encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
237
238%% Read a symbolic link
239readlink(XF, ReqID, Path) ->
240    xf_request(XF, ?SSH_FXP_READLINK,
241	       [?uint32(ReqID),
242		?string_utf8(Path)]).
243
244
245%% Create a symbolic link
246symlink(XF, ReqID, LinkPath, TargetPath) ->
247    LinkPath1 = unicode:characters_to_binary(LinkPath),
248    TargetPath1 = unicode:characters_to_binary(TargetPath),
249    xf_request(XF, ?SSH_FXP_SYMLINK,
250	       [?uint32(ReqID),
251		?binary(LinkPath1),
252		?binary(TargetPath1)]).
253
254%% Convert a path into a 'canonical' form
255realpath(XF, ReqID, Path) ->
256    xf_request(XF, ?SSH_FXP_REALPATH,
257	       [?uint32(ReqID),
258		?string_utf8(Path)]).
259
260extended(XF, ReqID, Request, Data) ->
261    xf_request(XF, ?SSH_FXP_EXTENDED,
262	       [?uint32(ReqID),
263		?string(Request),
264		?binary(Data)]).
265
266
267%% Send xfer request to connection manager
268xf_request(XF, Op, Arg) ->
269    CM = XF#ssh_xfer.cm,
270    Channel = XF#ssh_xfer.channel,
271    Data = if
272	       is_binary(Arg) ->
273		   Arg;
274	       is_list(Arg) ->
275		   list_to_binary(Arg)
276	   end,
277    Size = 1+size(Data),
278    ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]).
279
280xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) ->
281    Data = if
282	       is_binary(Arg) ->
283		   Arg;
284	       is_list(Arg) ->
285		   list_to_binary(Arg)
286	   end,
287    Size = 1 + size(Data),
288    ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]).
289
290xf_send_name(XF, ReqId, Name, Attr) ->
291    xf_send_names(XF, ReqId, [{Name, Attr}]).
292
293
294xf_send_handle(#ssh_xfer{cm = CM, channel = Channel},
295	       ReqId, Handle) ->
296    HLen = length(Handle),
297    Size = 1 + 4 + 4+HLen,
298    ToSend = [<<?UINT32(Size), ?SSH_FXP_HANDLE, ?UINT32(ReqId), ?UINT32(HLen)>>,
299	      Handle],
300    ssh_connection:send(CM, Channel, ToSend).
301
302xf_send_names(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn},
303	      ReqId, NamesAndAttrs) ->
304    Count = length(NamesAndAttrs),
305    {Data, Len} = encode_names(Vsn, NamesAndAttrs),
306    Size = 1 + 4 + 4 + Len,
307    ToSend = [<<?UINT32(Size),
308		?SSH_FXP_NAME,
309		?UINT32(ReqId),
310		?UINT32(Count)>>,
311	      Data],
312    ssh_connection:send(CM, Channel, ToSend).
313
314xf_send_status(XF, ReqId, ErrorCode) ->
315    xf_send_status(XF, ReqId, ErrorCode, "").
316
317xf_send_status(XF, ReqId, ErrorCode, ErrorMsg) ->
318    xf_send_status(XF, ReqId, ErrorCode, ErrorMsg, <<>>).
319
320xf_send_status(#ssh_xfer{cm = CM, channel = Channel},
321	       ReqId, ErrorCode, ErrorMsg, Data) ->
322    LangTag = "en",
323    ELen = length(ErrorMsg),
324    TLen = 2, %% length(LangTag),
325    Size = 1 + 4 + 4 + 4+ELen + 4+TLen + size(Data),
326    ToSend = [<<?UINT32(Size), ?SSH_FXP_STATUS, ?UINT32(ReqId),
327	       ?UINT32(ErrorCode)>>,
328	      <<?UINT32(ELen)>>, ErrorMsg,
329	      <<?UINT32(TLen)>>, LangTag,
330	      Data],
331    ssh_connection:send(CM, Channel, ToSend).
332
333xf_send_attr(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, ReqId, Attr) ->
334    EncAttr = encode_ATTR(Vsn, Attr),
335    ALen = size(EncAttr),
336    Size = 1 + 4 + ALen,
337    ToSend = [<<?UINT32(Size), ?SSH_FXP_ATTRS, ?UINT32(ReqId)>>, EncAttr],
338    ssh_connection:send(CM, Channel, ToSend).
339
340xf_send_data(#ssh_xfer{cm = CM, channel = Channel}, ReqId, Data) ->
341    DLen = size(Data),
342    Size = 1 + 4 + 4+DLen,
343    ToSend = [<<?UINT32(Size), ?SSH_FXP_DATA, ?UINT32(ReqId), ?UINT32(DLen)>>,
344	      Data],
345    ssh_connection:send(CM, Channel, ToSend).
346
347xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status),
348	      ?UINT32(ELen), Err:ELen/binary,
349	      ?UINT32(LLen), Lang:LLen/binary,
350	      Reply/binary >> ) ->
351    Stat = decode_status(Status),
352    {status, ReqID, {Stat,binary_to_list(Err),binary_to_list(Lang),
353		     Reply}};
354xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status)>> ) ->
355    Stat = decode_status(Status),
356    {status, ReqID, {Stat,"","",<<>>}};
357xf_reply(_XF, <<?SSH_FXP_HANDLE, ?UINT32(ReqID),
358	      ?UINT32(HLen), Handle:HLen/binary>>) ->
359    {handle, ReqID, Handle};
360xf_reply(_XF, <<?SSH_FXP_DATA, ?UINT32(ReqID),
361	      ?UINT32(DLen), Data:DLen/binary>>) ->
362    {data, ReqID, Data};
363xf_reply(XF, <<?SSH_FXP_NAME, ?UINT32(ReqID),
364	      ?UINT32(Count), AData/binary>>) ->
365    {name, ReqID, decode_names(XF#ssh_xfer.vsn, Count, AData)};
366xf_reply(XF, <<?SSH_FXP_ATTRS, ?UINT32(ReqID),
367	      AData/binary>>) ->
368    {A, _} = decode_ATTR(XF#ssh_xfer.vsn, AData),
369    {attrs, ReqID, A};
370xf_reply(_XF, <<?SSH_FXP_EXTENDED_REPLY, ?UINT32(ReqID),
371	      RData>>) ->
372    {extended_reply, ReqID, RData}.
373
374
375
376decode_status(Status) ->
377    case Status of
378	?SSH_FX_OK -> ok;
379	?SSH_FX_EOF -> eof;
380	?SSH_FX_NO_SUCH_FILE -> no_such_file;
381	?SSH_FX_PERMISSION_DENIED -> permission_denied;
382	?SSH_FX_FAILURE -> failure;
383	?SSH_FX_BAD_MESSAGE -> bad_message;
384	?SSH_FX_NO_CONNECTION -> no_connection;
385	?SSH_FX_CONNECTION_LOST -> connection_lost;
386	?SSH_FX_OP_UNSUPPORTED -> op_unsupported;
387	?SSH_FX_INVALID_HANDLE -> invalid_handle;
388	?SSH_FX_NO_SUCH_PATH -> no_such_path;
389	?SSH_FX_FILE_ALREADY_EXISTS -> file_already_exists;
390	?SSH_FX_WRITE_PROTECT -> write_protect;
391	?SSH_FX_NO_MEDIA -> no_media;
392	?SSH_FX_NO_SPACE_ON_FILESYSTEM -> no_space_on_filesystem;
393	?SSH_FX_QUOTA_EXCEEDED -> quota_exceeded;
394	?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle;
395	?SSH_FX_LOCK_CONFlICT -> lock_conflict;
396	?SSH_FX_NOT_A_DIRECTORY -> not_a_directory;
397	?SSH_FX_FILE_IS_A_DIRECTORY -> file_is_a_directory;
398	?SSH_FX_CANNOT_DELETE -> cannot_delete;
399	_ -> {error,Status}
400    end.
401
402encode_erlang_status(Status) ->
403    case Status of
404	ok -> ?SSH_FX_OK;
405	eof -> ?SSH_FX_EOF;
406	enoent -> ?SSH_FX_NO_SUCH_FILE;
407	eacces -> ?SSH_FX_PERMISSION_DENIED;
408	eisdir -> ?SSH_FX_FILE_IS_A_DIRECTORY;
409	eperm -> ?SSH_FX_CANNOT_DELETE;
410	eexist -> ?SSH_FX_FILE_ALREADY_EXISTS;
411	_ -> ?SSH_FX_FAILURE
412    end.
413
414decode_ext(<<?UINT32(NameLen), Name:NameLen/binary,
415	    ?UINT32(DataLen), Data:DataLen/binary,
416	    Tail/binary>>) ->
417    [{binary_to_list(Name), binary_to_list(Data)}
418     | decode_ext(Tail)];
419decode_ext(<<>>) ->
420    [].
421
422%%
423%% Encode rename flags
424%%
425encode_rename_flags(Flags) ->
426    encode_bits(
427      fun(overwrite) -> ?SSH_FXP_RENAME_OVERWRITE;
428	 (atomic) -> ?SSH_FXP_RENAME_ATOMIC;
429	 (native) -> ?SSH_FXP_RENAME_NATIVE
430      end, Flags).
431
432%% decode_rename_flags(F) ->
433%%     decode_bits(F,
434%% 		[{?SSH_FXP_RENAME_OVERWRITE, overwrite},
435%% 		 {?SSH_FXP_RENAME_ATOMIC, atomic},
436%% 		 {?SSH_FXP_RENAME_NATIVE, native}]).
437
438
439encode_open_flags(Flags) ->
440    encode_bits(
441      fun (read) -> ?SSH_FXF_READ;
442	  (write) -> ?SSH_FXF_WRITE;
443	  (append) -> ?SSH_FXF_APPEND;
444	  (creat) -> ?SSH_FXF_CREAT;
445	  (trunc)  -> ?SSH_FXF_TRUNC;
446	  (excl)   -> ?SSH_FXF_EXCL;
447	  (create_new) -> ?SSH_FXF_CREATE_NEW;
448	  (create_truncate) -> ?SSH_FXF_CREATE_TRUNCATE;
449	  (open_existing) -> ?SSH_FXF_OPEN_EXISTING;
450	  (open_or_create) -> ?SSH_FXF_OPEN_OR_CREATE;
451	  (truncate_existing) -> ?SSH_FXF_TRUNCATE_EXISTING;
452	  (append_data) -> ?SSH_FXF_ACCESS_APPEND_DATA;
453	  (append_data_atomic) -> ?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC;
454	  (text_mode) -> ?SSH_FXF_ACCESS_TEXT_MODE;
455	  (read_lock) -> ?SSH_FXF_ACCESS_READ_LOCK;
456	  (write_lock) -> ?SSH_FXF_ACCESS_WRITE_LOCK;
457	  (delete_lock) -> ?SSH_FXF_ACCESS_DELETE_LOCK
458      end, Flags).
459
460encode_ace_mask(Access) ->
461    encode_bits(
462      fun(read_data) -> ?ACE4_READ_DATA;
463	 (list_directory) -> ?ACE4_LIST_DIRECTORY;
464	 (write_data) -> ?ACE4_WRITE_DATA;
465	 (add_file) -> ?ACE4_ADD_FILE;
466	 (append_data) -> ?ACE4_APPEND_DATA;
467	 (add_subdirectory) -> ?ACE4_ADD_SUBDIRECTORY;
468	 (read_named_attrs) -> ?ACE4_READ_NAMED_ATTRS;
469	 (write_named_attrs) -> ?ACE4_WRITE_NAMED_ATTRS;
470	 (execute) -> ?ACE4_EXECUTE;
471	 (delete_child) -> ?ACE4_DELETE_CHILD;
472	 (read_attributes) -> ?ACE4_READ_ATTRIBUTES;
473	 (write_attributes) -> ?ACE4_WRITE_ATTRIBUTES;
474	 (delete) -> ?ACE4_DELETE;
475	 (read_acl) -> ?ACE4_READ_ACL;
476	 (write_acl) -> ?ACE4_WRITE_ACL;
477	 (write_owner) -> ?ACE4_WRITE_OWNER;
478	 (synchronize) -> ?ACE4_SYNCHRONIZE
479      end, Access).
480
481decode_ace_mask(F) ->
482    decode_bits(F,
483		[
484		 {?ACE4_READ_DATA, read_data},
485		 {?ACE4_LIST_DIRECTORY, list_directory},
486		 {?ACE4_WRITE_DATA, write_data},
487		 {?ACE4_ADD_FILE, add_file},
488		 {?ACE4_APPEND_DATA, append_data},
489		 {?ACE4_ADD_SUBDIRECTORY, add_subdirectory},
490		 {?ACE4_READ_NAMED_ATTRS, read_named_attrs},
491		 {?ACE4_WRITE_NAMED_ATTRS, write_named_attrs},
492		 {?ACE4_EXECUTE, execute},
493		 {?ACE4_DELETE_CHILD, delete_child},
494		 {?ACE4_READ_ATTRIBUTES, read_attributes},
495		 {?ACE4_WRITE_ATTRIBUTES, write_attributes},
496		 {?ACE4_DELETE, delete},
497		 {?ACE4_READ_ACL, read_acl},
498		 {?ACE4_WRITE_ACL, write_acl},
499		 {?ACE4_WRITE_OWNER, write_owner},
500		 {?ACE4_SYNCHRONIZE, synchronize}
501		]).
502
503decode_open_flags(Vsn, F) when Vsn =< 3 ->
504    decode_bits(F,
505		[
506		 {?SSH_FXF_READ, read},
507		 {?SSH_FXF_WRITE, write},
508		 {?SSH_FXF_APPEND, append},
509		 {?SSH_FXF_CREAT, creat},
510		 {?SSH_FXF_TRUNC, trunc},
511		 {?SSH_FXF_EXCL, excl}
512		 ]);
513decode_open_flags(Vsn, F) when Vsn >= 4 ->
514    R = decode_bits(F,
515		    [
516		     {?SSH_FXF_ACCESS_APPEND_DATA, append_data},
517		     {?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC, append_data_atomic},
518		     {?SSH_FXF_ACCESS_TEXT_MODE, text_mode},
519		     {?SSH_FXF_ACCESS_READ_LOCK, read_lock},
520		     {?SSH_FXF_ACCESS_WRITE_LOCK, write_lock},
521		     {?SSH_FXF_ACCESS_DELETE_LOCK, delete_lock}
522		    ]),
523    AD = case F band ?SSH_FXF_ACCESS_DISPOSITION of
524	     ?SSH_FXF_CREATE_NEW -> create_new;
525	     ?SSH_FXF_CREATE_TRUNCATE -> create_truncate;
526	     ?SSH_FXF_OPEN_EXISTING -> open_existing;
527	     ?SSH_FXF_OPEN_OR_CREATE -> open_or_create;
528	     ?SSH_FXF_TRUNCATE_EXISTING -> truncate_existing
529	 end,
530    [AD | R].
531
532encode_ace_type(Type) ->
533    case Type of
534	access_allowed -> ?ACE4_ACCESS_ALLOWED_ACE_TYPE;
535	access_denied  -> ?ACE4_ACCESS_DENIED_ACE_TYPE;
536	system_audit   -> ?ACE4_SYSTEM_AUDIT_ACE_TYPE;
537	system_alarm   -> ?ACE4_SYSTEM_ALARM_ACE_TYPE
538    end.
539
540decode_ace_type(F) ->
541    case F of
542	?ACE4_ACCESS_ALLOWED_ACE_TYPE -> access_allowed;
543	?ACE4_ACCESS_DENIED_ACE_TYPE -> access_denied;
544	?ACE4_SYSTEM_AUDIT_ACE_TYPE -> system_audit;
545	?ACE4_SYSTEM_ALARM_ACE_TYPE -> system_alarm
546    end.
547
548encode_ace_flag(Flag) ->
549    encode_bits(
550      fun(file_inherit) -> ?ACE4_FILE_INHERIT_ACE;
551	 (directory_inherit) -> ?ACE4_DIRECTORY_INHERIT_ACE;
552	 (no_propagte_inherit) -> ?ACE4_NO_PROPAGATE_INHERIT_ACE;
553	 (inherit_only) -> ?ACE4_INHERIT_ONLY_ACE;
554	 (successful_access) -> ?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG;
555	 (failed_access) -> ?ACE4_FAILED_ACCESS_ACE_FLAG;
556	 (identifier_group) -> ?ACE4_IDENTIFIER_GROUP
557      end, Flag).
558
559decode_ace_flag(F) ->
560    decode_bits(F,
561		[
562		 {?ACE4_FILE_INHERIT_ACE, file_inherit},
563		 {?ACE4_DIRECTORY_INHERIT_ACE, directory_inherit},
564		 {?ACE4_NO_PROPAGATE_INHERIT_ACE, no_propagte_inherit},
565		 {?ACE4_INHERIT_ONLY_ACE, inherit_only},
566		 {?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, successful_access},
567		 {?ACE4_FAILED_ACCESS_ACE_FLAG, failed_access},
568		 {?ACE4_IDENTIFIER_GROUP, identifier_group}
569		]).
570
571encode_attr_flags(Vsn, all) ->
572    encode_attr_flags(Vsn,
573		      [size, uidgid, permissions,
574		       acmodtime, accesstime, createtime,
575		       modifytime, acl, ownergroup, subsecond_times,
576		       bits, extended]);
577encode_attr_flags(Vsn, Flags) ->
578    encode_bits(
579      fun(size) -> ?SSH_FILEXFER_ATTR_SIZE;
580	 (uidgid) when Vsn =<3 -> ?SSH_FILEXFER_ATTR_UIDGID;
581	 (permissions) -> ?SSH_FILEXFER_ATTR_PERMISSIONS;
582	 (acmodtime) when Vsn =< 3 -> ?SSH_FILEXFER_ATTR_ACMODTIME;
583	 (accesstime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACCESSTIME;
584	 (createtime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_CREATETIME;
585	 (modifytime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_MODIFYTIME;
586	 (acl) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACL;
587	 (ownergroup) when  Vsn >= 5 -> ?SSH_FILEXFER_ATTR_OWNERGROUP;
588	 (subsecond_times) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
589	 (bits) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_BITS;
590	 (extended) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_EXTENDED;
591	 (_) -> 0
592      end, Flags).
593
594encode_file_type(Type) ->
595    case Type of
596	regular -> ?SSH_FILEXFER_TYPE_REGULAR;
597	directory -> ?SSH_FILEXFER_TYPE_DIRECTORY;
598	symlink -> ?SSH_FILEXFER_TYPE_SYMLINK;
599	special -> ?SSH_FILEXFER_TYPE_SPECIAL;
600	unknown -> ?SSH_FILEXFER_TYPE_UNKNOWN;
601	other -> ?SSH_FILEXFER_TYPE_UNKNOWN;
602	socket -> ?SSH_FILEXFER_TYPE_SOCKET;
603	char_device -> ?SSH_FILEXFER_TYPE_CHAR_DEVICE;
604	block_device -> ?SSH_FILEXFER_TYPE_BLOCK_DEVICE;
605	fifo -> ?SSH_FILEXFER_TYPE_FIFO;
606	undefined -> ?SSH_FILEXFER_TYPE_UNKNOWN
607    end.
608
609decode_file_type(Type) ->
610    case Type of
611	?SSH_FILEXFER_TYPE_REGULAR -> regular;
612	?SSH_FILEXFER_TYPE_DIRECTORY -> directory;
613	?SSH_FILEXFER_TYPE_SYMLINK -> symlink;
614	?SSH_FILEXFER_TYPE_SPECIAL -> special;
615	?SSH_FILEXFER_TYPE_UNKNOWN -> other; % unknown
616	?SSH_FILEXFER_TYPE_SOCKET -> socket;
617	?SSH_FILEXFER_TYPE_CHAR_DEVICE -> char_device;
618	?SSH_FILEXFER_TYPE_BLOCK_DEVICE -> block_device;
619	?SSH_FILEXFER_TYPE_FIFO -> fifo
620    end.
621
622encode_attrib_bits(Bits) ->
623    encode_bits(
624      fun(readonly) -> ?SSH_FILEXFER_ATTR_FLAGS_READONLY;
625	 (system) -> ?SSH_FILEXFER_ATTR_FLAGS_SYSTEM;
626	 (hidden) -> ?SSH_FILEXFER_ATTR_FLAGS_HIDDEN;
627	 (case_insensitive) -> ?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE;
628	 (arcive) -> ?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE;
629	 (encrypted) -> ?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED;
630	 (compressed) -> ?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED;
631	 (sparse) -> ?SSH_FILEXFER_ATTR_FLAGS_SPARSE;
632	 (append_only) -> ?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY;
633	 (immutable) -> ?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE;
634	 (sync) -> ?SSH_FILEXFER_ATTR_FLAGS_SYNC
635      end, Bits).
636
637decode_attrib_bits(F) ->
638    decode_bits(F,
639		[{?SSH_FILEXFER_ATTR_FLAGS_READONLY, readonly},
640		 {?SSH_FILEXFER_ATTR_FLAGS_SYSTEM, system},
641		 {?SSH_FILEXFER_ATTR_FLAGS_HIDDEN, hidden},
642		 {?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, case_insensitive},
643		 {?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, arcive},
644		 {?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, encrypted},
645		 {?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, compressed},
646		 {?SSH_FILEXFER_ATTR_FLAGS_SPARSE, sparse},
647		 {?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, append_only},
648		 {?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, immutable},
649		 {?SSH_FILEXFER_ATTR_FLAGS_SYNC, sync}]).
650
651
652%%
653%% Encode file attributes
654%%
655encode_ATTR(Vsn, A) ->
656    {Flags,As} =
657	encode_As(Vsn,
658		  [{size, A#ssh_xfer_attr.size},
659		   {ownergroup, A#ssh_xfer_attr.owner},
660		   {ownergroup, A#ssh_xfer_attr.group},
661		   {permissions, A#ssh_xfer_attr.permissions},
662		   {acmodtime, A#ssh_xfer_attr.atime},
663		   {acmodtime, A#ssh_xfer_attr.mtime},
664		   {accesstime,  A#ssh_xfer_attr.atime},
665		   {subsecond_times, A#ssh_xfer_attr.atime_nseconds},
666		   {createtime,  A#ssh_xfer_attr.createtime},
667		   {subsecond_times, A#ssh_xfer_attr.createtime_nseconds},
668		   {modifytime,  A#ssh_xfer_attr.mtime},
669		   {subsecond_times, A#ssh_xfer_attr.mtime_nseconds},
670		   {acl, A#ssh_xfer_attr.acl},
671		   {bits, A#ssh_xfer_attr.attrib_bits},
672		   {extended, A#ssh_xfer_attr.extensions}],
673		  0, []),
674    Type = encode_file_type(A#ssh_xfer_attr.type),
675    Result = list_to_binary([?uint32(Flags),
676			     if Vsn >= 5 ->
677				     ?byte(Type);
678				true ->
679				     (<<>>)
680			     end, As]),
681    Result.
682
683
684encode_As(Vsn, [{_AName, undefined}|As], Flags, Acc) ->
685    encode_As(Vsn, As, Flags, Acc);
686encode_As(Vsn, [{AName, X}|As], Flags, Acc) ->
687    case AName of
688	size ->
689	    encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_SIZE,
690		      [?uint64(X) | Acc]);
691	ownergroup when Vsn=<4 ->
692	     encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_UIDGID,
693		       [?uint32(X) | Acc]);
694	ownergroup when Vsn>=5 ->
695	    X1 = list_to_binary(integer_to_list(X)), % TODO: check owner and group
696	    encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_OWNERGROUP,
697		      [?binary(X1) | Acc]);
698	permissions ->
699	    encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_PERMISSIONS,
700		      [?uint32(X) | Acc]);
701	acmodtime when Vsn=<3 ->
702	    encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_ACMODTIME,
703		      [?uint32(X) | Acc]);
704	accesstime when Vsn>=5 ->
705	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACCESSTIME,
706		      [?uint64(X) | Acc]);
707	createtime when Vsn>=5->
708	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_CREATETIME,
709		      [?uint64(X) | Acc]);
710	modifytime when Vsn>=5 ->
711	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_MODIFYTIME,
712		      [?uint64(X) | Acc]);
713	subsecond_times when Vsn>=5 ->
714	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,
715		      [?uint64(X) | Acc]);
716	acl when Vsn >=5 ->
717	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACL,
718		      [encode_acl(X) | Acc]);
719	bits when Vsn>=5 ->
720	    F = encode_attrib_bits(X),
721	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_BITS,
722		      [?uint32(F) | Acc]);
723	extended ->
724	    encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_EXTENDED,
725		      [encode_extensions(X) | Acc]);
726	_ ->
727	    encode_As(Vsn, As, Flags, Acc)
728    end;
729encode_As(_Vsn, [], Flags, Acc) ->
730    {Flags, reverse(Acc)}.
731
732
733decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) ->
734    {Type,Tail2} =
735	if Vsn =< 3 ->
736		{?SSH_FILEXFER_TYPE_UNKNOWN, Tail};
737	   true ->
738		<<?BYTE(T), TL/binary>> = Tail,
739		{T, TL}
740	end,
741    decode_As(Vsn,
742	      [{size, #ssh_xfer_attr.size},
743	       {ownergroup, #ssh_xfer_attr.owner},
744	       {ownergroup, #ssh_xfer_attr.group},
745	       {permissions, #ssh_xfer_attr.permissions},
746	       {acmodtime, #ssh_xfer_attr.atime},
747	       {acmodtime, #ssh_xfer_attr.mtime},
748	       {accesstime,  #ssh_xfer_attr.atime},
749	       {subsecond_times, #ssh_xfer_attr.atime_nseconds},
750	       {createtime,  #ssh_xfer_attr.createtime},
751	       {subsecond_times, #ssh_xfer_attr.createtime_nseconds},
752	       {modifytime,  #ssh_xfer_attr.mtime},
753	       {subsecond_times, #ssh_xfer_attr.mtime_nseconds},
754	       {acl, #ssh_xfer_attr.acl},
755	       {bits, #ssh_xfer_attr.attrib_bits},
756	       {extended, #ssh_xfer_attr.extensions}],
757	      #ssh_xfer_attr { type = decode_file_type(Type) },
758	      Flags,
759	      Tail2).
760
761decode_As(Vsn, [{AName, AField}|As], R, Flags, Tail) ->
762    case AName of
763	size when ?is_set(?SSH_FILEXFER_ATTR_SIZE, Flags) ->
764	    <<?UINT64(X), Tail2/binary>> = Tail,
765	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
766	ownergroup when ?is_set(?SSH_FILEXFER_ATTR_UIDGID, Flags),Vsn=<3 ->
767	    <<?UINT32(X), Tail2/binary>> = Tail,
768	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
769	ownergroup when ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP, Flags),Vsn>=5 ->
770	    <<?UINT32(Len), Bin:Len/binary, Tail2/binary>> = Tail,
771	    X = binary_to_list(Bin),
772	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
773
774	permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn>=5->
775	    <<?UINT32(X), Tail2/binary>> = Tail,
776	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
777
778	permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn=<3->
779	    <<?UINT32(X), Tail2/binary>> = Tail,
780	    R1 = setelement(AField, R, X),
781	    Type = case X band ?S_IFMT of
782		       ?S_IFDIR -> directory;
783		       ?S_IFCHR -> char_device;
784		       ?S_IFBLK -> block_device;
785		       ?S_IFIFO -> fifi;
786		       ?S_IFREG -> regular;
787		       ?S_IFSOCK -> socket;
788		       ?S_IFLNK -> symlink;
789		       _ -> unknown
790		   end,
791	    decode_As(Vsn, As, R1#ssh_xfer_attr { type=Type}, Flags, Tail2);
792
793	acmodtime when ?is_set(?SSH_FILEXFER_ATTR_ACMODTIME,Flags),Vsn=<3 ->
794	    <<?UINT32(X), Tail2/binary>> = Tail,
795	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
796	accesstime when ?is_set(?SSH_FILEXFER_ATTR_ACCESSTIME,Flags),Vsn>=5 ->
797	    <<?UINT64(X), Tail2/binary>> = Tail,
798	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
799	modifytime when ?is_set(?SSH_FILEXFER_ATTR_MODIFYTIME,Flags),Vsn>=5 ->
800	    <<?UINT64(X), Tail2/binary>> = Tail,
801	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
802	createtime when ?is_set(?SSH_FILEXFER_ATTR_CREATETIME,Flags),Vsn>=5 ->
803	    <<?UINT64(X), Tail2/binary>> = Tail,
804	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
805	subsecond_times when ?is_set(?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,Flags),Vsn>=5 ->
806	    <<?UINT32(X), Tail2/binary>> = Tail,
807	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
808	acl when ?is_set(?SSH_FILEXFER_ATTR_ACL, Flags), Vsn>=5 ->
809	    {X,Tail2} = decode_acl(Tail),
810	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
811	bits when ?is_set(?SSH_FILEXFER_ATTR_BITS, Flags), Vsn >=5 ->
812	    <<?UINT32(Y), Tail2/binary>> = Tail,
813	    X = decode_attrib_bits(Y),
814	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
815	extended when ?is_set(?SSH_FILEXFER_ATTR_EXTENDED, Flags) ->
816	    {X,Tail2} = decode_extended(Tail),
817	    decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
818	_ ->
819	    decode_As(Vsn, As, R, Flags, Tail)
820    end;
821decode_As(_Vsn, [], R, _, Tail) ->
822    {R, Tail}.
823
824
825
826
827decode_names(_Vsn, 0, _Data) ->
828    [];
829decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary,
830		      ?UINT32(LLen), _LongName:LLen/binary,
831		      Tail/binary>>) when Vsn =< 3 ->
832    Name = unicode:characters_to_list(FileName),
833    {A, Tail2} = decode_ATTR(Vsn, Tail),
834    [{Name, A} | decode_names(Vsn, I-1, Tail2)];
835decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary,
836		      Tail/binary>>) when Vsn >= 4 ->
837    Name = unicode:characters_to_list(FileName),
838    {A, Tail2} = decode_ATTR(Vsn, Tail),
839    [{Name, A} | decode_names(Vsn, I-1, Tail2)].
840
841encode_names(Vsn, NamesAndAttrs) ->
842    lists:mapfoldl(fun(N, L) -> encode_name(Vsn, N, L) end, 0, NamesAndAttrs).
843
844encode_name(Vsn, {NameUC,Attr}, Len) when Vsn =< 3 ->
845    Name = binary_to_list(unicode:characters_to_binary(NameUC)),
846    NLen = length(Name),
847    EncAttr = encode_ATTR(Vsn, Attr),
848    ALen = size(EncAttr),
849    NewLen = Len + NLen*2 + 4 + 4 + ALen,
850    {[<<?UINT32(NLen)>>, Name, <<?UINT32(NLen)>>, Name, EncAttr], NewLen};
851encode_name(Vsn, {NameUC,Attr}, Len) when Vsn >= 4 ->
852    Name = binary_to_list(unicode:characters_to_binary(NameUC)),
853    NLen = length(Name),
854    EncAttr = encode_ATTR(Vsn, Attr),
855    ALen = size(EncAttr),
856    {[<<?UINT32(NLen)>>, Name, EncAttr],
857     Len + 4 + NLen + ALen}.
858
859encode_acl(ACLList) ->
860    Count = length(ACLList),
861    [?uint32(Count) | encode_acl_items(ACLList)].
862
863encode_acl_items([ACE|As]) ->
864    Type = encode_ace_type(ACE#ssh_xfer_ace.type),
865    Flag = encode_ace_flag(ACE#ssh_xfer_ace.flag),
866    Mask = encode_ace_mask(ACE#ssh_xfer_ace.mask),
867    Who = ACE#ssh_xfer_ace.who,
868    [?uint32(Type), ?uint32(Flag), ?uint32(Mask),
869     ?string_utf8(Who) | encode_acl_items(As)];
870encode_acl_items([]) ->
871    [].
872
873
874decode_acl(<<?UINT32(Count), Tail/binary>>) ->
875    decode_acl_items(Count, Tail, []).
876
877decode_acl_items(0, Tail, Acc) ->
878    {reverse(Acc), Tail};
879decode_acl_items(I, <<?UINT32(Type),
880	       ?UINT32(Flag),
881	       ?UINT32(Mask),
882	       ?UINT32(WLen), BWho:WLen/binary,
883	       Tail/binary>>, Acc) ->
884    decode_acl_items(I-1, Tail,
885		     [#ssh_xfer_ace { type = decode_ace_type(Type),
886				      flag = decode_ace_flag(Flag),
887				      mask = decode_ace_mask(Mask),
888				      who = unicode:characters_to_list(BWho)} | Acc]).
889
890encode_extensions(Exts) ->
891    Count = length(Exts),
892    [?uint32(Count) | encode_ext(Exts)].
893
894encode_ext([{Type, Data} | Exts]) ->
895    [?string(Type), ?string(Data) | encode_ext(Exts)];
896encode_ext([]) ->
897    [].
898
899
900decode_extended(<<?UINT32(Count), Tail/binary>>) ->
901    decode_ext(Count, Tail, []).
902
903decode_ext(0, Tail, Acc) ->
904    {reverse(Acc), Tail};
905decode_ext(I, <<?UINT32(TLen), Type:TLen/binary,
906	       ?UINT32(DLen), Data:DLen/binary,
907	       Tail/binary>>,  Acc) ->
908    decode_ext(I-1, Tail, [{binary_to_list(Type), Data}|Acc]).
909
910
911
912%% Encode bit encoded flags
913encode_bits(Fun, BitNames) ->
914    encode_bits(Fun, 0, BitNames).
915
916encode_bits(Fun, F, [Bit|BitNames]) ->
917    encode_bits(Fun, Fun(Bit) bor F, BitNames);
918encode_bits(_Fun, F, []) ->
919    F.
920
921%% Decode bit encoded flags
922decode_bits(F, [{Bit,BitName}|Bits]) ->
923    if F band Bit == Bit ->
924	    [BitName | decode_bits(F, Bits)];
925       true ->
926	    decode_bits(F, Bits)
927    end;
928decode_bits(_F, []) ->
929    [].
930