/* * p4filter.c: A short program to modify and interfere with the Perforce * protocol * David Maze <dmaze@akamai.com> * * $Header$ */ #include <config.h> #include "netjunk.h" #include "packet.h" #include "pair.h" #include "protocol.h" #include "util.h" #include <errno.h> #include <malloc.h> #include <netdb.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> /* Correct things that might be missing. */ #ifndef INADDR_NONE #define INADDR_NONE ((uint32_t)0xffffffff) #endif /* * GLOBAL DATA * * We need to know: * -- The file descriptors attached to the child process. These are the * child's standard input and output, respectively (so we write to * child_in and read from child_out). * -- What sort of child it is, so we can clean up properly. * * The first is in a struct protocol_data (see protocol.h); save the * second here. */ enum childtype { PROCESS, SOCKET } child_type; /* * FUNCTION PROTOTYPES */ /* Prints a usage message to standard error. */ void usage(void); /* Parses p4host as one might see a P4HOST environment variable. Either * spawns a child or creates an appropriate socket, setting the child_* * variables appropriately. exit()s on error. */ void parse_p4host(const char *p4host, struct protocol_data *setup); /* Sets up running a local command as a child. */ void p4host_is_command(const char *cmd, struct protocol_data *setup); /* Sets up a network connection to a Perforce server. If hostname is NULL, * use the local machine. */ void p4host_with_port(const char *hostname, const char *portnum, struct protocol_data *setup); /* Given a valid setup, clean everything up since we appear to be done. */ void clean_up(struct protocol_data *setup); /* * INVOCATION * * We expect to get run with a valid Perforce host specification and a * user name as command-line parameters. The host specification can be * a port number (interpreted as being on 'localhost'), "rsh:command", * in which case a pipe to that command will happen, or "host:port". * We'll also understand options like --verbose and --help to print * protocol information and a usage message, respectively. */ int main(int argc, char **argv) { char *p4host, *newuser; int i; int j = 0; struct protocol_data setup; /* Defaults for global options */ verbose = 0; /* Read command-line arguments. Skip argv[0], since that's our * program name. */ for (i = 1; i < argc; i++) { /* Is this --verbose? */ if (!strcmp(argv[i], "--verbose")) verbose = 1; /* Is this --help? */ else if (!strcmp(argv[i], "--help")) { verbose = 1; usage(); return 0; } /* Is this --debug? Idle until a debugger kicks us. */ else if (!strcmp(argv[i], "--debug")) { volatile int q = 1; errmsg("pid is %d\n", getpid()); while (q) ; } /* Is this some other -- option? */ else if (!strncmp(argv[i], "--", 2)) { errmsg("Unrecognized option on command line\n"); usage(); return 1; } /* Not an option. */ else if (j == 0) { j = 1; p4host = argv[i]; } else if (j == 1) { j = 2; newuser = argv[i]; } else { errmsg("Too many command line options\n"); usage(); return 1; } } /* If j isn't 2 here, we've read too few options. */ if (j < 2) { errmsg("Too few command line options\n"); usage(); return 1; } /* The client is on our standard input and output. */ setup.client_in = 1; /* Notice: client's input is our output. */ setup.client_out = 0; /* And vice versa. */ setup.user = newuser; /* At this point, we have valid options. Yay. Try to process the * host specification to get child filehandles. */ parse_p4host(p4host, &setup); /* Speak the protocol. */ do_protocol(&setup); /* Clean up. */ clean_up(&setup); return 0; } void usage(void) { errmsg("p4filter: monitor or modify a Perforce data stream Usage: p4filter [--verbose] [--help] p4host user --help: print this message --verbose: print the data stream as it goes by p4host: Perforce host specification for the actual Perforce server user: New username to put in the stream Note that standard input and output should be connected to a Perforce client, and that any actual messages will be printed to standard error. Nothing is printed if neither --verbose nor --help are specified. "); } void parse_p4host(const char *p4host, struct protocol_data *setup) { const char *colon; char *hostname; char *portnum; unsigned len; /* Look for a colon in the host specifier. */ colon = strchr(p4host, ':'); /* Is there a colon? */ if (colon) { /* Yes, extract the bit before as the host name... */ len = colon - p4host; hostname = malloc(len + 1); strncpy(hostname, p4host, len); /* ...and the bit after as the port number. */ portnum = strdup(colon + 1); } else { /* No colon; everything is a port number. */ hostname = NULL; portnum = strdup(p4host); } /* Is this a command-type thingy? */ if (hostname && !strcmp(hostname, "rsh")) { p4host_is_command(portnum, setup); } else { p4host_with_port(hostname, portnum, setup); } } void p4host_is_command(const char *cmd, struct protocol_data *setup) { /* So, here's the plan: create a pair of sockets from ourselves to * ourselves. fork(). In the child, exec() the command. */ /* In each of these pairs, [0] is the reader and [1] is the writer. */ int in_fd[2], out_fd[2]; int childpid; /* Create socket pairs for standard input and output. */ if (pipe(in_fd) != 0) { errmsg("pipe(in_fd) failed: %s\n", strerror(errno)); exit(1); } if (pipe(out_fd) != 0) { errmsg("pipe(out_fd) failed: %s\n", strerror(errno)); exit(1); } /* Now, we can talk to the child. Fork. */ childpid = fork(); /* Error? */ if (childpid < 0) { errmsg("fork() failed: %s\n", strerror(errno)); exit(1); } /* Are we the child? */ if (childpid == 0) { /* Right. We will read from out input and write to our output. * Close down the other end of those sockets. */ close(in_fd[1]); /* writer of input */ close(out_fd[0]); /* reader of output */ /* Tweak file descriptors. We want standard input (0) to go to * the input reader, and standard output (1) to go to the output * writer. dup2() the right fd's on to stdin/out, and close what's * left of the pipes. */ if (in_fd[0] != 0) { if (dup2(in_fd[0], 0) < 0) errmsg("dup2(%d, 0) failed: %s\n", in_fd[0], strerror(errno)); close(in_fd[0]); } if (out_fd[1] != 1) { if (dup2(out_fd[1], 1) < 0) errmsg("dup2(%d, 1) failed: %s\n", out_fd[1], strerror(errno)); close(out_fd[1]); } /* Now try to exec() the child. This shouldn't return. */ execl("/bin/sh", "sh", "-c", cmd, NULL); /* Umm, oops. That failed. That's no good. We can still print * error messages since our stderr didn't change. */ errmsg("exec() failed: %s\n", strerror(errno)); exit(127); } /* Okay, we're the parent. Close down the input reader and the output * writer, since those belong to the child. */ close(in_fd[0]); close(out_fd[1]); /* Save the other file descriptors as the global child input/output. */ setup->server_in = in_fd[1]; setup->server_out = out_fd[0]; child_type = PROCESS; /* All done. Go on with life. */ return; } void p4host_with_port(const char *hostname, const char *portnum, struct protocol_data *setup) { int fd; uint32_t inaddr; /* autoconf test for in_addr_t? */ struct hostent *he; struct sockaddr_in sa; struct in_addr **ia; long port; char *endptr; /* Try to figure out the port number. */ port = strtol(portnum, &endptr, 0); if (*portnum && *endptr) /* The string began with something non-null, * but the last valid digit wasn't at the end * of the string */ { errmsg("Invalid port %s\n", portnum); exit(1); } /* Try to figure out the hostname. */ /* If we don't have one, default to localhost. */ if (!hostname) hostname = "127.0.0.1"; /* Is it a dotted quad address? */ inaddr = inet_addr(hostname); if (inaddr == INADDR_NONE) { /* Not a dotted quad address. Try using the resolver. */ he = gethostbyname(hostname); /* If that failed too, bail. */ if (!he) { errmsg("gethostbyname() failed: %s\n", hstrerror(h_errno)); exit(1); } /* Confirm that we got an IPv4 address. */ if (he->h_addrtype != AF_INET) { errmsg("Specified host doesn't have an IPv4 address\n"); exit(1); } /* Fail immediately if there aren't any addresses. */ if (!he->h_addr_list || !*(he->h_addr_list)) { errmsg("Specified host has no addresses\n"); exit(1); } /* Try to connect to each address in turn. */ for (ia = (struct in_addr **)he->h_addr_list; *ia; ia++) { /* Create a socket. */ fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { errmsg("socket() failed: %s\n", strerror(errno)); exit(1); } /* Populate the socket address. */ memset(&sa, '\0', sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); memcpy(&sa.sin_addr, *ia, sizeof(struct in_addr)); /* Try to connect. */ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) break; /* We win! */ /* Nope, we lost, go back and try the next address. */ errmsg("connect() failed: %s\n", strerror(errno)); } /* Did we fail completely? */ if (!*ia) { errmsg("All addresses for specified host failed\n"); exit(1); } /* We're connected now. */ } else { /* We do have a dotted quad address. Try connecting directly. */ fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { errmsg("socket() failed: %s\n", strerror(errno)); exit(1); } /* Populate the socket address. */ memset(&sa, '\0', sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); memcpy(&sa.sin_addr, &inaddr, sizeof(inaddr)); /* Try to connect. */ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { errmsg("connect() failed: %s\n", strerror(errno)); exit(1); } } /* At this point, given that we haven't exit()ed, we now have * fd as a socket connection to the requested host. */ setup->server_in = fd; setup->server_out = fd; child_type = SOCKET; return; } void clean_up(struct protocol_data *setup) { /* What sort of connection were we? */ switch (child_type) { case SOCKET: /* Close the socket filehandle(s). */ close(setup->server_in); if (setup->server_in != setup->server_out) close(setup->server_out); break; case PROCESS: /* Close the child's filehandle(s). */ close(setup->server_in); if (setup->server_in != setup->server_out) close(setup->server_out); /* Wait for the child to exit. */ wait(NULL); /* Don't catch errors */ break; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 450 | sandy_currier |
Initial import of p4filter code. This contains a solaris2.6 binary but no others. |