]> git.meshlink.io Git - catta/blobdiff - avahi-autoipd/main.c
work around KDE misbehaviour. (Patch from Martin Putt; Closes #135)
[catta] / avahi-autoipd / main.c
index 82dba887a1bfda38ac88f8a9d7387f244703ee78..26a516ae98ae4c79c748013a245a4a589a781b85 100644 (file)
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <sys/wait.h>
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+
+#ifdef __linux__
 #include <netpacket/packet.h>
+#endif
 #include <net/ethernet.h>
-#include <fcntl.h>
-#include <time.h>
+#include <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_dl.h>
+#include <net/route.h>
+#endif
+#include <arpa/inet.h>
+
 #include <assert.h>
 #include <errno.h>
-#include <string.h>
 #include <inttypes.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-#include <sys/ioctl.h>
-#include <poll.h>
-#include <net/if.h>
+#include <fcntl.h>
+#include <stdlib.h>
 #include <stdio.h>
-#include <getopt.h>
 #include <signal.h>
-#include <sys/wait.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
+#include <grp.h>
+#include <poll.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#include <pcap.h>
+#endif
 
 #include <avahi-common/malloc.h>
 #include <avahi-common/timeval.h>
-
 #include <avahi-daemon/setproctitle.h>
 
 #include <libdaemon/dfork.h>
 #include "main.h"
 #include "iface.h"
 
-#ifndef __linux__
-#error "avahi-autoipd is only available on Linux for now"
-#endif
-
 /* An implementation of RFC 3927 */
 
 /* Constants from the RFC */
 #define IPV4LL_NETWORK 0xA9FE0000L
 #define IPV4LL_NETMASK 0xFFFF0000L
 #define IPV4LL_HOSTMASK 0x0000FFFFL
+#define IPV4LL_BROADCAST 0xA9FEFFFFL
 
 #define ETHER_ADDRLEN 6
+#define ETHER_HDR_SIZE (2+2*ETHER_ADDRLEN)
 #define ARP_PACKET_SIZE (8+4+4+2*ETHER_ADDRLEN)
 
 typedef enum ArpOperation {
@@ -95,6 +112,11 @@ typedef struct ArpPacketInfo {
     uint8_t sender_hw_address[ETHER_ADDRLEN], target_hw_address[ETHER_ADDRLEN];
 } ArpPacketInfo;
 
+typedef struct ArpPacket {
+    uint8_t *ether_header;
+    uint8_t *ether_payload;
+} ArpPacket;
+
 static State state = STATE_START;
 static int n_iteration = 0;
 static int n_conflict = 0;
@@ -109,6 +131,11 @@ static int use_syslog = 0;
 static int debug = 0;
 static int modify_proc_title = 1;
 static int force_bind = 0;
+#ifdef HAVE_CHROOT
+static int no_chroot = 0;
+#endif
+static int no_drop_root = 0;
+static int wrote_pid_file = 0;
 
 static enum {
     DAEMON_RUN,
@@ -178,21 +205,107 @@ static uint32_t pick_addr(uint32_t old_addr) {
         
         addr = htonl(IPV4LL_NETWORK | (uint32_t) r);
 
-    } while (addr == old_addr);
+    } while (addr == old_addr || !is_ll_address(addr));
 
     return addr;
 }
 
-static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
+static int load_address(const char *fn, uint32_t *addr) {
+    FILE *f;
+    unsigned a, b, c, d;
+
+    assert(fn);
+    assert(addr);
+    
+    if (!(f = fopen(fn, "r"))) {
+
+        if (errno == ENOENT) {
+            *addr = 0;
+            return 0;
+        }
+        
+        daemon_log(LOG_ERR, "fopen() failed: %s", strerror(errno));
+        goto fail;
+    }
+
+    if (fscanf(f, "%u.%u.%u.%u\n", &a, &b, &c, &d) != 4) {
+        daemon_log(LOG_ERR, "Parse failure");
+        goto fail;
+    }
+
+    fclose(f);
+
+    *addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
+    return 0;
+    
+fail:
+    if (f)
+        fclose(f);
+
+    return -1;
+}
+
+static int save_address(const char *fn, uint32_t addr) {
+    FILE *f;
+    char buf[32];
+
+    assert(fn);
+    
+    if (!(f = fopen(fn, "w"))) {
+        daemon_log(LOG_ERR, "fopen() failed: %s", strerror(errno));
+        goto fail;
+    }
+
+    fprintf(f, "%s\n", inet_ntop(AF_INET, &addr, buf, sizeof (buf)));
+    fclose(f);
+
+    return 0;
+    
+fail:
+    if (f)
+        fclose(f);
+
+    return -1;
+}
+
+/*
+ * Allocate a buffer with two pointers in front, one of which is
+ * guaranteed to point ETHER_HDR_SIZE bytes into it.
+ */
+static ArpPacket* packet_new(size_t packet_len) {
+    ArpPacket *p;
+    uint8_t *b;
+
+    assert(packet_len > 0);
+
+#ifdef __linux__
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = NULL;
+    p->ether_payload = b + sizeof(struct ArpPacket);
+    
+#else
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + ETHER_HDR_SIZE + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = b + sizeof(struct ArpPacket);
+    p->ether_payload = b + sizeof(struct ArpPacket) + ETHER_HDR_SIZE;
+#endif
+
+    return p;
+}
+
+static ArpPacket* packet_new_with_info(const ArpPacketInfo *info, size_t *packet_len) {
+    ArpPacket *p = NULL;
     uint8_t *r;
 
     assert(info);
-    assert(packet_len);
     assert(info->operation == ARP_REQUEST || info->operation == ARP_RESPONSE);
+    assert(packet_len != NULL);
 
     *packet_len = ARP_PACKET_SIZE;
-    r = avahi_new0(uint8_t, *packet_len);
-    
+    p = packet_new(*packet_len);
+    r = p->ether_payload;
+
     r[1] = 1; /* HTYPE */
     r[2] = 8; /* PTYPE */
     r[4] = ETHER_ADDRLEN; /* HLEN */
@@ -204,10 +317,10 @@ static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
     memcpy(r+18, info->target_hw_address, ETHER_ADDRLEN);
     memcpy(r+24, &info->target_ip_address, 4);
 
-    return r;
+    return p;
 }
 
-static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
     ArpPacketInfo info;
     
     memset(&info, 0, sizeof(info));
@@ -215,10 +328,10 @@ static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, siz
     memcpy(info.sender_hw_address, hw_address, ETHER_ADDRLEN);
     info.target_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
     ArpPacketInfo info;
 
     memset(&info, 0, sizeof(info));
@@ -227,13 +340,15 @@ static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_addr
     info.target_ip_address = ip_address;
     info.sender_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static int packet_parse(const void *data, size_t packet_len, ArpPacketInfo *info) {
-    const uint8_t *p = data;
+static int packet_parse(const ArpPacket *packet, size_t packet_len, ArpPacketInfo *info) {
+    const uint8_t *p;
     
-    assert(data);
+    assert(packet);
+    p = (uint8_t *)packet->ether_payload;
+    assert(p);
 
     if (packet_len < ARP_PACKET_SIZE)
         return -1;
@@ -278,16 +393,14 @@ static void set_state(State st, int reset_counter, uint32_t address) {
         n_iteration = 0;
     }
 
-    if (modify_proc_title) {
-        if (state == STATE_SLEEPING) 
-            avahi_set_proc_title(argv0, "%s(%s): sleeping", argv0, interface_name);
-        else if (state == STATE_ANNOUNCING)
-            avahi_set_proc_title(argv0, "%s(%s): announcing %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
-        else if (state == STATE_RUNNING)
-            avahi_set_proc_title(argv0, "%s(%s): bound %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
-        else
-            avahi_set_proc_title(argv0, "%s(%s): probing %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
-    }
+    if (state == STATE_SLEEPING) 
+        avahi_set_proc_title(argv0, "%s: [%s] sleeping", argv0, interface_name);
+    else if (state == STATE_ANNOUNCING)
+        avahi_set_proc_title(argv0, "%s: [%s] announcing %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
+    else if (state == STATE_RUNNING)
+        avahi_set_proc_title(argv0, "%s: [%s] bound %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
+    else
+        avahi_set_proc_title(argv0, "%s: [%s] probing %s", argv0, interface_name, inet_ntop(AF_INET, &address, buf, sizeof(buf)));
 }
 
 static int interface_up(int iface) {
@@ -328,6 +441,10 @@ fail:
     return -1;
 }
 
+#ifdef __linux__
+
+/* Linux 'packet socket' specific implementation */
+
 static int open_socket(int iface, uint8_t *hw_address) {
     int fd = -1;
     struct sockaddr_ll sa;
@@ -373,7 +490,7 @@ fail:
     return -1;
 }
 
-static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
+static int send_packet(int fd, int iface, ArpPacket *packet, size_t packet_len) {
     struct sockaddr_ll sa;
     
     assert(fd >= 0);
@@ -395,7 +512,7 @@ static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
     return 0;
 }
 
-static int recv_packet(int fd, void **packet, size_t *packet_len) {
+static int recv_packet(int fd, ArpPacket **packet, size_t *packet_len) {
     int s;
     struct sockaddr_ll sa;
     socklen_t sa_len;
@@ -415,10 +532,10 @@ static int recv_packet(int fd, void **packet, size_t *packet_len) {
     if (s <= 0)
         s = 4096;
 
-    *packet = avahi_new(uint8_t, s);
+    *packet = packet_new(s);
 
     sa_len = sizeof(sa);
-    if ((r = recvfrom(fd, *packet, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
+    if ((r = recvfrom(fd, (*packet)->ether_payload, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
         daemon_log(LOG_ERR, "recvfrom() failed: %s", strerror(errno));
         goto fail;
     }
@@ -435,11 +552,222 @@ fail:
 
     return -1;
 }
+
+static void
+close_socket(int fd) {
+    close(fd);
+}
+
+#else /* !__linux__ */
+/* PCAP-based implementation */
+
+static pcap_t *__pp;
+static char __pcap_errbuf[PCAP_ERRBUF_SIZE];
+static uint8_t __lladdr[ETHER_ADDRLEN];
+
+#ifndef elementsof
+#define elementsof(array)      (sizeof(array)/sizeof(array[0]))
+#endif
+
+static int
+__get_ether_addr(int ifindex, u_char *lladdr)
+{
+       int                      mib[6];
+       char                    *buf;
+       struct if_msghdr        *ifm;
+       char                    *lim;
+       char                    *next;
+       struct sockaddr_dl      *sdl;
+       size_t                   len;
+
+       mib[0] = CTL_NET;
+       mib[1] = PF_ROUTE;
+       mib[2] = 0;
+       mib[3] = 0;
+       mib[4] = NET_RT_IFLIST;
+       mib[5] = ifindex;
+
+       if (sysctl(mib, elementsof(mib), NULL, &len, NULL, 0) != 0) {
+               daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
+                   strerror(errno));
+               return (-1);
+       }
+
+       buf = malloc(len);
+       if (buf == NULL) {
+               daemon_log(LOG_ERR, "malloc(%d): %s", len, strerror(errno));
+               return (-1);
+       }
+
+       if (sysctl(mib, elementsof(mib), buf, &len, NULL, 0) != 0) {
+               daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
+                   strerror(errno));
+               free(buf);
+               return (-1);
+       }
+
+       lim = buf + len;
+       for (next = buf; next < lim; next += ifm->ifm_msglen) {
+               ifm = (struct if_msghdr *)next;
+               if (ifm->ifm_type == RTM_IFINFO) {
+                       sdl = (struct sockaddr_dl *)(ifm + 1);
+                       memcpy(lladdr, LLADDR(sdl), ETHER_ADDRLEN);
+               }
+       }
+       free(buf);
+
+       return (0);
+}
+
+static int
+open_socket(int iface, uint8_t *hw_address)
+{
+       struct bpf_program       bpf;
+       char                     ifname[IFNAMSIZ];
+       pcap_t                  *pp;
+       int                      err;
+       int                      fd;
+
+       assert(__pp == NULL);
+
+       if (interface_up(iface) < 0) {
+               return (-1);
+       }
+       if (__get_ether_addr(iface, __lladdr) == -1) {
+               return (-1);
+       }
+       if (if_indextoname(iface, ifname) == NULL) {
+               return (-1);
+       }
+
+       pp = pcap_open_live(ifname, 1500, 0, 0, __pcap_errbuf);
+       if (pp == NULL) {
+               return (-1);
+       }
+       err = pcap_set_datalink(pp, DLT_EN10MB);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_set_datalink: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       err = pcap_setdirection(pp, PCAP_D_IN);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_setdirection: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+
+       fd = pcap_get_selectable_fd(pp);
+       if (fd == -1) {
+               pcap_close(pp);
+               return (-1);
+       }
+#if 0
+       /* XXX: can we use this with pcap_next_ex() ? */
+       err = pcap_setnonblock(pp, 1, __pcap_errbuf);
+       if (err == -1) {
+               pcap_close(pp);
+               return (-1);
+       }
+#endif
+
+       err = pcap_compile(pp, &bpf,
+                          "arp and ether dst ff:ff:ff:ff:ff:ff", 1, 0);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_compile: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       err = pcap_setfilter(pp, &bpf);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_setfilter: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       pcap_freecode(&bpf);
+
+       /* Stash pcap-specific context away. */
+       memcpy(hw_address, __lladdr, ETHER_ADDRLEN);
+       __pp = pp;
+
+       return (fd);
+}
+
+static void
+close_socket(int fd __unused)
+{
+
+       assert(__pp != NULL);
+       pcap_close(__pp);
+       __pp = NULL;
+}
+
+/*
+ * We trick avahi into allocating sizeof(packet) + sizeof(ether_header),
+ * and prepend the required ethernet header information before sending.
+ */
+static int
+send_packet(int fd __unused, int iface __unused, ArpPacket *packet,
+    size_t packet_len)
+{
+       struct ether_header *eh;
+
+       assert(__pp != NULL);
+       assert(packet != NULL);
+
+       eh = (struct ether_header *)packet->ether_header;
+       memset(eh->ether_dhost, 0xFF, ETHER_ADDRLEN);
+       memcpy(eh->ether_shost, __lladdr, ETHER_ADDRLEN);
+       eh->ether_type = htons(0x0806);
+
+       return (pcap_inject(__pp, (void *)eh, packet_len + sizeof(*eh)));
+}
+
+static int
+recv_packet(int fd __unused, ArpPacket **packet, size_t *packet_len)
+{
+       struct pcap_pkthdr      *ph;
+       u_char                  *pd;
+       ArpPacket               *ap;
+       int                      err;
+       int                      retval;
+
+       assert(__pp != NULL);
+       assert(packet != NULL);
+       assert(packet_len != NULL);
+
+       *packet = NULL;
+       *packet_len = 0;
+       retval = -1;
+
+       err = pcap_next_ex(__pp, &ph, (const u_char **)&pd);
+       if (err == 1 && ph->caplen <= ph->len) {
+               ap = packet_new(ph->caplen);
+               memcpy(ap->ether_header, pd, ph->caplen);
+               *packet = ap;
+               *packet_len = (ph->caplen - sizeof(struct ether_header));
+               retval = 0;
+       } else {
+               if (err == 1) {
+                       daemon_log(LOG_ERR, "pcap len > caplen");
+               } else {
+                       daemon_log(LOG_ERR, "pcap_next_ex: %s",
+                           pcap_geterr(__pp));
+               }
+       }
+
+       return (retval);
+}
+#endif /* __linux__ */
+
 int is_ll_address(uint32_t addr) {
-    return (ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK;
+    return
+        ((ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK) &&
+        ((ntohl(addr) & 0x0000FF00) != 0x0000) &&
+        ((ntohl(addr) & 0x0000FF00) != 0xFF00);
 }
 
+
 static struct timeval *elapse_time(struct timeval *tv, unsigned msec, unsigned jitter) {
     assert(tv);
 
@@ -478,7 +806,7 @@ static FILE* fork_dispatcher(void) {
         
         setsid();
 
-        avahi_set_proc_title(argv0, "%s(%s): callout dispatcher", argv0, interface_name);
+        avahi_set_proc_title(argv0, "%s: [%s] callout dispatcher", argv0, interface_name);
 
         close(fds[1]);
 
@@ -527,6 +855,14 @@ static FILE* fork_dispatcher(void) {
 
         if (f)
             fclose(f);
+
+#ifdef HAVE_CHROOT
+        /* If the main process is trapped inside a chroot() we have to
+         * remove the PID file for it */
+        
+        if (!no_chroot && wrote_pid_file)
+            daemon_pid_file_remove();
+#endif
         
         _exit(r);
     }
@@ -573,6 +909,129 @@ static int do_callout(FILE *f, CalloutEvent event, int iface, uint32_t addr) {
     return 0;
 }
 
+#define set_env(key, value) putenv(avahi_strdup_printf("%s=%s", (key), (value)))
+
+static int drop_privs(void) {
+    struct passwd *pw;
+    struct group * gr;
+    int r;
+    mode_t u;
+
+    pw = NULL;
+    gr = NULL;
+
+    /* Get user/group ID */
+    
+    if (!no_drop_root) {
+    
+        if (!(pw = getpwnam(AVAHI_AUTOIPD_USER))) {
+            daemon_log(LOG_ERR, "Failed to find user '"AVAHI_AUTOIPD_USER"'.");
+            return -1;
+        }
+        
+        if (!(gr = getgrnam(AVAHI_AUTOIPD_GROUP))) {
+            daemon_log(LOG_ERR, "Failed to find group '"AVAHI_AUTOIPD_GROUP"'.");
+            return -1;
+        }
+        
+        daemon_log(LOG_INFO, "Found user '"AVAHI_AUTOIPD_USER"' (UID %lu) and group '"AVAHI_AUTOIPD_GROUP"' (GID %lu).", (unsigned long) pw->pw_uid, (unsigned long) gr->gr_gid);
+    }
+
+    /* Create directory */
+    u = umask(0000);
+    r = mkdir(AVAHI_IPDATA_DIR, 0755);
+    umask(u);
+    
+    if (r < 0 && errno != EEXIST) {
+        daemon_log(LOG_ERR, "mkdir(\""AVAHI_IPDATA_DIR"\"): %s", strerror(errno));
+        return -1;
+    }
+
+    /* Convey working directory */
+    
+    if (!no_drop_root) {
+        struct stat st;
+        
+        chown(AVAHI_IPDATA_DIR, pw->pw_uid, gr->gr_gid);
+        
+        if (stat(AVAHI_IPDATA_DIR, &st) < 0) {
+            daemon_log(LOG_ERR, "stat(): %s\n", strerror(errno));
+            return -1;
+        }
+        
+        if (!S_ISDIR(st.st_mode) || st.st_uid != pw->pw_uid || st.st_gid != gr->gr_gid) {
+            daemon_log(LOG_ERR, "Failed to create runtime directory "AVAHI_IPDATA_DIR".");
+            return -1;
+        }
+    }
+
+#ifdef HAVE_CHROOT
+
+    if (!no_chroot) {
+        if (chroot(AVAHI_IPDATA_DIR) < 0) {
+            daemon_log(LOG_ERR, "Failed to chroot(): %s", strerror(errno));
+            return -1;
+        }
+
+        daemon_log(LOG_INFO, "Successfully called chroot().");
+        chdir("/");
+
+        /* Since we are now trapped inside a chroot we cannot remove
+         * the pid file anymore, the helper process will do that for us. */
+        wrote_pid_file = 0;
+    }
+    
+#endif
+
+    if (!no_drop_root) {
+
+        if (initgroups(AVAHI_AUTOIPD_USER, gr->gr_gid) != 0) {
+            daemon_log(LOG_ERR, "Failed to change group list: %s", strerror(errno));
+            return -1;
+        }
+        
+#if defined(HAVE_SETRESGID)
+        r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid);
+#elif defined(HAVE_SETEGID)
+        if ((r = setgid(gr->gr_gid)) >= 0)
+            r = setegid(gr->gr_gid);
+#elif defined(HAVE_SETREGID)
+        r = setregid(gr->gr_gid, gr->gr_gid);
+#else
+#error "No API to drop priviliges"
+#endif
+
+        if (r < 0) {
+            daemon_log(LOG_ERR, "Failed to change GID: %s", strerror(errno));
+            return -1;
+        }
+        
+#if defined(HAVE_SETRESUID)
+        r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid);
+#elif defined(HAVE_SETEUID)
+        if ((r = setuid(pw->pw_uid)) >= 0)
+            r = seteuid(pw->pw_uid);
+#elif defined(HAVE_SETREUID)
+        r = setreuid(pw->pw_uid, pw->pw_uid);
+#else
+#error "No API to drop priviliges"
+#endif
+        
+        if (r < 0) {
+            daemon_log(LOG_ERR, "Failed to change UID: %s", strerror(errno));
+            return -1;
+        }
+
+        set_env("USER", pw->pw_name);
+        set_env("LOGNAME", pw->pw_name);
+        set_env("HOME", pw->pw_dir);
+        
+        daemon_log(LOG_INFO, "Successfully dropped root privileges.");
+    }
+    
+    return 0;
+}
+
 static int loop(int iface, uint32_t addr) {
     enum {
         FD_ARP,
@@ -585,17 +1044,19 @@ static int loop(int iface, uint32_t addr) {
     struct timeval next_wakeup;
     int next_wakeup_valid = 0;
     char buf[64];
-    void *in_packet = NULL;
+    ArpPacket *in_packet = NULL;
     size_t in_packet_len;
-    void *out_packet = NULL;
+    ArpPacket *out_packet = NULL;
     size_t out_packet_len;
     uint8_t hw_address[ETHER_ADDRLEN];
     struct pollfd pollfds[FD_MAX];
-    int iface_fd;
+    int iface_fd = -1;
     Event event = EVENT_NULL;
     int retval_sent = !daemonize;
     State st;
     FILE *dispatcher = NULL;
+    char *address_fn = NULL;
+    const char *p;
 
     daemon_signal_init(SIGINT, SIGTERM, SIGCHLD, SIGHUP,0);
 
@@ -608,16 +1069,32 @@ static int loop(int iface, uint32_t addr) {
     if ((iface_fd = iface_init(iface)) < 0)
         goto fail;
 
-/*     if (drop_privs() < 0) */
-/*         goto fail; */
+    if (drop_privs() < 0)
+        goto fail;
 
     if (force_bind)
         st = STATE_START;
     else if (iface_get_initial_state(&st) < 0)
         goto fail;
 
+#ifdef HAVE_CHROOT
+    if (!no_chroot)
+        p = "";
+    else
+#endif
+        p = AVAHI_IPDATA_DIR;
+    
+    address_fn = avahi_strdup_printf(
+            "%s/%02x:%02x:%02x:%02x:%02x:%02x", p,
+            hw_address[0], hw_address[1],
+            hw_address[2], hw_address[3],
+            hw_address[4], hw_address[5]);
+    
+    if (!addr)
+        load_address(address_fn, &addr);
+
     if (addr && !is_ll_address(addr)) {
-        daemon_log(LOG_WARNING, "Requested address %s is not from IPv4LL range 169.254/16, ignoring.", inet_ntop(AF_INET, &addr, buf, sizeof(buf)));
+        daemon_log(LOG_WARNING, "Requested address %s is not from IPv4LL range 169.254/16 or a reserved address, ignoring.", inet_ntop(AF_INET, &addr, buf, sizeof(buf)));
         addr = 0;
     }
 
@@ -697,11 +1174,6 @@ static int loop(int iface, uint32_t addr) {
                     goto fail;
                 
                 n_conflict = 0;
-
-                if (!retval_sent) {
-                    daemon_retval_send(0);
-                    retval_sent = 1;
-                }
             }
 
         } else if ((state == STATE_ANNOUNCING && event == EVENT_TIMEOUT && n_iteration >= ANNOUNCE_NUM-1)) {
@@ -710,7 +1182,14 @@ static int loop(int iface, uint32_t addr) {
             set_state(STATE_RUNNING, 0, addr);
 
             next_wakeup_valid = 0;
+
+            save_address(address_fn, addr);
             
+            if (!retval_sent) {
+                daemon_retval_send(0);
+                retval_sent = 1;
+            }
+
         } else if (event == EVENT_PACKET) {
             ArpPacketInfo info;
 
@@ -728,7 +1207,9 @@ static int loop(int iface, uint32_t addr) {
                 } else if (state == STATE_WAITING_PROBE || state == STATE_PROBING || state == STATE_WAITING_ANNOUNCE) {
                     /* Probe conflict */
                     conflict = info.target_ip_address == addr && memcmp(hw_address, info.sender_hw_address, ETHER_ADDRLEN);
-                    daemon_log(LOG_INFO, "Recieved conflicting probe ARP packet.");
+
+                    if (conflict)
+                        daemon_log(LOG_INFO, "Recieved conflicting probe ARP packet.");
                 }
 
                 if (conflict) {
@@ -837,7 +1318,7 @@ static int loop(int iface, uint32_t addr) {
                 if (pollfds[FD_ARP].revents == POLLERR) {
                     /* The interface is probably down, let's recreate our socket */
                     
-                    close(fd);
+                    close_socket(fd);
 
                     if ((fd = open_socket(iface, hw_address)) < 0)
                         goto fail;
@@ -907,7 +1388,7 @@ fail:
     avahi_free(in_packet);
     
     if (fd >= 0)
-        close(fd);
+        close_socket(fd);
 
     if (iface_fd >= 0)
         iface_done();
@@ -917,6 +1398,9 @@ fail:
 
     if (dispatcher)
         fclose(dispatcher);
+
+    if (address_fn)
+        avahi_free(address_fn);
     
     return ret;
 }
@@ -936,9 +1420,13 @@ static void help(FILE *f, const char *a0) {
             "                        169.254.0.0/16\n"
             "    -w --wait           Wait until an address has been acquired before\n"
             "                        daemonizing\n"
-            "       --no-proc-title  Don't modify process title\n"
-            "       --force-bind     Assign an IPv4LL address even if routable address\n"
+            "       --force-bind     Assign an IPv4LL address even if a routable address\n"
             "                        is already assigned\n"
+            "       --no-drop-root   Don't drop privileges\n"
+#ifdef HAVE_CHROOT            
+            "       --no-chroot      Don't chroot()\n"
+#endif            
+            "       --no-proc-title  Don't modify process title\n"
             "       --debug          Increase verbosity\n",
             a0);
 }
@@ -949,7 +1437,11 @@ static int parse_command_line(int argc, char *argv[]) {
     enum {
         OPTION_NO_PROC_TITLE = 256,
         OPTION_FORCE_BIND,
-        OPTION_DEBUG
+        OPTION_DEBUG,
+        OPTION_NO_DROP_ROOT,
+#ifdef HAVE_CHROOT
+        OPTION_NO_CHROOT
+#endif        
     };
     
     static const struct option long_options[] = {
@@ -962,13 +1454,16 @@ static int parse_command_line(int argc, char *argv[]) {
         { "version",       no_argument,       NULL, 'V' },
         { "start",         required_argument, NULL, 'S' },
         { "wait",          no_argument,       NULL, 'w' },
-        { "no-proc-title", no_argument,       NULL, OPTION_NO_PROC_TITLE },
         { "force-bind",    no_argument,       NULL, OPTION_FORCE_BIND },
+        { "no-drop-root",  no_argument,       NULL, OPTION_NO_DROP_ROOT },
+#ifdef HAVE_CHROOT            
+        { "no-chroot",     no_argument,       NULL, OPTION_NO_CHROOT },
+#endif        
+        { "no-proc-title", no_argument,       NULL, OPTION_NO_PROC_TITLE },
         { "debug",         no_argument,       NULL, OPTION_DEBUG },
         { NULL, 0, NULL, 0 }
     };
 
-    opterr = 0;
     while ((c = getopt_long(argc, argv, "hDskrcVS:w", long_options, NULL)) >= 0) {
 
         switch(c) {
@@ -1016,8 +1511,17 @@ static int parse_command_line(int argc, char *argv[]) {
                 force_bind = 1;
                 break;
 
+            case OPTION_NO_DROP_ROOT:
+                no_drop_root = 1;
+                break;
+
+#ifdef HAVE_CHROOT
+            case OPTION_NO_CHROOT:
+                no_chroot = 1;
+                break;
+#endif
+
             default:
-                fprintf(stderr, "Invalid command line argument: %c\n", c);
                 return -1;
         }
     }
@@ -1049,25 +1553,23 @@ static const char* pid_file_proc(void) {
 
 int main(int argc, char*argv[]) {
     int r = 1;
-    int wrote_pid_file = 0;
     char *log_ident = NULL;
 
-    avahi_init_proc_title(argc, argv);
-
     signal(SIGPIPE, SIG_IGN);
     
     if ((argv0 = strrchr(argv[0], '/')))
-        argv0++;
+        argv0 = avahi_strdup(argv0 + 1);
     else
-        argv0 = argv[0];
-
-    argv0 = avahi_strdup(argv0);
+        argv0 = avahi_strdup(argv[0]);
 
     daemon_log_ident = argv0;
     
     if (parse_command_line(argc, argv) < 0)
         goto finish;
 
+    if (modify_proc_title)
+        avahi_init_proc_title(argc, argv);
+
     daemon_log_ident = log_ident = avahi_strdup_printf("%s(%s)", argv0, interface_name);
     daemon_pid_file_proc = pid_file_proc;
     pid_file_name = avahi_strdup_printf(AVAHI_RUNTIME_DIR"/avahi-autoipd.%s.pid", interface_name);
@@ -1128,7 +1630,7 @@ int main(int argc, char*argv[]) {
         } else
             wrote_pid_file = 1;
 
-        avahi_set_proc_title(argv0, "%s(%s): starting up", argv0, interface_name);
+        avahi_set_proc_title(argv0, "%s: [%s] starting up", argv0, interface_name);
         
         if (loop(ifindex, start_address) < 0)
             goto finish;
@@ -1175,11 +1677,3 @@ finish:
 
     return r;
 }
-
-/* TODO:
-
-- chroot/drop privs/caps
-- store last used address
-- man page
-
-*/