summaryrefslogtreecommitdiff
path: root/usr.sbin/npppd/pptp/pptpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/npppd/pptp/pptpd.c')
-rw-r--r--usr.sbin/npppd/pptp/pptpd.c1151
1 files changed, 1151 insertions, 0 deletions
diff --git a/usr.sbin/npppd/pptp/pptpd.c b/usr.sbin/npppd/pptp/pptpd.c
new file mode 100644
index 00000000000..82cf429b644
--- /dev/null
+++ b/usr.sbin/npppd/pptp/pptpd.c
@@ -0,0 +1,1151 @@
+/*-
+ * 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.
+ */
+/* $Id: pptpd.c,v 1.1 2010/01/11 04:20:57 yasuoka Exp $ */
+/**@file
+ * PPTPデーモンの実装。現在は PAC(PPTP Access Concentrator) としての実装
+ * のみです。
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <string.h>
+#include <event.h>
+#include <ifaddrs.h>
+
+#ifdef USE_LIBSOCKUTIL
+#include <seil/sockfromto.h>
+#endif
+
+#include "net_utils.h"
+#include "bytebuf.h"
+#include "debugutil.h"
+#include "hash.h"
+#include "slist.h"
+#include "addr_range.h"
+#include "properties.h"
+#include "config_helper.h"
+#ifdef _SEIL_EXT_
+#include "rtev.h"
+#endif
+
+#include "pptp.h"
+#include "pptp_local.h"
+
+static int pptpd_seqno = 0;
+
+#ifdef PPTPD_DEBUG
+#define PPTPD_ASSERT(x) ASSERT(x)
+#define PPTPD_DBG(x) pptpd_log x
+#else
+#define PPTPD_ASSERT(x)
+#define PPTPD_DBG(x)
+#endif
+
+static void pptpd_log (pptpd *, int, const char *, ...) __printflike(3,4);
+static void pptpd_close_gre (pptpd *);
+static void pptpd_close_1723 (pptpd *);
+static void pptpd_io_event (int, short, void *);
+static void pptpd_gre_io_event (int, short, void *);
+static void pptpd_gre_input (pptpd_listener *, struct sockaddr *, u_char *, int);
+static void pptp_ctrl_start_by_pptpd (pptpd *, int, int, struct sockaddr *);
+static int pptp_call_cmp (const void *, const void *);
+static uint32_t pptp_call_hash (const void *, int);
+static void pptp_gre_header_string (struct pptp_gre_header *, char *, int);
+
+#define PPTPD_SHUFFLE_MARK -1
+
+/** PPTPデーモンを初期化します */
+int
+pptpd_init(pptpd *_this)
+{
+ int i, m;
+ struct sockaddr_in sin0;
+ uint16_t call0, call[UINT16_MAX - 1];
+
+ memset(_this, 0, sizeof(pptpd));
+ _this->id = pptpd_seqno++;
+
+ slist_init(&_this->listener);
+ memset(&sin0, 0, sizeof(sin0));
+ sin0.sin_len = sizeof(sin0);
+ sin0.sin_family = AF_INET;
+ if (pptpd_add_listener(_this, 0, PPTPD_DEFAULT_LAYER2_LABEL,
+ (struct sockaddr *)&sin0) != 0) {
+ return 1;
+ }
+
+ _this->ip4_allow = NULL;
+
+ slist_init(&_this->ctrl_list);
+ slist_init(&_this->call_free_list);
+
+ /* Call-ID シャッフル */
+ for (i = 0; i < countof(call) ; i++)
+ call[i] = i + 1;
+ for (i = countof(call); i > 1; i--) {
+ m = random() % i;
+ call0 = call[m];
+ call[m] = call[i - 1];
+ call[i - 1] = call0;
+ }
+ /* 必要個だけを slist に */
+ for (i = 0; i < MIN(PPTP_MAX_CALL, countof(call)); i++)
+ slist_add(&_this->call_free_list, (void *)(uintptr_t)call[i]);
+ /* 末尾に SHUFFLE_MARK。次回は slist_shuffle で shuflle される */
+ slist_add(&_this->call_free_list, (void *)PPTPD_SHUFFLE_MARK);
+
+ if (_this->call_id_map == NULL)
+ _this->call_id_map = hash_create(pptp_call_cmp, pptp_call_hash,
+ 0);
+
+ return 0;
+}
+
+/**
+ * {@link ::pptpd PPTPデーモン}に{@link ::pptpd_listener リスナ}を追加します。
+ * @param _this {@link ::pptpd PPTPデーモン}
+ * @param idx リスナのインデックス
+ * @param label 物理層としてのラベル。"PPTP" など
+ * @param bindaddr 待ち受けるアドレス
+ */
+int
+pptpd_add_listener(pptpd *_this, int idx, const char *label,
+ struct sockaddr *bindaddr)
+{
+ int inaddr_any;
+ pptpd_listener *plistener, *plstn;
+
+ plistener = NULL;
+ if (idx == 0 && slist_length(&_this->listener) > 0) {
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ slist_itr_next(&_this->listener);
+ plstn = slist_itr_remove(&_this->listener);
+ PPTPD_ASSERT(plstn != NULL);
+ PPTPD_ASSERT(plstn->sock == -1);
+ PPTPD_ASSERT(plstn->sock_gre == -1);
+ free(plstn);
+ }
+ }
+ PPTPD_ASSERT(slist_length(&_this->listener) == idx);
+ if (slist_length(&_this->listener) != idx) {
+ pptpd_log(_this, LOG_ERR,
+ "Invalid argument error on %s(): idx must be %d but %d",
+ __func__, slist_length(&_this->listener), idx);
+ goto reigai;
+ }
+ if ((plistener = malloc(sizeof(pptpd_listener))) == NULL) {
+ pptpd_log(_this, LOG_ERR, "malloc() failed in %s: %m",
+ __func__);
+ goto reigai;
+ }
+ memset(plistener, 0, sizeof(pptpd_listener));
+
+ PPTPD_ASSERT(sizeof(plistener->bind_sin) >= bindaddr->sa_len);
+ memcpy(&plistener->bind_sin, bindaddr, bindaddr->sa_len);
+ memcpy(&plistener->bind_sin_gre, bindaddr, bindaddr->sa_len);
+
+ /* ポート番号が省略された場合は、デフォルト (1723/tcp)を使う */
+ if (plistener->bind_sin.sin_port == 0)
+ plistener->bind_sin.sin_port = htons(PPTPD_DEFAULT_TCP_PORT);
+
+ /*
+ * raw ソケットで、INADDR_ANY と明示的な IP アドレス指定したソケット両
+ * 方を bind した場合、パケットは両方のソケットで受信される。この状態が
+ * 発生すると、パケットが重複して受信したように見えてしまうため、このよ
+ * うな設定は許さないこととした。
+ */
+ inaddr_any = 0;
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ plstn = slist_itr_next(&_this->listener);
+ if (plstn->bind_sin_gre.sin_addr.s_addr == INADDR_ANY)
+ inaddr_any++;
+ }
+ if (plistener->bind_sin_gre.sin_addr.s_addr == INADDR_ANY)
+ inaddr_any++;
+ if (inaddr_any > 0 && idx > 0) {
+ log_printf(LOG_ERR, "configuration error at pptpd.listener_in: "
+ "combination 0.0.0.0 and other address is not allowed.");
+ goto reigai;
+ }
+
+ plistener->bind_sin_gre.sin_port = 0;
+ plistener->sock = -1;
+ plistener->sock_gre = -1;
+ plistener->self = _this;
+ plistener->index = idx;
+ strlcpy(plistener->phy_label, label, sizeof(plistener->phy_label));
+
+ if (slist_add(&_this->listener, plistener) == NULL) {
+ pptpd_log(_this, LOG_ERR, "slist_add() failed in %s: %m",
+ __func__);
+ goto reigai;
+ }
+ return 0;
+reigai:
+ if (plistener != NULL)
+ free(plistener);
+ return 1;
+}
+
+void
+pptpd_uninit(pptpd *_this)
+{
+ pptpd_listener *plstn;
+
+ slist_fini(&_this->ctrl_list);
+ slist_fini(&_this->call_free_list);
+
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ plstn = slist_itr_next(&_this->listener);
+ PPTPD_ASSERT(plstn != NULL);
+ PPTPD_ASSERT(plstn->sock == -1);
+ PPTPD_ASSERT(plstn->sock_gre == -1);
+ free(plstn);
+ }
+ slist_fini(&_this->listener);
+ if (_this->call_id_map != NULL) {
+ // アイテムの削除?
+ hash_free(_this->call_id_map);
+ }
+ if (_this->ip4_allow != NULL)
+ in_addr_range_list_remove_all(&_this->ip4_allow);
+ _this->call_id_map = NULL;
+ _this->config = NULL;
+}
+
+#define CALL_MAP_KEY(call) \
+ (void *)(call->id | (call->ctrl->listener_index << 16))
+#define CALL_ID(item) ((uint32_t)item & 0xffff)
+
+/** PPTPを割り当てます */
+int
+pptpd_assign_call(pptpd *_this, pptp_call *call)
+{
+ int shuffle_cnt = 0, call_id;
+
+ shuffle_cnt = 0;
+ slist_itr_first(&_this->call_free_list);
+ while (slist_length(&_this->call_free_list) > 1 &&
+ slist_itr_has_next(&_this->call_free_list)) {
+ call_id = (int)slist_itr_next(&_this->call_free_list);
+ if (call_id == 0)
+ break;
+ slist_itr_remove(&_this->call_free_list);
+ if (call_id == PPTPD_SHUFFLE_MARK) {
+ if (shuffle_cnt++ > 0)
+ break;
+ slist_shuffle(&_this->call_free_list);
+ slist_add(&_this->call_free_list,
+ (void *)PPTPD_SHUFFLE_MARK);
+ slist_itr_first(&_this->call_free_list);
+ continue;
+ }
+ call->id = call_id;
+ hash_insert(_this->call_id_map, CALL_MAP_KEY(call), call);
+
+ return 0;
+ }
+ errno = EBUSY;
+ pptpd_log(_this, LOG_ERR, "call request reached limit=%d",
+ PPTP_MAX_CALL);
+ return -1;
+}
+
+/** PPTPを解放します。*/
+void
+pptpd_release_call(pptpd *_this, pptp_call *call)
+{
+ if (call->id != 0)
+ slist_add(&_this->call_free_list, (void *)call->id);
+ hash_delete(_this->call_id_map, CALL_MAP_KEY(call), 0);
+ call->id = 0;
+}
+
+static int
+pptpd_listener_start(pptpd_listener *_this)
+{
+ int sock, ival, sock_gre;
+ struct sockaddr_in bind_sin, bind_sin_gre;
+ int wildcardbinding;
+#ifdef NPPPD_FAKEBIND
+ extern void set_faith(int, int);
+#endif
+
+ wildcardbinding =
+ (_this->bind_sin.sin_addr.s_addr == INADDR_ANY)? 1 : 0;
+ sock = -1;
+ sock_gre = -1;
+ memcpy(&bind_sin, &_this->bind_sin, sizeof(bind_sin));
+ memcpy(&bind_sin_gre, &_this->bind_sin_gre, sizeof(bind_sin_gre));
+
+ if (_this->phy_label[0] == '\0')
+ strlcpy(_this->phy_label, PPTPD_DEFAULT_LAYER2_LABEL,
+ sizeof(_this->phy_label));
+ // 1723/tcp
+ if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ pptpd_log(_this->self, LOG_ERR, "socket() failed at %s(): %m",
+ __func__);
+ goto reigai;
+ }
+ ival = 1;
+ if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &ival, sizeof(ival)) < 0){
+ pptpd_log(_this->self, LOG_WARNING,
+ "setsockopt(SO_REUSEPORT) failed at %s(): %m", __func__);
+ }
+#ifdef NPPPD_FAKEBIND
+ if (!wildcardbinding)
+ set_faith(sock, 1);
+#endif
+#if defined(IP_STRICT_RCVIF) && defined(USE_STRICT_RCVIF)
+ ival = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_STRICT_RCVIF, &ival, sizeof(ival))
+ != 0)
+ pptpd_log(_this->self, LOG_WARNING,
+ "%s(): setsockopt(IP_STRICT_RCVIF) failed: %m", __func__);
+#endif
+ if ((ival = fcntl(sock, F_GETFL, 0)) < 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "fcntl(F_GET_FL) failed at %s(): %m", __func__);
+ goto reigai;
+ } else if (fcntl(sock, F_SETFL, ival | O_NONBLOCK) < 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "fcntl(F_SET_FL) failed at %s(): %m", __func__);
+ goto reigai;
+ }
+ if (bind(sock, (struct sockaddr *)&_this->bind_sin,
+ _this->bind_sin.sin_len) != 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "bind(%s:%u) failed at %s(): %m",
+ inet_ntoa(_this->bind_sin.sin_addr),
+ ntohs(_this->bind_sin.sin_port), __func__);
+ goto reigai;
+ }
+ if (listen(sock, PPTP_BACKLOG) != 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "listen(%s:%u) failed at %s(): %m",
+ inet_ntoa(_this->bind_sin.sin_addr),
+ ntohs(_this->bind_sin.sin_port), __func__);
+ goto reigai;
+ }
+#ifdef NPPPD_FAKEBIND
+ if (!wildcardbinding)
+ set_faith(sock, 0);
+#endif
+ pptpd_log(_this->self, LOG_INFO, "Listening %s:%u/tcp (PPTP PAC) [%s]",
+ inet_ntoa(_this->bind_sin.sin_addr),
+ ntohs(_this->bind_sin.sin_port), _this->phy_label);
+
+ /* GRE */
+ bind_sin_gre.sin_port = 0;
+ if ((sock_gre = socket(AF_INET, SOCK_RAW, IPPROTO_GRE)) < 0) {
+ pptpd_log(_this->self, LOG_ERR, "socket() failed at %s(): %m",
+ __func__);
+ goto reigai;
+ }
+#ifdef NPPPD_FAKEBIND
+ if (!wildcardbinding)
+ set_faith(sock_gre, 1);
+#endif
+#if defined(IP_STRICT_RCVIF) && defined(USE_STRICT_RCVIF)
+ ival = 1;
+ if (setsockopt(sock_gre, IPPROTO_IP, IP_STRICT_RCVIF, &ival,
+ sizeof(ival)) != 0)
+ pptpd_log(_this->self, LOG_WARNING,
+ "%s(): setsockopt(IP_STRICT_RCVIF) failed: %m", __func__);
+#endif
+ if ((ival = fcntl(sock_gre, F_GETFL, 0)) < 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "fcntl(F_GET_FL) failed at %s(): %m", __func__);
+ goto reigai;
+ } else if (fcntl(sock_gre, F_SETFL, ival | O_NONBLOCK) < 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "fcntl(F_SET_FL) failed at %s(): %m", __func__);
+ goto reigai;
+ }
+ if (bind(sock_gre, (struct sockaddr *)&bind_sin_gre,
+ bind_sin_gre.sin_len) != 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "bind(%s:%u) failed at %s(): %m",
+ inet_ntoa(bind_sin_gre.sin_addr),
+ ntohs(bind_sin_gre.sin_port), __func__);
+ goto reigai;
+ }
+#ifdef NPPPD_FAKEBIND
+ if (!wildcardbinding)
+ set_faith(sock_gre, 0);
+#endif
+ if (wildcardbinding) {
+#ifdef USE_LIBSOCKUTIL
+ if (setsockoptfromto(sock) != 0) {
+ pptpd_log(_this->self, LOG_ERR,
+ "setsockoptfromto() failed in %s(): %m", __func__);
+ goto reigai;
+ }
+#else
+ /* nothing to do */
+#endif
+ }
+ pptpd_log(_this->self, LOG_INFO, "Listening %s:gre (PPTP PAC)",
+ inet_ntoa(bind_sin_gre.sin_addr));
+
+ _this->sock = sock;
+ _this->sock_gre = sock_gre;
+
+ event_set(&_this->ev_sock, _this->sock, EV_READ | EV_PERSIST,
+ pptpd_io_event, _this);
+ event_add(&_this->ev_sock, NULL);
+
+ event_set(&_this->ev_sock_gre, _this->sock_gre, EV_READ | EV_PERSIST,
+ pptpd_gre_io_event, _this);
+ event_add(&_this->ev_sock_gre, NULL);
+
+ return 0;
+reigai:
+ if (sock >= 0)
+ close(sock);
+ if (sock_gre >= 0)
+ close(sock_gre);
+
+ _this->sock = -1;
+ _this->sock_gre = -1;
+
+ return 1;
+}
+/** PPTPデーモンを開始します */
+int
+pptpd_start(pptpd *_this)
+{
+ int rval = 0;
+ pptpd_listener *plistener;
+
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ plistener = slist_itr_next(&_this->listener);
+ PPTPD_ASSERT(plistener != NULL);
+ rval |= pptpd_listener_start(plistener);
+ }
+ if (rval == 0)
+ _this->state = PPTPD_STATE_RUNNING;
+
+ return rval;
+}
+
+static void
+pptpd_listener_close_gre(pptpd_listener *_this)
+{
+ if (_this->sock_gre >= 0) {
+ event_del(&_this->ev_sock_gre);
+ close(_this->sock_gre);
+ pptpd_log(_this->self, LOG_INFO, "Shutdown %s/gre",
+ inet_ntoa(_this->bind_sin_gre.sin_addr));
+ }
+ _this->sock_gre = -1;
+}
+
+/** GREの待ち受けソケットを close します */
+static void
+pptpd_close_gre(pptpd *_this)
+{
+ pptpd_listener *plistener;
+
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ plistener = slist_itr_next(&_this->listener);
+ pptpd_listener_close_gre(plistener);
+ }
+}
+
+/** 1723/tcp の待ち受けソケットを close します */
+static void
+pptpd_listener_close_1723(pptpd_listener *_this)
+{
+ if (_this->sock >= 0) {
+ event_del(&_this->ev_sock);
+ close(_this->sock);
+ pptpd_log(_this->self, LOG_INFO, "Shutdown %s:%u/tcp",
+ inet_ntoa(_this->bind_sin.sin_addr),
+ ntohs(_this->bind_sin.sin_port));
+ }
+ _this->sock = -1;
+}
+/** 1723/tcp の待ち受けソケットを close します */
+static void
+pptpd_close_1723(pptpd *_this)
+{
+ pptpd_listener *plistener;
+
+ slist_itr_first(&_this->listener);
+ while (slist_itr_has_next(&_this->listener)) {
+ plistener = slist_itr_next(&_this->listener);
+ pptpd_listener_close_1723(plistener);
+ }
+}
+
+/** PPTPデーモンを本当に終了します。**/
+void
+pptpd_stop_immediatly(pptpd *_this)
+{
+ pptp_ctrl *ctrl;
+
+ if (event_initialized(&_this->ev_timer))
+ evtimer_del(&_this->ev_timer);
+ if (_this->state != PPTPD_STATE_STOPPED) {
+ /*
+ * pptp_ctrl_stop を呼び出すと、この関数が再度呼ばれる可能
+ * 性がある。このため、このstate 変更は重要。
+ */
+ _this->state = PPTPD_STATE_STOPPED;
+
+ pptpd_close_1723(_this);
+ for (slist_itr_first(&_this->ctrl_list);
+ (ctrl = slist_itr_next(&_this->ctrl_list)) != NULL;) {
+ pptp_ctrl_stop(ctrl, 0);
+ }
+ pptpd_close_gre(_this);
+ slist_fini(&_this->ctrl_list);
+ slist_fini(&_this->call_free_list);
+ pptpd_log(_this, LOG_NOTICE, "Stopped");
+ } else {
+ PPTPD_DBG((_this, LOG_DEBUG, "(Already) Stopped"));
+ }
+}
+
+static void
+pptpd_stop_timeout(int fd, short event, void *ctx)
+{
+ pptpd *_this;
+
+ _this = ctx;
+ pptpd_stop_immediatly(_this);
+}
+
+/** PPTPデーモンを終了します */
+void
+pptpd_stop(pptpd *_this)
+{
+ int nctrl;
+ pptp_ctrl *ctrl;
+ struct timeval tv;
+
+ if (event_initialized(&_this->ev_timer))
+ evtimer_del(&_this->ev_timer);
+ pptpd_close_1723(_this);
+ /* このあたりの動作は l2tpd_stop とあわせるべき */
+
+ if (pptpd_is_stopped(_this))
+ return;
+ if (pptpd_is_shutting_down(_this)) {
+ pptpd_stop_immediatly(_this);
+ return;
+ }
+ _this->state = PPTPD_STATE_SHUTTING_DOWN;
+ nctrl = 0;
+ for (slist_itr_first(&_this->ctrl_list);
+ (ctrl = slist_itr_next(&_this->ctrl_list)) != NULL;) {
+ pptp_ctrl_stop(ctrl, PPTP_CDN_RESULT_ADMIN_SHUTDOWN);
+ nctrl++;
+ }
+ if (nctrl > 0) {
+ // タイマーセット
+ tv.tv_sec = PPTPD_SHUTDOWN_TIMEOUT;
+ tv.tv_usec = 0;
+
+ evtimer_set(&_this->ev_timer, pptpd_stop_timeout, _this);
+ evtimer_add(&_this->ev_timer, &tv);
+
+ return;
+ }
+ pptpd_stop_immediatly(_this);
+}
+
+/***********************************************************************
+ * 設定関連
+ ***********************************************************************/
+#define CFG_KEY(p, s) config_key_prefix((p), (s))
+#define VAL_SEP " \t\r\n"
+
+CONFIG_FUNCTIONS(pptpd_config, pptpd, config);
+PREFIXED_CONFIG_FUNCTIONS(pptp_ctrl_config, pptp_ctrl, pptpd->config,
+ phy_label);
+int
+pptpd_reload(pptpd *_this, struct properties *config, const char *name,
+ int default_enabled)
+{
+ int i, do_start, aierr;
+ const char *val;
+ char *tok, *cp, buf[PPTPD_CONFIG_BUFSIZ], *label;
+ struct addrinfo *ai;
+
+ ASSERT(_this != NULL);
+ ASSERT(config != NULL);
+
+ _this->config = config; /* 現在は copy しなくて大丈夫 */
+ do_start = 0;
+ if (pptpd_config_str_equal(_this, CFG_KEY(name, "enabled"), "true",
+ default_enabled)) {
+ // false にした直後に true にされるかもしれない。
+ if (pptpd_is_shutting_down(_this))
+ pptpd_stop_immediatly(_this);
+ if (pptpd_is_stopped(_this))
+ do_start = 1;
+ } else {
+ if (!pptpd_is_stopped(_this))
+ pptpd_stop(_this);
+ return 0;
+ }
+ if (do_start && pptpd_init(_this) != 0)
+ return 1;
+ // pptpd_init でリセットされてしまうので。
+ _this->config = config;
+
+ _this->ctrl_in_pktdump = pptpd_config_str_equal(_this,
+ "log.pptp.ctrl.in.pktdump", "true", 0);
+ _this->data_in_pktdump = pptpd_config_str_equal(_this,
+ "log.pptp.data.in.pktdump", "true", 0);
+ _this->ctrl_out_pktdump = pptpd_config_str_equal(_this,
+ "log.pptp.ctrl.out.pktdump", "true", 0);
+ _this->data_out_pktdump = pptpd_config_str_equal(_this,
+ "log.pptp.data.out.pktdump", "true", 0);
+ _this->phy_label_with_ifname = pptpd_config_str_equal(_this,
+ CFG_KEY(name, "label_with_ifname"), "true", 0);
+
+ // ip4_allow をパース
+ in_addr_range_list_remove_all(&_this->ip4_allow);
+ val = pptpd_config_str(_this, CFG_KEY(name, "ip4_allow"));
+ if (val != NULL) {
+ if (strlen(val) >= sizeof(buf)) {
+ log_printf(LOG_ERR, "configuration error at "
+ "%s: too long", CFG_KEY(name, "ip4_allow"));
+ return 1;
+ }
+ strlcpy(buf, val, sizeof(buf));
+ for (cp = buf; (tok = strsep(&cp, VAL_SEP)) != NULL;) {
+ if (*tok == '\0')
+ continue;
+ if (in_addr_range_list_add(&_this->ip4_allow, tok)
+ != 0) {
+ pptpd_log(_this, LOG_ERR,
+ "configuration error at %s: %s",
+ CFG_KEY(name, "ip4_allow"), tok);
+ return 1;
+ }
+ }
+ }
+
+ if (do_start) {
+ /*
+ * 起動直後と、pptpd.enable が false -> true に変更された
+ * 場合に、do_start。すべてのリスナーが、初期化された状態を
+ * 仮定できる
+ */
+ // pptpd.listener_in の読み込む
+ val = pptpd_config_str(_this, CFG_KEY(name, "listener_in"));
+ if (val != NULL) {
+ if (strlen(val) >= sizeof(buf)) {
+ pptpd_log(_this, LOG_ERR,
+ "configuration error at "
+ "%s: too long", CFG_KEY(name, "listener"));
+ return 1;
+ }
+ strlcpy(buf, val, sizeof(buf));
+
+ label = NULL;
+ // タブ、スペース区切りで、複数指定可能
+ for (i = 0, cp = buf;
+ (tok = strsep(&cp, VAL_SEP)) != NULL;) {
+ if (*tok == '\0')
+ continue;
+ if (label == NULL) {
+ label = tok;
+ continue;
+ }
+ if ((aierr = addrport_parse(tok, IPPROTO_TCP,
+ &ai)) != 0) {
+ pptpd_log(_this, LOG_ERR,
+ "configuration error at "
+ "%s: %s: %s",
+ CFG_KEY(name, "listener_in"), tok,
+ gai_strerror(aierr));
+ return 1;
+ }
+ PPTPD_ASSERT(ai != NULL &&
+ ai->ai_family == AF_INET);
+ if (pptpd_add_listener(_this, i, label,
+ ai->ai_addr) != 0) {
+ freeaddrinfo(ai);
+ label = NULL;
+ break;
+ }
+ freeaddrinfo(ai);
+ label = NULL;
+ i++;
+ }
+ if (label != NULL) {
+ pptpd_log(_this, LOG_ERR,
+ "configuration error at %s: %s",
+ CFG_KEY(name, "listner_in"), label);
+ return 1;
+ }
+ }
+ if (pptpd_start(_this) != 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/***********************************************************************
+ * I/O関連
+ ***********************************************************************/
+static void
+pptpd_log_access_deny(pptpd *_this, const char *reason, struct sockaddr *peer)
+{
+ char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV];
+
+ if (getnameinfo(peer, peer->sa_len, hostbuf, sizeof(hostbuf),
+ servbuf, sizeof(servbuf), NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+ pptpd_log(_this, LOG_ERR, "getnameinfo() failed at %s(): %m",
+ __func__);
+ return;
+ }
+ pptpd_log(_this, LOG_ALERT, "denied a connection from %s:%s/tcp: %s",
+ hostbuf, servbuf, reason);
+}
+
+/** 1723/tcp の IOイベントハンドラ */
+static void
+pptpd_io_event(int fd, short evmask, void *ctx)
+{
+ int newsock;
+ const char *reason;
+ socklen_t peerlen;
+ struct sockaddr_storage peer;
+ pptpd *_this;
+ pptpd_listener *listener;
+
+ listener = ctx;
+ PPTPD_ASSERT(listener != NULL);
+ _this = listener->self;
+ PPTPD_ASSERT(_this != NULL);
+
+ if ((evmask & EV_READ) != 0) {
+ for (;;) { // EAGAIN まで 連続して accept
+ peerlen = sizeof(peer);
+ if ((newsock = accept(listener->sock,
+ (struct sockaddr *)&peer, &peerlen)) < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ break;
+ case ECONNABORTED:
+ pptpd_log(_this, LOG_WARNING,
+ "accept() failed at %s(): %m",
+ __func__);
+ break;
+ default:
+ pptpd_log(_this, LOG_ERR,
+ "accept() failed at %s(): %m",
+ __func__);
+ pptpd_listener_close_1723(listener);
+ pptpd_stop(_this);
+ }
+ break;
+ }
+ // 送信元チェック
+ switch (peer.ss_family) {
+ case AF_INET:
+ if (!in_addr_range_list_includes(
+ &_this->ip4_allow,
+ &((struct sockaddr_in *)&peer)->sin_addr)) {
+ reason = "not allowed by acl.";
+ break;
+ }
+ goto accept;
+ default:
+ reason = "address family is not supported.";
+ break;
+ }
+ // 許可されていない
+ pptpd_log_access_deny(_this, reason,
+ (struct sockaddr *)&peer);
+ close(newsock);
+ continue;
+ // NOTREACHED
+accept:
+ // 許可
+ pptp_ctrl_start_by_pptpd(_this, newsock,
+ listener->index, (struct sockaddr *)&peer);
+ }
+ }
+}
+
+/** GRE の IOイベントハンドラー */
+static void
+pptpd_gre_io_event(int fd, short evmask, void *ctx)
+{
+ int sz;
+ u_char pkt[65535];
+ socklen_t peerlen;
+ struct sockaddr_storage peer;
+ pptpd *_this;
+ pptpd_listener *listener;
+
+ listener = ctx;
+ PPTPD_ASSERT(listener != NULL);
+ _this = listener->self;
+ PPTPD_ASSERT(_this != NULL);
+
+ if (evmask & EV_READ) {
+ for (;;) {
+ // Block するまで読む
+ peerlen = sizeof(peer);
+ if ((sz = recvfrom(listener->sock_gre, pkt, sizeof(pkt),
+ 0, (struct sockaddr *)&peer, &peerlen)) <= 0) {
+ if (sz < 0 &&
+ (errno == EAGAIN || errno == EINTR))
+ break;
+ pptpd_log(_this, LOG_INFO,
+ "read(GRE) failed: %m");
+ pptpd_stop(_this);
+ return;
+ }
+ pptpd_gre_input(listener, (struct sockaddr *)&peer, pkt,
+ sz);
+ }
+ }
+}
+
+/** GREの受信 → pptp_call に配送 */
+static void
+pptpd_gre_input(pptpd_listener *listener, struct sockaddr *peer, u_char *pkt,
+ int lpkt)
+{
+ int hlen, input_flags;
+ uint32_t seq, ack, call_id;
+ struct ip *iphdr;
+ struct pptp_gre_header *grehdr;
+ char hbuf0[NI_MAXHOST], logbuf[512];
+ const char *reason;
+ pptp_call *call;
+ hash_link *hl;
+ pptpd *_this;
+
+ seq = 0;
+ ack = 0;
+ input_flags = 0;
+ reason = "No error";
+ _this = listener->self;
+
+ PPTPD_ASSERT(peer->sa_family == AF_INET);
+
+ strlcpy(hbuf0, "<unknown>", sizeof(hbuf0));
+ if (getnameinfo(peer, peer->sa_len, hbuf0, sizeof(hbuf0), NULL, 0,
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+ pptpd_log(_this, LOG_ERR,
+ "getnameinfo() failed at %s(): %m", __func__);
+ goto reigai;
+ }
+ if (_this->data_in_pktdump != 0) {
+ pptpd_log(_this, LOG_DEBUG, "PPTP Data input packet dump");
+ show_hd(debug_get_debugfp(), pkt, lpkt);
+ }
+ if (peer->sa_family != AF_INET) {
+ pptpd_log(_this, LOG_ERR,
+ "Received malformed GRE packet: address family is not "
+ "supported: peer=%s af=%d", hbuf0, peer->sa_family);
+ goto reigai;
+ }
+
+ if (lpkt < sizeof(struct ip)) {
+ pptpd_log(_this, LOG_ERR,
+ "Received a short length packet length=%d, from %s", lpkt,
+ hbuf0);
+ goto reigai;
+ }
+ iphdr = (struct ip *)pkt;
+
+ // IPヘッダは ntohs 済み NetBSD の場合
+#if !defined(__NetBSD__)
+ iphdr->ip_len = ntohs(iphdr->ip_len);
+#endif
+ hlen = iphdr->ip_hl * 4;
+
+ if (iphdr->ip_len > lpkt ||
+ iphdr->ip_len < sizeof(struct pptp_gre_header)) {
+ pptpd_log(_this, LOG_ERR,
+ "Received a broken packet: ip_hl=%d iplen=%d lpkt=%d", hlen,
+ iphdr->ip_len, lpkt);
+ show_hd(debug_get_debugfp(), pkt, lpkt);
+ goto reigai;
+ }
+ pkt += hlen;
+ lpkt -= hlen;
+ grehdr = (struct pptp_gre_header *)pkt;
+ pkt += sizeof(struct pptp_gre_header);
+ lpkt -= sizeof(struct pptp_gre_header);
+
+ grehdr->protocol_type = htons(grehdr->protocol_type);
+ grehdr->payload_length = htons(grehdr->payload_length);
+ grehdr->call_id = htons(grehdr->call_id);
+
+ if (!(grehdr->protocol_type == PPTP_GRE_PROTOCOL_TYPE &&
+ grehdr->C == 0 && grehdr->R == 0 && grehdr->K != 0 &&
+ grehdr->recur == 0 && grehdr->s == 0 && grehdr->flags == 0 &&
+ grehdr->ver == PPTP_GRE_VERSION)) {
+ reason = "GRE header is broken";
+ goto bad_gre;
+ }
+ if (grehdr->S != 0) {
+ if (lpkt < 2) {
+ reason = "No enough space for seq number";
+ goto bad_gre;
+ }
+ input_flags |= PPTP_GRE_PKT_SEQ_PRESENT;
+ seq = ntohl(*(uint32_t *)pkt);
+ pkt += 4;
+ lpkt -= 4;
+ }
+
+ if (grehdr->A != 0) {
+ if (lpkt < 2) {
+ reason = "No enough space for ack number";
+ goto bad_gre;
+ }
+ input_flags |= PPTP_GRE_PKT_ACK_PRESENT;
+ ack = ntohl(*(uint32_t *)pkt);
+ pkt += 4;
+ lpkt -= 4;
+ }
+
+ if (grehdr->payload_length > lpkt) {
+ reason = "'Payload Length' is mismatch from actual length";
+ goto bad_gre;
+ }
+
+
+ // pptp_call に配送
+ call_id = grehdr->call_id;
+
+ hl = hash_lookup(_this->call_id_map,
+ (void *)(call_id | (listener->index << 16)));
+ if (hl == NULL) {
+ reason = "Received GRE packet has unknown call_id";
+ goto bad_gre;
+ }
+ call = hl->item;
+ pptp_call_gre_input(call, seq, ack, input_flags, pkt, lpkt);
+
+ return;
+bad_gre:
+ pptp_gre_header_string(grehdr, logbuf, sizeof(logbuf));
+ pptpd_log(_this, LOG_INFO,
+ "Received malformed GRE packet: %s: peer=%s sock=%s %s seq=%u: "
+ "ack=%u ifidx=%d", reason, hbuf0, inet_ntoa(iphdr->ip_dst), logbuf,
+ seq, ack, listener->index);
+reigai:
+ return;
+}
+
+/** PPTPコントロールを開始します。(新しい接続があれば呼び出される。) */
+static void
+pptp_ctrl_start_by_pptpd(pptpd *_this, int sock, int listener_index,
+ struct sockaddr *peer)
+{
+ int ival;
+ pptp_ctrl *ctrl;
+ socklen_t sslen;
+ char ifname[IF_NAMESIZE], msgbuf[128];
+
+ ctrl = NULL;
+ if ((ctrl = pptp_ctrl_create()) == NULL)
+ goto reigai;
+ if (pptp_ctrl_init(ctrl) != 0)
+ goto reigai;
+
+ memset(&ctrl->peer, 0, sizeof(ctrl->peer));
+ memcpy(&ctrl->peer, peer, peer->sa_len);
+ ctrl->pptpd = _this;
+ ctrl->sock = sock;
+ ctrl->listener_index = listener_index;
+
+ sslen = sizeof(ctrl->our);
+ if (getsockname(ctrl->sock, (struct sockaddr *)&ctrl->our,
+ &sslen) != 0) {
+ pptpd_log(_this, LOG_WARNING,
+ "getsockname() failed at %s(): %m", __func__);
+ goto reigai;
+ }
+ /* "L2TP%em0.mru" などと、インタフェースで設定を変更する場合 */
+ if (_this->phy_label_with_ifname != 0) {
+ if (get_ifname_by_sockaddr((struct sockaddr *)&ctrl->our,
+ ifname) == NULL) {
+ pptpd_log_access_deny(_this,
+ "could not get interface informations", peer);
+ goto reigai;
+ }
+ if (pptpd_config_str_equal(_this,
+ config_key_prefix("pptpd.interface", ifname), "accept", 0)){
+ snprintf(ctrl->phy_label, sizeof(ctrl->phy_label),
+ "%s%%%s", PPTP_CTRL_LISTENER_LABEL(ctrl), ifname);
+ } else if (pptpd_config_str_equal(_this,
+ config_key_prefix("pptpd.interface", "any"), "accept", 0)){
+ snprintf(ctrl->phy_label, sizeof(ctrl->phy_label),
+ "%s", PPTP_CTRL_LISTENER_LABEL(ctrl));
+ } else {
+ /* このインタフェースは許可されていない。*/
+ snprintf(msgbuf, sizeof(msgbuf),
+ "'%s' is not allowed by config.", ifname);
+ pptpd_log_access_deny(_this, msgbuf, peer);
+ goto reigai;
+ }
+#if defined(_SEIL_EXT_) && !defined(USE_LIBSOCKUTIL)
+ if (!rtev_ifa_is_primary(ifname,
+ (struct sockaddr *)&ctrl->our)) {
+ char hostbuf[NI_MAXHOST];
+
+ getnameinfo((struct sockaddr *)&ctrl->our,
+ ctrl->our.ss_len, hostbuf,
+ sizeof(hostbuf), NULL, 0, NI_NUMERICHOST);
+ snprintf(msgbuf, sizeof(msgbuf),
+ "connecting to %s (an alias address of %s)"
+ " is not allowed by this version.",
+ hostbuf, ifname);
+ pptpd_log_access_deny(_this, msgbuf, peer);
+
+ goto reigai;
+ }
+#endif
+ } else
+ strlcpy(ctrl->phy_label, PPTP_CTRL_LISTENER_LABEL(ctrl),
+ sizeof(ctrl->phy_label));
+
+ if ((ival = pptp_ctrl_config_int(ctrl, "pptp.echo_interval", 0)) != 0)
+ ctrl->echo_interval = ival;
+
+ if ((ival = pptp_ctrl_config_int(ctrl, "pptp.echo_timeout", 0)) != 0)
+ ctrl->echo_timeout = ival;
+
+ if (pptp_ctrl_start(ctrl) != 0)
+ goto reigai;
+
+ slist_add(&_this->ctrl_list, ctrl);
+
+ return;
+reigai:
+ close(sock);
+ pptp_ctrl_destroy(ctrl);
+ return;
+}
+
+/** PPTPコントロールが終了後に通知してきます。*/
+void
+pptpd_ctrl_finished_notify(pptpd *_this, pptp_ctrl *ctrl)
+{
+ pptp_ctrl *ctrl1;
+ int i, nctrl;
+
+ PPTPD_ASSERT(_this != NULL);
+ PPTPD_ASSERT(ctrl != NULL);
+
+ nctrl = 0;
+ for (i = 0; i < slist_length(&_this->ctrl_list); i++) {
+ ctrl1 = slist_get(&_this->ctrl_list, i);
+ if (ctrl1 == ctrl) {
+ slist_remove(&_this->ctrl_list, i);
+ break;
+ }
+ }
+ pptp_ctrl_destroy(ctrl);
+
+ PPTPD_DBG((_this, LOG_DEBUG, "Remains %d ctrls", nctrl));
+ if (pptpd_is_shutting_down(_this) && nctrl == 0)
+ // シャットダウン中最後の一人
+ pptpd_stop_immediatly(_this);
+}
+
+/***********************************************************************
+ * その他、ユーティリティ関数
+ ***********************************************************************/
+/** このインスタンスに基づいたラベルから始まるログを記録します。 */
+static void
+pptpd_log(pptpd *_this, int prio, const char *fmt, ...)
+{
+ char logbuf[BUFSIZ];
+ va_list ap;
+
+ PPTPD_ASSERT(_this != NULL);
+ va_start(ap, fmt);
+#ifdef PPTPD_MULITPLE
+ snprintf(logbuf, sizeof(logbuf), "pptpd id=%u %s", _this->id, fmt);
+#else
+ snprintf(logbuf, sizeof(logbuf), "pptpd %s", fmt);
+#endif
+ vlog_printf(prio, logbuf, ap);
+ va_end(ap);
+}
+
+static int
+pptp_call_cmp(const void *a0, const void *b0)
+{
+ return ((uint32_t)a0 - (uint32_t)b0);
+}
+
+static uint32_t
+pptp_call_hash(const void *ctx, int size)
+{
+ return (uint32_t)ctx % size;
+}
+
+/** GREパケットヘッダを文字列として */
+static void
+pptp_gre_header_string(struct pptp_gre_header *grehdr, char *buf, int lbuf)
+{
+ snprintf(buf, lbuf,
+ "[%s%s%s%s%s%s] ver=%d "
+ "protocol_type=%04x payload_length=%d call_id=%d",
+ (grehdr->C != 0)? "C" : "", (grehdr->R != 0)? "R" : "",
+ (grehdr->K != 0)? "K" : "", (grehdr->S != 0)? "S" : "",
+ (grehdr->s != 0)? "s" : "", (grehdr->A != 0)? "A" : "", grehdr->ver,
+ grehdr->protocol_type, grehdr->payload_length, grehdr->call_id);
+}