001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.transaction;
020
021import org.jpos.core.Configurable;
022import org.jpos.core.Configuration;
023import org.jpos.core.ConfigurationException;
024import org.jpos.iso.ISOException;
025import org.jpos.iso.ISOMsg;
026import org.jpos.iso.ISOUtil;
027import org.jpos.tlv.TLVList;
028import org.jpos.util.FSDMsg;
029import org.jpos.util.ProtectedLogListener;
030
031import java.io.Serializable;
032import java.util.Arrays;
033
034/**
035 *Sample Usage:
036 *<pre>
037 *    &lt;participant class="org.jpos.transaction.ProtectDebugInfo"&gt;
038 *
039 *        &lt;property name="protect-entry" value="REQUEST" /&gt;
040 *        &lt;property name="protect-entry" value="RESPONSE" /&gt;
041 *        &lt;property name="protect-entry" value="PAN, EXP, REQUEST_ICC_DATA" /&gt;
042 *        &lt;property name="wipe-entry"    value="EXPDATE" /&gt;
043 *
044 *        &lt;!-- if the protected ctx entry is an ISOMsg --&gt;
045 *        &lt;property name="protect-ISOMsg" value="2" /&gt;
046 *        &lt;property name="protect-ISOMsg" value="35, 45" /&gt;
047 *        &lt;property name="wipe-ISOMsg"    value="52, 55" /&gt;
048 *
049 *        &lt;!-- if the protected ctx entry is a TLVList --&gt;
050 *        &lt;property name="wipe-TLVList" value="0x56" /&gt;
051 *        &lt;property name="wipe-TLVList" value="0x57" /&gt;
052 *        &lt;property name="wipe-TLVList" value="0x5a, 0x5f20" /&gt;
053 *
054 *        &lt;!-- if the protected ctx entry is a FSDMsg --&gt;
055 *        &lt;property name="protect-FSDMsg" value="account-number" /&gt;
056 *        &lt;property name="protect-FSDMsg" value="track2-data" /&gt;
057 *        &lt;property name="wipe-FSDMsg"    value="secret-key" /&gt;
058 *
059 *    &lt;/participant&gt;
060 *</pre>
061 *
062 * Configuration properties accept comma/space-separated values, but can also be given in multiple occurrences.
063 * All occurrences of the same property will be merged into a single list.
064 *
065 * @author Alejandro Revilla
066 * @author David Bergert
067 * @author Barzilai Spinak
068 **/
069
070public class ProtectDebugInfo implements AbortParticipant, Configurable {
071     /** Creates the participant; configuration is supplied via {@link #setConfiguration(Configuration)}. */
072     public ProtectDebugInfo() {}
073     private static final String COMMA_SPACE_SEPARATOR = "[,\\s]+";
074     private String[] protectedEntries;
075     private String[] wipedEntries;
076     private String[] protectFSD;
077     private String[] protectISO;
078     private String[] wipeISO;
079     private String[] wipeFSD;
080     private String[] wipeTLV;
081
082     public int prepare (long id, Serializable o) {
083         return PREPARED | READONLY;
084     }
085     public int prepareForAbort (long id, Serializable o) {
086         return PREPARED | READONLY;
087     }
088     public void commit (long id, Serializable o) {
089         protect ((Context) o);
090     }
091     public void abort  (long id, Serializable o) {
092         protect ((Context) o);
093     }
094
095     private void protect (Context ctx) {
096         /* wipe by removing entries from the context  */
097         for (String s: wipedEntries)
098             ctx.remove(s);
099
100         /* Protect entry items */
101         for (String s: protectedEntries) {
102             Object o = ctx.get (s);
103
104             if (o instanceof ISOMsg) {
105                 ISOMsg m = ctx.get (s);
106                 if (m != null) {
107                     m = (ISOMsg) m.clone();
108                     ctx.put (s, m);   // place a clone in the context
109                     for (String p: protectISO)
110                         protectField(m,p);
111                     for (String p: wipeISO)
112                         wipeField(m,p);
113                 }
114             }
115
116             if (o instanceof FSDMsg){
117                 FSDMsg m = ctx.get (s);
118                 if (m != null) {
119                     for (String p: protectFSD)
120                         protectField(m,p);
121                     for (String p: wipeFSD)
122                         wipeField(m,p);
123                 }
124             }
125
126             if (o instanceof String){
127                 String p = ctx.get(s);
128                 if (p != null){
129                     ctx.put(s, protect (p));
130                 }
131             }
132
133             if (o instanceof TLVList) {
134                 TLVList tlv = ctx.get(s);
135                 if (tlv != null) {
136                     for (String t: wipeTLV)
137                        wipeTag(tlv, t);
138                 }
139             }
140         }
141     }
142
143     private void protectField (ISOMsg m, String f) {
144         if (m != null) {
145             m.set (f, protect (m.getString (f)));
146         }
147     }
148
149    private void wipeField (ISOMsg m, String f) {
150        if (m != null) {
151            try {
152                Object v = m.getValue(f);
153                if (v != null) {
154                    if (v instanceof String)
155                        m.set(f, ProtectedLogListener.WIPED);
156                    else
157                        m.set(f, ProtectedLogListener.BINARY_WIPED);
158                }
159            } catch (ISOException ignored) {
160                //ignore, valid routes for some messages in the context may not be valid for others
161                //e.g. in transaction switches with protocol conversion
162            }
163        }
164    }
165
166    static void wipeTag(TLVList tlv, String tag) {
167        if (tlv == null)
168            return;
169        try {
170            int tagName = Integer.decode(tag);
171            if (tlv.hasTag(tagName)) {
172                tlv.deleteByTag(tagName);
173                tlv.append(tagName, ProtectedLogListener.BINARY_WIPED);
174            }
175        }
176        catch (Throwable ignored) { }
177    }
178
179    private void protectField (FSDMsg m, String f) {
180         if (f != null) {
181             String s = m.get (f);
182             if (s != null)
183                 m.set (f, ISOUtil.protect (s));
184         }
185     }
186
187     private void wipeField (FSDMsg m, String f) {
188         if (m != null && m.get(f) != null) {
189             m.set (f, "*");
190         }
191     }
192
193     private String protect (String s) {
194         return s != null ? ISOUtil.protect (s) : s;
195     }
196
197     public void setConfiguration (Configuration cfg) throws ConfigurationException {
198         this.protectedEntries = getValues(cfg, "protect-entry");
199         this.wipedEntries = getValues(cfg, "wipe-entry");
200         this.protectFSD = getValues(cfg, "protect-FSDMsg");
201         this.protectISO = getValues(cfg, "protect-ISOMsg");
202         this.wipeISO = getValues(cfg, "wipe-ISOMsg");
203         this.wipeFSD = getValues(cfg, "wipe-FSDMsg");
204         this.wipeTLV = getValues(cfg, "wipe-TLVList");
205     }
206     private String[] getValues (Configuration cfg, String name) {
207         return Arrays.stream(cfg.getAll(name))
208             .flatMap(v -> Arrays.stream(v.split(COMMA_SPACE_SEPARATOR)))
209             .map(String::trim)
210             .filter(v -> !v.isEmpty())
211             .toArray(String[]::new);
212     }
213}