/* $OpenBSD: pptp_ctrl.c,v 1.13 2021/03/29 03:54:40 yasuoka Exp $ */ /*- * Copyright (c) 2009 Internet Initiative Japan Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /**@file * PPTP(RFC 2637) control connection implementation. * currently it only support PAC part */ /* $Id: pptp_ctrl.c,v 1.13 2021/03/29 03:54:40 yasuoka Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bytebuf.h" #include "debugutil.h" #include "hash.h" #include "slist.h" #include "time_utils.h" #include "version.h" #include "pptp.h" #include "pptp_local.h" #include "pptp_subr.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) /* periods of pptp_ctrl_timeout */ #define PPTP_CTRL_TIMEOUT_IVAL_SEC 2 #ifdef PPTP_CTRL_DEBUG #define PPTP_CTRL_ASSERT(x) ASSERT(x) #define PPTP_CTRL_DBG(x) pptp_ctrl_log x #else #define PPTP_CTRL_ASSERT(x) #define PPTP_CTRL_DBG(x) #endif static unsigned pptp_ctrl_seqno = 0; static void pptp_ctrl_log (pptp_ctrl *, int, const char *, ...) __printflike(3,4); static void pptp_ctrl_timeout (int, short, void *); static void pptp_ctrl_reset_timeout (pptp_ctrl *); static void pptp_ctrl_io_event (int, short, void *); static void pptp_ctrl_set_io_event (pptp_ctrl *); static int pptp_ctrl_output_flush (pptp_ctrl *); static void pptp_ctrl_SCCRx_string (struct pptp_scc *, u_char *, int); static int pptp_ctrl_recv_SCCRQ (pptp_ctrl *, u_char *, int); static int pptp_ctrl_recv_StopCCRP (pptp_ctrl *, u_char *, int); static int pptp_ctrl_send_StopCCRQ (pptp_ctrl *, int); static int pptp_ctrl_recv_StopCCRQ (pptp_ctrl *, u_char *, int); static int pptp_ctrl_send_StopCCRP (pptp_ctrl *, int, int); static int pptp_ctrl_send_SCCRP (pptp_ctrl *, int, int); static void pptp_ctrl_send_CDN (pptp_ctrl *, int, int, int, const char *); static void pptp_ctrl_process_echo_req (pptp_ctrl *, u_char *, int); static int pptp_ctrl_recv_echo_rep (pptp_ctrl *, u_char *, int); static void pptp_ctrl_send_echo_req (pptp_ctrl *); static int pptp_ctrl_input (pptp_ctrl *, u_char *, int); static int pptp_ctrl_call_input (pptp_ctrl *, int, u_char *, int); static const char *pptp_ctrl_state_string (int); static void pptp_ctrl_fini(pptp_ctrl *); /* * pptp_ctrl instance operation functions */ pptp_ctrl * pptp_ctrl_create(void) { pptp_ctrl *_this; if ((_this = malloc(sizeof(pptp_ctrl))) == NULL) return NULL; return _this; } int pptp_ctrl_init(pptp_ctrl *_this) { time_t curr_time; PPTP_CTRL_ASSERT(_this != NULL); curr_time = get_monosec(); memset(_this, 0, sizeof(pptp_ctrl)); _this->id = pptp_ctrl_seqno++; _this->sock = -1; if ((_this->recv_buf = bytebuffer_create(PPTP_BUFSIZ)) == NULL) { pptp_ctrl_log(_this, LOG_ERR, "bytebuffer_create() failed at " "%s(): %m", __func__); goto fail; } if ((_this->send_buf = bytebuffer_create(PPTP_BUFSIZ)) == NULL) { pptp_ctrl_log(_this, LOG_ERR, "bytebuffer_create() failed at " "%s(): %m", __func__); goto fail; } _this->last_rcv_ctrl = curr_time; _this->last_snd_ctrl = curr_time; _this->echo_seq = arc4random(); _this->echo_interval = PPTP_CTRL_DEFAULT_ECHO_INTERVAL; _this->echo_timeout = PPTP_CTRL_DEFAULT_ECHO_TIMEOUT; slist_init(&_this->call_list); evtimer_set(&_this->ev_timer, pptp_ctrl_timeout, _this); return 0; fail: return 1; } int pptp_ctrl_start(pptp_ctrl *_this) { int ival; char hbuf0[NI_MAXHOST], sbuf0[NI_MAXSERV]; char hbuf1[NI_MAXHOST], sbuf1[NI_MAXSERV]; struct sockaddr_storage sock; socklen_t socklen; PPTP_CTRL_ASSERT(_this != NULL); PPTP_CTRL_ASSERT(_this->sock >= 0); /* convert address to strings for logging */ strlcpy(hbuf0, "", sizeof(hbuf0)); strlcpy(sbuf0, "", sizeof(sbuf0)); strlcpy(hbuf1, "", sizeof(hbuf1)); strlcpy(sbuf1, "", sizeof(sbuf1)); if (getnameinfo((struct sockaddr *)&_this->peer, _this->peer.ss_len, hbuf0, sizeof(hbuf0), sbuf0, sizeof(sbuf0), NI_NUMERICHOST | NI_NUMERICSERV) != 0) { pptp_ctrl_log(_this, LOG_ERR, "getnameinfo() failed at %s(): %m", __func__); } socklen = sizeof(sock); if (getsockname(_this->sock, (struct sockaddr *)&sock, &socklen) != 0) { pptp_ctrl_log(_this, LOG_ERR, "getsockname() failed at %s(): %m", __func__); goto fail; } if (getnameinfo((struct sockaddr *)&sock, sock.ss_len, hbuf1, sizeof(hbuf1), sbuf1, sizeof(sbuf1), NI_NUMERICHOST | NI_NUMERICSERV) != 0) { pptp_ctrl_log(_this, LOG_ERR, "getnameinfo() failed at %s(): %m", __func__); } pptp_ctrl_log(_this, LOG_INFO, "Starting peer=%s:%s/tcp " "sock=%s:%s/tcp", hbuf0, sbuf0, hbuf1, sbuf1); if ((ival = fcntl(_this->sock, F_GETFL)) < 0) { pptp_ctrl_log(_this, LOG_ERR, "fcntl(F_GET_FL) failed at %s(): %m", __func__); goto fail; } else if (fcntl(_this->sock, F_SETFL, ival | O_NONBLOCK) < 0) { pptp_ctrl_log(_this, LOG_ERR, "fcntl(F_SET_FL) failed at %s(): %m", __func__); goto fail; } pptp_ctrl_set_io_event(_this); pptp_ctrl_reset_timeout(_this); return 0; fail: return 1; } /* Timer */ static void pptp_ctrl_timeout(int fd, short event, void *ctx) { int i; pptp_call *call; pptp_ctrl *_this; time_t last, curr_time; _this = ctx; curr_time = get_monosec(); PPTP_CTRL_DBG((_this, DEBUG_LEVEL_3, "enter %s()", __func__)); /* clean up call */ i = 0; while (i < slist_length(&_this->call_list)) { call = slist_get(&_this->call_list, i); if (call->state == PPTP_CALL_STATE_CLEANUP_WAIT && curr_time - call->last_io > PPTP_CALL_CLEANUP_WAIT_TIME) { pptp_call_stop(call); pptp_call_destroy(call); slist_remove(&_this->call_list, i); } else i++; } /* State machine: Timeout */ switch (_this->state) { default: case PPTP_CTRL_STATE_WAIT_CTRL_REPLY: case PPTP_CTRL_STATE_IDLE: if (curr_time - _this->last_rcv_ctrl > PPTPD_IDLE_TIMEOUT) { pptp_ctrl_log(_this, LOG_ERR, "Timeout in state %s", pptp_ctrl_state_string(_this->state)); pptp_ctrl_fini(_this); return; } break; case PPTP_CTRL_STATE_ESTABLISHED: last = MAXIMUM(_this->last_rcv_ctrl, _this->last_snd_ctrl); if (curr_time - _this->last_rcv_ctrl >= _this->echo_interval + _this->echo_timeout) { pptp_ctrl_log(_this, LOG_INFO, "Timeout waiting for echo reply"); pptp_ctrl_fini(_this); return; } if (curr_time - last >= _this->echo_interval) { PPTP_CTRL_DBG((_this, LOG_DEBUG, "Echo")); _this->echo_seq++; pptp_ctrl_send_echo_req(_this); } break; case PPTP_CTRL_STATE_WAIT_STOP_REPLY: if (curr_time - _this->last_snd_ctrl > PPTP_CTRL_StopCCRP_WAIT_TIME) { pptp_ctrl_log(_this, LOG_WARNING, "Timeout waiting for StopCCRP"); pptp_ctrl_fini(_this); return; } break; case PPTP_CTRL_STATE_DISPOSING: pptp_ctrl_fini(_this); return; } pptp_ctrl_reset_timeout(_this); } static void pptp_ctrl_reset_timeout(pptp_ctrl *_this) { struct timeval tv; switch (_this->state) { case PPTP_CTRL_STATE_DISPOSING: /* call back immediately */ timerclear(&tv); break; default: tv.tv_sec = PPTP_CTRL_TIMEOUT_IVAL_SEC; tv.tv_usec = 0; break; } evtimer_add(&_this->ev_timer, &tv); } /** * Terminate PPTP control connection * @result The value for Stop-Control-Connection-Request(StopCCRQ) result. This function will not sent StopCCRQ when the value == 0 and the specification does not require to sent it. * @see ::#PPTP_StopCCRQ_REASON_STOP_PROTOCOL * @see ::#PPTP_StopCCRQ_REASON_STOP_LOCAL_SHUTDOWN */ void pptp_ctrl_stop(pptp_ctrl *_this, int result) { int i; pptp_call *call; switch (_this->state) { case PPTP_CTRL_STATE_WAIT_STOP_REPLY: /* waiting response. */ /* this state will timeout by pptp_ctrl_timeout */ break; case PPTP_CTRL_STATE_ESTABLISHED: if (result != 0) { for (i = 0; i < slist_length(&_this->call_list); i++) { call = slist_get(&_this->call_list, i); pptp_call_disconnect(call, PPTP_CDN_RESULT_ADMIN_SHUTDOWN, 0, NULL); } pptp_ctrl_send_StopCCRQ(_this, result); _this->state = PPTP_CTRL_STATE_WAIT_STOP_REPLY; break; } /* FALLTHROUGH */ default: pptp_ctrl_fini(_this); } return; } /* finish PPTP control */ static void pptp_ctrl_fini(pptp_ctrl *_this) { pptp_call *call; PPTP_CTRL_ASSERT(_this != NULL); if (_this->sock >= 0) { event_del(&_this->ev_sock); close(_this->sock); _this->sock = -1; } for (slist_itr_first(&_this->call_list); slist_itr_has_next(&_this->call_list);) { call = slist_itr_next(&_this->call_list); pptp_call_stop(call); pptp_call_destroy(call); slist_itr_remove(&_this->call_list); } if (_this->on_io_event != 0) { /* * as the complete terminate process needs complicated * exception handling, do partially at here. * rest of part will be handled by timer-event-handler. */ PPTP_CTRL_DBG((_this, LOG_DEBUG, "Disposing")); _this->state = PPTP_CTRL_STATE_DISPOSING; pptp_ctrl_reset_timeout(_this); return; } evtimer_del(&_this->ev_timer); slist_fini(&_this->call_list); pptp_ctrl_log (_this, LOG_NOTICE, "logtype=Finished"); /* disable _this */ pptpd_ctrl_finished_notify(_this->pptpd, _this); } /* free PPTP control context */ void pptp_ctrl_destroy(pptp_ctrl *_this) { if (_this->send_buf != NULL) { bytebuffer_destroy(_this->send_buf); _this->send_buf = NULL; } if (_this->recv_buf != NULL) { bytebuffer_destroy(_this->recv_buf); _this->recv_buf = NULL; } free(_this); } /* * network I/O */ /* I/O event dispather */ static void pptp_ctrl_io_event(int fd, short evmask, void *ctx) { int sz, lpkt, hdrlen; u_char *pkt; pptp_ctrl *_this; _this = ctx; PPTP_CTRL_ASSERT(_this != NULL); _this->on_io_event = 1; if ((evmask & EV_WRITE) != 0) { if (pptp_ctrl_output_flush(_this) != 0 || _this->state == PPTP_CTRL_STATE_DISPOSING) goto fail; _this->send_ready = 1; } if ((evmask & EV_READ) != 0) { sz = read(_this->sock, bytebuffer_pointer(_this->recv_buf), bytebuffer_remaining(_this->recv_buf)); if (sz <= 0) { if (sz == 0 || errno == ECONNRESET) { pptp_ctrl_log(_this, LOG_INFO, "Connection closed by foreign host"); pptp_ctrl_fini(_this); goto fail; } else if (errno != EAGAIN && errno != EINTR) { pptp_ctrl_log(_this, LOG_INFO, "read() failed at %s(): %m", __func__); pptp_ctrl_fini(_this); goto fail; } } bytebuffer_put(_this->recv_buf, BYTEBUFFER_PUT_DIRECT, sz); bytebuffer_flip(_this->recv_buf); for (;;) { pkt = bytebuffer_pointer(_this->recv_buf); lpkt = bytebuffer_remaining(_this->recv_buf); if (pkt == NULL || lpkt < sizeof(struct pptp_ctrl_header)) break; /* read again */ hdrlen = pkt[0] << 8 | pkt[1]; if (lpkt < hdrlen) break; /* read again */ bytebuffer_get(_this->recv_buf, NULL, hdrlen); if (pptp_ctrl_input(_this, pkt, hdrlen) != 0 || _this->state == PPTP_CTRL_STATE_DISPOSING) { bytebuffer_compact(_this->recv_buf); goto fail; } } bytebuffer_compact(_this->recv_buf); } if (pptp_ctrl_output_flush(_this) != 0) goto fail; pptp_ctrl_set_io_event(_this); fail: _this->on_io_event = 0; } /* set i/o event mask */ static void pptp_ctrl_set_io_event(pptp_ctrl *_this) { int evmask; PPTP_CTRL_ASSERT(_this != NULL); PPTP_CTRL_ASSERT(_this->sock >= 0); evmask = 0; if (bytebuffer_remaining(_this->recv_buf) > 128) evmask |= EV_READ; if (_this->send_ready == 0) evmask |= EV_WRITE; event_del(&_this->ev_sock); if (evmask != 0) { event_set(&_this->ev_sock, _this->sock, evmask, pptp_ctrl_io_event, _this); event_add(&_this->ev_sock, NULL); } } /** * Output PPTP control packet * @param pkt pointer to packet buffer. * when it was appended by _this-.send_buf using bytebuffer, * specify NULL. * @param lpkt packet length */ void pptp_ctrl_output(pptp_ctrl *_this, u_char *pkt, int lpkt) { PPTP_CTRL_ASSERT(_this != NULL); PPTP_CTRL_ASSERT(lpkt > 0); /* just put the packet into the buffer now. send it later */ bytebuffer_put(_this->send_buf, pkt, lpkt); if (_this->on_io_event != 0) { /* * pptp_ctrl_output_flush() will be called by the end of * the I/O event handler. */ } else { /* * When this function is called by other than I/O event handler, * we need to call pptp_ctrl_output_flush(). However if we do * it here, then we need to consider the situation * 'flush => write failure => finalize'. The situation requires * the caller function to handle the exception and causes * complication. So we call pptp_ctrl_output_flush() by the * the next send ready event. */ _this->send_ready = 0; /* clear 'send ready' */ pptp_ctrl_set_io_event(_this); /* wait 'send ready */ } return; } /* Send Stop-Control-Connection-Request */ /* flush output packet */ static int pptp_ctrl_output_flush(pptp_ctrl *_this) { int sz; time_t curr_time; curr_time = get_monosec(); if (bytebuffer_position(_this->send_buf) <= 0) return 0; /* nothing to write */ if (_this->send_ready == 0) { pptp_ctrl_set_io_event(_this); return 0; /* not ready to write */ } bytebuffer_flip(_this->send_buf); if (PPTP_CTRL_CONF(_this)->ctrl_out_pktdump != 0) { pptp_ctrl_log(_this, LOG_DEBUG, "PPTP Control output packet"); show_hd(debug_get_debugfp(), bytebuffer_pointer(_this->send_buf), bytebuffer_remaining(_this->send_buf)); } if ((sz = write(_this->sock, bytebuffer_pointer(_this->send_buf), bytebuffer_remaining(_this->send_buf))) < 0) { pptp_ctrl_log(_this, LOG_ERR, "write to socket failed: %m"); pptp_ctrl_fini(_this); return 1; } _this->last_snd_ctrl = curr_time; bytebuffer_get(_this->send_buf, NULL, sz); bytebuffer_compact(_this->send_buf); _this->send_ready = 0; return 0; } /* convert Start-Control-Connection-{Request,Reply} packet to strings */ static void pptp_ctrl_SCCRx_string(struct pptp_scc *scc, u_char *buf, int lbuf) { char results[128]; if (scc->result_code != 0) snprintf(results, sizeof(results), "result=%d error=%d ", scc->result_code, scc->error_code); else results[0] = '\0'; snprintf(buf, lbuf, "protocol_version=%d.%d %sframing=%s bearer=%s max_channels=%d " "firmware_revision=%d(0x%04x) host_name=\"%.*s\" " "vendor_string=\"%.*s\"", scc->protocol_version >> 8, scc->protocol_version & 0xff, results, pptp_framing_string(scc->framing_caps), pptp_bearer_string(scc->bearer_caps), scc->max_channels, scc->firmware_revision, scc->firmware_revision, (u_int)sizeof(scc->host_name), scc->host_name, (u_int)sizeof(scc->vendor_string), scc->vendor_string); } /* receive Start-Control-Connection-Request */ static int pptp_ctrl_recv_SCCRQ(pptp_ctrl *_this, u_char *pkt, int lpkt) { char logbuf[512]; struct pptp_scc *scc; /* sanity check */ if (lpkt < sizeof(struct pptp_scc)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad SCCRQ: packet too " "short: %d < %d", lpkt, (int)sizeof(struct pptp_scc)); return 1; } scc = (struct pptp_scc *)pkt; scc->protocol_version = ntohs(scc->protocol_version); scc->framing_caps = htonl(scc->framing_caps); scc->bearer_caps = htonl(scc->bearer_caps); scc->max_channels = htons(scc->max_channels); scc->firmware_revision = htons(scc->firmware_revision); /* check protocol version */ if (scc->protocol_version != PPTP_RFC_2637_VERSION) { pptp_ctrl_log(_this, LOG_ERR, "Received bad SCCRQ: " "unknown protocol version %d", scc->protocol_version); return 1; } pptp_ctrl_SCCRx_string(scc, logbuf, sizeof(logbuf)); pptp_ctrl_log(_this, LOG_INFO, "RecvSCCRQ %s", logbuf); return 0; } /* Receive Stop-Control-Connection-Reply */ static int pptp_ctrl_recv_StopCCRP(pptp_ctrl *_this, u_char *pkt, int lpkt) { struct pptp_stop_ccrp *stop_ccrp; if (lpkt < sizeof(struct pptp_stop_ccrp)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad StopCCRP: packet " "too short: %d < %d", lpkt, (int)sizeof(struct pptp_stop_ccrp)); return 1; } stop_ccrp = (struct pptp_stop_ccrp *)pkt; pptp_ctrl_log(_this, LOG_INFO, "RecvStopCCRP reason=%s(%u)", pptp_StopCCRP_result_string(stop_ccrp->result), stop_ccrp->result); return 0; } static int pptp_ctrl_send_StopCCRQ(pptp_ctrl *_this, int reason) { int lpkt; struct pptp_stop_ccrq *stop_ccrq; stop_ccrq = bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (lpkt < sizeof(struct pptp_stop_ccrq)) { pptp_ctrl_log(_this, LOG_ERR, "SendCCRP failed: No buffer space available"); return -1; } memset(stop_ccrq, 0, sizeof(struct pptp_stop_ccrq)); pptp_init_header(&stop_ccrq->header, sizeof(struct pptp_stop_ccrq), PPTP_CTRL_MES_CODE_StopCCRQ); stop_ccrq->reason = reason; pptp_ctrl_log(_this, LOG_INFO, "SendStopCCRQ reason=%s(%u)", pptp_StopCCRQ_reason_string(stop_ccrq->reason), stop_ccrq->reason); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_stop_ccrq)); return 0; } /* Receive Stop-Control-Connection-Request */ static int pptp_ctrl_recv_StopCCRQ(pptp_ctrl *_this, u_char *pkt, int lpkt) { struct pptp_stop_ccrq *stop_ccrq; if (lpkt < sizeof(struct pptp_stop_ccrq)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad StopCCRQ: packet " "too short: %d < %d", lpkt, (int)sizeof(struct pptp_stop_ccrq)); return 1; } stop_ccrq = (struct pptp_stop_ccrq *)pkt; pptp_ctrl_log(_this, LOG_INFO, "RecvStopCCRQ reason=%s(%u)", pptp_StopCCRQ_reason_string(stop_ccrq->reason), stop_ccrq->reason); return 0; } /* Send Stop-Control-Connection-Reply */ static int pptp_ctrl_send_StopCCRP(pptp_ctrl *_this, int result, int error) { int lpkt; struct pptp_stop_ccrp *stop_ccrp; stop_ccrp = bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (lpkt < sizeof(struct pptp_stop_ccrp)) { pptp_ctrl_log(_this, LOG_ERR, "SendCCRQ failed: No buffer space available"); return -1; } memset(stop_ccrp, 0, sizeof(struct pptp_stop_ccrp)); pptp_init_header(&stop_ccrp->header, sizeof(struct pptp_stop_ccrp), PPTP_CTRL_MES_CODE_StopCCRP); stop_ccrp->result = result; stop_ccrp->error = error; pptp_ctrl_log(_this, LOG_INFO, "SendStopCCRP result=%s(%u) error=%s(%u)", pptp_StopCCRP_result_string(stop_ccrp->result), stop_ccrp->result, pptp_general_error_string(stop_ccrp->error), stop_ccrp->error); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_stop_ccrp)); return 0; } /* Send Start-Control-Connection-Reply */ static int pptp_ctrl_send_SCCRP(pptp_ctrl *_this, int result, int error) { int lpkt; struct pptp_scc *scc; char logbuf[512]; const char *val; scc = bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (lpkt < sizeof(struct pptp_scc)) { pptp_ctrl_log(_this, LOG_ERR, "SendSCCRP failed: No buffer space available"); return -1; } memset(scc, 0, sizeof(struct pptp_scc)); pptp_init_header(&scc->header, sizeof(struct pptp_scc), PPTP_CTRL_MES_CODE_SCCRP); scc->protocol_version = PPTP_RFC_2637_VERSION; scc->result_code = result; scc->error_code = error; /* XXX only support sync frames */ scc->framing_caps = PPTP_CTRL_FRAMING_SYNC; scc->bearer_caps = PPTP_CTRL_BEARER_DIGITAL; scc->max_channels = 4; /* XXX */ scc->firmware_revision = MAJOR_VERSION << 8 | MINOR_VERSION; /* this implementation only support these strings up to * 63 character */ /* host name */ if ((val = PPTP_CTRL_CONF(_this)->hostname) == NULL) val = ""; strlcpy(scc->host_name, val, sizeof(scc->host_name)); /* vendor name */ if (PPTP_CTRL_CONF(_this)->vendor_name == NULL) val = PPTPD_DEFAULT_VENDOR_NAME; strlcpy(scc->vendor_string, val, sizeof(scc->vendor_string)); pptp_ctrl_SCCRx_string(scc, logbuf, sizeof(logbuf)); pptp_ctrl_log(_this, LOG_INFO, "SendSCCRP %s", logbuf); scc->protocol_version = htons(scc->protocol_version); scc->framing_caps = htonl(scc->framing_caps); scc->bearer_caps = htonl(scc->bearer_caps); scc->max_channels = htons(scc->max_channels); scc->firmware_revision = htons(scc->firmware_revision); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_scc)); return 0; } /* receive ECHO and reply */ static void pptp_ctrl_process_echo_req(pptp_ctrl *_this, u_char *pkt, int lpkt) { struct pptp_echo_rq *echo_rq; struct pptp_echo_rp *echo_rp; if (lpkt < sizeof(struct pptp_echo_rq)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad EchoReq: packet " "too short: %d < %d", lpkt, (int)sizeof(struct pptp_echo_rq)); return; } echo_rq = (struct pptp_echo_rq *)pkt; PPTP_CTRL_DBG((_this, LOG_DEBUG, "RecvEchoReq")); echo_rp = bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (echo_rp == NULL || lpkt < sizeof(struct pptp_echo_rp)) { pptp_ctrl_log(_this, LOG_ERR, "Failed to send EchoReq: No buffer space available"); return; } memset(echo_rp, 0, sizeof(struct pptp_echo_rp)); pptp_init_header(&echo_rp->header, sizeof(struct pptp_echo_rp), PPTP_CTRL_MES_CODE_ECHO_RP); echo_rp->identifier = echo_rq->identifier; echo_rp->result_code = PPTP_ECHO_RP_RESULT_OK; echo_rp->error_code = PPTP_ERROR_NONE; echo_rp->reserved1 = htons(0); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_echo_rp)); PPTP_CTRL_DBG((_this, LOG_DEBUG, "SendEchoReply")); } /* receiver Echo-Reply */ static int pptp_ctrl_recv_echo_rep(pptp_ctrl *_this, u_char *pkt, int lpkt) { struct pptp_echo_rp *echo_rp; if (lpkt < sizeof(struct pptp_echo_rp)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad EchoReq: packet " "too short: %d < %d", lpkt, (int)sizeof(struct pptp_echo_rp)); return 1; } echo_rp = (struct pptp_echo_rp *)pkt; if (echo_rp->result_code != PPTP_ECHO_RP_RESULT_OK) { pptp_ctrl_log(_this, LOG_ERR, "Received negative EchoReply: %s", pptp_general_error_string(echo_rp->error_code)); return 1; } if (_this->echo_seq != ntohl(echo_rp->identifier)) { pptp_ctrl_log(_this, LOG_ERR, "Received bad EchoReply: " "Identifier mismatch sent=%u recv=%u", _this->echo_seq , ntohl(echo_rp->identifier)); return 1; } PPTP_CTRL_DBG((_this, LOG_DEBUG, "RecvEchoReply")); return 0; } /* send Echo-Request */ static void pptp_ctrl_send_echo_req(pptp_ctrl *_this) { int lpkt; struct pptp_echo_rq *echo_rq; echo_rq = (struct pptp_echo_rq *)bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (echo_rq == NULL || lpkt < sizeof(struct pptp_echo_rq)) { pptp_ctrl_log(_this, LOG_ERR, "SendEchoReq failed: No buffer space available"); return; } memset(echo_rq, 0, sizeof(struct pptp_echo_rq)); pptp_init_header(&echo_rq->header, sizeof(struct pptp_echo_rq), PPTP_CTRL_MES_CODE_ECHO_RQ); echo_rq->identifier = htonl(_this->echo_seq); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_echo_rq)); PPTP_CTRL_DBG((_this, LOG_DEBUG, "SendEchoReq")); } /* send Call-Disconnect-Notify */ static void pptp_ctrl_send_CDN(pptp_ctrl *_this, int result, int error, int cause, const char *statistics) { int lpkt; struct pptp_cdn *cdn; cdn = bytebuffer_pointer(_this->send_buf); lpkt = bytebuffer_remaining(_this->send_buf); if (lpkt < sizeof(struct pptp_cdn)) { pptp_ctrl_log(_this, LOG_ERR, "SendCCR failed: No buffer space available"); return; } memset(cdn, 0, sizeof(struct pptp_cdn)); pptp_init_header(&cdn->header, sizeof(struct pptp_cdn), PPTP_CTRL_MES_CODE_CDN); cdn->call_id = _this->id; cdn->result_code = result; cdn->error_code = error; cdn->cause_code = cause; if (statistics != NULL) strlcpy(cdn->statistics, statistics, sizeof(cdn->statistics)); cdn->call_id = htons(cdn->call_id); cdn->cause_code = htons(cdn->cause_code); pptp_ctrl_output(_this, NULL, sizeof(struct pptp_cdn)); } /* receive Control-packet */ static int pptp_ctrl_input(pptp_ctrl *_this, u_char *pkt, int lpkt) { char errmes[256]; time_t curr_time; struct pptp_ctrl_header *hdr; PPTP_CTRL_ASSERT(lpkt >= sizeof(struct pptp_ctrl_header)); curr_time = get_monosec(); hdr = (struct pptp_ctrl_header *)pkt; hdr->length = ntohs(hdr->length); hdr->pptp_message_type = ntohs(hdr->pptp_message_type); hdr->magic_cookie = ntohl(hdr->magic_cookie); hdr->control_message_type = ntohs(hdr->control_message_type); hdr->reserved0 = ntohs(hdr->reserved0); /* sanity check */ PPTP_CTRL_ASSERT(hdr->length <= lpkt); _this->last_rcv_ctrl = curr_time; if (PPTP_CTRL_CONF(_this)->ctrl_in_pktdump != 0) { pptp_ctrl_log(_this, LOG_DEBUG, "PPTP Control input packet dump: mestype=%s(%d)", pptp_ctrl_mes_type_string(hdr->control_message_type), hdr->control_message_type); show_hd(debug_get_debugfp(), pkt, lpkt); } /* inspect packet body */ /* message type */ if (hdr->pptp_message_type != PPTP_MES_TYPE_CTRL) { snprintf(errmes, sizeof(errmes), "unknown message type %d", hdr->pptp_message_type); goto bad_packet; } /* magic cookie */ if (hdr->magic_cookie != PPTP_MAGIC_COOKIE) { snprintf(errmes, sizeof(errmes), "wrong magic %08x != %08x", hdr->magic_cookie, PPTP_MAGIC_COOKIE); goto bad_packet; } /* As there is possibility of state conflicts, * ECHO Reply requiries special care. */ switch (hdr->control_message_type) { case PPTP_CTRL_MES_CODE_ECHO_RP: if (pptp_ctrl_recv_echo_rep(_this, pkt, lpkt) != 0) { pptp_ctrl_fini(_this); return 1; } return 0; } /* * State machine */ switch (_this->state) { case PPTP_CTRL_STATE_IDLE: switch (hdr->control_message_type) { case PPTP_CTRL_MES_CODE_SCCRQ: if (pptp_ctrl_recv_SCCRQ(_this, pkt, lpkt) != 0) { return 0; } if (pptp_ctrl_send_SCCRP(_this, PPTP_SCCRP_RESULT_SUCCESS, PPTP_ERROR_NONE) != 0) { return 0; } _this->state = PPTP_CTRL_STATE_ESTABLISHED; return 0; default: break; } break; case PPTP_CTRL_STATE_ESTABLISHED: switch (hdr->control_message_type) { case PPTP_CTRL_MES_CODE_ECHO_RQ: pptp_ctrl_process_echo_req(_this, pkt, lpkt); return 0; /* dispatch to pptp_call_input() if it is call-related-packet */ case PPTP_CTRL_MES_CODE_SLI: case PPTP_CTRL_MES_CODE_ICRQ: case PPTP_CTRL_MES_CODE_ICRP: case PPTP_CTRL_MES_CODE_OCRQ: case PPTP_CTRL_MES_CODE_OCRP: case PPTP_CTRL_MES_CODE_ICCN: case PPTP_CTRL_MES_CODE_CDN: case PPTP_CTRL_MES_CODE_CCR: return pptp_ctrl_call_input(_this, hdr->control_message_type, pkt, lpkt); case PPTP_CTRL_MES_CODE_StopCCRQ: if (pptp_ctrl_recv_StopCCRQ(_this, pkt, lpkt) != 0) { pptp_ctrl_stop(_this, PPTP_StopCCRQ_REASON_STOP_PROTOCOL); return 0; } if (pptp_ctrl_send_StopCCRP(_this, PPTP_StopCCRP_RESULT_OK, PPTP_ERROR_NONE)!= 0) { return 0; } pptp_ctrl_fini(_this); return 1; default: break; } case PPTP_CTRL_STATE_WAIT_STOP_REPLY: switch (hdr->control_message_type) { case PPTP_CTRL_MES_CODE_StopCCRP: pptp_ctrl_recv_StopCCRP(_this, pkt, lpkt); pptp_ctrl_fini(_this); return 1; } break; case PPTP_CTRL_STATE_WAIT_CTRL_REPLY: /* XXX this implementation only support PAC mode */ break; } pptp_ctrl_log(_this, LOG_WARNING, "Unhandled control message type=%s(%d)", pptp_ctrl_mes_type_string(hdr->control_message_type), hdr->control_message_type); return 0; bad_packet: pptp_ctrl_log(_this, LOG_ERR, "Received bad packet: %s", errmes); pptp_ctrl_stop(_this, PPTP_StopCCRQ_REASON_STOP_PROTOCOL); return 0; } /* receiver PPTP Call related messages */ static int pptp_ctrl_call_input(pptp_ctrl *_this, int mes_type, u_char *pkt, int lpkt) { int i, call_id, lpkt0; pptp_call *call; const char *reason; u_char *pkt0; pkt0 = pkt; lpkt0 = lpkt; call_id = -1; pkt += sizeof(struct pptp_ctrl_header); lpkt -= sizeof(struct pptp_ctrl_header); reason = "(no reason)"; /* sanity check */ if (lpkt < 4) { reason = "received packet is too short"; goto badpacket; } call = NULL; call_id = ntohs(*(uint16_t *)pkt); switch (mes_type) { case PPTP_CTRL_MES_CODE_SLI: /* PNS <=> PAC */ /* only SLI contains Call-ID of this peer */ for (i = 0; i < slist_length(&_this->call_list); i++) { call = slist_get(&_this->call_list, i); if (call->id == call_id) break; call = NULL; } if (call == NULL) { reason = "Call Id is not associated by this control"; goto badpacket; } goto call_searched; case PPTP_CTRL_MES_CODE_ICRP: /* PNS => PAC */ /* * as this implementation never sent ICRQ, this case * should not happen. * But just to make sure, pptp_call.c can handle this * message. */ /* FALLTHROUGH */ case PPTP_CTRL_MES_CODE_OCRQ: /* PNS => PAC */ case PPTP_CTRL_MES_CODE_CCR: /* PNS => PAC */ /* liner-search will be enough */ for (i = 0; i < slist_length(&_this->call_list); i++) { call = slist_get(&_this->call_list, i); if (call->peers_call_id == call_id) break; call = NULL; } if (call == NULL && mes_type == PPTP_CTRL_MES_CODE_CCR) { pptp_ctrl_send_CDN(_this, PPTP_CDN_RESULT_GENRIC_ERROR, PPTP_ERROR_BAD_CALL, 0, NULL); goto call_searched; } if (mes_type == PPTP_CTRL_MES_CODE_OCRQ) { /* make new call */ if (call != NULL) { pptp_call_input(call, mes_type, pkt0, lpkt0); return 0; } if ((call = pptp_call_create()) == NULL) { pptp_ctrl_log(_this, LOG_ERR, "pptp_call_create() failed: %m"); goto fail; } if (pptp_call_init(call, _this) != 0) { pptp_ctrl_log(_this, LOG_ERR, "pptp_call_init() failed: %m"); pptp_call_destroy(call); goto fail; } slist_add(&_this->call_list, call); } call_searched: if (call == NULL) { reason = "Call Id is not associated by this control"; goto badpacket; } pptp_call_input(call, mes_type, pkt0, lpkt0); return 0; case PPTP_CTRL_MES_CODE_OCRP: /* PAC => PNS */ case PPTP_CTRL_MES_CODE_ICRQ: /* PAC => PNS */ case PPTP_CTRL_MES_CODE_ICCN: /* PAC => PNS */ case PPTP_CTRL_MES_CODE_CDN: /* PAC => PNS */ /* don't receive because above messages are only of PNS */ default: break; } reason = "Message type is unexpected."; /* FALLTHROUGH */ badpacket: pptp_ctrl_log(_this, LOG_INFO, "Received a bad %s(%d) call_id=%d: %s", pptp_ctrl_mes_type_string(mes_type), mes_type, call_id, reason); return 0; fail: pptp_ctrl_stop(_this, PPTP_StopCCRQ_REASON_STOP_PROTOCOL); return 0; } /* * utilities */ /* logging with the label of the instance */ static void pptp_ctrl_log(pptp_ctrl *_this, int prio, const char *fmt, ...) { char logbuf[BUFSIZ]; va_list ap; va_start(ap, fmt); #ifdef PPTPD_MULTIPLE snprintf(logbuf, sizeof(logbuf), "pptpd id=%u ctrl=%u %s", _this->pptpd->id, _this->id, fmt); #else snprintf(logbuf, sizeof(logbuf), "pptpd ctrl=%u %s", _this->id, fmt); #endif vlog_printf(prio, logbuf, ap); va_end(ap); } static const char * pptp_ctrl_state_string(int state) { switch (state) { case PPTP_CTRL_STATE_IDLE: return "idle"; case PPTP_CTRL_STATE_WAIT_CTRL_REPLY: return "wait-ctrl-reply"; case PPTP_CTRL_STATE_ESTABLISHED: return "established"; case PPTP_CTRL_STATE_WAIT_STOP_REPLY: return "wait-stop-reply"; } return "unknown"; }