Using CVS Clients form MacOS and Win95

Luke Tierney
1998/09/02

Introduction

This document outlines how I am using CVS from MacOS and from Win95. My repository is on my UNIX workstation, and my Mac and Windows development systems are a PowerBook 3400. I can bring the PowerBook to my office, where it runs in the local domain, but I also want to be able to use it from home, which is outside the local domain. We do not allow 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.

Accessing the Server

Accessing the Server from MacOS

I tried two clients, MacCVS Pro and and MacCVS. Both seem to handle 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.

Accessing the Server from Win95

I tried two clients. One, WinCVS, is the Windows version of MacCVS. It looks nice, but unfortunately on my system it just complains that it can't initialize WinSock when I try to do something with it.

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:

Fixing the first problem is beyond me. In principle I think what is needed is a call to 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_number
from
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.

The CVS Server

The recommended method of running 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.

Using the Server

This is a simple front for running cvs pserver. To use this, compile it with something like

<compilation command>=
gcc -o cvsserv serv.o sock.o

Then just run it as

<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.

Server Implementation

The main function initializes the socket library and opens a connection to the standard 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 port portnumber as listenfd>= (<-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>

Simple Socket Module

This is a simple communications module derived from the socket implementation of UIUC in [cite robbins96:_pract_unix_progr, Program B.4].

Interface

<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

Implementation

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>

Passwords

To generate passwords for a 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"));
}

References

[1] Kay A. Robbins and Steven Robbins. Practical UNIX Programming. Prentice Hall, Upper Saddle River, NJ, 1996.