/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* 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 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 .
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "config.h"
#ifdef HAVE_STRING_H
#include
#endif
#include // secure_getenv
#include "protocol.h"
#include "md5.h"
// libmonit
#include "exceptions/IOException.h"
#include "exceptions/ProtocolException.h"
// https://github.com/nmap/nmap/blob/master/nselib/pgsql.lua
#define PROTO_V3 3
#define PKT_Error 'E'
#define PKT_AuthRequest 'R'
#define PKT_Password 'p'
#define PKT_Terminate 'X'
#define AUTH_Plain 3
#define AUTH_MD5 5
const char * safe_getenv(const char * szName, const char * szDefault)
{
const char * tmp;
#ifdef secure_getenv // glibc 2.17
tmp = secure_getenv(szName);
#else
tmp = getenv(szName);
#endif
if (tmp == NULL || *tmp == 0)
return szDefault;
else
return tmp;
}
char * str_push(char * buffer, const char * szString)
{
if (szString == NULL)
return buffer;
strcpy(buffer, szString);
return buffer + strlen(szString) + 1;
}
void md5(char * output, const char * data1, int len1, const char * data2, int len2)
{
static char hex[] = "0123456789abcdef";
md5_context_t ctx;
md5_byte_t hash[16];
char * tmp = output;
md5_init(&ctx);
md5_append(&ctx, (const md5_byte_t *) data1, len1);
md5_append(&ctx, (const md5_byte_t *) data2, len2);
md5_finish(&ctx, hash);
for (int i = 0; i < 16; i++)
{
*tmp ++ = hex[hash[i] >> 4];
*tmp ++ = hex[hash[i] & 0xf];
}
}
/**
* PostgreSQL test.
*
* @file
*/
void check_pgsql(Socket_T socket)
{
const char * szPgUser = safe_getenv("PGUSER", "postgres");
const char * szPgDatabase = safe_getenv("PGDATABASE", "postgres");
const char * szPgPassword = safe_getenv("PGPASSWORD", "");
#define LOGIN_MAXLEN 255 //!!! lazy: update only one byte: requestLogin[3]
char requestLogin[LOGIN_MAXLEN];
#define REPLY_MINLEN 9
#define REPLY_MAXLEN 1024
char reply[REPLY_MAXLEN];
int br;
#define PKT_AuthRequestLen 9
char responseAuthOk[PKT_AuthRequestLen] =
{
PKT_AuthRequest,
0x00, /** Length */
0x00,
0x00,
0x08,
0x00, /** OK code 0 */
0x00,
0x00,
0x00
};
char requestTerminate[5] =
{
PKT_Terminate,
0x00, /** Length */
0x00,
0x00,
0x04
};
size_t iLoginLen;
char * ptr;
iLoginLen = 8 + 4 + 1 + strlen(szPgUser) + 1 + 8 + 1 + strlen(szPgDatabase) + 1 + 1;
if (iLoginLen > LOGIN_MAXLEN)
THROW(ProtocolException, "PGSQL: internal error (insufficient buffer for request) -- %s", STRERROR);
memset(requestLogin, 0, LOGIN_MAXLEN); // set last NULL after payload
ptr = requestLogin + 8;
ptr = str_push(ptr, "user");
ptr = str_push(ptr, szPgUser);
ptr = str_push(ptr, "database");
ptr = str_push(ptr, szPgDatabase);
++ ptr; // trailing NULL
if (iLoginLen != ptr - &requestLogin[0])
THROW(ProtocolException, "PGSQL: internal error (request packet) -- %s", STRERROR);
requestLogin[3] = (unsigned char) iLoginLen;
requestLogin[5] = PROTO_V3;
ASSERT(socket);
if (Socket_write(socket, requestLogin, iLoginLen) != iLoginLen)
THROW(IOException, "PGSQL: error sending data -- %s", STRERROR);
br = Socket_read(socket, reply, REPLY_MAXLEN - 1);
if (br < REPLY_MINLEN)
THROW(IOException, "PGSQL: error receiving data -- %s", STRERROR);
if (*reply == PKT_Error)
THROW(ProtocolException, "PGSQL: server replies Error -- %s", STRERROR);
// server *expects* password?
if (*reply == PKT_AuthRequest && memcmp(reply, responseAuthOk, PKT_AuthRequestLen) != 0)
{
if (br == PKT_AuthRequestLen && reply[4] == br-1 && reply[8] == AUTH_Plain)
{
iLoginLen = 4 + strlen(szPgPassword);
memset(requestLogin, 0, LOGIN_MAXLEN);
requestLogin[0] = PKT_Password;
requestLogin[4] = (unsigned char) iLoginLen;
str_push(requestLogin + 5, szPgPassword);
Socket_write(socket, requestLogin, iLoginLen + 1);
}
else
if (br == PKT_AuthRequestLen + 4 && reply[4] == br-1 && reply[8] == AUTH_MD5)
{
//!!! first char is PKT_Password
char pkt_hash[8 + 32 + 1] = "p\0\0\0\x28md5 \0";
md5(pkt_hash + 8, szPgPassword, strlen(szPgPassword), szPgUser, strlen(szPgUser));
md5(pkt_hash + 8, pkt_hash + 8, 32, reply + 9, 4);
Socket_write(socket, pkt_hash, sizeof(pkt_hash));
}
else
THROW(ProtocolException, "PGSQL: unknown authentication type -- %s", STRERROR);
br = Socket_read(socket, reply, REPLY_MAXLEN - 1);
if (br < REPLY_MINLEN)
THROW(IOException, "PGSQL: error receiving data -- %s", STRERROR);
//!!! PKT_Error: FATAL C28000 password authentication failed for user...
}
// R: successful connection?
if (br >= PKT_AuthRequestLen && memcmp(reply, responseAuthOk, PKT_AuthRequestLen) == 0)
{
reply[br] = 0;
if (strstr(reply, "does not exist") != NULL)
THROW(ProtocolException, "PGSQL: user/database not exist -- %s", STRERROR);
Socket_write(socket, requestTerminate, sizeof(requestTerminate));
return;
}
THROW(ProtocolException, "PGSQL: unknown server response -- %s", STRERROR);
}