]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_spanningtree.cpp
Added nickname syncing
[user/henk/code/inspircd.git] / src / modules / m_spanningtree.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  Inspire is copyright (C) 2002-2005 ChatSpike-Dev.
6  *                       E-mail:
7  *                <brain@chatspike.net>
8  *                <Craig@chatspike.net>
9  *     
10  * Written by Craig Edwards, Craig McLure, and others.
11  * This program is free but copyrighted software; see
12  *            the file COPYING for details.
13  *
14  * ---------------------------------------------------
15  */
16
17 using namespace std;
18
19 #include <stdio.h>
20 #include <vector>
21 #include <deque>
22 #include "globals.h"
23 #include "inspircd_config.h"
24 #ifdef GCC3
25 #include <ext/hash_map>
26 #else
27 #include <hash_map>
28 #endif
29 #include "users.h"
30 #include "channels.h"
31 #include "modules.h"
32 #include "socket.h"
33 #include "helperfuncs.h"
34 #include "inspircd.h"
35 #include "inspstring.h"
36 #include "hashcomp.h"
37 #include "message.h"
38
39 #ifdef GCC3
40 #define nspace __gnu_cxx
41 #else
42 #define nspace std
43 #endif
44
45 enum ServerState { LISTENER, CONNECTING, WAIT_AUTH_1, WAIT_AUTH_2, CONNECTED };
46
47 typedef nspace::hash_map<std::string, userrec*, nspace::hash<string>, irc::StrHashComp> user_hash;
48 extern user_hash clientlist;
49
50 class TreeServer;
51 class TreeSocket;
52
53 class TreeServer
54 {
55         TreeServer* Parent;
56         std::vector<TreeServer*> Children;
57         std::string ServerName;
58         std::string ServerDesc;
59         std::string VersionString;
60         int UserCount;
61         int OperCount;
62         TreeSocket* Socket;     // for directly connected servers this points at the socket object
63         
64  public:
65
66         TreeServer()
67         {
68                 Parent = NULL;
69                 ServerName = "";
70                 ServerDesc = "";
71                 VersionString = "";
72                 UserCount = OperCount = 0;
73         }
74
75         TreeServer(std::string Name, std::string Desc) : ServerName(Name), ServerDesc(Desc)
76         {
77                 Parent = NULL;
78                 VersionString = "";
79                 UserCount = OperCount = 0;
80         }
81
82         TreeServer(std::string Name, std::string Desc, TreeServer* Above, TreeSocket* Sock) : Parent(Above), ServerName(Name), ServerDesc(Desc), Socket(Sock)
83         {
84                 VersionString = "";
85                 UserCount = OperCount = 0;
86         }
87
88         std::string GetName()
89         {
90                 return this->ServerName;
91         }
92
93         std::string GetDesc()
94         {
95                 return this->ServerDesc;
96         }
97
98         std::string GetVersion()
99         {
100                 return this->VersionString;
101         }
102
103         int GetUserCount()
104         {
105                 return this->UserCount;
106         }
107
108         int GetOperCount()
109         {
110                 return this->OperCount;
111         }
112
113         TreeSocket* GetSocket()
114         {
115                 return this->Socket;
116         }
117
118         TreeServer* GetParent()
119         {
120                 return this->Parent;
121         }
122
123         unsigned int ChildCount()
124         {
125                 return Children.size();
126         }
127
128         TreeServer* GetChild(unsigned int n)
129         {
130                 if (n < Children.size())
131                 {
132                         return Children[n];
133                 }
134                 else
135                 {
136                         return NULL;
137                 }
138         }
139
140         void AddChild(TreeServer* Child)
141         {
142                 Children.push_back(Child);
143         }
144
145         bool DelChild(TreeServer* Child)
146         {
147                 for (std::vector<TreeServer*>::iterator a = Children.begin(); a < Children.end(); a++)
148                 {
149                         if (*a == Child)
150                         {
151                                 Children.erase(a);
152                                 return true;
153                         }
154                 }
155                 return false;
156         }
157 };
158
159 class Link
160 {
161  public:
162          std::string Name;
163          std::string IPAddr;
164          int Port;
165          std::string SendPass;
166          std::string RecvPass;
167 };
168
169 /* $ModDesc: Povides a spanning tree server link protocol */
170
171 Server *Srv;
172 ConfigReader *Conf;
173 TreeServer *TreeRoot;
174 std::vector<Link> LinkBlocks;
175
176 TreeServer* RouteEnumerate(TreeServer* Current, std::string ServerName)
177 {
178         if (Current->GetName() == ServerName)
179                 return Current;
180         for (unsigned int q = 0; q < Current->ChildCount(); q++)
181         {
182                 TreeServer* found = RouteEnumerate(Current->GetChild(q),ServerName);
183                 if (found)
184                 {
185                         return found;
186                 }
187         }
188         return NULL;
189 }
190
191 // Returns the locally connected server we must route a
192 // message through to reach server 'ServerName'. This
193 // only applies to one-to-one and not one-to-many routing.
194 TreeServer* BestRouteTo(std::string ServerName)
195 {
196         log(DEBUG,"Finding best route to %s",ServerName.c_str());
197         // first, find the server by recursively walking the tree
198         TreeServer* Found = RouteEnumerate(TreeRoot,ServerName);
199         // did we find it? If not, they did something wrong, abort.
200         if (!Found)
201         {
202                 log(DEBUG,"Failed to find %s by walking tree!",ServerName.c_str());
203                 return NULL;
204         }
205         else
206         {
207                 // The server exists, follow its parent nodes until
208                 // the parent of the current is 'TreeRoot', we know
209                 // then that this is a directly-connected server.
210                 while ((Found) && (Found->GetParent() != TreeRoot))
211                 {
212                         Found = Found->GetParent();
213                 }
214                 log(DEBUG,"Route to %s is via %s",ServerName.c_str(),Found->GetName().c_str());
215                 return Found;
216         }
217 }
218
219 class TreeSocket : public InspSocket
220 {
221         std::string myhost;
222         std::string in_buffer;
223         ServerState LinkState;
224         std::string InboundServerName;
225         std::string InboundDescription;
226         
227  public:
228
229         TreeSocket(std::string host, int port, bool listening, unsigned long maxtime)
230                 : InspSocket(host, port, listening, maxtime)
231         {
232                 Srv->Log(DEBUG,"Create new listening");
233                 myhost = host;
234                 this->LinkState = LISTENER;
235         }
236
237         TreeSocket(std::string host, int port, bool listening, unsigned long maxtime, std::string ServerName)
238                 : InspSocket(host, port, listening, maxtime)
239         {
240                 Srv->Log(DEBUG,"Create new outbound");
241                 myhost = ServerName;
242                 this->LinkState = CONNECTING;
243         }
244
245         TreeSocket(int newfd, char* ip)
246                 : InspSocket(newfd, ip)
247         {
248                 Srv->Log(DEBUG,"Associate new inbound");
249                 this->LinkState = WAIT_AUTH_1;
250         }
251         
252         virtual bool OnConnected()
253         {
254                 if (this->LinkState == CONNECTING)
255                 {
256                         Srv->SendOpers("*** Connection to "+myhost+"["+this->GetIP()+"] established.");
257                         // we should send our details here.
258                         // if the other side is satisfied, they send theirs.
259                         // we do not need to change state here.
260                         for (std::vector<Link>::iterator x = LinkBlocks.begin(); x < LinkBlocks.end(); x++)
261                         {
262                                 if (x->Name == this->myhost)
263                                 {
264                                         // found who we're supposed to be connecting to, send the neccessary gubbins.
265                                         this->WriteLine("SERVER "+Srv->GetServerName()+" "+x->SendPass+" 0 :"+Srv->GetServerDescription());
266                                         return true;
267                                 }
268                         }
269                 }
270                 log(DEBUG,"Outbound connection ERROR: Could not find the right link block!");
271                 return true;
272         }
273         
274         virtual void OnError(InspSocketError e)
275         {
276         }
277
278         virtual int OnDisconnect()
279         {
280                 return true;
281         }
282
283         // recursively send the server tree with distances as hops
284         void SendServers(TreeServer* Current, TreeServer* s, int hops)
285         {
286                 char command[1024];
287                 for (unsigned int q = 0; q < Current->ChildCount(); q++)
288                 {
289                         TreeServer* recursive_server = Current->GetChild(q);
290                         if (recursive_server != s)
291                         {
292                                 // :source.server SERVER server.name hops :Description
293                                 snprintf(command,1024,":%s SERVER %s * %d :%s",Current->GetName().c_str(),recursive_server->GetName().c_str(),hops,recursive_server->GetDesc().c_str());
294                                 this->WriteLine(command);
295                                 // down to next level
296                                 this->SendServers(recursive_server, s, hops+1);
297                         }
298                 }
299         }
300
301         bool IntroduceClient(std::string source, std::deque<std::string> params)
302         {
303                 // NICK age nick host dhost ident +modes ip :gecos
304                 //       0   1    2    3      4     5    6   7
305                 std::string nick = params[1];
306                 std::string host = params[2];
307                 std::string dhost = params[3];
308                 std::string ident = params[4];
309                 time_t age = atoi(params[0].c_str());
310                 std::string modes = params[5];
311                 std::string ip = params[6];
312                 std::string gecos = params[7];
313                 char* tempnick = (char*)nick.c_str();
314                 log(DEBUG,"Introduce client %s!%s@%s",tempnick,ident.c_str(),host.c_str());
315                 
316                 user_hash::iterator iter;
317                 iter = clientlist.find(tempnick);
318                 if (iter != clientlist.end())
319                 {
320                         // nick collision
321                         log(DEBUG,"Nick collision on %s!%s@%s",tempnick,ident.c_str(),host.c_str());
322                         return true;
323                 }
324                 
325                 clientlist[tempnick] = new userrec();
326                 clientlist[tempnick]->fd = FD_MAGIC_NUMBER;
327                 strlcpy(clientlist[tempnick]->nick, tempnick,NICKMAX);
328                 strlcpy(clientlist[tempnick]->host, host.c_str(),160);
329                 strlcpy(clientlist[tempnick]->dhost, dhost.c_str(),160);
330                 clientlist[tempnick]->server = (char*)FindServerNamePtr(source.c_str());
331                 strlcpy(clientlist[tempnick]->ident, ident.c_str(),IDENTMAX);
332                 strlcpy(clientlist[tempnick]->fullname, gecos.c_str(),MAXGECOS);
333                 clientlist[tempnick]->registered = 7;
334                 clientlist[tempnick]->signon = age;
335                 strlcpy(clientlist[tempnick]->ip,ip.c_str(),16);
336                 for (int i = 0; i < MAXCHANS; i++)
337                 {
338                         clientlist[tempnick]->chans[i].channel = NULL;
339                         clientlist[tempnick]->chans[i].uc_modes = 0;
340                 }
341                 return true;
342         }
343
344         // send all users and their channels
345         void SendUsers(TreeServer* Current)
346         {
347                 char data[MAXBUF];
348                 for (user_hash::iterator u = clientlist.begin(); u != clientlist.end(); u++)
349                 {
350                         snprintf(data,MAXBUF,":%s NICK %lu %s %s %s %s +%s %s :%s",u->second->server,(unsigned long)u->second->age,u->second->nick,u->second->host,u->second->dhost,u->second->ident,u->second->modes,u->second->ip,u->second->fullname);
351                         this->WriteLine(data);
352                         if (strchr(u->second->modes,'o'))
353                         {
354                                 this->WriteLine(":"+std::string(u->second->nick)+" OPERTYPE "+std::string(u->second->oper));
355                         }
356                         char* chl = chlist(u->second,u->second);
357                         if (*chl)
358                         {
359                                 this->WriteLine(":"+std::string(u->second->nick)+" FJOIN "+std::string(chl));
360                         }
361                 }
362         }
363
364         void DoBurst(TreeServer* s)
365         {
366                 log(DEBUG,"Beginning network burst");
367                 Srv->SendOpers("*** Bursting to "+s->GetName()+".");
368                 this->WriteLine("BURST");
369                 // Send server tree
370                 this->SendServers(TreeRoot,s,1);
371                 // Send users and their channels
372                 this->SendUsers(s);
373                 // TODO: Send everything else (channel modes etc)
374                 this->WriteLine("ENDBURST");
375         }
376
377         virtual bool OnDataReady()
378         {
379                 char* data = this->Read();
380                 if (data)
381                 {
382                         this->in_buffer += data;
383                         while (in_buffer.find("\n") != std::string::npos)
384                         {
385                                 char* line = (char*)in_buffer.c_str();
386                                 std::string ret = "";
387                                 while ((*line != '\n') && (strlen(line)))
388                                 {
389                                         ret = ret + *line;
390                                         line++;
391                                 }
392                                 if ((*line == '\n') || (*line == '\r'))
393                                         line++;
394                                 in_buffer = line;
395                                 if (!this->ProcessLine(ret))
396                                 {
397                                         return false;
398                                 }
399                         }
400                 }
401                 return (data != NULL);
402         }
403
404         int WriteLine(std::string line)
405         {
406                 return this->Write(line + "\r\n");
407         }
408
409         bool Error(std::deque<std::string> params)
410         {
411                 if (params.size() < 1)
412                         return false;
413                 std::string Errmsg = params[0];
414                 std::string SName = myhost;
415                 if (InboundServerName != "")
416                 {
417                         SName = InboundServerName;
418                 }
419                 Srv->SendOpers("*** ERROR from "+SName+": "+Errmsg);
420                 // we will return false to cause the socket to close.
421                 return false;
422         }
423
424         bool Outbound_Reply_Server(std::deque<std::string> params)
425         {
426                 if (params.size() < 4)
427                         return false;
428                 std::string servername = params[0];
429                 std::string password = params[1];
430                 int hops = atoi(params[2].c_str());
431                 if (hops)
432                 {
433                         this->WriteLine("ERROR :Server too far away for authentication");
434                         return false;
435                 }
436                 std::string description = params[3];
437                 for (std::vector<Link>::iterator x = LinkBlocks.begin(); x < LinkBlocks.end(); x++)
438                 {
439                         if ((x->Name == servername) && (x->RecvPass == password))
440                         {
441                                 // Begin the sync here. this kickstarts the
442                                 // other side, waiting in WAIT_AUTH_2 state,
443                                 // into starting their burst, as it shows
444                                 // that we're happy.
445                                 this->LinkState = CONNECTED;
446                                 // we should add the details of this server now
447                                 // to the servers tree, as a child of the root
448                                 // node.
449                                 TreeServer* Node = new TreeServer(servername,description,TreeRoot,this);
450                                 TreeRoot->AddChild(Node);
451                                 this->DoBurst(Node);
452                                 return true;
453                         }
454                 }
455                 this->WriteLine("ERROR :Invalid credentials");
456                 return false;
457         }
458
459         bool Inbound_Server(std::deque<std::string> params)
460         {
461                 if (params.size() < 4)
462                         return false;
463                 std::string servername = params[0];
464                 std::string password = params[1];
465                 int hops = atoi(params[2].c_str());
466                 if (hops)
467                 {
468                         this->WriteLine("ERROR :Server too far away for authentication");
469                         return false;
470                 }
471                 std::string description = params[3];
472                 for (std::vector<Link>::iterator x = LinkBlocks.begin(); x < LinkBlocks.end(); x++)
473                 {
474                         if ((x->Name == servername) && (x->RecvPass == password))
475                         {
476                                 Srv->SendOpers("*** Verified incoming server connection from "+servername+"["+this->GetIP()+"] ("+description+")");
477                                 this->InboundServerName = servername;
478                                 this->InboundDescription = description;
479                                 // this is good. Send our details: Our server name and description and hopcount of 0,
480                                 // along with the sendpass from this block.
481                                 this->WriteLine("SERVER "+Srv->GetServerName()+" "+x->SendPass+" 0 :"+Srv->GetServerDescription());
482                                 // move to the next state, we are now waiting for THEM.
483                                 this->LinkState = WAIT_AUTH_2;
484                                 return true;
485                         }
486                 }
487                 this->WriteLine("ERROR :Invalid credentials");
488                 return false;
489         }
490
491         std::deque<std::string> Split(std::string line)
492         {
493                 std::deque<std::string> n;
494                 std::stringstream s(line);
495                 std::string param = "";
496                 n.clear();
497                 int item = 0;
498                 while (!s.eof())
499                 {
500                         s >> param;
501                         if ((param.c_str()[0] == ':') && (item))
502                         {
503                                 char* str = (char*)param.c_str();
504                                 str++;
505                                 param = str;
506                                 std::string append;
507                                 while (!s.eof())
508                                 {
509                                         append = "";
510                                         s >> append;
511                                         if (append != "")
512                                         {
513                                                 param = param + " " + append;
514                                         }
515                                 }
516                         }
517                         item++;
518                         n.push_back(param);
519                 }
520                 return n;
521         }
522
523         bool ProcessLine(std::string line)
524         {
525                 Srv->SendToModeMask("o",WM_AND,"inbound-line: '"+line+"'");
526
527                 std::deque<std::string> params = this->Split(line);
528                 std::string command = "";
529                 std::string prefix = "";
530                 if (((params[0].c_str())[0] == ':') && (params.size() > 1))
531                 {
532                         prefix = params[0];
533                         command = params[1];
534                         char* pref = (char*)prefix.c_str();
535                         prefix = ++pref;
536                         params.pop_front();
537                         params.pop_front();
538                 }
539                 else
540                 {
541                         prefix = "";
542                         command = params[0];
543                         params.pop_front();
544                 }
545                 
546                 switch (this->LinkState)
547                 {
548                         TreeServer* Node;
549                         
550                         case WAIT_AUTH_1:
551                                 // Waiting for SERVER command from remote server. Server initiating
552                                 // the connection sends the first SERVER command, listening server
553                                 // replies with theirs if its happy, then if the initiator is happy,
554                                 // it starts to send its net sync, which starts the merge, otherwise
555                                 // it sends an ERROR.
556                                 if (command == "SERVER")
557                                 {
558                                         return this->Inbound_Server(params);
559                                 }
560                                 else if (command == "ERROR")
561                                 {
562                                         return this->Error(params);
563                                 }
564                         break;
565                         case WAIT_AUTH_2:
566                                 // Waiting for start of other side's netmerge to say they liked our
567                                 // password.
568                                 if (command == "SERVER")
569                                 {
570                                         // cant do this, they sent it to us in the WAIT_AUTH_1 state!
571                                         // silently ignore.
572                                         return true;
573                                 }
574                                 else if (command == "BURST")
575                                 {
576                                         this->LinkState = CONNECTED;
577                                         Node = new TreeServer(InboundServerName,InboundDescription,TreeRoot,this);
578                                         TreeRoot->AddChild(Node);
579                                         this->DoBurst(Node);
580                                 }
581                                 else if (command == "ERROR")
582                                 {
583                                         return this->Error(params);
584                                 }
585                                 
586                         break;
587                         case LISTENER:
588                                 this->WriteLine("ERROR :Internal error -- listening socket accepted its own descriptor!!!");
589                                 return false;
590                         break;
591                         case CONNECTING:
592                                 if (command == "SERVER")
593                                 {
594                                         // another server we connected to, which was in WAIT_AUTH_1 state,
595                                         // has just sent us their credentials. If we get this far, theyre
596                                         // happy with OUR credentials, and they are now in WAIT_AUTH_2 state.
597                                         // if we're happy with this, we should send our netburst which
598                                         // kickstarts the merge.
599                                         return this->Outbound_Reply_Server(params);
600                                 }
601                         break;
602                         case CONNECTED:
603                                 // This is the 'authenticated' state, when all passwords
604                                 // have been exchanged and anything past this point is taken
605                                 // as gospel.
606                                 if ((command == "NICK") && (params.size() > 1))
607                                 {
608                                         return this->IntroduceClient(prefix,params);
609                                 }
610                                 return true;
611                         break;  
612                 }
613                 return true;
614         }
615
616         virtual void OnTimeout()
617         {
618                 if (this->LinkState = CONNECTING)
619                 {
620                         Srv->SendOpers("*** CONNECT: Connection to "+myhost+" timed out.");
621                 }
622         }
623
624         virtual void OnClose()
625         {
626         }
627
628         virtual int OnIncomingConnection(int newsock, char* ip)
629         {
630                 TreeSocket* s = new TreeSocket(newsock, ip);
631                 Srv->AddSocket(s);
632                 return true;
633         }
634 };
635
636 class ModuleSpanningTree : public Module
637 {
638         std::vector<TreeSocket*> Bindings;
639
640  public:
641
642         void ReadConfiguration(bool rebind)
643         {
644                 if (rebind)
645                 {
646                         for (int j =0; j < Conf->Enumerate("bind"); j++)
647                         {
648                                 std::string Type = Conf->ReadValue("bind","type",j);
649                                 std::string IP = Conf->ReadValue("bind","address",j);
650                                 long Port = Conf->ReadInteger("bind","port",j,true);
651                                 if (Type == "servers")
652                                 {
653                                         if (IP == "*")
654                                         {
655                                                 IP = "";
656                                         }
657                                         TreeSocket* listener = new TreeSocket(IP.c_str(),Port,true,10);
658                                         if (listener->GetState() == I_LISTENING)
659                                         {
660                                                 Srv->AddSocket(listener);
661                                                 Bindings.push_back(listener);
662                                         }
663                                         else
664                                         {
665                                                 log(DEFAULT,"m_spanningtree: Warning: Failed to bind server port %d",Port);
666                                                 listener->Close();
667                                                 delete listener;
668                                         }
669                                 }
670                         }
671                 }
672                 LinkBlocks.clear();
673                 for (int j =0; j < Conf->Enumerate("link"); j++)
674                 {
675                         Link L;
676                         L.Name = Conf->ReadValue("link","name",j);
677                         L.IPAddr = Conf->ReadValue("link","ipaddr",j);
678                         L.Port = Conf->ReadInteger("link","port",j,true);
679                         L.SendPass = Conf->ReadValue("link","sendpass",j);
680                         L.RecvPass = Conf->ReadValue("link","recvpass",j);
681                         LinkBlocks.push_back(L);
682                         log(DEBUG,"m_spanningtree: Read server %s with host %s:%d",L.Name.c_str(),L.IPAddr.c_str(),L.Port);
683                 }
684         }
685
686         ModuleSpanningTree()
687         {
688                 Srv = new Server;
689                 Conf = new ConfigReader;
690                 Bindings.clear();
691
692                 // Create the root of the tree
693                 TreeRoot = new TreeServer(Srv->GetServerName(),Srv->GetServerDescription());
694
695                 ReadConfiguration(true);
696         }
697
698         void HandleLinks(char** parameters, int pcnt, userrec* user)
699         {
700                 return;
701         }
702
703         void HandleLusers(char** parameters, int pcnt, userrec* user)
704         {
705                 return;
706         }
707
708         void HandleMap(char** parameters, int pcnt, userrec* user)
709         {
710                 return;
711         }
712
713         int HandleSquit(char** parameters, int pcnt, userrec* user)
714         {
715                 return 1;
716         }
717
718         int HandleConnect(char** parameters, int pcnt, userrec* user)
719         {
720                 for (std::vector<Link>::iterator x = LinkBlocks.begin(); x < LinkBlocks.end(); x++)
721                 {
722                         if (Srv->MatchText(x->Name.c_str(),parameters[0]))
723                         {
724                                 WriteServ(user->fd,"NOTICE %s :*** CONNECT: Connecting to server: %s (%s:%d)",user->nick,x->Name.c_str(),x->IPAddr.c_str(),x->Port);
725                                 TreeSocket* newsocket = new TreeSocket(x->IPAddr,x->Port,false,10,x->Name);
726                                 Srv->AddSocket(newsocket);
727                                 return 1;
728                         }
729                 }
730                 WriteServ(user->fd,"NOTICE %s :*** CONNECT: No matching server could be found in the config file.",user->nick);
731                 return 1;
732         }
733
734         virtual int OnPreCommand(std::string command, char **parameters, int pcnt, userrec *user)
735         {
736                 if (command == "CONNECT")
737                 {
738                         return this->HandleConnect(parameters,pcnt,user);
739                 }
740                 else if (command == "SQUIT")
741                 {
742                         return this->HandleSquit(parameters,pcnt,user);
743                 }
744                 else if (command == "MAP")
745                 {
746                         this->HandleMap(parameters,pcnt,user);
747                         return 1;
748                 }
749                 else if (command == "LUSERS")
750                 {
751                         this->HandleLusers(parameters,pcnt,user);
752                         return 1;
753                 }
754                 else if (command == "LINKS")
755                 {
756                         this->HandleLinks(parameters,pcnt,user);
757                         return 1;
758                 }
759                 return 0;
760         }
761
762         virtual ~ModuleSpanningTree()
763         {
764                 delete Srv;
765         }
766
767         virtual Version GetVersion()
768         {
769                 return Version(1,0,0,0,VF_STATIC|VF_VENDOR);
770         }
771 };
772
773
774 class ModuleSpanningTreeFactory : public ModuleFactory
775 {
776  public:
777         ModuleSpanningTreeFactory()
778         {
779         }
780         
781         ~ModuleSpanningTreeFactory()
782         {
783         }
784         
785         virtual Module * CreateModule()
786         {
787                 return new ModuleSpanningTree;
788         }
789         
790 };
791
792
793 extern "C" void * init_module( void )
794 {
795         return new ModuleSpanningTreeFactory;
796 }
797