diff options
Diffstat (limited to 'pppd_plugin/src/pptp_ctrl.c')
-rw-r--r-- | pppd_plugin/src/pptp_ctrl.c | 1077 |
1 files changed, 1077 insertions, 0 deletions
diff --git a/pppd_plugin/src/pptp_ctrl.c b/pppd_plugin/src/pptp_ctrl.c new file mode 100644 index 00000000..1bab3f9a --- /dev/null +++ b/pppd_plugin/src/pptp_ctrl.c @@ -0,0 +1,1077 @@ +/* pptp_ctrl.c ... handle PPTP control connection. + * C. Scott Ananian <cananian@alumni.princeton.edu> + * + * $Id: pptp_ctrl.c,v 1.31 2005/03/31 07:42:39 quozl Exp $ + */ + +#include <errno.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include "pptp_msg.h" +#include "pptp_ctrl.h" +#include "pptp_options.h" +#include "vector.h" +#include "util.h" +#include "pptp_quirks.h" + +/* BECAUSE OF SIGNAL LIMITATIONS, EACH PROCESS CAN ONLY MANAGE ONE + * CONNECTION. SO THIS 'PPTP_CONN' STRUCTURE IS A BIT MISLEADING. + * WE'LL KEEP CONNECTION-SPECIFIC INFORMATION IN THERE ANYWAY (AS + * OPPOSED TO USING GLOBAL VARIABLES), BUT BEWARE THAT THE ENTIRE + * UNIX SIGNAL-HANDLING SEMANTICS WOULD HAVE TO CHANGE (OR THE + * TIME-OUT CODE DRASTICALLY REWRITTEN) BEFORE YOU COULD DO A + * PPTP_CONN_OPEN MORE THAN ONCE PER PROCESS AND GET AWAY WITH IT. + */ + +/* This structure contains connection-specific information that the + * signal handler needs to see. Thus, it needs to be in a global + * variable. If you end up using pthreads or something (why not + * just processes?), this would have to be placed in a thread-specific + * data area, using pthread_get|set_specific, etc., so I've + * conveniently encapsulated it for you. + * [linux threads will have to support thread-specific signals + * before this would work at all, which, as of this writing + * (linux-threads v0.6, linux kernel 2.1.72), it does not.] + */ + +/* Globals */ + +/* control the number of times echo packets will be logged */ +static int nlogecho = 10; + +static struct thread_specific { + struct sigaction old_sigaction; /* evil signals */ + PPTP_CONN * conn; +} global; + +#define INITIAL_BUFSIZE 512 /* initial i/o buffer size. */ + +struct PPTP_CONN { + int inet_sock; + /* Connection States */ + enum { + CONN_IDLE, CONN_WAIT_CTL_REPLY, CONN_WAIT_STOP_REPLY, CONN_ESTABLISHED + } conn_state; /* on startup: CONN_IDLE */ + /* Keep-alive states */ + enum { + KA_NONE, KA_OUTSTANDING + } ka_state; /* on startup: KA_NONE */ + /* Keep-alive ID; monotonically increasing (watch wrap-around!) */ + u_int32_t ka_id; /* on startup: 1 */ + /* Other properties. */ + u_int16_t version; + u_int16_t firmware_rev; + u_int8_t hostname[64], vendor[64]; + /* XXX these are only PNS properties, currently XXX */ + /* Call assignment information. */ + u_int16_t call_serial_number; + VECTOR *call; + void * closure; + pptp_conn_cb callback; + /******* IO buffers ******/ + char * read_buffer, *write_buffer; + size_t read_alloc, write_alloc; + size_t read_size, write_size; +}; + +struct PPTP_CALL { + /* Call properties */ + enum { + PPTP_CALL_PAC, PPTP_CALL_PNS + } call_type; + union { + enum pptp_pac_state { + PAC_IDLE, PAC_WAIT_REPLY, PAC_ESTABLISHED, PAC_WAIT_CS_ANS + } pac; + enum pptp_pns_state { + PNS_IDLE, PNS_WAIT_REPLY, PNS_ESTABLISHED, PNS_WAIT_DISCONNECT + } pns; + } state; + u_int16_t call_id, peer_call_id; + u_int16_t sernum; + u_int32_t speed; + /* For user data: */ + pptp_call_cb callback; + void * closure; +}; + + +/* PPTP error codes: ----------------------------------------------*/ + +/* (General Error Codes) */ +static const struct { + const char *name, *desc; +} pptp_general_errors[] = { +#define PPTP_GENERAL_ERROR_NONE 0 + { "(None)", "No general error" }, +#define PPTP_GENERAL_ERROR_NOT_CONNECTED 1 + { "(Not-Connected)", "No control connection exists yet for this " + "PAC-PNS pair" }, +#define PPTP_GENERAL_ERROR_BAD_FORMAT 2 + { "(Bad-Format)", "Length is wrong or Magic Cookie value is incorrect" }, +#define PPTP_GENERAL_ERROR_BAD_VALUE 3 + { "(Bad-Value)", "One of the field values was out of range or " + "reserved field was non-zero" }, +#define PPTP_GENERAL_ERROR_NO_RESOURCE 4 + { "(No-Resource)", "Insufficient resources to handle this command now" }, +#define PPTP_GENERAL_ERROR_BAD_CALLID 5 + { "(Bad-Call ID)", "The Call ID is invalid in this context" }, +#define PPTP_GENERAL_ERROR_PAC_ERROR 6 + { "(PAC-Error)", "A generic vendor-specific error occured in the PAC" } +}; + +#define MAX_GENERAL_ERROR ( sizeof(pptp_general_errors) / \ + sizeof(pptp_general_errors[0]) - 1) + +/* Outgoing Call Reply Result Codes */ +static const char *pptp_out_call_reply_result[] = { +/* 0 */ "Unknown Result Code", +/* 1 */ "Connected", +/* 2 */ "General Error", +/* 3 */ "No Carrier Detected", +/* 4 */ "Busy Signal", +/* 5 */ "No Dial Tone", +/* 6 */ "Time Out", +/* 7 */ "Not Accepted, Call is administratively prohibited" }; + +#define MAX_OUT_CALL_REPLY_RESULT 7 + +/* Call Disconnect Notify Result Codes */ +static const char *pptp_call_disc_ntfy[] = { +/* 0 */ "Unknown Result Code", +/* 1 */ "Lost Carrier", +/* 2 */ "General Error", +/* 3 */ "Administrative Shutdown", +/* 4 */ "(your) Request" }; + +#define MAX_CALL_DISC_NTFY 4 + +/* Call Disconnect Notify Result Codes */ +static const char *pptp_start_ctrl_conn_rply[] = { +/* 0 */ "Unknown Result Code", +/* 1 */ "Successful Channel Establishment", +/* 2 */ "General Error", +/* 3 */ "Command Channel Already Exists", +/* 4 */ "Requester is not Authorized" }; + +#define MAX_START_CTRL_CONN_REPLY 4 + +/* timing options */ +int idle_wait = PPTP_TIMEOUT; +int max_echo_wait = PPTP_TIMEOUT; + +/* Local prototypes */ +static void pptp_reset_timer(void); +static void pptp_handle_timer(); +/* Write/read as much as we can without blocking. */ +int pptp_write_some(PPTP_CONN * conn); +int pptp_read_some(PPTP_CONN * conn); +/* Make valid packets from read_buffer */ +int pptp_make_packet(PPTP_CONN * conn, void **buf, size_t *size); +/* Add packet to write_buffer */ +int pptp_send_ctrl_packet(PPTP_CONN * conn, void * buffer, size_t size); +/* Dispatch packets (general) */ +int pptp_dispatch_packet(PPTP_CONN * conn, void * buffer, size_t size); +/* Dispatch packets (control messages) */ +int ctrlp_disp(PPTP_CONN * conn, void * buffer, size_t size); +/* Set link info, for pptp servers that need it. + this is a noop, unless the user specified a quirk and + there's a set_link hook defined in the quirks table + for that quirk */ +void pptp_set_link(PPTP_CONN * conn, int peer_call_id); + +/*** log error information in control packets *********************************/ +static void ctrlp_error( int result, int error, int cause, + const char *result_text[], int max_result) +{ + if( cause >= 0) + log("Result code is %d '%s'. Error code is %d, Cause code is %d", + result, result_text[result <= max_result ? result : 0], error, + cause ); + else + log("Reply result code is %d '%s'. Error code is %d", + result, result_text[result <= max_result ? result : 0], error); + if ((error > 0) && (error <= MAX_GENERAL_ERROR)){ + if( result != PPTP_RESULT_GENERAL_ERROR ) + log("Result code is something else then \"general error\", " + "so the following error is probably bogus."); + log("Error is '%s', Error message: '%s'", + pptp_general_errors[error].name, + pptp_general_errors[error].desc); + } +} + +static const char *ctrl_msg_types[] = { + "invalid control message type", +/* (Control Connection Management) */ + "Start-Control-Connection-Request", /* 1 */ + "Start-Control-Connection-Reply", /* 2 */ + "Stop-Control-Connection-Request", /* 3 */ + "Stop-Control-Connection-Reply", /* 4 */ + "Echo-Request", /* 5 */ + "Echo-Reply", /* 6 */ +/* (Call Management) */ + "Outgoing-Call-Request", /* 7 */ + "Outgoing-Call-Reply", /* 8 */ + "Incoming-Call-Request", /* 9 */ + "Incoming-Call-Reply", /* 10 */ + "Incoming-Call-Connected", /* 11 */ + "Call-Clear-Request", /* 12 */ + "Call-Disconnect-Notify", /* 13 */ +/* (Error Reporting) */ + "WAN-Error-Notify", /* 14 */ +/* (PPP Session Control) */ + "Set-Link-Info" /* 15 */ +}; +#define MAX_CTRLMSG_TYPE 15 + +/*** report a sent packet ****************************************************/ +static void ctrlp_rep( void * buffer, int size, int isbuff) +{ + struct pptp_header *packet = buffer; + unsigned int type; + if(size < sizeof(struct pptp_header)) return; + type = ntoh16(packet->ctrl_type); + /* FIXME: do not report sending echo requests as long as they are + * sent in a signal handler. This may dead lock as the syslog call + * is not reentrant */ + if( type == PPTP_ECHO_RQST ) return; + /* don't keep reporting sending of echo's */ + if( (type == PPTP_ECHO_RQST || type == PPTP_ECHO_RPLY) && nlogecho <= 0 ) return; + log("%s control packet type is %d '%s'\n",isbuff ? "Buffered" : "Sent", + type, ctrl_msg_types[type <= MAX_CTRLMSG_TYPE ? type : 0]); + +} + + + +/* Open new pptp_connection. Returns NULL on failure. */ +PPTP_CONN * pptp_conn_open(int inet_sock, int isclient, pptp_conn_cb callback) +{ + PPTP_CONN *conn; + /* Allocate structure */ + if ((conn = malloc(sizeof(*conn))) == NULL) return NULL; + if ((conn->call = vector_create()) == NULL) { free(conn); return NULL; } + /* Initialize */ + conn->inet_sock = inet_sock; + conn->conn_state = CONN_IDLE; + conn->ka_state = KA_NONE; + conn->ka_id = 1; + conn->call_serial_number = 0; + conn->callback = callback; + /* Create I/O buffers */ + conn->read_size = conn->write_size = 0; + conn->read_alloc = conn->write_alloc = INITIAL_BUFSIZE; + conn->read_buffer = + malloc(sizeof(*(conn->read_buffer)) * conn->read_alloc); + conn->write_buffer = + malloc(sizeof(*(conn->write_buffer)) * conn->write_alloc); + if (conn->read_buffer == NULL || conn->write_buffer == NULL) { + if (conn->read_buffer != NULL) free(conn->read_buffer); + if (conn->write_buffer != NULL) free(conn->write_buffer); + vector_destroy(conn->call); free(conn); return NULL; + } + /* Make this socket non-blocking. */ + fcntl(conn->inet_sock, F_SETFL, O_NONBLOCK); + /* Request connection from server, if this is a client */ + if (isclient) { + struct pptp_start_ctrl_conn packet = { + PPTP_HEADER_CTRL(PPTP_START_CTRL_CONN_RQST), + hton16(PPTP_VERSION), 0, 0, + hton32(PPTP_FRAME_CAP), hton32(PPTP_BEARER_CAP), + hton16(PPTP_MAX_CHANNELS), hton16(PPTP_FIRMWARE_VERSION), + PPTP_HOSTNAME, PPTP_VENDOR + }; + /* fix this packet, if necessary */ + int idx, rc; + idx = get_quirk_index(); + if (idx != -1 && pptp_fixups[idx].start_ctrl_conn) { + if ((rc = pptp_fixups[idx].start_ctrl_conn(&packet))) + warn("calling the start_ctrl_conn hook failed (%d)", rc); + } + if (pptp_send_ctrl_packet(conn, &packet, sizeof(packet))) + conn->conn_state = CONN_WAIT_CTL_REPLY; + else + return NULL; /* could not send initial start request. */ + } + /* Set up interval/keep-alive timer */ + /* First, register handler for SIGALRM */ + sigpipe_create(); + sigpipe_assign(SIGALRM); + global.conn = conn; + /* Reset event timer */ + pptp_reset_timer(); + /* all done. */ + return conn; +} + +int pptp_conn_established(PPTP_CONN *conn) { + return (conn->conn_state == CONN_ESTABLISHED); +} + +/* This currently *only* works for client call requests. + * We need to do something else to allocate calls for incoming requests. + */ +PPTP_CALL * pptp_call_open(PPTP_CONN * conn, int call_id,pptp_call_cb callback, + char *phonenr,int window) +{ + PPTP_CALL * call; + int idx, rc; + /* Send off the call request */ + struct pptp_out_call_rqst packet = { + PPTP_HEADER_CTRL(PPTP_OUT_CALL_RQST), + 0,0, /*call_id, sernum */ + hton32(PPTP_BPS_MIN), hton32(PPTP_BPS_MAX), + hton32(PPTP_BEARER_CAP), hton32(PPTP_FRAME_CAP), + hton16(window), 0, 0, 0, {0}, {0} + }; + assert(conn && conn->call); + assert(conn->conn_state == CONN_ESTABLISHED); + /* Assign call id */ + if (!call_id && !vector_scan(conn->call, 0, PPTP_MAX_CHANNELS - 1, &call_id)) + /* no more calls available! */ + return NULL; + /* allocate structure. */ + if ((call = malloc(sizeof(*call))) == NULL) return NULL; + /* Initialize call structure */ + call->call_type = PPTP_CALL_PNS; + call->state.pns = PNS_IDLE; + call->call_id = (u_int16_t) call_id; + call->sernum = conn->call_serial_number++; + call->callback = callback; + call->closure = NULL; + packet.call_id = htons(call->call_id); + packet.call_sernum = htons(call->sernum); + /* if we have a quirk, build a new packet to fit it */ + idx = get_quirk_index(); + if (idx != -1 && pptp_fixups[idx].out_call_rqst_hook) { + if ((rc = pptp_fixups[idx].out_call_rqst_hook(&packet))) + warn("calling the out_call_rqst hook failed (%d)", rc); + } + /* fill in the phone number if it was specified */ + if (phonenr) { + strncpy(packet.phone_num, phonenr, sizeof(packet.phone_num)); + packet.phone_len = strlen(phonenr); + if( packet.phone_len > sizeof(packet.phone_num)) + packet.phone_len = sizeof(packet.phone_num); + packet.phone_len = hton16 (packet.phone_len); + } + if (pptp_send_ctrl_packet(conn, &packet, sizeof(packet))) { + pptp_reset_timer(); + call->state.pns = PNS_WAIT_REPLY; + /* and add it to the call vector */ + vector_insert(conn->call, call_id, call); + return call; + } else { /* oops, unsuccessful. Deallocate. */ + free(call); + return NULL; + } +} + +/*** pptp_call_close **********************************************************/ +void pptp_call_close(PPTP_CONN * conn, PPTP_CALL * call) +{ + struct pptp_call_clear_rqst rqst = { + PPTP_HEADER_CTRL(PPTP_CALL_CLEAR_RQST), 0, 0 + }; + assert(conn && conn->call); assert(call); + assert(vector_contains(conn->call, call->call_id)); + /* haven't thought about PAC yet */ + assert(call->call_type == PPTP_CALL_PNS); + assert(call->state.pns != PNS_IDLE); + rqst.call_id = hton16(call->call_id); + /* don't check state against WAIT_DISCONNECT... allow multiple disconnect + * requests to be made. + */ + pptp_send_ctrl_packet(conn, &rqst, sizeof(rqst)); + pptp_reset_timer(); + call->state.pns = PNS_WAIT_DISCONNECT; + /* call structure will be freed when we have confirmation of disconnect. */ +} + +/*** hard close ***************************************************************/ +void pptp_call_destroy(PPTP_CONN *conn, PPTP_CALL *call) +{ + assert(conn && conn->call); assert(call); + assert(vector_contains(conn->call, call->call_id)); + /* notify */ + if (call->callback != NULL) call->callback(conn, call, CALL_CLOSE_DONE); + /* deallocate */ + vector_remove(conn->call, call->call_id); + free(call); +} + +/*** this is a soft close *****************************************************/ +void pptp_conn_close(PPTP_CONN * conn, u_int8_t close_reason) +{ + struct pptp_stop_ctrl_conn rqst = { + PPTP_HEADER_CTRL(PPTP_STOP_CTRL_CONN_RQST), + hton8(close_reason), 0, 0 + }; + int i; + assert(conn && conn->call); + /* avoid repeated close attempts */ + if (conn->conn_state == CONN_IDLE || conn->conn_state == CONN_WAIT_STOP_REPLY) + return; + /* close open calls, if any */ + for (i = 0; i < vector_size(conn->call); i++) + pptp_call_close(conn, vector_get_Nth(conn->call, i)); + /* now close connection */ + log("Closing PPTP connection"); + pptp_send_ctrl_packet(conn, &rqst, sizeof(rqst)); + pptp_reset_timer(); /* wait 60 seconds for reply */ + conn->conn_state = CONN_WAIT_STOP_REPLY; + return; +} + +/*** this is a hard close *****************************************************/ +void pptp_conn_destroy(PPTP_CONN * conn) +{ + int i; + assert(conn != NULL); assert(conn->call != NULL); + /* destroy all open calls */ + for (i = 0; i < vector_size(conn->call); i++) + pptp_call_destroy(conn, vector_get_Nth(conn->call, i)); + /* notify */ + if (conn->callback != NULL) conn->callback(conn, CONN_CLOSE_DONE); + sigpipe_close(); + close(conn->inet_sock); + /* deallocate */ + vector_destroy(conn->call); + free(conn); +} + +/*** Deal with messages, in a non-blocking manner + * Add file descriptors used by pptp to fd_set. + */ +void pptp_fd_set(PPTP_CONN * conn, fd_set * read_set, fd_set * write_set, + int * max_fd) +{ + assert(conn && conn->call); + /* Add fd to write_set if there are outstanding writes. */ + if (conn->write_size > 0) + FD_SET(conn->inet_sock, write_set); + /* Always add fd to read_set. (always want something to read) */ + FD_SET(conn->inet_sock, read_set); + if (*max_fd < conn->inet_sock) *max_fd = conn->inet_sock; + /* Add signal pipe file descriptor to set */ + int sig_fd = sigpipe_fd(); + FD_SET(sig_fd, read_set); + if (*max_fd < sig_fd) *max_fd = sig_fd; +} + +/*** handle any pptp file descriptors set in fd_set, and clear them ***********/ +int pptp_dispatch(PPTP_CONN * conn, fd_set * read_set, fd_set * write_set) +{ + int r = 0; + assert(conn && conn->call); + /* Check for signals */ + if (FD_ISSET(sigpipe_fd(), read_set)) { + if (sigpipe_read() == SIGALRM) pptp_handle_timer(); + FD_CLR(sigpipe_fd(), read_set); + } + /* Check write_set could be set. */ + if (FD_ISSET(conn->inet_sock, write_set)) { + FD_CLR(conn->inet_sock, write_set); + if (conn->write_size > 0) + r = pptp_write_some(conn);/* write as much as we can without blocking */ + } + /* Check read_set */ + if (r >= 0 && FD_ISSET(conn->inet_sock, read_set)) { + void *buffer; size_t size; + FD_CLR(conn->inet_sock, read_set); + r = pptp_read_some(conn); /* read as much as we can without blocking */ + if (r < 0) + return r; + /* make packets of the buffer, while we can. */ + while (r >= 0 && pptp_make_packet(conn, &buffer, &size)) { + r = pptp_dispatch_packet(conn, buffer, size); + free(buffer); + } + } + /* That's all, folks. Simple, eh? */ + return r; +} + +/*** Non-blocking write *******************************************************/ +int pptp_write_some(PPTP_CONN * conn) { + ssize_t retval; + assert(conn && conn->call); + retval = write(conn->inet_sock, conn->write_buffer, conn->write_size); + if (retval < 0) { /* error. */ + if (errno == EAGAIN || errno == EINTR) { + return 0; + } else { /* a real error */ + log("write error: %s", strerror(errno)); + return -1; + } + } + assert(retval <= conn->write_size); + conn->write_size -= retval; + memmove(conn->write_buffer, conn->write_buffer + retval, conn->write_size); + ctrlp_rep(conn->write_buffer, retval, 0); + return 0; +} + +/*** Non-blocking read ********************************************************/ +int pptp_read_some(PPTP_CONN * conn) +{ + ssize_t retval; + assert(conn && conn->call); + if (conn->read_size == conn->read_alloc) { /* need to alloc more memory */ + char *new_buffer = realloc(conn->read_buffer, + sizeof(*(conn->read_buffer)) * conn->read_alloc * 2); + if (new_buffer == NULL) { + log("Out of memory"); return -1; + } + conn->read_alloc *= 2; + conn->read_buffer = new_buffer; + } + retval = read(conn->inet_sock, conn->read_buffer + conn->read_size, + conn->read_alloc - conn->read_size); + if (retval == 0) { + log("read returned zero, peer has closed"); + return -1; + } + if (retval < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + else { /* a real error */ + log("read error: %s", strerror(errno)); + return -1; + } + } + conn->read_size += retval; + assert(conn->read_size <= conn->read_alloc); + return 0; +} + +/*** Packet formation *********************************************************/ +int pptp_make_packet(PPTP_CONN * conn, void **buf, size_t *size) +{ + struct pptp_header *header; + size_t bad_bytes = 0; + assert(conn && conn->call); assert(buf != NULL); assert(size != NULL); + /* Give up unless there are at least sizeof(pptp_header) bytes */ + while ((conn->read_size-bad_bytes) >= sizeof(struct pptp_header)) { + /* Throw out bytes until we have a valid header. */ + header = (struct pptp_header *) (conn->read_buffer + bad_bytes); + if (ntoh32(header->magic) != PPTP_MAGIC) goto throwitout; + if (ntoh16(header->reserved0) != 0) + log("reserved0 field is not zero! (0x%x) Cisco feature? \n", + ntoh16(header->reserved0)); + if (ntoh16(header->length) < sizeof(struct pptp_header)) goto throwitout; + if (ntoh16(header->length) > PPTP_CTRL_SIZE_MAX) goto throwitout; + /* well. I guess it's good. Let's see if we've got it all. */ + if (ntoh16(header->length) > (conn->read_size-bad_bytes)) + /* nope. Let's wait until we've got it, then. */ + goto flushbadbytes; + /* One last check: */ + if ((ntoh16(header->pptp_type) == PPTP_MESSAGE_CONTROL) && + (ntoh16(header->length) != + PPTP_CTRL_SIZE(ntoh16(header->ctrl_type)))) + goto throwitout; + /* well, I guess we've got it. */ + *size = ntoh16(header->length); + *buf = malloc(*size); + if (*buf == NULL) { log("Out of memory."); return 0; /* ack! */ } + memcpy(*buf, conn->read_buffer + bad_bytes, *size); + /* Delete this packet from the read_buffer. */ + conn->read_size -= (bad_bytes + *size); + memmove(conn->read_buffer, conn->read_buffer + bad_bytes + *size, + conn->read_size); + if (bad_bytes > 0) + log("%lu bad bytes thrown away.", (unsigned long) bad_bytes); + return 1; +throwitout: + bad_bytes++; + } +flushbadbytes: + /* no more packets. Let's get rid of those bad bytes */ + conn->read_size -= bad_bytes; + memmove(conn->read_buffer, conn->read_buffer + bad_bytes, conn->read_size); + if (bad_bytes > 0) + log("%lu bad bytes thrown away.", (unsigned long) bad_bytes); + return 0; +} + +/*** pptp_send_ctrl_packet ****************************************************/ +int pptp_send_ctrl_packet(PPTP_CONN * conn, void * buffer, size_t size) +{ + assert(conn && conn->call); assert(buffer); + if( conn->write_size > 0) pptp_write_some( conn); + if( conn->write_size == 0) { + ssize_t retval; + retval = write(conn->inet_sock, buffer, size); + if (retval < 0) { /* error. */ + if (errno == EAGAIN || errno == EINTR) { + /* ignore */; + retval = 0; + } else { /* a real error */ + log("write error: %s", strerror(errno)); + pptp_conn_destroy(conn); /* shut down fast. */ + return 0; + } + } + ctrlp_rep( buffer, retval, 0); + size -= retval; + if( size <= 0) return 1; + } + /* Shove anything not written into the write buffer */ + if (conn->write_size + size > conn->write_alloc) { /* need more memory */ + char *new_buffer = realloc(conn->write_buffer, + sizeof(*(conn->write_buffer)) * conn->write_alloc * 2); + if (new_buffer == NULL) { + log("Out of memory"); return 0; + } + conn->write_alloc *= 2; + conn->write_buffer = new_buffer; + } + memcpy(conn->write_buffer + conn->write_size, buffer, size); + conn->write_size += size; + ctrlp_rep( buffer,size,1); + return 1; +} + +/*** Packet Dispatch **********************************************************/ +int pptp_dispatch_packet(PPTP_CONN * conn, void * buffer, size_t size) +{ + int r = 0; + struct pptp_header *header = (struct pptp_header *)buffer; + assert(conn && conn->call); assert(buffer); + assert(ntoh32(header->magic) == PPTP_MAGIC); + assert(ntoh16(header->length) == size); + switch (ntoh16(header->pptp_type)) { + case PPTP_MESSAGE_CONTROL: + r = ctrlp_disp(conn, buffer, size); + break; + case PPTP_MESSAGE_MANAGE: + /* MANAGEMENT messages aren't even part of the spec right now. */ + log("PPTP management message received, but not understood."); + break; + default: + log("Unknown PPTP control message type received: %u", + (unsigned int) ntoh16(header->pptp_type)); + break; + } + return r; +} + +/*** log echo request/replies *************************************************/ +static void logecho( int type) +{ + /* hack to stop flooding the log files (the most interesting part is right + * after the connection built-up) */ + if( nlogecho > 0) { + log( "Echo Re%s received.", type == PPTP_ECHO_RQST ? "quest" :"ply"); + if( --nlogecho == 0) + log("no more Echo Reply/Request packets will be reported."); + } +} + +/*** pptp_dispatch_ctrl_packet ************************************************/ +int ctrlp_disp(PPTP_CONN * conn, void * buffer, size_t size) +{ + struct pptp_header *header = (struct pptp_header *)buffer; + u_int8_t close_reason = PPTP_STOP_NONE; + assert(conn && conn->call); assert(buffer); + assert(ntoh32(header->magic) == PPTP_MAGIC); + assert(ntoh16(header->length) == size); + assert(ntoh16(header->pptp_type) == PPTP_MESSAGE_CONTROL); + if (size < PPTP_CTRL_SIZE(ntoh16(header->ctrl_type))) { + log("Invalid packet received [type: %d; length: %d].", + (int) ntoh16(header->ctrl_type), (int) size); + return 0; + } + switch (ntoh16(header->ctrl_type)) { + /* ----------- STANDARD Start-Session MESSAGES ------------ */ + case PPTP_START_CTRL_CONN_RQST: + { + struct pptp_start_ctrl_conn *packet = + (struct pptp_start_ctrl_conn *) buffer; + struct pptp_start_ctrl_conn reply = { + PPTP_HEADER_CTRL(PPTP_START_CTRL_CONN_RPLY), + hton16(PPTP_VERSION), 0, 0, + hton32(PPTP_FRAME_CAP), hton32(PPTP_BEARER_CAP), + hton16(PPTP_MAX_CHANNELS), hton16(PPTP_FIRMWARE_VERSION), + PPTP_HOSTNAME, PPTP_VENDOR }; + int idx, rc; + log("Received Start Control Connection Request"); + /* fix this packet, if necessary */ + idx = get_quirk_index(); + if (idx != -1 && pptp_fixups[idx].start_ctrl_conn) { + if ((rc = pptp_fixups[idx].start_ctrl_conn(&reply))) + warn("calling the start_ctrl_conn hook failed (%d)", rc); + } + if (conn->conn_state == CONN_IDLE) { + if (ntoh16(packet->version) < PPTP_VERSION) { + /* Can't support this (earlier) PPTP_VERSION */ + reply.version = packet->version; + /* protocol version not supported */ + reply.result_code = hton8(5); + pptp_send_ctrl_packet(conn, &reply, sizeof(reply)); + pptp_reset_timer(); /* give sender a chance for a retry */ + } else { /* same or greater version */ + if (pptp_send_ctrl_packet(conn, &reply, sizeof(reply))) { + conn->conn_state = CONN_ESTABLISHED; + log("server connection ESTABLISHED."); + pptp_reset_timer(); + } + } + } + break; + } + case PPTP_START_CTRL_CONN_RPLY: + { + struct pptp_start_ctrl_conn *packet = + (struct pptp_start_ctrl_conn *) buffer; + log("Received Start Control Connection Reply"); + if (conn->conn_state == CONN_WAIT_CTL_REPLY) { + /* XXX handle collision XXX [see rfc] */ + if (ntoh16(packet->version) != PPTP_VERSION) { + if (conn->callback != NULL) + conn->callback(conn, CONN_OPEN_FAIL); + close_reason = PPTP_STOP_PROTOCOL; + goto pptp_conn_close; + } + if (ntoh8(packet->result_code) != 1 && + /* J'ai change le if () afin que la connection ne se ferme + * pas pour un "rien" :p adel@cybercable.fr - + * + * Don't close the connection if the result code is zero + * (feature found in certain ADSL modems) + */ + ntoh8(packet->result_code) != 0) { + log("Negative reply received to our Start Control " + "Connection Request"); + ctrlp_error(packet->result_code, packet->error_code, + -1, pptp_start_ctrl_conn_rply, + MAX_START_CTRL_CONN_REPLY); + if (conn->callback != NULL) + conn->callback(conn, CONN_OPEN_FAIL); + close_reason = PPTP_STOP_PROTOCOL; + goto pptp_conn_close; + } + conn->conn_state = CONN_ESTABLISHED; + /* log session properties */ + conn->version = ntoh16(packet->version); + conn->firmware_rev = ntoh16(packet->firmware_rev); + memcpy(conn->hostname, packet->hostname, sizeof(conn->hostname)); + memcpy(conn->vendor, packet->vendor, sizeof(conn->vendor)); + pptp_reset_timer(); /* 60 seconds until keep-alive */ + log("Client connection established."); + if (conn->callback != NULL) + conn->callback(conn, CONN_OPEN_DONE); + } /* else goto pptp_conn_close; */ + break; + } + /* ----------- STANDARD Stop-Session MESSAGES ------------ */ + case PPTP_STOP_CTRL_CONN_RQST: + { + /* conn_state should be CONN_ESTABLISHED, but it could be + * something else */ + struct pptp_stop_ctrl_conn reply = { + PPTP_HEADER_CTRL(PPTP_STOP_CTRL_CONN_RPLY), + hton8(1), hton8(PPTP_GENERAL_ERROR_NONE), 0 + }; + log("Received Stop Control Connection Request."); + if (conn->conn_state == CONN_IDLE) break; + if (pptp_send_ctrl_packet(conn, &reply, sizeof(reply))) { + if (conn->callback != NULL) + conn->callback(conn, CONN_CLOSE_RQST); + conn->conn_state = CONN_IDLE; + return -1; + } + break; + } + case PPTP_STOP_CTRL_CONN_RPLY: + { + log("Received Stop Control Connection Reply."); + /* conn_state should be CONN_WAIT_STOP_REPLY, but it + * could be something else */ + if (conn->conn_state == CONN_IDLE) break; + conn->conn_state = CONN_IDLE; + return -1; + } + /* ----------- STANDARD Echo/Keepalive MESSAGES ------------ */ + case PPTP_ECHO_RPLY: + { + struct pptp_echo_rply *packet = + (struct pptp_echo_rply *) buffer; + logecho( PPTP_ECHO_RPLY); + if ((conn->ka_state == KA_OUTSTANDING) && + (ntoh32(packet->identifier) == conn->ka_id)) { + conn->ka_id++; + conn->ka_state = KA_NONE; + pptp_reset_timer(); + } + break; + } + case PPTP_ECHO_RQST: + { + struct pptp_echo_rqst *packet = + (struct pptp_echo_rqst *) buffer; + struct pptp_echo_rply reply = { + PPTP_HEADER_CTRL(PPTP_ECHO_RPLY), + packet->identifier, /* skip hton32(ntoh32(id)) */ + hton8(1), hton8(PPTP_GENERAL_ERROR_NONE), 0 + }; + logecho( PPTP_ECHO_RQST); + pptp_send_ctrl_packet(conn, &reply, sizeof(reply)); + pptp_reset_timer(); + break; + } + /* ----------- OUTGOING CALL MESSAGES ------------ */ + case PPTP_OUT_CALL_RQST: + { + struct pptp_out_call_rqst *packet = + (struct pptp_out_call_rqst *)buffer; + struct pptp_out_call_rply reply = { + PPTP_HEADER_CTRL(PPTP_OUT_CALL_RPLY), + 0 /* callid */, packet->call_id, 1, PPTP_GENERAL_ERROR_NONE, 0, + hton32(PPTP_CONNECT_SPEED), + hton16(PPTP_WINDOW), hton16(PPTP_DELAY), 0 + }; + log("Received Outgoing Call Request."); + /* XXX PAC: eventually this should make an outgoing call. XXX */ + reply.result_code = hton8(7); /* outgoing calls verboten */ + pptp_send_ctrl_packet(conn, &reply, sizeof(reply)); + break; + } + case PPTP_OUT_CALL_RPLY: + { + struct pptp_out_call_rply *packet = + (struct pptp_out_call_rply *)buffer; + PPTP_CALL * call; + u_int16_t callid = ntoh16(packet->call_id_peer); + log("Received Outgoing Call Reply."); + if (!vector_search(conn->call, (int) callid, &call)) { + log("PPTP_OUT_CALL_RPLY received for non-existant call: " + "peer call ID (us) %d call ID (them) %d.", + callid, ntoh16(packet->call_id)); + break; + } + if (call->call_type != PPTP_CALL_PNS) { + log("Ack! How did this call_type get here?"); /* XXX? */ + break; + } + if (call->state.pns != PNS_WAIT_REPLY) { + warn("Unexpected(?) Outgoing Call Reply will be ignored."); + break; + } + /* check for errors */ + if (packet->result_code != 1) { + /* An error. Log it verbosely. */ + log("Our outgoing call request [callid %d] has not been " + "accepted.", (int) callid); + ctrlp_error(packet->result_code, packet->error_code, + packet->cause_code, pptp_out_call_reply_result, + MAX_OUT_CALL_REPLY_RESULT); + call->state.pns = PNS_IDLE; + if (call->callback != NULL) + call->callback(conn, call, CALL_OPEN_FAIL); + pptp_call_destroy(conn, call); + } else { + /* connection established */ + call->state.pns = PNS_ESTABLISHED; + call->peer_call_id = ntoh16(packet->call_id); + call->speed = ntoh32(packet->speed); + pptp_reset_timer(); + /* call pptp_set_link. unless the user specified a quirk + and this quirk has a set_link hook, this is a noop */ + pptp_set_link(conn, call->peer_call_id); + if (call->callback != NULL) + call->callback(conn, call, CALL_OPEN_DONE); + log("Outgoing call established (call ID %u, peer's " + "call ID %u).\n", call->call_id, call->peer_call_id); + } + break; + } + /* ----------- INCOMING CALL MESSAGES ------------ */ + /* XXX write me XXX */ + /* ----------- CALL CONTROL MESSAGES ------------ */ + case PPTP_CALL_CLEAR_RQST: + { + struct pptp_call_clear_rqst *packet = + (struct pptp_call_clear_rqst *)buffer; + struct pptp_call_clear_ntfy reply = { + PPTP_HEADER_CTRL(PPTP_CALL_CLEAR_NTFY), packet->call_id, + 1, PPTP_GENERAL_ERROR_NONE, 0, 0, {0} + }; + log("Received Call Clear Request."); + if (vector_contains(conn->call, ntoh16(packet->call_id))) { + PPTP_CALL * call; + vector_search(conn->call, ntoh16(packet->call_id), &call); + if (call->callback != NULL) + call->callback(conn, call, CALL_CLOSE_RQST); + pptp_send_ctrl_packet(conn, &reply, sizeof(reply)); + pptp_call_destroy(conn, call); + log("Call closed (RQST) (call id %d)", (int) call->call_id); + } + break; + } + case PPTP_CALL_CLEAR_NTFY: + { + struct pptp_call_clear_ntfy *packet = + (struct pptp_call_clear_ntfy *)buffer; + log("Call disconnect notification received (call id %d)", + ntoh16(packet->call_id)); + if (vector_contains(conn->call, ntoh16(packet->call_id))) { + PPTP_CALL * call; + ctrlp_error(packet->result_code, packet->error_code, + packet->cause_code, pptp_call_disc_ntfy, + MAX_CALL_DISC_NTFY); + vector_search(conn->call, ntoh16(packet->call_id), &call); + pptp_call_destroy(conn, call); + } + /* XXX we could log call stats here XXX */ + /* XXX not all servers send this XXX */ + break; + } + case PPTP_SET_LINK_INFO: + { + /* I HAVE NO CLUE WHAT TO DO IF send_accm IS NOT 0! */ + /* this is really dealt with in the HDLC deencapsulation, anyway. */ + struct pptp_set_link_info *packet = + (struct pptp_set_link_info *)buffer; + /* log it. */ + log("PPTP_SET_LINK_INFO received from peer_callid %u", + (unsigned int) ntoh16(packet->call_id_peer)); + log(" send_accm is %08lX, recv_accm is %08lX", + (unsigned long) ntoh32(packet->send_accm), + (unsigned long) ntoh32(packet->recv_accm)); + if (!(ntoh32(packet->send_accm) == 0 && + ntoh32(packet->recv_accm) == 0)) + warn("Non-zero Async Control Character Maps are not supported!"); + break; + } + default: + log("Unrecognized Packet %d received.", + (int) ntoh16(((struct pptp_header *)buffer)->ctrl_type)); + /* goto pptp_conn_close; */ + break; + } + return 0; +pptp_conn_close: + warn("pptp_conn_close(%d)", (int) close_reason); + pptp_conn_close(conn, close_reason); + return 0; +} + +/*** pptp_set_link **************************************************************/ +void pptp_set_link(PPTP_CONN* conn, int peer_call_id) +{ + int idx, rc; + /* if we need to send a set_link packet because of buggy + hardware or pptp server, do it now */ + if ((idx = get_quirk_index()) != -1 && pptp_fixups[idx].set_link_hook) { + struct pptp_set_link_info packet; + if ((rc = pptp_fixups[idx].set_link_hook(&packet, peer_call_id))) + warn("calling the set_link hook failed (%d)", rc); + if (pptp_send_ctrl_packet(conn, &packet, sizeof(packet))) { + pptp_reset_timer(); + } + } +} + +/*** Get info from call structure *********************************************/ +/* NOTE: The peer_call_id is undefined until we get a server response. */ +void pptp_call_get_ids(PPTP_CONN * conn, PPTP_CALL * call, + u_int16_t * call_id, u_int16_t * peer_call_id) +{ + assert(conn != NULL); assert(call != NULL); + *call_id = call->call_id; + *peer_call_id = call->peer_call_id; +} + +/*** pptp_call_closure_put ****************************************************/ +void pptp_call_closure_put(PPTP_CONN * conn, PPTP_CALL * call, void *cl) +{ + assert(conn != NULL); assert(call != NULL); + call->closure = cl; +} + +/*** pptp_call_closure_get ****************************************************/ +void * pptp_call_closure_get(PPTP_CONN * conn, PPTP_CALL * call) +{ + assert(conn != NULL); assert(call != NULL); + return call->closure; +} + +/*** pptp_conn_closure_put ****************************************************/ +void pptp_conn_closure_put(PPTP_CONN * conn, void *cl) +{ + assert(conn != NULL); + conn->closure = cl; +} + +/*** pptp_conn_closure_get ****************************************************/ +void * pptp_conn_closure_get(PPTP_CONN * conn) +{ + assert(conn != NULL); + return conn->closure; +} + +/*** Reset keep-alive timer ***************************************************/ +static void pptp_reset_timer(void) +{ + const struct itimerval tv = { { 0, 0 }, /* stop on time-out */ + { idle_wait, 0 } }; + if (idle_wait) setitimer(ITIMER_REAL, &tv, NULL); +} + + +/*** Handle keep-alive timer **************************************************/ +static void pptp_handle_timer() +{ + int i; + /* "Keep Alives and Timers, 1": check connection state */ + if (global.conn->conn_state != CONN_ESTABLISHED) { + if (global.conn->conn_state == CONN_WAIT_STOP_REPLY) + /* hard close. */ + pptp_conn_destroy(global.conn); + else /* soft close */ + pptp_conn_close(global.conn, PPTP_STOP_NONE); + } + /* "Keep Alives and Timers, 2": check echo status */ + if (global.conn->ka_state == KA_OUTSTANDING) { + /* no response to keep-alive */ + log ("closing control connection due to missing echo reply"); + pptp_conn_close(global.conn, PPTP_STOP_NONE); + } else { /* ka_state == NONE */ /* send keep-alive */ + struct pptp_echo_rqst rqst = { + PPTP_HEADER_CTRL(PPTP_ECHO_RQST), hton32(global.conn->ka_id) }; + pptp_send_ctrl_packet(global.conn, &rqst, sizeof(rqst)); + global.conn->ka_state = KA_OUTSTANDING; + } + /* check incoming/outgoing call states for !IDLE && !ESTABLISHED */ + for (i = 0; i < vector_size(global.conn->call); i++) { + PPTP_CALL * call = vector_get_Nth(global.conn->call, i); + if (call->call_type == PPTP_CALL_PNS) { + if (call->state.pns == PNS_WAIT_REPLY) { + /* send close request */ + pptp_call_close(global.conn, call); + assert(call->state.pns == PNS_WAIT_DISCONNECT); + } else if (call->state.pns == PNS_WAIT_DISCONNECT) { + /* hard-close the call */ + pptp_call_destroy(global.conn, call); + } + } else if (call->call_type == PPTP_CALL_PAC) { + if (call->state.pac == PAC_WAIT_REPLY) { + /* XXX FIXME -- drop the PAC connection XXX */ + } else if (call->state.pac == PAC_WAIT_CS_ANS) { + /* XXX FIXME -- drop the PAC connection XXX */ + } + } + } + pptp_reset_timer(); +} |