#include #include #include #include #include "../../paths.h" #include #include "nfsconf.h" #include "nfsconf.m" #include static HELP_FILE helpf ("nfsconf","exports"); static const char subsys_nfsserv[] = "nfsserv"; static LINUXCONF_SUBSYS subb (subsys_nfsserv ,P_MSG_U(M_NFSSERV,"NFS server")); CONFIG_FILE f_exports (ETC_EXPORTS,helpf ,CONFIGF_MANAGED|CONFIGF_OPTIONAL|CONFIGF_PROBED ,subsys_nfsserv); /* #Specification: /etc/exports / format The /etc/exports file separate lines for each portion of the file system exported. The format of each line is # mount_point client_host[(option)] [ client_host[(options) ...] # */ class HOST_OPT: public ARRAY_OBJ{ public: SSTRING host; char root; // Root access allowed char link_relative; char rw; char secure; /*~PROTOBEG~ HOST_OPT */ public: HOST_OPT (const char *_host, const char *options); HOST_OPT (void); private: void init (void); public: void write (char *buf); ~HOST_OPT (void); /*~PROTOEND~ HOST_OPT */ }; PRIVATE void HOST_OPT::init() { root = 0; link_relative = 0; rw = 0; secure = 1; } PUBLIC HOST_OPT::HOST_OPT (const char *_host, const char *options) { init(); host.setfrom (_host); if (options != NULL){ while (1){ const char *start = options = str_skip (options); if (*options == '\0'){ break; }else{ while (*options > ' ' && *options != ',') options++; int len = (int)(options - start); if (strncmp(start,"rw",len)==0){ rw = 1; }else if (strncmp(start,"ro",len)==0){ rw = 0; }else if (strncmp(start,"no_root_squash",len)==0){ root = 1; }else if (strncmp(start,"root_squash",len)==0){ root = 0; }else if (strncmp(start,"link_relative",len)==0){ link_relative = 1; }else if (strncmp(start,"link_absolute",len)==0){ link_relative = 0; }else if (strncmp(start,"secure",len)==0){ secure = 1; }else if (strncmp(start,"insecure",len)==0){ secure = 0; }else{ fprintf (stderr,"Unknown option %s\n",start); } options = str_skip(options); if (*options == ',') options++; } } } } PUBLIC HOST_OPT::HOST_OPT() { init(); } PUBLIC HOST_OPT::~HOST_OPT() { } static char exports_wopt (const char *str, char *buf, char sep) { if (str[0] != '\0'){ int len = strlen(buf); buf[len++] = sep; sep = ','; strcpy (buf+len,str); } return sep; } /* Format a host export specification like in /etc/exports */ PUBLIC void HOST_OPT::write (char *buf) { host.copy (buf); /* #Specification: /etc/exports / default values When writing back the /etc/exports file, we try to avoid the generation of default value for option. The idea is to keep the file readable by avoiding long option name. We are making an exception for option ro/rw, because it is terribly important and because it is a short one. */ // The keyword associated with the default behavior is set to "" // so we know we don't have to generate it. char sep = '('; static char *tb_rw[]={"ro","rw"}; sep = exports_wopt (tb_rw[rw],buf,sep); static char *tb_root[]={"","no_root_squash"}; sep = exports_wopt (tb_root[root],buf,sep); static char *tb_link[]={"","link_relative"}; sep = exports_wopt (tb_link[link_relative],buf,sep); static char *tb_sec[]={"insecure",""}; sep = exports_wopt (tb_sec[secure],buf,sep); if (sep == ',') strcat (buf,")"); strcat (buf," "); } class HOSTS_OPT: public ARRAY{ /*~PROTOBEG~ HOSTS_OPT */ public: void add (HOST_OPT *o); void add (const char *_host, const char *_option); HOST_OPT *getitem (int no); char *parse_add (const char *pt, int &); void write (FILE_CFG *fout); /*~PROTOEND~ HOSTS_OPT */ }; PUBLIC void HOSTS_OPT::add (const char *_host, const char *_option) { HOST_OPT *opt = new HOST_OPT(_host,_option); if (opt != NULL) ARRAY::add (opt); } PUBLIC void HOSTS_OPT::add (HOST_OPT *o) { ARRAY::add (o); } /* Parse a string to extract a host(option) spec. Return a pointer after the spec. If the spec is valid, it is add to the array. */ PUBLIC char *HOSTS_OPT::parse_add (const char *pt, int &) { char tmp[200]; char *dst = tmp; while (*pt > ' ' && *pt != '(') *dst++ = *pt++; *dst = '\0'; pt = str_skip(pt); if (*pt == '('){ pt++; char opt[1000]; dst = opt; while (*pt != '\0' && *pt != ')') *dst++ = *pt++; *dst = '\0'; if (*pt == ')') pt++; add (tmp,opt); }else if (dst != tmp){ add (tmp,NULL); } return (char*)pt; } PUBLIC HOST_OPT *HOSTS_OPT::getitem(int no) { return (HOST_OPT *)ARRAY::getitem(no); } PUBLIC void HOSTS_OPT::write (FILE_CFG *fout) { for (int i=0; iwrite(buf); fputs (buf,fout); } } class EXPORT_FS: public ARRAY_OBJ{ SSTRING comment; SSTRING path; HOSTS_OPT tbhost; /*~PROTOBEG~ EXPORT_FS */ public: EXPORT_FS (const char *buf, int noline); int edit (void); void format_info (SSTRING&buf); const char *getpath (void); private: void setonehost (DIALOG&dia, HOST_OPT *o); public: void write (FILE_CFG *fout); ~EXPORT_FS (void); /*~PROTOEND~ EXPORT_FS */ }; /* Parse one line of /etc/exports */ PUBLIC EXPORT_FS::EXPORT_FS (const char *buf, int noline) { /* #Specification: /etc/exports / configurator The configurator remember comments, either full line or at the end of a line. It read it and write it back. This means that /etc/exports may still be edited by hand or by netconf. */ char *pt = str_skip(buf); if (*pt == '#'){ comment.setfrom (pt+1); }else if (*pt != '\0'){ pt = path.copyword (pt); if (!path.is_empty()){ int err = 0; while (1){ pt = str_skip (pt); if (*pt == '#'){ comment.setfrom (pt+1); break; }else if (*pt == '\0'){ break; }else{ pt = tbhost.parse_add (pt,err); } } if (err){ xconf_error (MSG_U(E_IVLEXPORTS ,"Invalid line %d in file %s\n%s\n") ,noline,ETC_EXPORTS,buf); path.setfrom (""); comment.setfrom (""); }else if (tbhost.getnb()==0){ // Set default value when there is only a path supplied tbhost.parse_add ("(rw)",err); } } } } PUBLIC EXPORT_FS::~EXPORT_FS () { } PUBLIC const char *EXPORT_FS::getpath() { return path.get(); } PUBLIC void EXPORT_FS::write (FILE_CFG *fout) { if (!path.is_empty()){ fprintf (fout,"%s ",path.get()); tbhost.write (fout); fputc (' ',fout); // Put a space for the comment in case. // Look nicer } if (!comment.is_empty()){ fprintf (fout,"# %s",comment.get()); } fputc ('\n',fout); } PUBLIC void EXPORT_FS::format_info(SSTRING &buf) { buf.setfrom (""); HOST_OPT *ho = tbhost.getitem(0); if (ho != NULL){ buf.setfrom (ho->host); if (tbhost.getnb()>1) buf.append ("\t..."); } } PRIVATE void EXPORT_FS::setonehost(DIALOG &dia, HOST_OPT *o) { dia.newf_str (MSG_U(F_CLIHOSTS,"Client name(s)"),o->host); dia.newf_chk ("",o->rw,MSG_U(F_READWRITE,"May write")); dia.newf_chk ("",o->root,MSG_U(F_ROOTSQUASH,"Root privileges")); #if 0 // link relative is not supported by newer kernel nfs // daemon. // We still manage the directive but it is not shown // in the dialog dia.newf_chk ("",o->link_relative,MSG_U(F_LINKREL ,"translate symbolic links")); #endif dia.newf_chk ("",o->secure,MSG_U(F_SECURE ,"Request access from secure port")); } /* Edit the spec of one export directory Return -1 if escape, 0 if accept, 1 if the record must be deleted */ PUBLIC int EXPORT_FS::edit() { int ret = -1; DIALOG dia; dia.newf_str (MSG_U(F_PATHEXP,"Path to export"),path); dia.newf_str (MSG_U(F_COMMENT,"Comment (opt)"),comment); SSTRINGS tmps; for (int i=0; ihost.is_empty()){ tbhost.remove_del (o); k--; } } return ret; } /* EXPORTS handle the file /etc/exports. Each line of this file is a EXPORT_FS (including comments). */ class EXPORTS: public ARRAY{ /*~PROTOBEG~ EXPORTS */ public: EXPORTS (void); int edit (void); EXPORT_FS *getitem (int no); void locate (const char *dir, bool setting); int write (void); /*~PROTOEND~ EXPORTS */ }; PUBLIC EXPORTS::EXPORTS() { /* #Specification: /etc/exports / missing A missing /etc/exports is equivalent to an empty one. */ FILE_CFG *fin = f_exports.fopen ("r"); if (fin != NULL){ char buf[1000]; int noline = 0; while (fgets_strip(buf,sizeof(buf)-1,fin,'\\',(char)255,&noline)!=NULL){ if (buf[0] != '\0'){ add (new EXPORT_FS(buf,noline)); } } fclose (fin); } } PUBLIC EXPORT_FS *EXPORTS::getitem(int no) { return (EXPORT_FS *)ARRAY::getitem(no); } PUBLIC int EXPORTS::write () { int ret = -1; FILE_CFG *fout = f_exports.fopen ("w"); if (fout != NULL){ for (int i=0; iwrite(fout); ret = fclose (fout); } return ret; } /* Edite /etc/exports, return -1 if some error or abort */ PUBLIC int EXPORTS::edit() { EXPORTS *exp; EXPORTS valid; glocal.exp = this; glocal.valid.neverdelete(); (MSG_U(T_EXPORTED,"Exported file systems") ,MSG_U(I_EXPORTED ,"Setup file systems available for client hosts\n" "Those systems will be accessible through NFS\n") ,helpf); newf_head (MSG_U(H_EXPORTS,"Directory\tHost\tMore")); addwhat (MSG_U(F_ADDONE,"Select [Add] to add a new definition")); int nbp = glocal.exp->getnb(); glocal.valid.remove_all(); for (int i=0; igetitem(i); const char *path = fs->getpath(); if (path[0] != '\0'){ SSTRING buf1,buf2; fs->format_info(buf2); if (buf2.truncate(30)) buf2.append ("..."); buf1.setfrom (path); if (buf1.truncate(30)) buf1.append ("..."); new_menuitem (buf1.get(),buf2.get()); glocal.valid.add (fs); } } EXPORT_FS *fs = glocal.valid.getitem(no); glocal.exp->manage_edit (fs,fs->edit()); EXPORT_FS *fs = new EXPORT_FS("",0); glocal.exp->manage_edit (fs,fs->edit()); return 0; } /* Edit the file /etc/exports */ void exports_edit() { module_requestpkg ("nfsserver"); EXPORTS exp; exp.edit(); } PUBLIC void EXPORTS::locate (const char *dir, bool setting) { bool found = false; for (int i=0; igetpath(),dir)==0){ manage_edit (fs,fs->edit()); found = true; break; } } if (!found && setting){ EXPORT_FS *fs = new EXPORT_FS (dir,0); manage_edit (fs,fs->edit()); } } static void export_locate (const char *dir, bool setting) { EXPORTS exp; exp.locate (dir,setting); } #include static REGISTER_VARIABLE_LOOKUP_MSG export_var_list[]={ {"directory",NULL,P_MSG_R(F_PATHEXP),NULL,export_locate}, {"client",NULL,P_MSG_R(F_CLIHOSTS),NULL,export_locate}, {"readwrite",NULL,P_MSG_R(F_READWRITE),NULL,export_locate}, {"norootsquash",NULL,P_MSG_R(F_ROOTSQUASH),NULL,export_locate}, {"secureport",NULL,P_MSG_R(F_SECURE),NULL,export_locate}, { NULL, NULL, NULL, NULL } }; static REGISTER_VARIABLES host_registry1("nfs",export_var_list); #ifdef TEST int main(int argc, char *argv[]) { exports_edit(); } #endif