Update project structure to org.onap
[dmaap/datarouter.git] / datarouter-node / src / main / java / org / onap / dmaap / datarouter / node / NodeServlet.java
1 /*******************************************************************************\r
2  * ============LICENSE_START==================================================\r
3  * * org.onap.dmaap\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * * \r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * * \r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 \r
24 \r
25 package org.onap.dmaap.datarouter.node;\r
26 \r
27 import javax.servlet.*;\r
28 import javax.servlet.http.*;\r
29 import java.util.*;\r
30 import java.util.regex.*;\r
31 import java.io.*;\r
32 import java.nio.file.*;\r
33 import org.apache.log4j.Logger;\r
34 import org.onap.dmaap.datarouter.node.eelf.EelfMsgs;\r
35 \r
36 import com.att.eelf.configuration.EELFLogger;\r
37 import com.att.eelf.configuration.EELFManager;\r
38 \r
39 import java.net.*;\r
40 \r
41 /**\r
42  *      Servlet for handling all http and https requests to the data router node\r
43  *      <p>\r
44  *      Handled requests are:\r
45  *      <br>\r
46  *      GET http://<i>node</i>/internal/fetchProv - fetch the provisioning data\r
47  *      <br>\r
48  *      PUT/DELETE https://<i>node</i>/internal/publish/<i>fileid</i> - n2n transfer\r
49  *      <br>\r
50  *      PUT/DELETE https://<i>node</i>/publish/<i>feedid</i>/<i>fileid</i> - publsh request\r
51  */\r
52 public class NodeServlet extends HttpServlet    {\r
53         private static Logger logger = Logger.getLogger("org.onap.dmaap.datarouter.node.NodeServlet");\r
54         private static NodeConfigManager        config;\r
55         private static Pattern  MetaDataPattern;\r
56         private static SubnetMatcher internalsubnet = new SubnetMatcher("135.207.136.128/25");\r
57         //Adding EELF Logger Rally:US664892  \r
58     private static EELFLogger eelflogger = EELFManager.getInstance().getLogger("org.onap.dmaap.datarouter.node.NodeServlet");\r
59 \r
60         static {\r
61                 try {\r
62                         String ws = "\\s*";\r
63                         // assume that \\ and \" have been replaced by X\r
64                         String string = "\"[^\"]*\"";\r
65                         //String string = "\"(?:[^\"\\\\]|\\\\.)*\"";\r
66                         String number = "[+-]?(?:\\.\\d+|(?:0|[1-9]\\d*)(?:\\.\\d*)?)(?:[eE][+-]?\\d+)?";\r
67                         String value = "(?:" + string + "|" + number + "|null|true|false)";\r
68                         String item = string + ws + ":" + ws + value + ws;\r
69                         String object = ws + "\\{" + ws + "(?:" + item + "(?:" + "," + ws + item + ")*)?\\}" + ws;\r
70                         MetaDataPattern = Pattern.compile(object, Pattern.DOTALL);\r
71                 } catch (Exception e) {\r
72                 }\r
73         }\r
74         /**\r
75          *      Get the NodeConfigurationManager\r
76          */\r
77         public void init() {\r
78                 config = NodeConfigManager.getInstance();\r
79                 logger.info("NODE0101 Node Servlet Configured");\r
80         }\r
81         private boolean down(HttpServletResponse resp) throws IOException {\r
82                 if (config.isShutdown() || !config.isConfigured()) {\r
83                         resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);\r
84                         logger.info("NODE0102 Rejecting request: Service is being quiesced");\r
85                         return(true);\r
86                 }\r
87                 return(false);\r
88         }\r
89         /**\r
90          *      Handle a GET for /internal/fetchProv\r
91          */\r
92         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\r
93                 NodeUtils.setIpAndFqdnForEelf("doGet");\r
94                 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader("X-ATT-DR-ON-BEHALF-OF"),getIdFromPath(req)+"");\r
95                 if (down(resp)) {\r
96                         return;\r
97                 }\r
98                 String path = req.getPathInfo();\r
99                 String qs = req.getQueryString();\r
100                 String ip = req.getRemoteAddr();\r
101                 if (qs != null) {\r
102                         path = path + "?" + qs;\r
103                 }\r
104                 if ("/internal/fetchProv".equals(path)) {\r
105                         config.gofetch(ip);\r
106                         resp.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
107                         return;\r
108                 } else if (path.startsWith("/internal/resetSubscription/")) {\r
109                         String subid = path.substring(28);\r
110                         if (subid.length() != 0 && subid.indexOf('/') == -1) {\r
111                                 NodeMain.resetQueue(subid, ip);\r
112                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
113                                 return;\r
114                         }\r
115                 }\r
116                 if (internalsubnet.matches(NodeUtils.getInetAddress(ip))) {\r
117                         if (path.startsWith("/internal/logs/")) {\r
118                                 String f = path.substring(15);\r
119                                 File fn = new File(config.getLogDir() + "/" + f);\r
120                                 if (f.indexOf('/') != -1 || !fn.isFile()) {\r
121                                         logger.info("NODE0103 Rejecting invalid GET of " + path + " from " + ip);\r
122                                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);\r
123                                         return;\r
124                                 }\r
125                                 byte[] buf = new byte[65536];\r
126                                 resp.setContentType("text/plain");\r
127                                 resp.setContentLength((int)fn.length());\r
128                                 resp.setStatus(200);\r
129                                 InputStream is = new FileInputStream(fn);\r
130                                 OutputStream os = resp.getOutputStream();\r
131                                 int i;\r
132                                 while ((i = is.read(buf)) > 0) {\r
133                                         os.write(buf, 0, i);\r
134                                 }\r
135                                 is.close();\r
136                                 return;\r
137                         }\r
138                         if (path.startsWith("/internal/rtt/")) {\r
139                                 String xip = path.substring(14);\r
140                                 long st = System.currentTimeMillis();\r
141                                 String status = " unknown";\r
142                                 try {\r
143                                         Socket s = new Socket(xip, 443);\r
144                                         s.close();\r
145                                         status = " connected";\r
146                                 } catch (Exception e) {\r
147                                         status = " error " + e.toString();\r
148                                 }\r
149                                 long dur = System.currentTimeMillis() - st;\r
150                                 resp.setContentType("text/plain");\r
151                                 resp.setStatus(200);\r
152                                 byte[] buf = (dur + status + "\n").getBytes();\r
153                                 resp.setContentLength(buf.length);\r
154                                 resp.getOutputStream().write(buf);\r
155                                 return;\r
156                         }\r
157                 }\r
158                 logger.info("NODE0103 Rejecting invalid GET of " + path + " from " + ip);\r
159                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);\r
160                 return;\r
161         }\r
162         /**\r
163          *      Handle all PUT requests\r
164          */\r
165         protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\r
166                 NodeUtils.setIpAndFqdnForEelf("doPut");\r
167                 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader("X-ATT-DR-ON-BEHALF-OF"),getIdFromPath(req)+"");\r
168                 common(req, resp, true);\r
169         }\r
170         /**\r
171          *      Handle all DELETE requests\r
172          */\r
173         protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\r
174                 NodeUtils.setIpAndFqdnForEelf("doDelete");\r
175                 eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader("X-ATT-DR-ON-BEHALF-OF"),getIdFromPath(req)+"");\r
176                 common(req, resp, false);\r
177         }\r
178         private void common(HttpServletRequest req, HttpServletResponse resp, boolean isput) throws ServletException, IOException {\r
179                 if (down(resp)) {\r
180                         return;\r
181                 }\r
182                 if (!req.isSecure()) {\r
183                         logger.info("NODE0104 Rejecting insecure PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
184                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "https required on publish requests");\r
185                         return;\r
186                 }\r
187                 String fileid = req.getPathInfo();\r
188                 if (fileid == null) {\r
189                         logger.info("NODE0105 Rejecting bad URI for PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
190                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid request URI.  Expecting <feed-publishing-url>/<fileid>.");\r
191                         return;\r
192                 }\r
193                 String feedid = null;\r
194                 String user = null;\r
195                 String credentials = req.getHeader("Authorization");\r
196                 if (credentials == null) {\r
197                         logger.info("NODE0106 Rejecting unauthenticated PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
198                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Authorization header required");\r
199                         return;\r
200                 }\r
201                 String ip = req.getRemoteAddr();\r
202                 String lip = req.getLocalAddr();\r
203                 String pubid = null;\r
204                 String xpubid = null;\r
205                 String rcvd = NodeUtils.logts(System.currentTimeMillis()) + ";from=" + ip + ";by=" + lip;\r
206                 Target[]        targets = null;\r
207                 if (fileid.startsWith("/publish/")) {\r
208                         fileid = fileid.substring(9);\r
209                         int i = fileid.indexOf('/');\r
210                         if (i == -1 || i == fileid.length() - 1) {\r
211                                 logger.info("NODE0105 Rejecting bad URI for PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
212                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid request URI.  Expecting <feed-publishing-url>/<fileid>.  Possible missing fileid.");\r
213                                 return;\r
214                         }\r
215                         feedid = fileid.substring(0, i);\r
216                         fileid = fileid.substring(i + 1);\r
217                         pubid = config.getPublishId();\r
218                         xpubid = req.getHeader("X-ATT-DR-PUBLISH-ID");\r
219                         targets = config.getTargets(feedid);\r
220                 } else if (fileid.startsWith("/internal/publish/")) {\r
221                         if (!config.isAnotherNode(credentials, ip)) {\r
222                                 logger.info("NODE0107 Rejecting unauthorized node-to-node transfer attempt from " + ip);\r
223                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);\r
224                                 return;\r
225                         }\r
226                         fileid = fileid.substring(18);\r
227                         pubid = req.getHeader("X-ATT-DR-PUBLISH-ID");\r
228                         targets = config.parseRouting(req.getHeader("X-ATT-DR-ROUTING"));\r
229                 } else {\r
230                         logger.info("NODE0105 Rejecting bad URI for PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
231                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid request URI.  Expecting <feed-publishing-url>/<fileid>.");\r
232                         return;\r
233                 }\r
234                 if (fileid.indexOf('/') != -1) {\r
235                         logger.info("NODE0105 Rejecting bad URI for PUT or DELETE of " + req.getPathInfo() + " from " + req.getRemoteAddr());\r
236                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid request URI.  Expecting <feed-publishing-url>/<fileid>.");\r
237                         return;\r
238                 }\r
239                 String qs = req.getQueryString();\r
240                 if (qs != null) {\r
241                         fileid = fileid + "?" + qs;\r
242                 }\r
243                 String hp = config.getMyName();\r
244                 int xp = config.getExtHttpsPort();\r
245                 if (xp != 443) {\r
246                         hp = hp + ":" + xp;\r
247                 }\r
248                 String logurl = "https://" + hp + "/internal/publish/" + fileid;\r
249                 if (feedid != null) {\r
250                         logurl = "https://" + hp + "/publish/" + feedid + "/" + fileid;\r
251                         String reason = config.isPublishPermitted(feedid, credentials, ip);\r
252                         if (reason != null) {\r
253                                 logger.info("NODE0111 Rejecting unauthorized publish attempt to feed " + feedid + " fileid " + fileid + " from " + ip + " reason " + reason);\r
254                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN,reason);\r
255                                 return;\r
256                         }\r
257                         user = config.getAuthUser(feedid, credentials);\r
258                         String newnode = config.getIngressNode(feedid, user, ip);\r
259                         if (newnode != null) {\r
260                                 String port = "";\r
261                                 int iport = config.getExtHttpsPort();\r
262                                 if (iport != 443) {\r
263                                         port = ":" + iport;\r
264                                 }\r
265                                 String redirto = "https://" + newnode + port + "/publish/" + feedid + "/" + fileid;\r
266                                 logger.info("NODE0108 Redirecting publish attempt for feed " + feedid + " user " + user + " ip " + ip + " to " + redirto);\r
267                                 resp.sendRedirect(redirto);\r
268                                 return;\r
269                         }\r
270                         resp.setHeader("X-ATT-DR-PUBLISH-ID", pubid);\r
271                 }\r
272                 String fbase = config.getSpoolDir() + "/" + pubid;\r
273                 File data = new File(fbase);\r
274                 File meta = new File(fbase + ".M");\r
275                 OutputStream dos = null;\r
276                 Writer mw = null;\r
277                 InputStream is = null;\r
278                 try {\r
279                         StringBuffer mx = new StringBuffer();\r
280                         mx.append(req.getMethod()).append('\t').append(fileid).append('\n');\r
281                         Enumeration hnames = req.getHeaderNames();\r
282                         String ctype = null;\r
283                         while (hnames.hasMoreElements()) {\r
284                                 String hn = (String)hnames.nextElement();\r
285                                 String hnlc = hn.toLowerCase();\r
286                                 if ((isput && ("content-type".equals(hnlc) ||\r
287                                     "content-language".equals(hnlc) ||\r
288                                     "content-md5".equals(hnlc) ||\r
289                                     "content-range".equals(hnlc))) ||\r
290                                     "x-att-dr-meta".equals(hnlc) ||\r
291                                     (feedid == null && "x-att-dr-received".equals(hnlc)) ||\r
292                                     (hnlc.startsWith("x-") && !hnlc.startsWith("x-att-dr-"))) {\r
293                                         Enumeration hvals = req.getHeaders(hn);\r
294                                         while (hvals.hasMoreElements()) {\r
295                                                 String hv = (String)hvals.nextElement();\r
296                                                 if ("content-type".equals(hnlc)) {\r
297                                                         ctype = hv;\r
298                                                 }\r
299                                                 if ("x-att-dr-meta".equals(hnlc)) {\r
300                                                         if (hv.length() > 4096) {\r
301                                                                 logger.info("NODE0109 Rejecting publish attempt with metadata too long for feed " + feedid + " user " + user + " ip " + ip);\r
302                                                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Metadata too long");\r
303                                                                 return;\r
304                                                         }\r
305                                                         if (!MetaDataPattern.matcher(hv.replaceAll("\\\\.", "X")).matches()) {\r
306                                                                 logger.info("NODE0109 Rejecting publish attempt with malformed metadata for feed " + feedid + " user " + user + " ip " + ip);\r
307                                                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Malformed metadata");\r
308                                                                 return;\r
309                                                         }\r
310                                                 }\r
311                                                 mx.append(hn).append('\t').append(hv).append('\n');\r
312                                         }\r
313                                 }\r
314                         }\r
315                         mx.append("X-ATT-DR-RECEIVED\t").append(rcvd).append('\n');\r
316                         String metadata = mx.toString();\r
317                         byte[] buf = new byte[1024 * 1024];\r
318                         int i;\r
319                         try {\r
320                                 is = req.getInputStream();\r
321                                 dos = new FileOutputStream(data);\r
322                                 while ((i = is.read(buf)) > 0) {\r
323                                         dos.write(buf, 0, i);\r
324                                 }\r
325                                 is.close();\r
326                                 is = null;\r
327                                 dos.close();\r
328                                 dos = null;\r
329                         } catch (IOException ioe) {\r
330                                 long exlen = -1;\r
331                                 try {\r
332                                         exlen = Long.parseLong(req.getHeader("Content-Length"));\r
333                                 } catch (Exception e) {\r
334                                 }\r
335                                 StatusLog.logPubFail(pubid, feedid, logurl, req.getMethod(), ctype, exlen, data.length(), ip, user, ioe.getMessage());\r
336                                 throw ioe;\r
337                         }\r
338                         Path dpath = Paths.get(fbase);\r
339                         for (Target t: targets) {\r
340                                 DestInfo di = t.getDestInfo();\r
341                                 if (di == null) {\r
342                                         // TODO: unknown destination\r
343                                         continue;\r
344                                 }\r
345                                 String dbase = di.getSpool() + "/" + pubid;\r
346                                 Files.createLink(Paths.get(dbase), dpath);\r
347                                 mw = new FileWriter(meta);\r
348                                 mw.write(metadata);\r
349                                 if (di.getSubId() == null) {\r
350                                         mw.write("X-ATT-DR-ROUTING\t" + t.getRouting() + "\n");\r
351                                 }\r
352                                 mw.close();\r
353                                 meta.renameTo(new File(dbase + ".M"));\r
354                         }\r
355                         resp.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
356                         resp.getOutputStream().close();\r
357                         StatusLog.logPub(pubid, feedid, logurl, req.getMethod(), ctype, data.length(), ip, user, HttpServletResponse.SC_NO_CONTENT);\r
358                 } catch (IOException ioe) {\r
359                         logger.info("NODE0110 IO Exception receiving publish attempt for feed " + feedid + " user " + user + " ip " + ip + " " + ioe.toString(), ioe);\r
360                         throw ioe;\r
361                 } finally {\r
362                         if (is != null) { try { is.close(); } catch (Exception e) {}}\r
363                         if (dos != null) { try { dos.close(); } catch (Exception e) {}}\r
364                         if (mw != null) { try { mw.close(); } catch (Exception e) {}}\r
365                         try { data.delete(); } catch (Exception e) {}\r
366                         try { meta.delete(); } catch (Exception e) {}\r
367                 }\r
368         }\r
369         \r
370         private int getIdFromPath(HttpServletRequest req) {\r
371                 String path = req.getPathInfo();\r
372                 if (path == null || path.length() < 2)\r
373                         return -1;\r
374                 try {\r
375                         return Integer.parseInt(path.substring(1));\r
376                 } catch (NumberFormatException e) {\r
377                         return -1;\r
378                 }\r
379         }\r
380 }\r