1 22 23 package server; 24 25 import java.io.IOException ; 26 import javax.net.ssl.*; 27 import java.util.Calendar ; 28 import java.util.Enumeration ; 29 import java.util.Hashtable ; 30 import java.util.LinkedList ; 31 import java.io.BufferedOutputStream ; 32 import java.io.File ; 33 import java.io.FileNotFoundException ; 34 import java.io.FileOutputStream ; 35 import java.io.PrintWriter ; 36 import java.io.BufferedReader ; 37 import java.io.FileReader ; 38 import org.xml.sax.*; 39 import org.xml.sax.helpers.DefaultHandler ; 40 import javax.xml.parsers.SAXParserFactory ; 41 import javax.xml.parsers.ParserConfigurationException ; 42 import javax.xml.parsers.SAXParser ; 43 44 import common.*; 45 import common.sd.*; 46 47 48 53 public final class LlamaChatServer extends Thread { 54 55 private static LlamaChatServer listeningServer; 56 public static int PORT = 42412; 57 public static String sysLogFile = "llamachat.log"; 58 public static String adminPass = "llamachat"; 59 public static LinkedList connectingUsers; 60 public static Hashtable connectedUsers; 61 public static boolean running; 62 public static boolean allowAdmin = true; 63 private static PrintWriter sysLogOut; 64 public static String chatLogPath = "."; 65 private static Hashtable channelFiles; 66 public static String userExportFile = null; 67 public static ChannelManager channels; 68 public static String welcomeMessage = null; 69 public static String serverConfigFile = "llamachatconf.xml"; 70 71 75 synchronized public void newUser(SSLSocket s) { 76 ClientConnection cc = new ClientConnection(this, s); 77 connectingUsers.add(cc); 78 } 79 80 89 synchronized public boolean finalizeUser(String uname, 90 ClientConnection cc) { 91 if (connectedUsers.containsKey(uname) || 92 connectedUsers.contains(cc)) { 93 cc.writeObject(new SD_Error("Already a user of that name or " + 94 "already connected. \nType \\rename <new name> to " + 95 "choose a different name")); 96 log(cc, "failed [duplicate exists]"); 97 return false; 98 } else if (uname.length() > 10) { 99 cc.writeObject(new SD_Error("Username too long.\n" + 100 "Type \\rename <new name> to choose " + 101 "a different name")); 102 log(cc, "failed [bad name]"); 103 return false; 104 } else if (uname.equals("server") || !uname.matches("[\\w_-]+?")) { 105 cc.writeObject(new SD_Error("Invalid username " + uname + ".\n" + 106 "Type \\rename <new name> to choose " + 107 "a different name")); 108 log(cc, "failed [bad name]"); 109 return false; 110 } else if (connectingUsers.remove(cc)) { 111 cc.writeObject(new SD_ServerCap(SD_ServerCap.T_CREATE, 112 new Character (channels.allowUserChannels))); 113 if (welcomeMessage != null) { 114 cc.writeObject(new SD_Chat("server", welcomeMessage)); 115 } 116 cc.writeObject(new SD_Channel(false, cc.channel, null)); 117 sendUserList(cc); 118 Enumeration e = channels.enumerate(); 119 while (e.hasMoreElements()) { 120 String channel = (String ) e.nextElement(); 121 cc.writeObject(new SD_Channel(true, channel, 122 channels.channelHasPass(channel))); 123 } 124 connectedUsers.put(uname, cc); 125 broadcast(new SD_UserAdd(uname), uname, cc.channel); 126 updateUserExport(); 127 log(cc, "connected as " + uname); 128 return true; 129 } 130 131 cc.writeObject(new SD_Error("invalid connection procedure, please try again")); 132 return false; 133 } 134 135 139 synchronized public void sendUserList(ClientConnection cc) { 140 Enumeration e = connectedUsers.keys(); 141 while (e.hasMoreElements()) { 142 String un = (String ) e.nextElement(); 143 if (cc.name.equals(un)) { 144 continue; 145 } 146 ClientConnection o = (ClientConnection) connectedUsers.get(un); 147 if (o.channel.equals(cc.channel)) { 148 cc.writeObject(new SD_UserAdd(un)); 149 if (o.isAdmin()) { 150 cc.writeObject(new SD_AdminAdd(un)); 151 } 152 } 153 } 154 cc.writeObject(new SD_UserAdd(null)); } 156 157 163 synchronized public boolean sendTo(SocketData sd, String to) { 164 ClientConnection o = (ClientConnection) connectedUsers.get(to); 165 if (o != null) { 166 o.writeObject(sd); 167 return true; 168 } else { 169 return false; 170 } 171 } 172 173 179 synchronized public void broadcast(SocketData sd, String from) { 180 Enumeration e = connectedUsers.keys(); 181 while (e.hasMoreElements()) { 182 String to = (String ) e.nextElement(); 183 if (!to.equals(from)) { 184 ClientConnection o = (ClientConnection) connectedUsers.get(to); 185 o.writeObject(sd); 186 } 187 } 188 } 189 190 198 synchronized public void broadcast(SocketData sd, String from, String c) { 199 Enumeration e = connectedUsers.keys(); 200 while (e.hasMoreElements()) { 201 String to = (String ) e.nextElement(); 202 if (!to.equals(from)) { 203 ClientConnection o = (ClientConnection) connectedUsers.get(to); 204 if (o.channel.equals(c)) { 205 o.writeObject(sd); 206 207 } 208 } 209 } 210 } 211 212 217 synchronized public void delete(ClientConnection cc) { 218 broadcast(new SD_UserDel(cc.name), null, cc.channel); 219 } 220 221 226 synchronized public void kill(ClientConnection cc) { 227 try { 228 if (cc.name != null && connectedUsers.remove(cc.name) == cc) { 229 log(cc, cc.name + " disconnected"); 230 updateUserExport(); 231 if (channels.userDel(cc.channel)) { 232 broadcast(new SD_Channel(true, cc.channel, null), null); 233 chatLog(cc, false); 234 } 235 delete(cc); 236 } 237 } catch (NullPointerException e) { 238 e.printStackTrace(); 239 if (cc != null) { 240 log("null pointer on kill: " + cc.name + " (" + cc.channel + ")"); 241 } else { 242 log("got sent a null cc"); 243 } 244 } 245 } 246 247 252 public static String getTimestamp() { 253 Calendar rightNow = Calendar.getInstance(); 254 return rightNow.get(Calendar.MONTH) + "/" + 255 rightNow.get(Calendar.DATE) + "/" + 256 rightNow.get(Calendar.YEAR) + " " + 257 rightNow.get(Calendar.HOUR_OF_DAY) + ":" + 258 rightNow.get(Calendar.MINUTE) + ":" + 259 rightNow.get(Calendar.SECOND); 260 } 261 262 266 synchronized private void log(String s) { 267 if (sysLogOut != null) { 268 sysLogOut.println(getTimestamp() + " - " + s); 269 sysLogOut.flush(); 270 } 271 } 272 273 279 public void log(ClientConnection cc, String s) { 280 log(cc.ip + " - " + s); 281 } 282 283 291 synchronized public boolean chatLog(ClientConnection cc, boolean start) { 292 if (cc == null) { 293 Enumeration e = channelFiles.keys(); 294 while (e.hasMoreElements()) { 295 String name = (String ) e.nextElement(); 296 ChatFileItem item = (ChatFileItem)channelFiles.get(name); 297 closeLog(null, item); 298 } 299 return true; 300 } 301 ChatFileItem currentWriter=(ChatFileItem)channelFiles.get(cc.channel); 302 if (start) { 303 if (currentWriter != null && currentWriter.logging) { 304 cc.writeObject(new SD_Error("already logging " + cc.channel)); 305 return false; 306 } 307 if (currentWriter == null) { 308 currentWriter = new ChatFileItem(); 309 channelFiles.put(cc.channel, currentWriter); 310 } 311 String fname = chatLogPath + System.getProperty("file.separator") + "chat-" + cc.channel + ".log"; 312 try { 313 currentWriter.chatOut = 314 new PrintWriter (new BufferedOutputStream ( 315 new FileOutputStream (new File (fname), true))); 316 currentWriter.chatOut.println("log started by " + 317 (cc!=null?cc.name:"server") + 318 " at " + getTimestamp()); 319 currentWriter.chatOut.println("===================================================="); 320 currentWriter.chatOut.flush(); 321 currentWriter.logging = true; 322 } catch (FileNotFoundException e) { 323 cc.writeObject(new SD_Error("error opening " + fname)); 324 listeningServer.running = false; 325 return false; 326 } 327 return true; 328 } 329 330 return closeLog(cc, currentWriter); 331 } 332 333 339 private boolean closeLog(ClientConnection cc, ChatFileItem currentWriter) { 340 if (currentWriter == null || currentWriter.chatOut == null) { 341 return false; 343 } 344 currentWriter.chatOut.println("===================================================="); 345 currentWriter.chatOut.println("log stopped by " + 346 (cc!=null?cc.name:"server") + 347 " at " + getTimestamp()); 348 currentWriter.chatOut.println("===================================================="); 349 currentWriter.chatOut.flush(); 350 currentWriter.chatOut.close(); 351 currentWriter.chatOut = null; 352 currentWriter.logging = false; 353 if (cc != null) { 354 channelFiles.remove(cc.channel); 355 } 356 return true; 357 } 358 359 364 synchronized public void chatLog(ClientConnection cc, String message) { 365 ChatFileItem currentWriter = (ChatFileItem)channelFiles.get(cc.channel); 366 if (currentWriter != null && currentWriter.logging && 367 currentWriter.chatOut != null) { 368 currentWriter.chatOut.println(cc.name + ": " + message); 369 currentWriter.chatOut.flush(); 370 } 371 } 372 373 379 synchronized static public boolean updateUserExport() { 380 if (userExportFile == null) { 381 return false; 382 } 383 try { 384 PrintWriter pw = new PrintWriter (new BufferedOutputStream ( 385 new FileOutputStream (new File (userExportFile), false))); 386 Enumeration e = connectedUsers.keys(); 387 int i; 388 for (i = 0; e.hasMoreElements(); i++) { 389 String usr = (String ) e.nextElement(); 390 ClientConnection o = (ClientConnection)connectedUsers.get(usr); 391 pw.println(usr + " (" + o.channel + ")"); 392 } 393 394 if (i == 0) { 395 pw.println("(no connected users)"); 396 } 397 398 pw.close(); 399 } catch (FileNotFoundException e) { 400 System.err.println("unable to access userExportFile: " + 401 userExportFile); 402 userExportFile = null; 403 } 404 return true; 405 } 406 407 414 synchronized public String newChannel(String name, String pass, ClientConnection cc) { 415 String ret; 416 if ((ret = channels.addUserChannel(name, pass, cc)) != null) { 417 return ret; 418 } 419 broadcast(new SD_Channel(true, name, (pass == null ? null : "")), null); 420 return null; 421 } 422 423 428 synchronized public boolean channelExists(String name) { 429 return channels.channelExists(name); 430 } 431 432 437 public void run() { 438 Runtime.getRuntime().addShutdownHook(new ShutdownThread(this)); 439 try { 440 SSLServerSocketFactory factory = 441 (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); 442 SSLServerSocket sslIncoming = 443 (SSLServerSocket) factory.createServerSocket (PORT); 444 String [] enabledCipher = sslIncoming.getSupportedCipherSuites (); 445 sslIncoming.setEnabledCipherSuites (enabledCipher); 446 447 log("Server Started"); 448 while(running) { 449 SSLSocket s = (SSLSocket)sslIncoming.accept(); 450 newUser(s); 451 } 452 } catch (IOException e) { System.out.println("Error: " + e); } 453 log("Server Stopped"); 454 } 455 456 457 460 LlamaChatServer() { 461 running = true; 462 } 463 464 468 public static void main(String args[]) { 469 listeningServer = new LlamaChatServer(); 470 472 connectingUsers = new LinkedList (); 473 connectedUsers = new Hashtable (); 474 channels = new ChannelManager(); 475 channelFiles = new Hashtable (); 476 477 478 try { 479 DefaultHandler handler = new ConfigParser(listeningServer); 480 SAXParserFactory factory = SAXParserFactory.newInstance(); 481 SAXParser saxParser = factory.newSAXParser(); 482 saxParser.parse(new File (serverConfigFile), handler); 483 } catch (Throwable t) { 484 System.err.println("error parsing " + serverConfigFile); 485 running = false; 486 } 487 488 try { 489 sysLogOut = new PrintWriter (new BufferedOutputStream ( 490 new FileOutputStream (new File (sysLogFile), true))); 491 } catch (FileNotFoundException e) { 492 System.err.println(sysLogFile + " not found."); 493 running = false; 494 } 495 496 updateUserExport(); 497 498 listeningServer.start(); 499 try { 500 listeningServer.join(); 501 } catch (InterruptedException e) { } 502 } 503 504 507 public class ShutdownThread extends Thread { 508 private LlamaChatServer lcs; 509 ShutdownThread(LlamaChatServer l) { 510 lcs = l; 511 } 512 public void run() { 513 log("Server Stopped"); 514 sysLogOut.close(); 515 chatLog(null, false); 516 } 517 } 518 519 522 private class ChatFileItem { 523 public boolean logging; 524 public PrintWriter chatOut; 525 526 ChatFileItem() { 527 logging = false; 528 chatOut = null; 529 } 530 } 531 } 532 | Popular Tags |