1
2/*
3 * utils.c - various utility functions used in pppoed.
4 *
5 * mostly stolen from ppp-2.3.10 by Marc Boucher <marc@mbsi.ca>
6 *
7 * Feb 18/2000 Made fully re-entrant (JHS)
8 *
9 * Copyright (c) 1999 The Australian National University.
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms are permitted
13 * provided that the above copyright poe_notice and this paragraph are
14 * duplicated in all such forms and that any documentation,
15 * advertising materials, and other materials related to such
16 * distribution and use acknowledge that the software was developed
17 * by the Australian National University.  The name of the University
18 * may not be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
22 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 */
24
25#include <stdio.h>		/* stdio               */
26#include <stdlib.h>		/* strtoul(), realloc() */
27#include <string.h>		/* memcpy()             */
28#include <unistd.h>		/* STDIN_FILENO,exec    */
29#include <errno.h>		/* errno                */
30
31#include <sys/time.h>
32
33#include <net/ethernet.h>
34#include <netinet/in.h>
35
36#include <stdarg.h>
37#include <ctype.h>
38#include <syslog.h>
39#include <limits.h>
40#include <paths.h>
41
42#include "pppoe.h"
43
44static char pidfilename[PATH_MAX];	/* name of pid file */
45
46/*
47static int detached = 0;
48   log_to_fd = -1;
49 */
50
51static void vslp_printer (void *, char *,...);
52static void format_packet (struct pppoe_packet *, int, void (*)(void *, char *,...), void *);
53static void format_tag (struct pppoe_tag *, void (*)(void *, char *,...), void *);
54struct buffer_poe_info {
55  char *ptr;
56  int len;
57};
58
59void poe_die (int status);
60
61
62/*
63 * vpoe_slprintf - like vsprintf, except we
64 * also specify the length of the output buffer, and we handle
65 * %r (recursive format), %m (poe_error message), %v (visible string),
66 * %q (quoted string), %t (current time) and %E (Ether address) formats.
67 * Doesn't do floating-point formats.
68 * Returns the number of chars put into buf.
69 */
70#define OUTCHAR(c)	(buflen > 0? (--buflen, *buf++ = (c)): 0)
71
72int
73vpoe_slprintf (char *buf, int buflen, char *fmt, va_list args)
74{
75  int c, i, n;
76  int width, prec, fillch;
77  int base, len, neg, quoted;
78  unsigned long val = 0;
79  char *str, *f, *buf0;
80  unsigned char *p;
81  char num[32];
82  time_t t;
83  static char hexchars[] = "0123456789abcdef";
84  struct buffer_poe_info bufpoe_info;
85
86  buf0 = buf;
87  --buflen;
88  while (buflen > 0) {
89    for (f = fmt; *f != '%' && *f != 0; ++f);
90    if (f > fmt) {
91      len = f - fmt;
92      if (len > buflen)
93	len = buflen;
94      memcpy (buf, fmt, len);
95      buf += len;
96      buflen -= len;
97      fmt = f;
98    }
99    if (*fmt == 0)
100      break;
101    c = *++fmt;
102    width = 0;
103    prec = -1;
104    fillch = ' ';
105    if (c == '0') {
106      fillch = '0';
107      c = *++fmt;
108    }
109    if (c == '*') {
110      width = va_arg (args, int);
111      c = *++fmt;
112    }
113    else {
114      while (isdigit (c)) {
115	width = width * 10 + c - '0';
116	c = *++fmt;
117      }
118    }
119    if (c == '.') {
120      c = *++fmt;
121      if (c == '*') {
122	prec = va_arg (args, int);
123	c = *++fmt;
124      }
125      else {
126	prec = 0;
127	while (isdigit (c)) {
128	  prec = prec * 10 + c - '0';
129	  c = *++fmt;
130	}
131      }
132    }
133    str = 0;
134    base = 0;
135    neg = 0;
136    ++fmt;
137    switch (c) {
138    case 'd':
139      i = va_arg (args, int);
140      if (i < 0) {
141	neg = 1;
142	val = -i;
143      }
144      else
145	val = i;
146      base = 10;
147      break;
148    case 'o':
149      val = va_arg (args, unsigned int);
150      base = 8;
151      break;
152    case 'x':
153    case 'X':
154      val = va_arg (args, unsigned int);
155      base = 16;
156      break;
157    case 'p':
158      val = (unsigned long) va_arg (args, void *);
159      base = 16;
160      neg = 2;
161      break;
162    case 's':
163      str = va_arg (args, char *);
164      break;
165    case 'c':
166      num[0] = va_arg (args, int);
167      num[1] = 0;
168      str = num;
169      break;
170    case 'm':
171      str = strerror (errno);
172      break;
173    case 'E':
174      p = va_arg (args, unsigned char *);
175      for (n = ETH_ALEN; n > 0; --n) {
176	c = *p++;
177	OUTCHAR (hexchars[(c >> 4) & 0xf]);
178	OUTCHAR (hexchars[c & 0xf]);
179	if (n > 1)
180	  OUTCHAR (':');
181      }
182      continue;
183    case 'r':
184      f = va_arg (args, char *);
185#ifndef __powerpc__
186      n = vpoe_slprintf (buf, buflen + 1, f, va_arg (args, va_list));
187#else
188      /* On the powerpc, a va_list is an array of 1 structure */
189      n = vpoe_slprintf (buf, buflen + 1, f, va_arg (args, void *));
190#endif
191      buf += n;
192      buflen -= n;
193      continue;
194    case 't':
195      time (&t);
196      str = ctime (&t);
197      str += 4;			/* chop off the day name */
198      str[15] = 0;		/* chop off year and newline */
199      break;
200    case 'v':			/* "visible" string */
201    case 'q':			/* quoted string */
202      quoted = c == 'q';
203      p = va_arg (args, unsigned char *);
204      if (fillch == '0' && prec >= 0) {
205	n = prec;
206      }
207      else {
208	n = strlen ((char *) p);
209	if (prec >= 0 && n > prec)
210	  n = prec;
211      }
212      while (n > 0 && buflen > 0) {
213	c = *p++;
214	--n;
215	if (!quoted && c >= 0x80) {
216	  OUTCHAR ('M');
217	  OUTCHAR ('-');
218	  c -= 0x80;
219	}
220	if (quoted && (c == '"' || c == '\\'))
221	  OUTCHAR ('\\');
222	if (c < 0x20 || (0x7f <= c && c < 0xa0)) {
223	  if (quoted) {
224	    OUTCHAR ('\\');
225	    switch (c) {
226	    case '\t':
227	      OUTCHAR ('t');
228	      break;
229	    case '\n':
230	      OUTCHAR ('n');
231	      break;
232	    case '\b':
233	      OUTCHAR ('b');
234	      break;
235	    case '\f':
236	      OUTCHAR ('f');
237	      break;
238	    default:
239	      OUTCHAR ('x');
240	      OUTCHAR (hexchars[c >> 4]);
241	      OUTCHAR (hexchars[c & 0xf]);
242	    }
243	  }
244	  else {
245	    if (c == '\t')
246	      OUTCHAR (c);
247	    else {
248	      OUTCHAR ('^');
249	      OUTCHAR (c ^ 0x40);
250	    }
251	  }
252	}
253	else
254	  OUTCHAR (c);
255      }
256      continue;
257    case 'P':			/* print PPPoE packet */
258      bufpoe_info.ptr = buf;
259      bufpoe_info.len = buflen + 1;
260      p = va_arg (args, unsigned char *);
261      n = va_arg (args, int);
262      format_packet ((struct pppoe_packet *) p, n, vslp_printer, &bufpoe_info);
263      buf = bufpoe_info.ptr;
264      buflen = bufpoe_info.len - 1;
265      continue;
266    case 'T':			/* print PPPoE tag */
267      bufpoe_info.ptr = buf;
268      bufpoe_info.len = buflen + 1;
269      p = va_arg (args, unsigned char *);
270      format_tag ((struct pppoe_tag *) p, vslp_printer, &bufpoe_info);
271      buf = bufpoe_info.ptr;
272      buflen = bufpoe_info.len - 1;
273      continue;
274    case 'B':
275      p = va_arg (args, unsigned char *);
276      for (n = prec; n > 0; --n) {
277	c = *p++;
278	if (fillch == ' ')
279	  OUTCHAR (' ');
280	OUTCHAR (hexchars[(c >> 4) & 0xf]);
281	OUTCHAR (hexchars[c & 0xf]);
282      }
283      continue;
284    default:
285      *buf++ = '%';
286      if (c != '%')
287	--fmt;			/* so %z outputs %z etc. */
288      --buflen;
289      continue;
290    }
291    if (base != 0) {
292      str = num + sizeof (num);
293      *--str = 0;
294      while (str > num + neg) {
295	*--str = hexchars[val % base];
296	val = val / base;
297	if (--prec <= 0 && val == 0)
298	  break;
299      }
300      switch (neg) {
301      case 1:
302	*--str = '-';
303	break;
304      case 2:
305	*--str = 'x';
306	*--str = '0';
307	break;
308      }
309      len = num + sizeof (num) - 1 - str;
310    }
311    else {
312      len = strlen (str);
313      if (prec >= 0 && len > prec)
314	len = prec;
315    }
316    if (width > 0) {
317      if (width > buflen)
318	width = buflen;
319      if ((n = width - len) > 0) {
320	buflen -= n;
321	for (; n > 0; --n)
322	  *buf++ = fillch;
323      }
324    }
325    if (len > buflen)
326      len = buflen;
327    memcpy (buf, str, len);
328    buf += len;
329    buflen -= len;
330  }
331  *buf = 0;
332  return buf - buf0;
333}
334
335/*
336 * vslp_printer - used in processing a %P format
337 */
338static void
339vslp_printer (void *arg, char *fmt,...)
340{
341  int n;
342  va_list pvar;
343  struct buffer_poe_info *bi;
344
345  va_start (pvar, fmt);
346
347  bi = (struct buffer_poe_info *) arg;
348  n = vpoe_slprintf (bi->ptr, bi->len, fmt, pvar);
349  va_end (pvar);
350
351  bi->ptr += n;
352  bi->len -= n;
353}
354
355/*
356 * format_packet - make a readable representation of a packet,
357 * calling `printer(arg, format, ...)' to output it.
358 */
359static void
360format_packet (struct pppoe_packet *p,
361	       int len,
362	       void (*printer) (void *, char *,...),
363	       void *arg)
364{
365  struct pppoe_tag *t;
366
367  printer (arg, "Ether addr: %E\n", p->addr.sll_addr);
368
369  switch ((unsigned) ntohs (p->addr.sll_protocol)) {
370  case ETH_P_PPPOE_DISC:
371    printer (arg, " (PPPOE Discovery)\n");
372    break;
373  case ETH_P_PPPOE_SESS:
374    printer (arg, " (PPPOE Session)\n");
375    break;
376  }
377
378  printer (arg, " PPPoE hdr: ver=0x%01x type=0x%01x code=0x%02x "
379	   "sid=0x%04x length=0x%04x ", (unsigned) p->hdr->ver,
380	   (unsigned) p->hdr->type, (unsigned) p->hdr->code, (unsigned) p->hdr->sid,
381	   (unsigned) ntohs (p->hdr->length));
382
383  switch (p->hdr->code) {
384  case PADI_CODE:
385    printer (arg, "(PADI)\n");
386    break;
387  case PADO_CODE:
388    printer (arg, "(PADO)\n");
389    break;
390  case PADR_CODE:
391    printer (arg, "(PADR)\n");
392    break;
393  case PADS_CODE:
394    printer (arg, "(PADS)\n");
395    break;
396  case PADT_CODE:
397    printer (arg, "(PADT)\n");
398    break;
399  default:
400    printer (arg, "(Unknown)\n");
401  }
402
403
404  for(t = (struct pppoe_tag *) (&p->hdr->tag);
405      (t < (struct pppoe_tag *) ((char *) (&p->hdr->tag) + ntohs (p->hdr->length))) &&
406	  ntohs (t->tag_type) != PTT_EOL;
407      t = (struct pppoe_tag *) ((char *) (t + 1) + ntohs (t->tag_len))) {
408      format_tag (t, printer, arg);
409  }
410}
411
412/*
413 * format_tag - make a readable representation of a tag,
414 * calling `printer(arg, format, ...)' to output it.
415 */
416static void
417format_tag (struct pppoe_tag *t,
418	       void (*printer) (void *, char *,...),
419	       void *arg)
420{
421    printer (arg, " PPPoE tag: type=%04x length=%04x ",
422	     ntohs (t->tag_type), ntohs (t->tag_len));
423    switch ( t->tag_type ) {
424    case PTT_EOL:
425      printer (arg, "(End of list)");
426      break;
427    case PTT_SRV_NAME:
428      printer (arg, "(Service name)");
429      break;
430    case PTT_AC_NAME:
431      printer (arg, "(AC Name)");
432      break;
433    case PTT_HOST_UNIQ:
434      printer (arg, "(Host Uniq)");
435      break;
436    case PTT_AC_COOKIE:
437      printer (arg, "(AC Cookie)");
438      break;
439    case PTT_VENDOR:
440      printer (arg, "(Vendor Specific)");
441      break;
442    case PTT_RELAY_SID:
443      printer (arg, "(Relay Session ID)");
444      break;
445    case PTT_SRV_ERR:
446      printer (arg, "(Service Name Error)");
447      break;
448    case PTT_SYS_ERR:
449      printer (arg, "(AC System Error)");
450      break;
451    case PTT_GEN_ERR:
452      printer (arg, "(Generic Error)");
453      break;
454    default:
455      printer (arg, "(Unknown)");
456    }
457    if (ntohs (t->tag_len) > 0)
458      switch ( t->tag_type ) {
459      case PTT_SRV_NAME:
460      case PTT_AC_NAME:
461      case PTT_SRV_ERR:
462      case PTT_SYS_ERR:
463      case PTT_GEN_ERR:	/* ascii data */
464	{
465	  char *buf;
466	  buf = malloc (ntohs (t->tag_len) + 1);
467	  memset (buf, 0, ntohs (t->tag_len) + 1);
468	  strncpy (buf, (char *) (t + 1), ntohs (t->tag_len));
469//	  buf[ntohs (t->tag_len)] = '\0';
470	  printer (arg, " data (UTF-8): %s", buf);
471	  free (buf);
472	  break;
473	}
474
475      case PTT_HOST_UNIQ:
476      case PTT_AC_COOKIE:
477      case PTT_RELAY_SID:
478	printer (arg, " data (bin): %.*B", ntohs (t->tag_len), (char *) (t + 1));
479	break;
480
481      default:
482	printer (arg, " unrecognized data");
483      }
484}
485
486/*
487 * poe_logit - does the hard work for poe_fatal et al.
488 */
489static void
490poe_logit (struct session *ses,int level, char *fmt, va_list args)
491{
492  int n;
493  char buf[256];
494
495  n = vpoe_slprintf (buf, sizeof (buf), fmt, args);
496  syslog (level, "%s", buf);
497  if (log_to_fd >= 0 && (level != LOG_DEBUG || ses->opt_debug)) {
498    if (buf[n - 1] != '\n')
499      buf[n++] = '\n';
500    if (write (log_to_fd, buf, n) != n)
501      log_to_fd = -1;
502  }
503}
504
505/*
506 * poe_fatal - log an poe_error message and poe_die horribly.
507 */
508void
509poe_fatal (struct session *ses, char *fmt,...)
510{
511  va_list pvar;
512
513  va_start (pvar, fmt);
514
515  poe_logit (ses,LOG_ERR, fmt, pvar);
516  va_end (pvar);
517
518  poe_die(1);			/* as promised */
519}
520
521/*
522 * poe_error - log an poe_error message.
523 */
524void
525poe_error (struct session *ses,char *fmt,...)
526{
527  va_list pvar;
528
529  va_start (pvar, fmt);
530
531  poe_logit (ses,LOG_ERR, fmt, pvar);
532  va_end (pvar);
533}
534
535/*
536 * poe_warn - log a poe_warning message.
537 */
538void
539poe_warn (struct session *ses,char *fmt,...)
540{
541  va_list pvar;
542
543  va_start (pvar, fmt);
544
545  poe_logit (ses,LOG_WARNING, fmt, pvar);
546  va_end (pvar);
547}
548
549/*
550 * poe_info - log an poe_informational message.
551 */
552void
553poe_info (struct session *ses,char *fmt,...)
554{
555  va_list pvar;
556
557  va_start (pvar, fmt);
558
559  poe_logit (ses,LOG_INFO, fmt, pvar);
560  va_end (pvar);
561}
562
563/*
564 * poe_dbglog - log a debug message.
565 */
566void
567poe_dbglog (struct session *ses ,char *fmt,...)
568{
569  va_list pvar;
570
571  va_start (pvar, fmt);
572
573  poe_logit (ses,LOG_DEBUG, fmt, pvar);
574  va_end (pvar);
575}
576
577/*
578 * Create a file containing our process ID.
579 */
580void
581poe_create_pidfile (struct session *ses)
582{
583  FILE *pidfile;
584
585  sprintf (pidfilename, "%s%s.pid", _PATH_VARRUN, "pppoed");
586  if ((pidfile = fopen (pidfilename, "w")) != NULL) {
587    fprintf (pidfile, "%d\n", getpid ());
588    (void) fclose (pidfile);
589  }
590  else {
591    poe_error (ses,"Failed to create pid file %s: %m", pidfilename);
592    pidfilename[0] = 0;
593  }
594}
595
596/*
597 * detach - detach us from the controlling terminal.
598 */
599void
600poe_detach (struct session *ses)
601{
602  if (ses->detached)
603    return;
604
605  if ((daemon (0, 0)) < 0) {
606    poe_error (ses,"Couldn't detach (daemon failed: %m)");
607  }
608  ses->detached = 1;
609  ses->log_to_fd = -1;
610  /* update pid files if they have been written already */
611  if (pidfilename[0])
612    poe_create_pidfile (ses);
613}
614
615/*
616 * cleanup - restore anything which needs to be restored before we exit
617 */
618/* ARGSUSED */
619static void
620cleanup ()
621{
622  if (pidfilename[0] != 0 && unlink (pidfilename) < 0 && errno != ENOENT)
623    syslog (LOG_INFO,"unable to delete pid file ");
624  pidfilename[0] = 0;
625}
626
627/*
628 * poe_die - clean up state and exit with the specified status.
629 */
630void
631poe_die (int status)
632{
633  cleanup ();
634  syslog (LOG_INFO, "Exit.");
635  exit (status);
636}
637