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
.exe
file. 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 exec
ing 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 portportnumber
aslistenfd
> for (;;) { <listen for a connection, store it incommunfd
> if (fork()) Sock_close(communfd, NULL); else { <exec thecvs 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 portportnumber
aslistenfd
>= (<-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 select
s 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