| 1 | import java.io.BufferedReader; |
|---|
| 2 | import java.io.File; |
|---|
| 3 | import java.io.FileOutputStream; |
|---|
| 4 | import java.io.IOException; |
|---|
| 5 | import java.io.InputStreamReader; |
|---|
| 6 | import java.io.PrintStream; |
|---|
| 7 | import java.io.PrintWriter; |
|---|
| 8 | import java.io.UnsupportedEncodingException; |
|---|
| 9 | import java.net.InetAddress; |
|---|
| 10 | import java.net.URLEncoder; |
|---|
| 11 | import java.net.UnknownHostException; |
|---|
| 12 | import java.sql.Connection; |
|---|
| 13 | import java.sql.PreparedStatement; |
|---|
| 14 | import java.sql.ResultSet; |
|---|
| 15 | import java.sql.SQLException; |
|---|
| 16 | import java.text.SimpleDateFormat; |
|---|
| 17 | import java.util.ArrayList; |
|---|
| 18 | import java.util.Collections; |
|---|
| 19 | import java.util.Date; |
|---|
| 20 | import java.util.HashMap; |
|---|
| 21 | import java.util.HashSet; |
|---|
| 22 | import java.util.LinkedList; |
|---|
| 23 | import java.util.List; |
|---|
| 24 | import java.util.Map; |
|---|
| 25 | import java.util.Queue; |
|---|
| 26 | import java.util.Set; |
|---|
| 27 | import java.util.Map.Entry; |
|---|
| 28 | import java.util.regex.Matcher; |
|---|
| 29 | import java.util.regex.Pattern; |
|---|
| 30 | |
|---|
| 31 | import javax.security.auth.callback.Callback; |
|---|
| 32 | |
|---|
| 33 | import org.jibble.pircbot.ReplyConstants; |
|---|
| 34 | |
|---|
| 35 | import uk.co.uwcs.choob.modules.Modules; |
|---|
| 36 | import uk.co.uwcs.choob.support.IRCInterface; |
|---|
| 37 | import uk.co.uwcs.choob.support.events.ChannelMessage; |
|---|
| 38 | import uk.co.uwcs.choob.support.events.ContextEvent; |
|---|
| 39 | import uk.co.uwcs.choob.support.events.Message; |
|---|
| 40 | import uk.co.uwcs.choob.support.events.ServerResponse; |
|---|
| 41 | |
|---|
| 42 | public 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 | } |
|---|