/*
   sfingerd.c :

   Smallest/Safest fingerd possible [if you pay attention to setup]
   maybe not rfc compliant

   Free software - GNU like license
   by Laurent Demailly - dl - <dl@hplyot.obspm.fr>.
   Last important change to this source file June 9th, 1994,
   current package version : v 1.8.3

   *IMPORTANT* :
   run it through INETD, needs ROOT 'cause of CHROOT
   DEFINE CHROOT_PATH and put the info files you want to make
   avaliable THERE, NOTHING MORE.
   ie to make info available for "finger <user>@this.host"
   put a file named <user> in the CHROOT_PATH directory
   for the the -l output create a file "W<user>" (or ln [-s] <user> W<user>)
   (right, its an ugly kludge, but its *fast* (and, I think, safe))
   ** see the support script same dir for automatic generation of these files

   [it *could* work and be safe without chroot() (thanks to the char filtering
    in openend file name but one never knows...]

   DEFINE NO_PRIV_UID & NO_PRIV_GID to some non priviledged uid
   that can anyway read into CHROOT_PATH

   if you are on a system that have no strerror please define NO_STRERROR
   (SunOS for instance)

   Go on reading below :
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <syslog.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

/* very important : changes theses to your site... */
#define CHROOT_PATH "/usr/local/etc/fingerdroot"
#define NO_PRIV_UID 32767
#define NO_PRIV_GID 32766

/* define NO_STRERROR when missing in system libs */

#ifdef NO_STRERROR
char *strerror(err_no)
     int err_no;
{extern char *sys_errlist[]; return sys_errlist[err_no];}
#endif

/* CHANGE the 3 strings to be customized for your site here : */

/* thats the generic reply for 'finger @your.site' (stdard give a list of
   your connected users, which is unsafe) - customize like you want : 
   (for instance I add a line telling ppl to use a WWW browser instead) */
/* if you want to show a file (slightly slower, but you can give more infos)
   instead of strReplyGen #define below : */
#undef FILEREPLYGEN
#ifdef FILEREPLYGEN
/* file name */
char *fileReplyGen="finger.default";
#else
/* or string value : */
char *strReplyGen="sorry, generic fingerd is not running on this host.\ntry finger <username>@... for user specific informations";
#endif
/* thats the reply for 'finger xxx@your.site' when the xxx is not found
   customize like you want : */
char *strReplyNotFound="no finger information available for your query on this host.";
/* thats the 'header' added to all successfull replies, you can either put 
   NULL or a string, for instance to warn that its not a conventional 
   fingerd, and/or that answers correspond to user changeable files,etc...
   ( J Bern's idea - thx ) */
/* char *strReplyHeader=NULL;  /* for no special header */
char *strReplyHeader="=== Warning : This site runs sfingerd-1.8 (secure fingerd), the only ===\n=== information available for your query is :                        ===\n"; /* for a special header */

/* feel free to change to anyother facility  : */
#define LOGFACILITY LOG_DAEMON

/***** no more changes should be needed below ******/

/* auxiliary support function str_remove(result,from,what) : */
/* remove all occurences of 'what', in 'from', place result in 'result' */
/* result & from must be already allocated, result must be writable... */
/* works fine with 'result'=='from' */
#define REMOVE_INVIS  /*also auto-wipe weird chars...*/
void str_remove(result,from,what)
     char *result,*from,*what;
{
  char *pw,c;
  int flag;
  for (;(c=*from++);) {
#ifdef REMOVE_INVIS
    flag=(c>=' ' && c<127);
#else
    flag=1;
#endif    
    for (pw=what; flag && (*pw);) flag= (c!=(*pw++)) ;
    if (flag) *result++=c;
  }
  *result=0;
}

char scsid[]="@(#) $Author: sfingerd 1.8 - by Laurent Demailly - dl@hplyot.obspm.fr $";

int main()
{
  int i;
  struct hostent *hp;
  char buf[128];
  struct sockaddr_in peer;
  char *name;
  FILE *fic;
  
  openlog("fingerd",LOG_NDELAY,LOGFACILITY); 

  i = sizeof( peer );
  if ( getpeername( 0, (struct sockaddr *)&peer, &i) ) {
    syslog(LOG_WARNING,"getpeername: %s",strerror(errno));
    exit(1);
    };
  hp = gethostbyaddr( (char *)&peer.sin_addr, sizeof(peer.sin_addr), AF_INET);
  if( hp == NULL)
    name = "-no name-";
  else
    name = hp->h_name;
  if (nice(10)==-1) {
    syslog(LOG_ERR,"error in nice: %s",strerror(errno));
    exit(1);
    }
  buf[0]=0;
  alarm(10); /* timeout */
  if (!fgets(buf,sizeof(buf),stdin)) {
    syslog(LOG_WARNING,"%s [%s] : fgets: %s",  
	   inet_ntoa( peer.sin_addr ),
	   name,strerror(errno));
    exit(1);
  }
#ifdef REMOVE_INVIS
  str_remove(buf,buf,".,-/#~ ");
#else
  str_remove(buf,buf,".,-/#~ \n\r");
#endif
  syslog(LOG_NOTICE,"%s [%s] : '%s'",inet_ntoa( peer.sin_addr ),name,buf);
/*  not needed, the chdir / after chroot is enough : (maybe do this only
    instead of the chroot is safe too (thx to the filtering)
  if (chdir(CHROOT_PATH)) {
    syslog(LOG_ERR,"error in chdir: %s: %s",CHROOT_PATH,strerror(errno));
    exit(1);
    } 
*/
  if (chroot(CHROOT_PATH)) {
    syslog(LOG_ERR,"error in chroot: %s: %s",CHROOT_PATH,strerror(errno));
    exit(1);
    }
  if (chdir("/")) {
    syslog(LOG_ERR,"error in chdir / : %s",strerror(errno));
    exit(1);
    }
  if (setgid(NO_PRIV_GID)==-1) {
    syslog(LOG_ERR,"error in setgid: %s",strerror(errno));
    exit(1);
    }
  if (setuid(NO_PRIV_UID)==-1) {
    syslog(LOG_ERR,"error in setuid: %s",strerror(errno));
    exit(1);
    }
  closelog();
  sleep(4);
  i=strlen(buf);
  if (i==0||(i==1 && *buf=='W')) 
    {
#ifdef FILEREPLYGEN
      strcpy(buf,fileReplyGen);
#else      
      puts(strReplyGen);
      exit(0);
#endif
    }
  if (!(fic=fopen(buf,"r"))) 
    {
      printf(strReplyNotFound);
      exit(0);
    }
  if (strReplyHeader)
    puts(strReplyHeader);
  i=sizeof(buf);
  while (fgets(buf,i,fic))
      fputs(buf,stdout);
  exit(0);
}
