/* Copyright 1998 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * * NBIP.C -- NetBIOS-over-IP implementation * * 13-12-95 INH Original * 30-12-96 INH Completed * * * This file is an implementation of the name service and session * service functions for a B (broadcast) node, as defined in * RFC1001-1002. It does the following things: * * - locates remote NetBIOS names (i.e. finds IP addresses for them) * - maintains & defends local NetBIOS names * - establishes sessions with remote hosts & sends & receives data * using these sessions. * * We also do a few M-node functions, if a name server is specified * by giving its IP address in LanMan$NameServer. This consists * largely of sending name registrations, releases, find requests * and status requests to the name server as well as to broadcast. */ /* Standard includes */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include "kernel.h" #include "os.h" #include "swis.h" /* Our includes */ #include "stdtypes.h" #include "lanman.h" #include "buflib.h" #include "tcpip.h" #include "netbios.h" #include "xlate.h" /* For string functions */ #include "stats.h" #include "lmvars.h" #include "nbip.h" #include "LanMan_MH.h" #define EXPORT static /* Our definitions ------------------ */ #define EventV 0x10 #define INVALID_SOCKET (-1) #define MAX_NAMES (MAX_DRIVES+4) #define MAX_SESSIONS (MAX_DRIVES+4) #define RECV_TIMEOUT 3000 /* Wait time for general receives, in .01s */ /* Time to live values */ #define FIND_TTL 300000 #define REGISTER_TTL 300000 /* Definitions for status field */ #define NAME_FREE 0 #define LCL_AWAIT_ADD 1 /* Local name awaiting registration */ #define LCL_NAME_OK 2 /* Valid local name */ #define LCL_IN_CONFLICT 3 /* Local name in conflict */ #define LCL_AWAIT_RLSE 4 /* Local name about to be released */ #define RMT_AWAIT_FIND 5 /* Remote name currently being searched for */ #define RMT_FOUND 6 /* Remote name, found OK */ #define RMT_CACHED 7 /* Remote name which may be dropped from table */ #define RMT_STATUS_Q 8 /* Remote name doing a status query */ /* ---------------- */ struct status_resp /* Structure used to process STATUS_RESPONSE */ { int nt_search; /* Name-type to respond to */ struct FindName_res *pRes; /* Pointer to result buf */ int spaces_left; /* Space left in result buf */ }; /* ----------- */ /* We keep both our own names and some remote names in the table, to act as a name-to-IP-address cache. Remote names can (and will) be chucked out without warning. */ typedef struct { int status; NETNAME nn; /* 16-byte NetBIOS name */ int nbflags; /* Group status & node type */ uint TTL_StartTime; /* Time at which a valid 'time to live' was received */ uint TTL_Interval; /* Time to live, in centiseconds */ struct in_addr IPaddress; /* IP address of this name */ struct status_resp *pStatResp; /* Pointer to result buf */ } NAME_ENTRY; /* ----------- */ /* Globals ============================================================= */ static NAME_ENTRY NB_NameTable[MAX_NAMES]; static int NB_FirstFreeName; static struct in_addr NB_IPAddress; #define SCOPE_ID_MAX 80 static BYTE NB_ScopeID[SCOPE_ID_MAX]; /* NetBIOS Scope ID, in compressed format */ static int NB_ScopeIDlen; /* Length, including terminating zero */ static BYTE DatagramBuf[576]; static int NBNS_Socket = INVALID_SOCKET; /* Socket for NBNS server on this machine */ static int NBNS_RequestCount; static struct sockaddr *NBNS_Broadcast; /* Ready-made broadcast address */ static struct sockaddr *NBNS_NameServer = NULL; /* IP address of name server */ /* NetBIOS name basics ==================================== */ #ifdef DEBUG static char *debug_name_buf ( void *pnn_v, char *buf ) { BYTE *pnn = pnn_v; char lbuf[16]; memcpy(lbuf, pnn, 16); lbuf[15] = '\0'; sprintf(buf, "<%s[%02X]>", lbuf, pnn[15]); return buf; } static char *debug_name ( void *pnn ) { static char lclbuf[32]; return debug_name_buf(pnn, lclbuf); } static void debug_scope ( BYTE *src ) { int i; if ( *src == 0 ) return; while ( *src != 0 ) { i = *src; if ( i >= 0xC0 ) { printf("->pointer"); break; } if ( i >= 0x40 ) { printf("->invalid"); break; } printf("."); src++; while ( i-- > 0 ) printf("%c", *src++); } } #else #define debug_name(a) #define debug_scope(a) #endif static void show_scope ( BYTE *src ) { int i; if ( *src == 0 ) { printf(" No NetBIOS scope ID set\n"); return; } printf(" NetBIOS scope ID is '"); while ( *src != 0 ) { i = *src; if ( i >= 0x40 ) { printf("(oops)"); break; } src++; while ( i-- > 0 ) printf("%c", *src++); if ( *src != 0 ) printf("."); } printf("'\n"); } /* NETNAME management ==================================== */ EXPORT bool _NB_MatchName ( NETNAME *nn1, NETNAME *nn2 ) { return (bool) ( nn1->c4[0] == nn2->c4[0] && nn1->c4[1] == nn2->c4[1] && nn1->c4[2] == nn2->c4[2] && nn1->c4[3] == nn2->c4[3] ); } /* --------------------- */ EXPORT err_t _NB_FormatName ( nametype_t nt, char *name, NETNAME *res ) { int i; for ( i=0; i<15; i++) { if ( name[i] <= 0x20 ) break; if (name[i] == '\xA0') res->b[i] = ' '; else res->b[i] = toupper(name[i]); } while (i<15) res->b[i++] = 0x20; res->b[15] = nt; return OK; } /* --------------------- */ EXPORT nametype_t _NB_DecodeName ( NETNAME *pnn, char *buf ) { BYTE *p = pnn->b; char *last_nonsp = buf; int i; for (i=0; i<15 && *p >= 0x20; ++i) *buf++ = *p++; *buf = 0; while (--buf > last_nonsp) { if (*buf == ' ') *buf = 0; } return (nametype_t) (pnn->b[15]); } /* -------------------- */ static uint NB_GetTime (void) { uint tick; /* Centisecond tick */ (void) _swix(OS_ReadMonotonicTime, _OUT(0), &tick); return tick; } /* NAME_ENTRY management ============================= */ static void FreeNameEntry ( NAME_ENTRY *p ) { int n; p->status = NAME_FREE; n = p - NB_NameTable; if (NB_FirstFreeName > n) NB_FirstFreeName = n; } /* ------------------ */ static void CheckExpiredNames ( void ) { int i; NAME_ENTRY *pNE; pNE = NB_NameTable; for ( i=0; i < MAX_NAMES; i++ ) { if ( pNE->status == RMT_CACHED && (NB_GetTime() - pNE->TTL_StartTime) > pNE->TTL_Interval ) { FreeNameEntry(pNE); } } } /* ------------------ */ static NAME_ENTRY *AllocNameEntry (int stat) { int i; CheckExpiredNames(); /* Make some space if possible */ for ( i=NB_FirstFreeName; i < MAX_NAMES; i++) if ( NB_NameTable[i].status == NAME_FREE ) { NB_NameTable[i].status = stat; NB_FirstFreeName = i+1; return &NB_NameTable[i]; } /* No more space. Chuck out the first cached remote name we find */ for ( i=0; i < MAX_NAMES; i++ ) if ( NB_NameTable[i].status == RMT_CACHED ) { NB_FirstFreeName = i+1; NB_NameTable[i].status = stat; return &NB_NameTable[i]; } return NULL; } /* --------------- */ static NAME_ENTRY *FindNameEntry ( NETNAME *pNN ) { int i; NAME_ENTRY *pNE; pNE = NB_NameTable; for ( i=0; i < MAX_NAMES; i++ ) { if ( pNE->status != NAME_FREE ) { if ( _NB_MatchName ( pNN, &(pNE->nn) ) ) return pNE; } pNE++; } return NULL; } /* NBNS Subroutines ================================= */ /* Struct in_addr's always contain addresses in network byte order, so the integer value of the s_addr field, as perceived by C, will vary between big-endian and little-endian implementations. Hence, we have to phrase our routines in such a way that they are independent of the endian-ness (preferably by not treating struct in_addr as an integer value at all). The union below helps */ union in_addr_byte { struct in_addr ina; BYTE nb[4]; }; /* ------------------- */ static struct sockaddr_in NameServer_sin; static void SetupNameServer ( void ) { if (NBNS_NameServer == NULL) { char *ns_addr = getenv("LanMan$NameServer"); union in_addr_byte iab; int b0, b1, b2, b3; if ((ns_addr == NULL) || (sscanf(ns_addr, "%d.%d.%d.%d",&b0, &b1, &b2, &b3) != 4)) { b0 = RdCMOS(NBNSIPCMOS0); b1 = RdCMOS(NBNSIPCMOS1); b2 = RdCMOS(NBNSIPCMOS2); b3 = RdCMOS(NBNSIPCMOS3); } if ( ((b0 != 0) && (b0 != 127) && (b0 <= 223)) && ((b3 != 0) && (b3 != 255)) ) { iab.nb[0]=b0, iab.nb[1] = b1, iab.nb[2] = b2, iab.nb[3] = b3; NameServer_sin.sin_family = AF_INET; NameServer_sin.sin_port = htons(NBNS_PORT); NameServer_sin.sin_addr = iab.ina; NBNS_NameServer = (struct sockaddr *) &NameServer_sin; } } } /* ----------------- */ /* Given a scope ID (e.g. "netbios.ibm.com"), converts this to the NBNS compacted format. */ static void SetScopeID ( char *src ) { int i, l; char *p; if ( src==NULL) src = ""; NB_ScopeIDlen=0; /* Get number of characters to next dot, or if no more dots, to end of string. If it's too long, it gets rudely truncated! */ do { p = strchr(src, '.'); /* Find '.' */ if (p == NULL) /* Last bit */ l = strlen(src); else l = p-src; /* Length to dot */ if ( l == 0 /* No more */ || l >= 64 /* Illegal */ || NB_ScopeIDlen+l+1 >= SCOPE_ID_MAX ) break; NB_ScopeID [NB_ScopeIDlen++] = l; for ( i=0; i<l; i++ ) NB_ScopeID [NB_ScopeIDlen++] = toupper(*src++); src++; /* Skip 'dot' if present */ } while ( p != NULL ); NB_ScopeID [NB_ScopeIDlen] = 0; NB_ScopeIDlen++; } /* ---------------------------------------- */ static uint GetLong( BYTE *p) { return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } /* ---------- */ static BYTE *PutLong ( BYTE *ptr, uint v ) { *ptr++ = (v >> 24); *ptr++ = (v >> 16); *ptr++ = (v >> 8); *ptr++ = v; return ptr; } /* ---------- */ static struct in_addr GetIPAddr( BYTE *p) { union in_addr_byte ib; ib.nb[0] = p[0]; ib.nb[1] = p[1]; ib.nb[2] = p[2]; ib.nb[3] = p[3]; return ib.ina; } /* ---------- */ static BYTE *PutIPAddr ( BYTE *ptr, struct in_addr ia ) { union in_addr_byte ib; ib.ina = ia; *ptr++ = ib.nb[0]; *ptr++ = ib.nb[1]; *ptr++ = ib.nb[2]; *ptr++ = ib.nb[3]; return ptr; } /* ------------- */ static uint GetShort( BYTE *p) { return (p[0] << 8) | p[1]; } /* ------------- */ static BYTE *PutShort ( BYTE *ptr, uint v ) { *ptr++ = (v >> 8); *ptr++ = v; return ptr; } /* --------------- */ static BYTE *PutNetname ( BYTE *ptr, NETNAME *pNN ) { /* Put NetBIOS name in a compressed ASCII-coded representation */ int i,c; BYTE *p; p = pNN->b; /* Pointer to first part of name */ *ptr++ = 32; /* This is always 32 characters */ for (i=0; i<16; i++) { c = *p++; *ptr++ = 'A' + ((c >> 4) & 0xF); *ptr++ = 'A' + (c & 0xF); } memcpy ( ptr, NB_ScopeID, NB_ScopeIDlen ); return ptr + NB_ScopeIDlen; } /* --------------- */ static BYTE *PutNetnameIndirect ( BYTE *ptr, BYTE *name, BYTE *buf_start ) { int c = name-buf_start; if ( c > 0 && c <= 0x3FFF ) ptr=PutShort(ptr, c|0xC000 ); else { STAT(STA_SERIOUS_BARF); *ptr++ = 0; } return ptr; } /* -------------- */ static BYTE *PutResourceInfo ( BYTE *p, NAME_ENTRY *pNE, uint ttl ) { p = PutLong ( p, ttl ); p = PutShort ( p, 0x0006 ); /* 6 extra data bytes */ p = PutShort ( p, pNE->nbflags ); return PutIPAddr ( p, pNE->IPaddress ); } /* -------------- */ static BYTE *CreateNBNSheader(BYTE *ptr, int opcode, int trn_ID ) { /* Creates an NBNS header */ ptr = PutShort(ptr, trn_ID); /* Create new request ID */ ptr = PutShort(ptr, opcode & 0xFFFF); ptr = PutShort(ptr, (opcode & HAS_QUERY) ? 1 : 0); ptr = PutShort(ptr, (opcode & HAS_ANSWER) ? 1 : 0); ptr = PutShort(ptr, (opcode & HAS_AUTHORITY) ? 1 : 0); return PutShort(ptr, (opcode & HAS_ADDITIONAL) ? 1 : 0); } /* Transmit routines ============================================ */ static void SendDatagram ( struct sockaddr *pDst, BYTE *start, BYTE *end ) { dprintf((__FILE__, "SendDatagram ID:&%04x to %s\n", GetShort(start), inet_ntoa(((struct sockaddr_in *) pDst)->sin_addr))); sendto ( NBNS_Socket, start, end-start, 0, pDst, sizeof(struct sockaddr_in) ); } /* --------------------- */ static void SendRegisterRequest ( struct sockaddr *pDst, NAME_ENTRY *pNE ) { BYTE *p, *name_p; p = CreateNBNSheader(DatagramBuf, NAME_REG_REQUEST, ++NBNS_RequestCount); name_p = p; p = PutNetname( p, &(pNE->nn) ); /* Query section */ p = PutLong (p, INET_NAME_TAG ); /* Resource section, using a name pointer */ p = PutNetnameIndirect ( p, name_p, DatagramBuf ); p = PutLong (p, INET_NAME_TAG ); p = PutResourceInfo (p, pNE, REGISTER_TTL ); SendDatagram ( pDst, DatagramBuf, p ); } /* --------------------- */ static void SendReleaseRequest ( struct sockaddr *pDst, NAME_ENTRY *pNE ) { BYTE *p, *name_p; p = CreateNBNSheader(DatagramBuf, NAME_RLSE_REQUEST, ++NBNS_RequestCount); name_p = p; p = PutNetname( p, &(pNE->nn) ); /* Query section */ p = PutLong (p, INET_NAME_TAG ); /* Resource section, using a name pointer */ p = PutNetnameIndirect ( p, name_p, DatagramBuf ); p = PutLong (p, INET_NAME_TAG ); p = PutResourceInfo (p, pNE, 0 ); SendDatagram ( pDst, DatagramBuf, p ); } /* --------------------- */ static void SendFindRequest ( struct sockaddr *pDst, NAME_ENTRY *pNE ) { BYTE *p; p = CreateNBNSheader(DatagramBuf, NAME_FIND_REQUEST, ++NBNS_RequestCount); p = PutNetname( p, &(pNE->nn) ); /* Query section */ p = PutLong (p, INET_NAME_TAG ); #ifdef DEBUG { struct sockaddr_in *sin = (void *) pDst; debug2("SendFindRequest (%s)-> [%s]\n", debug_name(&(pNE->nn)), inet_ntoa(sin->sin_addr)); } #endif SendDatagram ( pDst, DatagramBuf, p ); } /* --------------------- */ static void SendStatusRequest ( struct sockaddr *pDst, NAME_ENTRY *pNE ) { BYTE *p; p = CreateNBNSheader(DatagramBuf,NAME_STATUS_REQUEST,++NBNS_RequestCount); p = PutNetname( p, &(pNE->nn) ); /* Query section */ p = PutLong (p, INET_STATUS_TAG ); dprintf((__FILE__, "SendStatusRequest (%s)\n", debug_name(&(pNE->nn)))); SendDatagram ( pDst, DatagramBuf, p ); } /* Name Decoding subroutines ==================================== */ /* Read RFC-1001 for more info on the bizarre network name representation scheme. */ static bool ValidateName( BYTE *src, BYTE *buf_start, BYTE *buf_end ) { int c, loopy_count; loopy_count = 0; while( src >= buf_start && src < buf_end && loopy_count++ < 100 ) { c = *src; if ( c == 0 ) /* Reached the end without incident */ return true; else if ( c < 0x40 ) /* Ordinary name length segment */ src += (c+1); else if ( c >= 0xC0 ) /* Name pointer */ { if ( src+2 > buf_end ) return false; src = buf_start + (GetShort(src) & 0x3FFF); } else /* Invalid */ { debug1("Invalid byte %Xh in name\n", c ); return false; } } /* Get here if we go wrong somewhere e.g. name is truncated */ debug0("Invalid name - exceeded length\n"); return false; } /* -------------- */ static BYTE *FindNameEnd ( BYTE *src ) { int c; while ( c=src[0], c != 0 ) { if ( c >= 0xC0 ) /* Pointer */ return src+2; src += (c+1); } return src+1; } /* --------------------- */ /* ChasePointer is a subroutine for FindNBNSName. It keeps following a pointer in an encoded representation to find out where it starts. */ static BYTE *ChasePointer( BYTE *src, BYTE *buf_start ) { while( src[0] >= 0xC0 ) src = buf_start + (GetShort(src) & 0x3FFF); return src; } /* FindNBNSName decodes the encoded name representation in NBNS packets (dear Anne, why oh why...?) and searches the name table for a match. All names of interest are kept in the table, so this is the only place that name decoding needs to be done. It returns NULL if the name is badly-formed, not found in the table, or exceeds the end of the buffer as given by buf_end. It should be called only with names which have passed ValidateName(), otherwise all sorts may happen. */ static NAME_ENTRY *FindNBNSName ( BYTE *src, BYTE *buf_start) { NETNAME netname; BYTE *id; int i; /* Find first bit of name */ src=ChasePointer(src, buf_start); if ( src[0] != 32 ) /* Encoded NetBIOS name is always 32 characters */ return NULL; /* src points to the encoded NetBIOS name - decode it now */ src++; for (i=0; i<16; i++) { netname.b[i] = ( (src[0] -'A') << 4 ) + src[1] - 'A'; src+=2; } debug1("Name=%s", debug_name(&netname)); debug_scope (src); debug0("\n"); /* Now check NetBIOS scope ID */ id = NB_ScopeID; while (1) { src = ChasePointer(src, buf_start); i = src[0]; /* Length of this segment */ if ( i != id[0] ) /* Length mismatch */ return NULL; if ( i == 0 ) /* The end */ break; if ( memcmp (src+1, id+1, i) != 0 ) return NULL; src += i+1; id += i+1; } /* Now check name table */ return FindNameEntry ( &netname ); } /* ---------------- */ struct NBNS_packet { struct sockaddr from; int trn_id; /* Transaction ID */ int opcode; /* Opcode */ int n_query; /* No. of query records */ int n_answer; /* No. of answer records */ int n_auth; /* No. of authority records */ int n_add; /* No. of additional records */ BYTE *buf_start; /* Start of raw data */ BYTE *buf_end; /* Last byte of raw data plus one */ BYTE *record_ptr; /* Current record pointer, for GetNextRecord */ }; struct NBNS_resource_record { NAME_ENTRY *pName; int name_tag; int time_to_live; int data_length; BYTE *data_ptr; }; /* ------------------------------- */ #define GET_QUERY 0 #define GET_RESOURCE 1 static bool GetNBNSRecord ( struct NBNS_packet *pNBP, struct NBNS_resource_record *pDst, int flg ) { BYTE *src = pNBP->record_ptr; /* Name truncated or garbage? */ if ( !ValidateName(src, pNBP->buf_start, pNBP->buf_end) ) { debug0("Name truncated\n"); return false; } /* Is name known to us? */ pDst->pName = FindNBNSName ( src, pNBP->buf_start ); if ( pDst->pName == NULL ) debug0("Name unknown to us\n"); src = FindNameEnd ( src ); if ( flg & GET_RESOURCE ) { if ( src + 10 > pNBP->buf_end ) { debug0("Resource truncated\n"); return false; } pDst->name_tag = GetLong(src); pDst->time_to_live = GetLong(src+4); pDst->data_length = GetShort(src+8); src += 10; pDst->data_ptr = src; if ( src + pDst->data_length > pNBP->buf_end ) { /* Benign truncation - we give all the data that's available */ pDst->data_length = pNBP->buf_end - src; } src += pDst->data_length; } else { if ( src + 4 > pNBP->buf_end ) { debug0("Query truncated\n"); return false; } pDst->name_tag = GetLong(src); pDst->time_to_live = 0; pDst->data_length = 0; src += 4; } pNBP->record_ptr = src; return true; } /* Receive processing routines ======================================= */ static void NameFindRequest ( struct NBNS_packet *pNBP, struct NBNS_resource_record *pNBRR ) { NAME_ENTRY *pNE = pNBRR->pName; BYTE *p; if ( pNE->status == LCL_NAME_OK ) { p = CreateNBNSheader ( DatagramBuf, NAME_FIND_REPLY, pNBP->trn_id ); p = PutNetname ( p, &(pNE->nn) ); p = PutLong (p, INET_NAME_TAG ); p = PutResourceInfo(p, pNE, FIND_TTL); sendto ( NBNS_Socket, DatagramBuf, p-DatagramBuf, 0, &(pNBP->from), sizeof(pNBP->from) ); } } /* ---------------- */ static void NameRegisterRequest ( struct NBNS_packet *pNBP, struct NBNS_resource_record *pNBRR_query ) { NAME_ENTRY *pNE; struct NBNS_resource_record NBR_add; BYTE *p; pNE = pNBRR_query->pName; /* Get additional record, containing group status & IP address */ if ( /* Are all the parameters OK */ pNE->status != LCL_NAME_OK || pNBP->n_add < 1 || !GetNBNSRecord( pNBP, &NBR_add, GET_RESOURCE ) || NBR_add.data_length < 6 ) { return; } /* Are they both group names? If so, don't worry */ if ( (GetShort (NBR_add.data_ptr) & NBFLG_GROUP) != 0 && (pNE->nbflags & NBFLG_GROUP) != 0 ) { return; } /* If the IP addresses match, panic not */ if ( GetIPAddr(NBR_add.data_ptr+2).s_addr == pNE->IPaddress.s_addr ) { return; } /* Send a negative name registration response with our details */ p = CreateNBNSheader ( DatagramBuf, NAME_REG_REPLY | ST_ACT_ERR, pNBP->trn_id ); p = PutNetname ( p, &(pNE->nn) ); p = PutLong (p, INET_NAME_TAG ); p = PutResourceInfo(p, pNE, REGISTER_TTL); sendto ( NBNS_Socket, DatagramBuf, p-DatagramBuf, 0, &(pNBP->from), sizeof(pNBP->from) ); } /* ---------------------------- */ static void NameFindReply ( struct NBNS_packet *pNBP, struct NBNS_resource_record *pNBRR ) { NAME_ENTRY *pNE = pNBRR->pName; BYTE *dp = pNBRR->data_ptr; /* Are we waiting for a reply? Is this the one? */ if ( (pNBP->opcode & OPC_STATUS_MASK) == ST_OK && pNE->status == RMT_AWAIT_FIND && pNBRR->data_length >= 6 /* At least one entry in reply */ ) { debug0("NameFindReply - found\n"); pNE->status = RMT_FOUND; pNE->nbflags = GetShort(dp); pNE->IPaddress = GetIPAddr(dp+2); /* Process time-to-live in milliseconds */ pNE->TTL_StartTime = NB_GetTime(); if ( pNBRR->time_to_live > 0 ) pNE->TTL_Interval = pNBRR->time_to_live/10; else pNE->TTL_Interval = FIND_TTL/10; } } /* ---------------------------- */ static void NameStatusReply ( struct NBNS_packet *pNBP, struct NBNS_resource_record *pNBRR ) { struct status_resp *pSR; BYTE *dp; int n, flg; (void) pNBP; /* Don't use it for now */ /* Are we waiting for a status reply? */ dp = pNBRR->data_ptr; pSR = pNBRR->pName->pStatResp; if ( pSR == NULL || pNBRR->pName->status != RMT_STATUS_Q || pNBRR->data_length < 1 /* At least one byte for name */ ) { debug0("Puzzling status response\n"); return; } n = *dp++; /* Number of names returned */ if (n*18 >= pNBRR->data_length) { n = (pNBRR->data_length-1) / 18; } debug3("Status: %d names, type %Xh, %d spc\n", n, pSR->nt_search, pSR->spaces_left); ddumpbuf(__FILE__, dp, n*18, 0); while( pSR->spaces_left > 0 && n-- > 0 ) { #ifdef DEBUG char namebuf[32]; dprintf((__FILE__, "Found %s\n", debug_name_buf(dp, namebuf))); #endif /* dp points to a network name */ if ( pSR->nt_search == ANY_NAME_TYPE || pSR->nt_search == dp[15] ) { memcpy ( &(pSR->pRes->name), dp, 16 ); pSR->pRes->type = (nametype_t) (dp[15]); flg = GetShort(dp+16); pSR->pRes->flags = (flg & NBFLG_GROUP) ? FN_GROUPNAME : 0; pSR->spaces_left--; pSR->pRes++; } dp+=18; } while (n-- > 0) { debug1("Found but ignoring %s\n", debug_name( dp )); dp+=18; } } /* ---------------------------- */ #ifdef DEBUG static void debug_opc(struct NBNS_packet *p) { static const char *statuses[16] = { "OK", "FMT_ERR", "SRV_ERR", "NAM_ERR", "IMP_ERR", "RFS_ERR", "ACT_ERR", "CFT_ERR" }; static const char *opcodes[16] = { "FIND", "1", "2", "3", "4", "REGISTER", "RELEASE", "WACK", "REFRESH", "9", "10" }; static char flags[256]; const char *opcode, *status, *reply; reply = (p->opcode & OPC_REPLY) ? "Reply" : "Query"; *flags = '\0'; if (p->opcode & OPC_AUTHORITY) strcat(flags, "AUTH "); if (p->opcode & OPC_TRUNCATED) strcat(flags, "TRUNC "); if (p->opcode & OPC_REC_DESIRED) strcat(flags, "REC_DESIRED "); if (p->opcode & OPC_REC_AVAIL) strcat(flags, "REC_AVAIL "); if (p->opcode & OPC_BROADCAST) strcat(flags, "BROADCAST "); opcode = opcodes[(p->opcode & OPC_OPCODE_MASK) >> 11]; status = (p->opcode & OPC_REPLY) ? statuses[(p->opcode & OPC_STATUS_MASK)] : ""; dprintf((__FILE__, "* %s ID:&%04x %s %s\n", reply, p->trn_id, opcode?opcode:"<UNKNOWN>", status?status:"<UNKNOWN>")); dprintf((__FILE__, "n_query: %d, n_answer: %d, n_auth: %d, n_add: %d\n", p->n_query, p->n_answer, p->n_auth, p->n_add)); } #endif static void NBNS_ProcessDatagram ( struct sockaddr *pFrom, BYTE *buf, int len ) { struct NBNS_packet NBP; struct NBNS_resource_record NBRR; #ifdef DEBUG { struct sockaddr_in *from2 = (struct sockaddr_in *)pFrom; debug3("Datagram from inaddr %s, port %d, len %d\n", inet_ntoa(from2->sin_addr), ntohs(from2->sin_port), len ); } #endif if ( len < 12 ) /* Too short for header */ return; NBP.from = *pFrom; NBP.trn_id = GetShort(buf+0); /* Transaction ID */ NBP.opcode = GetShort(buf+2); /* Opcode, status & flags */ NBP.n_query = GetShort(buf+4); NBP.n_answer = GetShort(buf+6); NBP.n_auth = GetShort(buf+8); NBP.n_add = GetShort(buf+10); NBP.buf_start = buf; NBP.buf_end = buf+len; NBP.record_ptr = buf+12; #ifdef DEBUG debug_opc(&NBP); #endif if ( NBP.opcode & OPC_REPLY ) { /* Response to one of our questions, either a name registration request, or a name query request */ if ( NBP.n_answer >=1 && GetNBNSRecord( &NBP, &NBRR, GET_RESOURCE ) && NBRR.pName != NULL ) { if ( NBRR.name_tag == INET_NAME_TAG ) { switch ( NBP.opcode & OPC_OPCODE_MASK ) { case OPC_FIND: /* Name find reply */ NameFindReply ( &NBP, &NBRR ); break; case OPC_REGISTER: /* Name registration reply */ /* If this is a negative reply, & it's a local name, mark it as in-conflict */ if ((NBP.opcode & OPC_STATUS_MASK) != ST_OK) { if ( NBRR.pName->status == LCL_AWAIT_ADD || NBRR.pName->status == LCL_NAME_OK ) { NBRR.pName->status = LCL_IN_CONFLICT; } } break; default: debug0("Unknown reply\n"); break; } } else if ( NBRR.name_tag == INET_STATUS_TAG ) { if ( (NBP.opcode & OPC_OPCODE_MASK) == OPC_FIND && (NBP.opcode & OPC_STATUS_MASK) == ST_OK) { NameStatusReply ( &NBP, &NBRR ); } } } } else { /* Request to us from someone else */ if ( NBP.n_query >=1 && GetNBNSRecord( &NBP, &NBRR, GET_QUERY ) && NBRR.pName != NULL ) { if ( NBRR.name_tag == INET_NAME_TAG ) switch ( NBP.opcode & OPC_OPCODE_MASK ) { case OPC_FIND: /* Name find request */ NameFindRequest ( &NBP, &NBRR ); break; case OPC_REGISTER: /* Name registration request */ NameRegisterRequest ( &NBP, &NBRR ); break; case OPC_RELEASE: /* Name release request */ if ( NBRR.pName->status == RMT_CACHED ) FreeNameEntry ( NBRR.pName ); break; default: break; } } } } /* Event and callback functions ==================================== */ /* Under RISCOS, background processing is done by having a system event - the Internet event - which gets called every time a socket receives something (amongst other things). This lets us schedule a callback to pick up the data and process it. */ extern void EventFn (void); /* Provided by CMHG */ extern void NBIP_CallbackFn(void); /* EventFn() ------------------------------------------ */ static bool EventsClaimed=false; static bool CallbackSet = false; /* ---------------- */ int EventFn_handler ( _kernel_swi_regs *R, void *pw ) { (void) pw; if ( R->r[0] == Internet_Event && R->r[1] == 1 && /* ensure it was the data arrived event! */ R->r[2] == NBNS_Socket && NBNS_Socket != INVALID_SOCKET && !CallbackSet ) { STAT(STA_IP_EVENTS); CallbackSet = true; _swix(OS_AddCallBack, _INR(0,1), NBIP_CallbackFn, LM_pw); } return 1; /* Don't claim, others may wish to know */ } static void RemoveCallbacks(void) { _swix(OS_RemoveCallBack, _INR(0,1), NBIP_CallbackFn, LM_pw); CallbackSet = false; } /* -------------------------------- */ int NBIP_CallbackFn_handler(void) { fd_set read_set; struct timeval tv; int len, flen; struct sockaddr sa; CallbackSet = false; while (NBNS_Socket != INVALID_SOCKET) /* Do as many packets as we can */ { tv.tv_sec=0; tv.tv_usec=0; FD_ZERO(&read_set); FD_SET( NBNS_Socket, &read_set ); if ( select ( NBNS_Socket + 1, &read_set, NULL, NULL, &tv ) == 0 ) break; flen = sizeof(sa); len = recvfrom ( NBNS_Socket, DatagramBuf, sizeof(DatagramBuf), 0, &sa, &flen ); if ( len > 0 ) { STAT(STA_IP_RXDGRAM); NBNS_ProcessDatagram ( &sa, DatagramBuf, len ); } } return 1; } int NBIP_CallbackFn_handler_ctrl(_kernel_swi_regs *r, void *pw) { (void) r; (void) pw; return NBIP_CallbackFn_handler(); } /* Exported routines: name service & general =========================== */ /* -------------------- */ /* This function will return a pointer to a NAME_ENTRY structure if the name can be found. The status field will be set to RMT_FOUND to ensure that the name entry is not disposed of before it can be used. After it has been used, it should be set to RMT_CACHED, to allow it to be dropped from the table after a while. */ static NAME_ENTRY *FindRemoteName ( NETNAME *pnn ) { int i; uint tstart; char plain_name[NAME_LIMIT]; NAME_ENTRY *pNE; struct hostent *pHE; debug1("Find remote name %s\n", debug_name(pnn)); /* Check name cache */ CheckExpiredNames(); pNE = FindNameEntry ( pnn ); if ( pNE != NULL ) /* Found it */ { if ( pNE->status == RMT_FOUND || pNE->status == RMT_CACHED ) { pNE->status = RMT_FOUND; return pNE; } return NULL; /* Local name - can't be found */ } /* Try to find it by broadcast, then ask name server */ pNE = AllocNameEntry(RMT_AWAIT_FIND); if ( pNE==NULL ) /* No spaces left */ return NULL; pNE->nn = *pnn; /* Copy name */ pNE->pStatResp = NULL; /* for safety */ for ( i=3; i>=0; i-- ) { if ( i > 0 ) /* Send 3 broadcasts */ { debug0("Broadcast find request\n"); SendFindRequest ( NBNS_Broadcast, pNE ); } else /* Ask name server */ { SetupNameServer(); if ( NBNS_NameServer == NULL ) break; debug0("Send find request to nameserver\n"); SendFindRequest ( NBNS_NameServer, pNE ); } tstart = NB_GetTime(); do { CollectCallbacks(); NBIP_CallbackFn_handler(); if (pNE->status == RMT_FOUND) { debug1("Name found at %X\n", pNE->IPaddress.s_addr); return pNE; } } while ( (NB_GetTime() - tstart) < 50 ); } /* Still no joy - can we look it up in hosts file? */ _NB_DecodeName ( pnn, plain_name ); strcpyn_lower ( plain_name, plain_name, NAME_LIMIT ); debug0("Looking up name in hosts file\n"); pHE = gethostbyname ( plain_name ); if ( pHE != NULL && pHE->h_addr_list != NULL && pHE->h_addr_list[0] != NULL ) { pNE->status = RMT_FOUND; pNE->nbflags = NBFLG_UNIQUE | NBFLG_BNODE; /* Don't know - make it up */ pNE->TTL_StartTime = NB_GetTime(); pNE->TTL_Interval = 1; /* Doesn't last for long */ pNE->IPaddress = GetIPAddr ( (BYTE *) (pHE->h_addr_list[0]) ); debug1("Name found in hosts at %X\n", pNE->IPaddress.s_addr); return pNE; } FreeNameEntry(pNE); return NULL; } /* ----------------------- */ static NAME_ENTRY *ValidatehName( hNAME hName ) { NAME_ENTRY *pN = (NAME_ENTRY *)hName; if ( pN != NULL ) { debug1("Status => %d\n", pN->status); if ( (pN->status == LCL_NAME_OK) || (pN->status == LCL_IN_CONFLICT) ) return pN; } return NULL; } /* ----------------- */ EXPORT err_t _NB_AddLocalName ( nametype_t nt, char *name, hNAME *phName ) { NAME_ENTRY *pNE; NETNAME netname; int i; uint tstart; debug1("Add name '%s'\n", name ); _NB_FormatName ( nt, name, &netname ); pNE = FindNameEntry ( &netname ); if ( pNE != NULL ) /* Either local or remote */ return ENAMEEXISTS; pNE = AllocNameEntry(LCL_AWAIT_ADD); if ( pNE == NULL ) return ENOHANDLES; pNE->IPaddress = NB_IPAddress; pNE->nbflags = NBFLG_UNIQUE | NBFLG_BNODE; pNE->nn = netname; pNE->pStatResp = NULL; for ( i=3; i >= 0; i-- ) /* 3 opportunities to complain */ { if ( i > 0 ) /* Broadcast 3 times */ { SendRegisterRequest ( NBNS_Broadcast, pNE ); } else /* Then check with the nameserver if present */ { SetupNameServer(); if ( NBNS_NameServer == NULL ) break; SendRegisterRequest ( NBNS_NameServer, pNE ); } tstart = NB_GetTime(); do { CollectCallbacks(); NBIP_CallbackFn_handler(); if ( pNE->status == LCL_IN_CONFLICT ) /* Failed */ { FreeNameEntry(pNE); return ENAMEEXISTS; } } while ( (NB_GetTime() - tstart) < 50 ); } /* No-one complained */ pNE->status = LCL_NAME_OK; *phName = (hNAME) pNE; return OK; } /* ----------------------- */ EXPORT err_t _NB_RemoveLocalName ( hNAME hName ) { NAME_ENTRY *pNE; int i; uint tstart; pNE = ValidatehName(hName); if ( pNE == NULL ) return EBADPARAM; /* Should vape all connections to this name */ if ( pNE->status != LCL_IN_CONFLICT ) { for ( i=0; i<3; i++ ) { SendReleaseRequest ( NBNS_Broadcast, pNE ); tstart = NB_GetTime(); do { CollectCallbacks(); NBIP_CallbackFn_handler(); } while ( (NB_GetTime() - tstart) < 50 ); } /* Tell name server if we have one */ if ( NBNS_NameServer != NULL ) SendReleaseRequest ( NBNS_NameServer, pNE ); } FreeNameEntry(pNE); return OK; } /* ----------------------- */ EXPORT int _NB_FindNames ( NETNAME *pnnFind, nametype_t ntFind, struct FindName_res *pResults, int results_max, int timeout ) { NAME_ENTRY *pNE; struct status_resp SR; uint tstart; pNE = AllocNameEntry(RMT_STATUS_Q); if ( pNE == NULL ) return 0; debug2("Find name %s type %Xh\n", debug_name(pnnFind), ntFind); SR.nt_search = ntFind; SR.pRes = pResults; SR.spaces_left = results_max; pNE->nn = *pnnFind; pNE->pStatResp = &SR; /* Try broadcast to start with */ SendStatusRequest( NBNS_Broadcast, pNE); tstart = NB_GetTime(); do { CollectCallbacks(); NBIP_CallbackFn_handler(); } while ( SR.spaces_left > 0 && (NB_GetTime() - tstart) < timeout ); /* If more to do, ask the name server */ if ( SR.spaces_left == 0 ) goto search_done; SetupNameServer(); if ( NBNS_NameServer == NULL ) goto search_done; SendStatusRequest ( NBNS_NameServer, pNE); tstart = NB_GetTime(); do { CollectCallbacks(); NBIP_CallbackFn_handler(); } while ( SR.spaces_left > 0 && (NB_GetTime() - tstart) < timeout ); search_done: FreeNameEntry(pNE); return results_max - SR.spaces_left; } /* Session service *************************************************** */ #define SESS_FREE 0 #define SESS_CONNECTED 1 typedef struct { int status; int sid; /* Socket ID */ bool LinkOK; NAME_ENTRY *lcl_name; struct sockaddr_in rmt_addr; } NBIP_SESSION; static NBIP_SESSION NB_Sessions[MAX_SESSIONS]; /* ------------------------ */ static NBIP_SESSION *AllocSession ( void ) { int i; for (i=0; i < MAX_SESSIONS; i++) if ( NB_Sessions[i].status == SESS_FREE ) return &NB_Sessions[i]; return NULL; } /* ------------------------ */ static void FreeSession (NBIP_SESSION *pNS) { pNS->status = SESS_FREE; } /* ----------------- */ static NBIP_SESSION *ValidatehSession( hSESSION hSess ) { NBIP_SESSION *pN = (NBIP_SESSION *)hSess; if ( pN != NULL && pN->status == SESS_CONNECTED ) return pN; return NULL; } /* ------------------- */ static bool ReadData ( int sid, BYTE *where, int len, uint timeout, int flags ) { uint tstart; fd_set read_set; struct timeval tv; int rdlen; tstart = NB_GetTime(); while ( len > 0 ) { CollectCallbacks(); /* Let IP do its thing */ NBIP_CallbackFn_handler(); /* Process any datagrams */ tv.tv_sec=0; tv.tv_usec=0; FD_ZERO(&read_set); FD_SET( sid, &read_set ); /* SNB: Changed first param to sid+1 - is more efficient as Internet module * no longer has to search entire array for set bits */ if ( select ( sid + 1, &read_set, NULL, NULL, &tv ) != 0 ) { rdlen = recv ( sid, where, len, flags ); if ( rdlen > 0 ) { if (flags & MSG_PEEK) break; len -= rdlen; where += rdlen; tstart = NB_GetTime(); continue; } } if ( NB_GetTime() - tstart > timeout ) { if ( timeout > 0) debug1("Timeout after %dcs\n", timeout); return false; } } return true; } /* --------------- */ static err_t ConnectAttempt ( NBIP_SESSION *pNS, NETNAME *pnnFarEnd ) { struct sockaddr_in sa; BYTE *p; uint len; debug2("Attempting to connect - addr=%Xh, port %d\n", pNS->rmt_addr.sin_addr.s_addr, ntohs(pNS->rmt_addr.sin_port) ); /* Entered with pNS->sid = a socket descriptor, and pNS->rmt_addr is a starting IP address & port number. This should attempt to bind(), connect(), then establish a session with the far end. Can return ERETARGET with pNS->rmt_addr set to a new address, in which case it will get called again with a new socket */ sa.sin_family = AF_INET; sa.sin_port = 0; /* Alloc a port */ sa.sin_addr.s_addr = INADDR_ANY; if ( bind ( pNS->sid, (struct sockaddr *)&sa, sizeof(sa) ) != 0 ) return ECREATESOCKET; /* Try to connect to remote end */ if ( connect ( pNS->sid, (struct sockaddr *)(&pNS->rmt_addr), sizeof(pNS->rmt_addr) ) != 0 ) return ECONNECTSOCKET; /* OK, we're connected - try a 'session request' */ p = DatagramBuf; *p++ = NBIP_SESS_REQUEST; *p++ = 0; /* Flags */ p+=2; /* Skip length for now */ p = DatagramBuf+4; p = PutNetname ( p, pnnFarEnd ); /* Called name */ p = PutNetname ( p, &(pNS->lcl_name->nn) ); /* Calling name */ len = p-DatagramBuf; PutShort(DatagramBuf+2, len-4); if ( send ( pNS->sid, DatagramBuf, len, 0 ) != len ) return ECONNECTSOCKET; debug0("Sent session request: "); /* Now get reply */ if ( !ReadData(pNS->sid, DatagramBuf, 4, RECV_TIMEOUT, 0) ) return ETIMEOUT; switch ( DatagramBuf[0] ) { case NBIP_SESS_OK: /* Well and good */ return OK; case NBIP_SESS_REJECT: return ECONNREJECT; case NBIP_SESS_RETARGET: if ( !ReadData( pNS->sid, DatagramBuf, 6, RECV_TIMEOUT, 0) ) return EDATALEN; pNS->rmt_addr.sin_addr = GetIPAddr( DatagramBuf ); pNS->rmt_addr.sin_port = htons(GetShort( DatagramBuf+4 )); debug2("Retargeted to %Xh, port %d\n", pNS->rmt_addr.sin_addr.s_addr, ntohs(pNS->rmt_addr.sin_port) ); return ERETARGET; } return ECONNECTSOCKET; } /* ------------------------ */ EXPORT err_t _NB_OpenSession ( hNAME hLocalName, NETNAME *pnnFarEnd, hSESSION *phSession ) { NBIP_SESSION *pNS; NAME_ENTRY *lcl_end, *far_end; err_t res; int retargets=0; /* Check parameters */ debug0("ValidatehName..\n"); lcl_end = ValidatehName(hLocalName); if ( lcl_end == NULL || lcl_end->status != LCL_NAME_OK ) return EBADPARAM; /* Try to find remote end */ debug0("FindRemoteName..\n"); far_end = FindRemoteName ( pnnFarEnd ); if ( far_end == NULL ) return ECANTFINDNAME; /* Alloc session details */ pNS = AllocSession(); if ( pNS == NULL ) return ENOHANDLES; pNS->lcl_name = lcl_end; pNS->rmt_addr.sin_family = AF_INET; pNS->rmt_addr.sin_addr = far_end->IPaddress; pNS->rmt_addr.sin_port = htons(NBIP_SESSION_PORT); far_end->status = RMT_CACHED; /* No longer needed */ /* Create socket for connection, with retries */ do { pNS->sid = socket ( PF_INET, SOCK_STREAM, 0 ); if ( pNS->sid == INVALID_SOCKET ) { res = ECREATESOCKET; break; } res = ConnectAttempt ( pNS, pnnFarEnd ); /* May return 'ERETARGET' - this isn't really an error */ if ( res == OK ) { pNS->status = SESS_CONNECTED; pNS->LinkOK = true; *phSession = (hSESSION) pNS; return OK; } debug1("ConnectAttempt, error %d\n", res); socketclose ( pNS->sid ); } while ( res == ERETARGET && ++retargets < 10 ); /* Failed - don't even keep the remote name's IP address in cache */ FreeSession(pNS); FreeNameEntry ( far_end ); debug1("NB_OpenSession(IP) returning (res=%d)\n", res); return res; } /* ----------------------- */ #define IOV_MAX 4 EXPORT err_t _NB_SendData ( hSESSION hS, BUFCHAIN Data ) { NBIP_SESSION *pNS; BYTE hdr[4]; int i, len; struct GBP_in_out GBP; struct iovec iov[IOV_MAX]; len = ChainLen(Data); if ( len >= 0x20000 ) { FreeChain(Data); return EDATALEN; } pNS = ValidatehSession(hS); if ( pNS == NULL ) { FreeChain(Data); return EBADPARAM; } PutLong( hdr, len | (NBIP_SESS_DATA << 24) ); /* We could just send the header. Instead, adding it to the chain gives better performance */ GBP.pChain = Data = AddChain(Data, hdr, 4); if (Data == NULL) return ENOBUFS; do { i=len=0; while (i<IOV_MAX && GetBlockPointer(&GBP) ) { iov[i].iov_base = (char *)GBP.pBlock; iov[i].iov_len = GBP.BlockLen; len+=GBP.BlockLen; i++; } if ( socketwritev(pNS->sid, iov, i) != len ) { FreeChain(Data); pNS->LinkOK = false; return ELINKFAILED; } } while ( i==IOV_MAX ); CollectCallbacks(); FreeChain(Data); return OK; } /* ----------------------- */ EXPORT err_t _NB_SendBlockData ( hSESSION hS, BYTE *where, int datalen ) { NBIP_SESSION *pNS; BYTE hdr[4]; err_t res; struct iovec iov[2]; while ( datalen > 0x10000 ) /* Max 64K in one go */ { res = _NB_SendBlockData ( hS, where, 0x10000 ); if ( res != OK ) return res; where += 0x10000; datalen -= 0x10000; } pNS = ValidatehSession(hS); if ( pNS == NULL ) return EBADPARAM; /* Send header */ PutLong( hdr, datalen | (NBIP_SESS_DATA << 24) ); iov[0].iov_base = (char *)hdr; iov[0].iov_len = 4; iov[1].iov_base = (char *) where; iov[1].iov_len = datalen; if ( socketwritev ( pNS->sid, iov, 2 ) != (datalen+4) ) { pNS->LinkOK = false; return ELINKFAILED; } CollectCallbacks(); return OK; } /* ----------------------- */ EXPORT err_t _NB_ClearRxQueue ( hSESSION hS ) { #ifdef LONGNAMES NBIP_SESSION *pNS; BYTE buf[4]; pNS = ValidatehSession(hS); if ( pNS == NULL ) return EBADPARAM; if ( !ReadData(pNS->sid, buf, 4, 0, MSG_PEEK) ) { return OK; } return ERXNOTREADY; #endif (void) hS; /* ClearRxQueue - not needed? !! */ return OK; } /* ----------------------- */ EXPORT err_t _NB_GetData ( hSESSION hS, BUFCHAIN *pOutData, int timeout ) { BUFCHAIN Chain; struct GBP_in_out GBP; NBIP_SESSION *pNS; BYTE hdr[4]; uint len; debug0(" Rd"); pNS = ValidatehSession(hS); if ( pNS == NULL ) return EBADPARAM; /* Get header */ retry: if ( !ReadData(pNS->sid, hdr, 4, timeout, 0) ) { pNS->LinkOK = false; return ETIMEOUT; } len = GetLong(hdr); if ( (len >> 24) != NBIP_SESS_DATA ) /* Keepalive packet? */ { debug1(" Packet header %Xh", len); goto retry; } len &= 0x1FFFF; /* Max 128K of data */ Chain = AllocBlankChain(len); if ( Chain == NULL ) return ENOBUFS; GBP.pChain = Chain; while ( GetBlockPointer(&GBP) ) { if ( !ReadData(pNS->sid, GBP.pBlock, GBP.BlockLen, timeout, 0) ) { pNS->LinkOK = false; FreeChain(Chain); return ETIMEOUT; } } debug1("=%d\n", len); *pOutData = Chain; return OK; } /* ----------------------- */ EXPORT err_t _NB_GetBlockData ( hSESSION hS, BYTE *where, int *len_in_out, int timeout ) { NBIP_SESSION *pNS; BYTE hdr[4]; uint len; debug0(" RdB"); pNS = ValidatehSession(hS); if ( pNS == NULL ) return EBADPARAM; /* Get header */ retry: if ( !ReadData(pNS->sid, hdr, 4, timeout, 0) ) { pNS->LinkOK = false; return ETIMEOUT; } len = GetLong(hdr); if ( (len >> 24) != NBIP_SESS_DATA ) /* Keepalive packet? */ { debug1("Packet header %Xh", len); goto retry; } len &= 0x1FFFF; /* Max 128K of data */ if ( !ReadData(pNS->sid, where, len, timeout, 0) ) { pNS->LinkOK = false; return ETIMEOUT; } debug1("=%d\n", len); *len_in_out = len; return OK; } /* ----------------------- */ EXPORT bool _NB_LinkOK ( hSESSION hS ) { NBIP_SESSION *pNS; pNS = ValidatehSession(hS); if ( pNS == NULL ) return false; return pNS->LinkOK; } /* ----------------------- */ EXPORT err_t _NB_CloseSession ( hSESSION hS ) { NBIP_SESSION *pNS; debug0("CloseSession()\n"); pNS = ValidatehSession(hS); if ( pNS != NULL ) { /* Kill the connection, that's it */ socketclose(pNS->sid); FreeSession(pNS); } return OK; } /* ----------------------- */ EXPORT char * _NB_DescribeLink ( hSESSION hS ) { static char namebuf[40]; NBIP_SESSION *pNS = ValidatehSession(hS); if ( pNS == NULL ) return NULL; sprintf(namebuf, "%s port %d", inet_ntoa(pNS->rmt_addr.sin_addr), htons(pNS->rmt_addr.sin_port )); return namebuf; } /* Init & Shutdown routines ====================================== */ EXPORT void _NB_Shutdown(void) { int i; /* Important: we de-register the event handler first, because socketclose() calls the internet event. We don't want this setting a callback, because we'll be dead by the time it happens */ if ( EventsClaimed ) { EventsClaimed = false; _swix(OS_Release, _INR(0,2), EventV, (int)EventFn, LM_pw); _swix(OS_Byte, _INR(0,1), 13, Internet_Event); /* Disable event */ } if ( NBNS_Socket != INVALID_SOCKET ) { socketclose(NBNS_Socket); NBNS_Socket = INVALID_SOCKET; } for (i=0; i < MAX_SESSIONS; i++) if ( NB_Sessions[i].status == SESS_CONNECTED ) { socketclose ( NB_Sessions[i].sid ); NB_Sessions[i].status = SESS_FREE; } RemoveCallbacks(); } /* NB_Startup() ----------------------------------------------------- On startup, we have to i) Set the NetBIOS scope ID ii) create a socket for the NBNS daemon iii) install an Internet Event handler iv) register our name on the net */ static int On = 1; static struct sockaddr_in Broadcast_sin; EXPORT err_t _NB_Startup(void) { err_t res; int i; struct sockaddr_in sa; struct ifreq IFR; if ( LM_Vars.verbose ) printf(" Starting TCP/IP transport\n"); /* Clear out name table & session table */ for ( i=0; i < MAX_NAMES; i++ ) NB_NameTable[i].status = NAME_FREE; for (i=0; i < MAX_SESSIONS; i++) NB_Sessions[i].status = SESS_FREE; NB_FirstFreeName = 0; NBNS_RequestCount = 0; Stat_ClassMask |= SCLASS_IP; SetScopeID(getenv("LanMan$ScopeID")); /* Set up broadcast addresses */ Broadcast_sin.sin_family = AF_INET; Broadcast_sin.sin_port = htons(NBNS_PORT); Broadcast_sin.sin_addr.s_addr = INADDR_BROADCAST; NBNS_Broadcast = (struct sockaddr *) &Broadcast_sin; /* Create a socket for the NBNS server (checks TCPIP works!) */ NBNS_Socket = socket ( PF_INET, SOCK_DGRAM, 0 ); if ( NBNS_Socket == INVALID_SOCKET ) { debug0("Couldn't create socket\n"); return ENOSOCKETS; } /* Get a network address */ strcpy ( IFR.ifr_name, LM_Vars.drivername ); /* Will be Inet$EtherType unless overridden */ if ( socketioctl ( NBNS_Socket, SIOCGIFADDR, (char *)&IFR ) < 0 ) { if ( LM_Vars.verbose ) { printf(" Couldn't find IP address for interface '%s'\n", LM_Vars.drivername ); } (void) socketclose(NBNS_Socket); NBNS_Socket = INVALID_SOCKET; return ENOIFADDR; } NB_IPAddress = ((struct sockaddr_in *)&IFR.ifr_addr)->sin_addr; /* Invent machine name from host address if needs be */ if ( LM_Vars.machinename[0] == 0 ) { union in_addr_byte iab; iab.ina = NB_IPAddress; sprintf( LM_Vars.machinename, "ARMIP%02X%02X%02X%02X", iab.nb[0], iab.nb[1], iab.nb[2], iab.nb[3] ); } if ( LM_Vars.verbose ) { show_scope(NB_ScopeID); printf(" Machine name is '%s', IP address %s\n", LM_Vars.machinename, inet_ntoa ( NB_IPAddress ) ); } /* Bind socket */ sa.sin_family = AF_INET; sa.sin_port = htons(NBNS_PORT); sa.sin_addr.s_addr = INADDR_ANY; if ( bind( NBNS_Socket, (struct sockaddr *)&sa, sizeof(sa) ) < 0 ) { /* Socket will be closed on exit */ debug0("Couldn't bind socket\n"); (void) socketclose(NBNS_Socket); NBNS_Socket = INVALID_SOCKET; return ENOSOCKETS; } /* Allow broadcasts */ setsockopt ( NBNS_Socket, SOL_SOCKET, SO_BROADCAST, &On, sizeof(int)); /* Generate events */ socketioctl ( NBNS_Socket, FIOASYNC, &On ); /* It's now ready for action - set up internet event handler */ /*CallbackSet = false;*/ if (!EventsClaimed) { _swix(OS_Claim, _INR(0,2), EventV, (int) EventFn, LM_pw); _swix(OS_Byte, _INR(0,1), 14, Internet_Event); /* Enable event */ EventsClaimed = true; debug0("Events claimed\n"); } // strcpy ( LM_Vars.drivername, "TCP/IP"); /* Now try to register name... */ debug1("Setting our local name as `%s'\n", LM_Vars.machinename); res = _NB_AddLocalName ( ntMACHINE, LM_Vars.machinename, &NB_MachineName); if (res != OK) _NB_Shutdown(); return res; } /* NB_InternetGone() --------------------------- The Internet module has disappeared. Mark all sessions as disconnected and pray that a new Internet module comes back before anything tries to do anything with a session. */ static void _NB_InternetGone(void) { int i; for (i=0; i < MAX_SESSIONS; ++i) { if (NB_Sessions[i].status == SESS_CONNECTED) { NB_Sessions[i].sid = INVALID_SOCKET; NB_Sessions[i].LinkOK = false; } } NBNS_Socket = INVALID_SOCKET; RemoveCallbacks(); } /* NB_InternetInit() --------------------------- The Internet module has arrived. Mark all sessions as disconnected and try to re-initialise things. However, we must be careful - only do this if we saw the old Internet module dying, because this might just be the main ROM one sending the service call on a callback. We must only reinit if we saw a dying service call. */ static void _NB_InternetInit(void) { if (NBNS_Socket == INVALID_SOCKET) { int i; for (i=0; i < MAX_SESSIONS; ++i) { if (NB_Sessions[i].status == SESS_CONNECTED) { NB_Sessions[i].sid = INVALID_SOCKET; NB_Sessions[i].LinkOK = false; } } _NB_Startup(); } } /* Setup routine ------------------------------- */ static struct NETBIOS_TRANSPORT NBIP_Transport; void NB_NBIP_Setup ( void ) { struct NETBIOS_TRANSPORT *p; NB_ActiveTransport = p = &NBIP_Transport; p->pfnStartup = _NB_Startup; p->pfnShutdown = _NB_Shutdown; p->pfnFormatName = _NB_FormatName; p->pfnDecodeName = _NB_DecodeName; p->pfnMatchName = _NB_MatchName; p->pfnAddLocalName = _NB_AddLocalName; p->pfnRemoveLocalName = _NB_RemoveLocalName; p->pfnOpenSession = _NB_OpenSession; p->pfnSendData = _NB_SendData; p->pfnSendBlockData = _NB_SendBlockData; p->pfnClearRxQueue = _NB_ClearRxQueue; p->pfnGetData = _NB_GetData; p->pfnGetBlockData = _NB_GetBlockData; p->pfnLinkOK = _NB_LinkOK; p->pfnCloseSession = _NB_CloseSession; p->pfnFindNames = _NB_FindNames; p->pfnDescribeLink = _NB_DescribeLink; p->pfnInternetGone = _NB_InternetGone; p->pfnInternetInit = _NB_InternetInit; }