ForkedDaemon.pm 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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. while (my $c = $fd->accept) {
  103. my $pid = fork();
  104. if (not defined $pid)
  105. {
  106. logger "Could not allocate resources for Fast Download Client.",1;
  107. }
  108. # Only the forked child goes here.
  109. elsif ($pid == 0)
  110. {
  111. if(%aliases)
  112. {
  113. while(my $r = $c->get_request) {
  114. process_client_request($FastDownload::Settings{listing}, $r, $c);
  115. $c->close;
  116. }
  117. }
  118. else
  119. {
  120. while(my $r = $c->get_request) {
  121. $c->send_error(403,"");
  122. $c->close;
  123. }
  124. }
  125. undef($c);
  126. # Child process must exit.
  127. exit(0);
  128. }
  129. }
  130. sub process_client_request
  131. {
  132. my($listing, $r, $c) = @_;
  133. my @uri_alias = split /\//, $r->uri->path;
  134. if(defined $uri_alias[1])
  135. {
  136. my $alias = $uri_alias[1];
  137. if ($r->method eq 'GET' and defined $aliases{$alias})
  138. {
  139. my $home = $aliases{$alias}{home};
  140. my (@extensions,@subnets);
  141. if(defined $aliases{$alias}{match_file_extension})
  142. {
  143. @extensions = split /,/, $aliases{$alias}{match_file_extension};
  144. }
  145. if(defined $aliases{$alias}{match_client_ip})
  146. {
  147. @subnets = split /,/, $aliases{$alias}{match_client_ip};
  148. }
  149. my $client = getpeername($c);
  150. my ($port, $iaddr) = unpack_sockaddr_in($client);
  151. my $client_ip = inet_ntoa($iaddr);
  152. my $uri = uri_unescape($r->uri->path);
  153. my $escaped_alias = "\/" . $alias;
  154. $uri =~ s/^$escaped_alias//g;
  155. my $location = $home . $uri;
  156. my $is_subnet;
  157. if(!grep {defined($_)} @subnets)
  158. {
  159. $is_subnet = 1;
  160. }
  161. else
  162. {
  163. foreach my $subnet (@subnets)
  164. {
  165. $is_subnet = in_subnet($client_ip, $subnet);
  166. if($is_subnet)
  167. {
  168. last;
  169. }
  170. }
  171. }
  172. if($is_subnet)
  173. {
  174. if(-d $location)
  175. {
  176. my $index = $location . "/" . "index.html";
  177. if(-f $index)
  178. {
  179. $c->send_file_response($index);
  180. }
  181. else
  182. {
  183. if($listing == 1)
  184. {
  185. # Loop through all files and folders
  186. my @dirs = ();
  187. my @bins = ();
  188. my @files = ();
  189. opendir(DIR, $location);
  190. while (my $entry = readdir(DIR))
  191. {
  192. # Skip . and ..
  193. next if $entry =~ /^\./;
  194. my $link_location = $location."/".$entry;
  195. if(-d $link_location)
  196. {
  197. push(@dirs, $entry);
  198. }
  199. elsif(-B $link_location)
  200. {
  201. push(@bins, $entry);
  202. }
  203. else
  204. {
  205. push(@files, $entry);
  206. }
  207. }
  208. closedir(DIR);
  209. @dirs = sort @dirs;
  210. @bins = sort @bins;
  211. @files = sort @files;
  212. my ($content, $href);
  213. foreach my $dir (@dirs)
  214. {
  215. $href = Path::Class::Dir->new($r->uri->path, $dir);
  216. $content .= "<a href='" . $href . "' >".$dir."</a><br>";
  217. }
  218. foreach my $bin (@bins)
  219. {
  220. $href = Path::Class::File->new($r->uri->path, $bin);
  221. $content .= "<a href='" . $href . "' >".$bin."</a><br>";
  222. }
  223. foreach my $file (@files)
  224. {
  225. $href = Path::Class::File->new($r->uri->path, $file);
  226. $content .= "<a href='" . $href . "' >".$file."</a><br>";
  227. }
  228. my $response = HTTP::Response->new(200);
  229. $response->content($content);
  230. $response->header("Content-Type" => "text/html");
  231. $c->send_response($response);
  232. }
  233. else
  234. {
  235. $c->send_error(403,"");
  236. }
  237. }
  238. }
  239. else
  240. {
  241. my @extension = split /\./, $uri;
  242. my $extension = $extension[-1];
  243. if(grep {$_ eq $extension} @extensions or !grep {defined($_)} @extensions)
  244. {
  245. $c->send_file_response($location);
  246. }
  247. else
  248. {
  249. $c->send_error(403,"");
  250. }
  251. }
  252. }
  253. else
  254. {
  255. $c->send_error(403,"");
  256. }
  257. }
  258. else
  259. {
  260. $c->send_error(403,"");
  261. }
  262. }
  263. else
  264. {
  265. $c->send_error(403,"");
  266. }
  267. }
  268. sub ip2long($)
  269. {
  270. return( unpack( 'N', inet_aton(shift) ) );
  271. }
  272. sub in_subnet($$)
  273. {
  274. my $ip = shift;
  275. my $subnet = shift;
  276. my $ip_long = ip2long( $ip );
  277. 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})$| )
  278. {
  279. my $subnet = ip2long($1);
  280. my $mask = ip2long($2);
  281. if( ($ip_long & $mask)==$subnet )
  282. {
  283. return 1;
  284. }
  285. }
  286. elsif( $subnet=~m|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/(\d{1,2})$| )
  287. {
  288. my $subnet = ip2long($1);
  289. my $bits = $2;
  290. my $mask = -1<<(32-$bits);
  291. $subnet&= $mask;
  292. if( ($ip_long & $mask)==$subnet )
  293. {
  294. return 1;
  295. }
  296. }
  297. elsif( $subnet=~m|(^\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})-(\d{1,3})$| )
  298. {
  299. my $start_ip = ip2long($1.$2);
  300. my $end_ip = ip2long($1.$3);
  301. if( $start_ip<=$ip_long and $end_ip>=$ip_long )
  302. {
  303. return 1;
  304. }
  305. }
  306. elsif( $subnet=~m|^[\d\*]{1,3}\.[\d\*]{1,3}\.[\d\*]{1,3}\.[\d\*]{1,3}$| )
  307. {
  308. my $search_string = $subnet;
  309. $search_string=~s/\./\\\./g;
  310. $search_string=~s/\*/\.\*/g;
  311. if( $ip=~/^$search_string$/ )
  312. {
  313. return 1;
  314. }
  315. }
  316. return 0;
  317. }