Poor Man's Access by GT PMA (Poor Man's Access) is a UNIX-based TCP/IP client/server application written in C. It provides limited shell (csh) access to multiple clients from the server's host. PMA is sort of a 'telnet' without the login program. PMA has been tested on the following UNIX platforms: HP/UX 10.0.1 AIX 4.0 SunOS 4.1.3 Solaris 2.5 DG/UX 5.4.2 There are some very System Vish things that PMA does. Its extremely unlikely that it will work with all System V versions of UNIX. There is virtually no chance that it will work with a "true" BSD UNIX. PMA has only been tested using GNU C. Other C compilers may cause problems. PMA consists of the following files: The files printed on the following pages are 'pma.c' (client source), 'pmad.c' (server [daemon] source), and 'socklib.c' (used by both client and server). History Back in 1992 the company that I was working for finally decided to get connected to the Internet. A small workstation was situated between our network and the ISP. that we were using. Software was installed to prevent unauthorized access to our network and this workstation became known as "The Firewall." At this time I was fairly new to UNIX and was interested in learning how to program TCP/IP sockets. I had done some TOPS-20/DECNET programming some ten years prior and correctly assumed that conceptually it was more or less the same, just different syntax. I started by studying a date and time server that had been written by a co-worker for one of our products. It was a simple sequential (one request at a time) program that provided a date and time stamp via an established socket to the requesting client process. I modified the server to write whatever it received to the terminal instead of sending a date and time stamp back to the client. And I wrote a new client that reads a string from the terminal and sends it to the server. Pretty simple stuff, but that's where one usually starts. For some reason, I decided to test The Firewall with my newly coded client/server terminal echo programs. I had a UNIX account on a machine outside of The Firewall. I ported the client program to this machine and left the server running in the background on a UNIX machine inside The Firewall. I then ran the client and much to my surprise, it worked. Whatever I typed outside The Firewall was being displayed inside The Firewall. I was baffled. Attempts to 'telnet' and 'ftp' (from the outside) to the UNIX machine inside The Firewall failed. Why did my little programs work? What I had discovered was that The Firewall was only blocking connection attempts to "known" ports, i.e., port numbers less than 1024. My server program was using port number 4321, which was the arbitrary port number that the original date/time stamp server used. I hadn't seen any need to change it and didn't know that the port number would make a difference as long as it was not in use. The conclusion that I came to was that The Firewall was dumb and very susceptible to "inside jobs." Like most people, when faced with learning something new (TCP/IP socket programming in this case) it is nice to have some task of substance as opposed to just reading books. I now had my task. Build a client/server app that provides simple access from outside The Firewall. Of course, I quickly realized that all I needed to do was put a copy of 'telnet' on a free port number greater than 1024 and the task was complete. Which I did, and it worked. But this required root access (which I had). So I amended the task. No root access could be used. PMA was born. Closing Remarks Over the years I would work on PMA one or two days a month. Sometimes months would come and go and PMA would get no attention. Eventually I bought some books (Internetworking with TCP/IP by Douglas E. Comer and UNIX Network Programming by W. Richard Stevens) which really shed a lot of light on what I was trying to do. PMA is basically hack code. I didn't pay any attention to efficiency. Many things don't work, like 'pine', 'more', 'elm', etc. However, the editor we love to hate, 'vi', does work. First and foremost PMA was a vehicle to learn UNIX network programming. There are still many things about that topic that I do not know and it remains to be seen if PMA has outlived its usefulness or not. Recently, I bought a shell account on a major ISP and ported PMA to it. This ISP does a pretty good job or preventing you from being logged in more than once and it cleaning up any background processes that you may have had. However, if you start up the PMA daemon and log out, the PMA daemon lives on. Of course, there are many firewalls that prevent "inside jobs." And I have no idea just how dumb firewalls are still in use. But I'm sure that a firewall will never be invented which cannot be rendered dumb by someone. Usage You need a UNIX client and server. (Of course, it can be the same host). You also need a 4-digit (more than 1024) unused port number, say 1776. On the server: $ gcc -o pmad pmad.c socklib.c (You may get some warnings. If you get 'undefined symbols' try adding: -lsocket and -lnsl to the gcc command.) $ ./pmad 1776 (The PMA daemon should now be running.) On the client: $ gcc -o pma pma.c socklib.c $ ./pma server.inside.com 1776 Trying... Connected PMA> HELO ok PMA> hostname server PMA> pwd /tmp PMA> You should be able to issue any valid 'csh' command against the server and your current directory is /tmp. Running programs that are not line-oriented generally do not work. There is however support for 'vi'. To exit from PMA: PMA> done Don't forget that 'pmad' is still running. Use 'kill -9' to get rid of it. --begin code-- /* pma.c * * This is the client of pma. * Basically all it does is get a string from the terminal and write it out * to an established socket. * * Tested on: * HP/UX 10.0.1 * AIX 4.0 * SunOS 4.1.3 * Solaris 2.5 * DG/UX 5.4.2 * */ #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <sgtty.h> char buf[5000], tbuf[100]; int sockfd, vimode = 0; struct sgttyb tty_mode; main(int argc, char *argv[]) { int cnt, port, pid, idx = 0; FILE *lout; if (argc < 3) { fprintf(stderr, "Specify host and port.\n"); exit(0); } signal(SIGCHLD, SIG_IGN); /* Don't create any zombie */ ioctl(0, TIOCGETP, &tty_mode); fprintf(stderr, "Trying..."); port = atoi(argv[2]); /* See if there is a server on the user give host and port */ sockfd = socket_connect(argv[1], port); if (sockfd == -1) { fprintf(stderr, "\nsocket_connect(pms) failed.\n"); exit (1); } fprintf(stderr, "\nConnected.\n"); pid = forkio(); cnt = write(sockfd, buf, strlen(buf)); while (1) { if (!vimode) { gets(&buf[idx]); idx = 0; } else { buf[0] = getchar(); vimode = checkmode(); if (!vimode) { idx = 1; continue; } buf[1] = '\0'; } checkcmd(pid); cnt = write(sockfd, buf, strlen(buf)); } } int forkio() { int pid; /* Fork off a child which endlessly reads from the socket and * writes to the terminal. */ if ((pid = fock()) < 0) exit(system("echo fork error in fockio >> pma.org")); else if (pid > 0) return(pid); while (1) rdsock(); } int rdsock() { /* If we see the prompt, "PMA> ", it means that we could be coming out * vi mode, so turn on echo and turn off raw mode. Note that our * parent will get to the getchar() before it see that echo is back * on. This means that the first character typed after exiting from * vi will not go to the gets() like we would really want. That * first character can be flakey sometimes, but it basically works */ int cnt; cnt = read(sockfd, buf, sizeof(buf)); buf[cnt] = '\0'; fprintf(stderr, "%s", buf); if (!strcmp(&buf[strlen(buf) - 5], "PMA> ")) echo(); } int checkmode() { struct sgttyb tty_mode; int mode; ioctl(0, TIOCGETP, &tty_mode); if (tty_mode.sg_flags & ECHO) mode = 0 else mode = 1; return(mode); } int noecho() { /* system("stty -echo;stty raw"); */ tty_mode.sg_flags &= ~ECHO; tty_mode.sg_flags |= RAW; ioctl(0, TIOCSETP, &tty_mode); } int echo() { /* system("stty -echo;stty cooked"); */ tty_mode.sg_flags |= ECHO; tty_mode.sg_flags &= ~RAW; ioctl(0, TIOCSETP, &tty_mode); } int checkcmd(int pid) { if (!strcmp(buf, "done")) exit(kill(pid, 9)); if (!vimode) strcat(buf, "\n"); if (!strncmp(buf, "vi ", 3)) { noecho(); vimode = 1; strcpy(tbuf, "vi -w24"); strcat(tbuf, &buf[2]); strcpy(buf, tbuf); } } --end code-- --begin code-- /* pmad.c * * This is the server of pma. * It basically reads from an established socket, * writes what it gets to a shell in the background, * reads the output from the shell, and then sends it * back to the client via the socket. * */ #include <stdio.h> #include <fcntl.h> #include <signal.h> int in, cnt, pipin, sockfd, newsockfd, passok = 0; char buf[5000], passwd[50], iname[20], oname[20]; FILE *log; main(int argc, char *argv[]) { int port, cpid; /* Daemonize - System V style */ if ((cpid = fork()) < 0) exit(system("echo start up fork error >> pma.org")); else if (cpid > 0) exit(0); setpgrp(); chdir("/tmp"); /* temp work space */ signal(SIGCHLD, SIG_IGN); /* Don't create zombies */ if (argc != 2) exit(printf("Specify port\n")); port = atoi(argv[1]); sockfd = socket_declare(port); if (sockfd == -1) /* Say we are here at user given port */ exit(system("echo socket_declare failed >> pma.org")); strcpy(passwd, "HELO\n"); /* Simple password */ while (1) { newsockfd = socket_accept(sockfd); /* Wait for connection */ if (newsockfd == -1) exit(system("echo socket_accept failed >> pma.org")); if ((cpid = fork()) < 0) /* Got one, fork off child */ exit(system("echo fork error >> pma.org")); else if (cpid > 0) { close(newsockfd); continue; /* Wait for next user */ } do_child(); } } int do_child() { /* We now have an established socket. * Read from it and write to the shell */ int opid; close(sockfd); opid = do_csh(); system("date >> pma.org"); while (1) { cnt = read(newsockfd, buf, sizeof(buf)); if (cnt <= 0) exit(kill(opid, 9)); buf[cnt] = '\0'; /* logit(buf) */ seewhat(); cnt = write(pipin, buf, strlen(buf)); } } int do_csh() { /* First create some pipes. * Next fire up csh in prompt mode (-i) * doing its IO from the pipes. * The fork off another child that sets * the prompt to "PMA> " and then * endlessly reads from the shell and * writes to the socket. */ char sbuf[100]; int pid; pid = getpid(); sprintf(iname, "inpipe%d", pid); sprintf(oname, "outpipe%d", pid); sprintf(sbuf, "/etc/mknod %s p; /etc/mknod %s p", iname, oname); system(sbuf); pipin = open(iname, O_RDWR, 0); sprintf(sbuf, "csh -i <%s >%s 2>&1 &", iname, oname); system(sbuf); in = open(oname, O_RDONLY, 0); if ((pid = fork()) < 0) exit(system("echo fork error in do_csh >> pma.org")); else if (pid > 0) return(pid); read(in, buf, sizeof(buf)); strcpy(buf, "set prompt='PMA> '\n"); write(pipin, buf, strlen(buf)); read(in, buf, sizeof(buf)); while (1) getoutput(); } int getoutput() { cnt = read(in, buf, sizeof(buf)); write(newsockfd, buf, cnt); } int seewhat() { /* Don't let 'em do anything until they type in the dumb password */ if (passok) return; if (!strcmp(buf, passwd)) passok = (int) strcpy(buf, "echo ok\n"); else strcpy(buf, "echo nope\n"); } int logit(char *msg) { /* Sonetimes useful when debugging */ log = fopen("pma.org", "a"); fprintf(log, "%s", msg); fclose(log); } --end code-- --begin code-- /* socklib.c * * This module has all the socket stuff needed to get an * established connection. * * Basically the server side calls socket_declare() and * socket_accept(). * The client side calls socket_connect() */ #include <stdio.h> #include <ctype.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define TRUE 1 #define FALSE 0 static u_long squeeze_ip(); static int four_octets(); int socket_connect(char *hostname, int port) { struct sockaddr_in srvadr; struct hostent *hinfo; struct in_addr *haddr; char **address_list; int sockfd; memset(&srvadr, '\0', sizeof(srvadr)); srvadr.sin_family = AF_INET; if (four_octets(hostname)) srvadr.sin_addr.s_addr = squeeze_ip(hostname); else { hinfo = gethostbyname(hostname); if (hinfo == NULL) return (-1); address_list = hinfo->h_addr_list; haddr = (struct in_addr *) *address_list; srvadr.sin_addr.s_addr = haddr->s_addr; } srvadr.sin_port = htons(port); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return(-1); if (connect(sockfd, (struct sockaddr *) &srvadr, sizeof(srvadr)) == -1) { close(sockfd); return(-1); } return sockfd; } int socket_declare(int port) { struct sockaddr_in srvadr; int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return(-1); memset(&srvadr, '\0', sizeof(srvadr)); srvadr.sin_family = AF_INET; srvadr.sin_addr.s_addr = htonl(INADDR_ANY); srvadr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr *) &srvadr, sizeof(srvadr)) == -1) { close(sockfd); return(-1); } if (listen(sockfd, 0) == -1) { close(sockfd); return(-1); } return sockfd; } int socket_accept(int insockfd) { int clisockfd; int clilen; struct sockaddr_in cliadr; clilen = sizeof(cliadr); clisockfd = accept(insockfd, (struct sockaddr *) &cliadr, &clilen); return clisockfd; } static u_long squeeze_ip(char *ipstr) { int ip1, ip2, ip3, ip4; union { u_char ichar[4]; u_long ilong; } iunion; sscanf(ipstr, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4); iunion.ichar[0] = ip1; iunion.ichar[1] = ip2; iunion.ichar[2] = ip3; iunion.ichar[3] = ip4; return(iunion.ilong); } static int four_octets(char *hname) { int ans = TRUE; int i; for (i = 0; i < strlen(hname); ++i) if ((hname[i] < '0' || hname[i] > '9') && hname[i] != '.') ans = FALSE; return(ans); } --end code- Source Code - pma.c Source Code - pmad.c Source Code - socklib.c
Return to $2600 Index