/* $Id: Dialog.java,v 1.3 2005/06/10 18:03:02 kleiner Exp $
This file is part of HBCI4Java
Copyright (C) 2001-2005 Stefan Palme
HBCI4Java is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
HBCI4Java is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.kapott.hbci.server;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.Security;
import java.util.Date;
import java.util.Hashtable;
import java.util.Properties;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.manager.MsgGen;
import org.kapott.hbci.passport.HBCIPassportInternal;
import org.kapott.hbci.protocol.MSG;
import org.kapott.hbci.protocol.SEG;
import org.kapott.hbci.protocol.factory.MSGFactory;
import org.kapott.hbci.security.Crypt;
import org.kapott.hbci.security.HBCIProvider;
import org.kapott.hbci.security.factory.CryptFactory;
import org.kapott.hbci.server.msg.MsgHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class Dialog
{
private MsgGen msggen; // nachrichtengenerator fr aktuellen dialog
private Properties bpd; // bpd, die fr aktuellen dialog gltig sind
// anonymes passport fr signatur und entschlsselund auf server-seite
private String passportType;
private HBCIPassportInternal localPassport;
private boolean isInitialized; // dialog-init oder quivalent schon ausgefhrt?
private boolean isAnonymous; // anonymer dialog
private Hashtable flags; // flags fr sign/encrypt response usw.
private String hbciversion; // hbci-version, in der dieser dialog gefhrt wird
private String userid; // userid fr aktuellen dialog
private String customerid; // kunden-id fr aktuellen dialog
private String sysid; // system-id fr aktuellen dialog
private String dialogid; // dialog-id fr aktuellen dialog
private int msgnum; // zaehler fr nachrichtennummer
private Date lastactive;
public Dialog(String passportType)
{
HBCIUtils.log("creating new dialog object",HBCIUtils.LOG_DEBUG);
this.flags=new Hashtable();
this.hbciversion=null;
this.msggen=null;
this.bpd=null;
this.userid=null;
this.customerid=null;
this.sysid="0";
this.dialogid="0";
this.msgnum=1;
this.passportType=passportType;
setInitialized(false);
setAnonymous(true);
setFlag("forceEnd",false);
touch();
}
// eingehende Nachricht handeln
public String handleMessage(StringBuffer msg)
{
touch();
MSG ret=null;
try {
initDialog(msg);
// ist nachricht verschlsselt?
StringBuffer cryptedMsgData=new StringBuffer(msg.toString());
MSG cryptedMsg=null;
try {
HBCIUtils.log("checking if message is encrypted",HBCIUtils.LOG_DEBUG);
cryptedMsg=MSGFactory.getInstance().createMSG("Crypted",cryptedMsgData.toString(),cryptedMsgData.length(),msggen,false);
HBCIUtils.log("message is encrypted",HBCIUtils.LOG_INFO);
} catch (Exception e) {
HBCIUtils.log("message does not seem to be encrypted",HBCIUtils.LOG_INFO);
}
try {
StringBuffer decryptedMsgData=new StringBuffer(cryptedMsgData.toString());
if (cryptedMsg!=null) {
HBCIUtils.log("analyzing crypted message",HBCIUtils.LOG_DEBUG);
// schlsselkopf extrahieren
SEG cryptHead=(SEG)cryptedMsg.getElement("Crypted.CryptHead");
// daten aus schlsselkopf holen
Hashtable cryptValues=new Hashtable();
cryptHead.extractValues(cryptValues);
// schlsselkopf checken
checkCryptHead(cryptValues);
try {
// anonymes passport erzeugen (oder holen)
HBCIUtils.log("trying to decrypt encrypted message",HBCIUtils.LOG_DEBUG);
Crypt crypt=CryptFactory.getInstance().createCrypt(cryptedMsg,msggen,getLocalPassport());
try {
decryptedMsgData=new StringBuffer(crypt.decryptIt());
HBCIUtils.log("decrypted message: "+decryptedMsgData,HBCIUtils.LOG_DEBUG);
} finally {
CryptFactory.getInstance().unuseObject(crypt);
}
} catch (Exception e) {
throw new HBCI_Exception("error while decrypting message",e);
}
}
// nachrichtentyp ermitteln (alle msgdefs durchgehen und versuchen zu parsen)
// *** hier evtl. nicht *alle* msgs checken, sondern nur die, die an dieser
// stelle sinn machen
Document syntax=msggen.getSyntax();
NodeList msgdefs=syntax.getElementsByTagName("MSGdef");
int len=msgdefs.getLength();
// alle msgdef-elemente durchlaufen
MSG decryptedMsg=null;
for (int i=0;i<len;i++) {
Element msgdef=(Element)msgdefs.item(i);
String msgName=msgdef.getAttribute("id");
// wenn das eine client-nachrichtenspez. ist
if (!msgName.endsWith("Res")) {
try {
HBCIUtils.log("trying to parse msg as "+msgName,HBCIUtils.LOG_DEBUG);
// crypted / nicht crypted checken
String dontCrypt=msgdef.getAttribute("dontcrypt");
if (dontCrypt==null || dontCrypt.length()==0) { // nachricht muss verschlsselt werden
if (cryptedMsg==null) { // war aber nicht verschlsselt
throw new HBCI_Exception("was not encrypted");
}
} else { // nachricht muss nicht verschlsselt werden
if (cryptedMsg!=null) { // war sie aber doch
throw new HBCI_Exception("was encrypted");
}
}
// versuch, msg zu parsen
decryptedMsg=MSGFactory.getInstance().createMSG(msgName,
decryptedMsgData.toString(),
decryptedMsgData.length(),
msggen,true);
} catch (Exception e) {
HBCIUtils.log("failed",HBCIUtils.LOG_DEBUG);
}
// wenn message parsen geklappt hat
if (decryptedMsg!=null)
break;
}
}
// wenn nachricht gar nicht geparst werden konnte, fehler
if (decryptedMsg==null)
throw new HBCI_Exception("can not parse this message");
HBCIUtils.log("message has been parsed as "+decryptedMsg.getName()+" message",HBCIUtils.LOG_INFO);
try {
// messagehandler fr den jeweiligen nachrichtentyp erzeugen
Class cl=Class.forName("org.kapott.hbci.server.msg.MsgHandler"+decryptedMsg.getName());
Constructor cons=cl.getConstructor(new Class[] {MSG.class,MSG.class,String.class,Dialog.class});
// ret=messagehandler aufrufen (originalnachricht, entschlsselte nachricht
// und <this> bergeben)
MsgHandler handler=(MsgHandler)cons.newInstance(new Object[]
{cryptedMsg,decryptedMsg,decryptedMsgData.toString(),this});
ret=handler.handleMessage();
msgnum=handler.updateMsgNum(msgnum);
} finally {
if (decryptedMsg!=null)
MSGFactory.getInstance().unuseObject(decryptedMsg);
}
} finally {
if (cryptedMsg!=null)
MSGFactory.getInstance().unuseObject(cryptedMsg);
}
} catch (Exception e) {
HBCIUtils.log(e);
if (ret!=null)
MSGFactory.getInstance().unuseObject(ret);
ret=createAbortMessage(e.getClass().getName()+": "+e.getMessage());
}
String retvalue=ret.toString(0);
MSGFactory.getInstance().unuseObject(ret);
return retvalue;
}
// erzeugen einer Dialog-Abbruch-Nachricht
private MSG createAbortMessage(String msg)
{
HBCIUtils.log("creating dialog abort message",HBCIUtils.LOG_INFO);
if (msggen==null) {
createMsgGen("210"); // TODO: hier die hchste der untersttzten hbci-versionen verwenden
}
msggen.reset();
String msgNumber=getMsgNum();
String dialogId=getDialogId();
msggen.set("DialogInitAnonRes.MsgHead.dialogid",dialogId);
msggen.set("DialogInitAnonRes.MsgHead.msgnum",msgNumber);
msggen.set("DialogInitAnonRes.MsgHead.MsgRef.dialogid",dialogId);
msggen.set("DialogInitAnonRes.MsgHead.MsgRef.msgnum",msgNumber);
msggen.set("DialogInitAnonRes.MsgTail.msgnum",msgNumber);
if (msg.length()>80)
msg=msg.substring(0,80);
msggen.set("DialogInitAnonRes.RetGlob.RetVal.code","9800");
msggen.set("DialogInitAnonRes.RetGlob.RetVal.text",msg);
return msggen.generate("DialogInitAnonRes");
}
private void initDialog(StringBuffer msg)
{
// hbciversion steht noch nicht fest
if (msggen==null) {
HBCIUtils.log("have to determine used hbci version",HBCIUtils.LOG_DEBUG);
// hbciversion aus msghead extrahieren
int[] dotPos=new int[3];
for (int i=0;i<3;i++) {
dotPos[i]=msg.indexOf("+",(i==0)?0:(dotPos[i-1]+1));
if (dotPos[i]==-1)
throw new HBCI_Exception("can not extract hbci version from message");
}
// kernel-objekt fr diese hbciversion erzeugen
hbciversion=msg.substring(dotPos[1]+1,dotPos[2]);
if (hbciversion.equals("220") && passportType.equals("PinTan")) {
hbciversion="plus";
}
createMsgGen(hbciversion);
bpd=ServerData.getInstance().getBPD(hbciversion);
}
}
// nachrichtengenerator fr eine bestimmte hbci-version erzeugen
private void createMsgGen(String hbciversion)
{
HBCIUtils.log("searching for already initialized msggen for version "+hbciversion,HBCIUtils.LOG_DEBUG);
// schon mal einen msggen fr diese hbciversion initialisiert?
MsgGen oldgen=ServerData.getInstance().getMsgGen(hbciversion);
if (oldgen==null) { // nein, also neuen msggen erzeugen
HBCIUtils.log("initializing new message generator for version "+hbciversion,HBCIUtils.LOG_INFO);
// pfad zur xml-spec ermitteln
String xmlpath=HBCIUtils.getParam("kernel.kernel.xmlpath");
InputStream syntaxStream=null;
if (xmlpath==null) {
xmlpath="";
}
// inputstream fr xml-spec erzeugen
ClassLoader cl=HBCIUtils.class.getClassLoader();
String filename=xmlpath+"hbci-"+hbciversion+".xml";
syntaxStream=cl.getResourceAsStream(filename);
if (syntaxStream==null)
throw new HBCI_Exception("could not find syntax specification for hbciversion "+hbciversion);
// message-generator erzeugen
try {
msggen=new MsgGen(syntaxStream);
ServerData.getInstance().storeMsgGen(hbciversion,msggen);
if (Security.getProvider("HBCIProvider")==null) {
Security.addProvider(new HBCIProvider());
}
} catch (Exception e) {
throw new HBCI_Exception("could not initialize message generator for hbciversion "+hbciversion,e);
}
HBCIUtils.log("message generator for hbciversion "+hbciversion+" initialized",HBCIUtils.LOG_DEBUG);
} else { // es gab schon mal einen msggen fr diese hbciversion
// msggen muss nicht komplett neu erzeugt werden (syntax parsen dauert lange),
// sondern es kann die syntax eines alten msggen verwendet werden
HBCIUtils.log("found already initialized msggen for version "+hbciversion+" - reusing it",HBCIUtils.LOG_DEBUG);
msggen=new MsgGen(oldgen.getSyntax());
}
}
private void checkCryptHead(Hashtable values)
{
HBCIUtils.log("checking cryptHead data",HBCIUtils.LOG_DEBUG);
String st;
if (!(st=((String)values.get("Crypted.CryptHead.SegHead.seq"))).equals("998"))
throw new HBCI_Exception("segment number of crypthead is invalid: "+st);
/* *** disabled for pintan
if (!(st=((String)values.get("Crypted.CryptHead.secfunc"))).equals("4"))
throw new HBCI_Exception("secfunc of crypthead has invalid value: "+st); */
if (!(st=((String)values.get("Crypted.CryptHead.role"))).equals("1"))
throw new HBCI_Exception("role of crypthead has invalid value: "+st);
// *** usw.
// *** die sysid muss nachtrglich geprft werden, wenn userpassport klar
// ist und sysid feststeht
HBCIUtils.log("cryptHead data seems to be ok",HBCIUtils.LOG_DEBUG);
}
public void createNewDialogId()
{
dialogid=Long.toString(ServerData.getInstance().nextRandom()).substring(1);
if (dialogid.length()>30)
dialogid=dialogid.substring(0,30);
DialogMgr.getInstance().addDialog(dialogid,this);
HBCIUtils.log("created new dialogid "+dialogid,HBCIUtils.LOG_INFO);
}
public String getDialogId()
{
return dialogid;
}
public String getMsgNum()
{
return Integer.toString(msgnum);
}
public MsgGen getMsgGen()
{
return msggen;
}
public boolean isInitialized()
{
return isInitialized;
}
public void setInitialized(boolean init)
{
isInitialized=init;
}
public boolean isAnonymous()
{
return isAnonymous;
}
public void setAnonymous(boolean anon)
{
isAnonymous=anon;
}
public Properties getBPD()
{
return bpd;
}
public String getHBCIVersion()
{
return hbciversion;
}
public boolean getFlag(String name)
{
return ((Boolean)flags.get(name)).booleanValue();
}
public void setFlag(String name,boolean value)
{
flags.put(name,new Boolean(value));
}
public HBCIPassportInternal getLocalPassport()
{
if (localPassport==null) {
try {
Class cl=Class.forName("org.kapott.hbci.server.passport.LocalPassport"+getPassportType());
Constructor cons=cl.getConstructor(null);
localPassport=(HBCIPassportInternal)cons.newInstance(null);
} catch (Exception e) {
throw new HBCI_Exception(e);
}
}
return localPassport;
}
public String getUserId()
{
return userid;
}
public void setUserId(String userid)
{
this.userid=userid;
}
public String getCustomerId()
{
return customerid;
}
public void setCustomerId(String customerid)
{
this.customerid=customerid;
}
public String getSysId()
{
return sysid;
}
public void setSysId(String sysid)
{
this.sysid=sysid;
}
public String getPassportType()
{
return passportType;
}
private void touch()
{
this.lastactive=new Date();
}
public Date getLastActive()
{
return lastactive;
}
}