root/trunk/contrib/alpha/Where.java

Revision 1237, 20.1 kB (checked in by mulletron, 7 months ago)

Removes accidental hardcoding

  • Property svn:keywords set to Rev Date
Line 
1import java.io.BufferedReader;
2import java.io.File;
3import java.io.FileOutputStream;
4import java.io.IOException;
5import java.io.InputStreamReader;
6import java.io.PrintStream;
7import java.io.PrintWriter;
8import java.io.UnsupportedEncodingException;
9import java.net.InetAddress;
10import java.net.URLEncoder;
11import java.net.UnknownHostException;
12import java.sql.Connection;
13import java.sql.PreparedStatement;
14import java.sql.ResultSet;
15import java.sql.SQLException;
16import java.text.SimpleDateFormat;
17import java.util.ArrayList;
18import java.util.Collections;
19import java.util.Date;
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Queue;
26import java.util.Set;
27import java.util.Map.Entry;
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30
31import javax.security.auth.callback.Callback;
32
33import org.jibble.pircbot.ReplyConstants;
34
35import uk.co.uwcs.choob.modules.Modules;
36import uk.co.uwcs.choob.support.IRCInterface;
37import uk.co.uwcs.choob.support.events.ChannelMessage;
38import uk.co.uwcs.choob.support.events.ContextEvent;
39import uk.co.uwcs.choob.support.events.Message;
40import uk.co.uwcs.choob.support.events.ServerResponse;
41
42public class Where
43{
44        public String[] info()
45        {
46                return new String[] {
47                        "Location information plugin",
48                        "The Choob Team",
49                        "choob@uwcs.co.uk",
50                        "$Rev$$Date$"
51                };
52        }
53
54        final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
55        Modules mods;
56        IRCInterface irc;
57        private PrintStream logFile;
58
59        private void minilog(final String s)
60        {
61                logFile.println(sdf.format(new Date()) + " - " + Integer.toHexString(hashCode()) + ": " + s);
62        }
63
64        //ignore hosts people screen from that we can't finger.
65        //hardcoding ftw.
66        private final static String[] IGNORED_HOSTS =
67        {
68                //raw
69                "137.205.210.18/32",
70                "raw.sunion.warwick.ac.uk/.*"
71        };
72        private final String channels[] = { "#compsoc", "#wuglug", "#bots", "#wug", "#choob" };
73        private enum Location { Campus, DCS, Resnet }
74
75        abstract class Callback
76        {
77                Callback(final ContextEvent con)
78                {
79                        target = con;
80                }
81
82                final ContextEvent target;
83
84                abstract void complete(Details d);
85
86                void fail()
87                {
88                        irc.sendContextReply(target, "Timeout expired while trying to service request.");
89                }
90
91        }
92
93        Callback fromPredicate(final ContextEvent con, final Predicate p, final String msg)
94        {
95                return new Callback(con)
96                {
97                        @Override
98                        void complete(final Details d)
99                        {
100                                final List<String> hitted = new ArrayList<String>();
101                                for (final Entry<String, Set<InetAddress>> entr : d.users.entrySet())
102                                        for (final InetAddress add : entr.getValue())
103                                                if (p.hit(add))
104                                                        hitted.add(entr.getKey());
105
106                                Collections.sort(hitted);
107                                irc.sendContextReply(target, hitted.size() + " " + (hitted.size() == 1 ? "person" : "people") + " (" + hrList(hitted) + ") " + (hitted.size() == 1 ? "is" : "are") + " " + msg + ".");
108                        }
109                };
110        }
111
112        abstract class Predicate
113        {
114                abstract boolean hit(InetAddress add);
115        }
116
117        class Details
118        {
119                Details(final Callback c)
120                {
121                        users = new HashMap<String, Set<InetAddress>>();
122                        callback = c;
123                        localmap = buildLocalMap();
124                }
125
126                Callback callback;
127                // Username -> addresses
128                Map<String, Set<InetAddress>> localmap;
129
130                // Nick -> host.
131                Map<String, Set<InetAddress>> users;
132        }
133
134        // (Target) channel -> Outstanding queries in it.
135        Map<String, Queue<Details>> outst = makeMap();
136
137        private static Map<String, Queue<Details>> makeMap()
138        {
139                return Collections.synchronizedMap(new HashMap<String, Queue<Details>>());
140        }
141
142        public synchronized void commandReset(final Message mes)
143        {
144                int failed = 0;
145                for (final Entry<String, Queue<Details>> ent : outst.entrySet())
146                        for (final Details qu : ent.getValue())
147                        {
148                                qu.callback.fail();
149                                ++failed;
150                        }
151                outst = makeMap();
152                irc.sendContextReply(mes, "Okay, purged " + failed + " item" + (failed == 1 ? "" : "s") + ".");
153        }
154
155        public Where(final Modules mods, final IRCInterface irc) throws IOException
156        {
157                this.irc = irc;
158                this.mods = mods;
159                final String whatRegex = "^BadgerBOT ([^ ]+) (.*)$";
160               
161                logFile = new PrintStream(new FileOutputStream(new File(System.getProperty("user.home")+"/where.log"), true));
162                minilog("whatRegex: " + whatRegex);
163                whatExtract = Pattern.compile(whatRegex);
164        }
165
166        final Pattern whatExtract;
167
168        <T>     boolean matches(final Pattern p, final T o)
169        {
170                return p.matcher(o.toString()).find();
171        }
172
173        // InetAddress.getByName doesn't resolve textual ips (to addresses) by default, trigger it (and discard the result) such that toString() returns the hostname.
174        // Note that the reverse lookups are cached (indefinitely) by Java, so that's not duplicated here.
175        private InetAddress getByName(final String name) throws UnknownHostException
176        {
177                minilog("getByName: enter: " + name);
178                final InetAddress temp = InetAddress.getByName(name);
179                temp.getHostName();
180
181                // above not part of logging
182                minilog("getByName: exit: " + temp.getHostName());
183                return temp;
184        }
185
186        private static String encode(final String s)
187        {
188                try
189                {
190                        return URLEncoder.encode(s, "UTF-8");
191                }
192                catch (final UnsupportedEncodingException e)
193                {
194                        return "FAIL";
195                }
196        }
197
198        // "User", hostname, server, nick, ...
199        final Pattern splitUp = Pattern.compile("^~?([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) .*");
200        public synchronized void onServerResponse(final ServerResponse resp)
201        {
202                minilog("onServerResponse: enter: " + resp.getCode());
203                // Ensure resp is related to WHO, and remember if we're at the end. I'm sure there's a sane way to write this.
204                final boolean atend = resp.getCode() == ReplyConstants.RPL_ENDOFWHO;
205                if (!(atend || resp.getCode() == ReplyConstants.RPL_WHOREPLY))
206                        return;
207
208                Matcher ma;
209                if (!(ma = whatExtract.matcher(resp.getResponse())).matches())
210                {
211                        minilog("Lol whut: Couldn't parse server's reply: " + encode(resp.getResponse()));
212                        return; // Lol whut.
213                }
214
215                final String aboutWhat = ma.group(1);
216                final String tail = ma.group(2);
217
218                Queue<Details> detst = outst.get(aboutWhat);
219                minilog(aboutWhat + " -> " + detst);
220
221                if (detst == null || detst.isEmpty())
222                {
223                        // The server has sent us a "WHO" line for a channel we don't care about.
224                        // Ignoring when it hates us, and we're hitting a RAAAAAAACE CONDITIONS..
225                        // it does this when we asked for a WHO nick. Assume that this is the only one we're going to get.
226                        minilog("Looks like a line for an individual: " + resp.getResponse());
227                        if (!(ma = splitUp.matcher(tail)).matches())
228                        {
229                                minilog("splitUp 1 didn't match >>" + encode(tail) + "<<");
230                                return;
231                        }
232
233                        if ((detst = outst.get(ma.group(4))) == null) // Read the nick.
234                        {
235                                minilog("...which we wern't expecting. BAIL.");
236                                return; // If we wern't looking for this either, just bail.
237                        }
238                }
239                else
240                        minilog("Looks like a line for a channel: " + encode(resp.getResponse()));
241
242                final Details d = detst.peek();
243                if (d == null)
244                {
245                        minilog("Wtf? There are no outstanding incomming messages for this item (" + encode(aboutWhat) + ")");
246                        return; // Lol whut.
247                }
248
249                if (atend)
250                {
251                        minilog("..and it was the end, so notify them.");
252                        d.callback.complete(d);
253                        detst.remove();
254                        minilog("done notifying");
255                }
256                else
257                {
258                        minilog("..which is not at the end, continuing: ");
259                        if (!(ma = splitUp.matcher(tail)).matches())
260                        {
261                                minilog("splitUp 2 didn't match >>" + encode(tail) + "<<");
262                                return; // Lol whut.
263                        }
264
265                        try
266                        {
267                                // D.users is Nick -> Ip.
268                                // If the user is local, attempt to hax their real ip.
269                                final InetAddress toStore = getByName(ma.group(2));
270                                final String nick = ma.group(4);
271
272                                // Work out where we're going to add the nick.
273                                Set<InetAddress> addto;
274
275                                if ((addto = d.users.get(nick)) == null)
276                                        d.users.put(nick, addto = new HashSet<InetAddress>());
277
278                                //ignore hosts we can't/don't want to check.
279                                if (shouldIgnore(toStore))
280                                {
281                                        minilog("Ignore a host and don't store it.");
282                                        return;
283                                }
284
285                                Set<InetAddress> newones;
286
287                                if (!isLocal(toStore) || (newones = d.localmap.get(ma.group(1))) == null)
288                                        addto.add(toStore);
289                                else
290                                        addto.addAll(newones);
291
292                        }
293                        catch (final UnknownHostException e)
294                        {
295                                minilog("Warning: Couldn't resolve real ip for " + ma.group(4) + ", not a problem: " + ma.group(2));
296                                // Ignore, abort the put.
297                        }
298                }
299        }
300
301        boolean isLocal(final InetAddress add)
302        {
303                return matches(Pattern.compile("/127.*"), add) ||
304                        matches(Pattern.compile("/137.205.210.240"), add);
305        }
306
307        boolean shouldIgnore(final InetAddress add)
308        {
309                for (final String ignoreHost : IGNORED_HOSTS)
310                {
311                        if (matches(Pattern.compile(ignoreHost),add))
312                        {
313                                return true;
314                        }
315                }
316                return false;
317        }
318
319        boolean isCampus(final InetAddress add)
320        {
321                return !isLocal(add) && matches(Pattern.compile("/137\\.205"), add);
322        }
323
324        boolean isResnet(final InetAddress add)
325        {
326                return !isLocal(add) &&
327                        (matches(Pattern.compile("\\.res\\.warwick\\.ac\\.uk/"), add)
328                                        || matches(Pattern.compile("/137\\.205\\.32\\."), add));
329        }
330
331        boolean isDCS(final InetAddress add)
332        {
333                return matches(Pattern.compile("/137\\.205\\.11"), add);
334        }
335       
336        Map<String, Set<InetAddress>> lastLoginMap(Map<String, Set<InetAddress>> localMap) {
337                for (Map.Entry<String, Set<InetAddress>> user : localMap.entrySet()) {
338                        if(user.getValue().size() > 1) {
339                                try {
340                                        final Process proc = Runtime.getRuntime().exec("finger "+user.getKey()+"| grep 'Last login' | cut -d ' ' -f 12");
341                                        final BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
342                                        user.setValue(Collections.singleton(InetAddress.getByName(in.readLine())));
343                                } catch (Exception e) {
344                                        e.printStackTrace();
345                                }
346                        }
347                }
348                return localMap;
349        }
350
351        // Username -> set of addresses.
352        Map<String, Set<InetAddress>> buildLocalMap()
353        {
354                minilog("buildLocalMap: enter");
355                final Map<String, Set<InetAddress>> ret = new HashMap<String, Set<InetAddress>>();
356                try
357                {
358                        final Process proc = Runtime.getRuntime().exec("/usr/bin/finger");
359
360                        // There's a nefarious reason why this is here.
361                        if (proc.waitFor() != 0)
362                                return ret;
363
364                        final BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
365                        br.readLine(); // Discard header.
366                        String s;
367                        Matcher ma;
368                        while ((s = br.readLine()) != null)
369                        {
370                                // faux       Christopher West    pts/16         Feb 11 16:25 (82.16.66.10:S.0)
371                                // account-name SPACE some crap SPACE terminal SPACE date (ip:junk)$
372                                if ((ma = Pattern.compile("^([^ ]+) .* \\((.*?)(?::S\\.[0-9]+)?\\)$").matcher(s)).matches())
373                                {
374                                        final String un = ma.group(1);
375                                        if (ret.get(un) == null)
376                                                ret.put(un, new HashSet<InetAddress>());
377                                        ret.get(un).add(getByName(ma.group(2)));
378                                }
379                        }
380                }
381                catch (final Throwable t)
382                {
383                        minilog("buildLocalMap: exception:" + t);
384                        // Discard:
385                }
386                minilog("buildLocalMap: exit");
387                return lastLoginMap(ret); // Don't care about part results.
388        }
389
390        <T> String hrList(final List<T> el)
391        {
392                return hrList(el, " and ");
393        }
394
395        <T> String hrList(final List<T> el, final String inclusor)
396        {
397                String ret = "";
398                for (int i=0; i<el.size(); ++i)
399                {
400                        ret += el.get(i).toString();
401                        if (i == el.size() - 2)
402                                ret += inclusor;
403                        else if (i == el.size() - 1)
404                                ret += "";
405                        else
406                                ret += ", ";
407                }
408                return ret;
409        }
410
411        // mes only for command line.
412        void goDo( final Message mes, final Callback c )
413        {
414                String what = mods.util.getParamString(mes).trim().intern();
415                if (what.length() == 0)
416                        what = mes.getContext();
417
418                goDo(what, c);
419        }
420
421        class IntervalArgs
422        {
423                final String what;
424                final Details det;
425
426                public IntervalArgs(final String what, final Details det)
427                {
428                        this.what = what;
429                        this.det = det;
430                }
431
432        }
433
434        public synchronized void interval(final Object o)
435        {
436                final IntervalArgs ia = (IntervalArgs) o;
437                final Queue<Details> queue = outst.get(ia.what);
438                if (queue == null)
439                {
440                        minilog("Attempting to clean-up a non-event, whut.");
441                        return;
442                }
443
444                // If it's still present, remove and fail it.
445                if (queue.remove(ia.det))
446                {
447                        minilog("Interval failing " + ia.det);
448                        ia.det.callback.fail();
449                }
450                else
451                        minilog("Interval found only success.");
452        }
453
454        void goDo( final String what, final Callback c )
455        {
456                Queue<Details> d = outst.get(what);
457                if (d == null)
458                        d = new LinkedList<Details>();
459
460                final Details det = new Details(c);
461                d.add(det);
462
463                outst.put(what, d);
464                mods.interval.callBack(new IntervalArgs(what, det), 10000);
465
466                irc.sendRawLine("WHO " + what);
467        }
468
469        public void commandDebug(final Message mes)
470        {
471                goDo(mes,
472                        new Callback(mes)
473                        {
474                                @Override
475                                public void complete(final Details d)
476                                {
477                                        final List<String> unres = new ArrayList<String>(), wcampus = new ArrayList<String>();
478                                        for (final Entry<String, Set<InetAddress>> entr : d.users.entrySet())
479                                                for (final InetAddress add : entr.getValue())
480                                                        if (isCampus(add))
481                                                                wcampus.add(entr.getKey());
482                                                        else if (isLocal(add))
483                                                                unres.add(entr.getKey());
484                                                        else
485                                                                System.out.println(add.toString());
486                                        irc.sendContextReply(target, "Found " + d.users.size() + " people, " + wcampus.size() + " are on campus (" + hrList(wcampus) + "), " + hrList(unres) + " are unresolvable.");
487                                }
488                        }
489                );
490        }
491
492        private void hint(final Message mes)
493        {
494                if (!(mes instanceof ChannelMessage) && mods.util.getParams(mes,2).size() < 2)
495                        irc.sendContextReply(mes,"Hint, try " + mods.util.getParams(mes,2).get(0) + " <ChannelName> for relevant results in PM");
496        }
497
498        public String[] helpCommandOnCampus = {
499                "Displays a list of people connected to IRC from IPs in the university of warwick IP range. Also works properly for those using screens on the server on which the plugin is running.",
500                "[<ChannelName>]",
501                "<ChannelName> is the name of the channel to return results for."
502        };
503        public void commandOnCampus(final Message mes)
504        {
505                hint(mes);
506                goDo(mes, fromPredicate(mes, new Predicate() { @Override boolean hit(final InetAddress add) { return isCampus(add); } }, "on campus"));
507        }
508
509        public String[] helpCommandOnResnet = {
510                "Displays a list of people connected to IRC from IPs in the University of Warwick resnet IP range. Also works properly for those using screens on the server on which the plugin is running.",
511                "[<ChannelName>]",
512                "<ChannelName> is the name of the channel to return results for."
513        };
514        public void commandOnResnet(final Message mes)
515        {
516                hint(mes);
517                goDo(mes, fromPredicate(mes, new Predicate() { @Override boolean hit(final InetAddress add) { return isResnet(add); } }, "on resnet"));
518        }
519
520        public String[] helpCommandInDCS = {
521                "Displays a list of people connected to IRC from IPs in the university of warwick department of computer science IP range. Also works properly for those using screens on the server on which the plugin is running.",
522                "[<ChannelName>]",
523                "<ChannelName> is the name of the channel to return results for."
524        };
525        public void commandInDCS(final Message mes)
526        {
527                hint(mes);
528                goDo(mes, fromPredicate(mes, new Predicate() { @Override boolean hit(final InetAddress add) { return isDCS(add); } }, "in DCS"));
529        }
530
531        public String[] helpCommandRegex = {
532                "Displays a list of people connected to IRC from IPs matching the specified regex",
533                "<regex> [<ChannelName>]",
534                "<Regex> is the regex to match people's IPs against",
535                "<ChannelName> is the name of the channel to return results for."
536        };
537        public void commandRegex(final Message mes)
538        {
539                final Matcher ma = Pattern.compile("^(.+?)((?: [^ ]+)?)$").matcher(mods.util.getParamString(mes).trim());
540                if (!ma.matches())
541                {
542                        irc.sendContextReply(mes, "Expected: regex [what]");
543                        return;
544                }
545                String what = ma.group(2).trim();
546                if (what.length() == 0)
547                        what = mes.getContext();
548
549                final Pattern p = Pattern.compile(ma.group(1));
550
551                goDo(what, fromPredicate(mes, new Predicate() { @Override boolean hit(final InetAddress add) { return matches(p, add); } }, "matching"));
552        }
553
554        class MutableInteger
555        {
556                private int value;
557
558                public MutableInteger(final int value)
559                {
560                        this.value = value;
561                }
562
563                public int preDecrement()
564                {
565                        return --value;
566                }
567        }
568
569        public void commandGlobalDCS(final Message mes)
570        {
571                global(mes, Location.DCS, "in DCS");
572        }
573
574        public void commandGlobalResnet(final Message mes)
575        {
576                global(mes, Location.Resnet, "on resnet");
577        }
578
579        public void commandGlobalCampus(final Message mes)
580        {
581                global(mes, Location.Campus, "on campus");
582        }
583
584        public void commandHostname(final Message mes) throws UnknownHostException
585        {
586                final List<String> params = new ArrayList<String>(mods.util.getParams(mes));
587                String context = mes.getContext();
588
589                if (params.size() < 2)
590                {
591                        irc.sendContextReply(mes, "Usage: " + params.get(0) + " address [[address...] context]");
592                        return;
593                }
594                params.remove(0); // Command name.
595
596                if (params.size() >= 2)
597                        context = params.remove(params.size()-1);
598
599                final Set<InetAddress> ipees = new HashSet<InetAddress>();
600                for (final String host : params)
601                        ipees.add(InetAddress.getByName(host));
602
603                final String finalContext = context;
604
605                goDo(context, fromPredicate(mes, new Predicate()
606                {
607                        @Override
608                        boolean hit(final InetAddress add)
609                        {
610                                // Compares the IPs alone, ignoring the host string.
611                                for (final InetAddress ip : ipees)
612                                        if (add.equals(ip))
613                                                return true;
614                                return false;
615                        }
616                }, "at the same place as " + hrList(params, " and/or ") +
617                        (finalContext.charAt(0) != '#' ? " (Did you really mean to look in " + finalContext + "?)" : "")));
618        }
619
620        private void global(final Message mes, final Location loc, final String str)
621        {
622                final Set<String> users = new HashSet<String>();
623                final MutableInteger count = new MutableInteger(channels.length);
624
625                for (final String channel : channels)
626                {
627                        goDo(channel,
628                                new Callback(mes)
629                                {
630                                        @Override
631                                        public void complete(final Details d)
632                                        {
633                                                for (final Entry<String, Set<InetAddress>> entr : d.users.entrySet())
634                                                {
635                                                        for (final InetAddress add : entr.getValue())
636                                                        {
637                                                                boolean flag = false;
638
639                                                                switch (loc)
640                                                                {
641                                                                        case DCS:
642                                                                                flag = isDCS(add);
643                                                                                break;
644                                                                        case Resnet:
645                                                                                flag = isResnet(add);
646                                                                                break;
647                                                                        case Campus:
648                                                                                flag = isCampus(add);
649                                                                                break;
650                                                                }
651
652                                                                if (flag)
653                                                                {
654                                                                        users.add(entr.getKey());
655                                                                }
656                                                        }
657                                                }
658
659                                                synchronized (count)
660                                                {
661                                                        if (count.preDecrement() == 0)
662                                                        {
663                                                                irc.sendContextReply(target, "Total users " + str + ": " + users.size());
664                                                        }
665                                                }
666                                        }
667                                }
668                        );
669                }
670        }
671       
672        private ContextEvent context(final String channel) {
673                return new ContextEvent() {
674                        @Override public String getContext() {
675                                return "#compsoc";
676                        }
677                };
678        }
679       
680        /**
681         * TODO: allow user settable hostnames
682         * Assumes 1..5,6..10,11..15 ...
683         * @return
684         */
685        private InetAddress[][] buildCache(final String prefix, final PrintWriter err) {
686                final InetAddress[][] cache = new InetAddress[5][5];
687                for (int row = 0; row < 5; row++) {
688                        for (int index = 1; index < 6; index++) {
689                                final int n = row*5 + index;
690                                try {
691                                        cache[row][index-1] = InetAddress.getByName(prefix+"-"+n+".dcs.warwick.ac.uk");
692                                } catch (UnknownHostException e) {
693                                        err.write("Couldn't lookup "+prefix+"-"+n);
694                                        cache[row][index-1] = null;
695                                }
696                        }
697                }
698                return cache;
699        }
700       
701        /**
702         *
703         * @param out
704         * @param params
705         * @param user
706         */
707        public void webStalker(final PrintWriter out, final String params, final String[] user) {
708                out.println("HTTP/1.0 200 OK");
709                out.println("Content-Type: text/html");
710                out.println();
711                out.println("<html><body><table>");
712               
713                final InetAddress[][] cache = buildCache("viglab", out);
714               
715                // this internal api really wants closures
716                goDo("#compsoc", new Callback(context("#compsoc")) {
717                        @Override
718                        void complete(Details d) {
719                                // Calculate inverse
720                                final Map<InetAddress,Set<String>> inverse = new HashMap<InetAddress, Set<String>>();
721                                for (Map.Entry<String, Set<InetAddress>> person : d.localmap.entrySet()) {
722                                        for (InetAddress addr : person.getValue()) {
723                                                Set<String> people = inverse.get(addr);
724                                                if(people == null) {
725                                                        people = new HashSet<String>();
726                                                        inverse.put(addr, people);
727                                                }
728                                                people.add(person.getKey());
729                                        }
730                                }
731                               
732                                for (final InetAddress[] row : cache) {
733                                        out.println("<tr>");
734                                        for (final InetAddress pc: row) {
735                                                out.println("<td>");
736                                                final Set<String> people = inverse.get(pc);
737                                                for (String name : people) {
738                                                        out.println("<p>"+name+"</p>");
739                                                }
740                                                out.println("</td>");
741                                        }
742                                        out.println("</tr>");
743                                }
744                               
745                                //d.localmap
746                                out.println("</table></body></html>");
747                        }
748                });
749        }
750}
Note: See TracBrowser for help on using the browser.