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