Commit | Line | Data |
---|---|---|
6855525e JL |
1 | #!/usr/bin/perl |
2 | # | |
3 | # Custom wrapper around the CVS pserver for Diogenes | |
4 | # | |
5 | # Copyright 2003-2004 Jeremy Lainé | |
6 | use strict; | |
7 | use IPC::Open2; | |
8 | use Socket; | |
9 | use Fcntl qw(:DEFAULT :flock); | |
10 | use Getopt::Std; | |
11 | use POSIX qw(strftime waitpid WNOHANG WIFEXITED); | |
12 | ||
13 | my $package = "cvs.pl"; | |
14 | my $version = "0.3"; | |
15 | my %opts; | |
16 | my $daemon; | |
17 | ||
18 | ||
19 | # get command-line arguments | |
20 | sub init { | |
21 | if ( not getopts('dhp:r:fms:', \%opts) or $opts{'h'}) { | |
22 | &syntax(); | |
23 | return 1; | |
24 | } | |
25 | ||
26 | # check we have a valid port | |
27 | if ($opts{'p'} !~ /^[0-9]+$/) { | |
28 | print("Error : no port or invalid port specified!\n"); | |
29 | &syntax; | |
30 | return 1; | |
31 | } | |
32 | ||
33 | # check we have a cvsroot | |
34 | if (!$opts{'r'}) { | |
35 | print("Error : no CVS repository was specified!\n"); | |
36 | &syntax; | |
37 | return 1; | |
38 | } | |
39 | $opts{'r'} =~ s/^(.*)\/$/$1/; | |
40 | if (! -d "$opts{'r'}/CVSROOT") { | |
41 | print("Error : no CVS repository found at $opts{'r'}\n"); | |
42 | return 1; | |
43 | } | |
44 | ||
45 | # check we have a valid suicide delay | |
46 | if ($opts{'s'} !~ /^[0-9]*$/) { | |
47 | print("Error : invalid suicide delay!\n"); | |
48 | &syntax; | |
49 | return 1; | |
50 | } | |
51 | ||
52 | return 0; | |
53 | } | |
54 | ||
55 | ||
56 | # debugging information | |
57 | sub debug() | |
58 | { | |
59 | if ($opts{'d'}) { | |
60 | &log(@_); | |
61 | } | |
62 | } | |
63 | ||
64 | ||
65 | # add a log entry | |
66 | sub log() | |
67 | { | |
68 | my $msg = shift; | |
69 | if (open(LOG,">> $opts{'r'}/CVSROOT/serverlog")) | |
70 | { | |
71 | my $hdr = strftime("%a %b %e %H:%M:%S",gmtime)." [$daemon]"; | |
72 | if ($$ != $daemon) { | |
73 | $hdr .= "[$$]"; | |
74 | } | |
75 | print LOG "$hdr $msg\n"; | |
76 | close LOG; | |
77 | } | |
78 | } | |
79 | ||
80 | ||
81 | # add a user to the passwd file | |
82 | sub pwdUser() | |
83 | { | |
84 | my $user = shift; | |
85 | my $pwfile = $opts{'r'}."/CVSROOT/passwd"; | |
86 | ||
87 | # read the password file, strip out current user | |
88 | my @lines; | |
89 | if (open(FH,"< $pwfile")) { | |
90 | @lines = <FH>; | |
91 | @lines = grep !/^$user(:.*)?(:*)?$/,@lines; | |
92 | close(FH); | |
93 | } | |
94 | ||
95 | # add user to password file | |
96 | my @pwuid = getpwuid($<); | |
97 | push @lines, $user . "::" . $pwuid[0] . "\n"; | |
98 | sysopen(FH, $pwfile, O_WRONLY | O_CREAT) | |
99 | or die("Can't open passwd file!"); | |
100 | flock(FH, LOCK_EX) | |
101 | or die("Can't lock passwd file!"); | |
102 | truncate(FH,0); | |
103 | print FH @lines; | |
104 | close(FH); | |
105 | #die; | |
106 | } | |
107 | ||
108 | ||
109 | # the main loop of the server | |
110 | sub serve() | |
111 | { | |
112 | # make the socket | |
113 | socket(Server, PF_INET, SOCK_STREAM, getprotobyname('tcp')); | |
114 | ||
115 | # so we can restart our server quickly | |
116 | setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, 1); | |
117 | ||
118 | # build socket address | |
119 | my $addr = sockaddr_in($opts{'p'}, 127.0.0.1); | |
120 | ||
121 | # bind and start listening | |
122 | if (!bind(Server, $addr) or !listen(Server,SOMAXCONN)) { | |
123 | print "Error : could not bind and listen to port $opts{'p'}!\n"; | |
124 | close Server; | |
125 | exit(2); | |
126 | } | |
127 | ||
128 | ||
129 | # if necessary, fork to background | |
130 | if ($opts{'f'} && fork) { | |
131 | # close parent | |
132 | exit; | |
133 | } | |
134 | ||
135 | # store the PID of the parent process | |
136 | $daemon = $$; | |
137 | ||
138 | # select single-shot or full server | |
139 | if ($opts{'m'}) { | |
140 | &log("forked daemon bound"); | |
141 | ||
142 | my $children = 0; | |
143 | # set up zombie reaper and suicide timer | |
144 | $SIG{CHLD} = sub { | |
145 | while (waitpid(-1,&WNOHANG) != -1) { | |
146 | &WIFEXITED($?) and $children--; | |
147 | } | |
148 | ||
149 | # if he have no more children, start suicide timer | |
150 | if (($$ == $daemon) && ($children == 0)) { | |
151 | alarm $opts{s}; | |
152 | } | |
153 | }; | |
154 | ||
155 | $SIG{ALRM} = sub { | |
156 | &log("reached inactivity limit of $opts{s} seconds, returning"); | |
157 | exit; | |
158 | }; | |
159 | ||
160 | # if set, start suicide timer | |
161 | alarm $opts{s}; | |
162 | ||
163 | # full-blown server that forks for each request | |
164 | for (my $conn = 0;; $conn++) { | |
165 | # we can get interrupted system calls | |
166 | # ignore these and loop back | |
167 | if (!accept(Client,Server)) { | |
168 | next; | |
169 | } | |
170 | ||
171 | # stop inactivity timer | |
172 | alarm 0; | |
173 | ||
174 | &debug("forking child to handle request"); | |
175 | ||
176 | if (my $cpid = fork) { | |
177 | ||
178 | # parent process, closes unused handle | |
179 | $children++; | |
180 | close Client; | |
181 | ||
182 | } elsif (defined $cpid) { | |
183 | ||
184 | # child process | |
185 | close Server; | |
186 | &serveClient(\*Client); | |
187 | close Client; | |
188 | exit; | |
189 | ||
190 | } else { | |
191 | print "Could not fork serveClient!"; | |
192 | exit(2); | |
193 | } | |
194 | } | |
195 | close Server; | |
196 | ||
197 | } else { | |
198 | ||
199 | &log("single-request daemon bound"); | |
200 | # accepts a single connection, then returns | |
201 | accept(Client,Server); | |
202 | close Server; | |
203 | &serveClient(\*Client); | |
204 | ||
205 | } | |
206 | ||
207 | &log("returning"); | |
208 | } | |
209 | ||
210 | ||
211 | # wraps around an instance of "cvs pserver" | |
212 | sub serveClient() | |
213 | { | |
214 | my $client = shift; | |
215 | my $cvsroot = $opts{'r'}; | |
216 | ||
217 | # begin auth | |
218 | chomp(my $in = <$client>); | |
219 | if ($in !~ /^BEGIN AUTH REQUEST$/) { | |
220 | &log("client did not send BEGIN AUTH REQUEST, returning!"); | |
221 | close $client; | |
222 | return; | |
223 | } | |
224 | chomp($in = <$client>); | |
225 | if ($in !~ /^$cvsroot$/) { | |
226 | close $client; | |
227 | return; | |
228 | } | |
229 | # user name | |
230 | chomp(my $user = <$client>); | |
231 | ||
232 | # password, discarded | |
233 | <$client>; | |
234 | # end auth | |
235 | chomp($in = <$client>); | |
236 | if ($in !~ /^END AUTH REQUEST$/) { | |
237 | close $client; | |
238 | return; | |
239 | } | |
240 | ||
241 | # add the user to the passwd file | |
242 | &log("authenticated $user"); | |
243 | &pwdUser($user); | |
244 | ||
245 | ||
246 | select($client); | |
247 | $| = 1; | |
248 | ||
249 | ||
250 | local(*Reader,*Writer); | |
251 | ||
252 | # create bidirectional pipe | |
253 | if ( !open2(\*Reader, \*Writer, "cvs -f --allow-root $cvsroot pserver") ) { | |
254 | close $client; | |
255 | return; | |
256 | } | |
257 | ||
258 | print Writer "BEGIN AUTH REQUEST\n$cvsroot\n$user\nA\nEND AUTH REQUEST\n"; | |
259 | ||
260 | # process input and output | |
261 | if (my $pid = fork) { | |
262 | close Reader; | |
263 | ||
264 | # process client input | |
265 | while (my $inn = <$client>) | |
266 | { | |
267 | print Writer $inn; | |
268 | } | |
269 | ||
270 | close Client; | |
271 | close Writer; | |
272 | ||
273 | } elsif (defined $pid) { | |
274 | close Writer; | |
275 | ||
276 | # feed output back to client | |
277 | while (my $out = <Reader>) | |
278 | { | |
279 | print $client $out; | |
280 | } | |
281 | ||
282 | close Reader; | |
283 | close Client; | |
284 | exit; | |
285 | ||
286 | } else { | |
287 | die("Could not fork!"); | |
288 | } | |
289 | ||
290 | } | |
291 | ||
292 | ||
293 | # display program syntax | |
294 | sub syntax { | |
295 | print "\n", | |
296 | "[ This is $package (v$version), Diogenes's wrapper around the CVS pserver ]\n\n", | |
297 | "Syntax:\n", | |
298 | " $package (-h | -p <port> -r <cvsroot>) [options]\n\n", | |
299 | " Help:\n", | |
300 | " -h - display this help message\n", | |
301 | " Required:\n", | |
302 | " -p <port> - listen on port <port>\n", | |
303 | " -r <cvsroot> - use the local CVS repository <cvsroot>\n", | |
304 | " Options:\n", | |
305 | " -d - debugging mode\n", | |
306 | " -f - fork to background\n", | |
307 | " -m - serve multiple client requests instead of dying after first client\n", | |
308 | " -s <seconds> - suicide after <seconds> seconds of inactivity\n"; | |
309 | } | |
310 | ||
311 | ||
312 | # execute program | |
313 | if (&init()) { | |
314 | exit(1); | |
315 | } else { | |
316 | &serve(); | |
317 | exit(0); | |
318 | } |