/* #Specification: netconf / process management All the rc's script in /etc/rc.d are week. They simply start a bunch of daemon. If something goes wrong, they generally continue to fire daemon after daemon. All this is fragile. Instead, all this management is done using netconf. Netconf will probe around to find out if a daemon must be started, if there is enough configuration information for this daemon available. One goal is to allow netconf to "update" the daemon after making some changes to the network configuration. For example, you edit /etc/exports and run netconf -update after. It will kill mountd and start it again. */ #include #include #include "netconf.h" #include "daemoni.h" #include #include #include "../askrunlevel/askrunlevel.h" #include "netconf.m" #include "internal.h" #include #include #include #include #include #include #include NETCONF_HELP_FILE help_control ("control"); static const char INTERNSERV[]="INTERNSERV"; static const char subsys_services[]="services"; static LINUXCONF_SUBSYS sube (subsys_services ,P_MSG_U(M_SERVICESACT,"Services activity")); /* Get the operationnal state of a service 0 : active 1 : disabled temporarily 2 : disabled */ static int start_getstate (const char *service) { return linuxconf_getvalnum (INTERNSERV,service,0); } /* Start a daemon if it is not already started Return -1 if any error. */ int netconf_startstop( const char *name, const char *service, // Key name of the service so we // can check if the service is disabled int &go) // go may be reset to 0 if the service is disabled { int ret = -1; DAEMON_INTERNAL *dae = daemon_find (name); if (dae != NULL){ if(!dae->is_managed()){ /* #Spécification: netconf / daemons and commands / not managed The user may choose (advance feature) not to let netconf managed some command. This may severly break a system. Off course a user who do so is on his own. For example a user who decided to disable the management of ifconfig and make a mistake will have no network and a bundle or error. Back to square one. The purpose of netconf is to make sure everything works... At least, netconf will print a message in the log as a remainder that if it was allowed, netconf would have done this and this. */ ret = 0; net_prtlog (NETLOG_VERB ,MSG_U(X_WOULDHAVE,"Would have %s %s\n") ,go ? MSG_U(X_STARTED,"started") : MSG_U(X_STOPPED,"stopped") ,name); }else{ if (go && service != NULL){ int state = start_getstate (service); if (state != 0) go = 0; } if (go){ ret = dae->startif(); }else{ ret = dae->stop(); } } } return ret; } static int netconf_stop (const char *name) { int go = 0; return netconf_startstop (name,NULL,go); } static int netconf_startstop ( int level, const char *name, const char *service, int go, BOOTRCS &rcs) { //int ret = -1; //if (!rcs.hasequiv(service)){ int ret = netconf_startstop (name,service,go); //} ret |= rcs.startsome (service); if (go){ dropin_activate (level,service,rcs); } return ret; } static const char SV_kerneld[] = "kerneld"; static const char SV_crond[] = "crond"; static const char SV_ipaliases[]= "ipaliases"; static const char SV_portmap[] = "portmap"; static const char SV_inetd[] = "inetd"; static const char SV_syslog[] = "syslog"; static const char SV_klogd[] = "klogd"; static const char SV_lpd[] = "lpd"; static const char SV_routed[] = "routed"; static const char SV_gated[] = "gated"; static const char SV_ypbind[] = "ypbind"; static const char SV_amd[] = "amd"; static const char SV_nfs[] = "nfs"; static const char SV_rarp[] = "rarp"; static const char SV_firewall[] = "firewall"; PUBLIC SERVICE::SERVICE ( const char *_name, const char *_desc, int _state, SERVICE_OWNER _owner) { name.setfrom (_name); desc.setfrom (_desc); state = (char)_state; previous = state; owner = _owner; } class SERVICE_INTERNAL: public SERVICE{ /*~PROTOBEG~ SERVICE_INTERNAL */ public: SERVICE_INTERNAL (const char *_name, const char *_desc, int _state); int control (SERVICE_OPER); int edit (void); const char *getconfstatus (void); const char *getrunstatus (void); /*~PROTOEND~ SERVICE_INTERNAL */ }; PUBLIC SERVICE_INTERNAL::SERVICE_INTERNAL( const char *_name, const char *_desc, int _state) : SERVICE (_name,_desc,_state,OWNER_INTERNAL) { } PUBLIC int SERVICE_INTERNAL::edit() { int ret = 0; DIALOG dia; static const char *tbstate[]={ MSG_U(I_ENABLED,"Enabled"), MSG_U(I_TMPDISABLED,"Temp-disabled"), MSG_U(I_DISABLED,"Disabled"), NULL }; dia.newf_chkm (MSG_U(F_STATUS,"Status"),state,tbstate); int nof = 0; while (1){ MENU_STATUS code = dia.edit (MSG_U(T_ONESERVICE,"Control one service") ,MSG_U(I_ONESERVICE,"You can enable/disable a service\n" "or you can start and stop it manually") ,help_control,nof); if (code == MENU_ESCAPE || code == MENU_CANCEL){ break; }else{ ret = 1; break; } } return ret; } /* Start, stop, restart a service */ PUBLIC int SERVICE_INTERNAL::control (SERVICE_OPER) { return 0; } /* Return the status of the service */ PUBLIC const char *SERVICE_INTERNAL::getrunstatus () { return ""; } /* Return the configuration status of the service */ PUBLIC const char *SERVICE_INTERNAL::getconfstatus () { static const char *tbstate[]={ MSG_R(I_ENABLED), MSG_R(I_TMPDISABLED), MSG_R(I_DISABLED), NULL }; return tbstate[state]; } PUBLIC SERVICE *SERVICES::getitem(int no) const { return (SERVICE*)ARRAY::getitem(no); } static int service_cmp(const ARRAY_OBJ *p1, const ARRAY_OBJ *p2) { SERVICE *s1 = (SERVICE*)p1; SERVICE *s2 = (SERVICE*)p2; return s1->name.cmp(s2->name); } PUBLIC void SERVICES::sort() { ARRAY::sort (service_cmp); } /* Collect all services available on the system */ int start_getservtb(SERVICES &tb) { int ret = 0; SERVICECTL_API *apis[MAX_API_PROVIDERS]; int nbapi = servicectl_apis_init("netconf/start",apis); if (nbapi > 0){ for (int i=0; icollect (tb); } servicectl_apis_end(apis,nbapi); tb.add (new SERVICE_INTERNAL(SV_firewall ,MSG_U(T_FIREWALLING,"Firewalling") ,start_getstate (SV_firewall))); }else{ static const char *tbs[][2]={ {SV_kerneld, MSG_U(T_KERNELD,"Kernel's modules manager")}, {SV_crond, MSG_U(T_CROND,"Scheduled tasks daemon")}, {SV_ipaliases, MSG_U(T_ALIASES,"IP aliases")}, {SV_portmap, MSG_U(T_PORTMAP,"Port mapper")}, {SV_inetd, MSG_U(T_INETD,"Inetd server")}, {SV_syslog, MSG_U(T_SYSLOG,"system logger")}, {SV_klogd, MSG_U(T_KLOGD,"Kernel logger")}, {SV_lpd, MSG_U(T_LPD,"Printer spooler")}, {SV_routed, MSG_U(T_ROUTED,"Routed dynamic router")}, {SV_gated, MSG_U(T_GATED,"Gated dynamic router")}, {SV_ypbind, MSG_U(T_YPBIND,"NIS client")}, {SV_amd, MSG_U(T_AMD,"Auto-mounter")}, {SV_nfs, MSG_U(T_NFS,"NFS server")}, {SV_rarp, MSG_U(T_RARP,"RARP service")}, {SV_firewall, MSG_R(T_FIREWALLING)}, }; ret = (int)(sizeof(tbs)/sizeof(tbs[0])); for (int i=0; iowner == OWNER_INTERNAL){ int state = s->state; const char *key = s->name.get(); if (state){ linuxconf_replace (INTERNSERV,key,state); }else{ // The default is active so no need to save linuxconf_removeall (INTERNSERV,key); } } } } /* Start various dropins which are tied to a specific service (start after) and all the sysv init script which may start after this service */ static void start_activate (int level, const char *service, BOOTRCS &rcs) { dropin_activate (level,service,rcs); rcs.startsome (service); } /* Establish a run level by starting, restarting or stopping some daemon. This functions assumes that linuxconf is handling many services internally */ static void netconf_runlevel0_internal( int level, // 0 = Minimal local service (loopback) // 1 = Basic client mode // 2 = full client/server mode // -1 == use the same as the last time // This is used by netconf --update BOOTRCS &rcs) // Optionnal SysV init script to start { dropin_activate_new(); /* #Specification: crond netconf make sure that crond is active (unless told no to do so). This is not really related to networking but since netconf manage almost all other daemons in the system, why not doing it there. Currently, it checks that crond is running and that's all. I am not aware of any reason (beside maintenance mode maybe) why crond would have to be shutdown or restart or signaled. So netconf only check it is active. */ net_section (MSG_U(S_SECTBASE,"Checking base configuration")); net_title (MSG_U(S_KMODULES,"Checking kernel's modules")); modules_check(); net_title (MSG_U(S_CROND,"Cron daemon")); netconf_startstop (0,"crond",SV_crond,1,rcs); /* #Specification: hostname / must be set If the hostname can't be set, the rest of the networking won't be activated. A message is printed, but nothing can be started. */ net_title (MSG_U(S_MOUNTALL,"Mounting local volumes")); fstab_checkmount (true); net_title (MSG_U(S_FIXPERM,"Checking files permissions")); fixperm_check(); char msg[10000]; if (!netconf_netok(msg) || netconf_sethostname() == -1){ xconf_error (MSG_U(E_IVLBASIC ,"Invalid basic configuration of the host\n%s\n") ,msg); }else{ rcs.startsome(NULL); if (level != -1){ netconf_setnetlevel(level); }else{ level = netconf_getnetlevel(); } net_title (MSG_U(S_LOOPBACK,"Setting network loopback")); netconf_setloopback(); net_title (MSG_U(S_ALIASLOOP,"Setting IP aliases on network loopback")); if (start_getstate(SV_ipaliases)==0) ipalias_setup("lo"); // See below net_title (MSG_U(S_PORTMAP,"Starting the RPC portmapper")); netconf_startstop (1,"rpc.portmap",SV_portmap,1,rcs); net_title (MSG_U(S_INETD,"Starting inetd")); netconf_startstop (1,"inetd",SV_inetd,1,rcs); net_title (MSG_U(S_SYSLOG,"Starting system loggers")); netconf_startstop (1,"syslogd",SV_syslog,1,rcs); netconf_startstop (1,"klogd",SV_klogd,1,rcs); net_title (MSG_U(S_PRTSPOOL,"Starting printer spooler")); netconf_startstop (1,"lpd",SV_lpd,1,rcs); /* #Specification: module / probing We let the modules do something at 4 different places during the probing for configuration changes. # At the end of the probing for local mode At the end of the client mode At the end of the server mode # */ module_probe (0,level); dropin_activate (0,"",rcs); if (level > 0){ net_section (MSG_U(S_SECTCLIENT,"Setting client networking")); net_title (MSG_U(S_IPDEVICES,"Configure network IP devices")); SSTRINGS reconf; netconf_setdevices(NULL,reconf); net_title (MSG_U(S_IPXDEVICES,"Configure network IPX devices")); ipx_set(NULL,reconf); net_title (MSG_U(S_IPROUTES,"Configure IP routes")); route_install(NULL,true,false); rcs.startsome ("network"); net_title (MSG_U(S_ROUTEDS,"Start routing daemons")); netconf_startstop (1,"routed",SV_routed,1,rcs); netconf_startstop (1,"gated",SV_gated,1,rcs); module_sendmessage ("startnamed",0,NULL); if (dns_ping()!=-1){ net_title (MSG_U(S_NIS,"Starting NIS")); netconf_startstop (1,"ypbind",SV_ypbind,1,rcs); /* #Specification: netconf / datetime / updating if configured, datetime_getfromnet() always perform an action. We avoid doing it while in simulation mode as it gives the impression the system is never in sync with its configuration. "netconf --status" would always complain. */ if (!simul_ison()) datetime_getfromnet(); net_title (MSG_U(S_AMD,"Starting automounter")); netconf_startstop (1,"amd",SV_amd,1,rcs); net_title (MSG_U(S_MOUNTNET,"Mounting network volumes")); fstab_checkmount (false); rcs.startsome ("mountnet"); module_probe (1,level); dropin_activate (1,"",rcs); if (level > 1){ net_section (MSG_U(S_SECTSERVER,"Setting server networking")); /* #Specification: netconf / aliases / activating IP aliases are only activated in server mode. I don't see much usage for it in another mode. They are not disactivated when going back in client mode though. There is a small exception. The alias for the loopback device are always activated. If someone setup an alias on "lo", better activate it when "lo" is. */ net_title (MSG_U(S_ALIAS,"Setting IP aliases on net devices")); if (start_getstate(SV_ipaliases)==0) ipalias_setup(); // Enable the route which depends on aliases route_install(NULL,false,true); start_activate (2,SV_ipaliases,rcs); net_title (MSG_U(S_NFS,"Starting NFS service")); netconf_startstop (2,"rpc.mountd",SV_nfs,1,rcs); netconf_startstop (2,"rpc.nfsd",SV_nfs,1,rcs); module_probe (2,level); dropin_activate (2,"",rcs); }else{ net_section (MSG_U(S_UNSETSERVER,"Unsetting server networking")); net_title (MSG_U(S_STOPNFS,"Stopping NFS service")); netconf_stop ("rpc.nfsd"); netconf_stop ("rpc.mountd"); } } }else{ net_section (MSG_U(S_UNSETCLIENT,"Unsetting networking")); net_title (MSG_U(S_KSENDMAIL,"Stopping sendmail")); netconf_stop ("sendmail"); net_title (MSG_U(S_KROUTEDS,"Stopping routing daemons")); netconf_stop ("gated"); netconf_stop ("routed"); net_title (MSG_U(S_KAMD,"Stopping automounter")); netconf_stop ("amd"); net_title (MSG_R(S_STOPNFS)); netconf_stop ("rpc.nfsd"); netconf_stop ("rpc.mountd"); } dropin_deactivate (level); rcs.startrest(); } } /* Start whatever has to be started before the sysv scripts This function is called by the "netconf --s00linuxconf" command */ void netconf_s00linuxconf(int level) { dropin_activate_new(); net_section (MSG_R(S_SECTBASE)); net_title (MSG_R(S_KMODULES)); modules_check(); net_title (MSG_R(S_MOUNTALL)); fstab_checkmount (true); net_title (MSG_R(S_FIXPERM)); fixperm_check(); module_probe (0,level); } /* Start whatever has to be started after the sysv scripts This function is called by the "netconf --s99linuxconf" command */ void netconf_s99linuxconf(int level) { dialog_settimeout (15,MENU_ESCAPE,true); BOOTRCS rcs (false); dropin_activate (0,"",rcs); module_probe (1,level); dropin_activate (1,"",rcs); module_probe (2,level); dropin_activate (2,"",rcs); dialog_settimeout (-1,MENU_ESCAPE,false); } /* Establish a run level by starting, restarting or stopping some daemon. This functions assumes that linuxconf is running on an enhanced distribution where most/all sysv init script are enhanced */ static void netconf_runlevel0_enhanced( int level, // 0 = Minimal local service (loopback) // 1 = Basic client mode // 2 = full client/server mode // -1 == use the same as the last time // This is used by netconf --update BOOTRCS &rcs) // Optionnal SysV init script to start { if (level == -1) level = 2; // No concept of network level // in this context. Maybe one day!! netconf_s00linuxconf(level); char msg[10000]; if (!netconf_netok(msg)){ xconf_error (MSG_R(E_IVLBASIC),msg); }else{ rcs.startrest(); netconf_s99linuxconf(level); } } /* Establish a run level by starting, restarting or stopping some daemon. */ void netconf_runlevel( int level, // 0 = Minimal local service (loopback) // 1 = Basic client mode // 2 = full client/server mode // -1 == use the same as the last time // This is used by netconf --update BOOTRCS &rcs, // Optionnal SysV init script to start bool booting) { /*# Specification: netconf / setting runlevel / timeout on msgs When netconf activate the different networking services and other, it may generate different error message. A timeout of 15 seconds is established. If there is no operator, netconf will continue by itself. This avoid to have a server with a small configuration problem failing to reboot because it wait for a single */ dialog_settimeout ( booting ? runlevels_getdiatimeout() : 15 ,MENU_ESCAPE,true); int is_simul = simul_ison(); while (1){ net_resetnberr(); daemon_setsession(1); simul_setdisable(false); process_flushcache(); if (distrib_isenhanced()){ netconf_runlevel0_enhanced(level,rcs); }else{ netconf_runlevel0_internal(level,rcs); } netconf_getlastmsgs(); if (!daemon_wasconfig() || is_simul) break; } simul_setdisable(false); daemon_setsession(0); if (net_getnberr() && dialog_yesno (MSG_U(Q_SOMEERRORS,"There were some errors") ,MSG_U(Q_SEELOGS ,"Some errors were reported\n" "Do you want to examine the logs") ,help_nil) == MENU_YES){ net_showlog(); } dialog_settimeout (-1,MENU_ESCAPE,false); } /* Establish a run level by starting, restarting or stopping some daemon. */ void netconf_runlevel( int level) // 0 = Minimal local service (loopback) // 1 = Basic client mode // 2 = full client/server mode // -1 == use the same as the last time // This is used by netconf --update { BOOTRCS rcs (true); extern CONFIG_FILE f_rcd; FILE_CFG *fin = f_rcd.fopen ("r"); if (fin != NULL){ char buf[PATH_MAX]; if (fgets(buf,sizeof(buf)-1,fin)!=NULL){ strip_end (buf); if (buf[0] != '\0'){ rcs.readdir (buf,NULL); } } fclose (fin); }else{ const char *dir = distrib_getval ("runlevel.dir"); if (dir != NULL) rcs.readdir(dir,NULL); } netconf_runlevel (level,rcs,false); } /* Dialog to control service (internal, dropins, sysv script) activity */ void service_control() { glocal SERVICES services; glocal bool title_done = false; (MSG_U(T_SERVICECTL,"Service control") ,MSG_U(I_SERVICECTL,"You can selectivly enable or disable\n" "any services. You can disable services on a permanent\n" "basis or on a temporary basis. Temporary means that\n" "Linuxconf will remind you about those and will reactivate\n" "them at the next reboot.") ,help_control); newf_head (MSG_U(H_SERVICESTATUS,"Name\tEnabled\tRunning")); sortable(); sortpolicy ("aaa"); glocal.services.remove_all(); start_getservtb (glocal.services); dropin_getservtb(glocal.services); glocal.services.sort(); for (int j=0; jname.get(); new_menuitemf (name,"%s\t%s",s->getconfstatus() ,s->getrunstatus()); } SERVICE *s = glocal.services.getitem(no); if (s->edit()){ // Something has changed if (s->previous != s->state){ if (!glocal.title_done){ net_introlog (NETINTRO_SERVICES); glocal.title_done = true; } net_prtlog (NETLOG_VERB ,MSG_U(I_SWITCHING,"Switch service %s to %s\n") ,s->name.get() ,s->getconfstatus()); linuxconf_setcursys (subsys_services); start_setservtb (glocal.services); dropin_setservtb (glocal.services); if (!dropin_savestate()) linuxconf_save(); } } } /* Print a list of hint allowing a script to configure a service properly */ int netconf_hint (int argc, char *argv[]) { int ret = -1; const char *service = argv[0]; const char *nextarg = argv[1]; /* Normally, this hinting is not interactive at all. In case of some errors, linuxconf may pop a message. The user has no way to see and interact with this message most of the time. So we put a 0 second timeout on it. Again, these popups should not happen, but nevertheless have been seen. One case happen when linuxconf is trying to probe the IP aliases and there is no alias ability in the kernel. Given that the hinting code is built using the same code used for activation control (used by non-enhanced distributions), we inherit some popups (signaling errors). The 0 seconds timeout is a patch. The solution will be to change the error mode to batch. */ dialog_settimeout (0,MENU_ESCAPE,true); error_setmode (true); simul_sethintflag(1); if (strcmp(service,"netdev")==0){ if (argc == 2){ SSTRINGS reconf; ret = netconf_setdevices (nextarg,reconf); ipx_set (nextarg,reconf); }else{ SSTRINGS reconf; if (netconf_setdevices (NULL,reconf)!=-1 && ipx_set (NULL,reconf) != -1){ reconf.sort(); reconf.remove_dups(); SSTRING tmp; for (int i=0; iget()); } net_hint ("DEV_RECONF",tmp.get()); if (ipalias_setup () != -1 && route_install(NULL,true,true) != -1){ ret = 0; } }else{ ret = -1; } } }else if (strcmp(service,"ipalias")==0){ if (argc == 2){ ret = ipalias_setup (nextarg); }else{ ret = ipalias_setup (); } }else if (strcmp(service,"routing")==0){ if (argc == 2){ ret = route_install(nextarg,true,true); }else{ ret = route_install(NULL,true,true); } }else{ ret = module_hint (service); if (ret==LNCF_NOT_APPLICABLE){ fprintf (stderr,MSG_U(E_SERVICE,"Unknown service %s\n"),service); } } simul_sethintflag(0); error_setmode (false); dialog_settimeout (-1,MENU_ESCAPE,false); return ret; }