]> jfr.im git - irc/rizon/acid.git/blob - acid/src/main/java/net/rizon/acid/core/Acidictive.java
ad8601eb793dc175cc616aa0eb3235d109680ab8
[irc/rizon/acid.git] / acid / src / main / java / net / rizon / acid / core / Acidictive.java
1 package net.rizon.acid.core;
2
3 import java.io.BufferedWriter;
4 import java.io.File;
5 import java.io.FileWriter;
6 import java.io.IOException;
7 import java.net.UnknownHostException;
8 import java.sql.PreparedStatement;
9 import java.sql.ResultSet;
10 import java.sql.SQLException;
11 import java.text.SimpleDateFormat;
12 import java.util.Arrays;
13 import java.util.Calendar;
14 import java.util.Date;
15 import java.util.List;
16 import java.util.logging.Level;
17
18 import net.rizon.acid.conf.Client;
19 import net.rizon.acid.conf.Config;
20 import net.rizon.acid.sql.SQL;
21 import net.rizon.acid.util.Blowfish;
22 import net.rizon.acid.util.ClassLoader;
23 import net.rizon.acid.util.Util;
24
25 public class Acidictive extends AcidCore
26 {
27 private static final Logger log = Logger.getLogger(Acidictive.class.getName());
28 public static Config conf;
29 public static long startTime;
30 public static int syncTime;
31 public static SQL acidcore_sql;
32 public static String bold = "\ 2";
33
34 public static String loaderBase = "commands";
35 public static ClassLoader loader; // loader for the core commands
36
37 public static String getVersion()
38 {
39 return "acid4 " + Version.getAttribute("revision");
40 }
41
42 public static void main(String[] args)
43 {
44 Version.load();
45
46 log.log(Level.INFO, getVersion() + " (" + Version.getFullVersion() + ") starting up...");
47
48 try
49 {
50 conf = (Config) Config.load("acidictive.yml", Config.class);
51 }
52 catch (Exception ex)
53 {
54 log.log(Level.CONFIG, "Unable to start due to configuration problems", ex);
55 System.exit(1);
56 }
57
58 AcidCore.start(conf.uplink.host, conf.uplink.port, conf.serverinfo.name, conf.serverinfo.description, conf.uplink.pass, conf.serverinfo.id, conf.uplink.ssl);
59
60 acidcore_sql = SQL.getConnection("acidcore");
61 if (acidcore_sql == null)
62 {
63 log.log(Level.SEVERE, "Unable to get connection for `acidcore`");
64 System.exit(-1);
65 }
66
67 loader = new ClassLoader(loaderBase);
68 if (conf.clients != null)
69 for (Client c : conf.clients)
70 new AcidUser(null, c);
71
72 if (conf.plugins != null)
73 for (String s : conf.plugins)
74 {
75 try
76 {
77 Plugin p = Plugin.loadPlugin(s);
78 log.log(Level.INFO, "Loaded plugin " + p.getName());
79 }
80 catch (Exception ex)
81 {
82 log.log(Level.SEVERE, "Unable to load plugin " + s, ex);
83 System.exit(-1);
84 }
85 }
86
87 try
88 {
89 AcidCore.run();
90 }
91 catch (UnknownHostException ex)
92 {
93 log.log(Level.SEVERE, "Unable to connect to uplink - unknown host");
94 System.exit(-1);
95 }
96 catch (IOException ex)
97 {
98 log.log(Level.SEVERE, "Unable to connect to uplink", ex);
99 System.exit(-1);
100 }
101
102 System.exit(0);
103 }
104
105 public static void onStart()
106 {
107 startTime = new Date().getTime();
108
109 for (String s : User.getUsers())
110 {
111 User u = User.findUser(s);
112 if (!(u instanceof AcidUser))
113 continue;
114
115 ((AcidUser) u).introduce();
116 }
117
118 Protocol.eob();
119 }
120
121 public static void onNick(User user)
122 {
123 for (Event e : Event.getEvents())
124 e.onUserConnect(user);
125 }
126
127 public static void onNickChange(User user, String oldnick)
128 {
129 for (Event e : Event.getEvents())
130 e.onNickChange(user, oldnick);
131 }
132
133 public static void onEOB(Server server)
134 {
135 for (Event e : Event.getEvents())
136 e.onEOB(server);
137 }
138
139 public static void onJoin(String channel, User[] users)
140 {
141 for (Event e : Event.getEvents())
142 e.onJoin(channel, users);
143 }
144
145 public static void onPart(String parter, String channel)
146 {
147 for (Event e : Event.getEvents())
148 e.onPart(parter, channel);
149 }
150
151 public static void onQuit(User user, String msg)
152 {
153 for (Event e : Event.getEvents())
154 e.onQuit(user, msg);
155 }
156
157 public static void onServer(Server server)
158 {
159 for (Event e : Event.getEvents())
160 e.onServerLink(server);
161 }
162
163 public static void onPrivmsg(String creator, String recipient, String msg)
164 {
165 try
166 {
167 User x = User.findUser(creator);
168
169 boolean stop = false;
170 for (Event e : Event.getEvents())
171 stop = stop || e.onPrivmsg(creator, recipient, msg);
172 if (stop)
173 return;
174
175 // All channel commands start with .
176 if (recipient.startsWith("#") && !msg.startsWith(conf.general.command_prefix))
177 return;
178
179 if (x.getIdentNick().isEmpty())
180 {
181 // Only lookup flags for people who are +o and +r
182 if (x.hasMode("o"))
183 {
184 // However +o and -r can do some commands (identify!)
185 if (!x.getSU().isEmpty())
186 {
187 x.loadAccess(x);
188
189 // Try to save a certfp if possible
190 //if (!x.getFlags().isEmpty())
191 // Access.addCertFP(x, true);
192 }
193 // If +o-r, try to auth via cert; in case services are gone
194 else if (!x.getCertFP().isEmpty())
195 {
196 // There might also be a CertFP for said user to use, check!
197 try
198 {
199 PreparedStatement ps = Acidictive.acidcore_sql.prepare("SELECT `certfp` FROM `access` WHERE `user` = ?");
200 ps.setString(1, x.getSU());
201 ResultSet rs = Acidictive.acidcore_sql.executeQuery(ps);
202 if (rs.next() && !rs.getString("certfp").isEmpty())
203 {
204 if (rs.getString("certfp").equalsIgnoreCase(x.getCertFP()))
205 {
206 x.loadAccess(x);
207 }
208 else
209 {
210 for (Event e : Event.getEvents())
211 e.onCommandCertFPMismatch(x, rs.getString("certfp"));
212 }
213 }
214 }
215 catch (SQLException ex)
216 {
217 log.log(Level.WARNING, "Unable to load certfp access", ex);
218 }
219 }
220 }
221 else
222 return;
223 }
224
225 // Not destined for anything else, must be a command.
226 String[] parts = msg.split("\\s+");
227 // strip leading "." if needed
228 String command = (parts[0].startsWith(conf.general.command_prefix) ? parts[0].substring(conf.general.command_prefix.length()) : parts[0]).toLowerCase();
229 String[] args = Arrays.copyOfRange(parts, 1, parts.length);
230
231 Channel c = null;
232 AcidUser to = null;
233 net.rizon.acid.conf.Command confCommand = null;
234 if (recipient.startsWith("#"))
235 {
236 c = Channel.findChannel(recipient);
237 if (c == null)
238 return;
239
240 // lol?
241 for (String s : c.getUsers())
242 {
243 User u = User.findUser(s);
244 if (u instanceof AcidUser)
245 {
246 AcidUser t = (AcidUser) u;
247 net.rizon.acid.conf.Command cmd = t.findConfCommand(command);
248
249 if (cmd != null)
250 {
251 to = t;
252 confCommand = cmd;
253 }
254 }
255 }
256
257 if (to == null)
258 return;
259 }
260 else
261 {
262 int i = recipient.indexOf('@');
263 if (i != -1)
264 recipient = recipient.substring(0, i);
265
266 User targ = User.findUser(recipient);
267 if (targ == null || !(targ instanceof AcidUser))
268 return;
269
270 to = (AcidUser) targ;
271 confCommand = to.findConfCommand(command);
272 }
273
274 if (confCommand == null)
275 return;
276
277 Plugin p = null;
278 // If the conf specifies a plugin, use that
279 if (confCommand.plugin != null)
280 p = Plugin.findPlugin(confCommand.plugin);
281 // Otherwise use the plugin of the target, if any
282 else
283 p = to.pkg;
284 // Use our loader if there is no plugin
285 ClassLoader cl = p != null ? p.loader : loader;
286 Class<?> commandClass = cl.loadClass(confCommand.clazz);
287 if (commandClass == null)
288 return;
289
290 Command cmd = (Command) commandClass.newInstance();
291
292 if (recipient.startsWith("#"))
293 {
294 // In the proper channels commands can go without access checks. (ish)
295 if (!confCommand.allowsChannel(recipient))
296 {
297 return; // Access
298 }
299
300 // Now that we're in the right channel check for just being logged in OR +o OR spoof
301 if (x.getIdentNick().isEmpty() && !x.hasMode("o") && !x.getIP().equals("0"))
302 {
303 return; // Access
304 }
305 }
306 // Message to me, check priv
307 else
308 {
309 // Non priv commands can be used by any oper
310 if (confCommand.privilege == null)
311 {
312 log.log(Level.INFO, "Denied access to " + confCommand.name + " to " + x + " [command has no privilege]");
313 // Commands with no priv can not be executed in PM
314 return;
315 }
316
317 // Explicitly requires no privilege
318 if (confCommand.privilege.equals("none"))
319 ;
320 else if (x.hasFlags(confCommand.privilege) == false)
321 {
322 log.log(Level.INFO, "Denied access to " + confCommand.name + " to " + x + " [user has no priv]");
323 return;
324 }
325 }
326
327 if (args.length < cmd.GetMinArgs())
328 {
329 reply(x, to, c, "Syntax error for \2" +
330 confCommand.name + "\2. Available commands are:");
331 cmd.onHelp(x, to, c);
332 return; // Syntax
333 }
334
335 if (args.length > cmd.GetMaxArgs())
336 {
337 String last = arrayFormat(args, cmd.GetMaxArgs() - 1, args.length - 1);
338 String[] parsed_args = new String[cmd.GetMaxArgs()];
339 for (int i = 0; i < cmd.GetMaxArgs() - 1; ++i)
340 parsed_args[i] = args[i];
341 parsed_args[cmd.GetMaxArgs() - 1] = last;
342 args = parsed_args;
343 }
344
345 try
346 {
347 cmd.Run(x, to, c, args);
348
349 /* log SUCCESSFUL privmsg command usages as well
350 * except help because that's just silly and nobody cares.
351 */
352 if (!command.equalsIgnoreCase("help")
353 && !recipient.startsWith("#"))
354 privmsg(conf.getChannelNamed("cmdlogchan"), x.getNick() + ": " + msg);
355 }
356 catch (Exception ex)
357 {
358 log.log(Level.WARNING, "Error running command " + confCommand.name, ex);
359 }
360 }
361 catch (Exception e)
362 {
363 log.log(Level.SEVERE, "Error processing PRIVMSG: " + msg, e);
364 }
365 }
366
367 public static void onCtcp(String creator, String recipient, String msg)
368 {
369 boolean stop = false;
370
371 if (msg.startsWith("\1VERSION"))
372 {
373 User u = User.findUser(recipient);
374 if (u instanceof AcidUser)
375 {
376 AcidUser au = (AcidUser) u;
377 if (au.client.version != null)
378 Acidictive.notice(au.getUID(), creator, "\1VERSION " + au.client.version + "\1");
379 }
380 }
381
382 for (Event e : Event.getEvents())
383 {
384 stop = stop || e.onCTCP(creator, recipient, msg);
385 if (stop)
386 return;
387 }
388 }
389
390 public static void onCtcpReply(User source, String target, String message)
391 {
392 for (Event e : Event.getEvents())
393 e.onCTCPReply(source, target, message);
394 }
395
396 public static void onNotice(String creator, String recipient, String msg)
397 {
398 if (creator.equalsIgnoreCase("nickserv") && msg.matches("^.*registered\\s+and\\s+protected.*$"))
399 {
400 AcidUser au = (AcidUser) User.findUser(recipient);
401 if (au != null && !au.getNSPass().isEmpty())
402 Protocol.privmsg(recipient, creator, "IDENTIFY " + au.getNSPass());
403 return;
404 }
405
406 boolean stop = false;
407 for (Event e : Event.getEvents())
408 {
409 stop = stop || e.onNotice(creator, recipient, msg);
410 if (stop)
411 return;
412 }
413 }
414
415 public static void onDie(String msg)
416 {
417 log.log(Level.INFO, msg);
418 }
419
420 public static void onSync()
421 {
422 syncTime = (int) new Date().getTime() - (int) startTime;
423 log.log(Level.INFO, "Synced in " + (double) syncTime / 1000 + " seconds.");
424
425 for (Event e : Event.getEvents())
426 e.onSync();
427 }
428
429 public static void onKill(final String killer, User user, final String reason)
430 {
431 for (Event e : Event.getEvents())
432 e.onKill(killer, user, reason);
433 }
434
435 public static int getUptimeTS()
436 {
437 return getTS() - (int) ((double) startTime / 1000);
438 }
439
440 public static String getUptime()
441 {
442 return Util.formatTime(getUptimeTS());
443 }
444
445 public static void onKick(String kicker, User victim, String channel, String reason)
446 {
447 for (Event e : Event.getEvents())
448 e.onKick(kicker, victim, channel, reason);
449
450 if (victim.getServer() == AcidCore.me)
451 {
452 AcidUser au = (AcidUser) victim;
453 au.joinChan(channel);
454 }
455 }
456
457 public static void onMode(String creator, String recipient, String modes)
458 {
459 }
460
461 public static void onChanMode(String creator, Channel chan, String modes)
462 {
463 String[] x = modes.split("\\s+");
464 if (x.length >= 1)
465 {
466 boolean give = true;
467 int whatnick = 1;
468 String m;
469 for (int i = 0; i < x[0].length(); i++)
470 {
471 m = x[0].substring(i, i + 1);
472
473 if (m.equals("+"))
474 give = true;
475 else if (m.equals("-"))
476 give = false;
477 else if (m.matches("(b|I|e)"))
478 whatnick++;
479 else if (m.equals("k"))
480 {
481 if (give)
482 chan.setKey(x[whatnick]);
483 else
484 chan.setKey(null);
485
486 ++whatnick;
487
488 if (give)
489 chan.setMode(m.charAt(0));
490 else
491 chan.unsetMode(m.charAt(0));
492 }
493 else if (m.contains("l"))
494 {
495 if (give)
496 {
497 chan.setLimit(Integer.parseInt(x[whatnick]));
498 whatnick++;
499 }
500 else
501 chan.setLimit(0);
502
503 if (give)
504 chan.setMode(m.charAt(0));
505 else
506 chan.unsetMode(m.charAt(0));
507 }
508 else if (m.matches("(v|h|o|a|q)"))
509 {
510 if (give)
511 chan.setMode(User.toName(x[whatnick]), m);
512 else
513 chan.remMode(User.toName(x[whatnick]), m);
514
515 whatnick++;
516 }
517 else
518 {
519 if (give)
520 chan.setMode(m.charAt(0));
521 else
522 chan.unsetMode(m.charAt(0));
523 }
524 }
525 }
526
527 for (Event e : Event.getEvents())
528 e.onChanModes(creator, chan, modes);
529 }
530
531 public static void setMode(String source, String target, String modes)
532 {
533 Channel c = Channel.findChannel(target);
534 if (c != null)
535 onChanMode(source, c, modes);
536
537 Protocol.mode(source, target, modes);
538 }
539
540 // The only calling instance, KKill, already checks if user exists
541 public static void skill(User u, String msg)
542 {
543 Protocol.kill(u.getUID(), msg);
544 u.onQuit();
545 onQuit(u, "Killed (" + me.getName() + " (" + msg + "))");
546 }
547
548 public static void onSquit(Server server)
549 {
550 for (Event e : Event.getEvents())
551 e.onServerDelink(server);
552 }
553
554 public static void privmsg(final Channel recipient, String message)
555 {
556 String crypto_pass = Acidictive.conf.getCryptoPass(recipient.getName());
557 if (crypto_pass != null)
558 {
559 Blowfish bf = new Blowfish(crypto_pass);
560 message = bf.Encrypt(message);
561 log.log(Level.FINE, "Blowfish encrypted:" + message);
562 }
563
564 Protocol.privmsg(conf.general.control, recipient.getName(), message);
565 }
566
567 public static void privmsg(final User recipient, String message)
568 {
569 Protocol.privmsg(conf.general.control, recipient.getUID(), message);
570 }
571
572 //@Deprecated This should be deprecated however I need the warnings to stfu atm in eclipse
573 public static void privmsg(final String recipient, String message)
574 {
575 privmsg(conf.general.control, recipient, message);
576 }
577
578 public static void privmsg(String source, String target, String message)
579 {
580 if (target.startsWith("#"))
581 {
582 String crypto_pass = Acidictive.conf.getCryptoPass(target);
583 if (crypto_pass != null)
584 {
585 Blowfish bf = new Blowfish(crypto_pass);
586 message = bf.Encrypt(message);
587 log.log(Level.FINE, "Blowfish encrypted:" + message);
588 }
589 }
590 Protocol.privmsg(source, target, message);
591 }
592
593 public static void notice(final Channel recipient, final String message)
594 {
595 Protocol.notice(conf.general.control, recipient.getName(), message);
596 }
597
598 public static void notice(final User recipient, final String message)
599 {
600 Protocol.notice(conf.general.control, recipient.getUID(), message);
601 }
602
603 public static void notice(final String source, final String recipient, final String message)
604 {
605 Protocol.notice(source, recipient, message);
606 }
607
608 @Deprecated
609 public static void notice(final String recipient, final String message)
610 {
611 Protocol.notice(conf.general.control, recipient, message);
612 }
613
614 @Deprecated
615 public static void sNotice(final String recipient, final String message)
616 {
617 Protocol.notice(me.getSID(), recipient, message);
618 }
619
620 static SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
621
622 public static void log(String str)
623 {
624 Calendar now = Calendar.getInstance();
625 File f = new File("logs//acidictive.log." + formatter.format(now.getTime()));
626 now.clear();
627 now = null;
628 String date = "[" + new Date().toString() + "]: ";
629 BufferedWriter print = createWriter(f, true);
630 try
631 {
632 print.write(date + str);
633 print.newLine();
634 print.close();
635 print = null;
636 }
637 catch (Exception e)
638 {
639 log.log(Level.WARNING, "Unable to write to logfile", e);
640 }
641 f = null;
642 }
643
644 public static BufferedWriter createWriter(File f, boolean append)
645 {
646 // delete the file if we are told not to append
647 if (!append)
648 f.delete();
649 // check if the folder exists, create if it doesn't
650 if (f.getParentFile() != null && !f.getParentFile().exists())
651 f.getParentFile().mkdir();
652 // check if the file exists, create if it doesn't
653 if (!f.exists())
654 {
655 try
656 {
657 f.createNewFile();
658 }
659 catch (Exception e)
660 {
661 log.log(Level.WARNING, "Unable to create file", e);
662 }
663 }
664 // all successful, let's create and return the BufferedWriter
665 try
666 {
667 return new BufferedWriter(new FileWriter(f.getPath(), append));
668 }
669 catch (Exception e)
670 {
671 log.log(Level.WARNING, e.getMessage(), e);
672 }
673 return null;
674 }
675
676 public static void reply(String source, String target, String msg)
677 {
678 if (!target.startsWith("#"))
679 // Reply to PMs in notice, reply to channel in privmsg
680 /* TODO: How the hell do we avoid using notice(String,String)?
681 * Thinking of adding a "Target" interface to make this at least
682 * target-agnostic.
683 */
684 notice(source, msg);
685 else
686 privmsg(target, msg);
687 }
688
689 public static void reply(User source, AcidUser to, Channel c, String message)
690 {
691 if (c != null)
692 privmsg(to.getNick(), c.getName(), message);
693 else
694 notice(to.getNick(), source.getNick(), message);
695 }
696
697 public static void loadClients(Plugin pkg, List<Client> clients)
698 {
699 for (Client c : clients)
700 {
701 User u = User.findUser(c.nick);
702 if (u != null)
703 {
704 if (u instanceof AcidUser)
705 {
706 AcidUser au = (AcidUser) u;
707
708 // update config and plugin
709 au.client = c;
710 au.pkg = pkg;
711 }
712 continue;
713 }
714
715 AcidUser au = new AcidUser(pkg, c);
716 if (syncTime != 0)
717 au.introduce();
718 }
719 }
720 }