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