Updated patches to work with the current trunk.
[rsync/rsync-patches.git] / slp.diff
1 This adds Service Location Protocol support.
2
3 To use this patch, run these commands for a successful build:
4
5     patch -p1 <patches/slp.diff
6     ./prepare-source
7     ./configure --enable-slp
8     make
9
10 TODO: the configure changes should abort if the user requests --enable-slp
11 and we can't honor that request.
12
13 diff --git a/Makefile.in b/Makefile.in
14 index feacb90..09e1547 100644
15 --- a/Makefile.in
16 +++ b/Makefile.in
17 @@ -13,6 +13,8 @@ CFLAGS=@CFLAGS@
18  CPPFLAGS=@CPPFLAGS@
19  EXEEXT=@EXEEXT@
20  LDFLAGS=@LDFLAGS@
21 +LIBSLP=@LIBSLP@
22 +SLPOBJ=@SLPOBJ@
23  
24  INSTALLCMD=@INSTALL@
25  INSTALLMAN=@INSTALL@
26 @@ -38,7 +40,7 @@ OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
27  OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
28         fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
29  OBJS3=progress.o pipe.o
30 -DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
31 +DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o $(SLPOBJ)
32  popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
33         popt/popthelp.o popt/poptparse.o
34  OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
35 @@ -74,7 +76,7 @@ install-strip:
36         $(MAKE) INSTALL_STRIP='-s' install
37  
38  rsync$(EXEEXT): $(OBJS)
39 -       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
40 +       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(LIBSLP)
41  
42  $(OBJS): $(HEADERS)
43  $(CHECK_OBJS): $(HEADERS)
44 diff --git a/clientserver.c b/clientserver.c
45 index b6afe00..a242809 100644
46 --- a/clientserver.c
47 +++ b/clientserver.c
48 @@ -1153,6 +1153,13 @@ int daemon_main(void)
49          * address too.  In fact, why not just do inet_ntop on the
50          * local address??? */
51  
52 +#ifdef HAVE_LIBSLP
53 +       if (register_services()) {
54 +               rprintf(FINFO,
55 +                   "Couldn't register with service discovery protocol, continuing anyway\n");
56 +       }
57 +#endif
58 +
59         start_accept_loop(rsync_port, start_daemon);
60         return -1;
61  }
62 diff --git a/configure.in b/configure.in
63 index bc7d4a7..f8dc177 100644
64 --- a/configure.in
65 +++ b/configure.in
66 @@ -647,6 +647,29 @@ if test $rsync_cv_can_hardlink_special = yes; then
67      AC_DEFINE(CAN_HARDLINK_SPECIAL, 1, [Define to 1 if link() can hard-link special files.])
68  fi
69  
70 +AC_ARG_ENABLE(slp, [  --disable-slp           turn off SLP support, defaults to on])
71 +AC_ARG_WITH(openslp-libs, [  --with-openslp-libs     set directory for OpenSLP library],
72 +    LDFLAGS="-L$withval $LDFLAGS"
73 +    DSOFLAGS="-L$withval $DSOFLAGS",)
74 +AC_ARG_WITH(openslp-includes, [  --with-openslp-includes set directory for OpenSLP includes],
75 +    CFLAGS="-I$withval $CFLAGS"
76 +    CXXFLAGS="-I$withval $CXXFLAGS"
77 +    CPPFLAGS="-I$withval $CPPFLAGS",)
78 +
79 +LIBSLP=""
80 +SLPOBJ=""
81 +
82 +if test x$enable_slp != xno; then
83 +    AC_CHECK_HEADER(slp.h,
84 +        AC_CHECK_LIB(slp, SLPOpen,
85 +           AC_DEFINE(HAVE_LIBSLP, 1, [Define to 1 for SLP support])
86 +           SLPOBJ="srvreg.o srvloc.o"
87 +            LIBSLP="-lslp"))
88 +fi
89 +
90 +AC_SUBST(LIBSLP)
91 +AC_SUBST(SLPOBJ)
92 +
93  AC_CACHE_CHECK([for working socketpair],rsync_cv_HAVE_SOCKETPAIR,[
94  AC_TRY_RUN([
95  #include <sys/types.h>
96 diff --git a/loadparm.c b/loadparm.c
97 index 8e48e6d..a833006 100644
98 --- a/loadparm.c
99 +++ b/loadparm.c
100 @@ -97,6 +97,9 @@ typedef struct {
101         char *socket_options;
102  
103         int rsync_port;
104 +#ifdef HAVE_LIBSLP
105 +       int slp_refresh;
106 +#endif
107  } global_vars;
108  
109  /* This structure describes a single section.  Their order must match the
110 @@ -311,6 +314,9 @@ static struct parm_struct parm_table[] =
111   {"motd file",         P_STRING, P_GLOBAL,&Vars.g.motd_file,           NULL,0},
112   {"pid file",          P_STRING, P_GLOBAL,&Vars.g.pid_file,            NULL,0},
113   {"port",              P_INTEGER,P_GLOBAL,&Vars.g.rsync_port,          NULL,0},
114 +#ifdef HAVE_LIBSLP
115 + {"slp refresh",       P_INTEGER,P_GLOBAL,&Vars.g.slp_refresh,         NULL,0},
116 +#endif
117   {"socket options",    P_STRING, P_GLOBAL,&Vars.g.socket_options,      NULL,0},
118  
119   {"auth users",        P_STRING, P_LOCAL, &Vars.l.auth_users,          NULL,0},
120 @@ -392,6 +398,9 @@ FN_GLOBAL_STRING(lp_pid_file, &Vars.g.pid_file)
121  FN_GLOBAL_STRING(lp_socket_options, &Vars.g.socket_options)
122  
123  FN_GLOBAL_INTEGER(lp_rsync_port, &Vars.g.rsync_port)
124 +#ifdef HAVE_LIBSLP
125 +FN_GLOBAL_INTEGER(lp_slp_refresh, &Vars.g.slp_refresh)
126 +#endif
127  
128  FN_LOCAL_STRING(lp_auth_users, auth_users)
129  FN_LOCAL_STRING(lp_charset, charset)
130 diff --git a/main.c b/main.c
131 index 2ef2f47..3535264 100644
132 --- a/main.c
133 +++ b/main.c
134 @@ -1195,6 +1195,18 @@ static int start_client(int argc, char *argv[])
135  
136         if (!read_batch) { /* for read_batch, NO source is specified */
137                 char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
138 +
139 +               if (shell_machine && !shell_machine[0]) {
140 +#ifdef HAVE_LIBSLP
141 +                       /* User entered just rsync:// URI */
142 +                       print_service_list();
143 +                       exit_cleanup(0);
144 +#else /* No SLP, die here */
145 +                       rprintf(FINFO, "No SLP support, cannot browse\n");
146 +                       exit_cleanup(RERR_SYNTAX);
147 +#endif
148 +               }
149 +
150                 if (path) { /* source is remote */
151                         char *dummy_host;
152                         int dummy_port = 0;
153 diff --git a/options.c b/options.c
154 index e7c6c61..dae5716 100644
155 --- a/options.c
156 +++ b/options.c
157 @@ -566,6 +566,7 @@ static void print_rsync_version(enum logcode f)
158         char const *links = "no ";
159         char const *iconv = "no ";
160         char const *ipv6 = "no ";
161 +       char const *slp = "no ";
162         STRUCT_STAT *dumstat;
163  
164  #if SUBPROTOCOL_VERSION != 0
165 @@ -599,6 +600,9 @@ static void print_rsync_version(enum logcode f)
166  #if defined HAVE_LUTIMES && defined HAVE_UTIMES
167         symtimes = "";
168  #endif
169 +#if HAVE_LIBSLP
170 +       slp = "";
171 +#endif
172  
173         rprintf(f, "%s  version %s  protocol version %d%s\n",
174                 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
175 @@ -612,8 +616,8 @@ static void print_rsync_version(enum logcode f)
176                 (int)(sizeof (int64) * 8));
177         rprintf(f, "    %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
178                 got_socketpair, hardlinks, links, ipv6, have_inplace);
179 -       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
180 -               have_inplace, acls, xattrs, iconv, symtimes);
181 +       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sSLP\n",
182 +               have_inplace, acls, xattrs, iconv, symtimes, slp);
183  
184  #ifdef MAINTAINER_MODE
185         rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
186 diff --git a/rsync.h b/rsync.h
187 index be7cf8a..8353124 100644
188 --- a/rsync.h
189 +++ b/rsync.h
190 @@ -194,6 +194,10 @@
191  #define SIGNIFICANT_ITEM_FLAGS (~(\
192         ITEM_BASIS_TYPE_FOLLOWS | ITEM_XNAME_FOLLOWS | ITEM_LOCAL_CHANGE))
193  
194 +/* this is the minimum we'll use, irrespective of config setting */
195 +/* definately don't set to less than about 30 seconds */
196 +#define SLP_MIN_TIMEOUT 120
197 +
198  #define CFN_KEEP_DOT_DIRS (1<<0)
199  #define CFN_KEEP_TRAILING_SLASH (1<<1)
200  #define CFN_DROP_TRAILING_DOT_DIR (1<<2)
201 diff --git a/rsync.yo b/rsync.yo
202 index 941f7a5..1518b3f 100644
203 --- a/rsync.yo
204 +++ b/rsync.yo
205 @@ -148,7 +148,12 @@ particular rsync daemon by leaving off the module name:
206  
207  quote(tt(rsync somehost.mydomain.com::))
208  
209 -See the following section for more details.
210 +And, if Service Location Protocol is available, the following will list the
211 +available rsync servers:
212 +
213 +quote(tt(rsync rsync://))
214 +
215 +See the following section for even more usage details.
216  
217  manpagesection(ADVANCED USAGE)
218  
219 diff --git a/rsyncd.conf b/rsyncd.conf
220 new file mode 100644
221 index 0000000..971723f
222 --- /dev/null
223 +++ b/rsyncd.conf
224 @@ -0,0 +1,3 @@
225 +
226 +slp refresh = 300
227 +
228 diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
229 index d4978cd..318f0ec 100644
230 --- a/rsyncd.conf.yo
231 +++ b/rsyncd.conf.yo
232 @@ -108,6 +108,15 @@ details on some of the options you may be able to set. By default no
233  special socket options are set.  These settings can also be specified
234  via the bf(--sockopts) command-line option.
235  
236 +dit(bf(slp refresh)) This parameter is used to determine how long service
237 +advertisements are valid (measured in seconds), and is only applicable if
238 +you have Service Location Protocol support compiled in. If this is
239 +not set or is set to zero, then service advertisements never time out. If
240 +this is set to less than 120 seconds, then 120 seconds is used. If it is
241 +set to more than 65535, then 65535 is used (which is a limitation of SLP).
242 +Using 3600 (one hour) is a good number if you tend to change your
243 +configuration.
244 +
245  enddit()
246  
247  manpagesection(MODULE PARAMETERS)
248 @@ -739,6 +748,7 @@ use chroot = yes
249  max connections = 4
250  syslog facility = local5
251  pid file = /var/run/rsyncd.pid
252 +slp refresh = 3600
253  
254  [ftp]
255          path = /var/ftp/./pub
256 diff --git a/socket.c b/socket.c
257 index 5df3a50..a4a2b0a 100644
258 --- a/socket.c
259 +++ b/socket.c
260 @@ -530,6 +530,16 @@ void start_accept_loop(int port, int (*fn)(int, int))
261  {
262         fd_set deffds;
263         int *sp, maxfd, i;
264 +#ifdef HAVE_LIBSLP
265 +       time_t next_slp_refresh;
266 +       short slp_timeout = lp_slp_refresh();
267 +       if (slp_timeout) {
268 +               if (slp_timeout < SLP_MIN_TIMEOUT)
269 +                       slp_timeout = SLP_MIN_TIMEOUT;
270 +               /* re-register before slp times out */
271 +               slp_timeout -= 15;
272 +       }
273 +#endif
274  
275  #ifdef HAVE_SIGACTION
276         sigact.sa_flags = SA_NOCLDSTOP;
277 @@ -558,14 +568,25 @@ void start_accept_loop(int port, int (*fn)(int, int))
278                         maxfd = sp[i];
279         }
280  
281 +#ifdef HAVE_LIBSLP
282 +       next_slp_refresh = time(NULL) + slp_timeout;
283 +#endif
284 +
285         /* now accept incoming connections - forking a new process
286          * for each incoming connection */
287         while (1) {
288                 fd_set fds;
289                 pid_t pid;
290                 int fd;
291 +               int sel_ret;
292                 struct sockaddr_storage addr;
293                 socklen_t addrlen = sizeof addr;
294 +#ifdef HAVE_LIBSLP
295 +               struct timeval slp_tv;
296 +
297 +               slp_tv.tv_sec = 10;
298 +               slp_tv.tv_usec = 0;
299 +#endif
300  
301                 /* close log file before the potentially very long select so
302                  * file can be trimmed by another process instead of growing
303 @@ -578,7 +599,18 @@ void start_accept_loop(int port, int (*fn)(int, int))
304                 fds = deffds;
305  #endif
306  
307 -               if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 1)
308 +#ifdef HAVE_LIBSLP
309 +               sel_ret = select(maxfd + 1, &fds, NULL, NULL,
310 +                                slp_timeout ? &slp_tv : NULL);
311 +               if (sel_ret == 0 && slp_timeout && time(NULL) > next_slp_refresh) {
312 +                       rprintf(FINFO, "Service registration expired, refreshing it\n");
313 +                       register_services();
314 +                       next_slp_refresh = time(NULL) + slp_timeout;
315 +               }
316 +#else
317 +               sel_ret = select(maxfd + 1, &fds, NULL, NULL, NULL);
318 +#endif
319 +               if (sel_ret < 1)
320                         continue;
321  
322                 for (i = 0, fd = -1; sp[i] >= 0; i++) {
323 diff --git a/srvloc.c b/srvloc.c
324 new file mode 100644
325 index 0000000..99eea16
326 --- /dev/null
327 +++ b/srvloc.c
328 @@ -0,0 +1,103 @@
329 +/* -*- c-file-style: "linux"; -*-
330 +
331 +   Copyright (C) 2002 by Brad Hards <bradh@frogmouth.net>
332 +
333 +   This program is free software; you can redistribute it and/or modify
334 +   it under the terms of the GNU General Public License as published by
335 +   the Free Software Foundation; either version 2 of the License, or
336 +   (at your option) any later version.
337 +
338 +   This program is distributed in the hope that it will be useful,
339 +   but WITHOUT ANY WARRANTY; without even the implied warranty of
340 +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
341 +   GNU General Public License for more details.
342 +
343 +   You should have received a copy of the GNU General Public License
344 +   along with this program; if not, write to the Free Software
345 +   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
346 +*/
347 +
348 +/* This file implements the service location functionality */
349 +/* Basically, it uses normal Service Location Protocol API */
350 +
351 +/* It is really a cheap hack - just to show how it might work
352 +   in a real application.
353 +*/
354 +
355 +#include "rsync.h"
356 +
357 +#include <slp.h>
358 +#include <stdio.h>
359 +#include <string.h>
360 +
361 +/* This one just prints out the attributes */
362 +static SLPBoolean getAttrCallback(UNUSED(SLPHandle hslp), const char *attrlist,
363 +                                 SLPError errcode, UNUSED(void *cookie))
364 +{
365 +       char *cleanstr;
366 +
367 +       if (errcode == SLP_OK) {
368 +               if (!strcmp(attrlist, "(comment=)"))
369 +                       rprintf(FINFO, "\t(No description)\n");
370 +               else {
371 +                       cleanstr = strrchr(attrlist, ')') ;
372 +                       *cleanstr = ' '; /* remove last ')' */
373 +                       rprintf(FINFO, "\t%s\n", strchr(attrlist, '=') + 1);
374 +               }
375 +       }
376 +       return SLP_FALSE;
377 +}
378 +
379 +static SLPBoolean getSLPSrvURLCallback(UNUSED(SLPHandle hslp),
380 +                       const char *srvurl, UNUSED(unsigned short lifetime),
381 +                       SLPError errcode, void *cookie)
382 +{
383 +       SLPError    result;
384 +       SLPHandle   attrhslp;
385 +
386 +       if (errcode == SLP_OK) {
387 +               /* chop service: off the front */
388 +               rprintf(FINFO, "  %s  ", (strchr(srvurl, ':') + 1));
389 +               /* check for any attributes */
390 +               if (SLPOpen("en", SLP_FALSE,&attrhslp) == SLP_OK) {
391 +                       result = SLPFindAttrs(attrhslp, srvurl,
392 +                                             "", /* return all attributes */
393 +                                             "", /* use configured scopes */
394 +                                             getAttrCallback, NULL);
395 +                       if (result != SLP_OK) {
396 +                               rprintf(FERROR, "errorcode: %i\n",result);
397 +                       }
398 +                       SLPClose(attrhslp);
399 +               }
400 +               *(SLPError*)cookie = SLP_OK;
401 +       } else
402 +               *(SLPError*)cookie = errcode;
403 +
404 +       /* Return SLP_TRUE because we want to be called again
405 +        * if more services were found. */
406 +
407 +       return SLP_TRUE;
408 +}
409 +
410 +int print_service_list(void)
411 +{
412 +       SLPError err;
413 +       SLPError callbackerr;
414 +       SLPHandle hslp;
415 +
416 +       err = SLPOpen("en",SLP_FALSE,&hslp);
417 +       if (err != SLP_OK) {
418 +               rprintf(FERROR, "Error opening slp handle %i\n", err);
419 +               return err;
420 +       }
421 +
422 +       SLPFindSrvs(hslp, "rsync",
423 +                   0, /* use configured scopes */
424 +                   0, /* no attr filter        */
425 +                   getSLPSrvURLCallback, &callbackerr);
426 +
427 +       /* Now that we're done using slp, close the slp handle */
428 +       SLPClose(hslp);
429 +
430 +       return 0;
431 +}
432 diff --git a/srvreg.c b/srvreg.c
433 new file mode 100644
434 index 0000000..04afe60
435 --- /dev/null
436 +++ b/srvreg.c
437 @@ -0,0 +1,128 @@
438 +/* -*- c-file-style: "linux"; -*-
439 +
440 +   Copyright (C) 2002 by Brad Hards <bradh@frogmouth.net>
441 +
442 +   This program is free software; you can redistribute it and/or modify
443 +   it under the terms of the GNU General Public License as published by
444 +   the Free Software Foundation; either version 2 of the License, or
445 +   (at your option) any later version.
446 +
447 +   This program is distributed in the hope that it will be useful,
448 +   but WITHOUT ANY WARRANTY; without even the implied warranty of
449 +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
450 +   GNU General Public License for more details.
451 +
452 +   You should have received a copy of the GNU General Public License
453 +   along with this program; if not, write to the Free Software
454 +   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
455 +*/
456 +
457 +/* This file implements the service registration functionality */
458 +
459 +/* Basically, it uses normal Service Location Protocol API */
460 +
461 +#include "rsync.h"
462 +#include "slp.h"
463 +#include "netdb.h"
464 +
465 +extern int rsync_port;
466 +
467 +static void slp_callback(UNUSED(SLPHandle hslp), SLPError errcode, void *cookie)
468 +{
469 +       /* return the error code in the cookie */
470 +       *(SLPError*)cookie = errcode;
471 +
472 +       /* You could do something else here like print out
473 +        * the errcode, etc.  Remember, as a general rule,
474 +        * do not try to do too much in a callback because
475 +        * it is being executed by the same thread that is
476 +        * reading slp packets from the wire. */
477 +}
478 +
479 +int register_services(void)
480 +{
481 +       SLPError err, callbackerr;
482 +       SLPHandle hslp;
483 +       int n;
484 +       int i;
485 +       char srv[120];
486 +       char attr[120];
487 +       char localhost[256];
488 +       extern char *config_file;
489 +       short timeout;
490 +       struct addrinfo aih, *ai = 0;
491 +
492 +       if (!lp_load(config_file, 0)) {
493 +               exit_cleanup(RERR_SYNTAX);
494 +       }
495 +
496 +       n = lp_numservices();
497 +
498 +       if (0 == lp_slp_refresh())
499 +               timeout = SLP_LIFETIME_MAXIMUM; /* don't expire, ever */
500 +       else if (SLP_MIN_TIMEOUT > lp_slp_refresh())
501 +               timeout = SLP_MIN_TIMEOUT; /* use a reasonable minimum */
502 +       else if (SLP_LIFETIME_MAXIMUM <= lp_slp_refresh())
503 +               timeout = (SLP_LIFETIME_MAXIMUM - 1); /* as long as possible */
504 +       else
505 +               timeout = lp_slp_refresh();
506 +
507 +       rprintf(FINFO, "rsyncd registering %d service%s with slpd for %d seconds:\n", n, ((n==1)? "":"s"), timeout);
508 +       err = SLPOpen("en",SLP_FALSE,&hslp);
509 +       if (err != SLP_OK) {
510 +               rprintf(FINFO, "Error opening slp handle %i\n",err);
511 +               return err;
512 +       }
513 +       if (gethostname(localhost, sizeof localhost)) {
514 +              rprintf(FINFO, "Could not get hostname: %s\n", strerror(errno));
515 +              return err;
516 +       }
517 +       memset(&aih, 0, sizeof aih);
518 +       aih.ai_family = PF_UNSPEC;
519 +       aih.ai_flags = AI_CANONNAME;
520 +       if (0 != (err = getaddrinfo(localhost, 0, &aih, &ai)) || !ai) {
521 +              rprintf(FINFO, "Could not resolve hostname: %s\n", gai_strerror(err));
522 +              return err;
523 +       }
524 +       /* Register each service with SLP */
525 +       for (i = 0; i < n; i++) {
526 +               if (!lp_list(i))
527 +                       continue;
528 +
529 +               snprintf(srv, sizeof srv, "service:rsync://%s:%d/%s",
530 +                        ai->ai_canonname,
531 +                        rsync_port,
532 +                        lp_name(i));
533 +               rprintf(FINFO, "    %s\n", srv);
534 +               if (lp_comment(i)) {
535 +                       snprintf(attr, sizeof attr, "(comment=%s)",
536 +                                lp_comment(i));
537 +               }
538 +               err = SLPReg(hslp,
539 +                            srv, /* service to register */
540 +                            timeout,
541 +                            0,  /* this is ignored */
542 +                            attr, /* attributes */
543 +                            SLP_TRUE, /* new registration - don't change this */
544 +                            slp_callback, /* callback */
545 +                            &callbackerr);
546 +
547 +               /* err may contain an error code that occurred as the slp library
548 +                * _prepared_ to make the call. */
549 +               if (err != SLP_OK || callbackerr != SLP_OK)
550 +                       rprintf(FINFO, "Error registering service with slp %i\n", err);
551 +
552 +               /* callbackerr may contain an error code (that was assigned through
553 +                * the callback cookie) that occurred as slp packets were sent on
554 +                * the wire. */
555 +               if (callbackerr != SLP_OK)
556 +                       rprintf(FINFO, "Error registering service with slp %i\n",callbackerr);
557 +       }
558 +
559 +       /* Now that we're done using slp, close the slp handle */
560 +       freeaddrinfo(ai);
561 +       SLPClose(hslp);
562 +
563 +       /* refresh is done in main select loop */
564 +       return 0;
565 +}