From 0b3acd3a1122d542e2994d7791cc9127e91778f1 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Tue, 4 Apr 2000 07:06:53 +0000 Subject: [PATCH] Multilink support. We now put details of each connection in a tdb database. At present the values we put in the database are just the same as the environment strings. --- pppd/Makefile.linux | 11 +- pppd/auth.c | 10 +- pppd/ipcp.c | 16 +- pppd/ipv6cp.c | 8 +- pppd/lcp.c | 123 +++-- pppd/lcp.h | 11 +- pppd/main.c | 328 +++++++---- pppd/options.c | 67 ++- pppd/patchlevel.h | 10 +- pppd/pathnames.h | 16 +- pppd/pppd.h | 34 +- pppd/sys-linux.c | 160 ++++-- pppd/tdb.c | 1282 +++++++++++++++++++++++++++++++++++++++++++ pppd/tdb.h | 77 +++ 14 files changed, 1894 insertions(+), 259 deletions(-) create mode 100644 pppd/tdb.c create mode 100644 pppd/tdb.h diff --git a/pppd/Makefile.linux b/pppd/Makefile.linux index f36d841..1a0662c 100644 --- a/pppd/Makefile.linux +++ b/pppd/Makefile.linux @@ -1,6 +1,6 @@ # # pppd makefile for Linux -# $Id: Makefile.linux,v 1.36 2000/03/27 06:02:59 paulus Exp $ +# $Id: Makefile.linux,v 1.37 2000/04/04 07:06:48 paulus Exp $ # # Default installation locations @@ -9,12 +9,13 @@ MANDIR = /usr/man PPPDSRCS = main.c magic.c fsm.c lcp.c ipcp.c upap.c chap.c md5.c ccp.c \ ipxcp.c auth.c options.c sys-linux.c md4.c chap_ms.c cbcp.c \ - demand.c utils.c + demand.c utils.c multilink.c tdb.c HEADERS = callout.h pathnames.h patchlevel.h chap.h md5.h chap_ms.h md4.h \ - ipxcp.h cbcp.h + ipxcp.h cbcp.h tdb.h MANPAGES = pppd.8 PPPDOBJS = main.o magic.o fsm.o lcp.o ipcp.o upap.o chap.o md5.o ccp.o \ - auth.o options.o demand.o utils.o sys-linux.o ipxcp.o + auth.o options.o demand.o utils.o sys-linux.o ipxcp.o multilink.o \ + tdb.o all: pppd @@ -50,7 +51,7 @@ PLUGIN=y INCLUDE_DIRS= -I../include -COMPILE_FLAGS= -D_linux_=1 -DHAVE_PATHS_H -DIPX_CHANGE -DHAVE_MULTILINK +COMPILE_FLAGS= -D_linux_=1 -DHAVE_PATHS_H -DIPX_CHANGE -DHAVE_MULTILINK -DHAVE_MMAP CFLAGS= $(COPTS) $(COMPILE_FLAGS) $(INCLUDE_DIRS) diff --git a/pppd/auth.c b/pppd/auth.c index 3d466e2..e97b48e 100644 --- a/pppd/auth.c +++ b/pppd/auth.c @@ -32,7 +32,7 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: auth.c,v 1.62 2000/03/27 06:02:59 paulus Exp $" +#define RCSID "$Id: auth.c,v 1.63 2000/04/04 07:06:49 paulus Exp $" #include #include @@ -510,6 +510,12 @@ start_networks() new_phase(PHASE_NETWORK); +#ifdef HAVE_MULTILINK + if (multilink) + if (mp_join_bundle()) + return; +#endif /* HAVE_MULTILINK */ + #if 0 if (!demand) set_filters(&pass_filter, &active_filter); @@ -571,7 +577,7 @@ auth_peer_success(unit, protocol, name, namelen) namelen = sizeof(peer_authname) - 1; BCOPY(name, peer_authname, namelen); peer_authname[namelen] = 0; - script_setenv("PEERNAME", peer_authname); + script_setenv("PEERNAME", peer_authname, 0); /* * If there is no more authentication still to be done, diff --git a/pppd/ipcp.c b/pppd/ipcp.c index b5749ac..829b2c9 100644 --- a/pppd/ipcp.c +++ b/pppd/ipcp.c @@ -17,7 +17,7 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: ipcp.c,v 1.52 1999/12/23 01:25:33 paulus Exp $" +#define RCSID "$Id: ipcp.c,v 1.53 2000/04/04 07:06:49 paulus Exp $" /* * TODO: @@ -1389,15 +1389,15 @@ ipcp_up(f) ipcp_close(f->unit, "Could not determine local IP address"); return; } - script_setenv("IPLOCAL", ip_ntoa(go->ouraddr)); - script_setenv("IPREMOTE", ip_ntoa(ho->hisaddr)); + script_setenv("IPLOCAL", ip_ntoa(go->ouraddr), 0); + script_setenv("IPREMOTE", ip_ntoa(ho->hisaddr), 1); if (usepeerdns && (go->dnsaddr[0] || go->dnsaddr[1])) { - script_setenv("USEPEERDNS", "1"); + script_setenv("USEPEERDNS", "1", 0); if (go->dnsaddr[0]) - script_setenv("DNS1", ip_ntoa(go->dnsaddr[0])); + script_setenv("DNS1", ip_ntoa(go->dnsaddr[0]), 0); if (go->dnsaddr[1]) - script_setenv("DNS2", ip_ntoa(go->dnsaddr[1])); + script_setenv("DNS2", ip_ntoa(go->dnsaddr[1]), 0); create_resolv(go->dnsaddr[0], go->dnsaddr[1]); } @@ -1423,13 +1423,13 @@ ipcp_up(f) ipcp_clear_addrs(f->unit, wo->ouraddr, wo->hisaddr); if (go->ouraddr != wo->ouraddr) { warn("Local IP address changed to %I", go->ouraddr); - script_setenv("OLDIPLOCAL", ip_ntoa(wo->ouraddr)); + script_setenv("OLDIPLOCAL", ip_ntoa(wo->ouraddr), 0); wo->ouraddr = go->ouraddr; } else script_unsetenv("OLDIPLOCAL"); if (ho->hisaddr != wo->hisaddr) { warn("Remote IP address changed to %I", ho->hisaddr); - script_setenv("OLDIPREMOTE", ip_ntoa(wo->hisaddr)); + script_setenv("OLDIPREMOTE", ip_ntoa(wo->hisaddr), 0); wo->hisaddr = ho->hisaddr; } else script_unsetenv("OLDIPREMOTE"); diff --git a/pppd/ipv6cp.c b/pppd/ipv6cp.c index 7bf5e5a..98cae54 100644 --- a/pppd/ipv6cp.c +++ b/pppd/ipv6cp.c @@ -95,10 +95,10 @@ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * - * $Id: ipv6cp.c,v 1.7 1999/10/08 01:08:18 masputra Exp $ + * $Id: ipv6cp.c,v 1.8 2000/04/04 07:06:50 paulus Exp $ */ -#define RCSID "$Id: ipv6cp.c,v 1.7 1999/10/08 01:08:18 masputra Exp $" +#define RCSID "$Id: ipv6cp.c,v 1.8 2000/04/04 07:06:50 paulus Exp $" /* * TODO: @@ -1120,8 +1120,8 @@ ipv6cp_up(f) return; } } - script_setenv("LLLOCAL", llv6_ntoa(go->ourid)); - script_setenv("LLREMOTE", llv6_ntoa(ho->hisid)); + script_setenv("LLLOCAL", llv6_ntoa(go->ourid), 0); + script_setenv("LLREMOTE", llv6_ntoa(ho->hisid), 0); #ifdef IPV6CP_COMP /* set tcp compression */ diff --git a/pppd/lcp.c b/pppd/lcp.c index 231b7e4..3092b5a 100644 --- a/pppd/lcp.c +++ b/pppd/lcp.c @@ -17,7 +17,7 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: lcp.c,v 1.48 2000/03/27 06:02:59 paulus Exp $"; +#define RCSID "$Id: lcp.c,v 1.49 2000/04/04 07:06:50 paulus Exp $"; /* * TODO: @@ -44,6 +44,11 @@ bool lax_recv = 0; /* accept control chars in asyncmap */ static int setescape __P((char **)); +#ifdef HAVE_MULTILINK +bool noendpoint = 0; /* don't send/accept endpoint discriminator */ +static int setendpoint __P((char **)); +#endif /* HAVE_MULTILINK */ + static option_t lcp_option_list[] = { /* LCP options */ { "noaccomp", o_bool, &lcp_wantoptions[0].neg_accompression, @@ -108,12 +113,19 @@ static option_t lcp_option_list[] = { { "receive-all", o_bool, &lax_recv, "Accept all received control characters", 1 }, #ifdef HAVE_MULTILINK + { "mrru", o_int, &lcp_wantoptions[0].mrru, + "Maximum received packet size for multilink bundle", + 0, &lcp_wantoptions[0].neg_mrru }, { "mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf, "Use short sequence numbers in multilink headers", OPT_A2COPY, &lcp_allowoptions[0].neg_ssnhf }, { "nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf, "Don't use short sequence numbers in multilink headers", OPT_A2COPY, &lcp_allowoptions[0].neg_ssnhf }, + { "endpoint", o_special, setendpoint, + "Endpoint discriminator for multilink" }, + { "noendpoint", o_bool, &noendpoint, + "Don't send or accept multilink endpoint discriminator", 1 }, #endif /* HAVE_MULTILINK */ {NULL} }; @@ -258,6 +270,20 @@ setescape(argv) return ret; } +#ifdef HAVE_MULTILINK +static int +setendpoint(argv) + char **argv; +{ + if (str_to_epdisc(&lcp_wantoptions[0].endpoint, *argv)) { + lcp_wantoptions[0].neg_endpoint = 1; + return 1; + } + option_error("Can't parse '%s' as an endpoint discriminator", *argv); + return 0; +} +#endif /* HAVE_MULTILINK */ + /* * lcp_init - Initialize LCP. */ @@ -275,41 +301,33 @@ lcp_init(unit) fsm_init(f); - wo->passive = 0; - wo->silent = 0; - wo->restart = 0; /* Set to 1 in kernels or multi-line - implementations */ + BZERO(wo, sizeof(*wo)); wo->neg_mru = 1; wo->mru = DEFMRU; wo->neg_asyncmap = 1; - wo->asyncmap = 0; - wo->neg_chap = 0; /* Set to 1 on server */ - wo->neg_upap = 0; /* Set to 1 on server */ wo->chap_mdtype = CHAP_DIGEST_MD5; wo->neg_magicnumber = 1; wo->neg_pcompression = 1; wo->neg_accompression = 1; - wo->neg_lqr = 0; /* no LQR implementation yet */ - wo->neg_cbcp = 0; + BZERO(ao, sizeof(*ao)); ao->neg_mru = 1; ao->mru = MAXMRU; ao->neg_asyncmap = 1; - ao->asyncmap = 0; ao->neg_chap = 1; ao->chap_mdtype = CHAP_DIGEST_MD5; ao->neg_upap = 1; ao->neg_magicnumber = 1; ao->neg_pcompression = 1; ao->neg_accompression = 1; - ao->neg_lqr = 0; /* no LQR implementation yet */ #ifdef CBCP_SUPPORT ao->neg_cbcp = 1; -#else - ao->neg_cbcp = 0; +#endif +#ifdef HAVE_MULTILINK + ao->neg_endpoint = 1; #endif - memset(xmit_accm[unit], 0, sizeof(xmit_accm[0])); + BZERO(xmit_accm[unit], sizeof(xmit_accm[0])); xmit_accm[unit][3] = 0x60000000; } @@ -539,12 +557,16 @@ lcp_resetci(f) fsm *f; { lcp_options *wo = &lcp_wantoptions[f->unit]; + lcp_options *go = &lcp_gotoptions[f->unit]; wo->magicnumber = magic(); wo->numloops = 0; - if (!wo->neg_multilink) - wo->neg_ssnhf = 0; - lcp_gotoptions[f->unit] = *wo; + *go = *wo; + if (!multilink) { + go->neg_mrru = 0; + go->neg_ssnhf = 0; + go->neg_endpoint = 0; + } peer_mru[f->unit] = PPP_MRU; auth_reset(f->unit); } @@ -578,9 +600,9 @@ lcp_cilen(f) LENCILONG(go->neg_magicnumber) + LENCIVOID(go->neg_pcompression) + LENCIVOID(go->neg_accompression) + - LENCISHORT(go->neg_multilink) + + LENCISHORT(go->neg_mrru) + LENCIVOID(go->neg_ssnhf) + - (go->neg_endpoint? CILEN_CHAR + go->endp_len: 0)); + (go->neg_endpoint? CILEN_CHAR + go->endpoint.length: 0)); } @@ -653,10 +675,10 @@ lcp_addci(f, ucp, lenp) ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber); ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression); ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression); - ADDCISHORT(CI_MRRU, go->neg_multilink, go->mrru); + ADDCISHORT(CI_MRRU, go->neg_mrru, go->mrru); ADDCIVOID(CI_SSNHF, go->neg_ssnhf); - ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endp_class, go->endpoint, - go->endp_len); + ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class, + go->endpoint.value, go->endpoint.length); if (ucp - start_ucp != *lenp) { /* this should never happen, because peer_mtu should be 1500 */ @@ -800,10 +822,10 @@ lcp_ackci(f, p, len) ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber); ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression); ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression); - ACKCISHORT(CI_MRRU, go->neg_multilink, go->mrru); + ACKCISHORT(CI_MRRU, go->neg_mrru, go->mrru); ACKCIVOID(CI_SSNHF, go->neg_ssnhf); - ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endp_class, go->endpoint, - go->endp_len); + ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class, + go->endpoint.value, go->endpoint.length); /* * If there are any remaining CIs, then this packet is bad. @@ -1052,8 +1074,8 @@ lcp_nakci(f, p, len) * Nak for MRRU option - accept their value if it is smaller * than the one we want. */ - if (go->neg_multilink) { - NAKCISHORT(CI_MRRU, neg_multilink, + if (go->neg_mrru) { + NAKCISHORT(CI_MRRU, neg_mrru, if (cishort <= wo->mrru) try.mrru = cishort; ); @@ -1134,7 +1156,7 @@ lcp_nakci(f, p, len) goto bad; break; case CI_MRRU: - if (go->neg_multilink || no.neg_multilink || cilen != CILEN_SHORT) + if (go->neg_mrru || no.neg_mrru || cilen != CILEN_SHORT) goto bad; break; case CI_SSNHF: @@ -1286,7 +1308,7 @@ lcp_rejci(f, p, len) p[1] == CILEN_CHAR + vlen) { \ int i; \ len -= CILEN_CHAR + vlen; \ - INCPTR(p[1], p); \ + INCPTR(2, p); \ GETCHAR(cichar, p); \ if (cichar != class) \ goto bad; \ @@ -1309,10 +1331,10 @@ lcp_rejci(f, p, len) REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber); REJCIVOID(CI_PCOMPRESSION, neg_pcompression); REJCIVOID(CI_ACCOMPRESSION, neg_accompression); - REJCISHORT(CI_MRRU, neg_multilink, go->mrru); + REJCISHORT(CI_MRRU, neg_mrru, go->mrru); REJCIVOID(CI_SSNHF, neg_ssnhf); - REJCIENDP(CI_EPDISC, neg_endpoint, go->endp_class, go->endpoint, - go->endp_len); + REJCIENDP(CI_EPDISC, neg_endpoint, go->endpoint.class, + go->endpoint.value, go->endpoint.length); /* * If there are any remaining CIs, then this packet is bad. @@ -1596,7 +1618,7 @@ lcp_reqci(f, inp, lenp, reject_if_disagree) break; case CI_MRRU: - if (!ao->neg_multilink || + if (!ao->neg_mrru || !multilink || cilen != CILEN_SHORT) { orc = CONFREJ; break; @@ -1604,12 +1626,12 @@ lcp_reqci(f, inp, lenp, reject_if_disagree) GETSHORT(cishort, p); /* possibly should insist on a minimum/maximum MRRU here */ - ho->neg_multilink = 1; + ho->neg_mrru = 1; ho->mrru = cishort; break; case CI_SSNHF: - if (!ao->neg_ssnhf || + if (!ao->neg_ssnhf || !multilink || cilen != CILEN_VOID) { orc = CONFREJ; break; @@ -1618,7 +1640,7 @@ lcp_reqci(f, inp, lenp, reject_if_disagree) break; case CI_EPDISC: - if (!ao->neg_endpoint || + if (!ao->neg_endpoint || !multilink || cilen < CILEN_CHAR || cilen > CILEN_CHAR + MAX_ENDP_LEN) { orc = CONFREJ; @@ -1627,9 +1649,9 @@ lcp_reqci(f, inp, lenp, reject_if_disagree) GETCHAR(cichar, p); cilen -= CILEN_CHAR; ho->neg_endpoint = 1; - ho->endp_class = cichar; - ho->endp_len = cilen; - BCOPY(p, ho->endpoint, cilen); + ho->endpoint.class = cichar; + ho->endpoint.length = cilen; + BCOPY(p, ho->endpoint.value, cilen); INCPTR(cilen, p); break; @@ -1783,10 +1805,6 @@ static char *lcp_codenames[] = { "EchoReq", "EchoRep", "DiscReq" }; -static char *endp_class_names[] = { - "null", "local", "IP", "MAC", "magic", "phone" -}; - static int lcp_printpkt(p, plen, printer, arg) u_char *p; @@ -1796,7 +1814,6 @@ lcp_printpkt(p, plen, printer, arg) { int code, id, len, olen, i; u_char *pstart, *optend; - u_char cichar; u_short cishort; u_int32_t cilong; @@ -1939,13 +1956,17 @@ lcp_printpkt(p, plen, printer, arg) break; case CI_EPDISC: if (olen >= CILEN_CHAR) { + struct epdisc epd; p += 2; - GETCHAR(cichar, p); - if (cichar <= 5) - printer(arg, "endpoint [%s]:", - endp_class_names[cichar]); - else - printer(arg, "endpoint [%d]:"); + GETCHAR(epd.class, p); + epd.length = olen - CILEN_CHAR; + if (epd.length > MAX_ENDP_LEN) + epd.length = MAX_ENDP_LEN; + if (epd.length > 0) { + BCOPY(p, epd.value, epd.length); + p += epd.length; + } + printer(arg, "endpoint [%s]", epdisc_to_str(&epd)); } break; } diff --git a/pppd/lcp.h b/pppd/lcp.h index f2e7c98..e52bdf5 100644 --- a/pppd/lcp.h +++ b/pppd/lcp.h @@ -16,7 +16,7 @@ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * - * $Id: lcp.h,v 1.14 2000/03/27 06:03:00 paulus Exp $ + * $Id: lcp.h,v 1.15 2000/04/04 07:06:51 paulus Exp $ */ /* @@ -43,9 +43,6 @@ #define DISCREQ 11 /* Discard Request */ #define CBCP_OPT 6 /* Use callback control protocol */ -/* maximum length of endpoint discriminator value */ -#define MAX_ENDP_LEN 20 - /* * The state of options is described by an lcp_options structure. */ @@ -62,7 +59,7 @@ typedef struct lcp_options { bool neg_accompression; /* HDLC Address/Control Field Compression? */ bool neg_lqr; /* Negotiate use of Link Quality Reports */ bool neg_cbcp; /* Negotiate use of CBCP */ - bool neg_multilink; /* negotiate multilink (MRRU) */ + bool neg_mrru; /* negotiate multilink MRRU */ bool neg_ssnhf; /* negotiate short sequence numbers */ bool neg_endpoint; /* negotiate endpoint discriminator */ int mru; /* Value of MRU */ @@ -72,9 +69,7 @@ typedef struct lcp_options { u_int32_t magicnumber; int numloops; /* Number of loops during magic number neg. */ u_int32_t lqr_period; /* Reporting period for LQR 1/100ths second */ - int endp_class; /* endpoint discriminator class */ - int endp_len; /* endpoint discriminator length */ - u_char endpoint[MAX_ENDP_LEN]; /* endpoint discriminator value */ + struct epdisc endpoint; /* endpoint discriminator */ } lcp_options; extern fsm lcp_fsm[]; diff --git a/pppd/main.c b/pppd/main.c index 982ab55..5b57e99 100644 --- a/pppd/main.c +++ b/pppd/main.c @@ -17,7 +17,7 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: main.c,v 1.91 2000/03/27 06:03:01 paulus Exp $" +#define RCSID "$Id: main.c,v 1.92 2000/04/04 07:06:51 paulus Exp $" #include #include @@ -55,6 +55,7 @@ #include "ccp.h" #include "pathnames.h" #include "patchlevel.h" +#include "tdb.h" #ifdef CBCP_SUPPORT #include "cbcp.h" @@ -96,6 +97,8 @@ int unsuccess; /* # unsuccessful connection attempts */ int do_callback; /* != 0 if we should do callback next */ int doing_callback; /* != 0 if we are doing callback */ char *callback_script; /* script for doing callback */ +TDB_CONTEXT *pppdb; /* database for storing status etc. */ +char db_key[32]; int (*holdoff_hook) __P((void)) = NULL; int (*new_phase_hook) __P((int)) = NULL; @@ -154,6 +157,7 @@ static struct subprocess *children; /* Prototypes for procedures local to this file. */ +static void setup_signals __P((void)); static void create_pidfile __P((void)); static void create_linkpidfile __P((void)); static void cleanup __P((void)); @@ -172,6 +176,9 @@ static void holdoff_end __P((void *)); static int device_script __P((char *, int, int, int)); static int reap_kids __P((int waitfor)); static void record_child __P((int, char *, void (*) (void *), void *)); +static void update_db_entry __P((void)); +static void add_db_key __P((const char *)); +static void delete_db_key __P((const char *)); static int open_socket __P((char *)); static int start_charshunt __P((int, int)); static void charshunt_done __P((void *)); @@ -224,7 +231,6 @@ main(argc, argv) char *argv[]; { int i, fdflags, t; - struct sigaction sa; char *p, *connector; struct passwd *pw; struct timeval timo; @@ -264,7 +270,7 @@ main(argc, argv) uid = getuid(); privileged = uid == 0; slprintf(numbuf, sizeof(numbuf), "%d", uid); - script_setenv("ORIG_UID", numbuf); + script_setenv("ORIG_UID", numbuf, 0); ngroups = getgroups(NGROUPS_MAX, groups); @@ -344,6 +350,9 @@ main(argc, argv) if (!sys_check_options()) exit(EXIT_OPTION_ERROR); auth_check_options(); +#ifdef HAVE_MULTILINK + mp_check_options(); +#endif for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->check_options != NULL) (*protp->check_options)(); @@ -400,8 +409,6 @@ main(argc, argv) && S_ISCHR(statbuf.st_mode) && statbuf.st_rdev == devstat.st_rdev) log_to_fd = -1; - script_setenv("DEVICE", devnam); - /* * Initialize system-dependent stuff. */ @@ -409,6 +416,18 @@ main(argc, argv) if (debug) setlogmask(LOG_UPTO(LOG_DEBUG)); + pppdb = tdb_open(_PATH_PPPDB, 0, TDB_CLEAR_IF_FIRST, O_RDWR|O_CREAT, 0644); + if (pppdb != NULL) { + slprintf(db_key, sizeof(db_key), "pppd%d", getpid()); + update_db_entry(); + } else { + warn("Warning: couldn't open ppp database %s", _PATH_PPPDB); + if (multilink) { + warn("Warning: disabling multilink"); + multilink = 0; + } + } + /* * Detach ourselves from the terminal, if required, * and identify who is running us. @@ -425,81 +444,12 @@ main(argc, argv) } syslog(LOG_NOTICE, "pppd %s.%d%s started by %s, uid %d", VERSION, PATCHLEVEL, IMPLEMENTATION, p, uid); - script_setenv("PPPLOGNAME", p); + script_setenv("PPPLOGNAME", p, 0); - /* - * Compute mask of all interesting signals and install signal handlers - * for each. Only one signal handler may be active at a time. Therefore, - * all other signals should be masked when any handler is executing. - */ - sigemptyset(&mask); - sigaddset(&mask, SIGHUP); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGCHLD); - sigaddset(&mask, SIGUSR2); + if (devnam[0]) + script_setenv("DEVICE", devnam, 1); -#define SIGNAL(s, handler) do { \ - sa.sa_handler = handler; \ - if (sigaction(s, &sa, NULL) < 0) \ - fatal("Couldn't establish signal handler (%d): %m", s); \ - } while (0) - - sa.sa_mask = mask; - sa.sa_flags = 0; - SIGNAL(SIGHUP, hup); /* Hangup */ - SIGNAL(SIGINT, term); /* Interrupt */ - SIGNAL(SIGTERM, term); /* Terminate */ - SIGNAL(SIGCHLD, chld); - - SIGNAL(SIGUSR1, toggle_debug); /* Toggle debug flag */ - SIGNAL(SIGUSR2, open_ccp); /* Reopen CCP */ - - /* - * Install a handler for other signals which would otherwise - * cause pppd to exit without cleaning up. - */ - SIGNAL(SIGABRT, bad_signal); - SIGNAL(SIGALRM, bad_signal); - SIGNAL(SIGFPE, bad_signal); - SIGNAL(SIGILL, bad_signal); - SIGNAL(SIGPIPE, bad_signal); - SIGNAL(SIGQUIT, bad_signal); - SIGNAL(SIGSEGV, bad_signal); -#ifdef SIGBUS - SIGNAL(SIGBUS, bad_signal); -#endif -#ifdef SIGEMT - SIGNAL(SIGEMT, bad_signal); -#endif -#ifdef SIGPOLL - SIGNAL(SIGPOLL, bad_signal); -#endif -#ifdef SIGPROF - SIGNAL(SIGPROF, bad_signal); -#endif -#ifdef SIGSYS - SIGNAL(SIGSYS, bad_signal); -#endif -#ifdef SIGTRAP - SIGNAL(SIGTRAP, bad_signal); -#endif -#ifdef SIGVTALRM - SIGNAL(SIGVTALRM, bad_signal); -#endif -#ifdef SIGXCPU - SIGNAL(SIGXCPU, bad_signal); -#endif -#ifdef SIGXFSZ - SIGNAL(SIGXFSZ, bad_signal); -#endif - - /* - * Apparently we can get a SIGPIPE when we call syslog, if - * syslogd has died and been restarted. Ignoring it seems - * be sufficient. - */ - signal(SIGPIPE, SIG_IGN); + setup_signals(); waiting = 0; @@ -513,12 +463,7 @@ main(argc, argv) * Open the loopback channel and set it up to be the ppp interface. */ fd_loop = open_ppp_loopback(); - - syslog(LOG_INFO, "Using interface ppp%d", ifunit); - slprintf(ifname, sizeof(ifname), "ppp%d", ifunit); - script_setenv("IFNAME", ifname); - - create_pidfile(); /* write pid to file */ + set_ifunit(1); /* * Configure the interface and mark it up, etc. @@ -766,7 +711,7 @@ main(argc, argv) } slprintf(numbuf, sizeof(numbuf), "%d", baud_rate); - script_setenv("SPEED", numbuf); + script_setenv("SPEED", numbuf, 0); /* run welcome script, if any */ if (welcomer && welcomer[0]) { @@ -781,14 +726,8 @@ main(argc, argv) goto disconnect; } - if (!demand && ifunit >= 0) { - - info("Using interface ppp%d", ifunit); - slprintf(ifname, sizeof(ifname), "ppp%d", ifunit); - script_setenv("IFNAME", ifname); - - create_pidfile(); /* write pid to file */ - } + if (!demand && ifunit >= 0) + set_ifunit(1); /* * Start opening the connection and wait for @@ -973,6 +912,105 @@ main(argc, argv) return 0; } +/* + * setup_signals - initialize signal handling. + */ +static void +setup_signals() +{ + struct sigaction sa; + sigset_t mask; + + /* + * Compute mask of all interesting signals and install signal handlers + * for each. Only one signal handler may be active at a time. Therefore, + * all other signals should be masked when any handler is executing. + */ + sigemptyset(&mask); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGUSR2); + +#define SIGNAL(s, handler) do { \ + sa.sa_handler = handler; \ + if (sigaction(s, &sa, NULL) < 0) \ + fatal("Couldn't establish signal handler (%d): %m", s); \ + } while (0) + + sa.sa_mask = mask; + sa.sa_flags = 0; + SIGNAL(SIGHUP, hup); /* Hangup */ + SIGNAL(SIGINT, term); /* Interrupt */ + SIGNAL(SIGTERM, term); /* Terminate */ + SIGNAL(SIGCHLD, chld); + + SIGNAL(SIGUSR1, toggle_debug); /* Toggle debug flag */ + SIGNAL(SIGUSR2, open_ccp); /* Reopen CCP */ + + /* + * Install a handler for other signals which would otherwise + * cause pppd to exit without cleaning up. + */ + SIGNAL(SIGABRT, bad_signal); + SIGNAL(SIGALRM, bad_signal); + SIGNAL(SIGFPE, bad_signal); + SIGNAL(SIGILL, bad_signal); + SIGNAL(SIGPIPE, bad_signal); + SIGNAL(SIGQUIT, bad_signal); + SIGNAL(SIGSEGV, bad_signal); +#ifdef SIGBUS + SIGNAL(SIGBUS, bad_signal); +#endif +#ifdef SIGEMT + SIGNAL(SIGEMT, bad_signal); +#endif +#ifdef SIGPOLL + SIGNAL(SIGPOLL, bad_signal); +#endif +#ifdef SIGPROF + SIGNAL(SIGPROF, bad_signal); +#endif +#ifdef SIGSYS + SIGNAL(SIGSYS, bad_signal); +#endif +#ifdef SIGTRAP + SIGNAL(SIGTRAP, bad_signal); +#endif +#ifdef SIGVTALRM + SIGNAL(SIGVTALRM, bad_signal); +#endif +#ifdef SIGXCPU + SIGNAL(SIGXCPU, bad_signal); +#endif +#ifdef SIGXFSZ + SIGNAL(SIGXFSZ, bad_signal); +#endif + + /* + * Apparently we can get a SIGPIPE when we call syslog, if + * syslogd has died and been restarted. Ignoring it seems + * be sufficient. + */ + signal(SIGPIPE, SIG_IGN); +} + +/* + * set_ifunit - do things we need to do once we know which ppp + * unit we are using. + */ +void +set_ifunit(iskey) + int iskey; +{ + info("Using interface ppp%d", ifunit); + slprintf(ifname, sizeof(ifname), "ppp%d", ifunit); + script_setenv("IFNAME", ifname, iskey); + create_pidfile(); /* write pid to file */ + create_linkpidfile(); +} + /* * detach - detach us from the controlling terminal. */ @@ -1040,9 +1078,7 @@ create_pidfile() pidfilename[0] = 0; } slprintf(numbuf, sizeof(numbuf), "%d", getpid()); - script_setenv("PPPD_PID", numbuf); - if (linkpidfile[0]) - create_linkpidfile(); + script_setenv("PPPD_PID", numbuf, 1); } static void @@ -1056,14 +1092,14 @@ create_linkpidfile() _PATH_VARRUN, linkname); if ((pidfile = fopen(linkpidfile, "w")) != NULL) { fprintf(pidfile, "%d\n", getpid()); - if (pidfilename[0]) + if (ifname[0]) fprintf(pidfile, "%s\n", ifname); (void) fclose(pidfile); } else { error("Failed to create pid file %s: %m", linkpidfile); linkpidfile[0] = 0; } - script_setenv("LINKNAME", linkname); + script_setenv("LINKNAME", linkname, 1); } /* @@ -1290,6 +1326,14 @@ cleanup() if (locked) unlock(); + + if (pppdb != NULL) { + TDB_DATA key; + + key.dptr = db_key; + key.dsize = strlen(db_key); + tdb_delete(pppdb, key); + } } /* @@ -1338,11 +1382,11 @@ update_link_stats(u) link_stats_valid = 1; slprintf(numbuf, sizeof(numbuf), "%d", link_connect_time); - script_setenv("CONNECT_TIME", numbuf); + script_setenv("CONNECT_TIME", numbuf, 0); slprintf(numbuf, sizeof(numbuf), "%d", link_stats.bytes_out); - script_setenv("BYTES_SENT", numbuf); + script_setenv("BYTES_SENT", numbuf, 0); slprintf(numbuf, sizeof(numbuf), "%d", link_stats.bytes_in); - script_setenv("BYTES_RCVD", numbuf); + script_setenv("BYTES_RCVD", numbuf, 0); } @@ -1881,10 +1925,12 @@ novm(msg) * for scripts that we run (e.g. ip-up, auth-up, etc.) */ void -script_setenv(var, value) +script_setenv(var, value, iskey) char *var, *value; + int iskey; { - size_t vl = strlen(var) + strlen(value) + 2; + size_t varl = strlen(var); + size_t vl = varl + strlen(value) + 2; int i; char *p, *newstring; @@ -1896,7 +1942,11 @@ script_setenv(var, value) /* check if this variable is already set */ if (script_env != 0) { for (i = 0; (p = script_env[i]) != 0; ++i) { - if (strncmp(p, var, vl) == 0 && p[vl] == '=') { + if (strncmp(p, var, varl) == 0 && p[varl] == '=') { + if (iskey && pppdb != NULL) { + delete_db_key(p); + add_db_key(newstring); + } free(p); script_env[i] = newstring; return; @@ -1924,6 +1974,12 @@ script_setenv(var, value) script_env[i] = newstring; script_env[i+1] = 0; + + if (pppdb != NULL) { + if (iskey) + add_db_key(newstring); + update_db_entry(); + } } /* @@ -1948,6 +2004,70 @@ script_unsetenv(var) break; } } + if (pppdb != NULL) + update_db_entry(); +} + +/* + * update_db_entry - update our entry in the database. + */ +static void +update_db_entry() +{ + TDB_DATA key, dbuf; + int vlen, i; + char *p, *q, *vbuf; + + if (script_env == NULL) + return; + vlen = 0; + for (i = 0; (p = script_env[i]) != 0; ++i) + vlen += strlen(p) + 1; + vbuf = malloc(vlen); + if (vbuf == 0) + novm("database entry"); + q = vbuf; + for (i = 0; (p = script_env[i]) != 0; ++i) + q += slprintf(q, vbuf + vlen - q, "%s;", p); + + key.dptr = db_key; + key.dsize = strlen(db_key); + dbuf.dptr = vbuf; + dbuf.dsize = vlen; + if (tdb_store(pppdb, key, dbuf, TDB_REPLACE)) + error("tdb_store failed: %s", tdb_error(pppdb)); + +} + +/* + * add_db_key - add a key that we can use to look up our database entry. + */ +static void +add_db_key(str) + const char *str; +{ + TDB_DATA key, dbuf; + + key.dptr = (char *) str; + key.dsize = strlen(str); + dbuf.dptr = db_key; + dbuf.dsize = strlen(db_key); + if (tdb_store(pppdb, key, dbuf, TDB_REPLACE)) + error("tdb_store key failed: %s", tdb_error(pppdb)); +} + +/* + * delete_db_key - delete a key for looking up our database entry. + */ +static void +delete_db_key(str) + const char *str; +{ + TDB_DATA key; + + key.dptr = (char *) str; + key.dsize = strlen(str); + tdb_delete(pppdb, key); } /* diff --git a/pppd/options.c b/pppd/options.c index 7ee1e07..07dd6d0 100644 --- a/pppd/options.c +++ b/pppd/options.c @@ -17,7 +17,7 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#define RCSID "$Id: options.c,v 1.71 2000/03/27 06:03:03 paulus Exp $" +#define RCSID "$Id: options.c,v 1.72 2000/04/04 07:06:52 paulus Exp $" #include #include @@ -105,6 +105,7 @@ int connect_delay = 1000; /* wait this many ms after connect script */ int max_data_rate; /* max bytes/sec through charshunt */ int req_unit = -1; /* requested interface unit */ bool multilink = 0; /* Enable multilink operation */ +char *bundle_name = NULL; /* bundle name for multilink */ extern option_t auth_options[]; extern struct stat devstat; @@ -289,6 +290,8 @@ option_t general_options[] = { "Enable multilink operation", 1 }, { "nomp", o_bool, &multilink, "Disable multilink operation", 0 }, + { "bundle", o_string, &bundle_name, + "Bundle name for multilink" }, #endif /* HAVE_MULTILINK */ #ifdef PLUGIN { "plugin", o_special, loadplugin, @@ -1486,39 +1489,20 @@ static int setnetmask(argv) char **argv; { - u_int32_t mask, b; - int n, ok; - char *p, *endp; + u_int32_t mask; + int n; + char *p; /* * Unfortunately, if we use inet_addr, we can't tell whether * a result of all 1s is an error or a valid 255.255.255.255. */ p = *argv; - ok = 0; - mask = 0; - for (n = 3;; --n) { - b = strtoul(p, &endp, 0); - if (endp == p) - break; - if (b > 255) { - if (n == 3) { - /* accept e.g. 0xffffff00 */ - p = endp; - mask = b; - } - break; - } - mask |= b << (n * 8); - p = endp; - if (*p != '.' || n == 0) - break; - ++p; - } + n = parse_dotted_ip(p, &mask); mask = htonl(mask); - if (*p != 0 || (netmask & ~mask) != 0) { + if (n == 0 || p[n] != 0 || (netmask & ~mask) != 0) { option_error("invalid netmask value '%s'", *argv); return 0; } @@ -1527,6 +1511,39 @@ setnetmask(argv) return (1); } +int +parse_dotted_ip(p, vp) + char *p; + u_int32_t *vp; +{ + int n; + u_int32_t v, b; + char *endp, *p0 = p; + + v = 0; + for (n = 3;; --n) { + b = strtoul(p, &endp, 0); + if (endp == p) + return 0; + if (b > 255) { + if (n < 3) + return 0; + /* accept e.g. 0xffffff00 */ + *vp = b; + return endp - p0; + } + v |= b << (n * 8); + p = endp; + if (n == 0) + break; + if (*p != '.') + return 0; + ++p; + } + *vp = v; + return p - p0; +} + static int setxonxoff(argv) char **argv; diff --git a/pppd/patchlevel.h b/pppd/patchlevel.h index eed91c1..72b601b 100644 --- a/pppd/patchlevel.h +++ b/pppd/patchlevel.h @@ -1,6 +1,6 @@ -/* $Id: patchlevel.h,v 1.45 1999/12/23 01:41:08 paulus Exp $ */ -#define PATCHLEVEL 11 +/* $Id: patchlevel.h,v 1.46 2000/04/04 07:06:52 paulus Exp $ */ +#define PATCHLEVEL 0 -#define VERSION "2.3" -#define IMPLEMENTATION "" -#define DATE "23 December 1999" +#define VERSION "2.4" +#define IMPLEMENTATION "b1" +#define DATE "30 March 2000" diff --git a/pppd/pathnames.h b/pppd/pathnames.h index 4aaba6e..416e9ed 100644 --- a/pppd/pathnames.h +++ b/pppd/pathnames.h @@ -1,18 +1,18 @@ /* * define path names * - * $Id: pathnames.h,v 1.12 1999/11/19 09:46:08 masputra Exp $ + * $Id: pathnames.h,v 1.13 2000/04/04 07:06:52 paulus Exp $ */ #ifdef HAVE_PATHS_H #include -#else +#else /* HAVE_PATHS_H */ #ifndef _PATH_VARRUN #define _PATH_VARRUN "/etc/ppp/" #endif #define _PATH_DEVNULL "/dev/null" -#endif +#endif /* HAVE_PATHS_H */ #ifndef _ROOT_PATH #define _ROOT_PATH @@ -41,3 +41,13 @@ #define _PATH_IPXUP _ROOT_PATH "/etc/ppp/ipx-up" #define _PATH_IPXDOWN _ROOT_PATH "/etc/ppp/ipx-down" #endif /* IPX_CHANGE */ + +#ifdef __STDC__ +#define _PATH_PPPDB _ROOT_PATH _PATH_VARRUN "pppd.tdb" +#else /* __STDC__ */ +#ifdef HAVE_PATHS_H +#define _PATH_PPPDB "/var/run/pppd.tdb" +#else +#define _PATH_PPPDB "/etc/ppp/pppd.tdb" +#endif +#endif /* __STDC__ */ diff --git a/pppd/pppd.h b/pppd/pppd.h index 538b8cd..465a63e 100644 --- a/pppd/pppd.h +++ b/pppd/pppd.h @@ -16,7 +16,7 @@ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * - * $Id: pppd.h,v 1.51 2000/03/27 06:03:06 paulus Exp $ + * $Id: pppd.h,v 1.52 2000/04/04 07:06:53 paulus Exp $ */ /* @@ -136,6 +136,22 @@ struct wordlist { char *word; }; +/* An endpoint discriminator, used with multilink. */ +#define MAX_ENDP_LEN 20 /* maximum length of discriminator value */ +struct epdisc { + unsigned char class; + unsigned char length; + unsigned char value[MAX_ENDP_LEN]; +}; + +/* values for epdisc.class */ +#define EPD_NULL 0 /* null discriminator, no data */ +#define EPD_LOCAL 1 +#define EPD_IP 2 +#define EPD_MAC 3 +#define EPD_MAGIC 4 +#define EPD_PHONENUM 5 + /* * Global variables. */ @@ -219,6 +235,8 @@ extern int connect_delay; /* Time to delay after connect script */ extern int max_data_rate; /* max bytes/sec through charshunt */ extern int req_unit; /* interface unit number to use */ extern bool multilink; /* enable multilink operation */ +extern bool noendpoint; /* don't send or accept endpt. discrim. */ +extern char *bundle_name; /* bundle name for multilink */ #ifdef PPP_FILTER extern struct bpf_program pass_filter; /* Filter for pkts to pass */ @@ -296,6 +314,7 @@ extern struct protent *protocols[]; */ /* Procedures exported from main.c. */ +void set_ifunit __P((int)); /* set stuff that depends on ifunit */ void detach __P((void)); /* Detach from controlling tty */ void die __P((int)); /* Cleanup and exit */ void quit __P((void)); /* like die(1) */ @@ -309,7 +328,7 @@ pid_t run_program __P((char *prog, char **args, int must_exist, /* Run program prog with args in child */ void reopen_log __P((void)); /* (re)open the connection to syslog */ void update_link_stats __P((int)); /* Get stats at link termination */ -void script_setenv __P((char *, char *)); /* set script env var */ +void script_setenv __P((char *, char *, int)); /* set script env var */ void script_unsetenv __P((char *)); /* unset script env var */ void new_phase __P((int)); /* signal start of new phase */ @@ -367,6 +386,12 @@ void demand_rexmit __P((int)); /* retransmit saved frames for an NP */ int loop_chars __P((unsigned char *, int)); /* process chars from loopback */ int loop_frame __P((unsigned char *, int)); /* should we bring link up? */ +/* Procedures exported from multilink.c */ +void mp_check_options __P((void)); /* Check multilink-related options */ +int mp_join_bundle __P((void)); /* join our link to an appropriate bundle */ +char *epdisc_to_str __P((struct epdisc *)); /* string from endpoint discrim. */ +int str_to_epdisc __P((struct epdisc *, char *)); /* endpt disc. from str */ + /* Procedures exported from sys-*.c */ void sys_init __P((void)); /* Do system-dependent initialization */ void sys_cleanup __P((void)); /* Restore system state before exiting */ @@ -378,6 +403,8 @@ int open_ppp_loopback __P((void)); /* Open loopback for demand-dialling */ int establish_ppp __P((int)); /* Turn serial port into a ppp interface */ void restore_loop __P((void)); /* Transfer ppp unit back to loopback */ void disestablish_ppp __P((int)); /* Restore port to normal operation */ +void make_new_bundle __P((int, int, int, int)); /* Create new bundle */ +int bundle_attach __P((int)); /* Attach link to existing bundle */ void clean_check __P((void)); /* Check if line was 8-bit clean */ void set_up_tty __P((int, int)); /* Set up port's speed, parameters, etc. */ void restore_tty __P((int)); /* Restore port's original parameters */ @@ -444,6 +471,8 @@ int set_filters __P((struct bpf_program *pass, struct bpf_program *active)); int sipxfaddr __P((int, unsigned long, unsigned char *)); int cipxfaddr __P((int)); #endif +int get_if_hwaddr __P((u_char *addr, char *name)); +char *get_first_ethernet __P((void)); /* Procedures exported from options.c */ int parse_args __P((int argc, char **argv)); @@ -462,6 +491,7 @@ void option_error __P((char *fmt, ...)); int int_option __P((char *, int *)); /* Simplified number_option for decimal ints */ void add_options __P((option_t *)); /* Add extra options */ +int parse_dotted_ip __P((char *, u_int32_t *)); /* * This structure is used to store information about certain diff --git a/pppd/sys-linux.c b/pppd/sys-linux.c index 1e52e3b..532fb13 100644 --- a/pppd/sys-linux.c +++ b/pppd/sys-linux.c @@ -149,6 +149,7 @@ static char proxy_arp_dev[16]; /* Device for proxy arp entry */ static u_int32_t our_old_addr; /* for detecting address changes */ static int dynaddr_set; /* 1 if ip_dynaddr set */ static int looped; /* 1 if using loop */ +static int link_mtu; /* mtu for the link (not bundle) */ static struct utsname utsname; /* for the kernel version */ static int kernel_version; @@ -176,6 +177,7 @@ static int get_ether_addr (u_int32_t ipaddr, struct sockaddr *hwaddr, static void decode_version (char *buf, int *version, int *mod, int *patch); static int set_kdebugflag(int level); static int ppp_registered(void); +static int make_ppp_unit(void); extern u_char inpacket_buf[]; /* borrowed from main.c */ @@ -418,17 +420,8 @@ int establish_ppp (int tty_fd) /* * Create a new PPP unit. */ - ifunit = req_unit; - x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); - if (x < 0 && req_unit >= 0 && errno == EEXIST) { - warn("Couldn't allocate PPP unit %d as it is already in use"); - ifunit = -1; - x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); - } - if (x < 0) { - error("Couldn't create new ppp unit: %m"); + if (make_ppp_unit() < 0) goto err_close; - } } if (looped) @@ -529,7 +522,7 @@ void disestablish_ppp(int tty_fd) if (new_style_driver) { close(ppp_fd); ppp_fd = -1; - if (!looped && ioctl(ppp_dev_fd, PPPIOCDETACH) < 0) + if (!looped && ifunit >= 0 && ioctl(ppp_dev_fd, PPPIOCDETACH) < 0) error("Couldn't release PPP unit: %m"); if (!multilink) remove_fd(ppp_dev_fd); @@ -537,30 +530,87 @@ void disestablish_ppp(int tty_fd) } /* - * bundle_attach - attach our link to a given PPP unit. + * make_ppp_unit - make a new ppp unit for ppp_dev_fd. + * Assumes new_style_driver. */ -int bundle_attach(int ifnum) +static int make_ppp_unit() { - int mrru = 1500; + int x; - if (!new_style_driver) - return -1; + ifunit = req_unit; + x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); + if (x < 0 && req_unit >= 0 && errno == EEXIST) { + warn("Couldn't allocate PPP unit %d as it is already in use"); + ifunit = -1; + x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); + } + if (x < 0) + error("Couldn't create new ppp unit: %m"); + return x; +} - if (ioctl(ppp_dev_fd, PPPIOCATTACH, &ifnum) < 0) { - error("Couldn't attach to interface unit %d: %m\n", ifnum); - return -1; - } - set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) | SC_MULTILINK); - if (ioctl(ppp_dev_fd, PPPIOCSMRRU, &mrru) < 0) - error("Couldn't set interface MRRU: %m"); +/* + * make_new_bundle - create a new PPP unit (i.e. a bundle) + * and connect our channel to it. This should only get called + * if `multilink' was set at the time establish_ppp was called. + */ +void make_new_bundle(int mrru, int mtru, int rssn, int tssn) +{ + int flags; + struct ifreq ifr; + + if (looped || !new_style_driver) + return; + dbglog("make_new_bundle(%d,%d,%d,%d)", mrru, mtru, rssn, tssn); + + /* make us a ppp unit */ + if (make_ppp_unit() < 0) + die(1); + + /* set the mrru, mtu and flags */ + if (ioctl(ppp_dev_fd, PPPIOCSMRRU, &mrru) < 0) + error("Couldn't set MRRU: %m"); + flags = get_flags(ppp_dev_fd); + flags &= ~(SC_MP_SHORTSEQ | SC_MP_XSHORTSEQ); + flags |= (rssn? SC_MP_SHORTSEQ: 0) | (tssn? SC_MP_XSHORTSEQ: 0); + + if (mtru > 0 && mtru != link_mtu) { + memset(&ifr, 0, sizeof(ifr)); + slprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "ppp%d", ifunit); + ifr.ifr_mtu = mtru; + if (ioctl(sock_fd, SIOCSIFMTU, &ifr) < 0) + error("Couldn't set interface MTU: %m"); + flags |= SC_MULTILINK; + } - if (ioctl(ppp_fd, PPPIOCCONNECT, &ifnum) < 0) { - error("Couldn't connect to interface unit %d: %m", ifnum); - return -1; - } + set_flags(ppp_dev_fd, flags); - dbglog("bundle_attach succeeded"); - return 0; + /* connect up the channel */ + if (ioctl(ppp_fd, PPPIOCCONNECT, &ifunit) < 0) + fatal("Couldn't attach to PPP unit %d: %m", ifunit); + add_fd(ppp_dev_fd); +} + +/* + * bundle_attach - attach our link to a given PPP unit. + */ +int bundle_attach(int ifnum) +{ + if (!new_style_driver) + return -1; + + if (ioctl(ppp_dev_fd, PPPIOCATTACH, &ifnum) < 0) { + if (errno == ENXIO) + return 0; /* doesn't still exist */ + fatal("Couldn't attach to interface unit %d: %m\n", ifnum); + } + if (ioctl(ppp_fd, PPPIOCCONNECT, &ifnum) < 0) + fatal("Couldn't connect to interface unit %d: %m", ifnum); + set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) | SC_MULTILINK); + + ifunit = ifnum; + dbglog("bundle_attach succeeded"); + return 1; } /******************************************************************** @@ -855,7 +905,7 @@ void output (int unit, unsigned char *p, int len) p += 2; len -= 2; proto = (p[0] << 8) + p[1]; - if (!multilink && proto != PPP_LCP) + if (ifunit >= 0 && !(proto >= 0xc000 || proto == PPP_CCPFRAG)) fd = ppp_dev_fd; } if (write(fd, p, len) < 0) { @@ -926,7 +976,7 @@ int read_packet (unsigned char *buf) if (nr < 0 && errno != EWOULDBLOCK && errno != EIO) error("read: %m"); } - if (nr < 0 && new_style_driver && !multilink) { + if (nr < 0 && new_style_driver && ifunit >= 0) { /* N.B. we read ppp_fd first since LCP packets come in there. */ nr = read(ppp_dev_fd, buf, len); if (nr < 0 && errno != EWOULDBLOCK && errno != EIO) @@ -988,7 +1038,8 @@ void ppp_send_config (int unit,int mtu,u_int32_t asyncmap,int pcomp,int accomp) if (ifunit >= 0 && ioctl(sock_fd, SIOCSIFMTU, (caddr_t) &ifr) < 0) fatal("ioctl(SIOCSIFMTU): %m(%d)", errno); - + link_mtu = mtu; + if (!still_ppp()) return; SYSDEBUG ((LOG_DEBUG, "send_config: asyncmap = %lx\n", asyncmap)); @@ -1600,6 +1651,38 @@ static int get_ether_addr (u_int32_t ipaddr, return 1; } +/* + * get_if_hwaddr - get the hardware address for the specified + * network interface device. + */ +int +get_if_hwaddr(u_char *addr, char *name) +{ + struct ifreq ifreq; + int ret, sock_fd; + + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd < 0) + return 0; + memset(&ifreq.ifr_hwaddr, 0, sizeof(struct sockaddr)); + strlcpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name)); + ret = ioctl(sock_fd, SIOCGIFHWADDR, &ifreq); + close(sock_fd); + if (ret >= 0) + memcpy(addr, ifreq.ifr_hwaddr.sa_data, 6); + return ret; +} + +/* + * get_first_ethernet - return the name of the first ethernet-style + * interface on this system. + */ +char * +get_first_ethernet() +{ + return "eth0"; +} + /******************************************************************** * * Return user specified netmask, modified by any mask we might determine @@ -2355,20 +2438,13 @@ get_pty(master_fdp, slave_fdp, slave_name, uid) int open_ppp_loopback(void) { - int flags, x; + int flags; looped = 1; if (new_style_driver) { /* allocate ourselves a ppp unit */ - ifunit = req_unit; - x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); - if (x < 0 && req_unit >= 0 && errno == EEXIST) { - warn("Couldn't allocate PPP unit %d as it is already in use"); - ifunit = -1; - x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); - } - if (x < 0) - fatal("Couldn't create PPP unit: %m"); + if (make_ppp_unit() < 0) + die(1); set_flags(ppp_dev_fd, SC_LOOP_TRAFFIC); set_kdebugflag(kdebugflag); ppp_fd = -1; diff --git a/pppd/tdb.c b/pppd/tdb.c new file mode 100644 index 0000000..7fd5829 --- /dev/null +++ b/pppd/tdb.c @@ -0,0 +1,1282 @@ +/* + * Database functions + * Copyright (C) Andrew Tridgell 1999 + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms AND provided that this software or + * any derived work is only used as part of the PPP daemon (pppd) + * and related utilities. + * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Note: this software is also available under the Gnu Public License + * version 2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tdb.h" + +#define TDB_VERSION (0x26011967 + 1) +#define TDB_MAGIC (0x26011999U) +#define TDB_FREE_MAGIC (~TDB_MAGIC) +#define TDB_ALIGN 4 +#define MIN_REC_SIZE (2*sizeof(struct list_struct) + TDB_ALIGN) +#define DEFAULT_HASH_SIZE 128 +#define TDB_PAGE_SIZE 0x2000 +#define TDB_LEN_MULTIPLIER 10 +#define FREELIST_TOP (sizeof(struct tdb_header)) + +#define LOCK_SET 1 +#define LOCK_CLEAR 0 + +/* lock offsets */ +#define GLOBAL_LOCK 0 +#define ACTIVE_LOCK 4 +#define LIST_LOCK_BASE 1024 + +#define BUCKET(hash) ((hash) % tdb->header.hash_size) + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +/* the body of the database is made of one list_struct for the free space + plus a separate data list for each hash value */ +struct list_struct { + tdb_len rec_len; /* total byte length of record */ + tdb_off next; /* offset of the next record in the list */ + tdb_len key_len; /* byte length of key */ + tdb_len data_len; /* byte length of data */ + unsigned full_hash; /* the full 32 bit hash of the key */ + unsigned magic; /* try to catch errors */ + /* + the following union is implied + union { + char record[rec_len]; + struct { + char key[key_len]; + char data[data_len]; + } + } + */ +}; + +/* a null data record - useful for error returns */ +static TDB_DATA null_data; + +/* a byte range locking function - return 0 on success + this functions locks/unlocks 1 byte at the specified offset */ +static int tdb_brlock(TDB_CONTEXT *tdb, tdb_off offset, + int set, int rw_type, int lck_type) +{ +#if NOLOCK + return 0; +#else + struct flock fl; + + if (tdb->fd == -1) return 0; /* for in memory tdb */ + + if (tdb->read_only) return -1; + + fl.l_type = set==LOCK_SET?rw_type:F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = offset; + fl.l_len = 1; + fl.l_pid = 0; + + if (fcntl(tdb->fd, lck_type, &fl) != 0) { +#if TDB_DEBUG + if (lck_type == F_SETLKW) { + printf("lock %d failed at %d (%s)\n", + set, offset, strerror(errno)); + } +#endif + tdb->ecode = TDB_ERR_LOCK; + return -1; + } + return 0; +#endif +} + +/* lock a list in the database. list -1 is the alloc list */ +static int tdb_lock(TDB_CONTEXT *tdb, int list) +{ + if (list < -1 || list >= (int)tdb->header.hash_size) { +#if TDB_DEBUG + printf("bad list %d\n", list); +#endif + return -1; + } + if (tdb->locked[list+1] == 0) { + if (tdb_brlock(tdb, LIST_LOCK_BASE + 4*list, LOCK_SET, + F_WRLCK, F_SETLKW) != 0) { + return -1; + } + } + tdb->locked[list+1]++; + return 0; +} + +/* unlock the database. */ +static int tdb_unlock(TDB_CONTEXT *tdb, int list) +{ + if (list < -1 || list >= (int)tdb->header.hash_size) { +#if TDB_DEBUG + printf("bad unlock list %d\n", list); +#endif + return -1; + } + + if (tdb->locked[list+1] == 0) { +#if TDB_DEBUG + printf("not locked %d\n", list); +#endif + tdb->ecode = TDB_ERR_LOCK; + return -1; + } + if (tdb->locked[list+1] == 1) { + if (tdb_brlock(tdb, LIST_LOCK_BASE + 4*list, LOCK_CLEAR, + F_WRLCK, F_SETLKW) != 0) { + return -1; + } + } + tdb->locked[list+1]--; + return 0; +} + +/* the hash algorithm - turn a key into an integer + This is based on the hash agorithm from gdbm */ +static unsigned tdb_hash(TDB_DATA *key) +{ + unsigned value; /* Used to compute the hash value. */ + unsigned i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + value = 0x238F13AF * key->dsize; + for (i=0; i < key->dsize; i++) { + value = (value + (key->dptr[i] << (i*5 % 24))); + } + + value = (1103515243 * value + 12345); + + return value; +} + +/* find the top of the hash chain for an open database */ +static tdb_off tdb_hash_top(TDB_CONTEXT *tdb, unsigned hash) +{ + tdb_off ret; + hash = BUCKET(hash); + ret = FREELIST_TOP + (hash+1)*sizeof(tdb_off); + return ret; +} + + +/* check for an out of bounds access - if it is out of bounds then + see if the database has been expanded by someone else and expand + if necessary */ +static int tdb_oob(TDB_CONTEXT *tdb, tdb_off offset) +{ + struct stat st; + if ((offset <= tdb->map_size) || (tdb->fd == -1)) return 0; + + fstat(tdb->fd, &st); + if (st.st_size <= (ssize_t)offset) { + tdb->ecode = TDB_ERR_IO; + return -1; + } + +#if HAVE_MMAP + if (tdb->map_ptr) { + munmap(tdb->map_ptr, tdb->map_size); + tdb->map_ptr = NULL; + } +#endif + + tdb->map_size = st.st_size; +#if HAVE_MMAP + tdb->map_ptr = (void *)mmap(NULL, tdb->map_size, + tdb->read_only?PROT_READ:PROT_READ|PROT_WRITE, + MAP_SHARED | MAP_FILE, tdb->fd, 0); +#endif + return 0; +} + + +/* write a lump of data at a specified offset */ +static int tdb_write(TDB_CONTEXT *tdb, tdb_off offset, const char *buf, tdb_len len) +{ + if (tdb_oob(tdb, offset + len) != 0) { + /* oops - trying to write beyond the end of the database! */ + return -1; + } + + if (tdb->map_ptr) { + memcpy(offset + (char *)tdb->map_ptr, buf, len); + } else { + if (lseek(tdb->fd, offset, SEEK_SET) != offset || + write(tdb->fd, buf, len) != (ssize_t)len) { + tdb->ecode = TDB_ERR_IO; + return -1; + } + } + return 0; +} + +/* read a lump of data at a specified offset */ +static int tdb_read(TDB_CONTEXT *tdb, tdb_off offset, char *buf, tdb_len len) +{ + if (tdb_oob(tdb, offset + len) != 0) { + /* oops - trying to read beyond the end of the database! */ + return -1; + } + + if (tdb->map_ptr) { + memcpy(buf, offset + (char *)tdb->map_ptr, len); + } else { + if (lseek(tdb->fd, offset, SEEK_SET) != offset || + read(tdb->fd, buf, len) != (ssize_t)len) { + tdb->ecode = TDB_ERR_IO; + return -1; + } + } + return 0; +} + + +/* read a lump of data, allocating the space for it */ +static char *tdb_alloc_read(TDB_CONTEXT *tdb, tdb_off offset, tdb_len len) +{ + char *buf; + + buf = (char *)malloc(len); + + if (!buf) { + tdb->ecode = TDB_ERR_OOM; + return NULL; + } + + if (tdb_read(tdb, offset, buf, len) == -1) { + free(buf); + return NULL; + } + + return buf; +} + +/* convenience routine for writing a record */ +static int rec_write(TDB_CONTEXT *tdb, tdb_off offset, struct list_struct *rec) +{ + return tdb_write(tdb, offset, (char *)rec, sizeof(*rec)); +} + +/* convenience routine for writing a tdb_off */ +static int ofs_write(TDB_CONTEXT *tdb, tdb_off offset, tdb_off *d) +{ + return tdb_write(tdb, offset, (char *)d, sizeof(*d)); +} + +/* read a tdb_off from the store */ +static int ofs_read(TDB_CONTEXT *tdb, tdb_off offset, tdb_off *d) +{ + return tdb_read(tdb, offset, (char *)d, sizeof(*d)); +} + +/* read a record and check for simple errors */ +static int rec_read(TDB_CONTEXT *tdb, tdb_off offset, struct list_struct *rec) +{ + if (tdb_read(tdb, offset, (char *)rec, sizeof(*rec)) == -1) return -1; + if (rec->magic != TDB_MAGIC) { +#if TDB_DEBUG + printf("bad magic 0x%08x at offset %d\n", + rec->magic, offset); +#endif + tdb->ecode = TDB_ERR_CORRUPT; + return -1; + } + if (tdb_oob(tdb, rec->next) != 0) { + return -1; + } + return 0; +} + +/* expand the database at least length bytes by expanding the + underlying file and doing the mmap again if necessary */ +static int tdb_expand(TDB_CONTEXT *tdb, tdb_off length) +{ + struct list_struct rec; + tdb_off offset, ptr; + char b = 0; + + tdb_lock(tdb,-1); + + /* make sure we know about any previous expansions by another + process */ + tdb_oob(tdb,tdb->map_size + 1); + + /* always make room for at least 10 more records */ + length *= TDB_LEN_MULTIPLIER; + + /* and round the database up to a multiple of TDB_PAGE_SIZE */ + length = ((tdb->map_size + length + TDB_PAGE_SIZE) & ~(TDB_PAGE_SIZE - 1)) - tdb->map_size; + + /* expand the file itself */ + if (tdb->fd != -1) { + lseek(tdb->fd, tdb->map_size + length - 1, SEEK_SET); + if (write(tdb->fd, &b, 1) != 1) goto fail; + } + + /* form a new freelist record */ + offset = FREELIST_TOP; + rec.rec_len = length - sizeof(rec); + rec.magic = TDB_FREE_MAGIC; + if (ofs_read(tdb, offset, &rec.next) == -1) { + goto fail; + } + +#if HAVE_MMAP + if (tdb->fd != -1 && tdb->map_ptr) { + munmap(tdb->map_ptr, tdb->map_size); + tdb->map_ptr = NULL; + } +#endif + + tdb->map_size += length; + + if (tdb->fd == -1) { + tdb->map_ptr = realloc(tdb->map_ptr, tdb->map_size); + } + + /* write it out */ + if (rec_write(tdb, tdb->map_size - length, &rec) == -1) { + goto fail; + } + + /* link it into the free list */ + ptr = tdb->map_size - length; + if (ofs_write(tdb, offset, &ptr) == -1) goto fail; + +#if HAVE_MMAP + if (tdb->fd != -1) { + tdb->map_ptr = (void *)mmap(NULL, tdb->map_size, + PROT_READ|PROT_WRITE, + MAP_SHARED | MAP_FILE, tdb->fd, 0); + } +#endif + + tdb_unlock(tdb, -1); + return 0; + + fail: + tdb_unlock(tdb,-1); + return -1; +} + +/* allocate some space from the free list. The offset returned points + to a unconnected list_struct within the database with room for at + least length bytes of total data + + 0 is returned if the space could not be allocated + */ +static tdb_off tdb_allocate(TDB_CONTEXT *tdb, tdb_len length) +{ + tdb_off offset, rec_ptr, last_ptr; + struct list_struct rec, lastrec, newrec; + + tdb_lock(tdb, -1); + + again: + last_ptr = 0; + offset = FREELIST_TOP; + + /* read in the freelist top */ + if (ofs_read(tdb, offset, &rec_ptr) == -1) { + goto fail; + } + + /* keep looking until we find a freelist record that is big + enough */ + while (rec_ptr) { + if (tdb_read(tdb, rec_ptr, (char *)&rec, sizeof(rec)) == -1) { + goto fail; + } + + if (rec.magic != TDB_FREE_MAGIC) { +#if TDB_DEBUG + printf("bad magic 0x%08x in free list\n", rec.magic); +#endif + goto fail; + } + + if (rec.rec_len >= length) { + /* found it - now possibly split it up */ + if (rec.rec_len > length + MIN_REC_SIZE) { + length = (length + TDB_ALIGN) & ~(TDB_ALIGN-1); + + newrec.rec_len = rec.rec_len - (sizeof(rec) + length); + newrec.next = rec.next; + newrec.magic = TDB_FREE_MAGIC; + + rec.rec_len = length; + rec.next = rec_ptr + sizeof(rec) + rec.rec_len; + + if (rec_write(tdb, rec.next, &newrec) == -1) { + goto fail; + } + + if (rec_write(tdb, rec_ptr, &rec) == -1) { + goto fail; + } + } + + /* remove it from the list */ + if (last_ptr == 0) { + offset = FREELIST_TOP; + + if (ofs_write(tdb, offset, &rec.next) == -1) { + goto fail; + } + } else { + lastrec.next = rec.next; + if (rec_write(tdb, last_ptr, &lastrec) == -1) { + goto fail; + } + } + + /* all done - return the new record offset */ + tdb_unlock(tdb, -1); + return rec_ptr; + } + + /* move to the next record */ + lastrec = rec; + last_ptr = rec_ptr; + rec_ptr = rec.next; + } + + /* we didn't find enough space. See if we can expand the + database and if we can then try again */ + if (tdb_expand(tdb, length + sizeof(rec)) == 0) goto again; + + fail: +#if TDB_DEBUG + printf("tdb_allocate failed for size %u\n", length); +#endif + tdb_unlock(tdb, -1); + return 0; +} + +/* initialise a new database with a specified hash size */ +static int tdb_new_database(TDB_CONTEXT *tdb, int hash_size) +{ + struct tdb_header header; + tdb_off offset; + int i, size = 0; + tdb_off buf[16]; + + /* create the header */ + memset(&header, 0, sizeof(header)); + memcpy(header.magic_food, TDB_MAGIC_FOOD, strlen(TDB_MAGIC_FOOD)+1); + header.version = TDB_VERSION; + header.hash_size = hash_size; + lseek(tdb->fd, 0, SEEK_SET); + ftruncate(tdb->fd, 0); + + if (tdb->fd != -1 && write(tdb->fd, &header, sizeof(header)) != + sizeof(header)) { + tdb->ecode = TDB_ERR_IO; + return -1; + } else size += sizeof(header); + + /* the freelist and hash pointers */ + offset = 0; + memset(buf, 0, sizeof(buf)); + + for (i=0;(hash_size+1)-i >= 16; i += 16) { + if (tdb->fd != -1 && write(tdb->fd, buf, sizeof(buf)) != + sizeof(buf)) { + tdb->ecode = TDB_ERR_IO; + return -1; + } else size += sizeof(buf); + } + + for (;ifd != -1 && write(tdb->fd, buf, sizeof(tdb_off)) != + sizeof(tdb_off)) { + tdb->ecode = TDB_ERR_IO; + return -1; + } else size += sizeof(tdb_off); + } + + if (tdb->fd == -1) { + tdb->map_ptr = calloc(size, 1); + tdb->map_size = size; + if (tdb->map_ptr == NULL) { + tdb->ecode = TDB_ERR_IO; + return -1; + } + memcpy(&tdb->header, &header, sizeof(header)); + } + +#if TDB_DEBUG + printf("initialised database of hash_size %u\n", + hash_size); +#endif + return 0; +} + +/* Returns 0 on fail. On success, return offset of record, and fills + in rec */ +static tdb_off tdb_find(TDB_CONTEXT *tdb, TDB_DATA key, unsigned int hash, + struct list_struct *rec) +{ + tdb_off offset, rec_ptr; + + /* find the top of the hash chain */ + offset = tdb_hash_top(tdb, hash); + + /* read in the hash top */ + if (ofs_read(tdb, offset, &rec_ptr) == -1) + return 0; + + /* keep looking until we find the right record */ + while (rec_ptr) { + if (rec_read(tdb, rec_ptr, rec) == -1) + return 0; + + if (hash == rec->full_hash && key.dsize == rec->key_len) { + char *k; + /* a very likely hit - read the key */ + k = tdb_alloc_read(tdb, rec_ptr + sizeof(*rec), + rec->key_len); + + if (!k) + return 0; + + if (memcmp(key.dptr, k, key.dsize) == 0) { + free(k); + return rec_ptr; + } + free(k); + } + + /* move to the next record */ + rec_ptr = rec->next; + } + return 0; +} + +/* + return an error string for the last tdb error +*/ +char *tdb_error(TDB_CONTEXT *tdb) +{ + int i; + static struct { + enum TDB_ERROR ecode; + char *estring; + } emap[] = { + {TDB_SUCCESS, "Success"}, + {TDB_ERR_CORRUPT, "Corrupt database"}, + {TDB_ERR_IO, "IO Error"}, + {TDB_ERR_LOCK, "Locking error"}, + {TDB_ERR_OOM, "Out of memory"}, + {TDB_ERR_EXISTS, "Record exists"}, + {-1, NULL}}; + if (tdb != NULL) { + for (i=0;emap[i].estring;i++) { + if (tdb->ecode == emap[i].ecode) return emap[i].estring; + } + } else { + return "Invalid tdb context"; + } + return "Invalid error code"; +} + + +/* update an entry in place - this only works if the new data size + is <= the old data size and the key exists. + on failure return -1 +*/ +int tdb_update(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf) +{ + unsigned hash; + struct list_struct rec; + tdb_off rec_ptr; + int ret = -1; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_update() called with null context\n"); +#endif + return -1; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + + tdb_lock(tdb, BUCKET(hash)); + rec_ptr = tdb_find(tdb, key, hash, &rec); + + if (!rec_ptr) + goto out; + + /* must be long enough */ + if (rec.rec_len < key.dsize + dbuf.dsize) + goto out; + + if (tdb_write(tdb, rec_ptr + sizeof(rec) + rec.key_len, + dbuf.dptr, dbuf.dsize) == -1) + goto out; + + if (dbuf.dsize != rec.data_len) { + /* update size */ + rec.data_len = dbuf.dsize; + ret = rec_write(tdb, rec_ptr, &rec); + } else + ret = 0; + + out: + tdb_unlock(tdb, BUCKET(hash)); + return ret; +} + +/* find an entry in the database given a key */ +TDB_DATA tdb_fetch(TDB_CONTEXT *tdb, TDB_DATA key) +{ + unsigned hash; + tdb_off rec_ptr; + struct list_struct rec; + TDB_DATA ret = null_data; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_fetch() called with null context\n"); +#endif + return null_data; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + + tdb_lock(tdb, BUCKET(hash)); + rec_ptr = tdb_find(tdb, key, hash, &rec); + + if (rec_ptr) { + ret.dptr = tdb_alloc_read(tdb, + rec_ptr + sizeof(rec) + rec.key_len, + rec.data_len); + ret.dsize = rec.data_len; + } + + tdb_unlock(tdb, BUCKET(hash)); + return ret; +} + +/* check if an entry in the database exists + + note that 1 is returned if the key is found and 0 is returned if not found + this doesn't match the conventions in the rest of this module, but is + compatible with gdbm +*/ +int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key) +{ + unsigned hash; + tdb_off rec_ptr; + struct list_struct rec; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_exists() called with null context\n"); +#endif + return 0; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + + tdb_lock(tdb, BUCKET(hash)); + rec_ptr = tdb_find(tdb, key, hash, &rec); + tdb_unlock(tdb, BUCKET(hash)); + + return rec_ptr != 0; +} + +/* traverse the entire database - calling fn(tdb, key, data) on each element. + return -1 on error or the record count traversed + if fn is NULL then it is not called + a non-zero return value from fn() indicates that the traversal should stop + */ +int tdb_traverse(TDB_CONTEXT *tdb, int (*fn)(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void* state), void* state) +{ + int count = 0; + unsigned h; + tdb_off offset, rec_ptr; + struct list_struct rec; + char *data; + TDB_DATA key, dbuf; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_traverse() called with null context\n"); +#endif + return -1; + } + + /* loop over all hash chains */ + for (h = 0; h < tdb->header.hash_size; h++) { + tdb_lock(tdb, BUCKET(h)); + + /* read in the hash top */ + offset = tdb_hash_top(tdb, h); + if (ofs_read(tdb, offset, &rec_ptr) == -1) { + goto fail; + } + + /* traverse all records for this hash */ + while (rec_ptr) { + if (rec_read(tdb, rec_ptr, &rec) == -1) { + goto fail; + } + + /* now read the full record */ + data = tdb_alloc_read(tdb, rec_ptr + sizeof(rec), + rec.key_len + rec.data_len); + if (!data) { + goto fail; + } + + key.dptr = data; + key.dsize = rec.key_len; + dbuf.dptr = data + rec.key_len; + dbuf.dsize = rec.data_len; + count++; + + if (fn && fn(tdb, key, dbuf, state) != 0) { + /* they want us to stop traversing */ + free(data); + tdb_unlock(tdb, BUCKET(h)); + return count; + } + + /* a miss - drat */ + free(data); + + /* move to the next record */ + rec_ptr = rec.next; + } + tdb_unlock(tdb, BUCKET(h)); + } + + /* return the number traversed */ + return count; + + fail: + tdb_unlock(tdb, BUCKET(h)); + return -1; +} + + +/* find the first entry in the database and return its key */ +TDB_DATA tdb_firstkey(TDB_CONTEXT *tdb) +{ + tdb_off offset, rec_ptr; + struct list_struct rec; + unsigned hash; + TDB_DATA ret; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_firstkey() called with null context\n"); +#endif + return null_data; + } + + /* look for a non-empty hash chain */ + for (hash = 0, rec_ptr = 0; + hash < tdb->header.hash_size; + hash++) { + /* find the top of the hash chain */ + offset = tdb_hash_top(tdb, hash); + + tdb_lock(tdb, BUCKET(hash)); + + /* read in the hash top */ + if (ofs_read(tdb, offset, &rec_ptr) == -1) { + goto fail; + } + + if (rec_ptr) break; + + tdb_unlock(tdb, BUCKET(hash)); + } + + if (rec_ptr == 0) return null_data; + + /* we've found a non-empty chain, now read the record */ + if (rec_read(tdb, rec_ptr, &rec) == -1) { + goto fail; + } + + /* allocate and read the key space */ + ret.dptr = tdb_alloc_read(tdb, rec_ptr + sizeof(rec), rec.key_len); + ret.dsize = rec.key_len; + tdb_unlock(tdb, BUCKET(hash)); + return ret; + + fail: + tdb_unlock(tdb, BUCKET(hash)); + return null_data; +} + +/* find the next entry in the database, returning its key */ +TDB_DATA tdb_nextkey(TDB_CONTEXT *tdb, TDB_DATA key) +{ + unsigned hash, hbucket; + tdb_off rec_ptr, offset; + struct list_struct rec; + TDB_DATA ret; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_nextkey() called with null context\n"); +#endif + return null_data; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + hbucket = BUCKET(hash); + + tdb_lock(tdb, hbucket); + rec_ptr = tdb_find(tdb, key, hash, &rec); + if (rec_ptr) { + /* we want the next record after this one */ + rec_ptr = rec.next; + } + + /* not found or last in hash: look for next non-empty hash chain */ + while (rec_ptr == 0) { + tdb_unlock(tdb, hbucket); + + if (++hbucket >= tdb->header.hash_size - 1) + return null_data; + + offset = tdb_hash_top(tdb, hbucket); + tdb_lock(tdb, hbucket); + /* read in the hash top */ + if (ofs_read(tdb, offset, &rec_ptr) == -1) { + tdb_unlock(tdb, hbucket); + return null_data; + } + } + + /* Read the record. */ + if (rec_read(tdb, rec_ptr, &rec) == -1) { + tdb_unlock(tdb, hbucket); + return null_data; + } + /* allocate and read the key */ + ret.dptr = tdb_alloc_read(tdb, rec_ptr + sizeof(rec), rec.key_len); + ret.dsize = rec.key_len; + tdb_unlock(tdb, hbucket); + + return ret; +} + +/* delete an entry in the database given a key */ +int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key) +{ + unsigned hash; + tdb_off offset, rec_ptr, last_ptr; + struct list_struct rec, lastrec; + char *data = NULL; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_delete() called with null context\n"); +#endif + return -1; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + + tdb_lock(tdb, BUCKET(hash)); + + /* find the top of the hash chain */ + offset = tdb_hash_top(tdb, hash); + + /* read in the hash top */ + if (ofs_read(tdb, offset, &rec_ptr) == -1) { + goto fail; + } + + last_ptr = 0; + + /* keep looking until we find the right record */ + while (rec_ptr) { + if (rec_read(tdb, rec_ptr, &rec) == -1) { + goto fail; + } + + if (hash == rec.full_hash && key.dsize == rec.key_len) { + /* a very likely hit - read the record and full key */ + data = tdb_alloc_read(tdb, rec_ptr + sizeof(rec), + rec.key_len); + if (!data) { + goto fail; + } + + if (memcmp(key.dptr, data, key.dsize) == 0) { + /* a definite match - delete it */ + if (last_ptr == 0) { + offset = tdb_hash_top(tdb, hash); + if (ofs_write(tdb, offset, &rec.next) == -1) { + goto fail; + } + } else { + lastrec.next = rec.next; + if (rec_write(tdb, last_ptr, &lastrec) == -1) { + goto fail; + } + } + tdb_unlock(tdb, BUCKET(hash)); + tdb_lock(tdb, -1); + /* and recover the space */ + offset = FREELIST_TOP; + if (ofs_read(tdb, offset, &rec.next) == -1) { + goto fail2; + } + rec.magic = TDB_FREE_MAGIC; + if (rec_write(tdb, rec_ptr, &rec) == -1) { + goto fail2; + } + if (ofs_write(tdb, offset, &rec_ptr) == -1) { + goto fail2; + } + + /* yipee - all done */ + free(data); + tdb_unlock(tdb, -1); + return 0; + } + + /* a miss - drat */ + free(data); + data = NULL; + } + + /* move to the next record */ + last_ptr = rec_ptr; + lastrec = rec; + rec_ptr = rec.next; + } + + fail: + if (data) free(data); + tdb_unlock(tdb, BUCKET(hash)); + return -1; + + fail2: + if (data) free(data); + tdb_unlock(tdb, -1); + return -1; +} + + +/* store an element in the database, replacing any existing element + with the same key + + return 0 on success, -1 on failure +*/ +int tdb_store(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag) +{ + struct list_struct rec; + unsigned hash; + tdb_off rec_ptr, offset; + char *p = NULL; + + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_store() called with null context\n"); +#endif + return -1; + } + + /* find which hash bucket it is in */ + hash = tdb_hash(&key); + + /* check for it existing */ + if (flag == TDB_INSERT && tdb_exists(tdb, key)) { + tdb->ecode = TDB_ERR_EXISTS; + return -1; + } + + /* first try in-place update */ + if (flag != TDB_INSERT && tdb_update(tdb, key, dbuf) == 0) { + return 0; + } + + rec_ptr = tdb_allocate(tdb, key.dsize + dbuf.dsize); + if (rec_ptr == 0) { + return -1; + } + + tdb_lock(tdb, BUCKET(hash)); + + /* delete any existing record - if it doesn't exist we don't care */ + if (flag != TDB_INSERT) { + tdb_delete(tdb, key); + } + + /* read the newly created record */ + if (tdb_read(tdb, rec_ptr, (char *)&rec, sizeof(rec)) == -1) { + goto fail; + } + + if (rec.magic != TDB_FREE_MAGIC) goto fail; + + /* find the top of the hash chain */ + offset = tdb_hash_top(tdb, hash); + + /* read in the hash top diretcly into our next pointer */ + if (ofs_read(tdb, offset, &rec.next) == -1) { + goto fail; + } + + rec.key_len = key.dsize; + rec.data_len = dbuf.dsize; + rec.full_hash = hash; + rec.magic = TDB_MAGIC; + + p = (char *)malloc(sizeof(rec) + key.dsize + dbuf.dsize); + if (!p) { + tdb->ecode = TDB_ERR_OOM; + goto fail; + } + + memcpy(p, &rec, sizeof(rec)); + memcpy(p+sizeof(rec), key.dptr, key.dsize); + memcpy(p+sizeof(rec)+key.dsize, dbuf.dptr, dbuf.dsize); + + if (tdb_write(tdb, rec_ptr, p, sizeof(rec)+key.dsize+dbuf.dsize) == -1) + goto fail; + + free(p); + p = NULL; + + /* and point the top of the hash chain at it */ + if (ofs_write(tdb, offset, &rec_ptr) == -1) goto fail; + + tdb_unlock(tdb, BUCKET(hash)); + return 0; + + fail: +#if TDB_DEBUG + printf("store failed for hash 0x%08x in bucket %u\n", hash, BUCKET(hash)); +#endif + if (p) free(p); + tdb_unlock(tdb, BUCKET(hash)); + return -1; +} + + +/* open the database, creating it if necessary + + The open_flags and mode are passed straight to the open call on the database + file. A flags value of O_WRONLY is invalid + + The hash size is advisory, use zero for a default value. + + return is NULL on error +*/ +TDB_CONTEXT *tdb_open(char *name, int hash_size, int tdb_flags, + int open_flags, mode_t mode) +{ + TDB_CONTEXT tdb, *ret; + struct stat st; + + memset(&tdb, 0, sizeof(tdb)); + + tdb.fd = -1; + tdb.name = NULL; + tdb.map_ptr = NULL; + + if ((open_flags & O_ACCMODE) == O_WRONLY) { + goto fail; + } + + if (hash_size == 0) hash_size = DEFAULT_HASH_SIZE; + + tdb.read_only = ((open_flags & O_ACCMODE) == O_RDONLY); + + if (name != NULL) { + tdb.fd = open(name, open_flags, mode); + if (tdb.fd == -1) { + goto fail; + } + } + + /* ensure there is only one process initialising at once */ + tdb_brlock(&tdb, GLOBAL_LOCK, LOCK_SET, F_WRLCK, F_SETLKW); + + if (tdb_flags & TDB_CLEAR_IF_FIRST) { + /* we need to zero the database if we are the only + one with it open */ + if (tdb_brlock(&tdb, ACTIVE_LOCK, LOCK_SET, F_WRLCK, F_SETLK) == 0) { + ftruncate(tdb.fd, 0); + tdb_brlock(&tdb, ACTIVE_LOCK, LOCK_CLEAR, F_WRLCK, F_SETLK); + } + } + + /* leave this lock in place */ + tdb_brlock(&tdb, ACTIVE_LOCK, LOCK_SET, F_RDLCK, F_SETLKW); + + if (read(tdb.fd, &tdb.header, sizeof(tdb.header)) != sizeof(tdb.header) || + strcmp(tdb.header.magic_food, TDB_MAGIC_FOOD) != 0 || + tdb.header.version != TDB_VERSION) { + /* its not a valid database - possibly initialise it */ + if (!(open_flags & O_CREAT)) { + goto fail; + } + if (tdb_new_database(&tdb, hash_size) == -1) goto fail; + + lseek(tdb.fd, 0, SEEK_SET); + if (tdb.fd != -1 && read(tdb.fd, &tdb.header, + sizeof(tdb.header)) != + sizeof(tdb.header)) + goto fail; + } + + if (tdb.fd != -1) { + fstat(tdb.fd, &st); + + /* map the database and fill in the return structure */ + tdb.name = (char *)strdup(name); + tdb.map_size = st.st_size; + } + + tdb.locked = (int *)calloc(tdb.header.hash_size+1, + sizeof(tdb.locked[0])); + if (!tdb.locked) { + goto fail; + } + +#if HAVE_MMAP + if (tdb.fd != -1) { + tdb.map_ptr = (void *)mmap(NULL, st.st_size, + tdb.read_only? PROT_READ : PROT_READ|PROT_WRITE, + MAP_SHARED | MAP_FILE, tdb.fd, 0); + } +#endif + + ret = (TDB_CONTEXT *)malloc(sizeof(tdb)); + if (!ret) goto fail; + + *ret = tdb; + +#if TDB_DEBUG + printf("mapped database of hash_size %u map_size=%u\n", + hash_size, tdb.map_size); +#endif + + tdb_brlock(&tdb, GLOBAL_LOCK, LOCK_CLEAR, F_WRLCK, F_SETLKW); + return ret; + + fail: + if (tdb.name) free(tdb.name); + if (tdb.fd != -1) close(tdb.fd); + if (tdb.map_ptr) munmap(tdb.map_ptr, tdb.map_size); + + return NULL; +} + +/* close a database */ +int tdb_close(TDB_CONTEXT *tdb) +{ + if (!tdb) return -1; + + if (tdb->name) free(tdb->name); + if (tdb->fd != -1) close(tdb->fd); + if (tdb->locked) free(tdb->locked); + + if (tdb->map_ptr) { + if (tdb->fd != -1) { + munmap(tdb->map_ptr, tdb->map_size); + } else { + free(tdb->map_ptr); + } + } + + memset(tdb, 0, sizeof(*tdb)); + free(tdb); + + return 0; +} + +/* lock the database. If we already have it locked then don't do anything */ +int tdb_writelock(TDB_CONTEXT *tdb) +{ + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_writelock() called with null context\n"); +#endif + return -1; + } + + return tdb_lock(tdb, -1); +} + +/* unlock the database. */ +int tdb_writeunlock(TDB_CONTEXT *tdb) +{ + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_writeunlock() called with null context\n"); +#endif + return -1; + } + + return tdb_unlock(tdb, -1); +} + +/* lock one hash chain. This is meant to be used to reduce locking + contention - it cannot guarantee how many records will be locked */ +int tdb_lockchain(TDB_CONTEXT *tdb, TDB_DATA key) +{ + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_lockchain() called with null context\n"); +#endif + return -1; + } + + return tdb_lock(tdb, BUCKET(tdb_hash(&key))); +} + + +/* unlock one hash chain */ +int tdb_unlockchain(TDB_CONTEXT *tdb, TDB_DATA key) +{ + if (tdb == NULL) { +#ifdef TDB_DEBUG + printf("tdb_unlockchain() called with null context\n"); +#endif + return -1; + } + + return tdb_unlock(tdb, BUCKET(tdb_hash(&key))); +} diff --git a/pppd/tdb.h b/pppd/tdb.h new file mode 100644 index 0000000..56ae0ac --- /dev/null +++ b/pppd/tdb.h @@ -0,0 +1,77 @@ +#define STANDALONE 1 +/* + * Database functions + * Copyright (C) Andrew Tridgell 1999 + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms AND provided that this software or + * any derived work is only used as part of the PPP daemon (pppd) + * and related utilities. + * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Note: this software is also available under the Gnu Public License + * version 2 or later. + */ + +typedef unsigned tdb_len; +typedef unsigned tdb_off; + +#define TDB_MAGIC_FOOD "TDB file\n" + +/* this is stored at the front of every database */ +struct tdb_header { + char magic_food[32]; /* for /etc/magic */ + unsigned version; /* version of the code */ + unsigned hash_size; /* number of hash entries */ +}; + +typedef struct { + char *dptr; + size_t dsize; +} TDB_DATA; + +/* this is the context structure that is returned from a db open */ +typedef struct { + char *name; /* the name of the database */ + void *map_ptr; /* where it is currently mapped */ + int fd; /* open file descriptor for the database */ + tdb_len map_size; /* how much space has been mapped */ + int read_only; /* opened read-only */ + int *locked; /* set if we have a chain locked */ + int ecode; /* error code for last tdb error */ + struct tdb_header header; /* a cached copy of the header */ +} TDB_CONTEXT; + +/* flags to tdb_store() */ +#define TDB_REPLACE 1 +#define TDB_INSERT 2 + +/* flags for tdb_open() */ +#define TDB_CLEAR_IF_FIRST 1 + +/* error codes */ +enum TDB_ERROR {TDB_SUCCESS=0, TDB_ERR_CORRUPT, TDB_ERR_IO, TDB_ERR_LOCK, + TDB_ERR_OOM, TDB_ERR_EXISTS}; + +#if STANDALONE +TDB_CONTEXT *tdb_open(char *name, int hash_size, int tdb_flags, + int open_flags, mode_t mode); +char *tdb_error(TDB_CONTEXT *tdb); +int tdb_writelock(TDB_CONTEXT *tdb); +int tdb_writeunlock(TDB_CONTEXT *tdb); +TDB_DATA tdb_fetch(TDB_CONTEXT *tdb, TDB_DATA key); +int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key); +int tdb_store(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag); +int tdb_close(TDB_CONTEXT *tdb); +TDB_DATA tdb_firstkey(TDB_CONTEXT *tdb); +TDB_DATA tdb_nextkey(TDB_CONTEXT *tdb, TDB_DATA key); +int tdb_traverse(TDB_CONTEXT *tdb, + int (*fn)(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state), + void *state); +int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key); +#endif -- 2.39.2