xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/ipmgmtd/ipmgmt_main.c (revision 3e95bd4ab92abca814bd28e854607d1975c7dc88)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * The ipmgmtd daemon is started by ip-interface-management SMF service. This
29  * daemon is used to manage, mapping of 'address object' to 'interface name' and
30  * 'logical interface number', on which the address is created. It also provides
31  * a means to update the ipadm persistent data-store.
32  *
33  * The daemon tracks the <addrobj, lifname> mapping in-memory using a linked
34  * list `aobjmap'. Access to this list is synchronized using a readers-writers
35  * lock. The active <addrobj, lifname> mapping is kept in
36  * /etc/svc/volatile/ipadm/aobjmap.conf cache file, so that the mapping can be
37  * recovered when ipmgmtd exits for some reason (e.g., when ipmgmtd is restarted
38  * using svcadm or accidentally killed).
39  *
40  * Today, the persistent configuration of interfaces, addresses and protocol
41  * properties is kept in /etc/ipadm/ipadm.conf. The access to the persistent
42  * data store is synchronized using reader-writers lock `ipmgmt_dbconf_lock'.
43  *
44  * The communication between the library, libipadm.so and the daemon, is through
45  * doors RPC. The library interacts with the daemon using the commands defined
46  * by `ipmgmt_door_cmd_type_t'. Further any 'write' operation would require
47  * the `NETWORK_INTERFACE_CONFIG_AUTH' authorization.
48  *
49  * On reboot, the aforementioned SMF service starts the daemon before any other
50  * networking service that configures network IP interfaces is started.
51  * Afterwards, the network/physical SMF script instantiates the persisted
52  * network interfaces, interface properties and addresses.
53  */
54 
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <priv_utils.h>
58 #include <signal.h>
59 #include <stdlib.h>
60 #include <stdio.h>
61 #include <strings.h>
62 #include <sys/param.h>
63 #include <sys/stat.h>
64 #include <unistd.h>
65 #include "ipmgmt_impl.h"
66 
67 const char		*progname;
68 
69 /* readers-writers lock for reading/writing daemon data store */
70 pthread_rwlock_t	ipmgmt_dbconf_lock;
71 
72 /* tracks address object to {ifname|logical number|interface id} mapping */
73 ipmgmt_aobjmap_list_t	aobjmap;
74 
75 /* used to communicate failure to parent process, which spawned the daemon */
76 static int		pfds[2];
77 
78 /* file descriptor to IPMGMT_DOOR */
79 static int		ipmgmt_door_fd = -1;
80 
81 static void		ipmgmt_exit(int);
82 static int		ipmgmt_init();
83 static int		ipmgmt_init_privileges();
84 
85 static int
86 ipmgmt_db_init()
87 {
88 	int		fd, err;
89 
90 	/* creates the address object data store, if it doesn't exist */
91 	if ((fd = open(ADDROBJ_MAPPING_DB_FILE, O_CREAT|O_RDONLY,
92 	    IPADM_FILE_MODE)) == -1) {
93 		err = errno;
94 		ipmgmt_log(LOG_ERR, "could not open %s: %s",
95 		    ADDROBJ_MAPPING_DB_FILE, strerror(err));
96 		return (err);
97 	}
98 	(void) close(fd);
99 
100 	aobjmap.aobjmap_head = NULL;
101 	(void) pthread_rwlock_init(&aobjmap.aobjmap_rwlock, NULL);
102 
103 	/*
104 	 * If the daemon is recovering from a crash or restart, read the
105 	 * address object to logical interface mapping and build an in-memory
106 	 * representation of the mapping. That is, build `aobjmap' structure
107 	 * from address object data store.
108 	 */
109 	if ((err = ipadm_rw_db(ipmgmt_aobjmap_init, NULL,
110 	    ADDROBJ_MAPPING_DB_FILE, 0, IPADM_DB_READ)) != 0) {
111 		/* if there was nothing to initialize, it's fine */
112 		if (err != ENOENT)
113 			return (err);
114 		err = 0;
115 	}
116 
117 	(void) pthread_rwlock_init(&ipmgmt_dbconf_lock, NULL);
118 	return (err);
119 }
120 
121 static int
122 ipmgmt_door_init()
123 {
124 	int fd;
125 	int err;
126 
127 	/* create the door file for ipmgmtd */
128 	if ((fd = open(IPMGMT_DOOR, O_CREAT|O_RDONLY, IPADM_FILE_MODE)) == -1) {
129 		err = errno;
130 		ipmgmt_log(LOG_ERR, "could not open %s: %s",
131 		    IPMGMT_DOOR, strerror(err));
132 		return (err);
133 	}
134 	(void) close(fd);
135 
136 	if ((ipmgmt_door_fd = door_create(ipmgmt_handler, NULL,
137 	    DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
138 		err = errno;
139 		ipmgmt_log(LOG_ERR, "failed to create door: %s", strerror(err));
140 		return (err);
141 	}
142 	/*
143 	 * fdetach first in case a previous daemon instance exited
144 	 * ungracefully.
145 	 */
146 	(void) fdetach(IPMGMT_DOOR);
147 	if (fattach(ipmgmt_door_fd, IPMGMT_DOOR) != 0) {
148 		err = errno;
149 		ipmgmt_log(LOG_ERR, "failed to attach door to %s: %s",
150 		    IPMGMT_DOOR, strerror(err));
151 		goto fail;
152 	}
153 	return (0);
154 fail:
155 	(void) door_revoke(ipmgmt_door_fd);
156 	ipmgmt_door_fd = -1;
157 	return (err);
158 }
159 
160 static void
161 ipmgmt_door_fini()
162 {
163 	if (ipmgmt_door_fd == -1)
164 		return;
165 
166 	(void) fdetach(IPMGMT_DOOR);
167 	if (door_revoke(ipmgmt_door_fd) == -1) {
168 		ipmgmt_log(LOG_ERR, "failed to revoke access to door %s: %s",
169 		    IPMGMT_DOOR, strerror(errno));
170 	}
171 }
172 
173 static int
174 ipmgmt_init()
175 {
176 	int err;
177 
178 	if (signal(SIGTERM, ipmgmt_exit) == SIG_ERR ||
179 	    signal(SIGINT, ipmgmt_exit) == SIG_ERR) {
180 		err = errno;
181 		ipmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s",
182 		    strerror(err));
183 		return (err);
184 	}
185 	if ((err = ipmgmt_db_init()) != 0 || (err = ipmgmt_door_init()) != 0)
186 		return (err);
187 	return (0);
188 }
189 
190 /*
191  * This is called by the child process to inform the parent process to
192  * exit with the given return value.
193  */
194 static void
195 ipmgmt_inform_parent_exit(int rv)
196 {
197 	if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) {
198 		ipmgmt_log(LOG_WARNING,
199 		    "failed to inform parent process of status: %s",
200 		    strerror(errno));
201 		(void) close(pfds[1]);
202 		exit(EXIT_FAILURE);
203 	}
204 	(void) close(pfds[1]);
205 }
206 
207 /*ARGSUSED*/
208 static void
209 ipmgmt_exit(int signo)
210 {
211 	(void) close(pfds[1]);
212 	ipmgmt_door_fini();
213 	exit(EXIT_FAILURE);
214 }
215 
216 /*
217  * Set the uid of this daemon to the "ipadm" user. Finish the following
218  * operations before setuid() because they need root privileges:
219  *
220  *    - create the /etc/svc/volatile/ipadm directory;
221  *    - change its uid/gid to "ipadm"/"sys";
222  */
223 static int
224 ipmgmt_init_privileges()
225 {
226 	struct stat	statbuf;
227 	int err;
228 
229 	/* create the IPADM_TMPFS_DIR directory */
230 	if (stat(IPADM_TMPFS_DIR, &statbuf) < 0) {
231 		if (mkdir(IPADM_TMPFS_DIR, (mode_t)0755) < 0) {
232 			err = errno;
233 			goto fail;
234 		}
235 	} else {
236 		if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
237 			err = ENOTDIR;
238 			goto fail;
239 		}
240 	}
241 
242 	if ((chmod(IPADM_TMPFS_DIR, 0755) < 0) ||
243 	    (chown(IPADM_TMPFS_DIR, UID_NETADM, GID_NETADM) < 0)) {
244 		err = errno;
245 		goto fail;
246 	}
247 
248 	/*
249 	 * limit the privileges of this daemon and set the uid of this
250 	 * daemon to UID_NETADM
251 	 */
252 	if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, UID_NETADM,
253 	    GID_NETADM, NULL) == -1) {
254 		err = EPERM;
255 		goto fail;
256 	}
257 
258 	return (0);
259 fail:
260 	(void) ipmgmt_log(LOG_ERR, "failed to initialize the daemon: %s",
261 	    strerror(err));
262 	return (err);
263 }
264 
265 /*
266  * Keep the pfds fd open, close other fds.
267  */
268 /*ARGSUSED*/
269 static int
270 closefunc(void *arg, int fd)
271 {
272 	if (fd != pfds[1])
273 		(void) close(fd);
274 	return (0);
275 }
276 
277 /*
278  * We cannot use libc's daemon() because the door we create is associated with
279  * the process ID. If we create the door before the call to daemon(), it will
280  * be associated with the parent and it's incorrect. On the other hand if we
281  * create the door later, after the call to daemon(), parent process exits
282  * early and gives a false notion to SMF that 'ipmgmtd' is up and running,
283  * which is incorrect. So, we have our own daemon() equivalent.
284  */
285 static boolean_t
286 ipmgmt_daemonize(void)
287 {
288 	pid_t pid;
289 	int rv;
290 
291 	if (pipe(pfds) < 0) {
292 		(void) fprintf(stderr, "%s: pipe() failed: %s\n",
293 		    progname, strerror(errno));
294 		exit(EXIT_FAILURE);
295 	}
296 
297 	if ((pid = fork()) == -1) {
298 		(void) fprintf(stderr, "%s: fork() failed: %s\n",
299 		    progname, strerror(errno));
300 		exit(EXIT_FAILURE);
301 	} else if (pid > 0) { /* Parent */
302 		(void) close(pfds[1]);
303 
304 		/*
305 		 * Parent should not exit early, it should wait for the child
306 		 * to return Success/Failure. If the parent exits early, then
307 		 * SMF will think 'ipmgmtd' is up and would start all the
308 		 * depended services.
309 		 *
310 		 * If the child process exits unexpectedly, read() returns -1.
311 		 */
312 		if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) {
313 			(void) kill(pid, SIGKILL);
314 			rv = EXIT_FAILURE;
315 		}
316 
317 		(void) close(pfds[0]);
318 		exit(rv);
319 	}
320 
321 	/* Child */
322 	(void) close(pfds[0]);
323 	(void) setsid();
324 
325 	/* close all files except pfds[1] */
326 	(void) fdwalk(closefunc, NULL);
327 	(void) chdir("/");
328 	openlog(progname, LOG_PID, LOG_DAEMON);
329 	return (B_TRUE);
330 }
331 
332 int
333 main(int argc, char *argv[])
334 {
335 	int opt;
336 	boolean_t fg = B_FALSE;
337 
338 	progname = strrchr(argv[0], '/');
339 	if (progname != NULL)
340 		progname++;
341 	else
342 		progname = argv[0];
343 
344 	/* Process options */
345 	while ((opt = getopt(argc, argv, "f")) != EOF) {
346 		switch (opt) {
347 		case 'f':
348 			fg = B_TRUE;
349 			break;
350 		default:
351 			(void) fprintf(stderr, "Usage: %s [-f]\n", progname);
352 			return (EXIT_FAILURE);
353 		}
354 	}
355 
356 	if (!fg && getenv("SMF_FMRI") == NULL) {
357 		(void) fprintf(stderr,
358 		    "ipmgmtd is a smf(5) managed service and cannot be run "
359 		    "from the command line.\n");
360 		return (EINVAL);
361 	}
362 
363 	if (!fg && !ipmgmt_daemonize())
364 		return (EXIT_FAILURE);
365 
366 	if (ipmgmt_init_privileges() != 0)
367 		goto child_out;
368 
369 	if (ipmgmt_init() != 0)
370 		goto child_out;
371 
372 	/* Inform the parent process that it can successfully exit */
373 	ipmgmt_inform_parent_exit(EXIT_SUCCESS);
374 
375 	for (;;)
376 		(void) pause();
377 
378 child_out:
379 	/* return from main() forcibly exits an MT process */
380 	ipmgmt_inform_parent_exit(EXIT_FAILURE);
381 	return (EXIT_FAILURE);
382 }
383