#!/usr/bin/perl -w # LiveJournal Remote Procedure Call Daemon (ljrpcd) # Copyright 2001 Dormando (dormando@rydia.net) # Going add a license (GPL?) later. # This daemon forks off into the background, logs if told to # waits for UDP messages on the given port, and executes # commands sent to it. # # # lib: IO::Socket # prog: bin/ljmaint.pl # use strict; use IO::Socket; # Max message length and port to bind. my $MAXLEN = 512; my $PORTNO = 6100; my $PIDFILE = '/var/run/ljrpcd.pid'; my $LOGFILE = '/var/log/ljrpcd.log'; my $TYPE = shift @ARGV; unless ($TYPE eq "web" || $TYPE eq "db") { print "Unknown/unspecified machine type, quitting.\n"; exit 1; } # Pid and pidfile. my $pid; my $is_parent = 1; # Socket. Needs to be here for the HUP stuff. my $sock; # In case we're shot, unlink the pidfile. $SIG{TERM} = sub { unlink($PIDFILE); exit 1; }; if (-e $PIDFILE) { open (PID, $PIDFILE); my $tpid; chomp ($tpid = ); close PID; if ($tpid) { # so Linux-specific, but Proc::ProcessTable #iterates over /dev forever, which sucks on a NetApp if (open (CMD, "/proc/$tpid/cmdline")) { my $cmdline = ; close CMD; if ($cmdline =~ /ljrpcd/) { print "Process exists already, quitting.\n"; exit 1; } } } } # Print a banner. print "LiveJournal RPC Daemon starting up into the background...\n"; # Perhaps I should give it a command to not do this in the future. if ($pid = fork) { # Parent, log pid and exit. open(PID, ">$PIDFILE") or die "Couldn't open $PIDFILE for writing: $!\n"; print PID $pid; close(PID); print "Closing ($pid) wrote to $PIDFILE\n"; $is_parent = 1; exit; } else { # This is the child, main loop-de-doo. my($cmdmsg, $remaddr, $remhost); # HUP signal handler. $SIG{HUP} = \&restart_request; open(LOG, ">>$LOGFILE") or die "Couldn't open log file for appending: $!\n"; flock(LOG, 2) or die "Couldn't flock log file for writing: $!\n"; select(LOG); # Why the hell not, eh? $| = 1; $sock = IO::Socket::INET->new(LocalPort => "$PORTNO", Proto => 'udp') or die "socket: $@"; $sock->sockopt(SO_BROADCAST, 1); print "Bound, awaiting UDP commands on port $PORTNO\n"; # Main loop, simple parser. while ($sock->recv($cmdmsg, $MAXLEN)) { my ($port, $ipaddr) = sockaddr_in($sock->peername); my $ip_addy = inet_ntoa($ipaddr); $remhost = gethostbyaddr($ipaddr, AF_INET); print "Client $remhost sent command: $cmdmsg\n"; # If the command is 'marco' return 'pollo' if ($cmdmsg =~ /^marco(\s+($TYPE|all))?$/) { print "Returning 'pollo' to client $remhost\n"; $sock->send("pollo") or print "Couldn't scream pollo at $remhost\n"; next; } elsif ($cmdmsg =~ s/^cmd:\s//) { # Handle the command. if ($cmdmsg eq "restart") { print "Restarting ljrpcd\n"; $sock->send("Restarting ljrpcd...\n"); restart_request(); } my $return = handle_request($cmdmsg); print "Returning $return to client $remhost\n"; $sock->send($return) or print "Couldn't return $return to $remhost\n"; next; } } die "recv: $!\n"; } #if # Sub to restart the daemon. sub restart_request { $sock->close; unlink($PIDFILE); exec($0, $TYPE); } # Handle a request... Do further parsing, pass it appropriately. # Should return a discernable ok. Usually 'done' 'ok' or an # informative reply no longer than 500 characters. sub handle_request { my $cmd = shift; my $su = ""; if ($cmd =~ s/^(\w+?)://) { my $user = $1; my ($login,$pass,$uid,$gid) = getpwnam($user) or return "$user not in passwd file."; $su = "su $user -c"; } unless ($cmd =~ /[\|\>\<]/) { my $home = $ENV{'LJHOME'} || "/home/lj"; $cmd =~ s/[;]/\\$&/; if ($su) { return `$su \'$home/bin/ljmaint.pl $cmd\'`; } else { return `$home/bin/ljmaint.pl $cmd`; } } return 'bad command'; }