xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.lib/pppoe/pppoed.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
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 
48 static int tunfd;		/* Global connection to tunnel device */
49 
50 char *myname;			/* Copied from argv[0] for logging */
51 static int main_argc;		/* Saved for reparse on SIGHUP */
52 static char **main_argv;	/* Saved for reparse on SIGHUP */
53 
54 static time_t time_started;	/* Time daemon was started; for debug */
55 static time_t last_reread;	/* Last time configuration was read. */
56 
57 /* Various operational statistics. */
58 static unsigned long input_packets, padi_packets, padr_packets;
59 static unsigned long output_packets;
60 static unsigned long sessions_started;
61 
62 static sigset_t sigmask;	/* Global signal mask */
63 
64 /*
65  * Used for handling errors that occur before we daemonize.
66  */
67 static void
68 early_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  */
85 static void
86 open_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*/
117 static int
118 fdcloser(void *arg, int fd)
119 {
120 	if (fd != tunfd)
121 		(void) close(fd);
122 	return (0);
123 }
124 
125 /*
126  * Become a daemon.
127  */
128 static void
129 daemonize(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*/
170 static void
171 handle_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*/
183 static void
184 handle_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 
217 static void
218 add_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  */
256 static void
257 handle_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 
385 static void
386 main_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 
423 int
424 main(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