/* * protocol.c: Deal with the Perforce protocol. * $Header$ */ #include <config.h> #include "netjunk.h" #include "protocol.h" #include "util.h" #include <errno.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/time.h> #include <sys/types.h> /* Where are packets coming from? This gives us lots of information: we * always want to read packets from that side's output, and write to * that side's input. */ typedef enum origin { CLIENT, SERVER } origin; /* Synthesize interactions with both the client and the server when * authorization fails. */ static void auth_failed(const struct protocol_data *setup, pair *pr); /* Wait for incoming data via select(). Returns which side has ready * data. */ static origin wait_for_incoming(const struct protocol_data *setup); /* Returns the amount of space a pair would require in the stream. */ static size_t pair_len(const pair *p); /* Prints detail on a pair list. */ static void print_pair_info(enum origin or, const pair *pr); /* Read an entire packet from the specified origin. */ static packet *read_packet_from(const struct protocol_data *setup, origin or); /* Write an entire packet to the *other* location. If origin is CLIENT, * send a packet to the server. */ static int write_packet_from(const struct protocol_data *setup, origin or, const packet *pk); /* Like the previous two, but convert pairs to/from packets in between. */ static pair *read_pairs_from(const struct protocol_data *setup, origin or); static int write_pairs_from(const struct protocol_data *setup, origin or, const pair *pr); /* The protocol is documented in some detail in the header file. */ void do_protocol(struct protocol_data *setup) { enum origin or; pair *pr, *userpr; packet *pk; int userok = 0; /* Have we validated the user yet? */ /* Send packets back and forth until we get a client-origin packet * with a user key. */ while (1) { /* Wait for somebody to have data, and note who. */ or = wait_for_incoming(setup); /* Read a packet. */ pk = read_packet_from(setup, or); /* If that failed, we're probably done. */ if (!pk) return; /* Look for a user key in a client packet. The protocol begins with * client and server both sending protocol version information. * The client then sends a packet with information about the * local environment (client name, machine, os, and so forth) * with the user name and request. We want to look for this packet; * it will be the first (and probably only, though I don't know * of such a guarantee) packet with a "user" key. If that matches * the user we're expecting, go on with our life (with userok set to * 1). Otherwise, synthesize interactions with both the client * and the server in auth_failed(). */ /* Is this a client-origin packet? */ if (!userok && or == CLIENT) { /* Decompose the packet. */ pr = packet_to_pair(pk); /* Okay, does it have a user key? */ userpr = pair_with_key(pr, "user"); if (userpr) { /* Does the username match the one we're expecting? */ if (!strncmp(userpr->data, setup->user, userpr->len)) userok = 1; /* And go on with life */ else { auth_failed(setup, pr); return; } } /* If there isn't a user key, just go on. */ } /* To improve performance, we're not taking apart packets. This * means we can't print out their contents trivially. */ /* print_pair_info(or, pr); */ /* That was fun. Write a packet. */ write_packet_from(setup, or, pk); free_packet(pk); } } static void auth_failed(const struct protocol_data *setup, pair *pr) { /* Okay. At this point, we have a client-origin packet, decomposed * in pr, where the user field doesn't match what we're expecting. * We want to completely separate the client interaction from the * server at this point. To the client, respond with a packet with * an "authorization denied" message in the data field and * "client-OutputError" in the func field. Then immediately send * a second packet with only a func field containing "release". * This ends the protocol; ignore anything else the client sends. * To the server, send the original pair, but with the command * changed to "user-help". Listen to and ignore packets it sends * back until it sends one with the func "release"; respond with * a packet with only func "release2", and stop. */ pair *newpr, *firstpr, *prin; origin or; int client_done; const char *error_message = "You are not authorized to access Perforce as this user.\n"; const char *user_help_key = "user-help"; const char *client_error_key = "client-OutputError"; const char *release_key = "release"; const char *release2_key = "release2"; errmsg("User authorization failed. Ignoring original request.\n"); /* Start by tweaking the packet we're sending to the server. */ newpr = pair_with_key(pr, "func"); /* If there isn't a func key, I think we're Really Doomed (TM). * Just ignore that case. */ if (newpr) set_pair_data(newpr, strlen(user_help_key), user_help_key); /* Send the modified packet on to the server (or, away from its * client origin). */ print_pair_info(CLIENT, pr); write_pairs_from(setup, CLIENT, pr); /* Synthesize the response to the client. */ firstpr = new_pair("data", strlen(error_message), error_message); newpr = new_pair("func", strlen(client_error_key), client_error_key); firstpr->next = newpr; print_pair_info(SERVER, firstpr); write_pairs_from(setup, SERVER, firstpr); free_pair(firstpr); /* Synthesize the "release" packet to the client. */ firstpr = new_pair("func", strlen(release_key), release_key); print_pair_info(SERVER, firstpr); write_pairs_from(setup, SERVER, firstpr); free_pair(firstpr); /* Okay, now go back into packet-looping mode. */ client_done = 0; /* Note that the Perforce client will just die on us before we have * a chance to finish talking to the server. Assuming the Perforce * daemon is reasonably well-designed, this shouldn't be a problem: * we're only sending an informational request ("p4 help"), and the * server should be able to intelligently deal with dropped connections. */ while (!client_done) { /* Get the origin and the packet. */ or = wait_for_incoming(setup); prin = read_pairs_from(setup, or); print_pair_info(or, prin); /* Do different things depending on which side sent stuff. */ if (or == CLIENT) { /* Are we done here? */ if (!prin) client_done = 1; /* Otherwise, completely ignore the client. */ } else /* or == SERVER */ { if (prin) { /* Is the func "release"? */ newpr = pair_with_key(prin, "func"); if (newpr && !strncmp(newpr->data, "release", newpr->len)) { /* Okay, tell the server the game's over. */ firstpr = new_pair("func", strlen(release2_key), release2_key); print_pair_info(SERVER, firstpr); write_pairs_from(setup, CLIENT, firstpr); free_pair(firstpr); } } } } } static origin wait_for_incoming(const struct protocol_data *setup) { fd_set set; int max_fd, retval, err; /* We're only looking at two file descriptors; note which one is bigger. */ if (setup->client_out > setup->server_out) max_fd = setup->client_out; else max_fd = setup->server_out; /* Loop until select() wins. */ while (1) { /* Create the set of fds we're listing for. */ FD_ZERO(&set); FD_SET(setup->client_out, &set); FD_SET(setup->server_out, &set); /* Listen. Be prepared to wait forever. */ retval = select(max_fd + 1, &set, NULL, NULL, NULL); /* Should we restart? */ if (retval < 0) { err = errno; if (err == EINTR) /* Interrupted signal */ continue; else /* Other failure */ { errmsg("select() failed: %s\n", strerror(errno)); exit(1); } } else if (retval > 0) { /* See if either of the fds we care about has data. If so, return * that side as the origin. Note that the server will never close * the connection on us, whereas the client does sometimes. Give * precedence to the server, since it will never give us EOF; if we * gave precedence to the client, we'd spin forever. It would be * more elegant to deal with this by noticing when one side or the * other closes and taking it out of the fd set. */ if (FD_ISSET(setup->server_out, &set)) return SERVER; else if (FD_ISSET(setup->client_out, &set)) return CLIENT; } /* So now we either got no fds or one we didn't recognize. That's * kind of odd. But go back and select() again anyways. */ } } /* Convert packets to/from pairs. */ pair *packet_to_pair(const packet *p) { pair *first = NULL, *current = NULL, *next = NULL; const char *here = p->data, *end = here + p->len; const char *key, *data; size_t len; while (here < end) { /* Look for a null-terminated string starting here. */ key = here; while (*here && here < end) here++; /* Sanity check that we succeeded. */ if (*here) { errmsg("packet_to_pair(): Packet ended before key was read\n"); exit(1); } if (here + 5 > end) { errmsg("packet_to_pair(): No room for data after key\n"); exit(1); } here++; /* The four bytes starting here are the packet length. */ len = read_p4_long(here); here += 4; /* Another sanity check. */ if (here + len + 1 > end) { errmsg("packet_to_pair(): Data doesn't fit in packet\n"); exit(1); } /* Remember where the data is, and move forward. */ data = here; here += len; /* This byte should be a null. */ if (*here != '\0') { errmsg("packet_to_pair(): Data isn't followed by null\n"); exit(1); } here++; /* Okay, now create the packet. */ next = new_pair(key, len, data); /* Do chaining if appropriate. */ if (current) current->next = next; if (!first) first = next; current = next; } /* All done; we win. Return the head of the list of pairs. */ return first; } static size_t pair_len(const pair *p) { /* The packet length of a pair is the length of the key, 1 (for a null), * 4 (for the length), the length of the data, and 1 (for a packet). */ return strlen(p->key) + p->len + 6; } packet *pair_to_packet(const pair *p) { const pair *q; packet *pk; char *here; size_t len = 0; /* Start by figuring out the length of the required data. */ for (q = p; q; q = q->next) len += pair_len(q); /* Now we have enough information to create the packet. */ pk = new_packet(len); /* Go through and put each item in the pair list into the packet. */ here = pk->data; for (q = p; q; q = q->next) { /* Start with the key and a trailing null. */ strcpy(here, q->key); here += strlen(q->key) + 1; /* Write in the length of the data. */ write_p4_long(here, q->len); here += 4; /* Then copy in the actual data. */ memcpy(here, q->data, q->len); here += q->len; /* Tack on an extra null. */ *here = '\0'; here++; } /* We've created the entire packet; return it. */ return pk; } packet *read_packet(int fd) { char tempbuf[5]; size_t len; int retval; packet *p; /* Start by getting the five-byte packet header. */ retval = doread(fd, &tempbuf, 5); if (retval < 5) { /* Oops, we lost. */ return NULL; } /* TODO: look at the header checksum. */ /* Extract the length from the header. */ len = read_p4_long(tempbuf + 1); /* Go ahead and create the data packet now. */ p = new_packet(len); /* At this point, we can go ahead and read the entire packet. */ retval = doread(fd, p->data, len); if (retval < len) { /* *sigh*...lost in reading the data. */ free_packet(p); return NULL; } /* All done; return the new packet. */ return p; } packet *read_packet_from(const struct protocol_data *setup, origin or) { int fd; switch (or) { case CLIENT: fd = setup->client_out; break; case SERVER: fd = setup->server_out; break; default: return NULL; } return read_packet(fd); } static pair *read_pairs_from(const struct protocol_data *setup, origin or) { packet *pk; pair *pr; pk = read_packet_from(setup, or); if (!pk) return NULL; pr = packet_to_pair(pk); free_packet(pk); return pr; } int write_packet(int fd, const packet *p) { int retval; void *blob; /* Get the blob for the packet. We know it's length (p->len + 5). */ blob = packet_to_blob(p); /* Write that out. */ retval = dowrite(fd, blob, p->len + 5); /* Clean up the blob now. */ free(blob); /* Return success or failure. */ if (retval > 0) return 1; else return 0; } static int write_packet_from(const struct protocol_data *setup, origin or, const packet *pk) { int fd; /* Remember, use the other side. */ switch (or) { case CLIENT: fd = setup->server_in; break; case SERVER: fd = setup->client_in; break; default: return 0; } return write_packet(fd, pk); } static int write_pairs_from(const struct protocol_data *setup, origin or, const pair *pr) { packet *pk = pair_to_packet(pr); int retval = write_packet_from(setup, or, pk); free_packet(pk); return retval; } static void print_pair_info(enum origin or, const pair *pr) { char *buf; const char *orname; switch (or) { case CLIENT: orname = "Client"; break; case SERVER: orname = "Server"; break; default: orname = "??????"; break; } while (pr) { buf = malloc(pr->len + 1); memcpy(buf, pr->data, pr->len); buf[pr->len] = '\0'; errmsg("%s %s: %s\n", orname, pr->key, buf); free(buf); pr = pr->next; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 450 | sandy_currier |
Initial import of p4filter code. This contains a solaris2.6 binary but no others. |