1 package net
.rizon
.acid
.core
;
3 import net
.rizon
.acid
.plugins
.Plugin
;
4 import com
.google
.common
.eventbus
.EventBus
;
5 import io
.netty
.util
.concurrent
.ScheduledFuture
;
6 import java
.net
.MalformedURLException
;
7 import java
.sql
.PreparedStatement
;
8 import java
.sql
.ResultSet
;
9 import java
.sql
.SQLException
;
10 import java
.util
.Arrays
;
11 import java
.util
.Date
;
12 import java
.util
.List
;
13 import java
.util
.concurrent
.TimeUnit
;
14 import net
.rizon
.acid
.capab
.QuitStorm
;
15 import net
.rizon
.acid
.conf
.Client
;
16 import net
.rizon
.acid
.conf
.Config
;
17 import net
.rizon
.acid
.conf
.PluginDesc
;
18 import net
.rizon
.acid
.events
.EventCTCP
;
19 import net
.rizon
.acid
.events
.EventCTCPReply
;
20 import net
.rizon
.acid
.events
.EventChanMode
;
21 import net
.rizon
.acid
.events
.EventCommandCertFPMismatch
;
22 import net
.rizon
.acid
.events
.EventEOB
;
23 import net
.rizon
.acid
.events
.EventEncap
;
24 import net
.rizon
.acid
.events
.EventInvite
;
25 import net
.rizon
.acid
.events
.EventJoin
;
26 import net
.rizon
.acid
.events
.EventKick
;
27 import net
.rizon
.acid
.events
.EventKill
;
28 import net
.rizon
.acid
.events
.EventNickChange
;
29 import net
.rizon
.acid
.events
.EventNotice
;
30 import net
.rizon
.acid
.events
.EventPart
;
31 import net
.rizon
.acid
.events
.EventPrivmsg
;
32 import net
.rizon
.acid
.events
.EventQuit
;
33 import net
.rizon
.acid
.events
.EventServerLink
;
34 import net
.rizon
.acid
.events
.EventServerNotice
;
35 import net
.rizon
.acid
.events
.EventSync
;
36 import net
.rizon
.acid
.events
.EventUserConnect
;
37 import net
.rizon
.acid
.events
.EventWebIRC
;
38 import net
.rizon
.acid
.events
.ExceptionHandler
;
39 import net
.rizon
.acid
.logging
.LoggerUtils
;
40 import net
.rizon
.acid
.plugins
.PluginManager
;
41 import net
.rizon
.acid
.sql
.SQL
;
42 import net
.rizon
.acid
.util
.Blowfish
;
43 import net
.rizon
.acid
.plugins
.ClassLoader
;
44 import net
.rizon
.acid
.util
.CloakGenerator
;
45 import net
.rizon
.acid
.util
.Util
;
46 import org
.slf4j
.Logger
;
47 import org
.slf4j
.LoggerFactory
;
49 public class Acidictive
extends AcidCore
51 private static final Logger log
= LoggerFactory
.getLogger(Acidictive
.class);
52 // logs here don't go to irc
53 public static final Logger fileLogger
= LoggerFactory
.getLogger("file");
55 public static CloakGenerator cloakGenerator
;
56 public static Config conf
;
57 public static long startTime
;
58 public static int syncTime
;
59 public static SQL acidcore_sql
;
60 public static char bold
= '\u0002';
62 public static ClassLoader loader
; // loader for the core commands
64 public static EventBus eventBus
= new EventBus(new ExceptionHandler());
65 private static QuitStorm quitStorm
;
67 public static String
getVersion()
69 return "acid-" + Version
.PROJECT_VERSION
+ "-" + Version
.GIT_REVISION_SHORT
;
72 public static void main(String
[] args
)
74 LoggerUtils
.initUncaughtExceptionHandler();
76 log
.info(getVersion() + " starting up...");
80 conf
= (Config
) Config
.load("acidictive.yml", Config
.class);
84 log
.error("Unable to start due to configuration problems", ex
);
90 cloakGenerator
= new CloakGenerator(conf
.serverinfo
.cloakkeys
, conf
.serverinfo
.network
+ "-");
92 catch (IllegalArgumentException ex
)
98 quitStorm
= new QuitStorm(eventBus
, User
.getUsersC());
100 AcidCore
.start(conf
.uplink
.host
, conf
.uplink
.port
, conf
.serverinfo
.name
, conf
.serverinfo
.description
, conf
.uplink
.pass
, conf
.serverinfo
.id
, conf
.uplink
.ssl
);
102 acidcore_sql
= SQL
.getConnection("acidcore");
103 if (acidcore_sql
== null)
105 log
.error("Unable to get connection for `acidcore`");
111 loader
= new ClassLoader("net.rizon.acid.commands.");
113 catch (MalformedURLException ex
)
119 if (conf
.clients
!= null)
120 for (Client c
: conf
.clients
)
121 new AcidUser(null, c
);
123 if (conf
.plugins
!= null)
124 for (PluginDesc desc
: conf
.plugins
)
128 Plugin p
= PluginManager
.loadPlugin(desc
.getGroupId(), desc
.getArtifactId(), desc
.getVersion());
131 log
.error("Unable to load plugin {}", desc
);
135 log
.info("Loaded plugin " + p
.getName());
139 log
.error("Unable to load plugin " + desc
, ex
);
148 catch (InterruptedException ex
)
153 acidcore_sql
.shutdown();
158 public static void onStart()
160 startTime
= new Date().getTime();
162 for (String s
: User
.getUsers())
164 User u
= User
.findUser(s
);
165 if (!(u
instanceof AcidUser
))
168 ((AcidUser
) u
).introduce();
174 public static void onNick(User user
)
176 EventUserConnect event
= new EventUserConnect();
178 eventBus
.post(event
);
181 public static void onNickChange(User user
, String oldnick
)
183 EventNickChange event
= new EventNickChange();
185 event
.setOldNick(oldnick
);
186 eventBus
.post(event
);
189 public static void onEOB(Server server
)
191 EventEOB event
= new EventEOB();
192 event
.setServer(server
);
193 eventBus
.post(event
);
196 public static void onJoin(Channel channel
, User
[] users
)
198 EventJoin event
= new EventJoin();
199 event
.setChannel(channel
);
200 event
.setUsers(users
);
201 eventBus
.post(event
);
204 public static void onPart(User user
, Channel channel
)
206 EventPart event
= new EventPart();
207 event
.setChannel(channel
);
209 eventBus
.post(event
);
212 public static void onQuit(User user
, String msg
)
214 EventQuit event
= new EventQuit();
217 eventBus
.post(event
);
220 public static void onServer(Server server
)
222 EventServerLink event
= new EventServerLink();
223 event
.setServer(server
);
224 eventBus
.post(event
);
227 public static void onPrivmsg(String creator
, String recipient
, String msg
)
231 User x
= User
.findUser(creator
);
233 EventPrivmsg event
= new EventPrivmsg();
234 event
.setCreator(creator
);
235 event
.setRecipient(recipient
);
237 eventBus
.post(event
);
239 if (event
.isStopProcessing())
242 // All channel commands start with .
243 if (recipient
.startsWith("#") && !msg
.startsWith(conf
.general
.command_prefix
))
246 if (x
.getIdentNick().isEmpty())
248 // Only lookup flags for people who are +o and +r
251 // However +o and -r can do some commands (identify!)
252 if (!x
.getSU().isEmpty())
256 // Try to save a certfp if possible
257 //if (!x.getFlags().isEmpty())
258 // Access.addCertFP(x, true);
260 // If +o-r, try to auth via cert; in case services are gone
261 else if (!x
.getCertFP().isEmpty())
263 // There might also be a CertFP for said user to use, check!
266 PreparedStatement ps
= Acidictive
.acidcore_sql
.prepare("SELECT `certfp` FROM `access` WHERE `user` = ?");
267 ps
.setString(1, x
.getSU());
268 ResultSet rs
= Acidictive
.acidcore_sql
.executeQuery(ps
);
269 if (rs
.next() && !rs
.getString("certfp").isEmpty())
271 if (rs
.getString("certfp").equalsIgnoreCase(x
.getCertFP()))
277 EventCommandCertFPMismatch e
= new EventCommandCertFPMismatch();
279 e
.setCertfp(rs
.getString("certfp"));
280 eventBus
.post(event
);
284 catch (SQLException ex
)
286 log
.warn("Unable to load certfp access", ex
);
292 // Not destined for anything else, must be a command.
293 String
[] parts
= msg
.split("\\s+");
294 // strip leading "." if needed
295 String command
= (parts
[0].startsWith(conf
.general
.command_prefix
) ? parts
[0].substring(conf
.general
.command_prefix
.length()) : parts
[0]).toLowerCase();
296 String
[] args
= Arrays
.copyOfRange(parts
, 1, parts
.length
);
300 net
.rizon
.acid
.conf
.Command confCommand
= null;
301 if (recipient
.startsWith("#"))
303 c
= Channel
.findChannel(recipient
);
308 for (String s
: c
.getUsers())
310 User u
= User
.findUser(s
);
311 if (u
instanceof AcidUser
)
313 AcidUser t
= (AcidUser
) u
;
314 net
.rizon
.acid
.conf
.Command cmd
= t
.findConfCommand(command
, c
.getName());
329 int i
= recipient
.indexOf('@');
331 recipient
= recipient
.substring(0, i
);
333 User targ
= User
.findUser(recipient
);
334 if (targ
== null || !(targ
instanceof AcidUser
))
337 to
= (AcidUser
) targ
;
338 confCommand
= to
.findConfCommand(command
, null);
341 if (confCommand
== null)
344 // This isn't really necessary, but is faster than causing the core loader to fail and then fall back
345 // by searching the plugin loaders.
347 // Use our loader if there is no plugin
348 ClassLoader cl
= p
!= null ? p
.loader
: loader
;
349 Class
<?
> commandClass
= cl
.loadClass(confCommand
.clazz
);
350 if (commandClass
== null)
353 if (recipient
.startsWith("#"))
355 // In the proper channels commands can go without access checks. (ish)
356 if (!confCommand
.allowsChannel(recipient
))
361 // Now that we're in the right channel check for just being logged in OR +o OR spoof
362 if (!x
.getIdentNick().isEmpty() || x
.hasMode("o") || x
.getIP().equals("0"))
368 // Only allow explicit "anyone" commands
369 if (confCommand
.privilege
== null || !confCommand
.privilege
.equals("anyone"))
373 // Message to me, check priv
378 if (!"anyone".equals(confCommand
.privilege
))
380 // Only accept commands that are explicitly accessible
387 // Non priv commands can be used by any oper
388 if (confCommand
.privilege
== null)
390 log
.info("Denied access to " + confCommand
.name
+ " to " + x
+ " [command has no privilege]");
391 // Commands with no priv can not be executed in PM
395 // Explicitly requires no privilege
396 if (confCommand
.privilege
.equals("none") || "anyone".equals(confCommand
.privilege
))
398 else if (x
.hasFlags(confCommand
.privilege
) == false)
400 log
.info("Denied access to " + confCommand
.name
+ " to " + x
+ " [user has no priv]");
406 Command cmd
= (Command
) commandClass
.newInstance();
408 if (args
.length
< cmd
.GetMinArgs())
410 reply(x
, to
, c
, "Syntax error for \2" +
411 confCommand
.name
+ "\2. Available commands are:");
412 cmd
.onHelp(x
, to
, c
);
416 if (args
.length
> cmd
.GetMaxArgs() && cmd
.GetMaxArgs() > 0)
418 String last
= arrayFormat(args
, cmd
.GetMaxArgs() - 1, args
.length
- 1);
419 String
[] parsed_args
= new String
[cmd
.GetMaxArgs()];
420 for (int i
= 0; i
< cmd
.GetMaxArgs() - 1; ++i
)
421 parsed_args
[i
] = args
[i
];
422 parsed_args
[cmd
.GetMaxArgs() - 1] = last
;
428 cmd
.Run(x
, to
, c
, args
);
430 /* log SUCCESSFUL privmsg command usages as well
431 * except help because that's just silly and nobody cares.
433 if (!command
.equalsIgnoreCase("help")
434 && !recipient
.startsWith("#"))
435 privmsg(conf
.getChannelNamed("cmdlogchan"), x
.getNick() + ": " + msg
);
439 log
.warn("Error running command " + confCommand
.name
, ex
);
444 log
.error("Error processing PRIVMSG: " + msg
, e
);
448 public static void onCtcp(String creator
, String recipient
, String msg
)
450 boolean stop
= false;
452 if (msg
.startsWith("\1VERSION"))
454 User u
= User
.findUser(recipient
);
455 if (u
instanceof AcidUser
)
457 AcidUser au
= (AcidUser
) u
;
458 if (au
.client
!= null && au
.client
.version
!= null)
459 Acidictive
.notice(au
.getUID(), creator
, "\1VERSION " + au
.client
.version
+ "\1");
463 EventCTCP eventCtcp
= new EventCTCP();
464 eventCtcp
.setCreator(creator
);
465 eventCtcp
.setRecipient(recipient
);
466 eventCtcp
.setMsg(msg
);
468 eventBus
.post(eventCtcp
);
471 public static void onCtcpReply(User source
, String target
, String message
)
473 EventCTCPReply event
= new EventCTCPReply();
474 event
.setSource(source
);
475 event
.setTarget(target
);
476 event
.setMessage(message
);
477 eventBus
.post(event
);
480 public static void onNotice(String creator
, String recipient
, String msg
)
482 if (creator
.equalsIgnoreCase("nickserv") && msg
.matches("^.*registered\\s+and\\s+protected.*$"))
484 AcidUser au
= (AcidUser
) User
.findUser(recipient
);
485 if (au
!= null && au
.getNSPass() != null && !au
.getNSPass().isEmpty())
486 Protocol
.privmsg(recipient
, creator
, "IDENTIFY " + au
.getNSPass());
490 EventNotice eventNotice
= new EventNotice();
491 eventNotice
.setCreator(creator
);
492 eventNotice
.setRecipient(recipient
);
493 eventNotice
.setMsg(msg
);
494 eventBus
.post(eventNotice
);
497 public static void onServerNotice(String source
, String recipient
, String msg
)
499 EventServerNotice event
= new EventServerNotice();
500 event
.setSource(source
);
501 event
.setRecipient(recipient
);
503 eventBus
.post(event
);
506 public static void onDie(String msg
)
511 public static void onSync()
513 syncTime
= (int) new Date().getTime() - (int) startTime
;
514 log
.info("Synced in " + (double) syncTime
/ 1000 + " seconds.");
516 EventSync event
= new EventSync();
517 eventBus
.post(event
);
520 public static void onKill(final String killer
, User user
, final String reason
)
522 EventKill eventKill
= new EventKill();
523 eventKill
.setKiller(killer
);
524 eventKill
.setUser(user
);
525 eventKill
.setReason(reason
);
526 eventBus
.post(eventKill
);
529 public static int getUptimeTS()
531 return getTS() - (int) ((double) startTime
/ 1000);
534 public static String
getUptime()
536 return Util
.formatTime(getUptimeTS());
539 public static void onKick(String kicker
, User victim
, Channel channel
, String reason
)
541 EventKick eventKick
= new EventKick();
542 eventKick
.setKicker(kicker
);
543 eventKick
.setVictim(victim
);
544 eventKick
.setChannel(channel
);
545 eventKick
.setReason(reason
);
546 eventBus
.post(eventKick
);
548 if (victim
.getServer() == AcidCore
.me
)
550 AcidUser au
= (AcidUser
) victim
;
551 au
.joinChan(channel
.getName());
555 public static void onInvite(User inviter
, User invitee
, Channel channel
)
557 EventInvite eventInvite
= new EventInvite();
558 eventInvite
.setInviter(inviter
);
559 eventInvite
.setInvitee(invitee
);
560 eventInvite
.setChannel(channel
);
561 eventBus
.post(eventInvite
);
564 public static void onMode(String creator
, String recipient
, String modes
)
568 public static void onChanMode(String creator
, Channel chan
, String modes
)
570 String
[] x
= modes
.split("\\s+");
576 for (int i
= 0; i
< x
[0].length(); i
++)
578 m
= x
[0].substring(i
, i
+ 1);
582 else if (m
.equals("-"))
584 else if (m
.matches("(b|I|e)"))
586 else if (m
.equals("k"))
589 chan
.setKey(x
[whatnick
]);
596 chan
.setMode(m
.charAt(0));
598 chan
.unsetMode(m
.charAt(0));
600 else if (m
.contains("l"))
604 chan
.setLimit(Integer
.parseInt(x
[whatnick
]));
611 chan
.setMode(m
.charAt(0));
613 chan
.unsetMode(m
.charAt(0));
615 else if (m
.matches("(v|h|o|a|q)"))
617 String targStr
= x
[whatnick
];
620 User target
= User
.findUser(targStr
);
624 Membership mem
= chan
.findUser(target
);
636 chan
.setMode(m
.charAt(0));
638 chan
.unsetMode(m
.charAt(0));
643 EventChanMode event
= new EventChanMode();
645 event
.setModes(modes
);
646 event
.setPrefix(creator
);
647 eventBus
.post(event
);
650 public static void onWebIRC(Server source
, String operation
, String uid
, String realhost
, String sockhost
, String webircPassword
, String webircUsername
, String fakeHost
, String fakeIp
)
652 EventWebIRC event
= new EventWebIRC();
653 event
.setSource(source
);
654 event
.setOperation(operation
);
656 event
.setRealhost(realhost
);
657 event
.setSockhost(sockhost
);
658 event
.setWebircPassword(webircPassword
);
659 event
.setWebircUsername(webircUsername
);
660 event
.setFakeHost(fakeHost
);
661 event
.setFakeIp(fakeIp
);
662 eventBus
.post(event
);
665 public static void onEncap(Server sourceServer
, User sourceUser
, Server target
, String command
, String
[] arguments
)
667 EventEncap event
= new EventEncap(sourceServer
, sourceUser
, target
, command
, arguments
);
668 eventBus
.post(event
);
671 public static void setMode(String source
, String target
, String modes
)
673 Channel c
= Channel
.findChannel(target
);
675 onChanMode(source
, c
, modes
);
678 String
[] s
= modes
.split(" ");
679 Object
[] o
= new Object
[s
.length
];
680 System
.arraycopy(s
, 0, o
, 0, s
.length
);
682 Protocol
.mode(source
, target
, o
);
685 // The only calling instance, KKill, already checks if user exists
686 public static void skill(User u
, String msg
)
688 Protocol
.kill(u
.getUID(), msg
);
690 onQuit(u
, "Killed (" + me
.getName() + " (" + msg
+ "))");
693 public static void onSquit(Server server
)
695 quitStorm
.onServerDelink(server
);
698 public static void kick(AcidUser kicker
, User kickee
, Channel channel
, final String reason
)
700 channel
.removeUser(kickee
);
701 kickee
.remChan(channel
);
703 Protocol
.kick(kicker
, kickee
, channel
.getName(), reason
);
704 onKick(kicker
.getNick(), kickee
, channel
, reason
);
707 public static void privmsg(final Channel recipient
, String message
)
709 String crypto_pass
= Acidictive
.conf
.getCryptoPass(recipient
.getName());
710 if (crypto_pass
!= null)
712 Blowfish bf
= new Blowfish(crypto_pass
);
713 message
= bf
.Encrypt(message
);
714 log
.debug("Blowfish encrypted:" + message
);
717 Protocol
.privmsg(conf
.general
.control
, recipient
.getName(), message
);
720 public static void privmsg(final User recipient
, String message
)
722 Protocol
.privmsg(conf
.general
.control
, recipient
.getUID(), message
);
725 //@Deprecated This should be deprecated however I need the warnings to stfu atm in eclipse
726 public static void privmsg(final String recipient
, String message
)
728 privmsg(conf
.general
.control
, recipient
, message
);
731 public static void privmsg(String source
, String target
, String message
)
733 if (target
.startsWith("#"))
735 String crypto_pass
= Acidictive
.conf
.getCryptoPass(target
);
736 if (crypto_pass
!= null)
738 Blowfish bf
= new Blowfish(crypto_pass
);
739 message
= bf
.Encrypt(message
);
740 log
.debug("Blowfish encrypted:" + message
);
743 Protocol
.privmsg(source
, target
, message
);
746 public static void notice(final Channel recipient
, final String message
)
748 Protocol
.notice(conf
.general
.control
, recipient
.getName(), message
);
751 public static void notice(final User recipient
, final String message
)
753 Protocol
.notice(conf
.general
.control
, recipient
.getUID(), message
);
756 public static void notice(final String source
, final String recipient
, final String message
)
758 Protocol
.notice(source
, recipient
, message
);
762 public static void notice(final String recipient
, final String message
)
764 Protocol
.notice(conf
.general
.control
, recipient
, message
);
768 public static void sNotice(final String recipient
, final String message
)
770 Protocol
.notice(me
.getSID(), recipient
, message
);
773 public static void reply(String source
, String target
, String msg
)
775 if (!target
.startsWith("#"))
776 // Reply to PMs in notice, reply to channel in privmsg
777 /* TODO: How the hell do we avoid using notice(String,String)?
778 * Thinking of adding a "Target" interface to make this at least
783 privmsg(target
, msg
);
786 public static void reply(User source
, AcidUser to
, Channel c
, String message
)
788 if (c
!= null && to
!= null)
789 privmsg(to
.getNick(), c
.getName(), message
);
790 else if (to
!= null && c
== null)
791 notice(to
.getNick(), source
.getNick(), message
);
792 else if (c
!= null && to
== null)
793 privmsg(conf
.general
.control
, c
.getName(), message
);
795 notice(conf
.general
.control
, source
.getNick(), message
);
798 public static void loadClients(Plugin pkg
, List
<Client
> clients
)
800 for (Client c
: clients
)
802 User u
= User
.findUser(c
.nick
);
805 if (u
instanceof AcidUser
)
807 AcidUser au
= (AcidUser
) u
;
809 // update config and plugin
816 AcidUser au
= new AcidUser(pkg
, c
);
822 private static Runnable
catchAll(Runnable r
)
831 log
.warn("uncaught exception thrown out of task", ex
);
836 public static ScheduledFuture
scheduleWithFixedDelay(Runnable r
, long t
, TimeUnit unit
)
838 ScheduledFuture future
= eventLoop
.scheduleWithFixedDelay(catchAll(r
), t
, t
, unit
);
842 public static ScheduledFuture
scheduleAtFixedRate(Runnable r
, long t
, TimeUnit unit
)
844 ScheduledFuture future
= eventLoop
.scheduleAtFixedRate(catchAll(r
), t
, t
, unit
);
848 public static ScheduledFuture
schedule(Runnable r
, long t
, TimeUnit unit
)
850 ScheduledFuture future
= eventLoop
.schedule(catchAll(r
), t
, unit
);