/* dCache - http://www.dcache.org/ * * Copyright (C) 2014 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.xrootd.plugins; import com.google.common.net.HostAndPort; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.haproxy.HAProxyCommand; import io.netty.handler.codec.haproxy.HAProxyMessage; import org.slf4j.Logger; import javax.security.auth.Subject; import java.net.InetSocketAddress; import java.time.Instant; import dmg.cells.nucleus.CDC; import org.dcache.auth.LoginReply; import org.dcache.auth.Subjects; import org.dcache.util.NetLoggerBuilder; import org.dcache.xrootd.door.LoginEvent; import org.dcache.xrootd.protocol.XrootdProtocol; import org.dcache.xrootd.protocol.messages.EndSessionRequest; import org.dcache.xrootd.protocol.messages.ErrorResponse; import org.dcache.xrootd.protocol.messages.LocateRequest; import org.dcache.xrootd.protocol.messages.LoginRequest; import org.dcache.xrootd.protocol.messages.LoginResponse; import org.dcache.xrootd.protocol.messages.MkDirRequest; import org.dcache.xrootd.protocol.messages.MvRequest; import org.dcache.xrootd.protocol.messages.OpenRequest; import org.dcache.xrootd.protocol.messages.OpenResponse; import org.dcache.xrootd.protocol.messages.PathRequest; import org.dcache.xrootd.protocol.messages.PrepareRequest; import org.dcache.xrootd.protocol.messages.ReadRequest; import org.dcache.xrootd.protocol.messages.ReadVRequest; import org.dcache.xrootd.protocol.messages.RedirectResponse; import org.dcache.xrootd.protocol.messages.SetRequest; import org.dcache.xrootd.protocol.messages.StatResponse; import org.dcache.xrootd.protocol.messages.StatxRequest; import org.dcache.xrootd.protocol.messages.WriteRequest; import org.dcache.xrootd.protocol.messages.XrootdRequest; import org.dcache.xrootd.protocol.messages.XrootdResponse; import org.dcache.xrootd.util.FileStatus; import static com.google.common.base.Strings.emptyToNull; import static org.dcache.util.NetLoggerBuilder.Level.*; import static org.dcache.xrootd.protocol.XrootdProtocol.*; @ChannelHandler.Sharable public class AccessLogHandler extends ChannelDuplexHandler { protected final Logger logger; public AccessLogHandler(Logger logger) { this.logger = logger; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof LoginEvent) { LoginReply loginReply = ((LoginEvent) evt).getLoginReply(); Subject subject = loginReply.getSubject(); NetLoggerBuilder log = new NetLoggerBuilder(INFO, "org.dcache.xrootd.login").omitNullValues(); log.add("session", CDC.getSession()); log.add("user.dn", Subjects.getDn(subject)); log.add("user.mapped", subject); log.toLogger(logger); } ctx.fireUserEventTriggered(evt); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { NetLoggerBuilder log = new NetLoggerBuilder(INFO, "org.dcache.xrootd.connection.start").omitNullValues(); log.add("session", CDC.getSession()); log.add("socket.remote", (InetSocketAddress) ctx.channel().remoteAddress()); log.add("socket.local", (InetSocketAddress) ctx.channel().localAddress()); log.toLogger(logger); ctx.fireChannelActive(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { NetLoggerBuilder log = new NetLoggerBuilder(INFO, "org.dcache.xrootd.connection.end").omitNullValues(); log.add("session", CDC.getSession()); log.toLogger(logger); ctx.fireChannelInactive(); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof XrootdResponse<?> && logger.isErrorEnabled()) { XrootdResponse<?> response = (XrootdResponse<?>) msg; XrootdRequest request = response.getRequest(); NetLoggerBuilder.Level level; if (response instanceof ErrorResponse) { level = ERROR; } else if (request instanceof WriteRequest || request instanceof ReadRequest || request instanceof ReadVRequest) { level = DEBUG; } else { level = INFO; } if (level == ERROR || level == INFO && logger.isInfoEnabled() || level == DEBUG && logger.isDebugEnabled()) { NetLoggerBuilder log = new NetLoggerBuilder(level, "org.dcache.xrootd.request").omitNullValues(); log.add("session", CDC.getSession()); log.add("request", getRequestId(request)); if (request instanceof PathRequest) { log.add("path", ((PathRequest) request).getPath()); if (request instanceof OpenRequest) { if (!((OpenRequest) request).isReadOnly()) { int mode = ((OpenRequest) request).getUMask(); if (mode == 0) { log.add("mode", "0"); } else { log.add("mode", "0" + Integer.toOctalString(mode)); } } log.add("options", "0x" + Integer.toHexString(((OpenRequest) request).getOptions())); } else if (request instanceof LocateRequest) { log.add("options", "0x" + Integer.toHexString(((LocateRequest) request).getOptions())); } else if (request instanceof MkDirRequest) { log.add("options", "0x" + Integer.toHexString(((MkDirRequest) request).getOptions())); } } else if (request instanceof LoginRequest) { log.add("username", ((LoginRequest) request).getUserName()); log.add("capver", ((LoginRequest) request).getClientProtocolVersion()); log.add("pid", ((LoginRequest) request).getPID()); log.add("token", emptyToNull(((LoginRequest) request).getToken())); } else if (request instanceof MvRequest) { log.add("source", ((MvRequest) request).getSourcePath()); log.add("target", ((MvRequest) request).getTargetPath()); } else if (request instanceof PrepareRequest) { log.add("options", "0x" + Integer.toHexString(((PrepareRequest) request).getOptions())); if (((PrepareRequest) request).getPathList().length == 1) { log.add("path", ((PrepareRequest) request).getPathList()[0]); } else { log.add("files", ((PrepareRequest) request).getPathList().length); } } else if (request instanceof StatxRequest) { if (((StatxRequest) request).getPaths().length == 1) { log.add("path", ((StatxRequest) request).getPaths()[0]); } else { log.add("files", ((StatxRequest) request).getPaths().length); } } else if (request instanceof SetRequest) { final String APPID_PREFIX = "appid "; final int APPID_PREFIX_LENGTH = APPID_PREFIX.length(); final int APPID_MSG_LENGTH = 80; String data = ((SetRequest) request).getData(); if (data.startsWith(APPID_PREFIX)) { log.add("appid", data.substring(APPID_PREFIX_LENGTH, Math.min(APPID_PREFIX_LENGTH + APPID_MSG_LENGTH, data.length()))); } } else if (request instanceof EndSessionRequest) { log.add("sessionId", ((EndSessionRequest) request).getSessionId()); } log.add("response", getStatusCode(response)); if (response instanceof ErrorResponse) { log.add("error.code", getErrorCode((ErrorResponse) response)); log.add("error.msg", ((ErrorResponse) response).getErrorMessage()); } else if (response instanceof RedirectResponse) { log.add("host", ((RedirectResponse) response).getHost()); log.add("port", ((RedirectResponse) response).getPort()); log.add("token", emptyToNull(((RedirectResponse) response).getToken())); } else if (response instanceof StatResponse) { log.add("flags", ((StatResponse) response).getFlags()); log.add("modtime", Instant.ofEpochSecond(((StatResponse) response).getModificationTime())); log.add("size", ((StatResponse) response).getSize()); } else if (response instanceof LoginResponse) { log.add("sessionId", ((LoginResponse) response).getSessionId()); log.add("sec", emptyToNull(((LoginResponse) response).getSec())); } else if (response instanceof OpenResponse) { FileStatus fs = ((OpenResponse) response).getFileStatus(); if (fs != null) { log.add("flags", fs.getFlags()); log.add("modtime", Instant.ofEpochSecond(fs.getModificationTime())); log.add("size", fs.getSize()); } } log.toLogger(logger); } } ctx.write(msg, promise); } private String getErrorCode(ErrorResponse response) { int errorNumber = response.getErrorNumber(); switch (errorNumber) { case kXR_ArgInvalid: return "ArgInvalid"; case kXR_ArgMissing: return "ArgMissing"; case kXR_ArgTooLong: return "ArgTooLong"; case kXR_FileLocked: return "FileLocked"; case kXR_FileNotOpen: return "FileNotOpen"; case kXR_FSError: return "FSError"; case kXR_InvalidRequest: return "InvalidRequest"; case kXR_IOError: return "IOError"; case kXR_NoMemory: return "NoMemory"; case kXR_NoSpace: return "NoSpace"; case kXR_NotAuthorized: return "NotAuhorized"; case kXR_NotFound: return "NotFound"; case kXR_ServerError: return "ServerError"; case kXR_Unsupported: return "Unsupported"; case kXR_noserver: return "noserver"; case kXR_NotFile: return "NotFile"; case kXR_isDirectory: return "isDirectory"; case kXR_Cancelled: return "Cancelled"; case kXR_ChkLenErr: return "ChkLenErr"; case kXR_ChkSumErr: return "ChkSumErr"; case kXR_inProgress: return "inProgress"; case kXR_noErrorYet: return "noErrorYet"; default: return String.valueOf(errorNumber); } } private static String getStatusCode(XrootdResponse<?> response) { int status = response.getStatus(); switch (status) { case XrootdProtocol.kXR_authmore: return "authmore"; case XrootdProtocol.kXR_error: return "error"; case XrootdProtocol.kXR_ok: return "ok"; case XrootdProtocol.kXR_oksofar: return "oksofar"; case XrootdProtocol.kXR_redirect: return "redirect"; case XrootdProtocol.kXR_wait: return "wait"; case XrootdProtocol.kXR_waitresp: return "waitresp"; default: return String.valueOf(status); } } private static String getRequestId(XrootdRequest request) { switch (request.getRequestId()) { case kXR_handshake: return "handshake"; case kXR_auth: return "auth"; case kXR_query: return "query"; case kXR_chmod: return "chdmod"; case kXR_close: return "close"; case kXR_dirlist: return "dirlist"; case kXR_getfile: return "getfile"; case kXR_protocol: return "protocol"; case kXR_login: return "login"; case kXR_mkdir: return "mkdir"; case kXR_mv: return "mv"; case kXR_open: return "open"; case kXR_ping: return "ping"; case kXR_putfile: return "putfile"; case kXR_read: return "read"; case kXR_rm: return "rm"; case kXR_rmdir: return "rmdir"; case kXR_sync: return "sync"; case kXR_stat: return "stat"; case kXR_set: return "set"; case kXR_write: return "write"; case kXR_admin: return "admin"; case kXR_prepare: return "prepare"; case kXR_statx: return "statx"; case kXR_endsess: return "endsess"; case kXR_bind: return "bind"; case kXR_readv: return "readv"; case kXR_verifyw: return "verifyw"; case kXR_locate: return "locate"; case kXR_truncate: return "truncate"; default: return String.valueOf(request.getRequestId()); } } }