r7595: start trying to split out the svcctl functions into separate files for better...
[kai/samba.git] / source3 / services / services_db.c
1 /* 
2  *  Unix SMB/CIFS implementation.
3  *  Service Control API Implementation
4  *  Copyright (C) Gerald Carter                   2005.
5  *  Copyright (C) Marcin Krzysztof Porwit         2005.
6  *  
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *  
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *  
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "includes.h"
23
24 /* backend database routines for services.tdb */
25
26 #define SERVICEDB_VERSION_V1 1 /* Will there be more? */
27 #define INTERNAL_SERVICES_LIST "NETLOGON Spooler"
28
29 /*                                                                                                                     */
30 /* scripts will execute from the following libdir, if they are in the enable svcctl=<list of scripts>                  */
31 /* these should likely be symbolic links. Note that information about them will be extracted from the files themselves */
32 /* using the LSB standard keynames for various information                                                             */
33
34 #define SCVCTL_DATABASE_VERSION_V1 1
35 static TDB_CONTEXT *service_tdb; /* used for services tdb file */
36
37 /* there are two types of services -- internal, and external.
38    Internal services are "built-in" to samba -- there may be 
39    functions that exist to provide the control and enumeration 
40    functions.  There certainly is information returned to be 
41    displayed in the typical management console.
42
43    External services are those that can be specified in the smb.conf 
44    file -- and they conform to the LSB specification as to having 
45    particular keywords in the scripts. Note that these "scripts" are 
46    located in the lib directory, and are likely links to LSB-compliant 
47    init.d scripts, such as those that might come with Suse. Note 
48    that the spec is located  http://www.linuxbase.org/spec/ */
49
50
51
52 /* Expand this to include what can and can't be done 
53    with a particular internal service. Expand as necessary 
54    to add other infromation like what can be controlled, 
55    etc. */
56
57 typedef struct Internal_service_struct
58 {
59         const char *filename;           /* internal name "index" */
60         const char *displayname;
61         const char *description;
62         const uint32 statustype;
63         void *status_fn; 
64         void *control_fn;
65 } Internal_service_description;
66
67
68 static const Internal_service_description ISD[] = {
69         { "NETLOGON",   "Net Logon",    "Provides logon and authentication service to the network",     0x110,  NULL, NULL},
70         { "Spooler",    "Spooler",      "Printing Services",                                            0x0020, NULL, NULL},
71         { NULL, NULL, NULL, 0, NULL, NULL}
72 };
73
74
75 /********************************************************************
76  allocate an array of external services and return them. Null return 
77  is okay, make sure &added is also zero! 
78 ********************************************************************/
79
80 int num_external_services(void)
81 {
82         int num_services;
83         char **svc_list;
84         pstring keystring, external_services_string;
85         TDB_DATA key_data;
86
87
88         if (!service_tdb) {
89                 DEBUG(8,("enum_external_services: service database is not open!!!\n"));
90                 num_services = 0;
91         } else {
92                 pstrcpy(keystring,"EXTERNAL_SERVICES");
93                 tdb_lock_bystring(service_tdb, keystring, 0);
94                 key_data = tdb_fetch_bystring(service_tdb, keystring);
95
96                 if ((key_data.dptr != NULL) && (key_data.dsize != 0)) {
97                         strncpy(external_services_string,key_data.dptr,key_data.dsize);
98                         external_services_string[key_data.dsize] = 0;
99                         DEBUG(8,("enum_external_services: services list is %s, size is %d\n",external_services_string,key_data.dsize));
100                 }
101                 tdb_unlock_bystring(service_tdb, keystring);
102         } 
103         svc_list = str_list_make(external_services_string,NULL);
104  
105         num_services = str_list_count( (const char **)svc_list);
106
107         return num_services;
108 }
109
110
111
112 /********************************************************************
113   Gather information on the "external services". These are services 
114   listed in the smb.conf file, and found to exist through checks in 
115   this code. Note that added will be incremented on the basis of the 
116   number of services added.  svc_ptr should have enough memory allocated 
117   to accommodate all of the services that exist. 
118
119   Typically num_external_services is used to "size" the amount of
120   memory allocated, but does little/no work. 
121
122   enum_external_services() actually examines each of the specified 
123   external services, populates the memory structures, and returns.
124
125   ** note that 'added' may end up with less than the number of services 
126   found in _num_external_services, such as the case when a service is
127   called out, but the actual service doesn't exist or the file can't be 
128   read for the service information.
129 ********************************************************************/
130
131 WERROR enum_external_services(TALLOC_CTX *tcx,ENUM_SERVICES_STATUS **svc_ptr, int existing_services,int *added) 
132 {
133         /* *svc_ptr must have pre-allocated memory */
134         int num_services = 0;
135         int i = 0;
136         ENUM_SERVICES_STATUS *services=NULL;
137         char **svc_list,**svcname;
138         pstring command, keystring, external_services_string;
139         int ret;
140         int fd = -1;
141         Service_info *si;
142         TDB_DATA key_data;
143
144         *added = num_services;
145
146         if (!service_tdb) {
147                 DEBUG(8,("enum_external_services: service database is not open!!!\n"));
148         } else {
149                 pstrcpy(keystring,"EXTERNAL_SERVICES");
150                 tdb_lock_bystring(service_tdb, keystring, 0);
151                 key_data = tdb_fetch_bystring(service_tdb, keystring);
152                 if ((key_data.dptr != NULL) && (key_data.dsize != 0)) {
153                         strncpy(external_services_string,key_data.dptr,key_data.dsize);
154                         external_services_string[key_data.dsize] = 0;
155                         DEBUG(8,("enum_external_services: services list is %s, size is %d\n",external_services_string,key_data.dsize));
156                 }
157                 tdb_unlock_bystring(service_tdb, keystring);
158         } 
159         svc_list = str_list_make(external_services_string,NULL);
160  
161         num_services = str_list_count( (const char **)svc_list);
162
163         if (0 == num_services) {
164                 DEBUG(8,("enum_external_services: there are no external services\n"));
165                 *added = num_services;
166                 return WERR_OK;
167         }
168         DEBUG(8,("enum_external_services: there are [%d] external services\n",num_services));
169         si=TALLOC_ARRAY( tcx, Service_info, 1 );
170         if (si == NULL) { 
171                 DEBUG(8,("enum_external_services: Failed to alloc si\n"));
172                 return WERR_NOMEM;
173         }
174
175 #if 0
176 /* *svc_ptr has the pointer to the array if there is one already. NULL if not. */
177         if ((existing_services>0) && svc_ptr && *svc_ptr) { /* reallocate vs. allocate */
178                 DEBUG(8,("enum_external_services: REALLOCing %x to %d services\n", *svc_ptr, existing_services+num_services));
179
180                 services=TALLOC_REALLOC_ARRAY(tcx,*svc_ptr,ENUM_SERVICES_STATUS,existing_services+num_services);
181                 DEBUG(8,("enum_external_services: REALLOCed to %x services\n", services));
182
183                 if (!services) return WERR_NOMEM;
184                         *svc_ptr = services;
185         } else {
186                 if ( !(services = TALLOC_ARRAY( tcx, ENUM_SERVICES_STATUS, num_services )) )
187                         return WERR_NOMEM;
188         }
189 #endif
190
191         if (!svc_ptr || !(*svc_ptr)) 
192                 return WERR_NOMEM;
193         services = *svc_ptr;
194         if (existing_services > 0) {
195                 i+=existing_services;
196         }
197
198         svcname = svc_list;
199         DEBUG(8,("enum_external_services: enumerating %d external services starting at index %d\n", num_services,existing_services));
200
201         while (*svcname) {
202                 DEBUG(10,("enum_external_services: Reading information on service %s, index %d\n",*svcname,i));
203                 /* get_LSB_data(*svcname,si);  */
204                 if (!get_service_info(service_tdb,*svcname, si)) {
205                         DEBUG(1,("enum_external_services: CAN'T FIND INFO FOR SERVICE %s in the services DB\n",*svcname));
206                 }
207
208                 if ((si->filename == NULL) || (*si->filename == 0)) {
209                         init_unistr(&services[i].servicename, *svcname );
210                 } else {
211                         init_unistr( &services[i].servicename, si->filename );    
212                         /* init_unistr( &services[i].servicename, si->servicename ); */
213                 }
214
215                 if ((si->provides == NULL) || (*si->provides == 0)) {
216                         init_unistr(&services[i].displayname, *svcname );
217                 } else {
218                         init_unistr( &services[i].displayname, si->provides );
219                 }
220
221                 /* TODO - we could keep the following info in the DB, too... */
222
223                 DEBUG(8,("enum_external_services: Service name [%s] displayname [%s]\n",
224                 si->filename, si->provides)); 
225                 services[i].status.type               = SVCCTL_WIN32_OWN_PROC; 
226                 services[i].status.win32_exit_code    = 0x0;
227                 services[i].status.service_exit_code  = 0x0;
228                 services[i].status.check_point        = 0x0;
229                 services[i].status.wait_hint          = 0x0;
230
231                 /* TODO - do callout here to get the status */
232
233                 memset(command, 0, sizeof(command));
234                 slprintf(command, sizeof(command)-1, "%s%s%s %s", dyn_LIBDIR, SVCCTL_SCRIPT_DIR, *svcname, "status");
235
236                 DEBUG(10, ("enum_external_services: status command is [%s]\n", command));
237
238                 /* TODO  - wrap in privilege check */
239
240                 ret = smbrun(command, &fd);
241                 DEBUGADD(10, ("returned [%d]\n", ret));
242                 close(fd);
243                 if(ret != 0)
244                         DEBUG(10, ("enum_external_services: Command returned  [%d]\n", ret));
245                 services[i].status.state              = SVCCTL_STOPPED;
246                 if (ret == 0) {
247                         services[i].status.state              = SVCCTL_RUNNING;
248                         services[i].status.controls_accepted  = SVCCTL_CONTROL_SHUTDOWN | SVCCTL_CONTROL_STOP;
249                 } else {
250                         services[i].status.state              = SVCCTL_STOPPED;
251                         services[i].status.controls_accepted  = 0;
252                 }
253                 svcname++; 
254                 i++;
255         } 
256
257         DEBUG(10,("enum_external_services: Read services %d\n",num_services));
258         *added = num_services;
259
260         return WERR_OK;
261 }
262
263 /********************************************************************
264 ********************************************************************/
265
266 int num_internal_services(void)
267 {
268         int num_services;
269         char **svc_list;
270         pstring keystring, internal_services_string;
271         TDB_DATA key_data;
272
273         if (!service_tdb) {
274                 DEBUG(8,("enum_internal_services: service database is not open!!!\n"));
275                 num_services = 0;
276         } else {
277                 pstrcpy(keystring,"INTERNAL_SERVICES");
278                 tdb_lock_bystring(service_tdb, keystring, 0);
279                 key_data = tdb_fetch_bystring(service_tdb, keystring);
280
281                 if ((key_data.dptr != NULL) && (key_data.dsize != 0)) {
282                         strncpy(internal_services_string,key_data.dptr,key_data.dsize);
283                         internal_services_string[key_data.dsize] = 0;
284                         DEBUG(8,("enum_internal_services: services list is %s, size is %d\n",internal_services_string,key_data.dsize));
285                 }
286                 tdb_unlock_bystring(service_tdb, keystring);
287         } 
288         svc_list = str_list_make(internal_services_string,NULL);
289  
290         num_services = str_list_count( (const char **)svc_list);
291
292         return num_services;
293 }
294
295 #if 0 
296 /*********************************************************************
297  given a service nice name, find the underlying service name
298 *********************************************************************/
299
300 static BOOL convert_service_displayname(TDB_CONTEXT *stdb,pstring service_nicename, pstring servicename,int szsvcname) 
301 {
302         pstring keystring;
303         TDB_DATA key_data;
304
305         if ((stdb == NULL) || (service_nicename==NULL) || (servicename == NULL)) 
306                 return False;
307
308         pstr_sprintf(keystring,"SERVICE_NICENAME/%s", servicename);
309
310         DEBUG(5, ("convert_service_displayname: Looking for service name [%s], key [%s]\n", 
311                 service_nicename, keystring));
312
313         key_data = tdb_fetch_bystring(stdb,keystring);
314
315         if (key_data.dsize == 0) {
316                 DEBUG(5, ("convert_service_displayname: [%s] Not found, tried key [%s]\n",service_nicename,keystring));
317                 return False; 
318         }
319
320         strncpy(servicename,key_data.dptr,szsvcname);
321         servicename[(key_data.dsize > szsvcname ? szsvcname : key_data.dsize)] = 0;
322         DEBUG(5, ("convert_service_displayname: Found service name [%s], name is  [%s]\n",
323                 service_nicename,servicename));
324
325         return True;
326 }
327 #endif
328
329 /*******************************************************************************
330  Get the INTERNAL services information for the given service name. 
331 *******************************************************************************/
332
333 static BOOL get_internal_service_data(const Internal_service_description *isd, Service_info *si)
334 {
335         ZERO_STRUCTP( si );
336 #if 0
337         
338         pstrcpy( si->servicename, isd->displayname);
339         pstrcpy( si->servicetype, "INTERNAL");
340         pstrcpy( si->filename, isd->filename);
341         pstrcpy( si->provides, isd->displayname);
342         pstrcpy( si->description, isd->description);
343         pstrcpy( si->shortdescription, isd->description);
344 #endif
345         
346         return True;
347 }
348
349 /********************************************************************
350 ********************************************************************/
351
352 BOOL get_service_info(TDB_CONTEXT *stdb,char *service_name, Service_info *si) 
353 {
354
355         pstring keystring;
356         TDB_DATA  key_data;
357
358         if ((stdb == NULL) || (si == NULL) || (service_name==NULL) || (*service_name == 0)) 
359                 return False;
360
361         /* TODO  - error handling -- what if the service isn't in the DB? */
362     
363         pstr_sprintf(keystring,"SERVICE/%s/TYPE", service_name);
364         key_data = tdb_fetch_bystring(stdb,keystring);
365         strncpy(si->servicetype,key_data.dptr,key_data.dsize);
366         si->servicetype[key_data.dsize] = 0;
367
368         /* crude check to see if the service exists... */
369         DEBUG(3,("Size of the TYPE field is %d\n",key_data.dsize));
370         if (key_data.dsize == 0) 
371                 return False;
372
373         pstr_sprintf(keystring,"SERVICE/%s/FILENAME", service_name);
374         key_data = tdb_fetch_bystring(stdb,keystring);
375         strncpy(si->filename,key_data.dptr,key_data.dsize);
376         si->filename[key_data.dsize] = 0;
377
378         pstr_sprintf(keystring,"SERVICE/%s/PROVIDES", service_name);
379         key_data = tdb_fetch_bystring(stdb,keystring);
380         strncpy(si->provides,key_data.dptr,key_data.dsize);
381         si->provides[key_data.dsize] = 0;
382         strncpy(si->servicename,key_data.dptr,key_data.dsize);
383         si->servicename[key_data.dsize] = 0;
384
385             
386         pstr_sprintf(keystring,"SERVICE/%s/DEPENDENCIES", service_name);
387         key_data = tdb_fetch_bystring(stdb,keystring);
388         strncpy(si->dependencies,key_data.dptr,key_data.dsize);
389         si->dependencies[key_data.dsize] = 0;
390
391         pstr_sprintf(keystring,"SERVICE/%s/SHOULDSTART", service_name);
392         key_data = tdb_fetch_bystring(stdb,keystring);
393         strncpy(si->shouldstart,key_data.dptr,key_data.dsize);
394         si->shouldstart[key_data.dsize] = 0;
395
396         pstr_sprintf(keystring,"SERVICE/%s/SHOULD_STOP", service_name);
397         key_data = tdb_fetch_bystring(stdb,keystring);
398         strncpy(si->shouldstop,key_data.dptr,key_data.dsize);
399         si->shouldstop[key_data.dsize] = 0;
400
401         pstr_sprintf(keystring,"SERVICE/%s/REQUIREDSTART", service_name);
402         key_data = tdb_fetch_bystring(stdb,keystring);
403         strncpy(si->requiredstart,key_data.dptr,key_data.dsize);
404         si->requiredstart[key_data.dsize] = 0;
405
406         pstr_sprintf(keystring,"SERVICE/%s/REQUIREDSTOP", service_name);
407         key_data = tdb_fetch_bystring(stdb,keystring);
408         strncpy(si->requiredstop,key_data.dptr,key_data.dsize);
409         si->requiredstop[key_data.dsize] = 0;
410
411         pstr_sprintf(keystring,"SERVICE/%s/DESCRIPTION", service_name);
412         key_data = tdb_fetch_bystring(stdb,keystring);
413         strncpy(si->description,key_data.dptr,key_data.dsize);
414         si->description[key_data.dsize] = 0;
415
416         pstr_sprintf(keystring,"SERVICE/%s/SHORTDESC", service_name);
417         key_data = tdb_fetch_bystring(stdb,keystring);
418         strncpy(si->shortdescription,key_data.dptr,key_data.dsize);
419         si->shortdescription[key_data.dsize] = 0;
420
421         return True;
422 }
423
424 /*********************************************************************
425 *********************************************************************/
426
427 BOOL store_service_info(TDB_CONTEXT *stdb,char *service_name, Service_info *si) 
428 {
429         pstring keystring;
430
431         /* Note -- when we write to the tdb, we "index" on the filename 
432            field, not the nice name. when a service is "opened", it is 
433            opened by the nice (SERVICENAME) name, not the file name. 
434            So there needs to be a mapping from nice name back to the file name. */
435
436         if ((stdb == NULL) || (si == NULL) || (service_name==NULL) || (*service_name == 0)) 
437                 return False;
438
439
440         /* Store the nicename */
441
442         pstr_sprintf(keystring,"SERVICE_NICENAME/%s", si->servicename);
443         tdb_store_bystring(stdb,keystring,string_tdb_data(service_name),TDB_REPLACE);
444
445         pstr_sprintf(keystring,"SERVICE/%s/TYPE", service_name);
446         tdb_store_bystring(stdb,keystring,string_tdb_data(si->servicetype),TDB_REPLACE);
447
448         pstr_sprintf(keystring,"SERVICE/%s/FILENAME", service_name);
449         tdb_store_bystring(stdb,keystring,string_tdb_data(si->filename),TDB_REPLACE);
450
451         pstr_sprintf(keystring,"SERVICE/%s/PROVIDES", service_name);
452         tdb_store_bystring(stdb,keystring,string_tdb_data(si->provides),TDB_REPLACE);
453
454         pstr_sprintf(keystring,"SERVICE/%s/SERVICENAME", service_name);
455         tdb_store_bystring(stdb,keystring,string_tdb_data(si->servicename),TDB_REPLACE);
456
457         pstr_sprintf(keystring,"SERVICE/%s/DEPENDENCIES", service_name);
458         tdb_store_bystring(stdb,keystring,string_tdb_data(si->dependencies),TDB_REPLACE);
459
460         pstr_sprintf(keystring,"SERVICE/%s/SHOULDSTART", service_name);
461         tdb_store_bystring(stdb,keystring,string_tdb_data(si->shouldstart),TDB_REPLACE);
462
463         pstr_sprintf(keystring,"SERVICE/%s/SHOULDSTOP", service_name);
464         tdb_store_bystring(stdb,keystring,string_tdb_data(si->shouldstop),TDB_REPLACE);
465
466         pstr_sprintf(keystring,"SERVICE/%s/REQUIREDSTART", service_name);
467         tdb_store_bystring(stdb,keystring,string_tdb_data(si->requiredstart),TDB_REPLACE);
468
469         pstr_sprintf(keystring,"SERVICE/%s/REQUIREDSTOP", service_name);
470         tdb_store_bystring(stdb,keystring,string_tdb_data(si->requiredstop),TDB_REPLACE);
471
472         pstr_sprintf(keystring,"SERVICE/%s/DESCRIPTION", service_name);
473         tdb_store_bystring(stdb,keystring,string_tdb_data(si->description),TDB_REPLACE);
474
475         pstr_sprintf(keystring,"SERVICE/%s/SHORTDESC", service_name);
476         tdb_lock_bystring(stdb, keystring, 0);
477         if (si->shortdescription && *si->shortdescription) 
478                 tdb_store_bystring(stdb,keystring,string_tdb_data(si->shortdescription),TDB_REPLACE);
479         else
480                 tdb_store_bystring(stdb,keystring,string_tdb_data(si->description),TDB_REPLACE);
481
482         return True;
483 }
484
485 /****************************************************************************
486  Create/Open the service control manager tdb. This code a clone of init_group_mapping.
487 ****************************************************************************/
488
489 BOOL init_svcctl_db(void)
490 {
491         const char *vstring = "INFO/version";
492         uint32 vers_id;
493         char **svc_list;
494         char **svcname;
495         pstring keystring;
496         pstring external_service_list;
497         pstring internal_service_list;
498         Service_info si;
499         const Internal_service_description *isd_ptr;
500         /* svc_list = str_list_make( "etc/init.d/skeleton  etc/init.d/syslog", NULL ); */
501         svc_list=(char **)lp_enable_svcctl(); 
502
503         if (service_tdb)
504                 return True;
505
506         pstrcpy(external_service_list,"");
507
508         service_tdb = tdb_open_log(lock_path("services.tdb"), 0, TDB_DEFAULT, O_RDWR, 0600);
509         if (!service_tdb) {
510                 DEBUG(0,("Failed to open service db\n"));
511                 service_tdb = tdb_open_log(lock_path("services.tdb"), 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600);
512                 if (!service_tdb) return False;
513                 DEBUG(0,("Created new services db\n"));
514         }
515
516         if ((-1 == tdb_fetch_uint32(service_tdb, vstring,&vers_id)) || (vers_id != SERVICEDB_VERSION_V1)) {
517           /* wrong version of DB, or db was just created */
518           tdb_traverse(service_tdb, tdb_traverse_delete_fn, NULL);
519           tdb_store_uint32(service_tdb, vstring, SERVICEDB_VERSION_V1);
520         }
521         tdb_unlock_bystring(service_tdb, vstring);
522
523         DEBUG(0,("Initializing services db\n"));
524         
525         svcname = svc_list;
526
527         /* Get the EXTERNAL services as mentioned by line in smb.conf */
528
529         while (*svcname) {
530                 DEBUG(10,("Reading information on service %s\n",*svcname));
531                 if (get_LSB_data(*svcname,&si));{
532                         /* write the information to the TDB */
533                         store_service_info(service_tdb,*svcname,&si);
534                         /* definitely not efficient to do it this way. */
535                         pstrcat(external_service_list,"\"");
536                         pstrcat(external_service_list,*svcname);
537                         pstrcat(external_service_list,"\" ");
538                 }
539                 svcname++;
540         }
541         pstrcpy(keystring,"EXTERNAL_SERVICES");
542         tdb_lock_bystring(service_tdb, keystring, 0);
543         DEBUG(8,("Storing external service list [%s]\n",external_service_list));
544         tdb_store_bystring(service_tdb,keystring,string_tdb_data(external_service_list),TDB_REPLACE);
545         tdb_unlock_bystring(service_tdb,keystring);
546
547         /* Get the INTERNAL services */
548         
549         pstrcpy(internal_service_list,"");
550         isd_ptr = ISD; 
551
552         while (isd_ptr && (isd_ptr->filename)) {
553                 DEBUG(10,("Reading information on service %s\n",isd_ptr->filename));
554                 if (get_internal_service_data(isd_ptr,&si)){
555                         /* write the information to the TDB */
556                         store_service_info(service_tdb,(char *)isd_ptr->filename,&si);
557                         /* definitely not efficient to do it this way. */
558                         pstrcat(internal_service_list,"\"");
559                         pstrcat(internal_service_list,isd_ptr->filename);
560                         pstrcat(internal_service_list,"\" ");
561
562                 }
563                 isd_ptr++;
564         }
565         pstrcpy(keystring,"INTERNAL_SERVICES");
566         tdb_lock_bystring(service_tdb, keystring, 0);
567         DEBUG(8,("Storing internal service list [%s]\n",internal_service_list));
568         tdb_store_bystring(service_tdb,keystring,string_tdb_data(internal_service_list),TDB_REPLACE);
569         tdb_unlock_bystring(service_tdb,keystring);
570
571         return True;
572 }
573