/********************************************************************** * %FUNCTION: load_handler * %ARGUMENTS: * fname -- filename to load * %RETURNS: * -1 on error, 0 if OK * %DESCRIPTION: * Dynamically-loads a handler and initializes it. If fname is not * an absolute path name, we load the handler from /usr/lib/l2tp/plugins ***********************************************************************/ int l2tp_load_handler(EventSelector *es, char const *fname) { char buf[1024]; void *handle; void *init; void (*init_fn)(EventSelector *); if (*fname == '/') { handle = dlopen(fname, RTLD_NOW); } else { snprintf(buf, sizeof(buf), PREFIX"/lib/l2tp/%s", fname); buf[sizeof(buf)-1] = 0; handle = dlopen(buf, RTLD_NOW); } if (!handle) { l2tp_set_errmsg("Could not dload %s: %s", fname, dlerror()); return -1; } init = dlsym(handle, "handler_init"); if (!init) { dlclose(handle); l2tp_set_errmsg("No handler_init found in %s", fname); return -1; } init_fn = (void (*)(EventSelector *)) init; init_fn(es); return 0; }
/********************************************************************** * %FUNCTION: pty_get * %ARGUMENTS: * mfp -- pointer to master file descriptor * sfp -- pointer to slave file descriptor * %RETURNS: * 0 on success, -1 on failure * %DESCRIPTION: * Opens a PTY and sets line discipline to N_HDLC for ppp. * Taken almost verbatim from Linux pppd code. ***********************************************************************/ int pty_get(int *mfp, int *sfp) { char pty_name[24]; struct termios tios; int mfd, sfd; int disc = N_HDLC; mfd = -1; sfd = -1; mfd = open("/dev/ptmx", O_RDWR); if (mfd >= 0) { int ptn; if (ioctl(mfd, TIOCGPTN, &ptn) >= 0) { snprintf(pty_name, sizeof(pty_name), "/dev/pts/%d", ptn); ptn = 0; if (ioctl(mfd, TIOCSPTLCK, &ptn) < 0) { /* warn("Couldn't unlock pty slave %s: %m", pty_name); */ } if ((sfd = open(pty_name, O_RDWR | O_NOCTTY)) < 0) { /* warn("Couldn't open pty slave %s: %m", pty_name); */ } } } if (sfd < 0 || mfd < 0) { if (sfd >= 0) close(sfd); if (mfd >= 0) close(mfd); return -1; } *mfp = mfd; *sfp = sfd; if (tcgetattr(sfd, &tios) == 0) { tios.c_cflag &= ~(CSIZE | CSTOPB | PARENB); tios.c_cflag |= CS8 | CREAD | CLOCAL; tios.c_iflag = IGNPAR; tios.c_oflag = 0; tios.c_lflag = 0; tcsetattr(sfd, TCSAFLUSH, &tios); } if (ioctl(sfd, TIOCSETD, &disc) < 0) { l2tp_set_errmsg("Unable to set line discipline to N_HDLC"); close(mfd); close(sfd); return -1; } disc = N_HDLC; if (ioctl(mfd, TIOCSETD, &disc) < 0) { l2tp_set_errmsg("Unable to set line discipline to N_HDLC"); close(mfd); close(sfd); return -1; } return 0; }
/********************************************************************** * %FUNCTION: process_option * %ARGUMENTS: * es -- event selector * name, value -- name and value of option * %RETURNS: * 0 on success; -1 on failure. * %DESCRIPTION: * Processes options. When last option has been processed, begins * command handler. ***********************************************************************/ static int process_option(EventSelector *es, char const *name, char const *value) { struct sockaddr_un addr; socklen_t len; int fd; if (!strcmp(name, "*begin*")) return 0; if (strcmp(name, "*end*")) { return l2tp_option_set(es, name, value, my_opts); } /* We have hit the end of our options. Open command socket */ if (!sockname) { sockname = "/var/run/l2tpctrl"; } (void) remove(sockname); fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { l2tp_set_errmsg("cmd: process_option: socket: %s", strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, sockname, sizeof(addr.sun_path) - 1); len = sizeof(addr); if (bind(fd, (struct sockaddr *) &addr, SUN_LEN(&addr)) < 0) { l2tp_set_errmsg("cmd: process_option: bind: %s", strerror(errno)); close(fd); return -1; } (void) chmod(sockname, 0600); if (listen(fd, 5) < 0) { l2tp_set_errmsg("cmd: process_option: listen: %s", strerror(errno)); close(fd); return -1; } /* Ignore sigpipe */ signal(SIGPIPE, SIG_IGN); /* Add an accept handler */ if (!EventTcp_CreateAcceptor(es, fd, cmd_acceptor)) { l2tp_set_errmsg("cmd: process_option: EventTcp_CreateAcceptor: %s", strerror(errno)); close(fd); return -1; } return 0; }
/********************************************************************** * %FUNCTION: parser_switch_context * %ARGUMENTS: * name, value -- words read from line. Either "global ..ignored.." * or "section context" * %RETURNS: * 0 if context-switch proceeded OK, -1 if not. * %DESCRIPTION: * Switches configuration contexts ***********************************************************************/ static int parser_switch_context(EventSelector *es, char const *name, char const *value) { int r; option_handler *handler; /* Switch out of old context */ if (option_context_fn) { r = option_context_fn(es, "*end*", "*end*"); option_context_fn = NULL; if (r < 0) return r; } if (!strcasecmp(name, "global")) { return 0; } /* Must be "section foo" */ handler = option_handlers; while(handler) { if (!strcasecmp(value, handler->section)) { option_context_fn = handler->process_option; option_context_fn(es, "*begin*", "*begin*"); return 0; } handler = handler->next; } l2tp_set_errmsg("No handler for section %s", value); return -1; }
/********************************************************************** * %FUNCTION: network_init * %ARGUMENTS: * es -- an event selector * %RETURNS: * >= 0 if all is OK, <0 if not * %DESCRIPTION: * Initializes network; opens socket on UDP port 1701; sets up * event handler for incoming packets. ***********************************************************************/ int l2tp_network_init(EventSelector *es) { struct sockaddr_in me; int flags; gethostname(Hostname, sizeof(Hostname)); Hostname[sizeof(Hostname)-1] = 0; Event_HandleSignal(es, SIGINT, sigint_handler); if (Sock >= 0) { if (NetworkReadHandler) { Event_DelHandler(es, NetworkReadHandler); NetworkReadHandler = NULL; } close(Sock); Sock = -1; } Sock = socket(PF_INET, SOCK_DGRAM, 0); if (Sock < 0) { l2tp_set_errmsg("network_init: socket: %s", strerror(errno)); return -1; } me.sin_family = AF_INET; me.sin_addr = Settings.listen_addr; me.sin_port = htons((uint16_t) Settings.listen_port); if (bind(Sock, (struct sockaddr *) &me, sizeof(me)) < 0) { l2tp_set_errmsg("network_init: bind: %s", strerror(errno)); close(Sock); Sock = -1; return -1; } /* Set socket non-blocking */ flags = fcntl(Sock, F_GETFL); flags |= O_NONBLOCK; fcntl(Sock, F_SETFL, flags); /* Set up the network read handler */ Event_AddHandler(es, Sock, EVENT_FLAG_READABLE, network_readable, NULL); return Sock; }
static int set_lns_handler(EventSelector *es, l2tp_opt_descriptor *desc, char const *value) { l2tp_lns_handler *handler = l2tp_session_find_lns_handler(value); if (!handler) { l2tp_set_errmsg("No LNS handler named '%s'", value); return -1; } prototype.lns_ops = handler->call_ops; return 0; }
/********************************************************************** * %FUNCTION: peer_insert * %ARGUMENTS: * addr -- IP address of peer * %RETURNS: * NULL if insert failed, pointer to new peer structure otherwise * %DESCRIPTION: * Inserts a new peer in the all_peers table ***********************************************************************/ l2tp_peer * l2tp_peer_insert(struct sockaddr_in *addr) { l2tp_peer *peer = malloc(sizeof(l2tp_peer)); if (!peer) { l2tp_set_errmsg("peer_insert: Out of memory"); return NULL; } memset(peer, 0, sizeof(*peer)); peer->addr = *addr; hash_insert(&all_peers, peer); return peer; }
/********************************************************************** * %FUNCTION: option_set * %ARGUMENTS: * es -- event selector * name -- name of option * value -- value of option * descriptors -- array of option descriptors for this context * %RETURNS: * 0 on success, -1 on failure * %DESCRIPTION: * Sets an option ***********************************************************************/ int l2tp_option_set(EventSelector *es, char const *name, char const *value, l2tp_opt_descriptor descriptors[]) { int i; for (i=0; descriptors[i].name; i++) { if (!strcasecmp(descriptors[i].name, name)) { return set_option(es, &descriptors[i], value); } } l2tp_set_errmsg("Option %s is not known in this context", name); return -1; }
/********************************************************************** * %FUNCTION: set_option * %ARGUMENTS: * es -- event selector * desc -- option descriptor * value -- value string parsed from config file * %RETURNS: * -1 on error, 0 if all is OK * %DESCRIPTION: * Sets an option value. ***********************************************************************/ static int set_option(EventSelector *es, l2tp_opt_descriptor *desc, char const *value) { long x; char *end; struct hostent *he; int (*fn)(EventSelector *, l2tp_opt_descriptor *, char const *); switch(desc->type) { case OPT_TYPE_BOOL: if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on") || !strcasecmp(value, "1")) { * (int *) (desc->addr) = 1; return 0; } if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off") || !strcasecmp(value, "0")) { * (int *) (desc->addr) = 0; return 0; } l2tp_set_errmsg("Expecting boolean value, found '%s'", value); return -1; case OPT_TYPE_INT: case OPT_TYPE_PORT: x = strtol(value, &end, 0); if (*end) { l2tp_set_errmsg("Expecting integer value, found '%s'", value); return -1; } if (desc->type == OPT_TYPE_PORT) { if (x < 1 || x > 65535) { l2tp_set_errmsg("Port values must range from 1 to 65535"); return -1; } } * (int *) desc->addr = (int) x; return 0; case OPT_TYPE_IPADDR: he = gethostbyname(value); if (!he) { l2tp_set_errmsg("Could not resolve %s as IP address: %s", value, strerror(errno)); return -1; } memcpy(desc->addr, he->h_addr, sizeof(he->h_addr)); return 0; case OPT_TYPE_STRING: if (* (char **) desc->addr) { free(* (char **) desc->addr); } * (char **) desc->addr = strdup(value); if (! * (char **) desc->addr) { l2tp_set_errmsg("Out of memory"); return -1; } return 0; case OPT_TYPE_CALLFUNC: fn = (int (*)(EventSelector *, l2tp_opt_descriptor *, char const *)) desc->addr; return fn(es, desc, value); } l2tp_set_errmsg("Unknown value type %d", desc->type); return -1; }
/********************************************************************** * %FUNCTION: parse_config_file * %ARGUMENTS: * es -- event selector * fname -- filename to parse * %RETURNS: * -1 on error, 0 if all is OK * %DESCRIPTION: * Parses configuration file. ***********************************************************************/ int l2tp_parse_config_file(EventSelector *es, char const *fname) { char buf[512]; char name[512]; char value[512]; int r = 0; size_t l; char *line; FILE *fp; /* Defaults */ Settings.listen_port = 1701; Settings.listen_addr.s_addr = htonl(INADDR_ANY); fp = fopen(fname, "r"); if (!fp) { l2tp_set_errmsg("Could not open '%s' for reading: %s", fname, strerror(errno)); return -1; } /* Start in global context */ option_context_fn = NULL; while (fgets(buf, sizeof(buf), fp) != NULL) { l = strlen(buf); if (l && (buf[l] == '\n')) { buf[l--] = 0; } /* Skip leading whitespace */ line = buf; while(*line && isspace(*line)) line++; /* Ignore blank lines and comments */ if (!*line || *line == '#') { continue; } /* Split line into two words */ split_line_into_words(line, name, value); /* Check for context switch */ if (!strcasecmp(name, "global") || !strcasecmp(name, "section")) { r = parser_switch_context(es, name, value); if (r < 0) break; continue; } r = handle_option(es, name, value); if (r < 0) break; } fclose(fp); if (r >= 0) { if (option_context_fn) { r = option_context_fn(es, "*end*", "*end*"); option_context_fn = NULL; } } return r; }
/********************************************************************** * %FUNCTION: peer_process_option * %ARGUMENTS: * es -- event selector * name, value -- name and value of option * %RETURNS: * 0 on success, -1 on failure * %DESCRIPTION: * Processes an option in the "peer" section ***********************************************************************/ static int peer_process_option(EventSelector *es, char const *name, char const *value) { l2tp_peer *peer; /* Special cases: begin and end */ if (!strcmp(name, "*begin*")) { /* Switching in to peer context */ memset(&prototype, 0, sizeof(prototype)); prototype.mask_bits = 32; prototype.validate_peer_ip = 1; port = 1701; return 0; } if (!strcmp(name, "*end*")) { /* Validate settings */ uint16_t u16 = (uint16_t) port; prototype.addr.sin_port = htons(u16); prototype.addr.sin_family = AF_INET; /* Allow non-authenticated tunnels if (!prototype.secret_len) { l2tp_set_errmsg("No secret supplied for peer"); return -1; } */ if (!prototype.lns_ops && !prototype.lac_ops) { l2tp_set_errmsg("You must enable at least one of lns-handler or lac-handler"); return -1; } /* Add the peer */ peer = l2tp_peer_insert(&prototype.addr); if (!peer) return -1; peer->mask_bits = prototype.mask_bits; memcpy(&peer->hostname,&prototype.hostname, sizeof(prototype.hostname)); peer->hostname_len = prototype.hostname_len; memcpy(&peer->peername,&prototype.peername, sizeof(prototype.peername)); peer->peername_len = prototype.peername_len; memcpy(&peer->secret, &prototype.secret, MAX_SECRET_LEN); peer->secret_len = prototype.secret_len; peer->lns_ops = prototype.lns_ops; memcpy(&peer->lns_options,&prototype.lns_options,sizeof(prototype.lns_options)); peer->lac_ops = prototype.lac_ops; memcpy(&peer->lac_options,&prototype.lac_options,sizeof(prototype.lac_options)); peer->hide_avps = prototype.hide_avps; peer->retain_tunnel = prototype.retain_tunnel; peer->persist = prototype.persist; peer->holdoff = prototype.holdoff; peer->maxfail = prototype.maxfail; peer->fail = 0; peer->validate_peer_ip = prototype.validate_peer_ip; return 0; } /* Process option */ return l2tp_option_set(es, name, value, peer_opts); }