/* dhcpcd - DHCP client daemon * Copyright 2006-2008 Roy Marples * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "client.h" #include "dhcpcd.h" #include "dhcp.h" #include "interface.h" #include "logger.h" #include "socket.h" #include "version.h" #include "logwriter.h" static int doversion = 0; static int dohelp = 0; #define EXTRA_OPTS static const struct option longopts[] = { {"arp", no_argument, NULL, 'a'}, {"script", required_argument, NULL, 'c'}, {"debug", no_argument, NULL, 'd'}, {"hostname", optional_argument, NULL, 'h'}, {"classid", optional_argument, NULL, 'i'}, {"release", no_argument, NULL, 'k'}, {"leasetime", required_argument, NULL, 'l'}, {"metric", required_argument, NULL, 'm'}, {"renew", no_argument, NULL, 'n'}, {"persistent", no_argument, NULL, 'p'}, {"qtsocketaddress", required_argument, NULL, 'q'}, {"inform", optional_argument, NULL, 's'}, {"request", optional_argument, NULL, 'r'}, {"timeout", required_argument, NULL, 't'}, {"userclass", required_argument, NULL, 'u'}, {"exit", no_argument, NULL, 'x'}, {"lastlease", no_argument, NULL, 'E'}, {"fqdn", required_argument, NULL, 'F'}, {"nogateway", no_argument, NULL, 'G'}, {"sethostname", no_argument, NULL, 'H'}, {"clientid", optional_argument, NULL, 'I'}, {"noipv4ll", no_argument, NULL, 'L'}, {"nomtu", no_argument, NULL, 'M'}, {"nontp", no_argument, NULL, 'N'}, {"nodns", no_argument, NULL, 'R'}, {"msscr", no_argument, NULL, 'S'}, {"test", no_argument, NULL, 'T'}, {"nonis", no_argument, NULL, 'Y'}, {"help", no_argument, &dohelp, 1}, {"version", no_argument, &doversion, 1}, #ifdef THERE_IS_NO_FORK {"daemonised", no_argument, NULL, 'f'}, {"skiproutes", required_argument, NULL, 'g'}, #endif {NULL, 0, NULL, 0} }; #ifdef THERE_IS_NO_FORK char dhcpcd[PATH_MAX]; char **dhcpcd_argv = NULL; int dhcpcd_argc = 0; char *dhcpcd_skiproutes = NULL; #undef EXTRA_OPTS #define EXTRA_OPTS "fg:" #endif static int atoint (const char *s) { char *t; long n; errno = 0; n = strtol (s, &t, 0); if ((errno != 0 && n == 0) || s == t || (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN))) { logger (LOG_ERR, "`%s' out of range", s); return (-1); } return ((int) n); } static pid_t read_pid (const char *pidfile) { FILE *fp; pid_t pid = 0; if ((fp = fopen (pidfile, "r")) == NULL) { errno = ENOENT; return 0; } fscanf (fp, "%d", &pid); fclose (fp); return (pid); } static void usage (void) { printf ("usage: "PACKAGE" [-adknpEGHMNRSTY] [-c script] [-h hostname] [-i classID]\n" " [-l leasetime] [-m metric] [-r ipaddress] [-s ipaddress]\n" " [-t timeout] [-u userclass] [-F none | ptr | both]\n" " [-I clientID] [-q qtsocketaddress] \n"); } int main (int argc, char **argv) { options_t *options; int userclasses = 0; int opt; int option_index = 0; char *prefix; pid_t pid; int debug = 0; int i; int pidfd = -1; int sig = 0; int retval = EXIT_FAILURE; /* Close any un-needed fd's */ for (i = getdtablesize() - 1; i >= 3; --i) close (i); openlog (PACKAGE, LOG_PID, LOG_LOCAL0); options = xzalloc (sizeof (*options)); options->script = (char *) DEFAULT_SCRIPT; snprintf (options->classid, CLASS_ID_MAX_LEN, "%s %s", PACKAGE, VERSION); options->doarp = true; options->dodns = true; options->domtu = true; options->donis = true; options->dontp = true; options->dogateway = true; options->daemonise = true; options->doinform = false; options->doipv4ll = true; options->doduid = true; options->timeout = DEFAULT_TIMEOUT; /* added by Niklas Goby, additional field, storing the socket address path for * communicating with Qt "server" * defined in dhcpcd.h */ strcpy(options->qtsocketaddress, DEFAULT_QTSOCKETADDRESS); gethostname (options->hostname, sizeof (options->hostname)); if (strcmp (options->hostname, "(none)") == 0 || strcmp (options->hostname, "localhost") == 0) memset (options->hostname, 0, sizeof (options->hostname)); /* Don't set any optional arguments here so we retain POSIX * compatibility with getopt */ while ((opt = getopt_long(argc, argv, EXTRA_OPTS "c:dh:i:kl:m:npq:r:s:t:u:xAEF:GHI:LMNRSTY", longopts, &option_index)) != -1) { switch (opt) { case 0: if (longopts[option_index].flag) break; logger (LOG_ERR, "option `%s' should set a flag", longopts[option_index].name); goto abort; case 'c': options->script = optarg; break; case 'd': debug++; switch (debug) { case 1: setloglevel (LOG_DEBUG); break; case 2: options->daemonise = false; break; } break; #ifdef THERE_IS_NO_FORK case 'f': options->daemonised = true; close_fds (); break; case 'g': dhcpcd_skiproutes = xstrdup (optarg); break; #endif case 'h': if (! optarg) *options->hostname = '\0'; else if (strlen (optarg) > MAXHOSTNAMELEN) { logger (LOG_ERR, "`%s' too long for HostName string, max is %d", optarg, MAXHOSTNAMELEN); goto abort; } else strlcpy (options->hostname, optarg, sizeof (options->hostname)); break; case 'i': if (! optarg) { *options->classid = '\0'; } else if (strlen (optarg) > CLASS_ID_MAX_LEN) { logger (LOG_ERR, "`%s' too long for ClassID string, max is %d", optarg, CLASS_ID_MAX_LEN); goto abort; } else strlcpy (options->classid, optarg, sizeof (options->classid)); break; case 'k': sig = SIGHUP; break; case 'l': if (*optarg == '-') { logger (LOG_ERR, "leasetime must be a positive value"); goto abort; } errno = 0; options->leasetime = (uint32_t) strtol (optarg, NULL, 0); if (errno == EINVAL || errno == ERANGE) { logger (LOG_ERR, "`%s' out of range", optarg); goto abort; } break; case 'm': options->metric = atoint (optarg); if (options->metric < 0) { logger (LOG_ERR, "metric must be a positive value"); goto abort; } break; case 'n': sig = SIGALRM; break; case 'p': options->persistent = true; break; case 'q': if (strlen(optarg) > QTSOCKETADDRESSLENGTH) { logger(LOG_ERR, "`%s' too long for an socket address path (max=%d)", optarg, QTSOCKETADDRESSLENGTH); goto abort; } strlcpy(options->qtsocketaddress, optarg, sizeof(options->qtsocketaddress)); break; case 's': options->doinform = true; options->doarp = false; if (! optarg || strlen (optarg) == 0) { options->request_address.s_addr = 0; break; } else { char *slash = strchr (optarg, '/'); if (slash) { int cidr; /* nullify the slash, so the -r option can read the * address */ *slash++ = '\0'; if (sscanf (slash, "%d", &cidr) != 1 || inet_cidrtoaddr (cidr, &options->request_netmask) != 0) { logger (LOG_ERR, "`%s' is not a valid CIDR", slash); goto abort; } } } /* FALLTHROUGH */ case 'r': if (! options->doinform) options->dorequest = true; if (strlen (optarg) > 0 && ! inet_aton (optarg, &options->request_address)) { logger (LOG_ERR, "`%s' is not a valid IP address", optarg); goto abort; } break; case 't': options->timeout = atoint (optarg); if (options->timeout < 0) { logger (LOG_ERR, "timeout must be a positive value"); goto abort; } break; case 'u': { int offset = 0; for (i = 0; i < userclasses; i++) offset += (int) options->userclass[offset] + 1; if (offset + 1 + strlen (optarg) > USERCLASS_MAX_LEN) { logger (LOG_ERR, "userclass overrun, max is %d", USERCLASS_MAX_LEN); goto abort; } userclasses++; memcpy (options->userclass + offset + 1 , optarg, strlen (optarg)); options->userclass[offset] = strlen (optarg); options->userclass_len += (strlen (optarg)) + 1; } break; case 'x': sig = SIGTERM; break; case 'A': #ifndef ENABLE_ARP logger (LOG_ERR, "arp not compiled into dhcpcd"); goto abort; #endif options->doarp = false; break; case 'E': #ifndef ENABLE_INFO logger (LOG_ERR, "info not compiled into dhcpcd"); goto abort; #endif options->dolastlease = true; break; case 'F': if (strncmp (optarg, "none", strlen (optarg)) == 0) options->fqdn = FQDN_NONE; else if (strncmp (optarg, "ptr", strlen (optarg)) == 0) options->fqdn = FQDN_PTR; else if (strncmp (optarg, "both", strlen (optarg)) == 0) options->fqdn = FQDN_BOTH; else { logger (LOG_ERR, "invalid value `%s' for FQDN", optarg); goto abort; } break; case 'G': options->dogateway = false; break; case 'H': options->dohostname++; break; case 'I': if (optarg) { if (strlen (optarg) > CLIENT_ID_MAX_LEN) { logger (LOG_ERR, "`%s' is too long for ClientID, max is %d", optarg, CLIENT_ID_MAX_LEN); goto abort; } if (strlcpy (options->clientid, optarg, sizeof (options->clientid)) == 0) /* empty string disabled duid */ options->doduid = false; } else { memset (options->clientid, 0, sizeof (options->clientid)); options->doduid = false; } break; case 'L': options->doipv4ll = false; break; case 'M': options->domtu = false; break; case 'N': options->dontp = false; break; case 'R': options->dodns = false; break; case 'S': options->domscsr++; break; case 'T': #ifndef ENABLE_INFO logger (LOG_ERR, "info support not compiled into dhcpcd"); goto abort; #endif options->test = true; options->persistent = true; break; case 'Y': options->donis = false; break; case '?': usage (); goto abort; default: usage (); goto abort; } } if (doversion) { printf (""PACKAGE" "VERSION"\n"); printf ("Compile time options:" #ifdef ENABLE_ARP " ARP" #endif #ifdef ENABLE_DUID " DUID" #endif #ifdef ENABLE_INFO " INFO" #endif #ifdef ENABLE_INFO_COMPAT " INFO_COMPAT" #endif #ifdef ENABLE_IPV4LL " IPV4LL" #endif #ifdef ENABLE_NIS " NIS" #endif #ifdef ENABLE_NTP " NTP" #endif #ifdef SERVICE " " SERVICE #endif #ifdef ENABLE_RESOLVCONF " RESOLVCONF" #endif #ifdef THERE_IS_NO_FORK " THERE_IS_NO_FORK" #endif "\n"); } if (dohelp) usage (); #ifdef THERE_IS_NO_FORK dhcpcd_argv = argv; dhcpcd_argc = argc; if (! realpath (argv[0], dhcpcd)) { logger (LOG_ERR, "unable to resolve the path `%s': %s", argv[0], strerror (errno)); goto abort; } #endif /* initializations for the ipc connection to qt*/ setSocketName(options->qtsocketaddress); if (initQtLoggerSocket() < 0) { logger(LOG_ERR, "initialization Qt Logger failed: %s ", strerror (errno)); goto abort; } if (optind < argc) { if (strlen(argv[optind]) > IF_NAMESIZE) { logger(LOG_ERR, "`%s' too long for an interface name (max=%d)", argv[optind], IF_NAMESIZE); goto abort; } strlcpy(options->interface, argv[optind], sizeof(options->interface)); setInterfaceName(options->interface); } else { /* If only version was requested then exit now */ if (doversion || dohelp) { retval = 0; goto abort; } logger(LOG_ERR, "no interface specified"); setInterfaceName("no_if"); goto abort; } if (strchr(options->hostname, '.')) { if (options->fqdn == FQDN_DISABLE) options->fqdn = FQDN_BOTH; } else options->fqdn = FQDN_DISABLE; if (options->request_address.s_addr == 0 && options->doinform) { if ((options->request_address.s_addr = get_address(options->interface)) != 0) options->keep_address = true; } if (IN_LINKLOCAL (ntohl (options->request_address.s_addr))) { logger (LOG_ERR, "you are not allowed to request a link local address"); logToQt(LOG_ERR, -1, "you are not allowed to request a link local address"); goto abort; } if (geteuid ()) { logger (LOG_WARNING, PACKAGE " will not work correctly unless" " run as root"); } prefix = xmalloc (sizeof (char) * (IF_NAMESIZE + 3)); snprintf (prefix, IF_NAMESIZE, "%s: ", options->interface); setlogprefix (prefix); snprintf (options->pidfile, sizeof (options->pidfile), PIDFILE, options->interface); free (prefix); chdir ("/"); umask (022); if (mkdir (INFODIR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) && errno != EEXIST) { logger (LOG_ERR, "mkdir(\"%s\",0): %s\n", INFODIR, strerror (errno)); goto abort; } if (mkdir (ETCDIR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) && errno != EEXIST) { logger (LOG_ERR, "mkdir(\"%s\",0): %s\n", ETCDIR, strerror (errno)); goto abort; } if (options->test) { if (options->dorequest || options->doinform) { logger (LOG_ERR, "cannot test with --inform or --request"); goto abort; } if (options->dolastlease) { logger (LOG_ERR, "cannot test with --lastlease"); goto abort; } if (sig != 0) { logger (LOG_ERR, "cannot test with --release or --renew"); goto abort; } } if (sig != 0) { int killed = -1; pid = read_pid (options->pidfile); if (pid != 0) logger (LOG_INFO, "sending signal %d to pid %d", sig, pid); if (! pid || (killed = kill (pid, sig))) logger (sig == SIGALRM ? LOG_INFO : LOG_ERR, ""PACKAGE" not running"); if (pid != 0 && (sig != SIGALRM || killed != 0)) unlink (options->pidfile); if (killed == 0) { retval = EXIT_SUCCESS; goto abort; } if (sig != SIGALRM) goto abort; } if (! options->test && ! options->daemonised) { if ((pid = read_pid (options->pidfile)) > 0 && kill (pid, 0) == 0) { logger (LOG_ERR, ""PACKAGE " already running on pid %d (%s)", pid, options->pidfile); goto abort; } pidfd = open (options->pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); if (pidfd == -1) { logger (LOG_ERR, "open `%s': %s", options->pidfile, strerror (errno)); goto abort; } /* Lock the file so that only one instance of dhcpcd runs * on an interface */ if (flock (pidfd, LOCK_EX | LOCK_NB) == -1) { logger (LOG_ERR, "flock `%s': %s", options->pidfile, strerror (errno)); goto abort; } /* dhcpcd.sh should not interhit this fd */ if ((i = fcntl (pidfd, F_GETFD, 0)) == -1 || fcntl (pidfd, F_SETFD, i | FD_CLOEXEC) == -1) logger (LOG_ERR, "fcntl: %s", strerror (errno)); writepid (pidfd, getpid ()); logger (LOG_INFO, PACKAGE " " VERSION " starting"); } /* Seed random */ srandomdev (); /* Massage our filters per platform */ setup_packet_filters (); /* dhcp_run : defined in client.c */ if (dhcp_run (options, &pidfd) == 0) retval = EXIT_SUCCESS; abort: /* If we didn't daemonise then we need to punt the pidfile now */ if (pidfd > -1) { close (pidfd); unlink (options->pidfile); } free (options); #ifdef THERE_IS_NO_FORK /* There may have been an error before the dhcp_run function * clears this, so just do it here to be safe */ free (dhcpcd_skiproutes); #endif logger (LOG_INFO, "exiting"); logToQt(LOG_INFO, DHCPCD_EXIT, "exiting due abort"); closeQtLoggerSocket(); exit (retval); /* NOTREACHED */ }