From Karl_Kleinpaste@charcoal.com Mon Oct 18 16:07:56 1993 From: Karl_Kleinpaste@charcoal.com Date: Sun, 17 Oct 93 22:45:43 GMT To: inn-gurus@uunet.UU.NET Subject: Server-based storage and retrieval of user's files You may have noticed me mumbling in news.software.{nntp,readers} a couple of weeks ago about an issue of an environment in which users do not have any disc space at all, nor even a $HOME, on the system from which they invoke a newsreader, and my need to devise a scheme by which to store such things at the server. Below is a mid-size patch to nnrpd which accomplishes this. It provides a new command: FILE FETCH|STORE|DELETE CONFIG|NEWSRC|KILL It provides the basic framework by which to do this sort of thing. As the comments observe, there is no attempt to specify the format of any of the three basic file types, and in fact their names are purely for convention. The most open-to-interpretation of them is the "config" file; I intend to use this for things like environment variables such as NAME and ORGANIZATION. On a similar note, how a reader puts a single killfile to work on a per-group basis is an implementation issue at the reader, and not the server's problem. As I mentioned in response to someone (Rich? not sure now), I have explicitly avoided any possibility of actually putting these files to use at the server, e.g., nnrpd is not providing a means by which to apply the killfile to a set of articles being requested. The server is just a repository for these files, and whatever entertainment value the reader finds in them will have to be discovered by the reader itself. The arrangement I'm working on will have a peculiar scheme by which to find a user's $HOME at the server, so there are in fact two config.data items which control this, FILEEXT to turn on the capability as a whole, and FILEEXT_OWN_GETHOME. The latter is set to DO if you have such a specialized scheme. I will be setting it to DO once I have an appropriate $HOME-discovery routine written that makes the rest of my environment feel warm and fuzzy, but most folk who would want to do this will probably want (at least initially) to use the ordinary-if-stupid GetHome() routine I've provided, so DONT is the provided default. I'd be most interested in opinion/improvement on this. Changes are made to: config/config.data include/configdata.h nnrpd/commands.c nnrpd/nnrpd.c hacking away, --karl *** config/config.data.orig Sat Oct 16 22:23:00 1993 --- config/config.data Sun Oct 17 17:38:54 1993 *************** *** 471,476 **** --- 471,486 ---- ## Do you have uustat, or just uuq? Pick DO or DONT #### =()@>()= HAVE_UUSTAT DO + ## Support the "file" NNRP extension? Pick DO or DONT. + ## This is server-supported storage of files: + ## FILE [ FETCH | STORE | DELETE ] [ CONFIG | NEWSRC | KILL ] + #### =()@>()= + FILEEXT DO + ## Iff FILEEXT == DO, do you use your own GetHome routine? Pick DO or DONT. + ## If you DO, you must set LIBS ('way up there) to + ## an appropriate .a name which contains GetHome(). + #### =()@>()= + FILEEXT_OWN_GETHOME DONT ## *** include/configdata.h.orig Sat Oct 16 14:57:41 1993 --- include/configdata.h Sun Oct 17 11:36:01 1993 *************** *** 311,316 **** --- 311,322 ---- /* Default number of bytes to hold in memory when buffered. */ /* =()<#define SITE_BUFFER_SIZE @@>()= */ #define SITE_BUFFER_SIZE (16 * 1024) + /* Support the "file" NNRP extension? */ + /* =()<#define @@_FILEEXT>()= */ + #define DO_FILEEXT + /* Iff FILEEXT == DO, do you use your own GetHome routine? */ + /* =()<#define @@_FILEEXT_OWN_GETHOME>()= */ + #define DONT_FILEEXT_OWN_GETHOME /* Function that returns no value, and a pointer to it. */ /* =()<#define FUNCTYPE @@>()= */ *** nnrpd/commands.c.orig Sat Oct 16 22:21:58 1993 --- nnrpd/commands.c Sun Oct 17 21:40:39 1993 *************** *** 459,461 **** --- 459,724 ---- else Reply("%d %s\r\n", NNTP_NOTHING_FOLLOWS_VAL, p); } + + + + #if defined(DO_FILEEXT) + /* + ** All the goo below provides for server-based storage and retrieval + ** of a user's .newsrc, killfile, and "personal configuration data". + ** No provision for multiple killfiles is made; it is expected that + ** the newsreader can manage a single physical killfile and treat it + ** (somehow) as however many killfiles it really needs. The obvious + ** suggestion is for something like: + ** GLOBAL: some global killfile line... + ** news.groups: a killfile line specific to news.groups... + ** The contents of these files are completely unspecified. Newsrc + ** and killfiles have obvious interpretations; "personal config" may + ** be environment variables, newsreader timeouts, or [roll your own]. + */ + + /* + ** The following two routines are borrowed (well, stolen) from + ** innd/innd.c. Such functions probably belong in lib someplace. + */ + + /* + ** Try to make one directory. Return FALSE on error. + */ + STATIC BOOL + MakeDir(Name) + char *Name; + { + struct stat Sb; + + if (mkdir(Name, GROUPDIR_MODE) >= 0) { + return TRUE; + } + + /* See if it failed because it already exists. */ + return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode); + } + + + /* + ** Given a directory, /news/homes/joe/schmo, create that directory + ** and all intermediate directories needed. Return 0 if ok, else -1. + ** + ** Unlike innd/innd.c's MakeSpoolDirectory(), we anticipate a leading + ** `/' which must be left intact, so we start one char forward. + */ + BOOL + MakeUserDirectory(Name) + register char *Name; + { + register char *p; + BOOL made; + + /* Optimize common case -- parent almost always exists. */ + if (MakeDir(Name)) + return TRUE; + + for (p = Name + 1; *p; p++) + if (*p == '/') { + *p = '\0'; + made = MakeDir(Name); + *p = '/'; + if (!made) + return FALSE; + } + + return MakeDir(Name); + } + + #if defined(DONT_FILEEXT_OWN_GETHOME) + #include + + /* + ** A standard (but boring) routine to provide the $HOME of a user. + ** We make no promises about the {read,writ}ability of files found there. + ** If we end up mkdir()ing it, we can probably use it as we see fit, but + ** if it's a standard /usr/joe directory...well, the user has to see fit + ** to provide writability to $NEWSUSER -- probably unwise. + */ + char * + GetHome(user) + char *user; + { + struct passwd *p; + + if (!user || (p = getpwnam(user)) == NULL) { + return ((char *) NULL); + } + return (p->pw_dir); + } + #endif /* defined(DONT_FILEEXT_OWN_GETHOME) */ + + #define NNTP_JUST_INFO "050" + + /* + ** The "file" command, for {fetch,stor,delet}ing user + ** data for personal configuration, newsrc, and killfiles. + ** + ** $HOME cheat: We substitute an additional `/' for the last `.' because + ** we expect to have to deal with hundreds of thousands of these -- + ** we must attempt to optimize directory sizes at least a little bit. + ** We do this to the supplied directory name in place, icktooeyfeh. + ** + */ + /* ARGSUSED */ + FUNCTYPE + CMDfile(ac, av) + int ac; + char *av[]; + { + register char *p; + enum { Fetch, Store, Delete } fsd; + enum { Config, Newsrc, Kill } filetype; + char *homep, FName[SPOOLNAMEBUFF]; + /* size overkill ^^^ */ + extern char *GetHome(); + QIOSTATE *qp; + struct stat Sb; + char line[START_BUFF_SIZE]; + /* size overkill ^^^ */ + int longline, length, errsave; + FILE *filedes; + register READTYPE r; + + if (caseEQ(av[1], "fetch")) + fsd = Fetch; + else if (caseEQ(av[1], "store")) + fsd = Store; + else if (caseEQ(av[1], "delete")) + fsd = Delete; + else { + Reply("%d op choices: \"fetch\" | \"store\" | \"delete\".\r\n", + NNTP_SYNTAX_VAL); + return; + } + + if (caseEQ(av[2], "config")) + filetype = Config; + else if (caseEQ(av[2], "newsrc")) + filetype = Newsrc; + else if (caseEQ(av[2], "kill")) + filetype = Kill; + else { + Reply("%d file choices: \"config\" | \"newsrc\" | \"kill\".\r\n", + NNTP_SYNTAX_VAL); + return; + } + + if ((homep = GetHome(PERMuser)) == NULL) { + Reply("%d You appear not to exist.\r\n", NNTP_ACCESS_VAL); + return; + } + /* Here's the uglification feat. */ + if (p = strrchr(homep, '.')) + *p = '/'; + + (void) strcpy(FName, homep); + switch(filetype) { + case Config: (void) strcat(FName, "/.newscf"); break; + case Newsrc: (void) strcat(FName, "/.newsrc"); break; + case Kill: (void) strcat(FName, "/.kill"); break; + } + + switch(fsd) { + case Fetch: + if ((qp = QIOopen(FName, QIO_BUFFER)) == NULL) { + Reply("%d Can't return %s contents: %s.\r\n", + NNTP_TEMPERR_VAL, av[2], strerror(errno)); + return; + } + + Reply("%d Contents of %s follow.\r\n", + NNTP_LIST_FOLLOWS_VAL, av[2]); + while ((p = QIOread(qp)) != NULL) { + if (*p) + Printf("%s\r\n", p); + } + QIOclose(qp); + Printf(".\r\n"); + break; + + case Store: + /* Make sure the user's (quasi-)$HOME is in place. */ + if (stat(homep, &Sb) < 0 || !S_ISDIR(Sb.st_mode)) { + if (MakeUserDirectory(homep) == FALSE) { + Reply("%d Can't make directory? %s\r\n", + NNTP_TEMPERR_VAL, strerror(errno)); + return; + } + } + + if ((filedes = fopen(FName, "w")) == NULL) { + Reply("%d Can't create file %s? %s\r\n", + NNTP_TEMPERR_VAL, av[2], strerror(errno)); + return; + } else + (void) fchmod(fileno(filedes), ARTFILE_MODE); + + Reply("%d Send %s data.\r\n", NNTP_START_POST_VAL, av[2]); + (void)fflush(stdout); + + /* This code picked up from CMDpost(). */ + for (longline = 0; ;) { + /* Read line, process bad cases. */ + switch (r = READline(line, START_BUFF_SIZE, DEFAULT_TIMEOUT)) { + default: + syslog(L_ERROR, "%s internal %d in file data", + ClientHost, r); + /* FALLTHROUGH */ + case RTtimeout: + syslog(L_ERROR, "%s timeout in file data", ClientHost); + Printf("%d timeout after %d seconds, closing.\r\n", + NNTP_TEMPERR_VAL, DEFAULT_TIMEOUT); + ExitWithStats(1); + /* NOTREACHED */ + case RTeof: + syslog(L_ERROR, "%s eof in file data", ClientHost); + ExitWithStats(1); + /* NOTREACHED */ + case RTlong: + longline++; + continue; + case RTok: + break; + } + + /* Process normal text. */ + if (line[0] != '.') { + if (fprintf(filedes, "%s\n", line) < 0) + errsave = errno; + continue; + } + + /* Got a leading period; see if it's the terminator. */ + if (line[1] == '\0') + break; + } + /* */ + + if (fclose(filedes) < 0) + errsave = errno; + + if (errsave) + Reply("%d File store %s error, %s.\r\n", NNTP_TEMPERR_VAL, + av[2], strerror(errsave)); + else if (longline) + Reply("%d A line was too long.\r\n", NNTP_REJECTIT_VAL); + else + Reply("%d Store of %s completed.\r\n", NNTP_TOOKIT_VAL, + av[2]); + break; + + case Delete: + unlink(FName); + Reply("%d Deleted %s.\r\n", NNTP_NOTHING_FOLLOWS_VAL, av[2]); + break; + } + + return; + } + #endif /* defined(DO_FILEEXT) */ *** nnrpd/nnrpd.c.orig Sat Oct 16 20:55:22 1993 --- nnrpd/nnrpd.c Sun Oct 17 17:44:28 1993 *************** *** 46,51 **** --- 46,54 ---- extern FUNCTYPE CMDauthinfo(); extern FUNCTYPE CMDdate(); extern FUNCTYPE CMDfetch(); + #if defined(DO_FILEEXT) + extern FUNCTYPE CMDfile(); + #endif /* defined(DO_FILEEXT) */ extern FUNCTYPE CMDgroup(); STATIC FUNCTYPE CMDhelp(); extern FUNCTYPE CMDlist(); *************** *** 75,80 **** --- 78,87 ---- CMDfetchhelp }, { "date", CMDdate, FALSE, 1, 1, NULL }, + #if defined(DO_FILEEXT) + { "file", CMDfile, FALSE, 3, 3, + "fetch|store|delete config|newsrc|kill" }, + #endif /* defined(DO_FILEEXT) */ { "group", CMDgroup, FALSE, 2, 2, "newsgroup" }, { "head", CMDfetch, FALSE, 1, 2,