/** * service_register - Register service, task or run commands * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) * @line: A complete command line with -- separated description text * @mtime: The modification time if service is loaded from /etc/finit.d * @username: Optional username to run service as, or %NULL to run as root * * This function is used to register commands to be run on different * system runlevels with optional username. The @type argument details * if it's service to bo monitored/respawned (daemon), a one-shot task * or a command that must run in sequence and not in parallell, like * service and task commands do. * * The @line can optionally start with a username, denoted by an @ * character. Like this: * * service @username [!0-6,S] <!EV> /path/to/daemon arg -- Description * task @username [!0-6,S] /path/to/task arg -- Description * run @username [!0-6,S] /path/to/cmd arg -- Description * inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description * * If the username is left out the command is started as root. The [] * brackets denote the allowed runlevels, if left out the default for a * service is set to [2-5]. Allowed runlevels mimic that of SysV init * with the addition of the 'S' runlevel, which is only run once at * startup. It can be seen as the system bootstrap. If a task or run * command is listed in more than the [S] runlevel they will be called * when changing runlevel. * * Services (daemons, not inetd services) also support an optional <!EV> * argument. This is for services that, e.g., require a system gateway * or interface to be up before they are started. Or restarted, or even * SIGHUP'ed, when the gateway changes or interfaces come and go. The * special case when a service is declared with <!> means it does not * support SIGHUP but must be STOP/START'ed at system reconfiguration. * * Supported service events are: GW, IFUP[:ifname], IFDN[:ifname], where * the interface name (:ifname) is optional. Actully, the check with a * service event declaration is string based, so 'IFUP:ppp' will match * any of "IFUP:ppp0" or "IFUP:pppoe1" sent by the netlink.so plugin. * * For multiple instances of the same command, e.g. multiple DHCP * clients, the user must enter an ID, using the :ID syntax. * * service :1 /sbin/udhcpc -i eth1 * service :2 /sbin/udhcpc -i eth2 * * Without the :ID syntax Finit will overwrite the first service line * with the contents of the second. The :ID must be [1,MAXINT]. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ int service_register(int type, char *line, time_t mtime, char *username) { int i = 0; int id = 1; /* Default to ID:1 */ #ifndef INETD_DISABLED int forking = 0; #endif char *service = NULL, *proto = NULL, *ifaces = NULL; char *cmd, *desc, *runlevels = NULL, *events = NULL; svc_t *svc; plugin_t *plugin = NULL; if (!line) { _e("Invalid input argument."); return errno = EINVAL; } desc = strstr(line, "-- "); if (desc) *desc = 0; cmd = strtok(line, " "); if (!cmd) { incomplete: _e("Incomplete service, cannot register."); return errno = ENOENT; } while (cmd) { if (cmd[0] != '/' && strchr(cmd, '/')) service = cmd; /* inetd service/proto */ #ifndef INETD_DISABLED else if (!strncasecmp(cmd, "nowait", 6)) forking = 1; else if (!strncasecmp(cmd, "wait", 4)) forking = 0; #endif else if (cmd[0] == '@') /* @username[:group] */ username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; else if (cmd[0] == '<') /* [!ev] */ events = &cmd[1]; else if (cmd[0] == ':') /* :ID */ id = atoi(&cmd[1]); else break; /* Check if valid command follows... */ cmd = strtok(NULL, " "); if (!cmd) goto incomplete; } /* Example: inetd ssh/tcp@eth0,eth1 or 222/tcp@eth2 */ if (service) { ifaces = strchr(service, '@'); if (ifaces) *ifaces++ = 0; proto = strchr(service, '/'); if (!proto) goto incomplete; *proto++ = 0; } #ifndef INETD_DISABLED /* Find plugin that provides a callback for this inetd service */ if (type == SVC_TYPE_INETD) { if (!strncasecmp(cmd, "internal", 8)) { char *ptr, *ps = service; /* internal.service */ ptr = strchr(cmd, '.'); if (ptr) { *ptr++ = 0; ps = ptr; } plugin = plugin_find(ps); if (!plugin || !plugin->inetd.cmd) { _e("Inetd service %s has no internal plugin, skipping.", service); return errno = ENOENT; } } /* Check if known inetd, then add ifnames for filtering only. */ svc = find_inetd_svc(cmd, service, proto); if (svc) goto inetd_setup; id = svc_next_id(cmd); } #endif svc = svc_find(cmd, id); if (!svc) { _d("Creating new svc for %s id #%d type %d", cmd, id, type); svc = svc_new(cmd, id, type); if (!svc) { _e("Out of memory, cannot register service %s", cmd); return errno = ENOMEM; } } /* New, recently modified or unchanged ... used on reload. */ svc_check_dirty(svc, mtime); if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); if (username) { char *ptr = strchr(username, ':'); if (ptr) { *ptr++ = 0; strlcpy(svc->group, ptr, sizeof(svc->group)); } strlcpy(svc->username, username, sizeof(svc->username)); } if (plugin) { /* Internal plugin provides this service */ svc->inetd.cmd = plugin->inetd.cmd; } else { strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); while ((cmd = strtok(NULL, " "))) strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); svc->args[i][0] = 0; plugin = plugin_find(svc->cmd); if (plugin && plugin->svc.cb) { svc->cb = plugin->svc.cb; svc->dynamic = plugin->svc.dynamic; svc->dynamic_stop = plugin->svc.dynamic_stop; } } svc->runlevels = conf_parse_runlevels(runlevels); _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); if (type == SVC_TYPE_SERVICE) conf_parse_events(svc, events); #ifndef INETD_DISABLED if (svc_is_inetd(svc)) { char *iface, *name = service; if (svc->inetd.cmd && plugin) name = plugin->name; if (inetd_new(&svc->inetd, name, service, proto, forking, svc)) { _e("Failed registering new inetd service %s.", service); inetd_del(&svc->inetd); return svc_del(svc); } inetd_setup: if (!ifaces) { _d("No specific iface listed for %s, allowing ANY.", service); return inetd_allow(&svc->inetd, NULL); } for (iface = strtok(ifaces, ","); iface; iface = strtok(NULL, ",")) { if (iface[0] == '!') inetd_deny(&svc->inetd, &iface[1]); else inetd_allow(&svc->inetd, iface); } } #endif return 0; }
/** * svc_register - Register service, task or run commands * @type: %SVC_CMD_SERVICE(0), %SVC_CMD_TASK(1), %SVC_CMD_RUN(2) * @line: A complete command line with -- separated description text * @username: Optional username to run service as, or %NULL to run as root * * This function is used to register commands to be run on different * system runlevels with optional username. The @type argument details * if it's service to bo monitored/respawned (daemon), a one-shot task * or a command that must run in sequence and not in parallell, like * service and task commands do. * * The @line can optionally start with a username, denoted by an @ * character. Like this: * * "service @username [!0-6,S] /path/to/daemon arg -- Description text" * "task @username [!0-6,S] /path/to/task arg -- Description text" * "run @username [!0-6,S] /path/to/cmd arg -- Description text" * "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description" * * If the username is left out the command is started as root. * Inetd services, launched on demand (SVC_CMD_INETD) * The [] brackets are there to denote the allowed runlevels. Allowed * runlevels mimic that of SysV init with the addition of the 'S' * runlevel, which is only run once at startup. It can be seen as the * system bootstrap. If a task or run command is listed in more than the * [S] runlevel they will be called when changing runlevel. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ int svc_register(int type, char *line, char *username) { int i = 0; #ifndef INETD_DISABLED int forking = 0; #endif char *service = NULL, *proto = NULL, *iface = NULL, *port = NULL; char *cmd, *desc, *runlevels = NULL; svc_t *svc; plugin_t *plugin = NULL; if (!line) { _e("Invalid input argument."); return errno = EINVAL; } desc = strstr(line, "-- "); if (desc) *desc = 0; cmd = strtok(line, " "); if (!cmd) { incomplete: _e("Incomplete service, cannot register."); return errno = ENOENT; } while (cmd) { if (cmd[0] != '/' && strchr(cmd, '/')) service = cmd; /* inetd service/proto */ #ifndef INETD_DISABLED else if (!strncasecmp(cmd, "nowait", 6)) forking = 1; else if (!strncasecmp(cmd, "wait", 4)) forking = 0; #endif else if (cmd[0] == '@') /* @username[:group] */ username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; else break; /* Check if valid command follows... */ cmd = strtok(NULL, " "); if (!cmd) goto incomplete; } /* Example: inetd ssh@eth0:222/tcp */ if (service) { proto = strchr(service, '/'); if (!proto) goto incomplete; *proto++ = 0; port = strchr(service, ':'); if (port) *port++ = 0; iface = strchr(service, '@'); if (iface) *iface++ = 0; } #ifndef INETD_DISABLED /* Find plugin that provides a callback for this inetd service */ if (type == SVC_CMD_INETD && !strncasecmp(cmd, "internal", 8)) { plugin = plugin_find(service); if (!plugin || !plugin->inetd.cmd) { _e("Inetd service %s has no internal plugin, skipping.", service); return errno = ENOENT; } } /* Check if known inetd, then add ifname for filtering only. */ svc = svc_find_inetd(cmd, service, proto, port); if (svc) return inetd_allow(&svc->inetd, iface); #endif svc = svc_new(); if (!svc) { _e("Out of memory, cannot register service %s", cmd); return errno = ENOMEM; } svc->type = type; if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); if (username) { char *ptr = strchr(username, ':'); if (ptr) { *ptr++ = 0; strlcpy(svc->group, ptr, sizeof(svc->group)); } strlcpy(svc->username, username, sizeof(svc->username)); } #ifndef INETD_DISABLED if (svc->type == SVC_CMD_INETD) { int result; result = inetd_new(&svc->inetd, service, proto, forking); result += inetd_init(&svc->inetd, svc, iface, port); if (result) { _e("Failed registering new inetd service %s.", service); inetd_del(&svc->inetd); return svc_del(svc); } } #endif if (plugin) { /* Internal plugin provides this service */ svc->inetd.cmd = plugin->inetd.cmd; } else { /* External program to call */ strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); while ((cmd = strtok(NULL, " "))) strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); svc->args[i][0] = 0; plugin = plugin_find(svc->cmd); if (plugin && plugin->svc.cb) { svc->cb = plugin->svc.cb; svc->dynamic = plugin->svc.dynamic; svc->dynamic_stop = plugin->svc.dynamic_stop; } } svc->runlevels = parse_runlevels(runlevels); _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); return 0; }