/*** 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; }
/* 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; }
/* 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((char *)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_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(); } } }
/*** 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(); }
/*** 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. */ }
/*** 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 [email protected] - * * 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; }