/* * utils.c - various utility functions used in pppd. * * Copyright (c) 1999-2002 Paul Mackerras. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. The name(s) of the authors of this software must not be used to * endorse or promote products derived from this software without * prior written permission. * * 3. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Paul Mackerras * ". * * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "netif/ppp/ppp_opts.h" #if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */ #if 0 /* UNUSED */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SVR4 #include #endif #endif /* UNUSED */ #include "netif/ppp/ppp_impl.h" #include "netif/ppp/fsm.h" #include "netif/ppp/lcp.h" #if defined(SUNOS4) extern char *strerror(); #endif static void ppp_logit(int level, const char *fmt, va_list args); static void ppp_log_write(int level, char *buf); #if PRINTPKT_SUPPORT static void ppp_vslp_printer(void *arg, const char *fmt, ...); static void ppp_format_packet(const u_char *p, int len, void (*printer) (void *, const char *, ...), void *arg); struct buffer_info { char *ptr; int len; }; #endif /* PRINTPKT_SUPPORT */ /* * ppp_strlcpy - like strcpy/strncpy, doesn't overflow destination buffer, * always leaves destination null-terminated (for len > 0). */ size_t ppp_strlcpy(char *dest, const char *src, size_t len) { size_t ret = strlen(src); if (len != 0) { if (ret < len) strcpy(dest, src); else { strncpy(dest, src, len - 1); dest[len-1] = 0; } } return ret; } /* * ppp_strlcat - like strcat/strncat, doesn't overflow destination buffer, * always leaves destination null-terminated (for len > 0). */ size_t ppp_strlcat(char *dest, const char *src, size_t len) { size_t dlen = strlen(dest); return dlen + ppp_strlcpy(dest + dlen, src, (len > dlen? len - dlen: 0)); } /* * ppp_slprintf - format a message into a buffer. Like sprintf except we * also specify the length of the output buffer, and we handle * %m (error message), %v (visible string), * %q (quoted string), %t (current time) and %I (IP address) formats. * Doesn't do floating-point formats. * Returns the number of chars put into buf. */ int ppp_slprintf(char *buf, int buflen, const char *fmt, ...) { va_list args; int n; va_start(args, fmt); n = ppp_vslprintf(buf, buflen, fmt, args); va_end(args); return n; } /* * ppp_vslprintf - like ppp_slprintf, takes a va_list instead of a list of args. */ #define OUTCHAR(c) (buflen > 0? (--buflen, *buf++ = (c)): 0) int ppp_vslprintf(char *buf, int buflen, const char *fmt, va_list args) { int c, i, n; int width, prec, fillch; int base, len, neg, quoted; unsigned long val = 0; const char *f; char *str, *buf0; const unsigned char *p; char num[32]; #if 0 /* need port */ time_t t; #endif /* need port */ u32_t ip; static char hexchars[] = "0123456789abcdef"; #if PRINTPKT_SUPPORT struct buffer_info bufinfo; #endif /* PRINTPKT_SUPPORT */ buf0 = buf; --buflen; while (buflen > 0) { for (f = fmt; *f != '%' && *f != 0; ++f) ; if (f > fmt) { len = f - fmt; if (len > buflen) len = buflen; memcpy(buf, fmt, len); buf += len; buflen -= len; fmt = f; } if (*fmt == 0) break; c = *++fmt; width = 0; prec = -1; fillch = ' '; if (c == '0') { fillch = '0'; c = *++fmt; } if (c == '*') { width = va_arg(args, int); c = *++fmt; } else { while (lwip_isdigit(c)) { width = width * 10 + c - '0'; c = *++fmt; } } if (c == '.') { c = *++fmt; if (c == '*') { prec = va_arg(args, int); c = *++fmt; } else { prec = 0; while (lwip_isdigit(c)) { prec = prec * 10 + c - '0'; c = *++fmt; } } } str = 0; base = 0; neg = 0; ++fmt; switch (c) { case 'l': c = *fmt++; switch (c) { case 'd': val = va_arg(args, long); if ((long)val < 0) { neg = 1; val = (unsigned long)-(long)val; } base = 10; break; case 'u': val = va_arg(args, unsigned long); base = 10; break; default: OUTCHAR('%'); OUTCHAR('l'); --fmt; /* so %lz outputs %lz etc. */ continue; } break; case 'd': i = va_arg(args, int); if (i < 0) { neg = 1; val = -i; } else val = i; base = 10; break; case 'u': val = va_arg(args, unsigned int); base = 10; break; case 'o': val = va_arg(args, unsigned int); base = 8; break; case 'x': case 'X': val = va_arg(args, unsigned int); base = 16; break; #if 0 /* unused (and wrong on LLP64 systems) */ case 'p': val = (unsigned long) va_arg(args, void *); base = 16; neg = 2; break; #endif /* unused (and wrong on LLP64 systems) */ case 's': str = va_arg(args, char *); break; case 'c': num[0] = va_arg(args, int); num[1] = 0; str = num; break; #if 0 /* do we always have strerror() in embedded ? */ case 'm': str = strerror(errno); break; #endif /* do we always have strerror() in embedded ? */ case 'I': ip = va_arg(args, u32_t); ip = lwip_ntohl(ip); ppp_slprintf(num, sizeof(num), "%d.%d.%d.%d", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); str = num; break; #if 0 /* need port */ case 't': time(&t); str = ctime(&t); str += 4; /* chop off the day name */ str[15] = 0; /* chop off year and newline */ break; #endif /* need port */ case 'v': /* "visible" string */ case 'q': /* quoted string */ quoted = c == 'q'; p = va_arg(args, unsigned char *); if (p == NULL) p = (const unsigned char *)""; if (fillch == '0' && prec >= 0) { n = prec; } else { n = strlen((const char *)p); if (prec >= 0 && n > prec) n = prec; } while (n > 0 && buflen > 0) { c = *p++; --n; if (!quoted && c >= 0x80) { OUTCHAR('M'); OUTCHAR('-'); c -= 0x80; } if (quoted && (c == '"' || c == '\\')) OUTCHAR('\\'); if (c < 0x20 || (0x7f <= c && c < 0xa0)) { if (quoted) { OUTCHAR('\\'); switch (c) { case '\t': OUTCHAR('t'); break; case '\n': OUTCHAR('n'); break; case '\b': OUTCHAR('b'); break; case '\f': OUTCHAR('f'); break; default: OUTCHAR('x'); OUTCHAR(hexchars[c >> 4]); OUTCHAR(hexchars[c & 0xf]); } } else { if (c == '\t') OUTCHAR(c); else { OUTCHAR('^'); OUTCHAR(c ^ 0x40); } } } else OUTCHAR(c); } continue; #if PRINTPKT_SUPPORT case 'P': /* print PPP packet */ bufinfo.ptr = buf; bufinfo.len = buflen + 1; p = va_arg(args, unsigned char *); n = va_arg(args, int); ppp_format_packet(p, n, ppp_vslp_printer, &bufinfo); buf = bufinfo.ptr; buflen = bufinfo.len - 1; continue; #endif /* PRINTPKT_SUPPORT */ case 'B': p = va_arg(args, unsigned char *); for (n = prec; n > 0; --n) { c = *p++; if (fillch == ' ') OUTCHAR(' '); OUTCHAR(hexchars[(c >> 4) & 0xf]); OUTCHAR(hexchars[c & 0xf]); } continue; default: *buf++ = '%'; if (c != '%') --fmt; /* so %z outputs %z etc. */ --buflen; continue; } if (base != 0) { str = num + sizeof(num); *--str = 0; while (str > num + neg) { *--str = hexchars[val % base]; val = val / base; if (--prec <= 0 && val == 0) break; } switch (neg) { case 1: *--str = '-'; break; case 2: *--str = 'x'; *--str = '0'; break; default: break; } len = num + sizeof(num) - 1 - str; } else { len = strlen(str); if (prec >= 0 && len > prec) len = prec; } if (width > 0) { if (width > buflen) width = buflen; if ((n = width - len) > 0) { buflen -= n; for (; n > 0; --n) *buf++ = fillch; } } if (len > buflen) len = buflen; memcpy(buf, str, len); buf += len; buflen -= len; } *buf = 0; return buf - buf0; } #if PRINTPKT_SUPPORT /* * vslp_printer - used in processing a %P format */ static void ppp_vslp_printer(void *arg, const char *fmt, ...) { int n; va_list pvar; struct buffer_info *bi; va_start(pvar, fmt); bi = (struct buffer_info *) arg; n = ppp_vslprintf(bi->ptr, bi->len, fmt, pvar); va_end(pvar); bi->ptr += n; bi->len -= n; } #endif /* PRINTPKT_SUPPORT */ #if 0 /* UNUSED */ /* * log_packet - format a packet and log it. */ void log_packet(p, len, prefix, level) u_char *p; int len; char *prefix; int level; { init_pr_log(prefix, level); ppp_format_packet(p, len, pr_log, &level); end_pr_log(); } #endif /* UNUSED */ #if PRINTPKT_SUPPORT /* * ppp_format_packet - make a readable representation of a packet, * calling `printer(arg, format, ...)' to output it. */ static void ppp_format_packet(const u_char *p, int len, void (*printer) (void *, const char *, ...), void *arg) { int i, n; u_short proto; const struct protent *protp; if (len >= 2) { GETSHORT(proto, p); len -= 2; for (i = 0; (protp = protocols[i]) != NULL; ++i) if (proto == protp->protocol) break; if (protp != NULL) { printer(arg, "[%s", protp->name); n = (*protp->printpkt)(p, len, printer, arg); printer(arg, "]"); p += n; len -= n; } else { for (i = 0; (protp = protocols[i]) != NULL; ++i) if (proto == (protp->protocol & ~0x8000)) break; if (protp != 0 && protp->data_name != 0) { printer(arg, "[%s data]", protp->data_name); if (len > 8) printer(arg, "%.8B ...", p); else printer(arg, "%.*B", len, p); len = 0; } else printer(arg, "[proto=0x%x]", proto); } } if (len > 32) printer(arg, "%.32B ...", p); else printer(arg, "%.*B", len, p); } #endif /* PRINTPKT_SUPPORT */ #if 0 /* UNUSED */ /* * init_pr_log, end_pr_log - initialize and finish use of pr_log. */ static char line[256]; /* line to be logged accumulated here */ static char *linep; /* current pointer within line */ static int llevel; /* level for logging */ void init_pr_log(prefix, level) const char *prefix; int level; { linep = line; if (prefix != NULL) { ppp_strlcpy(line, prefix, sizeof(line)); linep = line + strlen(line); } llevel = level; } void end_pr_log() { if (linep != line) { *linep = 0; ppp_log_write(llevel, line); } } /* * pr_log - printer routine for outputting to log */ void pr_log (void *arg, const char *fmt, ...) { int l, n; va_list pvar; char *p, *eol; char buf[256]; va_start(pvar, fmt); n = ppp_vslprintf(buf, sizeof(buf), fmt, pvar); va_end(pvar); p = buf; eol = strchr(buf, '\n'); if (linep != line) { l = (eol == NULL)? n: eol - buf; if (linep + l < line + sizeof(line)) { if (l > 0) { memcpy(linep, buf, l); linep += l; } if (eol == NULL) return; p = eol + 1; eol = strchr(p, '\n'); } *linep = 0; ppp_log_write(llevel, line); linep = line; } while (eol != NULL) { *eol = 0; ppp_log_write(llevel, p); p = eol + 1; eol = strchr(p, '\n'); } /* assumes sizeof(buf) <= sizeof(line) */ l = buf + n - p; if (l > 0) { memcpy(line, p, n); linep = line + l; } } #endif /* UNUSED */ /* * ppp_print_string - print a readable representation of a string using * printer. */ void ppp_print_string(const u_char *p, int len, void (*printer) (void *, const char *, ...), void *arg) { int c; printer(arg, "\""); for (; len > 0; --len) { c = *p++; if (' ' <= c && c <= '~') { if (c == '\\' || c == '"') printer(arg, "\\"); printer(arg, "%c", c); } else { switch (c) { case '\n': printer(arg, "\\n"); break; case '\r': printer(arg, "\\r"); break; case '\t': printer(arg, "\\t"); break; default: printer(arg, "\\%.3o", (u8_t)c); /* no break */ } } } printer(arg, "\""); } /* * ppp_logit - does the hard work for fatal et al. */ static void ppp_logit(int level, const char *fmt, va_list args) { char buf[1024]; ppp_vslprintf(buf, sizeof(buf), fmt, args); ppp_log_write(level, buf); } static void ppp_log_write(int level, char *buf) { LWIP_UNUSED_ARG(level); /* necessary if PPPDEBUG is defined to an empty function */ LWIP_UNUSED_ARG(buf); PPPDEBUG(level, ("%s\n", buf) ); #if 0 if (log_to_fd >= 0 && (level != LOG_DEBUG || debug)) { int n = strlen(buf); if (n > 0 && buf[n-1] == '\n') --n; if (write(log_to_fd, buf, n) != n || write(log_to_fd, "\n", 1) != 1) log_to_fd = -1; } #endif } /* * ppp_fatal - log an error message and die horribly. */ void ppp_fatal_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_ERR, fmt, pvar); va_end(pvar); LWIP_ASSERT("ppp_fatal", 0); /* as promised */ } /* * ppp_error - log an error message. */ void ppp_error_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_ERR, fmt, pvar); va_end(pvar); #if 0 /* UNUSED */ ++error_count; #endif /* UNUSED */ } /* * ppp_warn - log a warning message. */ void ppp_warn_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_WARNING, fmt, pvar); va_end(pvar); } /* * ppp_notice - log a notice-level message. */ void ppp_notice_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_NOTICE, fmt, pvar); va_end(pvar); } /* * ppp_info - log an informational message. */ void ppp_info_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_INFO, fmt, pvar); va_end(pvar); } /* * ppp_dbglog - log a debug message. */ void ppp_dbglog_impl(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); ppp_logit(LOG_DEBUG, fmt, pvar); va_end(pvar); } #if PRINTPKT_SUPPORT /* * ppp_dump_packet - print out a packet in readable form if it is interesting. * Assumes len >= PPP_HDRLEN. */ void ppp_dump_packet(ppp_pcb *pcb, const char *tag, unsigned char *p, int len) { int proto; /* * don't print data packets, i.e. IPv4, IPv6, VJ, and compressed packets. */ proto = (p[0] << 8) + p[1]; if (proto < 0xC000 && (proto & ~0x8000) == proto) return; /* * don't print valid LCP echo request/reply packets if the link is up. */ if (proto == PPP_LCP && pcb->phase == PPP_PHASE_RUNNING && len >= 2 + HEADERLEN) { unsigned char *lcp = p + 2; int l = (lcp[2] << 8) + lcp[3]; if ((lcp[0] == ECHOREQ || lcp[0] == ECHOREP) && l >= HEADERLEN && l <= len - 2) return; } ppp_dbglog(("%s %P", tag, p, len)); } #endif /* PRINTPKT_SUPPORT */ #if 0 /* Unused */ /* * complete_read - read a full `count' bytes from fd, * unless end-of-file or an error other than EINTR is encountered. */ ssize_t complete_read(int fd, void *buf, size_t count) { size_t done; ssize_t nb; char *ptr = buf; for (done = 0; done < count; ) { nb = read(fd, ptr, count - done); if (nb < 0) { if (errno == EINTR) continue; return -1; } if (nb == 0) break; done += nb; ptr += nb; } return done; } /* Procedures for locking the serial device using a lock file. */ #ifndef LOCK_DIR #ifdef __linux__ #define LOCK_DIR "/var/lock" #else #ifdef SVR4 #define LOCK_DIR "/var/spool/locks" #else #define LOCK_DIR "/var/spool/lock" #endif #endif #endif /* LOCK_DIR */ static char lock_file[MAXPATHLEN]; /* * lock - create a lock file for the named device */ int lock(dev) char *dev; { #ifdef LOCKLIB int result; result = mklock (dev, (void *) 0); if (result == 0) { ppp_strlcpy(lock_file, dev, sizeof(lock_file)); return 0; } if (result > 0) ppp_notice(("Device %s is locked by pid %d", dev, result)); else ppp_error(("Can't create lock file %s", lock_file)); return -1; #else /* LOCKLIB */ char lock_buffer[12]; int fd, pid, n; #ifdef SVR4 struct stat sbuf; if (stat(dev, &sbuf) < 0) { ppp_error(("Can't get device number for %s: %m", dev)); return -1; } if ((sbuf.st_mode & S_IFMT) != S_IFCHR) { ppp_error(("Can't lock %s: not a character device", dev)); return -1; } ppp_slprintf(lock_file, sizeof(lock_file), "%s/LK.%03d.%03d.%03d", LOCK_DIR, major(sbuf.st_dev), major(sbuf.st_rdev), minor(sbuf.st_rdev)); #else char *p; char lockdev[MAXPATHLEN]; if ((p = strstr(dev, "dev/")) != NULL) { dev = p + 4; strncpy(lockdev, dev, MAXPATHLEN-1); lockdev[MAXPATHLEN-1] = 0; while ((p = strrchr(lockdev, '/')) != NULL) { *p = '_'; } dev = lockdev; } else if ((p = strrchr(dev, '/')) != NULL) dev = p + 1; ppp_slprintf(lock_file, sizeof(lock_file), "%s/LCK..%s", LOCK_DIR, dev); #endif while ((fd = open(lock_file, O_EXCL | O_CREAT | O_RDWR, 0644)) < 0) { if (errno != EEXIST) { ppp_error(("Can't create lock file %s: %m", lock_file)); break; } /* Read the lock file to find out who has the device locked. */ fd = open(lock_file, O_RDONLY, 0); if (fd < 0) { if (errno == ENOENT) /* This is just a timing problem. */ continue; ppp_error(("Can't open existing lock file %s: %m", lock_file)); break; } #ifndef LOCK_BINARY n = read(fd, lock_buffer, 11); #else n = read(fd, &pid, sizeof(pid)); #endif /* LOCK_BINARY */ close(fd); fd = -1; if (n <= 0) { ppp_error(("Can't read pid from lock file %s", lock_file)); break; } /* See if the process still exists. */ #ifndef LOCK_BINARY lock_buffer[n] = 0; pid = atoi(lock_buffer); #endif /* LOCK_BINARY */ if (pid == getpid()) return 1; /* somebody else locked it for us */ if (pid == 0 || (kill(pid, 0) == -1 && errno == ESRCH)) { if (unlink (lock_file) == 0) { ppp_notice(("Removed stale lock on %s (pid %d)", dev, pid)); continue; } ppp_warn(("Couldn't remove stale lock on %s", dev)); } else ppp_notice(("Device %s is locked by pid %d", dev, pid)); break; } if (fd < 0) { lock_file[0] = 0; return -1; } pid = getpid(); #ifndef LOCK_BINARY ppp_slprintf(lock_buffer, sizeof(lock_buffer), "%10d\n", pid); write (fd, lock_buffer, 11); #else write(fd, &pid, sizeof (pid)); #endif close(fd); return 0; #endif } /* * relock - called to update our lockfile when we are about to detach, * thus changing our pid (we fork, the child carries on, and the parent dies). * Note that this is called by the parent, with pid equal to the pid * of the child. This avoids a potential race which would exist if * we had the child rewrite the lockfile (the parent might die first, * and another process could think the lock was stale if it checked * between when the parent died and the child rewrote the lockfile). */ int relock(pid) int pid; { #ifdef LOCKLIB /* XXX is there a way to do this? */ return -1; #else /* LOCKLIB */ int fd; char lock_buffer[12]; if (lock_file[0] == 0) return -1; fd = open(lock_file, O_WRONLY, 0); if (fd < 0) { ppp_error(("Couldn't reopen lock file %s: %m", lock_file)); lock_file[0] = 0; return -1; } #ifndef LOCK_BINARY ppp_slprintf(lock_buffer, sizeof(lock_buffer), "%10d\n", pid); write (fd, lock_buffer, 11); #else write(fd, &pid, sizeof(pid)); #endif /* LOCK_BINARY */ close(fd); return 0; #endif /* LOCKLIB */ } /* * unlock - remove our lockfile */ void unlock() { if (lock_file[0]) { #ifdef LOCKLIB (void) rmlock(lock_file, (void *) 0); #else unlink(lock_file); #endif lock_file[0] = 0; } } #endif /* Unused */ #endif /* PPP_SUPPORT */