ForkedDaemon.pm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. use strict;
  2. use warnings;
  3. use lib ".";
  4. use FastDownload::Settings; # Daemon Settings
  5. use Cwd; # Fast way to get the current directory
  6. use Fcntl ':flock'; # Import LOCK_* constants for file locking
  7. use File::Copy; # Simple file copy functions
  8. use Path::Class::File; # Handle files and directories.
  9. use HTTP::Daemon; # Create the Fast Download Daemon.
  10. use URI::Escape; # Translate url code for example: %20 to space
  11. use Socket qw( inet_aton ); # Work with network addresses.
  12. use constant RUN_DIR => getcwd();
  13. use constant FD_DIR => Path::Class::Dir->new(RUN_DIR, 'FastDownload');
  14. use constant FD_ALIASES_DIR => Path::Class::Dir->new(FD_DIR, 'aliases');
  15. use constant FD_PID_FILE => Path::Class::File->new(FD_DIR, 'fd.pid');
  16. use constant FD_LOG_FILE => Path::Class::File->new(FD_DIR, 'fastdownload.log');
  17. ### Logger function.
  18. ### @param line the line that is put to the log file.
  19. sub logger
  20. {
  21. my $logcmd = $_[0];
  22. my $also_print = 0;
  23. if (@_ == 2)
  24. {
  25. ($also_print) = $_[1];
  26. }
  27. $logcmd = localtime() . " $logcmd\n";
  28. if ($also_print == 1)
  29. {
  30. print "$logcmd";
  31. }
  32. open(LOGFILE, '>>', FD_LOG_FILE)
  33. or die("Can't open " . FD_LOG_FILE . " - $!");
  34. flock(LOGFILE, LOCK_EX) or die("Failed to lock log file.");
  35. seek(LOGFILE, 0, 2) or die("Failed to seek to end of file.");
  36. print LOGFILE "$logcmd" or die("Failed to write to log file.");
  37. flock(LOGFILE, LOCK_UN) or die("Failed to unlock log file.");
  38. close(LOGFILE) or die("Failed to close log file.");
  39. }
  40. # Rotate the log file
  41. if (-e FD_LOG_FILE)
  42. {
  43. if (-e FD_LOG_FILE . ".bak")
  44. {
  45. unlink(FD_LOG_FILE . ".bak");
  46. }
  47. logger "Rotating log file";
  48. move(FD_LOG_FILE, FD_LOG_FILE . ".bak");
  49. logger "New log file created";
  50. }
  51. if (open(PIDFILE, '>', FD_PID_FILE))
  52. {
  53. print PIDFILE $$;
  54. close(PIDFILE);
  55. }
  56. $SIG{'PIPE'} = 'IGNORE';
  57. my $fd = HTTP::Daemon->new(LocalAddr=>$FastDownload::Settings{ip},
  58. LocalPort=>$FastDownload::Settings{port},
  59. ReuseAddr=>'1') || die;
  60. logger "Fast Download Daemon Started at: <URL:" . $fd->url . "> - PID $$",1;
  61. my %aliases;
  62. if(-d FD_ALIASES_DIR)
  63. {
  64. if( !opendir(ALIASES, FD_ALIASES_DIR) )
  65. {
  66. logger "Error openning aliases directory " . FD_ALIASES_DIR . ", $!";
  67. }
  68. else
  69. {
  70. while (my $alias = readdir(ALIASES))
  71. {
  72. # Skip . and ..
  73. next if $alias =~ /^\./;
  74. if( !open(ALIAS, '<', Path::Class::Dir->new(FD_ALIASES_DIR, $alias)) )
  75. {
  76. logger "Error reading alias '$alias', $!";
  77. }
  78. else
  79. {
  80. my @file_lines = ();
  81. my $i = 0;
  82. while (<ALIAS>)
  83. {
  84. chomp $_;
  85. $file_lines[$i] = $_;
  86. $i++;
  87. }
  88. close(ALIAS);
  89. $aliases{$alias}{home} = $file_lines[0];
  90. $aliases{$alias}{match_file_extension} = $file_lines[1];
  91. $aliases{$alias}{match_client_ip} = $file_lines[2];
  92. }
  93. }
  94. closedir(ALIASES);
  95. }
  96. }
  97. else
  98. {
  99. logger "Aliases directory '" . FD_ALIASES_DIR . "' does not exist or is inaccessible.";
  100. }
  101. $SIG{CHLD} = 'IGNORE';
  102. my $origPid = fork();
  103. open(my $old_stderr, '>&', \*STDERR) or die "Can't save STDERR: $!";
  104. my $null_device = (-e '/dev/null') ? '/dev/null' : 'NUL';
  105. open(STDERR, '>', $null_device) or die "Can't redirect STDERR to $null_device: $!";
  106. while (my $c = $fd->accept) {
  107. my $pid = fork();
  108. if (not defined $pid)
  109. {
  110. logger "Could not allocate resources for Fast Download Client.",1;
  111. }
  112. # Only the forked child goes here.
  113. elsif ($pid == 0)
  114. {
  115. if(%aliases)
  116. {
  117. while(my $r = $c->get_request) {
  118. process_client_request($FastDownload::Settings{listing}, $r, $c);
  119. $c->close;
  120. }
  121. }
  122. else
  123. {
  124. while(my $r = $c->get_request) {
  125. $c->send_error(403,"");
  126. $c->close;
  127. }
  128. }
  129. undef($c);
  130. # Child process must exit.
  131. exit(0);
  132. }
  133. }
  134. open(STDERR, '>&', $old_stderr) or die "Can't restore STDERR: $!";
  135. close($old_stderr);
  136. sub process_client_request
  137. {
  138. my($listing, $r, $c) = @_;
  139. my @uri_alias = split /\//, $r->uri->path;
  140. if(defined $uri_alias[1])
  141. {
  142. my $alias = $uri_alias[1];
  143. if ($r->method eq 'GET' and defined $aliases{$alias})
  144. {
  145. my $home = $aliases{$alias}{home};
  146. my (@extensions,@subnets);
  147. if(defined $aliases{$alias}{match_file_extension})
  148. {
  149. @extensions = split /,/, $aliases{$alias}{match_file_extension};
  150. }
  151. if(defined $aliases{$alias}{match_client_ip})
  152. {
  153. @subnets = split /,/, $aliases{$alias}{match_client_ip};
  154. }
  155. my $client = getpeername($c);
  156. my ($port, $iaddr) = unpack_sockaddr_in($client);
  157. my $client_ip = inet_ntoa($iaddr);
  158. my $uri = uri_unescape($r->uri->path);
  159. my $escaped_alias = "\/" . $alias;
  160. $uri =~ s/^$escaped_alias//g;
  161. my $location = $home . $uri;
  162. my $is_subnet;
  163. if(!grep {defined($_)} @subnets)
  164. {
  165. $is_subnet = 1;
  166. }
  167. else
  168. {
  169. foreach my $subnet (@subnets)
  170. {
  171. $is_subnet = in_subnet($client_ip, $subnet);
  172. if($is_subnet)
  173. {
  174. last;
  175. }
  176. }
  177. }
  178. if($is_subnet)
  179. {
  180. if(-d $location)
  181. {
  182. my $index = $location . "/" . "index.html";
  183. if(-f $index)
  184. {
  185. $c->send_file_response($index);
  186. }
  187. else
  188. {
  189. if($listing == 1)
  190. {
  191. # Loop through all files and folders
  192. my @dirs = ();
  193. my @bins = ();
  194. my @files = ();
  195. opendir(DIR, $location);
  196. while (my $entry = readdir(DIR))
  197. {
  198. # Skip . and ..
  199. next if $entry =~ /^\./;
  200. my $link_location = $location."/".$entry;
  201. if(-d $link_location)
  202. {
  203. push(@dirs, $entry);
  204. }
  205. elsif(-B $link_location)
  206. {
  207. push(@bins, $entry);
  208. }
  209. else
  210. {
  211. push(@files, $entry);
  212. }
  213. }
  214. closedir(DIR);
  215. @dirs = sort @dirs;
  216. @bins = sort @bins;
  217. @files = sort @files;
  218. my ($content, $href);
  219. foreach my $dir (@dirs)
  220. {
  221. $href = Path::Class::Dir->new($r->uri->path, $dir);
  222. $content .= "<a href='" . $href . "' >".$dir."</a><br>";
  223. }
  224. foreach my $bin (@bins)
  225. {
  226. $href = Path::Class::File->new($r->uri->path, $bin);
  227. $content .= "<a href='" . $href . "' >".$bin."</a><br>";
  228. }
  229. foreach my $file (@files)
  230. {
  231. $href = Path::Class::File->new($r->uri->path, $file);
  232. $content .= "<a href='" . $href . "' >".$file."</a><br>";
  233. }
  234. my $response = HTTP::Response->new(200);
  235. $response->content($content);
  236. $response->header("Content-Type" => "text/html");
  237. $c->send_response($response);
  238. }
  239. else
  240. {
  241. $c->send_error(403,"");
  242. }
  243. }
  244. }
  245. else
  246. {
  247. my @extension = split /\./, $uri;
  248. my $extension = $extension[-1];
  249. if(grep {$_ eq $extension} @extensions or !grep {defined($_)} @extensions)
  250. {
  251. $c->send_file_response($location);
  252. }
  253. else
  254. {
  255. $c->send_error(403,"");
  256. }
  257. }
  258. }
  259. else
  260. {
  261. $c->send_error(403,"");
  262. }
  263. }
  264. else
  265. {
  266. $c->send_error(403,"");
  267. }
  268. }
  269. else
  270. {
  271. $c->send_error(403,"");
  272. }
  273. }
  274. sub ip2long($)
  275. {
  276. return( unpack( 'N', inet_aton(shift) ) );
  277. }
  278. sub in_subnet($$)
  279. {
  280. my $ip = shift;
  281. my $subnet = shift;
  282. my $ip_long = ip2long( $ip );
  283. if( $subnet=~m|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$| )
  284. {
  285. my $subnet = ip2long($1);
  286. my $mask = ip2long($2);
  287. if( ($ip_long & $mask)==$subnet )
  288. {
  289. return 1;
  290. }
  291. }
  292. elsif( $subnet=~m|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/(\d{1,2})$| )
  293. {
  294. my $subnet = ip2long($1);
  295. my $bits = $2;
  296. my $mask = -1<<(32-$bits);
  297. $subnet&= $mask;
  298. if( ($ip_long & $mask)==$subnet )
  299. {
  300. return 1;
  301. }
  302. }
  303. elsif( $subnet=~m|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})-(\d{1,3})$| )
  304. {
  305. my $start_ip = ip2long($1.$2);
  306. my $end_ip = ip2long($1.$3);
  307. if( $start_ip<=$ip_long and $end_ip>=$ip_long )
  308. {
  309. return 1;
  310. }
  311. }
  312. elsif( $subnet=~m|^[\d\*]{1,3}\.[\d\*]{1,3}\.[\d\*]{1,3}\.[\d\*]{1,3}$| )
  313. {
  314. my $search_string = $subnet;
  315. $search_string=~s/\./\\\./g;
  316. $search_string=~s/\*/\.\*/g;
  317. if( $ip=~/^$search_string$/ )
  318. {
  319. return 1;
  320. }
  321. }
  322. return 0;
  323. }