diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0785c75c..d61d0491 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -29,4 +29,4 @@ jobs: key: ${{ runner.os }}-java${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-java${{ matrix.java }}-m2 - name: Run Tests - run: mvn test -Dgpg.skip -Dmaven.javadoc.skip=true -B -V -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn + run: mvn verify -Dgpg.skip -Dmaven.javadoc.skip=true -B -V -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn diff --git a/README.md b/README.md index b60e8bca..8f2bcda3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add to your pom.xml: com.rapid7.client dcerpc - 0.10.0 + 0.11.0 ``` @@ -83,7 +83,8 @@ try (final Connection smbConnection = smbClient.connect("aaa.bbb.ccc.ddd")) { final RPCTransport transport = SMBTransportFactories.SRVSVC.getTransport(session); final ServerService serverService = new ServerService(transport); - final List shares = serverService.getShares(); + // Get shares at information level 0 + final List shares = serverService.getShares0(); for (final NetShareInfo0 share : shares) { System.out.println(share); } diff --git a/pom.xml b/pom.xml index fd10175b..a1a3b1ee 100644 --- a/pom.xml +++ b/pom.xml @@ -48,12 +48,14 @@ 3.4 18.0 1.3 - 4.13.1 + 5.7.2 1.10.19 - 0.10.0 + 0.11.1 6.11 - 1.7 - 1.7 + 1.60 + 1.16.0 + 1.8 + 1.8 UTF-8 @@ -79,6 +81,11 @@ smbj ${thirdparty.smbj.version} + + org.bouncycastle + bcprov-jdk15on + ${thirdparty.bouncycastle.version} + @@ -88,12 +95,6 @@ ${thirdparty.hamcrest.version} test - - junit - junit - ${thirdparty.junit.version} - test - org.mockito mockito-core @@ -106,6 +107,49 @@ ${thirdparty.testng.version} test + + org.testcontainers + testcontainers + ${thirdparty.testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${thirdparty.testcontainers.version} + test + + + + org.slf4j + slf4j-simple + 1.7.30 + test + + + org.junit.jupiter + junit-jupiter-api + ${thirdparty.junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${thirdparty.junit.version} + test + + + org.junit.vintage + junit-vintage-engine + ${thirdparty.junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${thirdparty.junit.version} + test + @@ -122,8 +166,56 @@ org.apache.maven.plugins maven-surefire-plugin - 2.20.1 + 2.22.2 + + maven-failsafe-plugin + 3.0.0-M5 + + src/integration-test/java + + + + + integration-test + verify + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.1.0 + + + add-test-source + generate-test-sources + + add-test-source + + + + src/integration-test/java + + + + + add-test-resource + generate-test-resources + + add-test-resource + + + + + src/integration-test/resources + + + + + + diff --git a/src/integration-test/java/IntegrationTestsIT.java b/src/integration-test/java/IntegrationTestsIT.java new file mode 100644 index 00000000..2523c0f3 --- /dev/null +++ b/src/integration-test/java/IntegrationTestsIT.java @@ -0,0 +1,92 @@ +import com.rapid7.client.dcerpc.msrrp.RegistryService; +import com.rapid7.client.dcerpc.mssrvs.ServerService; +import com.rapid7.client.dcerpc.transport.RPCTransport; +import com.rapid7.client.dcerpc.transport.SMBTransportFactories; +import com.hierynomus.mssmb2.SMB2Dialect; +import com.hierynomus.security.bc.BCSecurityProvider; +import com.hierynomus.smbj.SMBClient; +import com.hierynomus.smbj.SmbConfig; +import com.hierynomus.smbj.auth.AuthenticationContext; +import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.session.Session; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Testcontainers +class IntegrationTestsIT +{ + private static final Path DOCKER_BUILD_CONTEXT = Paths.get("src", "integration-test", "resources", "docker-image"); + + @Container + private static final GenericContainer sambaContainer = new GenericContainer( + new ImageFromDockerfile() + .withFileFromPath(".", DOCKER_BUILD_CONTEXT)) + .withExposedPorts(445); + + @ParameterizedTest + @MethodSource("testWinRegDoesKeyExistForEachSupportedSMBVersionArgs") + @DisplayName("Test registry service key exists function for different SMB protocols") + void testWinRegDoesKeyExistForEachSupportedSMBVersion(String keyPath, boolean shouldExist, SMB2Dialect dialect) + throws IOException + { + final SmbConfig smbConfig = SmbConfig.builder().withSecurityProvider(new BCSecurityProvider()).withDialects(dialect).build(); + final SMBClient smbClient = new SMBClient(smbConfig); + try (final Connection smbConnection = smbClient.connect("localhost", sambaContainer.getMappedPort(445))) { + final AuthenticationContext smbAuthenticationContext = new AuthenticationContext("smbj", "smbj".toCharArray(), ""); + final Session session = smbConnection.authenticate(smbAuthenticationContext); + + final RPCTransport transport = SMBTransportFactories.WINREG.getTransport(session); + final RegistryService registryService = new RegistryService(transport); + + assertEquals(dialect, smbConnection.getNegotiatedProtocol().getDialect()); + assertEquals(shouldExist, registryService.doesKeyExist("HKLM", keyPath)); + } + } + + @ParameterizedTest + @EnumSource(value = SMB2Dialect.class, names = {"SMB_2_0_2", "SMB_2_1", "SMB_3_0", "SMB_3_0_2", "SMB_3_1_1"}) + @DisplayName("Test service service enumerates shares for different SMB protocols") + void testSRVSVCReturnsSharesForEachSupportedSMBVersion(SMB2Dialect dialect) + throws IOException + { + final SmbConfig smbConfig = SmbConfig.builder().withSecurityProvider(new BCSecurityProvider()).withDialects(dialect).build(); + final SMBClient smbClient = new SMBClient(smbConfig); + try (final Connection smbConnection = smbClient.connect("localhost", sambaContainer.getMappedPort(445))) { + final AuthenticationContext smbAuthenticationContext = new AuthenticationContext("smbj", "smbj".toCharArray(), ""); + final Session session = smbConnection.authenticate(smbAuthenticationContext); + + final RPCTransport transport = SMBTransportFactories.SRVSVC.getTransport(session); + final ServerService serverService = new ServerService(transport); + + assertEquals(dialect, smbConnection.getNegotiatedProtocol().getDialect()); + assertEquals(5, serverService.getShares0().size()); + } + } + + static Stream testWinRegDoesKeyExistForEachSupportedSMBVersionArgs() { + return Stream.of( + Arguments.of("Software", true, SMB2Dialect.SMB_3_1_1), + Arguments.of("not_exist", false, SMB2Dialect.SMB_3_1_1), + Arguments.of("Software", true, SMB2Dialect.SMB_3_0_2), + Arguments.of("not_exist", false, SMB2Dialect.SMB_3_0_2), + Arguments.of("Software", true, SMB2Dialect.SMB_3_0), + Arguments.of("not_exist", false, SMB2Dialect.SMB_3_0), + Arguments.of("Software", true, SMB2Dialect.SMB_2_1), + Arguments.of("not_exist", false, SMB2Dialect.SMB_2_1), + Arguments.of("Software", true, SMB2Dialect.SMB_2_0_2), + Arguments.of("not_exist", false, SMB2Dialect.SMB_2_0_2) + ); + } +} diff --git a/src/integration-test/resources/docker-image/Dockerfile b/src/integration-test/resources/docker-image/Dockerfile new file mode 100644 index 00000000..17f60944 --- /dev/null +++ b/src/integration-test/resources/docker-image/Dockerfile @@ -0,0 +1,23 @@ +FROM alpine:3.7 + +RUN apk update && apk add --no-cache tini samba samba-common-tools supervisor bash + +ENV SMB_USER smbj +ENV SMB_PASSWORD smbj + +COPY smb.conf /etc/samba/smb.conf +COPY supervisord.conf /etc/supervisord.conf +COPY entrypoint.sh /entrypoint.sh +ADD public /opt/samba/share + +RUN mkdir -p /opt/samba/readonly /opt/samba/user /opt/samba/dfs && \ + chmod 777 /opt/samba/readonly /opt/samba/user /opt/samba/dfs && \ + adduser -s /bin/false "$SMB_USER" -D $SMB_PASSWORD && \ + (echo "$SMB_PASSWORD"; echo "$SMB_PASSWORD" ) | pdbedit -a -u "$SMB_USER" && \ + chmod ugo+x /entrypoint.sh + +EXPOSE 137/udp 138/udp 139 445 + +ENTRYPOINT ["/sbin/tini", "/entrypoint.sh"] +CMD ["supervisord"] + diff --git a/src/integration-test/resources/docker-image/entrypoint.sh b/src/integration-test/resources/docker-image/entrypoint.sh new file mode 100644 index 00000000..7b930174 --- /dev/null +++ b/src/integration-test/resources/docker-image/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +: "${SMB_USER:=smbuser}" +: "${SMB_PASSWORD:=smbpassword}" +# +#for netdev in /sys/class/net/*; do +# netdev=${netdev##*/} +# if [[ "$netdev" != "lo" ]]; then +# break +# fi +#done +#subnet=$(ip addr show "$netdev" | sed -n 's/.*inet \([0-9\.]*\/[0-9]*\) .*/\1/p') +#ip_address=${subnet%%/*} + +ip_address="127.0.0.1" + +# Create DFS links +# - /public -> public share +# - /user -> user share +# - /firstfail-public -> first listed server fails, second -> public share +ln -s "msdfs:${ip_address}\\public" /opt/samba/dfs/public +ln -s "msdfs:${ip_address}\\user" /opt/samba/dfs/user +ln -s "msdfs:192.0.2.1\\notthere,${ip_address}\\public" /opt/samba/dfs/firstfail-public + +exec "$@" diff --git a/src/integration-test/resources/docker-image/public/folder/do_not_remove b/src/integration-test/resources/docker-image/public/folder/do_not_remove new file mode 100644 index 00000000..e69de29b diff --git a/src/integration-test/resources/docker-image/public/test.txt b/src/integration-test/resources/docker-image/public/test.txt new file mode 100644 index 00000000..137d409d --- /dev/null +++ b/src/integration-test/resources/docker-image/public/test.txt @@ -0,0 +1 @@ +Hi there! diff --git a/src/integration-test/resources/docker-image/smb.conf b/src/integration-test/resources/docker-image/smb.conf new file mode 100644 index 00000000..6bde6f30 --- /dev/null +++ b/src/integration-test/resources/docker-image/smb.conf @@ -0,0 +1,61 @@ +[global] +security = user + +load printers = no +printcap name = /dev/null +printing = bsd + +unix charset = UTF-8 +dos charset = CP932 + +workgroup = WORKGROUP + +server string = %h server (Samba, Ubuntu) +dns proxy = no +interfaces = 192.168.2.0/24 eth0 +bind interfaces only = yes +log file = /var/log/samba/log.%m +max log size = 1000 +syslog = 0 +panic action = /usr/share/samba/panic-action %d +server role = standalone server +passdb backend = tdbsam +obey pam restrictions = yes +unix password sync = yes +passwd program = /usr/bin/passwd %u +passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* . +pam password change = yes +map to guest = Bad User +usershare allow guests = yes +host msdfs = yes + +[public] +path = /opt/samba/share +writable = yes +printable = no +public = yes +guest only = yes +create mode = 0777 +directory mode = 0777 + +[readonly] +path = /opt/samba/readonly +writable = no +printable = no +public = no + +[user] +path = /opt/samba/user +writable = yes +printable = no +public = no +create mode = 0777 +directory mode = 0777 + +[dfs] +path = /opt/samba/dfs +writable = no +printable = no +public = yes +guest ok = yes +msdfs root = yes diff --git a/src/integration-test/resources/docker-image/supervisord.conf b/src/integration-test/resources/docker-image/supervisord.conf new file mode 100644 index 00000000..d9582786 --- /dev/null +++ b/src/integration-test/resources/docker-image/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +/* user=root */ +loglevel=info + +[program:smbd] +/* command=/usr/sbin/smbd -i --daemon --foreground --log-stdout */ +command=/usr/sbin/smbd --daemon --foreground --log-stdout +redirect_stderr=true + +[program:nmbd] +/* command=/usr/sbin/nmbd -i --daemon --foreground --log-stdout */ +command=/usr/sbin/nmbd --daemon --foreground --log-stdout +redirect_stderr=true diff --git a/src/main/java/com/rapid7/helper/smbj/io/SMB2Exception.java b/src/main/java/com/rapid7/helper/smbj/io/SMB2Exception.java index c164f1d8..bbdeb8a3 100644 --- a/src/main/java/com/rapid7/helper/smbj/io/SMB2Exception.java +++ b/src/main/java/com/rapid7/helper/smbj/io/SMB2Exception.java @@ -20,7 +20,7 @@ import java.io.IOException; import com.hierynomus.mserref.NtStatus; -import com.hierynomus.mssmb2.SMB2Header; +import com.hierynomus.mssmb2.SMB2PacketHeader; import com.hierynomus.mssmb2.SMB2MessageCommandCode; @SuppressWarnings("serial") @@ -29,7 +29,7 @@ public class SMB2Exception extends IOException { private final SMB2MessageCommandCode failedCommand; private final long statusCode; - public SMB2Exception(final SMB2Header header, final String message) { + public SMB2Exception(final SMB2PacketHeader header, final String message) { super(String.format("%s returned %s (%d/%d): %s", header.getMessage(), header.getStatusCode(), header.getStatusCode(), header.getStatusCode(), message)); status = NtStatus.valueOf(header.getStatusCode()); diff --git a/src/main/java/com/rapid7/helper/smbj/io/SMB2SessionMessage.java b/src/main/java/com/rapid7/helper/smbj/io/SMB2SessionMessage.java index bebd5e82..2d2b4bb0 100644 --- a/src/main/java/com/rapid7/helper/smbj/io/SMB2SessionMessage.java +++ b/src/main/java/com/rapid7/helper/smbj/io/SMB2SessionMessage.java @@ -28,8 +28,9 @@ import java.util.concurrent.TimeoutException; import com.hierynomus.mserref.NtStatus; import com.hierynomus.mssmb2.SMB2Dialect; -import com.hierynomus.mssmb2.SMB2Header; +import com.hierynomus.mssmb2.SMB2PacketHeader; import com.hierynomus.mssmb2.SMB2Packet; +import com.hierynomus.smbj.SmbConfig; import com.hierynomus.smbj.session.Session; public abstract class SMB2SessionMessage { @@ -38,11 +39,11 @@ public abstract class SMB2SessionMessage { private final long sessionID; private final long timeout; - public SMB2SessionMessage(final Session session) { + public SMB2SessionMessage(final Session session, final SmbConfig config) { dialect = session.getConnection().getNegotiatedProtocol().getDialect(); this.session = session; sessionID = session.getSessionId(); - timeout = session.getConnection().getConfig().getTransactTimeout(); + timeout = config.getTransactTimeout(); } public SMB2Dialect getDialect() { @@ -93,7 +94,7 @@ public T sendAndRead(final SMB2Packet packet, final EnumS throws IOException { final Future future = send(packet); final T responsePacket = read(future); - final SMB2Header responseHeader = responsePacket.getHeader(); + final SMB2PacketHeader responseHeader = responsePacket.getHeader(); final NtStatus responseStatus = NtStatus.valueOf(responseHeader.getStatusCode()); if (!ok.contains(responseStatus)) { throw new SMB2Exception(responseHeader, "expected=" + ok); diff --git a/src/main/java/com/rapid7/helper/smbj/share/NamedPipe.java b/src/main/java/com/rapid7/helper/smbj/share/NamedPipe.java index ffc44f82..66d8b9ce 100644 --- a/src/main/java/com/rapid7/helper/smbj/share/NamedPipe.java +++ b/src/main/java/com/rapid7/helper/smbj/share/NamedPipe.java @@ -48,7 +48,7 @@ public class NamedPipe extends SMB2SessionMessage implements Closeable { private final int writeBufferSize; public NamedPipe(final Session session, final PipeShare share, final String name) throws IOException { - super(session); + super(session, share.getTreeConnect().getConfig()); this.share = share; @@ -56,9 +56,9 @@ public NamedPipe(final Session session, final PipeShare share, final String name final SMB2CreateResponse createResponse = sendAndRead(createRequest, EnumSet.of(NtStatus.STATUS_SUCCESS)); fileID = createResponse.getFileId(); - transactBufferSize = Math.min(session.getConnection().getConfig().getTransactBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxTransactSize()); - readBufferSize = Math.min(session.getConnection().getConfig().getReadBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxReadSize()); - writeBufferSize = Math.min(session.getConnection().getConfig().getWriteBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxWriteSize()); + transactBufferSize = Math.min(share.getTreeConnect().getConfig().getTransactBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxTransactSize()); + readBufferSize = Math.min(share.getTreeConnect().getConfig().getReadBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxReadSize()); + writeBufferSize = Math.min(share.getTreeConnect().getConfig().getWriteBufferSize(), session.getConnection().getNegotiatedProtocol().getMaxWriteSize()); } public byte[] transact(final byte[] inBuffer) throws IOException {