/* Unix SMB/CIFS implementation. process incoming packets - main loop Copyright (C) Jean François Micouleau 1998-2002. 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 "wins_repl.h" extern fd_set *listen_set; extern int listen_number; extern int *sock_array; WINS_OWNER global_wins_table[64][64]; int partner_count; TALLOC_CTX *mem_ctx; #define WINS_LIST "wins.tdb" #define INFO_VERSION "INFO/version" #define INFO_COUNT "INFO/num_entries" #define INFO_ID_HIGH "INFO/id_high" #define INFO_ID_LOW "INFO/id_low" #define ENTRY_PREFIX "ENTRY/" /******************************************************************* fill the header of a reply. ********************************************************************/ static void fill_header(GENERIC_PACKET *g, int opcode, int ctx, int mess) { if (g==NULL) return; g->header.opcode=opcode; g->header.assoc_ctx=ctx; g->header.mess_type=mess; } /******************************************************************* dump the global table, that's a debug code. ********************************************************************/ static void dump_global_table(void) { int i,j; for (i=0;isa_rp.assoc_ctx=time(NULL); fill_header(r, OPCODE_NON_NBT, q->sa_rq.assoc_ctx, MESSAGE_TYPE_START_ASSOC_REPLY); /* reply we are a NT4 server */ /* w2K is min=2, maj=5 */ r->sa_rp.min_ver=1; r->sa_rp.maj_ver=1; add_partner(r->sa_rp.assoc_ctx, q->sa_rq.assoc_ctx, False, False); } /******************************************************************* start association reply ********************************************************************/ static void start_assoc_reply(GENERIC_PACKET *q, GENERIC_PACKET *r) { int i; /* check if we have already registered this client */ if (!check_partner(q->header.assoc_ctx)) { DEBUG(0,("start_assoc_reply: unknown client\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } if (!update_server_partner(q->header.assoc_ctx, q->sa_rp.assoc_ctx)) { DEBUG(0,("start_assoc_reply: can't update server ctx\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } /* if pull, request map table */ if (check_pull_partner(q->header.assoc_ctx)) { fill_header(r, OPCODE_NON_NBT, get_server_assoc(q->header.assoc_ctx), MESSAGE_TYPE_REPLICATE); r->rep.msg_type=MESSAGE_REP_ADD_VERSION_REQUEST; DEBUG(5,("start_assoc_reply: requesting map table\n")); return; } /* if push, send our table */ if (check_push_partner(q->header.assoc_ctx)) { fill_header(r, OPCODE_NON_NBT, get_server_assoc(q->header.assoc_ctx), MESSAGE_TYPE_REPLICATE); r->rep.msg_type=MESSAGE_REP_UPDATE_NOTIFY_REQUEST; r->rep.un_rq.partner_count=partner_count; r->rep.un_rq.wins_owner=(WINS_OWNER *)talloc(mem_ctx, partner_count*sizeof(WINS_OWNER)); if (r->rep.un_rq.wins_owner==NULL) { DEBUG(0,("start_assoc_reply: can't alloc memory\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } for (i=0; irep.un_rq.wins_owner[i]=global_wins_table[0][i]; DEBUG(5,("start_assoc_reply: sending update table\n")); return; } /* neither push/pull, stop */ /* we should not come here */ DEBUG(0,("we have a partner which is neither push nor pull !\n")); stop_packet(q, r, STOP_REASON_USER_REASON); } /**************************************************************************** initialise and fill the in-memory partner table. ****************************************************************************/ int init_wins_partner_table(void) { int i=1,j=0,k; char **partner = lp_list_make(lp_wins_partners()); if (partner==NULL) { DEBUG(0,("wrepld: no partner list in smb.conf, exiting\n")); exit_server("normal exit"); return(0); } DEBUG(4, ("init_wins_partner_table: partners: %s\n", lp_wins_partners())); global_wins_table[0][0].address=*iface_n_ip(0); global_wins_table[0][0].max_version=0; global_wins_table[0][0].min_version=0; global_wins_table[0][0].type=0; while (partner[j]!=NULL) { DEBUG(3,("init_wins_partner_table, adding partner: %s\n", partner[j])); global_wins_table[0][i].address=*interpret_addr2(partner[j]); global_wins_table[0][i].max_version=0; global_wins_table[0][i].min_version=0; global_wins_table[0][i].type=0; global_wins_table[0][i].last_pull=0; global_wins_table[0][i].last_push=0; i++; j++; } for (k=1; kmax_version=((SMB_BIG_UINT)tdb_fetch_int32(tdb, INFO_ID_HIGH))<<32 | (SMB_BIG_UINT)tdb_fetch_int32(tdb, INFO_ID_LOW); tdb_close(tdb); } /**************************************************************************** send the list of wins server we know. ****************************************************************************/ static void send_version_number_map_table(GENERIC_PACKET *q, GENERIC_PACKET *r) { int i; int s_ctx=get_server_assoc(q->header.assoc_ctx); if (s_ctx==0) { DEBUG(5, ("send_version_number_map_table: request for a partner not in our table\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } /* * return an array of wins servers, we are partner with. * each entry contains the IP address and the version info * version: ID of the last entry we've got */ /* the first wins server must be self */ /* * get our last ID from the wins database * it can have been updated since last read * as nmbd got registration/release. */ get_our_last_id(&global_wins_table[0][0]); r->rep.avmt_rep.wins_owner=(WINS_OWNER *)talloc(mem_ctx, partner_count*sizeof(WINS_OWNER)); if (r->rep.avmt_rep.wins_owner==NULL) { stop_packet(q, r, STOP_REASON_USER_REASON); return; } DEBUG(5,("send_version_number_map_table: partner_count: %d\n", partner_count)); for (i=0; i %s, \n", i, inet_ntoa(global_wins_table[0][i].address))); r->rep.avmt_rep.wins_owner[i]=global_wins_table[0][i]; } r->rep.msg_type=1; r->rep.avmt_rep.partner_count=partner_count; r->rep.avmt_rep.initiating_wins_server.s_addr=0; /* blatant lie, NT4/w2K do the same ! */ fill_header(r, OPCODE_NON_NBT, s_ctx, MESSAGE_TYPE_REPLICATE); } /**************************************************************************** for a given partner, ask it to send entries we don't have. ****************************************************************************/ static BOOL check_partners_and_send_entries(GENERIC_PACKET *q, GENERIC_PACKET *r, int partner) { int server; int other; SMB_BIG_UINT temp; SMB_BIG_UINT current; /* * we check if our partner has more records than us. * we need to check more than our direct partners as * we can have this case: * us: A, partners: B,C, indirect partner: D * A<->B, A<->C, B<->D, C<->D * * So if we're talking to B, we need to check if between * B and C, which one have more records about D. * and also check if we don't already have the records. */ /* check all servers even indirect */ for (server=1; global_wins_table[0][server].address.s_addr!=0; server++) { current = global_wins_table[partner][server].max_version; temp=0; for (other=1; other temp) temp=global_wins_table[other][server].max_version; } if (current >= temp && current > global_wins_table[0][server].max_version) { /* * it has more records than every body else and more than us, * ask it the difference between what we have and what it has */ fill_header(r, OPCODE_NON_NBT, get_server_assoc(q->header.assoc_ctx), MESSAGE_TYPE_REPLICATE); r->rep.msg_type=MESSAGE_REP_SEND_ENTRIES_REQUEST; r->rep.se_rq.wins_owner.address=global_wins_table[partner][server].address; r->rep.se_rq.wins_owner.max_version=global_wins_table[partner][server].max_version; r->rep.se_rq.wins_owner.min_version=global_wins_table[0][server].max_version; r->rep.se_rq.wins_owner.type=0; write_server_assoc_table(q->header.assoc_ctx, global_wins_table[0][partner].address, global_wins_table[partner][server].address); /* * and we update our version for this server * as we can't use the IDs returned in the send_entries function * the max ID can be larger than the largest ID returned */ global_wins_table[0][server].max_version=global_wins_table[partner][server].max_version; return True; } } return False; } /**************************************************************************** receive the list of wins server we know. ****************************************************************************/ static void receive_version_number_map_table(GENERIC_PACKET *q, GENERIC_PACKET *r) { fstring peer; struct in_addr addr; int i,j,k,l; int s_ctx=get_server_assoc(q->header.assoc_ctx); if (s_ctx==0) { DEBUG(5, ("receive_version_number_map_table: request for a partner not in our table\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } fstrcpy(peer,get_socket_addr(q->fd)); addr=*interpret_addr2(peer); get_our_last_id(&global_wins_table[0][0]); DEBUG(5,("receive_version_number_map_table: received a map of %d server from: %s\n", q->rep.avmt_rep.partner_count ,inet_ntoa(q->rep.avmt_rep.initiating_wins_server))); DEBUG(5,("real peer is: %s\n", peer)); for (i=0; global_wins_table[0][i].address.s_addr!=addr.s_addr && irep.avmt_rep.partner_count;j++) { /* * search if we already have this entry or if it's a new one * it can be a new one in case of propagation */ for (k=0; global_wins_table[0][k].address.s_addr!=0 && global_wins_table[0][k].address.s_addr!=q->rep.avmt_rep.wins_owner[j].address.s_addr; k++); global_wins_table[i][k].address.s_addr=q->rep.avmt_rep.wins_owner[j].address.s_addr; global_wins_table[i][k].max_version=q->rep.avmt_rep.wins_owner[j].max_version; global_wins_table[i][k].min_version=q->rep.avmt_rep.wins_owner[j].min_version; global_wins_table[i][k].type=q->rep.avmt_rep.wins_owner[j].type; /* * in case it's a new one, rewrite the address for all the partner * to reserve the slot. */ for(l=0; lrep.avmt_rep.wins_owner[j].address.s_addr; } dump_global_table(); /* * if this server have newer records than what we have * for several wins servers, we need to ask it. * Alas a send entry request is only on one server. * So in the send entry reply, we'll ask for the next server if required. */ if (check_partners_and_send_entries(q, r, i)) return; /* it doesn't have more entries than us */ stop_packet(q, r, STOP_REASON_USER_REASON); } /**************************************************************************** add an entry to the wins list we'll send. ****************************************************************************/ static BOOL add_record_to_winsname(WINS_NAME **wins_name, int *max_names, char *name, int type, int wins_flags, int id, struct in_addr *ip_list, int num_ips) { WINS_NAME *temp_list; int i; int current=*max_names; temp_list=talloc_realloc(mem_ctx, *wins_name, (current+1)*sizeof(WINS_NAME)); if (temp_list==NULL) return False; temp_list[current].name_len=0x11; safe_strcpy(temp_list[current].name, name, 15); temp_list[current].type=type; temp_list[current].empty=0; temp_list[current].name_flag=wins_flags; if ( (wins_flags&0x03) == 1 || (wins_flags&0x03)==2) temp_list[current].group_flag=0x01000000; else temp_list[current].group_flag=0x00000000; temp_list[current].id=id; temp_list[current].owner.s_addr=ip_list[0].s_addr; if (temp_list[current].name_flag & 2) { temp_list[current].num_ip=num_ips; temp_list[current].others=(struct in_addr *)talloc(mem_ctx, sizeof(struct in_addr)*num_ips); if (temp_list[current].others==NULL) return False; for (i=0; iheader.assoc_ctx); int num_interfaces = iface_count(); if (s_ctx==0) { DEBUG(1, ("send_entry_request: request for a partner not in our table\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } wins_owner=&q->rep.se_rq.wins_owner; r->rep.se_rp.wins_name=NULL; DEBUG(3,("send_entry_request: we have been asked to send the list of wins records\n")); DEBUGADD(3,("owned by: %s and between min: %d and max: %d\n", inet_ntoa(wins_owner->address), (int)wins_owner->min_version, (int)wins_owner->max_version)); /* * if we are asked to send records owned by us * we overwrite the wins ip with 0.0.0.0 * to make it easy in case of multihomed */ for (i=0; iaddress, *iface_n_ip(i))) { wins_owner->address=*interpret_addr2("0.0.0.0"); break; } tdb = tdb_open_log(lock_path(WINS_LIST), 0, TDB_DEFAULT, O_RDONLY, 0600); if (!tdb) { DEBUG(2,("send_entry_request: Can't open wins database file %s. Error was %s\n", WINS_LIST, strerror(errno) )); return; } for (kbuf = tdb_firstkey(tdb); kbuf.dptr; newkey = tdb_nextkey(tdb, kbuf), safe_free(kbuf.dptr), kbuf=newkey) { pstring name_type, name, ip_str; char *p; int type = 0; int nb_flags; int ttl; unsigned int num_ips; int low, high; SMB_BIG_UINT version; struct in_addr wins_ip; struct in_addr *ip_list; int wins_flags; int len; if (strncmp(kbuf.dptr, ENTRY_PREFIX, strlen(ENTRY_PREFIX)) != 0) continue; dbuf = tdb_fetch(tdb, kbuf); if (!dbuf.dptr) continue; fstrcpy(name_type, kbuf.dptr+strlen(ENTRY_PREFIX)); pstrcpy(name, name_type); if((p = strchr(name,'#')) != NULL) { *p = 0; sscanf(p+1,"%x",&type); } len = tdb_unpack(dbuf.dptr, dbuf.dsize, "dddfddd", &nb_flags, &high, &low, ip_str, &ttl, &num_ips, &wins_flags); wins_ip=*interpret_addr2(ip_str); /* Allocate the space for the ip_list. */ if((ip_list = (struct in_addr *)talloc(mem_ctx, num_ips * sizeof(struct in_addr))) == NULL) { DEBUG(0,("initialise_wins: talloc fail !\n")); return; } for (i = 0; i < num_ips; i++) { len += tdb_unpack(dbuf.dptr+len, dbuf.dsize-len, "f", ip_str); ip_list[i] = *interpret_addr2(ip_str); } /* add all entries that have 60 seconds or more to live */ if ((ttl - 60) > time_now || ttl == PERMANENT_TTL) { if(ttl != PERMANENT_TTL) ttl -= time_now; DEBUG( 4, ("send_entry_request: add name: %s#%02x ttl = %d first IP %s flags = %2x\n", name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); /* add the record to the list to send */ version=((SMB_BIG_UINT)high)<<32 | low; if (wins_owner->min_version<=version && wins_owner->max_version>=version && wins_owner->address.s_addr==wins_ip.s_addr) { if(!add_record_to_winsname(&r->rep.se_rp.wins_name, &max_names, name, type, wins_flags, version, ip_list, num_ips)) return; max_names++; } } else { DEBUG(4, ("send_entry_request: not adding name (ttl problem) %s#%02x ttl = %d first IP %s flags = %2x\n", name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); } } tdb_close(tdb); DEBUG(4,("send_entry_request, sending %d records\n", max_names)); fill_header(r, OPCODE_NON_NBT, s_ctx, MESSAGE_TYPE_REPLICATE); r->rep.msg_type=MESSAGE_REP_SEND_ENTRIES_REPLY; /* reply */ r->rep.se_rp.max_names=max_names; } /**************************************************************************** . ****************************************************************************/ static void update_notify_request(GENERIC_PACKET *q, GENERIC_PACKET *r) { int i,j,k,l; UPDATE_NOTIFY_REQUEST *u; int s_ctx=get_server_assoc(q->header.assoc_ctx); if (s_ctx==0) { DEBUG(4, ("update_notify_request: request for a partner not in our table\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } u=&q->rep.un_rq; /* check if we already have the range of records */ DEBUG(5,("update_notify_request: wins server: %s offered this list of %d records:\n", inet_ntoa(u->initiating_wins_server), u->partner_count)); get_our_last_id(&global_wins_table[0][0]); for (i=0; iinitiating_wins_server.s_addr) { DEBUG(5,("update_notify_request: found initiator at index %d\n", i)); break; } } /* * some explanation is required, before someone say it's crap. * * let's take an example, we have 2 wins partners, we already now * that our max id is 10, partner 1 ID is 20 and partner 2 ID is 30 * the array looks like: * * 0 1 2 * 0 10 20 30 * 1 * 2 * * we receive an update from partner 2 saying he has: 1:15, 2:40, 3:50 * we must enlarge the array to add partner 3, it will look like: * * 0 1 2 3 * 0 10 20 30 * 1 * 2 15 40 50 * * now we know, we should pull from partner 2, the records 30->40 of 2 and 0->50 of 3. * once the pull will be over, our table will look like: * * 0 1 2 3 * 0 10 20 40 50 * 1 * 2 15 40 50 * * */ for (j=0; jpartner_count;j++) { /* * search if we already have this entry or if it's a new one * it can be a new one in case of propagation */ for (k=0; global_wins_table[0][k].address.s_addr!=0 && global_wins_table[0][k].address.s_addr!=u->wins_owner[j].address.s_addr; k++); global_wins_table[i][k].address.s_addr=u->wins_owner[j].address.s_addr; global_wins_table[i][k].max_version=u->wins_owner[j].max_version; global_wins_table[i][k].min_version=u->wins_owner[j].min_version; global_wins_table[i][k].type=u->wins_owner[j].type; /* * in case it's a new one, rewrite the address for all the partner * to reserve the slot. */ for(l=0; lwins_owner[j].address.s_addr; } dump_global_table(); stop_packet(q, r, STOP_REASON_USER_REASON); } /**************************************************************************** . ****************************************************************************/ static void send_entry_reply(GENERIC_PACKET *q, GENERIC_PACKET *r) { int i,j,k; struct in_addr partner, server; pid_t pid; int s_ctx=get_server_assoc(q->header.assoc_ctx); WINS_RECORD record; if (s_ctx==0) { DEBUG(1, ("send_entry_reply: request for a partner not in our table\n")); stop_packet(q, r, STOP_REASON_USER_REASON); return; } DEBUG(5,("send_entry_reply:got %d new records\n", q->rep.se_rp.max_names)); /* we got records from a wins partner but that can be from another wins server */ /* hopefully we track that */ /* and the only doc available from MS is wrong ! */ get_server_assoc_table(q->header.assoc_ctx, &partner, &server); for (j=0; global_wins_table[0][j].address.s_addr!=0; j++) { if (global_wins_table[0][j].address.s_addr==server.s_addr) { DEBUG(5,("send_entry_reply: found server at index %d\n", j)); break; } } pid = pidfile_pid("nmbd"); if (pid == 0) { DEBUG(0,("send_entry_reply: Can't find pid for nmbd\n")); return; } for (k=0; krep.se_rp.max_names; k++) { DEBUG(5,("send_entry_reply: %s<%02x> %d\n", q->rep.se_rp.wins_name[k].name, q->rep.se_rp.wins_name[k].type, (int)q->rep.se_rp.wins_name[k].id)); safe_strcpy(record.name, q->rep.se_rp.wins_name[k].name, 16); record.type=q->rep.se_rp.wins_name[k].type; record.id=q->rep.se_rp.wins_name[k].id; record.wins_flags=q->rep.se_rp.wins_name[k].name_flag&0x00ff; record.num_ips=q->rep.se_rp.wins_name[k].num_ip; record.wins_ip.s_addr=server.s_addr; if (record.num_ips==1) record.ip[0]=q->rep.se_rp.wins_name[k].owner; else for (i=0; irep.se_rp.wins_name[k].others[i]; record.nb_flags=0; if (record.wins_flags&WINS_NGROUP || record.wins_flags&WINS_SGROUP) record.nb_flags|=NB_GROUP; if (record.wins_flags&WINS_ACTIVE) record.nb_flags|=NB_ACTIVE; record.nb_flags|=record.wins_flags&WINS_HNODE; message_send_pid(pid, MSG_WINS_NEW_ENTRY, &record, sizeof(record), False); } dump_global_table(); /* * we got some entries, * ask the partner to send us the map table again * to get the other servers entries. * * we're getting the map table 1 time more than really * required. We could remove that call, but that * would complexify the code. I prefer this trade-of. */ fill_header(r, OPCODE_NON_NBT, s_ctx, MESSAGE_TYPE_REPLICATE); r->rep.msg_type=MESSAGE_REP_ADD_VERSION_REQUEST; } /**************************************************************************** decode the replication message and reply. ****************************************************************************/ static void replicate(GENERIC_PACKET *q, GENERIC_PACKET *r) { switch (q->rep.msg_type) { case 0: /* add version number map table request */ send_version_number_map_table(q, r); break; case 1: receive_version_number_map_table(q, r); break; case 2: /* send entry request */ send_entry_request(q, r); break; case 3: /* send entry reply */ send_entry_reply(q, r); break; case 4: /* update notification request */ update_notify_request(q, r); break; } } /**************************************************************************** do a switch on the message type, and return the response size ****************************************************************************/ static BOOL switch_message(GENERIC_PACKET *q, GENERIC_PACKET *r) { switch (q->header.mess_type) { case 0: /* Start association type */ start_assoc_process(q, r); return True; break; case 1: /* start association reply */ start_assoc_reply(q, r); return True; break; case 2: /* stop association message */ return False; break; case 3: /* replication message */ replicate(q, r); return True; break; } return False; } /**************************************************************************** construct a reply to the incoming packet ****************************************************************************/ void construct_reply(struct wins_packet_struct *p) { GENERIC_PACKET r; struct BUFFER buffer; buffer.buffer=NULL; buffer.offset=0; buffer.length=0; DEBUG(5,("dump: received packet\n")); dump_generic_packet(p->packet); /* Verify if the request we got is from a listed partner */ if (!check_partner(p->packet->header.assoc_ctx)) { fstring peer; struct in_addr addr; int i; fstrcpy(peer,get_socket_addr(p->fd)); addr=*interpret_addr2(peer); for (i=1; ipacket, &r, STOP_REASON_AUTH_FAILED); p->stop_packet=True; encode_generic_packet(&buffer, &r); if (!send_smb(p->fd, buffer.buffer)) exit_server("process_smb: send_smb failed."); return; } } if (switch_message(p->packet, &r)) { encode_generic_packet(&buffer, &r); DEBUG(5,("dump: sending packet\n")); dump_generic_packet(&r); if(buffer.offset > 0) { if (!send_smb(p->fd, buffer.buffer)) exit_server("process_smb: send_smb failed."); } } /* if we got a stop assoc or if we send a stop assoc, close the fd after */ if (p->packet->header.mess_type==MESSAGE_TYPE_STOP_ASSOC || r.header.mess_type==MESSAGE_TYPE_STOP_ASSOC) { remove_partner(p->packet->header.assoc_ctx); p->stop_packet=True; } } /**************************************************************************** contact periodically our wins partner to do a pull replication ****************************************************************************/ void run_pull_replication(time_t t) { /* we pull every 30 minutes to query about new records*/ int i, s; struct BUFFER buffer; GENERIC_PACKET p; buffer.buffer=NULL; buffer.offset=0; buffer.length=0; for (i=1; i 0) { if (!send_smb(s, buffer.buffer)) exit_server("run_pull_replication: send_smb failed."); } add_fd_to_sock_array(s); FD_SET(s, listen_set); /* add ourself as a client */ add_partner((int)t, 0, True, False); } } } /**************************************************************************** contact periodically our wins partner to do a push replication ****************************************************************************/ void run_push_replication(time_t t) { /* we push every 30 minutes or 25 new entries */ int i, s; struct BUFFER buffer; GENERIC_PACKET p; buffer.buffer=NULL; buffer.offset=0; buffer.length=0; for (i=1; i 0) { if (!send_smb(s, buffer.buffer)) exit_server("run_push_replication: send_smb failed."); } add_fd_to_sock_array(s); FD_SET(s, listen_set); /* add ourself as a client */ add_partner((int)t, 0, False, True); } } }