ftp.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. #***************************************************************************
  2. # _ _ ____ _
  3. # Project ___| | | | _ \| |
  4. # / __| | | | |_) | |
  5. # | (__| |_| | _ <| |___
  6. # \___|\___/|_| \_\_____|
  7. #
  8. # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  9. #
  10. # This software is licensed as described in the file COPYING, which
  11. # you should have received as part of this distribution. The terms
  12. # are also available at https://curl.se/docs/copyright.html.
  13. #
  14. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. # copies of the Software, and permit persons to whom the Software is
  16. # furnished to do so, under the terms of the COPYING file.
  17. #
  18. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. # KIND, either express or implied.
  20. #
  21. # SPDX-License-Identifier: curl
  22. #
  23. ###########################################################################
  24. BEGIN {
  25. # portable sleeping needs Time::HiRes
  26. eval {
  27. no warnings "all";
  28. require Time::HiRes;
  29. };
  30. # portable sleeping falls back to native Sleep on Win32
  31. eval {
  32. no warnings "all";
  33. require Win32;
  34. }
  35. }
  36. use strict;
  37. use warnings;
  38. use serverhelp qw(
  39. servername_id
  40. mainsockf_pidfilename
  41. datasockf_pidfilename
  42. );
  43. use pathhelp qw(
  44. os_is_win
  45. );
  46. #######################################################################
  47. # portable_sleep uses Time::HiRes::sleep if available and falls back
  48. # to the classic approach of using select(undef, undef, undef, ...).
  49. # even though that one is not portable due to being implemented using
  50. # select on Windows: https://perldoc.perl.org/perlport.html#select
  51. # Therefore it uses Win32::Sleep on Windows systems instead.
  52. #
  53. sub portable_sleep {
  54. my ($seconds) = @_;
  55. if($Time::HiRes::VERSION) {
  56. Time::HiRes::sleep($seconds);
  57. }
  58. elsif (os_is_win()) {
  59. Win32::Sleep($seconds*1000);
  60. }
  61. else {
  62. select(undef, undef, undef, $seconds);
  63. }
  64. }
  65. #######################################################################
  66. # pidfromfile returns the pid stored in the given pidfile. The value
  67. # of the returned pid will never be a negative value. It will be zero
  68. # on any file related error or if a pid can not be extracted from the
  69. # given file.
  70. #
  71. sub pidfromfile {
  72. my $pidfile = $_[0];
  73. my $pid = 0;
  74. if(-f $pidfile && -s $pidfile && open(PIDFH, "<$pidfile")) {
  75. $pid = 0 + <PIDFH>;
  76. close(PIDFH);
  77. $pid = 0 unless($pid > 0);
  78. }
  79. return $pid;
  80. }
  81. #######################################################################
  82. # pidexists checks if a process with a given pid exists and is alive.
  83. # This will return the positive pid if the process exists and is alive.
  84. # This will return the negative pid if the process exists differently.
  85. # This will return 0 if the process could not be found.
  86. #
  87. sub pidexists {
  88. my $pid = $_[0];
  89. if($pid > 0) {
  90. # verify if currently existing Windows process
  91. if ($pid > 65536 && os_is_win()) {
  92. $pid -= 65536;
  93. if($^O ne 'MSWin32') {
  94. my $filter = "PID eq $pid";
  95. my $result = `tasklist -fi \"$filter\" 2>nul`;
  96. if(index($result, "$pid") != -1) {
  97. return -$pid;
  98. }
  99. return 0;
  100. }
  101. }
  102. # verify if currently existing and alive
  103. if(kill(0, $pid)) {
  104. return $pid;
  105. }
  106. }
  107. return 0;
  108. }
  109. #######################################################################
  110. # pidterm asks the process with a given pid to terminate gracefully.
  111. #
  112. sub pidterm {
  113. my $pid = $_[0];
  114. if($pid > 0) {
  115. # request the process to quit
  116. if ($pid > 65536 && os_is_win()) {
  117. $pid -= 65536;
  118. if($^O ne 'MSWin32') {
  119. my $filter = "PID eq $pid";
  120. my $result = `tasklist -fi \"$filter\" 2>nul`;
  121. if(index($result, "$pid") != -1) {
  122. system("taskkill -fi \"$filter\" >nul 2>&1");
  123. }
  124. return;
  125. }
  126. }
  127. # signal the process to terminate
  128. kill("TERM", $pid);
  129. }
  130. }
  131. #######################################################################
  132. # pidkill kills the process with a given pid mercilessly and forcefully.
  133. #
  134. sub pidkill {
  135. my $pid = $_[0];
  136. if($pid > 0) {
  137. # request the process to quit
  138. if ($pid > 65536 && os_is_win()) {
  139. $pid -= 65536;
  140. if($^O ne 'MSWin32') {
  141. my $filter = "PID eq $pid";
  142. my $result = `tasklist -fi \"$filter\" 2>nul`;
  143. if(index($result, "$pid") != -1) {
  144. system("taskkill -f -fi \"$filter\" >nul 2>&1");
  145. # Windows XP Home compatibility
  146. system("tskill $pid >nul 2>&1");
  147. }
  148. return;
  149. }
  150. }
  151. # signal the process to terminate
  152. kill("KILL", $pid);
  153. }
  154. }
  155. #######################################################################
  156. # pidwait waits for the process with a given pid to be terminated.
  157. #
  158. sub pidwait {
  159. my $pid = $_[0];
  160. my $flags = $_[1];
  161. # check if the process exists
  162. if ($pid > 65536 && os_is_win()) {
  163. if($flags == &WNOHANG) {
  164. return pidexists($pid)?0:$pid;
  165. }
  166. while(pidexists($pid)) {
  167. portable_sleep(0.01);
  168. }
  169. return $pid;
  170. }
  171. # wait on the process to terminate
  172. return waitpid($pid, $flags);
  173. }
  174. #######################################################################
  175. # processexists checks if a process with the pid stored in the given
  176. # pidfile exists and is alive. This will return 0 on any file related
  177. # error or if a pid can not be extracted from the given file. When a
  178. # process with the same pid as the one extracted from the given file
  179. # is currently alive this returns that positive pid. Otherwise, when
  180. # the process is not alive, will return the negative value of the pid.
  181. #
  182. sub processexists {
  183. use POSIX ":sys_wait_h";
  184. my $pidfile = $_[0];
  185. # fetch pid from pidfile
  186. my $pid = pidfromfile($pidfile);
  187. if($pid > 0) {
  188. # verify if currently alive
  189. if(pidexists($pid)) {
  190. return $pid;
  191. }
  192. else {
  193. # get rid of the certainly invalid pidfile
  194. unlink($pidfile) if($pid == pidfromfile($pidfile));
  195. # reap its dead children, if not done yet
  196. pidwait($pid, &WNOHANG);
  197. # negative return value means dead process
  198. return -$pid;
  199. }
  200. }
  201. return 0;
  202. }
  203. #######################################################################
  204. # killpid attempts to gracefully stop processes in the given pid list
  205. # with a SIGTERM signal and SIGKILLs those which haven't died on time.
  206. #
  207. sub killpid {
  208. use POSIX ":sys_wait_h";
  209. my ($verbose, $pidlist) = @_;
  210. my @requested;
  211. my @signalled;
  212. my @reapchild;
  213. # The 'pidlist' argument is a string of whitespace separated pids.
  214. return if(not defined($pidlist));
  215. # Make 'requested' hold the non-duplicate pids from 'pidlist'.
  216. @requested = split(' ', $pidlist);
  217. return if(not @requested);
  218. if(scalar(@requested) > 2) {
  219. @requested = sort({$a <=> $b} @requested);
  220. }
  221. for(my $i = scalar(@requested) - 2; $i >= 0; $i--) {
  222. if($requested[$i] == $requested[$i+1]) {
  223. splice @requested, $i+1, 1;
  224. }
  225. }
  226. # Send a SIGTERM to processes which are alive to gracefully stop them.
  227. foreach my $tmp (@requested) {
  228. chomp $tmp;
  229. if($tmp =~ /^(\d+)$/) {
  230. my $pid = $1;
  231. if($pid > 0) {
  232. if(pidexists($pid)) {
  233. print("RUN: Process with pid $pid signalled to die\n")
  234. if($verbose);
  235. pidterm($pid);
  236. push @signalled, $pid;
  237. }
  238. else {
  239. print("RUN: Process with pid $pid already dead\n")
  240. if($verbose);
  241. # if possible reap its dead children
  242. pidwait($pid, &WNOHANG);
  243. push @reapchild, $pid;
  244. }
  245. }
  246. }
  247. }
  248. # Allow all signalled processes five seconds to gracefully die.
  249. if(@signalled) {
  250. my $twentieths = 5 * 20;
  251. while($twentieths--) {
  252. for(my $i = scalar(@signalled) - 1; $i >= 0; $i--) {
  253. my $pid = $signalled[$i];
  254. if(!pidexists($pid)) {
  255. print("RUN: Process with pid $pid gracefully died\n")
  256. if($verbose);
  257. splice @signalled, $i, 1;
  258. # if possible reap its dead children
  259. pidwait($pid, &WNOHANG);
  260. push @reapchild, $pid;
  261. }
  262. }
  263. last if(not scalar(@signalled));
  264. portable_sleep(0.05);
  265. }
  266. }
  267. # Mercilessly SIGKILL processes still alive.
  268. if(@signalled) {
  269. foreach my $pid (@signalled) {
  270. if($pid > 0) {
  271. print("RUN: Process with pid $pid forced to die with SIGKILL\n")
  272. if($verbose);
  273. pidkill($pid);
  274. # if possible reap its dead children
  275. pidwait($pid, &WNOHANG);
  276. push @reapchild, $pid;
  277. }
  278. }
  279. }
  280. # Reap processes dead children for sure.
  281. if(@reapchild) {
  282. foreach my $pid (@reapchild) {
  283. if($pid > 0) {
  284. pidwait($pid, 0);
  285. }
  286. }
  287. }
  288. }
  289. #######################################################################
  290. # killsockfilters kills sockfilter processes for a given server.
  291. #
  292. sub killsockfilters {
  293. my ($proto, $ipvnum, $idnum, $verbose, $which) = @_;
  294. my $server;
  295. my $pidfile;
  296. my $pid;
  297. return if($proto !~ /^(ftp|imap|pop3|smtp)$/);
  298. die "unsupported sockfilter: $which"
  299. if($which && ($which !~ /^(main|data)$/));
  300. $server = servername_id($proto, $ipvnum, $idnum) if($verbose);
  301. if(!$which || ($which eq 'main')) {
  302. $pidfile = mainsockf_pidfilename($proto, $ipvnum, $idnum);
  303. $pid = processexists($pidfile);
  304. if($pid > 0) {
  305. printf("* kill pid for %s-%s => %d\n", $server,
  306. ($proto eq 'ftp')?'ctrl':'filt', $pid) if($verbose);
  307. pidkill($pid);
  308. pidwait($pid, 0);
  309. }
  310. unlink($pidfile) if(-f $pidfile);
  311. }
  312. return if($proto ne 'ftp');
  313. if(!$which || ($which eq 'data')) {
  314. $pidfile = datasockf_pidfilename($proto, $ipvnum, $idnum);
  315. $pid = processexists($pidfile);
  316. if($pid > 0) {
  317. printf("* kill pid for %s-data => %d\n", $server,
  318. $pid) if($verbose);
  319. pidkill($pid);
  320. pidwait($pid, 0);
  321. }
  322. unlink($pidfile) if(-f $pidfile);
  323. }
  324. }
  325. #######################################################################
  326. # killallsockfilters kills sockfilter processes for all servers.
  327. #
  328. sub killallsockfilters {
  329. my $verbose = $_[0];
  330. for my $proto (('ftp', 'imap', 'pop3', 'smtp')) {
  331. for my $ipvnum (('4', '6')) {
  332. for my $idnum (('1', '2')) {
  333. killsockfilters($proto, $ipvnum, $idnum, $verbose);
  334. }
  335. }
  336. }
  337. }
  338. sub set_advisor_read_lock {
  339. my ($filename) = @_;
  340. if(open(FILEH, ">$filename")) {
  341. close(FILEH);
  342. return;
  343. }
  344. printf "Error creating lock file $filename error: $!";
  345. }
  346. sub clear_advisor_read_lock {
  347. my ($filename) = @_;
  348. if(-f $filename) {
  349. unlink($filename);
  350. }
  351. }
  352. 1;