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$";
53#endif /* not lint */
54
55#include <sys/param.h>
56#include <sys/time.h>
57#include <netinet/in.h>
58
59#include <errno.h>
60#include <fcntl.h>
61#include <stdio.h>
62#include <string.h>
63#include <syslog.h>
64#include <unistd.h>
65#include "defs.h"
66
67/*
68**  ProcessPacket -- determine packet type and do what's required.
69**
70**	An RMP BOOT packet has been received.  Look at the type field
71**	and process Boot Requests, Read Requests, and Boot Complete
72**	packets.  Any other type will be dropped with a warning msg.
73**
74**	Parameters:
75**		rconn - the new connection
76**		client - list of files available to this host
77**
78**	Returns:
79**		Nothing.
80**
81**	Side Effects:
82**		- If this is a valid boot request, it will be added to
83**		  the linked list of outstanding requests (RmpConns).
84**		- If this is a valid boot complete, its associated
85**		  entry in RmpConns will be deleted.
86**		- Also, unless we run out of memory, a reply will be
87**		  sent to the host that sent the packet.
88*/
89void
90ProcessPacket(RMPCONN *rconn, 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(RMPCONN *rconn)
179{
180	struct rmp_packet *rpl;
181	char *src, *dst;
182	u_int8_t *size;
183
184	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
185
186	/*
187	 *  Set up assorted fields in reply packet.
188	 */
189	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
190	rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
191	ZEROWORD(rpl->r_brpl.rmp_seqno);
192	rpl->r_brpl.rmp_session = 0;
193	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
194
195	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of host name */
196
197	/*
198	 *  Copy our host name into the reply packet incrementing the
199	 *  length as we go.  Stop at RMP_HOSTLEN or the first dot.
200	 */
201	src = MyHost;
202	dst = (char *) &rpl->r_brpl.rmp_flnm;
203	for (*size = 0; *size < RMP_HOSTLEN; (*size)++) {
204		if (*src == '.' || *src == '\0')
205			break;
206		*dst++ = *src++;
207	}
208
209	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
210
211	return(SendPacket(rconn));		/* send packet */
212}
213
214/*
215**  SendFileNo -- send the name of a bootable file to the requester.
216**
217**	Parameters:
218**		req - RMP BOOT packet containing the request.
219**		rconn - the reply packet to be formatted.
220**		filelist - list of files available to the requester.
221**
222**	Returns:
223**		1 on success, 0 on failure.
224**
225**	Side Effects:
226**		none.
227*/
228int
229SendFileNo(struct rmp_packet *req, RMPCONN *rconn, char *filelist[])
230{
231	struct rmp_packet *rpl;
232	char *src, *dst;
233	u_int8_t *size;
234	int i;
235
236	GETWORD(req->r_brpl.rmp_seqno, i);	/* SeqNo is really FileNo */
237	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
238
239	/*
240	 *  Set up assorted fields in reply packet.
241	 */
242	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
243	PUTWORD(i, rpl->r_brpl.rmp_seqno);
244	i--;
245	rpl->r_brpl.rmp_session = 0;
246	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
247
248	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of filename */
249	*size = 0;				/* init length to zero */
250
251	/*
252	 *  Copy the file name into the reply packet incrementing the
253	 *  length as we go.  Stop at end of string or when RMPBOOTDATA
254	 *  characters have been copied.  Also, set return code to
255	 *  indicate success or "no more files".
256	 */
257	if (i < C_MAXFILE && filelist[i] != NULL) {
258		src = filelist[i];
259		dst = (char *)&rpl->r_brpl.rmp_flnm;
260		for (; *src && *size < RMPBOOTDATA; (*size)++) {
261			if (*src == '\0')
262				break;
263			*dst++ = *src++;
264		}
265		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
266	} else
267		rpl->r_brpl.rmp_retcode = RMP_E_NODFLT;
268
269	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
270
271	return(SendPacket(rconn));		/* send packet */
272}
273
274/*
275**  SendBootRepl -- open boot file and respond to boot request.
276**
277**	Parameters:
278**		req - RMP BOOT packet containing the request.
279**		rconn - the reply packet to be formatted.
280**		filelist - list of files available to the requester.
281**
282**	Returns:
283**		1 on success, 0 on failure.
284**
285**	Side Effects:
286**		none.
287*/
288int
289SendBootRepl(struct rmp_packet *req, RMPCONN *rconn, char *filelist[])
290{
291	int retval;
292	char *filename, filepath[RMPBOOTDATA+1];
293	RMPCONN *oldconn;
294	struct rmp_packet *rpl;
295	char *src, *dst1, *dst2;
296	u_int8_t i;
297
298	/*
299	 *  If another connection already exists, delete it since we
300	 *  are obviously starting again.
301	 */
302	if ((oldconn = FindConn(rconn)) != NULL) {
303		syslog(LOG_WARNING, "%s: dropping existing connection",
304		       EnetStr(oldconn));
305		RemoveConn(oldconn);
306	}
307
308	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
309
310	/*
311	 *  Set up assorted fields in reply packet.
312	 */
313	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
314	COPYWORD(req->r_brq.rmp_seqno, rpl->r_brpl.rmp_seqno);
315	rpl->r_brpl.rmp_session = htons(GenSessID());
316	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
317	rpl->r_brpl.rmp_flnmsize = req->r_brq.rmp_flnmsize;
318
319	/*
320	 *  Copy file name to `filepath' string, and into reply packet.
321	 */
322	src = &req->r_brq.rmp_flnm;
323	dst1 = filepath;
324	dst2 = &rpl->r_brpl.rmp_flnm;
325	for (i = 0; i < req->r_brq.rmp_flnmsize; i++)
326		*dst1++ = *dst2++ = *src++;
327	*dst1 = '\0';
328
329	/*
330	 *  If we are booting HP-UX machines, their secondary loader will
331	 *  ask for files like "/hp-ux".  As a security measure, we do not
332	 *  allow boot files to lay outside the boot directory (unless they
333	 *  are purposely link'd out.  So, make `filename' become the path-
334	 *  stripped file name and spoof the client into thinking that it
335	 *  really got what it wanted.
336	 */
337	filename = (filename = strrchr(filepath,'/'))? ++filename: filepath;
338
339	/*
340	 *  Check that this is a valid boot file name.
341	 */
342	for (i = 0; i < C_MAXFILE && filelist[i] != NULL; i++)
343		if (STREQN(filename, filelist[i]))
344			goto match;
345
346	/*
347	 *  Invalid boot file name, set error and send reply packet.
348	 */
349	rpl->r_brpl.rmp_retcode = RMP_E_NOFILE;
350	retval = 0;
351	goto sendpkt;
352
353match:
354	/*
355	 *  This is a valid boot file.  Open the file and save the file
356	 *  descriptor associated with this connection and set success
357	 *  indication.  If the file couldnt be opened, set error:
358	 *  	"no such file or dir" - RMP_E_NOFILE
359	 *	"file table overflow" - RMP_E_BUSY
360	 *	"too many open files" - RMP_E_BUSY
361	 *	anything else         - RMP_E_OPENFILE
362	 */
363	if ((rconn->bootfd = open(filename, O_RDONLY, 0600)) < 0) {
364		rpl->r_brpl.rmp_retcode = (errno == ENOENT)? RMP_E_NOFILE:
365			(errno == EMFILE || errno == ENFILE)? RMP_E_BUSY:
366			RMP_E_OPENFILE;
367		retval = 0;
368	} else {
369		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
370		retval = 1;
371	}
372
373sendpkt:
374	syslog(LOG_INFO, "%s: request to boot %s (%s)",
375	       EnetStr(rconn), filename, retval? "granted": "denied");
376
377	rconn->rmplen = RMPBOOTSIZE(rpl->r_brpl.rmp_flnmsize);
378
379	return (retval & SendPacket(rconn));
380}
381
382/*
383**  SendReadRepl -- send a portion of the boot file to the requester.
384**
385**	Parameters:
386**		rconn - the reply packet to be formatted.
387**
388**	Returns:
389**		1 on success, 0 on failure.
390**
391**	Side Effects:
392**		none.
393*/
394int
395SendReadRepl(RMPCONN *rconn)
396{
397	int retval = 0;
398	RMPCONN *oldconn;
399	struct rmp_packet *rpl, *req;
400	int size = 0;
401	int madeconn = 0;
402
403	/*
404	 *  Find the old connection.  If one doesnt exist, create one only
405	 *  to return the error code.
406	 */
407	if ((oldconn = FindConn(rconn)) == NULL) {
408		if ((oldconn = NewConn(rconn)) == NULL)
409			return(0);
410		syslog(LOG_ERR, "SendReadRepl: no active connection (%s)",
411		       EnetStr(rconn));
412		madeconn++;
413	}
414
415	req = &rconn->rmp;		/* cache ptr to request packet */
416	rpl = &oldconn->rmp;		/* cache ptr to reply packet */
417
418	if (madeconn) {			/* no active connection above; abort */
419		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
420		retval = 1;
421		goto sendpkt;
422	}
423
424	/*
425	 *  Make sure Session ID's match.
426	 */
427	if (ntohs(req->r_rrq.rmp_session) !=
428	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
429	                                     ntohs(rpl->r_rrpl.rmp_session))) {
430		syslog(LOG_ERR, "SendReadRepl: bad session id (%s)",
431		       EnetStr(rconn));
432		rpl->r_rrpl.rmp_retcode = RMP_E_BADSID;
433		retval = 1;
434		goto sendpkt;
435	}
436
437	/*
438	 *  If the requester asks for more data than we can fit,
439	 *  silently clamp the request size down to RMPREADDATA.
440	 *
441	 *  N.B. I do not know if this is "legal", however it seems
442	 *  to work.  This is necessary for bpfwrite() on machines
443	 *  with MCLBYTES less than 1514.
444	 */
445	if (ntohs(req->r_rrq.rmp_size) > RMPREADDATA)
446		req->r_rrq.rmp_size = htons(RMPREADDATA);
447
448	/*
449	 *  Position read head on file according to info in request packet.
450	 */
451	GETWORD(req->r_rrq.rmp_offset, size);
452	if (lseek(oldconn->bootfd, (off_t)size, SEEK_SET) < 0) {
453		syslog(LOG_ERR, "SendReadRepl: lseek: %m (%s)",
454		       EnetStr(rconn));
455		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
456		retval = 1;
457		goto sendpkt;
458	}
459
460	/*
461	 *  Read data directly into reply packet.
462	 */
463	if ((size = read(oldconn->bootfd, &rpl->r_rrpl.rmp_data,
464	                 (int) ntohs(req->r_rrq.rmp_size))) <= 0) {
465		if (size < 0) {
466			syslog(LOG_ERR, "SendReadRepl: read: %m (%s)",
467			       EnetStr(rconn));
468			rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
469		} else {
470			rpl->r_rrpl.rmp_retcode = RMP_E_EOF;
471		}
472		retval = 1;
473		goto sendpkt;
474	}
475
476	/*
477	 *  Set success indication.
478	 */
479	rpl->r_rrpl.rmp_retcode = RMP_E_OKAY;
480
481sendpkt:
482	/*
483	 *  Set up assorted fields in reply packet.
484	 */
485	rpl->r_rrpl.rmp_type = RMP_READ_REPL;
486	COPYWORD(req->r_rrq.rmp_offset, rpl->r_rrpl.rmp_offset);
487	rpl->r_rrpl.rmp_session = req->r_rrq.rmp_session;
488
489	oldconn->rmplen = RMPREADSIZE(size);	/* set size of packet */
490
491	retval &= SendPacket(oldconn);		/* send packet */
492
493	if (madeconn)				/* clean up after ourself */
494		FreeConn(oldconn);
495
496	return (retval);
497}
498
499/*
500**  BootDone -- free up memory allocated for a connection.
501**
502**	Parameters:
503**		rconn - incoming boot complete packet.
504**
505**	Returns:
506**		1 on success, 0 on failure.
507**
508**	Side Effects:
509**		none.
510*/
511int
512BootDone(RMPCONN *rconn)
513{
514	RMPCONN *oldconn;
515	struct rmp_packet *rpl;
516
517	/*
518	 *  If we cant find the connection, ignore the request.
519	 */
520	if ((oldconn = FindConn(rconn)) == NULL) {
521		syslog(LOG_ERR, "BootDone: no existing connection (%s)",
522		       EnetStr(rconn));
523		return(0);
524	}
525
526	rpl = &oldconn->rmp;			/* cache ptr to RMP packet */
527
528	/*
529	 *  Make sure Session ID's match.
530	 */
531	if (ntohs(rconn->rmp.r_rrq.rmp_session) !=
532	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
533	                                    ntohs(rpl->r_rrpl.rmp_session))) {
534		syslog(LOG_ERR, "BootDone: bad session id (%s)",
535		       EnetStr(rconn));
536		return(0);
537	}
538
539	RemoveConn(oldconn);			/* remove connection */
540
541	syslog(LOG_INFO, "%s: boot complete", EnetStr(rconn));
542
543	return(1);
544}
545
546/*
547**  SendPacket -- send an RMP packet to a remote host.
548**
549**	Parameters:
550**		rconn - packet to be sent.
551**
552**	Returns:
553**		1 on success, 0 on failure.
554**
555**	Side Effects:
556**		none.
557*/
558int
559SendPacket(RMPCONN *rconn)
560{
561	/*
562	 *  Set Ethernet Destination address to Source (BPF and the enet
563	 *  driver will take care of getting our source address set).
564	 */
565	memmove((char *)&rconn->rmp.hp_hdr.daddr[0],
566	        (char *)&rconn->rmp.hp_hdr.saddr[0], RMP_ADDRLEN);
567	rconn->rmp.hp_hdr.len = htons(rconn->rmplen - sizeof(struct hp_hdr));
568
569	/*
570	 *  Reverse 802.2/HP Extended Source & Destination Access Pts.
571	 */
572	rconn->rmp.hp_llc.dxsap = htons(HPEXT_SXSAP);
573	rconn->rmp.hp_llc.sxsap = htons(HPEXT_DXSAP);
574
575	/*
576	 *  Last time this connection was active.
577	 */
578	(void) gettimeofday(&rconn->tstamp, (struct timezone *)0);
579
580	if (DbgFp != NULL)			/* display packet */
581		DispPkt(rconn,DIR_SENT);
582
583	/*
584	 *  Send RMP packet to remote host.
585	 */
586	return(BpfWrite(rconn));
587}
588