1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * PPPoE Server-mode daemon for use with Solaris PPP 4.0.
23 *
24 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 */
27
28#include <stdio.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <unistd.h>
32#include <stdlib.h>
33#include <string.h>
34#include <fcntl.h>
35#include <errno.h>
36#include <signal.h>
37#include <stropts.h>
38#include <wait.h>
39#include <sys/resource.h>
40#include <netinet/in.h>
41#include <net/sppptun.h>
42#include <net/pppoe.h>
43
44#include "common.h"
45#include "pppoed.h"
46#include "logging.h"
47
48static int tunfd;		/* Global connection to tunnel device */
49
50char *myname;			/* Copied from argv[0] for logging */
51static int main_argc;		/* Saved for reparse on SIGHUP */
52static char **main_argv;	/* Saved for reparse on SIGHUP */
53
54static time_t time_started;	/* Time daemon was started; for debug */
55static time_t last_reread;	/* Last time configuration was read. */
56
57/* Various operational statistics. */
58static unsigned long input_packets, padi_packets, padr_packets;
59static unsigned long output_packets;
60static unsigned long sessions_started;
61
62static sigset_t sigmask;	/* Global signal mask */
63
64/*
65 * Used for handling errors that occur before we daemonize.
66 */
67static void
68early_error(const char *str)
69{
70	const char *cp;
71
72	cp = mystrerror(errno);
73	if (isatty(2)) {
74		(void) fprintf(stderr, "%s: %s: %s\n", myname, str, cp);
75	} else {
76		reopen_log();
77		logerr("%s: %s", str, cp);
78	}
79	exit(1);
80}
81
82/*
83 * Open the sppptun driver.
84 */
85static void
86open_tunnel_dev(void)
87{
88	struct ppptun_peer ptp;
89
90	tunfd = open(tunnam, O_RDWR);
91	if (tunfd == -1) {
92		early_error(tunnam);
93	}
94
95	/*
96	 * Tell the device driver that I'm a daemon handling inbound
97	 * connections, not a PPP session.
98	 */
99	(void) memset(&ptp, '\0', sizeof (ptp));
100	ptp.ptp_style = PTS_PPPOE;
101	ptp.ptp_flags = PTPF_DAEMON;
102	(void) memcpy(ptp.ptp_address.pta_pppoe.ptma_mac, ether_bcast,
103	    sizeof (ptp.ptp_address.pta_pppoe.ptma_mac));
104	if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
105	    0) {
106		myperror("PPPTUN_SPEER");
107		exit(1);
108	}
109}
110
111/*
112 * Callback function for fdwalk.  Closes everything but the tunnel
113 * file descriptor when becoming daemon.  (Log file must be reopened
114 * manually, since syslog file descriptor, if any, is unknown.)
115 */
116/*ARGSUSED*/
117static int
118fdcloser(void *arg, int fd)
119{
120	if (fd != tunfd)
121		(void) close(fd);
122	return (0);
123}
124
125/*
126 * Become a daemon.
127 */
128static void
129daemonize(void)
130{
131	pid_t cpid;
132
133	/*
134	 * A little bit of magic here.  By the first fork+setsid, we
135	 * disconnect from our current controlling terminal and become
136	 * a session group leader.  By forking again without setsid,
137	 * we make certain that we're not the session group leader and
138	 * can never reacquire a controlling terminal.
139	 */
140	if ((cpid = fork()) == (pid_t)-1) {
141		early_error("fork 1");
142	}
143	if (cpid != 0) {
144		(void) wait(NULL);
145		_exit(0);
146	}
147	if (setsid() == (pid_t)-1) {
148		early_error("setsid");
149	}
150	if ((cpid = fork()) == (pid_t)-1) {
151		early_error("fork 2");
152	}
153	if (cpid != 0) {
154		/* Parent just exits */
155		(void) printf("%d\n", (int)cpid);
156		(void) fflush(stdout);
157		_exit(0);
158	}
159	(void) chdir("/");
160	(void) umask(0);
161	(void) fdwalk(fdcloser, NULL);
162	reopen_log();
163}
164
165/*
166 * Handle SIGHUP -- close and reopen non-syslog log files and reparse
167 * options.
168 */
169/*ARGSUSED*/
170static void
171handle_hup(int sig)
172{
173	close_log_files();
174	global_logging();
175	last_reread = time(NULL);
176	parse_options(tunfd, main_argc, main_argv);
177}
178
179/*
180 * Handle SIGINT -- write current daemon status to /tmp.
181 */
182/*ARGSUSED*/
183static void
184handle_int(int sig)
185{
186	FILE *fp;
187	char dumpname[MAXPATHLEN];
188	time_t now;
189	struct rusage rusage;
190
191	(void) snprintf(dumpname, sizeof (dumpname), "/tmp/pppoed.%ld",
192	    getpid());
193	if ((fp = fopen(dumpname, "w+")) == NULL) {
194		logerr("%s: %s", dumpname, mystrerror(errno));
195		return;
196	}
197	now = time(NULL);
198	(void) fprintf(fp, "pppoed running %s", ctime(&now));
199	(void) fprintf(fp, "Started on     %s", ctime(&time_started));
200	if (last_reread != 0)
201		(void) fprintf(fp, "Last reconfig  %s", ctime(&last_reread));
202	(void) putc('\n', fp);
203	if (getrusage(RUSAGE_SELF, &rusage) == 0) {
204		(void) fprintf(fp,
205		    "CPU usage:  user %ld.%06ld, system %ld.%06ld\n",
206		    rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec,
207		    rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec);
208	}
209	(void) fprintf(fp, "Packets:  %lu received (%lu PADI, %lu PADR), ",
210	    input_packets, padi_packets, padr_packets);
211	(void) fprintf(fp, "%lu transmitted\n", output_packets);
212	(void) fprintf(fp, "Sessions started:  %lu\n\n", sessions_started);
213	dump_configuration(fp);
214	(void) fclose(fp);
215}
216
217static void
218add_signal_handlers(void)
219{
220	struct sigaction sa;
221
222	(void) sigemptyset(&sigmask);
223	(void) sigaddset(&sigmask, SIGHUP);
224	(void) sigaddset(&sigmask, SIGCHLD);
225	(void) sigaddset(&sigmask, SIGINT);
226	(void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
227
228	sa.sa_mask = sigmask;
229	sa.sa_flags = 0;
230
231	/* Signals to handle */
232	sa.sa_handler = handle_hup;
233	if (sigaction(SIGHUP, &sa, NULL) < 0)
234		early_error("sigaction HUP");
235	sa.sa_handler = handle_int;
236	if (sigaction(SIGINT, &sa, NULL) < 0)
237		early_error("sigaction INT");
238
239	/*
240	 * Signals to ignore.  Ignoring SIGCHLD in this way makes the
241	 * children exit without ever creating zombies.  (No wait(2)
242	 * call required.)
243	 */
244	sa.sa_handler = SIG_IGN;
245	if (sigaction(SIGPIPE, &sa, NULL) < 0)
246		early_error("sigaction PIPE");
247	sa.sa_flags = SA_NOCLDWAIT;
248	if (sigaction(SIGCHLD, &sa, NULL) < 0)
249		early_error("sigaction CHLD");
250}
251
252/*
253 * Dispatch a message from the tunnel driver.  It could be an actual
254 * PPPoE message or just an event notification.
255 */
256static void
257handle_input(uint32_t *ctrlbuf, int ctrllen, uint32_t *databuf, int datalen)
258{
259	poep_t *poep = (poep_t *)databuf;
260	union ppptun_name ptn;
261	int retv;
262	struct strbuf ctrl;
263	struct strbuf data;
264	void *srvp;
265	boolean_t launch;
266	struct ppptun_control *ptc;
267
268	if (ctrllen != sizeof (*ptc)) {
269		logdbg("bogus %d byte control message from driver",
270		    ctrllen);
271		return;
272	}
273	ptc = (struct ppptun_control *)ctrlbuf;
274
275	/* Switch out on event notifications. */
276	switch (ptc->ptc_action) {
277	case PTCA_TEST:
278		logdbg("test reply for discriminator %X", ptc->ptc_discrim);
279		return;
280
281	case PTCA_CONTROL:
282		break;
283
284	case PTCA_DISCONNECT:
285		logdbg("session %d disconnected on %s; send PADT",
286		    ptc->ptc_rsessid, ptc->ptc_name);
287		poep = poe_mkheader(pkt_output, POECODE_PADT,
288		    ptc->ptc_rsessid);
289		ptc->ptc_action = PTCA_CONTROL;
290		ctrl.len = sizeof (*ptc);
291		ctrl.buf = (caddr_t)ptc;
292		data.len = poe_length(poep) + sizeof (*poep);
293		data.buf = (caddr_t)poep;
294		if (putmsg(tunfd, &ctrl, &data, 0) < 0) {
295			logerr("putmsg PADT: %s", mystrerror(errno));
296		} else {
297			output_packets++;
298		}
299		return;
300
301	case PTCA_UNPLUMB:
302		logdbg("%s unplumbed", ptc->ptc_name);
303		return;
304
305	case PTCA_BADCTRL:
306		logwarn("bad control data on %s for session %u", ptc->ptc_name,
307		    ptc->ptc_rsessid);
308		return;
309
310	default:
311		logdbg("unexpected code %d from driver", ptc->ptc_action);
312		return;
313	}
314
315	/* Only PPPoE control messages get here. */
316
317	input_packets++;
318	if (datalen < sizeof (*poep)) {
319		logdbg("incomplete PPPoE message from %s/%s",
320		    ehost(&ptc->ptc_address), ptc->ptc_name);
321		return;
322	}
323
324	/* Server handles only PADI and PADR; all others are ignored. */
325	if (poep->poep_code == POECODE_PADI) {
326		padi_packets++;
327	} else if (poep->poep_code == POECODE_PADR) {
328		padr_packets++;
329	} else {
330		loginfo("unexpected %s from %s",
331		    poe_codename(poep->poep_code), ehost(&ptc->ptc_address));
332		return;
333	}
334	logdbg("Recv from %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name,
335	    poe_codename(poep->poep_code));
336
337	/* Parse out service and formulate template reply. */
338	retv = locate_service(poep, datalen, ptc->ptc_name, &ptc->ptc_address,
339	    pkt_output, &srvp);
340
341	/* Continue formulating reply */
342	launch = B_FALSE;
343	if (retv != 1) {
344		/* Ignore initiation if we don't offer a service. */
345		if (retv <= 0 && poep->poep_code == POECODE_PADI) {
346			logdbg("no services; no reply");
347			return;
348		}
349		if (retv == 0)
350			(void) poe_add_str((poep_t *)pkt_output, POETT_NAMERR,
351			    "No such service.");
352	} else {
353		/* Exactly one service chosen; if it's PADR, then we start. */
354		if (poep->poep_code == POECODE_PADR) {
355			launch = B_TRUE;
356		}
357	}
358	poep = (poep_t *)pkt_output;
359
360	/* Select control interface for output. */
361	(void) strncpy(ptn.ptn_name, ptc->ptc_name, sizeof (ptn.ptn_name));
362	if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) {
363		logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno));
364		return;
365	}
366
367	/* Launch the PPP service */
368	if (launch && launch_service(tunfd, poep, srvp, ptc))
369		sessions_started++;
370
371	/* Send the reply. */
372	ctrl.len = sizeof (*ptc);
373	ctrl.buf = (caddr_t)ptc;
374	data.len = poe_length(poep) + sizeof (*poep);
375	data.buf = (caddr_t)poep;
376	if (putmsg(tunfd, &ctrl, &data, 0) < 0) {
377		logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno));
378	} else {
379		output_packets++;
380		logdbg("Send to   %s/%s: %s", ehost(&ptc->ptc_address),
381		    ptc->ptc_name, poe_codename(poep->poep_code));
382	}
383}
384
385static void
386main_loop(void)
387{
388	struct strbuf ctrl;
389	struct strbuf data;
390	int flags;
391	int rc;
392	int err;
393
394	for (;;) {
395		ctrl.maxlen = PKT_OCTL_LEN;
396		ctrl.buf = (caddr_t)pkt_octl;
397		data.maxlen = PKT_INPUT_LEN;
398		data.buf = (caddr_t)pkt_input;
399		/* Allow signals only while idle */
400		(void) sigprocmask(SIG_UNBLOCK, &sigmask, NULL);
401		errno = 0;
402		flags = 0;
403		rc = mygetmsg(tunfd, &ctrl, &data, &flags);
404		err = errno;
405		/*
406		 * Block signals -- data structures must not change
407		 * while we're busy dispatching the client's request
408		 */
409		(void) sigprocmask(SIG_BLOCK, &sigmask, NULL);
410		if (rc == -1) {
411			if (err == EAGAIN || err == EINTR)
412				continue;
413			logerr("%s getmsg: %s", tunnam, mystrerror(err));
414			exit(1);
415		}
416		if (rc > 0)
417			logwarn("%s returned truncated data", tunnam);
418		else
419			handle_input(pkt_octl, ctrl.len, pkt_input, data.len);
420	}
421}
422
423int
424main(int argc, char **argv)
425{
426	prog_name = "pppoed";
427	log_level = 1;		/* Default to error messages only at first */
428
429	time_started = time(NULL);
430
431	if ((myname = argv[0]) == NULL)
432		myname = "pppoed";
433
434	main_argc = argc;
435	main_argv = argv;
436
437	open_tunnel_dev();
438	add_signal_handlers();
439	daemonize();
440
441	parse_options(tunfd, argc, argv);
442	main_loop();
443
444	return (0);
445}
446