1/*	$NetBSD: rmpproto.c,v 1.14 2004/07/06 13:05:25 mycroft Exp $	*/
2
3/*
4 * Copyright (c) 1988, 1992 The University of Utah and the Center
5 *	for Software Science (CSS).
6 * Copyright (c) 1992, 1993
7 *	The Regents of the University of California.  All rights reserved.
8 *
9 * This code is derived from software contributed to Berkeley by
10 * the Center for Software Science of the University of Utah Computer
11 * Science Department.  CSS requests users of this software to return
12 * to css-dist@cs.utah.edu any improvements that they make and grant
13 * CSS redistribution rights.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 * 3. Neither the name of the University nor the names of its contributors
24 *    may be used to endorse or promote products derived from this software
25 *    without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 *
39 *	from: @(#)rmpproto.c	8.1 (Berkeley) 6/4/93
40 *
41 * From: Utah Hdr: rmpproto.c 3.1 92/07/06
42 * Author: Jeff Forys, University of Utah CSS
43 */
44
45#include <sys/cdefs.h>
46#ifndef lint
47#if 0
48static char sccsid[] = "@(#)rmpproto.c	8.1 (Berkeley) 6/4/93";
49#else
50__RCSID("$NetBSD: rmpproto.c,v 1.14 2004/07/06 13:05:25 mycroft Exp $");
51#endif
52#endif /* not lint */
53
54#include <sys/param.h>
55#include <sys/time.h>
56
57#include <errno.h>
58#include <fcntl.h>
59#include <stdio.h>
60#include <string.h>
61#include <syslog.h>
62#include <unistd.h>
63#include "defs.h"
64
65/*
66**  ProcessPacket -- determine packet type and do what's required.
67**
68**	An RMP BOOT packet has been received.  Look at the type field
69**	and process Boot Requests, Read Requests, and Boot Complete
70**	packets.  Any other type will be dropped with a warning msg.
71**
72**	Parameters:
73**		rconn - the new connection
74**		client - list of files available to this host
75**
76**	Returns:
77**		Nothing.
78**
79**	Side Effects:
80**		- If this is a valid boot request, it will be added to
81**		  the linked list of outstanding requests (RmpConns).
82**		- If this is a valid boot complete, its associated
83**		  entry in RmpConns will be deleted.
84**		- Also, unless we run out of memory, a reply will be
85**		  sent to the host that sent the packet.
86*/
87void
88ProcessPacket(rconn, client)
89	RMPCONN *rconn;
90	CLIENT *client;
91{
92	struct rmp_packet *rmp;
93	RMPCONN *rconnout;
94
95	rmp = &rconn->rmp;		/* cache pointer to RMP packet */
96
97	switch(rmp->r_type) {		/* do what we came here to do */
98		case RMP_BOOT_REQ:		/* boot request */
99			if ((rconnout = NewConn(rconn)) == NULL)
100				return;
101
102			/*
103			 *  If the Session ID is 0xffff, this is a "probe"
104			 *  packet and we do not want to add the connection
105			 *  to the linked list of active connections.  There
106			 *  are two types of probe packets, if the Sequence
107			 *  Number is 0 they want to know our host name, o/w
108			 *  they want the name of the file associated with
109			 *  the number spec'd by the Sequence Number.
110			 *
111			 *  If this is an actual boot request, open the file
112			 *  and send a reply.  If SendBootRepl() does not
113			 *  return 0, add the connection to the linked list
114			 *  of active connections, otherwise delete it since
115			 *  an error was encountered.
116			 */
117			if (ntohs(rmp->r_brq.rmp_session) == RMP_PROBESID) {
118				if (WORDZE(rmp->r_brq.rmp_seqno))
119					(void) SendServerID(rconnout);
120				else
121					(void) SendFileNo(rmp, rconnout,
122					                  client? client->files:
123					                          BootFiles);
124				FreeConn(rconnout);
125			} else {
126				if (SendBootRepl(rmp, rconnout,
127				    client? client->files: BootFiles))
128					AddConn(rconnout);
129				else
130					FreeConn(rconnout);
131			}
132			break;
133
134		case RMP_BOOT_REPL:		/* boot reply (not valid) */
135			syslog(LOG_WARNING, "%s: sent a boot reply",
136			       EnetStr(rconn));
137			break;
138
139		case RMP_READ_REQ:		/* read request */
140			/*
141			 *  Send a portion of the boot file.
142			 */
143			(void) SendReadRepl(rconn);
144			break;
145
146		case RMP_READ_REPL:		/* read reply (not valid) */
147			syslog(LOG_WARNING, "%s: sent a read reply",
148			       EnetStr(rconn));
149			break;
150
151		case RMP_BOOT_DONE:		/* boot complete */
152			/*
153			 *  Remove the entry from the linked list of active
154			 *  connections.
155			 */
156			(void) BootDone(rconn);
157			break;
158
159		default:			/* unknown RMP packet type */
160			syslog(LOG_WARNING, "%s: unknown packet type (%u)",
161			       EnetStr(rconn), rmp->r_type);
162	}
163}
164
165/*
166**  SendServerID -- send our host name to who ever requested it.
167**
168**	Parameters:
169**		rconn - the reply packet to be formatted.
170**
171**	Returns:
172**		1 on success, 0 on failure.
173**
174**	Side Effects:
175**		none.
176*/
177int
178SendServerID(rconn)
179	RMPCONN *rconn;
180{
181	struct rmp_packet *rpl;
182	char *src, *dst;
183	u_int8_t *size;
184
185	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
186
187	/*
188	 *  Set up assorted fields in reply packet.
189	 */
190	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
191	rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
192	ZEROWORD(rpl->r_brpl.rmp_seqno);
193	rpl->r_brpl.rmp_session = 0;
194	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
195
196	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of host name */
197
198	/*
199	 *  Copy our host name into the reply packet incrementing the
200	 *  length as we go.  Stop at RMP_HOSTLEN or the first dot.
201	 */
202	src = MyHost;
203	dst = (char *) &rpl->r_brpl.rmp_flnm;
204	for (*size = 0; *size < RMP_HOSTLEN; (*size)++) {
205		if (*src == '.' || *src == '\0')
206			break;
207		*dst++ = *src++;
208	}
209
210	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
211
212	return(SendPacket(rconn));		/* send packet */
213}
214
215/*
216**  SendFileNo -- send the name of a bootable file to the requester.
217**
218**	Parameters:
219**		req - RMP BOOT packet containing the request.
220**		rconn - the reply packet to be formatted.
221**		filelist - list of files available to the requester.
222**
223**	Returns:
224**		1 on success, 0 on failure.
225**
226**	Side Effects:
227**		none.
228*/
229int
230SendFileNo(req, rconn, filelist)
231	struct rmp_packet *req;
232	RMPCONN *rconn;
233	char *filelist[];
234{
235	struct rmp_packet *rpl;
236	char *src, *dst;
237	u_int8_t *size;
238	int i;
239
240	GETWORD(req->r_brpl.rmp_seqno, i);	/* SeqNo is really FileNo */
241	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
242
243	/*
244	 *  Set up assorted fields in reply packet.
245	 */
246	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
247	PUTWORD(i, rpl->r_brpl.rmp_seqno);
248	i--;
249	rpl->r_brpl.rmp_session = 0;
250	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
251
252	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of filename */
253	*size = 0;				/* init length to zero */
254
255	/*
256	 *  Copy the file name into the reply packet incrementing the
257	 *  length as we go.  Stop at end of string or when RMPBOOTDATA
258	 *  characters have been copied.  Also, set return code to
259	 *  indicate success or "no more files".
260	 */
261	if (i < C_MAXFILE && filelist[i] != NULL) {
262		src = filelist[i];
263		dst = (char *)&rpl->r_brpl.rmp_flnm;
264		for (; *src && *size < RMPBOOTDATA; (*size)++) {
265			if (*src == '\0')
266				break;
267			*dst++ = *src++;
268		}
269		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
270	} else
271		rpl->r_brpl.rmp_retcode = RMP_E_NODFLT;
272
273	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
274
275	return(SendPacket(rconn));		/* send packet */
276}
277
278/*
279**  SendBootRepl -- open boot file and respond to boot request.
280**
281**	Parameters:
282**		req - RMP BOOT packet containing the request.
283**		rconn - the reply packet to be formatted.
284**		filelist - list of files available to the requester.
285**
286**	Returns:
287**		1 on success, 0 on failure.
288**
289**	Side Effects:
290**		none.
291*/
292int
293SendBootRepl(req, rconn, filelist)
294	struct rmp_packet *req;
295	RMPCONN *rconn;
296	char *filelist[];
297{
298	int retval;
299	char *filename, filepath[RMPBOOTDATA+1];
300	RMPCONN *oldconn;
301	struct rmp_packet *rpl;
302	char *src, *dst1, *dst2;
303	u_int8_t i;
304
305	/*
306	 *  If another connection already exists, delete it since we
307	 *  are obviously starting again.
308	 */
309	if ((oldconn = FindConn(rconn)) != NULL) {
310		syslog(LOG_WARNING, "%s: dropping existing connection",
311		       EnetStr(oldconn));
312		RemoveConn(oldconn);
313	}
314
315	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
316
317	/*
318	 *  Set up assorted fields in reply packet.
319	 */
320	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
321	COPYWORD(req->r_brq.rmp_seqno, rpl->r_brpl.rmp_seqno);
322	rpl->r_brpl.rmp_session = htons(GenSessID());
323	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
324	rpl->r_brpl.rmp_flnmsize = req->r_brq.rmp_flnmsize;
325
326	/*
327	 *  Copy file name to `filepath' string, and into reply packet.
328	 */
329	dst1 = filepath;
330	dst2 = &rpl->r_brpl.rmp_flnm;
331	if (req->r_brq.rmp_flnmsize)
332		src = &req->r_brq.rmp_flnm;
333	else {
334		/* no file supplied, substitute the first one */
335		src = filelist[0];
336		req->r_brq.rmp_flnmsize = strlen(src);
337	}
338	for (i = 0; i < req->r_brq.rmp_flnmsize; i++)
339		*dst1++ = *dst2++ = *src++;
340	*dst1 = '\0';
341
342	/*
343	 *  If we are booting HP-UX machines, their secondary loader will
344	 *  ask for files like "/hp-ux".  As a security measure, we do not
345	 *  allow boot files to lay outside the boot directory (unless they
346	 *  are purposely link'd out.  So, make `filename' become the path-
347	 *  stripped file name and spoof the client into thinking that it
348	 *  really got what it wanted.
349	 */
350	if ((filename = strrchr(filepath,'/')) != NULL)
351		filename++;
352	else
353		filename = filepath;
354
355	/*
356	 *  Check that this is a valid boot file name.
357	 */
358	for (i = 0; i < C_MAXFILE && filelist[i] != NULL; i++)
359		if (STREQN(filename, filelist[i]))
360			goto match;
361
362	/*
363	 *  Invalid boot file name, set error and send reply packet.
364	 */
365	rpl->r_brpl.rmp_retcode = RMP_E_NOFILE;
366	retval = 0;
367	goto sendpkt;
368
369match:
370	/*
371	 *  This is a valid boot file.  Open the file and save the file
372	 *  descriptor associated with this connection and set success
373	 *  indication.  If the file couldnt be opened, set error:
374	 *  	"no such file or dir" - RMP_E_NOFILE
375	 *	"file table overflow" - RMP_E_BUSY
376	 *	"too many open files" - RMP_E_BUSY
377	 *	anything else         - RMP_E_OPENFILE
378	 */
379	if ((rconn->bootfd = open(filename, O_RDONLY, 0600)) < 0) {
380		rpl->r_brpl.rmp_retcode = (errno == ENOENT)? RMP_E_NOFILE:
381			(errno == EMFILE || errno == ENFILE)? RMP_E_BUSY:
382			RMP_E_OPENFILE;
383		retval = 0;
384	} else {
385		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
386		retval = 1;
387	}
388
389sendpkt:
390	syslog(LOG_INFO, "%s: request to boot %s (%s)",
391	       EnetStr(rconn), filename, retval? "granted": "denied");
392
393	rconn->rmplen = RMPBOOTSIZE(rpl->r_brpl.rmp_flnmsize);
394
395	return (retval & SendPacket(rconn));
396}
397
398/*
399**  SendReadRepl -- send a portion of the boot file to the requester.
400**
401**	Parameters:
402**		rconn - the reply packet to be formatted.
403**
404**	Returns:
405**		1 on success, 0 on failure.
406**
407**	Side Effects:
408**		none.
409*/
410int
411SendReadRepl(rconn)
412	RMPCONN *rconn;
413{
414	int retval = 0;
415	RMPCONN *oldconn;
416	struct rmp_packet *rpl, *req;
417	int size = 0;
418	int madeconn = 0;
419
420	/*
421	 *  Find the old connection.  If one doesnt exist, create one only
422	 *  to return the error code.
423	 */
424	if ((oldconn = FindConn(rconn)) == NULL) {
425		if ((oldconn = NewConn(rconn)) == NULL)
426			return(0);
427		syslog(LOG_ERR, "SendReadRepl: no active connection (%s)",
428		       EnetStr(rconn));
429		madeconn++;
430	}
431
432	req = &rconn->rmp;		/* cache ptr to request packet */
433	rpl = &oldconn->rmp;		/* cache ptr to reply packet */
434
435	if (madeconn) {			/* no active connection above; abort */
436		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
437		retval = 1;
438		goto sendpkt;
439	}
440
441	/*
442	 *  Make sure Session ID's match.
443	 */
444	if (ntohs(req->r_rrq.rmp_session) !=
445	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
446	                                     ntohs(rpl->r_rrpl.rmp_session))) {
447		syslog(LOG_ERR, "SendReadRepl: bad session id (%s)",
448		       EnetStr(rconn));
449		rpl->r_rrpl.rmp_retcode = RMP_E_BADSID;
450		retval = 1;
451		goto sendpkt;
452	}
453
454	/*
455	 *  If the requester asks for more data than we can fit,
456	 *  silently clamp the request size down to RMPREADDATA.
457	 *
458	 *  N.B. I do not know if this is "legal", however it seems
459	 *  to work.  This is necessary for bpfwrite() on machines
460	 *  with MCLBYTES less than 1514.
461	 */
462	if (ntohs(req->r_rrq.rmp_size) > RMPREADDATA)
463		req->r_rrq.rmp_size = htons(RMPREADDATA);
464
465	/*
466	 *  Position read head on file according to info in request packet.
467	 */
468	GETWORD(req->r_rrq.rmp_offset, size);
469	if (lseek(oldconn->bootfd, (off_t)size, SEEK_SET) < 0) {
470		syslog(LOG_ERR, "SendReadRepl: lseek: %m (%s)",
471		       EnetStr(rconn));
472		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
473		retval = 1;
474		goto sendpkt;
475	}
476
477	/*
478	 *  Read data directly into reply packet.
479	 */
480	if ((size = read(oldconn->bootfd, &rpl->r_rrpl.rmp_data,
481	                 (int) ntohs(req->r_rrq.rmp_size))) <= 0) {
482		if (size < 0) {
483			syslog(LOG_ERR, "SendReadRepl: read: %m (%s)",
484			       EnetStr(rconn));
485			rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
486		} else {
487			rpl->r_rrpl.rmp_retcode = RMP_E_EOF;
488		}
489		retval = 1;
490		goto sendpkt;
491	}
492
493	/*
494	 *  Set success indication.
495	 */
496	rpl->r_rrpl.rmp_retcode = RMP_E_OKAY;
497
498sendpkt:
499	/*
500	 *  Set up assorted fields in reply packet.
501	 */
502	rpl->r_rrpl.rmp_type = RMP_READ_REPL;
503	COPYWORD(req->r_rrq.rmp_offset, rpl->r_rrpl.rmp_offset);
504	rpl->r_rrpl.rmp_session = req->r_rrq.rmp_session;
505
506	oldconn->rmplen = RMPREADSIZE(size);	/* set size of packet */
507
508	retval &= SendPacket(oldconn);		/* send packet */
509
510	if (madeconn)				/* clean up after ourself */
511		FreeConn(oldconn);
512
513	return (retval);
514}
515
516/*
517**  BootDone -- free up memory allocated for a connection.
518**
519**	Parameters:
520**		rconn - incoming boot complete packet.
521**
522**	Returns:
523**		1 on success, 0 on failure.
524**
525**	Side Effects:
526**		none.
527*/
528int
529BootDone(rconn)
530	RMPCONN *rconn;
531{
532	RMPCONN *oldconn;
533	struct rmp_packet *rpl;
534
535	/*
536	 *  If we cant find the connection, ignore the request.
537	 */
538	if ((oldconn = FindConn(rconn)) == NULL) {
539		syslog(LOG_ERR, "BootDone: no existing connection (%s)",
540		       EnetStr(rconn));
541		return(0);
542	}
543
544	rpl = &oldconn->rmp;			/* cache ptr to RMP packet */
545
546	/*
547	 *  Make sure Session ID's match.
548	 */
549	if (ntohs(rconn->rmp.r_rrq.rmp_session) !=
550	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
551	                                    ntohs(rpl->r_rrpl.rmp_session))) {
552		syslog(LOG_ERR, "BootDone: bad session id (%s)",
553		       EnetStr(rconn));
554		return(0);
555	}
556
557	RemoveConn(oldconn);			/* remove connection */
558
559	syslog(LOG_INFO, "%s: boot complete", EnetStr(rconn));
560
561	return(1);
562}
563
564/*
565**  SendPacket -- send an RMP packet to a remote host.
566**
567**	Parameters:
568**		rconn - packet to be sent.
569**
570**	Returns:
571**		1 on success, 0 on failure.
572**
573**	Side Effects:
574**		none.
575*/
576int
577SendPacket(rconn)
578	RMPCONN *rconn;
579{
580	/*
581	 *  Set Ethernet Destination address to Source (BPF and the enet
582	 *  driver will take care of getting our source address set).
583	 */
584	memmove((char *)&rconn->rmp.hp_hdr.daddr[0],
585	    (char *)&rconn->rmp.hp_hdr.saddr[0], RMP_ADDRLEN);
586	rconn->rmp.hp_hdr.len = htons(rconn->rmplen - sizeof(struct hp_hdr));
587
588	/*
589	 *  Reverse 802.2/HP Extended Source & Destination Access Pts.
590	 */
591	rconn->rmp.hp_llc.dxsap = htons(HPEXT_SXSAP);
592	rconn->rmp.hp_llc.sxsap = htons(HPEXT_DXSAP);
593
594	/*
595	 *  Last time this connection was active.
596	 */
597	(void) gettimeofday(&rconn->tstamp, (struct timezone *)0);
598
599	if (DbgFp != NULL)			/* display packet */
600		DispPkt(rconn,DIR_SENT);
601
602	/*
603	 *  Send RMP packet to remote host.
604	 */
605	return(BpfWrite(rconn));
606}
607