/* pptp_ctrl.c ... handle PPTP control connection. * C. Scott Ananian * * $Id: pptp_ctrl.c,v 1.1.1.1 2008/10/15 03:30:51 james26_jang Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #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 */ extern int syncppp; /* 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(int sig); /* 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) { struct sigaction sigact; 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 */ sigact.sa_handler = pptp_handle_timer; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; global.conn = conn; sigaction(SIGALRM, &sigact, &global.old_sigaction); /* 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, pptp_call_cb callback, char *phonenr) { PPTP_CALL * call; int i; 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(PPTP_WINDOW), 0, 0, 0, {0}, {0} }; assert(conn && conn->call); assert(conn->conn_state == CONN_ESTABLISHED); /* Assign call id */ if (!vector_scan(conn->call, 0, PPTP_MAX_CHANNELS - 1, &i)) /* 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) i; 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, i, 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); sigaction(SIGALRM, &global.old_sigaction, NULL); 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; } /*** 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 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 (syncppp && !(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 sig) { 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*/ 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; /* XXX FIXME: wake up ctrl thread -- or will the SIGALRM do that * automagically? XXX */ } /* 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(); }