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