1 | #!/usr/bin/perl -w
|
1 | #!/usr/bin/perl -w |
2 | ##################
|
2 | ################## |
3 | # Playerfile download utility.
|
3 | # Playerfile download utility. |
4 | # Version 1.2
|
4 | # Version 1.2 |
5 | ####
|
5 | #### |
6 | # Note: This file requires the CGI.pm module to operate. |
6 | # Note: This file requires the CGI.pm module to operate. |
7 | # The player_dl.html file is a basic web page which |
7 | # The player_dl.html file is a basic web page which |
8 | # can be used for downloads. |
8 | # can be used for downloads. |
9 | # |
9 | # |
10 | # This CGI script allows players to download their players
|
10 | # This CGI script allows players to download their players |
11 | # files through a web interface. It does password checking
|
11 | # files through a web interface. It does password checking |
12 | # and has some extra options.
|
12 | # and has some extra options. |
13 | # |
13 | # |
14 | # Note 2: The player files and directories need to be readable |
14 | # Note 2: The player files and directories need to be readable |
15 | # by whatever uid runs this program. In many cases, this may be |
15 | # by whatever uid runs this program. In many cases, this may be |
16 | # nobody or apache or whatever. This script does not differentiate |
16 | # nobody or apache or whatever. This script does not differentiate |
17 | # invalid password or lack of ability to read player files. If |
17 | # invalid password or lack of ability to read player files. If |
… | |
… | |
21 | # Note 3: on some systems, differnet password encryption schemes |
21 | # Note 3: on some systems, differnet password encryption schemes |
22 | # are used. Eg, on windows, no encryption is used at all, while |
22 | # are used. Eg, on windows, no encryption is used at all, while |
23 | # on others, if des_crypt is available, that is used instead. |
23 | # on others, if des_crypt is available, that is used instead. |
24 | # this script would need modification to cover those cases. |
24 | # this script would need modification to cover those cases. |
25 | # |
25 | # |
26 | ####
|
26 | #### |
27 | # Copyright (c) 2003 by Philip Stolarczyk
|
27 | # Copyright (c) 2003 by Philip Stolarczyk |
28 | # This program is free software; you can redistribute it
|
28 | # This program is free software; you can redistribute it |
29 | # and/or modify it under the terms of the GNU General Public
|
29 | # and/or modify it under the terms of the GNU General Public |
30 | # License as published by the Free Software Foundation;
|
30 | # License as published by the Free Software Foundation; |
31 | # either version 2 of the License, or (at your option) any
|
31 | # either version 2 of the License, or (at your option) any |
32 | # later version.
|
32 | # later version. |
33 | # This program is distributed in the hope that it will be
|
33 | # This program is distributed in the hope that it will be |
34 | # useful, but WITHOUT ANY WARRANTY; without even the implied
|
34 | # useful, but WITHOUT ANY WARRANTY; without even the implied |
35 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
35 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
36 | # PURPOSE. See the GNU General Public License for more
|
36 | # PURPOSE. See the GNU General Public License for more |
37 | # details.
|
37 | # details. |
38 | ####
|
38 | #### |
39 | # Config options:
|
39 | # Config options: |
40 | #
|
40 | # |
41 | # Where the tar program is located.
|
41 | # Where the tar program is located. |
42 | $tar = '@TAR@';
|
42 | $tar = '@TAR@'; |
43 |
|
43 | |
44 | $prefix="@prefix@"; |
44 | $prefix="@prefix@"; |
45 | # Where the crossfire directory is located.
|
45 | # Where the crossfire directory is located. |
46 | $crossfire_home = "@pkgstatedir@/players"; |
46 | $crossfire_home = "@pkgstatedir@/players"; |
47 |
|
47 | |
48 | # Where to save temporary files
|
48 | # Where to save temporary files |
49 | $temp_dir = '/tmp';
|
49 | $temp_dir = '/tmp'; |
50 |
|
50 | |
51 | # How often a player can have their file sent to them, in seconds. (ie. 3600 is once/hour), set to 0 to disable.
|
51 | # How often a player can have their file sent to them, in seconds. (ie. 3600 is once/hour), set to 0 to disable. |
52 | $timelimit = 3600;
|
52 | $timelimit = 3600; |
53 |
|
53 | |
54 | # Where to save information on when which player files were downloaded, for the time limit function.
|
54 | # Where to save information on when which player files were downloaded, for the time limit function. |
55 | $statefile = "$temp_dir/pldl.dat";
|
55 | $statefile = "$temp_dir/pldl.dat"; |
56 |
|
56 | |
57 | # Whether to delete the player's file after they download it.
|
57 | # Whether to delete the player's file after they download it. |
58 | $delete_player = 0;
|
58 | $delete_player = 0; |
59 |
|
59 | |
60 | #
|
60 | # |
61 | ####
|
61 | #### |
62 | # BUGS:
|
62 | # BUGS: |
63 | # Systems that do NL to CRLF interpretation on CGI output
|
63 | # Systems that do NL to CRLF interpretation on CGI output |
64 | # will corrupt the .tar file. This includes Microsoft
|
64 | # will corrupt the .tar file. This includes Microsoft |
65 | # Windows systems. I haven't found any solution.
|
65 | # Windows systems. I haven't found any solution. |
66 | ##################
|
66 | ################## |
67 | # Code begins.
|
67 | # Code begins. |
68 |
|
68 | |
69 | use CGI;
|
69 | use CGI; |
70 | use CGI::Carp 'fatalsToBrowser';
|
70 | use CGI::Carp 'fatalsToBrowser'; |
71 | $CGI::POST_MAX=1024; # max 1K posts
|
71 | $CGI::POST_MAX=1024; # max 1K posts |
72 | $CGI::DISABLE_UPLOADS = 1; # no uploads
|
72 | $CGI::DISABLE_UPLOADS = 1; # no uploads |
73 |
|
73 | |
74 | $q = new CGI;
|
74 | $q = new CGI; |
75 |
|
75 | |
76 | # Verify that player name contains no invalid characters.
|
76 | # Verify that player name contains no invalid characters. |
77 | $playername = '';
|
77 | $playername = ''; |
78 | $playername = $q->param('playername') if $q->param('playername');
|
78 | $playername = $q->param('playername') if $q->param('playername'); |
79 | $playername =~ s/[^A-Za-z_\-]//g; # No invalid chars
|
79 | $playername =~ s/[^A-Za-z_\-]//g; # No invalid chars |
80 | $playername =~ s/^(.{1,64}).*$/$1/; # Max 64 chars, (really it's 16 or 32 in the server)
|
80 | $playername =~ s/^(.{1,64}).*$/$1/; # Max 64 chars, (really it's 16 or 32 in the server) |
81 |
|
81 | |
82 | # Default to not validated, until the password is checked.
|
82 | # Default to not validated, until the password is checked. |
83 | $valid = 0;
|
83 | $valid = 0; |
84 |
|
84 | |
85 | # No error to report yet.
|
85 | # No error to report yet. |
86 | $errormsg = '';
|
86 | $errormsg = ''; |
87 |
|
87 | |
88 | # We want to the time we ran to be consistent, even if it takes a couple seconds.
|
88 | # We want to the time we ran to be consistent, even if it takes a couple seconds. |
89 | $time = time();
|
89 | $time = time(); |
90 |
|
90 | |
91 |
|
91 | |
92 | # Validate password
|
92 | # Validate password |
93 | $password = $q->param('password');
|
93 | $password = $q->param('password'); |
94 | if ($playername) { # Make sure that the user typed in a playername.
|
94 | if ($playername) { # Make sure that the user typed in a playername. |
95 | if ((open PLAYERFILE, "$crossfire_home/$playername/$playername.pl") # Make sure the player's file exists
|
95 | if ((open PLAYERFILE, "$crossfire_home/$playername/$playername.pl") # Make sure the player's file exists |
96 | or (open PLAYERFILE, "$crossfire_home/$playername/$playername.pl.dead")) { # Or use the dead file, if no player is alive
|
96 | or (open PLAYERFILE, "$crossfire_home/$playername/$playername.pl.dead")) { # Or use the dead file, if no player is alive |
97 | foreach (<PLAYERFILE>) {
|
97 | foreach (<PLAYERFILE>) { |
98 | chomp; chomp;
|
98 | chomp; chomp; |
99 | # Do actual checking of password.
|
99 | # Do actual checking of password. |
100 | if ( /^password (.*)$/ ) { |
100 | if ( /^password (.*)$/ ) { |
101 | $cp = crypt($password,$1); |
101 | $cp = crypt($password,$1); |
102 | if ($cp eq $1) {
|
102 | if ($cp eq $1) { |
103 | $valid = 1;
|
103 | $valid = 1; |
104 | } |
104 | } |
105 | } |
105 | } |
106 | }
|
106 | } |
107 | close PLAYERFILE;
|
107 | close PLAYERFILE; |
108 | }
|
108 | } |
109 | }
|
109 | } |
110 | if (!$valid) { $errormsg = 'Invalid username or password' };
|
110 | if (!$valid) { $errormsg = 'Invalid username or password' }; |
111 |
|
111 | |
112 | # If the player is validated, and we're limiting how often players can download their files, do so.
|
112 | # If the player is validated, and we're limiting how often players can download their files, do so. |
113 | if ($valid and $timelimit and $statefile) {
|
113 | if ($valid and $timelimit and $statefile) { |
114 | open STATEFILE, "<$statefile";
|
114 | open STATEFILE, "<$statefile"; |
115 | @contents = <STATEFILE>;
|
115 | @contents = <STATEFILE>; |
116 | close STATEFILE;
|
116 | close STATEFILE; |
117 | # Don't allow more than 1024 players to download their files per $timelimit seconds.
|
117 | # Don't allow more than 1024 players to download their files per $timelimit seconds. |
118 | # This is to prevent STATEFILE from getting too large.
|
118 | # This is to prevent STATEFILE from getting too large. |
119 | if ($#contents > 1024) {
|
119 | if ($#contents > 1024) { |
120 | $valid = 0;
|
120 | $valid = 0; |
121 | $errormsg = 'Too many players have tried to download their files recently. Please wait a bit before trying again.\n';
|
121 | $errormsg = 'Too many players have tried to download their files recently. Please wait a bit before trying again.\n'; |
122 | }
|
122 | } |
123 |
|
123 | |
124 | # Check timestamp of last download for this player
|
124 | # Check timestamp of last download for this player |
125 | foreach (@contents) {
|
125 | foreach (@contents) { |
126 | chomp; chomp;
|
126 | chomp; chomp; |
127 | if (/^DL $playername (.*)$/) {
|
127 | if (/^DL $playername (.*)$/) { |
128 | # $1 is the last time the file was DLed.
|
128 | # $1 is the last time the file was DLed. |
129 | if ($time > ($timelimit + $1)) {
|
129 | if ($time > ($timelimit + $1)) { |
130 | $valid = 0;
|
130 | $valid = 0; |
131 | $errormsg = 'You just downloaded your file. Wait a bit before trying again.';
|
131 | $errormsg = 'You just downloaded your file. Wait a bit before trying again.'; |
132 | }
|
132 | } |
133 | }
|
133 | } |
134 | }
|
134 | } |
135 | }
|
135 | } |
136 |
|
136 | |
137 | if ($valid) {
|
137 | if ($valid) { |
138 | # Create and send file
|
138 | # Create and send file |
139 |
|
139 | |
140 | # Create a new archive
|
140 | # Create a new archive |
141 | # Sending binary data.
|
141 | # Sending binary data. |
142 | # Add content-disposition, in this way, the browser (at least mozilla) |
142 | # Add content-disposition, in this way, the browser (at least mozilla) |
143 | # will use it as the default filename, instead of the cgi script name. |
143 | # will use it as the default filename, instead of the cgi script name. |
144 | print $q->header(-type=>"application/x-compressed-tar", |
144 | print $q->header(-type=>"application/x-compressed-tar", |
145 | "-content-disposition"=>"inline; filename=\"$playername.tar\""); |
145 | "-content-disposition"=>"inline; filename=\"$playername.tar\""); |
146 | |
146 | |
147 | # Change to player directory, so that long pathname is not included in |
147 | # Change to player directory, so that long pathname is not included in |
148 | # sent file. |
148 | # sent file. |
149 | chdir("$crossfire_home"); |
149 | chdir("$crossfire_home"); |
150 | # archive up the player |
150 | # archive up the player |
151 | system("$tar -cf - $playername"); |
151 | system("$tar -cf - $playername"); |
152 |
|
152 | |
153 | # 'Delete' player's files, if applicable. (technically rename them, to hide from server.)
|
153 | # 'Delete' player's files, if applicable. (technically rename them, to hide from server.) |
154 | if ($valid and $delete_player) {
|
154 | if ($valid and $delete_player) { |
155 | @files= glob("$crossfire_home/$playername/*");
|
155 | @files= glob("$crossfire_home/$playername/*"); |
156 | # Rename all files except *.tar
|
156 | # Rename all files except *.tar |
157 | foreach (@files) {
|
157 | foreach (@files) { |
158 | next if ( /\.tar$/i );
|
158 | next if ( /\.tar$/i ); |
159 | rename $_, "$_.downloaded";
|
159 | rename $_, "$_.downloaded"; |
160 | }
|
160 | } |
161 | }
|
161 | } |
162 | |
162 | |
163 | # Set timestamp of last download for this player, if applicable.
|
163 | # Set timestamp of last download for this player, if applicable. |
164 | # Also, remove outdated player download timestamps, if applicable.
|
164 | # Also, remove outdated player download timestamps, if applicable. |
165 | if ($timelimit > 0 and $statefile) {
|
165 | if ($timelimit > 0 and $statefile) { |
166 | if (open STATEFILE, "<$statefile") {
|
166 | if (open STATEFILE, "<$statefile") { |
167 | @contents = <STATEFILE>;
|
167 | @contents = <STATEFILE>; |
168 | close STATEFILE;
|
168 | close STATEFILE; |
169 | } else {
|
169 | } else { |
170 | @contents = ();
|
170 | @contents = (); |
171 | }
|
171 | } |
172 |
|
172 | |
173 | if (open STATEFILE, ">$statefile") {
|
173 | if (open STATEFILE, ">$statefile") { |
174 | foreach (@contents) {
|
174 | foreach (@contents) { |
175 | chomp; chomp;
|
175 | chomp; chomp; |
176 | if (/^DL (.*) (.*)$/) {
|
176 | if (/^DL (.*) (.*)$/) { |
177 | # All lines starting with DL are download time records.
|
177 | # All lines starting with DL are download time records. |
178 | my ($playerdownloaded, $timedownloaded) = ($1, $2);
|
178 | my ($playerdownloaded, $timedownloaded) = ($1, $2); |
179 |
|
179 | |
180 | # If this player just downloaded their file, don't copy them yet. We update their timestamp later.
|
180 | # If this player just downloaded their file, don't copy them yet. We update their timestamp later. |
181 | next if ($valid and ($playerdownloaded eq $playername));
|
181 | next if ($valid and ($playerdownloaded eq $playername)); |
182 | # If this record has expired, don't copy it.
|
182 | # If this record has expired, don't copy it. |
183 | next if (($timedownloaded + $timelimit) < $time);
|
183 | next if (($timedownloaded + $timelimit) < $time); |
184 | # Otherwise, copy the record to the new state file.
|
184 | # Otherwise, copy the record to the new state file. |
185 | print "$_\n";
|
185 | print "$_\n"; |
186 | } else {
|
186 | } else { |
187 | # Allow other lines in this file.
|
187 | # Allow other lines in this file. |
188 | print "$_\n";
|
188 | print "$_\n"; |
189 | }
|
189 | } |
190 | }
|
190 | } |
191 | # If this player downloaded their file, save it.
|
191 | # If this player downloaded their file, save it. |
192 | if ($valid) {
|
192 | if ($valid) { |
193 | print STATEFILE "DL $playername $time\n";
|
193 | print STATEFILE "DL $playername $time\n"; |
194 | }
|
194 | } |
195 | close STATEFILE;
|
195 | close STATEFILE; |
196 | } else {
|
196 | } else { |
197 | die "Unable to save state to file $statefile.\n";
|
197 | die "Unable to save state to file $statefile.\n"; |
198 | }
|
198 | } |
199 | } |
199 | } |
200 | }
|
200 | } |
201 |
|
201 | |
202 | # If no file was sent, send a form and any error messages.
|
202 | # If no file was sent, send a form and any error messages. |
203 | if (!$valid) {
|
203 | if (!$valid) { |
204 | print $q->header('text/html');
|
204 | print $q->header('text/html'); |
205 |
|
205 | |
206 | # Print header
|
206 | # Print header |
207 | print $q->start_html('Download your player file');
|
207 | print $q->start_html('Download your player file'); |
208 | print "\n\n\n";
|
208 | print "\n\n\n"; |
209 |
|
209 | |
210 | # print any error message that may have occured.
|
210 | # print any error message that may have occured. |
211 | if ($errormsg) {
|
211 | if ($errormsg) { |
212 | print $q->h3("ERROR: ". $errormsg), $q->br(), $q->br();
|
212 | print $q->h3("ERROR: ". $errormsg), $q->br(), $q->br(); |
213 | }
|
213 | } |
214 |
|
214 | |
215 | # Print warnings if $delete_player is enabled.
|
215 | # Print warnings if $delete_player is enabled. |
216 | if ($delete_player) {
|
216 | if ($delete_player) { |
217 | print <<'(END)';
|
217 | print <<'(END)'; |
218 | <pre><font color="#FF0000">WARNING:</font>
|
218 | <pre><font color="#FF0000">WARNING:</font> |
219 | Downloading your file will remove it from the server. If the
|
219 | Downloading your file will remove it from the server. If the |
220 | download fails, contact the system administrator, and they may
|
220 | download fails, contact the system administrator, and they may |
221 | be able to retrieve the file.
|
221 | be able to retrieve the file. |
222 | </pre>
|
222 | </pre> |
223 | (END)
|
223 | (END) |
224 | }
|
224 | } |
225 |
|
225 | |
226 | print $q->h2("Download your player file:");
|
226 | print $q->h2("Download your player file:"); |
227 |
|
227 | |
228 | # Print generic form to allow player to download their file.
|
228 | # Print generic form to allow player to download their file. |
229 | print $q->start_form(),
|
229 | print $q->start_form(), |
230 | 'Character name: ', $q->textfield('playername'), $q->br(),
|
230 | 'Character name: ', $q->textfield('playername'), $q->br(), |
231 | 'Character password: ' , $q->password_field('password'), $q->br(),
|
231 | 'Character password: ' , $q->password_field('password'), $q->br(), |
232 | $q->submit('Download'), $q->reset('Clear Entries'), $q->end_form();
|
232 | $q->submit('Download'), $q->reset('Clear Entries'), $q->end_form(); |
233 |
|
233 | |
234 | print $q->end_html();
|
234 | print $q->end_html(); |
235 | }
|
235 | } |