/*---------------------------------------------------------------------+ | Copyright (c) 1995, SAS Institute Inc. | | Unpublished - All Rights Reserved | | S A S / C S A M P L E | | | | NAME: TCPLISTN | | LANGUAGE: C | | PURPOSE: Demonstrate a TCP listener application that "attaches" | | a TCP Server, TCPTSERV, on MVS. TCPLISTN details the | | following socket calls: | | socket(), bind(), listen(), accept(), read(), | | givesocket(), takesocket(), select() and close(). | | MVS - | | COMPILE, LINK: SUBMIT prefix.SAMPLE.AUX(TCPLISTN) | | EXECUTE: prefix.SAMPLE.AUX(TCPLSGO) | | where "prefix" is the installation defined high-level- | | qualifier for the SAS/C product. | | NOTES: TCPLISTN assumes a working TCP/IP environment on both | | the local and remote hosts, refer to MISC NOTES. | | TSO - | | COMPILE: LC370 CLIST - options ENXREF EXTNAME RENT | | LINK: CLK370 CLIST | | EXECUTE: call 'your.local.load(TCPLISTN)' | | NOTES: TCPLISTN assumes a working TCP/IP environment on both | | the local and remote hosts. Refer to MISC NOTES: | | CMS - N/A - Multi-tasking is NOT supported w/in CMS | | MISC NOTES: TCPLISTN needs to be customized for the local TCP/IP | | environment. The local information required by | | TCPLISTN is: | | LISTENPORT - defines the port TCPLISTN will do a | | listen(). Default is port 13. | | QUITSTRING - used as a method to terminate TCPLISTN. | | TCPLISTN will read the message from the | | client application, if the message is: | | QUITSTRING, then TCPLISTN terminates. | | MAXSOCKETS - defines the maximum number of sockets | | for the select() socket array. | | MISC NOTES: Use TCPCLNT to test the TCPLISTN listener. | | MISC NOTES: TCPLISTN uses the following environment variables: | | TCP_SOCK - Socket descriptor passed to TCPTSERV | | TCP_DOMAIN - AF_INET = 2, passed to TCPTSERV | | TCP_NAME - Listener Jobname, passed to TCPTSERV | | TCP_SUBTASKNAME - taskname from getclientid() passed | | to TCPTSERV. On the givesocket() call, | | sub-taskname is set to: ' ' | | MISC NOTES: Telnet may be used to terminate the TCPLISTN. Telnet | | to the HOST and PORT for TCPLISTN, and then enter the | | QUITSTRING as defined below. | | | +---------------------------------------------------------------------*/ #define LISTENPORT 13 /* Set default listen port */ #define QUITSTRING "LSNRQUIT" /* If the recv() message is QUITSTRING */ /* terminate TCPLISTN */ #define MAXSOCKETS 50 /* max sockets opened at once */ /*-------------------------------------------------------------------+ | Includes for socket calls. | +-------------------------------------------------------------------*/ #include #include #include #include #include #include #include #if __SASC__ == 550 #include #else #include #endif /*-------------------------------------------------------------------+ | Include for ATTACH. | +-------------------------------------------------------------------*/ #include /*-------------------------------------------------------------------+ | Miscellaneous includes. | +-------------------------------------------------------------------*/ #include #include #include #include #include #include #define TRUE 1 #define FALSE 0 #define MAXREQLEN 256 /* max length of request, in chars */ /*-------------------------------------------------------------------+ | Macros for converting EBCDIC <-> ASCII. | +-------------------------------------------------------------------*/ #define convFromAscii(str) {cp=(str); while(*cp = ntohcs(*cp)) cp++;} #define convToAscii(str) {cp=(str); while(*cp = htoncs(*cp)) cp++;} int setupSocket(void); int acceptRequest(void); int closeSockets(int); void Quit(void); struct Argument { short len; char parms[1000]; }; struct socklist { /* linked list of socket file descriptors */ int sock; int domain; /* so that it can issue takesocket() */ char name[8]; /* so that it can issue takesocket() */ char subtaskname[8]; char pgmname[8]; void *subtcb; /* Handle for ATTACHed program */ void *plist[1]; /* pointer to argument structure for ATTACH */ struct socklist *next; } *servlist; char *cp; /* char pointer for EBCDIC<->ASCII macros */ int listensock, /* socket on which we listen for requests */ numsockets, /* number of sockets opened by listener */ highsocket, /* highest socket number */ holdrequests; /* flag: listener cannot handle any more */ /* requests right now (all sockets used) */ fd_set pending, /* list of sockets waiting to be taken */ taken; /* list of sockets which have been taken */ struct clientid myid, /* clientid of listener for argument struct */ giveid; /* generic clientid used for givesocket() */ void main() { int ns; /* number of sockets with action */ fd_set incoming; /* bound socket description for select() */ struct timeval timeout; /*----------------------------------------------------------------+ | Set up a socket, initialize list of pending servers to none, | | and set timeout for select() call. | +----------------------------------------------------------------*/ if (setupSocket() == -1) exit(EXIT_FAILURE); FD_ZERO(&pending); holdrequests = FALSE; timeout.tv_sec = 60; timeout.tv_usec = 0; while (TRUE) { do { /*----------------------------------------------------------+ | Check for action on sockets given to ATTACHed programs, | | as well as incoming socket (unless all sockets are used). | +----------------------------------------------------------*/ FD_ZERO(&incoming); if ((!holdrequests) && (numsockets < MAXSOCKETS)) FD_SET(listensock, &incoming); taken = pending; ns = select(highsocket+1, &incoming, NULL, &taken, &timeout); } while (ns == 0); if (ns == -1) { perror("select() call failed"); Quit(); } /*-------------------------------------------------------------+ | If there was action on listensock, then handle the incoming | | request. | +-------------------------------------------------------------*/ if (FD_ISSET(listensock, &incoming)) { ns--; acceptRequest(); } /*-------------------------------------------------------------+ | If there was action on any other sockets, then release the | | sockets of those programs which have taken them. | +-------------------------------------------------------------*/ if (ns > 0) closeSockets(ns); } } /*-------------------------------------------------------------------+ | Open a socket and set it up to accept requests. Return -1 if error.| +-------------------------------------------------------------------*/ int setupSocket() { struct sockaddr_in sa_serv; /* socket address for server */ int sa_len = sizeof(sa_serv); char hostname[72]; /*----------------------------------------------------------------+ | Get a socket of the proper type. | +----------------------------------------------------------------*/ listensock = socket(AF_INET, SOCK_STREAM, 0); if (listensock == -1) { perror("socket() call failed in setupSocket()"); return -1; } numsockets = 1; /*----------------------------------------------------------------+ | Get information about listener to pass to ATTACHed servers. | | Clientid used for givesocket() should be the same but with a | | subtaskname of all blanks. | +----------------------------------------------------------------*/ if (getclientid(AF_INET, &myid) == -1) { perror("getclientid() call failed in setupSocket()"); close(listensock); return -1; } giveid = myid; memset(giveid.subtaskname, ' ', 8); /*----------------------------------------------------------------+ | Prepare a socket address for the server. We will request the | | port number LISTENPORT, unless LISTENPORT is 0, in which case | | we are assigned the next available port number. | +----------------------------------------------------------------*/ memset(&sa_serv, '\0', sizeof(sa_serv)); sa_serv.sin_family = AF_INET; sa_serv.sin_addr.s_addr = htonl(INADDR_ANY); sa_serv.sin_port = htons(LISTENPORT); /*----------------------------------------------------------------+ | Bind our socket to the desired address so clients may reach us. | +----------------------------------------------------------------*/ if (bind(listensock, &sa_serv, sizeof(sa_serv)) == -1) { perror("bind() call failed in setupSocket()"); close(listensock); return -1; } /*----------------------------------------------------------------+ | Print listener information. | +----------------------------------------------------------------*/ if (getsockname(listensock, &sa_serv, &sa_len) == -1) { perror("getsockname() failed in setupSocket()"); close(listensock); return -1; } if (gethostname(hostname, sizeof(hostname))) { perror("gethostname() failed in setupSocket()"); close(listensock); return -1; } printf("Listener is on Host: %.64s Port#: %d.\n", hostname, (int) ntohs(sa_serv.sin_port)); printf("Request server name '" QUITSTRING "' to quit.\n"); /*----------------------------------------------------------------+ | Set up a queue for incoming requests. SOMAXCONN is maximum | | size of queue. | +----------------------------------------------------------------*/ listen(listensock, SOMAXCONN); highsocket = listensock; return 0; } /*-------------------------------------------------------------------+ | A request has been detected--accept it, givesocket(), and ATTACH | | program. Return -1 if error. | +-------------------------------------------------------------------*/ int acceptRequest() { int numchars, /* number of chars read from socket */ rc, /* rc from MVS ATTACH function */ pos, /* index variable */ reqsock, /* request socket */ i; /* index variable */ char req[MAXREQLEN+1], /* buffer for reading in request */ errstring[MAXREQLEN+80];/* error message sent to client */ struct socklist *new; /* new node in list of open sockets */ struct sockaddr_in sa_client;/* socket address of client */ int sa_len = sizeof(sa_client); char Env_buff[256]; struct Argument *args; char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz" \ "0123456789@$#"; /*----------------------------------------------------------------+ | Allocate space for a new addtion to the socklist | +----------------------------------------------------------------*/ new = (struct socklist *)malloc(sizeof(struct socklist)); if (new == NULL) { perror("malloc() for new failed in acceptRequest()"); return -1; } memset(new, 0, sizeof(struct socklist)); args = (struct Argument *)malloc(sizeof(struct Argument)); if (args == NULL) { perror("malloc() for args failed in acceptRequest()"); return -1; } memset(args, 0, sizeof(struct Argument)); /*----------------------------------------------------------------+ | Divert incoming request to its own socket. If accept() call | | fails, then all sockets are being used, so hold off requests. | +----------------------------------------------------------------*/ reqsock = accept(listensock, &sa_client, &sa_len); if (reqsock == -1) { perror("accept() failed in acceptRequest()"); holdrequests = TRUE; return -1; } /*----------------------------------------------------------------+ | Read request from socket and convert from ASCII to EBCDIC. | +----------------------------------------------------------------*/ numchars = read(reqsock, req, MAXREQLEN); if (numchars < 0) { perror("socket read() failed in acceptRequest()"); close(reqsock); return -1; } req[numchars] = '\0'; convFromAscii(req); /*----------------------------------------------------------------+ | Check request for validity (a pretty lax check, admittedly). | +----------------------------------------------------------------*/ if (req[0] == '\0') { strcpy(errstring, "MVS listener: empty request received.\n"); convToAscii(errstring); write(reqsock, errstring, strlen(errstring)); close(reqsock); return -1; } /*----------------------------------------------------------------+ | Set up the Env vars string for subtaskname, name, sock, etc. | +----------------------------------------------------------------*/ new->domain = AF_INET; memcpy(new->name, myid.name, 8); memcpy(new->subtaskname, myid.subtaskname, 8); sprintf(Env_buff, " =TCP_SOCK=%d =TCP_DOMAIN=%d " \ "=TCP_NAME=%s =TCP_SUBTASKNAME=%s", reqsock, new->domain, new->name, new->subtaskname); /*----------------------------------------------------------------+ | Separate the command from the parmameters | +----------------------------------------------------------------*/ pos = strspn(req, valid); req[pos] = '\0'; strcpy(new->pgmname, req); strcpy(args->parms, (req+pos+1)); /*----------------------------------------------------------------+ | Clean up the Program name. | +----------------------------------------------------------------*/ for (i = 0; i < sizeof(new->pgmname); i++) { if (new->pgmname[i] < ' ') new->pgmname[i] = ' '; else new->pgmname[i] = toupper(new->pgmname[i]); } /*----------------------------------------------------------------+ | Check for QUIT request. | +----------------------------------------------------------------*/ if (strcmp(new->pgmname, QUITSTRING) == 0) { close(reqsock); Quit(); } /*----------------------------------------------------------------+ | Combine the Program parms and the environment vars and assign | | to the plist pointer in new. | +----------------------------------------------------------------*/ strcat(args->parms, Env_buff); args->len = strlen(args->parms); for (i = 0; i < args->len; i++) { if (args->parms[i] < ' ') args->parms[i] = ' '; } new->plist [0] = (void *) (0x80000000 | (unsigned) args); /*----------------------------------------------------------------+ | Give the socket and then attach the new subtask. | +----------------------------------------------------------------*/ givesocket(reqsock, &giveid); rc = ATTACH(&(new->subtcb), _Aep, new->pgmname, _Aparam, new->plist, _Aend); /*----------------------------------------------------------------+ | Check return code from ATTACH. | +----------------------------------------------------------------*/ if (rc != 0) { fprintf(stderr, "ATTACH for server '%s' failed with rc = %d.\n", new->pgmname, rc); close(reqsock); return -1; } /*----------------------------------------------------------------+ | Add this server to lists of pending programs. | +----------------------------------------------------------------*/ new->sock = reqsock; new->next = servlist; servlist = new; FD_SET(reqsock, &pending); if (reqsock > highsocket) highsocket = reqsock; numsockets++; return 0; } /*-------------------------------------------------------------------+ | Close sockets taken by ATTACHed servers. Return -1 if error. | +-------------------------------------------------------------------*/ int closeSockets(nt) int nt; /* number of sockets taken by ATTACHed servers */ { struct socklist *servptr, /* list of file descriptors of */ *next, *prev; /* sockets given to servers */ int nr = nt; /* number of sockets left to close */ prev = servptr = servlist; /* point to first socket in list */ while ((nr > 0) && (servptr != NULL)) { /*-------------------------------------------------------------+ | If this socket was taken by an ATTACHed server, then close | | the socket, remove it from our lists, and signal that another| | socket is free for use. | +-------------------------------------------------------------*/ if (FD_ISSET(servptr->sock, &taken)) { close(servptr->sock); nr--; numsockets--; holdrequests = FALSE; FD_CLR(servptr->sock, &pending); next = servptr->next; if (servptr == servlist) servlist = next; else prev->next = next; free((char *) servptr->plist[0]); free((char *) servptr); servptr = next; } else { prev = servptr; servptr = servptr->next; } } return 0; } /*-------------------------------------------------------------------+ | Close all sockets and quit. | +-------------------------------------------------------------------*/ void Quit() { struct socklist *servptr, *next; next = servlist; while (next != NULL) { servptr = next; next = servptr->next; close(servptr->sock); free((char *) servptr->plist[0]); free((char *) servptr); } close(listensock); exit(EXIT_SUCCESS); }