001  #!/usr/bin/perl
002  #It's already too late to do that check here, but at least try to print a
003  #warning
004  if ($> != $<)
005  {
006    print "Must not run with Set-UID bit set\n";
007    exit;
008  }
009  use English;
010  use Socket;
011  use Fcntl qw/O_RDWR O_CREAT O_NOFOLLOW/;
012  ($err_o_nofollow, $MY_O_NOFOLLOW) = Fcntl::constant("O_NOFOLLOW");
013  if ($err_o_nofollow ne "") { $MY_O_NOFOLLOW = 0; }
014  use POSIX qw(SEEK_SET SEEK_CUR SEEK_END);
015  sub bynum {$a <=> $b;}
016  sub childhnd {
017    $childs--;
018    wait();
019    return;
020  }
021  sub timeout {
022    exit(0);
023  }
024  sub dprint {
025    my($line)=@_;
026    unless ($SILENT)
027    {
028       print "$line";
029    }
030  }
031  if ($ARGV[0] =~ /^silent$/i) {$SILENT=1;}
032  $|=1;
033  #We need to be root at startup so we can bind to the ident port.
034  if ($< != 0)
035  {
036    print "Need to be root to bind to the ident port\n";
037    exit;
038  }
039  chdir("/") or die "Can not change working directory";
040  &dprint("\n***** Random Ident Server 0.9.3b *****\n");
041  #We first gather our words, this takes some time. If we want to drop privileges
042  #on restart, we would need to kill the old process before binding to the socket
043  #and droping privileges. For this reason we gather words before we drop privileges
044  #as doing so after would leave a large timegap for the service when restarted.
045  opendir(ISPELL,"/usr/lib/ispell")|| die "No /usr/lib/ispell dir found\n";
046  @files=readdir(ISPELL);
047  closedir(ISPELL);
048  $index=0;
049  while (($index <= $#files)&&(!$usefile))
050  {
051    $file=$files[$index];
052    # Note: As the hash filename is passed to /usr/bin/strings through a shell,
053    # we reject any filenames that begin with '-' (interpreted as an option by
054    # strings) or that contain a single quote (as we quote the filename in single
055    # quotes this would end the quoting).  Aside note: the regexp below does not
056    # allow the filename ".hash" either, but we do not care about this.
057    if ($file =~ /^[^-'][^']*\.hash$/)
058    {
059      $usefile=$file;
060    }
061    $index++;
062  }
063  unless ($usefile) {print "No hash file found in /usr/lib/ispell\n";exit;}
064  &dprint("* Using file /usr/lib/ispell/'$usefile'\n");
065  &dprint("* Counting usable words\n");
066  open(STR1,"/usr/bin/strings /usr/lib/ispell/'$usefile'|")||die "Can't start strings";
067  while(<STR1>)
068  {
069    chomp();
070    chomp();
071    $line=lc($_);
072    $len=length($line);
073    if (($line =~ /^[a-z]+$/)&&($len > 4) && ($len < 13))
074    {
075      $maxword++;
076      if (($maxword % 40) == 0)  { &dprint("\r  working: |  ($maxword)")};
077      if (($maxword % 40) == 10) { &dprint("\r  working: /  ($maxword)")};
078      if (($maxword % 40) == 20) { &dprint("\r  working: -  ($maxword)")};
079      if (($maxword % 40) == 30) { &dprint("\r  working: \\  ($maxword)")};
080    }
081  }
082  &dprint("\r                                               \r");
083  close(STR1);
084  $count=0;
085  $maxcount=1024+int(rand(2048));
086  &dprint("* Generating random selection of $maxcount from $maxword\n");
087  $setsize=$maxword;
088  @fetched=();
089  $fnum=0;
090  while($fnum < $maxcount)
091  {
092    $rnum=int(rand($setsize));
093    foreach $old (@fetched)
094    {
095      if ($old <= $rnum) {$rnum++;}
096    }
097    $continue=1;
098    while($continue)
099    {
100      $continue=0;
101      foreach $old (@fetched)
102      {
103         if ($old == $rnum)
104         {
105           $rnum++;
106           $continue=1;
107         }
108      }
109    }
110    $fetched[$fnum]=$rnum;
111    $fnum++;
112    $pnum=int(($fnum*$fnum*1000)/($maxcount*$maxcount))/10;
113    if (($fnum %8)==0) {&dprint("\r  working: |  ($pnum \%)");}
114    if (($fnum %8)==2) {&dprint("\r  working: /  ($pnum \%)");}
115    if (($fnum %8)==4) {&dprint("\r  working: -  ($pnum \%)");}
116    if (($fnum %8)==6) {&dprint("\r  working: \\  ($pnum \%)");}
117  }
118  &dprint("\r                                \r");
119  &dprint("* Generating insertion table\n");
120  foreach $index (0 .. $#fetched)
121  {
122    $inserttable{$fetched[$index]}=$index;
123  }
124  open(STR1,"/usr/bin/strings /usr/lib/ispell/'$usefile'|")||die "Can't start strings";
125  &dprint("* Acquiring names\n");
126  $count=0;
127  while(<STR1>)
128  {
129    chomp();
130    chomp();
131    $line=lc($_);
132    $len=length($line);
133    if (($line =~ /^[a-z]+$/)&&($len > 4) && ($len < 13))
134    {
135         if (defined($inserttable{$count}))
136         {
137           $words[$inserttable{$count}] =$line;
138         }
139         $count++;
140         $countp=int(1000*$count/$maxword)/10;
141         if (($count % 40) == 0)  { &dprint("\r  working: |  ($countp \%)")};
142         if (($count % 40) == 10) { &dprint("\r  working: /  ($countp \%)")};
143         if (($count % 40) == 20) { &dprint("\r  working: -  ($countp \%)")};
144         if (($count % 40) == 30) { &dprint("\r  working: \\  ($countp \%)")};
145    }
146  }
147  &dprint("\r                                 \r");
148  &dprint("* $maxcount words fetched\n");
149  &dprint("  (local ports belonging to each unique modulus of $maxcount get\n");
150  &dprint("   a dictionary word assigned to them)\n");
151
152  ($pnam,$dummy,$myuid,$mygid)=getpwnam("nobody");
153  unless (($myuid) && ($mygid))
154  {
155    print "No user nobody defined\n";
156    exit;
157  }
158  #Just in case  O_NOFOLLOW is not supported we double check.
159  if (-l "/tmp/rident.pid") {
160     print "ERROR: It apears someone has tampered with the pid file /tmp/rident.pid (test1)\n";
161     exit;
162  } elsif (sysopen(PID,"/tmp/rident.pid",O_RDONLY | $MY_O_NOFOLLOW)) {
163    $oldpid = 0;
164    ($pfuid,$pfgid) = (stat(PID))[4,5] or die "PANIC: Can not stat open pidfile";
165    unless ($pfuid == $myuid || $pfgid == $mygid)
166    {
167      print "ERROR: It apears someone has tampered with the pid file /tmp/rident.pid (test2)\n";
168      exit;
169    } else {
170      #See if the file was already written.
171      seek(PID,0,SEEK_END);
172      $size=tell(PID);
173      seek(PID,0,SEEK_SET);
174      #If it was read the old pid.
175      if ($size > 0 && $size < 100) {
176  	  &dprint("* PID file found\n");
177  	  sysread(PID,$oldpid,$size,0);
178  	  chomp $oldpid;
179  	  unless ($oldpid =~ /^\d+$/)
180  	  {
181  	    print "ERROR: It apears someone has tampered with the pid file /tmp/rident.pid (test3)\n";
182  	    exit;
183  	  }
184  	  $oldpid +=0;
185      } else {
186  	  &dprint("* PID file is corrupt\n");
187  	  exit;
188      }
189    }
190    close(PID);
191    #Now let's test if the found pid is really our old instance.
192    $killit=0;
193    if ($oldpid)
194    {
195      &dprint("* Checking for process with pid $oldpid\n");
196      open(PS,"/bin/ps -p '$oldpid'|");
197      $killit=0;
198      while (<PS>)
199      {
200         if (/\bridentd\.pl\b/)
201         {
202  	 $killit=$oldpid;
203         }
204      }
205      unless ($killit)
206      {
207        &dprint("* pid file apears to be old\n");
208      } else {
209        #We need to kill the old process, but we are root and thus to powerfull.
210        $tpid=fork();
211        unless (defined($tpid)) {
212  	print "ERR: Forking error\n";
213  	exit;
214        }
215        if ($tpid) {
216  	#The parent remains root and must wait for the child process.
217  	&dprint("* Started background process to kill old instance safely\n");
218  	waitpid($tpid,0);
219  	&dprint("* Background process finished.\n");
220  	sleep(1);
221        } else {
222  	unless ($)=$mygid) {print "Cleanup Unable to set group ID to $mygid\n";exit;}
223  	unless ($>=$myuid) {print "Cleanup Unable to set user ID to $myuid\n";exit;}
224  	kill(9,$killit);
225  	exit;
226        }
227      }
228    }
229  }
230  &dprint("* Binding to port 113\n");
231  $port=113;
232  $proto=getprotobyname('tcp');
233  socket(Server,PF_INET,SOCK_STREAM,$proto)|| die "socket $!";
234  setsockopt(Server,SOL_SOCKET,SO_REUSEADDR,pack("l",1))|| die "setsockopt $!";
235  bind(Server,sockaddr_in($port,INADDR_ANY))|| die "bind: $!";
236  listen(Server,SOMAXCON)||die "listen: $!";
237  unlink("/tmp/rident.pid");
238  &dprint("* Setting uid/gid to nobody $myuid/$mygid\n");
239  unless ($)=$mygid) { print "Unable to set group ID to $mygid\n"; exit; }
240  unless ($>=$myuid) { print "Unable to set user ID to $myuid\n"; exit; }
241  unless (sysopen(PID,"/tmp/rident.pid",O_RDWR | O_CREAT | O_EXCL, 0600)) {
242    print "PANIC: Could not create the pid file /tmp/rident.pid $! $?\n";
243    exit;
244  }
245  &dprint("* Forking to background\n");
246  $pid=fork();
247  unless (defined($pid)) {
248    print "ERR: Forking error\n";
249    close(PID);
250    exit;
251  }
252  if ($pid)
253  {
254    # parent
255    unless (syswrite(PID,"$pid\n",length("$pid\n"))) {
256      print "PANIC: Could not write to the pid file /tmp/rident.pid $! $?\n";
257      close(PID);
258      kill(9,$pid);
259    }
260    exit;
261  }
262  close(PID);
263  &dprint("* BG Process active\n");
264  &dprint("* Checking whether we can do socketpair lookups : ");
265  if (-r "/proc/net/tcp") {
266    &dprint("Yep: LINUX\n");
267    $LOOKUP="LINUX";
268  }
269  else {
270    &dprint("NO !!!\n");
271    $LOOKUP=0;
272  }
273  $childs=0;
274  $SIG{'CHLD'}='childhnd';
275  while(1) {
276   if ($paddr = accept(Client,Server))
277   {
278     ($remoteport,$remoteip)=sockaddr_in($paddr);
279     $remoteip=unpack("L",$remoteip);
280     #Limmited forking and extra sleeping, better for the service than the system to be dossed
281     if ($childs < 10)
282     {
283     $pid=fork();
284     unless (defined($pid)) {
285       print "ERR: Forking error\n";
286       exit;
287     }
288     if ($pid==0)
289     {
290       select Client;$|=1;select(STDOUT);
291       $SIG{"ALARM"}='timeout';
292       alarm(10);
293       $firstline=<Client>;
294       if ($firstline =~ /(\d+)\s*,\s*(\d*)/)
295       {
296          $port=$1;
297          $port2=$2;
298          $found=1;
299          #If there is no portablility for the lookup just skip it
300          if ($LOOKUP eq "LINUX")
301          {
302             $found=0;
303             $rip=sprintf("%lX",$remoteip);
304  	   while (length($rip) < 8) {$rip = "0$rip";}
305             $rprt=sprintf("%X",$port2);
306  	   while (length($rprt) < 4) {$rprt = "0$rprt";}
307  	   $lprt=sprintf("%X",$port);
308  	   while (length($lprt) < 4) {$lprt = "0$lprt";}
309             open(TCPFIL,"/proc/net/tcp");
310             while(<TCPFIL>)
311             {
312               if (/:$lprt\s+${rip}:$rprt\s+/)
313               {
314                 $found=1;
315               }
316             }
317          }
318          if ($found)
319          {
320            $word=$words[$port % $maxcount];
321            print Client "${port},${port2}:USERID:OTHER:$word\n";
322          }
323          else
324          {
325            print Client "${port},${port2}:ERROR:NO-USER\n";
326          }
327       }
328       close(Client);
329       sleep(2);
330       exit;
331     }
332     else
333     {
334       $childs++;
335       close(Client);
336     }
337     }
338     else
339     {
340       print Client "0,0:ERROR:UNKNOWN-ERROR\n";
341       close(Client);
342     }
343   }
344  }
