From: kibirev@csa.bu.edu (oleg kibirev)
Subject: Debugging program on mailing machine
Organization: Computer Science Department, Boston University, Boston, MA, USA
Originator: kibirev@csa
Date: Sat, 25 Jul 1992 01:23:55 GMT
X-Posted-From: csa.bu.edu
X-Posted-Through: sol.ctr.columbia.edu


As I already mentioned in some ObHack, I am writing a program to allow access
to Internet host (shell, maybe even line editor like ed(1)) via e-mail only
(if you have nothing else). Anyway, the problem was that we have centrilized
mailing host that delivers letters through NFS. It's incompatible with my host
and can't execute programs compiled here. And, I can't login to mailing machine
directly (/bin/nologin).

I managed to compile a program for correct CPU by having sendmail run a shell
script that does it. But, alas, gdb can't be used this way. I can't imagine my
life w/o gdb, so here is a solution: I wrote a daemon that recieves all mail and
looks for a special subject. It then accepts your connection to a well-known 
port and starts session similar to rsh but with tty on remote host (so that I can
^Z and ^C things). 

Not much fun? Well, you can do some interesting (but harmless) things with this 
program (Shhh... I didn't say I'll approve this, but for your information only :) )
 
 * If you have a cluster of hosts connected with [N,A]FS but with no centrilized
mail delivery, you can use mhook @hostname to explore all of them this way.

 * mhookd niether makes an entry in /etc/[u,w]tmp nor chowns tty (I wish I could...).
So, you can use it for stealth logins (last login time is not modified either).

Programs below may need to be hacked a bit to work under UNIXes other than SunOS.

Happy hacking,
      							Oleg Kibirev
---------mhookd.c-------------------------------------------------------

/*************************************************************************************** 
 * Connection daemon. This program scans incoming  mail for subject "MailHook-Session" *
 * (case-sensative). When found, it triggers by binding a socket to port 7815 of local *
 * host and  accepting  connection  from mhook. It then  opens a pseudo-terminal, sets *
 * its parameters to match peers tty and runs user's login shell, passing it data from *
 * the socket.									       *
 *										       *
 * To activate, create the following ~/.forward on target host:			       *
 * \yourname,"|/your/home/dir/mhookd"						       *	 
 *                                                                                     *
 * Compiling:                                                                          *
 * cc -o mhookd -O mhookd.c                                                            *
 * strip mhookd                                                                        *
 *                                                                                     *
 * If you can't directly  login  to target  host, but can put .forward to it  (through *
 * NFS), you can place these  lines  in a  shell  script  and have  sendmail on target *
 * pipe the letter through this script.					 	       *
 ***************************************************************************************/

 	char copyright[]="@(#) Copyleft 1992 Oleg Kibirev.\n";
	static char sccsid[]="@(#)mhookd.c 1.0 07/24/92";

#	include "stdio.h"
#	include "sys/types.h"
#	include "sys/socket.h"
#	include "sys/dir.h"
#	include "netinet/in.h"
#	include "string.h"
#	include "sys/time.h"
#	include "sys/resource.h"
#	include "sys/stat.h"
#	include "sys/ioctl.h"
#	include "fcntl.h"
#	include "signal.h"
#	include "netdb.h"
#	include "sys/wait.h"
#	include "pwd.h"
#	include "unistd.h"

	char *crypt(); /* Surprisingly, not found in SunOS 4.1 include files */

#	define ltrim(s) (s+strspn(s," \t\b\a\r\n")) 

	static char from[128];
	static char buf[1024];
	static int kill0,kill1;

	extern int errno;
	extern char *sys_errlist[];
	extern int sys_nerr;

	int chkpty(pty) /* Selects files for dir. list function */
	  struct direct *pty;
	  {
	  return(!strncmp(pty->d_name,"pty",3));
	  }

/*	The most annoying problem to debug was processes hanging around after
	shell exits. Before long, I have figured out that read on a hang
	socket will wait forever rather than sending SIGPIPE or at least
	returning an error. So will read on a tty with closed pty counterpart.
	Time for (really) brute force approach: make sure that when a process
	dies, it takes relitives with it. 
*/

        void die()
	  {
	  if(kill0)
            kill(kill0,SIGHUP); /* Soft kill-let an editor save text, etc. */
	  	  
          if(kill1)
	    kill(kill1,SIGHUP);

	  exit(0);
	  }

/*	Since we run detached, there is nobody to witness perror(). Here is a
	replacement that sends mail back to originator.
*/

	void p_error(msg)
	  char *msg;
	  {
	  static char letter[]="Subject: MailHook: %s: %s\nTo: %s\n.\n";
	  static int fd[2];	  
	  int pid;
	  
	  if(!*from) /* We haven't figured out who is calling us yet... */
	    die();
	  
	  sprintf(buf,letter,msg,(errno<=sys_nerr)?sys_errlist[errno]:"Unknown error",from);
	  if(pipe(fd) || (pid=fork())<0)
	    die(); /* Nothing else can be done to let them know */

	  if(pid) 
	    {
	    write(fd[1],buf,strlen(buf)); /* Pipe message to sendmail */
	    close(fd[1]); /* This should force EOF on read */
	    wait(NULL);
	    }
	  else
	    {
	    dup2(fd[0],0);
	    dup2(open("/dev/null",O_WRONLY),1); /* Don't really care if this fails */
	    dup2(1,2);
	    execl("/usr/lib/sendmail","/usr/lib/sendmail","-t",NULL); /* Tell sendmail that target is in "To:" line */
	    }
	  die();
	  }
	    
	main(argc,argv)
	  int argc;
	  char **argv;
	  {
	  int true=1,i,s,master,slave,death=0;
	  char *p;
	  static struct rlimit rlp;
	  static struct sockaddr_in myname={AF_INET,7815};
	  static struct winsize win;
	  struct passwd *user;
	  struct direct **ptys;

	  while(fgets(buf,1023,stdin))
	    {
	    p=ltrim(buf);
	    if(!*p)
	      break; 
	      
	    if(!strncasecmp(p,"From:",5))
	      {
	      strncpy(from,ltrim(p+5),127);
	      continue;
	      }

	    if(strncasecmp(p,"Subject:",8))
	      continue;
	    if(!strncmp(p=ltrim(p+8),"MailHook-Session",16))
	      goto run;
	    
	    /* Sending mail with Subject: MailHook-Cleanup is a quick way to
	       get rid of all processes on target host. Helps a lot when your
	       pgm fork()s the system */

	    if(!strncmp(p,"MailHook-Clean",14))
	      {
	      death=1;
	      goto run;
	      }

	    break;
	    }
	  exit(0);
  
  	
run:;
#ifdef xyzzy
	  if(fork()) /* Let sendmail go */
	    exit(0);
#endif        
          signal(SIGPIPE,die); /* redefine deadly signals to kill all mhookd's, not */
	  signal(SIGSEGV,die); /* just one that triggered it */
	  signal(SIGBUS,die);

	  signal(SIGXCPU,SIG_IGN); 
	  signal(SIGQUIT,SIG_IGN);
	  signal(SIGTSTP,SIG_IGN); /* No good for us to get stopped */
	  signal(SIGTTOU,SIG_IGN);

	  umask(022); /* Don't make files world-writable ... */
	  getrlimit(RLIMIT_CORE,&rlp);
	  rlp.rlim_cur=0;
	  setrlimit(RLIMIT_CORE,&rlp); /* ... or dump core if we shall be murdered */
	  while(*argv) 
	    *(argv++)=NULL;

          close(0);
	  if((s=socket(PF_INET,SOCK_STREAM,0))<0)
	    p_error("socket()");

	  gethostname(buf,1024);

	  /* I wonder how compatible would be just using localhost (127.0.0.1) */
	  (myname.sin_addr).s_addr=**(unsigned long **)(gethostbyname(buf)->h_addr_list);
	  if(bind(s,&myname,sizeof(myname)))
	    p_error("bind()");

	  if(listen(s,1))
	    p_error("listen()");

	  /* Won't wait forever for them to answer. The net/mhook could get hang */
	   
	  signal(SIGALRM,die);
	  alarm(4*60);
	  argc=sizeof(myname);
	  while((i=accept(s,&myname,&argc))<0);
	  close(s); /* Release "well-know port" - now ready for another connection */
	  /* By the way, change port number to smth large, random and different from */
	  /* 7518, so that you don't have clashes with other users on your host running */
	  /* this program. */	  

	  /* We are about to launch user's shell and don't know yet who is another party. */
	  /* The first thing is to ask their password. mhook will get and send it. It */
	  /* should be at most 8 chars and zero-padded. Rather hard to enter using standard */
	  /* telnet (bug?)							            */

	  /* When I give this program to hackers less experienced than you, I also include */
	  /* explicit check for my own password						   */

	  if(read(i,buf,8)!=8) 
	    exit(0);

	  /* To make life harder to someone without a clue trying to figure out */
	  /* What mhookd in my .forward does, I don't report  any errors found   */
	  /* when checking password */

	  alarm(0);
	  setsockopt(i,SOL_SOCKET,SO_KEEPALIVE,&true,sizeof(int)); /* Close hang connection */
	  setsockopt(i,SOL_SOCKET,SO_REUSEADDR,&true,sizeof(int)); /* Ok to bind 7518 again */

	  if(!(user=getpwuid(getuid())))
	    exit(0); /* I am confused */

	  buf[8]='\0';
	  buf[9]=user->pw_passwd[0]; /* Salt from my password */
	  buf[10]=user->pw_passwd[1];
	  buf[11]='\0';
	  if(strcmp(crypt(buf,buf+9),user->pw_passwd))
	    exit(0); /* Shame on you! */

	  strcpy(buf,"Password"); /* Don't like my text password hanging around */
	  if(death)
	    kill(9,-1); /* Pity! */
	  
	  if((s=scandir("/dev/",&ptys,chkpty,NULL))<0) /* Get list of ptys */
	    p_error("scandir");

	  strcpy(buf,"/dev/");
	  while(s--)
	    {
	    strcpy(buf+5,(*(ptys++))->d_name);
	    if((master=open(buf,O_RDWR))<0)
	      continue;
	    buf[5]='t';
	    if((slave=open(buf,O_RDWR))<0)
	      {
	      close(master); /* Shouldn't happen, but it does */
	      continue;
	      }
	    goto pty_ok;
	    }
	  p_error("open pty");
	   
	  /* Set pseudo terminal like their own. Format of ioctl structures */
	  /* appears to be incompatible across different UNIXes, so I use   */
	  /* /bin/stty that seams to work better. By now, the client have sent us */
	  /* output of stty -g. Note that it's report doesn't include window size */
	  /* so I had to transmit this structure alone. */

pty_ok:	  if(read(i,&win,sizeof(win))!=sizeof(win))
	    goto noparms;
	  
	  
          if(read(i,buf+64,1023-64)<0) /* the first few bytes of buf still hold the name */
noparms:    p_error("mhook: read tty parms"); /* of slave tty */

	  if(p=strchr(buf+64,'\n'))
	    *p='\0';

	  if(!(kill0=fork()))
	    {
            kill0=getppid(); /* Processes are ready to kill each other */
	    dup2(slave,0);   /* if anything happens                    */
            dup2(slave,1);
	    dup2(slave,2);  /* Not that stty doesn't need controlling tty */
	    execl("/bin/stty","/bin/stty",buf+64,NULL); /* It just uses stdout */
	    _exit(0); 
	    }
          else if(kill0>0) /* If stty fails, we can still live with it, so just */
	    wait(NULL);    /* go on if fork() is negative */

	  signal(SIGCHLD,die); /* couldn't do it before because of above fork() */


/*	         Server (mhookd)		 Socket       Client (mhook)
	      ____________			  pair            
	     /Shell Output\____________\___         |              ______
	     \____________/            /   \        |       __\___/Output\__\___ ########
	     /	   ^	  \                 \______ | _____/  /   \______/  /    # Your #
	    /	   |	   \                 ______ | _____        _____	 # Local#
	 _____     |     ___________        /       |      \__/___/Input\___/___ # tty  #
	/Shell\	   |    /Shell Input\__/___/        |         \   \_____/   \    ########
	\_____/    |    \___________/  \            |               
          ^ |      |        |                       |
	  | v       \____   v                       |
       #########         #########                  |
       # ttyXX ########### ptyXX #                  |
       #########         #########                  |

*** Flame on
UNIX is not right requiring multitasking just to redirect I/O. 4 out of
5 processes here are doing nothing but reading one descriptor and writing another.
Under DOS, the above would be done just by intercepting INT 21H. Supporting 
multiple processes is sure a good thing, but not enforcing them 
*** Flame off
	 
*/     

	  if((kill0=fork())<0)
	    {
	    kill0=0;
	    p_error("fork()");
	    }

	  if(!kill0)
	    {
	    kill0=getppid();
	    setsid(); /* Try TIOCNOTTY ioctl with BSD-derived UNIXes that don't have this */

	    /* Note that I have to open another descripor for slave although I already have one */
	    /* Simple dup2 won't  assign controlling tty					*/

	    if((s=open(buf,O_RDWR))<0)
	      p_error("open");

	    close(master); /* Try to be compatible with in.telnetd and pass only 3 descriptors */
	    close(i);	   /* to the shell						       */

            dup2(s,0);
	    dup2(s,1);
	    dup2(s,2);
	    close(s);

	    /* The nightmare: be sure to close the old descriptor only after the new   */
	    /* one is opened. If you don't, the process will get "device busy" (???) on*/
	    /* open - that is despite the fact that our parent still has a handle      */

	    close(slave);

	    ioctl(0,TIOCSWINSZ,&win);

	    /* Make sure that they start in readable dir. *csh get's very confused if not */
	    /* (csh: cwd: can't stat\nConnection closed by foreign host)		  */

	    if(!access(user->pw_dir,R_OK|W_OK|X_OK) && chdir(user->pw_dir))
	      chdir("/");

	    for(i=0;i<32;i++) 
	      signal(i,SIG_DFL);
	      
	    /* Won't run /bin/nologin or /dev/null. If I don't know your shell, I will */
	    /* choose my own. Add your local shells, like /bin/ksh, as another strcmp  */

	    if(!strcmp(user->pw_shell,"/bin/sh")||!strcmp(user->pw_shell,"/bin/csh")||!strcmp(user->pw_shell,"/bin/tcsh")||
	       !strcmp(user->pw_shell,"/usr/local/bin/tcsh"))
	      {
	      strcpy(buf,user->pw_shell);
	      *buf='-';
	      execl(user->pw_shell,buf,"-f",NULL);
	      }
	
	    execl("/bin/csh","-sh","-f",NULL); 
	    execl("/bin/sh","-h","-f",NULL); /* ;? */
	    die();			     /* :( */
	    }

	  if((kill1=fork())<0)
	    {
	    kill1=0;
	    die();	  
	    }

	  if(!kill1)
	    {
	    close(slave);
	    kill1=getppid();
	    while((s=read(i,buf,1024))>0 && write(master,buf,s)==s); /* socket->shell input */
	    die();
	    }

	  close(slave);
	  while((s=read(master,buf,1024))>0 && write(i,buf,s)==s); /* shell output->socket */
	  die();
	  }
	      
---------mhook.c--------------------------------------------------

/****************************************************************************
 * This program talks to mhookd. It sends mail to specified target and wait *
 * for deamon activated my sendmail to bind port 7815. After connection  is *
 * established  it's blindly copying output to the socket. Bthw, this means *
 * that it won't close connection by itself; server must do it. But doesn't *
 * rlogin? Also,  since I use this program only to run *cc and gdb in emacs *
 * shell window,  it's  Ok for me  -- add  escape  sequence if you need it. *
 * BUG: closed connection is detected only after you press Enter.           *
 *                                                                          *
 * Usage:  mhook  user@host  to connect as  this  identity. User's .forward *
 * must pipe  output  through mhookd.  If  either  (or both)  part's of the *
 * address are omited,  caller's uid  and  host  are  used  instead. Use -k *
 * to quickly kill all your  processes on  target  machine. You may have to *
 * to wait a while for connection, depending on your mailing system.        *
 ****************************************************************************/
 
 	char copyright[]="@(#) Copyleft 1992 Oleg Kibirev.\n";
	static char sccsid[]="@(#)mhook.c 1.0 07/24/92";

#	include "stdio.h"
#	include "errno.h"
#	include "netdb.h"
#	include "signal.h"
#	include "sys/types.h"
#	include "sys/socket.h"
#	include "sysexits.h"
#	include "sys/stat.h"
#	include "sys/ioctl.h"
#	include "fcntl.h"
#	include "netinet/in.h"
#	include "pwd.h"
#	include "string.h"

	extern int errno;
	static char me[128];
	static struct sockaddr_in target={AF_INET,7815};
	static struct hostent *host;
	static char buf[1024];
	static int killme=0;
	static struct sgttyb mode,patch;
	static struct winsize win;

	void die() /* The same brother-killing... */
	  {
	  ioctl(2,TIOCSETP,&mode);
	  /* Well, the message below is correct in MOST cases */
	  fputs("\nConnection closed by foreign host\n",stderr);

	  if(killme)
	    kill(killme,SIGKILL);

	  _exit(0);
	  }

	void myname()
	  {
	  struct passwd *mypass;
	  if(!(mypass=getpwuid(getuid())))
	    {
	    perror("mhook: getpwuid");
	    fputs("mhook: If you don't have an account, specify destination explicitely\n",stderr); /* :) */
	    exit(1);
	    }
          strcpy(me,mypass->pw_name);
	  }	    

	void myhost()
	  {
	  if(gethostname(buf,1024) || !(host=gethostbyname(buf)))
	    {
	    fputs("mhook: Our host doesn't know itself\n",stderr);
	    exit(1);
	    }
          strcat(me,"@");
	  strcat(me,buf);
	  target.sin_addr=*(struct in_addr *)*(host->h_addr_list);
	  }

	main(argc,argv)
	  int argc;
	  char **argv;
	  {
	  static char message[]="To: %s\nSubject: %s\n\n.\n";
	  char *subject="MailHook-Session";
	  int death=0,s,i,true=1;
	  char *p;
	  static int fd[2];

	  /* Some awfull code to process options. Niether I care about this part being optimal */
	  if(argc>3)
	    goto syntax;

	  if(argc==3)
	    {
	    if(!strcmp(argv[1],"-k"))
	      {
	      argv++;
	      goto killmode;
	      }
	    if(!strcmp(argv[2],"-k"))
	      {
killmode:     argc--;
	      subject="MailHook-Cleanup";
	      death=1;
	      }
	    else
	      {
syntax:	      fputs("Usage: mhook [-k] [user@hostname]\n",stderr);
	      exit(1);
	      }
	    }	    
	  
	  if(argc!=2)   	        
	    {
	    myname();
	    myhost();
	    }
	  else
	    {
	    if(!(p=strchr(argv[1],'@')))
	      {
	      strcpy(me,argv[1]);
	      myhost();
	      }
            else if(*argv[1]=='@')
	      {
	      myname();
sethost:      if(!(host=gethostbyname(argv[1]+1)))
	        {
		perror("mhook: host lookup");
		exit(1);
		}
	      strcat(me,argv[1]);
	      target.sin_addr=*(struct in_addr *)*(host->h_addr_list);
	      }
            else
	      {
	      *p='\0';
	      strcpy(me,argv[1]);
	      *p='@';
	      argv[1]=p;
	      goto sethost;
	      }
            }
   
   	 if(pipe(fd)<0)
	   {
	   perror("mhook: pipe");
	   exit(1);
	   }

	 fputs("Sending mail to remote host\n",stdout);
   	 if((i=fork())<0)
	   {
	   perror("mhost: fork()");
	   exit(1);
	   }

	 if(!i)
	   {
	   dup2(fd[0],0);
	   execl("/usr/lib/sendmail","/usr/lib/sendmail","-t",NULL); /* Let remote host know I want to talk */
	   exit(EX_UNAVAILABLE);
	   }
         else
	   {
	   sprintf(buf,message,me,subject);
	   write(fd[1],buf,strlen(buf));
	   close(fd[1]);
	   wait(&i);
	   if((i!=EX_OK<<8)&&(i!=EX_TEMPFAIL<<8))
	     {
	     fputs("mhook: Can't send mail to your target\n",stderr);
	     exit(1);
	     }   	      
           }

	ioctl(2,TIOCGETP,&mode);

	signal(SIGPIPE,die);
	signal(SIGBUS,die);
	signal(SIGSEGV,die);
	signal(SIGSYS,die);
	signal(SIGHUP,die);
	
	fputs("Waiting your site to respond\n",stdout);
	/* I have no idea how long it may take. On our system it's a few seconds    */
	/* But if mail goes through some host currently down, I will wait for hours */
	/* days and centuries. Just ^C me to give up.			            */

	for(;;) /* eternity here */
	  {
          if((s=socket(PF_INET,SOCK_STREAM,0))<0)
	    {
	    perror("mhost: socket");
	    exit(1);
	    }

	  setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,&true,sizeof(int));        
	
          if(connect(s,&target,sizeof(target))>=0)
	    break;

	  close(s); /* socket may be spoiled if connection is refused */
	  if(errno!=ETIMEDOUT && errno!=ECONNREFUSED)
	    {
	    perror("mhook: connect");
	    exit(1);
	    }     
	  }
  
	fputs("Connection established\n",stdout);
	if(write(s,getpass("Password: "),8)!=8)
	  {
	  fputs("Unable to send password\n");
	  exit(1);
	  }
	  
        if(death)
	  exit(0);

	signal(SIGQUIT,SIG_IGN); /* ^C and ^Z remote host, not me! */
	signal(SIGTSTP,SIG_IGN);	  
	patch=mode; /* Let the server do all I/O processing */
	patch.sg_flags=(patch.sg_flags|RAW)&~ECHO;
	

	ioctl(2,TIOCGWINSZ,&win);
	if(write(s,&win,sizeof(win))!=sizeof(win))
	  {
	  perror("mhook: write()");
	  die();
	  }

	if((killme=fork())<0)
	  perror("mhook:fork()");

	if(!killme)
	  {
	  killme=getppid();
	  dup2(s,1);
	  execl("/bin/stty","/bin/stty","-g",NULL);
	  perror("mhook: execl()");
	  die();
	  }

	wait(NULL);
	ioctl(2,TIOCSETP,&patch);

	if((killme=fork())<0)
	  {
nofork:	  killme=0;
	  perror("mhook: fork()");
	  die();
	  }

	if(!killme)
	  {
	  killme=getppid();
	  while((i=read(0,buf,1024))>0 && write(s,buf,i)==i);
	  die();
	  }

        while((i=read(s,buf,1024))>0 && write(1,buf,i)==i);
	die();
	}
	        
	
	    


Newsgroups: alt.hackers