diff options
Diffstat (limited to 'usr.sbin/npppd/pptp/pptp_ctrl.c')
-rw-r--r-- | usr.sbin/npppd/pptp/pptp_ctrl.c | 1168 |
1 files changed, 1168 insertions, 0 deletions
diff --git a/usr.sbin/npppd/pptp/pptp_ctrl.c b/usr.sbin/npppd/pptp/pptp_ctrl.c new file mode 100644 index 00000000000..7d8941bbc11 --- /dev/null +++ b/usr.sbin/npppd/pptp/pptp_ctrl.c @@ -0,0 +1,1168 @@ +/*- + * 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) コントロール接続部の実装。PACのみ。 + */ +/* $Id: pptp_ctrl.c,v 1.1 2010/01/11 04:20:57 yasuoka Exp $ */ +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <netdb.h> +#include <unistd.h> +#include <syslog.h> +#include <time.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <event.h> + +#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" + +/** 2 秒毎に 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 インスタンス操作 + ************************************************************************/ +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 reigai; + } + if ((_this->send_buf = bytebuffer_create(PPTP_BUFSIZ)) == NULL) { + pptp_ctrl_log(_this, LOG_ERR, "bytebuffer_create() failed at " + "%s(): %m", __func__); + goto reigai; + } + _this->last_rcv_ctrl = curr_time; + _this->last_snd_ctrl = curr_time; + _this->echo_seq = (random() << 16 )| (random() & 0xffff); + _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; +reigai: + 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); + + /* ログ用にアドレス=>文字列変換 */ + + strlcpy(hbuf0, "<unknown>", sizeof(hbuf0)); + strlcpy(sbuf0, "<unknown>", sizeof(sbuf0)); + strlcpy(hbuf1, "<unknown>", sizeof(hbuf1)); + strlcpy(sbuf1, "<unknown>", 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 reigai; + } + 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)) < 0) { + pptp_ctrl_log(_this, LOG_ERR, + "fcntl(F_GET_FL) failed at %s(): %m", __func__); + goto reigai; + } 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 reigai; + } + pptp_ctrl_set_io_event(_this); + pptp_ctrl_reset_timeout(_this); + + return 0; +reigai: + return 1; +} + +/** タイマー処理 */ +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__)); + /* 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++; + } + + /* ステートマシン、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 = MAX(_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: + tv.tv_sec = 0; /* すぐに call back */ + tv.tv_usec = 0; + break; + default: + tv.tv_sec = PPTP_CTRL_TIMEOUT_IVAL_SEC; + tv.tv_usec = 0; + break; + } + evtimer_add(&_this->ev_timer, &tv); +} + +/** + * PPTPコントロールコネクションを終了します。 + * @result Stop-Control-Connection-Request(StopCCRQ) の result に指定する + * 値。0 を指定すると、StopCCRQ を送信せずに、終了します。また、 + * プロトコル上、StopCCRQ を送信する必要がない場合も送信しません。 + * @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: + // 返事待ち。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; + } + // FALL THROUGH + default: + pptp_ctrl_fini(_this); + } + return; +} + + +/** PPTP コントロールを終了化します。*/ +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) { + /* + * I/O イベントハンドラ内での終了化処理は、最後まで行うと + * 例外処理が複雑になるので、途中までしか行わす、続きは、 + * タイマイベントハンドラで行う。 + */ + 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"); + + /* この後 _this は使用できない */ + pptpd_ctrl_finished_notify(_this->pptpd, _this); +} + +/* PPTP コントロールコンテキストを解放します。*/ +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); +} + +/************************************************************************ + * ネットワーク I/O + ************************************************************************/ +/** I/O イベントディスパッチャ */ +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 reigai; + _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 (errno == ECONNRESET || sz == 0) { + pptp_ctrl_log(_this, LOG_INFO, + "Connection closed by foreign host"); + pptp_ctrl_fini(_this); + goto reigai; + } else if (errno != EAGAIN && errno != EINTR) { + pptp_ctrl_log(_this, LOG_INFO, + "read() failed at %s(): %m", __func__); + pptp_ctrl_fini(_this); + goto reigai; + } + } + 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 reigai; + } + } + bytebuffer_compact(_this->recv_buf); + } + if (pptp_ctrl_output_flush(_this) != 0) + goto reigai; + pptp_ctrl_set_io_event(_this); +reigai: + _this->on_io_event = 0; +} + + +/** イベントマスクを設定する */ +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); + } +} + +/** + * PPTPコントロールパケットを出力します。 + * @param pkt パケットの領域へのポインタ。 + * bytebuffer を使って、_this->send_buf に追記している場合には、 + * NULL を指定します。 + * @param lpkt パケットの長さ。 + */ +void +pptp_ctrl_output(pptp_ctrl *_this, u_char *pkt, int lpkt) +{ + PPTP_CTRL_ASSERT(_this != NULL); + PPTP_CTRL_ASSERT(lpkt > 0); + + bytebuffer_put(_this->send_buf, pkt, lpkt); + /* 実際には書くのは後 */ + + if (_this->on_io_event != 0) { + /* pptp_ctrl_io_event で pptp_ctrl_output_flush を呼び出し */ + } else { + /* + * I/O イベントハンドラからの呼出しではない場合は、 + * pptp_ctrl_output_flush() を呼び出す必要があるが flush => + * write 失敗 => finalize となると、この関数呼び出し側に例外 + * 処理を実装し煩雑になるので、write ready イベントで発行し + * てもらって、そこで flush。 + */ + _this->send_ready = 0; + pptp_ctrl_set_io_event(_this); + } + + return; +} + +/** Stop-Control-Connection-Request の送信 */ + +/** 実際にパケット送信 */ +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 (_this->pptpd->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; +} + +/** Start-Control-Connection-Request、-Reply パケットを文字列で表現する */ +static void +pptp_ctrl_SCCRx_string(struct pptp_scc *scc, u_char *buf, int lbuf) +{ + char buf1[128], buf2[128], buf3[128]; + + // 64バイトギリギリまで入っている場合があるので + strlcpy(buf1, scc->host_name, sizeof(buf1)); + strlcpy(buf2, scc->vendor_string, sizeof(buf2)); + + if (scc->result_code != 0) + snprintf(buf3, sizeof(buf3), "result=%d error=%d ", + scc->result_code, scc->error_code); + else + buf3[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, buf3, + pptp_framing_string(scc->framing_caps), + pptp_bearer_string(scc->bearer_caps), scc->max_channels, + scc->firmware_revision, scc->firmware_revision, buf1, buf2); +} + +/** 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; + + // サイズ検査 + 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); + + // プロトコルバージョン + 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; +} + +/** 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; +} + +/** 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; +} + + + +/** 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; +} + +/** 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; + // 同期フレームしかサポートせず。 + //scc->framing_caps = PPTP_CTRL_FRAMING_ASYNC; + scc->framing_caps = PPTP_CTRL_FRAMING_SYNC; + scc->bearer_caps = PPTP_CTRL_BEARER_DIGITAL; + + scc->max_channels = 4; // XX 設定? + scc->firmware_revision = MAJOR_VERSION << 8 | MINOR_VERSION; + + // 63文字で切れても気にしない + + // ホスト名 + if ((val = pptp_ctrl_config_str(_this, "pptp.host_name")) == NULL) + val = ""; + strlcpy(scc->host_name, val, sizeof(scc->host_name)); + + // ベンダ名 + if ((val = pptp_ctrl_config_str(_this, "pptp.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; +} + +/** ECHO の受信 => 返信 */ +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")); +} + +/** 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; +} + +/** 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")); +} + +/* 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)); +} + +/** コントロールパケット受信 */ +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); + + // 長さ検査 + PPTP_CTRL_ASSERT(hdr->length <= lpkt); + + _this->last_rcv_ctrl = curr_time; + + if (_this->pptpd->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); + } + + /* パケット検査 */ + // メッセージタイプ + if (hdr->pptp_message_type != PPTP_MES_TYPE_CTRL) { + snprintf(errmes, sizeof(errmes), "unknown message type %d", + hdr->pptp_message_type); + goto bad_packet; + } + // マジッククッキー + if (hdr->magic_cookie != PPTP_MAGIC_COOKIE) { + snprintf(errmes, sizeof(errmes), "wrong magic %08x != %08x", + hdr->magic_cookie, PPTP_MAGIC_COOKIE); + goto bad_packet; + } + + // ECHO Reply は別処理。ステートが交錯する可能性があるので。*/ + 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; + } + /* + * ステートマシン + * - 正常に処理が終わったら、return する。 + */ + 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; + //コール関連パケットは、pptp_call_input にディスパッチ + 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: + // PAC の実装だけなので + 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; +} + +/** PPTP Call 関連のメッセージを受信 */ +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)"; + + // callId + 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 */ + /* SLI だけは、こちらの Call-ID が入っている */ + 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 */ + /* + * ICRQ は投げないのでこのメッセージは受信しないが、いちおう + * pptp_call.c 側で処理させる + */ + // FALL THROUGH + case PPTP_CTRL_MES_CODE_OCRQ: /* PNS => PAC */ + case PPTP_CTRL_MES_CODE_CCR: /* PNS => PAC */ + // リニアサーチでよい。 + 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) { + // 新しい 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 reigai; + } + if (pptp_call_init(call, _this) != 0) { + pptp_ctrl_log(_this, LOG_ERR, + "pptp_call_init() failed: %m"); + pptp_call_destroy(call); + goto reigai; + } + 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 */ + /* 以上 PNS 用なので、受信しない */ + default: + break; + } + reason = "Message type is unexpected."; + // FALL THROUGH +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; +reigai: + pptp_ctrl_stop(_this, PPTP_StopCCRQ_REASON_STOP_PROTOCOL); + return 0; +} + + +/************************************************************************ + * 雑多な関数 + ************************************************************************/ + +/** このインスタンスに基づいたラベルから始まるログを記録します。 */ +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_MULITPLE + 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"; +} |