source: trunk-3.0/source/nmbd/nmbd_browsesync.c@ 102

Last change on this file since 102 was 26, checked in by Paul Smedley, 18 years ago

Updated source to 3.0.25rc1

File size: 23.2 KB
Line 
1/*
2 Unix SMB/CIFS implementation.
3 NBT netbios routines and daemon - version 2
4 Copyright (C) Andrew Tridgell 1994-1998
5 Copyright (C) Luke Kenneth Casson Leighton 1994-1998
6 Copyright (C) Jeremy Allison 1994-2003
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22*/
23
24#include "includes.h"
25
26/* This is our local master browser list database. */
27extern struct browse_cache_record *lmb_browserlist;
28
29/****************************************************************************
30As a domain master browser, do a sync with a local master browser.
31**************************************************************************/
32
33static void sync_with_lmb(struct browse_cache_record *browc)
34{
35 struct work_record *work;
36
37 if( !(work = find_workgroup_on_subnet(unicast_subnet, browc->work_group)) ) {
38 if( DEBUGLVL( 0 ) ) {
39 dbgtext( "sync_with_lmb:\n" );
40 dbgtext( "Failed to get a workgroup for a local master browser " );
41 dbgtext( "cache entry workgroup " );
42 dbgtext( "%s, server %s\n", browc->work_group, browc->lmb_name );
43 }
44 return;
45 }
46
47 /* We should only be doing this if we are a domain master browser for
48 the given workgroup. Ensure this is so. */
49
50 if(!AM_DOMAIN_MASTER_BROWSER(work)) {
51 if( DEBUGLVL( 0 ) ) {
52 dbgtext( "sync_with_lmb:\n" );
53 dbgtext( "We are trying to sync with a local master browser " );
54 dbgtext( "%s for workgroup %s\n", browc->lmb_name, browc->work_group );
55 dbgtext( "and we are not a domain master browser on this workgroup.\n" );
56 dbgtext( "Error!\n" );
57 }
58 return;
59 }
60
61 if( DEBUGLVL( 2 ) ) {
62 dbgtext( "sync_with_lmb:\n" );
63 dbgtext( "Initiating sync with local master browser " );
64 dbgtext( "%s<0x20> at IP %s ", browc->lmb_name, inet_ntoa(browc->ip) );
65 dbgtext( "for workgroup %s\n", browc->work_group );
66 }
67
68 sync_browse_lists(work, browc->lmb_name, 0x20, browc->ip, True, True);
69
70 browc->sync_time += (CHECK_TIME_DMB_TO_LMB_SYNC * 60);
71}
72
73/****************************************************************************
74Sync or expire any local master browsers.
75**************************************************************************/
76
77void dmb_expire_and_sync_browser_lists(time_t t)
78{
79 static time_t last_run = 0;
80 struct browse_cache_record *browc;
81
82 /* Only do this every 20 seconds. */
83 if (t - last_run < 20)
84 return;
85
86 last_run = t;
87
88 expire_lmb_browsers(t);
89
90 for( browc = lmb_browserlist; browc; browc = browc->next ) {
91 if (browc->sync_time < t)
92 sync_with_lmb(browc);
93 }
94}
95
96/****************************************************************************
97As a local master browser, send an announce packet to the domain master browser.
98**************************************************************************/
99
100static void announce_local_master_browser_to_domain_master_browser( struct work_record *work)
101{
102 pstring outbuf;
103 unstring myname;
104 unstring dmb_name;
105 char *p;
106
107 if(ismyip(work->dmb_addr)) {
108 if( DEBUGLVL( 2 ) ) {
109 dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" );
110 dbgtext( "We are both a domain and a local master browser for " );
111 dbgtext( "workgroup %s. ", work->work_group );
112 dbgtext( "Do not announce to ourselves.\n" );
113 }
114 return;
115 }
116
117 memset(outbuf,'\0',sizeof(outbuf));
118 p = outbuf;
119 SCVAL(p,0,ANN_MasterAnnouncement);
120 p++;
121
122 unstrcpy(myname, global_myname());
123 strupper_m(myname);
124 myname[15]='\0';
125 /* The call below does CH_UNIX -> CH_DOS conversion. JRA */
126 push_pstring_base(p, myname, outbuf);
127
128 p = skip_string(outbuf,sizeof(outbuf),p);
129
130 if( DEBUGLVL( 4 ) ) {
131 dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" );
132 dbgtext( "Sending local master announce to " );
133 dbgtext( "%s for workgroup %s.\n", nmb_namestr(&work->dmb_name),
134 work->work_group );
135 }
136
137 /* Target name for send_mailslot must be in UNIX charset. */
138 pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
139 send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf),
140 global_myname(), 0x0, dmb_name, 0x0,
141 work->dmb_addr, FIRST_SUBNET->myip, DGRAM_PORT);
142}
143
144/****************************************************************************
145As a local master browser, do a sync with a domain master browser.
146**************************************************************************/
147
148static void sync_with_dmb(struct work_record *work)
149{
150 unstring dmb_name;
151
152 if( DEBUGLVL( 2 ) ) {
153 dbgtext( "sync_with_dmb:\n" );
154 dbgtext( "Initiating sync with domain master browser " );
155 dbgtext( "%s ", nmb_namestr(&work->dmb_name) );
156 dbgtext( "at IP %s ", inet_ntoa(work->dmb_addr) );
157 dbgtext( "for workgroup %s\n", work->work_group );
158 }
159
160 pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
161 sync_browse_lists(work, dmb_name, work->dmb_name.name_type,
162 work->dmb_addr, False, True);
163}
164
165/****************************************************************************
166 Function called when a node status query to a domain master browser IP succeeds.
167****************************************************************************/
168
169static void domain_master_node_status_success(struct subnet_record *subrec,
170 struct userdata_struct *userdata,
171 struct res_rec *answers,
172 struct in_addr from_ip)
173{
174 struct work_record *work = find_workgroup_on_subnet( subrec, userdata->data);
175
176 if( work == NULL ) {
177 if( DEBUGLVL( 0 ) ) {
178 dbgtext( "domain_master_node_status_success:\n" );
179 dbgtext( "Unable to find workgroup " );
180 dbgtext( "%s on subnet %s.\n", userdata->data, subrec->subnet_name );
181 }
182 return;
183 }
184
185 if( DEBUGLVL( 3 ) ) {
186 dbgtext( "domain_master_node_status_success:\n" );
187 dbgtext( "Success in node status for workgroup " );
188 dbgtext( "%s from ip %s\n", work->work_group, inet_ntoa(from_ip) );
189 }
190
191 /* Go through the list of names found at answers->rdata and look for
192 the first SERVER<0x20> name. */
193
194 if(answers->rdata != NULL) {
195 char *p = answers->rdata;
196 int numnames = CVAL(p, 0);
197
198 p += 1;
199
200 while (numnames--) {
201 unstring qname;
202 uint16 nb_flags;
203 int name_type;
204
205 pull_ascii_nstring(qname, sizeof(qname), p);
206 name_type = CVAL(p,15);
207 nb_flags = get_nb_flags(&p[16]);
208 trim_char(qname,'\0',' ');
209
210 p += 18;
211
212 if(!(nb_flags & NB_GROUP) && (name_type == 0x20)) {
213 struct nmb_name nmbname;
214
215 make_nmb_name(&nmbname, qname, name_type);
216
217 /* Copy the dmb name and IP address
218 into the workgroup struct. */
219
220 work->dmb_name = nmbname;
221 putip((char *)&work->dmb_addr, &from_ip);
222
223 /* Do the local master browser announcement to the domain
224 master browser name and IP. */
225 announce_local_master_browser_to_domain_master_browser( work );
226
227 /* Now synchronise lists with the domain master browser. */
228 sync_with_dmb(work);
229 break;
230 }
231 }
232 } else if( DEBUGLVL( 0 ) ) {
233 dbgtext( "domain_master_node_status_success:\n" );
234 dbgtext( "Failed to find a SERVER<0x20> name in reply from IP " );
235 dbgtext( "%s.\n", inet_ntoa(from_ip) );
236 }
237}
238
239/****************************************************************************
240 Function called when a node status query to a domain master browser IP fails.
241****************************************************************************/
242
243static void domain_master_node_status_fail(struct subnet_record *subrec,
244 struct response_record *rrec)
245{
246 struct userdata_struct *userdata = rrec->userdata;
247
248 if( DEBUGLVL( 0 ) ) {
249 dbgtext( "domain_master_node_status_fail:\n" );
250 dbgtext( "Doing a node status request to the domain master browser\n" );
251 dbgtext( "for workgroup %s ", userdata ? userdata->data : "NULL" );
252 dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) );
253 dbgtext( "Cannot sync browser lists.\n" );
254 }
255}
256
257/****************************************************************************
258 Function called when a query for a WORKGROUP<1b> name succeeds.
259****************************************************************************/
260
261static void find_domain_master_name_query_success(struct subnet_record *subrec,
262 struct userdata_struct *userdata_in,
263 struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec)
264{
265 /*
266 * Unfortunately, finding the IP address of the Domain Master Browser,
267 * as we have here, is not enough. We need to now do a sync to the
268 * SERVERNAME<0x20> NetBIOS name, as only recent NT servers will
269 * respond to the SMBSERVER name. To get this name from IP
270 * address we do a Node status request, and look for the first
271 * NAME<0x20> in the response, and take that as the server name.
272 * We also keep a cache of the Domain Master Browser name for this
273 * workgroup in the Workgroup struct, so that if the same IP addess
274 * is returned every time, we don't need to do the node status
275 * request.
276 */
277
278 struct work_record *work;
279 struct nmb_name nmbname;
280 struct userdata_struct *userdata;
281 size_t size = sizeof(struct userdata_struct) + sizeof(fstring)+1;
282 unstring qname;
283
284 pull_ascii_nstring(qname, sizeof(qname), q_name->name);
285 if( !(work = find_workgroup_on_subnet(subrec, qname)) ) {
286 if( DEBUGLVL( 0 ) ) {
287 dbgtext( "find_domain_master_name_query_success:\n" );
288 dbgtext( "Failed to find workgroup %s\n", qname);
289 }
290 return;
291 }
292
293 /* First check if we already have a dmb for this workgroup. */
294
295 if(!is_zero_ip(work->dmb_addr) && ip_equal(work->dmb_addr, answer_ip)) {
296 /* Do the local master browser announcement to the domain
297 master browser name and IP. */
298 announce_local_master_browser_to_domain_master_browser( work );
299
300 /* Now synchronise lists with the domain master browser. */
301 sync_with_dmb(work);
302 return;
303 } else {
304 zero_ip(&work->dmb_addr);
305 }
306
307 /* Now initiate the node status request. */
308
309 /* We used to use the name "*",0x0 here, but some Windows
310 * servers don't answer that name. However we *know* they
311 * have the name workgroup#1b (as we just looked it up).
312 * So do the node status request on this name instead.
313 * Found at LBL labs. JRA.
314 */
315
316 make_nmb_name(&nmbname,work->work_group,0x1b);
317
318 /* Put the workgroup name into the userdata so we know
319 what workgroup we're talking to when the reply comes
320 back. */
321
322 /* Setup the userdata_struct - this is copied so we can use
323 a stack variable for this. */
324
325 if((userdata = (struct userdata_struct *)SMB_MALLOC(size)) == NULL) {
326 DEBUG(0, ("find_domain_master_name_query_success: malloc fail.\n"));
327 return;
328 }
329
330 userdata->copy_fn = NULL;
331 userdata->free_fn = NULL;
332 userdata->userdata_len = strlen(work->work_group)+1;
333 overmalloc_safe_strcpy(userdata->data, work->work_group, size - sizeof(*userdata) - 1);
334
335 node_status( subrec, &nmbname, answer_ip,
336 domain_master_node_status_success,
337 domain_master_node_status_fail,
338 userdata);
339
340 zero_free(userdata, size);
341}
342
343/****************************************************************************
344 Function called when a query for a WORKGROUP<1b> name fails.
345 ****************************************************************************/
346
347static void find_domain_master_name_query_fail(struct subnet_record *subrec,
348 struct response_record *rrec,
349 struct nmb_name *question_name, int fail_code)
350{
351 if( DEBUGLVL( 0 ) ) {
352 dbgtext( "find_domain_master_name_query_fail:\n" );
353 dbgtext( "Unable to find the Domain Master Browser name " );
354 dbgtext( "%s for the workgroup %s.\n",
355 nmb_namestr(question_name), question_name->name );
356 dbgtext( "Unable to sync browse lists in this workgroup.\n" );
357 }
358}
359
360/****************************************************************************
361As a local master browser for a workgroup find the domain master browser
362name, announce ourselves as local master browser to it and then pull the
363full domain browse lists from it onto the given subnet.
364**************************************************************************/
365
366void announce_and_sync_with_domain_master_browser( struct subnet_record *subrec,
367 struct work_record *work)
368{
369 /* Only do this if we are using a WINS server. */
370 if(we_are_a_wins_client() == False) {
371 if( DEBUGLVL( 10 ) ) {
372 dbgtext( "announce_and_sync_with_domain_master_browser:\n" );
373 dbgtext( "Ignoring, as we are not a WINS client.\n" );
374 }
375 return;
376 }
377
378 /* First, query for the WORKGROUP<1b> name from the WINS server. */
379 query_name(unicast_subnet, work->work_group, 0x1b,
380 find_domain_master_name_query_success,
381 find_domain_master_name_query_fail,
382 NULL);
383}
384
385/****************************************************************************
386 Function called when a node status query to a domain master browser IP succeeds.
387 This function is only called on query to a Samba 1.9.18 or above WINS server.
388
389 Note that adding the workgroup name is enough for this workgroup to be
390 browsable by clients, as clients query the WINS server or broadcast
391 nets for the WORKGROUP<1b> name when they want to browse a workgroup
392 they are not in. We do not need to do a sync with this Domain Master
393 Browser in order for our browse clients to see machines in this workgroup.
394 JRA.
395****************************************************************************/
396
397static void get_domain_master_name_node_status_success(struct subnet_record *subrec,
398 struct userdata_struct *userdata,
399 struct res_rec *answers,
400 struct in_addr from_ip)
401{
402 struct work_record *work;
403 unstring server_name;
404
405 server_name[0] = 0;
406
407 if( DEBUGLVL( 3 ) ) {
408 dbgtext( "get_domain_master_name_node_status_success:\n" );
409 dbgtext( "Success in node status from ip %s\n", inet_ntoa(from_ip) );
410 }
411
412 /*
413 * Go through the list of names found at answers->rdata and look for
414 * the first WORKGROUP<0x1b> name.
415 */
416
417 if(answers->rdata != NULL) {
418 char *p = answers->rdata;
419 int numnames = CVAL(p, 0);
420
421 p += 1;
422
423 while (numnames--) {
424 unstring qname;
425 uint16 nb_flags;
426 int name_type;
427
428 pull_ascii_nstring(qname, sizeof(qname), p);
429 name_type = CVAL(p,15);
430 nb_flags = get_nb_flags(&p[16]);
431 trim_char(qname,'\0',' ');
432
433 p += 18;
434
435 if(!(nb_flags & NB_GROUP) && (name_type == 0x00) &&
436 server_name[0] == 0) {
437 /* this is almost certainly the server netbios name */
438 unstrcpy(server_name, qname);
439 continue;
440 }
441
442 if(!(nb_flags & NB_GROUP) && (name_type == 0x1b)) {
443 if( DEBUGLVL( 5 ) ) {
444 dbgtext( "get_domain_master_name_node_status_success:\n" );
445 dbgtext( "%s(%s) ", server_name, inet_ntoa(from_ip) );
446 dbgtext( "is a domain master browser for workgroup " );
447 dbgtext( "%s. Adding this name.\n", qname );
448 }
449
450 /*
451 * If we don't already know about this workgroup, add it
452 * to the workgroup list on the unicast_subnet.
453 */
454
455 if((work = find_workgroup_on_subnet( subrec, qname)) == NULL) {
456 struct nmb_name nmbname;
457 /*
458 * Add it - with an hour in the cache.
459 */
460 if(!(work= create_workgroup_on_subnet(subrec, qname, 60*60)))
461 return;
462
463 /* remember who the master is */
464 unstrcpy(work->local_master_browser_name, server_name);
465 make_nmb_name(&nmbname, server_name, 0x20);
466 work->dmb_name = nmbname;
467 work->dmb_addr = from_ip;
468 }
469 break;
470 }
471 }
472 } else if( DEBUGLVL( 0 ) ) {
473 dbgtext( "get_domain_master_name_node_status_success:\n" );
474 dbgtext( "Failed to find a WORKGROUP<0x1b> name in reply from IP " );
475 dbgtext( "%s.\n", inet_ntoa(from_ip) );
476 }
477}
478
479/****************************************************************************
480 Function called when a node status query to a domain master browser IP fails.
481****************************************************************************/
482
483static void get_domain_master_name_node_status_fail(struct subnet_record *subrec,
484 struct response_record *rrec)
485{
486 if( DEBUGLVL( 0 ) ) {
487 dbgtext( "get_domain_master_name_node_status_fail:\n" );
488 dbgtext( "Doing a node status request to the domain master browser " );
489 dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) );
490 dbgtext( "Cannot get workgroup name.\n" );
491 }
492}
493
494/****************************************************************************
495 Function called when a query for *<1b> name succeeds.
496****************************************************************************/
497
498static void find_all_domain_master_names_query_success(struct subnet_record *subrec,
499 struct userdata_struct *userdata_in,
500 struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec)
501{
502 /*
503 * We now have a list of all the domain master browsers for all workgroups
504 * that have registered with the WINS server. Now do a node status request
505 * to each one and look for the first 1b name in the reply. This will be
506 * the workgroup name that we will add to the unicast subnet as a 'non-local'
507 * workgroup.
508 */
509
510 struct nmb_name nmbname;
511 struct in_addr send_ip;
512 int i;
513
514 if( DEBUGLVL( 5 ) ) {
515 dbgtext( "find_all_domain_master_names_query_succes:\n" );
516 dbgtext( "Got answer from WINS server of %d ", (rrec->rdlength / 6) );
517 dbgtext( "IP addresses for Domain Master Browsers.\n" );
518 }
519
520 for(i = 0; i < rrec->rdlength / 6; i++) {
521 /* Initiate the node status requests. */
522 make_nmb_name(&nmbname, "*", 0);
523
524 putip((char *)&send_ip, (char *)&rrec->rdata[(i*6) + 2]);
525
526 /*
527 * Don't send node status requests to ourself.
528 */
529
530 if(ismyip( send_ip )) {
531 if( DEBUGLVL( 5 ) ) {
532 dbgtext( "find_all_domain_master_names_query_succes:\n" );
533 dbgtext( "Not sending node status to our own IP " );
534 dbgtext( "%s.\n", inet_ntoa(send_ip) );
535 }
536 continue;
537 }
538
539 if( DEBUGLVL( 5 ) ) {
540 dbgtext( "find_all_domain_master_names_query_success:\n" );
541 dbgtext( "Sending node status request to IP %s.\n", inet_ntoa(send_ip) );
542 }
543
544 node_status( subrec, &nmbname, send_ip,
545 get_domain_master_name_node_status_success,
546 get_domain_master_name_node_status_fail,
547 NULL);
548 }
549}
550
551/****************************************************************************
552 Function called when a query for *<1b> name fails.
553 ****************************************************************************/
554static void find_all_domain_master_names_query_fail(struct subnet_record *subrec,
555 struct response_record *rrec,
556 struct nmb_name *question_name, int fail_code)
557{
558 if( DEBUGLVL( 10 ) ) {
559 dbgtext( "find_domain_master_name_query_fail:\n" );
560 dbgtext( "WINS server did not reply to a query for name " );
561 dbgtext( "%s.\nThis means it ", nmb_namestr(question_name) );
562 dbgtext( "is probably not a Samba 1.9.18 or above WINS server.\n" );
563 }
564}
565
566/****************************************************************************
567 If we are a domain master browser on the unicast subnet, do a query to the
568 WINS server for the *<1b> name. This will only work to a Samba WINS server,
569 so ignore it if we fail. If we succeed, contact each of the IP addresses in
570 turn and do a node status request to them. If this succeeds then look for a
571 <1b> name in the reply - this is the workgroup name. Add this to the unicast
572 subnet. This is expensive, so we only do this every 15 minutes.
573**************************************************************************/
574
575void collect_all_workgroup_names_from_wins_server(time_t t)
576{
577 static time_t lastrun = 0;
578 struct work_record *work;
579
580 /* Only do this if we are using a WINS server. */
581 if(we_are_a_wins_client() == False)
582 return;
583
584 /* Check to see if we are a domain master browser on the unicast subnet. */
585 if((work = find_workgroup_on_subnet( unicast_subnet, lp_workgroup())) == NULL) {
586 if( DEBUGLVL( 0 ) ) {
587 dbgtext( "collect_all_workgroup_names_from_wins_server:\n" );
588 dbgtext( "Cannot find my workgroup %s ", lp_workgroup() );
589 dbgtext( "on subnet %s.\n", unicast_subnet->subnet_name );
590 }
591 return;
592 }
593
594 if(!AM_DOMAIN_MASTER_BROWSER(work))
595 return;
596
597 if ((lastrun != 0) && (t < lastrun + (15 * 60)))
598 return;
599
600 lastrun = t;
601
602 /* First, query for the *<1b> name from the WINS server. */
603 query_name(unicast_subnet, "*", 0x1b,
604 find_all_domain_master_names_query_success,
605 find_all_domain_master_names_query_fail,
606 NULL);
607}
608
609
610/****************************************************************************
611 If we are a domain master browser on the unicast subnet, do a regular sync
612 with all other DMBs that we know of on that subnet.
613
614To prevent exponential network traffic with large numbers of workgroups
615we use a randomised system where sync probability is inversely proportional
616to the number of known workgroups
617**************************************************************************/
618
619void sync_all_dmbs(time_t t)
620{
621 static time_t lastrun = 0;
622 struct work_record *work;
623 int count=0;
624
625 /* Only do this if we are using a WINS server. */
626 if(we_are_a_wins_client() == False)
627 return;
628
629 /* Check to see if we are a domain master browser on the
630 unicast subnet. */
631 work = find_workgroup_on_subnet(unicast_subnet, lp_workgroup());
632 if (!work)
633 return;
634
635 if (!AM_DOMAIN_MASTER_BROWSER(work))
636 return;
637
638 if ((lastrun != 0) && (t < lastrun + (5 * 60)))
639 return;
640
641 /* count how many syncs we might need to do */
642 for (work=unicast_subnet->workgrouplist; work; work = work->next) {
643 if (strcmp(lp_workgroup(), work->work_group)) {
644 count++;
645 }
646 }
647
648 /* sync with a probability of 1/count */
649 for (work=unicast_subnet->workgrouplist; work; work = work->next) {
650 if (strcmp(lp_workgroup(), work->work_group)) {
651 unstring dmb_name;
652
653 if (((unsigned)sys_random()) % count != 0)
654 continue;
655
656 lastrun = t;
657
658 if (!work->dmb_name.name[0]) {
659 /* we don't know the DMB - assume it is
660 the same as the unicast local master */
661 make_nmb_name(&work->dmb_name,
662 work->local_master_browser_name,
663 0x20);
664 }
665
666 pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
667
668 DEBUG(3,("Initiating DMB<->DMB sync with %s(%s)\n",
669 dmb_name, inet_ntoa(work->dmb_addr)));
670
671 sync_browse_lists(work,
672 dmb_name,
673 work->dmb_name.name_type,
674 work->dmb_addr, False, False);
675 }
676 }
677}
Note: See TracBrowser for help on using the repository browser.