/* $Id: AbstractMsgHandler.java,v 1.14 2005/06/10 18:03:03 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.msg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.manager.HBCIUtilsInternal;
import org.kapott.hbci.manager.MsgGen;
import org.kapott.hbci.passport.HBCIPassport;
import org.kapott.hbci.passport.HBCIPassportInternal;
import org.kapott.hbci.passport.HBCIPassportList;
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.Sig;
import org.kapott.hbci.security.factory.CryptFactory;
import org.kapott.hbci.security.factory.SigFactory;
import org.kapott.hbci.server.Dialog;
import org.kapott.hbci.server.ServerData;
import org.kapott.hbci.server.StatusProtEntry;
import org.kapott.hbci.status.HBCIRetVal;
import org.w3c.dom.Element;
public abstract class AbstractMsgHandler
implements MsgHandler
{
protected MSG cryptedMsg;
protected MSG decryptedMsg;
protected String decryptedMsgData;
protected Dialog dialog;
private String msgname;
private ServerData serverdata;
private Properties data;
private ArrayList globRets;
private ArrayList segRets;
protected abstract void initMessageHandler();
protected abstract boolean isMessageAllowed();
protected abstract void handleMessageContent();
protected abstract void postProcess();
// verifySysId() und verifySigId() knnen auch berschrieben werden
protected AbstractMsgHandler(MSG cryptedMsg,MSG decryptedMsg,
String decryptedMsgData,Dialog conn)
{
this.cryptedMsg=cryptedMsg;
this.decryptedMsg=decryptedMsg;
this.decryptedMsgData=decryptedMsgData;
this.dialog=conn;
this.msgname=decryptedMsg.getName();
this.serverdata=ServerData.getInstance();
this.data=decryptedMsg.getData();
this.globRets=new ArrayList();
// soviele segrets anlegen wie es segmente gibt
int size=Integer.parseInt(getData("MsgTail.SegHead.seq"));
HBCIUtils.log("creating segrets for "+size+" segments",HBCIUtils.LOG_DEBUG);
this.segRets=new ArrayList();
for (int i=0;i<size;i++)
this.segRets.add(null);
// extract element paths
Properties paths=new Properties();
decryptedMsg.getElementPaths(paths,null,null,null);
for (Enumeration e=paths.propertyNames();e.hasMoreElements();) {
String key=(String)e.nextElement();
data.setProperty(key,paths.getProperty(key));
}
}
public final MSG handleMessage()
{
HBCIUtils.log("handling incoming message: "+decryptedMsgData,HBCIUtils.LOG_DEBUG);
dialog.getMsgGen().reset();
initMessageHandler();
// first check dialogid/msgnum; if there is something wrong, abort
checkMessageFrame();
// ist nachricht im moment erlaubt? (dialoginit innerhalb eines dialoges
// ist es z.b. nicht); wenn nicht, dann abbruch des dialoges
if (!isMessageAllowed())
throw new HBCI_Exception("message not allowed in current context");
// signatur berprfen
if (checkMessageSignature()) {
// wenn signatur ok, dann nachrichteninhalt bearbeiten
handleMessageContent();
} else {
HBCIUtils.log("message signature is wrong",HBCIUtils.LOG_ERR);
}
// wenn nicht abgebrochen wurde (also wenn msgframe und allowed() okay
// waren und auch keine exceptions beim content-handling aufgetreten
// sind), dann antwortnachricht erzeugen
MSG ret=createResponseMessage();
postProcess();
return ret;
}
private void checkMessageFrame()
{
HBCIUtils.log("checking message frame",HBCIUtils.LOG_DEBUG);
// dialogid
String dialogId=getData("MsgHead.dialogid");
if (!dialogId.equals(dialog.getDialogId())) {
throw new HBCI_Exception("invalid dialog-id");
}
// msgnum (head und tail)
if (!getData("MsgHead.msgnum").equals(dialog.getMsgNum()) ||
!getData("MsgTail.msgnum").equals(dialog.getMsgNum())) {
throw new HBCI_Exception("invalid message number");
}
if (dialogId.equals("0"))
dialog.createNewDialogId();
}
private boolean checkMessageSignature()
{
HBCIUtils.log("checking message signature",HBCIUtils.LOG_DEBUG);
// sighead extrahieren
// TODO: hier multi-sigs implementieren
MsgGen msggen=dialog.getMsgGen();
SEG sighead=(SEG)decryptedMsg.getElement(msgname+".SigHead");
// ist eine signatur vorhanden?
if (sighead!=null) {
HBCIUtils.log("found sighead",HBCIUtils.LOG_DEBUG);
// daten aus sighead extrahieren
String header=sighead.getPath();
String userid=sighead.getValueOfDE(header+".KeyName.userid");
long sigid=Long.parseLong(getData("SigHead.secref"));
String sysid=sighead.getValueOfDE(header+".SecIdnDetails.sysid");
if (sysid==null)
sysid="0";
// TODO verify all sighead data (auer keynum/-version, hashalg, sigalg, sigmode)
HBCIUtils.log("found sighead data userid/sysid/sigid = "+userid+"/"+sysid+"/"+sigid,
HBCIUtils.LOG_DEBUG);
// userid berprfen
// wenn das die erste nachricht im dialog ist
if (!dialog.isInitialized()) {
HBCIUtils.log("checking first sighead in dialog",HBCIUtils.LOG_DEBUG);
// existiert diese userid berhaupt?
if (!getServerData().existsUserId(userid)) {
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigHead","11,2","9210","ungltige User-ID",null);
return false;
}
dialog.setUserId(userid);
// schlssel vorhanden? (ausnahme sendkeys)
if (!getServerData().existUserSigKeys(dialog)) {
HBCIUtils.log("keys for this user dont exist",HBCIUtils.LOG_WARN);
if (this instanceof MsgHandlerSendKeys) {
HBCIUtils.log("ignoring this, because this is a sendkeys message",HBCIUtils.LOG_WARN);
} else {
addGlobRet("9010","Vararbeitung nicht mglich",null);
addSegRet("SigHead",null,"9310","Elektronische Signatur noch nicht hinterlegt",null);
return false;
}
}
// systemid berprfen
if (!verifySysId(sysid))
return false;
dialog.setSysId(sysid);
} else { // nicht die erste nachricht im dialog
HBCIUtils.log("checking following sighead in dialog",HBCIUtils.LOG_DEBUG);
// userid muss ber den gesamten dialog gleich bleiben
if (!userid.equals(dialog.getUserId())) {
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigHead","11,2","9210","falsche User-ID",null);
return false;
}
// sysid muss konstant bleiben
if (!sysid.equals(dialog.getSysId())) {
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigHead","6,3","9210","falsche System-ID",null);
return false;
}
}
// check sigid (ausnahme sendkeys,sync)
if (!verifySigId(sigid))
return false;
// signatur berprfen
// bei sendkeys wird spter verified
if (!(this instanceof MsgHandlerSendKeys)) {
if (!checkDigitalSignature(userid,sysid))
return false;
} else {
HBCIUtils.log("delaying signature check",HBCIUtils.LOG_DEBUG);
}
return true;
}
// keine signatur vorhanden
HBCIUtils.log("message is not signed",HBCIUtils.LOG_DEBUG);
// nachrichtedefinition aus spez. holen
Element msgdef=msggen.getSyntax().getElementById(msgname);
String dontSign=msgdef.getAttribute("dontsign");
// wenn attribut "dontsign" gesetzt, dann ist das fehlen der sig. ok
if (dontSign!=null && dontSign.equals("1")) {
HBCIUtils.log("this message does not need to be signed",HBCIUtils.LOG_DEBUG);
} else if (dialog.getFlag("forceEnd") && !dialog.getFlag("endInSig") &&
(this instanceof MsgHandlerDialogEndAnon))
{
HBCIUtils.log("this is a special end message - no signature needed",HBCIUtils.LOG_DEBUG);
} else { // sonst fehler erzeugen
HBCIUtils.log("missing signature",HBCIUtils.LOG_ERR);
addGlobRet("9110","Signatur fehlt",null);
return false;
}
return true;
}
// berprfen, ob die System-ID im sighead ok ist
protected boolean verifySysId(String sysid)
{
// system-id ist ungltig, wenn sie entweder 0 oder nicht identisch
// mit einer aus den server-daten ist (das wird von einigen special-
// messages berschrieben)
String[] validSysIds_a=getServerData().getSysIds(dialog.getUserId());
ArrayList validSysIds=new ArrayList(Arrays.asList(validSysIds_a));
if (sysid.equals("0") || !validSysIds.contains(sysid)) {
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigHead","6,3","9210","ungltige System-ID",null);
return false;
}
return true;
}
// berprfen, on die sig-id ok ist
protected boolean verifySigId(long sigid)
{
if (!dialog.getPassportType().equals("PinTan")) {
// der normale fall ist, dass die sig-id noch nicht benutzt worden
// sein darf (einige special messages berschreiben dieses verhalten)
if (getServerData().existsSigId(dialog.getUserId(),dialog.getSysId(),sigid)) {
HBCIUtils.log("duplicate sigid found",HBCIUtils.LOG_WARN);
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigHead","7","9390","Doppeleinreichung",null);
return false;
}
// aktuelle sigid in liste der schon eingereichten ids aufnehmen
getServerData().addSigId(dialog.getUserId(),dialog.getSysId(),sigid);
return true;
}
return true;
}
protected boolean checkDigitalSignature(String userid,String sysid)
{
HBCIUtils.log("now checking digital signature",HBCIUtils.LOG_DEBUG);
MsgGen msggen=dialog.getMsgGen();
msggen.set("_origSignedMsg",decryptedMsgData);
// TODO: this has to be changed for multi-sigs
HBCIPassportList passports=new HBCIPassportList();
passports.addPassport(
getServerData().getPassport(dialog),
HBCIPassport.ROLE_ISS);
Sig sig=SigFactory.getInstance().createSig(decryptedMsg,msggen,passports);
boolean sigOk=sig.verify();
SigFactory.getInstance().unuseObject(sig);
// wenn signatur falsch ist, fehlermeldung erzeugen
if (!sigOk) {
addGlobRet("9010","Verarbeitung nicht mglich",null);
addSegRet("SigTail",null,"9340","Elektronische Signatur falsch",null);
return false;
}
return true;
}
private final MSG createResponseMessage()
{
HBCIUtils.log("creating response message",HBCIUtils.LOG_DEBUG);
String dialogid=dialog.getDialogId();
String msgnum=dialog.getMsgNum();
// fill msghead of response
setData("MsgHead.dialogid",dialogid);
setData("MsgHead.msgnum",msgnum);
setData("MsgHead.MsgRef.dialogid",dialogid);
setData("MsgHead.MsgRef.msgnum",msgnum);
setData("MsgTail.msgnum",msgnum);
StatusProtEntry pentry=new StatusProtEntry();
pentry.dialogid=dialogid;
pentry.msgnum=msgnum;
pentry.timestamp=new Date();
// create glob rets
int counter=0;
for (Iterator i=globRets.iterator();i.hasNext();) {
HBCIRetVal retval=(HBCIRetVal)i.next();
String header=HBCIUtilsInternal.withCounter("RetGlob.RetVal",counter++);
setData(header+".code",retval.code);
setData(header+".text",retval.text);
// *** parm
// save in status protokoll
pentry.retval=retval;
getServerData().addToStatusProt(dialog.getUserId(),pentry);
}
// creage seg rets
int segcounter=0;
for (Iterator i=segRets.iterator();i.hasNext();) {
ArrayList rets=(ArrayList)i.next();
if (rets!=null) {
String segheader=HBCIUtilsInternal.withCounter("RetSeg",segcounter++);
String segref=((HBCIRetVal)rets.get(0)).segref;
setData(segheader+".SegHead.ref",segref);
pentry.segref=segref;
counter=0;
for (Iterator j=rets.iterator();j.hasNext();) {
HBCIRetVal retval=(HBCIRetVal)j.next();
String header=HBCIUtilsInternal.withCounter(segheader+".RetVal",counter++);
setData(header+".code",retval.code);
setData(header+".text",retval.text);
if (retval.deref!=null)
setData(header+".ref",retval.deref);
// *** parm
// save in status protokoll
pentry.retval=retval;
getServerData().addToStatusProt(dialog.getUserId(),pentry);
}
}
}
// plaintextnachricht erzeugen
MSG msg=dialog.getMsgGen().generate(msgname+"Res");
// passport fr sig/crypt besorgen
String userid=dialog.getUserId();
HBCIPassportInternal passport=(userid==null?dialog.getLocalPassport()
:getServerData().getPassport(dialog));
// evtl. signieren
if (dialog.getFlag("signResponse")) {
HBCIUtils.log("trying to sign response message",HBCIUtils.LOG_DEBUG);
// this is for multisigs
HBCIPassportList passports=new HBCIPassportList();
passports.addPassport(passport,HBCIPassport.ROLE_ISS);
Sig sig=SigFactory.getInstance().createSig(msg,dialog.getMsgGen(),passports);
try {
if (!sig.signIt())
throw new HBCI_Exception("can not sign response message");
} finally {
SigFactory.getInstance().unuseObject(sig);
}
HBCIUtils.log("signature process done",HBCIUtils.LOG_DEBUG);
} else {
HBCIUtils.log("response message does not need to be signed",HBCIUtils.LOG_DEBUG);
}
HBCIUtils.log("response message: "+msg.toString(0),HBCIUtils.LOG_DEBUG);
// evtl. verschlsseln
if (dialog.getFlag("encryptResponse")) {
HBCIUtils.log("encrypting response message",HBCIUtils.LOG_DEBUG);
Crypt crypt=CryptFactory.getInstance().createCrypt(msg,dialog.getMsgGen(),passport);
try {
MSG old=msg;
msg=crypt.cryptIt("CryptedRes");
if (old!=msg) {
MSGFactory.getInstance().unuseObject(old);
}
} finally {
CryptFactory.getInstance().unuseObject(crypt);
}
} else {
HBCIUtils.log("response message will not be encrypted",HBCIUtils.LOG_DEBUG);
}
return msg;
}
protected ServerData getServerData()
{
return serverdata;
}
protected Properties getData()
{
return data;
}
protected String getData(String key)
{
return data.getProperty(key);
}
protected void setData(String key,String value)
{
dialog.getMsgGen().set(msgname+"Res."+key,value);
}
private boolean retExists(ArrayList rets,HBCIRetVal ret)
{
boolean exists=false;
for (Iterator i=rets.iterator();i.hasNext();) {
if (((HBCIRetVal)i.next()).equals(ret)) {
exists=true;
break;
}
}
return exists;
}
// einen rckgabewert zu einer menge von rckgabewerten hinzufgen.
// "menge" ist dabei entweder globrets oder die menge von retvals fr ein segment.
// ein retval wird nur dann hinzugefgt, wenn er noch nicht existiert.
// ein success-retval wird nur hinzugefgt, wenn noch kein error-retval existiert
// ein error-retval lscht alle schon vorhandenen success-retvals
private void addRet(ArrayList rets,HBCIRetVal ret)
{
if (!retExists(rets,ret)) {
boolean addIt=true;
char firstChar=ret.code.charAt(0);
if (firstChar=='0') {
for (Iterator i=rets.iterator();i.hasNext();) {
if (((HBCIRetVal)i.next()).code.charAt(0)=='9') {
HBCIUtils.log("can not add success message because error exists",HBCIUtils.LOG_DEBUG);
addIt=false;
break;
}
}
} else if (firstChar=='9') {
ArrayList newRets=new ArrayList();
for (Iterator i=rets.iterator();i.hasNext();) {
HBCIRetVal r=(HBCIRetVal)i.next();
if (r.code.charAt(0)!='0') {
newRets.add(r);
} else {
HBCIUtils.log("removing success message because of error message: "+r,HBCIUtils.LOG_DEBUG);
}
}
rets.clear();
rets.addAll(newRets);
}
if (addIt)
rets.add(ret);
}
}
protected void addGlobRet(String code,String text,String[] params)
{
HBCIRetVal ret=new HBCIRetVal(null,null,null,code,text,params);
addRet(globRets,ret);
}
protected void addSegRet(String segname,String deref,String code,String text,String[] params)
{
String segref=getData(segname+".SegHead.seq");
HBCIRetVal ret=new HBCIRetVal(segref,deref,null,code,text,params);
int segref2=Integer.parseInt(segref);
ArrayList rets=(ArrayList)segRets.get(segref2);
if (rets==null) {
rets=new ArrayList();
segRets.set(segref2,rets);
}
addRet(rets,ret);
}
private boolean retsOk(ArrayList rets)
{
boolean ok=true;
for (Iterator i=rets.iterator();i.hasNext();) {
if (((HBCIRetVal)i.next()).code.charAt(0)=='9') {
ok=false;
break;
}
}
return ok;
}
protected boolean globRetIsOk()
{
return retsOk(globRets);
}
public Dialog getDialog()
{
return dialog;
}
public int updateMsgNum(int msgnum)
{
return msgnum+1;
}
}