rcmd access from outside the local domain, we do not run
Kerberos on our workstations, and I have not found a reliable ssh
for Win95 (and haven't looked for MacOS), so I am pretty much
constrained to use pserver, at least from home. There are two
issues I need to deal with: accessing the pserver from the
clients, and running the server on the host.
pserver and rcmd authentication. I have not tested
either extensively, but I think I will be using MacCVS as it seems a
little easier with this one to get down to raw CVS. Unfortunately I
believe they use incompatible formats for storing the CVS information.
The other client is the NT port of the standard command line CVS 1.10 available from Cyclic. This almost works, except for two problems:
rcmd server, if there is an error in a cvs
command, it doesn't properly close the socket. As a result, you get
an error message of the form
...: cannot bind to socket: Invalid argumentwith your next CVS operation. You can cure this by restarting Windows. I suspect there are less radical solutions, but I don't know of any at this time---if anyone does, please let me know. So as long as you don't make mistakes, you are OK.
pserver it complains about not being able
to open a connection to port 0.
WSACleanup or some such on exit, but I'm not
sure exactly where this should go, how you make sure sockets have been
used, and so on.
The second problem turns out to be easy to fix. It turns out that a
call to getservbyname is returning a non-null value with a zero
port. This can be fixed by changing a test in
src/client.c/auth_server_port_numberfrom
if (s)to
if (s && s->s_port != 0)I did this, recompiled with VC++ 5.0, and the result worked. To use this modified version, get the standard distribution from Cyclic and then get and unzip the new
.exefile. If for some reason you want
to build the thing yourself, I have project files I ended up with
available---I had to do a very minor bit of
diddling, including adding an empty getwd.c file (I suppose I
could have just dropped it from the project).
Error aborts in client calls to pserver also cause problems but
they are a little less serious. The client does not shut down the
connection, so it stays in the ESTABLISHED state. If the server
shuts down, the connection goes into the FIN_WAIT_2 state
(netstat -an shows this). The server cannot be restarted until
this is cleared (e.g. by shutting down Windows). I suspect this would
be cured by a call to WSACleanup at the appropriate place.
So the bottom line is that, after some fiddling, pserver access
seems to work from Win95 as well. It might work with the original
executable for some WinSock implementations.
pserver is to run it from
inetd by editing /etc/inetd.conf and maybe also
/etc/services. I did not want to do this since I didn't want to
use superuser access and also didn't want the server running all the
time for security reasons. Instead, I wrote a little server that is
essentially like a primitive, specialized version of inetd that
only provides this one service. There are no bells and whistles for
daemonization or the like.
cvs pserver. To use this,
compile it with something like
<compilation command>= gcc -o cvsserv serv.o sock.o
<run command>= cvsserv root
where root is the CVS root you want to make available, and log
into it from the client. After the client is done, just kill the
server.
pserver port, 2401. It then loops,
listening for requests and execing the cvs pserver command for
each request it receives.
<main function>= (U->)
void main(int argc, char *argv[])
{
Sock_port_t portnumber = 2401; /* CVS pserver default port */
int listenfd, communfd;
char remote[MAX_CANON];
<initialize the server>
<open a socket on port portnumber as listenfd>
for (;;) {
<listen for a connection, store it in communfd>
if (fork())
Sock_close(communfd, NULL);
else {
<exec the cvs pserver command>
}
}
}
The initialization code checks that the root argument is supplied and initializes the socket library.
<initialize the server>= (<-U)
if (argc != 2) {
fprintf(stderr, "Usage: %s cvsroot\n", argv[0]);
exit(1);
}
if (Sock_init() != 0) {
fprintf(stderr, "Sock initialization failed");
exit(1);
}
Then a listening socket is opened on the standard port.
<open a socket on portportnumberaslistenfd>= (<-U) if ((listenfd = Sock_open(portnumber, NULL)) < 0) { perror("Unable to establish a port connection"); exit(1); }
The code to listen for a connection prints a message to stderr
when a connection is made.
<listen for a connection, store it in communfd>= (<-U)
if ((communfd = Sock_listen(listenfd, remote, MAX_CANON, NULL)) < 0) {
perror("Failure to listen on server");
exit(1);
}
fprintf(stderr, "Connection has been made to %s\n", remote);
The code to exec the CVS command assumes that the --allow-root
directive is available (and required) as it is, I think, from 1.9.10
on. For earlier CVS versions you can define NO_ALLOW_ROOT to
disable use of this directive. execlp is used, so the cvs
command is looked up in the PATH.
<exec the cvs pserver command>= (<-U)
#ifndef NO_ALLOW_ROOT
char root[1000];
sprintf(root, "--allow-root=%s", argv[1]);
#endif
Sock_close(listenfd, NULL);
dup2(communfd, 0);
dup2(communfd, 1);
dup2(communfd, 2);
#ifndef NO_ALLOW_ROOT
execlp("cvs", "cvs", root, "pserver", NULL);
#else
execlp("cvs", "cvs", "pserver", NULL);
#endif
<serv.c>= #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <limits.h> #include <signal.h> #include <sys/types.h> #include "sock.h" #define BLKSIZE 1024 <main function>
<sock.h>=
typedef unsigned short Sock_port_t;
typedef struct Sock_error_t {
int error;
int h_error;
} *Sock_error_t;
int Sock_init(void);
int Sock_open(Sock_port_t port, Sock_error_t perr);
int Sock_listen(int fd, char *cname, int buflen, Sock_error_t perr);
int Sock_connect(Sock_port_t port, char *sname, Sock_error_t perr);
int Sock_close(int fd, Sock_error_t perr);
ssize_t Sock_read(int fd, void *buf, size_t nbytes, Sock_error_t perr);
ssize_t Sock_write(int fd, void *buf, size_t nbytes, Sock_error_t perr);
Sock_init must be called to initialize the system. It returns zero
on successful completion and nonzero on error. All other functions
return error information in a struct Sock_error_t structure if the
supplied pointer is not NULL.
Sock_open return a file descriptor which is bound to the given
port. Returns -1 on error with details in perr.
Sock_listen is used by a server to listen for connections on the
specified port. It blocks until a connection request is received;
then it returns the communication file descriptor or -1 on error.
cname should be a string buffer of length at least buflen; the
name of the connecting client is returned in this buffer.
Sock_connect is used by a client to initiate communication with a
remote server sname on port. returns the file descriptor used
for communication or -1 if error.
Sock_close close communication for the given file descriptor
fd. A negative value indicates an error occurred.
Sock_read attempts to read nbytes into buf from a file
descriptor fd opened by Sock_listen or Sock_connect. A
negative value indicates an error occurred. Otherwise, the number of
bytes read is returned
Sock_write attempts to write nbytes from buf to the file
descriptor fd opened by Sock_listen or Sock_connect. A
negative value indicates an error occurred. Otherwise, the number of
bytes written is returned
Sock_init sets the SIGPIPE handler to SIG_IGN it it is
currently SIG_DFL. The need for this is explained in
[cite robbins96:_pract_unix_progr, p. 449]. **** I'm not sure I
understand this. **** explain winsock stuff **** explain sigpipe stuff
**** should WSACleanup be called someplace?
<public functions>= (U->) [D->]
int Sock_init()
{
#ifdef _Windows
WSADATA wsaData;
WORD wVers = MAKEWORD(1, 1);
if (WSAStartup(wVers, &wsaData) != 0)
return 1;
#endif
#ifdef DODO/*SIGPIPE*/
struct sigaction act;
if (sigaction(SIGPIPE, (struct sigaction *)NULL, &act) < 0)
return 1;
if (act.sa_handler == SIG_DFL) {
act.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &act, (struct sigaction *)NULL) < 0)
return 1;
}
#endif
return 0;
}
The function Sock_error collects the error information into the
error structure and prepares the return value.
<private functions>= (U->)
static int Sock_error(Sock_error_t perr, int e, int he)
{
if (perr != NULL) {
perr->error = e;
perr->h_error = he;
}
return -1;
}
Sock_open creates a socket, binds and listens on it. The backlog
maximum is set with the MAXBACKLOG macro.
<public functions>+= (U->) [<-D->]
int Sock_open(Sock_port_t port, Sock_error_t perr)
{
int sock;
struct sockaddr_in server;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return Sock_error(perr, errno, 0); /**** need lock */
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY; /**** need to convert byte order??*/
server.sin_port = htons((short)port);
if ((bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) ||
(listen(sock, MAXBACKLOG) < 0))
return Sock_error(perr, errno, 0); /**** need lock */
return sock;
}
<macros>= (U->) #define MAXBACKLOG 5
Sock_listen waits for read input on fd and then calls
accept. This is done in a loop in case of interrupts by signals
(**** I think). The WWW library uses another outer loop after making
fd non-blocking -- don't know if that is really needed. After
accept completes, the host name is retrieved. **** This should use
a lock or the thread-safe variant of gethostbyaddr. **** Also, the
host lookup should be made non-blocking if possible.
<public functions>+= (U->) [<-D->]
int Sock_listen(int fd, char *cname, int buflen, Sock_error_t perr)
{
struct sockaddr_in net_client;
int len = sizeof(struct sockaddr);
int retval;
struct hostent *hostptr;
do retval = accept(fd, (struct sockaddr *)(&net_client), &len);
while (retval == -1 && errno == EINTR);
if (retval == -1) return Sock_error(perr, errno, 0);
if (cname != NULL && buflen > 0) {
size_t nlen;
char *name;
struct in_addr *iaddr = &(net_client.sin_addr);
hostptr = gethostbyaddr((char *)iaddr, sizeof(struct in_addr), AF_INET);
name = (hostptr == NULL) ? "unknown" : hostptr->h_name;
nlen = strlen(name);
if (buflen < nlen + 1)
nlen = buflen - 1;
strncpy(cname, name, nlen);
cname[nlen] = 0;
}
return retval;
}
Sock_connect looks up the host address and then calls connect,
again in a loop in case of signal interrupts (**** I think). **** This
is blocking right now. The non-blocking version Motif and WWW lib use
makes the fd non-blocking and then selects for read (not
write). **** Again gethostbyname should be made thread-safe and
non-blocking if possible. **** I assume it is OK to close the socket
on error, but this should be checked.
<public functions>+= (U->) [<-D->]
int Sock_connect(Sock_port_t port, char *sname, Sock_error_t perr)
{
struct sockaddr_in server;
struct hostent *hp;
int sock;
int retval;
if (! (hp = gethostbyname(sname))
|| (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return Sock_error(perr, errno, h_errno); /**** need lock */
memcpy((char *)&server.sin_addr, hp->h_addr_list[0], hp->h_length);
server.sin_port = htons((short)port);
server.sin_family = AF_INET;
do retval = connect(sock, (struct sockaddr *) &server, sizeof(server));
while (retval == -1 && errno == EINTR);
if (retval == -1) {
Sock_error(perr, errno, 0);
close(sock);
return -1;
}
return sock;
}
The remaining three routines are pretty straightforward.
<public functions>+= (U->) [<-D]
int Sock_close(int fd, Sock_error_t perr)
{
if (close(fd) < 0)
return Sock_error(perr, errno, 0); /**** need lock */
else
return 0;
}
ssize_t Sock_read(int fd, void *buf, size_t size, Sock_error_t perr)
{
ssize_t retval;
do retval = recv(fd, buf, size, 0);
while (retval == -1 && errno == EINTR);
if (retval == -1) return Sock_error(perr, errno, 0);
else return retval;
}
ssize_t Sock_write(int fd, void *buf, size_t size, Sock_error_t perr)
{
ssize_t retval;
do retval = send(fd, buf, size, 0);
while (retval == -1 && errno == EINTR);
if (retval == -1) return Sock_error(perr, errno, 0);
else return retval;
}
<sock.c>= #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <signal.h> #include <errno.h> #ifdef _Windows #include <winsock.h> #else #include <netdb.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #endif #include "sock.h" extern int h_errno; /**** HP-UX 9.05 forgets to define this in netdb.h*/ <macros> <private functions> <public functions>
CVS passwd file, you can use
something like this:
<cr.c>=
#include <unistd.h>
#include <stdio.h>
void main(int argc, char **argv)
{
printf("%s\n", crypt(argv[1], "Az"));
}
[1] Kay A. Robbins and Steven Robbins. Practical UNIX Programming. Prentice Hall, Upper Saddle River, NJ, 1996.
cvs pserver command>: U1, D2
communfd>: U1, D2
portnumber as listenfd>: U1, D2