xref: /openbsd/usr.sbin/lpd/frontend.c (revision 771fbea0)
1 /*	$OpenBSD: frontend.c,v 1.2 2018/09/05 17:32:56 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2017 Eric Faurot <eric@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/tree.h>
20 
21 #include <errno.h>
22 #include <paths.h>
23 #include <pwd.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <syslog.h>
27 #include <unistd.h>
28 
29 #include "lpd.h"
30 
31 #include "log.h"
32 #include "proc.h"
33 
34 static void frontend_shutdown(void);
35 static void frontend_listen(struct listener *);
36 static void frontend_pause(struct listener *);
37 static void frontend_resume(struct listener *);
38 static void frontend_accept(int, short, void *);
39 static void frontend_dispatch_priv(struct imsgproc *, struct imsg *, void *);
40 static void frontend_dispatch_engine(struct imsgproc *, struct imsg *, void *);
41 
42 struct conn {
43 	SPLAY_ENTRY(conn)	 entry;
44 	struct listener		*listener;
45 	uint32_t		 id;
46 };
47 
48 static int conn_cmp(struct conn *, struct conn *);
49 
50 SPLAY_HEAD(conntree, conn);
51 SPLAY_PROTOTYPE(conntree, conn, entry, conn_cmp);
52 
53 static struct conntree conns;
54 static struct lpd_conf *tmpconf;
55 
56 static int
57 conn_cmp(struct conn *a, struct conn *b)
58 {
59 	if (a->id < b->id)
60 		return (-1);
61 	if (a->id > b->id)
62 		return (1);
63 	return (0);
64 }
65 
66 SPLAY_GENERATE(conntree, conn, entry, conn_cmp);
67 
68 void
69 frontend(int debug, int verbose)
70 {
71 	struct passwd *pw;
72 
73 	/* Early initialisation. */
74 	log_init(debug, LOG_LPR);
75 	log_setverbose(verbose);
76 	log_procinit("frontend");
77 	setproctitle("frontend");
78 
79 	SPLAY_INIT(&conns);
80 	lpr_init();
81 
82 	/* Drop priviledges. */
83 	if ((pw = getpwnam(LPD_USER)) == NULL)
84 		fatal("%s: getpwnam: %s", __func__, LPD_USER);
85 
86 	if (chroot(_PATH_VAREMPTY) == -1)
87 		fatal("%s: chroot", __func__);
88 	if (chdir("/") == -1)
89 		fatal("%s: chdir", __func__);
90 
91 	if (setgroups(1, &pw->pw_gid) ||
92 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
93 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
94 		fatal("%s: cannot drop privileges", __func__);
95 
96 	if (pledge("stdio unix inet recvfd sendfd", NULL) == -1)
97 		fatal("%s: pledge", __func__);
98 
99 	event_init();
100 
101 	signal(SIGPIPE, SIG_IGN);
102 
103 	/* Setup parent imsg socket. */
104 	p_priv = proc_attach(PROC_PRIV, 3);
105 	if (p_priv == NULL)
106 		fatal("%s: proc_attach", __func__);
107 	proc_setcallback(p_priv, frontend_dispatch_priv, NULL);
108 	proc_enable(p_priv);
109 
110 	event_dispatch();
111 
112 	frontend_shutdown();
113 }
114 
115 void
116 frontend_conn_closed(uint32_t connid)
117 {
118 	struct listener *l;
119 	struct conn key, *conn;
120 
121 	key.id = connid;
122 	conn = SPLAY_FIND(conntree, &conns, &key);
123 	if (conn == NULL)
124 		fatalx("%s: %08x unknown connid", __func__, connid);
125 
126 	l = conn->listener;
127 
128 	if (log_getverbose() > LOGLEVEL_CONN)
129 		log_debug("%08x close %s", conn->id,
130 		    log_fmt_proto(l->proto));
131 
132 	SPLAY_REMOVE(conntree, &conns, conn);
133 	free(conn);
134 
135 	if (l->pause)
136 		frontend_resume(l);
137 }
138 
139 static void
140 frontend_shutdown()
141 {
142 	struct listener *l;
143 
144 	TAILQ_FOREACH(l, &env->listeners, entry)
145 		close(l->sock);
146 
147 	log_debug("exiting");
148 
149 	exit(0);
150 }
151 
152 static void
153 frontend_listen(struct listener *l)
154 {
155 	if (log_getverbose() > LOGLEVEL_CONN)
156 		log_debug("listen %s %s", log_fmt_proto(l->proto),
157 		    log_fmt_sockaddr((struct sockaddr*)&l->ss));
158 
159 	if (listen(l->sock, 5) == -1)
160 		fatal("%s: listen", __func__);
161 
162 	frontend_resume(l);
163 }
164 
165 static void
166 frontend_pause(struct listener *l)
167 {
168 	struct timeval tv;
169 
170 	event_del(&l->ev);
171 
172 	tv.tv_sec = 2;
173 	tv.tv_usec = 0;
174 
175 	evtimer_set(&l->ev, frontend_accept, l);
176 	evtimer_add(&l->ev, &tv);
177 	l->pause = 1;
178 }
179 
180 static void
181 frontend_resume(struct listener *l)
182 {
183 	if (l->pause) {
184 		evtimer_del(&l->ev);
185 		l->pause = 0;
186 	}
187 	event_set(&l->ev, l->sock, EV_READ | EV_PERSIST, frontend_accept, l);
188 	event_add(&l->ev, NULL);
189 }
190 
191 static void
192 frontend_accept(int sock, short ev, void *arg)
193 {
194 	struct listener *l = arg;
195 	struct sockaddr_storage ss;
196 	struct sockaddr *sa;
197 	struct conn *conn;
198 	socklen_t len;
199 
200 	if (l->pause) {
201 		l->pause = 0;
202 		frontend_resume(l);
203 		return;
204 	}
205 
206 	conn = calloc(1, sizeof(*conn));
207 	if (conn == NULL)
208 		log_warn("%s: calloc", __func__);
209 
210 	sa = (struct sockaddr *)&ss;
211 	len = sizeof(ss);
212 	sock = accept4(sock, sa, &len, SOCK_NONBLOCK);
213 	if (sock == -1) {
214 		if (errno == ENFILE || errno == EMFILE)
215 			frontend_pause(l);
216 		else if (errno != EWOULDBLOCK && errno != EINTR &&
217 		    errno != ECONNABORTED)
218 			log_warn("%s: accept4", __func__);
219 		free(conn);
220 		return;
221 	}
222 
223 	if (conn == NULL) {
224 		close(sock);
225 		return;
226 	}
227 
228 	while (conn->id == 0 || SPLAY_FIND(conntree, &conns, conn))
229 		conn->id = arc4random();
230 	SPLAY_INSERT(conntree, &conns, conn);
231 	conn->listener = l;
232 
233 	if (log_getverbose() > LOGLEVEL_CONN)
234 		log_debug("%08x accept %s %s", conn->id,
235 		    log_fmt_proto(conn->listener->proto),
236 		    log_fmt_sockaddr((struct sockaddr*)&ss));
237 
238 	switch (l->proto) {
239 	case PROTO_LPR:
240 		lpr_conn(conn->id, l, sock, sa);
241 		break;
242 	default:
243 		fatalx("%s: unexpected protocol %d", __func__, l->proto);
244 	}
245 }
246 
247 static void
248 frontend_dispatch_priv(struct imsgproc *proc, struct imsg *imsg, void *arg)
249 {
250 	struct listener *l;
251 
252 	if (imsg == NULL) {
253 		log_debug("%s: imsg connection lost", __func__);
254 		event_loopexit(NULL);
255 		return;
256 	}
257 
258 	if (log_getverbose() > LOGLEVEL_IMSG)
259 		log_imsg(proc, imsg);
260 
261 	switch (imsg->hdr.type) {
262 	case IMSG_SOCK_ENGINE:
263 		if (imsg->fd == -1)
264 			fatalx("%s: engine socket not received", __func__);
265 		m_end(proc);
266 		p_engine = proc_attach(PROC_ENGINE, imsg->fd);
267 		proc_setcallback(p_engine, frontend_dispatch_engine, NULL);
268 		proc_enable(p_engine);
269 		break;
270 
271 	case IMSG_CONF_START:
272 		m_end(proc);
273 		if ((tmpconf = calloc(1, sizeof(*tmpconf))) == NULL)
274 			fatal("%s: calloc", __func__);
275 		TAILQ_INIT(&tmpconf->listeners);
276 		break;
277 
278 	case IMSG_CONF_LISTENER:
279 		if (imsg->fd == -1)
280 			fatalx("%s: listener socket not received", __func__);
281 		if ((l = calloc(1, sizeof(*l))) == NULL)
282 			fatal("%s: calloc", __func__);
283 		m_get_int(proc, &l->proto);
284 		m_get_sockaddr(proc, (struct sockaddr *)&l->ss);
285 		m_end(proc);
286 		l->sock = imsg->fd;
287 		TAILQ_INSERT_TAIL(&tmpconf->listeners, l, entry);
288 		break;
289 
290 	case IMSG_CONF_END:
291 		m_end(proc);
292 		TAILQ_FOREACH(l, &tmpconf->listeners, entry)
293 			frontend_listen(l);
294 		env = tmpconf;
295 		break;
296 
297 	default:
298 		fatalx("%s: unexpected imsg %s", __func__,
299 		    log_fmt_imsgtype(imsg->hdr.type));
300 	}
301 }
302 
303 static void
304 frontend_dispatch_engine(struct imsgproc *proc, struct imsg *imsg, void *arg)
305 {
306 	if (imsg == NULL) {
307 		log_debug("%s: imsg connection lost", __func__);
308 		event_loopexit(NULL);
309 		return;
310 	}
311 
312 	if (log_getverbose() > LOGLEVEL_IMSG)
313 		log_imsg(proc, imsg);
314 
315 	switch (imsg->hdr.type) {
316 	case IMSG_GETADDRINFO:
317 	case IMSG_GETADDRINFO_END:
318 	case IMSG_GETNAMEINFO:
319 		resolver_dispatch_result(proc, imsg);
320 		break;
321 
322 	case IMSG_LPR_ALLOWEDHOST:
323 	case IMSG_LPR_DISPLAYQ:
324 	case IMSG_LPR_RECVJOB:
325 	case IMSG_LPR_RECVJOB_CF:
326 	case IMSG_LPR_RECVJOB_DF:
327 	case IMSG_LPR_RMJOB:
328 		lpr_dispatch_engine(proc, imsg);
329 		break;
330 
331 	default:
332 		fatalx("%s: unexpected imsg %s", __func__,
333 		    log_fmt_imsgtype(imsg->hdr.type));
334 	}
335 }
336