ForkedDaemon.pm 7.4 KB

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