added a perl script to summarise the rsyncd log format
[rsync.git] / loadparm.c
1 /* This is based on loadparm.c from Samba, written by Andrew Tridgell
2    and Karl Auer */
3
4 /* 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14    
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 /*
21  *  Load parameters.
22  *
23  *  This module provides suitable callback functions for the params
24  *  module. It builds the internal table of service details which is
25  *  then used by the rest of the server.
26  *
27  * To add a parameter:
28  *
29  * 1) add it to the global or service structure definition
30  * 2) add it to the parm_table
31  * 3) add it to the list of available functions (eg: using FN_GLOBAL_STRING())
32  * 4) If it's a global then initialise it in init_globals. If a local
33  *    (ie. service) parameter then initialise it in the sDefault structure
34  *  
35  *
36  * Notes:
37  *   The configuration file is processed sequentially for speed. It is NOT
38  *   accessed randomly as happens in 'real' Windows. For this reason, there
39  *   is a fair bit of sequence-dependent code here - ie., code which assumes
40  *   that certain things happen before others. In particular, the code which
41  *   happens at the boundary between sections is delicately poised, so be
42  *   careful!
43  *
44  */
45
46 #include "rsync.h"
47 #define BOOL int
48 #define False 0
49 #define True 1
50 #define PTR_DIFF(p1,p2) ((ptrdiff_t)(((char *)(p1)) - (char *)(p2)))
51 #define strequal(a,b) (strcasecmp(a,b)==0)
52 #define BOOLSTR(b) ((b) ? "Yes" : "No")
53 typedef char pstring[1024];
54 #define pstrcpy(a,b) strlcpy(a,b,sizeof(pstring)-1)
55
56 /* the following are used by loadparm for option lists */
57 typedef enum
58 {
59         P_BOOL,P_BOOLREV,P_CHAR,P_INTEGER,P_OCTAL,
60         P_STRING,P_GSTRING,P_ENUM,P_SEP
61 } parm_type;
62
63 typedef enum
64 {
65         P_LOCAL,P_GLOBAL,P_SEPARATOR,P_NONE
66 } parm_class;
67
68 struct enum_list {
69         int value;
70         char *name;
71 };
72
73 struct parm_struct
74 {
75         char *label;
76         parm_type type;
77         parm_class class;
78         void *ptr;
79         struct enum_list *enum_list;
80         unsigned flags;
81 };
82
83 static BOOL bLoaded = False;
84
85 #ifndef GLOBAL_NAME
86 #define GLOBAL_NAME "global"
87 #endif
88
89 /* some helpful bits */
90 #define pSERVICE(i) ServicePtrs[i]
91 #define iSERVICE(i) (*pSERVICE(i))
92 #define LP_SNUM_OK(iService) (((iService) >= 0) && ((iService) < iNumServices))
93
94 /* 
95  * This structure describes global (ie., server-wide) parameters.
96  */
97 typedef struct
98 {
99         char *motd_file;
100         char *lock_file;
101         char *log_file;
102         char *pid_file;
103         int syslog_facility;
104         int max_connections;
105         char *socket_options;
106 } global;
107
108 static global Globals;
109
110
111
112 /* 
113  * This structure describes a single service. 
114  */
115 typedef struct
116 {
117         char *name;
118         char *path;
119         char *comment;
120         BOOL read_only;
121         BOOL list;
122         BOOL use_chroot;
123         BOOL transfer_logging;
124         char *uid;
125         char *gid;
126         char *hosts_allow;
127         char *hosts_deny;
128         char *auth_users;
129         char *secrets_file;
130         char *exclude;
131         char *exclude_from;
132         char *log_format;
133 } service;
134
135
136 /* This is a default service used to prime a services structure */
137 static service sDefault = 
138 {
139         NULL,    /* name */
140         NULL,    /* path */
141         NULL,    /* comment */
142         True,    /* read only */
143         True,    /* list */
144         True,    /* use chroot */
145         False,   /* transfer logging */
146         "nobody",/* uid */
147         "nobody",/* gid */
148         NULL,    /* hosts allow */
149         NULL,    /* hosts deny */
150         NULL,    /* auth users */
151         NULL,    /* secrets file */
152         NULL,    /* exclude */
153         NULL,    /* exclude from */
154         "%o %h [%a] %m (%u) %f %l",    /* log format */
155 };
156
157
158
159 /* local variables */
160 static service **ServicePtrs = NULL;
161 static int iNumServices = 0;
162 static int iServiceIndex = 0;
163 static BOOL bInGlobalSection = True;
164
165 #define NUMPARAMETERS (sizeof(parm_table) / sizeof(struct parm_struct))
166
167 static struct enum_list enum_facilities[] = {
168 #ifdef LOG_AUTH
169         { LOG_AUTH, "auth" },
170 #endif
171 #ifdef LOG_AUTHPRIV
172         { LOG_AUTHPRIV, "authpriv" },
173 #endif
174 #ifdef LOG_CRON
175         { LOG_CRON, "cron" },
176 #endif
177 #ifdef LOG_DAEMON
178         { LOG_DAEMON, "daemon" },
179 #endif
180 #ifdef LOG_FTP
181         { LOG_FTP, "ftp" },
182 #endif
183 #ifdef LOG_KERN
184         { LOG_KERN, "kern" },
185 #endif
186 #ifdef LOG_LPR
187         { LOG_LPR, "lpr" },
188 #endif
189 #ifdef LOG_MAIL
190         { LOG_MAIL, "mail" },
191 #endif
192 #ifdef LOG_NEWS
193         { LOG_NEWS, "news" },
194 #endif
195 #ifdef LOG_AUTH
196         { LOG_AUTH, "security" },               
197 #endif
198 #ifdef LOG_SYSLOG
199         { LOG_SYSLOG, "syslog" },
200 #endif
201 #ifdef LOG_USER
202         { LOG_USER, "user" },
203 #endif
204 #ifdef LOG_UUCP
205         { LOG_UUCP, "uucp" },
206 #endif
207 #ifdef LOG_LOCAL0
208         { LOG_LOCAL0, "local0" },
209 #endif
210 #ifdef LOG_LOCAL1
211         { LOG_LOCAL1, "local1" },
212 #endif
213 #ifdef LOG_LOCAL2
214         { LOG_LOCAL2, "local2" },
215 #endif
216 #ifdef LOG_LOCAL3
217         { LOG_LOCAL3, "local3" },
218 #endif
219 #ifdef LOG_LOCAL4
220         { LOG_LOCAL4, "local4" },
221 #endif
222 #ifdef LOG_LOCAL5
223         { LOG_LOCAL5, "local5" },
224 #endif
225 #ifdef LOG_LOCAL6
226         { LOG_LOCAL6, "local6" },
227 #endif
228 #ifdef LOG_LOCAL7
229         { LOG_LOCAL7, "local7" },
230 #endif
231         { -1, NULL }};
232
233
234 /* note that we do not initialise the defaults union - it is not allowed in ANSI C */
235 static struct parm_struct parm_table[] =
236 {
237   {"max connections",  P_INTEGER, P_GLOBAL, &Globals.max_connections,NULL, 0},
238   {"motd file",        P_STRING,  P_GLOBAL, &Globals.motd_file,    NULL,   0},
239   {"lock file",        P_STRING,  P_GLOBAL, &Globals.lock_file,    NULL,   0},
240   {"syslog facility",  P_ENUM,    P_GLOBAL, &Globals.syslog_facility, enum_facilities,0},
241   {"socket options",   P_STRING,  P_GLOBAL, &Globals.socket_options,NULL,  0},
242   {"log file",         P_STRING,  P_GLOBAL, &Globals.log_file,      NULL,  0},
243   {"pid file",         P_STRING,  P_GLOBAL, &Globals.pid_file,      NULL,  0},
244
245   {"name",             P_STRING,  P_LOCAL,  &sDefault.name,        NULL,   0},
246   {"comment",          P_STRING,  P_LOCAL,  &sDefault.comment,     NULL,   0},
247   {"path",             P_STRING,  P_LOCAL,  &sDefault.path,        NULL,   0},
248   {"read only",        P_BOOL,    P_LOCAL,  &sDefault.read_only,   NULL,   0},
249   {"list",             P_BOOL,    P_LOCAL,  &sDefault.list,        NULL,   0},
250   {"use chroot",       P_BOOL,    P_LOCAL,  &sDefault.use_chroot,  NULL,   0},
251   {"uid",              P_STRING,  P_LOCAL,  &sDefault.uid,         NULL,   0},
252   {"gid",              P_STRING,  P_LOCAL,  &sDefault.gid,         NULL,   0},
253   {"hosts allow",      P_STRING,  P_LOCAL,  &sDefault.hosts_allow, NULL,   0},
254   {"hosts deny",       P_STRING,  P_LOCAL,  &sDefault.hosts_deny,  NULL,   0},
255   {"auth users",       P_STRING,  P_LOCAL,  &sDefault.auth_users,  NULL,   0},
256   {"secrets file",     P_STRING,  P_LOCAL,  &sDefault.secrets_file,NULL,   0},
257   {"exclude",          P_STRING,  P_LOCAL,  &sDefault.exclude,     NULL,   0},
258   {"exclude from",     P_STRING,  P_LOCAL,  &sDefault.exclude_from,NULL,   0},
259   {"transfer logging", P_BOOL,    P_LOCAL,  &sDefault.transfer_logging,NULL,0},
260   {"log format",       P_STRING,  P_LOCAL,  &sDefault.log_format,  NULL,   0},
261   {NULL,               P_BOOL,    P_NONE,   NULL,                  NULL,   0}
262 };
263
264
265 /***************************************************************************
266 Initialise the global parameter structure.
267 ***************************************************************************/
268 static void init_globals(void)
269 {
270         memset(&Globals, 0, sizeof(Globals));
271 #ifdef LOG_DAEMON
272         Globals.syslog_facility = LOG_DAEMON;
273 #endif
274         Globals.lock_file = "/var/run/rsyncd.lock";
275 }
276
277 /***************************************************************************
278 Initialise the sDefault parameter structure.
279 ***************************************************************************/
280 static void init_locals(void)
281 {
282 }
283
284
285 /*
286    In this section all the functions that are used to access the 
287    parameters from the rest of the program are defined 
288 */
289
290 #define FN_GLOBAL_STRING(fn_name,ptr) \
291  char *fn_name(void) {return(*(char **)(ptr) ? *(char **)(ptr) : "");}
292 #define FN_GLOBAL_BOOL(fn_name,ptr) \
293  BOOL fn_name(void) {return(*(BOOL *)(ptr));}
294 #define FN_GLOBAL_CHAR(fn_name,ptr) \
295  char fn_name(void) {return(*(char *)(ptr));}
296 #define FN_GLOBAL_INTEGER(fn_name,ptr) \
297  int fn_name(void) {return(*(int *)(ptr));}
298
299 #define FN_LOCAL_STRING(fn_name,val) \
300  char *fn_name(int i) {return((LP_SNUM_OK(i)&&pSERVICE(i)->val)?pSERVICE(i)->val : (sDefault.val?sDefault.val:""));}
301 #define FN_LOCAL_BOOL(fn_name,val) \
302  BOOL fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);}
303 #define FN_LOCAL_CHAR(fn_name,val) \
304  char fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);}
305 #define FN_LOCAL_INTEGER(fn_name,val) \
306  int fn_name(int i) {return(LP_SNUM_OK(i)? pSERVICE(i)->val : sDefault.val);}
307
308
309 FN_GLOBAL_STRING(lp_motd_file, &Globals.motd_file)
310 FN_GLOBAL_STRING(lp_lock_file, &Globals.lock_file)
311 FN_GLOBAL_STRING(lp_log_file, &Globals.log_file)
312 FN_GLOBAL_STRING(lp_pid_file, &Globals.pid_file)
313 FN_GLOBAL_STRING(lp_socket_options, &Globals.socket_options)
314 FN_GLOBAL_INTEGER(lp_max_connections, &Globals.max_connections)
315 FN_GLOBAL_INTEGER(lp_syslog_facility, &Globals.syslog_facility)
316
317 FN_LOCAL_STRING(lp_name, name)
318 FN_LOCAL_STRING(lp_comment, comment)
319 FN_LOCAL_STRING(lp_path, path)
320 FN_LOCAL_BOOL(lp_read_only, read_only)
321 FN_LOCAL_BOOL(lp_list, list)
322 FN_LOCAL_BOOL(lp_use_chroot, use_chroot)
323 FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging)
324 FN_LOCAL_STRING(lp_uid, uid)
325 FN_LOCAL_STRING(lp_gid, gid)
326 FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
327 FN_LOCAL_STRING(lp_hosts_deny, hosts_deny)
328 FN_LOCAL_STRING(lp_auth_users, auth_users)
329 FN_LOCAL_STRING(lp_secrets_file, secrets_file)
330 FN_LOCAL_STRING(lp_exclude, exclude)
331 FN_LOCAL_STRING(lp_exclude_from, exclude_from)
332 FN_LOCAL_STRING(lp_log_format, log_format)
333
334 /* local prototypes */
335 static int    strwicmp( char *psz1, char *psz2 );
336 static int    map_parameter( char *parmname);
337 static BOOL   set_boolean( BOOL *pb, char *parmvalue );
338 static int    getservicebyname(char *name, service *pserviceDest);
339 static void   copy_service( service *pserviceDest, 
340                             service *pserviceSource);
341 static BOOL   do_parameter(char *parmname, char *parmvalue);
342 static BOOL   do_section(char *sectionname);
343
344
345 /***************************************************************************
346 initialise a service to the defaults
347 ***************************************************************************/
348 static void init_service(service *pservice)
349 {
350         memset((char *)pservice,0,sizeof(service));
351         copy_service(pservice,&sDefault);
352 }
353
354 static void string_set(char **s, char *v)
355 {
356         if (!v) {
357                 *s = NULL;
358                 return;
359         }
360         *s = strdup(v);
361         if (!*s) exit_cleanup(1);
362 }
363
364
365 /***************************************************************************
366 add a new service to the services array initialising it with the given 
367 service
368 ***************************************************************************/
369 static int add_a_service(service *pservice, char *name)
370 {
371   int i;
372   service tservice;
373   int num_to_alloc = iNumServices+1;
374
375   tservice = *pservice;
376
377   /* it might already exist */
378   if (name) 
379     {
380       i = getservicebyname(name,NULL);
381       if (i >= 0)
382         return(i);
383     }
384
385   i = iNumServices;
386
387   ServicePtrs = (service **)Realloc(ServicePtrs,sizeof(service *)*num_to_alloc);
388
389   if (ServicePtrs)
390           pSERVICE(iNumServices) = (service *)malloc(sizeof(service));
391
392   if (!ServicePtrs || !pSERVICE(iNumServices))
393           return(-1);
394
395   iNumServices++;
396
397   init_service(pSERVICE(i));
398   copy_service(pSERVICE(i),&tservice);
399   if (name)
400     string_set(&iSERVICE(i).name,name);  
401
402   return(i);
403 }
404
405 /***************************************************************************
406 Do a case-insensitive, whitespace-ignoring string compare.
407 ***************************************************************************/
408 static int strwicmp(char *psz1, char *psz2)
409 {
410    /* if BOTH strings are NULL, return TRUE, if ONE is NULL return */
411    /* appropriate value. */
412    if (psz1 == psz2)
413       return (0);
414    else
415       if (psz1 == NULL)
416          return (-1);
417       else
418           if (psz2 == NULL)
419               return (1);
420
421    /* sync the strings on first non-whitespace */
422    while (1)
423    {
424       while (isspace(*psz1))
425          psz1++;
426       while (isspace(*psz2))
427          psz2++;
428       if (toupper(*psz1) != toupper(*psz2) || *psz1 == '\0' || *psz2 == '\0')
429          break;
430       psz1++;
431       psz2++;
432    }
433    return (*psz1 - *psz2);
434 }
435
436 /***************************************************************************
437 Map a parameter's string representation to something we can use. 
438 Returns False if the parameter string is not recognised, else TRUE.
439 ***************************************************************************/
440 static int map_parameter(char *parmname)
441 {
442    int iIndex;
443
444    if (*parmname == '-')
445      return(-1);
446
447    for (iIndex = 0; parm_table[iIndex].label; iIndex++) 
448       if (strwicmp(parm_table[iIndex].label, parmname) == 0)
449          return(iIndex);
450
451    rprintf(FERROR, "Unknown parameter encountered: \"%s\"\n", parmname);
452    return(-1);
453 }
454
455
456 /***************************************************************************
457 Set a boolean variable from the text value stored in the passed string.
458 Returns True in success, False if the passed string does not correctly 
459 represent a boolean.
460 ***************************************************************************/
461 static BOOL set_boolean(BOOL *pb, char *parmvalue)
462 {
463    BOOL bRetval;
464
465    bRetval = True;
466    if (strwicmp(parmvalue, "yes") == 0 ||
467        strwicmp(parmvalue, "true") == 0 ||
468        strwicmp(parmvalue, "1") == 0)
469       *pb = True;
470    else
471       if (strwicmp(parmvalue, "no") == 0 ||
472           strwicmp(parmvalue, "False") == 0 ||
473           strwicmp(parmvalue, "0") == 0)
474          *pb = False;
475       else
476       {
477          rprintf(FERROR, "Badly formed boolean in configuration file: \"%s\".\n",
478                parmvalue);
479          bRetval = False;
480       }
481    return (bRetval);
482 }
483
484 /***************************************************************************
485 Find a service by name. Otherwise works like get_service.
486 ***************************************************************************/
487 static int getservicebyname(char *name, service *pserviceDest)
488 {
489    int iService;
490
491    for (iService = iNumServices - 1; iService >= 0; iService--)
492       if (strwicmp(iSERVICE(iService).name, name) == 0) 
493       {
494          if (pserviceDest != NULL)
495            copy_service(pserviceDest, pSERVICE(iService));
496          break;
497       }
498
499    return (iService);
500 }
501
502
503
504 /***************************************************************************
505 Copy a service structure to another
506
507 ***************************************************************************/
508 static void copy_service(service *pserviceDest, 
509                          service *pserviceSource)
510 {
511   int i;
512
513   for (i=0;parm_table[i].label;i++)
514     if (parm_table[i].ptr && parm_table[i].class == P_LOCAL) {
515         void *def_ptr = parm_table[i].ptr;
516         void *src_ptr = 
517           ((char *)pserviceSource) + PTR_DIFF(def_ptr,&sDefault);
518         void *dest_ptr = 
519           ((char *)pserviceDest) + PTR_DIFF(def_ptr,&sDefault);
520
521         switch (parm_table[i].type)
522           {
523           case P_BOOL:
524           case P_BOOLREV:
525             *(BOOL *)dest_ptr = *(BOOL *)src_ptr;
526             break;
527
528           case P_INTEGER:
529           case P_ENUM:
530           case P_OCTAL:
531             *(int *)dest_ptr = *(int *)src_ptr;
532             break;
533
534           case P_CHAR:
535             *(char *)dest_ptr = *(char *)src_ptr;
536             break;
537
538           case P_STRING:
539             string_set(dest_ptr,*(char **)src_ptr);
540             break;
541
542           default:
543             break;
544           }
545       }
546 }
547
548
549 /***************************************************************************
550 Process a parameter for a particular service number. If snum < 0
551 then assume we are in the globals
552 ***************************************************************************/
553 static BOOL lp_do_parameter(int snum, char *parmname, char *parmvalue)
554 {
555    int parmnum, i;
556    void *parm_ptr=NULL; /* where we are going to store the result */
557    void *def_ptr=NULL;
558
559    parmnum = map_parameter(parmname);
560
561    if (parmnum < 0)
562      {
563        rprintf(FERROR, "Ignoring unknown parameter \"%s\"\n", parmname);
564        return(True);
565      }
566
567    def_ptr = parm_table[parmnum].ptr;
568
569    /* we might point at a service, the default service or a global */
570    if (snum < 0) {
571      parm_ptr = def_ptr;
572    } else {
573        if (parm_table[parmnum].class == P_GLOBAL) {
574            rprintf(FERROR, "Global parameter %s found in service section!\n",parmname);
575            return(True);
576          }
577        parm_ptr = ((char *)pSERVICE(snum)) + PTR_DIFF(def_ptr,&sDefault);
578    }
579
580    /* now switch on the type of variable it is */
581    switch (parm_table[parmnum].type)
582      {
583      case P_BOOL:
584        set_boolean(parm_ptr,parmvalue);
585        break;
586
587      case P_BOOLREV:
588        set_boolean(parm_ptr,parmvalue);
589        *(BOOL *)parm_ptr = ! *(BOOL *)parm_ptr;
590        break;
591
592      case P_INTEGER:
593        *(int *)parm_ptr = atoi(parmvalue);
594        break;
595
596      case P_CHAR:
597        *(char *)parm_ptr = *parmvalue;
598        break;
599
600      case P_OCTAL:
601        sscanf(parmvalue,"%o",(int *)parm_ptr);
602        break;
603
604      case P_STRING:
605        string_set(parm_ptr,parmvalue);
606        break;
607
608      case P_GSTRING:
609        strlcpy((char *)parm_ptr,parmvalue,sizeof(pstring)-1);
610        break;
611
612      case P_ENUM:
613              for (i=0;parm_table[parmnum].enum_list[i].name;i++) {
614                      if (strequal(parmvalue, parm_table[parmnum].enum_list[i].name)) {
615                              *(int *)parm_ptr = parm_table[parmnum].enum_list[i].value;
616                              break;
617                      }
618              }
619              if (!parm_table[parmnum].enum_list[i].name) {
620                      if (atoi(parmvalue) > 0)
621                              *(int *)parm_ptr = atoi(parmvalue);
622              }
623              break;
624      case P_SEP:
625              break;
626      }
627
628    return(True);
629 }
630
631 /***************************************************************************
632 Process a parameter.
633 ***************************************************************************/
634 static BOOL do_parameter(char *parmname, char *parmvalue)
635 {
636    return lp_do_parameter(bInGlobalSection?-2:iServiceIndex, parmname, parmvalue);
637 }
638
639 /***************************************************************************
640 Process a new section (service). At this stage all sections are services.
641 Later we'll have special sections that permit server parameters to be set.
642 Returns True on success, False on failure.
643 ***************************************************************************/
644 static BOOL do_section(char *sectionname)
645 {
646    BOOL bRetval;
647    BOOL isglobal = (strwicmp(sectionname, GLOBAL_NAME) == 0);
648    bRetval = False;
649
650    /* if we were in a global section then do the local inits */
651    if (bInGlobalSection && !isglobal)
652      init_locals();
653
654    /* if we've just struck a global section, note the fact. */
655    bInGlobalSection = isglobal;   
656
657    /* check for multiple global sections */
658    if (bInGlobalSection)
659    {
660      return(True);
661    }
662
663    /* if we have a current service, tidy it up before moving on */
664    bRetval = True;
665
666    if (iServiceIndex >= 0)
667      bRetval = True;
668
669    /* if all is still well, move to the next record in the services array */
670    if (bRetval)
671      {
672        /* We put this here to avoid an odd message order if messages are */
673        /* issued by the post-processing of a previous section. */
674
675        if ((iServiceIndex=add_a_service(&sDefault,sectionname)) < 0)
676          {
677            rprintf(FERROR,"Failed to add a new service\n");
678            return(False);
679          }
680      }
681
682    return (bRetval);
683 }
684
685
686 /***************************************************************************
687 Load the services array from the services file. Return True on success, 
688 False on failure.
689 ***************************************************************************/
690 BOOL lp_load(char *pszFname, int globals_only)
691 {
692         pstring n2;
693         BOOL bRetval;
694  
695         bRetval = False;
696
697         bInGlobalSection = True;
698   
699         init_globals();
700
701         pstrcpy(n2,pszFname);
702
703         /* We get sections first, so have to start 'behind' to make up */
704         iServiceIndex = -1;
705         bRetval = pm_process(n2, globals_only?NULL:do_section, do_parameter);
706   
707         bLoaded = True;
708
709         return (bRetval);
710 }
711
712
713 /***************************************************************************
714 return the max number of services
715 ***************************************************************************/
716 int lp_numservices(void)
717 {
718   return(iNumServices);
719 }
720
721 /***************************************************************************
722 Return the number of the service with the given name, or -1 if it doesn't
723 exist. Note that this is a DIFFERENT ANIMAL from the internal function
724 getservicebyname()! This works ONLY if all services have been loaded, and
725 does not copy the found service.
726 ***************************************************************************/
727 int lp_number(char *name)
728 {
729    int iService;
730
731    for (iService = iNumServices - 1; iService >= 0; iService--)
732       if (strequal(lp_name(iService), name)) 
733          break;
734
735    return (iService);
736 }
737