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%% Protocol engine for trivial FTP 22%%------------------------------------------------------------------- 23 24-module(tftp_engine). 25 26%%%------------------------------------------------------------------- 27%%% Interface 28%%%------------------------------------------------------------------- 29 30%% application internal functions 31-export([ 32 daemon_start/1, 33 daemon_loop/1, 34 daemon_loop/3, %% Handle upgrade from old releases. Please, remove this function in next release. 35 client_start/4, 36 common_loop/6, 37 info/1, 38 change_config/2 39 ]). 40 41%% module internal 42-export([ 43 daemon_init/1, 44 server_init/2, 45 client_init/2, 46 wait_for_msg/3, 47 callback/4 48 ]). 49 50%% sys callback functions 51-export([ 52 system_continue/3, 53 system_terminate/4, 54 system_code_change/4 55 ]). 56 57-include("tftp.hrl"). 58 59-type prep_status() :: 'error' | 'last' | 'more' | 'terminate'. 60 61-record(daemon_state, {config, n_servers, server_tab, file_tab}). 62-record(server_info, {pid, req, peer}). 63-record(file_info, {peer_req, pid}). 64-record(sys_misc, {module, function, arguments}). 65-record(error, {where, code, text, filename}). 66-record(prepared, {status :: prep_status() | 'undefined', 67 result, block_no, next_data, prev_data}). 68-record(transfer_res, {status, decoded_msg, prepared}). 69-define(ERROR(Where, Code, Text, Filename), 70 #error{where = Where, code = Code, text = Text, filename = Filename}). 71 72%%%------------------------------------------------------------------- 73%%% Info 74%%%------------------------------------------------------------------- 75 76info(daemons) -> 77 Daemons = supervisor:which_children(tftp_sup), 78 [{Pid, info(Pid)} || {_, Pid, _, _} <- Daemons]; 79info(servers) -> 80 [{Pid, info(Pid)} || {_, {ok, DeamonInfo}} <- info(daemons), 81 {server, Pid} <- DeamonInfo]; 82info(ToPid) when is_pid(ToPid) -> 83 call(info, ToPid, timer:seconds(10)). 84 85change_config(daemons, Options) -> 86 Daemons = supervisor:which_children(tftp_sup), 87 [{Pid, change_config(Pid, Options)} || {_, Pid, _, _} <- Daemons]; 88change_config(servers, Options) -> 89 [{Pid, change_config(Pid, Options)} || {_, {ok, DeamonInfo}} <- info(daemons), 90 {server, Pid} <- DeamonInfo]; 91change_config(ToPid, Options) when is_pid(ToPid) -> 92 BadKeys = [host, port, udp], 93 BadOptions = [{Key, Val} || {Key, Val} <- Options, 94 BadKey <- BadKeys, 95 Key =:= BadKey], 96 case BadOptions of 97 [] -> 98 call({change_config, Options}, ToPid, timer:seconds(10)); 99 [{Key, Val} | _] -> 100 {error, {badarg, {Key, Val}}} 101 end. 102 103call(Req, ToPid, Timeout) when is_pid(ToPid) -> 104 Type = process, 105 Ref = erlang:monitor(Type, ToPid), 106 ToPid ! {Req, Ref, self()}, 107 receive 108 {Reply, Ref, FromPid} when FromPid =:= ToPid -> 109 erlang:demonitor(Ref, [flush]), 110 Reply; 111 {'DOWN', Ref, Type, FromPid, _Reason} when FromPid =:= ToPid -> 112 {error, timeout} 113 after Timeout -> 114 {error, timeout} 115 end. 116 117reply(Reply, Ref, ToPid) -> 118 ToPid ! {Reply, Ref, self()}. 119 120%%%------------------------------------------------------------------- 121%%% Daemon 122%%%------------------------------------------------------------------- 123 124%% Returns {ok, Port} 125daemon_start(Options) when is_list(Options) -> 126 Config = tftp_lib:parse_config(Options), 127 proc_lib:start_link(?MODULE, daemon_init, [Config], infinity). 128 129daemon_init(Config) when is_record(Config, config), 130 is_pid(Config#config.parent_pid) -> 131 process_flag(trap_exit, true), 132 {Port, UdpOptions} = prepare_daemon_udp(Config), 133 case catch gen_udp:open(Port, UdpOptions) of 134 {ok, Socket} -> 135 {ok, ActualPort} = inet:port(Socket), 136 proc_lib:init_ack({ok, self()}), 137 Config2 = Config#config{udp_socket = Socket, 138 udp_port = ActualPort}, 139 print_debug_info(Config2, daemon, open, #tftp_msg_req{filename = ""}), 140 ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]), 141 FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]), 142 State = #daemon_state{config = Config2, 143 n_servers = 0, 144 server_tab = ServerTab, 145 file_tab = FileTab}, 146 daemon_loop(State); 147 {error, Reason} -> 148 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])), 149 print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")), 150 exit({gen_udp_open, UdpOptions, Reason}); 151 Reason -> 152 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])), 153 print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")), 154 exit({gen_udp_open, UdpOptions, Reason}) 155 end. 156 157prepare_daemon_udp(#config{udp_port = Port, udp_options = UdpOptions} = Config) -> 158 case lists:keymember(fd, 1, UdpOptions) of 159 true -> 160 %% Use explicit fd 161 {Port, UdpOptions}; 162 false -> 163 %% Use fd from setuid_socket_wrap, such as -tftpd_69 164 InitArg = list_to_atom("tftpd_" ++ integer_to_list(Port)), 165 case init:get_argument(InitArg) of 166 {ok, [[FdStr]] = Badarg} when is_list(FdStr) -> 167 case catch list_to_integer(FdStr) of 168 Fd when is_integer(Fd) -> 169 {0, [{fd, Fd} | lists:keydelete(ip, 1, UdpOptions)]}; 170 {'EXIT', _} -> 171 Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])), 172 print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")), 173 exit({badarg, {prebound_fd, InitArg, Badarg}}) 174 end; 175 {ok, Badarg} -> 176 Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])), 177 print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")), 178 exit({badarg, {prebound_fd, InitArg, Badarg}}); 179 error -> 180 {Port, UdpOptions} 181 end 182 end. 183 184daemon_loop(DaemonConfig, N, Servers) when is_list(Servers) -> 185 %% Handle upgrade from old releases. Please, remove this function in next release. 186 ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]), 187 FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]), 188 State = #daemon_state{config = DaemonConfig, 189 n_servers = N, 190 server_tab = ServerTab, 191 file_tab = FileTab}, 192 Req = #tftp_msg_req{filename = dummy}, 193 [ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = dummy}) || Pid <- Servers], 194 daemon_loop(State). 195 196daemon_loop(#daemon_state{config = DaemonConfig, 197 n_servers = N, 198 server_tab = ServerTab, 199 file_tab = FileTab} = State) when is_record(DaemonConfig, config) -> 200 %% info_msg(DaemonConfig, "=====> TFTP: Daemon #~p\n", [N]), %% XXX 201 receive 202 {info, Ref, FromPid} when is_pid(FromPid) -> 203 Fun = fun(#server_info{pid = Pid}, Acc) -> [{server, Pid} | Acc] end, 204 ServerInfo = ets:foldl(Fun, [], ServerTab), 205 Info = internal_info(DaemonConfig, daemon) ++ [{n_conn, N}] ++ ServerInfo, 206 _ = reply({ok, Info}, Ref, FromPid), 207 ?MODULE:daemon_loop(State); 208 {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) -> 209 case catch tftp_lib:parse_config(Options, DaemonConfig) of 210 {'EXIT', Reason} -> 211 _ = reply({error, Reason}, Ref, FromPid), 212 ?MODULE:daemon_loop(State); 213 DaemonConfig2 when is_record(DaemonConfig2, config) -> 214 _ = reply(ok, Ref, FromPid), 215 ?MODULE:daemon_loop(State#daemon_state{config = DaemonConfig2}) 216 end; 217 {udp, Socket, RemoteHost, RemotePort, Bin} when is_binary(Bin) -> 218 _ = inet:setopts(Socket, [{active, once}]), 219 ServerConfig = DaemonConfig#config{parent_pid = self(), 220 udp_host = RemoteHost, 221 udp_port = RemotePort}, 222 Msg = (catch tftp_lib:decode_msg(Bin)), 223 print_debug_info(ServerConfig, daemon, recv, Msg), 224 case Msg of 225 Req when is_record(Req, tftp_msg_req), 226 N =< DaemonConfig#config.max_conn -> 227 Peer = peer_info(ServerConfig), 228 PeerReq = {Peer, Req}, 229 PeerInfo = lists:flatten(io_lib:format("~p", [Peer])), 230 case ets:lookup(FileTab, PeerReq) of 231 [] -> 232 Args = [ServerConfig, Req], 233 Pid = proc_lib:spawn_link(?MODULE, server_init, Args), 234 ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = Peer}), 235 ets:insert(FileTab, #file_info{peer_req = PeerReq, pid = Pid}), 236 ?MODULE:daemon_loop(State#daemon_state{n_servers = N + 1}); 237 [#file_info{pid = Pid}] -> 238 %% Yet another request of the file from same peer 239 warning_msg(DaemonConfig, "~p Reuse connection for ~s\n\t~p\n", 240 [Pid, PeerInfo, Req#tftp_msg_req.filename]), 241 ?MODULE:daemon_loop(State) 242 end; 243 Req when is_record(Req, tftp_msg_req) -> 244 Reply = #tftp_msg_error{code = enospc, text = "Too many connections"}, 245 Peer = peer_info(ServerConfig), 246 PeerInfo = lists:flatten(io_lib:format("~p", [Peer])), 247 warning_msg(DaemonConfig, 248 "Daemon has too many connections (~p)." 249 "\n\tRejecting request from ~s\n", 250 [N, PeerInfo]), 251 send_msg(ServerConfig, daemon, Reply), 252 ?MODULE:daemon_loop(State); 253 {'EXIT', Reply} when is_record(Reply, tftp_msg_error) -> 254 send_msg(ServerConfig, daemon, Reply), 255 ?MODULE:daemon_loop(State); 256 Req -> 257 Reply = #tftp_msg_error{code = badop, 258 text = "Illegal TFTP operation"}, 259 warning_msg(DaemonConfig, "Daemon received: ~p.\n\tfrom ~p:~p", 260 [Req, RemoteHost, RemotePort]), 261 send_msg(ServerConfig, daemon, Reply), 262 ?MODULE:daemon_loop(State) 263 end; 264 {system, From, Msg} -> 265 Misc = #sys_misc{module = ?MODULE, function = daemon_loop, arguments = [State]}, 266 sys:handle_system_msg(Msg, From, DaemonConfig#config.parent_pid, ?MODULE, [], Misc); 267 {'EXIT', Pid, Reason} when DaemonConfig#config.parent_pid =:= Pid -> 268 close_port(DaemonConfig, daemon, #tftp_msg_req{filename = ""}), 269 exit(Reason); 270 {'EXIT', Pid, _Reason} = Info -> 271 case ets:lookup(ServerTab, Pid) of 272 [] -> 273 warning_msg(DaemonConfig, "Daemon received: ~p", [Info]), 274 ?MODULE:daemon_loop(State); 275 [#server_info{req = Req, peer = Peer}] -> 276 PeerReq = {Peer, Req}, 277 ets:delete(FileTab, PeerReq), 278 ets:delete(ServerTab, Pid), 279 ?MODULE:daemon_loop(State#daemon_state{n_servers = N - 1}) 280 end; 281 Info -> 282 warning_msg(DaemonConfig, "Daemon received: ~p", [Info]), 283 ?MODULE:daemon_loop(State) 284 end; 285daemon_loop(#daemon_state{config = Config} = State) -> 286 %% Handle upgrade from old releases. Please, remove this clause in next release. 287 Config2 = upgrade_config(Config), 288 daemon_loop(State#daemon_state{config = Config2}). 289 290upgrade_config({config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy, 291 UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel, 292 Timeout, UserOptions, Callbacks}) -> 293 Callbacks2 = tftp_lib:add_default_callbacks(Callbacks), 294 Logger = tftp_logger, 295 MaxRetries = 5, 296 {config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy, 297 UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel, 298 Timeout, UserOptions, Callbacks2, Logger, MaxRetries}. 299 300%%%------------------------------------------------------------------- 301%%% Server 302%%%------------------------------------------------------------------- 303 304server_init(Config, Req) when is_record(Config, config), 305 is_pid(Config#config.parent_pid), 306 is_record(Req, tftp_msg_req) -> 307 process_flag(trap_exit, true), 308 %% Config = 309 %% case os:getenv("TFTPDEBUG") of 310 %% false -> 311 %% Config0; 312 %% DebugLevel -> 313 %% Config0#config{debug_level = list_to_atom(DebugLevel)} 314 %% end, 315 SuggestedOptions = Req#tftp_msg_req.options, 316 UdpOptions = Config#config.udp_options, 317 UdpOptions2 = lists:keydelete(fd, 1, UdpOptions), 318 Config1 = Config#config{udp_options = UdpOptions2}, 319 Config2 = tftp_lib:parse_config(SuggestedOptions, Config1), 320 SuggestedOptions2 = Config2#config.user_options, 321 Req2 = Req#tftp_msg_req{options = SuggestedOptions2}, 322 case open_free_port(Config2, server, Req2) of 323 {ok, Config3} -> 324 Filename = Req#tftp_msg_req.filename, 325 case match_callback(Filename, Config3#config.callbacks) of 326 {ok, Callback} -> 327 print_debug_info(Config3, server, match, Callback), 328 case pre_verify_options(Config3, Req2) of 329 ok -> 330 case callback({open, server_open}, Config3, Callback, Req2) of 331 {Callback2, {ok, AcceptedOptions}} -> 332 {LocalAccess, _} = local_file_access(Req2), 333 OptText = "Internal error. Not allowed to add new options.", 334 case post_verify_options(Config3, Req2, AcceptedOptions, OptText) of 335 {ok, Config4, Req3} when AcceptedOptions =/= [] -> 336 Reply = #tftp_msg_oack{options = AcceptedOptions}, 337 BlockNo = 338 case LocalAccess of 339 read -> 0; 340 write -> 1 341 end, 342 {Config5, Callback3, TransferRes} = 343 transfer(Config4, Callback2, Req3, Reply, LocalAccess, BlockNo, #prepared{}), 344 common_loop(Config5, Callback3, Req3, TransferRes, LocalAccess, BlockNo); 345 {ok, Config4, Req3} when LocalAccess =:= write -> 346 BlockNo = 0, 347 common_ack(Config4, Callback2, Req3, LocalAccess, BlockNo, #prepared{}); 348 {ok, Config4, Req3} when LocalAccess =:= read -> 349 BlockNo = 0, 350 common_read(Config4, Callback2, Req3, LocalAccess, BlockNo, BlockNo, #prepared{}); 351 {error, {Code, Text}} -> 352 {undefined, Error} = 353 callback({abort, {Code, Text}}, Config3, Callback2, Req2), 354 send_msg(Config3, Req, Error), 355 terminate(Config3, Req2, ?ERROR(post_verify_options, Code, Text, Req2#tftp_msg_req.filename)) 356 end; 357 {undefined, #tftp_msg_error{code = Code, text = Text} = Error} -> 358 send_msg(Config3, Req, Error), 359 terminate(Config3, Req, ?ERROR(server_open, Code, Text, Req2#tftp_msg_req.filename)) 360 end; 361 {error, {Code, Text}} -> 362 {undefined, Error} = 363 callback({abort, {Code, Text}}, Config2, Callback, Req2), 364 send_msg(Config2, Req, Error), 365 terminate(Config2, Req2, ?ERROR(pre_verify_options, Code, Text, Req2#tftp_msg_req.filename)) 366 end; 367 {error, #tftp_msg_error{code = Code, text = Text} = Error} -> 368 send_msg(Config3, Req, Error), 369 terminate(Config3, Req, ?ERROR(match_callback, Code, Text, Req2#tftp_msg_req.filename)) 370 end; 371 #error{} = Error -> 372 terminate(Config2, Req, Error) 373 end; 374server_init(Config, Req) when is_record(Req, tftp_msg_req) -> 375 Config2 = upgrade_config(Config), 376 server_init(Config2, Req). 377 378%%%------------------------------------------------------------------- 379%%% Client 380%%%------------------------------------------------------------------- 381 382%% LocalFilename = filename() | 'binary' | binary() 383%% Returns {ok, LastCallbackState} | {error, Reason} 384client_start(Access, RemoteFilename, LocalFilename, Options) -> 385 Config = tftp_lib:parse_config(Options), 386 Config2 = Config#config{parent_pid = self(), 387 udp_socket = undefined}, 388 Req = #tftp_msg_req{access = Access, 389 filename = RemoteFilename, 390 mode = lookup_mode(Config2#config.user_options), 391 options = Config2#config.user_options, 392 local_filename = LocalFilename}, 393 Args = [Config2, Req], 394 case proc_lib:start_link(?MODULE, client_init, Args, infinity) of 395 {ok, LastCallbackState} -> 396 {ok, LastCallbackState}; 397 {error, Error} -> 398 {error, Error} 399 end. 400 401client_init(Config, Req) when is_record(Config, config), 402 is_pid(Config#config.parent_pid), 403 is_record(Req, tftp_msg_req) -> 404 process_flag(trap_exit, true), 405 %% Config = 406 %% case os:getenv("TFTPDEBUG") of 407 %% false -> 408 %% Config0; 409 %% "none" -> 410 %% Config0; 411 %% DebugLevel -> 412 %% info_msg(Config, "TFTPDEBUG: ~s\n", [DebugLevel]), 413 %% Config0#config{debug_level = list_to_atom(DebugLevel)} 414 %% end, 415 case open_free_port(Config, client, Req) of 416 {ok, Config2} -> 417 Req2 = 418 case Config2#config.use_tsize of 419 true -> 420 SuggestedOptions = Req#tftp_msg_req.options, 421 SuggestedOptions2 = tftp_lib:replace_val("tsize", "0", SuggestedOptions), 422 Req#tftp_msg_req{options = SuggestedOptions2}; 423 false -> 424 Req 425 end, 426 LocalFilename = Req2#tftp_msg_req.local_filename, 427 case match_callback(LocalFilename, Config2#config.callbacks) of 428 {ok, Callback} -> 429 print_debug_info(Config2, client, match, Callback), 430 client_prepare(Config2, Callback, Req2); 431 {error, #tftp_msg_error{code = Code, text = Text}} -> 432 terminate(Config, Req, ?ERROR(match, Code, Text, Req#tftp_msg_req.filename)) 433 end; 434 #error{} = Error -> 435 terminate(Config, Req, Error) 436 end. 437 438client_prepare(Config, Callback, Req) when is_record(Req, tftp_msg_req) -> 439 case pre_verify_options(Config, Req) of 440 ok -> 441 case callback({open, client_prepare}, Config, Callback, Req) of 442 {Callback2, {ok, AcceptedOptions}} -> 443 OptText = "Internal error. Not allowed to add new options.", 444 case post_verify_options(Config, Req, AcceptedOptions, OptText) of 445 {ok, Config2, Req2} -> 446 {LocalAccess, _} = local_file_access(Req2), 447 BlockNo = 0, 448 {Config3, Callback3, TransferRes} = 449 transfer(Config2, Callback2, Req2, Req2, LocalAccess, BlockNo, #prepared{}), 450 client_open(Config3, Callback3, Req2, BlockNo, TransferRes); 451 {error, {Code, Text}} -> 452 _ = callback({abort, {Code, Text}}, Config, Callback2, Req), 453 terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename)) 454 end; 455 {undefined, #tftp_msg_error{code = Code, text = Text}} -> 456 terminate(Config, Req, ?ERROR(client_prepare, Code, Text, Req#tftp_msg_req.filename)) 457 end; 458 {error, {Code, Text}} -> 459 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 460 terminate(Config, Req, ?ERROR(pre_verify_options, Code, Text, Req#tftp_msg_req.filename)) 461 end. 462 463client_open(Config, Callback, Req, BlockNo, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}) -> 464 {LocalAccess, _} = local_file_access(Req), 465 case Status of 466 ok when is_record(Prepared, prepared) -> 467 case DecodedMsg of 468 Msg when is_record(Msg, tftp_msg_oack) -> 469 ServerOptions = Msg#tftp_msg_oack.options, 470 OptText = "Protocol violation. Server is not allowed new options", 471 case post_verify_options(Config, Req, ServerOptions, OptText) of 472 {ok, Config2, Req2} -> 473 {Config3, Callback2, Req3} = 474 do_client_open(Config2, Callback, Req2), 475 case LocalAccess of 476 read -> 477 common_read(Config3, Callback2, Req3, LocalAccess, BlockNo, BlockNo, Prepared); 478 write -> 479 common_ack(Config3, Callback2, Req3, LocalAccess, BlockNo, Prepared) 480 end; 481 {error, {Code, Text}} -> 482 {undefined, Error} = 483 callback({abort, {Code, Text}}, Config, Callback, Req), 484 send_msg(Config, Req, Error), 485 terminate(Config, Req, ?ERROR(verify_server_options, Code, Text, Req#tftp_msg_req.filename)) 486 end; 487 #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read -> 488 Req2 = Req#tftp_msg_req{options = []}, 489 {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2), 490 ExpectedBlockNo = 0, 491 common_read(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared); 492 #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write -> 493 Req2 = Req#tftp_msg_req{options = []}, 494 {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2), 495 ExpectedBlockNo = 1, 496 common_write(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared); 497 %% #tftp_msg_error{code = Code, text = Text} when Req#tftp_msg_req.options =/= [] -> 498 %% %% Retry without options 499 %% callback({abort, {Code, Text}}, Config, Callback, Req), 500 %% Req2 = Req#tftp_msg_req{options = []}, 501 %% client_prepare(Config, Callback, Req2); 502 #tftp_msg_error{code = Code, text = Text} -> 503 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 504 terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename)); 505 {'EXIT', #tftp_msg_error{code = Code, text = Text}} -> 506 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 507 terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename)); 508 Msg when is_tuple(Msg) -> 509 Code = badop, 510 Text = "Illegal TFTP operation", 511 {undefined, Error} = 512 callback({abort, {Code, Text}}, Config, Callback, Req), 513 send_msg(Config, Req, Error), 514 Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]), 515 terminate(Config, Req, ?ERROR(client_open, Code, Text2, Req#tftp_msg_req.filename)) 516 end; 517 error when is_record(Prepared, tftp_msg_error) -> 518 #tftp_msg_error{code = Code, text = Text} = Prepared, 519 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 520 terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename)) 521 end. 522 523do_client_open(Config, Callback, Req) -> 524 case callback({open, client_open}, Config, Callback, Req) of 525 {Callback2, {ok, FinalOptions}} -> 526 OptText = "Internal error. Not allowed to change options.", 527 case post_verify_options(Config, Req, FinalOptions, OptText) of 528 {ok, Config2, Req2} -> 529 {Config2, Callback2, Req2}; 530 {error, {Code, Text}} -> 531 {undefined, Error} = 532 callback({abort, {Code, Text}}, Config, Callback2, Req), 533 send_msg(Config, Req, Error), 534 terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename)) 535 end; 536 {undefined, #tftp_msg_error{code = Code, text = Text} = Error} -> 537 send_msg(Config, Req, Error), 538 terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename)) 539 end. 540 541%%%------------------------------------------------------------------- 542%%% Common loop for both client and server 543%%%------------------------------------------------------------------- 544 545common_loop(Config, Callback, Req, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}, LocalAccess, ExpectedBlockNo) 546 when is_record(Config, config)-> 547 %% Config = 548 %% case os:getenv("TFTPMAX") of 549 %% false -> 550 %% Config0; 551 %% MaxBlockNoStr when Config0#config.debug_level =/= none -> 552 %% case list_to_integer(MaxBlockNoStr) of 553 %% MaxBlockNo when ExpectedBlockNo > MaxBlockNo -> 554 %% info_msg(Config, "TFTPMAX: ~p\n", [MaxBlockNo]), 555 %% info_msg(Config, "TFTPDEBUG: none\n", []), 556 %% Config0#config{debug_level = none}; 557 %% _ -> 558 %% Config0 559 %% end; 560 %% _MaxBlockNoStr -> 561 %% Config0 562 %% end, 563 case Status of 564 ok when is_record(Prepared, prepared) -> 565 case DecodedMsg of 566 #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read -> 567 common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared); 568 #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write -> 569 common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared); 570 #tftp_msg_error{code = Code, text = Text} -> 571 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 572 terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename)); 573 {'EXIT', #tftp_msg_error{code = Code, text = Text} = Error} -> 574 _ = callback({abort, {Code, Text}}, Config, Callback, Req), 575 send_msg(Config, Req, Error), 576 terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename)); 577 Msg when is_tuple(Msg) -> 578 Code = badop, 579 Text = "Illegal TFTP operation", 580 {undefined, Error} = 581 callback({abort, {Code, Text}}, Config, Callback, Req), 582 send_msg(Config, Req, Error), 583 Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]), 584 terminate(Config, Req, ?ERROR(common_loop, Code, Text2, Req#tftp_msg_req.filename)) 585 end; 586 error when is_record(Prepared, tftp_msg_error) -> 587 #tftp_msg_error{code = Code, text = Text} = Prepared, 588 send_msg(Config, Req, Prepared), 589 terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename)) 590 end; 591common_loop(Config, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo) -> 592 %% Handle upgrade from old releases. Please, remove this clause in next release. 593 Config2 = upgrade_config(Config), 594 common_loop(Config2, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo). 595 596-spec common_read(#config{}, #callback{}, _, 'read', _, _, #prepared{}) -> no_return(). 597 598common_read(Config, _, Req, _, _, _, #prepared{status = terminate, result = Result}) -> 599 terminate(Config, Req, {ok, Result}); 600common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared) 601 when ActualBlockNo =:= ExpectedBlockNo, is_record(Prepared, prepared) -> 602 case early_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Prepared) of 603 {Callback2, #prepared{status = more, next_data = Data} = Prepared2} when is_binary(Data) -> 604 Prepared3 = Prepared2#prepared{prev_data = Data, next_data = undefined}, 605 do_common_read(Config, Callback2, Req, LocalAccess, ActualBlockNo, Data, Prepared3); 606 {undefined, #prepared{status = last, next_data = Data} = Prepared2} when is_binary(Data) -> 607 Prepared3 = Prepared2#prepared{status = terminate}, 608 do_common_read(Config, undefined, Req, LocalAccess, ActualBlockNo, Data, Prepared3); 609 {undefined, #prepared{status = error, result = Error}} -> 610 #tftp_msg_error{code = Code, text = Text} = Error, 611 send_msg(Config, Req, Error), 612 terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)) 613 end; 614common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared) 615 when ActualBlockNo =:= (ExpectedBlockNo - 1), is_record(Prepared, prepared) -> 616 case Prepared of 617 #prepared{status = more, prev_data = Data} when is_binary(Data) -> 618 do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared); 619 #prepared{status = last, prev_data = Data} when is_binary(Data) -> 620 do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared); 621 #prepared{status = error, result = Error} -> 622 #tftp_msg_error{code = Code, text = Text} = Error, 623 send_msg(Config, Req, Error), 624 terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)) 625 end; 626common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared) 627 when ActualBlockNo =< ExpectedBlockNo, is_record(Prepared, prepared) -> 628 %% error_logger:error_msg("TFTP READ ~s: Expected block ~p but got block ~p - IGNORED\n", 629 %% [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]), 630 case Prepared of 631 #prepared{status = more, prev_data = Data} when is_binary(Data) -> 632 Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data}, 633 {Config2, Callback2, TransferRes} = 634 wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared), 635 ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo); 636 #prepared{status = last, prev_data = Data} when is_binary(Data) -> 637 Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data}, 638 {Config2, Callback2, TransferRes} = 639 wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared), 640 ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo); 641 #prepared{status = error, result = Error} -> 642 #tftp_msg_error{code = Code, text = Text} = Error, 643 send_msg(Config, Req, Error), 644 terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)) 645 end; 646common_read(Config, Callback, Req, _LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared) 647 when is_record(Prepared, prepared) -> 648 Code = badblk, 649 Text = "Unknown transfer ID = " ++ 650 integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")", 651 {undefined, Error} = 652 callback({abort, {Code, Text}}, Config, Callback, Req), 653 send_msg(Config, Req, Error), 654 terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)). 655 656-spec do_common_read(#config{}, #callback{} | undefined, _, 'read', integer(), binary(), #prepared{}) -> no_return(). 657 658do_common_read(Config, Callback, Req, LocalAccess, BlockNo, Data, Prepared) 659 when is_binary(Data), is_record(Prepared, prepared) -> 660 NextBlockNo = (BlockNo + 1) rem 65536, 661 Reply = #tftp_msg_data{block_no = NextBlockNo, data = Data}, 662 {Config2, Callback2, TransferRes} = 663 transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared), 664 ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo). 665 666-spec common_write(#config{}, #callback{}, _, 'write', integer(), integer(), _, #prepared{}) -> no_return(). 667 668common_write(Config, _, Req, _, _, _, _, #prepared{status = terminate, result = Result}) -> 669 terminate(Config, Req, {ok, Result}); 670common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared) 671 when ActualBlockNo =:= ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) -> 672 case callback({write, Data}, Config, Callback, Req) of 673 {Callback2, #prepared{status = more} = Prepared2} -> 674 common_ack(Config, Callback2, Req, LocalAccess, ActualBlockNo, Prepared2); 675 {undefined, #prepared{status = last, result = Result} = Prepared2} -> 676 Config2 = pre_terminate(Config, Req, {ok, Result}), 677 Prepared3 = Prepared2#prepared{status = terminate}, 678 common_ack(Config2, undefined, Req, LocalAccess, ActualBlockNo, Prepared3); 679 {undefined, #prepared{status = error, result = Error}} -> 680 #tftp_msg_error{code = Code, text = Text} = Error, 681 send_msg(Config, Req, Error), 682 terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename)) 683 end; 684common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared) 685 when ActualBlockNo =:= (ExpectedBlockNo - 1), is_binary(Data), is_record(Prepared, prepared) -> 686 common_ack(Config, Callback, Req, LocalAccess, ExpectedBlockNo - 1, Prepared); 687common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared) 688 when ActualBlockNo =< ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) -> 689 %% error_logger:error_msg("TFTP WRITE ~s: Expected block ~p but got block ~p - IGNORED\n", 690 %% [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]), 691 Reply = #tftp_msg_ack{block_no = ExpectedBlockNo}, 692 {Config2, Callback2, TransferRes} = 693 wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared), 694 ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo); 695common_write(Config, Callback, Req, _, ExpectedBlockNo, ActualBlockNo, Data, Prepared) 696 when is_binary(Data), is_record(Prepared, prepared) -> 697 Code = badblk, 698 Text = "Unknown transfer ID = " ++ 699 integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")", 700 {undefined, Error} = 701 callback({abort, {Code, Text}}, Config, Callback, Req), 702 send_msg(Config, Req, Error), 703 terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename)). 704 705common_ack(Config, Callback, Req, LocalAccess, BlockNo, Prepared) 706 when is_record(Prepared, prepared) -> 707 Reply = #tftp_msg_ack{block_no = BlockNo}, 708 NextBlockNo = (BlockNo + 1) rem 65536, 709 {Config2, Callback2, TransferRes} = 710 transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared), 711 ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo). 712 713pre_terminate(Config, Req, Result) -> 714 if 715 Req#tftp_msg_req.local_filename =/= undefined, 716 Config#config.parent_pid =/= undefined -> 717 proc_lib:init_ack(Result), 718 unlink(Config#config.parent_pid), 719 Config#config{parent_pid = undefined, polite_ack = true}; 720 true -> 721 Config#config{polite_ack = true} 722 end. 723 724-spec terminate(#config{}, #tftp_msg_req{}, {'ok', _} | #error{}) -> no_return(). 725 726terminate(Config, Req, Result) -> 727 Result2 = 728 case Result of 729 {ok, _} -> 730 Result; 731 #error{where = Where, code = Code, text = Text} = Error -> 732 print_debug_info(Config, Req, Where, Error#error{filename = Req#tftp_msg_req.filename}), 733 {error, {Where, Code, Text}} 734 end, 735 if 736 Config#config.parent_pid =:= undefined -> 737 close_port(Config, client, Req), 738 exit(normal); 739 Req#tftp_msg_req.local_filename =/= undefined -> 740 %% Client 741 close_port(Config, client, Req), 742 proc_lib:init_ack(Result2), 743 unlink(Config#config.parent_pid), 744 exit(normal); 745 true -> 746 %% Server 747 close_port(Config, server, Req), 748 exit(shutdown) 749 end. 750 751close_port(Config, Who, Req) when is_record(Req, tftp_msg_req) -> 752 case Config#config.udp_socket of 753 undefined -> 754 ignore; 755 Socket -> 756 print_debug_info(Config, Who, close, Req), 757 gen_udp:close(Socket) 758 end. 759 760open_free_port(Config, Who, Req) when is_record(Config, config), is_record(Req, tftp_msg_req) -> 761 UdpOptions = Config#config.udp_options, 762 case Config#config.port_policy of 763 random -> 764 %% BUGBUG: Should be a random port 765 case catch gen_udp:open(0, UdpOptions) of 766 {ok, Socket} -> 767 Config2 = Config#config{udp_socket = Socket}, 768 print_debug_info(Config2, Who, open, Req), 769 {ok, Config2}; 770 {error, Reason} -> 771 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])), 772 ?ERROR(open, undef, Text, Req#tftp_msg_req.filename); 773 {'EXIT', _} = Reason -> 774 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])), 775 ?ERROR(open, undef, Text, Req#tftp_msg_req.filename) 776 end; 777 {range, Port, Max} when Port =< Max -> 778 case catch gen_udp:open(Port, UdpOptions) of 779 {ok, Socket} -> 780 Config2 = Config#config{udp_socket = Socket}, 781 print_debug_info(Config2, Who, open, Req), 782 {ok, Config2}; 783 {error, eaddrinuse} -> 784 PortPolicy = {range, Port + 1, Max}, 785 Config2 = Config#config{port_policy = PortPolicy}, 786 open_free_port(Config2, Who, Req); 787 {error, Reason} -> 788 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])), 789 ?ERROR(open, undef, Text, Req#tftp_msg_req.filename); 790 {'EXIT', _} = Reason-> 791 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])), 792 ?ERROR(open, undef, Text, Req#tftp_msg_req.filename) 793 end; 794 {range, Port, _Max} -> 795 Reason = "Port range exhausted", 796 Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])), 797 ?ERROR(Who, undef, Text, Req#tftp_msg_req.filename) 798 end. 799 800%%------------------------------------------------------------------- 801%% Transfer 802%%------------------------------------------------------------------- 803 804%% Returns {Config, Callback, #transfer_res{}} 805transfer(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared) 806 when is_record(Prepared, prepared) -> 807 IoList = tftp_lib:encode_msg(Msg), 808 Retries = Config#config.max_retries + 1, 809 do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries). 810 811do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries) 812 when is_record(Prepared, prepared), is_integer(Retries), Retries >= 0 -> 813 case do_send_msg(Config, Req, Msg, IoList) of 814 ok -> 815 {Callback2, Prepared2} = 816 early_read(Config, Callback, Req, LocalAccess, NextBlockNo, Prepared), 817 do_wait_for_msg_and_handle_timeout(Config, Callback2, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared2, Retries); 818 {error, _Reason} when Retries > 0 -> 819 Retries2 = 0, % Just retry once when send fails 820 do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2); 821 {error, Reason} -> 822 Code = undef, 823 Text = lists:flatten(io_lib:format("Transfer failed - giving up -> ~p", [Reason])), 824 Error = #tftp_msg_error{code = Code, text = Text}, 825 {Config, Callback, #transfer_res{status = error, prepared = Error}} 826 end. 827 828wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared) -> 829 IoList = tftp_lib:encode_msg(Msg), 830 Retries = Config#config.max_retries + 1, 831 do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries). 832 833do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries) -> 834 Code = undef, 835 Text = "Transfer timed out.", 836 case wait_for_msg(Config, Callback, Req) of 837 timeout when Config#config.polite_ack =:= true -> 838 do_send_msg(Config, Req, Msg, IoList), 839 case Prepared of 840 #prepared{status = terminate, result = Result} -> 841 terminate(Config, Req, {ok, Result}); 842 #prepared{} -> 843 terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename)) 844 end; 845 timeout when Retries > 0 -> 846 Retries2 = Retries - 1, 847 do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2); 848 timeout -> 849 Error = #tftp_msg_error{code = Code, text = Text}, 850 {Config, Callback, #transfer_res{status = error, prepared = Error}}; 851 {Config2, DecodedMsg} -> 852 {Config2, Callback, #transfer_res{status = ok, decoded_msg = DecodedMsg, prepared = Prepared}} 853 end. 854 855send_msg(Config, Req, Msg) -> 856 case catch tftp_lib:encode_msg(Msg) of 857 {'EXIT', Reason} -> 858 Code = undef, 859 Text = "Internal error. Encode failed", 860 Msg2 = #tftp_msg_error{code = Code, text = Text, details = Reason}, 861 send_msg(Config, Req, Msg2); 862 IoList -> 863 do_send_msg(Config, Req, Msg, IoList) 864 end. 865 866do_send_msg(#config{udp_socket = Socket, udp_host = RemoteHost, udp_port = RemotePort} = Config, Req, Msg, IoList) -> 867 %% {ok, LocalPort} = inet:port(Socket), 868 %% if 869 %% LocalPort =/= ?TFTP_DEFAULT_PORT -> 870 %% ok; 871 %% true -> 872 %% print_debug_info(Config#config{debug_level = all}, Req, send, Msg), 873 %% error(Config, 874 %% "Daemon replies from the default port (~p)\n\t to ~p:~p\n\t¨~p\n", 875 %% [LocalPort, RemoteHost, RemotePort, Msg]) 876 %% end, 877 878 print_debug_info(Config, Req, send, Msg), 879 880 %% case os:getenv("TFTPDUMP") of 881 %% false -> 882 %% ignore; 883 %% DumpPath -> 884 %% trace_udp_send(Req, Msg, IoList, DumpPath) 885 %% end, 886 try 887 ok = gen_udp:send(Socket, RemoteHost, RemotePort, IoList) 888 catch 889 error:{badmatch,{error,einval=Reason}}:StackTrace -> 890 error_msg(Config, 891 "Stacktrace; ~p\n gen_udp:send(~p, ~p, ~p, ~p) -> ~p\n", 892 [StackTrace, Socket, RemoteHost, RemotePort, IoList, {error, Reason}]); 893 error:{badmatch,{error,Reason}} -> 894 {error, Reason} 895 end. 896 897%% trace_udp_send(#tftp_msg_req{filename = [$/ | RelFile]} = Req, Msg, IoList, DumpPath) -> 898%% trace_udp_send(Req#tftp_msg_req{filename = RelFile}, Msg, IoList, DumpPath); 899%% trace_udp_send(#tftp_msg_req{filename = RelFile}, 900%% #tftp_msg_data{block_no = BlockNo, data = Data}, 901%% _IoList, 902%% DumpPath) -> 903%% File = filename:join([DumpPath, RelFile, "block" ++ string:right(integer_to_list(BlockNo), 5, $0) ++ ".dump"]), 904%% if 905%% (BlockNo rem 1000) =:= 1 -> 906%% info_msg(Config, "TFTPDUMP: Data ~s\n", [File]); 907%% true -> 908%% ignore 909%% end, 910%% ok = filelib:ensure_dir(File), 911%% ok = file:write_file(File, Data); 912%% trace_udp_send(#tftp_msg_req{filename = RelFile}, Msg, _IoList, _DumpPath) -> 913%% info_msg(Config, "TFTPDUMP: No data ~s -> ~p\n", [RelFile, element(1, Msg)]). 914 915wait_for_msg(Config, Callback, Req) -> 916 receive 917 {udp, Socket, RemoteHost, RemotePort, Bin} 918 when is_binary(Bin), Callback#callback.block_no =:= undefined -> 919 %% Client prepare 920 _ = inet:setopts(Socket, [{active, once}]), 921 Config2 = Config#config{udp_host = RemoteHost, 922 udp_port = RemotePort}, 923 DecodedMsg = (catch tftp_lib:decode_msg(Bin)), 924 print_debug_info(Config2, Req, recv, DecodedMsg), 925 {Config2, DecodedMsg}; 926 {udp, Socket, Host, Port, Bin} when is_binary(Bin), 927 Config#config.udp_host =:= Host, 928 Config#config.udp_port =:= Port -> 929 _ = inet:setopts(Socket, [{active, once}]), 930 DecodedMsg = (catch tftp_lib:decode_msg(Bin)), 931 print_debug_info(Config, Req, recv, DecodedMsg), 932 {Config, DecodedMsg}; 933 {info, Ref, FromPid} when is_pid(FromPid) -> 934 Type = 935 case Req#tftp_msg_req.local_filename =/= undefined of 936 true -> client; 937 false -> server 938 end, 939 Info = internal_info(Config, Type), 940 _ = reply({ok, Info}, Ref, FromPid), 941 wait_for_msg(Config, Callback, Req); 942 {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) -> 943 case catch tftp_lib:parse_config(Options, Config) of 944 {'EXIT', Reason} -> 945 _ = reply({error, Reason}, Ref, FromPid), 946 wait_for_msg(Config, Callback, Req); 947 Config2 when is_record(Config2, config) -> 948 _ = reply(ok, Ref, FromPid), 949 wait_for_msg(Config2, Callback, Req) 950 end; 951 {system, From, Msg} -> 952 Misc = #sys_misc{module = ?MODULE, function = wait_for_msg, arguments = [Config, Callback, Req]}, 953 sys:handle_system_msg(Msg, From, Config#config.parent_pid, ?MODULE, [], Misc); 954 {'EXIT', Pid, _Reason} when Config#config.parent_pid =:= Pid -> 955 Code = undef, 956 Text = "Parent exited.", 957 terminate(Config, Req, ?ERROR(wait_for_msg, Code, Text, Req#tftp_msg_req.filename)); 958 Msg when Req#tftp_msg_req.local_filename =/= undefined -> 959 warning_msg(Config, "Client received : ~p", [Msg]), 960 wait_for_msg(Config, Callback, Req); 961 Msg when Req#tftp_msg_req.local_filename =:= undefined -> 962 warning_msg(Config, "Server received : ~p", [Msg]), 963 wait_for_msg(Config, Callback, Req) 964 after Config#config.timeout * 1000 -> 965 print_debug_info(Config, Req, recv, timeout), 966 timeout 967 end. 968 969early_read(Config, Callback, Req, LocalAccess, _NextBlockNo, 970 #prepared{status = Status, next_data = NextData, prev_data = PrevData} = Prepared) -> 971 if 972 Status =/= terminate, 973 LocalAccess =:= read, 974 Callback#callback.block_no =/= undefined, 975 NextData =:= undefined -> 976 case callback(read, Config, Callback, Req) of 977 {undefined, Error} when is_record(Error, tftp_msg_error) -> 978 {undefined, Error}; 979 {Callback2, Prepared2} when is_record(Prepared2, prepared)-> 980 {Callback2, Prepared2#prepared{prev_data = PrevData}} 981 end; 982 true -> 983 {Callback, Prepared} 984 end. 985 986%%------------------------------------------------------------------- 987%% Callback 988%%------------------------------------------------------------------- 989 990callback(Access, Config, Callback, Req) -> 991 {Callback2, Result} = 992 do_callback(Access, Config, Callback, Req), 993 print_debug_info(Config, Req, call, {Callback2, Result}), 994 {Callback2, Result}. 995 996do_callback(read = Fun, Config, Callback, Req) 997 when is_record(Config, config), 998 is_record(Callback, callback), 999 is_record(Req, tftp_msg_req) -> 1000 Args = [Callback#callback.state], 1001 NextBlockNo = Callback#callback.block_no + 1, 1002 case catch safe_apply(Callback#callback.module, Fun, Args) of 1003 {more, Bin, NewState} when is_binary(Bin) -> 1004 Count = Callback#callback.count + size(Bin), 1005 Callback2 = Callback#callback{state = NewState, 1006 block_no = NextBlockNo, 1007 count = Count}, 1008 Prepared = #prepared{status = more, 1009 result = undefined, 1010 block_no = NextBlockNo, 1011 next_data = Bin}, 1012 verify_count(Config, Callback2, Req, Prepared); 1013 {last, Bin, Result} when is_binary(Bin) -> 1014 Prepared = #prepared{status = last, 1015 result = Result, 1016 block_no = NextBlockNo, 1017 next_data = Bin}, 1018 {undefined, Prepared}; 1019 {error, {Code, Text}} -> 1020 Error = #tftp_msg_error{code = Code, text = Text}, 1021 Prepared = #prepared{status = error, 1022 result = Error}, 1023 {undefined, Prepared}; 1024 Illegal -> 1025 Code = undef, 1026 Text = "Internal error. File handler error.", 1027 callback({abort, {Code, Text, Illegal}}, Config, Callback, Req) 1028 end; 1029do_callback({write = Fun, Bin}, Config, Callback, Req) 1030 when is_record(Config, config), 1031 is_record(Callback, callback), 1032 is_record(Req, tftp_msg_req), 1033 is_binary(Bin) -> 1034 Args = [Bin, Callback#callback.state], 1035 NextBlockNo = Callback#callback.block_no + 1, 1036 case catch safe_apply(Callback#callback.module, Fun, Args) of 1037 {more, NewState} -> 1038 Count = Callback#callback.count + size(Bin), 1039 Callback2 = Callback#callback{state = NewState, 1040 block_no = NextBlockNo, 1041 count = Count}, 1042 Prepared = #prepared{status = more, 1043 block_no = NextBlockNo}, 1044 verify_count(Config, Callback2, Req, Prepared); 1045 {last, Result} -> 1046 Prepared = #prepared{status = last, 1047 result = Result, 1048 block_no = NextBlockNo}, 1049 {undefined, Prepared}; 1050 {error, {Code, Text}} -> 1051 Error = #tftp_msg_error{code = Code, text = Text}, 1052 Prepared = #prepared{status = error, 1053 result = Error}, 1054 {undefined, Prepared}; 1055 Illegal -> 1056 Code = undef, 1057 Text = "Internal error. File handler error.", 1058 callback({abort, {Code, Text, Illegal}}, Config, Callback, Req) 1059 end; 1060do_callback({open, Type}, Config, Callback, Req) 1061 when is_record(Config, config), 1062 is_record(Callback, callback), 1063 is_record(Req, tftp_msg_req) -> 1064 {Access, Filename} = local_file_access(Req), 1065 {Fun, BlockNo} = 1066 case Type of 1067 client_prepare -> {prepare, undefined}; 1068 client_open -> {open, 0}; 1069 server_open -> {open, 0} 1070 end, 1071 Mod = Callback#callback.module, 1072 Args = [Access, 1073 Filename, 1074 Req#tftp_msg_req.mode, 1075 Req#tftp_msg_req.options, 1076 Callback#callback.state], 1077 PeerInfo = peer_info(Config), 1078 _ = fast_ensure_loaded(Mod), 1079 Args2 = 1080 case erlang:function_exported(Mod, Fun, length(Args)) of 1081 true -> Args; 1082 false -> [PeerInfo | Args] 1083 end, 1084 case catch safe_apply(Mod, Fun, Args2) of 1085 {ok, AcceptedOptions, NewState} -> 1086 Callback2 = Callback#callback{state = NewState, 1087 block_no = BlockNo, 1088 count = 0}, 1089 {Callback2, {ok, AcceptedOptions}}; 1090 {error, {Code, Text}} -> 1091 {undefined, #tftp_msg_error{code = Code, text = Text}}; 1092 Illegal -> 1093 Code = undef, 1094 Text = "Internal error. File handler error.", 1095 callback({abort, {Code, Text, Illegal}}, Config, Callback, Req) 1096 end; 1097do_callback({abort, {Code, Text}}, Config, Callback, Req) -> 1098 Error = #tftp_msg_error{code = Code, text = Text}, 1099 do_callback({abort, Error}, Config, Callback, Req); 1100do_callback({abort, {Code, Text, Details}}, Config, Callback, Req) -> 1101 Error = #tftp_msg_error{code = Code, text = Text, details = Details}, 1102 do_callback({abort, Error}, Config, Callback, Req); 1103do_callback({abort = Fun, #tftp_msg_error{code = Code, text = Text} = Error}, Config, Callback, Req) 1104 when is_record(Config, config), 1105 is_record(Callback, callback), 1106 is_record(Req, tftp_msg_req) -> 1107 Args = [Code, Text, Callback#callback.state], 1108 catch safe_apply(Callback#callback.module, Fun, Args), 1109 {undefined, Error}; 1110do_callback({abort, Error}, _Config, undefined, _Req) when is_record(Error, tftp_msg_error) -> 1111 {undefined, Error}. 1112 1113peer_info(#config{udp_host = Host, udp_port = Port}) -> 1114 if 1115 is_tuple(Host), size(Host) =:= 4 -> 1116 {inet, tftp_lib:host_to_string(Host), Port}; 1117 is_tuple(Host), size(Host) =:= 8 -> 1118 {inet6, tftp_lib:host_to_string(Host), Port}; 1119 true -> 1120 {undefined, Host, Port} 1121 end. 1122 1123match_callback(Filename, Callbacks) -> 1124 if 1125 Filename =:= binary -> 1126 lookup_callback_mod(tftp_binary, Callbacks); 1127 is_binary(Filename) -> 1128 lookup_callback_mod(tftp_binary, Callbacks); 1129 true -> 1130 do_match_callback(Filename, Callbacks) 1131 end. 1132 1133do_match_callback(Filename, [C | Tail]) when is_record(C, callback) -> 1134 case catch re:run(Filename, C#callback.internal, [{capture, none}]) of 1135 match -> 1136 {ok, C}; 1137 nomatch -> 1138 do_match_callback(Filename, Tail); 1139 Details -> 1140 Code = baduser, 1141 Text = "Internal error. File handler not found", 1142 {error, #tftp_msg_error{code = Code, text = Text, details = Details}} 1143 end; 1144do_match_callback(Filename, []) -> 1145 Code = baduser, 1146 Text = "Internal error. File handler not found", 1147 {error, #tftp_msg_error{code = Code, text = Text, details = Filename}}. 1148 1149lookup_callback_mod(Mod, Callbacks) -> 1150 {value, C} = lists:keysearch(Mod, #callback.module, Callbacks), 1151 {ok, C}. 1152 1153verify_count(Config, Callback, Req, Result) -> 1154 case Config#config.max_tsize of 1155 infinity -> 1156 {Callback, Result}; 1157 Max when Callback#callback.count =< Max -> 1158 {Callback, Result}; 1159 _Max -> 1160 Code = enospc, 1161 Text = "Too large file.", 1162 callback({abort, {Code, Text}}, Config, Callback, Req) 1163 end. 1164 1165%%------------------------------------------------------------------- 1166%% Miscellaneous 1167%%------------------------------------------------------------------- 1168 1169internal_info(Config, Type) when is_record(Config, config) -> 1170 {ok, ActualPort} = inet:port(Config#config.udp_socket), 1171 [ 1172 {type, Type}, 1173 {host, tftp_lib:host_to_string(Config#config.udp_host)}, 1174 {port, Config#config.udp_port}, 1175 {local_port, ActualPort}, 1176 {port_policy, Config#config.port_policy}, 1177 {udp, Config#config.udp_options}, 1178 {use_tsize, Config#config.use_tsize}, 1179 {max_tsize, Config#config.max_tsize}, 1180 {max_conn, Config#config.max_conn}, 1181 {rejected, Config#config.rejected}, 1182 {timeout, Config#config.timeout}, 1183 {polite_ack, Config#config.polite_ack}, 1184 {debug, Config#config.debug_level}, 1185 {parent_pid, Config#config.parent_pid} 1186 ] ++ Config#config.user_options ++ Config#config.callbacks. 1187 1188local_file_access(#tftp_msg_req{access = Access, 1189 local_filename = Local, 1190 filename = Filename}) -> 1191 case Local =:= undefined of 1192 true -> 1193 %% Server side 1194 {Access, Filename}; 1195 false -> 1196 %% Client side 1197 case Access of 1198 read -> {write, Local}; 1199 write -> {read, Local} 1200 end 1201 end. 1202 1203pre_verify_options(Config, Req) -> 1204 Options = Req#tftp_msg_req.options, 1205 case catch verify_reject(Config, Req, Options) of 1206 ok -> 1207 case verify_integer("tsize", 0, Config#config.max_tsize, Options) of 1208 true -> 1209 case verify_integer("blksize", 0, 65464, Options) of 1210 true -> 1211 ok; 1212 false -> 1213 {error, {badopt, "Too large blksize"}} 1214 end; 1215 false -> 1216 {error, {badopt, "Too large tsize"}} 1217 end; 1218 {error, Reason} -> 1219 {error, Reason} 1220 end. 1221 1222post_verify_options(Config, Req, NewOptions, Text) -> 1223 OldOptions = Req#tftp_msg_req.options, 1224 BadOptions = 1225 [Key || {Key, _Val} <- NewOptions, 1226 not lists:keymember(Key, 1, OldOptions)], 1227 case BadOptions =:= [] of 1228 true -> 1229 Config2 = Config#config{timeout = lookup_timeout(NewOptions)}, 1230 Req2 = Req#tftp_msg_req{options = NewOptions}, 1231 {ok, Config2, Req2}; 1232 false -> 1233 {error, {badopt, Text}} 1234 end. 1235 1236verify_reject(Config, Req, Options) -> 1237 Access = Req#tftp_msg_req.access, 1238 Rejected = Config#config.rejected, 1239 case lists:member(Access, Rejected) of 1240 true -> 1241 {error, {eacces, atom_to_list(Access) ++ " mode not allowed"}}; 1242 false -> 1243 [throw({error, {badopt, Key ++ " not allowed"}}) || 1244 {Key, _} <- Options, lists:member(Key, Rejected)], 1245 ok 1246 end. 1247 1248lookup_timeout(Options) -> 1249 case lists:keysearch("timeout", 1, Options) of 1250 {value, {_, Val}} -> 1251 list_to_integer(Val); 1252 false -> 1253 3 1254 end. 1255 1256lookup_mode(Options) -> 1257 case lists:keysearch("mode", 1, Options) of 1258 {value, {_, Val}} -> 1259 Val; 1260 false -> 1261 "octet" 1262 end. 1263 1264verify_integer(Key, Min, Max, Options) -> 1265 case lists:keysearch(Key, 1, Options) of 1266 {value, {_, Val}} when is_list(Val) -> 1267 case catch list_to_integer(Val) of 1268 {'EXIT', _} -> 1269 false; 1270 Int when Int >= Min, is_integer(Min), 1271 Max =:= infinity -> 1272 true; 1273 Int when Int >= Min, is_integer(Min), 1274 Int =< Max, is_integer(Max) -> 1275 true; 1276 _ -> 1277 false 1278 end; 1279 false -> 1280 true 1281 end. 1282 1283error_msg(#config{logger = Logger, debug_level = _Level}, F, A) -> 1284 safe_apply(Logger, error_msg, [F, A]). 1285 1286warning_msg(#config{logger = Logger, debug_level = Level}, F, A) -> 1287 case Level of 1288 none -> ok; 1289 error -> ok; 1290 _ -> safe_apply(Logger, warning_msg, [F, A]) 1291 end. 1292 1293info_msg(#config{logger = Logger}, F, A) -> 1294 safe_apply(Logger, info_msg, [F, A]). 1295 1296safe_apply(Mod, Fun, Args) -> 1297 _ = fast_ensure_loaded(Mod), 1298 apply(Mod, Fun, Args). 1299 1300fast_ensure_loaded(Mod) -> 1301 case erlang:function_exported(Mod, module_info, 0) of 1302 true -> 1303 ok; 1304 false -> 1305 Res = code:load_file(Mod), 1306 %% io:format("tftp: code:load_file(~p) -> ~p\n", [Mod, Res]), %% XXX 1307 Res 1308 end. 1309 1310print_debug_info(#config{debug_level = Level} = Config, Who, Where, Data) -> 1311 if 1312 Level =:= none -> 1313 ok; 1314 is_record(Data, error) -> 1315 do_print_debug_info(Config, Who, Where, Data); 1316 Level =:= warning -> 1317 ok; 1318 Level =:= error -> 1319 ok; 1320 Level =:= all -> 1321 do_print_debug_info(Config, Who, Where, Data); 1322 Where =:= open -> 1323 do_print_debug_info(Config, Who, Where, Data); 1324 Where =:= close -> 1325 do_print_debug_info(Config, Who, Where, Data); 1326 Level =:= brief -> 1327 ok; 1328 Where =/= recv, Where =/= send -> 1329 ok; 1330 is_record(Data, tftp_msg_data), Level =:= normal -> 1331 ok; 1332 is_record(Data, tftp_msg_ack), Level =:= normal -> 1333 ok; 1334 true -> 1335 do_print_debug_info(Config, Who, Where, Data) 1336 end. 1337 1338do_print_debug_info(Config, Who, Where, #tftp_msg_data{data = Bin} = Msg) when is_binary(Bin) -> 1339 Msg2 = Msg#tftp_msg_data{data = {bytes, size(Bin)}}, 1340 do_print_debug_info(Config, Who, Where, Msg2); 1341do_print_debug_info(Config, Who, Where, #tftp_msg_req{local_filename = Filename} = Msg) when is_binary(Filename) -> 1342 Msg2 = Msg#tftp_msg_req{local_filename = binary}, 1343 do_print_debug_info(Config, Who, Where, Msg2); 1344do_print_debug_info(Config, Who, Where, Data) -> 1345 Local = 1346 case catch inet:port(Config#config.udp_socket) of 1347 {'EXIT', _Reason} -> 1348 0; 1349 {ok, Port} -> 1350 Port 1351 end, 1352 %% Remote = Config#config.udp_port, 1353 PeerInfo = lists:flatten(io_lib:format("~p", [peer_info(Config)])), 1354 Side = 1355 if 1356 is_record(Who, tftp_msg_req), 1357 Who#tftp_msg_req.local_filename =/= undefined -> 1358 client; 1359 is_record(Who, tftp_msg_req), 1360 Who#tftp_msg_req.local_filename =:= undefined -> 1361 server; 1362 is_atom(Who) -> 1363 Who 1364 end, 1365 case {Where, Data} of 1366 {_, #error{where = Where, code = Code, text = Text, filename = Filename}} -> 1367 do_format(Config, Side, Local, "error ~s ->\n\t~p ~p\n\t~p ~p: ~s\n", 1368 [PeerInfo, self(), Filename, Where, Code, Text]); 1369 {open, #tftp_msg_req{filename = Filename}} -> 1370 do_format(Config, Side, Local, "open ~s ->\n\t~p ~p\n", 1371 [PeerInfo, self(), Filename]); 1372 {close, #tftp_msg_req{filename = Filename}} -> 1373 do_format(Config, Side, Local, "close ~s ->\n\t~p ~p\n", 1374 [PeerInfo, self(), Filename]); 1375 {recv, _} -> 1376 do_format(Config, Side, Local, "recv ~s <-\n\t~p\n", 1377 [PeerInfo, Data]); 1378 {send, _} -> 1379 do_format(Config, Side, Local, "send ~s ->\n\t~p\n", 1380 [PeerInfo, Data]); 1381 {match, _} when is_record(Data, callback) -> 1382 Mod = Data#callback.module, 1383 State = Data#callback.state, 1384 do_format(Config, Side, Local, "match ~s ~p =>\n\t~p\n", 1385 [PeerInfo, Mod, State]); 1386 {call, _} -> 1387 case Data of 1388 {Callback, _Result} when is_record(Callback, callback) -> 1389 Mod = Callback#callback.module, 1390 State = Callback#callback.state, 1391 do_format(Config, Side, Local, "call ~s ~p =>\n\t~p\n", 1392 [PeerInfo, Mod, State]); 1393 {undefined, Result} -> 1394 do_format(Config, Side, Local, "call ~s result =>\n\t~p\n", 1395 [PeerInfo, Result]) 1396 end 1397 end. 1398 1399do_format(Config, Side, Local, Format, Args) -> 1400 info_msg(Config, "~p(~p): " ++ Format, [Side, Local | Args]). 1401 1402%%------------------------------------------------------------------- 1403%% System upgrade 1404%%------------------------------------------------------------------- 1405 1406system_continue(_Parent, _Debug, #sys_misc{module = Mod, function = Fun, arguments = Args}) -> 1407 apply(Mod, Fun, Args); 1408system_continue(Parent, Debug, {Fun, Args}) -> 1409 %% Handle upgrade from old releases. Please, remove this clause in next release. 1410 system_continue(Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}). 1411 1412-spec system_terminate(_, _, _, #sys_misc{} | {_, _}) -> no_return(). 1413 1414system_terminate(Reason, _Parent, _Debug, #sys_misc{}) -> 1415 exit(Reason); 1416system_terminate(Reason, Parent, Debug, {Fun, Args}) -> 1417 %% Handle upgrade from old releases. Please, remove this clause in next release. 1418 system_terminate(Reason, Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}). 1419 1420system_code_change({Fun, Args}, _Module, _OldVsn, _Extra) -> 1421 {ok, {Fun, Args}}. 1422