mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
2f54bab951
we know answer send_requests correctly
metze
(This used to be commit aecc9ca1cc
)
414 lines
12 KiB
C
414 lines
12 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
WINS Replication server
|
|
|
|
Copyright (C) Stefan Metzmacher 2005
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "dlinklist.h"
|
|
#include "lib/events/events.h"
|
|
#include "lib/socket/socket.h"
|
|
#include "smbd/service_task.h"
|
|
#include "smbd/service_stream.h"
|
|
#include "lib/messaging/irpc.h"
|
|
#include "librpc/gen_ndr/ndr_winsrepl.h"
|
|
#include "librpc/gen_ndr/ndr_nbt.h"
|
|
#include "wrepl_server/wrepl_server.h"
|
|
#include "nbt_server/wins/winsdb.h"
|
|
#include "lib/ldb/include/ldb.h"
|
|
|
|
static NTSTATUS wreplsrv_in_start_association(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wrepl_start *start = &call->req_packet.message.start;
|
|
struct wrepl_start *start_reply = &call->rep_packet.message.start_reply;
|
|
|
|
if (call->req_packet.opcode & WREPL_OPCODE_BITS) {
|
|
/*
|
|
*if the assoc_ctx doesn't match ignore the packet
|
|
*/
|
|
if ((call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx)
|
|
&& (call->req_packet.assoc_ctx != 0)) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
call->wreplconn->assoc_ctx.our_ctx = WREPLSRV_INVALID_ASSOC_CTX;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
if (start->minor_version != 2 || start->major_version != 5) {
|
|
/* w2k terminate the connection if the versions doesn't match */
|
|
return NT_STATUS_UNKNOWN_REVISION;
|
|
}
|
|
|
|
call->wreplconn->assoc_ctx.stopped = False;
|
|
call->wreplconn->assoc_ctx.our_ctx = WREPLSRV_VALID_ASSOC_CTX;
|
|
call->wreplconn->assoc_ctx.peer_ctx = start->assoc_ctx;
|
|
|
|
call->rep_packet.mess_type = WREPL_START_ASSOCIATION_REPLY;
|
|
start_reply->assoc_ctx = call->wreplconn->assoc_ctx.our_ctx;
|
|
start_reply->minor_version = 2;
|
|
start_reply->major_version = 5;
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_stop_assoc_ctx(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wrepl_stop *stop_out = &call->rep_packet.message.stop;
|
|
|
|
call->wreplconn->assoc_ctx.stopped = True;
|
|
|
|
call->rep_packet.mess_type = WREPL_STOP_ASSOCIATION;
|
|
stop_out->reason = 4;
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_stop_association(struct wreplsrv_in_call *call)
|
|
{
|
|
/*
|
|
* w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set
|
|
*/
|
|
if (call->req_packet.opcode & WREPL_OPCODE_BITS) {
|
|
/*
|
|
*if the assoc_ctx doesn't match ignore the packet
|
|
*/
|
|
if (call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
/* when the opcode bits are set the connection should be directly terminated */
|
|
return NT_STATUS_CONNECTION_RESET;
|
|
}
|
|
|
|
if (call->wreplconn->assoc_ctx.stopped) {
|
|
/* this causes the connection to be directly terminated */
|
|
return NT_STATUS_CONNECTION_RESET;
|
|
}
|
|
|
|
/* this will cause to not receive packets anymore and terminate the connection if the reply is send */
|
|
call->wreplconn->terminate = True;
|
|
return wreplsrv_in_stop_assoc_ctx(call);
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_table_query(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wreplsrv_service *service = call->wreplconn->service;
|
|
struct wrepl_replication *repl_out = &call->rep_packet.message.replication;
|
|
struct wrepl_table *table_out = &call->rep_packet.message.replication.info.table;
|
|
struct wreplsrv_owner *cur;
|
|
uint64_t local_max_version;
|
|
uint32_t i = 0;
|
|
|
|
repl_out->command = WREPL_REPL_TABLE_REPLY;
|
|
|
|
table_out->partner_count = 0;
|
|
table_out->partners = NULL;
|
|
table_out->initiator = WINSDB_OWNER_LOCAL;
|
|
|
|
local_max_version = wreplsrv_local_max_version(service);
|
|
if (local_max_version > 0) {
|
|
table_out->partner_count++;
|
|
}
|
|
|
|
for (cur = service->table; cur; cur = cur->next) {
|
|
table_out->partner_count++;
|
|
}
|
|
|
|
table_out->partners = talloc_array(call, struct wrepl_wins_owner, table_out->partner_count);
|
|
NT_STATUS_HAVE_NO_MEMORY(table_out->partners);
|
|
|
|
if (local_max_version > 0) {
|
|
table_out->partners[i].address = call->wreplconn->our_ip;
|
|
table_out->partners[i].min_version = 0;
|
|
table_out->partners[i].max_version = local_max_version;
|
|
table_out->partners[i].type = 1;
|
|
i++;
|
|
}
|
|
|
|
for (cur = service->table; cur; cur = cur->next) {
|
|
table_out->partners[i] = cur->owner;
|
|
i++;
|
|
}
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static int wreplsrv_in_sort_wins_name(struct wrepl_wins_name *n1,
|
|
struct wrepl_wins_name *n2)
|
|
{
|
|
if (n1->id < n2->id) return -1;
|
|
if (n1->id > n2->id) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_record2wins_name(TALLOC_CTX *mem_ctx, struct wrepl_wins_name *name, struct winsdb_record *rec)
|
|
{
|
|
uint8_t *namebuf;
|
|
uint32_t namebuf_len;
|
|
uint32_t name_len;
|
|
|
|
name_len = strlen(rec->name->name);
|
|
if (name_len > 15) {
|
|
return NT_STATUS_INVALID_PARAMETER_MIX;
|
|
}
|
|
|
|
namebuf = (uint8_t *)talloc_asprintf(mem_ctx, "%-15s%c%s",
|
|
rec->name->name, rec->name->type,
|
|
(rec->name->scope?rec->name->scope:""));
|
|
NT_STATUS_HAVE_NO_MEMORY(namebuf);
|
|
namebuf_len = strlen((char *)namebuf) + 1;
|
|
|
|
/* oh wow, what a nasty bug in windows ... */
|
|
if (namebuf[15] == 0x1b && namebuf_len >= 16) {
|
|
namebuf[15] = namebuf[0];
|
|
namebuf[0] = 0x1b;
|
|
}
|
|
|
|
name->name_len = namebuf_len;
|
|
name->name = namebuf;
|
|
name->id = rec->version;
|
|
name->unknown = WINSDB_GROUP_ADDRESS;
|
|
|
|
name->flags = rec->nb_flags;
|
|
name->group_flag = 0;
|
|
|
|
switch (name->flags & 2) {
|
|
case 0:
|
|
name->addresses.ip = rec->addresses[0]->address;
|
|
talloc_steal(mem_ctx, rec->addresses[0]->address);
|
|
break;
|
|
case 2:
|
|
name->addresses.addresses.num_ips = 0;
|
|
name->addresses.addresses.ips = NULL;
|
|
break;
|
|
}
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_send_request(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wreplsrv_service *service = call->wreplconn->service;
|
|
struct wrepl_wins_owner *owner_in = &call->req_packet.message.replication.info.owner;
|
|
struct wrepl_replication *repl_out = &call->rep_packet.message.replication;
|
|
struct wrepl_send_reply *reply_out = &call->rep_packet.message.replication.info.reply;
|
|
struct wreplsrv_owner local_owner;
|
|
struct wreplsrv_owner *owner;
|
|
const char *filter;
|
|
struct ldb_message **res = NULL;
|
|
int ret;
|
|
struct wrepl_wins_name *names;
|
|
struct winsdb_record *rec;
|
|
NTSTATUS status;
|
|
uint32_t i;
|
|
|
|
if (strcmp(call->wreplconn->our_ip, owner_in->address) == 0) {
|
|
ZERO_STRUCT(local_owner);
|
|
local_owner.owner.address = WINSDB_OWNER_LOCAL;
|
|
local_owner.owner.min_version = 0;
|
|
local_owner.owner.max_version = wreplsrv_local_max_version(service);
|
|
local_owner.owner.type = 1;
|
|
owner = &local_owner;
|
|
} else {
|
|
owner = wreplsrv_find_owner(service->table, owner_in->address);
|
|
}
|
|
|
|
repl_out->command = WREPL_REPL_SEND_REPLY;
|
|
reply_out->num_names = 0;
|
|
reply_out->names = NULL;
|
|
|
|
/*
|
|
* if we didn't know this owner, must be a bug in the partners client code...
|
|
* return an empty list.
|
|
*/
|
|
if (!owner) {
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
* if the partner ask for nothing, or give invalid ranges,
|
|
* return an empty list.
|
|
*/
|
|
if (owner_in->min_version >= owner_in->max_version) {
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
* if the partner has already all records for nothing, or give invalid ranges,
|
|
* return an empty list.
|
|
*/
|
|
if (owner_in->min_version >= owner->owner.max_version) {
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
filter = talloc_asprintf(call, "(&(winsOwner=%s)(objectClass=wins)(active=1)(version>=%llu)(version<=%llu))",
|
|
owner->owner.address, owner_in->min_version, owner_in->max_version);
|
|
NT_STATUS_HAVE_NO_MEMORY(filter);
|
|
ret = ldb_search(service->wins_db, NULL, LDB_SCOPE_SUBTREE, filter, NULL, &res);
|
|
if (res != NULL) {
|
|
talloc_steal(call, res);
|
|
}
|
|
if (ret < 0) return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
|
if (ret == 0) return NT_STATUS_OK;
|
|
|
|
names = talloc_array(call, struct wrepl_wins_name, ret);
|
|
NT_STATUS_HAVE_NO_MEMORY(names);
|
|
|
|
for (i=0; i < ret; i++) {
|
|
rec = winsdb_record(res[i], call);
|
|
NT_STATUS_HAVE_NO_MEMORY(rec);
|
|
|
|
rec->name = winsdb_nbt_name(names, res[i]->dn);
|
|
if (!rec->name) {
|
|
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
|
}
|
|
|
|
status = wreplsrv_record2wins_name(names, &names[i], rec);
|
|
NT_STATUS_NOT_OK_RETURN(status);
|
|
talloc_free(rec);
|
|
talloc_free(res[i]);
|
|
}
|
|
|
|
/* sort the names before we send them */
|
|
qsort(names, ret, sizeof(struct wrepl_wins_name), (comparison_fn_t)wreplsrv_in_sort_wins_name);
|
|
|
|
reply_out->num_names = ret;
|
|
reply_out->names = names;
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_replication(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wrepl_replication *repl_in = &call->req_packet.message.replication;
|
|
NTSTATUS status;
|
|
|
|
/*
|
|
* w2k only check the assoc_ctx if the opcode has the 0x00007800 bits are set
|
|
*/
|
|
if (call->req_packet.opcode & WREPL_OPCODE_BITS) {
|
|
/*
|
|
*if the assoc_ctx doesn't match ignore the packet
|
|
*/
|
|
if (call->req_packet.assoc_ctx != call->wreplconn->assoc_ctx.our_ctx) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (!call->wreplconn->partner) {
|
|
return wreplsrv_in_stop_assoc_ctx(call);
|
|
}
|
|
|
|
switch (repl_in->command) {
|
|
case WREPL_REPL_TABLE_QUERY:
|
|
status = wreplsrv_in_table_query(call);
|
|
break;
|
|
|
|
case WREPL_REPL_TABLE_REPLY:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_REPL_SEND_REQUEST:
|
|
status = wreplsrv_in_send_request(call);
|
|
break;
|
|
|
|
case WREPL_REPL_SEND_REPLY:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_REPL_UPDATE:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_REPL_5:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_REPL_INFORM:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_REPL_9:
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
default:
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (NT_STATUS_IS_OK(status)) {
|
|
call->rep_packet.mess_type = WREPL_REPLICATION;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static NTSTATUS wreplsrv_in_invalid_assoc_ctx(struct wreplsrv_in_call *call)
|
|
{
|
|
struct wrepl_start *start = &call->rep_packet.message.start;
|
|
|
|
call->rep_packet.opcode = 0x00008583;
|
|
call->rep_packet.assoc_ctx = 0;
|
|
call->rep_packet.mess_type = WREPL_START_ASSOCIATION;
|
|
|
|
start->assoc_ctx = 0x0000000a;
|
|
start->minor_version = 0x0001;
|
|
start->major_version = 0x0000;
|
|
|
|
call->rep_packet.padding = data_blob_talloc(call, NULL, 4);
|
|
memset(call->rep_packet.padding.data, '\0', call->rep_packet.padding.length);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
NTSTATUS wreplsrv_in_call(struct wreplsrv_in_call *call)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (!(call->req_packet.opcode & WREPL_OPCODE_BITS)
|
|
&& (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX)) {
|
|
return wreplsrv_in_invalid_assoc_ctx(call);
|
|
}
|
|
|
|
switch (call->req_packet.mess_type) {
|
|
case WREPL_START_ASSOCIATION:
|
|
status = wreplsrv_in_start_association(call);
|
|
break;
|
|
case WREPL_START_ASSOCIATION_REPLY:
|
|
/* this is not valid here, so we ignore it */
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
case WREPL_STOP_ASSOCIATION:
|
|
status = wreplsrv_in_stop_association(call);
|
|
break;
|
|
|
|
case WREPL_REPLICATION:
|
|
status = wreplsrv_in_replication(call);
|
|
break;
|
|
default:
|
|
/* everythingelse is also not valid here, so we ignore it */
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (call->wreplconn->assoc_ctx.our_ctx == WREPLSRV_INVALID_ASSOC_CTX) {
|
|
return wreplsrv_in_invalid_assoc_ctx(call);
|
|
}
|
|
|
|
if (NT_STATUS_IS_OK(status)) {
|
|
call->rep_packet.opcode = WREPL_OPCODE_BITS;
|
|
call->rep_packet.assoc_ctx = call->wreplconn->assoc_ctx.peer_ctx;
|
|
}
|
|
|
|
return status;
|
|
}
|