/****************************************************************************** * * Copyright 1989, 1990, Xylogics, Inc. ALL RIGHTS RESERVED. * * ALL RIGHTS RESERVED. Licensed Material - Property of Xylogics, Inc. * This software is made available solely pursuant to the terms of a * software license agreement which governs its use. * Unauthorized duplication, distribution or sale is strictly prohibited. * * Module Function: * * Select SRPC or ERPC interface, make a remote procedure call * * Original Author: Dave Harris Created on: April 4, 1988 * *****************************************************************************/ /* Include Files */ #include "../inc/config.h" #include "../inc/port/port.h" #include #ifndef _WIN32 #include #include #include #include #else #include "../inc/port/xuio.h" #include #endif #include "../libannex/api_if.h" #include #ifdef SYS_V #include #else #ifndef _WIN32 #include #endif #endif #include "../inc/courier/courier.h" #include "../inc/erpc/netadmp.h" #define INIT #include "netadm.h" #include "netadm_err.h" #include "../libannex/srpc.h" /* Defines and Macros */ #define INCOMING_COUNT 1 #define NRETRIES 2 #define KEEPALIVE 5 /* Structure Definitions */ typedef struct password_cache { struct password_cache *pc_link; UINT32 pc_iaddr; int pc_keepalive; KEYDATA *pc_key; } PWCACHE; /* External Declarations */ #ifndef _WIN32 extern char *malloc(); extern char *inet_ntoa(); extern UINT32 inet_addr(); #endif extern time_t time(); extern int debug; /* Forward Routine Declarations */ char *get_password(), *getname(); KEYDATA *default_password(); PWCACHE *search_password(); void no_password(), ask_password(), add_password(); int init_socket(); int fetch_key(); int erpc_callresp (); int verify_response(); int return_param(); int srpc_create(); int copy_iov(); int srpc_callresp(); void devttynoecho(); /* turn echo off */ void devttyecho(); /* turn echo on */ /* Static Declarations */ static PWCACHE *cache_head = (PWCACHE *)(0), *cache_tail = (PWCACHE *)(&cache_head); static KEYDATA master_table = { "" }; static char keyboard[KEYSZ + 4]; static char unknown[] = "unknown"; static char null_key[KEYSZ] = ""; static UINT32 pep; /* PEP protocol id number */ /* Keep alive socket information from rpc() call to rpc() call */ static int s = 0; /* socket file descriptor */ static int isockopen = 0; /* is socket open? */ static time_t lasttime = 0; /* time last used, if ever */ static int keepalive; /* understand sessions? */ static struct sockaddr_in sock_inet; /* copy because port is set */ static SRPC srpc; /* SRPC state structure */ #ifdef _WIN32 /* Cheezy version of fgets() that doesn't echo. Used for reading passwords.. */ char *no_echo_fgets(buffer, len, s) char *buffer; int len,s; { char *kb = buffer; while(--len) { if((*kb++ = getch()) == '\r') { *(kb - 1) = '\n'; break; } } *kb = '\0'; return(buffer); } #endif /* * rpc() * * Make a remote procedure call for netadm clients. * * Initialize a socket. Abort on error. * * Select ERPC or SPRC interface, The ERPC interface is selected if there * is no password for the Annex Pinet_addr * in the cache. If the call is rejected * with a CMJ_SRPC, the SRPC interface is * used. Password will be prompted if * no set_global_password() was called. * * Use selected interface(s) to make a remote procedure call: * * - Make ERPC procedure call. Bump pep id, setup iovec's, and make * and ERPC call to COURRPN_NETADM. The * response is check for validity, aborts * and rejects are mapped to NA codes. In * particular, NAE_SREJECT falls through * to make a SRPC call w/default password. * Two different reject codes (CMJ_SRPC, * CMJ_SESSION) identify an Annex as Beta * release R4.0.1-3, or Beta R4.0.4 +. * Parameters are converted according to * the type argument, into the *Pdata arg. * * - Make SPRC procedure call. Bump pep id. Set up SRPC key (xmt) * and call srpc_create requesting prog * COURRPN_NETADM proc RPROC_SRPC_OPEN, * keep SRPC session alive until another * Annex is accessed, or 5 sec's silence. * Note that srpc_create() sets up new * keys, handles, and sequence numbers. * Return NAE_ABORT if any error occurred. * Make SRPC call. Map reject or abort * errors to NA errors. If successful, * convert response according to selected * type argument, put data in *Pdata. * Return errors. Exception is NA_SREJECT * which means the password was incorrect. * Try null password (no encryption) next. * If that doesn't work, prompt for one. * * Return. All exception conditions jump here, * returning the appropriate error code. * A successful invocation also ends here * returning NAE_SUCCESS. */ rpc(Pinet_addr, procedure, nvecs, outgoing, Pdata, type) struct sockaddr_in *Pinet_addr; /* Inet address of Annex */ int procedure; /* remote procedure call nr */ int nvecs; /* number of outgoing iovecs */ struct iovec outgoing[]; /* iovecs for data to be sent */ char *Pdata; /* place to put return value */ u_short type; /* type of return value expected */ { int erpc_retcd, /* bytes returned by erpc_callresp */ srpc_retcd, /* return code from SRPC layer */ length, /* actual message return length */ argsize, /* total length of call arguments */ retry = 0, /* SRPC retry with prompted password */ tried_null = 0, /* has null password been tried? */ return_code; /* final return code for caller */ time_t now; /* time in seconds since "epoch" */ KEYDATA *key; /* encryption key table */ struct iovec incoming[INCOMING_COUNT + 1]; char args[MESSAGE_LENGTH]; char response[RESPONSE_SIZE]; char *answer; /* Check *Pinet_addr address family. */ if (Pinet_addr->sin_family != AF_INET) return NAE_ADDR; if (debug) printf("rpc.c/rpc: address %X, port %d.\n", Pinet_addr->sin_addr.s_addr,ntohs(Pinet_addr->sin_port)); /* * If time-stamp is recent enough, then a session already exists * between this copy of NA and the Annex in question. SRPC * may continue to use it, ERPC uses a new socket every time. * Beta release R4.0.1 thru R4.0.3 do not understand session, * and should have been detected above as non-keepalive conversations. */ now = time((time_t *)0); if(keepalive && (Pinet_addr->sin_addr.s_addr == sock_inet.sin_addr.s_addr) && lasttime && (lasttime >= (now - KEEPALIVE))) { goto direct_call; } if(isockopen) /* close the socket if one was open */ (void)close(s); isockopen = 0; /* I am NOT open */ if((return_code = init_socket(&s)) != NAE_SUCC) return return_code; isockopen = -1; /* I am open */ /* * In case the password is incorrect, a new session needs to be * established, so a new copy of the destination is used. This * is required because the Srpc interface modified the sin_port. */ again: lasttime = (time_t)0; /* reset keep alive time for new session */ /* Copy destination so I can modify sin_port at will */ bcopy((caddr_t)Pinet_addr, (caddr_t)&sock_inet, sizeof(sock_inet)); /* Decide on SRPC or ERPC interface */ if(fetch_key(&sock_inet, &key, &keepalive)) { pep++; incoming[1].iov_base = response; incoming[1].iov_len = sizeof(response); /* Call erpc_callresp() to communicate the request to the annex. */ if (debug) printf("rpc.c/rpc: Calling erpc_callresp.\n"); erpc_retcd = erpc_callresp(s, &sock_inet, pep, COURRPN_NETADM, NETADM_VERSION, (unsigned short)procedure, nvecs, outgoing, RPC_DELAY, TIMEOUT, INCOMING_COUNT, incoming,0); if (debug) printf("rpc.c/rpc: erpc_callresp returns %d.\n", erpc_retcd); /* Check for timeout from erpc_sendresp(). */ if(erpc_retcd == -1) { return_code = NAE_TIME; goto exit; } /* Verify that the response is not a rejection or an abortion. */ return_code = verify_response(response, erpc_retcd); if(return_code == NAE_SREJECT || return_code == NAE_SESSION) /* means SRPC interface required */ { if(return_code == NAE_SESSION) { keepalive = -1; } else { keepalive = 0; } key = default_password(&sock_inet, keepalive); goto use_srpc; } else if(return_code != NAE_SUCC) /* return error to caller */ { goto exit; } else { /* decode return arguments according to parameter type */ length = erpc_retcd - sizeof(CMRETURN); answer = &response[sizeof(CMRETURN)]; return_code = return_param(Pdata, type, answer, length); goto exit; } /* only NAE_SREJECT should fall through - use SRPC interface */ } else { use_srpc: pep++; /* bump pep id for every call */ if (!key || key->password[0] == '\0') tried_null = -1; if (debug) printf("rpc.c/rpc: Calling srpc_create.\n"); srpc_retcd = srpc_create(&srpc, s, &sock_inet, pep, COURRPN_NETADM, NETADM_VERSION, RPROC_SRPC_OPEN, key); if (debug) printf("rpc.c/rpc: srpc_create returns %d.\n",srpc_retcd); if (srpc_retcd != 0) { length = 4; goto chkerr; } /* * Now that a session is open, calls can be made. * A time stamp keeps the socket open for KEEPALIVE * seconds. An rpc() for the same Annex within KEEPALIVE * seconds jumps directly to here, and is in the same * SRPC session. Same UDP port and encryption is used. */ direct_call: pep++; if(nvecs) { argsize = copy_iov(&outgoing[1], nvecs, args + sizeof(SHDR), (sizeof(args) - sizeof(SHDR))); argsize += sizeof(SHDR); } else argsize = sizeof(SHDR); if (debug) { printf("rpc.c/rpc: Calling srpc_callresp.\n"); printf("\tProcedure %d.\n",procedure); } srpc_retcd = srpc_callresp(&srpc, s, &sock_inet, pep, COURRPN_NETADM, NETADM_VERSION, (unsigned short)procedure, args, argsize, RPC_DELAY, TIMEOUT, response, sizeof(response), &length); if (debug) printf("rpc.c/rpc: srpc_callresp returns %d.\n", srpc_retcd); chkerr: /* return decoded parameters, or NA error reason code */ switch (srpc_retcd) { case S_SUCCESS: if(keepalive) lasttime = time((time_t *)0); /* keep session alive! */ answer = &response[sizeof(SHDR)]; return_code = return_param(Pdata, type, answer, length); break; case S_REJECTED: if (debug) printf("rpc.c/rpc: S_REJECTED, reason code %d.\n", length); if (length < 0 || length > MAX_DETAIL) { printf("\tOut of range 0 to %d.\n",MAX_DETAIL); return_code = NAE_REJ; } else return_code = details[length]; break; case S_ABORTED: if(length < 0 || length > MAX_ERROR) return_code = NAE_ABT; else return_code = errors[length]; break; case S_TIMEDOUT: return_code = NAE_TIME; break; default: return_code = NAE_SABORT; } if(srpc_retcd != S_SUCCESS) keepalive = 0; /* all errors require a new session */ /* * Password did not work, try another one! * First, if a null password has not yet been tried, do so! * Next time through, ask the user (/dev/tty) for one * Ask the user again, unless NRETRIES exceeded */ if (return_code == NAE_SREJECT && retry < NRETRIES) { if (debug) printf("rpc.c/rpc: Password didn't work.\n"); if (tried_null) { if (debug) printf("rpc.c/rpc: Retrying with password.\n"); retry++; ask_password(&sock_inet); } else { if (debug) printf("rpc.c/rpc: Retrying without password.\n"); no_password(&sock_inet); /* unencrypted */ } goto again; } } exit: if (debug) printf("rpc.c/rpc: returning with code %d.\n",return_code); return return_code; } /* rpc() */ /* * Copy from an IO vector into a finite contiguous memory space */ copy_iov(iovector, nvecs, args, maxargs) struct iovec iovector[]; /* array of IO vector entries */ int nvecs; /* number of IO vector entries */ char *args; /* contiguous destination */ int maxargs; /* size of destination, to be respected */ { int totalsize, i, length, space; space = maxargs; for(i = 0, totalsize = 0 ; i < nvecs && space; i++) { length = iovector[i].iov_len; if(length > space) length = space; /* can't copy into outer space */ bcopy(iovector[i].iov_base, args, length); args += length; /* point to next available byte */ space -= length; /* reduce amount of space left */ totalsize += length; /* increase amount copied */ } return totalsize; /* length of data actually copied */ } /* * Initialize encryption tables for the global (default) password */ void set_global_password(string) char *string; /* string to become global passwd */ { (void)make_table(string, &master_table); return; } /* * Fetch Annex password encryption tables from cache, or return "error" * -1 means no cache entry, client will use erpc first */ fetch_key(Pinet, key, keepalive) struct sockaddr_in *Pinet; /* points to inet address */ KEYDATA **key; /* ptr to ptr to crypt data */ int *keepalive; /* true if Annex handles sessions */ { PWCACHE *cachentry; cachentry = search_password(Pinet); if(cachentry == (PWCACHE *)0) { *key = (KEYDATA *)0; *keepalive = 0; return -1; } else { *key = cachentry->pc_key; *keepalive = cachentry->pc_keepalive; return 0; } } /* * Return a pointer to Annex password cache entry */ PWCACHE * search_password(Pinet) struct sockaddr_in *Pinet; /* points to inet address */ { PWCACHE *link; link = cache_head; while(link && link->pc_iaddr != Pinet->sin_addr.s_addr) link = link->pc_link; return link; } /* * Assign the default password encryption tables to a specific Annex * Also keep track of wether or not this Annex understands SRPC sessions */ KEYDATA * default_password(Pinet, keepalive) struct sockaddr_in *Pinet; /* points to inet address */ int keepalive; /* does annex understand sessions? */ { /* add the global password to the password cache */ add_password(Pinet, &master_table, keepalive); /* calculate encryption key and return */ return &master_table; } /* * Make a dummy encryption table with no password for an Annex * This function will change an existing password cache entry */ void no_password(Pinet) struct sockaddr_in *Pinet; /* points to inet address */ { PWCACHE *pwcent; pwcent = search_password(Pinet); if(!pwcent) /* cache entry is KNOWN to exist */ return; pwcent->pc_key = (KEYDATA *)0; return; } /* * Make an Annex password encryption table based on password query * This function will change an existing password cache entry */ void ask_password(Pinet) /* prompt for annex password */ struct sockaddr_in *Pinet; /* points to inet address */ { char *password; PWCACHE *pwcent; KEYDATA *key; password = get_password(&Pinet->sin_addr); pwcent = search_password(Pinet); if(!pwcent) return; /* cache entry is KNOWN to exist */ /* * If last encryption table in use was the master table, or if * no encryption (null pointer), allocate a new table */ if(pwcent->pc_key == &master_table) pwcent->pc_key = (KEYDATA *)0; key = make_table(password, pwcent->pc_key); pwcent->pc_key = key; return; } /* * Add a password cache entry for a given Annex with supplied passwd table */ void add_password(Pinet, keyinfo, keepalive) struct sockaddr_in *Pinet; /* ptr to address (inet, port) */ KEYDATA *keyinfo; /* ptr to encryption tables */ int keepalive; /* Annex understands sessions? */ { PWCACHE *new_entry; /* allocate a password cache entry, or return if no memory */ new_entry = (PWCACHE *)malloc(sizeof(PWCACHE)); if(new_entry == (PWCACHE *)0) { printf("Warning: password not cached due to ENOMEM\n"); return; } /* fill in entry with pertinent details, prompt for password */ new_entry->pc_iaddr = Pinet->sin_addr.s_addr; new_entry->pc_link = (PWCACHE *)0; new_entry->pc_keepalive = keepalive; new_entry->pc_key = keyinfo; /* link entry into list, declare entry as the new tail, return */ cache_tail->pc_link = new_entry; cache_tail = new_entry; return; } /* * Functions for password prompting, turning echo off and on (/dev/tty) */ static FILE *rfp; /* read file desc for /dev/tty */ static FILE *wfp; /* write file desc for /dev/tty */ static int isopen = 0; /* flag set on first call */ char * get_password(iaddr) /* prompt for a password from /dev/tty */ struct in_addr *iaddr; { char *p, /* pointer for parsing response */ *host, /* name of host, or unknown */ *internet; /* internet address in dot notation */ char *kb = keyboard; int len; if(!isopen) { isopen = -1; #ifdef _WIN32 rfp = stdin; wfp = stdout; #else rfp = fopen("/dev/tty", "r"); wfp = fopen("/dev/tty", "w"); #endif } if(!iaddr) { fprintf(wfp, "Password: "); } else { internet = inet_ntoa(*iaddr); host = getname((char *)iaddr); fprintf(wfp, "Password for %s <%s>: ", internet, host); } (void)fflush(wfp); devttynoecho(); #ifdef _WIN32 #define fgets no_echo_fgets #endif len = 0; while (len == 0) { bzero(keyboard, KEYSZ+4); (void)fgets(kb, KEYSZ + 2, rfp); len = strlen(kb); } devttyecho(); #ifdef _WIN32 #undef fgets #endif fprintf(wfp, "\n"); (void)fflush(wfp); keyboard[KEYSZ - 1] = '\0'; /* strip newline, replaced with a null */ for(p = keyboard; *p != '\0'; p++) if(*p == '\n') { *p = '\0'; break; } return keyboard; } void devttynoecho() /* turn echo off */ { #ifndef _WIN32 #ifdef SYS_V struct termio tio; #else struct sgttyb sg; #endif int fd; if(!isopen) return; fd = fileno(rfp); #ifdef SYS_V (void)ioctl(fd, (int)TCGETA, &tio); tio.c_lflag &= ~(ECHO); (void)ioctl(fd, (int)TCSETA, &tio); #else (void)ioctl(fd, (int)TIOCGETP, (char *)&sg); sg.sg_flags &= ~(ECHO); (void)ioctl(fd, (int)TIOCSETP, (char *)&sg); #endif #endif /* _WIN32 */ return; } void devttyecho() /* turn echo on */ { #ifndef _WIN32 #ifdef SYS_V struct termio tio; #else struct sgttyb sg; #endif int fd; if(!isopen) return; fd = fileno(rfp); #ifdef SYS_V (void)ioctl(fd, (int)TCGETA, &tio); tio.c_lflag |= ECHO; (void)ioctl(fd, (int)TCSETA, &tio); #else (void)ioctl(fd, (int)TIOCGETP, (char *)&sg); sg.sg_flags |= ECHO; (void)ioctl(fd, (int)TIOCSETP, (char *)&sg); #endif #endif return; } /* * fetch hostname from /etc/hosts, given inet address */ char * getname(address) char *address; { #ifdef need_netdb return unknown; #else struct hostent *hostdata; /* host data, name, etc. */ hostdata = gethostbyaddr(address, sizeof(struct in_addr), AF_INET); if(hostdata == (struct hostent *)0) return unknown; else return hostdata->h_name; #endif }