PageRenderTime 137ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/L2EmuProject-Login/src/main/java/net/l2emuproject/loginserver/manager/LoginManager.java

http://l2emu.googlecode.com/
Java | 696 lines | 451 code | 73 blank | 172 comment | 62 complexity | 5a1aa6da3464e866cdaad2dc6aa2ffe9 MD5 | raw file
Possible License(s): GPL-3.0
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package net.l2emuproject.loginserver.manager;
  16. import java.io.UnsupportedEncodingException;
  17. import java.math.BigDecimal;
  18. import java.net.InetAddress;
  19. import java.security.GeneralSecurityException;
  20. import java.security.KeyPairGenerator;
  21. import java.security.MessageDigest;
  22. import java.security.NoSuchAlgorithmException;
  23. import java.security.interfaces.RSAPrivateKey;
  24. import java.security.spec.RSAKeyGenParameterSpec;
  25. import java.sql.Connection;
  26. import java.sql.PreparedStatement;
  27. import java.sql.SQLException;
  28. import java.util.Collection;
  29. import java.util.Map;
  30. import javax.crypto.Cipher;
  31. import javolution.util.FastList;
  32. import javolution.util.FastMap;
  33. import net.l2emuproject.Config;
  34. import net.l2emuproject.L2Registry;
  35. import net.l2emuproject.loginserver.beans.Accounts;
  36. import net.l2emuproject.loginserver.beans.FailedLoginAttempt;
  37. import net.l2emuproject.loginserver.beans.GameServerInfo;
  38. import net.l2emuproject.loginserver.beans.SessionKey;
  39. import net.l2emuproject.loginserver.network.L2LoginClient;
  40. import net.l2emuproject.loginserver.services.AccountsServices;
  41. import net.l2emuproject.loginserver.services.exception.AccountBannedException;
  42. import net.l2emuproject.loginserver.services.exception.AccountModificationException;
  43. import net.l2emuproject.loginserver.services.exception.AccountWrongPasswordException;
  44. import net.l2emuproject.loginserver.services.exception.HackingException;
  45. import net.l2emuproject.loginserver.services.exception.IPRestrictedException;
  46. import net.l2emuproject.loginserver.services.exception.MaintenanceException;
  47. import net.l2emuproject.loginserver.services.exception.MaturityException;
  48. import net.l2emuproject.loginserver.status.LoginStatusServer;
  49. import net.l2emuproject.loginserver.thread.GameServerThread;
  50. import net.l2emuproject.network.status.ServerStatus;
  51. import net.l2emuproject.tools.codec.Base64;
  52. import net.l2emuproject.tools.math.ScrambledKeyPair;
  53. import net.l2emuproject.tools.random.Rnd;
  54. import org.apache.commons.logging.Log;
  55. import org.apache.commons.logging.LogFactory;
  56. /**
  57. * This class handles login on loginserver.
  58. * It store connection for each account.
  59. *
  60. * The ClientThread use LoginManager to :
  61. * - store his connection identifier
  62. * - retrieve basic information
  63. * - delog an account
  64. */
  65. public class LoginManager
  66. {
  67. private static final Log _log = LogFactory.getLog(LoginManager.class);
  68. private static final Log _logLogin = LogFactory.getLog("login");
  69. private static final Log _logLoginTries = LogFactory.getLog("login.try");
  70. private static final Log _logLoginFailed = LogFactory.getLog("login.failed");
  71. private static final class SingletonHolder
  72. {
  73. private static final LoginManager INSTANCE = new LoginManager();
  74. }
  75. public static LoginManager getInstance()
  76. {
  77. return SingletonHolder.INSTANCE;
  78. }
  79. /** Authed Clients on LoginServer*/
  80. protected Map<String, L2LoginClient> _loginServerClients = new FastMap<String, L2LoginClient>().shared();
  81. /** Keep trace of login attempt for an inetadress*/
  82. private Map<InetAddress, FailedLoginAttempt> _hackProtection;
  83. private ScrambledKeyPair[] _keyPairs;
  84. protected byte[][] _blowfishKeys;
  85. private static final int BLOWFISH_KEYS = 20;
  86. private AccountsServices _service = null;
  87. public static enum AuthLoginResult
  88. {
  89. INVALID_PASSWORD, ACCOUNT_BANNED, ALREADY_ON_LS, ALREADY_ON_GS, AUTH_SUCCESS, SYSTEM_ERROR
  90. }
  91. private FastList<L2LoginClient> _connections;
  92. /**
  93. * Private constructor to avoid direct instantiation.
  94. * Initialize a key generator.
  95. */
  96. private LoginManager()
  97. {
  98. try
  99. {
  100. _log.info("LoginManager: initializing.");
  101. _hackProtection = new FastMap<InetAddress, FailedLoginAttempt>();
  102. _keyPairs = new ScrambledKeyPair[10];
  103. _service = (AccountsServices) L2Registry.getBean("AccountsServices");
  104. _connections = new FastList<L2LoginClient>();
  105. KeyPairGenerator keygen = null;
  106. try
  107. {
  108. keygen = KeyPairGenerator.getInstance("RSA");
  109. final RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4);
  110. keygen.initialize(spec);
  111. }
  112. catch (final GeneralSecurityException e)
  113. {
  114. _log.fatal("Error in RSA setup:", e);
  115. _log.info("Server shutting down now");
  116. System.exit(1);
  117. return;
  118. }
  119. //generate the initial set of keys
  120. for (int i = 0; i < 10; i++)
  121. _keyPairs[i] = new ScrambledKeyPair(keygen.generateKeyPair());
  122. _log.info("LoginManager: Cached 10 KeyPairs for RSA communication");
  123. testCipher((RSAPrivateKey) _keyPairs[0].getPair().getPrivate());
  124. // Store keys for blowfish communication
  125. generateBlowFishKeys();
  126. }
  127. catch (final GeneralSecurityException e)
  128. {
  129. _log.fatal("FATAL: Failed initializing LoginManager. Reason: " + e.getMessage(), e);
  130. System.exit(1);
  131. }
  132. }
  133. /**
  134. * This is mostly to force the initialization of the Crypto Implementation, avoiding it being done on runtime when its first needed.<BR>
  135. * In short it avoids the worst-case execution time on runtime by doing it on loading.
  136. * @param key Any private RSA Key just for testing purposes.
  137. * @throws GeneralSecurityException if a underlying exception was thrown by the Cipher
  138. */
  139. private void testCipher(RSAPrivateKey key) throws GeneralSecurityException
  140. {
  141. // avoid worst-case execution, KenM
  142. final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
  143. rsaCipher.init(Cipher.DECRYPT_MODE, key);
  144. }
  145. private void generateBlowFishKeys()
  146. {
  147. _blowfishKeys = new byte[BLOWFISH_KEYS][16];
  148. for (int i = 0; i < BLOWFISH_KEYS; i++)
  149. for (int j = 0; j < _blowfishKeys[i].length; j++)
  150. _blowfishKeys[i][j] = (byte) (Rnd.nextInt(255) + 1);
  151. _log.info("Stored " + _blowfishKeys.length + " keys for Blowfish communication");
  152. }
  153. /**
  154. * @return Returns a random key
  155. */
  156. public byte[] getBlowfishKey()
  157. {
  158. return _blowfishKeys[(int) (Math.random() * BLOWFISH_KEYS)];
  159. }
  160. /**
  161. *
  162. * @param account
  163. * @param client
  164. * @return a SessionKey
  165. */
  166. public SessionKey assignSessionKeyToLogin(String account, L2LoginClient client)
  167. {
  168. SessionKey key;
  169. key = new SessionKey(Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE));
  170. _loginServerClients.put(account, client);
  171. return key;
  172. }
  173. public void removeAuthedLoginClient(String account)
  174. {
  175. _loginServerClients.remove(account);
  176. }
  177. public boolean isAccountInLoginServer(String account)
  178. {
  179. return _loginServerClients.containsKey(account);
  180. }
  181. public SessionKey assignSessionKeyToClient(String account, L2LoginClient client)
  182. {
  183. SessionKey key;
  184. key = new SessionKey(Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE), Rnd.nextInt(Integer.MAX_VALUE));
  185. _loginServerClients.put(account, client);
  186. return key;
  187. }
  188. public GameServerInfo getAccountOnGameServer(String account)
  189. {
  190. final Collection<GameServerInfo> serverList = GameServerManager.getInstance().getRegisteredGameServers().values();
  191. for (final GameServerInfo gsi : serverList)
  192. {
  193. final GameServerThread gst = gsi.getGameServerThread();
  194. if (gst != null && gst.hasAccountOnGameServer(account))
  195. return gsi;
  196. }
  197. return null;
  198. }
  199. public boolean isAccountInAnyGameServer(String account)
  200. {
  201. final Collection<GameServerInfo> serverList = GameServerManager.getInstance().getRegisteredGameServers().values();
  202. for (final GameServerInfo gsi : serverList)
  203. {
  204. final GameServerThread gst = gsi.getGameServerThread();
  205. if (gst != null && gst.hasAccountOnGameServer(account))
  206. return true;
  207. }
  208. return false;
  209. }
  210. /**
  211. *
  212. * @param account
  213. * @param password
  214. * @param client
  215. * @return true if validation succeed or false if we have technical problems
  216. * @throws HackingException if we detect a hacking attempt
  217. * @throws AccountBannedException if the use was banned
  218. * @throws AccountWrongPasswordException if the password was wrong
  219. */
  220. public AuthLoginResult tryAuthLogin(String account, String password, L2LoginClient client) throws AccountBannedException, AccountWrongPasswordException,
  221. IPRestrictedException
  222. {
  223. AuthLoginResult ret = AuthLoginResult.INVALID_PASSWORD;
  224. try
  225. {
  226. // check auth
  227. if (loginValid(account, password, client))
  228. {
  229. // login was successful, verify presence on Gameservers
  230. ret = AuthLoginResult.ALREADY_ON_GS;
  231. if (!isAccountInAnyGameServer(account))
  232. {
  233. // account isnt on any GS, verify LS itself
  234. ret = AuthLoginResult.ALREADY_ON_LS;
  235. // don't allow 2 simultaneous login
  236. synchronized (_loginServerClients)
  237. {
  238. if (!_loginServerClients.containsKey(account))
  239. {
  240. _loginServerClients.put(account, client);
  241. ret = AuthLoginResult.AUTH_SUCCESS;
  242. }
  243. }
  244. final Accounts acc = _service.getAccountById(account);
  245. // keep access level in the L2LoginClient
  246. client.setAccessLevel(acc.getAccessLevel());
  247. // keep last server choice
  248. client.setLastServerId(acc.getLastServerId());
  249. client.setAge(acc.getBirthYear(), acc.getBirthMonth(), acc.getBirthDay());
  250. }
  251. }
  252. }
  253. catch (final NoSuchAlgorithmException e)
  254. {
  255. _log.error("could not check password:", e);
  256. ret = AuthLoginResult.SYSTEM_ERROR;
  257. }
  258. catch (final UnsupportedEncodingException e)
  259. {
  260. _log.error("could not check password:", e);
  261. ret = AuthLoginResult.SYSTEM_ERROR;
  262. }
  263. catch (final AccountModificationException e)
  264. {
  265. _log.warn("could not check password:", e);
  266. ret = AuthLoginResult.SYSTEM_ERROR;
  267. }
  268. return ret;
  269. }
  270. public L2LoginClient getAuthedClient(String account)
  271. {
  272. return _loginServerClients.get(account);
  273. }
  274. public SessionKey getKeyForAccount(String account)
  275. {
  276. final L2LoginClient client = _loginServerClients.get(account);
  277. if (client != null)
  278. return client.getSessionKey();
  279. return null;
  280. }
  281. public String getHostForAccount(String account)
  282. {
  283. final L2LoginClient client = getAuthedClient(account);
  284. return client != null ? client.getIp() : "-1";
  285. }
  286. /**
  287. * Login is possible if number of player < max player for this GS
  288. * and the status of the GS != STATUS_GM_ONLY
  289. * All those conditions are not applied if the player is a GM
  290. * @return
  291. */
  292. public boolean isLoginPossible(int age, int access, int serverId) throws MaintenanceException, MaturityException
  293. {
  294. final GameServerInfo gsi = GameServerManager.getInstance().getRegisteredGameServerById(serverId);
  295. if (gsi != null && gsi.isAuthed())
  296. {
  297. if (gsi.getStatus() == ServerStatus.STATUS_GM_ONLY && access < Config.GM_MIN)
  298. throw MaintenanceException.MAINTENANCE;
  299. //Some accounts, like GM ones, can always connect
  300. if (age < gsi.getAgeLimitation())
  301. throw new MaturityException(age, gsi.getAgeLimitation());
  302. return gsi.getCurrentPlayerCount() < gsi.getMaxPlayers() || access >= Config.GM_MIN;
  303. }
  304. else
  305. throw MaintenanceException.MAINTENANCE;
  306. }
  307. /**
  308. *
  309. * @param ServerID
  310. * @return online player count for a server
  311. */
  312. public int getOnlinePlayerCount(int serverId)
  313. {
  314. final GameServerInfo gsi = GameServerManager.getInstance().getRegisteredGameServerById(serverId);
  315. if (gsi != null && gsi.isAuthed())
  316. return gsi.getCurrentPlayerCount();
  317. return 0;
  318. }
  319. /***
  320. *
  321. * @param ServerID
  322. * @return max allowed online player for a server
  323. */
  324. public int getMaxAllowedOnlinePlayers(int id)
  325. {
  326. final GameServerInfo gsi = GameServerManager.getInstance().getRegisteredGameServerById(id);
  327. if (gsi != null)
  328. return gsi.getMaxPlayers();
  329. return 0;
  330. }
  331. /**
  332. *
  333. * @param user
  334. * @param banLevel
  335. */
  336. public void setAccountAccessLevel(String account, int banLevel)
  337. {
  338. try
  339. {
  340. _service.changeAccountLevel(account, banLevel);
  341. }
  342. catch (final AccountModificationException e)
  343. {
  344. _log.error("Could not set accessLevel for user: " + account, e);
  345. }
  346. }
  347. /**
  348. *
  349. * @param user
  350. * @param lastServerId
  351. */
  352. public void setAccountLastServerId(String account, int lastServerId)
  353. {
  354. try
  355. {
  356. final Accounts acc = _service.getAccountById(account);
  357. acc.setLastServerId(lastServerId);
  358. _service.addOrUpdateAccount(acc);
  359. }
  360. catch (final AccountModificationException e)
  361. {
  362. _log.error("Could not set last server for user: " + account, e);
  363. }
  364. }
  365. /**
  366. *
  367. * @param user
  368. * @return true if a user is a GM account
  369. */
  370. public boolean isGM(Accounts acc)
  371. {
  372. if (acc != null)
  373. return acc.getAccessLevel() >= Config.GM_MIN;
  374. else
  375. return false;
  376. }
  377. /**
  378. *
  379. * @param user
  380. * @return account if exist, null if not
  381. */
  382. public Accounts getAccount(String user)
  383. {
  384. return _service.getAccountById(user);
  385. }
  386. /**
  387. * <p>This method returns one of the 10 {@link ScrambledKeyPair}.</p>
  388. * <p>One of them the renewed asynchronously using a {@link UpdateKeyPairTask} if necessary.</p>
  389. * @return a scrambled keypair
  390. */
  391. public ScrambledKeyPair getScrambledRSAKeyPair()
  392. {
  393. return _keyPairs[Rnd.nextInt(10)];
  394. }
  395. /**
  396. * user name is not case sensitive any more
  397. * @param user
  398. * @param password
  399. * @param address
  400. * @return true if all operations succeed
  401. * @throws NoSuchAlgorithmException if SHA is not supported
  402. * @throws UnsupportedEncodingException if UTF-8 is not supported
  403. * @throws AccountModificationException if we were unable to modify the account
  404. * @throws AccountBannedException if account is banned
  405. * @throws AccountWrongPasswordException if the password is wrong
  406. */
  407. public boolean loginValid(String user, String password, L2LoginClient client) throws NoSuchAlgorithmException, UnsupportedEncodingException,
  408. AccountModificationException, AccountBannedException, AccountWrongPasswordException, IPRestrictedException
  409. {
  410. final InetAddress address = client.getInetAddress();
  411. if (BanManager.getInstance().isRestrictedAddress(address))
  412. throw new IPRestrictedException();
  413. else if (BanManager.getInstance().isBannedAddress(address))
  414. throw new IPRestrictedException(BanManager.getInstance().getBanExpiry(address));
  415. // player disconnected meanwhile
  416. if (address == null)
  417. return false;
  418. return loginValid(user, password, address);
  419. }
  420. /**
  421. * user name is not case sensitive any more
  422. * @param user
  423. * @param password
  424. * @param address
  425. * @return true if all operations succeed
  426. * @throws NoSuchAlgorithmException if SHA is not supported
  427. * @throws UnsupportedEncodingException if UTF-8 is not supported
  428. * @throws AccountModificationException if we were unable to modify the account
  429. * @throws AccountBannedException if account is banned
  430. * @throws AccountWrongPasswordException if the password is wrong
  431. */
  432. public boolean loginValid(String user, String password, InetAddress address) throws NoSuchAlgorithmException, UnsupportedEncodingException,
  433. AccountModificationException, AccountBannedException, AccountWrongPasswordException
  434. {
  435. _logLoginTries.info("User trying to connect '" + user + "' " + (address == null ? "null" : address.getHostAddress()));
  436. // o Convert password in utf8 byte array
  437. // ----------------------------------
  438. final MessageDigest md = MessageDigest.getInstance("SHA");
  439. final byte[] raw = password.getBytes("UTF-8");
  440. final byte[] hash = md.digest(raw);
  441. // o find Account
  442. // -------------
  443. final Accounts acc = _service.getAccountById(user);
  444. // If account is not found
  445. // try to create it if AUTO_CREATE_ACCOUNTS is activated
  446. // or return false
  447. // ------------------------------------------------------
  448. if (acc == null)
  449. {
  450. if (handleAccountNotFound(user, address, hash))
  451. {
  452. handleGoodLogin(user, address);
  453. return true;
  454. }
  455. else
  456. {
  457. handleBadLogin(user, password, address);
  458. throw new AccountWrongPasswordException(user);
  459. }
  460. }
  461. // If account is found
  462. // check ban state
  463. // check password and update last ip/last active
  464. // ---------------------------------------------
  465. else
  466. {
  467. // check the account is not ban
  468. if (acc.getAccessLevel() < 0)
  469. {
  470. handleBadLogin(user, password, address);
  471. throw new AccountBannedException(user);
  472. }
  473. try
  474. {
  475. checkPassword(hash, acc);
  476. acc.setLastactive(new BigDecimal(System.currentTimeMillis()));
  477. if (address != null)
  478. acc.setLastIp(address.getHostAddress());
  479. _service.addOrUpdateAccount(acc);
  480. }
  481. // If password are different
  482. // -------------------------
  483. catch (final AccountWrongPasswordException e)
  484. {
  485. handleBadLogin(user, password, address);
  486. throw e;
  487. }
  488. }
  489. handleGoodLogin(user, address);
  490. return true;
  491. }
  492. /**
  493. * @param user
  494. * @param address
  495. */
  496. private void handleGoodLogin(String user, InetAddress address)
  497. {
  498. // for long running servers, this should prevent blocking
  499. // of users that mistype their passwords once every day :)
  500. if (address != null)
  501. _hackProtection.remove(address);
  502. if (_logLogin.isDebugEnabled())
  503. _logLogin.debug("login successfull for '" + user + "' " + (address == null ? "null" : address.getHostAddress()));
  504. }
  505. /**
  506. *
  507. * If login are different, increment hackProtection counter. It's maybe a hacking attempt
  508. *
  509. * @param user
  510. * @param password
  511. * @param address
  512. */
  513. private void handleBadLogin(String user, String password, InetAddress address)
  514. {
  515. _logLoginFailed.info("login failed for user : '" + user + "' " + (address == null ? "null" : address.getHostAddress()));
  516. // In special case, adress is null, so this protection is useless
  517. if (address != null)
  518. {
  519. final FailedLoginAttempt failedAttempt = _hackProtection.get(address);
  520. int failedCount;
  521. if (failedAttempt == null)
  522. {
  523. _hackProtection.put(address, new FailedLoginAttempt(address, password));
  524. failedCount = 1;
  525. }
  526. else
  527. {
  528. failedAttempt.increaseCounter(password);
  529. failedCount = failedAttempt.getCount();
  530. }
  531. if (failedCount >= Config.LOGIN_TRY_BEFORE_BAN)
  532. {
  533. _log.info("Temporary auto-ban for " + address.getHostAddress() + " (" + Config.LOGIN_BLOCK_AFTER_BAN + " seconds, " + failedCount
  534. + " login tries)");
  535. BanManager.getInstance().addBanForAddress(address, Config.LOGIN_BLOCK_AFTER_BAN * 1000);
  536. }
  537. }
  538. }
  539. /**
  540. * @param hash
  541. * @param acc
  542. * @throws AccountWrongPasswordException if password is wrong
  543. */
  544. private void checkPassword(byte[] hash, Accounts acc) throws AccountWrongPasswordException
  545. {
  546. if (_log.isDebugEnabled())
  547. _log.debug("account exists");
  548. final byte[] expected = Base64.decode(acc.getPassword());
  549. for (int i = 0; i < expected.length; i++)
  550. if (hash[i] != expected[i])
  551. throw new AccountWrongPasswordException(acc.getLogin());
  552. }
  553. /**
  554. * @param user
  555. * @param address
  556. * @param hash
  557. * @return true if accounts was successfully created or false is AUTO_CREATE_ACCOUNTS = false or creation failed
  558. * @throws AccountModificationException
  559. */
  560. private boolean handleAccountNotFound(String user, InetAddress address, byte[] hash) throws AccountModificationException
  561. {
  562. Accounts acc;
  563. if (Config.AUTO_CREATE_ACCOUNTS)
  564. {
  565. if (user.length() >= 2 && user.length() <= 14)
  566. {
  567. acc = new Accounts(user, Base64.encodeBytes(hash), new BigDecimal(System.currentTimeMillis()), 0, 0, 1900, 1, 1,
  568. (address == null ? "null" : address.getHostAddress()));
  569. _service.addOrUpdateAccount(acc);
  570. _logLogin.info("Account created: " + user);
  571. _log.info("An account was newly created: " + user);
  572. LoginStatusServer.tryBroadcast("Account created for player " + user);
  573. return true;
  574. }
  575. _logLogin.warn("Invalid username creation/use attempt: " + user);
  576. return false;
  577. }
  578. _logLogin.warn("No such account exists: " + user);
  579. return false;
  580. }
  581. public void addConnection(L2LoginClient lc)
  582. {
  583. _connections.add(lc);
  584. }
  585. public void remConnection(L2LoginClient lc)
  586. {
  587. _connections.remove(lc);
  588. }
  589. public void setAccountLastTracert(String account, String pcIp, String hop1, String hop2, String hop3, String hop4)
  590. {
  591. Connection con = null;
  592. PreparedStatement statement = null;
  593. try
  594. {
  595. con = L2Registry.getConnection(con);
  596. final String stmt = "UPDATE accounts SET pcIp=?, hop1=?, hop2=?, hop3=?, hop4=? WHERE login=?";
  597. statement = con.prepareStatement(stmt);
  598. statement.setString(1, pcIp);
  599. statement.setString(2, hop1);
  600. statement.setString(3, hop2);
  601. statement.setString(4, hop3);
  602. statement.setString(5, hop4);
  603. statement.setString(6, account);
  604. statement.executeUpdate();
  605. statement.close();
  606. }
  607. catch (final Exception e)
  608. {
  609. _log.warn("Could not set last tracert: " + e.getMessage(), e);
  610. }
  611. finally
  612. {
  613. try
  614. {
  615. con.close();
  616. }
  617. catch (final SQLException e)
  618. {
  619. _log.warn("", e);
  620. }
  621. }
  622. }
  623. }