allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (03/07/89)
Posting-number: Volume 6, Issue 54 Submitted-by: lars@ACC-SB-UNIX.ARPA (Lars J Poulsen) Archive-name: pserv PSERV - a sample piece of functional TCP/IP Berkeley socket programming. Every so often, programmers new to socket programming ask for working examples. I will give you this small example to play with. This program is a minimal remote spooling package, intended to solve a personal problem: I use every day two VMS systems and a Unix system. In my work area we have a LaserWriter connected to the Unix system, but I have to ride a sloooow elevator two floors to get to a VMS printer. I really wanted to write a small "lpd" client, but found that this would require the cooperation of system managers on both machines, since (1) LPD will only accept commands from known hosts (2) LPD will only accept connections from privileged ports (<1024) and the Wollongong VMS package enforces this also; you need system privileges to get a low-numbered port on WIN/TCP. So I decided to write my own mini protocol. This program illustrates the basic mechanism used by any server/client pair, and is small enough to dink around with fairly safely. [After all, if the system lets an unprivileged user do it, it must be safe :-) ?] The client runs un 4.3BSD or VMS/WIN/TCP; the server runs on 4.3BSD. Enjoy. / Lars Poulsen ACC Customer Service --------------------------------- Cut Here -------------------------------- # This is a shell archive. Remove anything before this line # then unpack it by saving it in a file and typing "sh file" # (Files unpacked will be owned by you and have default permissions). # This archive contains the following files: # ./copytoheap.c # ./filename.c # ./listen.c # ./rmtprint.c # ./strpak.c # ./Makefile # if `test ! -s ./copytoheap.c` then echo "writing ./copytoheap.c" cat > ./copytoheap.c << '\Rogue\Monster\' static char rcsid[] = "$Header: copytoheap.c,v 3.5 87/05/26 14:16:25 lars Production $"; #include <stdio.h> extern char *malloc(); extern int strlen(); char * copytoheap(string) char *string; { char *temp; int size; if (string == NULL) return NULL; size = strlen(string)+1; if (size > 64*1024) { fprintf(stderr,"copytoheap: object too large (%d bytes)\n",size); exit(-1); } temp = malloc(size); strcpy(temp,string); return temp; } \Rogue\Monster\ else echo "will not over write ./copytoheap.c" fi if `test ! -s ./filename.c` then echo "writing ./filename.c" cat > ./filename.c << '\Rogue\Monster\' static char rcsid[] = "$Header: filename.c,v 3.3 88/08/07 19:00:37 lars Exp $"; /* filename.c - extract the FILENAME part from a pathname string * written by Lars Poulsen <lars@acc.arpa> * for the ACC PSR system * * $Header: filename.c,v 3.3 88/08/07 19:00:37 lars Exp $ * * $Log: filename.c,v $ * Revision 3.3 88/08/07 19:00:37 lars * handle vms-filenames also: * if name contains a slash, it's a unix name. * else, if it has "]" or ":", cut off what's before those. * * Revision 3.2 87/05/18 13:36:19 lars * First stable version of PSR after pf_get/pf_write went in * * Revision 3.1 87/05/16 21:16:55 lars * At end of splitting up (First stable version) * */ extern char *rindex(); char * filename(path) char *path; { char *ptr; /* first deal with unix filenames */ ptr = rindex(path, '/'); if (ptr) return ++ptr; /* now deal with vmsisms */ ptr = rindex(path, ']'); if (ptr) return ++ptr; ptr = rindex(path, ':'); if (ptr) return ++ptr; return path; } \Rogue\Monster\ else echo "will not over write ./filename.c" fi if `test ! -s ./listen.c` then echo "writing ./listen.c" cat > ./listen.c << '\Rogue\Monster\' /* listen.c - a server daemon to match "rmtprint" * * Written by Lars Poulsen <lars@acc-sb-unix.arpa> * August 1988 * * This is a simple example of a TCP based network server. */ #include <ctype.h> #include <stdio.h> #include <sys/types.h> #include <errno.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <pwd.h> #define MY_PORT 12345 #define ANY_PORT 0 #define DEFPRT "mac7" #define FORKING 1 extern int errno; extern char *asctime(); extern struct tm *localtime(); extern char *mktemp(); extern long time(); extern char *copytoheap(); extern char *filename(); char *what_alarm = NULL; char temp_name[40]; int forking = 0; extern char *home_directory(); extern exit_server(); extern timeout(); extern wait_child(); char * tstamp() { long time_now; char *temp; time_now = time(0); temp = asctime(localtime(&time_now)) + 4; temp[12] = 0; return temp; } main(argc, argv) int argc; char *argv[]; { int s, cs; struct sockaddr_in my_name; struct sockaddr_in any_name; struct sockaddr_in his_name; int idsw; int his_addr_len; forking = FORKING; /* done this way in case it is an expression */ signal(SIGALRM, timeout); signal(SIGCHLD, wait_child); signal(SIGTERM, exit_server); printf("%s Print server starting - defprt = %s\n", tstamp(), DEFPRT); fflush(stdout); s = socket(AF_INET, SOCK_STREAM, 0); /* protocol #0 is IP */ if (0) printf("tcp: got socket fd=%d (Hex: %x)\n", s, s); bzero((char *) &my_name, sizeof(my_name)); my_name.sin_family = AF_INET; my_name.sin_port = htons(MY_PORT); my_name.sin_addr.s_addr = INADDR_ANY; idsw = bind(s, &my_name, sizeof(my_name)); if (idsw != 0) printf("tcp: bind returned %d - errno = %d\n", idsw, errno); bzero((char *) &any_name, sizeof(any_name)); any_name.sin_family = AF_INET; any_name.sin_port = htons(ANY_PORT); any_name.sin_addr.s_addr = INADDR_ANY; idsw = listen(s, 0); if (idsw != 0) printf("listen: listen returned %d - errno = %d\n", idsw, errno); next_in_file: alarm(0); bzero((char *) &his_name, sizeof(his_name)); his_addr_len = sizeof(his_name); cs = accept(s, &his_name, &his_addr_len); if (cs < 0) { printf("listen: accept failed, errno = %d his_addr_len = %d\n", errno, his_addr_len); fflush(stdout); exit(errno); } process_a_file(cs); goto next_in_file; } process_a_file(cs) { int bcnt; char buffer[512]; int ofd; char printer[20], rmtuser[40], rmtfile[80]; int file_size = 0; int idsw; if (forking) { idsw = fork(); if (idsw < 0) { printf("%s listen: Fatal error - fork() failed with error = %d\n", tstamp(), -idsw); exit(-idsw); } if (idsw != 0) /* parent listens for next connection */ { close(cs); return; } } what_alarm = "Reading Command Line"; alarm(30); if (get_command(cs, printer, rmtuser, rmtfile) != 0) { printf("%s Bad incoming commnd %s %s %s\n", tstamp(), printer, rmtuser, rmtfile); fflush(stdout); close(cs); return; } printf("%s Received connection %s %s %s\n", tstamp(), printer, rmtuser, rmtfile); fflush(stdout); ofd = open_out_file(printer, rmtuser, rmtfile); what_alarm = "Receiving File"; alarm(30); while (bcnt = recv(cs, buffer, sizeof(buffer), 0)) { write(ofd, buffer, bcnt); alarm(30); file_size += bcnt; if (buffer[bcnt - 1] == 0) break; } close(cs); printf("%s Received %d bytes for %s %s %s\r\n", tstamp(), file_size, printer, rmtuser, rmtfile); fflush(stdout); what_alarm = "Printing File"; alarm(5); if (strcmp(printer, "save") != 0) { check_printer(printer, DEFPRT); sprintf(buffer, "lpr -P %s -r -s %s", printer, temp_name); idsw = system(buffer); if (idsw) { printf("listen: lpr command returned %d\n", idsw); fflush(stdout); } } if (forking) exit(0); else return; } get_command(nfd, printer, rmtuser, rmtfile) int nfd; char *printer, *rmtuser, *rmtfile; { char c, *p; extern char readch(); p = printer; while (isalnum(c = readch(nfd))) *(p++) = c; *p = 0; if (c != ' ') goto cmd_error; p = rmtuser; while (isalnum(c = readch(nfd))) *(p++) = c; *p = 0; if (c != ' ') goto cmd_error; p = rmtfile; while ((c = readch(nfd)) && (c > ' ')) *(p++) = c; if (!isspace(c)) goto cmd_error; /* space or \n */ *p = 0; return 0; cmd_error: printf("syntax error in print command *p = %d\n", *p); printf("printer = %s user = %s filename = %s\n", printer, rmtuser, rmtfile); return -1; } char readch(fd) { char buf; begin: if (recv(fd, &buf, 1, 0) != 1) { printf("readch(%d) got error %d\n", fd, errno); return 0; } if (buf == 0) goto begin; return buf; } check_printer(printer, defprt) char *printer, *defprt; { char fname[80]; int fd; int err; /* The following check is bad: If the device exists and is busy, we will wait forever for the daemon to exit... */ if (0) { sprintf(fname, "/dev/%s", printer); errno = 0; fd = open(fname, 0, 0); err = errno; close(fd); if (errno == ENOENT) goto failed; } sprintf(fname, "/usr/spool/%s", printer); errno = 0; fd = open(fname, 0, 0); err = errno; close(fd); if (errno) goto failed; return; failed: printf("%s Failed to open %s - errno = %d\n", tstamp, fname, err); printf("%s %s is not a valid printer, %s substituted\n", tstamp, printer, defprt); fflush(stdout); strcpy(printer, defprt); return; } /* alarm clock is used to debug hang's */ timeout() { printf("%s Fatal Timeout While %s\n", tstamp(), what_alarm); exit(2); } /* dummy child handler - just get rid of <defunct> child */ wait_child() { wait(0); } /* clean exit on a simple "kill" */ exit_server() { printf("%s Received SIGTERM - exiting\n", tstamp()); exit(0); } open_out_file(printer, rmtuser, rmtfile) char *printer, *rmtuser, *rmtfile; { int ofd; char *directory; rmtfile = filename(rmtfile); if ((directory = home_directory(rmtuser)) == NULL) directory = "/tmp"; sprintf(temp_name, "%s/%s", directory, rmtfile); if ((ofd = open(temp_name, 0) < 0) && errno == ENOENT) /* no such file exists */ ofd = creat(temp_name, 0644); if (ofd >= 0) return ofd; close(ofd); strcpy(temp_name, "/tmp/spoolXXXXXX"); (void) mktemp(temp_name); ofd = creat(temp_name, 0644); return ofd; } char * home_directory(name) char *name; { struct passwd *pw; char *hd; setpwent(); for (pw = getpwent(); pw; pw = getpwent()) { if (strcmp_nocase(pw->pw_name, name) == 0) break; } if (pw) hd = copytoheap(pw->pw_dir); else hd = NULL; if (forking && !geteuid()) setuid(pw->pw_uid); /* try to set this child to the right userid */ endpwent(); return hd; } \Rogue\Monster\ else echo "will not over write ./listen.c" fi if `test ! -s ./rmtprint.c` then echo "writing ./rmtprint.c" cat > ./rmtprint.c << '\Rogue\Monster\' #include <stdio.h> #include <signal.h> #ifdef VAXC #include "twg$tcp:[netdist.include.sys]types.h" #include "twg$tcp:[netdist.include]errno.h" #include "twg$tcp:[netdist.include.sys]socket.h" #include "twg$tcp:[netdist.include.netinet]in.h" #include "twg$tcp:[netdist.include]netdb.h" #else #include <sys/types.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define uerrno errno #endif #define MY_PORT 0 #define HIS_PORT 12345 extern int uerrno; extern timeout(); char *what_alarm = "Name Service"; main(argc, argv) int argc; char *argv[]; { int s; struct sockaddr_in my_name; struct sockaddr_in his_name; int idsw; int namelen; struct hostent *gethostbyname(), *salt_ent; u_long *found_addr; struct servent *sp; char buffer[512]; int bcnt; int ifd; char cmdbuf[80]; int null = 0; if (argc != 4) { printf("usage: rmtprint host printer file\n"); exit(1); } s = socket(AF_INET, SOCK_STREAM, 0); /* protocol #0 is IP */ if (0) printf("tcp: got socket fd=%d (Hex: %x)\n", s, s); bzero((char *) &my_name, sizeof(my_name)); my_name.sin_family = AF_INET; my_name.sin_port = MY_PORT; my_name.sin_addr.s_addr = INADDR_ANY; idsw = bind(s, &my_name, sizeof(my_name)); if (idsw != 0) printf("tcp: bind returned %d - uerrno = %d\n", idsw, uerrno); signal(SIGALRM, timeout); alarm(30); salt_ent = gethostbyname(argv[1]); if (salt_ent) found_addr = (u_long *)salt_ent->h_addr_list[0]; if ((salt_ent == NULL) || (found_addr == 0)) { printf("rmtprint: %s is not a known host\n", argv[1]); exit(2); } bzero((char *) &his_name, sizeof(his_name)); his_name.sin_family = AF_INET; sp = getservbyname("rmtprint", "tcp"); if (sp) his_name.sin_port = sp->s_port; else his_name.sin_port = htons(HIS_PORT); bcopy(salt_ent->h_addr_list[0], &his_name.sin_addr, salt_ent->h_length); what_alarm = "Connection Request"; alarm(30); idsw = connect(s, &his_name, sizeof(my_name)); if (idsw != 0) { idsw = uerrno; printf("rmtprint: Connect to %s port %d returned Unix error %d\n", argv[1], ntohs(his_name.sin_port), idsw); if (idsw == 61) printf("rmtprint: Connection refused by %s - server not running ?\n", argv[1]); exit(2); } what_alarm = "Sending Command Line"; alarm(30); sprintf(cmdbuf, "%s %s %s\n", argv[2], getenv("USER"), argv[3]); send(s, cmdbuf, strlen(cmdbuf), 0); what_alarm = "Sending File"; ifd = open(argv[3], 0, 0); if (ifd < 0) {idsw = errno; printf("rmtprint: Failed to open %s errno = %d\n", argv[3], idsw); exit(idsw); } bcnt = read(ifd, buffer, sizeof(buffer)); while (bcnt > 0) { alarm(30); send(s, buffer, bcnt, 0); bcnt = read(ifd, buffer, sizeof(buffer)); } if (errno) printf("rmtprint: read ended with errno = %d\n",errno); close(ifd); sleep(1); send(s, &null, 1, 0); recv(s, buffer, sizeof(buffer), 0); close(s); exit(1); } timeout() { printf("rmtprint: Fatal timeout during %s\n", what_alarm); exit(4); } \Rogue\Monster\ else echo "will not over write ./rmtprint.c" fi if `test ! -s ./strpak.c` then echo "writing ./strpak.c" cat > ./strpak.c << '\Rogue\Monster\' static char rcsid[] = "$Header: strpak.c,v 1.10 88/08/07 20:18:49 lars Exp $"; #include <stdio.h> #include <ctype.h> extern char *index(); extern char *malloc(); char * str_firstline(multi_field) char *multi_field; { char *end_of_line; static int i = 0; static char buf[4][80]; i = (i+1)&3; strncpy(buf[i], multi_field, 80); if (end_of_line = index(buf[i],'\n')) *end_of_line = 0; return buf[i]; } char * str_firstword(multi_field) char *multi_field; { char *end_of_line; static int i = 0; static char buf[4][80]; i = (i+1)&3; strncpy(buf[i], multi_field, 80); for (end_of_line = buf[i]; isalpha(*end_of_line); end_of_line++); *end_of_line = 0; return buf[i]; } char *str_join3(a,b,c) char *a, *b, *c; { int size = strlen(a) + strlen(b) + strlen(c) +1; char *new; char *pp; pp = new = malloc(size); if (new == NULL) return NULL; if (a != NULL) while (*a) *(pp++) = *(a++); if (b != NULL) while (*b) *(pp++) = *(b++); if (c != NULL) while (*c) *(pp++) = *(c++); *pp = 0; return new; } int str_lines(string) char *string; { char *pp = string; char c; int count = 1; if (string == NULL) return 0; if (strlen(string) == 0) return 0; while(c = *pp++) if (c == '\n') count ++; return count; } int str_longest(string) char *string; { int lines, longest = 0, total; int thislen; char *thisline, *nextline; if (string == NULL) return 0; if ((total = strlen(string)) == 0) return 0; lines = str_lines(string); thisline = string; while (--lines) { nextline = index(thisline,'\n') + 1; if (nextline == NULL) { fprintf(stderr,"str_longest: Program bug !!\n"); exit(-1); } thislen = nextline - thisline - 1; if (thislen > longest) longest = thislen; thisline = nextline; } thislen = strlen(thisline); if (thislen > longest) longest = thislen; return longest; } char * str_trim(string) char *string; { char *pp; int length; static int i = 0; static char buf[4][80]; if (string == NULL) return NULL; i = (i+1)&3; pp = string + strlen(string) - 1; while ((*pp == 0) || (*pp == ' ') || (*pp == '\n') || (*pp == '\t') || (*pp == '_')) pp--; length = pp - string + 1; if (length <= 0) return NULL; strncpy(buf[i],string, length); buf[i][length] = 0; return buf[i]; } #define MAXINIT 3 char * str_initials(name) char *name; { static char init[4][MAXINIT + 1]; static int i = 0; int j; char *p; char c; if (name == NULL) return ""; if (strlen(name) == 0) return ""; i = (++i) & 3; j = 0; p = name; while ((c = *(p++)) && (j < MAXINIT)) if (isupper(c)) init[i][j++] = c; init[i][j] = 0; return init[i]; } /* * Compare strings (at most n bytes): s1>s2: >0 s1==s2: 0 s1<s2: <0 */ strncmp_nocase(s1, s2, n) register char *s1, *s2; register n; { register char c1, c2; while (--n >= 0) { c1 = *s1++; if (c1 == 0) return 0; if (isupper(c1)) c1 = tolower(c1); c2 = *s2++; if (c2 == 0) return 0; if (isupper(c2)) c2 = tolower(c2); if (c1 != c2) break; } return(n<0 ? 0 : c1 - c2); } strcmp_nocase(s1, s2) register char *s1, *s2; { register char c1, c2; while (1) { c1 = *s1++; if (isupper(c1)) c1 = tolower(c1); c2 = *s2++; if (isupper(c2)) c2 = tolower(c2); if (c1 == 0) break; if (c2 == 0) break; if (c1 != c2) break; } return(c1 - c2); } \Rogue\Monster\ else echo "will not over write ./strpak.c" fi if `test ! -s ./Makefile` then echo "writing ./Makefile" cat > ./Makefile << '\Rogue\Monster\' all: listen rmtprint listen: listen.o copytoheap.o filename.o strpak.o cc -o listen -g listen.o copytoheap.o filename.o strpak.o rmtprint: rmtprint.o cc -o rmtprint -g rmtprint.o listen.o: listen.c rmtprint.o: rmtprint.c copytoheap.o: copytoheap.c filename.o: filename.c strpak.o: strpak.c \Rogue\Monster\ else echo "will not over write ./Makefile" fi echo "Finished archive 1 of 1" # if you want to concatenate archives, remove anything after this line exit