more plugin fixups
[diogenes.git] / cvs.pl
CommitLineData
6855525e
JL
1#!/usr/bin/perl
2#
3# Custom wrapper around the CVS pserver for Diogenes
4#
5# Copyright 2003-2004 Jeremy Lainé
6use strict;
7use IPC::Open2;
8use Socket;
9use Fcntl qw(:DEFAULT :flock);
10use Getopt::Std;
11use POSIX qw(strftime waitpid WNOHANG WIFEXITED);
12
13my $package = "cvs.pl";
14my $version = "0.3";
15my %opts;
16my $daemon;
17
18
19# get command-line arguments
20sub 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
57sub debug()
58{
59 if ($opts{'d'}) {
60 &log(@_);
61 }
62}
63
64
65# add a log entry
66sub 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
82sub 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
110sub 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"
212sub 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
294sub 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
313if (&init()) {
314 exit(1);
315} else {
316 &serve();
317 exit(0);
318}